diff --git a/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java b/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
index 6ac7873..14e2352 100644
--- a/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
+++ b/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
@@ -16,7 +16,6 @@ import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
-import java.awt.geom.GeneralPath;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -50,10 +49,13 @@ import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.data.osm.visitor.paint.ArrowPaintHelper;
+import org.openstreetmap.josm.data.osm.visitor.paint.MapPath2D;
 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
 import org.openstreetmap.josm.gui.MainMenu;
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
 import org.openstreetmap.josm.gui.NavigatableComponent;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.MapViewPaintable;
@@ -74,7 +76,8 @@ import org.openstreetmap.josm.tools.Utils;
 public class DrawAction extends MapMode implements MapViewPaintable, SelectionChangedListener, KeyPressReleaseListener, ModifierListener {
 
     private static final Color ORANGE_TRANSPARENT = new Color(Color.ORANGE.getRed(), Color.ORANGE.getGreen(), Color.ORANGE.getBlue(), 128);
-    private static final double PHI = Math.toRadians(90);
+
+    private static final ArrowPaintHelper START_WAY_INDICATOR = new ArrowPaintHelper(Math.toRadians(90), 8);
 
     private final Cursor cursorJoinNode;
     private final Cursor cursorJoinWay;
@@ -1138,19 +1141,16 @@ public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh
             g2.setStroke(rubberLineStroke);
         } else if (!snapHelper.drawConstructionGeometry)
             return;
-        GeneralPath b = new GeneralPath();
-        Point p1 = mv.getPoint(getCurrentBaseNode());
-        Point p2 = mv.getPoint(currentMouseEastNorth);
-
-        double t = Math.atan2((double) p2.y - p1.y, (double) p2.x - p1.x) + Math.PI;
+        MapPath2D b = new MapPath2D();
+        MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode());
+        MapViewPoint p2 = mv.getState().getPointFor(currentMouseEastNorth);
 
-        b.moveTo(p1.x, p1.y);
-        b.lineTo(p2.x, p2.y);
+        b.moveTo(p1);
+        b.lineTo(p2);
 
         // if alt key is held ("start new way"), draw a little perpendicular line
         if (alt) {
-            b.moveTo((int) (p1.x + 8*Math.cos(t+PHI)), (int) (p1.y + 8*Math.sin(t+PHI)));
-            b.lineTo((int) (p1.x + 8*Math.cos(t-PHI)), (int) (p1.y + 8*Math.sin(t-PHI)));
+            START_WAY_INDICATOR.paintArrowAt(b, p1, p2);
         }
 
         g2.draw(b);
@@ -1473,56 +1473,51 @@ public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh
         public void drawIfNeeded(Graphics2D g2, MapView mv) {
             if (!snapOn || !active)
                 return;
-            Point p1 = mv.getPoint(getCurrentBaseNode());
-            Point p2 = mv.getPoint(dir2);
-            Point p3 = mv.getPoint(projected);
-            GeneralPath b;
+            MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode());
+            MapViewPoint p2 = mv.getState().getPointFor(dir2);
+            MapViewPoint p3 = mv.getState().getPointFor(projected);
             if (drawConstructionGeometry) {
                 g2.setColor(snapHelperColor);
                 g2.setStroke(helperStroke);
 
-                b = new GeneralPath();
+                MapPath2D b = new MapPath2D();
+                b.moveTo(p2);
                 if (absoluteFix) {
-                    b.moveTo(p2.x, p2.y);
-                    b.lineTo(2d*p1.x-p2.x, 2d*p1.y-p2.y); // bi-directional line
+                    b.lineTo(2d*p1.getInViewX()-p2.getInViewX(), 2d*p1.getInViewY()-p2.getInViewY()); // bi-directional line
                 } else {
-                    b.moveTo(p2.x, p2.y);
-                    b.lineTo(p3.x, p3.y);
+                    b.lineTo(p3);
                 }
                 g2.draw(b);
             }
             if (projectionSource != null) {
                 g2.setColor(snapHelperColor);
                 g2.setStroke(helperStroke);
-                b = new GeneralPath();
-                b.moveTo(p3.x, p3.y);
-                Point pp = mv.getPoint(projectionSource);
-                b.lineTo(pp.x, pp.y);
+                MapPath2D b = new MapPath2D();
+                b.moveTo(p3);
+                b.lineTo(mv.getState().getPointFor(projectionSource));
                 g2.draw(b);
             }
 
             if (customBaseHeading >= 0) {
                 g2.setColor(highlightColor);
                 g2.setStroke(highlightStroke);
-                b = new GeneralPath();
-                Point pp1 = mv.getPoint(segmentPoint1);
-                Point pp2 = mv.getPoint(segmentPoint2);
-                b.moveTo(pp1.x, pp1.y);
-                b.lineTo(pp2.x, pp2.y);
+                MapPath2D b = new MapPath2D();
+                b.moveTo(mv.getState().getPointFor(segmentPoint1));
+                b.lineTo(mv.getState().getPointFor(segmentPoint2));
                 g2.draw(b);
             }
 
             g2.setColor(rubberLineColor);
             g2.setStroke(normalStroke);
-            b = new GeneralPath();
-            b.moveTo(p1.x, p1.y);
-            b.lineTo(p3.x, p3.y);
+            MapPath2D b = new MapPath2D();
+            b.moveTo(p1);
+            b.lineTo(p3);
             g2.draw(b);
 
-            g2.drawString(labelText, p3.x-5, p3.y+20);
+            g2.drawString(labelText, (int) p3.getInViewX()-5, (int) p3.getInViewY()+20);
             if (showProjectedPoint) {
                 g2.setStroke(normalStroke);
-                g2.drawOval(p3.x-5, p3.y-5, 10, 10); // projected point
+                g2.drawOval((int) p3.getInViewX()-5, (int) p3.getInViewY()-5, 10, 10); // projected point
             }
 
             g2.setColor(snapHelperColor);
diff --git a/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java b/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java
index 604dbe5..cd94c1f 100644
--- a/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java
+++ b/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java
@@ -12,7 +12,6 @@ import java.awt.Point;
 import java.awt.Stroke;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
-import java.awt.geom.GeneralPath;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedList;
@@ -35,9 +34,11 @@ import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.data.osm.visitor.paint.MapPath2D;
 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
 import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
@@ -238,7 +239,7 @@ public class ImproveWayAccuracyAction extends MapMode implements
 
             List<Node> nodes = targetWay.getNodes();
 
-            GeneralPath b = new GeneralPath();
+            MapPath2D b = new MapPath2D();
             Point p0 = mv.getPoint(nodes.get(0));
             Point pn;
             b.moveTo(p0.x, p0.y);
@@ -296,7 +297,7 @@ public class ImproveWayAccuracyAction extends MapMode implements
 
 
             // Drawing preview lines
-            GeneralPath b = new GeneralPath();
+            MapPath2D b = new MapPath2D();
             if (alt && !ctrl) {
                 // In delete mode
                 if (p1 != null && p2 != null) {
@@ -332,7 +333,7 @@ public class ImproveWayAccuracyAction extends MapMode implements
         }
     }
 
