Ticket #13306: patch-patch-mapview-use-state-in-renderer-2.patch
File patch-patch-mapview-use-state-in-renderer-2.patch, 104.6 KB (added by , 9 years ago) |
---|
-
src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
diff --git a/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java b/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java index 6ac7873..14e2352 100644
a b import java.awt.event.ActionEvent; 16 16 import java.awt.event.KeyEvent; 17 17 import java.awt.event.MouseEvent; 18 18 import java.awt.event.MouseListener; 19 import java.awt.geom.GeneralPath;20 19 import java.util.ArrayList; 21 20 import java.util.Arrays; 22 21 import java.util.Collection; … … import org.openstreetmap.josm.data.osm.Node; 50 49 import org.openstreetmap.josm.data.osm.OsmPrimitive; 51 50 import org.openstreetmap.josm.data.osm.Way; 52 51 import org.openstreetmap.josm.data.osm.WaySegment; 52 import org.openstreetmap.josm.data.osm.visitor.paint.ArrowPaintHelper; 53 import org.openstreetmap.josm.data.osm.visitor.paint.MapPath2D; 53 54 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors; 54 55 import org.openstreetmap.josm.gui.MainMenu; 55 56 import org.openstreetmap.josm.gui.MapFrame; 56 57 import org.openstreetmap.josm.gui.MapView; 58 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 57 59 import org.openstreetmap.josm.gui.NavigatableComponent; 58 60 import org.openstreetmap.josm.gui.layer.Layer; 59 61 import org.openstreetmap.josm.gui.layer.MapViewPaintable; … … import org.openstreetmap.josm.tools.Utils; 74 76 public class DrawAction extends MapMode implements MapViewPaintable, SelectionChangedListener, KeyPressReleaseListener, ModifierListener { 75 77 76 78 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); 78 81 79 82 private final Cursor cursorJoinNode; 80 83 private final Cursor cursorJoinWay; … … public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 1138 1141 g2.setStroke(rubberLineStroke); 1139 1142 } else if (!snapHelper.drawConstructionGeometry) 1140 1143 return; 1141 GeneralPath b = new GeneralPath(); 1142 Point p1 = mv.getPoint(getCurrentBaseNode()); 1143 Point p2 = mv.getPoint(currentMouseEastNorth); 1144 1145 double t = Math.atan2((double) p2.y - p1.y, (double) p2.x - p1.x) + Math.PI; 1144 MapPath2D b = new MapPath2D(); 1145 MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode()); 1146 MapViewPoint p2 = mv.getState().getPointFor(currentMouseEastNorth); 1146 1147 1147 b.moveTo(p1 .x, p1.y);1148 b.lineTo(p2 .x, p2.y);1148 b.moveTo(p1); 1149 b.lineTo(p2); 1149 1150 1150 1151 // if alt key is held ("start new way"), draw a little perpendicular line 1151 1152 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); 1154 1154 } 1155 1155 1156 1156 g2.draw(b); … … public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 1473 1473 public void drawIfNeeded(Graphics2D g2, MapView mv) { 1474 1474 if (!snapOn || !active) 1475 1475 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); 1480 1479 if (drawConstructionGeometry) { 1481 1480 g2.setColor(snapHelperColor); 1482 1481 g2.setStroke(helperStroke); 1483 1482 1484 b = new GeneralPath(); 1483 MapPath2D b = new MapPath2D(); 1484 b.moveTo(p2); 1485 1485 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 1488 1487 } else { 1489 b.moveTo(p2.x, p2.y); 1490 b.lineTo(p3.x, p3.y); 1488 b.lineTo(p3); 1491 1489 } 1492 1490 g2.draw(b); 1493 1491 } 1494 1492 if (projectionSource != null) { 1495 1493 g2.setColor(snapHelperColor); 1496 1494 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)); 1501 1498 g2.draw(b); 1502 1499 } 1503 1500 1504 1501 if (customBaseHeading >= 0) { 1505 1502 g2.setColor(highlightColor); 1506 1503 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)); 1512 1507 g2.draw(b); 1513 1508 } 1514 1509 1515 1510 g2.setColor(rubberLineColor); 1516 1511 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); 1520 1515 g2.draw(b); 1521 1516 1522 g2.drawString(labelText, p3.x-5, p3.y+20);1517 g2.drawString(labelText, (int) p3.getInViewX()-5, (int) p3.getInViewY()+20); 1523 1518 if (showProjectedPoint) { 1524 1519 g2.setStroke(normalStroke); 1525 g2.drawOval( p3.x-5, p3.y-5, 10, 10); // projected point1520 g2.drawOval((int) p3.getInViewX()-5, (int) p3.getInViewY()-5, 10, 10); // projected point 1526 1521 } 1527 1522 1528 1523 g2.setColor(snapHelperColor); -
src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java
diff --git a/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java b/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java index 604dbe5..cd94c1f 100644
a b import java.awt.Point; 12 12 import java.awt.Stroke; 13 13 import java.awt.event.KeyEvent; 14 14 import java.awt.event.MouseEvent; 15 import java.awt.geom.GeneralPath;16 15 import java.util.ArrayList; 17 16 import java.util.Collection; 18 17 import java.util.LinkedList; … … import org.openstreetmap.josm.data.osm.Node; 35 34 import org.openstreetmap.josm.data.osm.OsmPrimitive; 36 35 import org.openstreetmap.josm.data.osm.Way; 37 36 import org.openstreetmap.josm.data.osm.WaySegment; 37 import org.openstreetmap.josm.data.osm.visitor.paint.MapPath2D; 38 38 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors; 39 39 import org.openstreetmap.josm.gui.MapFrame; 40 40 import org.openstreetmap.josm.gui.MapView; 41 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 41 42 import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable; 42 43 import org.openstreetmap.josm.gui.layer.Layer; 43 44 import org.openstreetmap.josm.gui.layer.OsmDataLayer; … … public class ImproveWayAccuracyAction extends MapMode implements 238 239 239 240 List<Node> nodes = targetWay.getNodes(); 240 241 241 GeneralPath b = new GeneralPath();242 MapPath2D b = new MapPath2D(); 242 243 Point p0 = mv.getPoint(nodes.get(0)); 243 244 Point pn; 244 245 b.moveTo(p0.x, p0.y); … … public class ImproveWayAccuracyAction extends MapMode implements 296 297 297 298 298 299 // Drawing preview lines 299 GeneralPath b = new GeneralPath();300 MapPath2D b = new MapPath2D(); 300 301 if (alt && !ctrl) { 301 302 // In delete mode 302 303 if (p1 != null && p2 != null) { … … public class ImproveWayAccuracyAction extends MapMode implements 332 333 } 333 334 } 334 335 335 protected void drawIntersectingWayHelperLines(MapView mv, GeneralPathb) {336 protected void drawIntersectingWayHelperLines(MapView mv, MapPath2D b) { 336 337 for (final OsmPrimitive referrer : candidateNode.getReferrers()) { 337 338 if (!(referrer instanceof Way) || targetWay.equals(referrer)) { 338 339 continue; … … public class ImproveWayAccuracyAction extends MapMode implements 343 344 continue; 344 345 } 345 346 if (i > 0) { 346 final Point p = mv.getPoint(nodes.get(i - 1));347 final MapViewPoint p = mv.getState().getPointFor(nodes.get(i - 1)); 347 348 b.moveTo(mousePos.x, mousePos.y); 348 b.lineTo(p .x, p.y);349 b.lineTo(p); 349 350 } 350 351 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)); 352 353 b.moveTo(mousePos.x, mousePos.y); 353 b.lineTo(p .x, p.y);354 b.lineTo(p); 354 355 } 355 356 } 356 357 } -
src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
diff --git a/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java b/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java index 7d2bcb4..e912a07 100644
a b import org.openstreetmap.josm.data.osm.visitor.paint.WireframeMapRenderer; 41 41 import org.openstreetmap.josm.gui.ExtendedDialog; 42 42 import org.openstreetmap.josm.gui.MapFrame; 43 43 import org.openstreetmap.josm.gui.MapView; 44 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 44 45 import org.openstreetmap.josm.gui.SelectionManager; 45 46 import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded; 46 47 import org.openstreetmap.josm.gui.layer.Layer; … … public class SelectAction extends MapMode implements ModifierListener, KeyPressR 1191 1192 1192 1193 wnp.a = w.getNode(ws.lowerIndex); 1193 1194 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); 1196 1197 if (WireframeMapRenderer.isLargeSegment(p1, p2, virtualSpace)) { 1197 Point2D pc = new Point2D.Double((p1.get X() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);1198 Point2D pc = new Point2D.Double((p1.getInViewX() + p2.getInViewX()) / 2, (p1.getInViewY() + p2.getInViewY()) / 2); 1198 1199 if (p.distanceSq(pc) < virtualSnapDistSq2) { 1199 1200 // Check that only segments on top of each other get added to the 1200 1201 // virtual ways list. Otherwise ways that coincidentally have their -
src/org/openstreetmap/josm/data/osm/Node.java
diff --git a/src/org/openstreetmap/josm/data/osm/Node.java b/src/org/openstreetmap/josm/data/osm/Node.java index 56ffb05..1f40769 100644
a b 2 2 package org.openstreetmap.josm.data.osm; 3 3 4 4 import java.util.Collection; 5 import java.util.Objects; 5 6 import java.util.Set; 6 7 import java.util.TreeSet; 7 8 import java.util.function.Predicate; … … import org.openstreetmap.josm.data.coor.EastNorth; 11 12 import org.openstreetmap.josm.data.coor.LatLon; 12 13 import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; 13 14 import org.openstreetmap.josm.data.osm.visitor.Visitor; 15 import org.openstreetmap.josm.data.projection.Projection; 14 16 import org.openstreetmap.josm.data.projection.Projections; 15 17 import org.openstreetmap.josm.tools.CheckParameterUtil; 16 18 import org.openstreetmap.josm.tools.Utils; … … public final class Node extends OsmPrimitive implements INode { 33 35 */ 34 36 private double east = Double.NaN; 35 37 private double north = Double.NaN; 38 /** 39 * The cache key to use for {@link #east} and {@link #north}. 40 */ 41 private Object eastNorthCacheKey; 36 42 37 43 /** 38 44 * Determines if this node has valid coordinates. … … public final class Node extends OsmPrimitive implements INode { 78 84 * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates. 79 85 * Internally caches the projected coordinates.</p> 80 86 * 81 * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must82 * {@link #invalidateEastNorthCache() invalidate the internal cache}.</p>83 *84 87 * <p>Replies {@code null} if this node doesn't know lat/lon-coordinates, i.e. because it is an incomplete node. 85 88 * 86 89 * @return the east north coordinates or {@code null} … … public final class Node extends OsmPrimitive implements INode { 89 92 */ 90 93 @Override 91 94 public EastNorth getEastNorth() { 92 if (!isLatLonKnown()) return null; 95 return getEastNorth(Main.getProjection()); 96 } 93 97 94 if (getDataSet() == null) 95 // there is no dataset that listens for projection changes 96 // and invalidates the cache, so we don't use the cache at all 97 return Projections.project(new LatLon(lat, lon)); 98 /** 99 * Replies the projected east/north coordinates. 100 * <p> 101 * The result of the last conversion is cached. The cache object is used as cache key. 102 * @param projection The projection to use. 103 * @return The projected east/north coordinates 104 * @since xxx 105 */ 106 public EastNorth getEastNorth(Projection projection) { 107 if (!isLatLonKnown()) return null; 98 108 99 if (Double.isNaN(east) || Double.isNaN(north) ) {109 if (Double.isNaN(east) || Double.isNaN(north) || !Objects.equals(projection.getCacheKey(), eastNorthCacheKey)) { 100 110 // projected coordinates haven't been calculated yet, 101 111 // so fill the cache of the projected node coordinates 102 112 EastNorth en = Projections.project(new LatLon(lat, lon)); 103 113 this.east = en.east(); 104 114 this.north = en.north(); 115 this.eastNorthCacheKey = projection.getCacheKey(); 105 116 } 106 117 return new EastNorth(east, north); 107 118 } … … public final class Node extends OsmPrimitive implements INode { 122 133 this.lon = ll.lon(); 123 134 this.east = eastNorth.east(); 124 135 this.north = eastNorth.north(); 136 this.eastNorthCacheKey = Main.getProjection().getCacheKey(); 125 137 } else { 126 138 this.lat = Double.NaN; 127 139 this.lon = Double.NaN; … … public final class Node extends OsmPrimitive implements INode { 345 357 public void invalidateEastNorthCache() { 346 358 this.east = Double.NaN; 347 359 this.north = Double.NaN; 360 this.eastNorthCacheKey = null; 348 361 } 349 362 350 363 @Override -
src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java index 06a919c..790cd19 100644
a b package org.openstreetmap.josm.data.osm.visitor.paint; 3 3 4 4 import java.awt.Color; 5 5 import java.awt.Graphics2D; 6 import java.awt.Point;7 6 import java.awt.geom.GeneralPath; 8 import java.awt.geom.Point2D; 7 import java.awt.geom.Path2D; 8 import java.awt.geom.Rectangle2D; 9 9 import java.util.Iterator; 10 10 11 11 import org.openstreetmap.josm.Main; … … import org.openstreetmap.josm.data.osm.DataSet; 14 14 import org.openstreetmap.josm.data.osm.Node; 15 15 import org.openstreetmap.josm.data.osm.Way; 16 16 import org.openstreetmap.josm.data.osm.WaySegment; 17 import org.openstreetmap.josm.gui.MapViewState; 18 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 19 import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle; 17 20 import org.openstreetmap.josm.gui.NavigatableComponent; 18 21 import org.openstreetmap.josm.tools.CheckParameterUtil; 19 22 … … public abstract class AbstractMapRenderer implements Rendering { 28 31 /** the map viewport - provides projection and hit detection functionality */ 29 32 protected NavigatableComponent nc; 30 33 34 /** 35 * The {@link MapViewState} to use to convert between coordinates. 36 */ 37 protected final MapViewState mapState; 38 31 39 /** if true, the paint visitor shall render OSM objects such that they 32 40 * look inactive. Example: rendering of data in an inactive layer using light gray as color only. */ 33 41 protected boolean isInactiveMode; … … public abstract class AbstractMapRenderer implements Rendering { 67 75 CheckParameterUtil.ensureParameterNotNull(nc); 68 76 this.g = g; 69 77 this.nc = nc; 78 this.mapState = nc.getState(); 70 79 this.isInactiveMode = isInactiveMode; 71 80 } 72 81 … … public abstract class AbstractMapRenderer implements Rendering { 89 98 * @param orderNumber The number of the segment in the way. 90 99 * @param clr The color to use for drawing the text. 91 100 */ 92 protected void drawOrderNumber( Point p1,Point p2, int orderNumber, Color clr) {101 protected void drawOrderNumber(MapViewPoint p1, MapViewPoint p2, int orderNumber, Color clr) { 93 102 if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) { 94 103 String on = Integer.toString(orderNumber); 95 104 int strlen = on.length(); 96 int x = (p1.x+p2.x)/2 - 4*strlen; 97 int y = (p1.y+p2.y)/2 + 4; 105 double centerX = (p1.getInViewX()+p2.getInViewX())/2; 106 double centerY = (p1.getInViewY()+p2.getInViewY())/2; 107 double x = centerX - 4*strlen; 108 double y = centerY + 4; 98 109 99 110 if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) { 100 y = (p1.y+p2.y)/2- virtualNodeSize - 3;111 y = centerY - virtualNodeSize - 3; 101 112 } 102 113 103 114 g.setColor(backgroundColor); 104 g.fill Rect(x-1, y-12, 8*strlen+1, 14);115 g.fill(new Rectangle2D.Double(x-1, y-12, 8*strlen+1, 14)); 105 116 g.setColor(clr); 106 g.drawString(on, x,y);117 g.drawString(on, (int) x, (int) y); 107 118 } 108 119 } 109 120 … … public abstract class AbstractMapRenderer implements Rendering { 182 193 * @param space The free space to check against. 183 194 * @return <code>true</code> if segment is larger than required space 184 195 */ 185 public static boolean isLargeSegment(Point2D p1, Point2D p2, int space) { 186 double xd = Math.abs(p1.getX()-p2.getX()); 187 double yd = Math.abs(p1.getY()-p2.getY()); 188 return xd + yd > space; 196 public static boolean isLargeSegment(MapViewPoint p1, MapViewPoint p2, int space) { 197 return p1.oneNormInView(p2) > space; 189 198 } 190 199 191 200 /** … … public abstract class AbstractMapRenderer implements Rendering { 193 202 * 194 203 * @param p1 First point of the way segment. 195 204 * @param p2 Second point of the way segment. 196 * @return <code>true</code> if segment isvisible.205 * @return <code>true</code> if segment may be visible. 197 206 */ 198 protected boolean isSegmentVisible(Point p1, Point p2) { 199 if ((p1.x < 0) && (p2.x < 0)) return false; 200 if ((p1.y < 0) && (p2.y < 0)) return false; 201 if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false; 202 if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false; 203 return true; 207 protected boolean isSegmentVisible(MapViewPoint p1, MapViewPoint p2) { 208 MapViewRectangle view = mapState.getViewArea(); 209 // not outside in the same direction 210 return (p1.getOutsideRectangleFlags(view) & p2.getOutsideRectangleFlags(view)) == 0; 204 211 } 205 212 206 213 /** … … public abstract class AbstractMapRenderer implements Rendering { 209 216 * @param path The path to append drawing to. 210 217 * @param w The ways to draw node for. 211 218 */ 212 public void visitVirtual( GeneralPathpath, Way w) {219 public void visitVirtual(Path2D path, Way w) { 213 220 Iterator<Node> it = w.getNodes().iterator(); 214 221 if (it.hasNext()) { 215 Point lastP = nc.getPoint(it.next());222 MapViewPoint lastP = mapState.getPointFor(it.next()); 216 223 while (it.hasNext()) { 217 Point p = nc.getPoint(it.next());224 MapViewPoint p = mapState.getPointFor(it.next()); 218 225 if (isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace)) { 219 int x = (p.x+lastP.x)/2;220 int y = (p.y+lastP.y)/2;221 path.moveTo( (double)x-virtualNodeSize, y);222 path.lineTo( (double)x+virtualNodeSize, y);223 path.moveTo(x, (double)y-virtualNodeSize);224 path.lineTo(x, (double)y+virtualNodeSize);226 double x = (p.getInViewX()+lastP.getInViewX())/2; 227 double y = (p.getInViewY()+lastP.getInViewY())/2; 228 path.moveTo(x-virtualNodeSize, y); 229 path.lineTo(x+virtualNodeSize, y); 230 path.moveTo(x, y-virtualNodeSize); 231 path.lineTo(x, y+virtualNodeSize); 225 232 } 226 233 lastP = p; 227 234 } -
new file src/org/openstreetmap/josm/data/osm/visitor/paint/ArrowPaintHelper.java
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/ArrowPaintHelper.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/ArrowPaintHelper.java new file mode 100644 index 0000000..b45d832
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.osm.visitor.paint; 3 4 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 5 import org.openstreetmap.josm.tools.Utils; 6 7 /** 8 * This class helps with painting arrows with fixed length along a path. 9 * @author Michael Zangl 10 * @since xxx 11 */ 12 public class ArrowPaintHelper { 13 private final double sin; 14 private final double cos; 15 private final double length; 16 17 /** 18 * Creates a new arrow helper. 19 * @param radians The angle of the arrow. 0 means that it lies on the current line. In radians 20 * @param length The length of the arrow lines. 21 */ 22 public ArrowPaintHelper(double radians, double length) { 23 this.sin = Math.sin(radians); 24 this.cos = Math.cos(radians); 25 this.length = length; 26 } 27 28 /** 29 * Paint the arrow 30 * @param path The path to append the arrow to. 31 * @param point The point to paint the tip at 32 * @param fromDirection The direction the line is comming from. 33 */ 34 public void paintArrowAt(MapPath2D path, MapViewPoint point, MapViewPoint fromDirection) { 35 double x = point.getInViewX(); 36 double y = point.getInViewY(); 37 double dx = fromDirection.getInViewX() - x; 38 double dy = fromDirection.getInViewY() - y; 39 double norm = Math.sqrt(dx * dx + dy * dy); 40 if (norm > 1e-10) { 41 dx *= length / norm; 42 dy *= length / norm; 43 path.moveTo(x + dx * cos + dy * sin, y + dx * -sin + dy * cos); 44 if (!Utils.equalsEpsilon(cos, 0)) { 45 path.lineTo(point); 46 } 47 path.lineTo(x + dx * cos + dy * -sin, y + dx * sin + dy * cos); 48 } 49 } 50 } -
src/org/openstreetmap/josm/data/osm/visitor/paint/LineClip.java
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/LineClip.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/LineClip.java index d259a90..ac22448 100644
a b import static java.awt.geom.Rectangle2D.OUT_LEFT; 6 6 import static java.awt.geom.Rectangle2D.OUT_RIGHT; 7 7 import static java.awt.geom.Rectangle2D.OUT_TOP; 8 8 9 import java.awt.Point;10 9 import java.awt.Rectangle; 10 import java.awt.geom.Point2D; 11 import java.awt.geom.Rectangle2D; 11 12 12 13 /** 13 14 * Computes the part of a line that is visible in a given rectangle. 14 15 * Using int leads to overflow, so we need long int. 15 16 */ 16 17 public class LineClip { 17 private Point p1, p2;18 private final Rectangle clipBounds;18 private Point2D p1, p2; 19 private final Rectangle2D clipBounds; 19 20 20 21 /** 21 22 * Constructs a new {@code LineClip}. … … public class LineClip { 23 24 * @param p2 end point of the clipped line 24 25 * @param clipBounds Clip bounds 25 26 */ 26 public LineClip(Point p1, Point p2, RectangleclipBounds) {27 public LineClip(Point2D p1, Point2D p2, Rectangle2D clipBounds) { 27 28 this.p1 = p1; 28 29 this.p2 = p2; 29 30 this.clipBounds = clipBounds; … … public class LineClip { 37 38 if (clipBounds == null) { 38 39 return false; 39 40 } 40 return cohenSutherland(p1.x, p1.y, p2.x, p2.y, clipBounds.x, clipBounds.y, 41 (long) clipBounds.x + clipBounds.width, 42 (long) clipBounds.y + clipBounds.height); 41 return cohenSutherland(p1.getX(), p1.getY(), p2.getX(), p2.getY(), clipBounds.getMinX(), clipBounds.getMinY(), 42 clipBounds.getMaxX(), clipBounds.getMaxY()); 43 43 } 44 44 45 45 /** 46 46 * @return start point of the clipped line 47 47 */ 48 public Point getP1() {48 public Point2D getP1() { 49 49 return p1; 50 50 } 51 51 52 52 /** 53 53 * @return end point of the clipped line 54 54 */ 55 public Point getP2() {55 public Point2D getP2() { 56 56 return p2; 57 57 } 58 58 … … public class LineClip { 69 69 * @param ymax maximal Y coordinate 70 70 * @return true, if line is visible in the given clip region 71 71 */ 72 private boolean cohenSutherland( long x1, long y1, long x2, long y2, long xmin, long ymin, long xmax, longymax) {72 private boolean cohenSutherland(double x1, double y1, double x2, double y2, double xmin, double ymin, double xmax, double ymax) { 73 73 int outcode0, outcode1, outcodeOut; 74 74 boolean accept = false; 75 75 boolean done = false; … … public class LineClip { 84 84 } else if ((outcode0 & outcode1) > 0) { 85 85 done = true; 86 86 } else { 87 longx = 0;88 longy = 0;87 double x = 0; 88 double y = 0; 89 89 outcodeOut = outcode0 != 0 ? outcode0 : outcode1; 90 90 if ((outcodeOut & OUT_TOP) != 0) { 91 91 x = x1 + (x2 - x1) * (ymax - y1)/(y2 - y1); … … public class LineClip { 114 114 while (!done); 115 115 116 116 if (accept) { 117 p1 = new Point ((int) x1, (int)y1);118 p2 = new Point ((int) x2, (int)y2);117 p1 = new Point2D.Double(x1, y1); 118 p2 = new Point2D.Double(x2, y2); 119 119 return true; 120 120 } 121 121 return false; … … public class LineClip { 132 132 * @param ymax maximal Y coordinate 133 133 * @return outcode 134 134 */ 135 private static int computeOutCode( long x, long y, long xmin, long ymin, long xmax, longymax) {135 private static int computeOutCode(double x, double y, double xmin, double ymin, double xmax, double ymax) { 136 136 int code = 0; 137 if (y > ymax) { 137 // ignore rounding errors. 138 if (y > ymax + 1e-10) { 138 139 code |= OUT_TOP; 139 } else if (y < ymin ) {140 } else if (y < ymin - 1e-10) { 140 141 code |= OUT_BOTTOM; 141 142 } 142 if (x > xmax ) {143 if (x > xmax + 1e-10) { 143 144 code |= OUT_RIGHT; 144 } else if (x < xmin ) {145 } else if (x < xmin - 1e-10) { 145 146 code |= OUT_LEFT; 146 147 } 147 148 return code; -
new file src/org/openstreetmap/josm/data/osm/visitor/paint/MapPath2D.java
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPath2D.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPath2D.java new file mode 100644 index 0000000..195a19b
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.osm.visitor.paint; 3 4 import java.awt.geom.Path2D; 5 6 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 7 8 /** 9 * An extension of {@link Path2D} with special methods for map positions. 10 * @author Michael Zangl 11 * @since xxx 12 */ 13 public class MapPath2D extends Path2D.Double { 14 /** 15 * Create a new, empty path. 16 */ 17 public MapPath2D() { 18 // no default definitions 19 } 20 21 /** 22 * Move the path to the view position of given point 23 * @param p The point 24 */ 25 public void moveTo(MapViewPoint p) { 26 moveTo(p.getInViewX(), p.getInViewY()); 27 } 28 29 /** 30 * Draw a line to the view position of given point 31 * @param p The point 32 */ 33 public void lineTo(MapViewPoint p) { 34 lineTo(p.getInViewX(), p.getInViewY()); 35 } 36 } -
src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java index 55405c7..a4f4d5c 100644
a b import java.awt.FontMetrics; 11 11 import java.awt.Graphics2D; 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; 17 16 import java.awt.Shape; … … import java.awt.geom.GeneralPath; 25 24 import java.awt.geom.Path2D; 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; 33 34 import java.util.List; … … import java.util.concurrent.ForkJoinPool; 37 38 import java.util.concurrent.ForkJoinTask; 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; 42 44 import javax.swing.FocusManager; … … import org.openstreetmap.josm.data.osm.visitor.Visitor; 58 60 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 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; 63 66 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; … … import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.Horizonta 69 72 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.VerticalTextAlignment; 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; 77 80 import org.openstreetmap.josm.tools.Geometry; … … public class StyledMapRenderer extends AbstractMapRenderer { 96 99 * There is no intention, to handle consecutive duplicate Nodes in a 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; 102 105 private final double offset; 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) { 113 117 this.nodes = nodes; … … public class StyledMapRenderer extends AbstractMapRenderer { 121 125 } 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++)); 132 MapViewPoint current = getForIndex(idx); 130 133 131 Point current = nc.getPoint(nodes.get(idx)); 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(), yPrev0 + current.getInViewY() - prev.getInViewY()); 137 143 } else { 138 144 return current; 139 145 } 140 146 } 141 147 142 Point next = nc.getPoint(nodes.get(idx+1));148 MapViewPoint next = getForIndex(idx + 1); 143 149 144 int dxNext = next.x - current.x;145 int dyNext = next.y - current.y;146 double lenNext = Math.sqrt( (double) dxNext*dxNext + (double)dyNext*dyNext);150 double dxNext = next.getInViewX() - current.getInViewX(); 151 double dyNext = next.getInViewY() - current.getInViewY(); 152 double lenNext = Math.sqrt(dxNext*dxNext + dyNext*dyNext); 147 153 148 if (lenNext == 0) {154 if (lenNext < 1e-3) { 149 155 lenNext = 1; // value does not matter, because dy_next and dx_next is 0 150 156 } 151 157 152 int xCurrent0 = current.x + (int) Math.round(offset * dyNext / lenNext);153 int yCurrent0 = current.y - (int) Math.round(offset * dxNext / lenNext);158 double xCurrent0 = current.getInViewX() + offset * dyNext / lenNext; 159 double yCurrent0 = current.getInViewY() - offset * dxNext / lenNext; 154 160 155 161 if (idx == 0) { 156 162 ++idx; 157 163 prev = current; 158 164 xPrev0 = xCurrent0; 159 165 yPrev0 = yCurrent0; 160 return new Point(xCurrent0, yCurrent0);166 return mapState.getForView(xCurrent0, yCurrent0); 161 167 } else { 162 int dxPrev = current.x - prev.x;163 int dyPrev = current.y - prev.y;168 double dxPrev = current.getInViewX() - prev.getInViewX(); 169 double dyPrev = current.getInViewY() - prev.getInViewY(); 164 170 165 171 // determine intersection of the lines parallel to the two segments 166 intdet = dxNext*dyPrev - dxPrev*dyNext;172 double det = dxNext*dyPrev - dxPrev*dyNext; 167 173 168 if ( det == 0) {174 if (Utils.equalsEpsilon(det, 0)) { 169 175 ++idx; 170 176 prev = current; 171 177 xPrev0 = xCurrent0; 172 178 yPrev0 = yCurrent0; 173 return new Point(xCurrent0, yCurrent0);179 return mapState.getForView(xCurrent0, yCurrent0); 174 180 } 175 181 176 intm = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0);182 double m = dxNext*(yCurrent0 - yPrev0) - dyNext*(xCurrent0 - xPrev0); 177 183 178 int cx = xPrev0 + (int) Math.round((double) m * dxPrev / det);179 int cy = yPrev0 + (int) Math.round((double) m * dyPrev / det);184 double cx = xPrev0 + m * dxPrev / det; 185 double cy = yPrev0 + m * dyPrev / det; 180 186 ++idx; 181 187 prev = current; 182 188 xPrev0 = xCurrent0; 183 189 yPrev0 = yCurrent0; 184 return new Point(cx, cy);190 return mapState.getForView(cx, cy); 185 191 } 186 192 } 187 193 194 private MapViewPoint getForIndex(int i) { 195 return mapState.getPointFor(nodes.get(i)); 196 } 197 188 198 @Override 189 199 public void remove() { 190 200 throw new UnsupportedOperationException(); … … public class StyledMapRenderer extends AbstractMapRenderer { 375 385 } 376 386 } 377 387 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, 388 private void displaySegments(Path2D path, Path2D orientationArrows, Path2D onewayArrows, Path2D onewayArrowsCasing, 394 389 Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) { 395 390 g.setColor(isInactiveMode ? inactiveColor : color); 396 391 if (useStrokes) { … … public class StyledMapRenderer extends AbstractMapRenderer { 488 483 protected void drawArea(OsmPrimitive osm, Path2D.Double path, Color color, 489 484 MapImage fillImage, Float extent, Path2D.Double pfClip, boolean disabled, TextLabel text) { 490 485 491 Shape area = path.createTransformedShape( nc.getAffineTransform());486 Shape area = path.createTransformedShape(mapState.getAffineTransform()); 492 487 493 488 if (!isOutlineOnly) { 494 489 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); … … public class StyledMapRenderer extends AbstractMapRenderer { 503 498 Shape oldClip = g.getClip(); 504 499 Shape clip = area; 505 500 if (pfClip != null) { 506 clip = pfClip.createTransformedShape( nc.getAffineTransform());501 clip = pfClip.createTransformedShape(mapState.getAffineTransform()); 507 502 } 508 503 g.clip(clip); 509 504 g.setStroke(new BasicStroke(2 * extent, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 4)); … … public class StyledMapRenderer extends AbstractMapRenderer { 526 521 g.clip(stroke.createStrokedShape(area)); 527 522 Shape fill = area; 528 523 if (pfClip != null) { 529 fill = pfClip.createTransformedShape( nc.getAffineTransform());524 fill = pfClip.createTransformedShape(mapState.getAffineTransform()); 530 525 } 531 526 g.fill(fill); 532 527 g.setClip(oldClip); … … public class StyledMapRenderer extends AbstractMapRenderer { 698 693 if (!isShowNames() || bs == null) 699 694 return; 700 695 701 Point p = nc.getPoint(n);696 MapViewPoint p = mapState.getPointFor(n); 702 697 TextLabel text = bs.text; 703 698 String s = text.labelCompositionStrategy.compose(n); 704 699 if (s == null) return; … … public class StyledMapRenderer extends AbstractMapRenderer { 706 701 Font defaultFont = g.getFont(); 707 702 g.setFont(text.font); 708 703 709 int x = p.x + text.xOffset;710 int y = p.y + text.yOffset;704 int x = (int) (p.getInViewX() + text.xOffset); 705 int y = (int) (p.getInViewY() + text.yOffset); 711 706 /** 712 707 * 713 708 * left-above __center-above___ right-above … … public class StyledMapRenderer extends AbstractMapRenderer { 769 764 final double repeat = imgWidth + spacing; 770 765 final int imgHeight = pattern.getHeight(); 771 766 772 Point lastP = null;773 767 double currentWayLength = phase % repeat; 774 768 if (currentWayLength < 0) { 775 769 currentWayLength += repeat; … … public class StyledMapRenderer extends AbstractMapRenderer { 793 787 throw new AssertionError(); 794 788 } 795 789 790 MapViewPoint lastP = null; 796 791 OffsetIterator it = new OffsetIterator(way.getNodes(), offset); 797 792 while (it.hasNext()) { 798 Point thisP = it.next();793 MapViewPoint thisP = it.next(); 799 794 800 795 if (lastP != null) { 801 final double segmentLength = thisP.distance (lastP);796 final double segmentLength = thisP.distanceToInView(lastP); 802 797 803 final double dx = (double) thisP.x - lastP.x;804 final double dy = (double) thisP.y - lastP.y;798 final double dx = thisP.getInViewX() - lastP.getInViewX(); 799 final double dy = thisP.getInViewY() - lastP.getInViewY(); 805 800 806 801 // pos is the position from the beginning of the current segment 807 802 // where an image should be painted 808 803 double pos = repeat - (currentWayLength % repeat); 809 804 810 805 AffineTransform saveTransform = g.getTransform(); 811 g.translate(lastP. x, lastP.y);806 g.translate(lastP.getInViewX(), lastP.getInViewY()); 812 807 g.rotate(Math.atan2(dy, dx)); 813 808 814 809 // draw the rest of the image from the last segment in case it … … public class StyledMapRenderer extends AbstractMapRenderer { 849 844 if (size <= 0 && !n.isHighlighted()) 850 845 return; 851 846 852 Point p = nc.getPoint(n);847 MapViewPoint p = mapState.getPointFor(n); 853 848 854 849 if (n.isHighlighted()) { 855 drawPointHighlight(p , size);850 drawPointHighlight(p.getInView(), size); 856 851 } 857 852 858 if (size > 1) { 859 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return; 853 if (size > 1 && p.isInView()) { 860 854 int radius = size / 2; 861 855 862 856 if (isInactiveMode || n.isDisabled()) { … … public class StyledMapRenderer extends AbstractMapRenderer { 864 858 } else { 865 859 g.setColor(color); 866 860 } 861 Rectangle2D rect = new Rectangle2D.Double(p.getInViewX()-radius-1, p.getInViewY()-radius-1, size + 1, size + 1); 867 862 if (fill) { 868 g.fill Rect(p.x-radius-1, p.y-radius-1, size + 1, size + 1);863 g.fill(rect); 869 864 } else { 870 g.draw Rect(p.x-radius-1, p.y-radius-1, size, size);865 g.draw(rect); 871 866 } 872 867 } 873 868 } 874 869 870 /** 871 * Draw the icon for a given node. 872 * @param n The node 873 * @param img The icon to draw at the node position 874 * @param disabled 875 * @param selected 876 * @param member 877 * @param theta 878 */ 875 879 public void drawNodeIcon(Node n, MapImage img, boolean disabled, boolean selected, boolean member, double theta) { 876 Point p = nc.getPoint(n);880 MapViewPoint p = mapState.getPointFor(n); 877 881 878 final int w = img.getWidth(), h = img.getHeight(); 882 int w = img.getWidth(); 883 int h = img.getHeight(); 879 884 if (n.isHighlighted()) { 880 drawPointHighlight(p , Math.max(w, h));885 drawPointHighlight(p.getInView(), Math.max(w, h)); 881 886 } 882 887 883 888 float alpha = img.getAlphaFloat(); 884 889 890 Graphics2D temporaryGraphics = (Graphics2D) g.create(); 885 891 if (!Utils.equalsEpsilon(alpha, 1f)) { 886 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));892 temporaryGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 887 893 } 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(); 894 895 double x = p.getInViewX(); 896 double y = p.getInViewY(); 897 temporaryGraphics.translate(-x, -y); 898 temporaryGraphics.rotate(theta); 899 temporaryGraphics.drawImage(img.getImage(disabled), w/2 + img.offsetX, h/2 + img.offsetY, nc); 892 900 if (selected || member) { 893 901 Color color; 894 902 if (disabled) { … … public class StyledMapRenderer extends AbstractMapRenderer { 899 907 color = relationSelectedColor; 900 908 } 901 909 g.setColor(color); 902 g.draw Rect(p.x - w/2 + img.offsetX - 2, p.y - h/2 + img.offsetY - 2, w + 4, h + 4);910 g.draw(new Rectangle2D.Double(x - w/2 + img.offsetX - 2, y - h/2 + img.offsetY - 2, w + 4, h + 4)); 903 911 } 904 912 } 905 913 914 /** 915 * Draw the symbol and possibly a highlight marking on a given node. 916 * @param n The position to draw the symbol on 917 * @param s The symbol to draw 918 * @param fillColor The color to fill the symbol with 919 * @param strokeColor The color to use for the outer corner of the symbol 920 */ 906 921 public void drawNodeSymbol(Node n, Symbol s, Color fillColor, Color strokeColor) { 907 Point p = nc.getPoint(n); 908 int radius = s.size / 2; 922 MapViewPoint p = mapState.getPointFor(n); 909 923 910 924 if (n.isHighlighted()) { 911 drawPointHighlight(p , s.size);925 drawPointHighlight(p.getInView(), s.size); 912 926 } 913 927 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(); 928 if (fillColor != null || strokeColor != null) { 929 Shape shape = s.buildShapeAround(p.getInViewX(), p.getInViewY()); 930 931 if (fillColor != null) { 932 g.setColor(fillColor); 933 g.fill(shape); 946 934 } 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(); 935 if (s.stroke != null) { 936 g.setStroke(s.stroke); 937 g.setColor(strokeColor); 938 g.draw(shape); 939 g.setStroke(new BasicStroke()); 981 940 } 982 g.setStroke(new BasicStroke());983 941 } 984 942 } 985 943 … … public class StyledMapRenderer extends AbstractMapRenderer { 993 951 * @param clr The color to use for drawing the text. 994 952 */ 995 953 public void drawOrderNumber(Node n1, Node n2, int orderNumber, Color clr) { 996 Point p1 = nc.getPoint(n1);997 Point p2 = nc.getPoint(n2);954 MapViewPoint p1 = mapState.getPointFor(n1); 955 MapViewPoint p2 = mapState.getPointFor(n2); 998 956 drawOrderNumber(p1, p2, orderNumber, clr); 999 957 } 1000 958 … … public class StyledMapRenderer extends AbstractMapRenderer { 1004 962 * @param path path to draw 1005 963 * @param line line style 1006 964 */ 1007 private void drawPathHighlight( GeneralPathpath, BasicStroke line) {965 private void drawPathHighlight(Path2D path, BasicStroke line) { 1008 966 if (path == null) 1009 967 return; 1010 968 g.setColor(highlightColorTransparent); … … public class StyledMapRenderer extends AbstractMapRenderer { 1023 981 * @param p point 1024 982 * @param size highlight size 1025 983 */ 1026 private void drawPointHighlight(Point p, int size) {984 private void drawPointHighlight(Point2D p, int size) { 1027 985 g.setColor(highlightColorTransparent); 1028 986 int s = size + highlightPointRadius; 1029 987 if (useWiderHighlight) s += widerHighlight; 1030 988 while (s >= size) { 1031 989 int r = (int) Math.floor(s/2d); 1032 g.fill RoundRect(p.x-r, p.y-r, s, s, r, r);990 g.fill(new RoundRectangle2D.Double(p.getX()-r, p.getY()-r, s, s, r, r)); 1033 991 s -= highlightStep; 1034 992 } 1035 993 } … … public class StyledMapRenderer extends AbstractMapRenderer { 1218 1176 } 1219 1177 1220 1178 /** 1179 * A half segment that can be used to place text on it. Used in the drawTextOnPath algorithm. 1180 * @author Michael Zangl 1181 * @since xxx 1182 */ 1183 private static class HalfSegment { 1184 /** 1185 * start point of half segment (as length along the way) 1186 */ 1187 final double start; 1188 /** 1189 * end point of half segment (as length along the way) 1190 */ 1191 final double end; 1192 /** 1193 * quality factor (off screen / partly on screen / fully on screen) 1194 */ 1195 final double quality; 1196 1197 /** 1198 * Create a new half segment 1199 * @param start The start along the way 1200 * @param end The end of the segment 1201 * @param quality A quality factor. 1202 */ 1203 public HalfSegment(double start, double end, double quality) { 1204 super(); 1205 this.start = start; 1206 this.end = end; 1207 this.quality = quality; 1208 } 1209 1210 @Override 1211 public String toString() { 1212 return "HalfSegment [start=" + start + ", end=" + end + ", quality=" + quality + "]"; 1213 } 1214 } 1215 1216 /** 1221 1217 * Draws a text along a given way. 1222 1218 * @param way The way to draw the text on. 1223 1219 * @param text The text definition (font/.../text content) to draw. … … public class StyledMapRenderer extends AbstractMapRenderer { 1234 1230 1235 1231 Rectangle bounds = g.getClipBounds(); 1236 1232 1237 Polygon poly = new Polygon(); 1238 Point lastPoint = null; 1239 Iterator<Node> it = way.getNodes().iterator(); 1240 double pathLength = 0; 1241 long dx, dy; 1233 List<MapViewPoint> points = way.getNodes().stream().map(mapState::getPointFor).collect(Collectors.toList()); 1242 1234 1243 1235 // 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); 1236 List<HalfSegment> longHalfSegment = new ArrayList<>(); 1252 1237 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 } 1238 double pathLength = computePath(2 * (rec.getWidth() + 4), bounds, points, longHalfSegment); 1287 1239 1288 1240 if (rec.getWidth() > pathLength) 1289 1241 return; 1290 1242 1291 1243 double t1, t2; 1292 1244 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 1245 if (!longHalfSegment.isEmpty()) { 1246 // find the segment with the best quality. If there are several with best quality, the one close to the center is prefered. 1247 HalfSegment best = longHalfSegment.stream().max( 1248 Comparator.comparingDouble( segment -> 1249 segment.quality - 1e-5 * Math.abs(0.5 * (segment.end + segment.start) - 0.5 * pathLength) 1250 )).get(); 1251 double remaining = best.end - best.start - rec.getWidth(); // total space left and right from the text 1319 1252 // The space left and right of the text should be distributed 20% - 80% (towards the center), 1320 1253 // but the smaller space should not be less than 7 px. 1321 1254 // However, if the total remaining space is less than 14 px, then distribute it evenly. 1322 1255 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;1256 if ((best.end + best.start)/2 < pathLength/2) { 1257 t2 = best.end - smallerSpace; 1325 1258 t1 = t2 - rec.getWidth(); 1326 1259 } else { 1327 t1 = best Start + smallerSpace;1260 t1 = best.start + smallerSpace; 1328 1261 t2 = t1 + rec.getWidth(); 1329 1262 } 1330 1263 } else { … … public class StyledMapRenderer extends AbstractMapRenderer { 1335 1268 t1 /= pathLength; 1336 1269 t2 /= pathLength; 1337 1270 1338 double[] p1 = pointAt(t1, po ly, pathLength);1339 double[] p2 = pointAt(t2, po ly, pathLength);1271 double[] p1 = pointAt(t1, points, pathLength); 1272 double[] p2 = pointAt(t2, points, pathLength); 1340 1273 1341 1274 if (p1 == null || p2 == null) 1342 1275 return; … … public class StyledMapRenderer extends AbstractMapRenderer { 1364 1297 for (int i = 0; i < gv.getNumGlyphs(); ++i) { 1365 1298 Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D(); 1366 1299 double t = tStart + offsetSign * (gvOffset + rect.getX() + rect.getWidth()/2) / pathLength; 1367 double[] p = pointAt(t, po ly, pathLength);1300 double[] p = pointAt(t, points, pathLength); 1368 1301 if (p != null) { 1369 1302 AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]); 1370 1303 trfm.rotate(p[2]+angleOffset); … … public class StyledMapRenderer extends AbstractMapRenderer { 1384 1317 } 1385 1318 } 1386 1319 1320 private double computePath(double minSegmentLength, Rectangle bounds, List<MapViewPoint> points, 1321 List<HalfSegment> longHalfSegment) { 1322 MapViewPoint lastPoint = points.get(0); 1323 double pathLength = 0; 1324 for (MapViewPoint p : points.subList(1, points.size())) { 1325 double segmentLength = p.distanceToInView(lastPoint); 1326 if (segmentLength > minSegmentLength) { 1327 Point2D center = new Point2D.Double((lastPoint.getInViewX() + p.getInViewX())/2, (lastPoint.getInViewY() + p.getInViewY())/2); 1328 double q = computeQuality(bounds, lastPoint, center); 1329 // prefer the first one for quality equality. 1330 longHalfSegment.add(new HalfSegment(pathLength, pathLength + segmentLength / 2, q)); 1331 1332 q = 0; 1333 if (bounds != null) { 1334 if (bounds.contains(center) && bounds.contains(p.getInView())) { 1335 q = 2; 1336 } else if (bounds.contains(center) || bounds.contains(p.getInView())) { 1337 q = 1; 1338 } 1339 } 1340 longHalfSegment.add(new HalfSegment(pathLength + segmentLength / 2, pathLength + segmentLength, q)); 1341 } 1342 pathLength += segmentLength; 1343 lastPoint = p; 1344 } 1345 return pathLength; 1346 } 1347 1348 private static double computeQuality(Rectangle bounds, MapViewPoint p1, Point2D p2) { 1349 double q = 0; 1350 if (bounds != null) { 1351 if (bounds.contains(p1.getInView())) { 1352 q += 1; 1353 } 1354 if (bounds.contains(p2)) { 1355 q += 1; 1356 } 1357 } 1358 return q; 1359 } 1360 1387 1361 /** 1388 1362 * draw way. This method allows for two draw styles (line using color, dashes using dashedColor) to be passed. 1389 1363 * @param way The way to draw … … public class StyledMapRenderer extends AbstractMapRenderer { 1403 1377 boolean showOrientation, boolean showHeadArrowOnly, 1404 1378 boolean showOneway, boolean onewayReversed) { 1405 1379 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;1380 MapPath2D path = new MapPath2D(); 1381 MapPath2D orientationArrows = showOrientation ? new MapPath2D() : null; 1382 MapPath2D onewayArrows = showOneway ? new MapPath2D() : null; 1383 MapPath2D onewayArrowsCasing = showOneway ? new MapPath2D() : null; 1410 1384 Rectangle bounds = g.getClipBounds(); 1411 1385 if (bounds != null) { 1412 1386 // avoid arrow heads at the border 1413 1387 bounds.grow(100, 100); 1414 1388 } 1415 1389 1416 double wayLength = 0;1417 Point lastPoint = null;1418 1390 boolean initialMoveToNeeded = true; 1419 1391 List<Node> wayNodes = way.getNodes(); 1420 1392 if (wayNodes.size() < 2) return; … … public class StyledMapRenderer extends AbstractMapRenderer { 1430 1402 highlightSegs = new GeneralPath(); 1431 1403 } 1432 1404 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);1405 Point2D p1 = mapState.getPointFor(ws.getFirstNode()).getInView(); 1406 Point2D p2 = mapState.getPointFor(ws.getSecondNode()).getInView(); 1407 highlightSegs.moveTo(p1.getX(), p1.getY()); 1408 highlightSegs.lineTo(p2.getX(), p2.getY()); 1437 1409 } 1438 1410 1439 1411 drawPathHighlight(highlightSegs, line); 1440 1412 } 1441 1413 1442 Iterator<Point> it = new OffsetIterator(wayNodes, offset); 1414 MapViewPoint lastPoint = null; 1415 double wayLength = 0; 1416 Iterator<MapViewPoint> it = new OffsetIterator(wayNodes, offset); 1443 1417 while (it.hasNext()) { 1444 Point p = it.next();1418 MapViewPoint p = it.next(); 1445 1419 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); 1420 MapViewPoint p1 = lastPoint; 1421 MapViewPoint p2 = p; 1465 1422 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); 1423 if (initialMoveToNeeded) { 1424 initialMoveToNeeded = false; 1425 path.moveTo(p1); 1426 } 1427 path.lineTo(p2); 1474 1428 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); 1478 } 1479 } 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 } 1429 /* draw arrow */ 1430 if (showHeadArrowOnly ? !it.hasNext() : showOrientation) { 1431 //TODO: Cache 1432 ArrowPaintHelper drawHelper = new ArrowPaintHelper(PHI, 10 + line.getLineWidth()); 1433 drawHelper.paintArrowAt(orientationArrows, p2, p1); 1434 } 1435 if (showOneway) { 1436 final double segmentLength = p1.distanceToInView(p2); 1437 if (segmentLength != 0) { 1438 final double nx = (p2.getInViewX() - p1.getInViewX()) / segmentLength; 1439 final double ny = (p2.getInViewY() - p1.getInViewY()) / segmentLength; 1440 1441 final double interval = 60; 1442 // distance from p1 1443 double dist = interval - (wayLength % interval); 1444 1445 while (dist < segmentLength) { 1446 appenOnewayPath(onewayReversed, p1, nx, ny, dist, 3d, onewayArrowsCasing); 1447 appenOnewayPath(onewayReversed, p1, nx, ny, dist, 2d, onewayArrows); 1448 dist += interval; 1512 1449 } 1513 wayLength += segmentLength;1514 1450 } 1451 wayLength += segmentLength; 1515 1452 } 1516 1453 } 1517 1454 lastPoint = p; … … public class StyledMapRenderer extends AbstractMapRenderer { 1522 1459 displaySegments(path, orientationArrows, onewayArrows, onewayArrowsCasing, color, line, dashes, dashedColor); 1523 1460 } 1524 1461 1462 private void appenOnewayPath(boolean onewayReversed, MapViewPoint p1, double nx, double ny, double dist, double onewaySize, Path2D onewayPath) { 1463 // scale such that border is 1 px 1464 final double fac = -(onewayReversed ? -1 : 1) * onewaySize * (1 + sinPHI) / (sinPHI * cosPHI); 1465 final double sx = nx * fac; 1466 final double sy = ny * fac; 1467 1468 // Attach the triangle at the incenter and not at the tip. 1469 // Makes the border even at all sides. 1470 final double x = p1.getInViewX() + nx * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI)); 1471 final double y = p1.getInViewY() + ny * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI)); 1472 1473 onewayPath.moveTo(x, y); 1474 onewayPath.lineTo(x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy); 1475 onewayPath.lineTo(x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy); 1476 onewayPath.lineTo(x, y); 1477 } 1478 1525 1479 /** 1526 1480 * Gets the "circum". This is the distance on the map in meters that 100 screen pixels represent. 1527 1481 * @return The "circum" … … public class StyledMapRenderer extends AbstractMapRenderer { 1711 1665 return null; 1712 1666 } 1713 1667 1668 /** 1669 * Test if the area is visible 1670 * @param area The area, interpreted in east/north space. 1671 * @return true if it is visible. 1672 */ 1714 1673 private boolean isAreaVisible(Path2D.Double area) { 1715 1674 Rectangle2D bounds = area.getBounds2D(); 1716 1675 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;1676 MapViewPoint p = mapState.getPointFor(new EastNorth(bounds.getX(), bounds.getY())); 1677 if (p.getInViewX() > mapState.getViewWidth()) return false; 1678 if (p.getInViewY() < 0) return false; 1679 p = mapState.getPointFor(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight())); 1680 if (p.getInViewX() < 0) return false; 1681 if (p.getInViewY() > mapState.getViewHeight()) return false; 1723 1682 return true; 1724 1683 } 1725 1684 … … public class StyledMapRenderer extends AbstractMapRenderer { 1735 1694 return showNames; 1736 1695 } 1737 1696 1738 private static double[] pointAt(double t, Polygonpoly, double pathLength) {1697 private static double[] pointAt(double t, List<MapViewPoint> poly, double pathLength) { 1739 1698 double totalLen = t * pathLength; 1740 1699 double curLen = 0; 1741 longdx, dy;1700 double dx, dy; 1742 1701 double segLen; 1743 1702 1744 1703 // Yes, it is inefficient to iterate from the beginning for each glyph. 1745 1704 // 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];1705 for (int i = 1; i < poly.size(); ++i) { 1706 dx = poly.get(i).getInViewX() - poly.get(i - 1).getInViewX(); 1707 dy = poly.get(i).getInViewY() - poly.get(i - 1).getInViewY(); 1749 1708 segLen = Math.sqrt(dx*dx + dy*dy); 1750 1709 if (totalLen > curLen + segLen) { 1751 1710 curLen += segLen; 1752 1711 continue; 1753 1712 } 1754 1713 return new double[] { 1755 poly. xpoints[i-1]+(totalLen - curLen)/segLen*dx,1756 poly. ypoints[i-1]+(totalLen - curLen)/segLen*dy,1714 poly.get(i - 1).getInViewX() + (totalLen - curLen) / segLen * dx, 1715 poly.get(i - 1).getInViewY() + (totalLen - curLen) / segLen * dy, 1757 1716 Math.atan2(dy, dx)}; 1758 1717 } 1759 1718 return null; -
src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java index 4414c36..d7e91fe 100644
a b package org.openstreetmap.josm.data.osm.visitor.paint; 4 4 import java.awt.BasicStroke; 5 5 import java.awt.Color; 6 6 import java.awt.Graphics2D; 7 import java.awt.Point;8 7 import java.awt.Rectangle; 9 8 import java.awt.RenderingHints; 10 9 import java.awt.Stroke; 10 import java.awt.geom.Ellipse2D; 11 11 import java.awt.geom.GeneralPath; 12 import java.awt.geom.Rectangle2D; 13 import java.awt.geom.Rectangle2D.Double; 12 14 import java.util.ArrayList; 13 15 import java.util.Iterator; 14 16 import java.util.List; … … import org.openstreetmap.josm.data.osm.RelationMember; 25 27 import org.openstreetmap.josm.data.osm.Way; 26 28 import org.openstreetmap.josm.data.osm.WaySegment; 27 29 import org.openstreetmap.josm.data.osm.visitor.Visitor; 30 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 28 31 import org.openstreetmap.josm.gui.NavigatableComponent; 29 32 30 33 /** … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 74 77 /** Color cache to draw subsequent segments of same color as one <code>Path</code>. */ 75 78 protected Color currentColor; 76 79 /** 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(); 78 81 /** 79 82 * <code>DataSet</code> passed to the @{link render} function to overcome the argument 80 83 * limitations of @{link Visitor} interface. Only valid until end of rendering call. … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 82 85 private DataSet ds; 83 86 84 87 /** 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); 90 89 91 90 /** Helper variable for {@link #visit(Relation)} */ 92 91 private final Stroke relatedWayStroke = new BasicStroke( … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 116 115 taggedColor = PaintColors.TAGGED.get(); 117 116 connectionColor = PaintColors.CONNECTION.get(); 118 117 119 if ( taggedColor != nodeColor) {118 if (!taggedColor.equals(nodeColor)) { 120 119 taggedConnectionColor = taggedColor; 121 120 } else { 122 121 taggedConnectionColor = connectionColor; … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 209 208 // in most of the cases there won't be more than one segment. Since the wireframe 210 209 // renderer does not feature any transparency there should be no visual difference. 211 210 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); 213 212 } 214 213 displaySegments(); 215 214 } … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 314 313 315 314 Iterator<Node> it = w.getNodes().iterator(); 316 315 if (it.hasNext()) { 317 Point lastP = nc.getPoint(it.next());316 MapViewPoint lastP = mapState.getPointFor(it.next()); 318 317 for (int orderNumber = 1; it.hasNext(); orderNumber++) { 319 Point p = nc.getPoint(it.next());318 MapViewPoint p = mapState.getPointFor(it.next()); 320 319 drawSegment(lastP, p, wayColor, 321 320 showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow); 322 321 if (showOrderNumber && !isInactiveMode) { … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 353 352 } 354 353 355 354 if (m.isNode()) { 356 Point p = nc.getPoint(m.getNode()); 357 if (p.x < 0 || p.y < 0 358 || p.x > nc.getWidth() || p.y > nc.getHeight()) { 359 continue; 355 MapViewPoint p = mapState.getPointFor(m.getNode()); 356 if (p.isInView()) { 357 g.draw(new Ellipse2D.Double(p.getInViewX()-4, p.getInViewY()-4, 9, 9)); 360 358 } 361 359 362 g.drawOval(p.x-4, p.y-4, 9, 9);363 360 } else if (m.isWay()) { 364 361 GeneralPath path = new GeneralPath(); 365 362 … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 368 365 if (!n.isDrawable()) { 369 366 continue; 370 367 } 371 Point p = nc.getPoint(n);368 MapViewPoint p = mapState.getPointFor(n); 372 369 if (first) { 373 path.moveTo(p. x, p.y);370 path.moveTo(p.getInViewX(), p.getInViewY()); 374 371 first = false; 375 372 } else { 376 path.lineTo(p. x, p.y);373 path.lineTo(p.getInViewX(), p.getInViewY()); 377 374 } 378 375 } 379 376 … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 392 389 @Override 393 390 public void drawNode(Node n, Color color, int size, boolean fill) { 394 391 if (size > 1) { 395 int radius = size / 2; 396 Point p = nc.getPoint(n); 397 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) 398 || (p.y > nc.getHeight())) 392 MapViewPoint p = mapState.getPointFor(n); 393 if (!p.isInView()) 399 394 return; 395 int radius = size / 2; 396 Double shape = new Rectangle2D.Double(p.getInViewX() - radius, p.getInViewY() - radius, size, size); 400 397 g.setColor(color); 401 398 if (fill) { 402 g.fillRect(p.x - radius, p.y - radius, size, size); 403 g.drawRect(p.x - radius, p.y - radius, size, size); 404 } else { 405 g.drawRect(p.x - radius, p.y - radius, size, size); 399 g.fill(shape); 406 400 } 401 g.draw(shape); 407 402 } 408 403 } 409 404 … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 411 406 * Draw a line with the given color. 412 407 * 413 408 * @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. 416 411 * @param showDirection <code>true</code> if segment direction should be indicated 417 412 */ 418 protected void drawSegment( GeneralPath path, Point p1, Point p2, boolean showDirection) {413 protected void drawSegment(MapPath2D path, MapViewPoint mv1, MapViewPoint mv2, boolean showDirection) { 419 414 Rectangle bounds = g.getClipBounds(); 420 415 bounds.grow(100, 100); // avoid arrow heads at the border 421 LineClip clip = new LineClip(p1, p2, bounds); 422 if (clip.execute()) { 423 p1 = clip.getP1(); 424 p2 = clip.getP2(); 425 path.moveTo(p1.x, p1.y); 426 path.lineTo(p2.x, p2.y); 427 416 if (mv1.rectTo(mv2).isInView()) { 417 path.moveTo(mv1); 418 path.lineTo(mv2); 428 419 if (showDirection) { 429 final double l = 10. / p1.distance(p2); 430 431 final double sx = l * (p1.x - p2.x); 432 final double sy = l * (p1.y - p2.y); 433 434 path.lineTo(p2.x + (double) Math.round(cosPHI * sx - sinPHI * sy), p2.y + (double) Math.round(sinPHI * sx + cosPHI * sy)); 435 path.moveTo(p2.x + (double) Math.round(cosPHI * sx + sinPHI * sy), p2.y + (double) Math.round(-sinPHI * sx + cosPHI * sy)); 436 path.lineTo(p2.x, p2.y); 420 ARROW_PAINT_HELPER.paintArrowAt(path, mv2, mv1); 437 421 } 438 422 } 439 423 } … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 446 430 * @param col The color to use for drawing line. 447 431 * @param showDirection <code>true</code> if segment direction should be indicated. 448 432 */ 449 protected void drawSegment( Point p1,Point p2, Color col, boolean showDirection) {450 if ( col != currentColor) {433 protected void drawSegment(MapViewPoint p1, MapViewPoint p2, Color col, boolean showDirection) { 434 if (!col.equals(currentColor)) { 451 435 displaySegments(col); 452 436 } 453 437 drawSegment(currentPath, p1, p2, showDirection); … … public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor 469 453 if (currentPath != null) { 470 454 g.setColor(currentColor); 471 455 g.draw(currentPath); 472 currentPath = new GeneralPath();456 currentPath = new MapPath2D(); 473 457 currentColor = newColor; 474 458 } 475 459 } -
src/org/openstreetmap/josm/data/projection/Projection.java
diff --git a/src/org/openstreetmap/josm/data/projection/Projection.java b/src/org/openstreetmap/josm/data/projection/Projection.java index b2bd016..e43ad65 100644
a b public interface Projection { 110 110 * @return true if natural order of coordinates is North East, false if East North 111 111 */ 112 112 boolean switchXY(); 113 114 /** 115 * Gets the object used as cache identifier when caching results of this projection. 116 * @return The object to use as cache key 117 */ 118 default Object getCacheKey() { 119 return this; 120 } 113 121 } -
src/org/openstreetmap/josm/gui/MapViewState.java
diff --git a/src/org/openstreetmap/josm/gui/MapViewState.java b/src/org/openstreetmap/josm/gui/MapViewState.java index f7dcd8d..fe1cff1 100644
a b package org.openstreetmap.josm.gui; 3 3 4 4 import java.awt.Container; 5 5 import java.awt.Point; 6 import java.awt.Rectangle;7 6 import java.awt.geom.AffineTransform; 8 7 import java.awt.geom.Point2D; 9 8 import java.awt.geom.Point2D.Double; … … import org.openstreetmap.josm.data.Bounds; 16 15 import org.openstreetmap.josm.data.ProjectionBounds; 17 16 import org.openstreetmap.josm.data.coor.EastNorth; 18 17 import org.openstreetmap.josm.data.coor.LatLon; 18 import org.openstreetmap.josm.data.osm.Node; 19 19 import org.openstreetmap.josm.data.projection.Projection; 20 20 import org.openstreetmap.josm.gui.download.DownloadDialog; 21 21 import org.openstreetmap.josm.tools.bugreport.BugReport; … … import org.openstreetmap.josm.tools.bugreport.BugReport; 27 27 */ 28 28 public final class MapViewState { 29 29 30 /** 31 * A flag indicating that the point is outside to the top of the map view. 32 */ 33 public static final int OUTSIDE_TOP = 1; 34 35 /** 36 * A flag indicating that the point is outside to the bottom of the map view. 37 */ 38 public static final int OUTSIDE_BOTTOM = 2; 39 40 /** 41 * A flag indicating that the point is outside to the left of the map view. 42 */ 43 public static final int OUTSIDE_LEFT = 3; 44 45 /** 46 * A flag indicating that the point is outside to the right of the map view. 47 */ 48 public static final int OUTSIDE_RIGHT = 4; 49 30 50 private final Projection projection; 31 51 32 52 private final int viewWidth; … … public final class MapViewState { 154 174 } 155 175 156 176 /** 177 * Gets the {@link MapViewPoint} for the given node. This is faster than {@link #getPointFor(LatLon)} because it uses the node east/north 178 * cache. 179 * @param node The node 180 * @return The position of that node. 181 */ 182 public MapViewPoint getPointFor(Node node) { 183 return getPointFor(node.getEastNorth(getProjection())); 184 } 185 186 /** 157 187 * Gets a rectangle representing the whole view area. 158 188 * @return The rectangle. 159 189 */ … … public final class MapViewState { 167 197 * @return The view area. 168 198 * @since 10458 169 199 */ 170 public MapViewRectangle getViewArea(Rectangle rectangle) {200 public MapViewRectangle getViewArea(Rectangle2D rectangle) { 171 201 return getForView(rectangle.getMinX(), rectangle.getMinY()).rectTo(getForView(rectangle.getMaxX(), rectangle.getMaxY())); 172 202 } 173 203 … … public final class MapViewState { 313 343 return new Point2D.Double(getInViewX(), getInViewY()); 314 344 } 315 345 316 protected abstract double getInViewX(); 346 /** 347 * Get the x coordinate in view space without creating an intermediate object. 348 * @return The x coordinate 349 */ 350 public abstract double getInViewX(); 317 351 318 protected abstract double getInViewY(); 352 /** 353 * Get the y coordinate in view space without creating an intermediate object. 354 * @return The y coordinate 355 */ 356 public abstract double getInViewY(); 319 357 320 358 /** 321 359 * Convert this point to window coordinates. … … public final class MapViewState { 371 409 public MapViewPoint add(EastNorth en) { 372 410 return new MapViewEastNorthPoint(getEastNorth().add(en)); 373 411 } 412 413 /** 414 * Check if this point is inside the view bounds. 415 * 416 * This is the case iff <code>getOutsideRectangleFlags(getViewArea())</code> returns no flags 417 * @return true if it is. 418 */ 419 public boolean isInView() { 420 return inRange(getInViewX(), 0, getViewWidth()) && inRange(getInViewY(), 0, getViewHeight()); 421 } 422 423 private boolean inRange(double val, int min, double max) { 424 return val >= min && val < max; 425 } 426 427 /** 428 * Gets the direction in which this point is outside of the given view rectangle. 429 * @param rect The rectangle to check agains. 430 * @return The direction in which it is outside of the view, as OUTSIDE_... flags. 431 */ 432 public int getOutsideRectangleFlags(MapViewRectangle rect) { 433 Rectangle2D bounds = rect.getInView(); 434 int flags = 0; 435 if (getInViewX() < bounds.getMinX()) { 436 flags |= OUTSIDE_LEFT; 437 } else if (getInViewX() > bounds.getMaxX()) { 438 flags |= OUTSIDE_RIGHT; 439 } 440 if (getInViewY() < bounds.getMinY()) { 441 flags |= OUTSIDE_TOP; 442 } else if (getInViewY() > bounds.getMaxY()) { 443 flags |= OUTSIDE_BOTTOM; 444 } 445 446 return flags; 447 } 448 449 /** 450 * Gets the sum of the x/y view distances between the points. |x1 - x2| + |y1 - y2| 451 * @param p2 The other point 452 * @return The norm 453 */ 454 public double oneNormInView(MapViewPoint p2) { 455 return Math.abs(getInViewX() - p2.getInViewX()) + Math.abs(getInViewY()) - p2.getInViewY(); 456 } 457 458 /** 459 * Gets the squared distance between this point and an other point. 460 * @param p2 The other point 461 * @return The squared distance. 462 */ 463 public double distanceToInViewSq(MapViewPoint p2) { 464 double dx = getInViewX() - p2.getInViewX(); 465 double dy = getInViewY() - p2.getInViewY(); 466 return dx * dx + dy * dy; 467 } 468 469 /** 470 * Gets the distance between this point and an other point. 471 * @param p2 The other point 472 * @return The distance. 473 */ 474 public double distanceToInView(MapViewPoint p2) { 475 return Math.sqrt(distanceToInViewSq(p2)); 476 } 374 477 } 375 478 376 479 private class MapViewViewPoint extends MapViewPoint { … … public final class MapViewState { 383 486 } 384 487 385 488 @Override 386 p rotecteddouble getInViewX() {489 public double getInViewX() { 387 490 return x; 388 491 } 389 492 390 493 @Override 391 p rotecteddouble getInViewY() {494 public double getInViewY() { 392 495 return y; 393 496 } 394 497 … … public final class MapViewState { 407 510 } 408 511 409 512 @Override 410 p rotecteddouble getInViewX() {513 public double getInViewX() { 411 514 return (eastNorth.east() - topLeft.east()) / scale; 412 515 } 413 516 414 517 @Override 415 p rotecteddouble getInViewY() {518 public double getInViewY() { 416 519 return (topLeft.north() - eastNorth.north()) / scale; 417 520 } 418 521 … … public final class MapViewState { 488 591 double y2 = p2.getInViewY(); 489 592 return new Rectangle2D.Double(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2)); 490 593 } 594 595 /** 596 * Check if the rectangle intersects the map view area. 597 * @return <code>true</code> if it intersects. 598 */ 599 public boolean isInView() { 600 return getInView().intersects(getViewArea().getInView()); 601 } 491 602 } 492 603 493 604 } -
src/org/openstreetmap/josm/gui/NavigatableComponent.java
diff --git a/src/org/openstreetmap/josm/gui/NavigatableComponent.java b/src/org/openstreetmap/josm/gui/NavigatableComponent.java index 7e4d6e7..a07664a 100644
a b public class NavigatableComponent extends JComponent implements Helpful { 504 504 } 505 505 506 506 // looses precision, may overflow (depends on p and current scale) 507 //@Deprecated507 @Deprecated 508 508 public Point getPoint(EastNorth p) { 509 509 Point2D d = getPoint2D(p); 510 510 return new Point((int) d.getX(), (int) d.getY()); 511 511 } 512 512 513 513 // looses precision, may overflow (depends on p and current scale) 514 //@Deprecated514 @Deprecated 515 515 public Point getPoint(LatLon latlon) { 516 516 Point2D d = getPoint2D(latlon); 517 517 return new Point((int) d.getX(), (int) d.getY()); 518 518 } 519 519 520 520 // looses precision, may overflow (depends on p and current scale) 521 //@Deprecated521 @Deprecated 522 522 public Point getPoint(Node n) { 523 523 Point2D d = getPoint2D(n); 524 524 return new Point((int) d.getX(), (int) d.getY()); -
src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java
diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java index 4aa58e6..e49906d 100644
a b import java.awt.Color; 6 6 import java.awt.Rectangle; 7 7 import java.awt.Stroke; 8 8 import java.util.Objects; 9 import java.util.Optional; 9 10 import java.util.stream.IntStream; 10 11 11 12 import org.openstreetmap.josm.Main; … … import org.openstreetmap.josm.gui.mappaint.MultiCascade; 22 23 import org.openstreetmap.josm.gui.mappaint.StyleElementList; 23 24 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProvider; 24 25 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.SimpleBoxProvider; 26 import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol.SymbolShape; 25 27 import org.openstreetmap.josm.gui.util.RotationAngle; 26 28 import org.openstreetmap.josm.tools.CheckParameterUtil; 27 29 import org.openstreetmap.josm.tools.Utils; … … import org.openstreetmap.josm.tools.Utils; 32 34 public class NodeElement extends StyleElement { 33 35 public final MapImage mapImage; 34 36 public final RotationAngle mapImageAngle; 37 /** 38 * The symbol that should be used for drawing this node. 39 */ 35 40 public final Symbol symbol; 36 41 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 @Override59 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 @Override71 public int hashCode() {72 return Objects.hash(symbol, size, stroke, strokeColor, fillColor);73 }74 75 @Override76 public String toString() {77 return "symbol=" + symbol + " size=" + size +78 (stroke != null ? " stroke=" + stroke + " strokeColor=" + strokeColor : "") +79 (fillColor != null ? " fillColor=" + fillColor : "");80 }81 }82 83 42 private static final String[] ICON_KEYS = {ICON_IMAGE, ICON_WIDTH, ICON_HEIGHT, ICON_OPACITY, ICON_OFFSET_X, ICON_OFFSET_Y}; 84 43 85 44 public static final NodeElement SIMPLE_NODE_ELEMSTYLE; … … public class NodeElement extends StyleElement { 193 152 194 153 private static Symbol createSymbol(Environment env) { 195 154 Cascade c = env.mc.getCascade(env.layer); 196 Cascade cDef = env.mc.getCascade("default");197 155 198 SymbolShape shape;199 156 Keyword shapeKW = c.get("symbol-shape", null, Keyword.class); 200 157 if (shapeKW == null) 201 158 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()) { 221 161 return null; 162 } 222 163 164 Cascade cDef = env.mc.getCascade("default"); 223 165 Float sizeOnDefault = cDef.get("symbol-size", null, Float.class); 224 166 if (sizeOnDefault != null && sizeOnDefault <= 0) { 225 167 sizeOnDefault = null; … … public class NodeElement extends StyleElement { 267 209 } 268 210 } 269 211 270 return new Symbol(shape , Math.round(size), stroke, strokeColor, fillColor);212 return new Symbol(shape.get(), Math.round(size), stroke, strokeColor, fillColor); 271 213 } 272 214 273 215 @Override … … public class NodeElement extends StyleElement { 279 221 painter.drawNodeIcon(n, mapImage, painter.isInactiveMode() || n.isDisabled(), selected, member, 280 222 mapImageAngle == null ? 0.0 : mapImageAngle.getRotationAngle(primitive)); 281 223 } 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); 303 225 } else { 304 226 Color color; 305 227 boolean isConnection = n.isConnectionNode(); … … public class NodeElement extends StyleElement { 343 265 } 344 266 } 345 267 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 346 293 public BoxProvider getBoxProvider() { 347 294 if (mapImage != null) 348 295 return mapImage.getBoxProvider(); -
new file src/org/openstreetmap/josm/gui/mappaint/styleelement/Symbol.java
diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/Symbol.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/Symbol.java new file mode 100644 index 0000000..30bb25e
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.mappaint.styleelement; 3 4 import java.awt.Color; 5 import java.awt.Shape; 6 import java.awt.Stroke; 7 import java.awt.geom.Ellipse2D; 8 import java.awt.geom.GeneralPath; 9 import java.awt.geom.Rectangle2D; 10 import java.util.Objects; 11 import java.util.Optional; 12 import java.util.stream.Stream; 13 14 /** 15 * The definition of a symbol that should be rendered at the node position. 16 * @since xxx Extracted from {@link NodeElement} 17 */ 18 public class Symbol { 19 private final SymbolShape symbolShape; 20 /** 21 * The width and height of this symbol 22 */ 23 public final int size; 24 /** 25 * The stroke to use for the outline 26 */ 27 public final Stroke stroke; 28 /** 29 * The color to draw the stroke with 30 */ 31 public final Color strokeColor; 32 /** 33 * The color to fill the interiour of the shape. 34 */ 35 public final Color fillColor; 36 37 /** 38 * Create a new symbol 39 * @param symbol The symbol type 40 * @param size The overall size of the symbol, both width and height are the same 41 * @param stroke The stroke to use for the outline 42 * @param strokeColor The color to draw the stroke with 43 * @param fillColor The color to fill the interiour of the shape. 44 */ 45 public Symbol(SymbolShape symbol, int size, Stroke stroke, Color strokeColor, Color fillColor) { 46 if (stroke != null && strokeColor == null) 47 throw new IllegalArgumentException("Stroke given without color"); 48 if (stroke == null && fillColor == null) 49 throw new IllegalArgumentException("Either a stroke or a fill color must be given"); 50 this.symbolShape = symbol; 51 this.size = size; 52 this.stroke = stroke; 53 this.strokeColor = strokeColor; 54 this.fillColor = fillColor; 55 } 56 57 @Override 58 public boolean equals(Object obj) { 59 if (obj == null || getClass() != obj.getClass()) 60 return false; 61 final Symbol other = (Symbol) obj; 62 return symbolShape == other.symbolShape && 63 size == other.size && 64 Objects.equals(stroke, other.stroke) && 65 Objects.equals(strokeColor, other.strokeColor) && 66 Objects.equals(fillColor, other.fillColor); 67 } 68 69 @Override 70 public int hashCode() { 71 return Objects.hash(symbolShape, size, stroke, strokeColor, fillColor); 72 } 73 74 @Override 75 public String toString() { 76 return "symbolShape=" + symbolShape + " size=" + size + 77 (stroke != null ? (" stroke=" + stroke + " strokeColor=" + strokeColor) : "") + 78 (fillColor != null ? (" fillColor=" + fillColor) : ""); 79 } 80 81 /** 82 * Builds the shape for this symbol 83 * @param x The center x coordinate 84 * @param y The center y coordinate 85 * @return The symbol shape. 86 */ 87 public Shape buildShapeAround(double x, double y) { 88 int radius = size / 2; 89 Shape shape; 90 switch (symbolShape) { 91 case SQUARE: 92 // optimize for performance reasons 93 shape = new Rectangle2D.Double(x - radius, y - radius, size, size); 94 break; 95 case CIRCLE: 96 shape = new Ellipse2D.Double(x - radius, y - radius, size, size); 97 break; 98 default: 99 shape = buildPolygon(x, y, radius); 100 break; 101 } 102 return shape; 103 } 104 105 private Shape buildPolygon(double cx, double cy, int radius) { 106 GeneralPath polygon = new GeneralPath(); 107 for (int i = 0; i < symbolShape.sides; i++) { 108 double angle = ((2 * Math.PI / symbolShape.sides) * i) - symbolShape.rotation; 109 double x = cx + radius * Math.cos(angle); 110 double y = cy + radius * Math.sin(angle); 111 if (i == 0) { 112 polygon.moveTo(x, y); 113 } else { 114 polygon.lineTo(x, y); 115 } 116 } 117 polygon.closePath(); 118 return polygon; 119 } 120 121 /** 122 * A list of possible symbol shapes. 123 */ 124 public enum SymbolShape { 125 /** 126 * A square 127 */ 128 SQUARE("square", 4, Math.PI / 4), 129 /** 130 * A circle 131 */ 132 CIRCLE("circle", 1, 0), 133 /** 134 * A triangle with sides of equal lengh 135 */ 136 TRIANGLE("triangle", 3, Math.PI / 2), 137 /** 138 * A pentagon 139 */ 140 PENTAGON("pentagon", 5, Math.PI / 2), 141 /** 142 * A hexagon 143 */ 144 HEXAGON("hexagon", 6, 0), 145 /** 146 * A heptagon 147 */ 148 HEPTAGON("heptagon", 7, Math.PI / 2), 149 /** 150 * An octagon 151 */ 152 OCTAGON("octagon", 8, Math.PI / 8), 153 /** 154 * a nonagon 155 */ 156 NONAGON("nonagon", 9, Math.PI / 2), 157 /** 158 * A decagon 159 */ 160 DECAGON("decagon", 10, 0); 161 162 private final String name; 163 final int sides; 164 165 final double rotation; 166 167 private SymbolShape(String name, int sides, double rotation) { 168 this.name = name; 169 this.sides = sides; 170 this.rotation = rotation; 171 } 172 173 /** 174 * Gets the number of normally straight sides this symbol has. Returns 1 for a circle. 175 * @return The sides of the symbol 176 */ 177 public int getSides() { 178 return sides; 179 } 180 181 /** 182 * Gets the rotateion of the first point of this symbol. 183 * @return The roration 184 */ 185 public double getRotation() { 186 return rotation; 187 } 188 189 /** 190 * Get the MapCSS name for this shape 191 * @return The name 192 */ 193 public String getName() { 194 return name; 195 } 196 197 /** 198 * Get the shape with the given name 199 * @param val The name to search 200 * @return The shape as optional 201 */ 202 public static Optional<SymbolShape> forName(String val) { 203 return Stream.of(values()).filter(val::equals).findAny(); 204 } 205 } 206 }