Index: /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionPane.java
===================================================================
--- /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionPane.java	(revision 25907)
+++ /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionPane.java	(revision 25908)
@@ -123,5 +123,11 @@
 		
 		private Point2D translateCoords(int x, int y) {
-			return new Point2D.Double(-translationX + x / scale, -translationY + y / scale);
+			final double c = Math.cos(-rotation);
+			final double s = Math.sin(-rotation);
+			
+			final double x2 = -translationX + x / scale;
+			final double y2 = -translationY + y / scale;
+			
+			return new Point2D.Double(x2 * c - y2 * s, x2 * s + y2 * c);
 		}
 	}
@@ -137,4 +143,5 @@
 	private int width = 0;
 	private int height = 0;
+	private double rotation = 0;
 	private double scale = 10;
 	private double translationX = 0;
@@ -200,4 +207,26 @@
 			public void actionPerformed(ActionEvent e) {
 				toggleAllTurns();
+			}
+		});
+		
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_DOWN_MASK), "rotateLeft");
+		getActionMap().put("rotateLeft", new AbstractAction() {
+			private static final long serialVersionUID = 1L;
+			
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				rotation -= Math.PI / 180;
+				setState(new State.Dirty(state));
+			}
+		});
+		
+		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK), "rotateRight");
+		getActionMap().put("rotateRight", new AbstractAction() {
+			private static final long serialVersionUID = 1L;
+			
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				rotation += Math.PI / 180;
+				setState(new State.Dirty(state));
 			}
 		});
@@ -227,4 +256,7 @@
 	private void center() {
 		final Rectangle2D bounds = container.getBounds();
+		
+		rotation = 0;
+		
 		scale = Math.min(getHeight() / 2 / bounds.getHeight(), getWidth() / 2 / bounds.getWidth());
 		
@@ -323,6 +355,6 @@
 		
 		g2d.scale(scale, scale);
-		
 		g2d.translate(translationX, translationY);
+		g2d.rotate(rotation);
 		
 		g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.7f));
@@ -356,4 +388,5 @@
 		g2d.scale(scale, scale);
 		g2d.translate(translationX, translationY);
+		g2d.rotate(rotation);
 		
 		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Index: /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Lane.java
===================================================================
--- /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Lane.java	(revision 25907)
+++ /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Lane.java	(revision 25908)
@@ -66,9 +66,12 @@
 		
 		if (countStr != null) {
-			return Integer.parseInt(countStr);
-		}
-		
-		// TODO default lane counts based on "highway" tag
-		return 2;
+			try {
+				return Integer.parseInt(countStr);
+			} catch (NumberFormatException e) {
+				throw UnexpectedDataException.Kind.INVALID_TAG_FORMAT.chuck("lanes", countStr);
+			}
+		}
+		
+		throw UnexpectedDataException.Kind.MISSING_TAG.chuck("lanes");
 	}
 	
Index: /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/UnexpectedDataException.java
===================================================================
--- /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/UnexpectedDataException.java	(revision 25908)
+++ /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/UnexpectedDataException.java	(revision 25908)
@@ -0,0 +1,49 @@
+package org.openstreetmap.josm.plugins.turnlanes.model;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Arrays;
+
+public final class UnexpectedDataException extends RuntimeException {
+	private static final long serialVersionUID = 7430280313889494242L;
+	
+	public enum Kind {
+		NO_MEMBER("No member with role \"{0}\".", 1),
+		MULTIPLE_MEMBERS("More than one member with role \"{0}\".", 1),
+		WRONG_MEMBER_TYPE("A member with role \"{0}\" is a {1} and not a {2} as expected.", 3),
+		INVALID_TAG_FORMAT("The tag \"{0}\" has an invalid format: {1}", 2),
+		MISSING_TAG("The tag \"{0}\" is missing.", 1);
+		
+		private final String message;
+		private final int params;
+		
+		private Kind(String message, int params) {
+			this.message = message;
+			this.params = params;
+		}
+		
+		public UnexpectedDataException chuck(Object... args) {
+			throw new UnexpectedDataException(this, format(args));
+		}
+		
+		public String format(Object... args) {
+			if (args.length != params) {
+				throw new IllegalArgumentException("Wrong argument count for " + this + ": " + Arrays.toString(args));
+			}
+			
+			return tr(message, args);
+		}
+	}
+	
+	private final Kind kind;
+	
+	public UnexpectedDataException(Kind kind, String message) {
+		super(message);
+		
+		this.kind = kind;
+	}
+	
+	public Kind getKind() {
+		return kind;
+	}
+}
Index: /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Utils.java
===================================================================
--- /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Utils.java	(revision 25907)
+++ /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Utils.java	(revision 25908)
@@ -52,7 +52,7 @@
 		final List<RelationMember> candidates = getMembers(r, role, type);
 		if (candidates.isEmpty()) {
-			throw new IllegalStateException("No member with given role and type.");
+			throw UnexpectedDataException.Kind.NO_MEMBER.chuck(role);
 		} else if (candidates.size() > 1) {
-			throw new IllegalStateException(candidates.size() + " members with given role and type.");
+			throw UnexpectedDataException.Kind.MULTIPLE_MEMBERS.chuck(role);
 		}
 		return candidates.get(0);