-    protected void drawIntersectingWayHelperLines(MapView mv, GeneralPath b) {
+    protected void drawIntersectingWayHelperLines(MapView mv, MapPath2D b) {
         for (final OsmPrimitive referrer : candidateNode.getReferrers()) {
             if (!(referrer instanceof Way) || targetWay.equals(referrer)) {
                 continue;
@@ -343,14 +344,14 @@ public class ImproveWayAccuracyAction extends MapMode implements
                     continue;
                 }
                 if (i > 0) {
-                    final Point p = mv.getPoint(nodes.get(i - 1));
+                    final MapViewPoint p = mv.getState().getPointFor(nodes.get(i - 1));
                     b.moveTo(mousePos.x, mousePos.y);
-                    b.lineTo(p.x, p.y);
+                    b.lineTo(p);
                 }
                 if (i < nodes.size() - 1) {
-                    final Point p = mv.getPoint(nodes.get(i + 1));
+                    final MapViewPoint p = mv.getState().getPointFor(nodes.get(i + 1));
                     b.moveTo(mousePos.x, mousePos.y);
-                    b.lineTo(p.x, p.y);
+                    b.lineTo(p);
                 }
             }
         }
diff --git a/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java b/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
index 7d2bcb4..e912a07 100644
--- a/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
+++ b/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
@@ -41,6 +41,7 @@ import org.openstreetmap.josm.data.osm.visitor.paint.WireframeMapRenderer;
 import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
 import org.openstreetmap.josm.gui.SelectionManager;
 import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded;
 import org.openstreetmap.josm.gui.layer.Layer;
@@ -1191,10 +1192,10 @@ public class SelectAction extends MapMode implements ModifierListener, KeyPressR
 
                     wnp.a = w.getNode(ws.lowerIndex);
                     wnp.b = w.getNode(ws.lowerIndex + 1);
-                    Point2D p1 = mv.getPoint2D(wnp.a);
-                    Point2D p2 = mv.getPoint2D(wnp.b);
+                    MapViewPoint p1 = mv.getState().getPointFor(wnp.a);
+                    MapViewPoint p2 = mv.getState().getPointFor(wnp.b);
                     if (WireframeMapRenderer.isLargeSegment(p1, p2, virtualSpace)) {
-                        Point2D pc = new Point2D.Double((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
+                        Point2D pc = new Point2D.Double((p1.getInViewX() + p2.getInViewX()) / 2, (p1.getInViewY() + p2.getInViewY()) / 2);
                         if (p.distanceSq(pc) < virtualSnapDistSq2) {
                             // Check that only segments on top of each other get added to the
                             // virtual ways list. Otherwise ways that coincidentally have their
diff --git a/src/org/openstreetmap/josm/data/osm/Node.java b/src/org/openstreetmap/josm/data/osm/Node.java
index 56ffb05..1f40769 100644
--- a/src/org/openstreetmap/josm/data/osm/Node.java
+++ b/src/org/openstreetmap/josm/data/osm/Node.java
@@ -2,6 +2,7 @@
 package org.openstreetmap.josm.data.osm;
 
 import java.util.Collection;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Predicate;
@@ -11,6 +12,7 @@ import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
+import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.data.projection.Projections;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.Utils;
@@ -33,6 +35,10 @@ public final class Node extends OsmPrimitive implements INode {
      */
     private double east = Double.NaN;
     private double north = Double.NaN;
+    /**
+     * The cache key to use for {@link #east} and {@link #north}.
+     */
+    private Object eastNorthCacheKey;
 
     /**
      * Determines if this node has valid coordinates.
@@ -78,9 +84,6 @@ public final class Node extends OsmPrimitive implements INode {
      * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates.
      * Internally caches the projected coordinates.</p>
      *
-     * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must
-     * {@link #invalidateEastNorthCache() invalidate the internal cache}.</p>
-     *
      * <p>Replies {@code null} if this node doesn't know lat/lon-coordinates, i.e. because it is an incomplete node.
      *
      * @return the east north coordinates or {@code null}
@@ -89,19 +92,27 @@ public final class Node extends OsmPrimitive implements INode {
      */
     @Override
     public EastNorth getEastNorth() {
-        if (!isLatLonKnown()) return null;
+        return getEastNorth(Main.getProjection());
+    }
 
-        if (getDataSet() == null)
-            // there is no dataset that listens for projection changes
-            // and invalidates the cache, so we don't use the cache at all
-            return Projections.project(new LatLon(lat, lon));
+    /**
+     * Replies the projected east/north coordinates.
+     * <p>
+     * The result of the last conversion is cached. The cache object is used as cache key.
+     * @param projection The projection to use.
+     * @return The projected east/north coordinates
+     * @since xxx
+     */
+    public EastNorth getEastNorth(Projection projection) {
+        if (!isLatLonKnown()) return null;
 
-        if (Double.isNaN(east) || Double.isNaN(north)) {
+        if (Double.isNaN(east) || Double.isNaN(north) || !Objects.equals(projection.getCacheKey(), eastNorthCacheKey)) {
             // projected coordinates haven't been calculated yet,
             // so fill the cache of the projected node coordinates
             EastNorth en = Projections.project(new LatLon(lat, lon));
             this.east = en.east();
             this.north = en.north();
+            this.eastNorthCacheKey = projection.getCacheKey();
         }
         return new EastNorth(east, north);
     }
@@ -122,6 +133,7 @@ public final class Node extends OsmPrimitive implements INode {
             this.lon = ll.lon();
             this.east = eastNorth.east();
             this.north = eastNorth.north();
+            this.eastNorthCacheKey = Main.getProjection().getCacheKey();
         } else {
             this.lat = Double.NaN;
             this.lon = Double.NaN;
@@ -345,6 +357,7 @@ public final class Node extends OsmPrimitive implements INode {
     public void invalidateEastNorthCache() {
         this.east = Double.NaN;
         this.north = Double.NaN;
+        this.eastNorthCacheKey = null;
     }
 
     @Override
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java
index 06a919c..790cd19 100644
--- a/src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java
@@ -3,9 +3,9 @@ package org.openstreetmap.josm.data.osm.visitor.paint;
 
 import java.awt.Color;
 import java.awt.Graphics2D;
-import java.awt.Point;
 import java.awt.geom.GeneralPath;
-import java.awt.geom.Point2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
 import java.util.Iterator;
 
 import org.openstreetmap.josm.Main;
@@ -14,6 +14,9 @@ import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.gui.MapViewState;
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
+import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle;
 import org.openstreetmap.josm.gui.NavigatableComponent;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 
@@ -28,6 +31,11 @@ public abstract class AbstractMapRenderer implements Rendering {
     /** the map viewport - provides projection and hit detection functionality */
     protected NavigatableComponent nc;
 
+    /**
+     * The {@link MapViewState} to use to convert between coordinates.
+     */
+    protected final MapViewState mapState;
+
     /** if true, the paint visitor shall render OSM objects such that they
      * look inactive. Example: rendering of data in an inactive layer using light gray as color only. */
     protected boolean isInactiveMode;
@@ -67,6 +75,7 @@ public abstract class AbstractMapRenderer implements Rendering {
         CheckParameterUtil.ensureParameterNotNull(nc);
         this.g = g;
         this.nc = nc;
+        this.mapState = nc.getState();
         this.isInactiveMode = isInactiveMode;
     }
 
@@ -89,21 +98,23 @@ public abstract class AbstractMapRenderer implements Rendering {
      * @param orderNumber The number of the segment in the way.
      * @param clr The color to use for drawing the text.
      */
-    protected void drawOrderNumber(Point p1, Point p2, int orderNumber, Color clr) {
+    protected void drawOrderNumber(MapViewPoint p1, MapViewPoint p2, int orderNumber, Color clr) {
         if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
             String on = Integer.toString(orderNumber);
             int strlen = on.length();
-            int x = (p1.x+p2.x)/2 - 4*strlen;
-            int y = (p1.y+p2.y)/2 + 4;
+            double centerX = (p1.getInViewX()+p2.getInViewX())/2;
+            double centerY = (p1.getInViewY()+p2.getInViewY())/2;
+            double x = centerX - 4*strlen;
+            double y = centerY + 4;
 
             if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) {
-                y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
+                y = centerY - virtualNodeSize - 3;
             }
 
             g.setColor(backgroundColor);
-            g.fillRect(x-1, y-12, 8*strlen+1, 14);
+            g.fill(new Rectangle2D.Double(x-1, y-12, 8*strlen+1, 14));
             g.setColor(clr);
-            g.drawString(on, x, y);
+            g.drawString(on, (int) x, (int) y);
         }
     }
 
@@ -182,10 +193,8 @@ public abstract class AbstractMapRenderer implements Rendering {
      * @param space The free space to check against.
      * @return <code>true</code> if segment is larger than required space
      */
-    public static boolean isLargeSegment(Point2D p1, Point2D p2, int space) {
-        double xd = Math.abs(p1.getX()-p2.getX());
-        double yd = Math.abs(p1.getY()-p2.getY());
-        return xd + yd > space;
+    public static boolean isLargeSegment(MapViewPoint p1, MapViewPoint p2, int space) {
+        return p1.oneNormInView(p2) > space;
     }
 
     /**
@@ -193,14 +202,12 @@ public abstract class AbstractMapRenderer implements Rendering {
      *
      * @param p1 First point of the way segment.
      * @param p2 Second point of the way segment.
-     * @return <code>true</code> if segment is visible.
+     * @return <code>true</code> if segment may be visible.
      */
-    protected boolean isSegmentVisible(Point p1, Point p2) {
-        if ((p1.x < 0) && (p2.x < 0)) return false;
-        if ((p1.y < 0) && (p2.y < 0)) return false;
-        if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
-        if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
-        return true;
+    protected boolean isSegmentVisible(MapViewPoint p1, MapViewPoint p2) {
+        MapViewRectangle view = mapState.getViewArea();
+        // not outside in the same direction
+        return (p1.getOutsideRectangleFlags(view) & p2.getOutsideRectangleFlags(view)) == 0;
     }
 
     /**
@@ -209,19 +216,19 @@ public abstract class AbstractMapRenderer implements Rendering {
      * @param path The path to append drawing to.
      * @param w The ways to draw node for.
      */
-    public void visitVirtual(GeneralPath path, Way w) {
+    public void visitVirtual(Path2D path, Way w) {
         Iterator<Node> it = w.getNodes().iterator();
         if (it.hasNext()) {
-            Point lastP = nc.getPoint(it.next());
+            MapViewPoint lastP = mapState.getPointFor(it.next());
             while (it.hasNext()) {
-                Point p = nc.getPoint(it.next());
+                MapViewPoint p =  mapState.getPointFor(it.next());
                 if (isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace)) {
-                    int x = (p.x+lastP.x)/2;
-                    int y = (p.y+lastP.y)/2;
-                    path.moveTo((double) x-virtualNodeSize, y);
-                    path.lineTo((double) x+virtualNodeSize, y);
-                    path.moveTo(x, (double) y-virtualNodeSize);
-                    path.lineTo(x, (double) y+virtualNodeSize);
+                    double x = (p.getInViewX()+lastP.getInViewX())/2;
+                    double y = (p.getInViewY()+lastP.getInViewY())/2;
+                    path.moveTo(x-virtualNodeSize, y);
+                    path.lineTo(x+virtualNodeSize, y);
+                    path.moveTo(x, y-virtualNodeSize);
+                    path.lineTo(x, y+virtualNodeSize);
                 }
                 lastP = p;
             }
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/ArrowPaintHelper.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/ArrowPaintHelper.java
new file mode 100644
index 0000000..b45d832
--- /dev/null
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/ArrowPaintHelper.java
@@ -0,0 +1,50 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.visitor.paint;
+
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * This class helps with painting arrows with fixed length along a path.
+ * @author Michael Zangl
+ * @since xxx
+ */
+public class ArrowPaintHelper {
+    private final double sin;
+    private final double cos;
+    private final double length;
+
+    /**
+     * Creates a new arrow helper.
+     * @param radians The angle of the arrow. 0 means that it lies on the current line. In radians
+     * @param length The length of the arrow lines.
+     */
+    public ArrowPaintHelper(double radians, double length) {
+        this.sin = Math.sin(radians);
+        this.cos = Math.cos(radians);
+        this.length = length;
+    }
+
+    /**
+     * Paint the arrow
+    * @param path The path to append the arrow to.
+     * @param point The point to paint the tip at
+     * @param fromDirection The direction the line is comming from.
+     */
+    public void paintArrowAt(MapPath2D path, MapViewPoint point, MapViewPoint fromDirection) {
+        double x = point.getInViewX();
+        double y = point.getInViewY();
+        double dx = fromDirection.getInViewX() - x;
+        double dy = fromDirection.getInViewY() - y;
+        double norm = Math.sqrt(dx * dx + dy * dy);
+        if (norm > 1e-10) {
+            dx *= length / norm;
+            dy *= length / norm;
+            path.moveTo(x + dx * cos + dy * sin, y + dx * -sin + dy * cos);
+            if (!Utils.equalsEpsilon(cos, 0)) {
+                path.lineTo(point);
+            }
+            path.lineTo(x + dx * cos + dy * -sin, y + dx * sin + dy * cos);
+        }
+    }
+}
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/LineClip.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/LineClip.java
index d259a90..ac22448 100644
--- a/src/org/openstreetmap/josm/data/osm/visitor/paint/LineClip.java
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/LineClip.java
@@ -6,16 +6,17 @@ import static java.awt.geom.Rectangle2D.OUT_LEFT;
 import static java.awt.geom.Rectangle2D.OUT_RIGHT;
 import static java.awt.geom.Rectangle2D.OUT_TOP;
 
-import java.awt.Point;
 import java.awt.Rectangle;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
 
 /**
  * Computes the part of a line that is visible in a given rectangle.
  * Using int leads to overflow, so we need long int.
  */
 public class LineClip {
-    private Point p1, p2;
-    private final Rectangle clipBounds;
+    private Point2D p1, p2;
+    private final Rectangle2D clipBounds;
 
     /**
      * Constructs a new {@code LineClip}.
@@ -23,7 +24,7 @@ public class LineClip {
      * @param p2 end point of the clipped line
      * @param clipBounds Clip bounds
      */
-    public LineClip(Point p1, Point p2, Rectangle clipBounds) {
+    public LineClip(Point2D p1, Point2D p2, Rectangle2D clipBounds) {
         this.p1 = p1;
         this.p2 = p2;
         this.clipBounds = clipBounds;
@@ -37,22 +38,21 @@ public class LineClip {
         if (clipBounds == null) {
             return false;
         }
-        return cohenSutherland(p1.x, p1.y, p2.x, p2.y, clipBounds.x, clipBounds.y,
-                (long) clipBounds.x + clipBounds.width,
-                (long) clipBounds.y + clipBounds.height);
+        return cohenSutherland(p1.getX(), p1.getY(), p2.getX(), p2.getY(), clipBounds.getMinX(), clipBounds.getMinY(),
+                clipBounds.getMaxX(), clipBounds.getMaxY());
     }
 
     /**
      * @return start point of the clipped line
      */
-    public Point getP1() {
+    public Point2D getP1() {
         return p1;
     }
 
     /**
      * @return end point of the clipped line
      */
-    public Point getP2() {
+    public Point2D getP2() {
         return p2;
     }
 
@@ -69,7 +69,7 @@ public class LineClip {
      * @param ymax maximal Y coordinate
      * @return true, if line is visible in the given clip region
      */
-    private boolean cohenSutherland(long x1, long y1, long x2, long y2, long xmin, long ymin, long xmax, long ymax) {
+    private boolean cohenSutherland(double x1, double y1, double x2, double y2, double xmin, double ymin, double xmax, double ymax) {
         int outcode0, outcode1, outcodeOut;
         boolean accept = false;
         boolean done = false;
@@ -84,8 +84,8 @@ public class LineClip {
             } else if ((outcode0 & outcode1) > 0) {
                 done = true;
             } else {
-                long x = 0;
-                long y = 0;
+                double x = 0;
+                double y = 0;
                 outcodeOut = outcode0 != 0 ? outcode0 : outcode1;
                 if ((outcodeOut & OUT_TOP) != 0) {
                     x = x1 + (x2 - x1) * (ymax - y1)/(y2 - y1);
@@ -114,8 +114,8 @@ public class LineClip {
         while (!done);
 
         if (accept) {
-            p1 = new Point((int) x1, (int) y1);
-            p2 = new Point((int) x2, (int) y2);
+            p1 = new Point2D.Double(x1, y1);
+            p2 = new Point2D.Double(x2, y2);
             return true;
         }
         return false;
@@ -132,16 +132,17 @@ public class LineClip {
      * @param ymax maximal Y coordinate
      * @return outcode
      */
-    private static int computeOutCode(long x, long y, long xmin, long ymin, long xmax, long ymax) {
+    private static int computeOutCode(double x, double y, double xmin, double ymin, double xmax, double ymax) {
         int code = 0;
-        if (y > ymax) {
+        // ignore rounding errors.
+        if (y > ymax + 1e-10) {
             code |= OUT_TOP;
-        } else if (y < ymin) {
+        } else if (y < ymin - 1e-10) {
             code |= OUT_BOTTOM;
         }
-        if (x > xmax) {
+        if (x > xmax + 1e-10) {
             code |= OUT_RIGHT;
-        } else if (x < xmin) {
+        } else if (x < xmin - 1e-10) {
             code |= OUT_LEFT;
         }
         return code;
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPath2D.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPath2D.java
new file mode 100644
index 0000000..195a19b
--- /dev/null
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPath2D.java
@@ -0,0 +1,36 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.visitor.paint;
+
+import java.awt.geom.Path2D;
+
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
+
+/**
+ * An extension of {@link Path2D} with special methods for map positions.
+ * @author Michael Zangl
+ * @since xxx
+ */
+public class MapPath2D extends Path2D.Double {
+    /**
+     * Create a new, empty path.
+     */
+    public MapPath2D() {
+        // no default definitions
+    }
+
+    /**
+     * Move the path to the view position of given point
+     * @param p The point
+     */
+    public void moveTo(MapViewPoint p) {
+        moveTo(p.getInViewX(), p.getInViewY());
+    }
+
+    /**
+     * Draw a line to the view position of given point
+     * @param p The point
+     */
+    public void lineTo(MapViewPoint p) {
+        lineTo(p.getInViewX(), p.getInViewY());
+    }
+}
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
index 55405c7..a4f4d5c 100644
--- a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
@@ -11,7 +11,6 @@ import java.awt.FontMetrics;
 import java.awt.Graphics2D;
 import java.awt.Image;
 import java.awt.Point;
-import java.awt.Polygon;
 import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.Shape;
@@ -25,9 +24,11 @@ import java.awt.geom.GeneralPath;
 import java.awt.geom.Path2D;
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -37,6 +38,7 @@ import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.ForkJoinTask;
 import java.util.concurrent.RecursiveTask;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import javax.swing.AbstractButton;
 import javax.swing.FocusManager;
@@ -58,6 +60,7 @@ import org.openstreetmap.josm.data.osm.visitor.Visitor;
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
 import org.openstreetmap.josm.gui.NavigatableComponent;
 import org.openstreetmap.josm.gui.mappaint.ElemStyles;
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
@@ -69,9 +72,9 @@ import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.Horizonta
 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.VerticalTextAlignment;
 import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage;
 import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement;
-import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement.Symbol;
 import org.openstreetmap.josm.gui.mappaint.styleelement.RepeatImageElement.LineImageAlignment;
 import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
+import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol;
 import org.openstreetmap.josm.gui.mappaint.styleelement.TextLabel;
 import org.openstreetmap.josm.tools.CompositeList;
 import org.openstreetmap.josm.tools.Geometry;
@@ -96,18 +99,19 @@ public class StyledMapRenderer extends AbstractMapRenderer {
      * There is no intention, to handle consecutive duplicate Nodes in a
      * perfect way, but it should not throw an exception.
      */
-    private class OffsetIterator implements Iterator<Point> {
+    private class OffsetIterator implements Iterator<MapViewPoint> {
 
         private final List<Node> nodes;
         private final double offset;
         private int idx;
 
-        private Point prev;
+        private MapViewPoint prev;
         /* 'prev0' is a point that has distance 'offset' from 'prev' and the
          * line from 'prev' to 'prev0' is perpendicular to the way segment from
          * 'prev' to the next point.
          */
-        private int xPrev0, yPrev0;
+        private double xPrev0;
+        private double yPrev0;
 
         OffsetIterator(List<Node> nodes, double offset) {
             this.nodes = nodes;
@@ -121,70 +125,76 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         }
 
         @Override
-        public Point next() {
+        public MapViewPoint next() {
             if (!hasNext())
                 throw new NoSuchElementException();
 
-            if (Math.abs(offset) < 0.1d)
-                return nc.getPoint(nodes.get(idx++));
+            MapViewPoint current = getForIndex(idx);
 
-            Point current = nc.getPoint(nodes.get(idx));
+            if (Math.abs(offset) < 0.1d) {
+                idx++;
+                return current;
+            }
 
             if (idx == nodes.size() - 1) {
                 ++idx;
                 if (prev != null) {
-                    return new Point(xPrev0 + current.x - prev.x, yPrev0 + current.y - prev.y);
+                    return mapState.getForView(xPrev0 + current.getInViewX() - prev.getInViewX(), yPrev0 + current.getInViewY() - prev.getInViewY());
                 } else {
                     return current;
                 }
             }
 
-            Point next = nc.getPoint(nodes.get(idx+1));
+            MapViewPoint next = getForIndex(idx + 1);
 
-            int dxNext = next.x - current.x;
-            int dyNext = next.y - current.y;
-            double lenNext = Math.sqrt((double) dxNext*dxNext + (double) dyNext*dyNext);
+            double dxNext = next.getInViewX() - current.getInViewX();
+            double dyNext = next.getInViewY() - current.getInViewY();
+            double lenNext = Math.sqrt(dxNext*dxNext + dyNext*dyNext);
 
-            if (lenNext == 0) {
+            if (lenNext < 1e-3) {
                 lenNext = 1; // value does not matter, because dy_next and dx_next is 0
             }
 
-            int xCurrent0 = current.x + (int) Math.round(offset * dyNext / lenNext);
-            int yCurrent0 = current.y - (int) Math.round(offset * dxNext / lenNext);
+            double xCurrent0 = current.getInViewX() + offset * dyNext / lenNext;
+            double yCurrent0 = current.getInViewY() - offset * dxNext / lenNext;
 
             if (idx == 0) {
                 ++idx;
                 prev = current;
                 xPrev0 = xCurrent0;
                 yPrev0 = yCurrent0;
-                return new Point(xCurrent0, yCurrent0);
+                return mapState.getForView(xCurrent0, yCurrent0);
             } else {
-                int dxPrev = current.x - prev.x;
-                int dyPrev = current.y - prev.y;
+                double dxPrev = current.getInViewX() - prev.getInViewX();
+                double dyPrev = current.getInViewY() - prev.getInViewY();
 
                 // determine intersection of the lines parallel to the two segments
-                int det = dxNext*dyPrev - dxPrev*dyNext;
+                double det = dxNext*dyPrev - dxPrev*dyNext;
 
-                if (det == 0) {
+                if (Utils.equalsEpsilon(det, 0)) {
                     ++idx;
                     prev = current;
                     xPrev0 = xCurrent0;
                     yPrev0 = yCurrent0;
-                    return new Point(xCurrent0, yCurrent0);
+                    return mapState.getForView(xCurrent0, yCurrent0);
                 }
 
-                int m = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0);
+                double m = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0);
 
-                int cx = xPrev0 + (int) Math.round((double) m * dxPrev / det);
-                int cy = yPrev0 + (int) Math.round((double) m * dyPrev / det);
+                double cx = xPrev0 + m * dxPrev / det;
+                double cy = yPrev0 + m * dyPrev / det;
                 ++idx;
                 prev = current;
                 xPrev0 = xCurrent0;
                 yPrev0 = yCurrent0;
-                return new Point(cx, cy);
+                return mapState.getForView(cx, cy);
             }
         }
 
+        private MapViewPoint getForIndex(int i) {
+            return mapState.getPointFor(nodes.get(i));
+        }
+
         @Override
         public void remove() {
             throw new UnsupportedOperationException();
@@ -375,22 +385,7 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         }
     }
 
-    private static Polygon buildPolygon(Point center, int radius, int sides) {
-        return buildPolygon(center, radius, sides, 0.0);
-    }
-
-    private static Polygon buildPolygon(Point center, int radius, int sides, double rotation) {
-        Polygon polygon = new Polygon();
-        for (int i = 0; i < sides; i++) {
-            double angle = ((2 * Math.PI / sides) * i) - rotation;
-            int x = (int) Math.round(center.x + radius * Math.cos(angle));
-            int y = (int) Math.round(center.y + radius * Math.sin(angle));
-            polygon.addPoint(x, y);
-        }
-        return polygon;
-    }
-
-    private void displaySegments(GeneralPath path, GeneralPath orientationArrows, GeneralPath onewayArrows, GeneralPath onewayArrowsCasing,
+    private void displaySegments(Path2D path, Path2D orientationArrows, Path2D onewayArrows, Path2D onewayArrowsCasing,
             Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) {
         g.setColor(isInactiveMode ? inactiveColor : color);
         if (useStrokes) {
@@ -488,7 +483,7 @@ public class StyledMapRenderer extends AbstractMapRenderer {
     protected void drawArea(OsmPrimitive osm, Path2D.Double path, Color color,
             MapImage fillImage, Float extent, Path2D.Double pfClip, boolean disabled, TextLabel text) {
 
-        Shape area = path.createTransformedShape(nc.getAffineTransform());
+        Shape area = path.createTransformedShape(mapState.getAffineTransform());
 
         if (!isOutlineOnly) {
             g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
@@ -503,7 +498,7 @@ public class StyledMapRenderer extends AbstractMapRenderer {
                     Shape oldClip = g.getClip();
                     Shape clip = area;
                     if (pfClip != null) {
-                        clip = pfClip.createTransformedShape(nc.getAffineTransform());
+                        clip = pfClip.createTransformedShape(mapState.getAffineTransform());
                     }
                     g.clip(clip);
                     g.setStroke(new BasicStroke(2 * extent, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 4));
@@ -526,7 +521,7 @@ public class StyledMapRenderer extends AbstractMapRenderer {
                     g.clip(stroke.createStrokedShape(area));
                     Shape fill = area;
                     if (pfClip != null) {
-                        fill = pfClip.createTransformedShape(nc.getAffineTransform());
+                        fill = pfClip.createTransformedShape(mapState.getAffineTransform());
                     }
                     g.fill(fill);
                     g.setClip(oldClip);
@@ -698,7 +693,7 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         if (!isShowNames() || bs == null)
             return;
 
-        Point p = nc.getPoint(n);
+        MapViewPoint p = mapState.getPointFor(n);
         TextLabel text = bs.text;
         String s = text.labelCompositionStrategy.compose(n);
         if (s == null) return;
@@ -706,8 +701,8 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         Font defaultFont = g.getFont();
         g.setFont(text.font);
 
-        int x = p.x + text.xOffset;
-        int y = p.y + text.yOffset;
+        int x = (int) (p.getInViewX() + text.xOffset);
+        int y = (int) (p.getInViewY() + text.yOffset);
         /**
          *
          *       left-above __center-above___ right-above
@@ -769,7 +764,6 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         final double repeat = imgWidth + spacing;
         final int imgHeight = pattern.getHeight();
 
-        Point lastP = null;
         double currentWayLength = phase % repeat;
         if (currentWayLength < 0) {
             currentWayLength += repeat;
@@ -793,22 +787,23 @@ public class StyledMapRenderer extends AbstractMapRenderer {
                 throw new AssertionError();
         }
 
+        MapViewPoint lastP = null;
         OffsetIterator it = new OffsetIterator(way.getNodes(), offset);
         while (it.hasNext()) {
-            Point thisP = it.next();
+            MapViewPoint thisP = it.next();
 
             if (lastP != null) {
-                final double segmentLength = thisP.distance(lastP);
+                final double segmentLength = thisP.distanceToInView(lastP);
 
-                final double dx = (double) thisP.x - lastP.x;
-                final double dy = (double) thisP.y - lastP.y;
+                final double dx = thisP.getInViewX() - lastP.getInViewX();
+                final double dy = thisP.getInViewY() - lastP.getInViewY();
 
                 // pos is the position from the beginning of the current segment
                 // where an image should be painted
                 double pos = repeat - (currentWayLength % repeat);
 
                 AffineTransform saveTransform = g.getTransform();
-                g.translate(lastP.x, lastP.y);
+                g.translate(lastP.getInViewX(), lastP.getInViewY());
                 g.rotate(Math.atan2(dy, dx));
 
                 // draw the rest of the image from the last segment in case it
@@ -849,14 +844,13 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         if (size <= 0 && !n.isHighlighted())
             return;
 
-        Point p = nc.getPoint(n);
+        MapViewPoint p = mapState.getPointFor(n);
 
         if (n.isHighlighted()) {
-            drawPointHighlight(p, size);
+            drawPointHighlight(p.getInView(), size);
         }
 
-        if (size > 1) {
-            if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
+        if (size > 1 && p.isInView()) {
             int radius = size / 2;
 
             if (isInactiveMode || n.isDisabled()) {
@@ -864,31 +858,45 @@ public class StyledMapRenderer extends AbstractMapRenderer {
             } else {
                 g.setColor(color);
             }
+            Rectangle2D rect = new Rectangle2D.Double(p.getInViewX()-radius-1, p.getInViewY()-radius-1, size + 1, size + 1);
             if (fill) {
-                g.fillRect(p.x-radius-1, p.y-radius-1, size + 1, size + 1);
+                g.fill(rect);
             } else {
-                g.drawRect(p.x-radius-1, p.y-radius-1, size, size);
+                g.draw(rect);
             }
         }
     }
 
+    /**
+     * Draw the icon for a given node.
+     * @param n The node
+     * @param img The icon to draw at the node position
+     * @param disabled
+     * @param selected
+     * @param member
+     * @param theta
+     */
     public void drawNodeIcon(Node n, MapImage img, boolean disabled, boolean selected, boolean member, double theta) {
-        Point p = nc.getPoint(n);
+        MapViewPoint p = mapState.getPointFor(n);
 
-        final int w = img.getWidth(), h = img.getHeight();
+        int w = img.getWidth();
+        int h = img.getHeight();
         if (n.isHighlighted()) {
-            drawPointHighlight(p, Math.max(w, h));
+            drawPointHighlight(p.getInView(), Math.max(w, h));
         }
 
         float alpha = img.getAlphaFloat();
 
+        Graphics2D temporaryGraphics = (Graphics2D) g.create();
         if (!Utils.equalsEpsilon(alpha, 1f)) {
-            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
+            temporaryGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
         }
-        g.rotate(theta, p.x, p.y);
-        g.drawImage(img.getImage(disabled), p.x - w/2 + img.offsetX, p.y - h/2 + img.offsetY, nc);
-        g.rotate(-theta, p.x, p.y);
-        g.setPaintMode();
+
+        double x = p.getInViewX();
+        double y = p.getInViewY();
+        temporaryGraphics.translate(-x, -y);
+        temporaryGraphics.rotate(theta);
+        temporaryGraphics.drawImage(img.getImage(disabled), w/2 + img.offsetX, h/2 + img.offsetY, nc);
         if (selected || member) {
             Color color;
             if (disabled) {
@@ -899,87 +907,37 @@ public class StyledMapRenderer extends AbstractMapRenderer {
                 color = relationSelectedColor;
             }
             g.setColor(color);
-            g.drawRect(p.x - w/2 + img.offsetX - 2, p.y - h/2 + img.offsetY - 2, w + 4, h + 4);
+            g.draw(new Rectangle2D.Double(x - w/2 + img.offsetX - 2, y - h/2 + img.offsetY - 2, w + 4, h + 4));
         }
     }
 
+    /**
+     * Draw the symbol and possibly a highlight marking on a given node.
+     * @param n The position to draw the symbol on
+     * @param s The symbol to draw
+     * @param fillColor The color to fill the symbol with
+     * @param strokeColor The color to use for the outer corner of the symbol
+     */
     public void drawNodeSymbol(Node n, Symbol s, Color fillColor, Color strokeColor) {
-        Point p = nc.getPoint(n);
-        int radius = s.size / 2;
+        MapViewPoint p = mapState.getPointFor(n);
 
         if (n.isHighlighted()) {
-            drawPointHighlight(p, s.size);
+            drawPointHighlight(p.getInView(), s.size);
         }
 
-        if (fillColor != null) {
-            g.setColor(fillColor);
-            switch (s.symbol) {
-            case SQUARE:
-                g.fillRect(p.x - radius, p.y - radius, s.size, s.size);
-                break;
-            case CIRCLE:
-                g.fillOval(p.x - radius, p.y - radius, s.size, s.size);
-                break;
-            case TRIANGLE:
-                g.fillPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
-                break;
-            case PENTAGON:
-                g.fillPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
-                break;
-            case HEXAGON:
-                g.fillPolygon(buildPolygon(p, radius, 6));
-                break;
-            case HEPTAGON:
-                g.fillPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
-                break;
-            case OCTAGON:
-                g.fillPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
-                break;
-            case NONAGON:
-                g.fillPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
-                break;
-            case DECAGON:
-                g.fillPolygon(buildPolygon(p, radius, 10));
-                break;
-            default:
-                throw new AssertionError();
+        if (fillColor != null || strokeColor != null) {
+            Shape shape = s.buildShapeAround(p.getInViewX(), p.getInViewY());
+
+            if (fillColor != null) {
+                g.setColor(fillColor);
+                g.fill(shape);
             }
-        }
-        if (s.stroke != null) {
-            g.setStroke(s.stroke);
-            g.setColor(strokeColor);
-            switch (s.symbol) {
-            case SQUARE:
-                g.drawRect(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
-                break;
-            case CIRCLE:
-                g.drawOval(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
-                break;
-            case TRIANGLE:
-                g.drawPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
-                break;
-            case PENTAGON:
-                g.drawPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
-                break;
-            case HEXAGON:
-                g.drawPolygon(buildPolygon(p, radius, 6));
-                break;
-            case HEPTAGON:
-                g.drawPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
-                break;
-            case OCTAGON:
-                g.drawPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
-                break;
-            case NONAGON:
-                g.drawPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
-                break;
-            case DECAGON:
-                g.drawPolygon(buildPolygon(p, radius, 10));
-                break;
-            default:
-                throw new AssertionError();
+            if (s.stroke != null) {
+                g.setStroke(s.stroke);
+                g.setColor(strokeColor);
+                g.draw(shape);
+                g.setStroke(new BasicStroke());
             }
-            g.setStroke(new BasicStroke());
         }
     }
 
@@ -993,8 +951,8 @@ public class StyledMapRenderer extends AbstractMapRenderer {
      * @param clr The color to use for drawing the text.
      */
     public void drawOrderNumber(Node n1, Node n2, int orderNumber, Color clr) {
-        Point p1 = nc.getPoint(n1);
-        Point p2 = nc.getPoint(n2);
+        MapViewPoint p1 = mapState.getPointFor(n1);
+        MapViewPoint p2 = mapState.getPointFor(n2);
         drawOrderNumber(p1, p2, orderNumber, clr);
     }
 
@@ -1004,7 +962,7 @@ public class StyledMapRenderer extends AbstractMapRenderer {
      * @param path path to draw
      * @param line line style
      */
-    private void drawPathHighlight(GeneralPath path, BasicStroke line) {
+    private void drawPathHighlight(Path2D path, BasicStroke line) {
         if (path == null)
             return;
         g.setColor(highlightColorTransparent);
@@ -1023,13 +981,13 @@ public class StyledMapRenderer extends AbstractMapRenderer {
      * @param p point
      * @param size highlight size
      */
-    private void drawPointHighlight(Point p, int size) {
+    private void drawPointHighlight(Point2D p, int size) {
         g.setColor(highlightColorTransparent);
         int s = size + highlightPointRadius;
         if (useWiderHighlight) s += widerHighlight;
         while (s >= size) {
             int r = (int) Math.floor(s/2d);
-            g.fillRoundRect(p.x-r, p.y-r, s, s, r, r);
+            g.fill(new RoundRectangle2D.Double(p.getX()-r, p.getY()-r, s, s, r, r));
             s -= highlightStep;
         }
     }
@@ -1218,6 +1176,44 @@ public class StyledMapRenderer extends AbstractMapRenderer {
     }
 
     /**
+     * A half segment that can be used to place text on it. Used in the drawTextOnPath algorithm.
+     * @author Michael Zangl
+     * @since xxx
+     */
+    private static class HalfSegment {
+        /**
+         * start point of half segment (as length along the way)
+         */
+        final double start;
+        /**
+         * end point of half segment (as length along the way)
+         */
+        final double end;
+        /**
+         * quality factor (off screen / partly on screen / fully on screen)
+         */
+        final double quality;
+
+        /**
+         * Create a new half segment
+         * @param start The start along the way
+         * @param end The end of the segment
+         * @param quality A quality factor.
+         */
+        public HalfSegment(double start, double end, double quality) {
+            super();
+            this.start = start;
+            this.end = end;
+            this.quality = quality;
+        }
+
+        @Override
+        public String toString() {
+            return "HalfSegment [start=" + start + ", end=" + end + ", quality=" + quality + "]";
+        }
+    }
+
+    /**
      * Draws a text along a given way.
      * @param way The way to draw the text on.
      * @param text The text definition (font/.../text content) to draw.
@@ -1234,97 +1230,34 @@ public class StyledMapRenderer extends AbstractMapRenderer {
 
         Rectangle bounds = g.getClipBounds();
 
-        Polygon poly = new Polygon();
-        Point lastPoint = null;
-        Iterator<Node> it = way.getNodes().iterator();
-        double pathLength = 0;
-        long dx, dy;
+        List<MapViewPoint> points = way.getNodes().stream().map(mapState::getPointFor).collect(Collectors.toList());
 
         // find half segments that are long enough to draw text on (don't draw text over the cross hair in the center of each segment)
-        List<Double> longHalfSegmentStart = new ArrayList<>(); // start point of half segment (as length along the way)
-        List<Double> longHalfSegmentEnd = new ArrayList<>(); // end point of half segment (as length along the way)
-        List<Double> longHalfsegmentQuality = new ArrayList<>(); // quality factor (off screen / partly on screen / fully on screen)
-
-        while (it.hasNext()) {
-            Node n = it.next();
-            Point p = nc.getPoint(n);
-            poly.addPoint(p.x, p.y);
+        List<HalfSegment> longHalfSegment = new ArrayList<>();
 
-            if (lastPoint != null) {
-                dx = (long) p.x - lastPoint.x;
-                dy = (long) p.y - lastPoint.y;
-                double segmentLength = Math.sqrt(dx*dx + dy*dy);
-                if (segmentLength > 2*(rec.getWidth()+4)) {
-                    Point center = new Point((lastPoint.x + p.x)/2, (lastPoint.y + p.y)/2);
-                    double q = 0;
-                    if (bounds != null) {
-                        if (bounds.contains(lastPoint) && bounds.contains(center)) {
-                            q = 2;
-                        } else if (bounds.contains(lastPoint) || bounds.contains(center)) {
-                            q = 1;
-                        }
-                    }
-                    longHalfSegmentStart.add(pathLength);
-                    longHalfSegmentEnd.add(pathLength + segmentLength / 2);
-                    longHalfsegmentQuality.add(q);
-
-                    q = 0;
-                    if (bounds != null) {
-                        if (bounds.contains(center) && bounds.contains(p)) {
-                            q = 2;
-                        } else if (bounds.contains(center) || bounds.contains(p)) {
-                            q = 1;
-                        }
-                    }
-                    longHalfSegmentStart.add(pathLength + segmentLength / 2);
-                    longHalfSegmentEnd.add(pathLength + segmentLength);
-                    longHalfsegmentQuality.add(q);
-                }
-                pathLength += segmentLength;
-            }
-            lastPoint = p;
-        }
+        double pathLength = computePath(2 * (rec.getWidth() + 4), bounds, points, longHalfSegment);
 
         if (rec.getWidth() > pathLength)
             return;
 
         double t1, t2;
 
-        if (!longHalfSegmentStart.isEmpty()) {
-            if (way.getNodesCount() == 2) {
-                // For 2 node ways, the two half segments are exactly the same size and distance from the center.
-                // Prefer the first one for consistency.
-                longHalfsegmentQuality.set(0, longHalfsegmentQuality.get(0) + 0.5);
-            }
-
-            // find the long half segment that is closest to the center of the way
-            // candidates with higher quality value are preferred
-            double bestStart = Double.NaN;
-            double bestEnd = Double.NaN;
-            double bestDistanceToCenter = Double.MAX_VALUE;
-            double bestQuality = -1;
-            for (int i = 0; i < longHalfSegmentStart.size(); i++) {
-                double start = longHalfSegmentStart.get(i);
-                double end = longHalfSegmentEnd.get(i);
-                double dist = Math.abs(0.5 * (end + start) - 0.5 * pathLength);
-                if (longHalfsegmentQuality.get(i) > bestQuality
-                        || (dist < bestDistanceToCenter && Utils.equalsEpsilon(longHalfsegmentQuality.get(i), bestQuality))) {
-                    bestStart = start;
-                    bestEnd = end;
-                    bestDistanceToCenter = dist;
-                    bestQuality = longHalfsegmentQuality.get(i);
-                }
-            }
-            double remaining = bestEnd - bestStart - rec.getWidth(); // total space left and right from the text
+        if (!longHalfSegment.isEmpty()) {
+            // find the segment with the best quality. If there are several with best quality, the one close to the center is prefered.
+            HalfSegment best = longHalfSegment.stream().max(
+                    Comparator.comparingDouble( segment ->
+                        segment.quality - 1e-5 * Math.abs(0.5 * (segment.end + segment.start) - 0.5 * pathLength)
+                    )).get();
+            double remaining = best.end - best.start - rec.getWidth(); // total space left and right from the text
             // The space left and right of the text should be distributed 20% - 80% (towards the center),
             // but the smaller space should not be less than 7 px.
             // However, if the total remaining space is less than 14 px, then distribute it evenly.
             double smallerSpace = Math.min(Math.max(0.2 * remaining, 7), 0.5 * remaining);
-            if ((bestEnd + bestStart)/2 < pathLength/2) {
-                t2 = bestEnd - smallerSpace;
+            if ((best.end + best.start)/2 < pathLength/2) {
+                t2 = best.end - smallerSpace;
                 t1 = t2 - rec.getWidth();
             } else {
-                t1 = bestStart + smallerSpace;
+                t1 = best.start + smallerSpace;
                 t2 = t1 + rec.getWidth();
             }
         } else {
@@ -1335,8 +1268,8 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         t1 /= pathLength;
         t2 /= pathLength;
 
-        double[] p1 = pointAt(t1, poly, pathLength);
-        double[] p2 = pointAt(t2, poly, pathLength);
+        double[] p1 = pointAt(t1, points, pathLength);
+        double[] p2 = pointAt(t2, points, pathLength);
 
         if (p1 == null || p2 == null)
             return;
@@ -1364,7 +1297,7 @@ public class StyledMapRenderer extends AbstractMapRenderer {
             for (int i = 0; i < gv.getNumGlyphs(); ++i) {
                 Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D();
                 double t = tStart + offsetSign * (gvOffset + rect.getX() + rect.getWidth()/2) / pathLength;
-                double[] p = pointAt(t, poly, pathLength);
+                double[] p = pointAt(t, points, pathLength);
                 if (p != null) {
                     AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]);
                     trfm.rotate(p[2]+angleOffset);
@@ -1384,6 +1317,47 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         }
     }
 
+    private double computePath(double minSegmentLength, Rectangle bounds, List<MapViewPoint> points,
+            List<HalfSegment> longHalfSegment) {
+        MapViewPoint lastPoint = points.get(0);
+        double pathLength = 0;
+        for (MapViewPoint p : points.subList(1, points.size())) {
+            double segmentLength = p.distanceToInView(lastPoint);
+            if (segmentLength > minSegmentLength) {
+                Point2D center = new Point2D.Double((lastPoint.getInViewX() + p.getInViewX())/2, (lastPoint.getInViewY() + p.getInViewY())/2);
+                double q = computeQuality(bounds, lastPoint, center);
+                // prefer the first one for quality equality.
+                longHalfSegment.add(new HalfSegment(pathLength, pathLength + segmentLength / 2, q));
+
+                q = 0;
+                if (bounds != null) {
+                    if (bounds.contains(center) && bounds.contains(p.getInView())) {
+                        q = 2;
+                    } else if (bounds.contains(center) || bounds.contains(p.getInView())) {
+                        q = 1;
+                    }
+                }
+                longHalfSegment.add(new HalfSegment(pathLength + segmentLength / 2, pathLength + segmentLength, q));
+            }
+            pathLength += segmentLength;
+            lastPoint = p;
+        }
+        return pathLength;
+    }
+
+    private static double computeQuality(Rectangle bounds, MapViewPoint p1, Point2D p2) {
+        double q = 0;
+        if (bounds != null) {
+            if (bounds.contains(p1.getInView())) {
+                q += 1;
+            }
+            if (bounds.contains(p2)) {
+                q += 1;
+            }
+        }
+        return q;
+    }
+
     /**
      * draw way. This method allows for two draw styles (line using color, dashes using dashedColor) to be passed.
      * @param way The way to draw
@@ -1403,18 +1377,16 @@ public class StyledMapRenderer extends AbstractMapRenderer {
             boolean showOrientation, boolean showHeadArrowOnly,
             boolean showOneway, boolean onewayReversed) {
 
-        GeneralPath path = new GeneralPath();
-        GeneralPath orientationArrows = showOrientation ? new GeneralPath() : null;
-        GeneralPath onewayArrows = showOneway ? new GeneralPath() : null;
-        GeneralPath onewayArrowsCasing = showOneway ? new GeneralPath() : null;
+        MapPath2D path = new MapPath2D();
+        MapPath2D orientationArrows = showOrientation ? new MapPath2D() : null;
+        MapPath2D onewayArrows = showOneway ? new MapPath2D() : null;
+        MapPath2D onewayArrowsCasing = showOneway ? new MapPath2D() : null;
         Rectangle bounds = g.getClipBounds();
         if (bounds != null) {
             // avoid arrow heads at the border
             bounds.grow(100, 100);
         }
 
-        double wayLength = 0;
-        Point lastPoint = null;
         boolean initialMoveToNeeded = true;
         List<Node> wayNodes = way.getNodes();
         if (wayNodes.size() < 2) return;
@@ -1430,88 +1402,53 @@ public class StyledMapRenderer extends AbstractMapRenderer {
                     highlightSegs = new GeneralPath();
                 }
 
-                Point p1 = nc.getPoint(ws.getFirstNode());
-                Point p2 = nc.getPoint(ws.getSecondNode());
-                highlightSegs.moveTo(p1.x, p1.y);
-                highlightSegs.lineTo(p2.x, p2.y);
+                Point2D p1 = mapState.getPointFor(ws.getFirstNode()).getInView();
+                Point2D p2 = mapState.getPointFor(ws.getSecondNode()).getInView();
+                highlightSegs.moveTo(p1.getX(), p1.getY());
+                highlightSegs.lineTo(p2.getX(), p2.getY());
             }
 
             drawPathHighlight(highlightSegs, line);
         }
 
-        Iterator<Point> it = new OffsetIterator(wayNodes, offset);
+        MapViewPoint lastPoint = null;
+        double wayLength = 0;
+        Iterator<MapViewPoint> it = new OffsetIterator(wayNodes, offset);
         while (it.hasNext()) {
-            Point p = it.next();
+            MapViewPoint p = it.next();
             if (lastPoint != null) {
-                Point p1 = lastPoint;
-                Point p2 = p;
-
-                /**
-                 * Do custom clipping to work around openjdk bug. It leads to
-                 * drawing artefacts when zooming in a lot. (#4289, #4424)
-                 * (Looks like int overflow.)
-                 */
-                LineClip clip = new LineClip(p1, p2, bounds);
-                if (clip.execute()) {
-                    if (!p1.equals(clip.getP1())) {
-                        p1 = clip.getP1();
-                        path.moveTo(p1.x, p1.y);
-                    } else if (initialMoveToNeeded) {
-                        initialMoveToNeeded = false;
-                        path.moveTo(p1.x, p1.y);
-                    }
-                    p2 = clip.getP2();
-                    path.lineTo(p2.x, p2.y);
+                MapViewPoint p1 = lastPoint;
+                MapViewPoint p2 = p;
 
-                    /* draw arrow */
-                    if (showHeadArrowOnly ? !it.hasNext() : showOrientation) {
-                        final double segmentLength = p1.distance(p2);
-                        if (segmentLength != 0) {
-                            final double l = (10. + line.getLineWidth()) / segmentLength;
-
-                            final double sx = l * (p1.x - p2.x);
-                            final double sy = l * (p1.y - p2.y);
+                if (initialMoveToNeeded) {
+                    initialMoveToNeeded = false;
+                    path.moveTo(p1);
+                }
+                path.lineTo(p2);
 
-                            orientationArrows.moveTo(p2.x + cosPHI * sx - sinPHI * sy, p2.y + sinPHI * sx + cosPHI * sy);
-                            orientationArrows.lineTo(p2.x, p2.y);
-                            orientationArrows.lineTo(p2.x + cosPHI * sx + sinPHI * sy, p2.y - sinPHI * sx + cosPHI * sy);
-                        }
-                    }
-                    if (showOneway) {
-                        final double segmentLength = p1.distance(p2);
-                        if (segmentLength != 0) {
-                            final double nx = (p2.x - p1.x) / segmentLength;
-                            final double ny = (p2.y - p1.y) / segmentLength;
-
-                            final double interval = 60;
-                            // distance from p1
-                            double dist = interval - (wayLength % interval);
-
-                            while (dist < segmentLength) {
-                                for (int i = 0; i < 2; ++i) {
-                                    double onewaySize = i == 0 ? 3d : 2d;
-                                    GeneralPath onewayPath = i == 0 ? onewayArrowsCasing : onewayArrows;
-
-                                    // scale such that border is 1 px
-                                    final double fac = -(onewayReversed ? -1 : 1) * onewaySize * (1 + sinPHI) / (sinPHI * cosPHI);
-                                    final double sx = nx * fac;
-                                    final double sy = ny * fac;
-
-                                    // Attach the triangle at the incenter and not at the tip.
-                                    // Makes the border even at all sides.
-                                    final double x = p1.x + nx * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
-                                    final double y = p1.y + ny * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
-
-                                    onewayPath.moveTo(x, y);
-                                    onewayPath.lineTo(x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy);
-                                    onewayPath.lineTo(x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy);
-                                    onewayPath.lineTo(x, y);
-                                }
-                                dist += interval;
-                            }
+                /* draw arrow */
+                if (showHeadArrowOnly ? !it.hasNext() : showOrientation) {
+                    //TODO: Cache
+                    ArrowPaintHelper drawHelper = new ArrowPaintHelper(PHI, 10 + line.getLineWidth());
+                    drawHelper.paintArrowAt(orientationArrows, p2, p1);
+                }
+                if (showOneway) {
+                    final double segmentLength = p1.distanceToInView(p2);
+                    if (segmentLength != 0) {
+                        final double nx = (p2.getInViewX() - p1.getInViewX()) / segmentLength;
+                        final double ny = (p2.getInViewY() - p1.getInViewY()) / segmentLength;
+
+                        final double interval = 60;
+                        // distance from p1
+                        double dist = interval - (wayLength % interval);
+
+                        while (dist < segmentLength) {
+                            appenOnewayPath(onewayReversed, p1, nx, ny, dist, 3d, onewayArrowsCasing);
+                            appenOnewayPath(onewayReversed, p1, nx, ny, dist, 2d, onewayArrows);
+                            dist += interval;
                         }
-                        wayLength += segmentLength;
                     }
+                    wayLength += segmentLength;
                 }
             }
             lastPoint = p;
@@ -1522,6 +1459,23 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         displaySegments(path, orientationArrows, onewayArrows, onewayArrowsCasing, color, line, dashes, dashedColor);
     }
 
+    private void appenOnewayPath(boolean onewayReversed, MapViewPoint p1, double nx, double ny, double dist, double onewaySize, Path2D onewayPath) {
+        // scale such that border is 1 px
+        final double fac = -(onewayReversed ? -1 : 1) * onewaySize * (1 + sinPHI) / (sinPHI * cosPHI);
+        final double sx = nx * fac;
+        final double sy = ny * fac;
+
+        // Attach the triangle at the incenter and not at the tip.
+        // Makes the border even at all sides.
+        final double x = p1.getInViewX() + nx * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
+        final double y = p1.getInViewY() + ny * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
+
+        onewayPath.moveTo(x, y);
+        onewayPath.lineTo(x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy);
+        onewayPath.lineTo(x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy);
+        onewayPath.lineTo(x, y);
+    }
+
     /**
      * Gets the "circum". This is the distance on the map in meters that 100 screen pixels represent.
      * @return The "circum"
@@ -1711,15 +1665,20 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         return null;
     }
 
+    /**
+     * Test if the area is visible
+     * @param area The area, interpreted in east/north space.
+     * @return true if it is visible.
+     */
     private boolean isAreaVisible(Path2D.Double area) {
         Rectangle2D bounds = area.getBounds2D();
         if (bounds.isEmpty()) return false;
-        Point2D p = nc.getPoint2D(new EastNorth(bounds.getX(), bounds.getY()));
-        if (p.getX() > nc.getWidth()) return false;
-        if (p.getY() < 0) return false;
-        p = nc.getPoint2D(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));
-        if (p.getX() < 0) return false;
-        if (p.getY() > nc.getHeight()) return false;
+        MapViewPoint p = mapState.getPointFor(new EastNorth(bounds.getX(), bounds.getY()));
+        if (p.getInViewX() > mapState.getViewWidth()) return false;
+        if (p.getInViewY() < 0) return false;
+        p = mapState.getPointFor(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));
+        if (p.getInViewX() < 0) return false;
+        if (p.getInViewY() > mapState.getViewHeight()) return false;
         return true;
     }
 
@@ -1735,25 +1694,25 @@ public class StyledMapRenderer extends AbstractMapRenderer {
         return showNames;
     }
 
-    private static double[] pointAt(double t, Polygon poly, double pathLength) {
+    private static double[] pointAt(double t, List<MapViewPoint> poly, double pathLength) {
         double totalLen = t * pathLength;
         double curLen = 0;
-        long dx, dy;
+        double dx, dy;
         double segLen;
 
         // Yes, it is inefficient to iterate from the beginning for each glyph.
         // Can be optimized if it turns out to be slow.
-        for (int i = 1; i < poly.npoints; ++i) {
-            dx = (long) poly.xpoints[i] - poly.xpoints[i-1];
-            dy = (long) poly.ypoints[i] - poly.ypoints[i-1];
+        for (int i = 1; i < poly.size(); ++i) {
+            dx = poly.get(i).getInViewX() - poly.get(i - 1).getInViewX();
+            dy = poly.get(i).getInViewY() - poly.get(i - 1).getInViewY();
             segLen = Math.sqrt(dx*dx + dy*dy);
             if (totalLen > curLen + segLen) {
                 curLen += segLen;
                 continue;
             }
             return new double[] {
-                    poly.xpoints[i-1]+(totalLen - curLen)/segLen*dx,
-                    poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
+                    poly.get(i - 1).getInViewX() + (totalLen - curLen) / segLen * dx,
+                    poly.get(i - 1).getInViewY() + (totalLen - curLen) / segLen * dy,
                     Math.atan2(dy, dx)};
         }
         return null;
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java
index 4414c36..d7e91fe 100644
--- a/src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java
@@ -4,11 +4,13 @@ package org.openstreetmap.josm.data.osm.visitor.paint;
 import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Graphics2D;
-import java.awt.Point;
 import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.Stroke;
+import java.awt.geom.Ellipse2D;
 import java.awt.geom.GeneralPath;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Rectangle2D.Double;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -25,6 +27,7 @@ import org.openstreetmap.josm.data.osm.RelationMember;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
 import org.openstreetmap.josm.data.osm.visitor.Visitor;
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
 import org.openstreetmap.josm.gui.NavigatableComponent;
 
 /**
@@ -74,7 +77,7 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
     /** Color cache to draw subsequent segments of same color as one <code>Path</code>. */
     protected Color currentColor;
     /** Path store to draw subsequent segments of same color as one <code>Path</code>. */
-    protected GeneralPath currentPath = new GeneralPath();
+    protected MapPath2D currentPath = new MapPath2D();
     /**
       * <code>DataSet</code> passed to the @{link render} function to overcome the argument
       * limitations of @{link Visitor} interface. Only valid until end of rendering call.
@@ -82,11 +85,7 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
     private DataSet ds;
 
     /** Helper variable for {@link #drawSegment} */
-    private static final double PHI = Math.toRadians(20);
-    /** Helper variable for {@link #drawSegment} */
-    private static final double cosPHI = Math.cos(PHI);
-    /** Helper variable for {@link #drawSegment} */
-    private static final double sinPHI = Math.sin(PHI);
+    private static final ArrowPaintHelper ARROW_PAINT_HELPER = new ArrowPaintHelper(Math.toRadians(20), 10);
 
     /** Helper variable for {@link #visit(Relation)} */
     private final Stroke relatedWayStroke = new BasicStroke(
@@ -116,7 +115,7 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
         taggedColor = PaintColors.TAGGED.get();
         connectionColor = PaintColors.CONNECTION.get();
 
-        if (taggedColor != nodeColor) {
+        if (!taggedColor.equals(nodeColor)) {
             taggedConnectionColor = taggedColor;
         } else {
             taggedConnectionColor = connectionColor;
@@ -209,7 +208,7 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
         // in most of the cases there won't be more than one segment. Since the wireframe
         // renderer does not feature any transparency there should be no visual difference.
         for (final WaySegment wseg : data.getHighlightedWaySegments()) {
-            drawSegment(nc.getPoint(wseg.getFirstNode()), nc.getPoint(wseg.getSecondNode()), highlightColor, false);
+            drawSegment(mapState.getPointFor(wseg.getFirstNode()), mapState.getPointFor(wseg.getSecondNode()), highlightColor, false);
         }
         displaySegments();
     }
@@ -314,9 +313,9 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
 
         Iterator<Node> it = w.getNodes().iterator();
         if (it.hasNext()) {
-            Point lastP = nc.getPoint(it.next());
+            MapViewPoint lastP = mapState.getPointFor(it.next());
             for (int orderNumber = 1; it.hasNext(); orderNumber++) {
-                Point p = nc.getPoint(it.next());
+                MapViewPoint p = mapState.getPointFor(it.next());
                 drawSegment(lastP, p, wayColor,
                         showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
                 if (showOrderNumber && !isInactiveMode) {
@@ -353,13 +352,11 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
             }
 
             if (m.isNode()) {
-                Point p = nc.getPoint(m.getNode());
-                if (p.x < 0 || p.y < 0
-                        || p.x > nc.getWidth() || p.y > nc.getHeight()) {
-                    continue;
+                MapViewPoint p = mapState.getPointFor(m.getNode());
+                if (p.isInView()) {
+                    g.draw(new Ellipse2D.Double(p.getInViewX()-4, p.getInViewY()-4, 9, 9));
                 }
 
-                g.drawOval(p.x-4, p.y-4, 9, 9);
             } else if (m.isWay()) {
                 GeneralPath path = new GeneralPath();
 
@@ -368,12 +365,12 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
                     if (!n.isDrawable()) {
                         continue;
                     }
-                    Point p = nc.getPoint(n);
+                    MapViewPoint p = mapState.getPointFor(n);
                     if (first) {
-                        path.moveTo(p.x, p.y);
+                        path.moveTo(p.getInViewX(), p.getInViewY());
                         first = false;
                     } else {
-                        path.lineTo(p.x, p.y);
+                        path.lineTo(p.getInViewX(), p.getInViewY());
                     }
                 }
 
@@ -392,18 +389,16 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
     @Override
     public void drawNode(Node n, Color color, int size, boolean fill) {
         if (size > 1) {
-            int radius = size / 2;
-            Point p = nc.getPoint(n);
-            if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth())
-                    || (p.y > nc.getHeight()))
+            MapViewPoint p = mapState.getPointFor(n);
+            if (!p.isInView())
                 return;
+            int radius = size / 2;
+            Double shape = new Rectangle2D.Double(p.getInViewX() - radius, p.getInViewY() - radius, size, size);
             g.setColor(color);
             if (fill) {
-                g.fillRect(p.x - radius, p.y - radius, size, size);
-                g.drawRect(p.x - radius, p.y - radius, size, size);
-            } else {
-                g.drawRect(p.x - radius, p.y - radius, size, size);
+                g.fill(shape);
             }
+            g.draw(shape);
         }
     }
 
@@ -411,29 +406,18 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
      * Draw a line with the given color.
      *
      * @param path The path to append this segment.
-     * @param p1 First point of the way segment.
-     * @param p2 Second point of the way segment.
+     * @param mv1 First point of the way segment.
+     * @param mv2 Second point of the way segment.
      * @param showDirection <code>true</code> if segment direction should be indicated
      */
-    protected void drawSegment(GeneralPath path, Point p1, Point p2, boolean showDirection) {
+    protected void drawSegment(MapPath2D path, MapViewPoint mv1, MapViewPoint mv2, boolean showDirection) {
         Rectangle bounds = g.getClipBounds();
         bounds.grow(100, 100);                  // avoid arrow heads at the border
-        LineClip clip = new LineClip(p1, p2, bounds);
-        if (clip.execute()) {
-            p1 = clip.getP1();
-            p2 = clip.getP2();
-            path.moveTo(p1.x, p1.y);
-            path.lineTo(p2.x, p2.y);
-
+        if (mv1.rectTo(mv2).isInView()) {
+            path.moveTo(mv1);
+            path.lineTo(mv2);
             if (showDirection) {
-                final double l = 10. / p1.distance(p2);
-
-                final double sx = l * (p1.x - p2.x);
-                final double sy = l * (p1.y - p2.y);
-
-                path.lineTo(p2.x + (double) Math.round(cosPHI * sx - sinPHI * sy), p2.y + (double) Math.round(sinPHI * sx + cosPHI * sy));
-                path.moveTo(p2.x + (double) Math.round(cosPHI * sx + sinPHI * sy), p2.y + (double) Math.round(-sinPHI * sx + cosPHI * sy));
-                path.lineTo(p2.x, p2.y);
+                ARROW_PAINT_HELPER.paintArrowAt(path, mv2, mv1);
             }
         }
     }
@@ -446,8 +430,8 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
      * @param col The color to use for drawing line.
      * @param showDirection <code>true</code> if segment direction should be indicated.
      */
-    protected void drawSegment(Point p1, Point p2, Color col, boolean showDirection) {
-        if (col != currentColor) {
+    protected void drawSegment(MapViewPoint p1, MapViewPoint p2, Color col, boolean showDirection) {
+        if (!col.equals(currentColor)) {
             displaySegments(col);
         }
         drawSegment(currentPath, p1, p2, showDirection);
@@ -469,7 +453,7 @@ public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor
         if (currentPath != null) {
             g.setColor(currentColor);
             g.draw(currentPath);
-            currentPath = new GeneralPath();
+            currentPath = new MapPath2D();
             currentColor = newColor;
         }
     }
diff --git a/src/org/openstreetmap/josm/data/projection/Projection.java b/src/org/openstreetmap/josm/data/projection/Projection.java
index 62c7050..c1db917 100644
--- a/src/org/openstreetmap/josm/data/projection/Projection.java
+++ b/src/org/openstreetmap/josm/data/projection/Projection.java
@@ -102,4 +102,12 @@ public interface Projection extends Projecting {
      * @return true if natural order of coordinates is North East, false if East North
      */
     boolean switchXY();
+
+    /**
+     * Gets the object used as cache identifier when caching results of this projection.
+     * @return The object to use as cache key
+     */
+    default Object getCacheKey() {
+        return this;
+    }
 }
diff --git a/src/org/openstreetmap/josm/gui/MapViewState.java b/src/org/openstreetmap/josm/gui/MapViewState.java
index 6466eef..77bb17a 100644
--- a/src/org/openstreetmap/josm/gui/MapViewState.java
+++ b/src/org/openstreetmap/josm/gui/MapViewState.java
@@ -3,7 +3,6 @@ package org.openstreetmap.josm.gui;
 
 import java.awt.Container;
 import java.awt.Point;
-import java.awt.Rectangle;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Area;
 import java.awt.geom.Path2D;
@@ -18,6 +17,7 @@ import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.projection.Projecting;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.gui.download.DownloadDialog;
@@ -30,6 +30,26 @@ import org.openstreetmap.josm.tools.bugreport.BugReport;
  */
 public final class MapViewState {
 
+    /**
+     * A flag indicating that the point is outside to the top of the map view.
+     */
+    public static final int OUTSIDE_TOP = 1;
+
+    /**
+     * A flag indicating that the point is outside to the bottom of the map view.
+     */
+    public static final int OUTSIDE_BOTTOM = 2;
+
+    /**
+     * A flag indicating that the point is outside to the left of the map view.
+     */
+    public static final int OUTSIDE_LEFT = 3;
+
+    /**
+     * A flag indicating that the point is outside to the right of the map view.
+     */
+    public static final int OUTSIDE_RIGHT = 4;
+
     private final Projecting projecting;
 
     private final int viewWidth;
@@ -157,6 +177,16 @@ public final class MapViewState {
     }
 
     /**
+     * Gets the {@link MapViewPoint} for the given node. This is faster than {@link #getPointFor(LatLon)} because it uses the node east/north
+     * cache.
+     * @param node The node
+     * @return The position of that node.
+     */
+    public MapViewPoint getPointFor(Node node) {
+        return getPointFor(node.getEastNorth(getProjection()));
+    }
+
+    /**
      * Gets a rectangle representing the whole view area.
      * @return The rectangle.
      */
@@ -170,7 +200,7 @@ public final class MapViewState {
      * @return The view area.
      * @since 10458
      */
-    public MapViewRectangle getViewArea(Rectangle rectangle) {
+    public MapViewRectangle getViewArea(Rectangle2D rectangle) {
         return getForView(rectangle.getMinX(), rectangle.getMinY()).rectTo(getForView(rectangle.getMaxX(), rectangle.getMaxY()));
     }
 
@@ -330,9 +360,17 @@ public final class MapViewState {
             return new Point2D.Double(getInViewX(), getInViewY());
         }
 
-        protected abstract double getInViewX();
+        /**
+         * Get the x coordinate in view space without creating an intermediate object.
+         * @return The x coordinate
+         */
+        public abstract double getInViewX();
 
-        protected abstract double getInViewY();
+        /**
+         * Get the y coordinate in view space without creating an intermediate object.
+         * @return The y coordinate
+         */
+        public abstract double getInViewY();
 
         /**
          * Convert this point to window coordinates.
@@ -398,6 +436,71 @@ public final class MapViewState {
         public MapViewPoint add(EastNorth en) {
             return new MapViewEastNorthPoint(getEastNorth().add(en));
         }
+
+        /**
+         * Check if this point is inside the view bounds.
+         *
+         * This is the case iff <code>getOutsideRectangleFlags(getViewArea())</code> returns no flags
+         * @return true if it is.
+         */
+        public boolean isInView() {
+            return inRange(getInViewX(), 0, getViewWidth()) && inRange(getInViewY(), 0, getViewHeight());
+        }
+
+        private boolean inRange(double val, int min, double max) {
+            return val >= min && val < max;
+        }
+
+        /**
+         * Gets the direction in which this point is outside of the given view rectangle.
+         * @param rect The rectangle to check agains.
+         * @return The direction in which it is outside of the view, as OUTSIDE_... flags.
+         */
+        public int getOutsideRectangleFlags(MapViewRectangle rect) {
+            Rectangle2D bounds = rect.getInView();
+            int flags = 0;
+            if (getInViewX() < bounds.getMinX()) {
+                flags |= OUTSIDE_LEFT;
+            } else if (getInViewX() > bounds.getMaxX()) {
+                flags |= OUTSIDE_RIGHT;
+            }
+            if (getInViewY() < bounds.getMinY()) {
+                flags |= OUTSIDE_TOP;
+            } else if (getInViewY() > bounds.getMaxY()) {
+                flags |= OUTSIDE_BOTTOM;
+            }
+
+            return flags;
+        }
+
+        /**
+         * Gets the sum of the x/y view distances between the points. |x1 - x2| + |y1 - y2|
+         * @param p2 The other point
+         * @return The norm
+         */
+        public double oneNormInView(MapViewPoint p2) {
+            return Math.abs(getInViewX() - p2.getInViewX()) + Math.abs(getInViewY()) - p2.getInViewY();
+        }
+
+        /**
+         * Gets the squared distance between this point and an other point.
+         * @param p2 The other point
+         * @return The squared distance.
+         */
+        public double distanceToInViewSq(MapViewPoint p2) {
+            double dx = getInViewX() - p2.getInViewX();
+            double dy = getInViewY() - p2.getInViewY();
+            return dx * dx + dy * dy;
+        }
+
+        /**
+         * Gets the distance between this point and an other point.
+         * @param p2 The other point
+         * @return The distance.
+         */
+        public double distanceToInView(MapViewPoint p2) {
+            return Math.sqrt(distanceToInViewSq(p2));
+        }
     }
 
     private class MapViewViewPoint extends MapViewPoint {
@@ -410,12 +513,12 @@ public final class MapViewState {
         }
 
         @Override
-        protected double getInViewX() {
+        public double getInViewX() {
             return x;
         }
 
         @Override
-        protected double getInViewY() {
+        public double getInViewY() {
             return y;
         }
 
@@ -434,12 +537,12 @@ public final class MapViewState {
         }
 
         @Override
-        protected double getInViewX() {
+        public double getInViewX() {
             return (eastNorth.east() - topLeft.east()) / scale;
         }
 
         @Override
-        protected double getInViewY() {
+        public double getInViewY() {
             return (topLeft.north() - eastNorth.north()) / scale;
         }
 
@@ -516,6 +619,14 @@ public final class MapViewState {
             double y2 = p2.getInViewY();
             return new Rectangle2D.Double(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
         }
+
+        /**
+         * Check if the rectangle intersects the map view area.
+         * @return <code>true</code> if it intersects.
+         */
+        public boolean isInView() {
+            return getInView().intersects(getViewArea().getInView());
+        }
     }
 
 }
diff --git a/src/org/openstreetmap/josm/gui/NavigatableComponent.java b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
index 7e4d6e7..a07664a 100644
--- a/src/org/openstreetmap/josm/gui/NavigatableComponent.java
+++ b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
@@ -504,21 +504,21 @@ public class NavigatableComponent extends JComponent implements Helpful {
     }
 
     // looses precision, may overflow (depends on p and current scale)
-    //@Deprecated
+    @Deprecated
     public Point getPoint(EastNorth p) {
         Point2D d = getPoint2D(p);
         return new Point((int) d.getX(), (int) d.getY());
     }
 
     // looses precision, may overflow (depends on p and current scale)
-    //@Deprecated
+    @Deprecated
     public Point getPoint(LatLon latlon) {
         Point2D d = getPoint2D(latlon);
         return new Point((int) d.getX(), (int) d.getY());
     }
 
     // looses precision, may overflow (depends on p and current scale)
-    //@Deprecated
+    @Deprecated
     public Point getPoint(Node n) {
         Point2D d = getPoint2D(n);
         return new Point((int) d.getX(), (int) d.getY());
diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java
index 4aa58e6..e49906d 100644
--- a/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java
+++ b/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java
@@ -6,6 +6,7 @@ import java.awt.Color;
 import java.awt.Rectangle;
 import java.awt.Stroke;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.stream.IntStream;
 
 import org.openstreetmap.josm.Main;
@@ -22,6 +23,7 @@ import org.openstreetmap.josm.gui.mappaint.MultiCascade;
 import org.openstreetmap.josm.gui.mappaint.StyleElementList;
 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProvider;
 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.SimpleBoxProvider;
+import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol.SymbolShape;
 import org.openstreetmap.josm.gui.util.RotationAngle;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.Utils;
@@ -32,54 +34,11 @@ import org.openstreetmap.josm.tools.Utils;
 public class NodeElement extends StyleElement {
     public final MapImage mapImage;
     public final RotationAngle mapImageAngle;
+    /**
+     * The symbol that should be used for drawing this node.
+     */
     public final Symbol symbol;
 
-    public enum SymbolShape { SQUARE, CIRCLE, TRIANGLE, PENTAGON, HEXAGON, HEPTAGON, OCTAGON, NONAGON, DECAGON }
-
-    public static class Symbol {
-        public SymbolShape symbol;
-        public int size;
-        public Stroke stroke;
-        public Color strokeColor;
-        public Color fillColor;
-
-        public Symbol(SymbolShape symbol, int size, Stroke stroke, Color strokeColor, Color fillColor) {
-            if (stroke != null && strokeColor == null)
-                throw new IllegalArgumentException("Stroke given without color");
-            if (stroke == null && fillColor == null)
-                throw new IllegalArgumentException("Either a stroke or a fill color must be given");
-            this.symbol = symbol;
-            this.size = size;
-            this.stroke = stroke;
-            this.strokeColor = strokeColor;
-            this.fillColor = fillColor;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj == null || getClass() != obj.getClass())
-                return false;
-            final Symbol other = (Symbol) obj;
-            return symbol == other.symbol &&
-                    size == other.size &&
-                    Objects.equals(stroke, other.stroke) &&
-                    Objects.equals(strokeColor, other.strokeColor) &&
-                    Objects.equals(fillColor, other.fillColor);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(symbol, size, stroke, strokeColor, fillColor);
-        }
-
-        @Override
-        public String toString() {
-            return "symbol=" + symbol + " size=" + size +
-                    (stroke != null ? " stroke=" + stroke + " strokeColor=" + strokeColor : "") +
-                    (fillColor != null ? " fillColor=" + fillColor : "");
-        }
-    }
-
     private static final String[] ICON_KEYS = {ICON_IMAGE, ICON_WIDTH, ICON_HEIGHT, ICON_OPACITY, ICON_OFFSET_X, ICON_OFFSET_Y};
 
     public static final NodeElement SIMPLE_NODE_ELEMSTYLE;
@@ -193,33 +152,16 @@ public class NodeElement extends StyleElement {
 
     private static Symbol createSymbol(Environment env) {
         Cascade c = env.mc.getCascade(env.layer);
-        Cascade cDef = env.mc.getCascade("default");
 
-        SymbolShape shape;
         Keyword shapeKW = c.get("symbol-shape", null, Keyword.class);
         if (shapeKW == null)
             return null;
-        if ("square".equals(shapeKW.val)) {
-            shape = SymbolShape.SQUARE;
-        } else if ("circle".equals(shapeKW.val)) {
-            shape = SymbolShape.CIRCLE;
-        } else if ("triangle".equals(shapeKW.val)) {
-            shape = SymbolShape.TRIANGLE;
-        } else if ("pentagon".equals(shapeKW.val)) {
-            shape = SymbolShape.PENTAGON;
-        } else if ("hexagon".equals(shapeKW.val)) {
-            shape = SymbolShape.HEXAGON;
-        } else if ("heptagon".equals(shapeKW.val)) {
-            shape = SymbolShape.HEPTAGON;
-        } else if ("octagon".equals(shapeKW.val)) {
-            shape = SymbolShape.OCTAGON;
-        } else if ("nonagon".equals(shapeKW.val)) {
-            shape = SymbolShape.NONAGON;
-        } else if ("decagon".equals(shapeKW.val)) {
-            shape = SymbolShape.DECAGON;
-        } else
+        Optional<SymbolShape> shape = SymbolShape.forName(shapeKW.val);
+        if (!shape.isPresent()) {
             return null;
+        }
 
+        Cascade cDef = env.mc.getCascade("default");
         Float sizeOnDefault = cDef.get("symbol-size", null, Float.class);
         if (sizeOnDefault != null && sizeOnDefault <= 0) {
             sizeOnDefault = null;
@@ -267,7 +209,7 @@ public class NodeElement extends StyleElement {
             }
         }
 
-        return new Symbol(shape, Math.round(size), stroke, strokeColor, fillColor);
+        return new Symbol(shape.get(), Math.round(size), stroke, strokeColor, fillColor);
     }
 
     @Override
@@ -279,27 +221,7 @@ public class NodeElement extends StyleElement {
                 painter.drawNodeIcon(n, mapImage, painter.isInactiveMode() || n.isDisabled(), selected, member,
                         mapImageAngle == null ? 0.0 : mapImageAngle.getRotationAngle(primitive));
             } else if (symbol != null) {
-                Color fillColor = symbol.fillColor;
-                if (fillColor != null) {
-                    if (painter.isInactiveMode() || n.isDisabled()) {
-                        fillColor = settings.getInactiveColor();
-                    } else if (defaultSelectedHandling && selected) {
-                        fillColor = settings.getSelectedColor(fillColor.getAlpha());
-                    } else if (member) {
-                        fillColor = settings.getRelationSelectedColor(fillColor.getAlpha());
-                    }
-                }
-                Color strokeColor = symbol.strokeColor;
-                if (strokeColor != null) {
-                    if (painter.isInactiveMode() || n.isDisabled()) {
-                        strokeColor = settings.getInactiveColor();
-                    } else if (defaultSelectedHandling && selected) {
-                        strokeColor = settings.getSelectedColor(strokeColor.getAlpha());
-                    } else if (member) {
-                        strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha());
-                    }
-                }
-                painter.drawNodeSymbol(n, symbol, fillColor, strokeColor);
+                paintWithSymbol(settings, painter, selected, member, n);
             } else {
                 Color color;
                 boolean isConnection = n.isConnectionNode();
@@ -343,6 +265,31 @@ public class NodeElement extends StyleElement {
         }
     }
 
+    private void paintWithSymbol(MapPaintSettings settings, StyledMapRenderer painter, boolean selected, boolean member,
+            Node n) {
+        Color fillColor = symbol.fillColor;
+        if (fillColor != null) {
+            if (painter.isInactiveMode() || n.isDisabled()) {
+                fillColor = settings.getInactiveColor();
+            } else if (defaultSelectedHandling && selected) {
+                fillColor = settings.getSelectedColor(fillColor.getAlpha());
+            } else if (member) {
+                fillColor = settings.getRelationSelectedColor(fillColor.getAlpha());
+            }
+        }
+        Color strokeColor = symbol.strokeColor;
+        if (strokeColor != null) {
+            if (painter.isInactiveMode() || n.isDisabled()) {
+                strokeColor = settings.getInactiveColor();
+            } else if (defaultSelectedHandling && selected) {
+                strokeColor = settings.getSelectedColor(strokeColor.getAlpha());
+            } else if (member) {
+                strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha());
+            }
+        }
+        painter.drawNodeSymbol(n, symbol, fillColor, strokeColor);
+    }
+
     public BoxProvider getBoxProvider() {
         if (mapImage != null)
             return mapImage.getBoxProvider();
diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/Symbol.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/Symbol.java
new file mode 100644
index 0000000..30bb25e
--- /dev/null
+++ b/src/org/openstreetmap/josm/gui/mappaint/styleelement/Symbol.java
@@ -0,0 +1,206 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.styleelement;
+
+import java.awt.Color;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Rectangle2D;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * The definition of a symbol that should be rendered at the node position.
+ * @since xxx Extracted from {@link NodeElement}
+ */
+public class Symbol {
+    private final SymbolShape symbolShape;
+    /**
+     * The width and height of this symbol
+     */
+    public final int size;
+    /**
+     * The stroke to use for the outline
+     */
+    public final Stroke stroke;
+    /**
+     * The color to draw the stroke with
+     */
+    public final Color strokeColor;
+    /**
+     * The color to fill the interiour of the shape.
+     */
+    public final Color fillColor;
+
+    /**
+     * Create a new symbol
+     * @param symbol The symbol type
+     * @param size The overall size of the symbol, both width and height are the same
+     * @param stroke The stroke to use for the outline
+     * @param strokeColor The color to draw the stroke with
+     * @param fillColor The color to fill the interiour of the shape.
+     */
+    public Symbol(SymbolShape symbol, int size, Stroke stroke, Color strokeColor, Color fillColor) {
+        if (stroke != null && strokeColor == null)
+            throw new IllegalArgumentException("Stroke given without color");
+        if (stroke == null && fillColor == null)
+            throw new IllegalArgumentException("Either a stroke or a fill color must be given");
+        this.symbolShape = symbol;
+        this.size = size;
+        this.stroke = stroke;
+        this.strokeColor = strokeColor;
+        this.fillColor = fillColor;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        final Symbol other = (Symbol) obj;
+        return symbolShape == other.symbolShape &&
+                size == other.size &&
+                Objects.equals(stroke, other.stroke) &&
+                Objects.equals(strokeColor, other.strokeColor) &&
+                Objects.equals(fillColor, other.fillColor);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(symbolShape, size, stroke, strokeColor, fillColor);
+    }
+
+    @Override
+    public String toString() {
+        return "symbolShape=" + symbolShape + " size=" + size +
+                (stroke != null ? (" stroke=" + stroke + " strokeColor=" + strokeColor) : "") +
+                (fillColor != null ? (" fillColor=" + fillColor) : "");
+    }
+
+    /**
+     * Builds the shape for this symbol
+     * @param x The center x coordinate
+     * @param y The center y coordinate
+     * @return The symbol shape.
+     */
+    public Shape buildShapeAround(double x, double y) {
+        int radius = size / 2;
+        Shape shape;
+        switch (symbolShape) {
+        case SQUARE:
+            // optimize for performance reasons
+            shape = new Rectangle2D.Double(x - radius,  y - radius, size, size);
+            break;
+        case CIRCLE:
+            shape = new Ellipse2D.Double(x - radius, y - radius, size, size);
+            break;
+        default:
+            shape = buildPolygon(x, y, radius);
+            break;
+        }
+        return shape;
+    }
+
+    private Shape buildPolygon(double cx, double cy, int radius) {
+        GeneralPath polygon = new GeneralPath();
+        for (int i = 0; i < symbolShape.sides; i++) {
+            double angle = ((2 * Math.PI / symbolShape.sides) * i) - symbolShape.rotation;
+            double x = cx + radius * Math.cos(angle);
+            double y = cy + radius * Math.sin(angle);
+            if (i == 0) {
+                polygon.moveTo(x, y);
+            } else {
+                polygon.lineTo(x, y);
+            }
+        }
+        polygon.closePath();
+        return polygon;
+    }
+
+    /**
+     * A list of possible symbol shapes.
+     */
+    public enum SymbolShape {
+        /**
+         * A square
+         */
+        SQUARE("square", 4, Math.PI / 4),
+        /**
+         * A circle
+         */
+        CIRCLE("circle", 1, 0),
+        /**
+         * A triangle with sides of equal lengh
+         */
+        TRIANGLE("triangle", 3, Math.PI / 2),
+        /**
+         * A pentagon
+         */
+        PENTAGON("pentagon", 5, Math.PI / 2),
+        /**
+         * A hexagon
+         */
+        HEXAGON("hexagon", 6, 0),
+        /**
+         * A heptagon
+         */
+        HEPTAGON("heptagon", 7, Math.PI / 2),
+        /**
+         * An octagon
+         */
+        OCTAGON("octagon", 8, Math.PI / 8),
+        /**
+         * a nonagon
+         */
+        NONAGON("nonagon", 9, Math.PI / 2),
+        /**
+         * A decagon
+         */
+        DECAGON("decagon", 10, 0);
+
+        private final String name;
+        final int sides;
+
+        final double rotation;
+
+        private SymbolShape(String name, int sides, double rotation) {
+            this.name = name;
+            this.sides = sides;
+            this.rotation = rotation;
+        }
+
+        /**
+         * Gets the number of normally straight sides this symbol has. Returns 1 for a circle.
+         * @return The sides of the symbol
+         */
+        public int getSides() {
+            return sides;
+        }
+
+        /**
+         * Gets the rotateion of the first point of this symbol.
+         * @return The roration
+         */
+        public double getRotation() {
+            return rotation;
+        }
+
+        /**
+         * Get the MapCSS name for this shape
+         * @return The name
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Get the shape with the given name
+         * @param val The name to search
+         * @return The shape as optional
+         */
+        public static Optional<SymbolShape> forName(String val) {
+            return Stream.of(values()).filter(val::equals).findAny();
+        }
+    }
+}
