Changeset 10827 in josm for trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
- Timestamp:
- 2016-08-17T20:14:58+02:00 (8 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
r10697 r10827 12 12 import java.awt.Image; 13 13 import java.awt.Point; 14 import java.awt.Polygon;15 14 import java.awt.Rectangle; 16 15 import java.awt.RenderingHints; … … 26 25 import java.awt.geom.Point2D; 27 26 import java.awt.geom.Rectangle2D; 27 import java.awt.geom.RoundRectangle2D; 28 28 import java.util.ArrayList; 29 29 import java.util.Collection; 30 30 import java.util.Collections; 31 import java.util.Comparator; 31 32 import java.util.HashMap; 32 33 import java.util.Iterator; … … 38 39 import java.util.concurrent.RecursiveTask; 39 40 import java.util.function.Supplier; 41 import java.util.stream.Collectors; 40 42 41 43 import javax.swing.AbstractButton; … … 59 61 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData; 60 62 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 63 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 61 64 import org.openstreetmap.josm.gui.NavigatableComponent; 62 65 import org.openstreetmap.josm.gui.mappaint.ElemStyles; … … 70 73 import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage; 71 74 import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement; 72 import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement.Symbol;73 75 import org.openstreetmap.josm.gui.mappaint.styleelement.RepeatImageElement.LineImageAlignment; 74 76 import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement; 77 import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol; 75 78 import org.openstreetmap.josm.gui.mappaint.styleelement.TextLabel; 76 79 import org.openstreetmap.josm.tools.CompositeList; … … 97 100 * perfect way, but it should not throw an exception. 98 101 */ 99 private class OffsetIterator implements Iterator< Point> {102 private class OffsetIterator implements Iterator<MapViewPoint> { 100 103 101 104 private final List<Node> nodes; … … 103 106 private int idx; 104 107 105 private Point prev;108 private MapViewPoint prev; 106 109 /* 'prev0' is a point that has distance 'offset' from 'prev' and the 107 110 * line from 'prev' to 'prev0' is perpendicular to the way segment from 108 111 * 'prev' to the next point. 109 112 */ 110 private int xPrev0, yPrev0; 113 private double xPrev0; 114 private double yPrev0; 111 115 112 116 OffsetIterator(List<Node> nodes, double offset) { … … 122 126 123 127 @Override 124 public Point next() {128 public MapViewPoint next() { 125 129 if (!hasNext()) 126 130 throw new NoSuchElementException(); 127 131 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 } 132 138 133 139 if (idx == nodes.size() - 1) { 134 140 ++idx; 135 141 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()); 137 144 } else { 138 145 return current; … … 140 147 } 141 148 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) { 149 156 lenNext = 1; // value does not matter, because dy_next and dx_next is 0 150 157 } 151 158 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; 154 161 155 162 if (idx == 0) { … … 158 165 xPrev0 = xCurrent0; 159 166 yPrev0 = yCurrent0; 160 return new Point(xCurrent0, yCurrent0);167 return mapState.getForView(xCurrent0, yCurrent0); 161 168 } 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(); 164 171 165 172 // determine intersection of the lines parallel to the two segments 166 intdet = dxNext*dyPrev - dxPrev*dyNext;167 168 if ( det == 0) {173 double det = dxNext*dyPrev - dxPrev*dyNext; 174 175 if (Utils.equalsEpsilon(det, 0)) { 169 176 ++idx; 170 177 prev = current; 171 178 xPrev0 = xCurrent0; 172 179 yPrev0 = yCurrent0; 173 return new Point(xCurrent0, yCurrent0);174 } 175 176 intm = 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; 180 187 ++idx; 181 188 prev = current; 182 189 xPrev0 = xCurrent0; 183 190 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)); 186 197 } 187 198 … … 376 387 } 377 388 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, 394 390 Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) { 395 391 g.setColor(isInactiveMode ? inactiveColor : color); … … 489 485 MapImage fillImage, Float extent, Path2D.Double pfClip, boolean disabled, TextLabel text) { 490 486 491 Shape area = path.createTransformedShape( nc.getAffineTransform());487 Shape area = path.createTransformedShape(mapState.getAffineTransform()); 492 488 493 489 if (!isOutlineOnly) { … … 504 500 Shape clip = area; 505 501 if (pfClip != null) { 506 clip = pfClip.createTransformedShape( nc.getAffineTransform());502 clip = pfClip.createTransformedShape(mapState.getAffineTransform()); 507 503 } 508 504 g.clip(clip); … … 527 523 Shape fill = area; 528 524 if (pfClip != null) { 529 fill = pfClip.createTransformedShape( nc.getAffineTransform());525 fill = pfClip.createTransformedShape(mapState.getAffineTransform()); 530 526 } 531 527 g.fill(fill); … … 699 695 return; 700 696 701 Point p = nc.getPoint(n);697 MapViewPoint p = mapState.getPointFor(n); 702 698 TextLabel text = bs.text; 703 699 String s = text.labelCompositionStrategy.compose(n); … … 707 703 g.setFont(text.font); 708 704 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); 711 707 /** 712 708 * … … 770 766 final int imgHeight = pattern.getHeight(); 771 767 772 Point lastP = null;773 768 double currentWayLength = phase % repeat; 774 769 if (currentWayLength < 0) { … … 794 789 } 795 790 791 MapViewPoint lastP = null; 796 792 OffsetIterator it = new OffsetIterator(way.getNodes(), offset); 797 793 while (it.hasNext()) { 798 Point thisP = it.next();794 MapViewPoint thisP = it.next(); 799 795 800 796 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(); 805 801 806 802 // pos is the position from the beginning of the current segment … … 809 805 810 806 AffineTransform saveTransform = g.getTransform(); 811 g.translate(lastP. x, lastP.y);807 g.translate(lastP.getInViewX(), lastP.getInViewY()); 812 808 g.rotate(Math.atan2(dy, dx)); 813 809 … … 850 846 return; 851 847 852 Point p = nc.getPoint(n);848 MapViewPoint p = mapState.getPointFor(n); 853 849 854 850 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()) { 860 855 int radius = size / 2; 861 856 … … 865 860 g.setColor(color); 866 861 } 862 Rectangle2D rect = new Rectangle2D.Double(p.getInViewX()-radius-1, p.getInViewY()-radius-1, size + 1, size + 1); 867 863 if (fill) { 868 g.fill Rect(p.x-radius-1, p.y-radius-1, size + 1, size + 1);864 g.fill(rect); 869 865 } 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 */ 875 876 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(); 879 881 if (n.isHighlighted()) { 880 drawPointHighlight(p , Math.max(w, h));882 drawPointHighlight(p.getInView(), Math.max(w, h)); 881 883 } 882 884 883 885 float alpha = img.getAlphaFloat(); 884 886 887 Graphics2D temporaryGraphics = (Graphics2D) g.create(); 885 888 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); 892 897 if (selected || member) { 893 898 Color color; … … 900 905 } 901 906 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 */ 906 918 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); 909 920 910 921 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 } 983 938 } 984 939 } … … 994 949 */ 995 950 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); 998 953 drawOrderNumber(p1, p2, orderNumber, clr); 999 954 } … … 1005 960 * @param line line style 1006 961 */ 1007 private void drawPathHighlight( GeneralPathpath, BasicStroke line) {962 private void drawPathHighlight(Path2D path, BasicStroke line) { 1008 963 if (path == null) 1009 964 return; … … 1024 979 * @param size highlight size 1025 980 */ 1026 private void drawPointHighlight(Point p, int size) {981 private void drawPointHighlight(Point2D p, int size) { 1027 982 g.setColor(highlightColorTransparent); 1028 983 int s = size + highlightPointRadius; … … 1030 985 while (s >= size) { 1031 986 int r = (int) Math.floor(s/2d); 1032 g.fill RoundRect(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)); 1033 988 s -= highlightStep; 1034 989 } … … 1219 1174 1220 1175 /** 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 /** 1221 1213 * Draws a text along a given way. 1222 1214 * @param way The way to draw the text on. … … 1235 1227 Rectangle bounds = g.getClipBounds(); 1236 1228 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()); 1242 1230 1243 1231 // 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); 1287 1235 1288 1236 if (rec.getWidth() > pathLength) … … 1291 1239 double t1, t2; 1292 1240 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 1319 1248 // The space left and right of the text should be distributed 20% - 80% (towards the center), 1320 1249 // but the smaller space should not be less than 7 px. 1321 1250 // However, if the total remaining space is less than 14 px, then distribute it evenly. 1322 1251 double smallerSpace = Math.min(Math.max(0.2 * remaining, 7), 0.5 * remaining); 1323 if ((best End + bestStart)/2 < pathLength/2) {1324 t2 = best End - smallerSpace;1252 if ((best.end + best.start)/2 < pathLength/2) { 1253 t2 = best.end - smallerSpace; 1325 1254 t1 = t2 - rec.getWidth(); 1326 1255 } else { 1327 t1 = best Start + smallerSpace;1256 t1 = best.start + smallerSpace; 1328 1257 t2 = t1 + rec.getWidth(); 1329 1258 } … … 1336 1265 t2 /= pathLength; 1337 1266 1338 double[] p1 = pointAt(t1, po ly, pathLength);1339 double[] p2 = pointAt(t2, po ly, pathLength);1267 double[] p1 = pointAt(t1, points, pathLength); 1268 double[] p2 = pointAt(t2, points, pathLength); 1340 1269 1341 1270 if (p1 == null || p2 == null) … … 1365 1294 Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D(); 1366 1295 double t = tStart + offsetSign * (gvOffset + rect.getX() + rect.getWidth()/2) / pathLength; 1367 double[] p = pointAt(t, po ly, pathLength);1296 double[] p = pointAt(t, points, pathLength); 1368 1297 if (p != null) { 1369 1298 AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]); … … 1383 1312 gvOffset += gvWidth; 1384 1313 } 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; 1385 1355 } 1386 1356 … … 1404 1374 boolean showOneway, boolean onewayReversed) { 1405 1375 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; 1410 1380 Rectangle bounds = g.getClipBounds(); 1411 1381 if (bounds != null) { … … 1414 1384 } 1415 1385 1416 double wayLength = 0;1417 Point lastPoint = null;1418 1386 boolean initialMoveToNeeded = true; 1419 1387 List<Node> wayNodes = way.getNodes(); … … 1431 1399 } 1432 1400 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()); 1437 1405 } 1438 1406 … … 1440 1408 } 1441 1409 1442 Iterator<Point> it = new OffsetIterator(wayNodes, offset); 1410 MapViewPoint lastPoint = null; 1411 double wayLength = 0; 1412 Iterator<MapViewPoint> it = new OffsetIterator(wayNodes, offset); 1443 1413 while (it.hasNext()) { 1444 Point p = it.next();1414 MapViewPoint p = it.next(); 1445 1415 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; 1478 1445 } 1479 1446 } 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; 1515 1448 } 1516 1449 } … … 1521 1454 } 1522 1455 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); 1523 1474 } 1524 1475 … … 1712 1663 } 1713 1664 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 */ 1714 1670 private boolean isAreaVisible(Path2D.Double area) { 1715 1671 Rectangle2D bounds = area.getBounds2D(); 1716 1672 if (bounds.isEmpty()) return false; 1717 Point2D p = nc.getPoint2D(new EastNorth(bounds.getX(), bounds.getY()));1718 if (p.get X() > nc.getWidth()) return false;1719 if (p.get Y() < 0) return false;1720 p = nc.getPoint2D(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));1721 if (p.get X() < 0) return false;1722 if (p.get Y() > 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; 1723 1679 return true; 1724 1680 } … … 1736 1692 } 1737 1693 1738 private static double[] pointAt(double t, Polygonpoly, double pathLength) {1694 private static double[] pointAt(double t, List<MapViewPoint> poly, double pathLength) { 1739 1695 double totalLen = t * pathLength; 1740 1696 double curLen = 0; 1741 longdx, dy;1697 double dx, dy; 1742 1698 double segLen; 1743 1699 1744 1700 // Yes, it is inefficient to iterate from the beginning for each glyph. 1745 1701 // 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(); 1749 1705 segLen = Math.sqrt(dx*dx + dy*dy); 1750 1706 if (totalLen > curLen + segLen) { … … 1753 1709 } 1754 1710 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, 1757 1713 Math.atan2(dy, dx)}; 1758 1714 }
Note:
See TracChangeset
for help on using the changeset viewer.