@@ -63,5 +63,5 @@
 		for (RelationMember m : getMembers(r, role)) {
 			if (m.getType() != type) {
-				throw new IllegalArgumentException("Member has the specified role, but wrong type.");
+				throw UnexpectedDataException.Kind.WRONG_MEMBER_TYPE.chuck(role, m.getType(), type);
 			}
 		}
Index: /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Validator.java
===================================================================
--- /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Validator.java	(revision 25907)
+++ /applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Validator.java	(revision 25908)
@@ -17,5 +17,4 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
@@ -109,11 +108,6 @@
 		final List<Relation> turns = new ArrayList<Relation>();
 		
-		for (OsmPrimitive p : dataSet.allPrimitives()) {
-			if (p.getType() != OsmPrimitiveType.RELATION) {
-				continue;
-			}
-			
-			final Relation r = (Relation) p;
-			final String type = p.get("type");
+		for (Relation r : OsmPrimitive.getFilteredList(dataSet.allPrimitives(), Relation.class)) {
+			final String type = r.get("type");
 			
 			if (Constants.TYPE_LENGTHS.equals(type)) {
@@ -153,39 +147,40 @@
 		final List<Issue> issues = new ArrayList<Issue>();
 		
-		final Node end = validateLengthsEnd(r, issues);
-		
-		if (end == null) {
+		try {
+			final Node end = Utils.getMemberNode(r, Constants.LENGTHS_ROLE_END);
+			final Route route = validateLengthsWays(r, end, issues);
+			
+			if (route == null) {
+				return issues;
+			}
+			
+			final List<Double> left = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_LEFT, 0);
+			final List<Double> right = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_RIGHT, 0);
+			
+			int tooLong = 0;
+			for (Double l : left) {
+				if (l > route.getLength()) {
+					++tooLong;
+				}
+			}
+			for (Double l : right) {
+				if (l > route.getLength()) {
+					++tooLong;
+				}
+			}
+			
+			if (tooLong > 0) {
+				issues.add(Issue.newError(r, end, "The lengths-relation specifies " + tooLong
+				    + " extra-lanes which are longer than its ways."));
+			}
+			
+			putIncomingLanes(route, left, right, incomingLanes);
+			
 			return issues;
-		}
-		
-		final Route route = validateLengthsWays(r, end, issues);
-		
-		if (route == null) {
+			
+		} catch (UnexpectedDataException e) {
+			issues.add(Issue.newError(r, e.getMessage()));
 			return issues;
 		}
-		
-		final List<Double> left = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_LEFT, 0);
-		final List<Double> right = Lane.loadLengths(r, Constants.LENGTHS_KEY_LENGTHS_RIGHT, 0);
-		
-		int tooLong = 0;
-		for (Double l : left) {
-			if (l > route.getLength()) {
-				++tooLong;
-			}
-		}
-		for (Double l : right) {
-			if (l > route.getLength()) {
-				++tooLong;
-			}
-		}
-		
-		if (tooLong > 0) {
-			issues.add(Issue.newError(r, end, "The lengths-relation specifies " + tooLong
-			    + " extra-lanes which are longer than its ways."));
-		}
-		
-		putIncomingLanes(route, left, right, incomingLanes);
-		
-		return issues;
 	}
 	
