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

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • 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        }
Note: See TracChangeset for help on using the changeset viewer.