Changeset 10827 in josm


Ignore:
Timestamp:
2016-08-17T20:14:58+02:00 (3 years ago)
Author:
Don-vip
Message:

fix #13306 - Make map paint code use double coordinates (patch by michael2402) - gsoc-core

Location:
trunk/src/org/openstreetmap/josm
Files:
3 added
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java

    r10716 r10827  
    1717import java.awt.event.MouseEvent;
    1818import java.awt.event.MouseListener;
    19 import java.awt.geom.GeneralPath;
    2019import java.util.ArrayList;
    2120import java.util.Arrays;
     
    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;
     
    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;
     
    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;
    1146 
    1147         b.moveTo(p1.x, p1.y);
    1148         b.lineTo(p2.x, p2.y);
     1144        MapPath2D b = new MapPath2D();
     1145        MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode());
     1146        MapViewPoint p2 = mv.getState().getPointFor(currentMouseEastNorth);
     1147
     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
     
    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);
     
    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            }
     
    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            }
     
    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
  • trunk/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java

    r10701 r10827  
    1313import java.awt.event.KeyEvent;
    1414import java.awt.event.MouseEvent;
    15 import java.awt.geom.GeneralPath;
    1615import java.util.ArrayList;
    1716import java.util.Collection;
     
    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;
     
    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;
     
    297298
    298299            // Drawing preview lines
    299             GeneralPath b = new GeneralPath();
     300            MapPath2D b = new MapPath2D();
    300301            if (alt && !ctrl) {
    301302                // In delete mode
     
    333334    }
    334335
    335     protected void drawIntersectingWayHelperLines(MapView mv, GeneralPath b) {
     336    protected void drawIntersectingWayHelperLines(MapView mv, MapPath2D b) {
    336337        for (final OsmPrimitive referrer : candidateNode.getReferrers()) {
    337338            if (!(referrer instanceof Way) || targetWay.equals(referrer)) {
     
    344345                }
    345346                if (i > 0) {
    346                     final Point p = mv.getPoint(nodes.get(i - 1));
     347                    final MapViewPoint p = mv.getState().getPointFor(nodes.get(i - 1));
    347348                    b.moveTo(mousePos.x, mousePos.y);
    348                     b.lineTo(p.x, p.y);
     349                    b.lineTo(p);
    349350                }
    350351                if (i < nodes.size() - 1) {
    351                     final Point p = mv.getPoint(nodes.get(i + 1));
     352                    final MapViewPoint p = mv.getState().getPointFor(nodes.get(i + 1));
    352353                    b.moveTo(mousePos.x, mousePos.y);
    353                     b.lineTo(p.x, p.y);
     354                    b.lineTo(p);
    354355                }
    355356            }
  • trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

    r10601 r10827  
    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;
     
    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
  • trunk/src/org/openstreetmap/josm/data/osm/Node.java

    r10763 r10827  
    33
    44import java.util.Collection;
     5import java.util.Objects;
    56import java.util.Set;
    67import java.util.TreeSet;
     
    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;
     
    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    /**
     
    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     *
     
    9093    @Override
    9194    public EastNorth getEastNorth() {
     95        return getEastNorth(Main.getProjection());
     96    }
     97
     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 10826
     105     */
     106    public EastNorth getEastNorth(Projection projection) {
    92107        if (!isLatLonKnown()) return null;
    93108
    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         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
     
    103113            this.east = en.east();
    104114            this.north = en.north();
     115            this.eastNorthCacheKey = projection.getCacheKey();
    105116        }
    106117        return new EastNorth(east, north);
     
    123134            this.east = eastNorth.east();
    124135            this.north = eastNorth.north();
     136            this.eastNorthCacheKey = Main.getProjection().getCacheKey();
    125137        } else {
    126138            this.lat = Double.NaN;
     
    346358        this.east = Double.NaN;
    347359        this.north = Double.NaN;
     360        this.eastNorthCacheKey = null;
    348361    }
    349362
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java

    r10469 r10827  
    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
     
    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;
     
    2831    /** the map viewport - provides projection and hit detection functionality */
    2932    protected NavigatableComponent nc;
     33
     34    /**
     35     * The {@link MapViewState} to use to convert between coordinates.
     36     */
     37    protected final MapViewState mapState;
    3038
    3139    /** if true, the paint visitor shall render OSM objects such that they
     
    6876        this.g = g;
    6977        this.nc = nc;
     78        this.mapState = nc.getState();
    7079        this.isInactiveMode = isInactiveMode;
    7180    }
     
    8998     * @param orderNumber The number of the segment in the way.
    9099     * @param clr The color to use for drawing the text.
    91      */
    92     protected void drawOrderNumber(Point p1, Point p2, int orderNumber, Color clr) {
     100     * @since 10826
     101     */
     102    protected void drawOrderNumber(MapViewPoint p1, MapViewPoint p2, int orderNumber, Color clr) {
    93103        if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
    94104            String on = Integer.toString(orderNumber);
    95105            int strlen = on.length();
    96             int x = (p1.x+p2.x)/2 - 4*strlen;
    97             int y = (p1.y+p2.y)/2 + 4;
     106            double centerX = (p1.getInViewX()+p2.getInViewX())/2;
     107            double centerY = (p1.getInViewY()+p2.getInViewY())/2;
     108            double x = centerX - 4*strlen;
     109            double y = centerY + 4;
    98110
    99111            if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) {
    100                 y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
     112                y = centerY - virtualNodeSize - 3;
    101113            }
    102114
    103115            g.setColor(backgroundColor);
    104             g.fillRect(x-1, y-12, 8*strlen+1, 14);
     116            g.fill(new Rectangle2D.Double(x-1, y-12, 8*strlen+1, 14));
    105117            g.setColor(clr);
    106             g.drawString(on, x, y);
     118            g.drawString(on, (int) x, (int) y);
    107119        }
    108120    }
     
    182194     * @param space The free space to check against.
    183195     * @return <code>true</code> if segment is larger than required space
    184      */
    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     * @since 10826
     197     */
     198    public static boolean isLargeSegment(MapViewPoint p1, MapViewPoint p2, int space) {
     199        return p1.oneNormInView(p2) > space;
    189200    }
    190201
     
    194205     * @param p1 First point of the way segment.
    195206     * @param p2 Second point of the way segment.
    196      * @return <code>true</code> if segment is visible.
    197      */
    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     * @return <code>true</code> if segment may be visible.
     208     * @since 10826
     209     */
     210    protected boolean isSegmentVisible(MapViewPoint p1, MapViewPoint p2) {
     211        MapViewRectangle view = mapState.getViewArea();
     212        // not outside in the same direction
     213        return (p1.getOutsideRectangleFlags(view) & p2.getOutsideRectangleFlags(view)) == 0;
    204214    }
    205215
     
    209219     * @param path The path to append drawing to.
    210220     * @param w The ways to draw node for.
    211      */
    212     public void visitVirtual(GeneralPath path, Way w) {
     221     * @since 10826
     222     */
     223    public void visitVirtual(Path2D path, Way w) {
    213224        Iterator<Node> it = w.getNodes().iterator();
    214225        if (it.hasNext()) {
    215             Point lastP = nc.getPoint(it.next());
     226            MapViewPoint lastP = mapState.getPointFor(it.next());
    216227            while (it.hasNext()) {
    217                 Point p = nc.getPoint(it.next());
     228                MapViewPoint p = mapState.getPointFor(it.next());
    218229                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);
     230                    double x = (p.getInViewX()+lastP.getInViewX())/2;
     231                    double y = (p.getInViewY()+lastP.getInViewY())/2;
     232                    path.moveTo(x-virtualNodeSize, y);
     233                    path.lineTo(x+virtualNodeSize, y);
     234                    path.moveTo(x, y-virtualNodeSize);
     235                    path.lineTo(x, y+virtualNodeSize);
    225236                }
    226237                lastP = p;
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/LineClip.java

    r10244 r10827  
    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/**
     
    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    /**
     
    2324     * @param p2 end point of the clipped line
    2425     * @param clipBounds Clip bounds
     26     * @since 10826
    2527     */
    26     public LineClip(Point p1, Point p2, Rectangle clipBounds) {
     28    public LineClip(Point2D p1, Point2D p2, Rectangle2D clipBounds) {
    2729        this.p1 = p1;
    2830        this.p2 = p2;
     
    3840            return false;
    3941        }
    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);
     42        return cohenSutherland(p1.getX(), p1.getY(), p2.getX(), p2.getY(), clipBounds.getMinX(), clipBounds.getMinY(),
     43                clipBounds.getMaxX(), clipBounds.getMaxY());
    4344    }
    4445
    4546    /**
    4647     * @return start point of the clipped line
     48     * @since 10826
    4749     */
    48     public Point getP1() {
     50    public Point2D getP1() {
    4951        return p1;
    5052    }
     
    5254    /**
    5355     * @return end point of the clipped line
     56     * @since 10826
    5457     */
    55     public Point getP2() {
     58    public Point2D getP2() {
    5659        return p2;
    5760    }
     
    7073     * @return true, if line is visible in the given clip region
    7174     */
    72     private boolean cohenSutherland(long x1, long y1, long x2, long y2, long xmin, long ymin, long xmax, long ymax) {
     75    private boolean cohenSutherland(double x1, double y1, double x2, double y2, double xmin, double ymin, double xmax, double ymax) {
    7376        int outcode0, outcode1, outcodeOut;
    7477        boolean accept = false;
     
    8588                done = true;
    8689            } else {
    87                 long x = 0;
    88                 long y = 0;
     90                double x = 0;
     91                double y = 0;
    8992                outcodeOut = outcode0 != 0 ? outcode0 : outcode1;
    9093                if ((outcodeOut & OUT_TOP) != 0) {
     
    115118
    116119        if (accept) {
    117             p1 = new Point((int) x1, (int) y1);
    118             p2 = new Point((int) x2, (int) y2);
     120            p1 = new Point2D.Double(x1, y1);
     121            p2 = new Point2D.Double(x2, y2);
    119122            return true;
    120123        }
     
    133136     * @return outcode
    134137     */
    135     private static int computeOutCode(long x, long y, long xmin, long ymin, long xmax, long ymax) {
     138    private static int computeOutCode(double x, double y, double xmin, double ymin, double xmax, double ymax) {
    136139        int code = 0;
    137         if (y > ymax) {
     140        // ignore rounding errors.
     141        if (y > ymax + 1e-10) {
    138142            code |= OUT_TOP;
    139         } else if (y < ymin) {
     143        } else if (y < ymin - 1e-10) {
    140144            code |= OUT_BOTTOM;
    141145        }
    142         if (x > xmax) {
     146        if (x > xmax + 1e-10) {
    143147            code |= OUT_RIGHT;
    144         } else if (x < xmin) {
     148        } else if (x < xmin - 1e-10) {
    145149            code |= OUT_LEFT;
    146150        }
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java

    r10697 r10827  
    1212import java.awt.Image;
    1313import java.awt.Point;
    14 import java.awt.Polygon;
    1514import java.awt.Rectangle;
    1615import java.awt.RenderingHints;
     
    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;
     
    3839import java.util.concurrent.RecursiveTask;
    3940import java.util.function.Supplier;
     41import java.util.stream.Collectors;
    4042
    4143import javax.swing.AbstractButton;
     
    5961import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
    6062import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
     63import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    6164import org.openstreetmap.josm.gui.NavigatableComponent;
    6265import org.openstreetmap.josm.gui.mappaint.ElemStyles;
     
    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;
     
    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;
     
    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) {
     
    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++));
    130 
    131             Point current = nc.getPoint(nodes.get(idx));
     132            MapViewPoint current = getForIndex(idx);
     133
     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(),
     143                                               yPrev0 + current.getInViewY() - prev.getInViewY());
    137144                } else {
    138145                    return current;
     
    140147            }
    141148
    142             Point next = nc.getPoint(nodes.get(idx+1));
    143 
    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);
    147 
    148             if (lenNext == 0) {
     149            MapViewPoint next = getForIndex(idx + 1);
     150
     151            double dxNext = next.getInViewX() - current.getInViewX();
     152            double dyNext = next.getInViewY() - current.getInViewY();
     153            double lenNext = Math.sqrt(dxNext*dxNext + dyNext*dyNext);
     154
     155            if (lenNext < 1e-3) {
    149156                lenNext = 1; // value does not matter, because dy_next and dx_next is 0
    150157            }
    151158
    152             int xCurrent0 = current.x + (int) Math.round(offset * dyNext / lenNext);
    153             int yCurrent0 = current.y - (int) Math.round(offset * dxNext / lenNext);
     159            double xCurrent0 = current.getInViewX() + offset * dyNext / lenNext;
     160            double yCurrent0 = current.getInViewY() - offset * dxNext / lenNext;
    154161
    155162            if (idx == 0) {
     
    158165                xPrev0 = xCurrent0;
    159166                yPrev0 = yCurrent0;
    160                 return new Point(xCurrent0, yCurrent0);
     167                return mapState.getForView(xCurrent0, yCurrent0);
    161168            } else {
    162                 int dxPrev = current.x - prev.x;
    163                 int dyPrev = current.y - prev.y;
     169                double dxPrev = current.getInViewX() - prev.getInViewX();
     170                double dyPrev = current.getInViewY() - prev.getInViewY();
    164171
    165172                // determine intersection of the lines parallel to the two segments
    166                 int det = dxNext*dyPrev - dxPrev*dyNext;
    167 
    168                 if (det == 0) {
     173                double det = dxNext*dyPrev - dxPrev*dyNext;
     174
     175                if (Utils.equalsEpsilon(det, 0)) {
    169176                    ++idx;
    170177                    prev = current;
    171178                    xPrev0 = xCurrent0;
    172179                    yPrev0 = yCurrent0;
    173                     return new Point(xCurrent0, yCurrent0);
    174                 }
    175 
    176                 int m = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0);
    177 
    178                 int cx = xPrev0 + (int) Math.round((double) m * dxPrev / det);
    179                 int cy = yPrev0 + (int) Math.round((double) m * dyPrev / det);
     180                    return mapState.getForView(xCurrent0, yCurrent0);
     181                }
     182
     183                double m = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0);
     184
     185                double cx = xPrev0 + m * dxPrev / det;
     186                double cy = yPrev0 + m * dyPrev / det;
    180187                ++idx;
    181188                prev = current;
    182189                xPrev0 = xCurrent0;
    183190                yPrev0 = yCurrent0;
    184                 return new Point(cx, cy);
    185             }
     191                return mapState.getForView(cx, cy);
     192            }
     193        }
     194
     195        private MapViewPoint getForIndex(int i) {
     196            return mapState.getPointFor(nodes.get(i));
    186197        }
    187198
     
    376387    }
    377388
    378     private static Polygon buildPolygon(Point center, int radius, int sides) {
    379         return buildPolygon(center, radius, sides, 0.0);
    380     }
    381 
    382     private static Polygon buildPolygon(Point center, int radius, int sides, double rotation) {
    383         Polygon polygon = new Polygon();
    384         for (int i = 0; i < sides; i++) {
    385             double angle = ((2 * Math.PI / sides) * i) - rotation;
    386             int x = (int) Math.round(center.x + radius * Math.cos(angle));
    387             int y = (int) Math.round(center.y + radius * Math.sin(angle));
    388             polygon.addPoint(x, y);
    389         }
    390         return polygon;
    391     }
    392 
    393     private void displaySegments(GeneralPath path, GeneralPath orientationArrows, GeneralPath onewayArrows, GeneralPath onewayArrowsCasing,
     389    private void displaySegments(Path2D path, Path2D orientationArrows, Path2D onewayArrows, Path2D onewayArrowsCasing,
    394390            Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) {
    395391        g.setColor(isInactiveMode ? inactiveColor : color);
     
    489485            MapImage fillImage, Float extent, Path2D.Double pfClip, boolean disabled, TextLabel text) {
    490486
    491         Shape area = path.createTransformedShape(nc.getAffineTransform());
     487        Shape area = path.createTransformedShape(mapState.getAffineTransform());
    492488
    493489        if (!isOutlineOnly) {
     
    504500                    Shape clip = area;
    505501                    if (pfClip != null) {
    506                         clip = pfClip.createTransformedShape(nc.getAffineTransform());
     502                        clip = pfClip.createTransformedShape(mapState.getAffineTransform());
    507503                    }
    508504                    g.clip(clip);
     
    527523                    Shape fill = area;
    528524                    if (pfClip != null) {
    529                         fill = pfClip.createTransformedShape(nc.getAffineTransform());
     525                        fill = pfClip.createTransformedShape(mapState.getAffineTransform());
    530526                    }
    531527                    g.fill(fill);
     
    699695            return;
    700696
    701         Point p = nc.getPoint(n);
     697        MapViewPoint p = mapState.getPointFor(n);
    702698        TextLabel text = bs.text;
    703699        String s = text.labelCompositionStrategy.compose(n);
     
    707703        g.setFont(text.font);
    708704
    709         int x = p.x + text.xOffset;
    710         int y = p.y + text.yOffset;
     705        int x = (int) (p.getInViewX() + text.xOffset);
     706        int y = (int) (p.getInViewY() + text.yOffset);
    711707        /**
    712708         *
     
    770766        final int imgHeight = pattern.getHeight();
    771767
    772         Point lastP = null;
    773768        double currentWayLength = phase % repeat;
    774769        if (currentWayLength < 0) {
     
    794789        }
    795790
     791        MapViewPoint lastP = null;
    796792        OffsetIterator it = new OffsetIterator(way.getNodes(), offset);
    797793        while (it.hasNext()) {
    798             Point thisP = it.next();
     794            MapViewPoint thisP = it.next();
    799795
    800796            if (lastP != null) {
    801                 final double segmentLength = thisP.distance(lastP);
    802 
    803                 final double dx = (double) thisP.x - lastP.x;
    804                 final double dy = (double) thisP.y - lastP.y;
     797                final double segmentLength = thisP.distanceToInView(lastP);
     798
     799                final double dx = thisP.getInViewX() - lastP.getInViewX();
     800                final double dy = thisP.getInViewY() - lastP.getInViewY();
    805801
    806802                // pos is the position from the beginning of the current segment
     
    809805
    810806                AffineTransform saveTransform = g.getTransform();
    811                 g.translate(lastP.x, lastP.y);
     807                g.translate(lastP.getInViewX(), lastP.getInViewY());
    812808                g.rotate(Math.atan2(dy, dx));
    813809
     
    850846            return;
    851847
    852         Point p = nc.getPoint(n);
     848        MapViewPoint p = mapState.getPointFor(n);
    853849
    854850        if (n.isHighlighted()) {
    855             drawPointHighlight(p, size);
    856         }
    857 
    858         if (size > 1) {
    859             if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
     851            drawPointHighlight(p.getInView(), size);
     852        }
     853
     854        if (size > 1 && p.isInView()) {
    860855            int radius = size / 2;
    861856
     
    865860                g.setColor(color);
    866861            }
     862            Rectangle2D rect = new Rectangle2D.Double(p.getInViewX()-radius-1, p.getInViewY()-radius-1, size + 1, size + 1);
    867863            if (fill) {
    868                 g.fillRect(p.x-radius-1, p.y-radius-1, size + 1, size + 1);
     864                g.fill(rect);
    869865            } else {
    870                 g.drawRect(p.x-radius-1, p.y-radius-1, size, size);
    871             }
    872         }
    873     }
    874 
     866                g.draw(rect);
     867            }
     868        }
     869    }
     870
     871    /**
     872     * Draw the icon for a given node.
     873     * @param n The node
     874     * @param img The icon to draw at the node position
     875     */
    875876    public void drawNodeIcon(Node n, MapImage img, boolean disabled, boolean selected, boolean member, double theta) {
    876         Point p = nc.getPoint(n);
    877 
    878         final int w = img.getWidth(), h = img.getHeight();
     877        MapViewPoint p = mapState.getPointFor(n);
     878
     879        int w = img.getWidth();
     880        int h = img.getHeight();
    879881        if (n.isHighlighted()) {
    880             drawPointHighlight(p, Math.max(w, h));
     882            drawPointHighlight(p.getInView(), Math.max(w, h));
    881883        }
    882884
    883885        float alpha = img.getAlphaFloat();
    884886
     887        Graphics2D temporaryGraphics = (Graphics2D) g.create();
    885888        if (!Utils.equalsEpsilon(alpha, 1f)) {
    886             g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
    887         }
    888         g.rotate(theta, p.x, p.y);
    889         g.drawImage(img.getImage(disabled), p.x - w/2 + img.offsetX, p.y - h/2 + img.offsetY, nc);
    890         g.rotate(-theta, p.x, p.y);
    891         g.setPaintMode();
     889            temporaryGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
     890        }
     891
     892        double x = p.getInViewX();
     893        double y = p.getInViewY();
     894        temporaryGraphics.translate(-x, -y);
     895        temporaryGraphics.rotate(theta);
     896        temporaryGraphics.drawImage(img.getImage(disabled), w/2 + img.offsetX, h/2 + img.offsetY, nc);
    892897        if (selected || member) {
    893898            Color color;
     
    900905            }
    901906            g.setColor(color);
    902             g.drawRect(p.x - w/2 + img.offsetX - 2, p.y - h/2 + img.offsetY - 2, w + 4, h + 4);
    903         }
    904     }
    905 
     907            g.draw(new Rectangle2D.Double(x - w/2 + img.offsetX - 2, y - h/2 + img.offsetY - 2, w + 4, h + 4));
     908        }
     909    }
     910
     911    /**
     912     * Draw the symbol and possibly a highlight marking on a given node.
     913     * @param n The position to draw the symbol on
     914     * @param s The symbol to draw
     915     * @param fillColor The color to fill the symbol with
     916     * @param strokeColor The color to use for the outer corner of the symbol
     917     */
    906918    public void drawNodeSymbol(Node n, Symbol s, Color fillColor, Color strokeColor) {
    907         Point p = nc.getPoint(n);
    908         int radius = s.size / 2;
     919        MapViewPoint p = mapState.getPointFor(n);
    909920
    910921        if (n.isHighlighted()) {
    911             drawPointHighlight(p, s.size);
    912         }
    913 
    914         if (fillColor != null) {
    915             g.setColor(fillColor);
    916             switch (s.symbol) {
    917             case SQUARE:
    918                 g.fillRect(p.x - radius, p.y - radius, s.size, s.size);
    919                 break;
    920             case CIRCLE:
    921                 g.fillOval(p.x - radius, p.y - radius, s.size, s.size);
    922                 break;
    923             case TRIANGLE:
    924                 g.fillPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
    925                 break;
    926             case PENTAGON:
    927                 g.fillPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
    928                 break;
    929             case HEXAGON:
    930                 g.fillPolygon(buildPolygon(p, radius, 6));
    931                 break;
    932             case HEPTAGON:
    933                 g.fillPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
    934                 break;
    935             case OCTAGON:
    936                 g.fillPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
    937                 break;
    938             case NONAGON:
    939                 g.fillPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
    940                 break;
    941             case DECAGON:
    942                 g.fillPolygon(buildPolygon(p, radius, 10));
    943                 break;
    944             default:
    945                 throw new AssertionError();
    946             }
    947         }
    948         if (s.stroke != null) {
    949             g.setStroke(s.stroke);
    950             g.setColor(strokeColor);
    951             switch (s.symbol) {
    952             case SQUARE:
    953                 g.drawRect(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
    954                 break;
    955             case CIRCLE:
    956                 g.drawOval(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
    957                 break;
    958             case TRIANGLE:
    959                 g.drawPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
    960                 break;
    961             case PENTAGON:
    962                 g.drawPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
    963                 break;
    964             case HEXAGON:
    965                 g.drawPolygon(buildPolygon(p, radius, 6));
    966                 break;
    967             case HEPTAGON:
    968                 g.drawPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
    969                 break;
    970             case OCTAGON:
    971                 g.drawPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
    972                 break;
    973             case NONAGON:
    974                 g.drawPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
    975                 break;
    976             case DECAGON:
    977                 g.drawPolygon(buildPolygon(p, radius, 10));
    978                 break;
    979             default:
    980                 throw new AssertionError();
    981             }
    982             g.setStroke(new BasicStroke());
     922            drawPointHighlight(p.getInView(), s.size);
     923        }
     924
     925        if (fillColor != null || strokeColor != null) {
     926            Shape shape = s.buildShapeAround(p.getInViewX(), p.getInViewY());
     927
     928            if (fillColor != null) {
     929                g.setColor(fillColor);
     930                g.fill(shape);
     931            }
     932            if (s.stroke != null) {
     933                g.setStroke(s.stroke);
     934                g.setColor(strokeColor);
     935                g.draw(shape);
     936                g.setStroke(new BasicStroke());
     937            }
    983938        }
    984939    }
     
    994949     */
    995950    public void drawOrderNumber(Node n1, Node n2, int orderNumber, Color clr) {
    996         Point p1 = nc.getPoint(n1);
    997         Point p2 = nc.getPoint(n2);
     951        MapViewPoint p1 = mapState.getPointFor(n1);
     952        MapViewPoint p2 = mapState.getPointFor(n2);
    998953        drawOrderNumber(p1, p2, orderNumber, clr);
    999954    }
     
    1005960     * @param line line style
    1006961     */
    1007     private void drawPathHighlight(GeneralPath path, BasicStroke line) {
     962    private void drawPathHighlight(Path2D path, BasicStroke line) {
    1008963        if (path == null)
    1009964            return;
     
    1024979     * @param size highlight size
    1025980     */
    1026     private void drawPointHighlight(Point p, int size) {
     981    private void drawPointHighlight(Point2D p, int size) {
    1027982        g.setColor(highlightColorTransparent);
    1028983        int s = size + highlightPointRadius;
     
    1030985        while (s >= size) {
    1031986            int r = (int) Math.floor(s/2d);
    1032             g.fillRoundRect(p.x-r, p.y-r, s, s, r, r);
     987            g.fill(new RoundRectangle2D.Double(p.getX()-r, p.getY()-r, s, s, r, r));
    1033988            s -= highlightStep;
    1034989        }
     
    12191174
    12201175    /**
     1176     * A half segment that can be used to place text on it. Used in the drawTextOnPath algorithm.
     1177     * @author Michael Zangl
     1178     */
     1179    private static class HalfSegment {
     1180        /**
     1181         * start point of half segment (as length along the way)
     1182         */
     1183        final double start;
     1184        /**
     1185         * end point of half segment (as length along the way)
     1186         */
     1187        final double end;
     1188        /**
     1189         * quality factor (off screen / partly on screen / fully on screen)
     1190         */
     1191        final double quality;
     1192
     1193        /**
     1194         * Create a new half segment
     1195         * @param start The start along the way
     1196         * @param end The end of the segment
     1197         * @param quality A quality factor.
     1198         */
     1199        HalfSegment(double start, double end, double quality) {
     1200            super();
     1201            this.start = start;
     1202            this.end = end;
     1203            this.quality = quality;
     1204        }
     1205
     1206        @Override
     1207        public String toString() {
     1208            return "HalfSegment [start=" + start + ", end=" + end + ", quality=" + quality + "]";
     1209        }
     1210    }
     1211
     1212    /**
    12211213     * Draws a text along a given way.
    12221214     * @param way The way to draw the text on.
     
    12351227        Rectangle bounds = g.getClipBounds();
    12361228
    1237         Polygon poly = new Polygon();
    1238         Point lastPoint = null;
    1239         Iterator<Node> it = way.getNodes().iterator();
    1240         double pathLength = 0;
    1241         long dx, dy;
     1229        List<MapViewPoint> points = way.getNodes().stream().map(mapState::getPointFor).collect(Collectors.toList());
    12421230
    12431231        // 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)
    1244         List<Double> longHalfSegmentStart = new ArrayList<>(); // start point of half segment (as length along the way)
    1245         List<Double> longHalfSegmentEnd = new ArrayList<>(); // end point of half segment (as length along the way)
    1246         List<Double> longHalfsegmentQuality = new ArrayList<>(); // quality factor (off screen / partly on screen / fully on screen)
    1247 
    1248         while (it.hasNext()) {
    1249             Node n = it.next();
    1250             Point p = nc.getPoint(n);
    1251             poly.addPoint(p.x, p.y);
    1252 
    1253             if (lastPoint != null) {
    1254                 dx = (long) p.x - lastPoint.x;
    1255                 dy = (long) p.y - lastPoint.y;
    1256                 double segmentLength = Math.sqrt(dx*dx + dy*dy);
    1257                 if (segmentLength > 2*(rec.getWidth()+4)) {
    1258                     Point center = new Point((lastPoint.x + p.x)/2, (lastPoint.y + p.y)/2);
    1259                     double q = 0;
    1260                     if (bounds != null) {
    1261                         if (bounds.contains(lastPoint) && bounds.contains(center)) {
    1262                             q = 2;
    1263                         } else if (bounds.contains(lastPoint) || bounds.contains(center)) {
    1264                             q = 1;
    1265                         }
    1266                     }
    1267                     longHalfSegmentStart.add(pathLength);
    1268                     longHalfSegmentEnd.add(pathLength + segmentLength / 2);
    1269                     longHalfsegmentQuality.add(q);
    1270 
    1271                     q = 0;
    1272                     if (bounds != null) {
    1273                         if (bounds.contains(center) && bounds.contains(p)) {
    1274                             q = 2;
    1275                         } else if (bounds.contains(center) || bounds.contains(p)) {
    1276                             q = 1;
    1277                         }
    1278                     }
    1279                     longHalfSegmentStart.add(pathLength + segmentLength / 2);
    1280                     longHalfSegmentEnd.add(pathLength + segmentLength);
    1281                     longHalfsegmentQuality.add(q);
    1282                 }
    1283                 pathLength += segmentLength;
    1284             }
    1285             lastPoint = p;
    1286         }
     1232        List<HalfSegment> longHalfSegment = new ArrayList<>();
     1233
     1234        double pathLength = computePath(2 * (rec.getWidth() + 4), bounds, points, longHalfSegment);
    12871235
    12881236        if (rec.getWidth() > pathLength)
     
    12911239        double t1, t2;
    12921240
    1293         if (!longHalfSegmentStart.isEmpty()) {
    1294             if (way.getNodesCount() == 2) {
    1295                 // For 2 node ways, the two half segments are exactly the same size and distance from the center.
    1296                 // Prefer the first one for consistency.
    1297                 longHalfsegmentQuality.set(0, longHalfsegmentQuality.get(0) + 0.5);
    1298             }
    1299 
    1300             // find the long half segment that is closest to the center of the way
    1301             // candidates with higher quality value are preferred
    1302             double bestStart = Double.NaN;
    1303             double bestEnd = Double.NaN;
    1304             double bestDistanceToCenter = Double.MAX_VALUE;
    1305             double bestQuality = -1;
    1306             for (int i = 0; i < longHalfSegmentStart.size(); i++) {
    1307                 double start = longHalfSegmentStart.get(i);
    1308                 double end = longHalfSegmentEnd.get(i);
    1309                 double dist = Math.abs(0.5 * (end + start) - 0.5 * pathLength);
    1310                 if (longHalfsegmentQuality.get(i) > bestQuality
    1311                         || (dist < bestDistanceToCenter && Utils.equalsEpsilon(longHalfsegmentQuality.get(i), bestQuality))) {
    1312                     bestStart = start;
    1313                     bestEnd = end;
    1314                     bestDistanceToCenter = dist;
    1315                     bestQuality = longHalfsegmentQuality.get(i);
    1316                 }
    1317             }
    1318             double remaining = bestEnd - bestStart - rec.getWidth(); // total space left and right from the text
     1241        if (!longHalfSegment.isEmpty()) {
     1242            // find the segment with the best quality. If there are several with best quality, the one close to the center is prefered.
     1243            HalfSegment best = longHalfSegment.stream().max(
     1244                    Comparator.comparingDouble(segment ->
     1245                        segment.quality - 1e-5 * Math.abs(0.5 * (segment.end + segment.start) - 0.5 * pathLength)
     1246                    )).get();
     1247            double remaining = best.end - best.start - rec.getWidth(); // total space left and right from the text
    13191248            // The space left and right of the text should be distributed 20% - 80% (towards the center),
    13201249            // but the smaller space should not be less than 7 px.
    13211250            // However, if the total remaining space is less than 14 px, then distribute it evenly.
    13221251            double smallerSpace = Math.min(Math.max(0.2 * remaining, 7), 0.5 * remaining);
    1323             if ((bestEnd + bestStart)/2 < pathLength/2) {
    1324                 t2 = bestEnd - smallerSpace;
     1252            if ((best.end + best.start)/2 < pathLength/2) {
     1253                t2 = best.end - smallerSpace;
    13251254                t1 = t2 - rec.getWidth();
    13261255            } else {
    1327                 t1 = bestStart + smallerSpace;
     1256                t1 = best.start + smallerSpace;
    13281257                t2 = t1 + rec.getWidth();
    13291258            }
     
    13361265        t2 /= pathLength;
    13371266
    1338         double[] p1 = pointAt(t1, poly, pathLength);
    1339         double[] p2 = pointAt(t2, poly, pathLength);
     1267        double[] p1 = pointAt(t1, points, pathLength);
     1268        double[] p2 = pointAt(t2, points, pathLength);
    13401269
    13411270        if (p1 == null || p2 == null)
     
    13651294                Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D();
    13661295                double t = tStart + offsetSign * (gvOffset + rect.getX() + rect.getWidth()/2) / pathLength;
    1367                 double[] p = pointAt(t, poly, pathLength);
     1296                double[] p = pointAt(t, points, pathLength);
    13681297                if (p != null) {
    13691298                    AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]);
     
    13831312            gvOffset += gvWidth;
    13841313        }
     1314    }
     1315
     1316    private double computePath(double minSegmentLength, Rectangle bounds, List<MapViewPoint> points,
     1317            List<HalfSegment> longHalfSegment) {
     1318        MapViewPoint lastPoint = points.get(0);
     1319        double pathLength = 0;
     1320        for (MapViewPoint p : points.subList(1, points.size())) {
     1321            double segmentLength = p.distanceToInView(lastPoint);
     1322            if (segmentLength > minSegmentLength) {
     1323                Point2D center = new Point2D.Double((lastPoint.getInViewX() + p.getInViewX())/2, (lastPoint.getInViewY() + p.getInViewY())/2);
     1324                double q = computeQuality(bounds, lastPoint, center);
     1325                // prefer the first one for quality equality.
     1326                longHalfSegment.add(new HalfSegment(pathLength, pathLength + segmentLength / 2, q));
     1327
     1328                q = 0;
     1329                if (bounds != null) {
     1330                    if (bounds.contains(center) && bounds.contains(p.getInView())) {
     1331                        q = 2;
     1332                    } else if (bounds.contains(center) || bounds.contains(p.getInView())) {
     1333                        q = 1;
     1334                    }
     1335                }
     1336                longHalfSegment.add(new HalfSegment(pathLength + segmentLength / 2, pathLength + segmentLength, q));
     1337            }
     1338            pathLength += segmentLength;
     1339            lastPoint = p;
     1340        }
     1341        return pathLength;
     1342    }
     1343
     1344    private static double computeQuality(Rectangle bounds, MapViewPoint p1, Point2D p2) {
     1345        double q = 0;
     1346        if (bounds != null) {
     1347            if (bounds.contains(p1.getInView())) {
     1348                q += 1;
     1349            }
     1350            if (bounds.contains(p2)) {
     1351                q += 1;
     1352            }
     1353        }
     1354        return q;
    13851355    }
    13861356
     
    14041374            boolean showOneway, boolean onewayReversed) {
    14051375
    1406         GeneralPath path = new GeneralPath();
    1407         GeneralPath orientationArrows = showOrientation ? new GeneralPath() : null;
    1408         GeneralPath onewayArrows = showOneway ? new GeneralPath() : null;
    1409         GeneralPath onewayArrowsCasing = showOneway ? new GeneralPath() : null;
     1376        MapPath2D path = new MapPath2D();
     1377        MapPath2D orientationArrows = showOrientation ? new MapPath2D() : null;
     1378        MapPath2D onewayArrows = showOneway ? new MapPath2D() : null;
     1379        MapPath2D onewayArrowsCasing = showOneway ? new MapPath2D() : null;
    14101380        Rectangle bounds = g.getClipBounds();
    14111381        if (bounds != null) {
     
    14141384        }
    14151385
    1416         double wayLength = 0;
    1417         Point lastPoint = null;
    14181386        boolean initialMoveToNeeded = true;
    14191387        List<Node> wayNodes = way.getNodes();
     
    14311399                }
    14321400
    1433                 Point p1 = nc.getPoint(ws.getFirstNode());
    1434                 Point p2 = nc.getPoint(ws.getSecondNode());
    1435                 highlightSegs.moveTo(p1.x, p1.y);
    1436                 highlightSegs.lineTo(p2.x, p2.y);
     1401                Point2D p1 = mapState.getPointFor(ws.getFirstNode()).getInView();
     1402                Point2D p2 = mapState.getPointFor(ws.getSecondNode()).getInView();
     1403                highlightSegs.moveTo(p1.getX(), p1.getY());
     1404                highlightSegs.lineTo(p2.getX(), p2.getY());
    14371405            }
    14381406
     
    14401408        }
    14411409
    1442         Iterator<Point> it = new OffsetIterator(wayNodes, offset);
     1410        MapViewPoint lastPoint = null;
     1411        double wayLength = 0;
     1412        Iterator<MapViewPoint> it = new OffsetIterator(wayNodes, offset);
    14431413        while (it.hasNext()) {
    1444             Point p = it.next();
     1414            MapViewPoint p = it.next();
    14451415            if (lastPoint != null) {
    1446                 Point p1 = lastPoint;
    1447                 Point p2 = p;
    1448 
    1449                 /**
    1450                  * Do custom clipping to work around openjdk bug. It leads to
    1451                  * drawing artefacts when zooming in a lot. (#4289, #4424)
    1452                  * (Looks like int overflow.)
    1453                  */
    1454                 LineClip clip = new LineClip(p1, p2, bounds);
    1455                 if (clip.execute()) {
    1456                     if (!p1.equals(clip.getP1())) {
    1457                         p1 = clip.getP1();
    1458                         path.moveTo(p1.x, p1.y);
    1459                     } else if (initialMoveToNeeded) {
    1460                         initialMoveToNeeded = false;
    1461                         path.moveTo(p1.x, p1.y);
    1462                     }
    1463                     p2 = clip.getP2();
    1464                     path.lineTo(p2.x, p2.y);
    1465 
    1466                     /* draw arrow */
    1467                     if (showHeadArrowOnly ? !it.hasNext() : showOrientation) {
    1468                         final double segmentLength = p1.distance(p2);
    1469                         if (segmentLength != 0) {
    1470                             final double l = (10. + line.getLineWidth()) / segmentLength;
    1471 
    1472                             final double sx = l * (p1.x - p2.x);
    1473                             final double sy = l * (p1.y - p2.y);
    1474 
    1475                             orientationArrows.moveTo(p2.x + cosPHI * sx - sinPHI * sy, p2.y + sinPHI * sx + cosPHI * sy);
    1476                             orientationArrows.lineTo(p2.x, p2.y);
    1477                             orientationArrows.lineTo(p2.x + cosPHI * sx + sinPHI * sy, p2.y - sinPHI * sx + cosPHI * sy);
     1416                MapViewPoint p1 = lastPoint;
     1417                MapViewPoint p2 = p;
     1418
     1419                if (initialMoveToNeeded) {
     1420                    initialMoveToNeeded = false;
     1421                    path.moveTo(p1);
     1422                }
     1423                path.lineTo(p2);
     1424
     1425                /* draw arrow */
     1426                if (showHeadArrowOnly ? !it.hasNext() : showOrientation) {
     1427                    //TODO: Cache
     1428                    ArrowPaintHelper drawHelper = new ArrowPaintHelper(PHI, 10 + line.getLineWidth());
     1429                    drawHelper.paintArrowAt(orientationArrows, p2, p1);
     1430                }
     1431                if (showOneway) {
     1432                    final double segmentLength = p1.distanceToInView(p2);
     1433                    if (segmentLength != 0) {
     1434                        final double nx = (p2.getInViewX() - p1.getInViewX()) / segmentLength;
     1435                        final double ny = (p2.getInViewY() - p1.getInViewY()) / segmentLength;
     1436
     1437                        final double interval = 60;
     1438                        // distance from p1
     1439                        double dist = interval - (wayLength % interval);
     1440
     1441                        while (dist < segmentLength) {
     1442                            appenOnewayPath(onewayReversed, p1, nx, ny, dist, 3d, onewayArrowsCasing);
     1443                            appenOnewayPath(onewayReversed, p1, nx, ny, dist, 2d, onewayArrows);
     1444                            dist += interval;
    14781445                        }
    14791446                    }
    1480                     if (showOneway) {
    1481                         final double segmentLength = p1.distance(p2);
    1482                         if (segmentLength != 0) {
    1483                             final double nx = (p2.x - p1.x) / segmentLength;
    1484                             final double ny = (p2.y - p1.y) / segmentLength;
    1485 
    1486                             final double interval = 60;
    1487                             // distance from p1
    1488                             double dist = interval - (wayLength % interval);
    1489 
    1490                             while (dist < segmentLength) {
    1491                                 for (int i = 0; i < 2; ++i) {
    1492                                     double onewaySize = i == 0 ? 3d : 2d;
    1493                                     GeneralPath onewayPath = i == 0 ? onewayArrowsCasing : onewayArrows;
    1494 
    1495                                     // scale such that border is 1 px
    1496                                     final double fac = -(onewayReversed ? -1 : 1) * onewaySize * (1 + sinPHI) / (sinPHI * cosPHI);
    1497                                     final double sx = nx * fac;
    1498                                     final double sy = ny * fac;
    1499 
    1500                                     // Attach the triangle at the incenter and not at the tip.
    1501                                     // Makes the border even at all sides.
    1502                                     final double x = p1.x + nx * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
    1503                                     final double y = p1.y + ny * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
    1504 
    1505                                     onewayPath.moveTo(x, y);
    1506                                     onewayPath.lineTo(x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy);
    1507                                     onewayPath.lineTo(x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy);
    1508                                     onewayPath.lineTo(x, y);
    1509                                 }
    1510                                 dist += interval;
    1511                             }
    1512                         }
    1513                         wayLength += segmentLength;
    1514                     }
     1447                    wayLength += segmentLength;
    15151448                }
    15161449            }
     
    15211454        }
    15221455        displaySegments(path, orientationArrows, onewayArrows, onewayArrowsCasing, color, line, dashes, dashedColor);
     1456    }
     1457
     1458    private void appenOnewayPath(boolean onewayReversed, MapViewPoint p1, double nx, double ny, double dist,
     1459            double onewaySize, Path2D onewayPath) {
     1460        // scale such that border is 1 px
     1461        final double fac = -(onewayReversed ? -1 : 1) * onewaySize * (1 + sinPHI) / (sinPHI * cosPHI);
     1462        final double sx = nx * fac;
     1463        final double sy = ny * fac;
     1464
     1465        // Attach the triangle at the incenter and not at the tip.
     1466        // Makes the border even at all sides.
     1467        final double x = p1.getInViewX() + nx * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
     1468        final double y = p1.getInViewY() + ny * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI));
     1469
     1470        onewayPath.moveTo(x, y);
     1471        onewayPath.lineTo(x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy);
     1472        onewayPath.lineTo(x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy);
     1473        onewayPath.lineTo(x, y);
    15231474    }
    15241475
     
    17121663    }
    17131664
     1665    /**
     1666     * Test if the area is visible
     1667     * @param area The area, interpreted in east/north space.
     1668     * @return true if it is visible.
     1669     */
    17141670    private boolean isAreaVisible(Path2D.Double area) {
    17151671        Rectangle2D bounds = area.getBounds2D();
    17161672        if (bounds.isEmpty()) return false;
    1717         Point2D p = nc.getPoint2D(new EastNorth(bounds.getX(), bounds.getY()));
    1718         if (p.getX() > nc.getWidth()) return false;
    1719         if (p.getY() < 0) return false;
    1720         p = nc.getPoint2D(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));
    1721         if (p.getX() < 0) return false;
    1722         if (p.getY() > nc.getHeight()) return false;
     1673        MapViewPoint p = mapState.getPointFor(new EastNorth(bounds.getX(), bounds.getY()));
     1674        if (p.getInViewX() > mapState.getViewWidth()) return false;
     1675        if (p.getInViewY() < 0) return false;
     1676        p = mapState.getPointFor(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));
     1677        if (p.getInViewX() < 0) return false;
     1678        if (p.getInViewY() > mapState.getViewHeight()) return false;
    17231679        return true;
    17241680    }
     
    17361692    }
    17371693
    1738     private static double[] pointAt(double t, Polygon poly, double pathLength) {
     1694    private static double[] pointAt(double t, List<MapViewPoint> poly, double pathLength) {
    17391695        double totalLen = t * pathLength;
    17401696        double curLen = 0;
    1741         long dx, dy;
     1697        double dx, dy;
    17421698        double segLen;
    17431699
    17441700        // Yes, it is inefficient to iterate from the beginning for each glyph.
    17451701        // Can be optimized if it turns out to be slow.
    1746         for (int i = 1; i < poly.npoints; ++i) {
    1747             dx = (long) poly.xpoints[i] - poly.xpoints[i-1];
    1748             dy = (long) poly.ypoints[i] - poly.ypoints[i-1];
     1702        for (int i = 1; i < poly.size(); ++i) {
     1703            dx = poly.get(i).getInViewX() - poly.get(i - 1).getInViewX();
     1704            dy = poly.get(i).getInViewY() - poly.get(i - 1).getInViewY();
    17491705            segLen = Math.sqrt(dx*dx + dy*dy);
    17501706            if (totalLen > curLen + segLen) {
     
    17531709            }
    17541710            return new double[] {
    1755                     poly.xpoints[i-1]+(totalLen - curLen)/segLen*dx,
    1756                     poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
     1711                    poly.get(i - 1).getInViewX() + (totalLen - curLen) / segLen * dx,
     1712                    poly.get(i - 1).getInViewY() + (totalLen - curLen) / segLen * dy,
    17571713                    Math.atan2(dy, dx)};
    17581714        }
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java

    r10378 r10827  
    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;
     
    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
     
    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
     
    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)} */
     
    117116        connectionColor = PaintColors.CONNECTION.get();
    118117
    119         if (taggedColor != nodeColor) {
     118        if (!taggedColor.equals(nodeColor)) {
    120119            taggedConnectionColor = taggedColor;
    121120        } else {
     
    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();
     
    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);
     
    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;
    360                 }
    361 
    362                 g.drawOval(p.x-4, p.y-4, 9, 9);
     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));
     358                }
     359
    363360            } else if (m.isWay()) {
    364361                GeneralPath path = new GeneralPath();
     
    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                }
     
    393390    public void drawNode(Node n, Color color, int size, boolean fill) {
    394391        if (size > 1) {
     392            MapViewPoint p = mapState.getPointFor(n);
     393            if (!p.isInView())
     394                return;
    395395            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()))
    399                 return;
     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);
    406             }
     399                g.fill(shape);
     400            }
     401            g.draw(shape);
    407402        }
    408403    }
     
    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
    417      */
    418     protected void drawSegment(GeneralPath path, Point p1, Point p2, boolean showDirection) {
     412     * @since 10826
     413     */
     414    protected void drawSegment(MapPath2D path, MapViewPoint mv1, MapViewPoint mv2, boolean showDirection) {
    419415        Rectangle bounds = g.getClipBounds();
    420416        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 
     417        if (mv1.rectTo(mv2).isInView()) {
     418            path.moveTo(mv1);
     419            path.lineTo(mv2);
    428420            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);
     421                ARROW_PAINT_HELPER.paintArrowAt(path, mv2, mv1);
    437422            }
    438423        }
     
    446431     * @param col The color to use for drawing line.
    447432     * @param showDirection <code>true</code> if segment direction should be indicated.
    448      */
    449     protected void drawSegment(Point p1, Point p2, Color col, boolean showDirection) {
    450         if (col != currentColor) {
     433     * @since 10826
     434     */
     435    protected void drawSegment(MapViewPoint p1, MapViewPoint p2, Color col, boolean showDirection) {
     436        if (!col.equals(currentColor)) {
    451437            displaySegments(col);
    452438        }
     
    470456            g.setColor(currentColor);
    471457            g.draw(currentPath);
    472             currentPath = new GeneralPath();
     458            currentPath = new MapPath2D();
    473459            currentColor = newColor;
    474460        }
  • trunk/src/org/openstreetmap/josm/data/projection/Projection.java

    r10805 r10827  
    103103     */
    104104    boolean switchXY();
     105
     106    /**
     107     * Gets the object used as cache identifier when caching results of this projection.
     108     * @return The object to use as cache key
     109     * @since 10826
     110     */
     111    default Object getCacheKey() {
     112        return this;
     113    }
    105114}
  • trunk/src/org/openstreetmap/josm/gui/MapViewState.java

    r10806 r10827  
    44import java.awt.Container;
    55import java.awt.Point;
    6 import java.awt.Rectangle;
    76import java.awt.geom.AffineTransform;
    87import java.awt.geom.Area;
     
    1918import org.openstreetmap.josm.data.coor.EastNorth;
    2019import org.openstreetmap.josm.data.coor.LatLon;
     20import org.openstreetmap.josm.data.osm.Node;
    2121import org.openstreetmap.josm.data.projection.Projecting;
    2222import org.openstreetmap.josm.data.projection.Projection;
     
    3030 */
    3131public final class MapViewState {
     32
     33    /**
     34     * A flag indicating that the point is outside to the top of the map view.
     35     * @since 10826
     36     */
     37    public static final int OUTSIDE_TOP = 1;
     38
     39    /**
     40     * A flag indicating that the point is outside to the bottom of the map view.
     41     * @since 10826
     42     */
     43    public static final int OUTSIDE_BOTTOM = 2;
     44
     45    /**
     46     * A flag indicating that the point is outside to the left of the map view.
     47     * @since 10826
     48     */
     49    public static final int OUTSIDE_LEFT = 3;
     50
     51    /**
     52     * A flag indicating that the point is outside to the right of the map view.
     53     * @since 10826
     54     */
     55    public static final int OUTSIDE_RIGHT = 4;
    3256
    3357    private final Projecting projecting;
     
    158182
    159183    /**
     184     * Gets the {@link MapViewPoint} for the given node. This is faster than {@link #getPointFor(LatLon)} because it uses the node east/north
     185     * cache.
     186     * @param node The node
     187     * @return The position of that node.
     188     * @since 10826
     189     */
     190    public MapViewPoint getPointFor(Node node) {
     191        return getPointFor(node.getEastNorth(getProjection()));
     192    }
     193
     194    /**
    160195     * Gets a rectangle representing the whole view area.
    161196     * @return The rectangle.
     
    169204     * @param rectangle The rectangle to get.
    170205     * @return The view area.
    171      * @since 10458
    172      */
    173     public MapViewRectangle getViewArea(Rectangle rectangle) {
     206     * @since 10826
     207     */
     208    public MapViewRectangle getViewArea(Rectangle2D rectangle) {
    174209        return getForView(rectangle.getMinX(), rectangle.getMinY()).rectTo(getForView(rectangle.getMaxX(), rectangle.getMaxY()));
    175210    }
     
    331366        }
    332367
    333         protected abstract double getInViewX();
    334 
    335         protected abstract double getInViewY();
     368        /**
     369         * Get the x coordinate in view space without creating an intermediate object.
     370         * @return The x coordinate
     371         * @since 10826
     372         */
     373        public abstract double getInViewX();
     374
     375        /**
     376         * Get the y coordinate in view space without creating an intermediate object.
     377         * @return The y coordinate
     378         * @since 10826
     379         */
     380        public abstract double getInViewY();
    336381
    337382        /**
     
    399444            return new MapViewEastNorthPoint(getEastNorth().add(en));
    400445        }
     446
     447        /**
     448         * Check if this point is inside the view bounds.
     449         *
     450         * This is the case iff <code>getOutsideRectangleFlags(getViewArea())</code> returns no flags
     451         * @return true if it is.
     452         * @since 10826
     453         */
     454        public boolean isInView() {
     455            return inRange(getInViewX(), 0, getViewWidth()) && inRange(getInViewY(), 0, getViewHeight());
     456        }
     457
     458        private boolean inRange(double val, int min, double max) {
     459            return val >= min && val < max;
     460        }
     461
     462        /**
     463         * Gets the direction in which this point is outside of the given view rectangle.
     464         * @param rect The rectangle to check agains.
     465         * @return The direction in which it is outside of the view, as OUTSIDE_... flags.
     466         * @since 10826
     467         */
     468        public int getOutsideRectangleFlags(MapViewRectangle rect) {
     469            Rectangle2D bounds = rect.getInView();
     470            int flags = 0;
     471            if (getInViewX() < bounds.getMinX()) {
     472                flags |= OUTSIDE_LEFT;
     473            } else if (getInViewX() > bounds.getMaxX()) {
     474                flags |= OUTSIDE_RIGHT;
     475            }
     476            if (getInViewY() < bounds.getMinY()) {
     477                flags |= OUTSIDE_TOP;
     478            } else if (getInViewY() > bounds.getMaxY()) {
     479                flags |= OUTSIDE_BOTTOM;
     480            }
     481
     482            return flags;
     483        }
     484
     485        /**
     486         * Gets the sum of the x/y view distances between the points. |x1 - x2| + |y1 - y2|
     487         * @param p2 The other point
     488         * @return The norm
     489         * @since 10826
     490         */
     491        public double oneNormInView(MapViewPoint p2) {
     492            return Math.abs(getInViewX() - p2.getInViewX()) + Math.abs(getInViewY()) - p2.getInViewY();
     493        }
     494
     495        /**
     496         * Gets the squared distance between this point and an other point.
     497         * @param p2 The other point
     498         * @return The squared distance.
     499         * @since 10826
     500         */
     501        public double distanceToInViewSq(MapViewPoint p2) {
     502            double dx = getInViewX() - p2.getInViewX();
     503            double dy = getInViewY() - p2.getInViewY();
     504            return dx * dx + dy * dy;
     505        }
     506
     507        /**
     508         * Gets the distance between this point and an other point.
     509         * @param p2 The other point
     510         * @return The distance.
     511         * @since 10826
     512         */
     513        public double distanceToInView(MapViewPoint p2) {
     514            return Math.sqrt(distanceToInViewSq(p2));
     515        }
    401516    }
    402517
     
    411526
    412527        @Override
    413         protected double getInViewX() {
     528        public double getInViewX() {
    414529            return x;
    415530        }
    416531
    417532        @Override
    418         protected double getInViewY() {
     533        public double getInViewY() {
    419534            return y;
    420535        }
     
    435550
    436551        @Override
    437         protected double getInViewX() {
     552        public double getInViewX() {
    438553            return (eastNorth.east() - topLeft.east()) / scale;
    439554        }
    440555
    441556        @Override
    442         protected double getInViewY() {
     557        public double getInViewY() {
    443558            return (topLeft.north() - eastNorth.north()) / scale;
    444559        }
     
    517632            return new Rectangle2D.Double(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
    518633        }
     634
     635        /**
     636         * Check if the rectangle intersects the map view area.
     637         * @return <code>true</code> if it intersects.
     638         * @since 10826
     639         */
     640        public boolean isInView() {
     641            return getInView().intersects(getViewArea().getInView());
     642        }
    519643    }
    520644
  • trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java

    r10657 r10827  
    504504    }
    505505
    506     // looses precision, may overflow (depends on p and current scale)
    507     //@Deprecated
     506    /**
     507     * @deprecated looses precision, may overflow (depends on p and current scale)
     508     */
     509    @Deprecated
    508510    public Point getPoint(EastNorth p) {
    509511        Point2D d = getPoint2D(p);
     
    511513    }
    512514
    513     // looses precision, may overflow (depends on p and current scale)
    514     //@Deprecated
     515    /**
     516     * @deprecated looses precision, may overflow (depends on p and current scale)
     517     */
     518    @Deprecated
    515519    public Point getPoint(LatLon latlon) {
    516520        Point2D d = getPoint2D(latlon);
     
    518522    }
    519523
    520     // looses precision, may overflow (depends on p and current scale)
    521     //@Deprecated
     524    /**
     525     * @deprecated looses precision, may overflow (depends on p and current scale)
     526     */
     527    @Deprecated
    522528    public Point getPoint(Node n) {
    523529        Point2D d = getPoint2D(n);
  • trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java

    r10824 r10827  
    230230                    // Re-load data when display preference change
    231231                    updateSelection();
    232                 }};
     232                }
     233            };
    233234
    234235    private final transient TaggingPresetHandler presetHandler = new TaggingPresetHandler() {
  • trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java

    r10760 r10827  
    77import java.awt.Stroke;
    88import java.util.Objects;
     9import java.util.Optional;
    910import java.util.stream.IntStream;
    1011
     
    2324import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProvider;
    2425import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.SimpleBoxProvider;
     26import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol.SymbolShape;
    2527import org.openstreetmap.josm.gui.util.RotationAngle;
    2628import org.openstreetmap.josm.tools.CheckParameterUtil;
     
    3335    public final MapImage mapImage;
    3436    public final RotationAngle mapImageAngle;
     37    /**
     38     * The symbol that should be used for drawing this node.
     39     */
    3540    public final Symbol symbol;
    36 
    37     public enum SymbolShape { SQUARE, CIRCLE, TRIANGLE, PENTAGON, HEXAGON, HEPTAGON, OCTAGON, NONAGON, DECAGON }
    38 
    39     public static class Symbol {
    40         public SymbolShape symbol;
    41         public int size;
    42         public Stroke stroke;
    43         public Color strokeColor;
    44         public Color fillColor;
    45 
    46         public Symbol(SymbolShape symbol, int size, Stroke stroke, Color strokeColor, Color fillColor) {
    47             if (stroke != null && strokeColor == null)
    48                 throw new IllegalArgumentException("Stroke given without color");
    49             if (stroke == null && fillColor == null)
    50                 throw new IllegalArgumentException("Either a stroke or a fill color must be given");
    51             this.symbol = symbol;
    52             this.size = size;
    53             this.stroke = stroke;
    54             this.strokeColor = strokeColor;
    55             this.fillColor = fillColor;
    56         }
    57 
    58         @Override
    59         public boolean equals(Object obj) {
    60             if (obj == null || getClass() != obj.getClass())
    61                 return false;
    62             final Symbol other = (Symbol) obj;
    63             return symbol == other.symbol &&
    64                     size == other.size &&
    65                     Objects.equals(stroke, other.stroke) &&
    66                     Objects.equals(strokeColor, other.strokeColor) &&
    67                     Objects.equals(fillColor, other.fillColor);
    68         }
    69 
    70         @Override
    71         public int hashCode() {
    72             return Objects.hash(symbol, size, stroke, strokeColor, fillColor);
    73         }
    74 
    75         @Override
    76         public String toString() {
    77             return "symbol=" + symbol + " size=" + size +
    78                     (stroke != null ? " stroke=" + stroke + " strokeColor=" + strokeColor : "") +
    79                     (fillColor != null ? " fillColor=" + fillColor : "");
    80         }
    81     }
    8241
    8342    private static final String[] ICON_KEYS = {ICON_IMAGE, ICON_WIDTH, ICON_HEIGHT, ICON_OPACITY, ICON_OFFSET_X, ICON_OFFSET_Y};
     
    194153    private static Symbol createSymbol(Environment env) {
    195154        Cascade c = env.mc.getCascade(env.layer);
    196         Cascade cDef = env.mc.getCascade("default");
    197 
    198         SymbolShape shape;
     155
    199156        Keyword shapeKW = c.get("symbol-shape", null, Keyword.class);
    200157        if (shapeKW == null)
    201158            return null;
    202         if ("square".equals(shapeKW.val)) {
    203             shape = SymbolShape.SQUARE;
    204         } else if ("circle".equals(shapeKW.val)) {
    205             shape = SymbolShape.CIRCLE;
    206         } else if ("triangle".equals(shapeKW.val)) {
    207             shape = SymbolShape.TRIANGLE;
    208         } else if ("pentagon".equals(shapeKW.val)) {
    209             shape = SymbolShape.PENTAGON;
    210         } else if ("hexagon".equals(shapeKW.val)) {
    211             shape = SymbolShape.HEXAGON;
    212         } else if ("heptagon".equals(shapeKW.val)) {
    213             shape = SymbolShape.HEPTAGON;
    214         } else if ("octagon".equals(shapeKW.val)) {
    215             shape = SymbolShape.OCTAGON;
    216         } else if ("nonagon".equals(shapeKW.val)) {
    217             shape = SymbolShape.NONAGON;
    218         } else if ("decagon".equals(shapeKW.val)) {
    219             shape = SymbolShape.DECAGON;
    220         } else
     159        Optional<SymbolShape> shape = SymbolShape.forName(shapeKW.val);
     160        if (!shape.isPresent()) {
    221161            return null;
    222 
     162        }
     163
     164        Cascade cDef = env.mc.getCascade("default");
    223165        Float sizeOnDefault = cDef.get("symbol-size", null, Float.class);
    224166        if (sizeOnDefault != null && sizeOnDefault <= 0) {
     
    268210        }
    269211
    270         return new Symbol(shape, Math.round(size), stroke, strokeColor, fillColor);
     212        return new Symbol(shape.get(), Math.round(size), stroke, strokeColor, fillColor);
    271213    }
    272214
     
    280222                        mapImageAngle == null ? 0.0 : mapImageAngle.getRotationAngle(primitive));
    281223            } else if (symbol != null) {
    282                 Color fillColor = symbol.fillColor;
    283                 if (fillColor != null) {
    284                     if (painter.isInactiveMode() || n.isDisabled()) {
    285                         fillColor = settings.getInactiveColor();
    286                     } else if (defaultSelectedHandling && selected) {
    287                         fillColor = settings.getSelectedColor(fillColor.getAlpha());
    288                     } else if (member) {
    289                         fillColor = settings.getRelationSelectedColor(fillColor.getAlpha());
    290                     }
    291                 }
    292                 Color strokeColor = symbol.strokeColor;
    293                 if (strokeColor != null) {
    294                     if (painter.isInactiveMode() || n.isDisabled()) {
    295                         strokeColor = settings.getInactiveColor();
    296                     } else if (defaultSelectedHandling && selected) {
    297                         strokeColor = settings.getSelectedColor(strokeColor.getAlpha());
    298                     } else if (member) {
    299                         strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha());
    300                     }
    301                 }
    302                 painter.drawNodeSymbol(n, symbol, fillColor, strokeColor);
     224                paintWithSymbol(settings, painter, selected, member, n);
    303225            } else {
    304226                Color color;
     
    344266    }
    345267
     268    private void paintWithSymbol(MapPaintSettings settings, StyledMapRenderer painter, boolean selected, boolean member,
     269            Node n) {
     270        Color fillColor = symbol.fillColor;
     271        if (fillColor != null) {
     272            if (painter.isInactiveMode() || n.isDisabled()) {
     273                fillColor = settings.getInactiveColor();
     274            } else if (defaultSelectedHandling && selected) {
     275                fillColor = settings.getSelectedColor(fillColor.getAlpha());
     276            } else if (member) {
     277                fillColor = settings.getRelationSelectedColor(fillColor.getAlpha());
     278            }
     279        }
     280        Color strokeColor = symbol.strokeColor;
     281        if (strokeColor != null) {
     282            if (painter.isInactiveMode() || n.isDisabled()) {
     283                strokeColor = settings.getInactiveColor();
     284            } else if (defaultSelectedHandling && selected) {
     285                strokeColor = settings.getSelectedColor(strokeColor.getAlpha());
     286            } else if (member) {
     287                strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha());
     288            }
     289        }
     290        painter.drawNodeSymbol(n, symbol, fillColor, strokeColor);
     291    }
     292
    346293    public BoxProvider getBoxProvider() {
    347294        if (mapImage != null)
Note: See TracChangeset for help on using the changeset viewer.