@@ -206,12 +201,14 @@
 		}
 		
-		final double length = route.getLastSegment().getLength();
-		final List<Double> newLeft = reduceLengths(left, length);
-		final List<Double> newRight = new ArrayList<Double>(right.size());
-		
-		if (route.getSegments().size() > 1) {
-			final Route subroute = route.subRoute(0, route.getSegments().size() - 1);
-			putIncomingLanes(subroute, newLeft, newRight, incomingLanes);
-		}
+		// TODO this tends to produce a bunch of useless errors
+		// turn lanes really should not span from one junction past another, remove??
+		//		final double length = route.getLastSegment().getLength();
+		//		final List<Double> newLeft = reduceLengths(left, length);
+		//		final List<Double> newRight = new ArrayList<Double>(right.size());
+		//		
+		//		if (route.getSegments().size() > 1) {
+		//			final Route subroute = route.subRoute(0, route.getSegments().size() - 1);
+		//			putIncomingLanes(subroute, newLeft, newRight, incomingLanes);
+		//		}
 	}
 	
@@ -240,5 +237,5 @@
 		for (Way w : ways) {
 			if (!w.isFirstLastNode(current)) {
-				return orderWays(r, ways, current, issues);
+				return orderWays(r, ways, current, issues, "ways", "lengths");
 			}
 			
@@ -249,5 +246,5 @@
 	}
 	
-	private Route orderWays(final Relation r, List<Way> ways, Node end, List<Issue> issues) {
+	private Route orderWays(final Relation r, List<Way> ways, Node end, List<Issue> issues, String role, String type) {
 		final List<Way> unordered = new ArrayList<Way>(ways);
 		final List<Way> ordered = new ArrayList<Way>(ways.size());
@@ -257,5 +254,6 @@
 		findNext: while (!unordered.isEmpty()) {
 			if (!ends.add(current)) {
-				issues.add(Issue.newError(r, ways, "The ways of the lengths-relation are unordered (and contain cycles)."));
+				issues.add(Issue.newError(r, ways, "The " + role + " of the " + type
+				    + "-relation are unordered (and contain cycles)."));
 				return null;
 			}
@@ -273,5 +271,5 @@
 			}
 			
-			issues.add(Issue.newError(r, ways, "The ways of the lengths-relation are disconnected."));
+			issues.add(Issue.newError(r, ways, "The " + role + " of the " + type + "-relation are disconnected."));
 			return null;
 		}
@@ -301,16 +299,4 @@
 	}
 	
