Ticket #13306: patch-mapview-use-state-in-renderer.patch

File patch-mapview-use-state-in-renderer.patch, 105.2 KB (added by michael2402, 9 years ago)
  • src/org/openstreetmap/josm/actions/mapmode/DrawAction.java

    diff --git a/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java b/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
    index 305dfce..62d25cd 100644
    a b import java.awt.event.ActionEvent;  
    1616import java.awt.event.KeyEvent;
    1717import java.awt.event.MouseEvent;
    1818import java.awt.event.MouseListener;
    19 import java.awt.geom.GeneralPath;
    2019import java.util.ArrayList;
    2120import java.util.Arrays;
    2221import java.util.Collection;
    import org.openstreetmap.josm.data.osm.Node;  
    5049import org.openstreetmap.josm.data.osm.OsmPrimitive;
    5150import org.openstreetmap.josm.data.osm.Way;
    5251import org.openstreetmap.josm.data.osm.WaySegment;
     52import org.openstreetmap.josm.data.osm.visitor.paint.ArrowPaintHelper;
     53import org.openstreetmap.josm.data.osm.visitor.paint.MapPath2D;
    5354import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
    5455import org.openstreetmap.josm.gui.MainMenu;
    5556import org.openstreetmap.josm.gui.MapFrame;
    5657import org.openstreetmap.josm.gui.MapView;
     58import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    5759import org.openstreetmap.josm.gui.NavigatableComponent;
    5860import org.openstreetmap.josm.gui.layer.Layer;
    5961import org.openstreetmap.josm.gui.layer.MapViewPaintable;
    import org.openstreetmap.josm.tools.Utils;  
    7476public class DrawAction extends MapMode implements MapViewPaintable, SelectionChangedListener, KeyPressReleaseListener, ModifierListener {
    7577
    7678    private static final Color ORANGE_TRANSPARENT = new Color(Color.ORANGE.getRed(), Color.ORANGE.getGreen(), Color.ORANGE.getBlue(), 128);
    77     private static final double PHI = Math.toRadians(90);
     79
     80    private static final ArrowPaintHelper START_WAY_INDICATOR = new ArrowPaintHelper(Math.toRadians(90), 8);
    7881
    7982    private final Cursor cursorJoinNode;
    8083    private final Cursor cursorJoinWay;
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh  
    11381141            g2.setStroke(rubberLineStroke);
    11391142        } else if (!snapHelper.drawConstructionGeometry)
    11401143            return;
    1141         GeneralPath b = new GeneralPath();
    1142         Point p1 = mv.getPoint(getCurrentBaseNode());
    1143         Point p2 = mv.getPoint(currentMouseEastNorth);
    1144 
    1145         double t = Math.atan2((double) p2.y - p1.y, (double) p2.x - p1.x) + Math.PI;
     1144        MapPath2D b = new MapPath2D();
     1145        MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode());
     1146        MapViewPoint p2 = mv.getState().getPointFor(currentMouseEastNorth);
    11461147
    1147         b.moveTo(p1.x, p1.y);
    1148         b.lineTo(p2.x, p2.y);
     1148        b.moveTo(p1);
     1149        b.lineTo(p2);
    11491150
    11501151        // if alt key is held ("start new way"), draw a little perpendicular line
    11511152        if (alt) {
    1152             b.moveTo((int) (p1.x + 8*Math.cos(t+PHI)), (int) (p1.y + 8*Math.sin(t+PHI)));
    1153             b.lineTo((int) (p1.x + 8*Math.cos(t-PHI)), (int) (p1.y + 8*Math.sin(t-PHI)));
     1153            START_WAY_INDICATOR.paintArrowAt(b, p1, p2);
    11541154        }
    11551155
    11561156        g2.draw(b);
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh  
    14731473        public void drawIfNeeded(Graphics2D g2, MapView mv) {
    14741474            if (!snapOn || !active)
    14751475                return;
    1476             Point p1 = mv.getPoint(getCurrentBaseNode());
    1477             Point p2 = mv.getPoint(dir2);
    1478             Point p3 = mv.getPoint(projected);
    1479             GeneralPath b;
     1476            MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode());
     1477            MapViewPoint p2 = mv.getState().getPointFor(dir2);
     1478            MapViewPoint p3 = mv.getState().getPointFor(projected);
    14801479            if (drawConstructionGeometry) {
    14811480                g2.setColor(snapHelperColor);
    14821481                g2.setStroke(helperStroke);
    14831482
    1484                 b = new GeneralPath();
     1483                MapPath2D b = new MapPath2D();
     1484                b.moveTo(p2);
    14851485                if (absoluteFix) {
    1486                     b.moveTo(p2.x, p2.y);
    1487                     b.lineTo(2d*p1.x-p2.x, 2d*p1.y-p2.y); // bi-directional line
     1486                    b.lineTo(2d*p1.getInViewX()-p2.getInViewX(), 2d*p1.getInViewY()-p2.getInViewY()); // bi-directional line
    14881487                } else {
    1489                     b.moveTo(p2.x, p2.y);
    1490                     b.lineTo(p3.x, p3.y);
     1488                    b.lineTo(p3);
    14911489                }
    14921490                g2.draw(b);
    14931491            }
    14941492            if (projectionSource != null) {
    14951493                g2.setColor(snapHelperColor);
    14961494                g2.setStroke(helperStroke);
    1497                 b = new GeneralPath();
    1498                 b.moveTo(p3.x, p3.y);
    1499                 Point pp = mv.getPoint(projectionSource);
    1500                 b.lineTo(pp.x, pp.y);
     1495                MapPath2D b = new MapPath2D();
     1496                b.moveTo(p3);
     1497                b.lineTo(mv.getState().getPointFor(projectionSource));
    15011498                g2.draw(b);
    15021499            }
    15031500
    15041501            if (customBaseHeading >= 0) {
    15051502                g2.setColor(highlightColor);
    15061503                g2.setStroke(highlightStroke);
    1507                 b = new GeneralPath();
    1508                 Point pp1 = mv.getPoint(segmentPoint1);
    1509                 Point pp2 = mv.getPoint(segmentPoint2);
    1510                 b.moveTo(pp1.x, pp1.y);
    1511                 b.lineTo(pp2.x, pp2.y);
     1504                MapPath2D b = new MapPath2D();
     1505                b.moveTo(mv.getState().getPointFor(segmentPoint1));
     1506                b.lineTo(mv.getState().getPointFor(segmentPoint2));
    15121507                g2.draw(b);
    15131508            }
    15141509
    15151510            g2.setColor(rubberLineColor);
    15161511            g2.setStroke(normalStroke);
    1517             b = new GeneralPath();
    1518             b.moveTo(p1.x, p1.y);
    1519             b.lineTo(p3.x, p3.y);
     1512            MapPath2D b = new MapPath2D();
     1513            b.moveTo(p1);
     1514            b.lineTo(p3);
    15201515            g2.draw(b);
    15211516
    1522             g2.drawString(labelText, p3.x-5, p3.y+20);
     1517            g2.drawString(labelText, (int) p3.getInViewX()-5, (int) p3.getInViewY()+20);
    15231518            if (showProjectedPoint) {
    15241519                g2.setStroke(normalStroke);
    1525                 g2.drawOval(p3.x-5, p3.y-5, 10, 10); // projected point
     1520                g2.drawOval((int) p3.getInViewX()-5, (int) p3.getInViewY()-5, 10, 10); // projected point
    15261521            }
    15271522
    15281523            g2.setColor(snapHelperColor);
  • src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java

    diff --git a/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java b/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java
    index 72d4740..4afef70 100644
    a b import java.awt.Point;  
    1212import java.awt.Stroke;
    1313import java.awt.event.KeyEvent;
    1414import java.awt.event.MouseEvent;
    15 import java.awt.geom.GeneralPath;
    1615import java.util.ArrayList;
    1716import java.util.Collection;
    1817import java.util.LinkedList;
    import org.openstreetmap.josm.data.osm.Node;  
    3534import org.openstreetmap.josm.data.osm.OsmPrimitive;
    3635import org.openstreetmap.josm.data.osm.Way;
    3736import org.openstreetmap.josm.data.osm.WaySegment;
     37import org.openstreetmap.josm.data.osm.visitor.paint.MapPath2D;
    3838import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
    3939import org.openstreetmap.josm.gui.MapFrame;
    4040import org.openstreetmap.josm.gui.MapView;
     41import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    4142import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable;
    4243import org.openstreetmap.josm.gui.layer.Layer;
    4344import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    public class ImproveWayAccuracyAction extends MapMode implements  
    238239
    239240            List<Node> nodes = targetWay.getNodes();
    240241
    241             GeneralPath b = new GeneralPath();
     242            MapPath2D b = new MapPath2D();
    242243            Point p0 = mv.getPoint(nodes.get(0));
    243244            Point pn;
    244245            b.moveTo(p0.x, p0.y);
    public class ImproveWayAccuracyAction extends MapMode implements  
    293294
    294295
    295296            // Drawing preview lines
    296             GeneralPath b = new GeneralPath();
     297            MapPath2D b = new MapPath2D();
    297298            if (alt && !ctrl) {
    298299                // In delete mode
    299300                if (p1 != null && p2 != null) {
    public class ImproveWayAccuracyAction extends MapMode implements  
    329330        }
    330331    }
    331332
    332     protected void drawIntersectingWayHelperLines(MapView mv, GeneralPath b) {
     333    protected void drawIntersectingWayHelperLines(MapView mv, MapPath2D b) {
    333334        for (final OsmPrimitive referrer : candidateNode.getReferrers()) {
    334335            if (!(referrer instanceof Way) || targetWay.equals(referrer)) {
    335336                continue;
    public class ImproveWayAccuracyAction extends MapMode implements  
    340341                    continue;
    341342                }
    342343                if (i > 0) {
    343                     final Point p = mv.getPoint(nodes.get(i - 1));
     344                    final MapViewPoint p = mv.getState().getPointFor(nodes.get(i - 1));
    344345                    b.moveTo(mousePos.x, mousePos.y);
    345                     b.lineTo(p.x, p.y);
     346                    b.lineTo(p);
    346347                }
    347348                if (i < nodes.size() - 1) {
    348                     final Point p = mv.getPoint(nodes.get(i + 1));
     349                    final MapViewPoint p = mv.getState().getPointFor(nodes.get(i + 1));
    349350                    b.moveTo(mousePos.x, mousePos.y);
    350                     b.lineTo(p.x, p.y);
     351                    b.lineTo(p);
    351352                }
    352353            }
    353354        }
  • src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

    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 b import org.openstreetmap.josm.data.osm.visitor.paint.WireframeMapRenderer;  
    4141import org.openstreetmap.josm.gui.ExtendedDialog;
    4242import org.openstreetmap.josm.gui.MapFrame;
    4343import org.openstreetmap.josm.gui.MapView;
     44import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    4445import org.openstreetmap.josm.gui.SelectionManager;
    4546import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded;
    4647import org.openstreetmap.josm.gui.layer.Layer;
    public class SelectAction extends MapMode implements ModifierListener, KeyPressR  
    11911192
    11921193                    wnp.a = w.getNode(ws.lowerIndex);
    11931194                    wnp.b = w.getNode(ws.lowerIndex + 1);
    1194                     Point2D p1 = mv.getPoint2D(wnp.a);
    1195                     Point2D p2 = mv.getPoint2D(wnp.b);
     1195                    MapViewPoint p1 = mv.getState().getPointFor(wnp.a);
     1196                    MapViewPoint p2 = mv.getState().getPointFor(wnp.b);
    11961197                    if (WireframeMapRenderer.isLargeSegment(p1, p2, virtualSpace)) {
    1197                         Point2D pc = new Point2D.Double((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
     1198                        Point2D pc = new Point2D.Double((p1.getInViewX() + p2.getInViewX()) / 2, (p1.getInViewY() + p2.getInViewY()) / 2);
    11981199                        if (p.distanceSq(pc) < virtualSnapDistSq2) {
    11991200                            // Check that only segments on top of each other get added to the
    12001201                            // virtual ways list. Otherwise ways that coincidentally have their
  • src/org/openstreetmap/josm/data/osm/Node.java

    diff --git a/src/org/openstreetmap/josm/data/osm/Node.java b/src/org/openstreetmap/josm/data/osm/Node.java
    index 8709f6a..13e8ac3 100644
    a b  
    22package org.openstreetmap.josm.data.osm;
    33
    44import java.util.Collection;
     5import java.util.Objects;
    56import java.util.Set;
    67import java.util.TreeSet;
    78import java.util.function.Predicate;
    import org.openstreetmap.josm.data.coor.EastNorth;  
    1112import org.openstreetmap.josm.data.coor.LatLon;
    1213import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
    1314import org.openstreetmap.josm.data.osm.visitor.Visitor;
     15import org.openstreetmap.josm.data.projection.Projection;
    1416import org.openstreetmap.josm.data.projection.Projections;
    1517import org.openstreetmap.josm.tools.CheckParameterUtil;
    1618import org.openstreetmap.josm.tools.Utils;
    public final class Node extends OsmPrimitive implements INode {  
    3335     */
    3436    private double east = Double.NaN;
    3537    private double north = Double.NaN;
     38    /**
     39     * The cache key to use for {@link #east} and {@link #north}.
     40     */
     41    private Object eastNorthCacheKey;
    3642
    3743    /**
    3844     * Determines if this node has valid coordinates.
    public final class Node extends OsmPrimitive implements INode {  
    7884     * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates.
    7985     * Internally caches the projected coordinates.</p>
    8086     *
    81      * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must
    82      * {@link #invalidateEastNorthCache() invalidate the internal cache}.</p>
    83      *
    8487     * <p>Replies {@code null} if this node doesn't know lat/lon-coordinates, i.e. because it is an incomplete node.
    8588     *
    8689     * @return the east north coordinates or {@code null}
    public final class Node extends OsmPrimitive implements INode {  
    8992     */
    9093    @Override
    9194    public EastNorth getEastNorth() {
    92         if (!isLatLonKnown()) return null;
     95        return getEastNorth(Main.getProjection());
     96    }
    9397
    94         if (getDataSet() == null)
    95             // there is no dataset that listens for projection changes
    96             // and invalidates the cache, so we don't use the cache at all
    97             return Projections.project(new LatLon(lat, lon));
     98    /**
     99     * Replies the projected east/north coordinates.
     100     * <p>
     101     * The result of the last conversion is cached. The cache object is used as cache key.
     102     * @param projection The projection to use.
     103     * @return The projected east/north coordinates
     104     * @since xxx
     105     */
     106    public EastNorth getEastNorth(Projection projection) {
     107        if (!isLatLonKnown()) return null;
    98108
    99         if (Double.isNaN(east) || Double.isNaN(north)) {
     109        if (Double.isNaN(east) || Double.isNaN(north) || !Objects.equals(projection.getCacheKey(), eastNorthCacheKey)) {
    100110            // projected coordinates haven't been calculated yet,
    101111            // so fill the cache of the projected node coordinates
    102112            EastNorth en = Projections.project(new LatLon(lat, lon));
    103113            this.east = en.east();
    104114            this.north = en.north();
     115            this.eastNorthCacheKey = projection.getCacheKey();
    105116        }
    106117        return new EastNorth(east, north);
    107118    }
    public final class Node extends OsmPrimitive implements INode {  
    122133            this.lon = ll.lon();
    123134            this.east = eastNorth.east();
    124135            this.north = eastNorth.north();
     136            this.eastNorthCacheKey = Main.getProjection().getCacheKey();
    125137        } else {
    126138            this.lat = Double.NaN;
    127139            this.lon = Double.NaN;
    public final class Node extends OsmPrimitive implements INode {  
    345357    public void invalidateEastNorthCache() {
    346358        this.east = Double.NaN;
    347359        this.north = Double.NaN;
     360        this.eastNorthCacheKey = null;
    348361    }
    349362
    350363    @Override
  • src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java

    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 b package org.openstreetmap.josm.data.osm.visitor.paint;  
    33
    44import java.awt.Color;
    55import java.awt.Graphics2D;
    6 import java.awt.Point;
    76import java.awt.geom.GeneralPath;
    8 import java.awt.geom.Point2D;
     7import java.awt.geom.Path2D;
     8import java.awt.geom.Rectangle2D;
    99import java.util.Iterator;
    1010
    1111import org.openstreetmap.josm.Main;
    import org.openstreetmap.josm.data.osm.DataSet;  
    1414import org.openstreetmap.josm.data.osm.Node;
    1515import org.openstreetmap.josm.data.osm.Way;
    1616import org.openstreetmap.josm.data.osm.WaySegment;
     17import org.openstreetmap.josm.gui.MapViewState;
     18import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
     19import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle;
    1720import org.openstreetmap.josm.gui.NavigatableComponent;
    1821import org.openstreetmap.josm.tools.CheckParameterUtil;
    1922
    public abstract class AbstractMapRenderer implements Rendering {  
    2831    /** the map viewport - provides projection and hit detection functionality */
    2932    protected NavigatableComponent nc;
    3033
     34    /**
     35     * The {@link MapViewState} to use to convert between coordinates.
     36     */
     37    protected final MapViewState mapState;
     38
    3139    /** if true, the paint visitor shall render OSM objects such that they
    3240     * look inactive. Example: rendering of data in an inactive layer using light gray as color only. */
    3341    protected boolean isInactiveMode;
    public abstract class AbstractMapRenderer implements Rendering {  
    6775        CheckParameterUtil.ensureParameterNotNull(nc);
    6876        this.g = g;
    6977        this.nc = nc;
     78        this.mapState = nc.getState();
    7079        this.isInactiveMode = isInactiveMode;
    7180    }
    7281
    public abstract class AbstractMapRenderer implements Rendering {  
    8998     * @param orderNumber The number of the segment in the way.
    9099     * @param clr The color to use for drawing the text.
    91100     */
    92     protected void drawOrderNumber(Point p1, Point p2, int orderNumber, Color clr) {
     101    protected void drawOrderNumber(MapViewPoint p1, MapViewPoint p2, int orderNumber, Color clr) {
    93102        if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
    94103            String on = Integer.toString(orderNumber);
    95104            int strlen = on.length();
    96             int x = (p1.x+p2.x)/2 - 4*strlen;
    97             int y = (p1.y+p2.y)/2 + 4;
     105            double centerX = (p1.getInViewX()+p2.getInViewX())/2;
     106            double centerY = (p1.getInViewY()+p2.getInViewY())/2;
     107            double x = centerX - 4*strlen;
     108            double y = centerY + 4;
    98109
    99110            if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) {
    100                 y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
     111                y = centerY - virtualNodeSize - 3;
    101112            }
    102113
    103114            g.setColor(backgroundColor);
    104             g.fillRect(x-1, y-12, 8*strlen+1, 14);
     115            g.fill(new Rectangle2D.Double(x-1, y-12, 8*strlen+1, 14));
    105116            g.setColor(clr);
    106             g.drawString(on, x, y);
     117            g.drawString(on, (int) x, (int) y);
    107118        }
    108119    }
    109120
    public abstract class AbstractMapRenderer implements Rendering {  
    182193     * @param space The free space to check against.
    183194     * @return <code>true</code> if segment is larger than required space
    184195     */
    185     public static boolean isLargeSegment(Point2D p1, Point2D p2, int space) {
    186         double xd = Math.abs(p1.getX()-p2.getX());
    187         double yd = Math.abs(p1.getY()-p2.getY());
    188         return xd + yd > space;
     196    public static boolean isLargeSegment(MapViewPoint p1, MapViewPoint p2, int space) {
     197        return p1.oneNormInView(p2) > space;
    189198    }
    190199
    191200    /**
    public abstract class AbstractMapRenderer implements Rendering {  
    193202     *
    194203     * @param p1 First point of the way segment.
    195204     * @param p2 Second point of the way segment.
    196      * @return <code>true</code> if segment is visible.
     205     * @return <code>true</code> if segment may be visible.
    197206     */
    198     protected boolean isSegmentVisible(Point p1, Point p2) {
    199         if ((p1.x < 0) && (p2.x < 0)) return false;
    200         if ((p1.y < 0) && (p2.y < 0)) return false;
    201         if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
    202         if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
    203         return true;
     207    protected boolean isSegmentVisible(MapViewPoint p1, MapViewPoint p2) {
     208        MapViewRectangle view = mapState.getViewArea();
     209        // not outside in the same direction
     210        return (p1.getOutsideRectangleFlags(view) & p2.getOutsideRectangleFlags(view)) == 0;
    204211    }
    205212
    206213    /**
    public abstract class AbstractMapRenderer implements Rendering {  
    209216     * @param path The path to append drawing to.
    210217     * @param w The ways to draw node for.
    211218     */
    212     public void visitVirtual(GeneralPath path, Way w) {
     219    public void visitVirtual(Path2D path, Way w) {
    213220        Iterator<Node> it = w.getNodes().iterator();
    214221        if (it.hasNext()) {
    215             Point lastP = nc.getPoint(it.next());
     222            MapViewPoint lastP = mapState.getPointFor(it.next());
    216223            while (it.hasNext()) {
    217                 Point p = nc.getPoint(it.next());
     224                MapViewPoint p =  mapState.getPointFor(it.next());
    218225                if (isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace)) {
    219                     int x = (p.x+lastP.x)/2;
    220                     int y = (p.y+lastP.y)/2;
    221                     path.moveTo((double) x-virtualNodeSize, y);
    222                     path.lineTo((double) x+virtualNodeSize, y);
    223                     path.moveTo(x, (double) y-virtualNodeSize);
    224                     path.lineTo(x, (double) y+virtualNodeSize);
     226                    double x = (p.getInViewX()+lastP.getInViewX())/2;
     227                    double y = (p.getInViewY()+lastP.getInViewY())/2;
     228                    path.moveTo(x-virtualNodeSize, y);
     229                    path.lineTo(x+virtualNodeSize, y);
     230                    path.moveTo(x, y-virtualNodeSize);
     231                    path.lineTo(x, y+virtualNodeSize);
    225232                }
    226233                lastP = p;
    227234            }
  • new file src/org/openstreetmap/josm/data/osm/visitor/paint/ArrowPaintHelper.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.osm.visitor.paint;
     3
     4import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
     5import org.openstreetmap.josm.tools.Utils;
     6
     7/**
     8 * This class helps with painting arrows with fixed length along a path.
     9 * @author Michael Zangl
     10 * @since xxx
     11 */
     12public class ArrowPaintHelper {
     13    private final double sin;
     14    private final double cos;
     15    private final double length;
     16
     17    /**
     18     * Creates a new arrow helper.
     19     * @param radians The angle of the arrow. 0 means that it lies on the current line. In radians
     20     * @param length The length of the arrow lines.
     21     */
     22    public ArrowPaintHelper(double radians, double length) {
     23        this.sin = Math.sin(radians);
     24        this.cos = Math.cos(radians);
     25        this.length = length;
     26    }
     27
     28    /**
     29     * Paint the arrow
     30    * @param path The path to append the arrow to.
     31     * @param point The point to paint the tip at
     32     * @param fromDirection The direction the line is comming from.
     33     */
     34    public void paintArrowAt(MapPath2D path, MapViewPoint point, MapViewPoint fromDirection) {
     35        double x = point.getInViewX();
     36        double y = point.getInViewY();
     37        double dx = fromDirection.getInViewX() - x;
     38        double dy = fromDirection.getInViewY() - y;
     39        double norm = Math.sqrt(dx * dx + dy * dy);
     40        if (norm > 1e-10) {
     41            dx *= length / norm;
     42            dy *= length / norm;
     43            path.moveTo(x + dx * cos + dy * sin, y + dx * -sin + dy * cos);
     44            if (!Utils.equalsEpsilon(cos, 0)) {
     45                path.lineTo(point);
     46            }
     47            path.lineTo(x + dx * cos + dy * -sin, y + dx * sin + dy * cos);
     48        }
     49    }
     50}
  • src/org/openstreetmap/josm/data/osm/visitor/paint/LineClip.java

    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 b import static java.awt.geom.Rectangle2D.OUT_LEFT;  
    66import static java.awt.geom.Rectangle2D.OUT_RIGHT;
    77import static java.awt.geom.Rectangle2D.OUT_TOP;
    88
    9 import java.awt.Point;
    109import java.awt.Rectangle;
     10import java.awt.geom.Point2D;
     11import java.awt.geom.Rectangle2D;
    1112
    1213/**
    1314 * Computes the part of a line that is visible in a given rectangle.
    1415 * Using int leads to overflow, so we need long int.
    1516 */
    1617public class LineClip {
    17     private Point p1, p2;
    18     private final Rectangle clipBounds;
     18    private Point2D p1, p2;
     19    private final Rectangle2D clipBounds;
    1920
    2021    /**
    2122     * Constructs a new {@code LineClip}.
    public class LineClip {  
    2324     * @param p2 end point of the clipped line
    2425     * @param clipBounds Clip bounds
    2526     */
    26     public LineClip(Point p1, Point p2, Rectangle clipBounds) {
     27    public LineClip(Point2D p1, Point2D p2, Rectangle2D clipBounds) {
    2728        this.p1 = p1;
    2829        this.p2 = p2;
    2930        this.clipBounds = clipBounds;
    public class LineClip {  
    3738        if (clipBounds == null) {
    3839            return false;
    3940        }
    40         return cohenSutherland(p1.x, p1.y, p2.x, p2.y, clipBounds.x, clipBounds.y,
    41                 (long) clipBounds.x + clipBounds.width,
    42                 (long) clipBounds.y + clipBounds.height);
     41        return cohenSutherland(p1.getX(), p1.getY(), p2.getX(), p2.getY(), clipBounds.getMinX(), clipBounds.getMinY(),
     42                clipBounds.getMaxX(), clipBounds.getMaxY());
    4343    }
    4444
    4545    /**
    4646     * @return start point of the clipped line
    4747     */
    48     public Point getP1() {
     48    public Point2D getP1() {
    4949        return p1;
    5050    }
    5151
    5252    /**
    5353     * @return end point of the clipped line
    5454     */
    55     public Point getP2() {
     55    public Point2D getP2() {
    5656        return p2;
    5757    }
    5858
    public class LineClip {  
    6969     * @param ymax maximal Y coordinate
    7070     * @return true, if line is visible in the given clip region
    7171     */
    72     private boolean cohenSutherland(long x1, long y1, long x2, long y2, long xmin, long ymin, long xmax, long ymax) {
     72    private boolean cohenSutherland(double x1, double y1, double x2, double y2, double xmin, double ymin, double xmax, double ymax) {
    7373        int outcode0, outcode1, outcodeOut;
    7474        boolean accept = false;
    7575        boolean done = false;
    public class LineClip {  
    8484            } else if ((outcode0 & outcode1) > 0) {
    8585                done = true;
    8686            } else {
    87                 long x = 0;
    88                 long y = 0;
     87                double x = 0;
     88                double y = 0;
    8989                outcodeOut = outcode0 != 0 ? outcode0 : outcode1;
    9090                if ((outcodeOut & OUT_TOP) != 0) {
    9191                    x = x1 + (x2 - x1) * (ymax - y1)/(y2 - y1);
    public class LineClip {  
    114114        while (!done);
    115115
    116116        if (accept) {
    117             p1 = new Point((int) x1, (int) y1);
    118             p2 = new Point((int) x2, (int) y2);
     117            p1 = new Point2D.Double(x1, y1);
     118            p2 = new Point2D.Double(x2, y2);
    119119            return true;
    120120        }
    121121        return false;
    public class LineClip {  
    132132     * @param ymax maximal Y coordinate
    133133     * @return outcode
    134134     */
    135     private static int computeOutCode(long x, long y, long xmin, long ymin, long xmax, long ymax) {
     135    private static int computeOutCode(double x, double y, double xmin, double ymin, double xmax, double ymax) {
    136136        int code = 0;
    137         if (y > ymax) {
     137        // ignore rounding errors.
     138        if (y > ymax + 1e-10) {
    138139            code |= OUT_TOP;
    139         } else if (y < ymin) {
     140        } else if (y < ymin - 1e-10) {
    140141            code |= OUT_BOTTOM;
    141142        }
    142         if (x > xmax) {
     143        if (x > xmax + 1e-10) {
    143144            code |= OUT_RIGHT;
    144         } else if (x < xmin) {
     145        } else if (x < xmin - 1e-10) {
    145146            code |= OUT_LEFT;
    146147        }
    147148        return code;
  • new file src/org/openstreetmap/josm/data/osm/visitor/paint/MapPath2D.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.osm.visitor.paint;
     3
     4import java.awt.geom.Path2D;
     5
     6import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
     7
     8/**
     9 * An extension of {@link Path2D} with special methods for map positions.
     10 * @author Michael Zangl
     11 * @since xxx
     12 */
     13public class MapPath2D extends Path2D.Double {
     14    /**
     15     * Create a new, empty path.
     16     */
     17    public MapPath2D() {
     18        // no default definitions
     19    }
     20
     21    /**
     22     * Move the path to the view position of given point
     23     * @param p The point
     24     */
     25    public void moveTo(MapViewPoint p) {
     26        moveTo(p.getInViewX(), p.getInViewY());
     27    }
     28
     29    /**
     30     * Draw a line to the view position of given point
     31     * @param p The point
     32     */
     33    public void lineTo(MapViewPoint p) {
     34        lineTo(p.getInViewX(), p.getInViewY());
     35    }
     36}
  • src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java

    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 3bef3af..68b0ed9 100644
    a b import java.awt.FontMetrics;  
    1111import java.awt.Graphics2D;
    1212import java.awt.Image;
    1313import java.awt.Point;
    14 import java.awt.Polygon;
    1514import java.awt.Rectangle;
    1615import java.awt.RenderingHints;
    1716import java.awt.Shape;
    import java.awt.geom.GeneralPath;  
    2524import java.awt.geom.Path2D;
    2625import java.awt.geom.Point2D;
    2726import java.awt.geom.Rectangle2D;
     27import java.awt.geom.RoundRectangle2D;
    2828import java.util.ArrayList;
    2929import java.util.Collection;
    3030import java.util.Collections;
     31import java.util.Comparator;
    3132import java.util.HashMap;
    3233import java.util.Iterator;
    3334import java.util.List;
    import java.util.NoSuchElementException;  
    3637import java.util.concurrent.ForkJoinPool;
    3738import java.util.concurrent.ForkJoinTask;
    3839import java.util.concurrent.RecursiveTask;
     40import java.util.stream.Collectors;
    3941
    4042import javax.swing.AbstractButton;
    4143import javax.swing.FocusManager;
    import org.openstreetmap.josm.data.osm.visitor.Visitor;  
    5759import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
    5860import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
    5961import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
     62import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    6063import org.openstreetmap.josm.gui.NavigatableComponent;
    6164import org.openstreetmap.josm.gui.mappaint.ElemStyles;
    6265import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
    import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.Horizonta  
    6972import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.VerticalTextAlignment;
    7073import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage;
    7174import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement;
    72 import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement.Symbol;
    7375import org.openstreetmap.josm.gui.mappaint.styleelement.RepeatImageElement.LineImageAlignment;
    7476import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
     77import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol;
    7578import org.openstreetmap.josm.gui.mappaint.styleelement.TextLabel;
    7679import org.openstreetmap.josm.tools.CompositeList;
    7780import org.openstreetmap.josm.tools.Geometry;
    public class StyledMapRenderer extends AbstractMapRenderer {  
    9699     * There is no intention, to handle consecutive duplicate Nodes in a
    97100     * perfect way, but it should not throw an exception.
    98101     */
    99     private class OffsetIterator implements Iterator<Point> {
     102    private class OffsetIterator implements Iterator<MapViewPoint> {
    100103
    101104        private final List<Node> nodes;
    102105        private final double offset;
    103106        private int idx;
    104107
    105         private Point prev;
     108        private MapViewPoint prev;
    106109        /* 'prev0' is a point that has distance 'offset' from 'prev' and the
    107110         * line from 'prev' to 'prev0' is perpendicular to the way segment from
    108111         * 'prev' to the next point.
    109112         */
    110         private int xPrev0, yPrev0;
     113        private double xPrev0;
     114        private double yPrev0;
    111115
    112116        OffsetIterator(List<Node> nodes, double offset) {
    113117            this.nodes = nodes;
    public class StyledMapRenderer extends AbstractMapRenderer {  
    121125        }
    122126
    123127        @Override
    124         public Point next() {
     128        public MapViewPoint next() {
    125129            if (!hasNext())
    126130                throw new NoSuchElementException();
    127131
    128             if (Math.abs(offset) < 0.1d)
    129                 return nc.getPoint(nodes.get(idx++));
     132            MapViewPoint current = getForIndex(idx);
    130133
    131             Point current = nc.getPoint(nodes.get(idx));
     134            if (Math.abs(offset) < 0.1d) {
     135                idx++;
     136                return current;
     137            }
    132138
    133139            if (idx == nodes.size() - 1) {
    134140                ++idx;
    135141                if (prev != null) {
    136                     return new Point(xPrev0 + current.x - prev.x, yPrev0 + current.y - prev.y);
     142                    return mapState.getForView(xPrev0 + current.getInViewX() - prev.getInViewX(), yPrev0 + current.getInViewY() - prev.getInViewY());
    137143                } else {
    138144                    return current;
    139145                }
    140146            }
    141147
    142             Point next = nc.getPoint(nodes.get(idx+1));
     148            MapViewPoint next = getForIndex(idx + 1);
    143149
    144             int dxNext = next.x - current.x;
    145             int dyNext = next.y - current.y;
    146             double lenNext = Math.sqrt((double) dxNext*dxNext + (double) dyNext*dyNext);
     150            double dxNext = next.getInViewX() - current.getInViewX();
     151            double dyNext = next.getInViewY() - current.getInViewY();
     152            double lenNext = Math.sqrt(dxNext*dxNext + dyNext*dyNext);
    147153
    148             if (lenNext == 0) {
     154            if (lenNext < 1e-3) {
    149155                lenNext = 1; // value does not matter, because dy_next and dx_next is 0
    150156            }
    151157
    152             int xCurrent0 = current.x + (int) Math.round(offset * dyNext / lenNext);
    153             int yCurrent0 = current.y - (int) Math.round(offset * dxNext / lenNext);
     158            double xCurrent0 = current.getInViewX() + offset * dyNext / lenNext;
     159            double yCurrent0 = current.getInViewY() - offset * dxNext / lenNext;
    154160
    155161            if (idx == 0) {
    156162                ++idx;
    157163                prev = current;
    158164                xPrev0 = xCurrent0;
    159165                yPrev0 = yCurrent0;
    160                 return new Point(xCurrent0, yCurrent0);
     166                return mapState.getForView(xCurrent0, yCurrent0);
    161167            } else {
    162                 int dxPrev = current.x - prev.x;
    163                 int dyPrev = current.y - prev.y;
     168                double dxPrev = current.getInViewX() - prev.getInViewX();
     169                double dyPrev = current.getInViewY() - prev.getInViewY();
    164170
    165171                // determine intersection of the lines parallel to the two segments
    166                 int det = dxNext*dyPrev - dxPrev*dyNext;
     172                double det = dxNext*dyPrev - dxPrev*dyNext;
    167173
    168                 if (det == 0) {
     174                if (Utils.equalsEpsilon(det, 0)) {
    169175                    ++idx;
    170176                    prev = current;
    171177                    xPrev0 = xCurrent0;
    172178                    yPrev0 = yCurrent0;
    173                     return new Point(xCurrent0, yCurrent0);
     179                    return mapState.getForView(xCurrent0, yCurrent0);
    174180                }
    175181
    176                 int m = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0);
     182                double m = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0);
    177183
    178                 int cx = xPrev0 + (int) Math.round((double) m * dxPrev / det);
    179                 int cy = yPrev0 + (int) Math.round((double) m * dyPrev / det);
     184                double cx = xPrev0 + m * dxPrev / det;
     185                double cy = yPrev0 + m * dyPrev / det;
    180186                ++idx;
    181187                prev = current;
    182188                xPrev0 = xCurrent0;
    183189                yPrev0 = yCurrent0;
    184                 return new Point(cx, cy);
     190                return mapState.getForView(cx, cy);
    185191            }
    186192        }
    187193
     194        private MapViewPoint getForIndex(int i) {
     195            return mapState.getPointFor(nodes.get(i));
     196        }
     197
    188198        @Override
    189199        public void remove() {
    190200            throw new UnsupportedOperationException();
    public class StyledMapRenderer extends AbstractMapRenderer {  
    392402        }
    393403    }
    394404
    395     private static Polygon buildPolygon(Point center, int radius, int sides) {
    396         return buildPolygon(center, radius, sides, 0.0);
    397     }
    398 
    399     private static Polygon buildPolygon(Point center, int radius, int sides, double rotation) {
    400         Polygon polygon = new Polygon();
    401         for (int i = 0; i < sides; i++) {
    402             double angle = ((2 * Math.PI / sides) * i) - rotation;
    403             int x = (int) Math.round(center.x + radius * Math.cos(angle));
    404             int y = (int) Math.round(center.y + radius * Math.sin(angle));
    405             polygon.addPoint(x, y);
    406         }
    407         return polygon;
    408     }
    409 
    410     private void displaySegments(GeneralPath path, GeneralPath orientationArrows, GeneralPath onewayArrows, GeneralPath onewayArrowsCasing,
     405    private void displaySegments(Path2D path, Path2D orientationArrows, Path2D onewayArrows, Path2D onewayArrowsCasing,
    411406            Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) {
    412407        g.setColor(isInactiveMode ? inactiveColor : color);
    413408        if (useStrokes) {
    public class StyledMapRenderer extends AbstractMapRenderer {  
    505500    protected void drawArea(OsmPrimitive osm, Path2D.Double path, Color color,
    506501            MapImage fillImage, Float extent, Path2D.Double pfClip, boolean disabled, TextLabel text) {
    507502
    508         Shape area = path.createTransformedShape(nc.getAffineTransform());
     503        Shape area = path.createTransformedShape(mapState.getAffineTransform());
    509504
    510505        if (!isOutlineOnly) {
    511506            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    public class StyledMapRenderer extends AbstractMapRenderer {  
    520515                    Shape oldClip = g.getClip();
    521516                    Shape clip = area;
    522517                    if (pfClip != null) {
    523                         clip = pfClip.createTransformedShape(nc.getAffineTransform());
     518                        clip = pfClip.createTransformedShape(mapState.getAffineTransform());
    524519                    }
    525520                    g.clip(clip);
    526521                    g.setStroke(new BasicStroke(2 * extent, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 4));
    public class StyledMapRenderer extends AbstractMapRenderer {  
    543538                    g.clip(stroke.createStrokedShape(area));
    544539                    Shape fill = area;
    545540                    if (pfClip != null) {
    546                         fill = pfClip.createTransformedShape(nc.getAffineTransform());
     541                        fill = pfClip.createTransformedShape(mapState.getAffineTransform());
    547542                    }
    548543                    g.fill(fill);
    549544                    g.setClip(oldClip);
    public class StyledMapRenderer extends AbstractMapRenderer {  
    715710        if (!isShowNames() || bs == null)
    716711            return;
    717712
    718         Point p = nc.getPoint(n);
     713        MapViewPoint p = mapState.getPointFor(n);
    719714        TextLabel text = bs.text;
    720715        String s = text.labelCompositionStrategy.compose(n);
    721716        if (s == null) return;
    public class StyledMapRenderer extends AbstractMapRenderer {  
    723718        Font defaultFont = g.getFont();
    724719        g.setFont(text.font);
    725720
    726         int x = p.x + text.xOffset;
    727         int y = p.y + text.yOffset;
     721        int x = (int) (p.getInViewX() + text.xOffset);
     722        int y = (int) (p.getInViewY() + text.yOffset);
    728723        /**
    729724         *
    730725         *       left-above __center-above___ right-above
    public class StyledMapRenderer extends AbstractMapRenderer {  
    786781        final double repeat = imgWidth + spacing;
    787782        final int imgHeight = pattern.getHeight();
    788783
    789         Point lastP = null;
    790784        double currentWayLength = phase % repeat;
    791785        if (currentWayLength < 0) {
    792786            currentWayLength += repeat;
    public class StyledMapRenderer extends AbstractMapRenderer {  
    810804                throw new AssertionError();
    811805        }
    812806
     807        MapViewPoint lastP = null;
    813808        OffsetIterator it = new OffsetIterator(way.getNodes(), offset);
    814809        while (it.hasNext()) {
    815             Point thisP = it.next();
     810            MapViewPoint thisP = it.next();
    816811
    817812            if (lastP != null) {
    818                 final double segmentLength = thisP.distance(lastP);
     813                final double segmentLength = thisP.distanceToInView(lastP);
    819814
    820                 final double dx = (double) thisP.x - lastP.x;
    821                 final double dy = (double) thisP.y - lastP.y;
     815                final double dx = thisP.getInViewX() - lastP.getInViewX();
     816                final double dy = thisP.getInViewY() - lastP.getInViewY();
    822817
    823818                // pos is the position from the beginning of the current segment
    824819                // where an image should be painted
    825820                double pos = repeat - (currentWayLength % repeat);
    826821
    827822                AffineTransform saveTransform = g.getTransform();
    828                 g.translate(lastP.x, lastP.y);
     823                g.translate(lastP.getInViewX(), lastP.getInViewY());
    829824                g.rotate(Math.atan2(dy, dx));
    830825
    831826                // draw the rest of the image from the last segment in case it
    public class StyledMapRenderer extends AbstractMapRenderer {  
    866861        if (size <= 0 && !n.isHighlighted())
    867862            return;
    868863
    869         Point p = nc.getPoint(n);
     864        MapViewPoint p = mapState.getPointFor(n);
    870865
    871866        if (n.isHighlighted()) {
    872             drawPointHighlight(p, size);
     867            drawPointHighlight(p.getInView(), size);
    873868        }
    874869
    875         if (size > 1) {
    876             if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
     870        if (size > 1 && p.isInView()) {
    877871            int radius = size / 2;
    878872
    879873            if (isInactiveMode || n.isDisabled()) {
    public class StyledMapRenderer extends AbstractMapRenderer {  
    881875            } else {
    882876                g.setColor(color);
    883877            }
     878            Rectangle2D rect = new Rectangle2D.Double(p.getInViewX()-radius-1, p.getInViewY()-radius-1, size + 1, size + 1);
    884879            if (fill) {
    885                 g.fillRect(p.x-radius-1, p.y-radius-1, size + 1, size + 1);
     880                g.fill(rect);
    886881            } else {
    887                 g.drawRect(p.x-radius-1, p.y-radius-1, size, size);
     882                g.draw(rect);
    888883            }
    889884        }
    890885    }
    891886
     887    /**
     888     * Draw the icon for a given node.
     889     * @param n The node
     890     * @param img The icon to draw at the node position
     891     * @param disabled
     892     * @param selected
     893     * @param member
     894     * @param theta
     895     */
    892896    public void drawNodeIcon(Node n, MapImage img, boolean disabled, boolean selected, boolean member, double theta) {
    893         Point p = nc.getPoint(n);
     897        MapViewPoint p = mapState.getPointFor(n);
    894898
    895         final int w = img.getWidth(), h = img.getHeight();
     899        int w = img.getWidth();
     900        int h = img.getHeight();
    896901        if (n.isHighlighted()) {
    897             drawPointHighlight(p, Math.max(w, h));
     902            drawPointHighlight(p.getInView(), Math.max(w, h));
    898903        }
    899904
    900905        float alpha = img.getAlphaFloat();
    901906
     907        Graphics2D temporaryGraphics = (Graphics2D) g.create();
    902908        if (!Utils.equalsEpsilon(alpha, 1f)) {
    903             g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
     909            temporaryGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
    904910        }
    905         g.rotate(theta, p.x, p.y);
    906         g.drawImage(img.getImage(disabled), p.x - w/2 + img.offsetX, p.y - h/2 + img.offsetY, nc);
    907         g.rotate(-theta, p.x, p.y);
    908         g.setPaintMode();
     911
     912        double x = p.getInViewX();
     913        double y = p.getInViewY();
     914        temporaryGraphics.translate(-x, -y);
     915        temporaryGraphics.rotate(theta);
     916        temporaryGraphics.drawImage(img.getImage(disabled), w/2 + img.offsetX, h/2 + img.offsetY, nc);
    909917        if (selected || member) {
    910918            Color color;
    911919            if (disabled) {
    public class StyledMapRenderer extends AbstractMapRenderer {  
    916924                color = relationSelectedColor;
    917925            }
    918926            g.setColor(color);
    919             g.drawRect(p.x - w/2 + img.offsetX - 2, p.y - h/2 + img.offsetY - 2, w + 4, h + 4);
     927            g.draw(new Rectangle2D.Double(x - w/2 + img.offsetX - 2, y - h/2 + img.offsetY - 2, w + 4, h + 4));
    920928        }
    921929    }
    922930
     931    /**
     932     * Draw the symbol and possibly a highlight marking on a given node.
     933     * @param n The position to draw the symbol on
     934     * @param s The symbol to draw
     935     * @param fillColor The color to fill the symbol with
     936     * @param strokeColor The color to use for the outer corner of the symbol
     937     */
    923938    public void drawNodeSymbol(Node n, Symbol s, Color fillColor, Color strokeColor) {
    924         Point p = nc.getPoint(n);
    925         int radius = s.size / 2;
     939        MapViewPoint p = mapState.getPointFor(n);
    926940
    927941        if (n.isHighlighted()) {
    928             drawPointHighlight(p, s.size);
     942            drawPointHighlight(p.getInView(), s.size);
    929943        }
    930944
    931         if (fillColor != null) {
    932             g.setColor(fillColor);
    933             switch (s.symbol) {
    934             case SQUARE:
    935                 g.fillRect(p.x - radius, p.y - radius, s.size, s.size);
    936                 break;
    937             case CIRCLE:
    938                 g.fillOval(p.x - radius, p.y - radius, s.size, s.size);
    939                 break;
    940             case TRIANGLE:
    941                 g.fillPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
    942                 break;
    943             case PENTAGON:
    944                 g.fillPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
    945                 break;
    946             case HEXAGON:
    947                 g.fillPolygon(buildPolygon(p, radius, 6));
    948                 break;
    949             case HEPTAGON:
    950                 g.fillPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
    951                 break;
    952             case OCTAGON:
    953                 g.fillPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
    954                 break;
    955             case NONAGON:
    956                 g.fillPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
    957                 break;
    958             case DECAGON:
    959                 g.fillPolygon(buildPolygon(p, radius, 10));
    960                 break;
    961             default:
    962                 throw new AssertionError();
     945        if (fillColor != null || strokeColor != null) {
     946            Shape shape = s.buildShapeAround(p.getInViewX(), p.getInViewY());
     947
     948            if (fillColor != null) {
     949                g.setColor(fillColor);
     950                g.fill(shape);
    963951            }
    964         }
    965         if (s.stroke != null) {
    966             g.setStroke(s.stroke);
    967             g.setColor(strokeColor);
    968             switch (s.symbol) {
    969             case SQUARE:
    970                 g.drawRect(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
    971                 break;
    972             case CIRCLE:
    973                 g.drawOval(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
    974                 break;
    975             case TRIANGLE:
    976                 g.drawPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
    977                 break;
    978             case PENTAGON:
    979                 g.drawPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
    980                 break;
    981             case HEXAGON:
    982                 g.drawPolygon(buildPolygon(p, radius, 6));
    983                 break;
    984             case HEPTAGON:
    985                 g.drawPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
    986                 break;
    987             case OCTAGON:
    988                 g.drawPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
    989                 break;
    990             case NONAGON:
    991                 g.drawPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
    992                 break;
    993             case DECAGON:
    994                 g.drawPolygon(buildPolygon(p, radius, 10));
    995                 break;
    996             default:
    997                 throw new AssertionError();
     952            if (s.stroke != null) {
     953                g.setStroke(s.stroke);
     954                g.setColor(strokeColor);
     955                g.draw(shape);
     956                g.setStroke(new BasicStroke());
    998957            }
    999             g.setStroke(new BasicStroke());
    1000958        }
    1001959    }
    1002960
    public class StyledMapRenderer extends AbstractMapRenderer {  
    1010968     * @param clr The color to use for drawing the text.
    1011969     */
    1012970    public void drawOrderNumber(Node n1, Node n2, int orderNumber, Color clr) {
    1013         Point p1 = nc.getPoint(n1);
    1014         Point p2 = nc.getPoint(n2);
     971        MapViewPoint p1 = mapState.getPointFor(n1);
     972        MapViewPoint p2 = mapState.getPointFor(n2);
    1015973        drawOrderNumber(p1, p2, orderNumber, clr);
    1016974    }
    1017975
    public class StyledMapRenderer extends AbstractMapRenderer {  
    1021979     * @param path path to draw
    1022980     * @param line line style
    1023981     */
    1024     private void drawPathHighlight(GeneralPath path, BasicStroke line) {
     982    private void drawPathHighlight(Path2D path, BasicStroke line) {
    1025983        if (path == null)
    1026984            return;
    1027985        g.setColor(highlightColorTransparent);
    public class StyledMapRenderer extends AbstractMapRenderer {  
    1040998     * @param p point
    1041999     * @param size highlight size
    10421000     */
    1043     private void drawPointHighlight(Point p, int size) {
     1001    private void drawPointHighlight(Point2D p, int size) {
    10441002        g.setColor(highlightColorTransparent);
    10451003        int s = size + highlightPointRadius;
    10461004        if (useWiderHighlight) s += widerHighlight;
    10471005        while (s >= size) {
    10481006            int r = (int) Math.floor(s/2d);
    1049             g.fillRoundRect(p.x-r, p.y-r, s, s, r, r);
     1007            g.fill(new RoundRectangle2D.Double(p.getX()-r, p.getY()-r, s, s, r, r));
    10501008            s -= highlightStep;
    10511009        }
    10521010    }
    public class StyledMapRenderer extends AbstractMapRenderer {  
    12351193    }
    12361194
    12371195    /**
     1196     * A half segment that can be used to place text on it. Used in the drawTextOnPath algorithm.
     1197     * @author Michael Zangl
     1198     * @since xxx
     1199     */
     1200    private static class HalfSegment {
     1201        /**
     1202         * start point of half segment (as length along the way)
     1203         */
     1204        final double start;
     1205        /**
     1206         * end point of half segment (as length along the way)
     1207         */
     1208        final double end;
     1209        /**
     1210         * quality factor (off screen / partly on screen / fully on screen)
     1211         */
     1212        final double quality;
     1213
     1214        /**
     1215         * Create a new half segment
     1216         * @param start The start along the way
     1217         * @param end The end of the segment
     1218         * @param quality A quality factor.
     1219         */
     1220        public HalfSegment(double start, double end, double quality) {
     1221            super();
     1222            this.start = start;
     1223            this.end = end;
     1224            this.quality = quality;
     1225        }
     1226
     1227        @Override
     1228        public String toString() {
     1229            return "HalfSegment [start=" + start + ", end=" + end + ", quality=" + quality + "]";
     1230        }
     1231    }
     1232
     1233    /**
    12381234     * Draws a text along a given way.
    12391235     * @param way The way to draw the text on.
    12401236     * @param text The text definition (font/.../text content) to draw.
    public class StyledMapRenderer extends AbstractMapRenderer {  
    12511247
    12521248        Rectangle bounds = g.getClipBounds();
    12531249
    1254         Polygon poly = new Polygon();
    1255         Point lastPoint = null;
    1256         Iterator<Node> it = way.getNodes().iterator();
    1257         double pathLength = 0;
    1258         long dx, dy;
     1250        List<MapViewPoint> points = way.getNodes().stream().map(mapState::getPointFor).collect(Collectors.toList());
    12591251
    12601252        // 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)
    1261         List<Double> longHalfSegmentStart = new ArrayList<>(); // start point of half segment (as length along the way)
    1262         List<Double> longHalfSegmentEnd = new ArrayList<>(); // end point of half segment (as length along the way)
    1263         List<Double> longHalfsegmentQuality = new ArrayList<>(); // quality factor (off screen / partly on screen / fully on screen)
    1264 
    1265         while (it.hasNext()) {
    1266             Node n = it.next();
    1267             Point p = nc.getPoint(n);
    1268             poly.addPoint(p.x, p.y);
     1253        List<HalfSegment> longHalfSegment = new ArrayList<>();
    12691254
    1270             if (lastPoint != null) {
    1271                 dx = (long) p.x - lastPoint.x;
    1272                 dy = (long) p.y - lastPoint.y;
    1273                 double segmentLength = Math.sqrt(dx*dx + dy*dy);
    1274                 if (segmentLength > 2*(rec.getWidth()+4)) {
    1275                     Point center = new Point((lastPoint.x + p.x)/2, (lastPoint.y + p.y)/2);
    1276                     double q = 0;
    1277                     if (bounds != null) {
    1278                         if (bounds.contains(lastPoint) && bounds.contains(center)) {
    1279                             q = 2;
    1280                         } else if (bounds.contains(lastPoint) || bounds.contains(center)) {
    1281                             q = 1;
    1282                         }
    1283                     }
    1284                     longHalfSegmentStart.add(pathLength);
    1285                     longHalfSegmentEnd.add(pathLength + segmentLength / 2);
    1286                     longHalfsegmentQuality.add(q);
    1287 
    1288                     q = 0;
    1289                     if (bounds != null) {
    1290                         if (bounds.contains(center) && bounds.contains(p)) {
    1291                             q = 2;
    1292                         } else if (bounds.contains(center) || bounds.contains(p)) {
    1293                             q = 1;
    1294                         }
    1295                     }
    1296                     longHalfSegmentStart.add(pathLength + segmentLength / 2);
    1297                     longHalfSegmentEnd.add(pathLength + segmentLength);
    1298                     longHalfsegmentQuality.add(q);
    1299                 }
    1300                 pathLength += segmentLength;
    1301             }
    1302             lastPoint = p;
    1303         }
     1255        double pathLength = computePath(2 * (rec.getWidth() + 4), bounds, points, longHalfSegment);
    13041256
    13051257        if (rec.getWidth() > pathLength)
    13061258            return;
    13071259
    13081260        double t1, t2;
    13091261
    1310         if (!longHalfSegmentStart.isEmpty()) {
    1311             if (way.getNodesCount() == 2) {
    1312                 // For 2 node ways, the two half segments are exactly the same size and distance from the center.
    1313                 // Prefer the first one for consistency.
    1314                 longHalfsegmentQuality.set(0, longHalfsegmentQuality.get(0) + 0.5);
    1315             }
    1316 
    1317             // find the long half segment that is closest to the center of the way
    1318             // candidates with higher quality value are preferred
    1319             double bestStart = Double.NaN;
    1320             double bestEnd = Double.NaN;
    1321             double bestDistanceToCenter = Double.MAX_VALUE;
    1322             double bestQuality = -1;
    1323             for (int i = 0; i < longHalfSegmentStart.size(); i++) {
    1324                 double start = longHalfSegmentStart.get(i);
    1325                 double end = longHalfSegmentEnd.get(i);
    1326                 double dist = Math.abs(0.5 * (end + start) - 0.5 * pathLength);
    1327                 if (longHalfsegmentQuality.get(i) > bestQuality
    1328                         || (dist < bestDistanceToCenter && Utils.equalsEpsilon(longHalfsegmentQuality.get(i), bestQuality))) {
    1329                     bestStart = start;
    1330                     bestEnd = end;
    1331                     bestDistanceToCenter = dist;
    1332                     bestQuality = longHalfsegmentQuality.get(i);
    1333                 }
    1334             }
    1335             double remaining = bestEnd - bestStart - rec.getWidth(); // total space left and right from the text
     1262        if (!longHalfSegment.isEmpty()) {
     1263            // find the segment with the best quality. If there are several with best quality, the one close to the center is prefered.
     1264            HalfSegment best = longHalfSegment.stream().max(
     1265                    Comparator.comparingDouble( segment ->
     1266                        segment.quality - 1e-5 * Math.abs(0.5 * (segment.end + segment.start) - 0.5 * pathLength)
     1267                    )).get();
     1268            double remaining = best.end - best.start - rec.getWidth(); // total space left and right from the text
    13361269            // The space left and right of the text should be distributed 20% - 80% (towards the center),
    13371270            // but the smaller space should not be less than 7 px.
    13381271            // However, if the total remaining space is less than 14 px, then distribute it evenly.
    13391272            double smallerSpace = Math.min(Math.max(0.2 * remaining, 7), 0.5 * remaining);
    1340             if ((bestEnd + bestStart)/2 < pathLength/2) {
    1341                 t2 = bestEnd - smallerSpace;
     1273            if ((best.end + best.start)/2 < pathLength/2) {
     1274                t2 = best.end - smallerSpace;
    13421275                t1 = t2 - rec.getWidth();
    13431276            } else {
    1344                 t1 = bestStart + smallerSpace;
     1277                t1 = best.start + smallerSpace;
    13451278                t2 = t1 + rec.getWidth();
    13461279            }
    13471280        } else {
    public class StyledMapRenderer extends AbstractMapRenderer {  
    13521285        t1 /= pathLength;
    13531286        t2 /= pathLength;
    13541287
    1355         double[] p1 = pointAt(t1, poly, pathLength);
    1356         double[] p2 = pointAt(t2, poly, pathLength);
     1288        double[] p1 = pointAt(t1, points, pathLength);
     1289        double[] p2 = pointAt(t2, points, pathLength);
    13571290
    13581291        if (p1 == null || p2 == null)
    13591292            return;
    public class StyledMapRenderer extends AbstractMapRenderer {  
    13811314            for (int i = 0; i < gv.getNumGlyphs(); ++i) {
    13821315                Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D();
    13831316                double t = tStart + offsetSign * (gvOffset + rect.getX() + rect.getWidth()/2) / pathLength;
    1384                 double[] p = pointAt(t, poly, pathLength);
     1317                double[] p = pointAt(t, points, pathLength);
    13851318                if (p != null) {
    13861319                    AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]);
    13871320                    trfm.rotate(p[2]+angleOffset);
    public class StyledMapRenderer extends AbstractMapRenderer {  
    14011334        }
    14021335    }
    14031336
     1337    private double computePath(double minSegmentLength, Rectangle bounds, List<MapViewPoint> points,
     1338            List<HalfSegment> longHalfSegment) {
     1339        MapViewPoint lastPoint = points.get(0);
     1340        double pathLength = 0;
     1341        for (MapViewPoint p : points.subList(1, points.size())) {
     1342            double segmentLength = p.distanceToInView(lastPoint);
     1343            if (segmentLength > minSegmentLength) {
     1344                Point2D center = new Point2D.Double((lastPoint.getInViewX() + p.getInViewX())/2, (lastPoint.getInViewY() + p.getInViewY())/2);
     1345                double q = computeQuality(bounds, lastPoint, center);
     1346                // prefer the first one for quality equality.
     1347                longHalfSegment.add(new HalfSegment(pathLength, pathLength + segmentLength / 2, q));
     1348
     1349                q = 0;
     1350                if (bounds != null) {
     1351                    if (bounds.contains(center) && bounds.contains(p.getInView())) {
     1352                        q = 2;
     1353                    } else if (bounds.contains(center) || bounds.contains(p.getInView())) {
     1354                        q = 1;
     1355                    }
     1356                }
     1357                longHalfSegment.add(new HalfSegment(pathLength + segmentLength / 2, pathLength + segmentLength, q));
     1358            }
     1359            pathLength += segmentLength;
     1360            lastPoint = p;
     1361        }
     1362        return pathLength;
     1363    }
     1364
     1365    private static double computeQuality(Rectangle bounds, MapViewPoint p1, Point2D p2) {
     1366        double q = 0;
     1367        if (bounds != null) {
     1368            if (bounds.contains(p1.getInView())) {
     1369                q += 1;
     1370            }
     1371            if (bounds.contains(p2)) {
     1372                q += 1;
     1373            }
     1374        }
     1375        return q;
     1376    }
     1377
    14041378    /**
    14051379     * draw way. This method allows for two draw styles (line using color, dashes using dashedColor) to be passed.
    14061380     * @param way The way to draw
    public class StyledMapRenderer extends AbstractMapRenderer {  
    14201394            boolean showOrientation, boolean showHeadArrowOnly,
    14211395            boolean showOneway, boolean onewayReversed) {
    14221396
    1423         GeneralPath path = new GeneralPath();
    1424         GeneralPath orientationArrows = showOrientation ? new GeneralPath() : null;
    1425         GeneralPath onewayArrows = showOneway ? new GeneralPath() : null;
    1426         GeneralPath onewayArrowsCasing = showOneway ? new GeneralPath() : null;
     1397        MapPath2D path = new MapPath2D();
     1398        MapPath2D orientationArrows = showOrientation ? new MapPath2D() : null;
     1399        MapPath2D onewayArrows = showOneway ? new MapPath2D() : null;
     1400        MapPath2D onewayArrowsCasing = showOneway ? new MapPath2D() : null;
    14271401        Rectangle bounds = g.getClipBounds();
    14281402        if (bounds != null) {
    14291403            // avoid arrow heads at the border
    14301404            bounds.grow(100, 100);
    14311405        }
    14321406
    1433         double wayLength = 0;
    1434         Point lastPoint = null;
    14351407        boolean initialMoveToNeeded = true;
    14361408        List<Node> wayNodes = way.getNodes();
    14371409        if (wayNodes.size() < 2) return;
    public class StyledMapRenderer extends AbstractMapRenderer {  
    14471419                    highlightSegs = new GeneralPath();
    14481420                }
    14491421
    1450                 Point p1 = nc.getPoint(ws.getFirstNode());
    1451                 Point p2 = nc.getPoint(ws.getSecondNode());
    1452                 highlightSegs.moveTo(p1.x, p1.y);
    1453                 highlightSegs.lineTo(p2.x, p2.y);
     1422                Point2D p1 = mapState.getPointFor(ws.getFirstNode()).getInView();
     1423                Point2D p2 = mapState.getPointFor(ws.getSecondNode()).getInView();
     1424                highlightSegs.moveTo(p1.getX(), p1.getY());
     1425                highlightSegs.lineTo(p2.getX(), p2.getY());
    14541426            }
    14551427
    14561428            drawPathHighlight(highlightSegs, line);
    14571429        }
    14581430
    1459         Iterator<Point> it = new OffsetIterator(wayNodes, offset);
     1431        MapViewPoint lastPoint = null;
     1432        double wayLength = 0;
     1433        Iterator<MapViewPoint> it = new OffsetIterator(wayNodes, offset);
    14601434        while (it.hasNext()) {
    1461             Point p = it.next();
     1435            MapViewPoint p = it.next();
    14621436            if (lastPoint != null) {
    1463                 Point p1 = lastPoint;
    1464                 Point p2 = p;
    1465 
    1466                 /**
    1467                  * Do custom clipping to work around openjdk bug. It leads to
    1468                  * drawing artefacts when zooming in a lot. (#4289, #4424)
    1469                  * (Looks like int overflow.)
    1470                  */
    1471                 LineClip clip = new LineClip(p1, p2, bounds);
    1472                 if (clip.execute()) {
    1473                     if (!p1.equals(clip.getP1())) {
    1474                         p1 = clip.getP1();
    1475                         path.moveTo(p1.x, p1.y);
    1476                     } else if (initialMoveToNeeded) {
    1477                         initialMoveToNeeded = false;
    1478                         path.moveTo(p1.x, p1.y);
    1479                     }
    1480                     p2 = clip.getP2();
    1481                     path.lineTo(p2.x, p2.y);
     1437                MapViewPoint p1 = lastPoint;
     1438                MapViewPoint p2 = p;
    14821439
    1483                     /* draw arrow */
    1484                     if (showHeadArrowOnly ? !it.hasNext() : showOrientation) {
    1485                         final double segmentLength = p1.distance(p2);
    1486                         if (segmentLength != 0) {
    1487                             final double l = (10. + line.getLineWidth()) / segmentLength;
    1488 
    1489                             final double sx = l * (p1.x - p2.x);
    1490                             final double sy = l * (p1.y - p2.y);
     1440                if (initialMoveToNeeded) {
     1441                    initialMoveToNeeded = false;
     1442                    path.moveTo(p1);
     1443                }
     1444                path.lineTo(p2);
    14911445
    1492                             orientationArrows.moveTo(p2.x + cosPHI * sx - sinPHI * sy, p2.y + sinPHI * sx + cosPHI * sy);
    1493                             orientationArrows.lineTo(p2.x, p2.y);
    1494                             orientationArrows.lineTo(p2.x + cosPHI * sx + sinPHI * sy, p2.y - sinPHI * sx + cosPHI * sy);
    1495                         }
    1496                     }
    1497                     if (showOneway) {
    1498                         final double segmentLength = p1.distance(p2);
    1499                         if (segmentLength != 0) {
    1500                             final double nx = (p2.x - p1.x) / segmentLength;
    1501                             final double ny = (p2.y - p1.y) / segmentLength;
    1502 
    1503                             final double interval = 60;
    1504                             // distance from p1
    1505                             double dist = interval - (wayLength % interval);
    1506 
    1507                             while (dist < segmentLength) {
    1508                                 for (int i = 0; i < 2; ++i) {
    1509                                     double onewaySize = i == 0 ? 3d : 2d;
    1510                                     GeneralPath onewayPath = i == 0 ? onewayArrowsCasing : onewayArrows;
    1511 
    1512                                     // scale such that border is 1 px
    1513                                     final double fac = -(onewayReversed ? -1 : 1) * onewaySize * (1 + sinPHI) / (sinPHI * cosPHI);
    1514                                     final double sx = nx * fac;
    1515                                     final double sy = ny * fac;
    1516 
    1517                                     // Attach the triangle at the incenter and not at the tip.
    1518                                     // Makes the border even at all sides.
    1519                                     final double x = p1.x + nx * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
    1520                                     final double y = p1.y + ny * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
    1521 
    1522                                     onewayPath.moveTo(x, y);
    1523                                     onewayPath.lineTo(x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy);
    1524                                     onewayPath.lineTo(x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy);
    1525                                     onewayPath.lineTo(x, y);
    1526                                 }
    1527                                 dist += interval;
    1528                             }
     1446                /* draw arrow */
     1447                if (showHeadArrowOnly ? !it.hasNext() : showOrientation) {
     1448                    //TODO: Cache
     1449                    ArrowPaintHelper drawHelper = new ArrowPaintHelper(PHI, 10 + line.getLineWidth());
     1450                    drawHelper.paintArrowAt(orientationArrows, p2, p1);
     1451                }
     1452                if (showOneway) {
     1453                    final double segmentLength = p1.distanceToInView(p2);
     1454                    if (segmentLength != 0) {
     1455                        final double nx = (p2.getInViewX() - p1.getInViewX()) / segmentLength;
     1456                        final double ny = (p2.getInViewY() - p1.getInViewY()) / segmentLength;
     1457
     1458                        final double interval = 60;
     1459                        // distance from p1
     1460                        double dist = interval - (wayLength % interval);
     1461
     1462                        while (dist < segmentLength) {
     1463                            appenOnewayPath(onewayReversed, p1, nx, ny, dist, 3d, onewayArrowsCasing);
     1464                            appenOnewayPath(onewayReversed, p1, nx, ny, dist, 2d, onewayArrows);
     1465                            dist += interval;
    15291466                        }
    1530                         wayLength += segmentLength;
    15311467                    }
     1468                    wayLength += segmentLength;
    15321469                }
    15331470            }
    15341471            lastPoint = p;
    public class StyledMapRenderer extends AbstractMapRenderer {  
    15391476        displaySegments(path, orientationArrows, onewayArrows, onewayArrowsCasing, color, line, dashes, dashedColor);
    15401477    }
    15411478
     1479    private void appenOnewayPath(boolean onewayReversed, MapViewPoint p1, double nx, double ny, double dist, double onewaySize, Path2D onewayPath) {
     1480        // scale such that border is 1 px
     1481        final double fac = -(onewayReversed ? -1 : 1) * onewaySize * (1 + sinPHI) / (sinPHI * cosPHI);
     1482        final double sx = nx * fac;
     1483        final double sy = ny * fac;
     1484
     1485        // Attach the triangle at the incenter and not at the tip.
     1486        // Makes the border even at all sides.
     1487        final double x = p1.getInViewX() + nx * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
     1488        final double y = p1.getInViewY() + ny * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
     1489
     1490        onewayPath.moveTo(x, y);
     1491        onewayPath.lineTo(x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy);
     1492        onewayPath.lineTo(x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy);
     1493        onewayPath.lineTo(x, y);
     1494    }
     1495
    15421496    /**
    15431497     * Gets the "circum". This is the distance on the map in meters that 100 screen pixels represent.
    15441498     * @return The "circum"
    public class StyledMapRenderer extends AbstractMapRenderer {  
    17281682        return null;
    17291683    }
    17301684
     1685    /**
     1686     * Test if the area is visible
     1687     * @param area The area, interpreted in east/north space.
     1688     * @return true if it is visible.
     1689     */
    17311690    private boolean isAreaVisible(Path2D.Double area) {
    17321691        Rectangle2D bounds = area.getBounds2D();
    17331692        if (bounds.isEmpty()) return false;
    1734         Point2D p = nc.getPoint2D(new EastNorth(bounds.getX(), bounds.getY()));
    1735         if (p.getX() > nc.getWidth()) return false;
    1736         if (p.getY() < 0) return false;
    1737         p = nc.getPoint2D(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));
    1738         if (p.getX() < 0) return false;
    1739         if (p.getY() > nc.getHeight()) return false;
     1693        MapViewPoint p = mapState.getPointFor(new EastNorth(bounds.getX(), bounds.getY()));
     1694        if (p.getInViewX() > mapState.getViewWidth()) return false;
     1695        if (p.getInViewY() < 0) return false;
     1696        p = mapState.getPointFor(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));
     1697        if (p.getInViewX() < 0) return false;
     1698        if (p.getInViewY() > mapState.getViewHeight()) return false;
    17401699        return true;
    17411700    }
    17421701
    public class StyledMapRenderer extends AbstractMapRenderer {  
    17521711        return showNames;
    17531712    }
    17541713
    1755     private static double[] pointAt(double t, Polygon poly, double pathLength) {
     1714    private static double[] pointAt(double t, List<MapViewPoint> poly, double pathLength) {
    17561715        double totalLen = t * pathLength;
    17571716        double curLen = 0;
    1758         long dx, dy;
     1717        double dx, dy;
    17591718        double segLen;
    17601719
    17611720        // Yes, it is inefficient to iterate from the beginning for each glyph.
    17621721        // Can be optimized if it turns out to be slow.
    1763         for (int i = 1; i < poly.npoints; ++i) {
    1764             dx = (long) poly.xpoints[i] - poly.xpoints[i-1];
    1765             dy = (long) poly.ypoints[i] - poly.ypoints[i-1];
     1722        for (int i = 1; i < poly.size(); ++i) {
     1723            dx = poly.get(i).getInViewX() - poly.get(i - 1).getInViewX();
     1724            dy = poly.get(i).getInViewY() - poly.get(i - 1).getInViewY();
    17661725            segLen = Math.sqrt(dx*dx + dy*dy);
    17671726            if (totalLen > curLen + segLen) {
    17681727                curLen += segLen;
    17691728                continue;
    17701729            }
    17711730            return new double[] {
    1772                     poly.xpoints[i-1]+(totalLen - curLen)/segLen*dx,
    1773                     poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
     1731                    poly.get(i - 1).getInViewX() + (totalLen - curLen) / segLen * dx,
     1732                    poly.get(i - 1).getInViewY() + (totalLen - curLen) / segLen * dy,
    17741733                    Math.atan2(dy, dx)};
    17751734        }
    17761735        return null;
  • src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java

    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 b package org.openstreetmap.josm.data.osm.visitor.paint;  
    44import java.awt.BasicStroke;
    55import java.awt.Color;
    66import java.awt.Graphics2D;
    7 import java.awt.Point;
    87import java.awt.Rectangle;
    98import java.awt.RenderingHints;
    109import java.awt.Stroke;
     10import java.awt.geom.Ellipse2D;
    1111import java.awt.geom.GeneralPath;
     12import java.awt.geom.Rectangle2D;
     13import java.awt.geom.Rectangle2D.Double;
    1214import java.util.ArrayList;
    1315import java.util.Iterator;
    1416import java.util.List;
    import org.openstreetmap.josm.data.osm.RelationMember;  
    2527import org.openstreetmap.josm.data.osm.Way;
    2628import org.openstreetmap.josm.data.osm.WaySegment;
    2729import org.openstreetmap.josm.data.osm.visitor.Visitor;
     30import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    2831import org.openstreetmap.josm.gui.NavigatableComponent;
    2932
    3033/**
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    7477    /** Color cache to draw subsequent segments of same color as one <code>Path</code>. */
    7578    protected Color currentColor;
    7679    /** Path store to draw subsequent segments of same color as one <code>Path</code>. */
    77     protected GeneralPath currentPath = new GeneralPath();
     80    protected MapPath2D currentPath = new MapPath2D();
    7881    /**
    7982      * <code>DataSet</code> passed to the @{link render} function to overcome the argument
    8083      * limitations of @{link Visitor} interface. Only valid until end of rendering call.
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    8285    private DataSet ds;
    8386
    8487    /** Helper variable for {@link #drawSegment} */
    85     private static final double PHI = Math.toRadians(20);
    86     /** Helper variable for {@link #drawSegment} */
    87     private static final double cosPHI = Math.cos(PHI);
    88     /** Helper variable for {@link #drawSegment} */
    89     private static final double sinPHI = Math.sin(PHI);
     88    private static final ArrowPaintHelper ARROW_PAINT_HELPER = new ArrowPaintHelper(Math.toRadians(20), 10);
    9089
    9190    /** Helper variable for {@link #visit(Relation)} */
    9291    private final Stroke relatedWayStroke = new BasicStroke(
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    116115        taggedColor = PaintColors.TAGGED.get();
    117116        connectionColor = PaintColors.CONNECTION.get();
    118117
    119         if (taggedColor != nodeColor) {
     118        if (!taggedColor.equals(nodeColor)) {
    120119            taggedConnectionColor = taggedColor;
    121120        } else {
    122121            taggedConnectionColor = connectionColor;
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    209208        // in most of the cases there won't be more than one segment. Since the wireframe
    210209        // renderer does not feature any transparency there should be no visual difference.
    211210        for (final WaySegment wseg : data.getHighlightedWaySegments()) {
    212             drawSegment(nc.getPoint(wseg.getFirstNode()), nc.getPoint(wseg.getSecondNode()), highlightColor, false);
     211            drawSegment(mapState.getPointFor(wseg.getFirstNode()), mapState.getPointFor(wseg.getSecondNode()), highlightColor, false);
    213212        }
    214213        displaySegments();
    215214    }
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    314313
    315314        Iterator<Node> it = w.getNodes().iterator();
    316315        if (it.hasNext()) {
    317             Point lastP = nc.getPoint(it.next());
     316            MapViewPoint lastP = mapState.getPointFor(it.next());
    318317            for (int orderNumber = 1; it.hasNext(); orderNumber++) {
    319                 Point p = nc.getPoint(it.next());
     318                MapViewPoint p = mapState.getPointFor(it.next());
    320319                drawSegment(lastP, p, wayColor,
    321320                        showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
    322321                if (showOrderNumber && !isInactiveMode) {
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    353352            }
    354353
    355354            if (m.isNode()) {
    356                 Point p = nc.getPoint(m.getNode());
    357                 if (p.x < 0 || p.y < 0
    358                         || p.x > nc.getWidth() || p.y > nc.getHeight()) {
    359                     continue;
     355                MapViewPoint p = mapState.getPointFor(m.getNode());
     356                if (p.isInView()) {
     357                    g.draw(new Ellipse2D.Double(p.getInViewX()-4, p.getInViewY()-4, 9, 9));
    360358                }
    361359
    362                 g.drawOval(p.x-4, p.y-4, 9, 9);
    363360            } else if (m.isWay()) {
    364361                GeneralPath path = new GeneralPath();
    365362
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    368365                    if (!n.isDrawable()) {
    369366                        continue;
    370367                    }
    371                     Point p = nc.getPoint(n);
     368                    MapViewPoint p = mapState.getPointFor(n);
    372369                    if (first) {
    373                         path.moveTo(p.x, p.y);
     370                        path.moveTo(p.getInViewX(), p.getInViewY());
    374371                        first = false;
    375372                    } else {
    376                         path.lineTo(p.x, p.y);
     373                        path.lineTo(p.getInViewX(), p.getInViewY());
    377374                    }
    378375                }
    379376
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    392389    @Override
    393390    public void drawNode(Node n, Color color, int size, boolean fill) {
    394391        if (size > 1) {
    395             int radius = size / 2;
    396             Point p = nc.getPoint(n);
    397             if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth())
    398                     || (p.y > nc.getHeight()))
     392            MapViewPoint p = mapState.getPointFor(n);
     393            if (!p.isInView())
    399394                return;
     395            int radius = size / 2;
     396            Double shape = new Rectangle2D.Double(p.getInViewX() - radius, p.getInViewY() - radius, size, size);
    400397            g.setColor(color);
    401398            if (fill) {
    402                 g.fillRect(p.x - radius, p.y - radius, size, size);
    403                 g.drawRect(p.x - radius, p.y - radius, size, size);
    404             } else {
    405                 g.drawRect(p.x - radius, p.y - radius, size, size);
     399                g.fill(shape);
    406400            }
     401            g.draw(shape);
    407402        }
    408403    }
    409404
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    411406     * Draw a line with the given color.
    412407     *
    413408     * @param path The path to append this segment.
    414      * @param p1 First point of the way segment.
    415      * @param p2 Second point of the way segment.
     409     * @param mv1 First point of the way segment.
     410     * @param mv2 Second point of the way segment.
    416411     * @param showDirection <code>true</code> if segment direction should be indicated
    417412     */
    418     protected void drawSegment(GeneralPath path, Point p1, Point p2, boolean showDirection) {
     413    protected void drawSegment(MapPath2D path, MapViewPoint mv1, MapViewPoint mv2, boolean showDirection) {
    419414        Rectangle bounds = g.getClipBounds();
    420415        bounds.grow(100, 100);                  // avoid arrow heads at the border
    421         LineClip clip = new LineClip(p1, p2, bounds);
    422         if (clip.execute()) {
    423             p1 = clip.getP1();
    424             p2 = clip.getP2();
    425             path.moveTo(p1.x, p1.y);
    426             path.lineTo(p2.x, p2.y);
    427 
     416        if (mv1.rectTo(mv2).isInView()) {
     417            path.moveTo(mv1);
     418            path.lineTo(mv2);
    428419            if (showDirection) {
    429                 final double l = 10. / p1.distance(p2);
    430 
    431                 final double sx = l * (p1.x - p2.x);
    432                 final double sy = l * (p1.y - p2.y);
    433 
    434                 path.lineTo(p2.x + (double) Math.round(cosPHI * sx - sinPHI * sy), p2.y + (double) Math.round(sinPHI * sx + cosPHI * sy));
    435                 path.moveTo(p2.x + (double) Math.round(cosPHI * sx + sinPHI * sy), p2.y + (double) Math.round(-sinPHI * sx + cosPHI * sy));
    436                 path.lineTo(p2.x, p2.y);
     420                ARROW_PAINT_HELPER.paintArrowAt(path, mv2, mv1);
    437421            }
    438422        }
    439423    }
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    446430     * @param col The color to use for drawing line.
    447431     * @param showDirection <code>true</code> if segment direction should be indicated.
    448432     */
    449     protected void drawSegment(Point p1, Point p2, Color col, boolean showDirection) {
    450         if (col != currentColor) {
     433    protected void drawSegment(MapViewPoint p1, MapViewPoint p2, Color col, boolean showDirection) {
     434        if (!col.equals(currentColor)) {
    451435            displaySegments(col);
    452436        }
    453437        drawSegment(currentPath, p1, p2, showDirection);
    public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor  
    469453        if (currentPath != null) {
    470454            g.setColor(currentColor);
    471455            g.draw(currentPath);
    472             currentPath = new GeneralPath();
     456            currentPath = new MapPath2D();
    473457            currentColor = newColor;
    474458        }
    475459    }
  • src/org/openstreetmap/josm/data/projection/Projection.java

    diff --git a/src/org/openstreetmap/josm/data/projection/Projection.java b/src/org/openstreetmap/josm/data/projection/Projection.java
    index b2bd016..e43ad65 100644
    a b public interface Projection {  
    110110     * @return true if natural order of coordinates is North East, false if East North
    111111     */
    112112    boolean switchXY();
     113
     114    /**
     115     * Gets the object used as cache identifier when caching results of this projection.
     116     * @return The object to use as cache key
     117     */
     118    default Object getCacheKey() {
     119        return this;
     120    }
    113121}
  • src/org/openstreetmap/josm/gui/MapViewState.java

    diff --git a/src/org/openstreetmap/josm/gui/MapViewState.java b/src/org/openstreetmap/josm/gui/MapViewState.java
    index f7dcd8d..fe1cff1 100644
    a b package org.openstreetmap.josm.gui;  
    33
    44import java.awt.Container;
    55import java.awt.Point;
    6 import java.awt.Rectangle;
    76import java.awt.geom.AffineTransform;
    87import java.awt.geom.Point2D;
    98import java.awt.geom.Point2D.Double;
    import org.openstreetmap.josm.data.Bounds;  
    1615import org.openstreetmap.josm.data.ProjectionBounds;
    1716import org.openstreetmap.josm.data.coor.EastNorth;
    1817import org.openstreetmap.josm.data.coor.LatLon;
     18import org.openstreetmap.josm.data.osm.Node;
    1919import org.openstreetmap.josm.data.projection.Projection;
    2020import org.openstreetmap.josm.gui.download.DownloadDialog;
    2121import org.openstreetmap.josm.tools.bugreport.BugReport;
    import org.openstreetmap.josm.tools.bugreport.BugReport;  
    2727 */
    2828public final class MapViewState {
    2929
     30    /**
     31     * A flag indicating that the point is outside to the top of the map view.
     32     */
     33    public static final int OUTSIDE_TOP = 1;
     34
     35    /**
     36     * A flag indicating that the point is outside to the bottom of the map view.
     37     */
     38    public static final int OUTSIDE_BOTTOM = 2;
     39
     40    /**
     41     * A flag indicating that the point is outside to the left of the map view.
     42     */
     43    public static final int OUTSIDE_LEFT = 3;
     44
     45    /**
     46     * A flag indicating that the point is outside to the right of the map view.
     47     */
     48    public static final int OUTSIDE_RIGHT = 4;
     49
    3050    private final Projection projection;
    3151
    3252    private final int viewWidth;
    public final class MapViewState {  
    154174    }
    155175
    156176    /**
     177     * Gets the {@link MapViewPoint} for the given node. This is faster than {@link #getPointFor(LatLon)} because it uses the node east/north
     178     * cache.
     179     * @param node The node
     180     * @return The position of that node.
     181     */
     182    public MapViewPoint getPointFor(Node node) {
     183        return getPointFor(node.getEastNorth(getProjection()));
     184    }
     185
     186    /**
    157187     * Gets a rectangle representing the whole view area.
    158188     * @return The rectangle.
    159189     */
    public final class MapViewState {  
    167197     * @return The view area.
    168198     * @since 10458
    169199     */
    170     public MapViewRectangle getViewArea(Rectangle rectangle) {
     200    public MapViewRectangle getViewArea(Rectangle2D rectangle) {
    171201        return getForView(rectangle.getMinX(), rectangle.getMinY()).rectTo(getForView(rectangle.getMaxX(), rectangle.getMaxY()));
    172202    }
    173203
    public final class MapViewState {  
    313343            return new Point2D.Double(getInViewX(), getInViewY());
    314344        }
    315345
    316         protected abstract double getInViewX();
     346        /**
     347         * Get the x coordinate in view space without creating an intermediate object.
     348         * @return The x coordinate
     349         */
     350        public abstract double getInViewX();
    317351
    318         protected abstract double getInViewY();
     352        /**
     353         * Get the y coordinate in view space without creating an intermediate object.
     354         * @return The y coordinate
     355         */
     356        public abstract double getInViewY();
    319357
    320358        /**
    321359         * Convert this point to window coordinates.
    public final class MapViewState {  
    371409        public MapViewPoint add(EastNorth en) {
    372410            return new MapViewEastNorthPoint(getEastNorth().add(en));
    373411        }
     412
     413        /**
     414         * Check if this point is inside the view bounds.
     415         *
     416         * This is the case iff <code>getOutsideRectangleFlags(getViewArea())</code> returns no flags
     417         * @return true if it is.
     418         */
     419        public boolean isInView() {
     420            return inRange(getInViewX(), 0, getViewWidth()) && inRange(getInViewY(), 0, getViewHeight());
     421        }
     422
     423        private boolean inRange(double val, int min, double max) {
     424            return val >= min && val < max;
     425        }
     426
     427        /**
     428         * Gets the direction in which this point is outside of the given view rectangle.
     429         * @param rect The rectangle to check agains.
     430         * @return The direction in which it is outside of the view, as OUTSIDE_... flags.
     431         */
     432        public int getOutsideRectangleFlags(MapViewRectangle rect) {
     433            Rectangle2D bounds = rect.getInView();
     434            int flags = 0;
     435            if (getInViewX() < bounds.getMinX()) {
     436                flags |= OUTSIDE_LEFT;
     437            } else if (getInViewX() > bounds.getMaxX()) {
     438                flags |= OUTSIDE_RIGHT;
     439            }
     440            if (getInViewY() < bounds.getMinY()) {
     441                flags |= OUTSIDE_TOP;
     442            } else if (getInViewY() > bounds.getMaxY()) {
     443                flags |= OUTSIDE_BOTTOM;
     444            }
     445
     446            return flags;
     447        }
     448
     449        /**
     450         * Gets the sum of the x/y view distances between the points. |x1 - x2| + |y1 - y2|
     451         * @param p2 The other point
     452         * @return The norm
     453         */
     454        public double oneNormInView(MapViewPoint p2) {
     455            return Math.abs(getInViewX() - p2.getInViewX()) + Math.abs(getInViewY()) - p2.getInViewY();
     456        }
     457
     458        /**
     459         * Gets the squared distance between this point and an other point.
     460         * @param p2 The other point
     461         * @return The squared distance.
     462         */
     463        public double distanceToInViewSq(MapViewPoint p2) {
     464            double dx = getInViewX() - p2.getInViewX();
     465            double dy = getInViewY() - p2.getInViewY();
     466            return dx * dx + dy * dy;
     467        }
     468
     469        /**
     470         * Gets the distance between this point and an other point.
     471         * @param p2 The other point
     472         * @return The distance.
     473         */
     474        public double distanceToInView(MapViewPoint p2) {
     475            return Math.sqrt(distanceToInViewSq(p2));
     476        }
    374477    }
    375478
    376479    private class MapViewViewPoint extends MapViewPoint {
    public final class MapViewState {  
    383486        }
    384487
    385488        @Override
    386         protected double getInViewX() {
     489        public double getInViewX() {
    387490            return x;
    388491        }
    389492
    390493        @Override
    391         protected double getInViewY() {
     494        public double getInViewY() {
    392495            return y;
    393496        }
    394497
    public final class MapViewState {  
    407510        }
    408511
    409512        @Override
    410         protected double getInViewX() {
     513        public double getInViewX() {
    411514            return (eastNorth.east() - topLeft.east()) / scale;
    412515        }
    413516
    414517        @Override
    415         protected double getInViewY() {
     518        public double getInViewY() {
    416519            return (topLeft.north() - eastNorth.north()) / scale;
    417520        }
    418521
    public final class MapViewState {  
    488591            double y2 = p2.getInViewY();
    489592            return new Rectangle2D.Double(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
    490593        }
     594
     595        /**
     596         * Check if the rectangle intersects the map view area.
     597         * @return <code>true</code> if it intersects.
     598         */
     599        public boolean isInView() {
     600            return getInView().intersects(getViewArea().getInView());
     601        }
    491602    }
    492603
    493604}
  • src/org/openstreetmap/josm/gui/NavigatableComponent.java

    diff --git a/src/org/openstreetmap/josm/gui/NavigatableComponent.java b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
    index 7e4d6e7..a07664a 100644
    a b public class NavigatableComponent extends JComponent implements Helpful {  
    504504    }
    505505
    506506    // looses precision, may overflow (depends on p and current scale)
    507     //@Deprecated
     507    @Deprecated
    508508    public Point getPoint(EastNorth p) {
    509509        Point2D d = getPoint2D(p);
    510510        return new Point((int) d.getX(), (int) d.getY());
    511511    }
    512512
    513513    // looses precision, may overflow (depends on p and current scale)
    514     //@Deprecated
     514    @Deprecated
    515515    public Point getPoint(LatLon latlon) {
    516516        Point2D d = getPoint2D(latlon);
    517517        return new Point((int) d.getX(), (int) d.getY());
    518518    }
    519519
    520520    // looses precision, may overflow (depends on p and current scale)
    521     //@Deprecated
     521    @Deprecated
    522522    public Point getPoint(Node n) {
    523523        Point2D d = getPoint2D(n);
    524524        return new Point((int) d.getX(), (int) d.getY());
  • src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java

    diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java
    index 8b5b530..3ea8916 100644
    a b import java.awt.Color;  
    66import java.awt.Rectangle;
    77import java.awt.Stroke;
    88import java.util.Objects;
     9import java.util.Optional;
    910
    1011import org.openstreetmap.josm.Main;
    1112import org.openstreetmap.josm.data.osm.Node;
    import org.openstreetmap.josm.gui.mappaint.MultiCascade;  
    2122import org.openstreetmap.josm.gui.mappaint.StyleElementList;
    2223import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProvider;
    2324import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.SimpleBoxProvider;
     25import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol.SymbolShape;
    2426import org.openstreetmap.josm.gui.util.RotationAngle;
    2527import org.openstreetmap.josm.tools.CheckParameterUtil;
    2628import org.openstreetmap.josm.tools.Utils;
    import org.openstreetmap.josm.tools.Utils;  
    3133public class NodeElement extends StyleElement {
    3234    public final MapImage mapImage;
    3335    public final RotationAngle mapImageAngle;
     36    /**
     37     * The symbol that should be used for drawing this node.
     38     */
    3439    public final Symbol symbol;
    3540
    36     public enum SymbolShape { SQUARE, CIRCLE, TRIANGLE, PENTAGON, HEXAGON, HEPTAGON, OCTAGON, NONAGON, DECAGON }
    37 
    38     public static class Symbol {
    39         public SymbolShape symbol;
    40         public int size;
    41         public Stroke stroke;
    42         public Color strokeColor;
    43         public Color fillColor;
    44 
    45         public Symbol(SymbolShape symbol, int size, Stroke stroke, Color strokeColor, Color fillColor) {
    46             if (stroke != null && strokeColor == null)
    47                 throw new IllegalArgumentException("Stroke given without color");
    48             if (stroke == null && fillColor == null)
    49                 throw new IllegalArgumentException("Either a stroke or a fill color must be given");
    50             this.symbol = symbol;
    51             this.size = size;
    52             this.stroke = stroke;
    53             this.strokeColor = strokeColor;
    54             this.fillColor = fillColor;
    55         }
    56 
    57         @Override
    58         public boolean equals(Object obj) {
    59             if (obj == null || getClass() != obj.getClass())
    60                 return false;
    61             final Symbol other = (Symbol) obj;
    62             return symbol == other.symbol &&
    63                     size == other.size &&
    64                     Objects.equals(stroke, other.stroke) &&
    65                     Objects.equals(strokeColor, other.strokeColor) &&
    66                     Objects.equals(fillColor, other.fillColor);
    67         }
    68 
    69         @Override
    70         public int hashCode() {
    71             return Objects.hash(symbol, size, stroke, strokeColor, fillColor);
    72         }
    73 
    74         @Override
    75         public String toString() {
    76             return "symbol=" + symbol + " size=" + size +
    77                     (stroke != null ? " stroke=" + stroke + " strokeColor=" + strokeColor : "") +
    78                     (fillColor != null ? " fillColor=" + fillColor : "");
    79         }
    80     }
    81 
    8241    private static final String[] ICON_KEYS = {ICON_IMAGE, ICON_WIDTH, ICON_HEIGHT, ICON_OPACITY, ICON_OFFSET_X, ICON_OFFSET_Y};
    8342
    8443    public static final NodeElement SIMPLE_NODE_ELEMSTYLE;
    public class NodeElement extends StyleElement {  
    182141        mapImage.offsetX = Math.round(offsetXF);
    183142        mapImage.offsetY = Math.round(offsetYF);
    184143
    185         mapImage.alpha = Math.min(255, Math.max(0, Integer.valueOf(Main.pref.getInteger("mappaint.icon-image-alpha", 255))));
     144        mapImage.alpha = Math.min(255, Math.max(0, Main.pref.getInteger("mappaint.icon-image-alpha", 255)));
    186145        Integer pAlpha = Utils.color_float2int(c.get(keys[ICON_OPACITY_IDX], null, float.class));
    187146        if (pAlpha != null) {
    188147            mapImage.alpha = pAlpha;
    public class NodeElement extends StyleElement {  
    192151
    193152    private static Symbol createSymbol(Environment env) {
    194153        Cascade c = env.mc.getCascade(env.layer);
    195         Cascade cDef = env.mc.getCascade("default");
    196154
    197         SymbolShape shape;
    198155        Keyword shapeKW = c.get("symbol-shape", null, Keyword.class);
    199156        if (shapeKW == null)
    200157            return null;
    201         if ("square".equals(shapeKW.val)) {
    202             shape = SymbolShape.SQUARE;
    203         } else if ("circle".equals(shapeKW.val)) {
    204             shape = SymbolShape.CIRCLE;
    205         } else if ("triangle".equals(shapeKW.val)) {
    206             shape = SymbolShape.TRIANGLE;
    207         } else if ("pentagon".equals(shapeKW.val)) {
    208             shape = SymbolShape.PENTAGON;
    209         } else if ("hexagon".equals(shapeKW.val)) {
    210             shape = SymbolShape.HEXAGON;
    211         } else if ("heptagon".equals(shapeKW.val)) {
    212             shape = SymbolShape.HEPTAGON;
    213         } else if ("octagon".equals(shapeKW.val)) {
    214             shape = SymbolShape.OCTAGON;
    215         } else if ("nonagon".equals(shapeKW.val)) {
    216             shape = SymbolShape.NONAGON;
    217         } else if ("decagon".equals(shapeKW.val)) {
    218             shape = SymbolShape.DECAGON;
    219         } else
     158        Optional<SymbolShape> shape = SymbolShape.forName(shapeKW.val);
     159        if (!shape.isPresent()) {
    220160            return null;
     161        }
    221162
     163        Cascade cDef = env.mc.getCascade("default");
    222164        Float sizeOnDefault = cDef.get("symbol-size", null, Float.class);
    223165        if (sizeOnDefault != null && sizeOnDefault <= 0) {
    224166            sizeOnDefault = null;
    public class NodeElement extends StyleElement {  
    266208            }
    267209        }
    268210
    269         return new Symbol(shape, Math.round(size), stroke, strokeColor, fillColor);
     211        return new Symbol(shape.get(), Math.round(size), stroke, strokeColor, fillColor);
    270212    }
    271213
    272214    @Override
    public class NodeElement extends StyleElement {  
    278220                painter.drawNodeIcon(n, mapImage, painter.isInactiveMode() || n.isDisabled(), selected, member,
    279221                        mapImageAngle == null ? 0.0 : mapImageAngle.getRotationAngle(primitive));
    280222            } else if (symbol != null) {
    281                 Color fillColor = symbol.fillColor;
    282                 if (fillColor != null) {
    283                     if (painter.isInactiveMode() || n.isDisabled()) {
    284                         fillColor = settings.getInactiveColor();
    285                     } else if (defaultSelectedHandling && selected) {
    286                         fillColor = settings.getSelectedColor(fillColor.getAlpha());
    287                     } else if (member) {
    288                         fillColor = settings.getRelationSelectedColor(fillColor.getAlpha());
    289                     }
    290                 }
    291                 Color strokeColor = symbol.strokeColor;
    292                 if (strokeColor != null) {
    293                     if (painter.isInactiveMode() || n.isDisabled()) {
    294                         strokeColor = settings.getInactiveColor();
    295                     } else if (defaultSelectedHandling && selected) {
    296                         strokeColor = settings.getSelectedColor(strokeColor.getAlpha());
    297                     } else if (member) {
    298                         strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha());
    299                     }
    300                 }
    301                 painter.drawNodeSymbol(n, symbol, fillColor, strokeColor);
     223                paintWithSymbol(settings, painter, selected, member, n);
    302224            } else {
    303225                Color color;
    304226                boolean isConnection = n.isConnectionNode();
    public class NodeElement extends StyleElement {  
    341263        }
    342264    }
    343265
     266    private void paintWithSymbol(MapPaintSettings settings, StyledMapRenderer painter, boolean selected, boolean member,
     267            Node n) {
     268        Color fillColor = symbol.fillColor;
     269        if (fillColor != null) {
     270            if (painter.isInactiveMode() || n.isDisabled()) {
     271                fillColor = settings.getInactiveColor();
     272            } else if (defaultSelectedHandling && selected) {
     273                fillColor = settings.getSelectedColor(fillColor.getAlpha());
     274            } else if (member) {
     275                fillColor = settings.getRelationSelectedColor(fillColor.getAlpha());
     276            }
     277        }
     278        Color strokeColor = symbol.strokeColor;
     279        if (strokeColor != null) {
     280            if (painter.isInactiveMode() || n.isDisabled()) {
     281                strokeColor = settings.getInactiveColor();
     282            } else if (defaultSelectedHandling && selected) {
     283                strokeColor = settings.getSelectedColor(strokeColor.getAlpha());
     284            } else if (member) {
     285                strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha());
     286            }
     287        }
     288        painter.drawNodeSymbol(n, symbol, fillColor, strokeColor);
     289    }
     290
    344291    public BoxProvider getBoxProvider() {
    345292        if (mapImage != null)
    346293            return mapImage.getBoxProvider();
  • new file src/org/openstreetmap/josm/gui/mappaint/styleelement/Symbol.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.mappaint.styleelement;
     3
     4import java.awt.Color;
     5import java.awt.Shape;
     6import java.awt.Stroke;
     7import java.awt.geom.Ellipse2D;
     8import java.awt.geom.GeneralPath;
     9import java.awt.geom.Rectangle2D;
     10import java.util.Objects;
     11import java.util.Optional;
     12import java.util.stream.Stream;
     13
     14/**
     15 * The definition of a symbol that should be rendered at the node position.
     16 * @since xxx Extracted from {@link NodeElement}
     17 */
     18public class Symbol {
     19    private final SymbolShape symbolShape;
     20    /**
     21     * The width and height of this symbol
     22     */
     23    public final int size;
     24    /**
     25     * The stroke to use for the outline
     26     */
     27    public final Stroke stroke;
     28    /**
     29     * The color to draw the stroke with
     30     */
     31    public final Color strokeColor;
     32    /**
     33     * The color to fill the interiour of the shape.
     34     */
     35    public final Color fillColor;
     36
     37    /**
     38     * Create a new symbol
     39     * @param symbol The symbol type
     40     * @param size The overall size of the symbol, both width and height are the same
     41     * @param stroke The stroke to use for the outline
     42     * @param strokeColor The color to draw the stroke with
     43     * @param fillColor The color to fill the interiour of the shape.
     44     */
     45    public Symbol(SymbolShape symbol, int size, Stroke stroke, Color strokeColor, Color fillColor) {
     46        if (stroke != null && strokeColor == null)
     47            throw new IllegalArgumentException("Stroke given without color");
     48        if (stroke == null && fillColor == null)
     49            throw new IllegalArgumentException("Either a stroke or a fill color must be given");
     50        this.symbolShape = symbol;
     51        this.size = size;
     52        this.stroke = stroke;
     53        this.strokeColor = strokeColor;
     54        this.fillColor = fillColor;
     55    }
     56
     57    @Override
     58    public boolean equals(Object obj) {
     59        if (obj == null || getClass() != obj.getClass())
     60            return false;
     61        final Symbol other = (Symbol) obj;
     62        return symbolShape == other.symbolShape &&
     63                size == other.size &&
     64                Objects.equals(stroke, other.stroke) &&
     65                Objects.equals(strokeColor, other.strokeColor) &&
     66                Objects.equals(fillColor, other.fillColor);
     67    }
     68
     69    @Override
     70    public int hashCode() {
     71        return Objects.hash(symbolShape, size, stroke, strokeColor, fillColor);
     72    }
     73
     74    @Override
     75    public String toString() {
     76        return "symbolShape=" + symbolShape + " size=" + size +
     77                (stroke != null ? (" stroke=" + stroke + " strokeColor=" + strokeColor) : "") +
     78                (fillColor != null ? (" fillColor=" + fillColor) : "");
     79    }
     80
     81    /**
     82     * Builds the shape for this symbol
     83     * @param x The center x coordinate
     84     * @param y The center y coordinate
     85     * @return The symbol shape.
     86     */
     87    public Shape buildShapeAround(double x, double y) {
     88        int radius = size / 2;
     89        Shape shape;
     90        switch (symbolShape) {
     91        case SQUARE:
     92            // optimize for performance reasons
     93            shape = new Rectangle2D.Double(x - radius,  y - radius, size, size);
     94            break;
     95        case CIRCLE:
     96            shape = new Ellipse2D.Double(x - radius, y - radius, size, size);
     97            break;
     98        default:
     99            shape = buildPolygon(x, y, radius);
     100            break;
     101        }
     102        return shape;
     103    }
     104
     105    private Shape buildPolygon(double cx, double cy, int radius) {
     106        GeneralPath polygon = new GeneralPath();
     107        for (int i = 0; i < symbolShape.sides; i++) {
     108            double angle = ((2 * Math.PI / symbolShape.sides) * i) - symbolShape.rotation;
     109            double x = cx + radius * Math.cos(angle);
     110            double y = cy + radius * Math.sin(angle);
     111            if (i == 0) {
     112                polygon.moveTo(x, y);
     113            } else {
     114                polygon.lineTo(x, y);
     115            }
     116        }
     117        polygon.closePath();
     118        return polygon;
     119    }
     120
     121    /**
     122     * A list of possible symbol shapes.
     123     */
     124    public enum SymbolShape {
     125        /**
     126         * A square
     127         */
     128        SQUARE("square", 4, Math.PI / 4),
     129        /**
     130         * A circle
     131         */
     132        CIRCLE("circle", 1, 0),
     133        /**
     134         * A triangle with sides of equal lengh
     135         */
     136        TRIANGLE("triangle", 3, Math.PI / 2),
     137        /**
     138         * A pentagon
     139         */
     140        PENTAGON("pentagon", 5, Math.PI / 2),
     141        /**
     142         * A hexagon
     143         */
     144        HEXAGON("hexagon", 6, 0),
     145        /**
     146         * A heptagon
     147         */
     148        HEPTAGON("heptagon", 7, Math.PI / 2),
     149        /**
     150         * An octagon
     151         */
     152        OCTAGON("octagon", 8, Math.PI / 8),
     153        /**
     154         * a nonagon
     155         */
     156        NONAGON("nonagon", 9, Math.PI / 2),
     157        /**
     158         * A decagon
     159         */
     160        DECAGON("decagon", 10, 0);
     161
     162        private final String name;
     163        final int sides;
     164
     165        final double rotation;
     166
     167        private SymbolShape(String name, int sides, double rotation) {
     168            this.name = name;
     169            this.sides = sides;
     170            this.rotation = rotation;
     171        }
     172
     173        /**
     174         * Gets the number of normally straight sides this symbol has. Returns 1 for a circle.
     175         * @return The sides of the symbol
     176         */
     177        public int getSides() {
     178            return sides;
     179        }
     180
     181        /**
     182         * Gets the rotateion of the first point of this symbol.
     183         * @return The roration
     184         */
     185        public double getRotation() {
     186            return rotation;
     187        }
     188
     189        /**
     190         * Get the MapCSS name for this shape
     191         * @return The name
     192         */
     193        public String getName() {
     194            return name;
     195        }
     196
     197        /**
     198         * Get the shape with the given name
     199         * @param val The name to search
     200         * @return The shape as optional
     201         */
     202        public static Optional<SymbolShape> forName(String val) {
     203            return Stream.of(values()).filter(val::equals).findAny();
     204        }
     205    }
     206}