Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/CollectionUtils.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/CollectionUtils.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/CollectionUtils.java	(revision 26154)
@@ -10,48 +10,48 @@
 
 public class CollectionUtils {
-	public static <E> Iterable<E> reverse(final List<E> list) {
-		return new Iterable<E>() {
-			@Override
-			public Iterator<E> iterator() {
-				final ListIterator<E> it = list.listIterator(list.size());
-				
-				return new Iterator<E>() {
-					@Override
-					public boolean hasNext() {
-						return it.hasPrevious();
-					}
-					
-					@Override
-					public E next() {
-						return it.previous();
-					}
-					
-					@Override
-					public void remove() {
-						it.remove();
-					}
-				};
-			}
-		};
-	}
-	
-	public static <E> Set<E> toSet(Iterable<? extends E> iterable) {
-		final Set<E> set = new HashSet<E>();
-		
-		for (E e : iterable) {
-			set.add(e);
-		}
-		
-		return Collections.unmodifiableSet(set);
-	}
-	
-	public static <E> List<E> toList(Iterable<? extends E> iterable) {
-		final List<E> list = new ArrayList<E>();
-		
-		for (E e : iterable) {
-			list.add(e);
-		}
-		
-		return Collections.unmodifiableList(list);
-	}
+    public static <E> Iterable<E> reverse(final List<E> list) {
+        return new Iterable<E>() {
+            @Override
+            public Iterator<E> iterator() {
+                final ListIterator<E> it = list.listIterator(list.size());
+                
+                return new Iterator<E>() {
+                    @Override
+                    public boolean hasNext() {
+                        return it.hasPrevious();
+                    }
+                    
+                    @Override
+                    public E next() {
+                        return it.previous();
+                    }
+                    
+                    @Override
+                    public void remove() {
+                        it.remove();
+                    }
+                };
+            }
+        };
+    }
+    
+    public static <E> Set<E> toSet(Iterable<? extends E> iterable) {
+        final Set<E> set = new HashSet<E>();
+        
+        for (E e : iterable) {
+            set.add(e);
+        }
+        
+        return Collections.unmodifiableSet(set);
+    }
+    
+    public static <E> List<E> toList(Iterable<? extends E> iterable) {
+        final List<E> list = new ArrayList<E>();
+        
+        for (E e : iterable) {
+            list.add(e);
+        }
+        
+        return Collections.unmodifiableList(list);
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/TurnLanesPlugin.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/TurnLanesPlugin.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/TurnLanesPlugin.java	(revision 26154)
@@ -7,14 +7,14 @@
 
 public class TurnLanesPlugin extends Plugin {
-	public TurnLanesPlugin(PluginInformation info) {
-		super(info);
-	}
-	
-	@Override
-	public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
-		if (oldFrame == null && newFrame != null) {
-			// there was none before
-			newFrame.addToggleDialog(new TurnLanesDialog());
-		}
-	}
+    public TurnLanesPlugin(PluginInformation info) {
+        super(info);
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        if (oldFrame == null && newFrame != null) {
+            // there was none before
+            newFrame.addToggleDialog(new TurnLanesDialog());
+        }
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiContainer.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiContainer.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiContainer.java	(revision 26154)
@@ -24,157 +24,157 @@
 
 class GuiContainer {
-	static final Color RED = new Color(234, 66, 108);
-	static final Color GREEN = new Color(66, 234, 108);
-	
-	private final ModelContainer mc;
-	
-	private final Point2D translation;
-	/**
-	 * Meters per pixel.
-	 */
-	private final double mpp;
-	private final double scale;
-	private final double laneWidth;
-	
-	private final Map<Junction, JunctionGui> junctions = new HashMap<Junction, JunctionGui>();
-	private final Map<Road, RoadGui> roads = new HashMap<Road, RoadGui>();
-	
-	private final Stroke connectionStroke;
-	
-	public GuiContainer(ModelContainer mc) {
-		final Point2D origin = avgOrigin(locs(mc.getPrimaryJunctions()));
-		
-		final LatLon originCoor = Main.proj.eastNorth2latlon(new EastNorth(origin.getX(), origin.getY()));
-		final LatLon relCoor = Main.proj.eastNorth2latlon(new EastNorth(origin.getX() + 1, origin.getY() + 1));
-		
-		// meters per source unit
-		final double mpsu = relCoor.greatCircleDistance(originCoor) / sqrt(2);
-		
-		this.mc = mc;
-		this.translation = new Point2D.Double(-origin.getX(), -origin.getY());
-		this.mpp = 0.2;
-		this.scale = mpsu / mpp;
-		this.laneWidth = 2 / mpp;
-		
-		this.connectionStroke = new BasicStroke((float) (laneWidth / 4), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
-		
-		for (Junction j : mc.getPrimaryJunctions()) {
-			getGui(j);
-		}
-	}
-	
-	private static Point2D avgOrigin(List<Point2D> locs) {
-		double x = 0;
-		double y = 0;
-		
-		for (Point2D l : locs) {
-			x += l.getX();
-			y += l.getY();
-		}
-		
-		return new Point2D.Double(x / locs.size(), y / locs.size());
-	}
-	
-	public JunctionGui getGui(Junction j) {
-		final JunctionGui existing = junctions.get(j);
-		if (existing != null) {
-			return existing;
-		}
-		
-		return new JunctionGui(this, j);
-	}
-	
-	void register(JunctionGui j) {
-		if (junctions.put(j.getModel(), j) != null) {
-			throw new IllegalStateException();
-		}
-	}
-	
-	public RoadGui getGui(Road r) {
-		final RoadGui gui = roads.get(r);
-		
-		if (gui == null) {
-			final RoadGui newGui = new RoadGui(this, r);
-			roads.put(r, newGui);
-			return newGui;
-		}
-		
-		return gui;
-	}
-	
-	Point2D translateAndScale(Point2D loc) {
-		return new Point2D.Double((loc.getX() + translation.getX()) * scale, (loc.getY() + translation.getY()) * scale);
-	}
-	
-	/**
-	 * @return meters per pixel
-	 */
-	public double getMpp() {
-		return mpp;
-	}
-	
-	public double getScale() {
-		return scale;
-	}
-	
-	public double getLaneWidth() {
-		return laneWidth;
-	}
-	
-	public Stroke getConnectionStroke() {
-		return connectionStroke;
-	}
-	
-	public LaneGui getGui(Lane lane) {
-		final RoadGui roadGui = roads.get(lane.getRoad());
-		
-		for (LaneGui l : roadGui.getLanes()) {
-			if (l.getModel().equals(lane)) {
-				return l;
-			}
-		}
-		
-		throw new IllegalArgumentException("No such lane.");
-	}
-	
-	public ModelContainer getModel() {
-		return mc;
-	}
-	
-	public Rectangle2D getBounds() {
-		final List<Junction> primaries = new ArrayList<Junction>(mc.getPrimaryJunctions());
-		final List<Double> top = new ArrayList<Double>();
-		final List<Double> left = new ArrayList<Double>();
-		final List<Double> right = new ArrayList<Double>();
-		final List<Double> bottom = new ArrayList<Double>();
-		
-		for (Junction j : primaries) {
-			final JunctionGui g = getGui(j);
-			final Rectangle2D b = g.getBounds();
-			
-			top.add(b.getMinY());
-			left.add(b.getMinX());
-			right.add(b.getMaxX());
-			bottom.add(b.getMaxY());
-		}
-		
-		final double t = Collections.min(top);
-		final double l = Collections.min(left);
-		final double r = Collections.max(right);
-		final double b = Collections.max(bottom);
-		
-		return new Rectangle2D.Double(l, t, r - l, b - t);
-	}
-	
-	public GuiContainer recalculate() {
-		return new GuiContainer(mc.recalculate());
-	}
-	
-	public Iterable<RoadGui> getRoads() {
-		return roads.values();
-	}
-	
-	public Iterable<JunctionGui> getJunctions() {
-		return junctions.values();
-	}
+    static final Color RED = new Color(234, 66, 108);
+    static final Color GREEN = new Color(66, 234, 108);
+    
+    private final ModelContainer mc;
+    
+    private final Point2D translation;
+    /**
+     * Meters per pixel.
+     */
+    private final double mpp;
+    private final double scale;
+    private final double laneWidth;
+    
+    private final Map<Junction, JunctionGui> junctions = new HashMap<Junction, JunctionGui>();
+    private final Map<Road, RoadGui> roads = new HashMap<Road, RoadGui>();
+    
+    private final Stroke connectionStroke;
+    
+    public GuiContainer(ModelContainer mc) {
+        final Point2D origin = avgOrigin(locs(mc.getPrimaryJunctions()));
+        
+        final LatLon originCoor = Main.getProjection().eastNorth2latlon(new EastNorth(origin.getX(), origin.getY()));
+        final LatLon relCoor = Main.getProjection().eastNorth2latlon(new EastNorth(origin.getX() + 1, origin.getY() + 1));
+        
+        // meters per source unit
+        final double mpsu = relCoor.greatCircleDistance(originCoor) / sqrt(2);
+        
+        this.mc = mc;
+        this.translation = new Point2D.Double(-origin.getX(), -origin.getY());
+        this.mpp = 0.2;
+        this.scale = mpsu / mpp;
+        this.laneWidth = 2 / mpp;
+        
+        this.connectionStroke = new BasicStroke((float) (laneWidth / 4), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+        
+        for (Junction j : mc.getPrimaryJunctions()) {
+            getGui(j);
+        }
+    }
+    
+    private static Point2D avgOrigin(List<Point2D> locs) {
+        double x = 0;
+        double y = 0;
+        
+        for (Point2D l : locs) {
+            x += l.getX();
+            y += l.getY();
+        }
+        
+        return new Point2D.Double(x / locs.size(), y / locs.size());
+    }
+    
+    public JunctionGui getGui(Junction j) {
+        final JunctionGui existing = junctions.get(j);
+        if (existing != null) {
+            return existing;
+        }
+        
+        return new JunctionGui(this, j);
+    }
+    
+    void register(JunctionGui j) {
+        if (junctions.put(j.getModel(), j) != null) {
+            throw new IllegalStateException();
+        }
+    }
+    
+    public RoadGui getGui(Road r) {
+        final RoadGui gui = roads.get(r);
+        
+        if (gui == null) {
+            final RoadGui newGui = new RoadGui(this, r);
+            roads.put(r, newGui);
+            return newGui;
+        }
+        
+        return gui;
+    }
+    
+    Point2D translateAndScale(Point2D loc) {
+        return new Point2D.Double((loc.getX() + translation.getX()) * scale, (loc.getY() + translation.getY()) * scale);
+    }
+    
+    /**
+     * @return meters per pixel
+     */
+    public double getMpp() {
+        return mpp;
+    }
+    
+    public double getScale() {
+        return scale;
+    }
+    
+    public double getLaneWidth() {
+        return laneWidth;
+    }
+    
+    public Stroke getConnectionStroke() {
+        return connectionStroke;
+    }
+    
+    public LaneGui getGui(Lane lane) {
+        final RoadGui roadGui = roads.get(lane.getRoad());
+        
+        for (LaneGui l : roadGui.getLanes()) {
+            if (l.getModel().equals(lane)) {
+                return l;
+            }
+        }
+        
+        throw new IllegalArgumentException("No such lane.");
+    }
+    
+    public ModelContainer getModel() {
+        return mc;
+    }
+    
+    public Rectangle2D getBounds() {
+        final List<Junction> primaries = new ArrayList<Junction>(mc.getPrimaryJunctions());
+        final List<Double> top = new ArrayList<Double>();
+        final List<Double> left = new ArrayList<Double>();
+        final List<Double> right = new ArrayList<Double>();
+        final List<Double> bottom = new ArrayList<Double>();
+        
+        for (Junction j : primaries) {
+            final JunctionGui g = getGui(j);
+            final Rectangle2D b = g.getBounds();
+            
+            top.add(b.getMinY());
+            left.add(b.getMinX());
+            right.add(b.getMaxX());
+            bottom.add(b.getMaxY());
+        }
+        
+        final double t = Collections.min(top);
+        final double l = Collections.min(left);
+        final double r = Collections.max(right);
+        final double b = Collections.max(bottom);
+        
+        return new Rectangle2D.Double(l, t, r - l, b - t);
+    }
+    
+    public GuiContainer recalculate() {
+        return new GuiContainer(mc.recalculate());
+    }
+    
+    public Iterable<RoadGui> getRoads() {
+        return roads.values();
+    }
+    
+    public Iterable<JunctionGui> getJunctions() {
+        return junctions.values();
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiUtil.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiUtil.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiUtil.java	(revision 26154)
@@ -17,133 +17,133 @@
 
 class GuiUtil {
-	static double normalize(double a) {
-		while (a < 0) {
-			a += 2 * Math.PI;
-		}
-		while (a > 2 * Math.PI) {
-			a -= 2 * Math.PI;
-		}
-		return a;
-	}
-	
-	// control point factor for curves (circle segment of angle a)
-	static double cpf(double a, double scale) {
-		return 4.0 / 3 * Math.tan(min(abs(a), PI - 0.001) / 4) * scale;
-	}
-	
-	static Point2D intersection(Line2D a, Line2D b) {
-		final double aa = GuiUtil.angle(a);
-		final double ab = GuiUtil.angle(b);
-		
-		// less than 1/2 degree => no intersection
-		if (Math.abs(Math.PI - abs(minAngleDiff(aa, ab))) < PI / 360) {
-			return null;
-		}
-		
-		final double d = (a.getX1() - a.getX2()) * (b.getY1() - b.getY2()) - (a.getY1() - a.getY2())
-		    * (b.getX1() - b.getX2());
-		
-		final double x = ((b.getX1() - b.getX2()) * (a.getX1() * a.getY2() - a.getY1() * a.getX2()) - (a.getX1() - a
-		    .getX2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2()))
-		    / d;
-		final double y = ((b.getY1() - b.getY2()) * (a.getX1() * a.getY2() - a.getY1() * a.getX2()) - (a.getY1() - a
-		    .getY2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2()))
-		    / d;
-		
-		return new Point2D.Double(x, y);
-	}
-	
-	static Point2D closest(Line2D l, Point2D p) {
-		final Point2D lv = vector(l.getP1(), l.getP2());
-		final double numerator = dot(vector(l.getP1(), p), lv);
-		
-		if (numerator < 0) {
-			return l.getP1();
-		}
-		
-		final double denominator = dot(lv, lv);
-		if (numerator >= denominator) {
-			return l.getP2();
-		}
-		
-		final double r = numerator / denominator;
-		return new Point2D.Double(l.getX1() + r * lv.getX(), l.getY1() + r * lv.getY());
-	}
-	
-	private static double dot(Point2D a, Point2D b) {
-		return a.getX() * b.getX() + a.getY() * b.getY();
-	}
-	
-	private static Point2D vector(Point2D from, Point2D to) {
-		return new Point2D.Double(to.getX() - from.getX(), to.getY() - from.getY());
-	}
-	
-	public static double angle(Point2D from, Point2D to) {
-		final double dx = to.getX() - from.getX();
-		final double dy = -(to.getY() - from.getY());
-		
-		return normalize(Math.atan2(dy, dx));
-	}
-	
-	public static Point2D relativePoint(Point2D p, double r, double a) {
-		return new Point2D.Double( //
-		    p.getX() + r * Math.cos(a), //
-		    p.getY() - r * Math.sin(a) //
-		);
-	}
-	
-	public static Line2D relativeLine(Line2D l, double r, double a) {
-		final double dx = r * Math.cos(a);
-		final double dy = -r * Math.sin(a);
-		
-		return new Line2D.Double( //
-		    l.getX1() + dx, //
-		    l.getY1() + dy, //
-		    l.getX2() + dx, //
-		    l.getY2() + dy //
-		);
-	}
-	
-	public static double angle(Line2D l) {
-		return angle(l.getP1(), l.getP2());
-	}
-	
-	public static double minAngleDiff(double a1, double a2) {
-		final double d = normalize(a2 - a1);
-		
-		return d > Math.PI ? -(2 * Math.PI - d) : d;
-	}
-	
-	public static final Point2D middle(Point2D a, Point2D b) {
-		return relativePoint(a, a.distance(b) / 2, angle(a, b));
-	}
-	
-	public static final Point2D middle(Line2D l) {
-		return middle(l.getP1(), l.getP2());
-	}
-	
-	public static Line2D line(Point2D p, double a) {
-		return new Line2D.Double(p, relativePoint(p, 1, a));
-	}
-	
-	public static Point2D loc(Node node) {
-		final EastNorth loc = Main.proj.latlon2eastNorth(node.getCoor());
-		return new Point2D.Double(loc.getX(), -loc.getY());
-	}
-	
-	public static List<Point2D> locs(Iterable<Junction> junctions) {
-		final List<Point2D> locs = new ArrayList<Point2D>();
-		
-		for (Junction j : junctions) {
-			locs.add(loc(j.getNode()));
-		}
-		
-		return locs;
-	}
-	
-	static void area(Path2D area, Path inner, Path outer) {
-		area.append(inner.getIterator(), false);
-		area.append(ReversePathIterator.reverse(outer.getIterator()), true);
-		area.closePath();
-	}
+    static double normalize(double a) {
+        while (a < 0) {
+            a += 2 * Math.PI;
+        }
+        while (a > 2 * Math.PI) {
+            a -= 2 * Math.PI;
+        }
+        return a;
+    }
+    
+    // control point factor for curves (circle segment of angle a)
+    static double cpf(double a, double scale) {
+        return 4.0 / 3 * Math.tan(min(abs(a), PI - 0.001) / 4) * scale;
+    }
+    
+    static Point2D intersection(Line2D a, Line2D b) {
+        final double aa = GuiUtil.angle(a);
+        final double ab = GuiUtil.angle(b);
+        
+        // less than 1/2 degree => no intersection
+        if (Math.abs(Math.PI - abs(minAngleDiff(aa, ab))) < PI / 360) {
+            return null;
+        }
+        
+        final double d = (a.getX1() - a.getX2()) * (b.getY1() - b.getY2()) - (a.getY1() - a.getY2())
+            * (b.getX1() - b.getX2());
+        
+        final double x = ((b.getX1() - b.getX2()) * (a.getX1() * a.getY2() - a.getY1() * a.getX2()) - (a.getX1() - a
+            .getX2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2()))
+            / d;
+        final double y = ((b.getY1() - b.getY2()) * (a.getX1() * a.getY2() - a.getY1() * a.getX2()) - (a.getY1() - a
+            .getY2()) * (b.getX1() * b.getY2() - b.getY1() * b.getX2()))
+            / d;
+        
+        return new Point2D.Double(x, y);
+    }
+    
+    static Point2D closest(Line2D l, Point2D p) {
+        final Point2D lv = vector(l.getP1(), l.getP2());
+        final double numerator = dot(vector(l.getP1(), p), lv);
+        
+        if (numerator < 0) {
+            return l.getP1();
+        }
+        
+        final double denominator = dot(lv, lv);
+        if (numerator >= denominator) {
+            return l.getP2();
+        }
+        
+        final double r = numerator / denominator;
+        return new Point2D.Double(l.getX1() + r * lv.getX(), l.getY1() + r * lv.getY());
+    }
+    
+    private static double dot(Point2D a, Point2D b) {
+        return a.getX() * b.getX() + a.getY() * b.getY();
+    }
+    
+    private static Point2D vector(Point2D from, Point2D to) {
+        return new Point2D.Double(to.getX() - from.getX(), to.getY() - from.getY());
+    }
+    
+    public static double angle(Point2D from, Point2D to) {
+        final double dx = to.getX() - from.getX();
+        final double dy = -(to.getY() - from.getY());
+        
+        return normalize(Math.atan2(dy, dx));
+    }
+    
+    public static Point2D relativePoint(Point2D p, double r, double a) {
+        return new Point2D.Double( //
+            p.getX() + r * Math.cos(a), //
+            p.getY() - r * Math.sin(a) //
+        );
+    }
+    
+    public static Line2D relativeLine(Line2D l, double r, double a) {
+        final double dx = r * Math.cos(a);
+        final double dy = -r * Math.sin(a);
+        
+        return new Line2D.Double( //
+            l.getX1() + dx, //
+            l.getY1() + dy, //
+            l.getX2() + dx, //
+            l.getY2() + dy //
+        );
+    }
+    
+    public static double angle(Line2D l) {
+        return angle(l.getP1(), l.getP2());
+    }
+    
+    public static double minAngleDiff(double a1, double a2) {
+        final double d = normalize(a2 - a1);
+        
+        return d > Math.PI ? -(2 * Math.PI - d) : d;
+    }
+    
+    public static final Point2D middle(Point2D a, Point2D b) {
+        return relativePoint(a, a.distance(b) / 2, angle(a, b));
+    }
+    
+    public static final Point2D middle(Line2D l) {
+        return middle(l.getP1(), l.getP2());
+    }
+    
+    public static Line2D line(Point2D p, double a) {
+        return new Line2D.Double(p, relativePoint(p, 1, a));
+    }
+    
+    public static Point2D loc(Node node) {
+        final EastNorth loc = Main.getProjection().latlon2eastNorth(node.getCoor());
+        return new Point2D.Double(loc.getX(), -loc.getY());
+    }
+    
+    public static List<Point2D> locs(Iterable<Junction> junctions) {
+        final List<Point2D> locs = new ArrayList<Point2D>();
+        
+        for (Junction j : junctions) {
+            locs.add(loc(j.getNode()));
+        }
+        
+        return locs;
+    }
+    
+    static void area(Path2D area, Path inner, Path outer) {
+        area.append(inner.getIterator(), false);
+        area.append(ReversePathIterator.reverse(outer.getIterator()), true);
+        area.closePath();
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/InteractiveElement.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/InteractiveElement.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/InteractiveElement.java	(revision 26154)
@@ -5,41 +5,41 @@
 
 abstract class InteractiveElement {
-	interface Type {
-		Type INCOMING_CONNECTOR = new Type() {};
-		Type OUTGOING_CONNECTOR = new Type() {};
-		Type TURN_CONNECTION = new Type() {};
-		Type LANE_ADDER = new Type() {};
-		Type EXTENDER = new Type() {};
-		Type VIA_CONNECTOR = new Type() {};
-	}
-	
-	public void paintBackground(Graphics2D g2d, State state) {}
-	
-	abstract void paint(Graphics2D g2d, State state);
-	
-	abstract boolean contains(Point2D p, State state);
-	
-	abstract Type getType();
-	
-	State activate(State old) {
-		return old;
-	}
-	
-	boolean beginDrag(double x, double y) {
-		return false;
-	}
-	
-	State drag(double x, double y, InteractiveElement target, State old) {
-		return old;
-	}
-	
-	State drop(double x, double y, InteractiveElement target, State old) {
-		return old;
-	}
-	
-	abstract int getZIndex();
-	
-	State click(State old) {
-		return old;
-	}
+    interface Type {
+        Type INCOMING_CONNECTOR = new Type() {};
+        Type OUTGOING_CONNECTOR = new Type() {};
+        Type TURN_CONNECTION = new Type() {};
+        Type LANE_ADDER = new Type() {};
+        Type EXTENDER = new Type() {};
+        Type VIA_CONNECTOR = new Type() {};
+    }
+    
+    public void paintBackground(Graphics2D g2d, State state) {}
+    
+    abstract void paint(Graphics2D g2d, State state);
+    
+    abstract boolean contains(Point2D p, State state);
+    
+    abstract Type getType();
+    
+    State activate(State old) {
+        return old;
+    }
+    
+    boolean beginDrag(double x, double y) {
+        return false;
+    }
+    
+    State drag(double x, double y, InteractiveElement target, State old) {
+        return old;
+    }
+    
+    State drop(double x, double y, InteractiveElement target, State old) {
+        return old;
+    }
+    
+    abstract int getZIndex();
+    
+    State click(State old) {
+        return old;
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionGui.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionGui.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionGui.java	(revision 26154)
@@ -37,417 +37,417 @@
 
 class JunctionGui {
-	private final class TurnConnection extends InteractiveElement {
-		private final Turn turn;
-		
-		private Point2D dragBegin;
-		private double dragOffsetX = 0;
-		private double dragOffsetY = 0;
-		
-		public TurnConnection(Turn turn) {
-			this.turn = turn;
-		}
-		
-		@Override
-		void paint(Graphics2D g2d, State state) {
-			if (isVisible(state)) {
-				g2d.setStroke(getContainer().getConnectionStroke());
-				g2d.setColor(isRemoveDragOffset() ? GuiContainer.RED : GuiContainer.GREEN);
-				g2d.translate(dragOffsetX, dragOffsetY);
-				g2d.draw(getPath());
-				g2d.translate(-dragOffsetX, -dragOffsetY);
-			}
-		}
-		
-		private Path2D getPath() {
-			final Path2D path = new Path2D.Double();
-			
-			final LaneGui laneGui = getContainer().getGui(turn.getFrom());
-			final RoadGui roadGui = getContainer().getGui(turn.getTo().getRoad());
-			
-			path.moveTo(laneGui.outgoing.getCenter().getX(), laneGui.outgoing.getCenter().getY());
-			
-			Junction j = laneGui.getModel().getOutgoingJunction();
-			for (Road v : turn.getVia()) {
-				final PathIterator it;
-				if (v.getFromEnd().getJunction().equals(j)) {
-					it = getContainer().getGui(v).getLaneMiddle(true).getIterator();
-					j = v.getToEnd().getJunction();
-				} else {
-					it = getContainer().getGui(v).getLaneMiddle(false).getIterator();
-					j = v.getFromEnd().getJunction();
-				}
-				
-				path.append(it, true);
-			}
-			
-			path.lineTo(roadGui.getConnector(turn.getTo()).getCenter().getX(), roadGui.getConnector(turn.getTo()).getCenter()
-			    .getY());
-			
-			return path;
-		}
-		
-		private boolean isVisible(State state) {
-			if (state instanceof State.AllTurns) {
-				return true;
-			} else if (state instanceof State.OutgoingActive) {
-				return turn.getFrom().equals(((State.OutgoingActive) state).getLane().getModel());
-			} else if (state instanceof State.IncomingActive) {
-				return turn.getTo().equals(((State.IncomingActive) state).getRoadEnd());
-			}
-			
-			return false;
-		}
-		
-		@Override
-		boolean contains(Point2D p, State state) {
-			if (!isVisible(state)) {
-				return false;
-			}
-			
-			final PathIterator it = new FlatteningPathIterator(getPath().getPathIterator(null), 0.05 / getContainer()
-			    .getMpp());
-			final double[] coords = new double[6];
-			double lastX = 0;
-			double lastY = 0;
-			while (!it.isDone()) {
-				if (it.currentSegment(coords) == PathIterator.SEG_LINETO) {
-					final Point2D closest = closest(new Line2D.Double(lastX, lastY, coords[0], coords[1]), p);
-					
-					if (p.distance(closest) <= strokeWidth() / 2) {
-						return true;
-					}
-				}
-				
-				lastX = coords[0];
-				lastY = coords[1];
-				it.next();
-			}
-			
-			return false;
-		}
-		
-		private double strokeWidth() {
-			final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke();
-			return stroke.getLineWidth();
-		}
-		
-		@Override
-		Type getType() {
-			return Type.TURN_CONNECTION;
-		}
-		
-		@Override
-		int getZIndex() {
-			return 0;
-		}
-		
-		@Override
-		boolean beginDrag(double x, double y) {
-			dragBegin = new Point2D.Double(x, y);
-			dragOffsetX = 0;
-			dragOffsetY = 0;
-			return true;
-		}
-		
-		@Override
-		State drag(double x, double y, InteractiveElement target, State old) {
-			dragOffsetX = x - dragBegin.getX();
-			dragOffsetY = y - dragBegin.getY();
-			return old;
-		}
-		
-		@Override
-		State drop(double x, double y, InteractiveElement target, State old) {
-			drag(x, y, target, old);
-			
-			if (isRemoveDragOffset()) {
-				turn.remove();
-			}
-			
-			dragBegin = null;
-			dragOffsetX = 0;
-			dragOffsetY = 0;
-			return new State.Dirty(old);
-		}
-		
-		private boolean isRemoveDragOffset() {
-			final double r = getContainer().getGui(turn.getFrom().getRoad()).connectorRadius;
-			final double max = r - strokeWidth() / 2;
-			return hypot(dragOffsetX, dragOffsetY) > max;
-		}
-	}
-	
-	private final class Corner {
-		final double x1;
-		final double y1;
-		
-		final double cx1;
-		final double cy1;
-		
-		final double cx2;
-		final double cy2;
-		
-		final double x2;
-		final double y2;
-		
-		public Corner(Point2D c1, Point2D cp1, Point2D cp2, Point2D c2) {
-			this.x1 = c1.getX();
-			this.y1 = c1.getY();
-			this.cx1 = cp1.getX();
-			this.cy1 = cp1.getY();
-			this.cx2 = cp2.getX();
-			this.cy2 = cp2.getY();
-			this.x2 = c2.getX();
-			this.y2 = c2.getY();
-		}
-		
-		@Override
-		public String toString() {
-			return "Corner [x1=" + x1 + ", y1=" + y1 + ", cx1=" + cx1 + ", cy1=" + cy1 + ", cx2=" + cx2 + ", cy2=" + cy2
-			    + ", x2=" + x2 + ", y2=" + y2 + "]";
-		}
-	}
-	
-	private final class Linkage implements Comparable<Linkage> {
-		final RoadGui roadGui;
-		final Road.End roadEnd;
-		final double angle;
-		
-		double lTrim;
-		double rTrim;
-		
-		public Linkage(Road.End roadEnd) {
-			this.roadGui = getContainer().getGui(roadEnd.getRoad());
-			this.roadEnd = roadEnd;
-			this.angle = normalize(roadGui.getAngle(roadEnd) + PI);
-			
-			roads.put(angle, this);
-		}
-		
-		@Override
-		public int compareTo(Linkage o) {
-			return Double.compare(angle, o.angle);
-		}
-		
-		public void trimLeft(Linkage right) {
-			right.trimRight(this);
-			
-			final Line2D leftCurb = roadGui.getLeftCurb(roadEnd);
-			final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd);
-			
-			final double leftAngle = angle(leftCurb);
-			final double rightAngle = angle(rightCurb);
-			
-			final Point2D isect;
-			if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) {
-				isect = intersection(leftCurb, rightCurb);
-			} else {
-				isect = GuiUtil.relativePoint(leftCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle);
-			}
-			
-			if (Math.abs(leftAngle - angle(leftCurb.getP1(), isect)) < 0.1) {
-				lTrim = leftCurb.getP1().distance(isect);
-			}
-		}
-		
-		private void trimRight(Linkage left) {
-			final Line2D rightCurb = roadGui.getRightCurb(roadEnd);
-			final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd);
-			
-			final double rightAngle = angle(rightCurb);
-			final double leftAngle = angle(leftCurb);
-			
-			final Point2D isect;
-			if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) {
-				isect = intersection(rightCurb, leftCurb);
-			} else {
-				isect = GuiUtil.relativePoint(rightCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle);
-			}
-			
-			if (Math.abs(rightAngle - angle(rightCurb.getP1(), isect)) < 0.1) {
-				rTrim = rightCurb.getP1().distance(isect);
-			}
-		}
-		
-		public void trimAdjust() {
-			final double MAX_TAN = tan(PI / 2 - MAX_ANGLE);
-			
-			final double sin = roadGui.getWidth(roadEnd);
-			final double cos = abs(lTrim - rTrim);
-			final double tan = sin / cos;
-			
-			if (tan < MAX_TAN) {
-				lTrim = max(lTrim, rTrim - sin / MAX_TAN);
-				rTrim = max(rTrim, lTrim - sin / MAX_TAN);
-			}
-			
-			lTrim += container.getLaneWidth() / 2;
-			rTrim += container.getLaneWidth() / 2;
-		}
-	}
-	
-	// max angle between corners
-	private static final double MAX_ANGLE = Math.toRadians(30);
-	
-	private final GuiContainer container;
-	private final Junction junction;
-	
-	final double x;
-	final double y;
-	
-	private final NavigableMap<Double, Linkage> roads = new TreeMap<Double, Linkage>();
-	
-	private final Path2D area = new Path2D.Double();
-	
-	public JunctionGui(GuiContainer container, Junction j) {
-		this.container = container;
-		this.junction = j;
-		
-		container.register(this);
-		
-		final Point2D loc = container.translateAndScale(loc(j.getNode()));
-		this.x = loc.getX();
-		this.y = loc.getY();
-		
-		final Set<Road> done = new HashSet<Road>();
-		for (Road r : j.getRoads()) {
-			if (!done.contains(r)) {
-				done.add(r);
-				
-				if (r.getFromEnd().getJunction().equals(j)) {
-					new Linkage(r.getFromEnd());
-				}
-				if (r.getToEnd().getJunction().equals(j)) {
-					new Linkage(r.getToEnd());
-				}
-			}
-		}
-		
-		recalculate();
-	}
-	
-	void recalculate() {
-		for (Linkage l : roads.values()) {
-			l.lTrim = 0;
-			l.rTrim = 0;
-		}
-		
-		area.reset();
-		if (roads.size() < 2) {
-			return;
-		}
-		
-		Linkage last = roads.lastEntry().getValue();
-		for (Linkage l : roads.values()) {
-			l.trimLeft(last);
-			last = l;
-		}
-		for (Linkage l : roads.values()) {
-			l.trimAdjust();
-		}
-		
-		boolean first = true;
-		for (Corner c : corners()) {
-			if (first) {
-				area.moveTo(c.x1, c.y1);
-				first = false;
-			} else {
-				area.lineTo(c.x1, c.y1);
-			}
-			
-			area.curveTo(c.cx1, c.cy1, c.cx2, c.cy2, c.x2, c.y2);
-		}
-		
-		area.closePath();
-	}
-	
-	private Iterable<Corner> corners() {
-		final List<Corner> result = new ArrayList<JunctionGui.Corner>(roads.size());
-		
-		Linkage last = roads.lastEntry().getValue();
-		for (Linkage l : roads.values()) {
-			result.add(corner(last, l));
-			last = l;
-		}
-		
-		return result;
-	}
-	
-	private Corner corner(Linkage right, Linkage left) {
-		final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd);
-		final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd);
-		
-		final double rightAngle = angle(rightCurb);
-		final double leftAngle = angle(leftCurb);
-		
-		final double delta = normalize(leftAngle - rightAngle);
-		
-		final boolean wide = delta > PI;
-		final double a = wide ? max(0, delta - (PI + 2 * MAX_ANGLE)) : delta;
-		
-		final double cpf1 = cpf(a, container.getLaneWidth() / 2 + (wide ? right.roadGui.getWidth(right.roadEnd) : 0));
-		final double cpf2 = cpf(a, container.getLaneWidth() / 2 + (wide ? left.roadGui.getWidth(left.roadEnd) : 0));
-		
-		final Point2D c1 = relativePoint(rightCurb.getP1(), cpf1, right.angle + PI);
-		final Point2D c2 = relativePoint(leftCurb.getP1(), cpf2, left.angle + PI);
-		
-		return new Corner(rightCurb.getP1(), c1, c2, leftCurb.getP1());
-	}
-	
-	public Set<RoadGui> getRoads() {
-		final Set<RoadGui> result = new HashSet<RoadGui>();
-		
-		for (Linkage l : roads.values()) {
-			result.add(l.roadGui);
-		}
-		
-		return Collections.unmodifiableSet(result);
-	}
-	
-	double getLeftTrim(Road.End end) {
-		return getLinkage(end).lTrim;
-	}
-	
-	private Linkage getLinkage(Road.End end) {
-		final double a = normalize(getContainer().getGui(end.getRoad()).getAngle(end) + PI);
-		final Map.Entry<Double, Linkage> e = roads.floorEntry(a);
-		return e != null ? e.getValue() : null;
-	}
-	
-	double getRightTrim(Road.End end) {
-		return getLinkage(end).rTrim;
-	}
-	
-	Point2D getPoint() {
-		return new Point2D.Double(x, y);
-	}
-	
-	public GuiContainer getContainer() {
-		return container;
-	}
-	
-	public Junction getModel() {
-		return junction;
-	}
-	
-	public List<InteractiveElement> paint(Graphics2D g2d) {
-		g2d.setColor(new Color(96, 96, 96));
-		g2d.fill(area);
-		
-		final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
-		
-		if (getModel().isPrimary()) {
-			for (Road.End r : new HashSet<Road.End>(getModel().getRoadEnds())) {
-				for (Turn t : r.getTurns()) {
-					result.add(new TurnConnection(t));
-				}
-			}
-		}
-		
-		return result;
-	}
-	
-	public Rectangle2D getBounds() {
-		return area.getBounds2D();
-	}
+    private final class TurnConnection extends InteractiveElement {
+        private final Turn turn;
+        
+        private Point2D dragBegin;
+        private double dragOffsetX = 0;
+        private double dragOffsetY = 0;
+        
+        public TurnConnection(Turn turn) {
+            this.turn = turn;
+        }
+        
+        @Override
+        void paint(Graphics2D g2d, State state) {
+            if (isVisible(state)) {
+                g2d.setStroke(getContainer().getConnectionStroke());
+                g2d.setColor(isRemoveDragOffset() ? GuiContainer.RED : GuiContainer.GREEN);
+                g2d.translate(dragOffsetX, dragOffsetY);
+                g2d.draw(getPath());
+                g2d.translate(-dragOffsetX, -dragOffsetY);
+            }
+        }
+        
+        private Path2D getPath() {
+            final Path2D path = new Path2D.Double();
+            
+            final LaneGui laneGui = getContainer().getGui(turn.getFrom());
+            final RoadGui roadGui = getContainer().getGui(turn.getTo().getRoad());
+            
+            path.moveTo(laneGui.outgoing.getCenter().getX(), laneGui.outgoing.getCenter().getY());
+            
+            Junction j = laneGui.getModel().getOutgoingJunction();
+            for (Road v : turn.getVia()) {
+                final PathIterator it;
+                if (v.getFromEnd().getJunction().equals(j)) {
+                    it = getContainer().getGui(v).getLaneMiddle(true).getIterator();
+                    j = v.getToEnd().getJunction();
+                } else {
+                    it = getContainer().getGui(v).getLaneMiddle(false).getIterator();
+                    j = v.getFromEnd().getJunction();
+                }
+                
+                path.append(it, true);
+            }
+            
+            path.lineTo(roadGui.getConnector(turn.getTo()).getCenter().getX(), roadGui.getConnector(turn.getTo()).getCenter()
+                .getY());
+            
+            return path;
+        }
+        
+        private boolean isVisible(State state) {
+            if (state instanceof State.AllTurns) {
+                return true;
+            } else if (state instanceof State.OutgoingActive) {
+                return turn.getFrom().equals(((State.OutgoingActive) state).getLane().getModel());
+            } else if (state instanceof State.IncomingActive) {
+                return turn.getTo().equals(((State.IncomingActive) state).getRoadEnd());
+            }
+            
+            return false;
+        }
+        
+        @Override
+        boolean contains(Point2D p, State state) {
+            if (!isVisible(state)) {
+                return false;
+            }
+            
+            final PathIterator it = new FlatteningPathIterator(getPath().getPathIterator(null), 0.05 / getContainer()
+                .getMpp());
+            final double[] coords = new double[6];
+            double lastX = 0;
+            double lastY = 0;
+            while (!it.isDone()) {
+                if (it.currentSegment(coords) == PathIterator.SEG_LINETO) {
+                    final Point2D closest = closest(new Line2D.Double(lastX, lastY, coords[0], coords[1]), p);
+                    
+                    if (p.distance(closest) <= strokeWidth() / 2) {
+                        return true;
+                    }
+                }
+                
+                lastX = coords[0];
+                lastY = coords[1];
+                it.next();
+            }
+            
+            return false;
+        }
+        
+        private double strokeWidth() {
+            final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke();
+            return stroke.getLineWidth();
+        }
+        
+        @Override
+        Type getType() {
+            return Type.TURN_CONNECTION;
+        }
+        
+        @Override
+        int getZIndex() {
+            return 0;
+        }
+        
+        @Override
+        boolean beginDrag(double x, double y) {
+            dragBegin = new Point2D.Double(x, y);
+            dragOffsetX = 0;
+            dragOffsetY = 0;
+            return true;
+        }
+        
+        @Override
+        State drag(double x, double y, InteractiveElement target, State old) {
+            dragOffsetX = x - dragBegin.getX();
+            dragOffsetY = y - dragBegin.getY();
+            return old;
+        }
+        
+        @Override
+        State drop(double x, double y, InteractiveElement target, State old) {
+            drag(x, y, target, old);
+            
+            if (isRemoveDragOffset()) {
+                turn.remove();
+            }
+            
+            dragBegin = null;
+            dragOffsetX = 0;
+            dragOffsetY = 0;
+            return new State.Dirty(old);
+        }
+        
+        private boolean isRemoveDragOffset() {
+            final double r = getContainer().getGui(turn.getFrom().getRoad()).connectorRadius;
+            final double max = r - strokeWidth() / 2;
+            return hypot(dragOffsetX, dragOffsetY) > max;
+        }
+    }
+    
+    private final class Corner {
+        final double x1;
+        final double y1;
+        
+        final double cx1;
+        final double cy1;
+        
+        final double cx2;
+        final double cy2;
+        
+        final double x2;
+        final double y2;
+        
+        public Corner(Point2D c1, Point2D cp1, Point2D cp2, Point2D c2) {
+            this.x1 = c1.getX();
+            this.y1 = c1.getY();
+            this.cx1 = cp1.getX();
+            this.cy1 = cp1.getY();
+            this.cx2 = cp2.getX();
+            this.cy2 = cp2.getY();
+            this.x2 = c2.getX();
+            this.y2 = c2.getY();
+        }
+        
+        @Override
+        public String toString() {
+            return "Corner [x1=" + x1 + ", y1=" + y1 + ", cx1=" + cx1 + ", cy1=" + cy1 + ", cx2=" + cx2 + ", cy2=" + cy2
+                + ", x2=" + x2 + ", y2=" + y2 + "]";
+        }
+    }
+    
+    private final class Linkage implements Comparable<Linkage> {
+        final RoadGui roadGui;
+        final Road.End roadEnd;
+        final double angle;
+        
+        double lTrim;
+        double rTrim;
+        
+        public Linkage(Road.End roadEnd) {
+            this.roadGui = getContainer().getGui(roadEnd.getRoad());
+            this.roadEnd = roadEnd;
+            this.angle = normalize(roadGui.getAngle(roadEnd) + PI);
+            
+            roads.put(angle, this);
+        }
+        
+        @Override
+        public int compareTo(Linkage o) {
+            return Double.compare(angle, o.angle);
+        }
+        
+        public void trimLeft(Linkage right) {
+            right.trimRight(this);
+            
+            final Line2D leftCurb = roadGui.getLeftCurb(roadEnd);
+            final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd);
+            
+            final double leftAngle = angle(leftCurb);
+            final double rightAngle = angle(rightCurb);
+            
+            final Point2D isect;
+            if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) {
+                isect = intersection(leftCurb, rightCurb);
+            } else {
+                isect = GuiUtil.relativePoint(leftCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle);
+            }
+            
+            if (Math.abs(leftAngle - angle(leftCurb.getP1(), isect)) < 0.1) {
+                lTrim = leftCurb.getP1().distance(isect);
+            }
+        }
+        
+        private void trimRight(Linkage left) {
+            final Line2D rightCurb = roadGui.getRightCurb(roadEnd);
+            final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd);
+            
+            final double rightAngle = angle(rightCurb);
+            final double leftAngle = angle(leftCurb);
+            
+            final Point2D isect;
+            if (abs(PI - normalize(rightAngle - leftAngle)) > PI / 12) {
+                isect = intersection(rightCurb, leftCurb);
+            } else {
+                isect = GuiUtil.relativePoint(rightCurb.getP1(), roadGui.getWidth(roadEnd) / 2, angle);
+            }
+            
+            if (Math.abs(rightAngle - angle(rightCurb.getP1(), isect)) < 0.1) {
+                rTrim = rightCurb.getP1().distance(isect);
+            }
+        }
+        
+        public void trimAdjust() {
+            final double MAX_TAN = tan(PI / 2 - MAX_ANGLE);
+            
+            final double sin = roadGui.getWidth(roadEnd);
+            final double cos = abs(lTrim - rTrim);
+            final double tan = sin / cos;
+            
+            if (tan < MAX_TAN) {
+                lTrim = max(lTrim, rTrim - sin / MAX_TAN);
+                rTrim = max(rTrim, lTrim - sin / MAX_TAN);
+            }
+            
+            lTrim += container.getLaneWidth() / 2;
+            rTrim += container.getLaneWidth() / 2;
+        }
+    }
+    
+    // max angle between corners
+    private static final double MAX_ANGLE = Math.toRadians(30);
+    
+    private final GuiContainer container;
+    private final Junction junction;
+    
+    final double x;
+    final double y;
+    
+    private final NavigableMap<Double, Linkage> roads = new TreeMap<Double, Linkage>();
+    
+    private final Path2D area = new Path2D.Double();
+    
+    public JunctionGui(GuiContainer container, Junction j) {
+        this.container = container;
+        this.junction = j;
+        
+        container.register(this);
+        
+        final Point2D loc = container.translateAndScale(loc(j.getNode()));
+        this.x = loc.getX();
+        this.y = loc.getY();
+        
+        final Set<Road> done = new HashSet<Road>();
+        for (Road r : j.getRoads()) {
+            if (!done.contains(r)) {
+                done.add(r);
+                
+                if (r.getFromEnd().getJunction().equals(j)) {
+                    new Linkage(r.getFromEnd());
+                }
+                if (r.getToEnd().getJunction().equals(j)) {
+                    new Linkage(r.getToEnd());
+                }
+            }
+        }
+        
+        recalculate();
+    }
+    
+    void recalculate() {
+        for (Linkage l : roads.values()) {
+            l.lTrim = 0;
+            l.rTrim = 0;
+        }
+        
+        area.reset();
+        if (roads.size() < 2) {
+            return;
+        }
+        
+        Linkage last = roads.lastEntry().getValue();
+        for (Linkage l : roads.values()) {
+            l.trimLeft(last);
+            last = l;
+        }
+        for (Linkage l : roads.values()) {
+            l.trimAdjust();
+        }
+        
+        boolean first = true;
+        for (Corner c : corners()) {
+            if (first) {
+                area.moveTo(c.x1, c.y1);
+                first = false;
+            } else {
+                area.lineTo(c.x1, c.y1);
+            }
+            
+            area.curveTo(c.cx1, c.cy1, c.cx2, c.cy2, c.x2, c.y2);
+        }
+        
+        area.closePath();
+    }
+    
+    private Iterable<Corner> corners() {
+        final List<Corner> result = new ArrayList<JunctionGui.Corner>(roads.size());
+        
+        Linkage last = roads.lastEntry().getValue();
+        for (Linkage l : roads.values()) {
+            result.add(corner(last, l));
+            last = l;
+        }
+        
+        return result;
+    }
+    
+    private Corner corner(Linkage right, Linkage left) {
+        final Line2D rightCurb = right.roadGui.getRightCurb(right.roadEnd);
+        final Line2D leftCurb = left.roadGui.getLeftCurb(left.roadEnd);
+        
+        final double rightAngle = angle(rightCurb);
+        final double leftAngle = angle(leftCurb);
+        
+        final double delta = normalize(leftAngle - rightAngle);
+        
+        final boolean wide = delta > PI;
+        final double a = wide ? max(0, delta - (PI + 2 * MAX_ANGLE)) : delta;
+        
+        final double cpf1 = cpf(a, container.getLaneWidth() / 2 + (wide ? right.roadGui.getWidth(right.roadEnd) : 0));
+        final double cpf2 = cpf(a, container.getLaneWidth() / 2 + (wide ? left.roadGui.getWidth(left.roadEnd) : 0));
+        
+        final Point2D c1 = relativePoint(rightCurb.getP1(), cpf1, right.angle + PI);
+        final Point2D c2 = relativePoint(leftCurb.getP1(), cpf2, left.angle + PI);
+        
+        return new Corner(rightCurb.getP1(), c1, c2, leftCurb.getP1());
+    }
+    
+    public Set<RoadGui> getRoads() {
+        final Set<RoadGui> result = new HashSet<RoadGui>();
+        
+        for (Linkage l : roads.values()) {
+            result.add(l.roadGui);
+        }
+        
+        return Collections.unmodifiableSet(result);
+    }
+    
+    double getLeftTrim(Road.End end) {
+        return getLinkage(end).lTrim;
+    }
+    
+    private Linkage getLinkage(Road.End end) {
+        final double a = normalize(getContainer().getGui(end.getRoad()).getAngle(end) + PI);
+        final Map.Entry<Double, Linkage> e = roads.floorEntry(a);
+        return e != null ? e.getValue() : null;
+    }
+    
+    double getRightTrim(Road.End end) {
+        return getLinkage(end).rTrim;
+    }
+    
+    Point2D getPoint() {
+        return new Point2D.Double(x, y);
+    }
+    
+    public GuiContainer getContainer() {
+        return container;
+    }
+    
+    public Junction getModel() {
+        return junction;
+    }
+    
+    public List<InteractiveElement> paint(Graphics2D g2d) {
+        g2d.setColor(new Color(96, 96, 96));
+        g2d.fill(area);
+        
+        final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
+        
+        if (getModel().isPrimary()) {
+            for (Road.End r : new HashSet<Road.End>(getModel().getRoadEnds())) {
+                for (Turn t : r.getTurns()) {
+                    result.add(new TurnConnection(t));
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    public Rectangle2D getBounds() {
+        return area.getBounds2D();
+    }
 }
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 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionPane.java	(revision 26154)
@@ -27,407 +27,407 @@
 
 class JunctionPane extends JComponent {
-	private final class MouseInputProcessor extends MouseAdapter {
-		private int originX;
-		private int originY;
-		private int button;
-		
-		public void mousePressed(MouseEvent e) {
-			setFocusable(true);
-			button = e.getButton();
-			
-			if (button == MouseEvent.BUTTON1) {
-				final Point2D mouse = translateMouseCoords(e);
-				for (InteractiveElement ie : interactives()) {
-					if (ie.contains(mouse, state)) {
-						setState(ie.activate(state));
-						repaint();
-						break;
-					}
-				}
-			}
-			
-			originX = e.getX();
-			originY = e.getY();
-		}
-		
-		@Override
-		public void mouseReleased(MouseEvent e) {
-			if (dragging != null) {
-				final Point2D mouse = translateMouseCoords(e);
-				setState(dragging.drop(mouse.getX(), mouse.getY(), dropTarget(mouse), state));
-			}
-			
-			dragging = null;
-			repaint();
-		}
-		
-		private InteractiveElement dropTarget(Point2D mouse) {
-			for (InteractiveElement ie : interactives()) {
-				if (ie.contains(mouse, state)) {
-					return ie;
-				}
-			}
-			
-			return null;
-		}
-		
-		@Override
-		public void mouseClicked(MouseEvent e) {
-			if (button == MouseEvent.BUTTON1) {
-				final Point2D mouse = translateMouseCoords(e);
-				for (InteractiveElement ie : interactives()) {
-					if (ie.contains(mouse, state)) {
-						setState(ie.click(state));
-						break;
-					}
-				}
-			}
-		}
-		
-		public void mouseDragged(MouseEvent e) {
-			if (button == MouseEvent.BUTTON1) {
-				final Point2D mouse = translateMouseCoords(e);
-				
-				if (dragging == null) {
-					final Point2D origin = translateCoords(originX, originY);
-					for (InteractiveElement ie : interactives()) {
-						if (ie.contains(origin, state)) {
-							if (ie.beginDrag(origin.getX(), origin.getY())) {
-								dragging = ie;
-							}
-							
-							break;
-						}
-					}
-				}
-				
-				if (dragging != null) {
-					setState(dragging.drag(mouse.getX(), mouse.getY(), dropTarget(mouse), state));
-				}
-			} else if (button == MouseEvent.BUTTON3) {
-				translate(e.getX() - originX, e.getY() - originY);
-				
-				originX = e.getX();
-				originY = e.getY();
-			}
-		}
-		
-		@Override
-		public void mouseWheelMoved(MouseWheelEvent e) {
-			scale(e.getX(), e.getY(), Math.pow(0.8, e.getWheelRotation()));
-		}
-		
-		private Point2D translateMouseCoords(MouseEvent e) {
-			return translateCoords(e.getX(), e.getY());
-		}
-		
-		private Point2D translateCoords(int x, int y) {
-			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);
-		}
-	}
-	
-	private static final long serialVersionUID = 6917061040674799271L;
-	
-	private static final Color TRANSPARENT = new Color(0, 0, 0, 0);
-	
-	private final MouseInputProcessor mip = new MouseInputProcessor();
-	
-	private GuiContainer container;
-	
-	private int width = 0;
-	private int height = 0;
-	private double rotation = 0;
-	private double scale = 10;
-	private double translationX = 0;
-	private double translationY = 0;
-	private boolean dirty = true;
-	private BufferedImage passive;
-	private BufferedImage interactive;
-	
-	private final NavigableMap<Integer, List<InteractiveElement>> interactives = new TreeMap<Integer, List<InteractiveElement>>();
-	private State state;
-	private InteractiveElement dragging;
-	
-	public JunctionPane(GuiContainer container) {
-		setJunction(container);
-		
-		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "refresh");
-		getActionMap().put("refresh", new AbstractAction() {
-			private static final long serialVersionUID = 1L;
-			
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				setState(new State.Invalid(state));
-			}
-		});
-		
-		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "zoomIn");
-		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoomIn");
-		getActionMap().put("zoomIn", new AbstractAction() {
-			private static final long serialVersionUID = 1L;
-			
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				scale(Math.pow(0.8, -1));
-			}
-		});
-		
-		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "zoomOut");
-		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoomOut");
-		getActionMap().put("zoomOut", new AbstractAction() {
-			private static final long serialVersionUID = 1L;
-			
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				scale(Math.pow(0.8, 1));
-			}
-		});
-		
-		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "center");
-		getActionMap().put("center", new AbstractAction() {
-			private static final long serialVersionUID = 1L;
-			
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				center();
-			}
-		});
-		
-		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK), "toggleAllTurns");
-		getActionMap().put("toggleAllTurns", new AbstractAction() {
-			private static final long serialVersionUID = 1L;
-			
-			@Override
-			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));
-			}
-		});
-	}
-	
-	public void setJunction(GuiContainer container) {
-		removeMouseListener(mip);
-		removeMouseMotionListener(mip);
-		removeMouseWheelListener(mip);
-		interactives.clear();
-		dragging = null;
-		this.container = container;
-		
-		if (container == null) {
-			this.state = null;
-		} else {
-			setState(new State.Dirty(new State.Default()));
-			
-			center();
-			
-			addMouseListener(mip);
-			addMouseMotionListener(mip);
-			addMouseWheelListener(mip);
-		}
-	}
-	
-	private void center() {
-		final Rectangle2D bounds = container.getBounds();
-		
-		rotation = 0;
-		
-		scale = Math.min(getHeight() / 2 / bounds.getHeight(), getWidth() / 2 / bounds.getWidth());
-		
-		translationX = -bounds.getCenterX();
-		translationY = -bounds.getCenterY();
-		
-		translate(getWidth() / 2d, getHeight() / 2d);
-	}
-	
-	private void toggleAllTurns() {
-		if (state instanceof State.AllTurns) {
-			setState(((State.AllTurns) state).unwrap());
-		} else {
-			setState(new State.AllTurns(state));
-		}
-	}
-	
-	private void setState(State state) {
-		if (state instanceof State.AllTurns) {
-			dirty = true;
-			this.state = state;
-		} else if (state instanceof State.Invalid) {
-			container = container.recalculate();
-			dirty = true;
-			this.state = new State.Default();
-		} else if (state instanceof State.Dirty) {
-			dirty = true;
-			this.state = ((State.Dirty) state).unwrap();
-		} else {
-			this.state = state;
-		}
-		
-		repaint();
-	}
-	
-	void scale(int x, int y, double scale) {
-		this.scale *= scale;
-		
-		final double w = getWidth();
-		final double h = getHeight();
-		
-		translationX -= (w * (scale - 1)) / (2 * this.scale);
-		translationY -= (h * (scale - 1)) / (2 * this.scale);
-		
-		dirty = true;
-		repaint();
-	}
-	
-	void scale(double scale) {
-		scale(getWidth() / 2, getHeight() / 2, scale);
-	}
-	
-	void translate(double x, double y) {
-		translationX += x / scale;
-		translationY += y / scale;
-		
-		dirty = true;
-		repaint();
-	}
-	
-	@Override
-	protected void paintComponent(Graphics g) {
-		if (getWidth() != width || getHeight() != height) {
-			translate((getWidth() - width) / 2d, (getHeight() - height) / 2d);
-			width = getWidth();
-			height = getHeight();
-			
-			// translate already set dirty flag
-			passive = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
-			interactive = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
-		}
-		
-		if (container == null) {
-			super.paintComponent(g);
-			return;
-		}
-		
-		if (dirty) {
-			paintPassive((Graphics2D) passive.getGraphics());
-			dirty = false;
-		}
-		paintInteractive((Graphics2D) interactive.getGraphics());
-		
-		final Graphics2D g2d = (Graphics2D) g;
-		
-		g2d.drawImage(passive, 0, 0, getWidth(), getHeight(), null);
-		g2d.drawImage(interactive, 0, 0, getWidth(), getHeight(), null);
-	}
-	
-	private void paintInteractive(Graphics2D g2d) {
-		g2d.setBackground(TRANSPARENT);
-		g2d.clearRect(0, 0, getWidth(), getHeight());
-		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
-		g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
-		
-		g2d.scale(scale, scale);
-		g2d.translate(translationX, translationY);
-		g2d.rotate(rotation);
-		
-		g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.7f));
-		
-		for (Map.Entry<Integer, List<InteractiveElement>> e : interactives.entrySet()) {
-			for (InteractiveElement ie : e.getValue()) {
-				ie.paintBackground(g2d, state);
-			}
-			for (InteractiveElement ie : e.getValue()) {
-				ie.paint(g2d, state);
-			}
-		}
-	}
-	
-	private List<InteractiveElement> interactives() {
-		final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
-		
-		for (List<InteractiveElement> ies : interactives.descendingMap().values()) {
-			result.addAll(ies);
-		}
-		
-		return result;
-	}
-	
-	private void paintPassive(Graphics2D g2d) {
-		interactives.clear();
-		
-		g2d.setBackground(new Color(100, 160, 240));
-		g2d.clearRect(0, 0, getWidth(), getHeight());
-		
-		g2d.scale(scale, scale);
-		g2d.translate(translationX, translationY);
-		g2d.rotate(rotation);
-		
-		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-		
-		g2d.setColor(Color.GRAY);
-		for (RoadGui r : container.getRoads()) {
-			addAllInteractives(r.paint(g2d));
-		}
-		
-		for (JunctionGui j : container.getJunctions()) {
-			addAllInteractives(j.paint(g2d));
-			dot(g2d, new Point2D.Double(j.x, j.y), container.getLaneWidth() / 5);
-		}
-	}
-	
-	private void addAllInteractives(List<InteractiveElement> ies) {
-		for (InteractiveElement ie : ies) {
-			final List<InteractiveElement> existing = interactives.get(ie.getZIndex());
-			
-			final List<InteractiveElement> list;
-			if (existing == null) {
-				list = new ArrayList<InteractiveElement>();
-				interactives.put(ie.getZIndex(), list);
-			} else {
-				list = existing;
-			}
-			
-			list.add(ie);
-		}
-	}
-	
-	static void dot(Graphics2D g2d, Point2D p, double r, Color c) {
-		final Color old = g2d.getColor();
-		
-		g2d.setColor(c);
-		g2d.fill(new Ellipse2D.Double(p.getX() - r, p.getY() - r, 2 * r, 2 * r));
-		
-		g2d.setColor(old);
-	}
-	
-	static void dot(Graphics2D g2d, Point2D p, double r) {
-		dot(g2d, p, r, Color.RED);
-	}
+    private final class MouseInputProcessor extends MouseAdapter {
+        private int originX;
+        private int originY;
+        private int button;
+        
+        public void mousePressed(MouseEvent e) {
+            setFocusable(true);
+            button = e.getButton();
+            
+            if (button == MouseEvent.BUTTON1) {
+                final Point2D mouse = translateMouseCoords(e);
+                for (InteractiveElement ie : interactives()) {
+                    if (ie.contains(mouse, state)) {
+                        setState(ie.activate(state));
+                        repaint();
+                        break;
+                    }
+                }
+            }
+            
+            originX = e.getX();
+            originY = e.getY();
+        }
+        
+        @Override
+        public void mouseReleased(MouseEvent e) {
+            if (dragging != null) {
+                final Point2D mouse = translateMouseCoords(e);
+                setState(dragging.drop(mouse.getX(), mouse.getY(), dropTarget(mouse), state));
+            }
+            
+            dragging = null;
+            repaint();
+        }
+        
+        private InteractiveElement dropTarget(Point2D mouse) {
+            for (InteractiveElement ie : interactives()) {
+                if (ie.contains(mouse, state)) {
+                    return ie;
+                }
+            }
+            
+            return null;
+        }
+        
+        @Override
+        public void mouseClicked(MouseEvent e) {
+            if (button == MouseEvent.BUTTON1) {
+                final Point2D mouse = translateMouseCoords(e);
+                for (InteractiveElement ie : interactives()) {
+                    if (ie.contains(mouse, state)) {
+                        setState(ie.click(state));
+                        break;
+                    }
+                }
+            }
+        }
+        
+        public void mouseDragged(MouseEvent e) {
+            if (button == MouseEvent.BUTTON1) {
+                final Point2D mouse = translateMouseCoords(e);
+                
+                if (dragging == null) {
+                    final Point2D origin = translateCoords(originX, originY);
+                    for (InteractiveElement ie : interactives()) {
+                        if (ie.contains(origin, state)) {
+                            if (ie.beginDrag(origin.getX(), origin.getY())) {
+                                dragging = ie;
+                            }
+                            
+                            break;
+                        }
+                    }
+                }
+                
+                if (dragging != null) {
+                    setState(dragging.drag(mouse.getX(), mouse.getY(), dropTarget(mouse), state));
+                }
+            } else if (button == MouseEvent.BUTTON3) {
+                translate(e.getX() - originX, e.getY() - originY);
+                
+                originX = e.getX();
+                originY = e.getY();
+            }
+        }
+        
+        @Override
+        public void mouseWheelMoved(MouseWheelEvent e) {
+            scale(e.getX(), e.getY(), Math.pow(0.8, e.getWheelRotation()));
+        }
+        
+        private Point2D translateMouseCoords(MouseEvent e) {
+            return translateCoords(e.getX(), e.getY());
+        }
+        
+        private Point2D translateCoords(int x, int y) {
+            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);
+        }
+    }
+    
+    private static final long serialVersionUID = 6917061040674799271L;
+    
+    private static final Color TRANSPARENT = new Color(0, 0, 0, 0);
+    
+    private final MouseInputProcessor mip = new MouseInputProcessor();
+    
+    private GuiContainer container;
+    
+    private int width = 0;
+    private int height = 0;
+    private double rotation = 0;
+    private double scale = 10;
+    private double translationX = 0;
+    private double translationY = 0;
+    private boolean dirty = true;
+    private BufferedImage passive;
+    private BufferedImage interactive;
+    
+    private final NavigableMap<Integer, List<InteractiveElement>> interactives = new TreeMap<Integer, List<InteractiveElement>>();
+    private State state;
+    private InteractiveElement dragging;
+    
+    public JunctionPane(GuiContainer container) {
+        setJunction(container);
+        
+        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "refresh");
+        getActionMap().put("refresh", new AbstractAction() {
+            private static final long serialVersionUID = 1L;
+            
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                setState(new State.Invalid(state));
+            }
+        });
+        
+        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "zoomIn");
+        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoomIn");
+        getActionMap().put("zoomIn", new AbstractAction() {
+            private static final long serialVersionUID = 1L;
+            
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                scale(Math.pow(0.8, -1));
+            }
+        });
+        
+        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "zoomOut");
+        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoomOut");
+        getActionMap().put("zoomOut", new AbstractAction() {
+            private static final long serialVersionUID = 1L;
+            
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                scale(Math.pow(0.8, 1));
+            }
+        });
+        
+        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "center");
+        getActionMap().put("center", new AbstractAction() {
+            private static final long serialVersionUID = 1L;
+            
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                center();
+            }
+        });
+        
+        getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK), "toggleAllTurns");
+        getActionMap().put("toggleAllTurns", new AbstractAction() {
+            private static final long serialVersionUID = 1L;
+            
+            @Override
+            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));
+            }
+        });
+    }
+    
+    public void setJunction(GuiContainer container) {
+        removeMouseListener(mip);
+        removeMouseMotionListener(mip);
+        removeMouseWheelListener(mip);
+        interactives.clear();
+        dragging = null;
+        this.container = container;
+        
+        if (container == null) {
+            this.state = null;
+        } else {
+            setState(new State.Dirty(new State.Default()));
+            
+            center();
+            
+            addMouseListener(mip);
+            addMouseMotionListener(mip);
+            addMouseWheelListener(mip);
+        }
+    }
+    
+    private void center() {
+        final Rectangle2D bounds = container.getBounds();
+        
+        rotation = 0;
+        
+        scale = Math.min(getHeight() / 2 / bounds.getHeight(), getWidth() / 2 / bounds.getWidth());
+        
+        translationX = -bounds.getCenterX();
+        translationY = -bounds.getCenterY();
+        
+        translate(getWidth() / 2d, getHeight() / 2d);
+    }
+    
+    private void toggleAllTurns() {
+        if (state instanceof State.AllTurns) {
+            setState(((State.AllTurns) state).unwrap());
+        } else {
+            setState(new State.AllTurns(state));
+        }
+    }
+    
+    private void setState(State state) {
+        if (state instanceof State.AllTurns) {
+            dirty = true;
+            this.state = state;
+        } else if (state instanceof State.Invalid) {
+            container = container.recalculate();
+            dirty = true;
+            this.state = new State.Default();
+        } else if (state instanceof State.Dirty) {
+            dirty = true;
+            this.state = ((State.Dirty) state).unwrap();
+        } else {
+            this.state = state;
+        }
+        
+        repaint();
+    }
+    
+    void scale(int x, int y, double scale) {
+        this.scale *= scale;
+        
+        final double w = getWidth();
+        final double h = getHeight();
+        
+        translationX -= (w * (scale - 1)) / (2 * this.scale);
+        translationY -= (h * (scale - 1)) / (2 * this.scale);
+        
+        dirty = true;
+        repaint();
+    }
+    
+    void scale(double scale) {
+        scale(getWidth() / 2, getHeight() / 2, scale);
+    }
+    
+    void translate(double x, double y) {
+        translationX += x / scale;
+        translationY += y / scale;
+        
+        dirty = true;
+        repaint();
+    }
+    
+    @Override
+    protected void paintComponent(Graphics g) {
+        if (getWidth() != width || getHeight() != height) {
+            translate((getWidth() - width) / 2d, (getHeight() - height) / 2d);
+            width = getWidth();
+            height = getHeight();
+            
+            // translate already set dirty flag
+            passive = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
+            interactive = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        }
+        
+        if (container == null) {
+            super.paintComponent(g);
+            return;
+        }
+        
+        if (dirty) {
+            paintPassive((Graphics2D) passive.getGraphics());
+            dirty = false;
+        }
+        paintInteractive((Graphics2D) interactive.getGraphics());
+        
+        final Graphics2D g2d = (Graphics2D) g;
+        
+        g2d.drawImage(passive, 0, 0, getWidth(), getHeight(), null);
+        g2d.drawImage(interactive, 0, 0, getWidth(), getHeight(), null);
+    }
+    
+    private void paintInteractive(Graphics2D g2d) {
+        g2d.setBackground(TRANSPARENT);
+        g2d.clearRect(0, 0, getWidth(), getHeight());
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+        
+        g2d.scale(scale, scale);
+        g2d.translate(translationX, translationY);
+        g2d.rotate(rotation);
+        
+        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.7f));
+        
+        for (Map.Entry<Integer, List<InteractiveElement>> e : interactives.entrySet()) {
+            for (InteractiveElement ie : e.getValue()) {
+                ie.paintBackground(g2d, state);
+            }
+            for (InteractiveElement ie : e.getValue()) {
+                ie.paint(g2d, state);
+            }
+        }
+    }
+    
+    private List<InteractiveElement> interactives() {
+        final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
+        
+        for (List<InteractiveElement> ies : interactives.descendingMap().values()) {
+            result.addAll(ies);
+        }
+        
+        return result;
+    }
+    
+    private void paintPassive(Graphics2D g2d) {
+        interactives.clear();
+        
+        g2d.setBackground(new Color(100, 160, 240));
+        g2d.clearRect(0, 0, getWidth(), getHeight());
+        
+        g2d.scale(scale, scale);
+        g2d.translate(translationX, translationY);
+        g2d.rotate(rotation);
+        
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        
+        g2d.setColor(Color.GRAY);
+        for (RoadGui r : container.getRoads()) {
+            addAllInteractives(r.paint(g2d));
+        }
+        
+        for (JunctionGui j : container.getJunctions()) {
+            addAllInteractives(j.paint(g2d));
+            dot(g2d, new Point2D.Double(j.x, j.y), container.getLaneWidth() / 5);
+        }
+    }
+    
+    private void addAllInteractives(List<InteractiveElement> ies) {
+        for (InteractiveElement ie : ies) {
+            final List<InteractiveElement> existing = interactives.get(ie.getZIndex());
+            
+            final List<InteractiveElement> list;
+            if (existing == null) {
+                list = new ArrayList<InteractiveElement>();
+                interactives.put(ie.getZIndex(), list);
+            } else {
+                list = existing;
+            }
+            
+            list.add(ie);
+        }
+    }
+    
+    static void dot(Graphics2D g2d, Point2D p, double r, Color c) {
+        final Color old = g2d.getColor();
+        
+        g2d.setColor(c);
+        g2d.fill(new Ellipse2D.Double(p.getX() - r, p.getY() - r, 2 * r, 2 * r));
+        
+        g2d.setColor(old);
+    }
+    
+    static void dot(Graphics2D g2d, Point2D p, double r) {
+        dot(g2d, p, r, Color.RED);
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/LaneGui.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/LaneGui.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/LaneGui.java	(revision 26154)
@@ -25,340 +25,340 @@
 
 final class LaneGui {
-	final class LengthSlider extends InteractiveElement {
-		private final Point2D center = new Point2D.Double();
-		private final Ellipse2D circle = new Ellipse2D.Double();
-		
-		private Point2D dragDelta;
-		
-		private LengthSlider() {}
-		
-		@Override
-		public void paint(Graphics2D g2d, State state) {
-			if (isVisible(state)) {
-				g2d.setColor(Color.BLUE);
-				g2d.fill(circle);
-				
-				final String len = METER_FORMAT.format(getModel().getLength());
-				final Rectangle2D bounds = circle.getBounds2D();
-				g2d.setFont(g2d.getFont().deriveFont(Font.BOLD, (float) bounds.getHeight()));
-				g2d.drawString(len, (float) bounds.getMaxX(), (float) bounds.getMaxY());
-			}
-		}
-		
-		private boolean isVisible(State state) {
-			if (state instanceof State.OutgoingActive) {
-				return LaneGui.this.equals(((State.OutgoingActive) state).getLane());
-			}
-			
-			return false;
-		}
-		
-		@Override
-		public boolean contains(Point2D p, State state) {
-			return isVisible(state) && circle.contains(p);
-		}
-		
-		@Override
-		public Type getType() {
-			return Type.INCOMING_CONNECTOR;
-		}
-		
-		@Override
-		boolean beginDrag(double x, double y) {
-			dragDelta = new Point2D.Double(center.getX() - x, center.getY() - y);
-			return true;
-		}
-		
-		@Override
-		State drag(double x, double y, InteractiveElement target, State old) {
-			move(x + dragDelta.getX(), y + dragDelta.getY());
-			return new State.Dirty(old);
-		}
-		
-		void move(double x, double y) {
-			final double r = getRoad().connectorRadius;
-			
-			final double offset = getRoad().getOffset(x, y);
-			final double newLength = getModel().getOutgoingRoadEnd().isFromEnd() ? offset : getRoad().getLength() - offset;
-			if (newLength > 0) {
-				getModel().setLength(newLength * getRoad().getContainer().getMpp());
-			}
-			
-			center.setLocation(x, y);
-			circle.setFrame(x - r, y - r, 2 * r, 2 * r);
-		}
-		
-		public void move(Point2D loc) {
-			final double x = loc.getX();
-			final double y = loc.getY();
-			final double r = getRoad().connectorRadius;
-			
-			center.setLocation(x, y);
-			circle.setFrame(x - r, y - r, 2 * r, 2 * r);
-		}
-		
-		@Override
-		int getZIndex() {
-			return 2;
-		}
-	}
-	
-	final class OutgoingConnector extends InteractiveElement {
-		private final Point2D center = new Point2D.Double();
-		private final Ellipse2D circle = new Ellipse2D.Double();
-		
-		private Point2D dragLocation;
-		private IncomingConnector dropTarget;
-		
-		private OutgoingConnector() {}
-		
-		@Override
-		public void paintBackground(Graphics2D g2d, State state) {
-			if (isActive(state)) {
-				final Composite old = g2d.getComposite();
-				g2d.setComposite(((AlphaComposite) old).derive(0.2f));
-				
-				g2d.setColor(new Color(255, 127, 31));
-				LaneGui.this.fill(g2d);
-				
-				g2d.setComposite(old);
-			}
-			
-			if (dragLocation != null) {
-				final State.Connecting s = (State.Connecting) state;
-				final Path2D path = new Path2D.Double();
-				path.moveTo(center.getX(), center.getY());
-				
-				final List<RoadGui.ViaConnector> vias = s.getViaConnectors();
-				for (int i = 0; i < vias.size() - 1; i += 2) {
-					final RoadGui.ViaConnector v = vias.get(i);
-					final PathIterator it = v.getRoad().getLaneMiddle(v.getRoadEnd().isFromEnd()).getIterator();
-					path.append(it, true);
-				}
-				if ((vias.size() & 1) != 0) {
-					final RoadGui.ViaConnector last = vias.get(vias.size() - 1);
-					path.lineTo(last.getCenter().getX(), last.getCenter().getY());
-				}
-				
-				if (dropTarget == null) {
-					g2d.setColor(GuiContainer.RED);
-					path.lineTo(dragLocation.getX(), dragLocation.getY());
-				} else {
-					g2d.setColor(GuiContainer.GREEN);
-					path.lineTo(dropTarget.getCenter().getX(), dropTarget.getCenter().getY());
-				}
-				
-				g2d.setStroke(getContainer().getConnectionStroke());
-				g2d.draw(path);
-			}
-		}
-		
-		@Override
-		public void paint(Graphics2D g2d, State state) {
-			if (isVisible(state)) {
-				final Composite old = g2d.getComposite();
-				if (isActive(state)) {
-					g2d.setComposite(((AlphaComposite) old).derive(1f));
-				}
-				
-				g2d.setColor(Color.WHITE);
-				g2d.fill(circle);
-				g2d.setComposite(old);
-			}
-		}
-		
-		private boolean isActive(State state) {
-			return state instanceof State.OutgoingActive && LaneGui.this.equals(((State.OutgoingActive) state).getLane());
-		}
-		
-		private boolean isVisible(State state) {
-			if (state instanceof State.Connecting) {
-				return ((State.Connecting) state).getLane().equals(getModel());
-			}
-			
-			return !getRoad().getModel().isPrimary() && getModel().getOutgoingJunction().isPrimary();
-		}
-		
-		@Override
-		public boolean contains(Point2D p, State state) {
-			return isVisible(state) && (circle.contains(p) || LaneGui.this.contains(p));
-		}
-		
-		@Override
-		public Type getType() {
-			return Type.OUTGOING_CONNECTOR;
-		}
-		
-		@Override
-		public State activate(State old) {
-			return new State.OutgoingActive(LaneGui.this);
-		}
-		
-		@Override
-		boolean beginDrag(double x, double y) {
-			return circle.contains(x, y);
-		}
-		
-		@Override
-		State.Connecting drag(double x, double y, InteractiveElement target, State old) {
-			dragLocation = new Point2D.Double(x, y);
-			dropTarget = null;
-			
-			if (!(old instanceof State.Connecting)) {
-				return new State.Connecting(getModel());
-			}
-			
-			final State.Connecting s = (State.Connecting) old;
-			if (target != null && target.getType() == Type.INCOMING_CONNECTOR) {
-				dropTarget = (IncomingConnector) target;
-				
-				return (s.getViaConnectors().size() & 1) == 0 ? s : s.pop();
-			} else if (target != null && target.getType() == Type.VIA_CONNECTOR) {
-				return s.next((RoadGui.ViaConnector) target);
-			}
-			
-			return s;
-		}
-		
-		@Override
-		State drop(double x, double y, InteractiveElement target, State old) {
-			final State.Connecting s = drag(x, y, target, old);
-			dragLocation = null;
-			if (dropTarget == null) {
-				return activate(old);
-			}
-			
-			final List<Road> via = new ArrayList<Road>();
-			assert (s.getViaConnectors().size() & 1) == 0;
-			for (int i = 0; i < s.getViaConnectors().size(); i += 2) {
-				final RoadGui.ViaConnector a = s.getViaConnectors().get(i);
-				final RoadGui.ViaConnector b = s.getViaConnectors().get(i + 1);
-				assert a.getRoadEnd().getOppositeEnd().equals(b);
-				via.add(a.getRoadEnd().getRoad());
-			}
-			
-			getModel().addTurn(via, dropTarget.getRoadEnd());
-			dropTarget = null;
-			return new State.Dirty(activate(old));
-		}
-		
-		public Point2D getCenter() {
-			return (Point2D) center.clone();
-		}
-		
-		void move(double x, double y) {
-			final double r = getRoad().connectorRadius;
-			
-			center.setLocation(x, y);
-			circle.setFrame(x - r, y - r, 2 * r, 2 * r);
-		}
-		
-		@Override
-		int getZIndex() {
-			return 1;
-		}
-	}
-	
-	static final NumberFormat METER_FORMAT = new DecimalFormat("0.0m");
-	
-	private final RoadGui road;
-	private final Lane lane;
-	
-	final Path2D area = new Path2D.Double();
-	
-	final OutgoingConnector outgoing = new OutgoingConnector();
-	final LengthSlider lengthSlider;
-	
-	private Shape clip;
-	
-	public LaneGui(RoadGui road, Lane lane) {
-		this.road = road;
-		this.lane = lane;
-		this.lengthSlider = lane.isExtra() ? new LengthSlider() : null;
-	}
-	
-	public double getLength() {
-		return getModel().isExtra() ? lane.getLength() / getRoad().getContainer().getMpp() : getRoad().getLength();
-	}
-	
-	public Lane getModel() {
-		return lane;
-	}
-	
-	public RoadGui getRoad() {
-		return road;
-	}
-	
-	public GuiContainer getContainer() {
-		return getRoad().getContainer();
-	}
-	
-	public Path recalculate(Path inner, Path2D innerLine) {
-		area.reset();
-		
-		final double W = getContainer().getLaneWidth();
-		final double L = getLength();
-		
-		final double WW = 3 / getContainer().getMpp();
-		
-		final LaneGui left = left();
-		final Lane leftModel = left == null ? null : left.getModel();
-		final double leftLength = leftModel == null
-		    || !leftModel.getOutgoingRoadEnd().equals(getModel().getOutgoingRoadEnd()) ? Double.NEGATIVE_INFINITY
-		    : leftModel.getKind() == Lane.Kind.EXTRA_LEFT ? left.getLength() : L;
-		
-		final Path outer;
-		if (getModel().getKind() == Lane.Kind.EXTRA_LEFT) {
-			final double AL = 30 / getContainer().getMpp();
-			final double SL = max(L, leftLength + AL);
-			
-			outer = inner.offset(W, SL, SL + AL, 0);
-			area(area, inner.subpath(0, L), outer.subpath(0, L + WW));
-			
-			lengthSlider.move(inner.getPoint(L));
-			
-			if (L > leftLength) {
-				innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
-				    || getModel().getOutgoingRoadEnd().isFromEnd());
-				final Point2D op = outer.getPoint(L + WW);
-				innerLine.lineTo(op.getX(), op.getY());
-			}
-		} else if (getModel().getKind() == Lane.Kind.EXTRA_RIGHT) {
-			outer = inner.offset(W, L, L + WW, 0);
-			area(area, inner.subpath(0, L + WW), outer.subpath(0, L));
-			
-			lengthSlider.move(outer.getPoint(L));
-		} else {
-			outer = inner.offset(W, -1, -1, W);
-			area(area, inner, outer);
-			
-			if (leftLength < L) {
-				innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
-				    || getModel().getOutgoingRoadEnd().isFromEnd());
-			}
-		}
-		
-		return outer;
-	}
-	
-	private LaneGui left() {
-		final List<LaneGui> lanes = getRoad().getLanes(getModel().getOutgoingRoadEnd());
-		final int i = lanes.indexOf(this);
-		return i > 0 ? lanes.get(i - 1) : null;
-	}
-	
-	public void fill(Graphics2D g2d) {
-		final Shape old = g2d.getClip();
-		g2d.clip(clip);
-		g2d.fill(area);
-		g2d.setClip(old);
-	}
-	
-	public void setClip(Shape clip) {
-		this.clip = clip;
-	}
-	
-	public boolean contains(Point2D p) {
-		return area.contains(p) && clip.contains(p);
-	}
+    final class LengthSlider extends InteractiveElement {
+        private final Point2D center = new Point2D.Double();
+        private final Ellipse2D circle = new Ellipse2D.Double();
+        
+        private Point2D dragDelta;
+        
+        private LengthSlider() {}
+        
+        @Override
+        public void paint(Graphics2D g2d, State state) {
+            if (isVisible(state)) {
+                g2d.setColor(Color.BLUE);
+                g2d.fill(circle);
+                
+                final String len = METER_FORMAT.format(getModel().getLength());
+                final Rectangle2D bounds = circle.getBounds2D();
+                g2d.setFont(g2d.getFont().deriveFont(Font.BOLD, (float) bounds.getHeight()));
+                g2d.drawString(len, (float) bounds.getMaxX(), (float) bounds.getMaxY());
+            }
+        }
+        
+        private boolean isVisible(State state) {
+            if (state instanceof State.OutgoingActive) {
+                return LaneGui.this.equals(((State.OutgoingActive) state).getLane());
+            }
+            
+            return false;
+        }
+        
+        @Override
+        public boolean contains(Point2D p, State state) {
+            return isVisible(state) && circle.contains(p);
+        }
+        
+        @Override
+        public Type getType() {
+            return Type.INCOMING_CONNECTOR;
+        }
+        
+        @Override
+        boolean beginDrag(double x, double y) {
+            dragDelta = new Point2D.Double(center.getX() - x, center.getY() - y);
+            return true;
+        }
+        
+        @Override
+        State drag(double x, double y, InteractiveElement target, State old) {
+            move(x + dragDelta.getX(), y + dragDelta.getY());
+            return new State.Dirty(old);
+        }
+        
+        void move(double x, double y) {
+            final double r = getRoad().connectorRadius;
+            
+            final double offset = getRoad().getOffset(x, y);
+            final double newLength = getModel().getOutgoingRoadEnd().isFromEnd() ? offset : getRoad().getLength() - offset;
+            if (newLength > 0) {
+                getModel().setLength(newLength * getRoad().getContainer().getMpp());
+            }
+            
+            center.setLocation(x, y);
+            circle.setFrame(x - r, y - r, 2 * r, 2 * r);
+        }
+        
+        public void move(Point2D loc) {
+            final double x = loc.getX();
+            final double y = loc.getY();
+            final double r = getRoad().connectorRadius;
+            
+            center.setLocation(x, y);
+            circle.setFrame(x - r, y - r, 2 * r, 2 * r);
+        }
+        
+        @Override
+        int getZIndex() {
+            return 2;
+        }
+    }
+    
+    final class OutgoingConnector extends InteractiveElement {
+        private final Point2D center = new Point2D.Double();
+        private final Ellipse2D circle = new Ellipse2D.Double();
+        
+        private Point2D dragLocation;
+        private IncomingConnector dropTarget;
+        
+        private OutgoingConnector() {}
+        
+        @Override
+        public void paintBackground(Graphics2D g2d, State state) {
+            if (isActive(state)) {
+                final Composite old = g2d.getComposite();
+                g2d.setComposite(((AlphaComposite) old).derive(0.2f));
+                
+                g2d.setColor(new Color(255, 127, 31));
+                LaneGui.this.fill(g2d);
+                
+                g2d.setComposite(old);
+            }
+            
+            if (dragLocation != null) {
+                final State.Connecting s = (State.Connecting) state;
+                final Path2D path = new Path2D.Double();
+                path.moveTo(center.getX(), center.getY());
+                
+                final List<RoadGui.ViaConnector> vias = s.getViaConnectors();
+                for (int i = 0; i < vias.size() - 1; i += 2) {
+                    final RoadGui.ViaConnector v = vias.get(i);
+                    final PathIterator it = v.getRoad().getLaneMiddle(v.getRoadEnd().isFromEnd()).getIterator();
+                    path.append(it, true);
+                }
+                if ((vias.size() & 1) != 0) {
+                    final RoadGui.ViaConnector last = vias.get(vias.size() - 1);
+                    path.lineTo(last.getCenter().getX(), last.getCenter().getY());
+                }
+                
+                if (dropTarget == null) {
+                    g2d.setColor(GuiContainer.RED);
+                    path.lineTo(dragLocation.getX(), dragLocation.getY());
+                } else {
+                    g2d.setColor(GuiContainer.GREEN);
+                    path.lineTo(dropTarget.getCenter().getX(), dropTarget.getCenter().getY());
+                }
+                
+                g2d.setStroke(getContainer().getConnectionStroke());
+                g2d.draw(path);
+            }
+        }
+        
+        @Override
+        public void paint(Graphics2D g2d, State state) {
+            if (isVisible(state)) {
+                final Composite old = g2d.getComposite();
+                if (isActive(state)) {
+                    g2d.setComposite(((AlphaComposite) old).derive(1f));
+                }
+                
+                g2d.setColor(Color.WHITE);
+                g2d.fill(circle);
+                g2d.setComposite(old);
+            }
+        }
+        
+        private boolean isActive(State state) {
+            return state instanceof State.OutgoingActive && LaneGui.this.equals(((State.OutgoingActive) state).getLane());
+        }
+        
+        private boolean isVisible(State state) {
+            if (state instanceof State.Connecting) {
+                return ((State.Connecting) state).getLane().equals(getModel());
+            }
+            
+            return !getRoad().getModel().isPrimary() && getModel().getOutgoingJunction().isPrimary();
+        }
+        
+        @Override
+        public boolean contains(Point2D p, State state) {
+            return isVisible(state) && (circle.contains(p) || LaneGui.this.contains(p));
+        }
+        
+        @Override
+        public Type getType() {
+            return Type.OUTGOING_CONNECTOR;
+        }
+        
+        @Override
+        public State activate(State old) {
+            return new State.OutgoingActive(LaneGui.this);
+        }
+        
+        @Override
+        boolean beginDrag(double x, double y) {
+            return circle.contains(x, y);
+        }
+        
+        @Override
+        State.Connecting drag(double x, double y, InteractiveElement target, State old) {
+            dragLocation = new Point2D.Double(x, y);
+            dropTarget = null;
+            
+            if (!(old instanceof State.Connecting)) {
+                return new State.Connecting(getModel());
+            }
+            
+            final State.Connecting s = (State.Connecting) old;
+            if (target != null && target.getType() == Type.INCOMING_CONNECTOR) {
+                dropTarget = (IncomingConnector) target;
+                
+                return (s.getViaConnectors().size() & 1) == 0 ? s : s.pop();
+            } else if (target != null && target.getType() == Type.VIA_CONNECTOR) {
+                return s.next((RoadGui.ViaConnector) target);
+            }
+            
+            return s;
+        }
+        
+        @Override
+        State drop(double x, double y, InteractiveElement target, State old) {
+            final State.Connecting s = drag(x, y, target, old);
+            dragLocation = null;
+            if (dropTarget == null) {
+                return activate(old);
+            }
+            
+            final List<Road> via = new ArrayList<Road>();
+            assert (s.getViaConnectors().size() & 1) == 0;
+            for (int i = 0; i < s.getViaConnectors().size(); i += 2) {
+                final RoadGui.ViaConnector a = s.getViaConnectors().get(i);
+                final RoadGui.ViaConnector b = s.getViaConnectors().get(i + 1);
+                assert a.getRoadEnd().getOppositeEnd().equals(b);
+                via.add(a.getRoadEnd().getRoad());
+            }
+            
+            getModel().addTurn(via, dropTarget.getRoadEnd());
+            dropTarget = null;
+            return new State.Dirty(activate(old));
+        }
+        
+        public Point2D getCenter() {
+            return (Point2D) center.clone();
+        }
+        
+        void move(double x, double y) {
+            final double r = getRoad().connectorRadius;
+            
+            center.setLocation(x, y);
+            circle.setFrame(x - r, y - r, 2 * r, 2 * r);
+        }
+        
+        @Override
+        int getZIndex() {
+            return 1;
+        }
+    }
+    
+    static final NumberFormat METER_FORMAT = new DecimalFormat("0.0m");
+    
+    private final RoadGui road;
+    private final Lane lane;
+    
+    final Path2D area = new Path2D.Double();
+    
+    final OutgoingConnector outgoing = new OutgoingConnector();
+    final LengthSlider lengthSlider;
+    
+    private Shape clip;
+    
+    public LaneGui(RoadGui road, Lane lane) {
+        this.road = road;
+        this.lane = lane;
+        this.lengthSlider = lane.isExtra() ? new LengthSlider() : null;
+    }
+    
+    public double getLength() {
+        return getModel().isExtra() ? lane.getLength() / getRoad().getContainer().getMpp() : getRoad().getLength();
+    }
+    
+    public Lane getModel() {
+        return lane;
+    }
+    
+    public RoadGui getRoad() {
+        return road;
+    }
+    
+    public GuiContainer getContainer() {
+        return getRoad().getContainer();
+    }
+    
+    public Path recalculate(Path inner, Path2D innerLine) {
+        area.reset();
+        
+        final double W = getContainer().getLaneWidth();
+        final double L = getLength();
+        
+        final double WW = 3 / getContainer().getMpp();
+        
+        final LaneGui left = left();
+        final Lane leftModel = left == null ? null : left.getModel();
+        final double leftLength = leftModel == null
+            || !leftModel.getOutgoingRoadEnd().equals(getModel().getOutgoingRoadEnd()) ? Double.NEGATIVE_INFINITY
+            : leftModel.getKind() == Lane.Kind.EXTRA_LEFT ? left.getLength() : L;
+        
+        final Path outer;
+        if (getModel().getKind() == Lane.Kind.EXTRA_LEFT) {
+            final double AL = 30 / getContainer().getMpp();
+            final double SL = max(L, leftLength + AL);
+            
+            outer = inner.offset(W, SL, SL + AL, 0);
+            area(area, inner.subpath(0, L), outer.subpath(0, L + WW));
+            
+            lengthSlider.move(inner.getPoint(L));
+            
+            if (L > leftLength) {
+                innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
+                    || getModel().getOutgoingRoadEnd().isFromEnd());
+                final Point2D op = outer.getPoint(L + WW);
+                innerLine.lineTo(op.getX(), op.getY());
+            }
+        } else if (getModel().getKind() == Lane.Kind.EXTRA_RIGHT) {
+            outer = inner.offset(W, L, L + WW, 0);
+            area(area, inner.subpath(0, L + WW), outer.subpath(0, L));
+            
+            lengthSlider.move(outer.getPoint(L));
+        } else {
+            outer = inner.offset(W, -1, -1, W);
+            area(area, inner, outer);
+            
+            if (leftLength < L) {
+                innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
+                    || getModel().getOutgoingRoadEnd().isFromEnd());
+            }
+        }
+        
+        return outer;
+    }
+    
+    private LaneGui left() {
+        final List<LaneGui> lanes = getRoad().getLanes(getModel().getOutgoingRoadEnd());
+        final int i = lanes.indexOf(this);
+        return i > 0 ? lanes.get(i - 1) : null;
+    }
+    
+    public void fill(Graphics2D g2d) {
+        final Shape old = g2d.getClip();
+        g2d.clip(clip);
+        g2d.fill(area);
+        g2d.setClip(old);
+    }
+    
+    public void setClip(Shape clip) {
+        this.clip = clip;
+    }
+    
+    public boolean contains(Point2D p) {
+        return area.contains(p) && clip.contains(p);
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/Path.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/Path.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/Path.java	(revision 26154)
@@ -26,552 +26,552 @@
  */
 abstract class Path {
-	private static final class SimplePathIterator implements PathIterator {
-		private final SimplePathIterator previous;
-		
-		private final int type;
-		private final double[] coords;
-		
-		private boolean done = false;
-		
-		public SimplePathIterator(SimplePathIterator previous, int type, double... coords) {
-			this.previous = previous;
-			this.type = type;
-			this.coords = coords;
-		}
-		
-		public SimplePathIterator(int type, double... coords) {
-			this(null, type, coords);
-		}
-		
-		@Override
-		public int getWindingRule() {
-			return WIND_NON_ZERO;
-		}
-		
-		@Override
-		public boolean isDone() {
-			return done;
-		}
-		
-		@Override
-		public void next() {
-			if (previous != null && !previous.isDone()) {
-				previous.next();
-			} else {
-				done = true;
-			}
-		}
-		
-		@Override
-		public int currentSegment(float[] coords) {
-			if (previous != null && !previous.isDone()) {
-				return previous.currentSegment(coords);
-			} else if (done) {
-				throw new NoSuchElementException("Iterator is already done.");
-			}
-			
-			for (int i = 0; i < 6; ++i) {
-				coords[i] = (float) this.coords[i];
-			}
-			
-			return type;
-		}
-		
-		@Override
-		public int currentSegment(double[] coords) {
-			if (previous != null && !previous.isDone()) {
-				return previous.currentSegment(coords);
-			} else if (done) {
-				throw new NoSuchElementException("Iterator is already done.");
-			}
-			
-			for (int i = 0; i < 6; ++i) {
-				coords[i] = this.coords[i];
-			}
-			
-			return type;
-		}
-		
-	}
-	
-	private static final class Line extends Path {
-		private final Path previous;
-		
-		private final double endX;
-		private final double endY;
-		
-		private final double angle;
-		
-		private final double length;
-		
-		public Line(Path previous, double x, double y, double length) {
-			this.previous = previous;
-			
-			this.endX = x;
-			this.endY = y;
-			
-			this.angle = angle(previous.getEnd(), getEnd());
-			
-			this.length = length;
-		}
-		
-		@Override
-		public Point2D getStart() {
-			return previous.getStart();
-		}
-		
-		@Override
-		public Point2D getEnd() {
-			return new Point2D.Double(endX, endY);
-		}
-		
-		@Override
-		public double getEndAngle() {
-			return angle;
-		}
-		
-		@Override
-		public double getLength() {
-			return previous.getLength() + length;
-		}
-		
-		@Override
-		public Path offset(double ws, double m1, double m2, double we) {
-			return offsetInternal(ws, m1, m2, we, angle);
-		}
-		
-		@Override
-		Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
-			final double PL = previous.getLength();
-			final double ML = PL + length;
-			
-			final Path prev = previous.offsetInternal(ws, m1, m2, we, angle);
-			
-			final double wStart = PL <= m1 ? ws : m2 <= PL ? we : ws + (PL - m1) * (we - ws) / (m2 - m1);
-			final Point2D from = prev.getEnd();
-			final Point2D to = offsetEnd(wStart, endAngle);
-			
-			if (abs(minAngleDiff(angle, angle(from, to))) > PI / 100) {
-				return previous.offsetInternal(ws, m1, m2, we, endAngle);
-			}
-			
-			if (ML <= m1) {
-				return simpleOffset(prev, ws, endAngle);
-			} else if (m2 <= PL) {
-				return simpleOffset(prev, we, endAngle);
-			}
-			
-			final double LL = from.distance(to);
-			
-			final Point2D m1o = PL <= m1 ? relativePoint(prev.getEnd(), LL * (m1 - PL) / length, angle) : null;
-			final Point2D m2t = m2 <= ML ? relativePoint(getEnd(), LL * (ML - m2) / length, angle + PI) : null;
-			final Point2D m2o = m2t == null ? null : relativePoint(m2t, we, (angle + endAngle - PI) / 2);
-			
-			if (m1o != null && m2o != null) {
-				final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL);
-				final Line l2 = new Line(l1, m2o.getX(), m2o.getY(), m2 - m1);
-				
-				final Point2D end = offsetEnd(we, endAngle);
-				
-				return new Line(l2, end.getX(), end.getY(), ML - m2);
-			} else if (m1o != null) {
-				final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL);
-				
-				final double w = ws + (ML - m1) * (we - ws) / (m2 - m1);
-				final Point2D end = offsetEnd(w, endAngle);
-				
-				return new Line(l1, end.getX(), end.getY(), ML - m1);
-			} else if (m2o != null) {
-				final Line l2 = new Line(prev, m2o.getX(), m2o.getY(), m2 - PL);
-				
-				final Point2D end = offsetEnd(we, endAngle);
-				
-				return new Line(l2, end.getX(), end.getY(), ML - m2);
-			} else {
-				final double w = ws + (PL - m1 + length) * (we - ws) / (m2 - m1);
-				final Point2D end = offsetEnd(w, endAngle);
-				return new Line(prev, end.getX(), end.getY(), length);
-			}
-		}
-		
-		private Path simpleOffset(Path prev, double w, double endAngle) {
-			final Point2D offset = offsetEnd(w, endAngle);
-			return new Line(prev, offset.getX(), offset.getY(), length);
-		}
-		
-		private Point2D offsetEnd(double w, double endAngle) {
-			final double da2 = minAngleDiff(angle, endAngle) / 2;
-			final double hypotenuse = w / cos(da2);
-			
-			return relativePoint(getEnd(), hypotenuse, angle + PI / 2 + da2);
-		}
-		
-		@Override
-		public SimplePathIterator getIterator() {
-			return new SimplePathIterator(previous.getIteratorInternal(angle), PathIterator.SEG_LINETO, endX, endY, 0, 0, 0,
-			    0);
-		}
-		
-		@Override
-		public Path subpath(double from, double to) {
-			final double PL = previous.getLength();
-			final double ML = PL + length;
-			
-			if (to < PL) {
-				return previous.subpath(from, to);
-			}
-			
-			final Point2D end = to < ML ? getPoint(to) : new Point2D.Double(endX, endY);
-			
-			final double EL = min(ML, to);
-			if (PL <= from) {
-				final Point2D start = getPoint(from);
-				return new Line(new Start(start.getX(), start.getY(), angle), end.getX(), end.getY(), EL - from);
-			} else {
-				return new Line(previous.subpath(from, to), end.getX(), end.getY(), EL - PL);
-			}
-		}
-		
-		@Override
-		public Point2D getPoint(double offset) {
-			final double PL = previous.getLength();
-			final double ML = PL + length;
-			
-			if (offset <= ML && offset >= PL) {
-				final double LL = previous.getEnd().distance(getEnd());
-				return relativePoint(getEnd(), LL * (ML - offset) / length, angle + PI);
-			} else {
-				return previous.getPoint(offset);
-			}
-		}
-		
-		@Override
-		SimplePathIterator getIteratorInternal(double endAngle) {
-			return getIterator();
-		}
-	}
-	
-	// TODO curves are still somewhat broken
-	private static class Curve extends Path {
-		private final Path previous;
-		
-		private final double height;
-		
-		private final double centerX;
-		private final double centerY;
-		private final double centerToFromAngle;
-		
-		private final double endX;
-		private final double endY;
-		
-		private final double fromAngle;
-		private final double fromRadius;
-		private final double toRadius;
-		private final double angle;
-		
-		private final double length;
-		
-		private Curve(Path previous, double r1, double r2, double a, double length, double fromAngle) {
-			this.previous = previous;
-			this.fromAngle = fromAngle;
-			this.fromRadius = r1;
-			this.toRadius = r2;
-			this.angle = a;
-			this.length = length;
-			
-			final Point2D from = previous.getEnd();
-			this.centerToFromAngle = fromAngle - signum(a) * PI / 2;
-			final Point2D center = relativePoint(from, r1, centerToFromAngle + PI);
-			
-			final double toAngle = centerToFromAngle + a;
-			this.endX = center.getX() + r2 * cos(toAngle);
-			this.endY = center.getY() - r2 * sin(toAngle);
-			
-			this.centerX = center.getX();
-			this.centerY = center.getY();
-			
-			final double y = new Line2D.Double(center, from).ptLineDist(endX, endY);
-			this.height = y / sin(angle);
-		}
-		
-		public Curve(Path previous, double r1, double r2, double a, double length) {
-			this(previous, r1, r2, a, length, previous.getEndAngle());
-		}
-		
-		public Point2D getStart() {
-			return previous.getStart();
-		}
-		
-		@Override
-		public Point2D getEnd() {
-			return new Point2D.Double(endX, endY);
-		}
-		
-		@Override
-		public double getEndAngle() {
-			return fromAngle + angle;
-		}
-		
-		@Override
-		public double getLength() {
-			return previous.getLength() + length;
-		}
-		
-		@Override
-		public Path offset(double ws, double m1, double m2, double we) {
-			return offsetInternal(ws, m1, m2, we, previous.getEndAngle() + angle);
-		}
-		
-		@Override
-		Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
-			final double PL = previous.getLength();
-			final double ML = PL + length;
-			
-			final Path prev = previous.offsetInternal(ws, m1, m2, we, fromAngle);
-			
-			if (ML <= m1) {
-				return simpleOffset(prev, ws);
-			} else if (m2 <= PL) {
-				return simpleOffset(prev, we);
-			}
-			
-			final double s = signum(angle);
-			
-			if (PL < m1 && m2 < ML) {
-				final double l1 = m1 - PL;
-				final double a1 = angle(l1);
-				final double r1 = radius(a1) - s * ws;
-				
-				final Curve c1 = new Curve(prev, fromRadius - ws, r1, offsetAngle(prev, a1), l1, fromAngle);
-				
-				final double l2 = m2 - m1;
-				final double a2 = angle(l2);
-				final double r2 = radius(a2) - s * we;
-				
-				final Curve c2 = new Curve(c1, r1, r2, a2 - a1, l2);
-				
-				return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2);
-			} else if (PL < m1) {
-				final double l1 = m1 - PL;
-				final double a1 = angle(l1);
-				final double r1 = radius(a1) - s * ws;
-				
-				final Curve c1 = new Curve(prev, fromRadius - s * ws, r1, offsetAngle(prev, a1), l1, fromAngle);
-				
-				final double w = ws + (ML - m1) * (we - ws) / (m2 - m1);
-				
-				return new Curve(c1, r1, toRadius - s * w, angle - a1, ML - m1);
-			} else if (m2 < ML) {
-				final double w = ws + (PL - m1) * (we - ws) / (m2 - m1);
-				
-				final double l2 = m2 - PL;
-				final double a2 = angle(l2);
-				final double r2 = radius(a2) - s * we;
-				
-				final Curve c2 = new Curve(prev, fromRadius - s * w, r2, offsetAngle(prev, a2), l2, fromAngle);
-				
-				return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2);
-			} else {
-				final double w1 = ws + (PL - m1) * (we - ws) / (m2 - m1);
-				final double w2 = we - (m2 - ML) * (we - ws) / (m2 - m1);
-				
-				return new Curve(prev, fromRadius - s * w1, toRadius - s * w2, offsetAngle(prev, angle), length, fromAngle);
-			}
-		}
-		
-		private double angle(double l) {
-			return l * angle / length;
-		}
-		
-		private double radius(double a) {
-			return hypot(fromRadius * cos(a), height * sin(a));
-		}
-		
-		private double offsetAngle(Path prev, double a) {
-			return a;// + GuiUtil.normalize(previous.getEndAngle()
-			// - prev.getEndAngle());
-		}
-		
-		private Path simpleOffset(Path prev, double w) {
-			final double s = signum(angle);
-			return new Curve(prev, fromRadius - s * w, toRadius - s * w, offsetAngle(prev, angle), length, fromAngle);
-		}
-		
-		@Override
-		public SimplePathIterator getIterator() {
-			return getIteratorInternal(previous.getEndAngle() + angle);
-		}
-		
-		@Override
-		public Path subpath(double from, double to) {
-			final double PL = previous.getLength();
-			final double ML = PL + length;
-			
-			if (to < PL) {
-				return previous.subpath(from, to);
-			}
-			
-			final double toA = to < ML ? angle(to - PL) : angle;
-			final double toR = to < ML ? radius(toA) : toRadius;
-			
-			final double fromA = from > PL ? angle(from - PL) : 0;
-			final double fromR = from > PL ? radius(fromA) : fromRadius;
-			
-			final double a = toA - fromA;
-			final double l = min(ML, to) - max(PL, from);
-			
-			if (from >= PL) {
-				final Point2D start = getPoint(from);
-				final double fa = fromAngle + fromA;
-				
-				return new Curve(new Start(start.getX(), start.getY(), fa), fromR, toR, a, l, fa);
-			} else {
-				return new Curve(previous.subpath(from, to), fromR, toR, a, l, fromAngle);
-			}
-		}
-		
-		@Override
-		public Point2D getPoint(double offset) {
-			final double PL = previous.getLength();
-			final double ML = PL + length;
-			
-			if (offset <= ML && offset >= PL) {
-				final double a = abs(angle(offset - PL));
-				final double w = fromRadius * cos(a);
-				final double h = -height * sin(a);
-				
-				final double r = centerToFromAngle; // rotation angle
-				final double x = w * cos(r) + h * sin(r);
-				final double y = -w * sin(r) + h * cos(r);
-				
-				return new Point2D.Double(centerX + x, centerY + y);
-			} else {
-				return previous.getPoint(offset);
-			}
-		}
-		
-		@Override
-		SimplePathIterator getIteratorInternal(double endAngle) {
-			final Point2D cp1 = relativePoint(previous.getEnd(), cpf(angle, fromRadius), previous.getEndAngle());
-			final Point2D cp2 = relativePoint(getEnd(), cpf(angle, toRadius), endAngle + PI);
-			
-			return new SimplePathIterator(previous.getIteratorInternal(getEndAngle()), PathIterator.SEG_CUBICTO, //
-			    cp1.getX(), cp1.getY(), cp2.getX(), cp2.getY(), endX, endY //
-			);
-			
-		}
-	}
-	
-	private static class Start extends Path {
-		private final double x;
-		private final double y;
-		
-		private final double endAngle;
-		
-		public Start(double x, double y, double endAngle) {
-			this.x = x;
-			this.y = y;
-			this.endAngle = endAngle;
-		}
-		
-		public Start(double x, double y) {
-			this(x, y, Double.NaN);
-		}
-		
-		public Point2D getStart() {
-			return new Point2D.Double(x, y);
-		}
-		
-		public Point2D getEnd() {
-			return new Point2D.Double(x, y);
-		}
-		
-		@Override
-		public double getEndAngle() {
-			if (Double.isNaN(endAngle)) {
-				throw new UnsupportedOperationException();
-			}
-			
-			return endAngle;
-		}
-		
-		@Override
-		public double getLength() {
-			return 0;
-		}
-		
-		@Override
-		public Path offset(double ws, double m1, double m2, double we) {
-			throw new UnsupportedOperationException();
-		}
-		
-		@Override
-		Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
-			final Point2D offset = relativePoint(getStart(), ws, endAngle + PI / 2);
-			return new Start(offset.getX(), offset.getY(), endAngle);
-		}
-		
-		@Override
-		public SimplePathIterator getIterator() {
-			return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0);
-		}
-		
-		@Override
-		public Path subpath(double from, double to) {
-			if (from > to) {
-				throw new IllegalArgumentException("from > to");
-			}
-			if (from < 0) {
-				throw new IllegalArgumentException("from < 0");
-			}
-			
-			return this;
-		}
-		
-		@Override
-		public Point2D getPoint(double offset) {
-			if (offset == 0) {
-				return getEnd();
-			} else {
-				throw new IllegalArgumentException(Double.toString(offset));
-			}
-		}
-		
-		@Override
-		SimplePathIterator getIteratorInternal(double endAngle) {
-			return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0);
-		}
-	}
-	
-	public static Path create(double x, double y) {
-		return new Start(x, y);
-	}
-	
-	public Path lineTo(double x, double y, double length) {
-		return new Line(this, x, y, length);
-	}
-	
-	public Path curveTo(double r1, double r2, double a, double length) {
-		return new Curve(this, r1, r2, a, length);
-	}
-	
-	public abstract Path offset(double ws, double m1, double m2, double we);
-	
-	abstract Path offsetInternal(double ws, double m1, double m2, double we, double endAngle);
-	
-	public abstract double getLength();
-	
-	public abstract double getEndAngle();
-	
-	public abstract Point2D getStart();
-	
-	public abstract Point2D getEnd();
-	
-	public abstract SimplePathIterator getIterator();
-	
-	abstract SimplePathIterator getIteratorInternal(double endAngle);
-	
-	public abstract Path subpath(double from, double to);
-	
-	public abstract Point2D getPoint(double offset);
+    private static final class SimplePathIterator implements PathIterator {
+        private final SimplePathIterator previous;
+        
+        private final int type;
+        private final double[] coords;
+        
+        private boolean done = false;
+        
+        public SimplePathIterator(SimplePathIterator previous, int type, double... coords) {
+            this.previous = previous;
+            this.type = type;
+            this.coords = coords;
+        }
+        
+        public SimplePathIterator(int type, double... coords) {
+            this(null, type, coords);
+        }
+        
+        @Override
+        public int getWindingRule() {
+            return WIND_NON_ZERO;
+        }
+        
+        @Override
+        public boolean isDone() {
+            return done;
+        }
+        
+        @Override
+        public void next() {
+            if (previous != null && !previous.isDone()) {
+                previous.next();
+            } else {
+                done = true;
+            }
+        }
+        
+        @Override
+        public int currentSegment(float[] coords) {
+            if (previous != null && !previous.isDone()) {
+                return previous.currentSegment(coords);
+            } else if (done) {
+                throw new NoSuchElementException("Iterator is already done.");
+            }
+            
+            for (int i = 0; i < 6; ++i) {
+                coords[i] = (float) this.coords[i];
+            }
+            
+            return type;
+        }
+        
+        @Override
+        public int currentSegment(double[] coords) {
+            if (previous != null && !previous.isDone()) {
+                return previous.currentSegment(coords);
+            } else if (done) {
+                throw new NoSuchElementException("Iterator is already done.");
+            }
+            
+            for (int i = 0; i < 6; ++i) {
+                coords[i] = this.coords[i];
+            }
+            
+            return type;
+        }
+        
+    }
+    
+    private static final class Line extends Path {
+        private final Path previous;
+        
+        private final double endX;
+        private final double endY;
+        
+        private final double angle;
+        
+        private final double length;
+        
+        public Line(Path previous, double x, double y, double length) {
+            this.previous = previous;
+            
+            this.endX = x;
+            this.endY = y;
+            
+            this.angle = angle(previous.getEnd(), getEnd());
+            
+            this.length = length;
+        }
+        
+        @Override
+        public Point2D getStart() {
+            return previous.getStart();
+        }
+        
+        @Override
+        public Point2D getEnd() {
+            return new Point2D.Double(endX, endY);
+        }
+        
+        @Override
+        public double getEndAngle() {
+            return angle;
+        }
+        
+        @Override
+        public double getLength() {
+            return previous.getLength() + length;
+        }
+        
+        @Override
+        public Path offset(double ws, double m1, double m2, double we) {
+            return offsetInternal(ws, m1, m2, we, angle);
+        }
+        
+        @Override
+        Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
+            final double PL = previous.getLength();
+            final double ML = PL + length;
+            
+            final Path prev = previous.offsetInternal(ws, m1, m2, we, angle);
+            
+            final double wStart = PL <= m1 ? ws : m2 <= PL ? we : ws + (PL - m1) * (we - ws) / (m2 - m1);
+            final Point2D from = prev.getEnd();
+            final Point2D to = offsetEnd(wStart, endAngle);
+            
+            if (abs(minAngleDiff(angle, angle(from, to))) > PI / 100) {
+                return previous.offsetInternal(ws, m1, m2, we, endAngle);
+            }
+            
+            if (ML <= m1) {
+                return simpleOffset(prev, ws, endAngle);
+            } else if (m2 <= PL) {
+                return simpleOffset(prev, we, endAngle);
+            }
+            
+            final double LL = from.distance(to);
+            
+            final Point2D m1o = PL <= m1 ? relativePoint(prev.getEnd(), LL * (m1 - PL) / length, angle) : null;
+            final Point2D m2t = m2 <= ML ? relativePoint(getEnd(), LL * (ML - m2) / length, angle + PI) : null;
+            final Point2D m2o = m2t == null ? null : relativePoint(m2t, we, (angle + endAngle - PI) / 2);
+            
+            if (m1o != null && m2o != null) {
+                final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL);
+                final Line l2 = new Line(l1, m2o.getX(), m2o.getY(), m2 - m1);
+                
+                final Point2D end = offsetEnd(we, endAngle);
+                
+                return new Line(l2, end.getX(), end.getY(), ML - m2);
+            } else if (m1o != null) {
+                final Line l1 = new Line(prev, m1o.getX(), m1o.getY(), m1 - PL);
+                
+                final double w = ws + (ML - m1) * (we - ws) / (m2 - m1);
+                final Point2D end = offsetEnd(w, endAngle);
+                
+                return new Line(l1, end.getX(), end.getY(), ML - m1);
+            } else if (m2o != null) {
+                final Line l2 = new Line(prev, m2o.getX(), m2o.getY(), m2 - PL);
+                
+                final Point2D end = offsetEnd(we, endAngle);
+                
+                return new Line(l2, end.getX(), end.getY(), ML - m2);
+            } else {
+                final double w = ws + (PL - m1 + length) * (we - ws) / (m2 - m1);
+                final Point2D end = offsetEnd(w, endAngle);
+                return new Line(prev, end.getX(), end.getY(), length);
+            }
+        }
+        
+        private Path simpleOffset(Path prev, double w, double endAngle) {
+            final Point2D offset = offsetEnd(w, endAngle);
+            return new Line(prev, offset.getX(), offset.getY(), length);
+        }
+        
+        private Point2D offsetEnd(double w, double endAngle) {
+            final double da2 = minAngleDiff(angle, endAngle) / 2;
+            final double hypotenuse = w / cos(da2);
+            
+            return relativePoint(getEnd(), hypotenuse, angle + PI / 2 + da2);
+        }
+        
+        @Override
+        public SimplePathIterator getIterator() {
+            return new SimplePathIterator(previous.getIteratorInternal(angle), PathIterator.SEG_LINETO, endX, endY, 0, 0, 0,
+                0);
+        }
+        
+        @Override
+        public Path subpath(double from, double to) {
+            final double PL = previous.getLength();
+            final double ML = PL + length;
+            
+            if (to < PL) {
+                return previous.subpath(from, to);
+            }
+            
+            final Point2D end = to < ML ? getPoint(to) : new Point2D.Double(endX, endY);
+            
+            final double EL = min(ML, to);
+            if (PL <= from) {
+                final Point2D start = getPoint(from);
+                return new Line(new Start(start.getX(), start.getY(), angle), end.getX(), end.getY(), EL - from);
+            } else {
+                return new Line(previous.subpath(from, to), end.getX(), end.getY(), EL - PL);
+            }
+        }
+        
+        @Override
+        public Point2D getPoint(double offset) {
+            final double PL = previous.getLength();
+            final double ML = PL + length;
+            
+            if (offset <= ML && offset >= PL) {
+                final double LL = previous.getEnd().distance(getEnd());
+                return relativePoint(getEnd(), LL * (ML - offset) / length, angle + PI);
+            } else {
+                return previous.getPoint(offset);
+            }
+        }
+        
+        @Override
+        SimplePathIterator getIteratorInternal(double endAngle) {
+            return getIterator();
+        }
+    }
+    
+    // TODO curves are still somewhat broken
+    private static class Curve extends Path {
+        private final Path previous;
+        
+        private final double height;
+        
+        private final double centerX;
+        private final double centerY;
+        private final double centerToFromAngle;
+        
+        private final double endX;
+        private final double endY;
+        
+        private final double fromAngle;
+        private final double fromRadius;
+        private final double toRadius;
+        private final double angle;
+        
+        private final double length;
+        
+        private Curve(Path previous, double r1, double r2, double a, double length, double fromAngle) {
+            this.previous = previous;
+            this.fromAngle = fromAngle;
+            this.fromRadius = r1;
+            this.toRadius = r2;
+            this.angle = a;
+            this.length = length;
+            
+            final Point2D from = previous.getEnd();
+            this.centerToFromAngle = fromAngle - signum(a) * PI / 2;
+            final Point2D center = relativePoint(from, r1, centerToFromAngle + PI);
+            
+            final double toAngle = centerToFromAngle + a;
+            this.endX = center.getX() + r2 * cos(toAngle);
+            this.endY = center.getY() - r2 * sin(toAngle);
+            
+            this.centerX = center.getX();
+            this.centerY = center.getY();
+            
+            final double y = new Line2D.Double(center, from).ptLineDist(endX, endY);
+            this.height = y / sin(angle);
+        }
+        
+        public Curve(Path previous, double r1, double r2, double a, double length) {
+            this(previous, r1, r2, a, length, previous.getEndAngle());
+        }
+        
+        public Point2D getStart() {
+            return previous.getStart();
+        }
+        
+        @Override
+        public Point2D getEnd() {
+            return new Point2D.Double(endX, endY);
+        }
+        
+        @Override
+        public double getEndAngle() {
+            return fromAngle + angle;
+        }
+        
+        @Override
+        public double getLength() {
+            return previous.getLength() + length;
+        }
+        
+        @Override
+        public Path offset(double ws, double m1, double m2, double we) {
+            return offsetInternal(ws, m1, m2, we, previous.getEndAngle() + angle);
+        }
+        
+        @Override
+        Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
+            final double PL = previous.getLength();
+            final double ML = PL + length;
+            
+            final Path prev = previous.offsetInternal(ws, m1, m2, we, fromAngle);
+            
+            if (ML <= m1) {
+                return simpleOffset(prev, ws);
+            } else if (m2 <= PL) {
+                return simpleOffset(prev, we);
+            }
+            
+            final double s = signum(angle);
+            
+            if (PL < m1 && m2 < ML) {
+                final double l1 = m1 - PL;
+                final double a1 = angle(l1);
+                final double r1 = radius(a1) - s * ws;
+                
+                final Curve c1 = new Curve(prev, fromRadius - ws, r1, offsetAngle(prev, a1), l1, fromAngle);
+                
+                final double l2 = m2 - m1;
+                final double a2 = angle(l2);
+                final double r2 = radius(a2) - s * we;
+                
+                final Curve c2 = new Curve(c1, r1, r2, a2 - a1, l2);
+                
+                return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2);
+            } else if (PL < m1) {
+                final double l1 = m1 - PL;
+                final double a1 = angle(l1);
+                final double r1 = radius(a1) - s * ws;
+                
+                final Curve c1 = new Curve(prev, fromRadius - s * ws, r1, offsetAngle(prev, a1), l1, fromAngle);
+                
+                final double w = ws + (ML - m1) * (we - ws) / (m2 - m1);
+                
+                return new Curve(c1, r1, toRadius - s * w, angle - a1, ML - m1);
+            } else if (m2 < ML) {
+                final double w = ws + (PL - m1) * (we - ws) / (m2 - m1);
+                
+                final double l2 = m2 - PL;
+                final double a2 = angle(l2);
+                final double r2 = radius(a2) - s * we;
+                
+                final Curve c2 = new Curve(prev, fromRadius - s * w, r2, offsetAngle(prev, a2), l2, fromAngle);
+                
+                return new Curve(c2, r2, toRadius - s * we, angle - a2, ML - m2);
+            } else {
+                final double w1 = ws + (PL - m1) * (we - ws) / (m2 - m1);
+                final double w2 = we - (m2 - ML) * (we - ws) / (m2 - m1);
+                
+                return new Curve(prev, fromRadius - s * w1, toRadius - s * w2, offsetAngle(prev, angle), length, fromAngle);
+            }
+        }
+        
+        private double angle(double l) {
+            return l * angle / length;
+        }
+        
+        private double radius(double a) {
+            return hypot(fromRadius * cos(a), height * sin(a));
+        }
+        
+        private double offsetAngle(Path prev, double a) {
+            return a;// + GuiUtil.normalize(previous.getEndAngle()
+            // - prev.getEndAngle());
+        }
+        
+        private Path simpleOffset(Path prev, double w) {
+            final double s = signum(angle);
+            return new Curve(prev, fromRadius - s * w, toRadius - s * w, offsetAngle(prev, angle), length, fromAngle);
+        }
+        
+        @Override
+        public SimplePathIterator getIterator() {
+            return getIteratorInternal(previous.getEndAngle() + angle);
+        }
+        
+        @Override
+        public Path subpath(double from, double to) {
+            final double PL = previous.getLength();
+            final double ML = PL + length;
+            
+            if (to < PL) {
+                return previous.subpath(from, to);
+            }
+            
+            final double toA = to < ML ? angle(to - PL) : angle;
+            final double toR = to < ML ? radius(toA) : toRadius;
+            
+            final double fromA = from > PL ? angle(from - PL) : 0;
+            final double fromR = from > PL ? radius(fromA) : fromRadius;
+            
+            final double a = toA - fromA;
+            final double l = min(ML, to) - max(PL, from);
+            
+            if (from >= PL) {
+                final Point2D start = getPoint(from);
+                final double fa = fromAngle + fromA;
+                
+                return new Curve(new Start(start.getX(), start.getY(), fa), fromR, toR, a, l, fa);
+            } else {
+                return new Curve(previous.subpath(from, to), fromR, toR, a, l, fromAngle);
+            }
+        }
+        
+        @Override
+        public Point2D getPoint(double offset) {
+            final double PL = previous.getLength();
+            final double ML = PL + length;
+            
+            if (offset <= ML && offset >= PL) {
+                final double a = abs(angle(offset - PL));
+                final double w = fromRadius * cos(a);
+                final double h = -height * sin(a);
+                
+                final double r = centerToFromAngle; // rotation angle
+                final double x = w * cos(r) + h * sin(r);
+                final double y = -w * sin(r) + h * cos(r);
+                
+                return new Point2D.Double(centerX + x, centerY + y);
+            } else {
+                return previous.getPoint(offset);
+            }
+        }
+        
+        @Override
+        SimplePathIterator getIteratorInternal(double endAngle) {
+            final Point2D cp1 = relativePoint(previous.getEnd(), cpf(angle, fromRadius), previous.getEndAngle());
+            final Point2D cp2 = relativePoint(getEnd(), cpf(angle, toRadius), endAngle + PI);
+            
+            return new SimplePathIterator(previous.getIteratorInternal(getEndAngle()), PathIterator.SEG_CUBICTO, //
+                cp1.getX(), cp1.getY(), cp2.getX(), cp2.getY(), endX, endY //
+            );
+            
+        }
+    }
+    
+    private static class Start extends Path {
+        private final double x;
+        private final double y;
+        
+        private final double endAngle;
+        
+        public Start(double x, double y, double endAngle) {
+            this.x = x;
+            this.y = y;
+            this.endAngle = endAngle;
+        }
+        
+        public Start(double x, double y) {
+            this(x, y, Double.NaN);
+        }
+        
+        public Point2D getStart() {
+            return new Point2D.Double(x, y);
+        }
+        
+        public Point2D getEnd() {
+            return new Point2D.Double(x, y);
+        }
+        
+        @Override
+        public double getEndAngle() {
+            if (Double.isNaN(endAngle)) {
+                throw new UnsupportedOperationException();
+            }
+            
+            return endAngle;
+        }
+        
+        @Override
+        public double getLength() {
+            return 0;
+        }
+        
+        @Override
+        public Path offset(double ws, double m1, double m2, double we) {
+            throw new UnsupportedOperationException();
+        }
+        
+        @Override
+        Path offsetInternal(double ws, double m1, double m2, double we, double endAngle) {
+            final Point2D offset = relativePoint(getStart(), ws, endAngle + PI / 2);
+            return new Start(offset.getX(), offset.getY(), endAngle);
+        }
+        
+        @Override
+        public SimplePathIterator getIterator() {
+            return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0);
+        }
+        
+        @Override
+        public Path subpath(double from, double to) {
+            if (from > to) {
+                throw new IllegalArgumentException("from > to");
+            }
+            if (from < 0) {
+                throw new IllegalArgumentException("from < 0");
+            }
+            
+            return this;
+        }
+        
+        @Override
+        public Point2D getPoint(double offset) {
+            if (offset == 0) {
+                return getEnd();
+            } else {
+                throw new IllegalArgumentException(Double.toString(offset));
+            }
+        }
+        
+        @Override
+        SimplePathIterator getIteratorInternal(double endAngle) {
+            return new SimplePathIterator(PathIterator.SEG_MOVETO, x, y, 0, 0, 0, 0);
+        }
+    }
+    
+    public static Path create(double x, double y) {
+        return new Start(x, y);
+    }
+    
+    public Path lineTo(double x, double y, double length) {
+        return new Line(this, x, y, length);
+    }
+    
+    public Path curveTo(double r1, double r2, double a, double length) {
+        return new Curve(this, r1, r2, a, length);
+    }
+    
+    public abstract Path offset(double ws, double m1, double m2, double we);
+    
+    abstract Path offsetInternal(double ws, double m1, double m2, double we, double endAngle);
+    
+    public abstract double getLength();
+    
+    public abstract double getEndAngle();
+    
+    public abstract Point2D getStart();
+    
+    public abstract Point2D getEnd();
+    
+    public abstract SimplePathIterator getIterator();
+    
+    abstract SimplePathIterator getIteratorInternal(double endAngle);
+    
+    public abstract Path subpath(double from, double to);
+    
+    public abstract Point2D getPoint(double offset);
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ReversePathIterator.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ReversePathIterator.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ReversePathIterator.java	(revision 26154)
@@ -16,135 +16,135 @@
  */
 class ReversePathIterator implements PathIterator {
-	private static final int[] COUNTS = {
-	    2, // SEG_MOVETO = 0
-	    2, // SEG_LINETO = 1
-	    4, // SEG_QUADTO = 2
-	    6, // SEG_CUBICTO = 3
-	    0, // SEG_CLOSE = 4
-	};
-	
-	public static ReversePathIterator reverse(PathIterator it) {
-		return new ReversePathIterator(it);
-	}
-	
-	private static int[] reverseTypes(int[] types, int length) {
-		if (length > 0 && types[0] != SEG_MOVETO) {
-			// the last segment of the reversed path is not defined
-			throw new IllegalArgumentException("Can not reverse path without initial SEG_MOVETO.");
-		}
-		
-		final int[] result = new int[length];
-		
-		result[0] = SEG_MOVETO;
-		
-		int lower = 1;
-		int upper = length - 1;
-		
-		while (lower <= upper) {
-			result[lower] = types[upper];
-			result[upper] = types[lower];
-			
-			++lower;
-			--upper;
-		}
-		
-		return result;
-	}
-	
-	private static double[] reverseCoords(double[] coords, int length) {
-		final double[] result = new double[length];
-		
-		int lower = 0;
-		int upper = length - 2;
-		
-		while (lower <= upper) {
-			result[lower] = coords[upper];
-			result[lower + 1] = coords[upper + 1];
-			result[upper] = coords[lower];
-			result[upper + 1] = coords[lower + 1];
-			
-			lower += 2;
-			upper -= 2;
-		}
-		
-		return result;
-	}
-	
-	private final int winding;
-	
-	private final int[] types;
-	private int typesIndex = 0;
-	
-	private final double[] coords;
-	private int coordsIndex = 0;
-	
-	private ReversePathIterator(PathIterator it) {
-		this.winding = it.getWindingRule();
-		
-		double[] tmpCoords = new double[62];
-		int[] tmpTypes = new int[11];
-		
-		int tmpCoordsI = 0;
-		int tmpTypesI = 0;
-		
-		while (!it.isDone()) {
-			if (tmpTypesI >= tmpTypes.length) {
-				tmpTypes = Arrays.copyOf(tmpTypes, 2 * tmpTypes.length);
-			}
-			
-			final double[] cs = new double[6];
-			final int t = it.currentSegment(cs);
-			tmpTypes[tmpTypesI++] = t;
-			final int count = COUNTS[t];
-			
-			if (tmpCoordsI + count > tmpCoords.length) {
-				tmpCoords = Arrays.copyOf(tmpCoords, 2 * tmpCoords.length);
-			}
-			System.arraycopy(cs, 0, tmpCoords, tmpCoordsI, count);
-			tmpCoordsI += count;
-			
-			it.next();
-		}
-		
-		this.types = reverseTypes(tmpTypes, tmpTypesI);
-		this.coords = reverseCoords(tmpCoords, tmpCoordsI);
-	}
-	
-	@Override
-	public int getWindingRule() {
-		return winding;
-	}
-	
-	@Override
-	public boolean isDone() {
-		return typesIndex >= types.length;
-	}
-	
-	@Override
-	public void next() {
-		coordsIndex += COUNTS[types[typesIndex]];
-		++typesIndex;
-	}
-	
-	@Override
-	public int currentSegment(float[] coords) {
-		final double[] tmp = new double[6];
-		final int type = currentSegment(tmp);
-		
-		coords[0] = (float) tmp[0];
-		coords[1] = (float) tmp[1];
-		coords[2] = (float) tmp[2];
-		coords[3] = (float) tmp[3];
-		coords[4] = (float) tmp[4];
-		coords[5] = (float) tmp[5];
-		
-		return type;
-	}
-	
-	@Override
-	public int currentSegment(double[] coords) {
-		final int type = types[typesIndex];
-		System.arraycopy(this.coords, coordsIndex, coords, 0, COUNTS[type]);
-		return type;
-	}
+    private static final int[] COUNTS = {
+        2, // SEG_MOVETO = 0
+        2, // SEG_LINETO = 1
+        4, // SEG_QUADTO = 2
+        6, // SEG_CUBICTO = 3
+        0, // SEG_CLOSE = 4
+    };
+    
+    public static ReversePathIterator reverse(PathIterator it) {
+        return new ReversePathIterator(it);
+    }
+    
+    private static int[] reverseTypes(int[] types, int length) {
+        if (length > 0 && types[0] != SEG_MOVETO) {
+            // the last segment of the reversed path is not defined
+            throw new IllegalArgumentException("Can not reverse path without initial SEG_MOVETO.");
+        }
+        
+        final int[] result = new int[length];
+        
+        result[0] = SEG_MOVETO;
+        
+        int lower = 1;
+        int upper = length - 1;
+        
+        while (lower <= upper) {
+            result[lower] = types[upper];
+            result[upper] = types[lower];
+            
+            ++lower;
+            --upper;
+        }
+        
+        return result;
+    }
+    
+    private static double[] reverseCoords(double[] coords, int length) {
+        final double[] result = new double[length];
+        
+        int lower = 0;
+        int upper = length - 2;
+        
+        while (lower <= upper) {
+            result[lower] = coords[upper];
+            result[lower + 1] = coords[upper + 1];
+            result[upper] = coords[lower];
+            result[upper + 1] = coords[lower + 1];
+            
+            lower += 2;
+            upper -= 2;
+        }
+        
+        return result;
+    }
+    
+    private final int winding;
+    
+    private final int[] types;
+    private int typesIndex = 0;
+    
+    private final double[] coords;
+    private int coordsIndex = 0;
+    
+    private ReversePathIterator(PathIterator it) {
+        this.winding = it.getWindingRule();
+        
+        double[] tmpCoords = new double[62];
+        int[] tmpTypes = new int[11];
+        
+        int tmpCoordsI = 0;
+        int tmpTypesI = 0;
+        
+        while (!it.isDone()) {
+            if (tmpTypesI >= tmpTypes.length) {
+                tmpTypes = Arrays.copyOf(tmpTypes, 2 * tmpTypes.length);
+            }
+            
+            final double[] cs = new double[6];
+            final int t = it.currentSegment(cs);
+            tmpTypes[tmpTypesI++] = t;
+            final int count = COUNTS[t];
+            
+            if (tmpCoordsI + count > tmpCoords.length) {
+                tmpCoords = Arrays.copyOf(tmpCoords, 2 * tmpCoords.length);
+            }
+            System.arraycopy(cs, 0, tmpCoords, tmpCoordsI, count);
+            tmpCoordsI += count;
+            
+            it.next();
+        }
+        
+        this.types = reverseTypes(tmpTypes, tmpTypesI);
+        this.coords = reverseCoords(tmpCoords, tmpCoordsI);
+    }
+    
+    @Override
+    public int getWindingRule() {
+        return winding;
+    }
+    
+    @Override
+    public boolean isDone() {
+        return typesIndex >= types.length;
+    }
+    
+    @Override
+    public void next() {
+        coordsIndex += COUNTS[types[typesIndex]];
+        ++typesIndex;
+    }
+    
+    @Override
+    public int currentSegment(float[] coords) {
+        final double[] tmp = new double[6];
+        final int type = currentSegment(tmp);
+        
+        coords[0] = (float) tmp[0];
+        coords[1] = (float) tmp[1];
+        coords[2] = (float) tmp[2];
+        coords[3] = (float) tmp[3];
+        coords[4] = (float) tmp[4];
+        coords[5] = (float) tmp[5];
+        
+        return type;
+    }
+    
+    @Override
+    public int currentSegment(double[] coords) {
+        final int type = types[typesIndex];
+        System.arraycopy(this.coords, coordsIndex, coords, 0, COUNTS[type]);
+        return type;
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/RoadGui.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/RoadGui.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/RoadGui.java	(revision 26154)
@@ -41,840 +41,840 @@
 
 class RoadGui {
-	final class ViaConnector extends InteractiveElement {
-		private final Road.End end;
-		
-		private final Line2D line;
-		private final float strokeWidth;
-		
-		public ViaConnector(Road.End end) {
-			this.end = end;
-			this.line = new Line2D.Double(getLeftCorner(end), getRightCorner(end));
-			this.strokeWidth = (float) (3 * getContainer().getLaneWidth() / 4);
-		}
-		
-		@Override
-		void paint(Graphics2D g2d, State state) {
-			if (isVisible(state)) {
-				g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
-				g2d.setColor(Color.ORANGE);
-				g2d.draw(line);
-			}
-		}
-		
-		@Override
-		boolean contains(Point2D p, State state) {
-			if (!isVisible(state)) {
-				return false;
-			}
-			
-			final Point2D closest = closest(line, p);
-			return p.distance(closest) <= strokeWidth / 2;
-		}
-		
-		private boolean isVisible(State state) {
-			if (!(state instanceof State.Connecting)) {
-				return false;
-			}
-			
-			final State.Connecting s = (State.Connecting) state;
-			
-			if (s.getJunction().equals(end.getJunction()) || equals(s.getBacktrackViaConnector())) {
-				return true;
-			} else if (!s.getViaConnectors().isEmpty()
-			    && s.getViaConnectors().get(s.getViaConnectors().size() - 1).getRoadModel().equals(getRoadModel())) {
-				return true;
-			}
-			
-			return false;
-		}
-		
-		private Road getRoadModel() {
-			return getModel();
-		}
-		
-		public RoadGui getRoad() {
-			return RoadGui.this;
-		}
-		
-		@Override
-		Type getType() {
-			return Type.VIA_CONNECTOR;
-		}
-		
-		@Override
-		int getZIndex() {
-			return 1;
-		}
-		
-		public Road.End getRoadEnd() {
-			return end;
-		}
-		
-		public Point2D getCenter() {
-			return relativePoint(line.getP1(), line.getP1().distance(line.getP2()) / 2, angle(line.getP1(), line.getP2()));
-		}
-	}
-	
-	private final class Extender extends InteractiveElement {
-		private final Road.End end;
-		private final Way way;
-		
-		private final Line2D line;
-		
-		public Extender(Road.End end, Way way, double angle) {
-			this.end = end;
-			this.way = way;
-			this.line = new Line2D.Double(a.getPoint(), relativePoint(a.getPoint(), getContainer().getLaneWidth() * 4, angle));
-		}
-		
-		@Override
-		void paint(Graphics2D g2d, State state) {
-			g2d.setStroke(getContainer().getConnectionStroke());
-			g2d.setColor(Color.CYAN);
-			g2d.draw(line);
-		}
-		
-		@Override
-		boolean contains(Point2D p, State state) {
-			final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke();
-			final double strokeWidth = stroke.getLineWidth();
-			
-			final Point2D closest = closest(line, p);
-			return p.distance(closest) <= strokeWidth / 2;
-		}
-		
-		@Override
-		State click(State old) {
-			end.extend(way);
-			return new State.Invalid(old);
-		}
-		
-		@Override
-		Type getType() {
-			return Type.EXTENDER;
-		}
-		
-		@Override
-		int getZIndex() {
-			return 0;
-		}
-	}
-	
-	private final class LaneAdder extends InteractiveElement {
-		private final Road.End end;
-		private final Lane.Kind kind;
-		
-		private final Point2D center;
-		private final Ellipse2D background;
-		
-		public LaneAdder(Road.End end, Lane.Kind kind) {
-			this.end = end;
-			this.kind = kind;
-			
-			final double a = getAngle(end) + PI;
-			final Point2D lc = getLeftCorner(end);
-			final Point2D rc = getRightCorner(end);
-			
-			final double r = connectorRadius;
-			final double cx;
-			final double cy;
-			if (kind == Lane.Kind.EXTRA_LEFT) {
-				final JunctionGui j = getContainer().getGui(end.getJunction());
-				final Point2D i = intersection(line(j.getPoint(), a), new Line2D.Double(lc, rc));
-				
-				cx = i.getX() + 21d / 16 * r * (2 * cos(a) + cos(a - PI / 2));
-				cy = i.getY() - 21d / 16 * r * (2 * sin(a) + sin(a - PI / 2));
-			} else {
-				cx = rc.getX() + 21d / 16 * r * (2 * cos(a) + cos(a + PI / 2));
-				cy = rc.getY() - 21d / 16 * r * (2 * sin(a) + sin(a + PI / 2));
-			}
-			
-			center = new Point2D.Double(cx, cy);
-			background = new Ellipse2D.Double(cx - r, cy - r, 2 * r, 2 * r);
-		}
-		
-		@Override
-		void paint(Graphics2D g2d, State state) {
-			if (!isVisible(state)) {
-				return;
-			}
-			
-			g2d.setColor(Color.DARK_GRAY);
-			g2d.fill(background);
-			
-			final double l = 2 * connectorRadius / 3;
-			final Line2D v = new Line2D.Double(center.getX(), center.getY() - l, center.getX(), center.getY() + l);
-			final Line2D h = new Line2D.Double(center.getX() - l, center.getY(), center.getX() + l, center.getY());
-			
-			g2d.setStroke(new BasicStroke((float) (connectorRadius / 5)));
-			g2d.setColor(Color.WHITE);
-			g2d.draw(v);
-			g2d.draw(h);
-		}
-		
-		private boolean isVisible(State state) {
-			return end.getJunction().isPrimary();
-		}
-		
-		@Override
-		boolean contains(Point2D p, State state) {
-			return isVisible(state) && background.contains(p);
-		}
-		
-		@Override
-		Type getType() {
-			return Type.LANE_ADDER;
-		}
-		
-		@Override
-		int getZIndex() {
-			return 2;
-		}
-		
-		@Override
-		public State click(State old) {
-			end.addLane(kind);
-			return new State.Invalid(old);
-		}
-	}
-	
-	final class IncomingConnector extends InteractiveElement {
-		private final Road.End end;
-		private final List<LaneGui> lanes;
-		
-		private final Point2D center = new Point2D.Double();
-		private final Ellipse2D circle = new Ellipse2D.Double();
-		
-		private IncomingConnector(Road.End end) {
-			this.end = end;
-			
-			final List<LaneGui> lanes = new ArrayList<LaneGui>(end.getLanes().size());
-			for (Lane l : end.getOppositeEnd().getLanes()) {
-				lanes.add(new LaneGui(RoadGui.this, l));
-			}
-			this.lanes = Collections.unmodifiableList(lanes);
-		}
-		
-		@Override
-		public void paintBackground(Graphics2D g2d, State state) {
-			if (isActive(state)) {
-				final Composite old = g2d.getComposite();
-				g2d.setComposite(((AlphaComposite) old).derive(0.2f));
-				
-				g2d.setColor(new Color(255, 127, 31));
-				
-				for (LaneGui l : lanes) {
-					l.fill(g2d);
-				}
-				
-				g2d.setComposite(old);
-			}
-		}
-		
-		@Override
-		public void paint(Graphics2D g2d, State state) {
-			if (isVisible(state)) {
-				final Composite old = g2d.getComposite();
-				if (isActive(state)) {
-					g2d.setComposite(((AlphaComposite) old).derive(1f));
-				}
-				
-				g2d.setColor(Color.LIGHT_GRAY);
-				g2d.fill(circle);
-				
-				g2d.setComposite(old);
-			}
-		}
-		
-		private boolean isActive(State state) {
-			if (!(state instanceof State.IncomingActive)) {
-				return false;
-			}
-			
-			final Road.End roadEnd = ((State.IncomingActive) state).getRoadEnd();
-			
-			return roadEnd.equals(getRoadEnd());
-		}
-		
-		private boolean isVisible(State state) {
-			if (getModel().isPrimary() || !getRoadEnd().getJunction().isPrimary()
-			    || getRoadEnd().getOppositeEnd().getLanes().isEmpty()) {
-				return false;
-			}
-			
-			if (state instanceof State.Connecting) {
-				return ((State.Connecting) state).getJunction().equals(getRoadEnd().getJunction());
-			}
-			
-			return true;
-		}
-		
-		@Override
-		public boolean contains(Point2D p, State state) {
-			if (!isVisible(state)) {
-				return false;
-			} else if (circle.contains(p)) {
-				return true;
-			}
-			
-			for (LaneGui l : lanes) {
-				if (l.contains(p)) {
-					return true;
-				}
-			}
-			
-			return false;
-		}
-		
-		@Override
-		public Type getType() {
-			return Type.INCOMING_CONNECTOR;
-		}
-		
-		@Override
-		public State activate(State old) {
-			return new State.IncomingActive(getRoadEnd());
-		}
-		
-		public Point2D getCenter() {
-			return (Point2D) center.clone();
-		}
-		
-		void move(double x, double y) {
-			final double r = connectorRadius;
-			
-			center.setLocation(x, y);
-			circle.setFrame(x - r, y - r, 2 * r, 2 * r);
-		}
-		
-		public Road.End getRoadEnd() {
-			return end;
-		}
-		
-		public List<LaneGui> getLanes() {
-			return lanes;
-		}
-		
-		@Override
-		int getZIndex() {
-			return 1;
-		}
-		
-		public void add(LaneGui lane) {
-			lanes.add(lane);
-		}
-	}
-	
-	// TODO rework to be a SegmentGui (with getModel())
-	private final class Segment {
-		final Point2D to;
-		final Point2D from;
-		
-		final Segment prev;
-		final Segment next;
-		
-		final double length;
-		final double angle;
-		
-		public Segment(Segment next, List<Point2D> bends, JunctionGui a) {
-			final Point2D head = (Point2D) bends.get(0).clone();
-			final List<Point2D> tail = bends.subList(1, bends.size());
-			
-			this.next = next;
-			this.to = head;
-			this.from = (Point2D) (tail.isEmpty() ? a.getPoint() : tail.get(0)).clone();
-			this.prev = tail.isEmpty() ? null : new Segment(this, tail, a);
-			this.length = from.distance(to);
-			this.angle = angle(from, to);
-			
-			// TODO create a factory method for the segments list and pass it to
-			// the constructor(s)
-			segments.add(this);
-		}
-		
-		public Segment(JunctionGui b, List<Point2D> bends, JunctionGui a) {
-			this((Segment) null, prepended(bends, (Point2D) b.getPoint().clone()), a);
-		}
-		
-		private double getFromOffset() {
-			return prev == null ? 0 : prev.getFromOffset() + prev.length;
-		}
-		
-		public double getOffset(double x, double y) {
-			return getOffsetInternal(new Point2D.Double(x, y), -1, Double.POSITIVE_INFINITY);
-		}
-		
-		private double getOffsetInternal(Point2D p, double offset, double quality) {
-			final Point2D closest = closest(new Line2D.Double(from, to), p);
-			final double myQuality = closest.distance(p);
-			
-			if (myQuality < quality) {
-				quality = myQuality;
-				
-				final Line2D normal = line(p, angle + PI / 2);
-				final Point2D isect = intersection(normal, new Line2D.Double(from, to));
-				final double d = from.distance(isect);
-				final boolean negative = Math.abs(angle(from, isect) - angle) > 1;
-				
-				offset = getFromOffset() + (negative ? -1 : 1) * d;
-			}
-			
-			return next == null ? offset : next.getOffsetInternal(p, offset, quality);
-		}
-		
-		public Path append(Path path, boolean forward, double offset) {
-			if (ROUND_CORNERS) {
-				final Segment n = forward ? prev : next;
-				final Point2D s = forward ? to : from;
-				final Point2D e = forward ? from : to;
-				
-				if (n == null) {
-					return path.lineTo(e.getX(), e.getY(), length - offset);
-				}
-				
-				final double a = minAngleDiff(angle, n.angle);
-				final double d = 3 * outerMargin + getWidth(getModel().getToEnd(), (forward && a < 0) || (!forward && a > 0));
-				final double l = d * tan(abs(a));
-				
-				if (length - offset < l / 2 || n.length < l / 2) {
-					return n.append(path.lineTo(e.getX(), e.getY(), length - offset), forward, 0);
-				} else {
-					final Point2D p = relativePoint(e, l / 2, angle(e, s));
-					
-					final Path line = path.lineTo(p.getX(), p.getY(), length - l / 2 - offset);
-					final Path curve = line.curveTo(d, d, a, l);
-					
-					return n.append(curve, forward, l / 2);
-				}
-			} else if (forward) {
-				final Path tmp = path.lineTo(from.getX(), from.getY(), length);
-				return prev == null ? tmp : prev.append(tmp, forward, 0);
-			} else {
-				final Path tmp = path.lineTo(to.getX(), to.getY(), length);
-				return next == null ? tmp : next.append(tmp, forward, 0);
-			}
-		}
-	}
-	
-	/**
-	 * This should become a setting, but rounding is (as of yet) still slightly buggy and a low
-	 * priority.
-	 */
-	private static final boolean ROUND_CORNERS = false;
-	
-	private static final List<Point2D> prepended(List<Point2D> bends, Point2D point) {
-		final List<Point2D> result = new ArrayList<Point2D>(bends.size() + 1);
-		result.add(point);
-		result.addAll(bends);
-		return result;
-	}
-	
-	private final GuiContainer container;
-	private final double innerMargin;
-	private final double outerMargin;
-	
-	private final float lineWidth;
-	private final Stroke regularStroke;
-	private final Stroke dashedStroke;
-	
-	private final JunctionGui a;
-	private final JunctionGui b;
-	private final double length;
-	
-	private final IncomingConnector incomingA;
-	private final IncomingConnector incomingB;
-	
-	private final Road road;
-	private final List<Segment> segments = new ArrayList<Segment>();
-	
-	final double connectorRadius;
-	
-	public RoadGui(GuiContainer container, Road road) {
-		this.container = container;
-		
-		this.road = road;
-		
-		this.a = container.getGui(road.getFromEnd().getJunction());
-		this.b = container.getGui(road.getToEnd().getJunction());
-		
-		this.incomingA = new IncomingConnector(road.getFromEnd());
-		this.incomingB = new IncomingConnector(road.getToEnd());
-		
-		final List<Point2D> bends = new ArrayList<Point2D>();
-		final List<Node> nodes = road.getRoute().getNodes();
-		for (int i = nodes.size() - 2; i > 0; --i) {
-			bends.add(container.translateAndScale(loc(nodes.get(i))));
-		}
-		
-		// they add themselves to this.segments
-		new Segment(b, bends, a);
-		double l = 0;
-		for (Segment s : segments) {
-			l += s.length;
-		}
-		this.length = l;
-		
-		this.innerMargin = !incomingA.getLanes().isEmpty() && !incomingB.getLanes().isEmpty() ? 1 * container
-		    .getLaneWidth() / 15 : 0;
-		this.outerMargin = container.getLaneWidth() / 6;
-		this.connectorRadius = 3 * container.getLaneWidth() / 8;
-		this.lineWidth = (float) (container.getLaneWidth() / 30);
-		this.regularStroke = new BasicStroke(2 * lineWidth);
-		this.dashedStroke = new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10f, new float[] {
-		    (float) (container.getLaneWidth() / 2), (float) (container.getLaneWidth() / 3)
-		}, 0);
-	}
-	
-	public JunctionGui getA() {
-		return a;
-	}
-	
-	public JunctionGui getB() {
-		return b;
-	}
-	
-	public Line2D getLeftCurb(Road.End end) {
-		return GuiUtil.line(getCorner(end, true), getAngle(end) + PI);
-	}
-	
-	public Line2D getRightCurb(Road.End end) {
-		return GuiUtil.line(getCorner(end, false), getAngle(end) + PI);
-	}
-	
-	private Point2D getLeftCorner(Road.End end) {
-		return getCorner(end, true);
-	}
-	
-	private Point2D getRightCorner(Road.End end) {
-		return getCorner(end, false);
-	}
-	
-	private Point2D getCorner(Road.End end, boolean left) {
-		final JunctionGui j = end.isFromEnd() ? a : b;
-		final double w = left ? getWidth(end, true) : getWidth(end, false);
-		final double s = (left ? 1 : -1);
-		final double a = getAngle(end) + PI;
-		final double t = left ? j.getLeftTrim(end) : j.getRightTrim(end);
-		
-		final double dx = s * cos(PI / 2 - a) * w + cos(a) * t;
-		final double dy = s * sin(PI / 2 - a) * w - sin(a) * t;
-		
-		return new Point2D.Double(j.x + dx, j.y + dy);
-	}
-	
-	private double getWidth(Road.End end, boolean left) {
-		if (!end.getRoad().equals(road)) {
-			throw new IllegalArgumentException();
-		}
-		
-		final int lcForward = incomingA.getLanes().size();
-		final int lcBackward = incomingB.getLanes().size();
-		
-		final double LW = getContainer().getLaneWidth();
-		final double M = innerMargin + outerMargin;
-		
-		if (end.isToEnd()) {
-			return (left ? lcBackward : lcForward) * LW + M;
-		} else {
-			return (left ? lcForward : lcBackward) * LW + M;
-		}
-	}
-	
-	List<InteractiveElement> paint(Graphics2D g2d) {
-		final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
-		
-		result.addAll(paintLanes(g2d));
-		
-		if (getModel().isPrimary()) {
-			result.add(new ViaConnector(getModel().getFromEnd()));
-			result.add(new ViaConnector(getModel().getToEnd()));
-		} else {
-			result.addAll(laneAdders());
-			result.addAll(extenders(getModel().getFromEnd()));
-			result.addAll(extenders(getModel().getToEnd()));
-		}
-		
-		g2d.setColor(Color.RED);
-		for (Segment s : segments) {
-			g2d.fill(new Ellipse2D.Double(s.from.getX() - 1, s.from.getY() - 1, 2, 2));
-		}
-		
-		return result;
-	}
-	
-	private List<LaneAdder> laneAdders() {
-		final List<LaneAdder> result = new ArrayList<LaneAdder>(4);
-		
-		if (!incomingA.getLanes().isEmpty()) {
-			result.add(new LaneAdder(getModel().getToEnd(), Lane.Kind.EXTRA_LEFT));
-			result.add(new LaneAdder(getModel().getToEnd(), Lane.Kind.EXTRA_RIGHT));
-		}
-		
-		if (!incomingB.getLanes().isEmpty()) {
-			result.add(new LaneAdder(getModel().getFromEnd(), Lane.Kind.EXTRA_LEFT));
-			result.add(new LaneAdder(getModel().getFromEnd(), Lane.Kind.EXTRA_RIGHT));
-		}
-		
-		return result;
-	}
-	
-	private List<Extender> extenders(Road.End end) {
-		if (!end.isExtendable()) {
-			return Collections.emptyList();
-		}
-		
-		final List<Extender> result = new ArrayList<Extender>();
-		
-		final Node n = end.getJunction().getNode();
-		for (Way w : OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)) {
-			if (w.getNodesCount() > 1 && !end.getWay().equals(w) && w.isFirstLastNode(n) && Utils.isRoad(w)) {
-				final Node nextNode = w.firstNode().equals(n) ? w.getNode(1) : w.getNode(w.getNodesCount() - 2);
-				final Point2D nextNodeLoc = getContainer().translateAndScale(loc(nextNode));
-				result.add(new Extender(end, w, angle(a.getPoint(), nextNodeLoc)));
-			}
-		}
-		
-		return result;
-	}
-	
-	public Road getModel() {
-		return road;
-	}
-	
-	public IncomingConnector getConnector(Road.End end) {
-		return end.isFromEnd() ? incomingA : incomingB;
-	}
-	
-	private List<InteractiveElement> paintLanes(Graphics2D g2d) {
-		final Path2D middleLines = new Path2D.Double();
-		
-		g2d.setStroke(regularStroke);
-		
-		final boolean forward = !incomingA.getLanes().isEmpty();
-		final boolean backward = !incomingB.getLanes().isEmpty();
-		
-		final Path2D middleArea;
-		if (forward && backward) {
-			paintLanes(g2d, middleLines, true);
-			paintLanes(g2d, middleLines, false);
-			
-			middleLines.closePath();
-			middleArea = middleLines;
-			g2d.setColor(new Color(160, 160, 160));
-		} else if (forward || backward) {
-			paintLanes(g2d, middleLines, forward);
-			
-			middleArea = new Path2D.Double();
-			middleArea.append(middleLines.getPathIterator(null), false);
-			middleArea.append(middlePath(backward).offset(outerMargin, -1, -1, outerMargin).getIterator(), true);
-			middleArea.closePath();
-			g2d.setColor(Color.GRAY);
-		} else {
-			throw new AssertionError();
-		}
-		
-		g2d.fill(middleArea);
-		g2d.setColor(Color.WHITE);
-		g2d.draw(middleLines);
-		
-		final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
-		
-		moveIncoming(getModel().getFromEnd());
-		moveIncoming(getModel().getToEnd());
-		result.add(incomingA);
-		result.add(incomingB);
-		
-		for (IncomingConnector c : Arrays.asList(incomingA, incomingB)) {
-			int offset = 0;
-			for (LaneGui l : c.getLanes()) {
-				moveOutgoing(l, offset++);
-				
-				result.add(l.outgoing);
-				if (l.getModel().isExtra()) {
-					result.add(l.lengthSlider);
-				}
-			}
-		}
-		
-		return result;
-	}
-	
-	private void paintLanes(Graphics2D g2d, Path2D middleLines, boolean forward) {
-		final Shape clip = clip();
-		g2d.clip(clip);
-		
-		final Path middle = middlePath(forward);
-		
-		Path innerPath = middle.offset(innerMargin, -1, -1, innerMargin);
-		final List<Path> linePaths = new ArrayList<Path>();
-		linePaths.add(innerPath);
-		
-		for (LaneGui l : forward ? incomingA.getLanes() : incomingB.getLanes()) {
-			l.setClip(clip);
-			innerPath = l.recalculate(innerPath, middleLines);
-			linePaths.add(innerPath);
-		}
-		
-		final Path2D area = new Path2D.Double();
-		area(area, middle, innerPath.offset(outerMargin, -1, -1, outerMargin));
-		g2d.setColor(Color.GRAY);
-		g2d.fill(area);
-		
-		g2d.setColor(Color.WHITE);
-		final Path2D lines = new Path2D.Double();
-		lines.append(innerPath.getIterator(), false);
-		g2d.draw(lines);
-		
-		// g2d.setColor(new Color(32, 128, 192));
-		g2d.setColor(Color.WHITE);
-		g2d.setStroke(dashedStroke);
-		for (Path p : linePaths) {
-			lines.reset();
-			lines.append(p.getIterator(), false);
-			g2d.draw(lines);
-		}
-		g2d.setStroke(regularStroke);
-		
-		// g2d.setColor(new Color(32, 128, 192));
-		// lines.reset();
-		// lines.append(middle.getIterator(), false);
-		// g2d.draw(lines);
-		
-		g2d.setClip(null);
-	}
-	
-	private Shape clip() {
-		final Area clip = new Area(new Rectangle2D.Double(-100000, -100000, 200000, 200000));
-		clip.subtract(new Area(negativeClip(true)));
-		clip.subtract(new Area(negativeClip(false)));
-		
-		return clip;
-	}
-	
-	private Shape negativeClip(boolean forward) {
-		final Road.End end = forward ? getModel().getToEnd() : getModel().getFromEnd();
-		final JunctionGui j = forward ? b : a;
-		
-		final Line2D lc = getLeftCurb(end);
-		final Line2D rc = getRightCurb(end);
-		
-		final Path2D negativeClip = new Path2D.Double();
-		
-		final double d = rc.getP1().distance(j.getPoint()) + lc.getP1().distance(j.getPoint());
-		
-		final double cm = 0.01 / getContainer().getMpp(); // 1 centimeter
-		final double rca = angle(rc) + PI;
-		final double lca = angle(lc) + PI;
-		final Point2D r1 = relativePoint(relativePoint(rc.getP1(), 1, angle(lc.getP1(), rc.getP1())), cm, rca);
-		final Point2D r2 = relativePoint(r1, d, rca);
-		final Point2D l1 = relativePoint(relativePoint(lc.getP1(), 1, angle(rc.getP1(), lc.getP1())), cm, lca);
-		final Point2D l2 = relativePoint(l1, d, lca);
-		
-		negativeClip.moveTo(r1.getX(), r1.getY());
-		negativeClip.lineTo(r2.getX(), r2.getY());
-		negativeClip.lineTo(l2.getX(), l2.getY());
-		negativeClip.lineTo(l1.getX(), l1.getY());
-		negativeClip.closePath();
-		
-		return negativeClip;
-	}
-	
-	public Path getLaneMiddle(boolean forward) {
-		final Path mid = middlePath(!forward);
-		final double w = getWidth(forward ? getModel().getFromEnd() : getModel().getToEnd(), true);
-		final double o = (w - outerMargin) / 2;
-		
-		return o > 0 ? mid.offset(-o, -1, -1, -o) : mid;
-	}
-	
-	private Path middlePath(boolean forward) {
-		final Path path = forward ? Path.create(b.x, b.y) : Path.create(a.x, a.y);
-		final Segment first = forward ? segments.get(segments.size() - 1) : segments.get(0);
-		
-		return first.append(path, forward, 0);
-	}
-	
-	private void moveIncoming(Road.End end) {
-		final Point2D lc = getLeftCorner(end);
-		final Point2D rc = getRightCorner(end);
-		final Line2D cornerLine = new Line2D.Double(lc, rc);
-		
-		final double a = getAngle(end);
-		final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a);
-		
-		final Point2D i = intersection(roadLine, cornerLine);
-		// TODO fix depending on angle(i, lc)
-		final double offset = innerMargin + (getWidth(end, true) - innerMargin - outerMargin) / 2;
-		final Point2D loc = relativePoint(i, offset, angle(i, lc));
-		
-		getConnector(end).move(loc.getX(), loc.getY());
-	}
-	
-	private void moveOutgoing(LaneGui lane, int offset) {
-		final Road.End end = lane.getModel().getOutgoingRoadEnd();
-		
-		final Point2D lc = getLeftCorner(end);
-		final Point2D rc = getRightCorner(end);
-		final Line2D cornerLine = new Line2D.Double(lc, rc);
-		
-		final double a = getAngle(end);
-		final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a);
-		
-		final Point2D i = intersection(roadLine, cornerLine);
-		// TODO fix depending on angle(i, rc)
-		final double d = innerMargin + (2 * offset + 1) * getContainer().getLaneWidth() / 2;
-		final Point2D loc = relativePoint(i, d, angle(i, rc));
-		
-		lane.outgoing.move(loc.getX(), loc.getY());
-	}
-	
-	public JunctionGui getJunction(Road.End end) {
-		if (!getModel().equals(end.getRoad())) {
-			throw new IllegalArgumentException();
-		}
-		
-		return end.isFromEnd() ? getA() : getB();
-	}
-	
-	public double getAngle(Road.End end) {
-		if (!getModel().equals(end.getRoad())) {
-			throw new IllegalArgumentException();
-		}
-		
-		if (end.isToEnd()) {
-			return segments.get(segments.size() - 1).angle;
-		} else {
-			final double angle = segments.get(0).angle;
-			return angle > PI ? angle - PI : angle + PI;
-		}
-	}
-	
-	public double getWidth(Road.End end) {
-		return getWidth(end, true) + getWidth(end, false);
-	}
-	
-	public double getLength() {
-		return length;
-	}
-	
-	public double getOffset(double x, double y) {
-		return segments.get(0).getOffset(x, y);
-	}
-	
-	public GuiContainer getContainer() {
-		return container;
-	}
-	
-	public List<LaneGui> getLanes() {
-		final List<LaneGui> result = new ArrayList<LaneGui>();
-		
-		result.addAll(incomingB.getLanes());
-		result.addAll(incomingA.getLanes());
-		
-		return Collections.unmodifiableList(result);
-	}
-	
-	public List<LaneGui> getLanes(Road.End end) {
-		return getConnector(end.getOppositeEnd()).getLanes();
-	}
+    final class ViaConnector extends InteractiveElement {
+        private final Road.End end;
+        
+        private final Line2D line;
+        private final float strokeWidth;
+        
+        public ViaConnector(Road.End end) {
+            this.end = end;
+            this.line = new Line2D.Double(getLeftCorner(end), getRightCorner(end));
+            this.strokeWidth = (float) (3 * getContainer().getLaneWidth() / 4);
+        }
+        
+        @Override
+        void paint(Graphics2D g2d, State state) {
+            if (isVisible(state)) {
+                g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
+                g2d.setColor(Color.ORANGE);
+                g2d.draw(line);
+            }
+        }
+        
+        @Override
+        boolean contains(Point2D p, State state) {
+            if (!isVisible(state)) {
+                return false;
+            }
+            
+            final Point2D closest = closest(line, p);
+            return p.distance(closest) <= strokeWidth / 2;
+        }
+        
+        private boolean isVisible(State state) {
+            if (!(state instanceof State.Connecting)) {
+                return false;
+            }
+            
+            final State.Connecting s = (State.Connecting) state;
+            
+            if (s.getJunction().equals(end.getJunction()) || equals(s.getBacktrackViaConnector())) {
+                return true;
+            } else if (!s.getViaConnectors().isEmpty()
+                && s.getViaConnectors().get(s.getViaConnectors().size() - 1).getRoadModel().equals(getRoadModel())) {
+                return true;
+            }
+            
+            return false;
+        }
+        
+        private Road getRoadModel() {
+            return getModel();
+        }
+        
+        public RoadGui getRoad() {
+            return RoadGui.this;
+        }
+        
+        @Override
+        Type getType() {
+            return Type.VIA_CONNECTOR;
+        }
+        
+        @Override
+        int getZIndex() {
+            return 1;
+        }
+        
+        public Road.End getRoadEnd() {
+            return end;
+        }
+        
+        public Point2D getCenter() {
+            return relativePoint(line.getP1(), line.getP1().distance(line.getP2()) / 2, angle(line.getP1(), line.getP2()));
+        }
+    }
+    
+    private final class Extender extends InteractiveElement {
+        private final Road.End end;
+        private final Way way;
+        
+        private final Line2D line;
+        
+        public Extender(Road.End end, Way way, double angle) {
+            this.end = end;
+            this.way = way;
+            this.line = new Line2D.Double(a.getPoint(), relativePoint(a.getPoint(), getContainer().getLaneWidth() * 4, angle));
+        }
+        
+        @Override
+        void paint(Graphics2D g2d, State state) {
+            g2d.setStroke(getContainer().getConnectionStroke());
+            g2d.setColor(Color.CYAN);
+            g2d.draw(line);
+        }
+        
+        @Override
+        boolean contains(Point2D p, State state) {
+            final BasicStroke stroke = (BasicStroke) getContainer().getConnectionStroke();
+            final double strokeWidth = stroke.getLineWidth();
+            
+            final Point2D closest = closest(line, p);
+            return p.distance(closest) <= strokeWidth / 2;
+        }
+        
+        @Override
+        State click(State old) {
+            end.extend(way);
+            return new State.Invalid(old);
+        }
+        
+        @Override
+        Type getType() {
+            return Type.EXTENDER;
+        }
+        
+        @Override
+        int getZIndex() {
+            return 0;
+        }
+    }
+    
+    private final class LaneAdder extends InteractiveElement {
+        private final Road.End end;
+        private final Lane.Kind kind;
+        
+        private final Point2D center;
+        private final Ellipse2D background;
+        
+        public LaneAdder(Road.End end, Lane.Kind kind) {
+            this.end = end;
+            this.kind = kind;
+            
+            final double a = getAngle(end) + PI;
+            final Point2D lc = getLeftCorner(end);
+            final Point2D rc = getRightCorner(end);
+            
+            final double r = connectorRadius;
+            final double cx;
+            final double cy;
+            if (kind == Lane.Kind.EXTRA_LEFT) {
+                final JunctionGui j = getContainer().getGui(end.getJunction());
+                final Point2D i = intersection(line(j.getPoint(), a), new Line2D.Double(lc, rc));
+                
+                cx = i.getX() + 21d / 16 * r * (2 * cos(a) + cos(a - PI / 2));
+                cy = i.getY() - 21d / 16 * r * (2 * sin(a) + sin(a - PI / 2));
+            } else {
+                cx = rc.getX() + 21d / 16 * r * (2 * cos(a) + cos(a + PI / 2));
+                cy = rc.getY() - 21d / 16 * r * (2 * sin(a) + sin(a + PI / 2));
+            }
+            
+            center = new Point2D.Double(cx, cy);
+            background = new Ellipse2D.Double(cx - r, cy - r, 2 * r, 2 * r);
+        }
+        
+        @Override
+        void paint(Graphics2D g2d, State state) {
+            if (!isVisible(state)) {
+                return;
+            }
+            
+            g2d.setColor(Color.DARK_GRAY);
+            g2d.fill(background);
+            
+            final double l = 2 * connectorRadius / 3;
+            final Line2D v = new Line2D.Double(center.getX(), center.getY() - l, center.getX(), center.getY() + l);
+            final Line2D h = new Line2D.Double(center.getX() - l, center.getY(), center.getX() + l, center.getY());
+            
+            g2d.setStroke(new BasicStroke((float) (connectorRadius / 5)));
+            g2d.setColor(Color.WHITE);
+            g2d.draw(v);
+            g2d.draw(h);
+        }
+        
+        private boolean isVisible(State state) {
+            return end.getJunction().isPrimary();
+        }
+        
+        @Override
+        boolean contains(Point2D p, State state) {
+            return isVisible(state) && background.contains(p);
+        }
+        
+        @Override
+        Type getType() {
+            return Type.LANE_ADDER;
+        }
+        
+        @Override
+        int getZIndex() {
+            return 2;
+        }
+        
+        @Override
+        public State click(State old) {
+            end.addLane(kind);
+            return new State.Invalid(old);
+        }
+    }
+    
+    final class IncomingConnector extends InteractiveElement {
+        private final Road.End end;
+        private final List<LaneGui> lanes;
+        
+        private final Point2D center = new Point2D.Double();
+        private final Ellipse2D circle = new Ellipse2D.Double();
+        
+        private IncomingConnector(Road.End end) {
+            this.end = end;
+            
+            final List<LaneGui> lanes = new ArrayList<LaneGui>(end.getLanes().size());
+            for (Lane l : end.getOppositeEnd().getLanes()) {
+                lanes.add(new LaneGui(RoadGui.this, l));
+            }
+            this.lanes = Collections.unmodifiableList(lanes);
+        }
+        
+        @Override
+        public void paintBackground(Graphics2D g2d, State state) {
+            if (isActive(state)) {
+                final Composite old = g2d.getComposite();
+                g2d.setComposite(((AlphaComposite) old).derive(0.2f));
+                
+                g2d.setColor(new Color(255, 127, 31));
+                
+                for (LaneGui l : lanes) {
+                    l.fill(g2d);
+                }
+                
+                g2d.setComposite(old);
+            }
+        }
+        
+        @Override
+        public void paint(Graphics2D g2d, State state) {
+            if (isVisible(state)) {
+                final Composite old = g2d.getComposite();
+                if (isActive(state)) {
+                    g2d.setComposite(((AlphaComposite) old).derive(1f));
+                }
+                
+                g2d.setColor(Color.LIGHT_GRAY);
+                g2d.fill(circle);
+                
+                g2d.setComposite(old);
+            }
+        }
+        
+        private boolean isActive(State state) {
+            if (!(state instanceof State.IncomingActive)) {
+                return false;
+            }
+            
+            final Road.End roadEnd = ((State.IncomingActive) state).getRoadEnd();
+            
+            return roadEnd.equals(getRoadEnd());
+        }
+        
+        private boolean isVisible(State state) {
+            if (getModel().isPrimary() || !getRoadEnd().getJunction().isPrimary()
+                || getRoadEnd().getOppositeEnd().getLanes().isEmpty()) {
+                return false;
+            }
+            
+            if (state instanceof State.Connecting) {
+                return ((State.Connecting) state).getJunction().equals(getRoadEnd().getJunction());
+            }
+            
+            return true;
+        }
+        
+        @Override
+        public boolean contains(Point2D p, State state) {
+            if (!isVisible(state)) {
+                return false;
+            } else if (circle.contains(p)) {
+                return true;
+            }
+            
+            for (LaneGui l : lanes) {
+                if (l.contains(p)) {
+                    return true;
+                }
+            }
+            
+            return false;
+        }
+        
+        @Override
+        public Type getType() {
+            return Type.INCOMING_CONNECTOR;
+        }
+        
+        @Override
+        public State activate(State old) {
+            return new State.IncomingActive(getRoadEnd());
+        }
+        
+        public Point2D getCenter() {
+            return (Point2D) center.clone();
+        }
+        
+        void move(double x, double y) {
+            final double r = connectorRadius;
+            
+            center.setLocation(x, y);
+            circle.setFrame(x - r, y - r, 2 * r, 2 * r);
+        }
+        
+        public Road.End getRoadEnd() {
+            return end;
+        }
+        
+        public List<LaneGui> getLanes() {
+            return lanes;
+        }
+        
+        @Override
+        int getZIndex() {
+            return 1;
+        }
+        
+        public void add(LaneGui lane) {
+            lanes.add(lane);
+        }
+    }
+    
+    // TODO rework to be a SegmentGui (with getModel())
+    private final class Segment {
+        final Point2D to;
+        final Point2D from;
+        
+        final Segment prev;
+        final Segment next;
+        
+        final double length;
+        final double angle;
+        
+        public Segment(Segment next, List<Point2D> bends, JunctionGui a) {
+            final Point2D head = (Point2D) bends.get(0).clone();
+            final List<Point2D> tail = bends.subList(1, bends.size());
+            
+            this.next = next;
+            this.to = head;
+            this.from = (Point2D) (tail.isEmpty() ? a.getPoint() : tail.get(0)).clone();
+            this.prev = tail.isEmpty() ? null : new Segment(this, tail, a);
+            this.length = from.distance(to);
+            this.angle = angle(from, to);
+            
+            // TODO create a factory method for the segments list and pass it to
+            // the constructor(s)
+            segments.add(this);
+        }
+        
+        public Segment(JunctionGui b, List<Point2D> bends, JunctionGui a) {
+            this((Segment) null, prepended(bends, (Point2D) b.getPoint().clone()), a);
+        }
+        
+        private double getFromOffset() {
+            return prev == null ? 0 : prev.getFromOffset() + prev.length;
+        }
+        
+        public double getOffset(double x, double y) {
+            return getOffsetInternal(new Point2D.Double(x, y), -1, Double.POSITIVE_INFINITY);
+        }
+        
+        private double getOffsetInternal(Point2D p, double offset, double quality) {
+            final Point2D closest = closest(new Line2D.Double(from, to), p);
+            final double myQuality = closest.distance(p);
+            
+            if (myQuality < quality) {
+                quality = myQuality;
+                
+                final Line2D normal = line(p, angle + PI / 2);
+                final Point2D isect = intersection(normal, new Line2D.Double(from, to));
+                final double d = from.distance(isect);
+                final boolean negative = Math.abs(angle(from, isect) - angle) > 1;
+                
+                offset = getFromOffset() + (negative ? -1 : 1) * d;
+            }
+            
+            return next == null ? offset : next.getOffsetInternal(p, offset, quality);
+        }
+        
+        public Path append(Path path, boolean forward, double offset) {
+            if (ROUND_CORNERS) {
+                final Segment n = forward ? prev : next;
+                final Point2D s = forward ? to : from;
+                final Point2D e = forward ? from : to;
+                
+                if (n == null) {
+                    return path.lineTo(e.getX(), e.getY(), length - offset);
+                }
+                
+                final double a = minAngleDiff(angle, n.angle);
+                final double d = 3 * outerMargin + getWidth(getModel().getToEnd(), (forward && a < 0) || (!forward && a > 0));
+                final double l = d * tan(abs(a));
+                
+                if (length - offset < l / 2 || n.length < l / 2) {
+                    return n.append(path.lineTo(e.getX(), e.getY(), length - offset), forward, 0);
+                } else {
+                    final Point2D p = relativePoint(e, l / 2, angle(e, s));
+                    
+                    final Path line = path.lineTo(p.getX(), p.getY(), length - l / 2 - offset);
+                    final Path curve = line.curveTo(d, d, a, l);
+                    
+                    return n.append(curve, forward, l / 2);
+                }
+            } else if (forward) {
+                final Path tmp = path.lineTo(from.getX(), from.getY(), length);
+                return prev == null ? tmp : prev.append(tmp, forward, 0);
+            } else {
+                final Path tmp = path.lineTo(to.getX(), to.getY(), length);
+                return next == null ? tmp : next.append(tmp, forward, 0);
+            }
+        }
+    }
+    
+    /**
+     * This should become a setting, but rounding is (as of yet) still slightly buggy and a low
+     * priority.
+     */
+    private static final boolean ROUND_CORNERS = false;
+    
+    private static final List<Point2D> prepended(List<Point2D> bends, Point2D point) {
+        final List<Point2D> result = new ArrayList<Point2D>(bends.size() + 1);
+        result.add(point);
+        result.addAll(bends);
+        return result;
+    }
+    
+    private final GuiContainer container;
+    private final double innerMargin;
+    private final double outerMargin;
+    
+    private final float lineWidth;
+    private final Stroke regularStroke;
+    private final Stroke dashedStroke;
+    
+    private final JunctionGui a;
+    private final JunctionGui b;
+    private final double length;
+    
+    private final IncomingConnector incomingA;
+    private final IncomingConnector incomingB;
+    
+    private final Road road;
+    private final List<Segment> segments = new ArrayList<Segment>();
+    
+    final double connectorRadius;
+    
+    public RoadGui(GuiContainer container, Road road) {
+        this.container = container;
+        
+        this.road = road;
+        
+        this.a = container.getGui(road.getFromEnd().getJunction());
+        this.b = container.getGui(road.getToEnd().getJunction());
+        
+        this.incomingA = new IncomingConnector(road.getFromEnd());
+        this.incomingB = new IncomingConnector(road.getToEnd());
+        
+        final List<Point2D> bends = new ArrayList<Point2D>();
+        final List<Node> nodes = road.getRoute().getNodes();
+        for (int i = nodes.size() - 2; i > 0; --i) {
+            bends.add(container.translateAndScale(loc(nodes.get(i))));
+        }
+        
+        // they add themselves to this.segments
+        new Segment(b, bends, a);
+        double l = 0;
+        for (Segment s : segments) {
+            l += s.length;
+        }
+        this.length = l;
+        
+        this.innerMargin = !incomingA.getLanes().isEmpty() && !incomingB.getLanes().isEmpty() ? 1 * container
+            .getLaneWidth() / 15 : 0;
+        this.outerMargin = container.getLaneWidth() / 6;
+        this.connectorRadius = 3 * container.getLaneWidth() / 8;
+        this.lineWidth = (float) (container.getLaneWidth() / 30);
+        this.regularStroke = new BasicStroke(2 * lineWidth);
+        this.dashedStroke = new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10f, new float[] {
+            (float) (container.getLaneWidth() / 2), (float) (container.getLaneWidth() / 3)
+        }, 0);
+    }
+    
+    public JunctionGui getA() {
+        return a;
+    }
+    
+    public JunctionGui getB() {
+        return b;
+    }
+    
+    public Line2D getLeftCurb(Road.End end) {
+        return GuiUtil.line(getCorner(end, true), getAngle(end) + PI);
+    }
+    
+    public Line2D getRightCurb(Road.End end) {
+        return GuiUtil.line(getCorner(end, false), getAngle(end) + PI);
+    }
+    
+    private Point2D getLeftCorner(Road.End end) {
+        return getCorner(end, true);
+    }
+    
+    private Point2D getRightCorner(Road.End end) {
+        return getCorner(end, false);
+    }
+    
+    private Point2D getCorner(Road.End end, boolean left) {
+        final JunctionGui j = end.isFromEnd() ? a : b;
+        final double w = left ? getWidth(end, true) : getWidth(end, false);
+        final double s = (left ? 1 : -1);
+        final double a = getAngle(end) + PI;
+        final double t = left ? j.getLeftTrim(end) : j.getRightTrim(end);
+        
+        final double dx = s * cos(PI / 2 - a) * w + cos(a) * t;
+        final double dy = s * sin(PI / 2 - a) * w - sin(a) * t;
+        
+        return new Point2D.Double(j.x + dx, j.y + dy);
+    }
+    
+    private double getWidth(Road.End end, boolean left) {
+        if (!end.getRoad().equals(road)) {
+            throw new IllegalArgumentException();
+        }
+        
+        final int lcForward = incomingA.getLanes().size();
+        final int lcBackward = incomingB.getLanes().size();
+        
+        final double LW = getContainer().getLaneWidth();
+        final double M = innerMargin + outerMargin;
+        
+        if (end.isToEnd()) {
+            return (left ? lcBackward : lcForward) * LW + M;
+        } else {
+            return (left ? lcForward : lcBackward) * LW + M;
+        }
+    }
+    
+    List<InteractiveElement> paint(Graphics2D g2d) {
+        final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
+        
+        result.addAll(paintLanes(g2d));
+        
+        if (getModel().isPrimary()) {
+            result.add(new ViaConnector(getModel().getFromEnd()));
+            result.add(new ViaConnector(getModel().getToEnd()));
+        } else {
+            result.addAll(laneAdders());
+            result.addAll(extenders(getModel().getFromEnd()));
+            result.addAll(extenders(getModel().getToEnd()));
+        }
+        
+        g2d.setColor(Color.RED);
+        for (Segment s : segments) {
+            g2d.fill(new Ellipse2D.Double(s.from.getX() - 1, s.from.getY() - 1, 2, 2));
+        }
+        
+        return result;
+    }
+    
+    private List<LaneAdder> laneAdders() {
+        final List<LaneAdder> result = new ArrayList<LaneAdder>(4);
+        
+        if (!incomingA.getLanes().isEmpty()) {
+            result.add(new LaneAdder(getModel().getToEnd(), Lane.Kind.EXTRA_LEFT));
+            result.add(new LaneAdder(getModel().getToEnd(), Lane.Kind.EXTRA_RIGHT));
+        }
+        
+        if (!incomingB.getLanes().isEmpty()) {
+            result.add(new LaneAdder(getModel().getFromEnd(), Lane.Kind.EXTRA_LEFT));
+            result.add(new LaneAdder(getModel().getFromEnd(), Lane.Kind.EXTRA_RIGHT));
+        }
+        
+        return result;
+    }
+    
+    private List<Extender> extenders(Road.End end) {
+        if (!end.isExtendable()) {
+            return Collections.emptyList();
+        }
+        
+        final List<Extender> result = new ArrayList<Extender>();
+        
+        final Node n = end.getJunction().getNode();
+        for (Way w : OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)) {
+            if (w.getNodesCount() > 1 && !end.getWay().equals(w) && w.isFirstLastNode(n) && Utils.isRoad(w)) {
+                final Node nextNode = w.firstNode().equals(n) ? w.getNode(1) : w.getNode(w.getNodesCount() - 2);
+                final Point2D nextNodeLoc = getContainer().translateAndScale(loc(nextNode));
+                result.add(new Extender(end, w, angle(a.getPoint(), nextNodeLoc)));
+            }
+        }
+        
+        return result;
+    }
+    
+    public Road getModel() {
+        return road;
+    }
+    
+    public IncomingConnector getConnector(Road.End end) {
+        return end.isFromEnd() ? incomingA : incomingB;
+    }
+    
+    private List<InteractiveElement> paintLanes(Graphics2D g2d) {
+        final Path2D middleLines = new Path2D.Double();
+        
+        g2d.setStroke(regularStroke);
+        
+        final boolean forward = !incomingA.getLanes().isEmpty();
+        final boolean backward = !incomingB.getLanes().isEmpty();
+        
+        final Path2D middleArea;
+        if (forward && backward) {
+            paintLanes(g2d, middleLines, true);
+            paintLanes(g2d, middleLines, false);
+            
+            middleLines.closePath();
+            middleArea = middleLines;
+            g2d.setColor(new Color(160, 160, 160));
+        } else if (forward || backward) {
+            paintLanes(g2d, middleLines, forward);
+            
+            middleArea = new Path2D.Double();
+            middleArea.append(middleLines.getPathIterator(null), false);
+            middleArea.append(middlePath(backward).offset(outerMargin, -1, -1, outerMargin).getIterator(), true);
+            middleArea.closePath();
+            g2d.setColor(Color.GRAY);
+        } else {
+            throw new AssertionError();
+        }
+        
+        g2d.fill(middleArea);
+        g2d.setColor(Color.WHITE);
+        g2d.draw(middleLines);
+        
+        final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
+        
+        moveIncoming(getModel().getFromEnd());
+        moveIncoming(getModel().getToEnd());
+        result.add(incomingA);
+        result.add(incomingB);
+        
+        for (IncomingConnector c : Arrays.asList(incomingA, incomingB)) {
+            int offset = 0;
+            for (LaneGui l : c.getLanes()) {
+                moveOutgoing(l, offset++);
+                
+                result.add(l.outgoing);
+                if (l.getModel().isExtra()) {
+                    result.add(l.lengthSlider);
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    private void paintLanes(Graphics2D g2d, Path2D middleLines, boolean forward) {
+        final Shape clip = clip();
+        g2d.clip(clip);
+        
+        final Path middle = middlePath(forward);
+        
+        Path innerPath = middle.offset(innerMargin, -1, -1, innerMargin);
+        final List<Path> linePaths = new ArrayList<Path>();
+        linePaths.add(innerPath);
+        
+        for (LaneGui l : forward ? incomingA.getLanes() : incomingB.getLanes()) {
+            l.setClip(clip);
+            innerPath = l.recalculate(innerPath, middleLines);
+            linePaths.add(innerPath);
+        }
+        
+        final Path2D area = new Path2D.Double();
+        area(area, middle, innerPath.offset(outerMargin, -1, -1, outerMargin));
+        g2d.setColor(Color.GRAY);
+        g2d.fill(area);
+        
+        g2d.setColor(Color.WHITE);
+        final Path2D lines = new Path2D.Double();
+        lines.append(innerPath.getIterator(), false);
+        g2d.draw(lines);
+        
+        // g2d.setColor(new Color(32, 128, 192));
+        g2d.setColor(Color.WHITE);
+        g2d.setStroke(dashedStroke);
+        for (Path p : linePaths) {
+            lines.reset();
+            lines.append(p.getIterator(), false);
+            g2d.draw(lines);
+        }
+        g2d.setStroke(regularStroke);
+        
+        // g2d.setColor(new Color(32, 128, 192));
+        // lines.reset();
+        // lines.append(middle.getIterator(), false);
+        // g2d.draw(lines);
+        
+        g2d.setClip(null);
+    }
+    
+    private Shape clip() {
+        final Area clip = new Area(new Rectangle2D.Double(-100000, -100000, 200000, 200000));
+        clip.subtract(new Area(negativeClip(true)));
+        clip.subtract(new Area(negativeClip(false)));
+        
+        return clip;
+    }
+    
+    private Shape negativeClip(boolean forward) {
+        final Road.End end = forward ? getModel().getToEnd() : getModel().getFromEnd();
+        final JunctionGui j = forward ? b : a;
+        
+        final Line2D lc = getLeftCurb(end);
+        final Line2D rc = getRightCurb(end);
+        
+        final Path2D negativeClip = new Path2D.Double();
+        
+        final double d = rc.getP1().distance(j.getPoint()) + lc.getP1().distance(j.getPoint());
+        
+        final double cm = 0.01 / getContainer().getMpp(); // 1 centimeter
+        final double rca = angle(rc) + PI;
+        final double lca = angle(lc) + PI;
+        final Point2D r1 = relativePoint(relativePoint(rc.getP1(), 1, angle(lc.getP1(), rc.getP1())), cm, rca);
+        final Point2D r2 = relativePoint(r1, d, rca);
+        final Point2D l1 = relativePoint(relativePoint(lc.getP1(), 1, angle(rc.getP1(), lc.getP1())), cm, lca);
+        final Point2D l2 = relativePoint(l1, d, lca);
+        
+        negativeClip.moveTo(r1.getX(), r1.getY());
+        negativeClip.lineTo(r2.getX(), r2.getY());
+        negativeClip.lineTo(l2.getX(), l2.getY());
+        negativeClip.lineTo(l1.getX(), l1.getY());
+        negativeClip.closePath();
+        
+        return negativeClip;
+    }
+    
+    public Path getLaneMiddle(boolean forward) {
+        final Path mid = middlePath(!forward);
+        final double w = getWidth(forward ? getModel().getFromEnd() : getModel().getToEnd(), true);
+        final double o = (w - outerMargin) / 2;
+        
+        return o > 0 ? mid.offset(-o, -1, -1, -o) : mid;
+    }
+    
+    private Path middlePath(boolean forward) {
+        final Path path = forward ? Path.create(b.x, b.y) : Path.create(a.x, a.y);
+        final Segment first = forward ? segments.get(segments.size() - 1) : segments.get(0);
+        
+        return first.append(path, forward, 0);
+    }
+    
+    private void moveIncoming(Road.End end) {
+        final Point2D lc = getLeftCorner(end);
+        final Point2D rc = getRightCorner(end);
+        final Line2D cornerLine = new Line2D.Double(lc, rc);
+        
+        final double a = getAngle(end);
+        final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a);
+        
+        final Point2D i = intersection(roadLine, cornerLine);
+        // TODO fix depending on angle(i, lc)
+        final double offset = innerMargin + (getWidth(end, true) - innerMargin - outerMargin) / 2;
+        final Point2D loc = relativePoint(i, offset, angle(i, lc));
+        
+        getConnector(end).move(loc.getX(), loc.getY());
+    }
+    
+    private void moveOutgoing(LaneGui lane, int offset) {
+        final Road.End end = lane.getModel().getOutgoingRoadEnd();
+        
+        final Point2D lc = getLeftCorner(end);
+        final Point2D rc = getRightCorner(end);
+        final Line2D cornerLine = new Line2D.Double(lc, rc);
+        
+        final double a = getAngle(end);
+        final Line2D roadLine = line(getContainer().getGui(end.getJunction()).getPoint(), a);
+        
+        final Point2D i = intersection(roadLine, cornerLine);
+        // TODO fix depending on angle(i, rc)
+        final double d = innerMargin + (2 * offset + 1) * getContainer().getLaneWidth() / 2;
+        final Point2D loc = relativePoint(i, d, angle(i, rc));
+        
+        lane.outgoing.move(loc.getX(), loc.getY());
+    }
+    
+    public JunctionGui getJunction(Road.End end) {
+        if (!getModel().equals(end.getRoad())) {
+            throw new IllegalArgumentException();
+        }
+        
+        return end.isFromEnd() ? getA() : getB();
+    }
+    
+    public double getAngle(Road.End end) {
+        if (!getModel().equals(end.getRoad())) {
+            throw new IllegalArgumentException();
+        }
+        
+        if (end.isToEnd()) {
+            return segments.get(segments.size() - 1).angle;
+        } else {
+            final double angle = segments.get(0).angle;
+            return angle > PI ? angle - PI : angle + PI;
+        }
+    }
+    
+    public double getWidth(Road.End end) {
+        return getWidth(end, true) + getWidth(end, false);
+    }
+    
+    public double getLength() {
+        return length;
+    }
+    
+    public double getOffset(double x, double y) {
+        return segments.get(0).getOffset(x, y);
+    }
+    
+    public GuiContainer getContainer() {
+        return container;
+    }
+    
+    public List<LaneGui> getLanes() {
+        final List<LaneGui> result = new ArrayList<LaneGui>();
+        
+        result.addAll(incomingB.getLanes());
+        result.addAll(incomingA.getLanes());
+        
+        return Collections.unmodifiableList(result);
+    }
+    
+    public List<LaneGui> getLanes(Road.End end) {
+        return getConnector(end.getOppositeEnd()).getLanes();
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/State.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/State.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/State.java	(revision 26154)
@@ -12,133 +12,133 @@
 
 interface State {
-	public class AllTurns implements State {
-		private final State wrapped;
-		
-		public AllTurns(State wrapped) {
-			this.wrapped = wrapped;
-		}
-		
-		public State unwrap() {
-			return wrapped;
-		}
-	}
-	
-	public class Connecting implements State {
-		private final Lane lane;
-		private final List<RoadGui.ViaConnector> vias;
-		
-		public Connecting(Lane lane) {
-			this(lane, Collections.<RoadGui.ViaConnector> emptyList());
-		}
-		
-		public Connecting(Lane lane, List<ViaConnector> vias) {
-			this.lane = lane;
-			this.vias = vias;
-		}
-		
-		public Connecting next(RoadGui.ViaConnector via) {
-			if (vias.isEmpty()) {
-				return new Connecting(lane, Collections.unmodifiableList(Arrays.asList(via)));
-			}
-			
-			final List<RoadGui.ViaConnector> tmp = new ArrayList<RoadGui.ViaConnector>(vias.size() + 1);
-			final boolean even = (vias.size() & 1) == 0;
-			final RoadGui.ViaConnector last = vias.get(vias.size() - 1);
-			
-			if (last.equals(via) || !even && last.getRoadEnd().getJunction().equals(via.getRoadEnd().getJunction())) {
-				return pop().next(via);
-			}
-			
-			if (vias.size() >= 2) {
-				if (lane.getOutgoingJunction().equals(via.getRoadEnd().getJunction())) {
-					return new Connecting(lane);
-				} else if (via.equals(getBacktrackViaConnector())) {
-					return new Connecting(lane, vias.subList(0, vias.size() - 1));
-				}
-			}
-			
-			for (RoadGui.ViaConnector v : vias) {
-				tmp.add(v);
-				
-				if (!(even && v.equals(last)) && v.getRoadEnd().getJunction().equals(via.getRoadEnd().getJunction())) {
-					return new Connecting(lane, Collections.unmodifiableList(tmp));
-				}
-			}
-			
-			tmp.add(via);
-			return new Connecting(lane, Collections.unmodifiableList(tmp));
-		}
-		
-		public Junction getJunction() {
-			return vias.isEmpty() ? lane.getOutgoingJunction() : vias.get(vias.size() - 1).getRoadEnd().getJunction();
-		}
-		
-		public RoadGui.ViaConnector getBacktrackViaConnector() {
-			return vias.size() < 2 ? null : vias.get(vias.size() - 2);
-		}
-		
-		public List<RoadGui.ViaConnector> getViaConnectors() {
-			return vias;
-		}
-		
-		public Lane getLane() {
-			return lane;
-		}
-		
-		public Connecting pop() {
-			return new Connecting(lane, vias.subList(0, vias.size() - 1));
-		}
-	}
-	
-	public class Invalid implements State {
-		private final State wrapped;
-		
-		public Invalid(State wrapped) {
-			this.wrapped = wrapped;
-		}
-		
-		public State unwrap() {
-			return wrapped;
-		}
-	}
-	
-	public class Dirty implements State {
-		private final State wrapped;
-		
-		public Dirty(State wrapped) {
-			this.wrapped = wrapped;
-		}
-		
-		public State unwrap() {
-			return wrapped;
-		}
-	}
-	
-	class Default implements State {
-		public Default() {}
-	}
-	
-	class IncomingActive implements State {
-		private final Road.End roadEnd;
-		
-		public IncomingActive(Road.End roadEnd) {
-			this.roadEnd = roadEnd;
-		}
-		
-		public Road.End getRoadEnd() {
-			return roadEnd;
-		}
-	}
-	
-	class OutgoingActive implements State {
-		private final LaneGui lane;
-		
-		public OutgoingActive(LaneGui lane) {
-			this.lane = lane;
-		}
-		
-		public LaneGui getLane() {
-			return lane;
-		}
-	}
+    public class AllTurns implements State {
+        private final State wrapped;
+        
+        public AllTurns(State wrapped) {
+            this.wrapped = wrapped;
+        }
+        
+        public State unwrap() {
+            return wrapped;
+        }
+    }
+    
+    public class Connecting implements State {
+        private final Lane lane;
+        private final List<RoadGui.ViaConnector> vias;
+        
+        public Connecting(Lane lane) {
+            this(lane, Collections.<RoadGui.ViaConnector> emptyList());
+        }
+        
+        public Connecting(Lane lane, List<ViaConnector> vias) {
+            this.lane = lane;
+            this.vias = vias;
+        }
+        
+        public Connecting next(RoadGui.ViaConnector via) {
+            if (vias.isEmpty()) {
+                return new Connecting(lane, Collections.unmodifiableList(Arrays.asList(via)));
+            }
+            
+            final List<RoadGui.ViaConnector> tmp = new ArrayList<RoadGui.ViaConnector>(vias.size() + 1);
+            final boolean even = (vias.size() & 1) == 0;
+            final RoadGui.ViaConnector last = vias.get(vias.size() - 1);
+            
+            if (last.equals(via) || !even && last.getRoadEnd().getJunction().equals(via.getRoadEnd().getJunction())) {
+                return pop().next(via);
+            }
+            
+            if (vias.size() >= 2) {
+                if (lane.getOutgoingJunction().equals(via.getRoadEnd().getJunction())) {
+                    return new Connecting(lane);
+                } else if (via.equals(getBacktrackViaConnector())) {
+                    return new Connecting(lane, vias.subList(0, vias.size() - 1));
+                }
+            }
+            
+            for (RoadGui.ViaConnector v : vias) {
+                tmp.add(v);
+                
+                if (!(even && v.equals(last)) && v.getRoadEnd().getJunction().equals(via.getRoadEnd().getJunction())) {
+                    return new Connecting(lane, Collections.unmodifiableList(tmp));
+                }
+            }
+            
+            tmp.add(via);
+            return new Connecting(lane, Collections.unmodifiableList(tmp));
+        }
+        
+        public Junction getJunction() {
+            return vias.isEmpty() ? lane.getOutgoingJunction() : vias.get(vias.size() - 1).getRoadEnd().getJunction();
+        }
+        
+        public RoadGui.ViaConnector getBacktrackViaConnector() {
+            return vias.size() < 2 ? null : vias.get(vias.size() - 2);
+        }
+        
+        public List<RoadGui.ViaConnector> getViaConnectors() {
+            return vias;
+        }
+        
+        public Lane getLane() {
+            return lane;
+        }
+        
+        public Connecting pop() {
+            return new Connecting(lane, vias.subList(0, vias.size() - 1));
+        }
+    }
+    
+    public class Invalid implements State {
+        private final State wrapped;
+        
+        public Invalid(State wrapped) {
+            this.wrapped = wrapped;
+        }
+        
+        public State unwrap() {
+            return wrapped;
+        }
+    }
+    
+    public class Dirty implements State {
+        private final State wrapped;
+        
+        public Dirty(State wrapped) {
+            this.wrapped = wrapped;
+        }
+        
+        public State unwrap() {
+            return wrapped;
+        }
+    }
+    
+    class Default implements State {
+        public Default() {}
+    }
+    
+    class IncomingActive implements State {
+        private final Road.End roadEnd;
+        
+        public IncomingActive(Road.End roadEnd) {
+            this.roadEnd = roadEnd;
+        }
+        
+        public Road.End getRoadEnd() {
+            return roadEnd;
+        }
+    }
+    
+    class OutgoingActive implements State {
+        private final LaneGui lane;
+        
+        public OutgoingActive(LaneGui lane) {
+            this.lane = lane;
+        }
+        
+        public LaneGui getLane() {
+            return lane;
+        }
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/TurnLanesDialog.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/TurnLanesDialog.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/TurnLanesDialog.java	(revision 26154)
@@ -18,4 +18,5 @@
 
 import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.SelectionChangedListener;
 import org.openstreetmap.josm.data.osm.DataSet;
@@ -27,105 +28,117 @@
 
 public class TurnLanesDialog extends ToggleDialog {
-	private final Action editAction = new JosmAction(tr("Edit"), "dialogs/edit",
-	    tr("Edit turn relations and lane lengths for selected node."), null, true) {
-		
-		private static final long serialVersionUID = 4114119073563457706L;
-		
-		@Override
-		public void actionPerformed(ActionEvent e) {
-			final CardLayout cl = (CardLayout) body.getLayout();
-			cl.show(body, CARD_EDIT);
-			editing = true;
-		}
-	};
-	private final Action validateAction = new JosmAction(tr("Validate"), "dialogs/validator",
-	    tr("Validate turn- and lane-length-relations for consistency."), null, true) {
-		
-		private static final long serialVersionUID = 7510740945725851427L;
-		
-		@Override
-		public void actionPerformed(ActionEvent e) {
-			final CardLayout cl = (CardLayout) body.getLayout();
-			cl.show(body, CARD_VALIDATE);
-			editing = false;
-		}
-	};
-	
-	private static final long serialVersionUID = -1998375221636611358L;
-	
-	private static final String CARD_EDIT = "EDIT";
-	private static final String CARD_VALIDATE = "VALIDATE";
-	private static final String CARD_ERROR = "ERROR";
-	
-	private final JPanel body = new JPanel();
-	private final JunctionPane junctionPane = new JunctionPane(null);
-	private final JLabel error = new JLabel();
-	
-	private boolean editing = true;
-	
-	public TurnLanesDialog() {
-		super(tr("Turn Lanes"), "turnlanes.png", tr("Edit turn lanes"), null, 200);
-		
-		DataSet.addSelectionListener(new SelectionChangedListener() {
-			@Override
-			public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
-				final Collection<OsmPrimitive> s = Collections.unmodifiableCollection(newSelection);
-				final List<Node> nodes = OsmPrimitive.getFilteredList(s, Node.class);
-				final List<Way> ways = OsmPrimitive.getFilteredList(s, Way.class);
-				
-				if (nodes.isEmpty()) {
-					setJunction(null);
-					return;
-				}
-				
-				try {
-					setJunction(ModelContainer.create(nodes, ways));
-				} catch (RuntimeException e) {
-					displayError(e);
-					return;
-				}
-			}
-		});
-		
-		final JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 4, 4));
-		final ButtonGroup group = new ButtonGroup();
-		final JToggleButton editButton = new JToggleButton(editAction);
-		final JToggleButton validateButton = new JToggleButton(validateAction);
-		group.add(editButton);
-		group.add(validateButton);
-		buttonPanel.add(editButton);
-		buttonPanel.add(validateButton);
-		
-		body.setLayout(new CardLayout(4, 4));
-		
-		add(buttonPanel, BorderLayout.SOUTH);
-		add(body, BorderLayout.CENTER);
-		
-		body.add(junctionPane, CARD_EDIT);
-		body.add(new ValidationPanel(), CARD_VALIDATE);
-		body.add(error, CARD_ERROR);
-		
-		editButton.doClick();
-	}
-	
-	void displayError(RuntimeException e) {
-		if (editing) {
-			e.printStackTrace();
-			
-			error.setText("<html>An error occured while constructing the model."
-			    + " Please run the validator to make sure the data is consistent.<br><br>Error: " + e.getMessage()
-			    + "</html>");
-			
-			final CardLayout cl = (CardLayout) body.getLayout();
-			cl.show(body, CARD_ERROR);
-		}
-	}
-	
-	void setJunction(ModelContainer mc) {
-		if (mc != null && editing) {
-			junctionPane.setJunction(new GuiContainer(mc));
-			final CardLayout cl = (CardLayout) body.getLayout();
-			cl.show(body, CARD_EDIT);
-		}
-	}
+    private class EditAction extends JosmAction {
+        private static final long serialVersionUID = 4114119073563457706L;
+        public EditAction() {
+            super(tr("Edit"), "dialogs/edit",
+            tr("Edit turn relations and lane lengths for selected node."), null, false);
+            putValue("toolbar", "turnlanes/edit");
+            Main.toolbar.register(this);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            final CardLayout cl = (CardLayout) body.getLayout();
+            cl.show(body, CARD_EDIT);
+            editing = true;
+        }
+    }
+
+    private class ValidateAction extends JosmAction {
+        private static final long serialVersionUID = 7510740945725851427L;
+        public ValidateAction() {
+            super(tr("Validate"), "dialogs/validator",
+            tr("Validate turn- and lane-length-relations for consistency."), null, false);
+            putValue("toolbar", "turnlanes/validate");
+            Main.toolbar.register(this);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            final CardLayout cl = (CardLayout) body.getLayout();
+            cl.show(body, CARD_VALIDATE);
+            editing = false;
+        }
+    }
+
+    private final Action editAction = new EditAction();
+    private final Action validateAction = new ValidateAction();
+
+    private static final long serialVersionUID = -1998375221636611358L;
+
+    private static final String CARD_EDIT = "EDIT";
+    private static final String CARD_VALIDATE = "VALIDATE";
+    private static final String CARD_ERROR = "ERROR";
+
+    private final JPanel body = new JPanel();
+    private final JunctionPane junctionPane = new JunctionPane(null);
+    private final JLabel error = new JLabel();
+
+    private boolean editing = true;
+
+    public TurnLanesDialog() {
+        super(tr("Turn Lanes"), "turnlanes.png", tr("Edit turn lanes"), null, 200);
+
+        DataSet.addSelectionListener(new SelectionChangedListener() {
+            @Override
+            public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
+                final Collection<OsmPrimitive> s = Collections.unmodifiableCollection(newSelection);
+                final List<Node> nodes = OsmPrimitive.getFilteredList(s, Node.class);
+                final List<Way> ways = OsmPrimitive.getFilteredList(s, Way.class);
+
+                if (nodes.isEmpty()) {
+                    setJunction(null);
+                    return;
+                }
+
+                try {
+                    setJunction(ModelContainer.create(nodes, ways));
+                } catch (RuntimeException e) {
+                    displayError(e);
+                    return;
+                }
+            }
+        });
+
+        final JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 4, 4));
+        final ButtonGroup group = new ButtonGroup();
+        final JToggleButton editButton = new JToggleButton(editAction);
+        final JToggleButton validateButton = new JToggleButton(validateAction);
+        group.add(editButton);
+        group.add(validateButton);
+        buttonPanel.add(editButton);
+        buttonPanel.add(validateButton);
+
+        body.setLayout(new CardLayout(4, 4));
+
+        add(buttonPanel, BorderLayout.SOUTH);
+        add(body, BorderLayout.CENTER);
+
+        body.add(junctionPane, CARD_EDIT);
+        body.add(new ValidationPanel(), CARD_VALIDATE);
+        body.add(error, CARD_ERROR);
+
+        editButton.doClick();
+    }
+
+    void displayError(RuntimeException e) {
+        if (editing) {
+            e.printStackTrace();
+
+            error.setText("<html>An error occured while constructing the model."
+                + " Please run the validator to make sure the data is consistent.<br><br>Error: " + e.getMessage()
+                + "</html>");
+
+            final CardLayout cl = (CardLayout) body.getLayout();
+            cl.show(body, CARD_ERROR);
+        }
+    }
+
+    void setJunction(ModelContainer mc) {
+        if (mc != null && editing) {
+            junctionPane.setJunction(new GuiContainer(mc));
+            final CardLayout cl = (CardLayout) body.getLayout();
+            cl.show(body, CARD_EDIT);
+        }
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ValidationPanel.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ValidationPanel.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/ValidationPanel.java	(revision 26154)
@@ -25,115 +25,115 @@
 
 class ValidationPanel extends JPanel {
-	private static final long serialVersionUID = -1585778734201458665L;
-	
-	private static final String[] COLUMN_NAMES = {
-	    tr("Description"), tr("Type"), tr("Quick-Fix")
-	};
-	
-	private final Action refreshAction = new JosmAction(tr("Refresh"), "dialogs/refresh",
-	    tr("Revalidate all turnlanes-relations."), null, false) {
-		private static final long serialVersionUID = -8110599654128234810L;
-		
-		@Override
-		public void actionPerformed(ActionEvent e) {
-			setIssues(new Validator().validate(Main.main.getCurrentDataSet()));
-		}
-	};
-	
-	private final Action fixAction = new JosmAction(tr("Fix"), "dialogs/fix", tr("Automatically fixes the issue."), null,
-	    false) {
-		private static final long serialVersionUID = -8110599654128234810L;
-		
-		@Override
-		public void actionPerformed(ActionEvent e) {
-			if (selected.getQuickFix().perform()) {
-				final int i = issues.indexOf(selected);
-				issueModel.removeRow(i);
-				issues.remove(i);
-			}
-		}
-	};
-	
-	private final Action selectAction = new JosmAction(tr("Select"), "dialogs/select",
-	    tr("Selects the offending relation."), null, false) {
-		private static final long serialVersionUID = -8110599654128234810L;
-		
-		@Override
-		public void actionPerformed(ActionEvent e) {
-			if (selected.getRelation() == null) {
-				Main.main.getCurrentDataSet().setSelected(selected.getPrimitives());
-			} else {
-				Main.main.getCurrentDataSet().setSelected(selected.getRelation());
-			}
-		}
-	};
-	
-	private final SideButton refreshButton = new SideButton(refreshAction);
-	private final SideButton fixButton = new SideButton(fixAction);
-	private final SideButton selectButton = new SideButton(selectAction);
-	
-	private final DefaultTableModel issueModel = new DefaultTableModel(COLUMN_NAMES, 0);
-	private final List<Issue> issues = new ArrayList<Issue>();
-	private final JTable issueTable = new JTable(issueModel) {
-		private static final long serialVersionUID = 6323348290180585298L;
-		
-		public boolean isCellEditable(int row, int column) {
-			return false;
-		};
-	};
-	
-	private Issue selected;
-	
-	public ValidationPanel() {
-		super(new BorderLayout(4, 4));
-		
-		final JPanel buttonPanel = new JPanel(new GridLayout(1, 3, 4, 4));
-		
-		buttonPanel.add(refreshButton);
-		buttonPanel.add(fixButton);
-		buttonPanel.add(selectButton);
-		
-		add(buttonPanel, BorderLayout.NORTH);
-		add(new JScrollPane(issueTable), BorderLayout.CENTER);
-		
-		issueTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-		issueTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
-			@Override
-			public void valueChanged(ListSelectionEvent e) {
-				final int i = issueTable.getSelectedRow();
-				final Issue issue = i >= 0 ? issues.get(i) : null;
-				
-				setSelected(issue);
-			}
-		});
-		
-		setSelected(null);
-	}
-	
-	private void setIssues(List<Issue> issues) {
-		issueModel.setRowCount(0);
-		this.issues.clear();
-		
-		for (Issue i : issues) {
-			final String[] row = {
-			    i.getDescription(), //
-			    i.getRelation() == null ? tr("(none)") : i.getRelation().get("type"), //
-			    i.getQuickFix().getDescription()
-			};
-			issueModel.addRow(row);
-			this.issues.add(i);
-		}
-	}
-	
-	private void setSelected(Issue selected) {
-		this.selected = selected;
-		
-		if (selected == null) {
-			fixButton.setEnabled(false);
-			selectButton.setEnabled(false);
-		} else {
-			fixButton.setEnabled(selected.getQuickFix() != Issue.QuickFix.NONE);
-			selectButton.setEnabled(true);
-		}
-	}
+    private static final long serialVersionUID = -1585778734201458665L;
+    
+    private static final String[] COLUMN_NAMES = {
+        tr("Description"), tr("Type"), tr("Quick-Fix")
+    };
+    
+    private final Action refreshAction = new JosmAction(tr("Refresh"), "dialogs/refresh",
+        tr("Revalidate all turnlanes-relations."), null, false) {
+        private static final long serialVersionUID = -8110599654128234810L;
+        
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            setIssues(new Validator().validate(Main.main.getCurrentDataSet()));
+        }
+    };
+    
+    private final Action fixAction = new JosmAction(tr("Fix"), "dialogs/fix", tr("Automatically fixes the issue."), null,
+        false) {
+        private static final long serialVersionUID = -8110599654128234810L;
+        
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            if (selected.getQuickFix().perform()) {
+                final int i = issues.indexOf(selected);
+                issueModel.removeRow(i);
+                issues.remove(i);
+            }
+        }
+    };
+    
+    private final Action selectAction = new JosmAction(tr("Select"), "dialogs/select",
+        tr("Selects the offending relation."), null, false) {
+        private static final long serialVersionUID = -8110599654128234810L;
+        
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            if (selected.getRelation() == null) {
+                Main.main.getCurrentDataSet().setSelected(selected.getPrimitives());
+            } else {
+                Main.main.getCurrentDataSet().setSelected(selected.getRelation());
+            }
+        }
+    };
+    
+    private final SideButton refreshButton = new SideButton(refreshAction);
+    private final SideButton fixButton = new SideButton(fixAction);
+    private final SideButton selectButton = new SideButton(selectAction);
+    
+    private final DefaultTableModel issueModel = new DefaultTableModel(COLUMN_NAMES, 0);
+    private final List<Issue> issues = new ArrayList<Issue>();
+    private final JTable issueTable = new JTable(issueModel) {
+        private static final long serialVersionUID = 6323348290180585298L;
+        
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        };
+    };
+    
+    private Issue selected;
+    
+    public ValidationPanel() {
+        super(new BorderLayout(4, 4));
+        
+        final JPanel buttonPanel = new JPanel(new GridLayout(1, 3, 4, 4));
+        
+        buttonPanel.add(refreshButton);
+        buttonPanel.add(fixButton);
+        buttonPanel.add(selectButton);
+        
+        add(buttonPanel, BorderLayout.NORTH);
+        add(new JScrollPane(issueTable), BorderLayout.CENTER);
+        
+        issueTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        issueTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+            @Override
+            public void valueChanged(ListSelectionEvent e) {
+                final int i = issueTable.getSelectedRow();
+                final Issue issue = i >= 0 ? issues.get(i) : null;
+                
+                setSelected(issue);
+            }
+        });
+        
+        setSelected(null);
+    }
+    
+    private void setIssues(List<Issue> issues) {
+        issueModel.setRowCount(0);
+        this.issues.clear();
+        
+        for (Issue i : issues) {
+            final String[] row = {
+                i.getDescription(), //
+                i.getRelation() == null ? tr("(none)") : i.getRelation().get("type"), //
+                i.getQuickFix().getDescription()
+            };
+            issueModel.addRow(row);
+            this.issues.add(i);
+        }
+    }
+    
+    private void setSelected(Issue selected) {
+        this.selected = selected;
+        
+        if (selected == null) {
+            fixButton.setEnabled(false);
+            selectButton.setEnabled(false);
+        } else {
+            fixButton.setEnabled(selected.getQuickFix() != Issue.QuickFix.NONE);
+            selectButton.setEnabled(true);
+        }
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Constants.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Constants.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Constants.java	(revision 26154)
@@ -4,23 +4,23 @@
 
 public interface Constants {
-	String SEPARATOR = ";";
-	String SPLIT_REGEX = "\\p{Zs}*[,:;]\\p{Zs}*";
-	Pattern SPLIT_PATTERN = Pattern.compile(SPLIT_REGEX);
-	
-	String TYPE_LENGTHS = "turnlanes:lengths";
-	
-	String LENGTHS_KEY_LENGTHS_LEFT = "lengths:left";
-	String LENGTHS_KEY_LENGTHS_RIGHT = "lengths:right";
-	
-	String TYPE_TURNS = "turnlanes:turns";
-	
-	String TURN_ROLE_VIA = "via";
-	String TURN_ROLE_FROM = "from";
-	String TURN_ROLE_TO = "to";
-	
-	String TURN_KEY_LANES = "lanes";
-	String TURN_KEY_EXTRA_LANES = "lanes:extra";
-	String LENGTHS_ROLE_END = "end";
-	String LENGTHS_ROLE_WAYS = "ways";
-	
+    String SEPARATOR = ";";
+    String SPLIT_REGEX = "\\p{Zs}*[,:;]\\p{Zs}*";
+    Pattern SPLIT_PATTERN = Pattern.compile(SPLIT_REGEX);
+    
+    String TYPE_LENGTHS = "turnlanes:lengths";
+    
+    String LENGTHS_KEY_LENGTHS_LEFT = "lengths:left";
+    String LENGTHS_KEY_LENGTHS_RIGHT = "lengths:right";
+    
+    String TYPE_TURNS = "turnlanes:turns";
+    
+    String TURN_ROLE_VIA = "via";
+    String TURN_ROLE_FROM = "from";
+    String TURN_ROLE_TO = "to";
+    
+    String TURN_KEY_LANES = "lanes";
+    String TURN_KEY_EXTRA_LANES = "lanes:extra";
+    String LENGTHS_ROLE_END = "end";
+    String LENGTHS_ROLE_WAYS = "ways";
+    
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Issue.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Issue.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Issue.java	(revision 26154)
@@ -12,86 +12,86 @@
 
 public class Issue {
-	public enum Severity {
-		INFO,
-		WARN,
-		ERROR;
-	}
-	
-	public static abstract class QuickFix {
-		public static final QuickFix NONE = new QuickFix(tr("None")) {
-			
-			@Override
-			public boolean perform() {
-				throw new UnsupportedOperationException("Don't call perform on Issue.QuickFix.NONE.");
-			}
-		};
-		
-		private final String description;
-		
-		public QuickFix(String description) {
-			this.description = description;
-		}
-		
-		public String getDescription() {
-			return description;
-		}
-		
-		public abstract boolean perform();
-	}
-	
-	private final Severity severity;
-	private final Relation relation;
-	private final List<OsmPrimitive> primitives;
-	private final String description;
-	private final QuickFix quickFix;
-	
-	private Issue(Severity severity, Relation relation, List<? extends OsmPrimitive> primitives, String description,
-	    QuickFix quickFix) {
-		this.relation = relation;
-		this.primitives = Collections.unmodifiableList(new ArrayList<OsmPrimitive>(primitives));
-		this.severity = severity;
-		this.description = description;
-		this.quickFix = quickFix;
-	}
-	
-	public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description,
-	    QuickFix quickFix) {
-		return new Issue(Severity.ERROR, relation, primitives, description, quickFix);
-	}
-	
-	public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description) {
-		return newError(relation, primitives, description, QuickFix.NONE);
-	}
-	
-	public static Issue newError(Relation relation, OsmPrimitive primitive, String description) {
-		return newError(relation, Arrays.asList(primitive), description, QuickFix.NONE);
-	}
-	
-	public static Issue newError(Relation relation, String description) {
-		return newError(relation, Collections.<OsmPrimitive> emptyList(), description, QuickFix.NONE);
-	}
-	
-	public static Issue newWarning(List<OsmPrimitive> primitives, String description) {
-		return new Issue(Severity.WARN, null, primitives, description, QuickFix.NONE);
-	}
-	
-	public Severity getSeverity() {
-		return severity;
-	}
-	
-	public String getDescription() {
-		return description;
-	}
-	
-	public Relation getRelation() {
-		return relation;
-	}
-	
-	public List<OsmPrimitive> getPrimitives() {
-		return primitives;
-	}
-	
-	public QuickFix getQuickFix() {
-		return quickFix;
-	}
+    public enum Severity {
+        INFO,
+        WARN,
+        ERROR;
+    }
+    
+    public static abstract class QuickFix {
+        public static final QuickFix NONE = new QuickFix(tr("None")) {
+            
+            @Override
+            public boolean perform() {
+                throw new UnsupportedOperationException("Don't call perform on Issue.QuickFix.NONE.");
+            }
+        };
+        
+        private final String description;
+        
+        public QuickFix(String description) {
+            this.description = description;
+        }
+        
+        public String getDescription() {
+            return description;
+        }
+        
+        public abstract boolean perform();
+    }
+    
+    private final Severity severity;
+    private final Relation relation;
+    private final List<OsmPrimitive> primitives;
+    private final String description;
+    private final QuickFix quickFix;
+    
+    private Issue(Severity severity, Relation relation, List<? extends OsmPrimitive> primitives, String description,
+        QuickFix quickFix) {
+        this.relation = relation;
+        this.primitives = Collections.unmodifiableList(new ArrayList<OsmPrimitive>(primitives));
+        this.severity = severity;
+        this.description = description;
+        this.quickFix = quickFix;
+    }
+    
+    public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description,
+        QuickFix quickFix) {
+        return new Issue(Severity.ERROR, relation, primitives, description, quickFix);
+    }
+    
+    public static Issue newError(Relation relation, List<? extends OsmPrimitive> primitives, String description) {
+        return newError(relation, primitives, description, QuickFix.NONE);
+    }
+    
+    public static Issue newError(Relation relation, OsmPrimitive primitive, String description) {
+        return newError(relation, Arrays.asList(primitive), description, QuickFix.NONE);
+    }
+    
+    public static Issue newError(Relation relation, String description) {
+        return newError(relation, Collections.<OsmPrimitive> emptyList(), description, QuickFix.NONE);
+    }
+    
+    public static Issue newWarning(List<OsmPrimitive> primitives, String description) {
+        return new Issue(Severity.WARN, null, primitives, description, QuickFix.NONE);
+    }
+    
+    public Severity getSeverity() {
+        return severity;
+    }
+    
+    public String getDescription() {
+        return description;
+    }
+    
+    public Relation getRelation() {
+        return relation;
+    }
+    
+    public List<OsmPrimitive> getPrimitives() {
+        return primitives;
+    }
+    
+    public QuickFix getQuickFix() {
+        return quickFix;
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Junction.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Junction.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Junction.java	(revision 26154)
@@ -10,79 +10,79 @@
 
 public class Junction {
-	private final ModelContainer container;
-	
-	private final Node node;
-	private final Set<Way> roads = new HashSet<Way>();
-	
-	Junction(ModelContainer container, Node n) {
-		this.container = container;
-		this.node = n;
-		
-		container.register(this);
-		
-		if (isPrimary()) {
-			// if turn data is invalid, this will force an exception now, not later during painting
-			// getTurns(); TODO force this again
-		}
-	}
-	
-	public boolean isPrimary() {
-		return getContainer().isPrimary(this);
-	}
-	
-	public Node getNode() {
-		return node;
-	}
-	
-	public List<Road> getRoads() {
-		final List<Road> result = new ArrayList<Road>(roads.size());
-		
-		for (Way w : roads) {
-			result.add(container.getRoad(w));
-		}
-		
-		return result;
-	}
-	
-	public List<Road.End> getRoadEnds() {
-		final List<Road.End> result = new ArrayList<Road.End>(roads.size());
-		
-		for (Way w : roads) {
-			result.add(getRoadEnd(w));
-		}
-		
-		return result;
-	}
-	
-	void addRoad(Way w) {
-		roads.add(w);
-	}
-	
-	Road.End getRoadEnd(Way w) {
-		final Road r = getContainer().getRoad(w);
-		
-		if (r.getRoute().getSegments().size() == 1) {
-			final boolean starts = r.getRoute().getStart().equals(node);
-			final boolean ends = r.getRoute().getEnd().equals(node);
-			
-			if (starts && ends) {
-				throw new IllegalArgumentException("Ambiguous: The way starts and ends at the junction node.");
-			} else if (starts) {
-				return r.getFromEnd();
-			} else if (ends) {
-				return r.getToEnd();
-			}
-		} else if (r.getRoute().getFirstSegment().getWay().equals(w)) {
-			return r.getFromEnd();
-		} else if (r.getRoute().getLastSegment().getWay().equals(w)) {
-			return r.getToEnd();
-		}
-		
-		throw new IllegalArgumentException("While there exists a road for the given way, the way neither "
-		    + "starts nor ends at the junction node.");
-	}
-	
-	public ModelContainer getContainer() {
-		return container;
-	}
+    private final ModelContainer container;
+    
+    private final Node node;
+    private final Set<Way> roads = new HashSet<Way>();
+    
+    Junction(ModelContainer container, Node n) {
+        this.container = container;
+        this.node = n;
+        
+        container.register(this);
+        
+        if (isPrimary()) {
+            // if turn data is invalid, this will force an exception now, not later during painting
+            // getTurns(); TODO force this again
+        }
+    }
+    
+    public boolean isPrimary() {
+        return getContainer().isPrimary(this);
+    }
+    
+    public Node getNode() {
+        return node;
+    }
+    
+    public List<Road> getRoads() {
+        final List<Road> result = new ArrayList<Road>(roads.size());
+        
+        for (Way w : roads) {
+            result.add(container.getRoad(w));
+        }
+        
+        return result;
+    }
+    
+    public List<Road.End> getRoadEnds() {
+        final List<Road.End> result = new ArrayList<Road.End>(roads.size());
+        
+        for (Way w : roads) {
+            result.add(getRoadEnd(w));
+        }
+        
+        return result;
+    }
+    
+    void addRoad(Way w) {
+        roads.add(w);
+    }
+    
+    Road.End getRoadEnd(Way w) {
+        final Road r = getContainer().getRoad(w);
+        
+        if (r.getRoute().getSegments().size() == 1) {
+            final boolean starts = r.getRoute().getStart().equals(node);
+            final boolean ends = r.getRoute().getEnd().equals(node);
+            
+            if (starts && ends) {
+                throw new IllegalArgumentException("Ambiguous: The way starts and ends at the junction node.");
+            } else if (starts) {
+                return r.getFromEnd();
+            } else if (ends) {
+                return r.getToEnd();
+            }
+        } else if (r.getRoute().getFirstSegment().getWay().equals(w)) {
+            return r.getFromEnd();
+        } else if (r.getRoute().getLastSegment().getWay().equals(w)) {
+            return r.getToEnd();
+        }
+        
+        throw new IllegalArgumentException("While there exists a road for the given way, the way neither "
+            + "starts nor ends at the junction node.");
+    }
+    
+    public ModelContainer getContainer() {
+        return container;
+    }
 }
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 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Lane.java	(revision 26154)
@@ -12,208 +12,208 @@
 
 public class Lane {
-	public enum Kind {
-		EXTRA_LEFT,
-		EXTRA_RIGHT,
-		REGULAR;
-		
-		public boolean isExtra() {
-			return this == EXTRA_LEFT || this == EXTRA_RIGHT;
-		}
-	}
-	
-	static List<Lane> load(Road.End roadEnd) {
-		final List<Lane> result = new ArrayList<Lane>();
-		int i;
-		
-		i = 0;
-		for (double l : CollectionUtils.reverse(roadEnd.getLengths(Kind.EXTRA_LEFT))) {
-			result.add(new Lane(roadEnd, --i, Kind.EXTRA_LEFT, l));
-		}
-		
-		final int regulars = getRegularCount(roadEnd.getWay(), roadEnd.getJunction().getNode());
-		for (i = 1; i <= regulars; ++i) {
-			result.add(new Lane(roadEnd, i));
-		}
-		
-		i = 0;
-		for (double l : roadEnd.getLengths(Kind.EXTRA_RIGHT)) {
-			result.add(new Lane(roadEnd, ++i, Kind.EXTRA_RIGHT, l));
-		}
-		
-		return result;
-	}
-	
-	static List<Double> loadLengths(Relation r, String key, double lengthBound) {
-		final List<Double> result = new ArrayList<Double>();
-		
-		if (r != null && r.get(key) != null) {
-			for (String s : Constants.SPLIT_PATTERN.split(r.get(key))) {
-				// TODO what should the exact input be (there should probably be
-				// a unit (m))
-				final Double length = Double.parseDouble(s.trim());
-				
-				if (length > lengthBound) {
-					result.add(length);
-				}
-			}
-		}
-		
-		return result;
-	}
-	
-	private static int getCount(Way w) {
-		final String countStr = w.get("lanes");
-		
-		if (countStr != null) {
-			try {
-				return Integer.parseInt(countStr);
-			} catch (NumberFormatException e) {
-				throw UnexpectedDataException.Kind.INVALID_TAG_FORMAT.chuck("lanes", countStr);
-			}
-		}
-		
-		throw UnexpectedDataException.Kind.MISSING_TAG.chuck("lanes");
-	}
-	
-	static int getRegularCount(Way w, Node end) {
-		final int count = getCount(w);
-		
-		if (w.hasDirectionKeys()) {
-			// TODO check for oneway=-1
-			if (w.lastNode().equals(end)) {
-				return count;
-			} else {
-				return 0;
-			}
-		} else {
-			if (w.lastNode().equals(end)) {
-				return (count + 1) / 2; // round up in direction of end
-			} else {
-				return count / 2; // round down in other direction
-			}
-		}
-	}
-	
-	private final Road.End roadEnd;
-	private final int index;
-	private final Kind kind;
-	
-	private double length = -1;
-	
-	public Lane(Road.End roadEnd, int index) {
-		this.roadEnd = roadEnd;
-		this.index = index;
-		this.kind = Kind.REGULAR;
-	}
-	
-	public Lane(Road.End roadEnd, int index, Kind kind, double length) {
-		assert kind == Kind.EXTRA_LEFT || kind == Kind.EXTRA_RIGHT;
-		
-		this.roadEnd = roadEnd;
-		this.index = index;
-		this.kind = kind;
-		this.length = length;
-		
-		if (length <= 0) {
-			throw new IllegalArgumentException("Length must be positive");
-		}
-	}
-	
-	public Road getRoad() {
-		return roadEnd.getRoad();
-	}
-	
-	public Kind getKind() {
-		return kind;
-	}
-	
-	public double getLength() {
-		return isExtra() ? length : getRoad().getLength();
-	}
-	
-	public void setLength(double length) {
-		if (!isExtra()) {
-			throw new UnsupportedOperationException("Length can only be set for extra lanes.");
-		} else if (length <= 0) {
-			throw new IllegalArgumentException("Length must positive.");
-		}
-		
-		// TODO if needed, increase length of other lanes
-		getOutgoingRoadEnd().updateLengths();
-		
-		this.length = length;
-	}
-	
-	public boolean isExtra() {
-		return getKind() != Kind.REGULAR;
-	}
-	
-	public int getIndex() {
-		return index;
-	}
-	
-	public Junction getOutgoingJunction() {
-		return getOutgoingRoadEnd().getJunction();
-	}
-	
-	public Junction getIncomingJunction() {
-		return getIncomingRoadEnd().getJunction();
-	}
-	
-	public Road.End getOutgoingRoadEnd() {
-		return roadEnd;
-	}
-	
-	public Road.End getIncomingRoadEnd() {
-		return roadEnd.getOppositeEnd();
-	}
-	
-	public ModelContainer getContainer() {
-		return getRoad().getContainer();
-	}
-	
-	public void addTurn(List<Road> via, Road.End to) {
-		assert equals(to.getJunction());
-		
-		Relation existing = null;
-		for (Turn t : to.getTurns()) {
-			if (t.getFrom().getOutgoingRoadEnd().equals(getOutgoingRoadEnd()) && t.getVia().equals(via)) {
-				if (t.getFrom().equals(this)) {
-					// was already added
-					return;
-				}
-				
-				existing = t.getRelation();
-			}
-		}
-		
-		final Relation r;
-		if (existing == null) {
-			r = new Relation();
-			r.put("type", Constants.TYPE_TURNS);
-			
-			r.addMember(new RelationMember(Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay()));
-			if (via.isEmpty()) {
-				r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, getOutgoingJunction().getNode()));
-			} else {
-				for (Way w : Utils.flattenVia(getOutgoingJunction().getNode(), via, to.getJunction().getNode())) {
-					r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, w));
-				}
-			}
-			r.addMember(new RelationMember(Constants.TURN_ROLE_TO, to.getWay()));
-			
-			getOutgoingJunction().getNode().getDataSet().addPrimitive(r);
-		} else {
-			r = existing;
-		}
-		
-		final String key = isExtra() ? Constants.TURN_KEY_EXTRA_LANES : Constants.TURN_KEY_LANES;
-		final List<Integer> lanes = Turn.indices(r, key);
-		lanes.add(getIndex());
-		r.put(key, Turn.join(lanes));
-	}
-	
-	public Set<Turn> getTurns() {
-		return Turn.load(getContainer(), Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay());
-	}
+    public enum Kind {
+        EXTRA_LEFT,
+        EXTRA_RIGHT,
+        REGULAR;
+        
+        public boolean isExtra() {
+            return this == EXTRA_LEFT || this == EXTRA_RIGHT;
+        }
+    }
+    
+    static List<Lane> load(Road.End roadEnd) {
+        final List<Lane> result = new ArrayList<Lane>();
+        int i;
+        
+        i = 0;
+        for (double l : CollectionUtils.reverse(roadEnd.getLengths(Kind.EXTRA_LEFT))) {
+            result.add(new Lane(roadEnd, --i, Kind.EXTRA_LEFT, l));
+        }
+        
+        final int regulars = getRegularCount(roadEnd.getWay(), roadEnd.getJunction().getNode());
+        for (i = 1; i <= regulars; ++i) {
+            result.add(new Lane(roadEnd, i));
+        }
+        
+        i = 0;
+        for (double l : roadEnd.getLengths(Kind.EXTRA_RIGHT)) {
+            result.add(new Lane(roadEnd, ++i, Kind.EXTRA_RIGHT, l));
+        }
+        
+        return result;
+    }
+    
+    static List<Double> loadLengths(Relation r, String key, double lengthBound) {
+        final List<Double> result = new ArrayList<Double>();
+        
+        if (r != null && r.get(key) != null) {
+            for (String s : Constants.SPLIT_PATTERN.split(r.get(key))) {
+                // TODO what should the exact input be (there should probably be
+                // a unit (m))
+                final Double length = Double.parseDouble(s.trim());
+                
+                if (length > lengthBound) {
+                    result.add(length);
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    private static int getCount(Way w) {
+        final String countStr = w.get("lanes");
+        
+        if (countStr != null) {
+            try {
+                return Integer.parseInt(countStr);
+            } catch (NumberFormatException e) {
+                throw UnexpectedDataException.Kind.INVALID_TAG_FORMAT.chuck("lanes", countStr);
+            }
+        }
+        
+        throw UnexpectedDataException.Kind.MISSING_TAG.chuck("lanes");
+    }
+    
+    static int getRegularCount(Way w, Node end) {
+        final int count = getCount(w);
+        
+        if (w.hasDirectionKeys()) {
+            // TODO check for oneway=-1
+            if (w.lastNode().equals(end)) {
+                return count;
+            } else {
+                return 0;
+            }
+        } else {
+            if (w.lastNode().equals(end)) {
+                return (count + 1) / 2; // round up in direction of end
+            } else {
+                return count / 2; // round down in other direction
+            }
+        }
+    }
+    
+    private final Road.End roadEnd;
+    private final int index;
+    private final Kind kind;
+    
+    private double length = -1;
+    
+    public Lane(Road.End roadEnd, int index) {
+        this.roadEnd = roadEnd;
+        this.index = index;
+        this.kind = Kind.REGULAR;
+    }
+    
+    public Lane(Road.End roadEnd, int index, Kind kind, double length) {
+        assert kind == Kind.EXTRA_LEFT || kind == Kind.EXTRA_RIGHT;
+        
+        this.roadEnd = roadEnd;
+        this.index = index;
+        this.kind = kind;
+        this.length = length;
+        
+        if (length <= 0) {
+            throw new IllegalArgumentException("Length must be positive");
+        }
+    }
+    
+    public Road getRoad() {
+        return roadEnd.getRoad();
+    }
+    
+    public Kind getKind() {
+        return kind;
+    }
+    
+    public double getLength() {
+        return isExtra() ? length : getRoad().getLength();
+    }
+    
+    public void setLength(double length) {
+        if (!isExtra()) {
+            throw new UnsupportedOperationException("Length can only be set for extra lanes.");
+        } else if (length <= 0) {
+            throw new IllegalArgumentException("Length must positive.");
+        }
+        
+        // TODO if needed, increase length of other lanes
+        getOutgoingRoadEnd().updateLengths();
+        
+        this.length = length;
+    }
+    
+    public boolean isExtra() {
+        return getKind() != Kind.REGULAR;
+    }
+    
+    public int getIndex() {
+        return index;
+    }
+    
+    public Junction getOutgoingJunction() {
+        return getOutgoingRoadEnd().getJunction();
+    }
+    
+    public Junction getIncomingJunction() {
+        return getIncomingRoadEnd().getJunction();
+    }
+    
+    public Road.End getOutgoingRoadEnd() {
+        return roadEnd;
+    }
+    
+    public Road.End getIncomingRoadEnd() {
+        return roadEnd.getOppositeEnd();
+    }
+    
+    public ModelContainer getContainer() {
+        return getRoad().getContainer();
+    }
+    
+    public void addTurn(List<Road> via, Road.End to) {
+        assert equals(to.getJunction());
+        
+        Relation existing = null;
+        for (Turn t : to.getTurns()) {
+            if (t.getFrom().getOutgoingRoadEnd().equals(getOutgoingRoadEnd()) && t.getVia().equals(via)) {
+                if (t.getFrom().equals(this)) {
+                    // was already added
+                    return;
+                }
+                
+                existing = t.getRelation();
+            }
+        }
+        
+        final Relation r;
+        if (existing == null) {
+            r = new Relation();
+            r.put("type", Constants.TYPE_TURNS);
+            
+            r.addMember(new RelationMember(Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay()));
+            if (via.isEmpty()) {
+                r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, getOutgoingJunction().getNode()));
+            } else {
+                for (Way w : Utils.flattenVia(getOutgoingJunction().getNode(), via, to.getJunction().getNode())) {
+                    r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, w));
+                }
+            }
+            r.addMember(new RelationMember(Constants.TURN_ROLE_TO, to.getWay()));
+            
+            getOutgoingJunction().getNode().getDataSet().addPrimitive(r);
+        } else {
+            r = existing;
+        }
+        
+        final String key = isExtra() ? Constants.TURN_KEY_EXTRA_LANES : Constants.TURN_KEY_LANES;
+        final List<Integer> lanes = Turn.indices(r, key);
+        lanes.add(getIndex());
+        r.put(key, Turn.join(lanes));
+    }
+    
+    public Set<Turn> getTurns() {
+        return Turn.load(getContainer(), Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay());
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/ModelContainer.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/ModelContainer.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/ModelContainer.java	(revision 26154)
@@ -18,220 +18,220 @@
 
 public class ModelContainer {
-	public static ModelContainer create(Iterable<Node> primaryNodes, Iterable<Way> primaryWays) {
-		final Set<Node> closedNodes = new HashSet<Node>(CollectionUtils.toList(primaryNodes));
-		final Set<Way> closedWays = new HashSet<Way>(CollectionUtils.toList(primaryWays));
-		
-		close(closedNodes, closedWays);
-		
-		return new ModelContainer(closedNodes, closedWays);
-	}
-	
-	private static void close(Set<Node> closedNodes, Set<Way> closedWays) {
-		boolean closed = false;
-		
-		while (!closed) {
-			closed = true;
-			
-			for (Node n : closedNodes) {
-				for (Way w : Utils.filterRoads(n.getReferrers())) {
-					if (w.isFirstLastNode(n)) {
-						closed &= close(closedNodes, closedWays, w, Constants.TURN_ROLE_FROM);
-						closed &= close(closedNodes, closedWays, w, Constants.TURN_ROLE_TO);
-					}
-				}
-				
-				for (Way w : closedWays) {
-					closed &= close(closedNodes, closedWays, w, Constants.TURN_ROLE_VIA);
-				}
-			}
-		}
-	}
-	
-	private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Way w, String role) {
-		boolean closed = true;
-		
-		for (Relation r : OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class)) {
-			if (!r.get("type").equals(Constants.TYPE_TURNS)) {
-				continue;
-			}
-			
-			for (RelationMember m : r.getMembers()) {
-				if (m.getRole().equals(role) && m.getMember().equals(w)) {
-					closed &= close(closedNodes, closedWays, r);
-				}
-			}
-		}
-		
-		return closed;
-	}
-	
-	private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Relation r) {
-		boolean closed = true;
-		
-		final List<Way> via = new ArrayList<Way>();
-		for (RelationMember m : Utils.getMembers(r, Constants.TURN_ROLE_VIA)) {
-			if (m.isWay()) {
-				closed &= !closedWays.add(m.getWay());
-				via.add(m.getWay());
-			} else if (m.isNode()) {
-				closed &= !closedNodes.add(m.getNode());
-			}
-		}
-		
-		if (!via.isEmpty()) {
-			final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
-			final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
-			
-			closed &= !closedNodes.add(Utils.lineUp(from, via.get(0)));
-			closed &= !closedNodes.add(Utils.lineUp(via.get(via.size() - 1), to));
-		}
-		
-		return closed;
-	}
-	
-	private final Map<Node, Junction> junctions = new HashMap<Node, Junction>();
-	private final Map<Way, Road> roads = new HashMap<Way, Road>();
-	
-	private final Set<Node> primaryNodes;
-	private final Set<Way> primaryWays;
-	
-	private ModelContainer(Set<Node> primaryNodes, Set<Way> primaryWays) {
-		this.primaryNodes = Collections.unmodifiableSet(new HashSet<Node>(primaryNodes));
-		this.primaryWays = Collections.unmodifiableSet(new HashSet<Way>(primaryWays));
-		
-		final Set<Pair<Way, Junction>> ws = new HashSet<Pair<Way, Junction>>();
-		for (Node n : primaryNodes) {
-			final Junction j = getOrCreateJunction(n);
-			
-			for (Way w : Utils.filterRoads(n.getReferrers())) {
-				if (w.isFirstLastNode(n)) {
-					ws.add(new Pair<Way, Junction>(w, j));
-				}
-			}
-		}
-		
-		final List<Route> rs = Utils.orderWays(primaryWays, primaryNodes);
-		for (Route r : rs) {
-			addRoad(new Road(this, r));
-		}
-		
-		for (Pair<Way, Junction> w : ws) {
-			if (!primaryWays.contains(w.a)) {
-				addRoad(new Road(this, w.a, w.b));
-			}
-		}
-	}
-	
-	Junction getOrCreateJunction(Node n) {
-		final Junction existing = junctions.get(n);
-		
-		if (existing != null) {
-			return existing;
-		}
-		
-		return new Junction(this, n);
-	}
-	
-	public Junction getJunction(Node n) {
-		Junction j = junctions.get(n);
-		
-		if (j == null) {
-			throw new IllegalArgumentException();
-		}
-		
-		return j;
-	}
-	
-	Road getRoad(Way w) {
-		final Road r = roads.get(w);
-		
-		if (r == null) {
-			throw new IllegalArgumentException("There is no road containing the given way.");
-		}
-		
-		return r;
-	}
-	
-	private void addRoad(Road newRoad, Road mergedA, Road mergedB) {
-		assert (mergedA == null) == (mergedB == null);
-		
-		for (Route.Segment s : newRoad.getRoute().getSegments()) {
-			final Road oldRoad = roads.put(s.getWay(), newRoad);
-			
-			if (oldRoad != null) {
-				if (mergedA == null) {
-					final Road mergedRoad = mergeRoads(oldRoad, newRoad);
-					addRoad(mergedRoad, oldRoad, newRoad);
-				} else if (!oldRoad.equals(mergedA) && !oldRoad.equals(mergedB)) {
-					throw new RuntimeException("A road can't be connected to more than two junctions.");
-				}
-			}
-		}
-	}
-	
-	private void addRoad(Road newRoad) {
-		addRoad(newRoad, null, null);
-	}
-	
-	private Road mergeRoads(Road a, Road b) {
-		final String ERR_ILLEGAL_ARGS = "The given roads can not be merged into one.";
-		
-		final List<Way> ws = new ArrayList<Way>(CollectionUtils.toList(CollectionUtils.reverse(a.getRoute().getWays())));
-		final List<Way> bws = b.getRoute().getWays();
-		
-		int i = -1;
-		for (Way w : ws) {
-			if (w.equals(bws.get(i + 1))) {
-				++i;
-			} else if (i >= 0) {
-				throw new IllegalArgumentException(ERR_ILLEGAL_ARGS);
-			}
-		}
-		
-		if (i < 0) {
-			throw new IllegalArgumentException(ERR_ILLEGAL_ARGS);
-		}
-		ws.addAll(bws.subList(i + 1, bws.size()));
-		
-		final Route mergedRoute = Route.create(ws, a.getRoute().getLastSegment().getEnd());
-		return new Road(this, mergedRoute);
-	}
-	
-	void register(Junction j) {
-		if (junctions.put(j.getNode(), j) != null) {
-			throw new IllegalStateException();
-		}
-	}
-	
-	public Set<Junction> getPrimaryJunctions() {
-		final Set<Junction> pjs = new HashSet<Junction>();
-		
-		for (Node n : primaryNodes) {
-			pjs.add(getOrCreateJunction(n));
-		}
-		
-		return pjs;
-	}
-	
-	public Set<Road> getPrimaryRoads() {
-		final Set<Road> prs = new HashSet<Road>();
-		
-		for (Way w : primaryWays) {
-			prs.add(roads.get(w));
-		}
-		
-		return prs;
-	}
-	
-	public ModelContainer recalculate() {
-		return new ModelContainer(primaryNodes, primaryWays);
-	}
-	
-	public boolean isPrimary(Junction j) {
-		return primaryNodes.contains(j.getNode());
-	}
-	
-	public boolean isPrimary(Road r) {
-		return primaryWays.contains(r.getRoute().getFirstSegment().getWay());
-	}
+    public static ModelContainer create(Iterable<Node> primaryNodes, Iterable<Way> primaryWays) {
+        final Set<Node> closedNodes = new HashSet<Node>(CollectionUtils.toList(primaryNodes));
+        final Set<Way> closedWays = new HashSet<Way>(CollectionUtils.toList(primaryWays));
+        
+        close(closedNodes, closedWays);
+        
+        return new ModelContainer(closedNodes, closedWays);
+    }
+    
+    private static void close(Set<Node> closedNodes, Set<Way> closedWays) {
+        boolean closed = false;
+        
+        while (!closed) {
+            closed = true;
+            
+            for (Node n : closedNodes) {
+                for (Way w : Utils.filterRoads(n.getReferrers())) {
+                    if (w.isFirstLastNode(n)) {
+                        closed &= close(closedNodes, closedWays, w, Constants.TURN_ROLE_FROM);
+                        closed &= close(closedNodes, closedWays, w, Constants.TURN_ROLE_TO);
+                    }
+                }
+                
+                for (Way w : closedWays) {
+                    closed &= close(closedNodes, closedWays, w, Constants.TURN_ROLE_VIA);
+                }
+            }
+        }
+    }
+    
+    private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Way w, String role) {
+        boolean closed = true;
+        
+        for (Relation r : OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class)) {
+            if (!r.get("type").equals(Constants.TYPE_TURNS)) {
+                continue;
+            }
+            
+            for (RelationMember m : r.getMembers()) {
+                if (m.getRole().equals(role) && m.getMember().equals(w)) {
+                    closed &= close(closedNodes, closedWays, r);
+                }
+            }
+        }
+        
+        return closed;
+    }
+    
+    private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Relation r) {
+        boolean closed = true;
+        
+        final List<Way> via = new ArrayList<Way>();
+        for (RelationMember m : Utils.getMembers(r, Constants.TURN_ROLE_VIA)) {
+            if (m.isWay()) {
+                closed &= !closedWays.add(m.getWay());
+                via.add(m.getWay());
+            } else if (m.isNode()) {
+                closed &= !closedNodes.add(m.getNode());
+            }
+        }
+        
+        if (!via.isEmpty()) {
+            final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
+            final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
+            
+            closed &= !closedNodes.add(Utils.lineUp(from, via.get(0)));
+            closed &= !closedNodes.add(Utils.lineUp(via.get(via.size() - 1), to));
+        }
+        
+        return closed;
+    }
+    
+    private final Map<Node, Junction> junctions = new HashMap<Node, Junction>();
+    private final Map<Way, Road> roads = new HashMap<Way, Road>();
+    
+    private final Set<Node> primaryNodes;
+    private final Set<Way> primaryWays;
+    
+    private ModelContainer(Set<Node> primaryNodes, Set<Way> primaryWays) {
+        this.primaryNodes = Collections.unmodifiableSet(new HashSet<Node>(primaryNodes));
+        this.primaryWays = Collections.unmodifiableSet(new HashSet<Way>(primaryWays));
+        
+        final Set<Pair<Way, Junction>> ws = new HashSet<Pair<Way, Junction>>();
+        for (Node n : primaryNodes) {
+            final Junction j = getOrCreateJunction(n);
+            
+            for (Way w : Utils.filterRoads(n.getReferrers())) {
+                if (w.isFirstLastNode(n)) {
+                    ws.add(new Pair<Way, Junction>(w, j));
+                }
+            }
+        }
+        
+        final List<Route> rs = Utils.orderWays(primaryWays, primaryNodes);
+        for (Route r : rs) {
+            addRoad(new Road(this, r));
+        }
+        
+        for (Pair<Way, Junction> w : ws) {
+            if (!primaryWays.contains(w.a)) {
+                addRoad(new Road(this, w.a, w.b));
+            }
+        }
+    }
+    
+    Junction getOrCreateJunction(Node n) {
+        final Junction existing = junctions.get(n);
+        
+        if (existing != null) {
+            return existing;
+        }
+        
+        return new Junction(this, n);
+    }
+    
+    public Junction getJunction(Node n) {
+        Junction j = junctions.get(n);
+        
+        if (j == null) {
+            throw new IllegalArgumentException();
+        }
+        
+        return j;
+    }
+    
+    Road getRoad(Way w) {
+        final Road r = roads.get(w);
+        
+        if (r == null) {
+            throw new IllegalArgumentException("There is no road containing the given way.");
+        }
+        
+        return r;
+    }
+    
+    private void addRoad(Road newRoad, Road mergedA, Road mergedB) {
+        assert (mergedA == null) == (mergedB == null);
+        
+        for (Route.Segment s : newRoad.getRoute().getSegments()) {
+            final Road oldRoad = roads.put(s.getWay(), newRoad);
+            
+            if (oldRoad != null) {
+                if (mergedA == null) {
+                    final Road mergedRoad = mergeRoads(oldRoad, newRoad);
+                    addRoad(mergedRoad, oldRoad, newRoad);
+                } else if (!oldRoad.equals(mergedA) && !oldRoad.equals(mergedB)) {
+                    throw new RuntimeException("A road can't be connected to more than two junctions.");
+                }
+            }
+        }
+    }
+    
+    private void addRoad(Road newRoad) {
+        addRoad(newRoad, null, null);
+    }
+    
+    private Road mergeRoads(Road a, Road b) {
+        final String ERR_ILLEGAL_ARGS = "The given roads can not be merged into one.";
+        
+        final List<Way> ws = new ArrayList<Way>(CollectionUtils.toList(CollectionUtils.reverse(a.getRoute().getWays())));
+        final List<Way> bws = b.getRoute().getWays();
+        
+        int i = -1;
+        for (Way w : ws) {
+            if (w.equals(bws.get(i + 1))) {
+                ++i;
+            } else if (i >= 0) {
+                throw new IllegalArgumentException(ERR_ILLEGAL_ARGS);
+            }
+        }
+        
+        if (i < 0) {
+            throw new IllegalArgumentException(ERR_ILLEGAL_ARGS);
+        }
+        ws.addAll(bws.subList(i + 1, bws.size()));
+        
+        final Route mergedRoute = Route.create(ws, a.getRoute().getLastSegment().getEnd());
+        return new Road(this, mergedRoute);
+    }
+    
+    void register(Junction j) {
+        if (junctions.put(j.getNode(), j) != null) {
+            throw new IllegalStateException();
+        }
+    }
+    
+    public Set<Junction> getPrimaryJunctions() {
+        final Set<Junction> pjs = new HashSet<Junction>();
+        
+        for (Node n : primaryNodes) {
+            pjs.add(getOrCreateJunction(n));
+        }
+        
+        return pjs;
+    }
+    
+    public Set<Road> getPrimaryRoads() {
+        final Set<Road> prs = new HashSet<Road>();
+        
+        for (Way w : primaryWays) {
+            prs.add(roads.get(w));
+        }
+        
+        return prs;
+    }
+    
+    public ModelContainer recalculate() {
+        return new ModelContainer(primaryNodes, primaryWays);
+    }
+    
+    public boolean isPrimary(Junction j) {
+        return primaryNodes.contains(j.getNode());
+    }
+    
+    public boolean isPrimary(Road r) {
+        return primaryWays.contains(r.getRoute().getFirstSegment().getWay());
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Road.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Road.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Road.java	(revision 26154)
@@ -20,320 +20,320 @@
 
 public class Road {
-	public class End {
-		private final boolean from;
-		private final Junction junction;
-		
-		private final Relation lengthsLeft;
-		private final Relation lengthsRight;
-		
-		private final double extraLengthLeft;
-		private final double extraLengthRight;
-		
-		private final List<Lane> lanes;
-		
-		private End(boolean from, Junction junction, Relation lengthsLeft, Relation lengthsRight) {
-			this.from = from;
-			this.junction = junction;
-			this.lengthsLeft = lengthsLeft;
-			this.lengthsRight = lengthsRight;
-			this.extraLengthLeft = lengthsLeft == null ? 0 : Route.load(lengthsLeft).getLengthFrom(getWay());
-			this.extraLengthRight = lengthsRight == null ? 0 : Route.load(lengthsRight).getLengthFrom(getWay());
-			this.lanes = Lane.load(this);
-			
-			junction.addRoad(getWay());
-		}
-		
-		private End(boolean from, Junction junction) {
-			this.from = from;
-			this.junction = junction;
-			this.lengthsLeft = null;
-			this.lengthsRight = null;
-			this.extraLengthLeft = 0;
-			this.extraLengthRight = 0;
-			this.lanes = Lane.load(this);
-			
-			junction.addRoad(getWay());
-		}
-		
-		public Road getRoad() {
-			return Road.this;
-		}
-		
-		public Way getWay() {
-			return isFromEnd() ? getRoute().getFirstSegment().getWay() : getRoute().getLastSegment().getWay();
-		}
-		
-		public Junction getJunction() {
-			return junction;
-		}
-		
-		public boolean isFromEnd() {
-			return from;
-		}
-		
-		public boolean isToEnd() {
-			return !isFromEnd();
-		}
-		
-		public End getOppositeEnd() {
-			return isFromEnd() ? toEnd : fromEnd;
-		}
-		
-		/**
-		 * @return the turns <em>onto</em> this road at this end
-		 */
-		public Set<Turn> getTurns() {
-			return Turn.load(getContainer(), Constants.TURN_ROLE_TO, getWay());
-		}
-		
-		public void addLane(Lane.Kind kind) {
-			if (kind == Lane.Kind.REGULAR) {
-				throw new IllegalArgumentException("Only extra lanes can be added.");
-			}
-			
-			double length = Double.POSITIVE_INFINITY;
-			for (Lane l : lanes) {
-				if (l.getKind() == kind) {
-					length = Math.max(0, Math.min(length, l.getLength() - 1));
-				}
-			}
-			
-			if (Double.isInfinite(length)) {
-				length = Math.min(20, 3 * getLength() / 4);
-			}
-			
-			addLane(kind, length);
-		}
-		
-		private void addLane(Lane.Kind kind, double length) {
-			assert kind == Lane.Kind.EXTRA_LEFT || kind == Lane.Kind.EXTRA_RIGHT;
-			
-			final boolean left = kind == Lane.Kind.EXTRA_LEFT;
-			final Relation rel = left ? lengthsLeft : lengthsRight;
-			final Relation other = left ? lengthsRight : lengthsLeft;
-			final Node n = getJunction().getNode();
-			
-			final String lengthStr = toLengthString(length);
-			final Relation target;
-			if (rel == null) {
-				if (other == null || !Utils.getMemberNode(other, "end").equals(n)) {
-					target = createLengthsRelation();
-				} else {
-					target = other;
-				}
-			} else {
-				target = rel;
-			}
-			
-			final String key = left ? Constants.LENGTHS_KEY_LENGTHS_LEFT : Constants.LENGTHS_KEY_LENGTHS_RIGHT;
-			final String old = target.get(key);
-			if (old == null) {
-				target.put(key, lengthStr);
-			} else {
-				target.put(key, old + Constants.SEPARATOR + lengthStr);
-			}
-		}
-		
-		private Relation createLengthsRelation() {
-			final Node n = getJunction().getNode();
-			
-			final Relation r = new Relation();
-			r.put("type", Constants.TYPE_LENGTHS);
-			
-			r.addMember(new RelationMember(Constants.LENGTHS_ROLE_END, n));
-			for (Route.Segment s : isFromEnd() ? route.getSegments() : CollectionUtils.reverse(route.getSegments())) {
-				r.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, s.getWay()));
-			}
-			
-			n.getDataSet().addPrimitive(r);
-			
-			return r;
-		}
-		
-		void updateLengths() {
-			for (final boolean left : Arrays.asList(true, false)) {
-				final Lane.Kind kind = left ? Lane.Kind.EXTRA_LEFT : Lane.Kind.EXTRA_RIGHT;
-				final Relation r = left ? lengthsLeft : lengthsRight;
-				final double extra = left ? extraLengthLeft : extraLengthRight;
-				
-				if (r == null) {
-					continue;
-				}
-				
-				final StringBuilder lengths = new StringBuilder(32);
-				for (Lane l : left ? CollectionUtils.reverse(lanes) : lanes) {
-					if (l.getKind() == kind) {
-						lengths.append(toLengthString(extra + l.getLength())).append(Constants.SEPARATOR);
-					}
-				}
-				
-				lengths.setLength(lengths.length() - Constants.SEPARATOR.length());
-				r.put(left ? Constants.LENGTHS_KEY_LENGTHS_LEFT : Constants.LENGTHS_KEY_LENGTHS_RIGHT, lengths.toString());
-			}
-		}
-		
-		public List<Lane> getLanes() {
-			return lanes;
-		}
-		
-		public Lane getLane(Lane.Kind kind, int index) {
-			for (Lane l : lanes) {
-				if (l.getKind() == kind && l.getIndex() == index) {
-					return l;
-				}
-			}
-			
-			throw new IllegalArgumentException("No such lane.");
-		}
-		
-		public Lane getExtraLane(int index) {
-			return index < 0 ? getLane(Lane.Kind.EXTRA_LEFT, index) : getLane(Lane.Kind.EXTRA_RIGHT, index);
-		}
-		
-		public boolean isExtendable() {
-			final End o = getOppositeEnd();
-			return (lengthsLeft == null && lengthsRight == null) && (o.lengthsLeft != null || o.lengthsRight != null);
-		}
-		
-		public void extend(Way way) {
-			if (!isExtendable()) {
-				throw new IllegalStateException();
-			}
-			
-			final End o = getOppositeEnd();
-			if (o.lengthsLeft != null) {
-				o.lengthsLeft.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, way));
-			}
-			if (o.lengthsRight != null) {
-				o.lengthsRight.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, way));
-			}
-		}
-		
-		public List<Double> getLengths(Lane.Kind kind) {
-			switch (kind) {
-				case EXTRA_LEFT:
-					return Lane.loadLengths(lengthsLeft, Constants.LENGTHS_KEY_LENGTHS_LEFT, extraLengthLeft);
-				case EXTRA_RIGHT:
-					return Lane.loadLengths(lengthsRight, Constants.LENGTHS_KEY_LENGTHS_RIGHT, extraLengthRight);
-				default:
-					throw new IllegalArgumentException(String.valueOf(kind));
-			}
-		}
-	}
-	
-	private static Pair<Relation, Relation> getLengthRelations(Way w, Node n) {
-		final List<Relation> left = new ArrayList<Relation>();
-		final List<Relation> right = new ArrayList<Relation>();
-		
-		for (OsmPrimitive p : w.getReferrers()) {
-			if (p.getType() != OsmPrimitiveType.RELATION) {
-				continue;
-			}
-			
-			Relation r = (Relation) p;
-			
-			if (Constants.TYPE_LENGTHS.equals(r.get("type")) && isRightDirection(r, w, n)) {
-				
-				if (r.get(Constants.LENGTHS_KEY_LENGTHS_LEFT) != null) {
-					left.add(r);
-				}
-				
-				if (r.get(Constants.LENGTHS_KEY_LENGTHS_RIGHT) != null) {
-					right.add(r);
-				}
-			}
-		}
-		
-		if (left.size() > 1) {
-			throw new IllegalArgumentException("Way is in " + left.size()
-			    + " lengths relations for given direction, both specifying left lane lengths.");
-		}
-		
-		if (right.size() > 1) {
-			throw new IllegalArgumentException("Way is in " + right.size()
-			    + " lengths relations for given direction, both specifying right lane lengths.");
-		}
-		
-		return new Pair<Relation, Relation>( //
-		    left.isEmpty() ? null : left.get(0), //
-		    right.isEmpty() ? null : right.get(0) //
-		);
-	}
-	
-	/**
-	 * @param r
-	 *          lengths relation
-	 * @param w
-	 *          the way to check for
-	 * @param n
-	 *          first or last node of w, determines the direction
-	 * @return whether the turn lane goes into the direction of n
-	 */
-	private static boolean isRightDirection(Relation r, Way w, Node n) {
-		for (Segment s : Route.load(r).getSegments()) {
-			if (w.equals(s.getWay())) {
-				return n.equals(s.getEnd());
-			}
-		}
-		
-		return false;
-	}
-	
-	private final ModelContainer container;
-	private final Route route;
-	private final End fromEnd;
-	private final End toEnd;
-	
-	Road(ModelContainer container, Way w, Junction j) {
-		final Node n = j.getNode();
-		if (!w.isFirstLastNode(n)) {
-			throw new IllegalArgumentException("Way must start or end in given node.");
-		}
-		final Pair<Relation, Relation> lengthsRelations = getLengthRelations(w, n);
-		
-		this.container = container;
-		this.route = lengthsRelations.a == null && lengthsRelations.b == null ? Route.create(Arrays.asList(w), n) : Route
-		    .load(lengthsRelations.a, lengthsRelations.b, w);
-		this.fromEnd = new End(true, container.getOrCreateJunction(route.getFirstSegment().getStart()));
-		this.toEnd = new End(false, j, lengthsRelations.a, lengthsRelations.b);
-	}
-	
-	Road(ModelContainer container, Route route) {
-		this.container = container;
-		this.route = route;
-		this.fromEnd = new End(true, container.getJunction(route.getStart()));
-		this.toEnd = new End(false, container.getJunction(route.getEnd()));
-	}
-	
-	public End getFromEnd() {
-		return fromEnd;
-	}
-	
-	public End getToEnd() {
-		return toEnd;
-	}
-	
-	public Route getRoute() {
-		return route;
-	}
-	
-	public double getLength() {
-		return route.getLength();
-	}
-	
-	private String toLengthString(double length) {
-		final DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance();
-		dfs.setDecimalSeparator('.');
-		final DecimalFormat nf = new DecimalFormat("0.0", dfs);
-		nf.setRoundingMode(RoundingMode.HALF_UP);
-		return nf.format(length);
-	}
-	
-	public ModelContainer getContainer() {
-		return container;
-	}
-	
-	public boolean isPrimary() {
-		return getContainer().isPrimary(this);
-	}
+    public class End {
+        private final boolean from;
+        private final Junction junction;
+        
+        private final Relation lengthsLeft;
+        private final Relation lengthsRight;
+        
+        private final double extraLengthLeft;
+        private final double extraLengthRight;
+        
+        private final List<Lane> lanes;
+        
+        private End(boolean from, Junction junction, Relation lengthsLeft, Relation lengthsRight) {
+            this.from = from;
+            this.junction = junction;
+            this.lengthsLeft = lengthsLeft;
+            this.lengthsRight = lengthsRight;
+            this.extraLengthLeft = lengthsLeft == null ? 0 : Route.load(lengthsLeft).getLengthFrom(getWay());
+            this.extraLengthRight = lengthsRight == null ? 0 : Route.load(lengthsRight).getLengthFrom(getWay());
+            this.lanes = Lane.load(this);
+            
+            junction.addRoad(getWay());
+        }
+        
+        private End(boolean from, Junction junction) {
+            this.from = from;
+            this.junction = junction;
+            this.lengthsLeft = null;
+            this.lengthsRight = null;
+            this.extraLengthLeft = 0;
+            this.extraLengthRight = 0;
+            this.lanes = Lane.load(this);
+            
+            junction.addRoad(getWay());
+        }
+        
+        public Road getRoad() {
+            return Road.this;
+        }
+        
+        public Way getWay() {
+            return isFromEnd() ? getRoute().getFirstSegment().getWay() : getRoute().getLastSegment().getWay();
+        }
+        
+        public Junction getJunction() {
+            return junction;
+        }
+        
+        public boolean isFromEnd() {
+            return from;
+        }
+        
+        public boolean isToEnd() {
+            return !isFromEnd();
+        }
+        
+        public End getOppositeEnd() {
+            return isFromEnd() ? toEnd : fromEnd;
+        }
+        
+        /**
+         * @return the turns <em>onto</em> this road at this end
+         */
+        public Set<Turn> getTurns() {
+            return Turn.load(getContainer(), Constants.TURN_ROLE_TO, getWay());
+        }
+        
+        public void addLane(Lane.Kind kind) {
+            if (kind == Lane.Kind.REGULAR) {
+                throw new IllegalArgumentException("Only extra lanes can be added.");
+            }
+            
+            double length = Double.POSITIVE_INFINITY;
+            for (Lane l : lanes) {
+                if (l.getKind() == kind) {
+                    length = Math.max(0, Math.min(length, l.getLength() - 1));
+                }
+            }
+            
+            if (Double.isInfinite(length)) {
+                length = Math.min(20, 3 * getLength() / 4);
+            }
+            
+            addLane(kind, length);
+        }
+        
+        private void addLane(Lane.Kind kind, double length) {
+            assert kind == Lane.Kind.EXTRA_LEFT || kind == Lane.Kind.EXTRA_RIGHT;
+            
+            final boolean left = kind == Lane.Kind.EXTRA_LEFT;
+            final Relation rel = left ? lengthsLeft : lengthsRight;
+            final Relation other = left ? lengthsRight : lengthsLeft;
+            final Node n = getJunction().getNode();
+            
+            final String lengthStr = toLengthString(length);
+            final Relation target;
+            if (rel == null) {
+                if (other == null || !Utils.getMemberNode(other, "end").equals(n)) {
+                    target = createLengthsRelation();
+                } else {
+                    target = other;
+                }
+            } else {
+                target = rel;
+            }
+            
+            final String key = left ? Constants.LENGTHS_KEY_LENGTHS_LEFT : Constants.LENGTHS_KEY_LENGTHS_RIGHT;
+            final String old = target.get(key);
+            if (old == null) {
+                target.put(key, lengthStr);
+            } else {
+                target.put(key, old + Constants.SEPARATOR + lengthStr);
+            }
+        }
+        
+        private Relation createLengthsRelation() {
+            final Node n = getJunction().getNode();
+            
+            final Relation r = new Relation();
+            r.put("type", Constants.TYPE_LENGTHS);
+            
+            r.addMember(new RelationMember(Constants.LENGTHS_ROLE_END, n));
+            for (Route.Segment s : isFromEnd() ? route.getSegments() : CollectionUtils.reverse(route.getSegments())) {
+                r.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, s.getWay()));
+            }
+            
+            n.getDataSet().addPrimitive(r);
+            
+            return r;
+        }
+        
+        void updateLengths() {
+            for (final boolean left : Arrays.asList(true, false)) {
+                final Lane.Kind kind = left ? Lane.Kind.EXTRA_LEFT : Lane.Kind.EXTRA_RIGHT;
+                final Relation r = left ? lengthsLeft : lengthsRight;
+                final double extra = left ? extraLengthLeft : extraLengthRight;
+                
+                if (r == null) {
+                    continue;
+                }
+                
+                final StringBuilder lengths = new StringBuilder(32);
+                for (Lane l : left ? CollectionUtils.reverse(lanes) : lanes) {
+                    if (l.getKind() == kind) {
+                        lengths.append(toLengthString(extra + l.getLength())).append(Constants.SEPARATOR);
+                    }
+                }
+                
+                lengths.setLength(lengths.length() - Constants.SEPARATOR.length());
+                r.put(left ? Constants.LENGTHS_KEY_LENGTHS_LEFT : Constants.LENGTHS_KEY_LENGTHS_RIGHT, lengths.toString());
+            }
+        }
+        
+        public List<Lane> getLanes() {
+            return lanes;
+        }
+        
+        public Lane getLane(Lane.Kind kind, int index) {
+            for (Lane l : lanes) {
+                if (l.getKind() == kind && l.getIndex() == index) {
+                    return l;
+                }
+            }
+            
+            throw new IllegalArgumentException("No such lane.");
+        }
+        
+        public Lane getExtraLane(int index) {
+            return index < 0 ? getLane(Lane.Kind.EXTRA_LEFT, index) : getLane(Lane.Kind.EXTRA_RIGHT, index);
+        }
+        
+        public boolean isExtendable() {
+            final End o = getOppositeEnd();
+            return (lengthsLeft == null && lengthsRight == null) && (o.lengthsLeft != null || o.lengthsRight != null);
+        }
+        
+        public void extend(Way way) {
+            if (!isExtendable()) {
+                throw new IllegalStateException();
+            }
+            
+            final End o = getOppositeEnd();
+            if (o.lengthsLeft != null) {
+                o.lengthsLeft.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, way));
+            }
+            if (o.lengthsRight != null) {
+                o.lengthsRight.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, way));
+            }
+        }
+        
+        public List<Double> getLengths(Lane.Kind kind) {
+            switch (kind) {
+                case EXTRA_LEFT:
+                    return Lane.loadLengths(lengthsLeft, Constants.LENGTHS_KEY_LENGTHS_LEFT, extraLengthLeft);
+                case EXTRA_RIGHT:
+                    return Lane.loadLengths(lengthsRight, Constants.LENGTHS_KEY_LENGTHS_RIGHT, extraLengthRight);
+                default:
+                    throw new IllegalArgumentException(String.valueOf(kind));
+            }
+        }
+    }
+    
+    private static Pair<Relation, Relation> getLengthRelations(Way w, Node n) {
+        final List<Relation> left = new ArrayList<Relation>();
+        final List<Relation> right = new ArrayList<Relation>();
+        
+        for (OsmPrimitive p : w.getReferrers()) {
+            if (p.getType() != OsmPrimitiveType.RELATION) {
+                continue;
+            }
+            
+            Relation r = (Relation) p;
+            
+            if (Constants.TYPE_LENGTHS.equals(r.get("type")) && isRightDirection(r, w, n)) {
+                
+                if (r.get(Constants.LENGTHS_KEY_LENGTHS_LEFT) != null) {
+                    left.add(r);
+                }
+                
+                if (r.get(Constants.LENGTHS_KEY_LENGTHS_RIGHT) != null) {
+                    right.add(r);
+                }
+            }
+        }
+        
+        if (left.size() > 1) {
+            throw new IllegalArgumentException("Way is in " + left.size()
+                + " lengths relations for given direction, both specifying left lane lengths.");
+        }
+        
+        if (right.size() > 1) {
+            throw new IllegalArgumentException("Way is in " + right.size()
+                + " lengths relations for given direction, both specifying right lane lengths.");
+        }
+        
+        return new Pair<Relation, Relation>( //
+            left.isEmpty() ? null : left.get(0), //
+            right.isEmpty() ? null : right.get(0) //
+        );
+    }
+    
+    /**
+     * @param r
+     *          lengths relation
+     * @param w
+     *          the way to check for
+     * @param n
+     *          first or last node of w, determines the direction
+     * @return whether the turn lane goes into the direction of n
+     */
+    private static boolean isRightDirection(Relation r, Way w, Node n) {
+        for (Segment s : Route.load(r).getSegments()) {
+            if (w.equals(s.getWay())) {
+                return n.equals(s.getEnd());
+            }
+        }
+        
+        return false;
+    }
+    
+    private final ModelContainer container;
+    private final Route route;
+    private final End fromEnd;
+    private final End toEnd;
+    
+    Road(ModelContainer container, Way w, Junction j) {
+        final Node n = j.getNode();
+        if (!w.isFirstLastNode(n)) {
+            throw new IllegalArgumentException("Way must start or end in given node.");
+        }
+        final Pair<Relation, Relation> lengthsRelations = getLengthRelations(w, n);
+        
+        this.container = container;
+        this.route = lengthsRelations.a == null && lengthsRelations.b == null ? Route.create(Arrays.asList(w), n) : Route
+            .load(lengthsRelations.a, lengthsRelations.b, w);
+        this.fromEnd = new End(true, container.getOrCreateJunction(route.getFirstSegment().getStart()));
+        this.toEnd = new End(false, j, lengthsRelations.a, lengthsRelations.b);
+    }
+    
+    Road(ModelContainer container, Route route) {
+        this.container = container;
+        this.route = route;
+        this.fromEnd = new End(true, container.getJunction(route.getStart()));
+        this.toEnd = new End(false, container.getJunction(route.getEnd()));
+    }
+    
+    public End getFromEnd() {
+        return fromEnd;
+    }
+    
+    public End getToEnd() {
+        return toEnd;
+    }
+    
+    public Route getRoute() {
+        return route;
+    }
+    
+    public double getLength() {
+        return route.getLength();
+    }
+    
+    private String toLengthString(double length) {
+        final DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance();
+        dfs.setDecimalSeparator('.');
+        final DecimalFormat nf = new DecimalFormat("0.0", dfs);
+        nf.setRoundingMode(RoundingMode.HALF_UP);
+        return nf.format(length);
+    }
+    
+    public ModelContainer getContainer() {
+        return container;
+    }
+    
+    public boolean isPrimary() {
+        return getContainer().isPrimary(this);
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Route.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Route.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Route.java	(revision 26154)
@@ -10,221 +10,221 @@
 
 public class Route {
-	public static final class Segment {
-		private final Node start;
-		private final Way way;
-		private final Node end;
-		
-		private final List<Node> nodes;
-		
-		Segment(Node start, Way way, Node end) {
-			this.start = start;
-			this.way = way;
-			this.end = end;
-			
-			final List<Node> ns = way.getNodes();
-			if (way.lastNode().equals(start)) {
-				Collections.reverse(ns);
-			}
-			
-			this.nodes = Collections.unmodifiableList(ns);
-		}
-		
-		public Node getStart() {
-			return start;
-		}
-		
-		public Way getWay() {
-			return way;
-		}
-		
-		public Node getEnd() {
-			return end;
-		}
-		
-		public List<Node> getNodes() {
-			return nodes;
-		}
-		
-		public double getLength() {
-			double length = 0;
-			
-			Node last = nodes.get(0);
-			for (Node n : nodes.subList(1, nodes.size())) {
-				length += last.getCoor().greatCircleDistance(n.getCoor());
-				last = n;
-			}
-			
-			return length;
-		}
-		
-		@Override
-		public int hashCode() {
-			final int prime = 31;
-			int result = 1;
-			result = prime * result + ((end == null) ? 0 : end.hashCode());
-			result = prime * result + ((start == null) ? 0 : start.hashCode());
-			result = prime * result + ((way == null) ? 0 : way.hashCode());
-			return result;
-		}
-		
-		@Override
-		public boolean equals(Object obj) {
-			if (this == obj)
-				return true;
-			if (obj == null)
-				return false;
-			if (getClass() != obj.getClass())
-				return false;
-			Segment other = (Segment) obj;
-			if (end == null) {
-				if (other.end != null)
-					return false;
-			} else if (!end.equals(other.end))
-				return false;
-			if (start == null) {
-				if (other.start != null)
-					return false;
-			} else if (!start.equals(other.start))
-				return false;
-			if (way == null) {
-				if (other.way != null)
-					return false;
-			} else if (!way.equals(other.way))
-				return false;
-			return true;
-		}
-	}
-	
-	public static Route load(Relation r) {
-		final Node end = Utils.getMemberNode(r, Constants.LENGTHS_ROLE_END);
-		final List<Way> ws = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS);
-		
-		return create(ws, end);
-	}
-	
-	public static Route load(Relation left, Relation right, Way w) {
-		left = left == null ? right : left;
-		right = right == null ? left : right;
-		
-		if (left == null) {
-			throw new IllegalArgumentException("At least one relation must not be null.");
-		}
-		
-		final Route leftRoute = load(left);
-		final Route rightRoute = load(right);
-		
-		int iLeft = 0;
-		while (!w.equals(leftRoute.getSegments().get(iLeft++).getWay()))
-			;
-		
-		int iRight = 0;
-		while (!w.equals(rightRoute.getSegments().get(iRight++).getWay()))
-			;
-		
-		final int min = Math.min(iLeft, iRight);
-		
-		final List<Segment> leftSegments = leftRoute.getSegments().subList(iLeft - min, iLeft);
-		final List<Segment> rightSegments = rightRoute.getSegments().subList(iRight - min, iRight);
-		
-		if (!leftSegments.equals(rightSegments)) {
-			throw new IllegalArgumentException("Routes are split across different ways.");
-		}
-		
-		return new Route(iLeft == min ? rightSegments : leftSegments);
-	}
-	
-	public static Route create(List<Way> ws, Node end) {
-		final List<Segment> segments = new ArrayList<Segment>(ws.size());
-		
-		for (Way w : ws) {
-			if (!w.isFirstLastNode(end)) {
-				throw new IllegalArgumentException("Ways must be ordered.");
-			}
-			
-			final Node start = Utils.getOppositeEnd(w, end);
-			segments.add(0, new Segment(start, w, end));
-			end = start;
-		}
-		
-		return new Route(segments);
-	}
-	
-	private final List<Segment> segments;
-	
-	private Route(List<Segment> segments) {
-		this.segments = Collections.unmodifiableList(new ArrayList<Segment>(segments));
-	}
-	
-	public List<Segment> getSegments() {
-		return segments;
-	}
-	
-	public List<Node> getNodes() {
-		final List<Node> ns = new ArrayList<Node>();
-		
-		ns.add(segments.get(0).getStart());
-		for (Segment s : segments) {
-			ns.addAll(s.getNodes().subList(1, s.getNodes().size()));
-		}
-		
-		return Collections.unmodifiableList(ns);
-	}
-	
-	public double getLengthFrom(Way w) {
-		double length = Double.NEGATIVE_INFINITY;
-		
-		for (Segment s : getSegments()) {
-			length += s.getLength();
-			
-			if (w.equals(s.getWay())) {
-				length = 0;
-			}
-		}
-		
-		if (length < 0) {
-			throw new IllegalArgumentException("Way must be part of the route.");
-		}
-		
-		return length;
-	}
-	
-	public double getLength() {
-		double length = 0;
-		
-		for (Segment s : getSegments()) {
-			length += s.getLength();
-		}
-		
-		return length;
-	}
-	
-	public Node getStart() {
-		return getFirstSegment().getStart();
-	}
-	
-	public Node getEnd() {
-		return getLastSegment().getEnd();
-	}
-	
-	public Segment getFirstSegment() {
-		return getSegments().get(0);
-	}
-	
-	public Segment getLastSegment() {
-		return getSegments().get(getSegments().size() - 1);
-	}
-	
-	public Route subRoute(int fromIndex, int toIndex) {
-		return new Route(segments.subList(fromIndex, toIndex));
-	}
-	
-	public List<Way> getWays() {
-		final List<Way> ws = new ArrayList<Way>();
-		
-		for (Segment s : segments) {
-			ws.add(s.getWay());
-		}
-		
-		return Collections.unmodifiableList(ws);
-	}
+    public static final class Segment {
+        private final Node start;
+        private final Way way;
+        private final Node end;
+        
+        private final List<Node> nodes;
+        
+        Segment(Node start, Way way, Node end) {
+            this.start = start;
+            this.way = way;
+            this.end = end;
+            
+            final List<Node> ns = way.getNodes();
+            if (way.lastNode().equals(start)) {
+                Collections.reverse(ns);
+            }
+            
+            this.nodes = Collections.unmodifiableList(ns);
+        }
+        
+        public Node getStart() {
+            return start;
+        }
+        
+        public Way getWay() {
+            return way;
+        }
+        
+        public Node getEnd() {
+            return end;
+        }
+        
+        public List<Node> getNodes() {
+            return nodes;
+        }
+        
+        public double getLength() {
+            double length = 0;
+            
+            Node last = nodes.get(0);
+            for (Node n : nodes.subList(1, nodes.size())) {
+                length += last.getCoor().greatCircleDistance(n.getCoor());
+                last = n;
+            }
+            
+            return length;
+        }
+        
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((end == null) ? 0 : end.hashCode());
+            result = prime * result + ((start == null) ? 0 : start.hashCode());
+            result = prime * result + ((way == null) ? 0 : way.hashCode());
+            return result;
+        }
+        
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            Segment other = (Segment) obj;
+            if (end == null) {
+                if (other.end != null)
+                    return false;
+            } else if (!end.equals(other.end))
+                return false;
+            if (start == null) {
+                if (other.start != null)
+                    return false;
+            } else if (!start.equals(other.start))
+                return false;
+            if (way == null) {
+                if (other.way != null)
+                    return false;
+            } else if (!way.equals(other.way))
+                return false;
+            return true;
+        }
+    }
+    
+    public static Route load(Relation r) {
+        final Node end = Utils.getMemberNode(r, Constants.LENGTHS_ROLE_END);
+        final List<Way> ws = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS);
+        
+        return create(ws, end);
+    }
+    
+    public static Route load(Relation left, Relation right, Way w) {
+        left = left == null ? right : left;
+        right = right == null ? left : right;
+        
+        if (left == null) {
+            throw new IllegalArgumentException("At least one relation must not be null.");
+        }
+        
+        final Route leftRoute = load(left);
+        final Route rightRoute = load(right);
+        
+        int iLeft = 0;
+        while (!w.equals(leftRoute.getSegments().get(iLeft++).getWay()))
+            ;
+        
+        int iRight = 0;
+        while (!w.equals(rightRoute.getSegments().get(iRight++).getWay()))
+            ;
+        
+        final int min = Math.min(iLeft, iRight);
+        
+        final List<Segment> leftSegments = leftRoute.getSegments().subList(iLeft - min, iLeft);
+        final List<Segment> rightSegments = rightRoute.getSegments().subList(iRight - min, iRight);
+        
+        if (!leftSegments.equals(rightSegments)) {
+            throw new IllegalArgumentException("Routes are split across different ways.");
+        }
+        
+        return new Route(iLeft == min ? rightSegments : leftSegments);
+    }
+    
+    public static Route create(List<Way> ws, Node end) {
+        final List<Segment> segments = new ArrayList<Segment>(ws.size());
+        
+        for (Way w : ws) {
+            if (!w.isFirstLastNode(end)) {
+                throw new IllegalArgumentException("Ways must be ordered.");
+            }
+            
+            final Node start = Utils.getOppositeEnd(w, end);
+            segments.add(0, new Segment(start, w, end));
+            end = start;
+        }
+        
+        return new Route(segments);
+    }
+    
+    private final List<Segment> segments;
+    
+    private Route(List<Segment> segments) {
+        this.segments = Collections.unmodifiableList(new ArrayList<Segment>(segments));
+    }
+    
+    public List<Segment> getSegments() {
+        return segments;
+    }
+    
+    public List<Node> getNodes() {
+        final List<Node> ns = new ArrayList<Node>();
+        
+        ns.add(segments.get(0).getStart());
+        for (Segment s : segments) {
+            ns.addAll(s.getNodes().subList(1, s.getNodes().size()));
+        }
+        
+        return Collections.unmodifiableList(ns);
+    }
+    
+    public double getLengthFrom(Way w) {
+        double length = Double.NEGATIVE_INFINITY;
+        
+        for (Segment s : getSegments()) {
+            length += s.getLength();
+            
+            if (w.equals(s.getWay())) {
+                length = 0;
+            }
+        }
+        
+        if (length < 0) {
+            throw new IllegalArgumentException("Way must be part of the route.");
+        }
+        
+        return length;
+    }
+    
+    public double getLength() {
+        double length = 0;
+        
+        for (Segment s : getSegments()) {
+            length += s.getLength();
+        }
+        
+        return length;
+    }
+    
+    public Node getStart() {
+        return getFirstSegment().getStart();
+    }
+    
+    public Node getEnd() {
+        return getLastSegment().getEnd();
+    }
+    
+    public Segment getFirstSegment() {
+        return getSegments().get(0);
+    }
+    
+    public Segment getLastSegment() {
+        return getSegments().get(getSegments().size() - 1);
+    }
+    
+    public Route subRoute(int fromIndex, int toIndex) {
+        return new Route(segments.subList(fromIndex, toIndex));
+    }
+    
+    public List<Way> getWays() {
+        final List<Way> ws = new ArrayList<Way>();
+        
+        for (Segment s : segments) {
+            ws.add(s.getWay());
+        }
+        
+        return Collections.unmodifiableList(ws);
+    }
 }
Index: applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Turn.java
===================================================================
--- applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Turn.java	(revision 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Turn.java	(revision 26154)
@@ -17,176 +17,176 @@
 
 public final class Turn {
-	static Set<Turn> load(ModelContainer c, String role, OsmPrimitive primitive) {
-		final Set<Turn> result = new HashSet<Turn>();
-		
-		for (Relation r : OsmPrimitive.getFilteredList(primitive.getReferrers(), Relation.class)) {
-			if (!r.get("type").equals(Constants.TYPE_TURNS)) {
-				continue;
-			}
-			
-			for (RelationMember m : r.getMembers()) {
-				if (m.getRole().equals(role) && m.getMember().equals(primitive)) {
-					result.addAll(load(c, r));
-				}
-			}
-		}
-		
-		return result;
-	}
-	
-	static Set<Turn> load(ModelContainer c, Relation r) {
-		for (RelationMember m : r.getMembers()) {
-			if (m.getRole().equals(Constants.TURN_ROLE_VIA)) {
-				if (m.isNode()) {
-					return loadWithViaNode(c, r);
-				} else if (m.isWay()) {
-					return loadWithViaWays(c, r);
-				}
-			}
-		}
-		
-		throw new IllegalArgumentException("No via node or way(s).");
-	}
-	
-	private static Set<Turn> loadWithViaWays(ModelContainer c, Relation r) {
-		final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
-		final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
-		
-		final List<Way> tmp = Utils.getMemberWays(r, Constants.TURN_ROLE_VIA);
-		final LinkedList<Road> via = new LinkedList<Road>();
-		
-		final Road.End fromRoadEnd = c.getJunction(Utils.lineUp(from, tmp.get(0))).getRoadEnd(from);
-		
-		Node n = fromRoadEnd.getJunction().getNode();
-		final Iterator<Way> it = tmp.iterator();
-		while (it.hasNext()) {
-			final Way w = it.next();
-			final Road v = c.getRoad(w);
-			via.add(v);
-			n = Utils.getOppositeEnd(w, n);
-			
-			if (!v.isPrimary()) {
-				throw new IllegalStateException("The road is not part of the junction.");
-			}
-			
-			final Iterator<Route.Segment> it2 = (v.getRoute().getFirstSegment().getWay().equals(w) ? v.getRoute()
-			    .getSegments() : CollectionUtils.reverse(v.getRoute().getSegments())).iterator();
-			it2.next(); // first is done
-			
-			while (it2.hasNext()) {
-				final Way w2 = it2.next().getWay();
-				n = Utils.getOppositeEnd(w2, n);
-				
-				if (!it.hasNext() || !w2.equals(it.next())) {
-					throw new IllegalStateException("The via ways of the relation do not form a road.");
-				}
-			}
-		}
-		final Road.End toRoadEnd = c.getJunction(n).getRoadEnd(to);
-		n = Utils.getOppositeEnd(to, n);
-		
-		final Set<Turn> result = new HashSet<Turn>();
-		for (int i : indices(r, Constants.TURN_KEY_LANES)) {
-			result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), via, toRoadEnd));
-		}
-		for (int i : indices(r, Constants.TURN_KEY_EXTRA_LANES)) {
-			result.add(new Turn(r, fromRoadEnd.getExtraLane(i), via, toRoadEnd));
-		}
-		return result;
-	}
-	
-	static List<Integer> indices(Relation r, String key) {
-		final String joined = r.get(key);
-		
-		if (joined == null) {
-			return new ArrayList<Integer>(1);
-		}
-		
-		final List<Integer> result = new ArrayList<Integer>();
-		for (String lane : Constants.SPLIT_PATTERN.split(joined)) {
-			result.add(Integer.parseInt(lane));
-		}
-		
-		return result;
-	}
-	
-	private static Set<Turn> loadWithViaNode(ModelContainer c, Relation r) {
-		final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
-		final Node via = Utils.getMemberNode(r, Constants.TURN_ROLE_VIA);
-		final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
-		
-		final Junction j = c.getJunction(via);
-		
-		final Road.End fromRoadEnd = j.getRoadEnd(from);
-		final Road.End toRoadEnd = j.getRoadEnd(to);
-		
-		final Set<Turn> result = new HashSet<Turn>();
-		for (int i : indices(r, Constants.TURN_KEY_LANES)) {
-			result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), Collections.<Road> emptyList(), toRoadEnd));
-		}
-		for (int i : indices(r, Constants.TURN_KEY_EXTRA_LANES)) {
-			result.add(new Turn(r, fromRoadEnd.getExtraLane(i), Collections.<Road> emptyList(), toRoadEnd));
-		}
-		return result;
-	}
-	
-	static String join(List<Integer> list) {
-		if (list.isEmpty()) {
-			return null;
-		}
-		
-		final StringBuilder builder = new StringBuilder(list.size() * (2 + Constants.SEPARATOR.length()));
-		
-		for (int e : list) {
-			builder.append(e).append(Constants.SEPARATOR);
-		}
-		
-		builder.setLength(builder.length() - Constants.SEPARATOR.length());
-		return builder.toString();
-	}
-	
-	private final Relation relation;
-	
-	private final Lane from;
-	private final List<Road> via;
-	private final Road.End to;
-	
-	public Turn(Relation relation, Lane from, List<Road> via, Road.End to) {
-		this.relation = relation;
-		this.from = from;
-		this.via = via;
-		this.to = to;
-	}
-	
-	public Lane getFrom() {
-		return from;
-	}
-	
-	public List<Road> getVia() {
-		return via;
-	}
-	
-	public Road.End getTo() {
-		return to;
-	}
-	
-	Relation getRelation() {
-		return relation;
-	}
-	
-	public void remove() {
-		final List<Integer> lanes = indices(relation, Constants.TURN_KEY_LANES);
-		final List<Integer> extraLanes = indices(relation, Constants.TURN_KEY_EXTRA_LANES);
-		
-		if (lanes.size() + extraLanes.size() == 1 && (from.isExtra() ^ !lanes.isEmpty())) {
-			relation.getDataSet().removePrimitive(relation.getPrimitiveId());
-		} else if (from.isExtra()) {
-			extraLanes.remove(Integer.valueOf(from.getIndex()));
-		} else {
-			lanes.remove(Integer.valueOf(from.getIndex()));
-		}
-		
-		relation.put(Constants.TURN_KEY_LANES, lanes.isEmpty() ? null : join(lanes));
-		relation.put(Constants.TURN_KEY_EXTRA_LANES, extraLanes.isEmpty() ? null : join(extraLanes));
-	}
+    static Set<Turn> load(ModelContainer c, String role, OsmPrimitive primitive) {
+        final Set<Turn> result = new HashSet<Turn>();
+        
+        for (Relation r : OsmPrimitive.getFilteredList(primitive.getReferrers(), Relation.class)) {
+            if (!r.get("type").equals(Constants.TYPE_TURNS)) {
+                continue;
+            }
+            
+            for (RelationMember m : r.getMembers()) {
+                if (m.getRole().equals(role) && m.getMember().equals(primitive)) {
+                    result.addAll(load(c, r));
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    static Set<Turn> load(ModelContainer c, Relation r) {
+        for (RelationMember m : r.getMembers()) {
+            if (m.getRole().equals(Constants.TURN_ROLE_VIA)) {
+                if (m.isNode()) {
+                    return loadWithViaNode(c, r);
+                } else if (m.isWay()) {
+                    return loadWithViaWays(c, r);
+                }
+            }
+        }
+        
+        throw new IllegalArgumentException("No via node or way(s).");
+    }
+    
+    private static Set<Turn> loadWithViaWays(ModelContainer c, Relation r) {
+        final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
+        final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
+        
+        final List<Way> tmp = Utils.getMemberWays(r, Constants.TURN_ROLE_VIA);
+        final LinkedList<Road> via = new LinkedList<Road>();
+        
+        final Road.End fromRoadEnd = c.getJunction(Utils.lineUp(from, tmp.get(0))).getRoadEnd(from);
+        
+        Node n = fromRoadEnd.getJunction().getNode();
+        final Iterator<Way> it = tmp.iterator();
+        while (it.hasNext()) {
+            final Way w = it.next();
+            final Road v = c.getRoad(w);
+            via.add(v);
+            n = Utils.getOppositeEnd(w, n);
+            
+            if (!v.isPrimary()) {
+                throw new IllegalStateException("The road is not part of the junction.");
+            }
+            
+            final Iterator<Route.Segment> it2 = (v.getRoute().getFirstSegment().getWay().equals(w) ? v.getRoute()
+                .getSegments() : CollectionUtils.reverse(v.getRoute().getSegments())).iterator();
+            it2.next(); // first is done
+            
+            while (it2.hasNext()) {
+                final Way w2 = it2.next().getWay();
+                n = Utils.getOppositeEnd(w2, n);
+                
+                if (!it.hasNext() || !w2.equals(it.next())) {
+                    throw new IllegalStateException("The via ways of the relation do not form a road.");
+                }
+            }
+        }
+        final Road.End toRoadEnd = c.getJunction(n).getRoadEnd(to);
+        n = Utils.getOppositeEnd(to, n);
+        
+        final Set<Turn> result = new HashSet<Turn>();
+        for (int i : indices(r, Constants.TURN_KEY_LANES)) {
+            result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), via, toRoadEnd));
+        }
+        for (int i : indices(r, Constants.TURN_KEY_EXTRA_LANES)) {
+            result.add(new Turn(r, fromRoadEnd.getExtraLane(i), via, toRoadEnd));
+        }
+        return result;
+    }
+    
+    static List<Integer> indices(Relation r, String key) {
+        final String joined = r.get(key);
+        
+        if (joined == null) {
+            return new ArrayList<Integer>(1);
+        }
+        
+        final List<Integer> result = new ArrayList<Integer>();
+        for (String lane : Constants.SPLIT_PATTERN.split(joined)) {
+            result.add(Integer.parseInt(lane));
+        }
+        
+        return result;
+    }
+    
+    private static Set<Turn> loadWithViaNode(ModelContainer c, Relation r) {
+        final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
+        final Node via = Utils.getMemberNode(r, Constants.TURN_ROLE_VIA);
+        final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
+        
+        final Junction j = c.getJunction(via);
+        
+        final Road.End fromRoadEnd = j.getRoadEnd(from);
+        final Road.End toRoadEnd = j.getRoadEnd(to);
+        
+        final Set<Turn> result = new HashSet<Turn>();
+        for (int i : indices(r, Constants.TURN_KEY_LANES)) {
+            result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), Collections.<Road> emptyList(), toRoadEnd));
+        }
+        for (int i : indices(r, Constants.TURN_KEY_EXTRA_LANES)) {
+            result.add(new Turn(r, fromRoadEnd.getExtraLane(i), Collections.<Road> emptyList(), toRoadEnd));
+        }
+        return result;
+    }
+    
+    static String join(List<Integer> list) {
+        if (list.isEmpty()) {
+            return null;
+        }
+        
+        final StringBuilder builder = new StringBuilder(list.size() * (2 + Constants.SEPARATOR.length()));
+        
+        for (int e : list) {
+            builder.append(e).append(Constants.SEPARATOR);
+        }
+        
+        builder.setLength(builder.length() - Constants.SEPARATOR.length());
+        return builder.toString();
+    }
+    
+    private final Relation relation;
+    
+    private final Lane from;
+    private final List<Road> via;
+    private final Road.End to;
+    
+    public Turn(Relation relation, Lane from, List<Road> via, Road.End to) {
+        this.relation = relation;
+        this.from = from;
+        this.via = via;
+        this.to = to;
+    }
+    
+    public Lane getFrom() {
+        return from;
+    }
+    
+    public List<Road> getVia() {
+        return via;
+    }
+    
+    public Road.End getTo() {
+        return to;
+    }
+    
+    Relation getRelation() {
+        return relation;
+    }
+    
+    public void remove() {
+        final List<Integer> lanes = indices(relation, Constants.TURN_KEY_LANES);
+        final List<Integer> extraLanes = indices(relation, Constants.TURN_KEY_EXTRA_LANES);
+        
+        if (lanes.size() + extraLanes.size() == 1 && (from.isExtra() ^ !lanes.isEmpty())) {
+            relation.getDataSet().removePrimitive(relation.getPrimitiveId());
+        } else if (from.isExtra()) {
+            extraLanes.remove(Integer.valueOf(from.getIndex()));
+        } else {
+            lanes.remove(Integer.valueOf(from.getIndex()));
+        }
+        
+        relation.put(Constants.TURN_KEY_LANES, lanes.isEmpty() ? null : join(lanes));
+        relation.put(Constants.TURN_KEY_EXTRA_LANES, extraLanes.isEmpty() ? null : join(extraLanes));
+    }
 }
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 26154)
@@ -6,44 +6,44 @@
 
 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;
-	}
+    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 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Utils.java	(revision 26154)
@@ -20,206 +20,206 @@
 
 public class Utils {
-	private static final Set<String> ROAD_HIGHWAY_VALUES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
-	    "motorway", "motorway_link", "trunk", "trunk_link", "primary", "primary_link", "secondary", "secondary_link",
-	    "tertiary", "residential", "unclassified", "road", "living_street", "service", "track", "pedestrian", "raceway",
-	    "services")));
-	
-	public static boolean isRoad(Way w) {
-		return ROAD_HIGHWAY_VALUES.contains(w.get("highway"));
-	}
-	
-	public static final List<Way> filterRoads(List<OsmPrimitive> of) {
-		final List<Way> result = new ArrayList<Way>();
-		
-		for (OsmPrimitive p : of) {
-			if (p.getType() == OsmPrimitiveType.WAY && Utils.isRoad((Way) p)) {
-				result.add((Way) p);
-			}
-		}
-		
-		return result;
-	}
-	
-	public static Node getMemberNode(Relation r, String role) {
-		return getMember(r, role, OsmPrimitiveType.NODE).getNode();
-	}
-	
-	public static Way getMemberWay(Relation r, String role) {
-		return getMember(r, role, OsmPrimitiveType.WAY).getWay();
-	}
-	
-	public static RelationMember getMember(Relation r, String role, OsmPrimitiveType type) {
-		final List<RelationMember> candidates = getMembers(r, role, type);
-		if (candidates.isEmpty()) {
-			throw UnexpectedDataException.Kind.NO_MEMBER.chuck(role);
-		} else if (candidates.size() > 1) {
-			throw UnexpectedDataException.Kind.MULTIPLE_MEMBERS.chuck(role);
-		}
-		return candidates.get(0);
-	}
-	
-	public static List<RelationMember> getMembers(Relation r, String role, OsmPrimitiveType type) {
-		final List<RelationMember> result = getMembers(r, role);
-		for (RelationMember m : getMembers(r, role)) {
-			if (m.getType() != type) {
-				throw UnexpectedDataException.Kind.WRONG_MEMBER_TYPE.chuck(role, m.getType(), type);
-			}
-		}
-		return result;
-	}
-	
-	public static List<RelationMember> getMembers(Relation r, String role) {
-		final List<RelationMember> result = new ArrayList<RelationMember>();
-		for (RelationMember m : r.getMembers()) {
-			if (m.getRole().equals(role)) {
-				result.add(m);
-			}
-		}
-		return result;
-	}
-	
-	public static List<Node> getMemberNodes(Relation r, String role) {
-		return mapMembers(getMembers(r, role, OsmPrimitiveType.NODE), Node.class);
-	}
-	
-	public static List<Way> getMemberWays(Relation r, String role) {
-		return mapMembers(getMembers(r, role, OsmPrimitiveType.WAY), Way.class);
-	}
-	
-	private static <T> List<T> mapMembers(List<RelationMember> ms, Class<T> t) {
-		final List<T> result = new ArrayList<T>(ms.size());
-		for (RelationMember m : ms) {
-			result.add(t.cast(m.getMember()));
-		}
-		return result;
-	}
-	
-	/**
-	 * 
-	 * @param a
-	 * @param b
-	 * @return the node at which {@code a} and {@code b} are connected
-	 */
-	public static Node lineUp(Way a, Way b) {
-		final Set<Node> s = new HashSet<Node>(Arrays.asList(a.firstNode(), a.lastNode(), b.firstNode(), b.lastNode()));
-		if (a.firstNode() == a.lastNode() || b.firstNode().equals(b.lastNode()) || s.size() == 2) {
-			throw new IllegalArgumentException("Cycles are not allowed.");
-		} else if (s.size() == 4) {
-			throw new IllegalArgumentException("Ways are not connected (at their first and last nodes).");
-		}
-		
-		if (a.firstNode() == b.firstNode() || a.lastNode() == b.firstNode()) {
-			return b.firstNode();
-		} else if (a.firstNode() == b.lastNode() || a.lastNode() == b.lastNode()) {
-			return b.lastNode();
-		} else {
-			throw new AssertionError();
-		}
-	}
-	
-	public static Node getOppositeEnd(Way w, Node n) {
-		final boolean first = n.equals(w.firstNode());
-		final boolean last = n.equals(w.lastNode());
-		
-		if (first && last) {
-			throw new IllegalArgumentException("Way starts as well as ends at the given node.");
-		} else if (first) {
-			return w.lastNode();
-		} else if (last) {
-			return w.firstNode();
-		} else {
-			throw new IllegalArgumentException("Way neither starts nor ends at given node.");
-		}
-	}
-	
-	/**
-	 * Orders the {@code ways} such that the combined ways out of each returned list form a path (in
-	 * order) from one node out of {@code nodes} to another out of {@code nodes}.
-	 * 
-	 * <ul>
-	 * <li>Each way is used exactly once.</li>
-	 * <li>Paths contain no {@code nodes} excepting the first and last nodes.</li>
-	 * <li>Paths contain no loops w.r.t. the ways' first and last nodes</li>
-	 * </ul>
-	 * 
-	 * @param ways
-	 *          ways to be ordered
-	 * @param nodes
-	 *          start/end nodes
-	 * @return
-	 * @throws IllegalArgumentException
-	 *           if the ways can't be ordered
-	 */
-	public static List<Route> orderWays(Iterable<Way> ways, Iterable<Node> nodes) {
-		final List<Way> ws = new LinkedList<Way>(CollectionUtils.toList(ways));
-		final Set<Node> ns = new HashSet<Node>(CollectionUtils.toList(nodes));
-		
-		final List<Route> result = new ArrayList<Route>();
-		
-		while (!ws.isEmpty()) {
-			result.add(findPath(ws, ns));
-		}
-		
-		return result;
-	}
-	
-	private static Route findPath(List<Way> ws, Set<Node> ns) {
-		final Way w = findPathSegment(ws, ns);
-		final boolean first = ns.contains(w.firstNode());
-		final boolean last = ns.contains(w.lastNode());
-		
-		if (first && last) {
-			return Route.create(Arrays.asList(w), w.firstNode());
-		} else if (!first && !last) {
-			throw new AssertionError();
-		}
-		
-		final List<Way> result = new ArrayList<Way>();
-		result.add(w);
-		Node n = first ? w.lastNode() : w.firstNode();
-		while (true) {
-			final Way next = findPathSegment(ws, Arrays.asList(n));
-			result.add(next);
-			n = getOppositeEnd(next, n);
-			
-			if (ns.contains(n)) {
-				return Route.create(result, first ? w.firstNode() : w.lastNode());
-			}
-		}
-	}
-	
-	private static Way findPathSegment(List<Way> ws, Collection<Node> ns) {
-		final Iterator<Way> it = ws.iterator();
-		
-		while (it.hasNext()) {
-			final Way w = it.next();
-			
-			if (ns.contains(w.firstNode()) || ns.contains(w.lastNode())) {
-				it.remove();
-				return w;
-			}
-		}
-		
-		throw new IllegalArgumentException("Ways can't be ordered.");
-	}
-	
-	public static Iterable<Way> flattenVia(Node start, List<Road> via, Node end) {
-		final List<Way> result = new ArrayList<Way>();
-		
-		Node n = start;
-		for (Road r : via) {
-			final Iterable<Route.Segment> segments = r.getRoute().getFirstSegment().getWay().isFirstLastNode(n) ? r
-			    .getRoute().getSegments() : CollectionUtils.reverse(r.getRoute().getSegments());
-			
-			for (Route.Segment s : segments) {
-				result.add(s.getWay());
-				n = Utils.getOppositeEnd(s.getWay(), n);
-			}
-		}
-		if (!end.equals(n)) {
-			throw new IllegalArgumentException("The given via ways don't end at the given node.");
-		}
-		
-		return result;
-	}
+    private static final Set<String> ROAD_HIGHWAY_VALUES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+        "motorway", "motorway_link", "trunk", "trunk_link", "primary", "primary_link", "secondary", "secondary_link",
+        "tertiary", "residential", "unclassified", "road", "living_street", "service", "track", "pedestrian", "raceway",
+        "services")));
+    
+    public static boolean isRoad(Way w) {
+        return ROAD_HIGHWAY_VALUES.contains(w.get("highway"));
+    }
+    
+    public static final List<Way> filterRoads(List<OsmPrimitive> of) {
+        final List<Way> result = new ArrayList<Way>();
+        
+        for (OsmPrimitive p : of) {
+            if (p.getType() == OsmPrimitiveType.WAY && Utils.isRoad((Way) p)) {
+                result.add((Way) p);
+            }
+        }
+        
+        return result;
+    }
+    
+    public static Node getMemberNode(Relation r, String role) {
+        return getMember(r, role, OsmPrimitiveType.NODE).getNode();
+    }
+    
+    public static Way getMemberWay(Relation r, String role) {
+        return getMember(r, role, OsmPrimitiveType.WAY).getWay();
+    }
+    
+    public static RelationMember getMember(Relation r, String role, OsmPrimitiveType type) {
+        final List<RelationMember> candidates = getMembers(r, role, type);
+        if (candidates.isEmpty()) {
+            throw UnexpectedDataException.Kind.NO_MEMBER.chuck(role);
+        } else if (candidates.size() > 1) {
+            throw UnexpectedDataException.Kind.MULTIPLE_MEMBERS.chuck(role);
+        }
+        return candidates.get(0);
+    }
+    
+    public static List<RelationMember> getMembers(Relation r, String role, OsmPrimitiveType type) {
+        final List<RelationMember> result = getMembers(r, role);
+        for (RelationMember m : getMembers(r, role)) {
+            if (m.getType() != type) {
+                throw UnexpectedDataException.Kind.WRONG_MEMBER_TYPE.chuck(role, m.getType(), type);
+            }
+        }
+        return result;
+    }
+    
+    public static List<RelationMember> getMembers(Relation r, String role) {
+        final List<RelationMember> result = new ArrayList<RelationMember>();
+        for (RelationMember m : r.getMembers()) {
+            if (m.getRole().equals(role)) {
+                result.add(m);
+            }
+        }
+        return result;
+    }
+    
+    public static List<Node> getMemberNodes(Relation r, String role) {
+        return mapMembers(getMembers(r, role, OsmPrimitiveType.NODE), Node.class);
+    }
+    
+    public static List<Way> getMemberWays(Relation r, String role) {
+        return mapMembers(getMembers(r, role, OsmPrimitiveType.WAY), Way.class);
+    }
+    
+    private static <T> List<T> mapMembers(List<RelationMember> ms, Class<T> t) {
+        final List<T> result = new ArrayList<T>(ms.size());
+        for (RelationMember m : ms) {
+            result.add(t.cast(m.getMember()));
+        }
+        return result;
+    }
+    
+    /**
+     * 
+     * @param a
+     * @param b
+     * @return the node at which {@code a} and {@code b} are connected
+     */
+    public static Node lineUp(Way a, Way b) {
+        final Set<Node> s = new HashSet<Node>(Arrays.asList(a.firstNode(), a.lastNode(), b.firstNode(), b.lastNode()));
+        if (a.firstNode() == a.lastNode() || b.firstNode().equals(b.lastNode()) || s.size() == 2) {
+            throw new IllegalArgumentException("Cycles are not allowed.");
+        } else if (s.size() == 4) {
+            throw new IllegalArgumentException("Ways are not connected (at their first and last nodes).");
+        }
+        
+        if (a.firstNode() == b.firstNode() || a.lastNode() == b.firstNode()) {
+            return b.firstNode();
+        } else if (a.firstNode() == b.lastNode() || a.lastNode() == b.lastNode()) {
+            return b.lastNode();
+        } else {
+            throw new AssertionError();
+        }
+    }
+    
+    public static Node getOppositeEnd(Way w, Node n) {
+        final boolean first = n.equals(w.firstNode());
+        final boolean last = n.equals(w.lastNode());
+        
+        if (first && last) {
+            throw new IllegalArgumentException("Way starts as well as ends at the given node.");
+        } else if (first) {
+            return w.lastNode();
+        } else if (last) {
+            return w.firstNode();
+        } else {
+            throw new IllegalArgumentException("Way neither starts nor ends at given node.");
+        }
+    }
+    
+    /**
+     * Orders the {@code ways} such that the combined ways out of each returned list form a path (in
+     * order) from one node out of {@code nodes} to another out of {@code nodes}.
+     * 
+     * <ul>
+     * <li>Each way is used exactly once.</li>
+     * <li>Paths contain no {@code nodes} excepting the first and last nodes.</li>
+     * <li>Paths contain no loops w.r.t. the ways' first and last nodes</li>
+     * </ul>
+     * 
+     * @param ways
+     *          ways to be ordered
+     * @param nodes
+     *          start/end nodes
+     * @return
+     * @throws IllegalArgumentException
+     *           if the ways can't be ordered
+     */
+    public static List<Route> orderWays(Iterable<Way> ways, Iterable<Node> nodes) {
+        final List<Way> ws = new LinkedList<Way>(CollectionUtils.toList(ways));
+        final Set<Node> ns = new HashSet<Node>(CollectionUtils.toList(nodes));
+        
+        final List<Route> result = new ArrayList<Route>();
+        
+        while (!ws.isEmpty()) {
+            result.add(findPath(ws, ns));
+        }
+        
+        return result;
+    }
+    
+    private static Route findPath(List<Way> ws, Set<Node> ns) {
+        final Way w = findPathSegment(ws, ns);
+        final boolean first = ns.contains(w.firstNode());
+        final boolean last = ns.contains(w.lastNode());
+        
+        if (first && last) {
+            return Route.create(Arrays.asList(w), w.firstNode());
+        } else if (!first && !last) {
+            throw new AssertionError();
+        }
+        
+        final List<Way> result = new ArrayList<Way>();
+        result.add(w);
+        Node n = first ? w.lastNode() : w.firstNode();
+        while (true) {
+            final Way next = findPathSegment(ws, Arrays.asList(n));
+            result.add(next);
+            n = getOppositeEnd(next, n);
+            
+            if (ns.contains(n)) {
+                return Route.create(result, first ? w.firstNode() : w.lastNode());
+            }
+        }
+    }
+    
+    private static Way findPathSegment(List<Way> ws, Collection<Node> ns) {
+        final Iterator<Way> it = ws.iterator();
+        
+        while (it.hasNext()) {
+            final Way w = it.next();
+            
+            if (ns.contains(w.firstNode()) || ns.contains(w.lastNode())) {
+                it.remove();
+                return w;
+            }
+        }
+        
+        throw new IllegalArgumentException("Ways can't be ordered.");
+    }
+    
+    public static Iterable<Way> flattenVia(Node start, List<Road> via, Node end) {
+        final List<Way> result = new ArrayList<Way>();
+        
+        Node n = start;
+        for (Road r : via) {
+            final Iterable<Route.Segment> segments = r.getRoute().getFirstSegment().getWay().isFirstLastNode(n) ? r
+                .getRoute().getSegments() : CollectionUtils.reverse(r.getRoute().getSegments());
+            
+            for (Route.Segment s : segments) {
+                result.add(s.getWay());
+                n = Utils.getOppositeEnd(s.getWay(), n);
+            }
+        }
+        if (!end.equals(n)) {
+            throw new IllegalArgumentException("The given via ways don't end at the given node.");
+        }
+        
+        return result;
+    }
 }
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 25908)
+++ applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Validator.java	(revision 26154)
@@ -23,393 +23,393 @@
 
 public class Validator {
-	private static final class IncomingLanes {
-		private static final class Key {
-			final Node junction;
-			final Way from;
-			
-			public Key(Node junction, Way from) {
-				this.junction = junction;
-				this.from = from;
-			}
-			
-			@Override
-			public int hashCode() {
-				final int prime = 31;
-				int result = 1;
-				result = prime * result + ((from == null) ? 0 : from.hashCode());
-				result = prime * result + ((junction == null) ? 0 : junction.hashCode());
-				return result;
-			}
-			
-			@Override
-			public boolean equals(Object obj) {
-				if (this == obj)
-					return true;
-				if (obj == null)
-					return false;
-				if (getClass() != obj.getClass())
-					return false;
-				Key other = (Key) obj;
-				if (from == null) {
-					if (other.from != null)
-						return false;
-				} else if (!from.equals(other.from))
-					return false;
-				if (junction == null) {
-					if (other.junction != null)
-						return false;
-				} else if (!junction.equals(other.junction))
-					return false;
-				return true;
-			}
-		}
-		
-		final Key key;
-		private final int extraLeft;
-		private final int regular;
-		private final int extraRight;
-		private final BitSet bitset;
-		
-		public IncomingLanes(Key key, int extraLeft, int regular, int extraRight) {
-			this.key = key;
-			this.extraLeft = extraLeft;
-			this.regular = regular;
-			this.extraRight = extraRight;
-			this.bitset = new BitSet(extraLeft + regular + extraRight);
-		}
-		
-		public boolean existsRegular(int l) {
-			if (l > 0 && l <= regular) {
-				bitset.set(extraLeft + l - 1);
-				return true;
-			}
-			
-			return false;
-		}
-		
-		public boolean existsExtra(int l) {
-			if (l < 0 && Math.abs(l) <= extraLeft) {
-				bitset.set(Math.abs(l) - 1);
-				return true;
-			} else if (l > 0 && l <= extraRight) {
-				bitset.set(extraLeft + regular + l - 1);
-				return true;
-			}
-			return false;
-		}
-		
-		public int unreferenced() {
-			return extraLeft + regular + extraRight - bitset.cardinality();
-		}
-	}
-	
-	public List<Issue> validate(DataSet dataSet) {
-		final List<Relation> lenghts = new ArrayList<Relation>();
-		final List<Relation> turns = new ArrayList<Relation>();
-		
-		for (Relation r : OsmPrimitive.getFilteredList(dataSet.allPrimitives(), Relation.class)) {
-			final String type = r.get("type");
-			
-			if (Constants.TYPE_LENGTHS.equals(type)) {
-				lenghts.add(r);
-			} else if (Constants.TYPE_TURNS.equals(type)) {
-				turns.add(r);
-			}
-		}
-		
-		final List<Issue> issues = new ArrayList<Issue>();
-		
-		final Map<IncomingLanes.Key, IncomingLanes> incomingLanes = new HashMap<IncomingLanes.Key, IncomingLanes>();
-		issues.addAll(validateLengths(lenghts, incomingLanes));
-		issues.addAll(validateTurns(turns, incomingLanes));
-		
-		for (IncomingLanes lanes : incomingLanes.values()) {
-			if (lanes.unreferenced() > 0) {
-				issues.add(Issue.newWarning(Arrays.asList(lanes.key.junction, lanes.key.from),
-				    tr("{0} lanes are not referenced in any turn-relation.", lanes.unreferenced())));
-			}
-		}
-		
-		return issues;
-	}
-	
-	private List<Issue> validateLengths(List<Relation> lenghts, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
-		final List<Issue> issues = new ArrayList<Issue>();
-		
-		for (Relation r : lenghts) {
-			issues.addAll(validateLengths(r, incomingLanes));
-		}
-		
-		return issues;
-	}
-	
-	private List<Issue> validateLengths(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
-		final List<Issue> issues = new ArrayList<Issue>();
-		
-		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;
-			
-		} catch (UnexpectedDataException e) {
-			issues.add(Issue.newError(r, e.getMessage()));
-			return issues;
-		}
-	}
-	
-	private void putIncomingLanes(Route route, List<Double> left, List<Double> right,
-	    Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
-		final Node end = route.getLastSegment().getEnd();
-		final Way way = route.getLastSegment().getWay();
-		
-		final IncomingLanes.Key key = new IncomingLanes.Key(end, way);
-		final IncomingLanes lanes = new IncomingLanes(key, left.size(), Lane.getRegularCount(way, end), right.size());
-		final IncomingLanes old = incomingLanes.put(key, lanes);
-		
-		if (old != null) {
-			incomingLanes.put(
-			    key,
-			    new IncomingLanes(key, Math.max(lanes.extraLeft, old.extraLeft), Math.max(lanes.regular, old.regular), Math
-			        .max(lanes.extraRight, old.extraRight)));
-		}
-		
-		// 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);
-		//		}
-	}
-	
-	private List<Double> reduceLengths(List<Double> lengths, double length) {
-		final List<Double> newLengths = new ArrayList<Double>(lengths.size());
-		
-		for (double l : lengths) {
-			if (l > length) {
-				newLengths.add(l - length);
-			}
-		}
-		
-		return newLengths;
-	}
-	
-	private Route validateLengthsWays(Relation r, Node end, List<Issue> issues) {
-		final List<Way> ways = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS);
-		
-		if (ways.isEmpty()) {
-			issues.add(Issue.newError(r, "A lengths-relation requires at least one member-way with role \""
-			    + Constants.LENGTHS_ROLE_WAYS + "\"."));
-			return null;
-		}
-		
-		Node current = end;
-		for (Way w : ways) {
-			if (!w.isFirstLastNode(current)) {
-				return orderWays(r, ways, current, issues, "ways", "lengths");
-			}
-			
-			current = Utils.getOppositeEnd(w, current);
-		}
-		
-		return Route.create(ways, end);
-	}
-	
-	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());
-		final Set<Node> ends = new HashSet<Node>(); // to find cycles
-		
-		Node current = end;
-		findNext: while (!unordered.isEmpty()) {
-			if (!ends.add(current)) {
-				issues.add(Issue.newError(r, ways, "The " + role + " of the " + type
-				    + "-relation are unordered (and contain cycles)."));
-				return null;
-			}
-			
-			Iterator<Way> it = unordered.iterator();
-			while (it.hasNext()) {
-				final Way w = it.next();
-				
-				if (w.isFirstLastNode(current)) {
-					it.remove();
-					ordered.add(w);
-					current = Utils.getOppositeEnd(w, current);
-					continue findNext;
-				}
-			}
-			
-			issues.add(Issue.newError(r, ways, "The " + role + " of the " + type + "-relation are disconnected."));
-			return null;
-		}
-		
-		final QuickFix quickFix = new QuickFix(tr("Put the ways in order.")) {
-			@Override
-			public boolean perform() {
-				for (int i = r.getMembersCount() - 1; i >= 0; --i) {
-					final RelationMember m = r.getMember(i);
-					
-					if (m.isWay() && Constants.LENGTHS_ROLE_WAYS.equals(m.getRole())) {
-						r.removeMember(i);
-					}
-				}
-				
-				for (Way w : ordered) {
-					r.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, w));
-				}
-				
-				return true;
-			}
-		};
-		
-		issues.add(Issue.newError(r, ways, "The ways of the lengths-relation are unordered.", quickFix));
-		
-		return Route.create(ordered, end);
-	}
-	
-	private List<Issue> validateTurns(List<Relation> turns, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
-		final List<Issue> issues = new ArrayList<Issue>();
-		
-		for (Relation r : turns) {
-			issues.addAll(validateTurns(r, incomingLanes));
-		}
-		
-		return issues;
-	}
-	
-	private List<Issue> validateTurns(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
-		final List<Issue> issues = new ArrayList<Issue>();
-		
-		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;
-		} catch (UnexpectedDataException e) {
-			issues.add(Issue.newError(r, e.getMessage()));
-			return issues;
-		}
-	}
-	
-	private List<Integer> splitInts(Relation r, String key, List<Issue> issues) {
-		final String ints = r.get(key);
-		
-		if (ints == null) {
-			return Collections.emptyList();
-		}
-		
-		final List<Integer> result = new ArrayList<Integer>();
-		
-		for (String s : Constants.SPLIT_PATTERN.split(ints)) {
-			try {
-				int i = Integer.parseInt(s.trim());
-				result.add(Integer.valueOf(i));
-			} catch (NumberFormatException e) {
-				issues.add(Issue.newError(r, tr("Integer list \"{0}\" contains unexpected values.", key)));
-			}
-		}
-		
-		return result;
-	}
-	
-	private IncomingLanes get(Map<IncomingLanes.Key, IncomingLanes> incomingLanes, Node via, Way from) {
-		final IncomingLanes.Key key = new IncomingLanes.Key(via, from);
-		final IncomingLanes lanes = incomingLanes.get(key);
-		
-		if (lanes == null) {
-			final IncomingLanes newLanes = new IncomingLanes(key, 0, Lane.getRegularCount(from, via), 0);
-			incomingLanes.put(key, newLanes);
-			return newLanes;
-		} else {
-			return lanes;
-		}
-	}
+    private static final class IncomingLanes {
+        private static final class Key {
+            final Node junction;
+            final Way from;
+            
+            public Key(Node junction, Way from) {
+                this.junction = junction;
+                this.from = from;
+            }
+            
+            @Override
+            public int hashCode() {
+                final int prime = 31;
+                int result = 1;
+                result = prime * result + ((from == null) ? 0 : from.hashCode());
+                result = prime * result + ((junction == null) ? 0 : junction.hashCode());
+                return result;
+            }
+            
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj)
+                    return true;
+                if (obj == null)
+                    return false;
+                if (getClass() != obj.getClass())
+                    return false;
+                Key other = (Key) obj;
+                if (from == null) {
+                    if (other.from != null)
+                        return false;
+                } else if (!from.equals(other.from))
+                    return false;
+                if (junction == null) {
+                    if (other.junction != null)
+                        return false;
+                } else if (!junction.equals(other.junction))
+                    return false;
+                return true;
+            }
+        }
+        
+        final Key key;
+        private final int extraLeft;
+        private final int regular;
+        private final int extraRight;
+        private final BitSet bitset;
+        
+        public IncomingLanes(Key key, int extraLeft, int regular, int extraRight) {
+            this.key = key;
+            this.extraLeft = extraLeft;
+            this.regular = regular;
+            this.extraRight = extraRight;
+            this.bitset = new BitSet(extraLeft + regular + extraRight);
+        }
+        
+        public boolean existsRegular(int l) {
+            if (l > 0 && l <= regular) {
+                bitset.set(extraLeft + l - 1);
+                return true;
+            }
+            
+            return false;
+        }
+        
+        public boolean existsExtra(int l) {
+            if (l < 0 && Math.abs(l) <= extraLeft) {
+                bitset.set(Math.abs(l) - 1);
+                return true;
+            } else if (l > 0 && l <= extraRight) {
+                bitset.set(extraLeft + regular + l - 1);
+                return true;
+            }
+            return false;
+        }
+        
+        public int unreferenced() {
+            return extraLeft + regular + extraRight - bitset.cardinality();
+        }
+    }
+    
+    public List<Issue> validate(DataSet dataSet) {
+        final List<Relation> lenghts = new ArrayList<Relation>();
+        final List<Relation> turns = new ArrayList<Relation>();
+        
+        for (Relation r : OsmPrimitive.getFilteredList(dataSet.allPrimitives(), Relation.class)) {
+            final String type = r.get("type");
+            
+            if (Constants.TYPE_LENGTHS.equals(type)) {
+                lenghts.add(r);
+            } else if (Constants.TYPE_TURNS.equals(type)) {
+                turns.add(r);
+            }
+        }
+        
+        final List<Issue> issues = new ArrayList<Issue>();
+        
+        final Map<IncomingLanes.Key, IncomingLanes> incomingLanes = new HashMap<IncomingLanes.Key, IncomingLanes>();
+        issues.addAll(validateLengths(lenghts, incomingLanes));
+        issues.addAll(validateTurns(turns, incomingLanes));
+        
+        for (IncomingLanes lanes : incomingLanes.values()) {
+            if (lanes.unreferenced() > 0) {
+                issues.add(Issue.newWarning(Arrays.asList(lanes.key.junction, lanes.key.from),
+                    tr("{0} lanes are not referenced in any turn-relation.", lanes.unreferenced())));
+            }
+        }
+        
+        return issues;
+    }
+    
+    private List<Issue> validateLengths(List<Relation> lenghts, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
+        final List<Issue> issues = new ArrayList<Issue>();
+        
+        for (Relation r : lenghts) {
+            issues.addAll(validateLengths(r, incomingLanes));
+        }
+        
+        return issues;
+    }
+    
+    private List<Issue> validateLengths(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
+        final List<Issue> issues = new ArrayList<Issue>();
+        
+        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;
+            
+        } catch (UnexpectedDataException e) {
+            issues.add(Issue.newError(r, e.getMessage()));
+            return issues;
+        }
+    }
+    
+    private void putIncomingLanes(Route route, List<Double> left, List<Double> right,
+        Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
+        final Node end = route.getLastSegment().getEnd();
+        final Way way = route.getLastSegment().getWay();
+        
+        final IncomingLanes.Key key = new IncomingLanes.Key(end, way);
+        final IncomingLanes lanes = new IncomingLanes(key, left.size(), Lane.getRegularCount(way, end), right.size());
+        final IncomingLanes old = incomingLanes.put(key, lanes);
+        
+        if (old != null) {
+            incomingLanes.put(
+                key,
+                new IncomingLanes(key, Math.max(lanes.extraLeft, old.extraLeft), Math.max(lanes.regular, old.regular), Math
+                    .max(lanes.extraRight, old.extraRight)));
+        }
+        
+        // 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);
+        //      }
+    }
+    
+    private List<Double> reduceLengths(List<Double> lengths, double length) {
+        final List<Double> newLengths = new ArrayList<Double>(lengths.size());
+        
+        for (double l : lengths) {
+            if (l > length) {
+                newLengths.add(l - length);
+            }
+        }
+        
+        return newLengths;
+    }
+    
+    private Route validateLengthsWays(Relation r, Node end, List<Issue> issues) {
+        final List<Way> ways = Utils.getMemberWays(r, Constants.LENGTHS_ROLE_WAYS);
+        
+        if (ways.isEmpty()) {
+            issues.add(Issue.newError(r, "A lengths-relation requires at least one member-way with role \""
+                + Constants.LENGTHS_ROLE_WAYS + "\"."));
+            return null;
+        }
+        
+        Node current = end;
+        for (Way w : ways) {
+            if (!w.isFirstLastNode(current)) {
+                return orderWays(r, ways, current, issues, "ways", "lengths");
+            }
+            
+            current = Utils.getOppositeEnd(w, current);
+        }
+        
+        return Route.create(ways, end);
+    }
+    
+    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());
+        final Set<Node> ends = new HashSet<Node>(); // to find cycles
+        
+        Node current = end;
+        findNext: while (!unordered.isEmpty()) {
+            if (!ends.add(current)) {
+                issues.add(Issue.newError(r, ways, "The " + role + " of the " + type
+                    + "-relation are unordered (and contain cycles)."));
+                return null;
+            }
+            
+            Iterator<Way> it = unordered.iterator();
+            while (it.hasNext()) {
+                final Way w = it.next();
+                
+                if (w.isFirstLastNode(current)) {
+                    it.remove();
+                    ordered.add(w);
+                    current = Utils.getOppositeEnd(w, current);
+                    continue findNext;
+                }
+            }
+            
+            issues.add(Issue.newError(r, ways, "The " + role + " of the " + type + "-relation are disconnected."));
+            return null;
+        }
+        
+        final QuickFix quickFix = new QuickFix(tr("Put the ways in order.")) {
+            @Override
+            public boolean perform() {
+                for (int i = r.getMembersCount() - 1; i >= 0; --i) {
+                    final RelationMember m = r.getMember(i);
+                    
+                    if (m.isWay() && Constants.LENGTHS_ROLE_WAYS.equals(m.getRole())) {
+                        r.removeMember(i);
+                    }
+                }
+                
+                for (Way w : ordered) {
+                    r.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, w));
+                }
+                
+                return true;
+            }
+        };
+        
+        issues.add(Issue.newError(r, ways, "The ways of the lengths-relation are unordered.", quickFix));
+        
+        return Route.create(ordered, end);
+    }
+    
+    private List<Issue> validateTurns(List<Relation> turns, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
+        final List<Issue> issues = new ArrayList<Issue>();
+        
+        for (Relation r : turns) {
+            issues.addAll(validateTurns(r, incomingLanes));
+        }
+        
+        return issues;
+    }
+    
+    private List<Issue> validateTurns(Relation r, Map<IncomingLanes.Key, IncomingLanes> incomingLanes) {
+        final List<Issue> issues = new ArrayList<Issue>();
+        
+        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;
+        } catch (UnexpectedDataException e) {
+            issues.add(Issue.newError(r, e.getMessage()));
+            return issues;
+        }
+    }
+    
+    private List<Integer> splitInts(Relation r, String key, List<Issue> issues) {
+        final String ints = r.get(key);
+        
+        if (ints == null) {
+            return Collections.emptyList();
+        }
+        
+        final List<Integer> result = new ArrayList<Integer>();
+        
+        for (String s : Constants.SPLIT_PATTERN.split(ints)) {
+            try {
+                int i = Integer.parseInt(s.trim());
+                result.add(Integer.valueOf(i));
+            } catch (NumberFormatException e) {
+                issues.add(Issue.newError(r, tr("Integer list \"{0}\" contains unexpected values.", key)));
+            }
+        }
+        
+        return result;
+    }
+    
+    private IncomingLanes get(Map<IncomingLanes.Key, IncomingLanes> incomingLanes, Node via, Way from) {
+        final IncomingLanes.Key key = new IncomingLanes.Key(via, from);
+        final IncomingLanes lanes = incomingLanes.get(key);
+        
+        if (lanes == null) {
+            final IncomingLanes newLanes = new IncomingLanes(key, 0, Lane.getRegularCount(from, via), 0);
+            incomingLanes.put(key, newLanes);
+            return newLanes;
+        } else {
+            return lanes;
+        }
+    }
 }