-	private Node validateLengthsEnd(Relation r, List<Issue> issues) {
-		final List<Node> endNodes = Utils.getMemberNodes(r, Constants.LENGTHS_ROLE_END);
-		
-		if (endNodes.size() != 1) {
-			issues.add(Issue.newError(r, endNodes, "A lengths-relation requires exactly one member-node with role \""
-			    + Constants.LENGTHS_ROLE_END + "\"."));
-			return null;
-		}
-		
-		return endNodes.get(0);
-	}
-	
 	private List<Issue> validateTurns(List<Relation> turns, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
 		final List<Issue> issues = new ArrayList<Issue>();
@@ -326,59 +312,70 @@
 		final List<Issue> issues = new ArrayList<Issue>();
 		
-		final List<Way> fromWays = Utils.getMemberWays(r, Constants.TURN_ROLE_FROM);
-		final List<Node> viaNodes = Utils.getMemberNodes(r, Constants.TURN_ROLE_VIA);
-		final List<Way> toWays = Utils.getMemberWays(r, Constants.TURN_ROLE_TO);
-		
-		if (fromWays.size() != 1) {
-			issues.add(Issue.newError(r, fromWays, "A turns-relation requires exactly one member-way with role \""
-			    + Constants.TURN_ROLE_FROM + "\"."));
-		}
-		if (viaNodes.size() != 1) {
-			issues.add(Issue.newError(r, viaNodes, "A turns-relation requires exactly one member-node with role \""
-			    + Constants.TURN_ROLE_VIA + "\"."));
-		}
-		if (toWays.size() != 1) {
-			issues.add(Issue.newError(r, toWays, "A turns-relation requires exactly one member-way with role \""
-			    + Constants.TURN_ROLE_TO + "\"."));
-		}
-		
-		if (!issues.isEmpty()) {
+		try {
+			final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
+			final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
+			
+			if (from.firstNode().equals(from.lastNode())) {
+				issues.add(Issue.newError(r, from, "The from-way both starts as well as ends at the via-node."));
+			}
+			if (to.firstNode().equals(to.lastNode())) {
+				issues.add(Issue.newError(r, to, "The to-way both starts as well as ends at the via-node."));
+			}
+			if (!issues.isEmpty()) {
+				return issues;
+			}
+			
+			final Node fromJunctionNode;
+			final List<RelationMember> viaMembers = Utils.getMembers(r, Constants.TURN_ROLE_VIA);
+			if (viaMembers.isEmpty()) {
+				throw UnexpectedDataException.Kind.NO_MEMBER.chuck(Constants.TURN_ROLE_VIA);
+			} else if (viaMembers.get(0).isWay()) {
+				final List<Way> vias = Utils.getMemberWays(r, Constants.TURN_ROLE_VIA);
+				
+				fromJunctionNode = Utils.lineUp(from, vias.get(0));
+				Node current = fromJunctionNode;
+				for (Way via : vias) {
+					if (!via.isFirstLastNode(current)) {
+						orderWays(r, vias, current, issues, "via-ways", "turns");
+						break;
+					}
+					
+					current = Utils.getOppositeEnd(via, current);
+				}
+			} else {
+				final Node via = Utils.getMemberNode(r, Constants.TURN_ROLE_VIA);
+				
+				if (!from.isFirstLastNode(via)) {
+					issues.add(Issue.newError(r, from, "The from-way does not start or end at the via-node."));
+				}
+				if (!to.isFirstLastNode(via)) {
+					issues.add(Issue.newError(r, to, "The to-way does not start or end at the via-node."));
+				}
+				
+				fromJunctionNode = via;
+			}
+			
+			if (!issues.isEmpty()) {
+				return issues;
+			}
+			final IncomingLanes lanes = get(incomingLanes, fromJunctionNode, from);
+			
+			for (int l : splitInts(r, Constants.TURN_KEY_LANES, issues)) {
+				if (!lanes.existsRegular(l)) {
+					issues.add(Issue.newError(r, tr("Relation references non-existent (regular) lane {0}", l)));
+				}
+			}
+			
+			for (int l : splitInts(r, Constants.TURN_KEY_EXTRA_LANES, issues)) {
+				if (!lanes.existsExtra(l)) {
+					issues.add(Issue.newError(r, tr("Relation references non-existent extra lane {0}", l)));
+				}
+			}
+			
 			return issues;
-		}
-		
-		final Way from = fromWays.get(0);
-		final Node via = viaNodes.get(0);
-		final Way to = toWays.get(0);
-		
-		if (!from.isFirstLastNode(via)) {
-			issues.add(Issue.newError(r, from, "The from-way does not start or end at the via-node."));
-		} else if (from.firstNode().equals(from.lastNode())) {
-			issues.add(Issue.newError(r, from, "The from-way both starts as well as ends at the via-node."));
-		}
-		if (!to.isFirstLastNode(via)) {
-			issues.add(Issue.newError(r, to, "The to-way does not start or end at the via-node."));
-		} else if (to.firstNode().equals(to.lastNode())) {
-			issues.add(Issue.newError(r, to, "The to-way both starts as well as ends at the via-node."));
-		}
-		
-		if (!issues.isEmpty()) {
+		} catch (UnexpectedDataException e) {
+			issues.add(Issue.newError(r, e.getMessage()));
 			return issues;
 		}
-		
-		final IncomingLanes lanes = get(incomingLanes, via, from);
-		
-		for (int l : splitInts(r, Constants.TURN_KEY_LANES, issues)) {
-			if (!lanes.existsRegular(l)) {
-				issues.add(Issue.newError(r, tr("Relation references non-existent (regular) lane {0}", l)));
-			}
-		}
-		
-		for (int l : splitInts(r, Constants.TURN_KEY_EXTRA_LANES, issues)) {
-			if (!lanes.existsExtra(l)) {
-				issues.add(Issue.newError(r, tr("Relation references non-existent extra lane {0}", l)));
-			}
-		}
-		
-		return issues;
 	}
 	
