Ticket #13306: patch-mapview-use-state-in-renderer.patch
File patch-mapview-use-state-in-renderer.patch, 105.2 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 305dfce..62d25cd 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 72d4740..4afef70 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 293 294 294 295 295 296 // Drawing preview lines 296 GeneralPath b = new GeneralPath();297 MapPath2D b = new MapPath2D(); 297 298 if (alt && !ctrl) { 298 299 // In delete mode 299 300 if (p1 != null && p2 != null) { … … public class ImproveWayAccuracyAction extends MapMode implements 329 330 } 330 331 } 331 332 332 protected void drawIntersectingWayHelperLines(MapView mv, GeneralPathb) {333 protected void drawIntersectingWayHelperLines(MapView mv, MapPath2D b) { 333 334 for (final OsmPrimitive referrer : candidateNode.getReferrers()) { 334 335 if (!(referrer instanceof Way) || targetWay.equals(referrer)) { 335 336 continue; … … public class ImproveWayAccuracyAction extends MapMode implements 340 341 continue; 341 342 } 342 343 if (i > 0) { 343 final Point p = mv.getPoint(nodes.get(i - 1));344 final MapViewPoint p = mv.getState().getPointFor(nodes.get(i - 1)); 344 345 b.moveTo(mousePos.x, mousePos.y); 345 b.lineTo(p .x, p.y);346 b.lineTo(p); 346 347 } 347 348 if (i < nodes.size() - 1) { 348 final Point p = mv.getPoint(nodes.get(i + 1));349 final MapViewPoint p = mv.getState().getPointFor(nodes.get(i + 1)); 349 350 b.moveTo(mousePos.x, mousePos.y); 350 b.lineTo(p .x, p.y);351 b.lineTo(p); 351 352 } 352 353 } 353 354 } -
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 8709f6a..13e8ac3 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 3bef3af..68b0ed9 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.NoSuchElementException; 36 37 import java.util.concurrent.ForkJoinPool; 37 38 import java.util.concurrent.ForkJoinTask; 38 39 import java.util.concurrent.RecursiveTask; 40 import java.util.stream.Collectors; 39 41 40 42 import javax.swing.AbstractButton; 41 43 import javax.swing.FocusManager; … … import org.openstreetmap.josm.data.osm.visitor.Visitor; 57 59 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 58 60 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData; 59 61 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 62 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 60 63 import org.openstreetmap.josm.gui.NavigatableComponent; 61 64 import org.openstreetmap.josm.gui.mappaint.ElemStyles; 62 65 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 { 392 402 } 393 403 } 394 404 395 private static Polygon buildPolygon(Point center, int radius, int sides) { 396 return buildPolygon(center, radius, sides, 0.0); 397 } 398 399 private static Polygon buildPolygon(Point center, int radius, int sides, double rotation) { 400 Polygon polygon = new Polygon(); 401 for (int i = 0; i < sides; i++) { 402 double angle = ((2 * Math.PI / sides) * i) - rotation; 403 int x = (int) Math.round(center.x + radius * Math.cos(angle)); 404 int y = (int) Math.round(center.y + radius * Math.sin(angle)); 405 polygon.addPoint(x, y); 406 } 407 return polygon; 408 } 409 410 private void displaySegments(GeneralPath path, GeneralPath orientationArrows, GeneralPath onewayArrows, GeneralPath onewayArrowsCasing, 405 private void displaySegments(Path2D path, Path2D orientationArrows, Path2D onewayArrows, Path2D onewayArrowsCasing, 411 406 Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) { 412 407 g.setColor(isInactiveMode ? inactiveColor : color); 413 408 if (useStrokes) { … … public class StyledMapRenderer extends AbstractMapRenderer { 505 500 protected void drawArea(OsmPrimitive osm, Path2D.Double path, Color color, 506 501 MapImage fillImage, Float extent, Path2D.Double pfClip, boolean disabled, TextLabel text) { 507 502 508 Shape area = path.createTransformedShape( nc.getAffineTransform());503 Shape area = path.createTransformedShape(mapState.getAffineTransform()); 509 504 510 505 if (!isOutlineOnly) { 511 506 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); … … public class StyledMapRenderer extends AbstractMapRenderer { 520 515 Shape oldClip = g.getClip(); 521 516 Shape clip = area; 522 517 if (pfClip != null) { 523 clip = pfClip.createTransformedShape( nc.getAffineTransform());518 clip = pfClip.createTransformedShape(mapState.getAffineTransform()); 524 519 } 525 520 g.clip(clip); 526 521 g.setStroke(new BasicStroke(2 * extent, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 4)); … … public class StyledMapRenderer extends AbstractMapRenderer { 543 538 g.clip(stroke.createStrokedShape(area)); 544 539 Shape fill = area; 545 540 if (pfClip != null) { 546 fill = pfClip.createTransformedShape( nc.getAffineTransform());541 fill = pfClip.createTransformedShape(mapState.getAffineTransform()); 547 542 } 548 543 g.fill(fill); 549 544 g.setClip(oldClip); … … public class StyledMapRenderer extends AbstractMapRenderer { 715 710 if (!isShowNames() || bs == null) 716 711 return; 717 712 718 Point p = nc.getPoint(n);713 MapViewPoint p = mapState.getPointFor(n); 719 714 TextLabel text = bs.text; 720 715 String s = text.labelCompositionStrategy.compose(n); 721 716 if (s == null) return; … … public class StyledMapRenderer extends AbstractMapRenderer { 723 718 Font defaultFont = g.getFont(); 724 719 g.setFont(text.font); 725 720 726 int x = p.x + text.xOffset;727 int y = p.y + text.yOffset;721 int x = (int) (p.getInViewX() + text.xOffset); 722 int y = (int) (p.getInViewY() + text.yOffset); 728 723 /** 729 724 * 730 725 * left-above __center-above___ right-above … … public class StyledMapRenderer extends AbstractMapRenderer { 786 781 final double repeat = imgWidth + spacing; 787 782 final int imgHeight = pattern.getHeight(); 788 783 789 Point lastP = null;790 784 double currentWayLength = phase % repeat; 791 785 if (currentWayLength < 0) { 792 786 currentWayLength += repeat; … … public class StyledMapRenderer extends AbstractMapRenderer { 810 804 throw new AssertionError(); 811 805 } 812 806 807 MapViewPoint lastP = null; 813 808 OffsetIterator it = new OffsetIterator(way.getNodes(), offset); 814 809 while (it.hasNext()) { 815 Point thisP = it.next();810 MapViewPoint thisP = it.next(); 816 811 817 812 if (lastP != null) { 818 final double segmentLength = thisP.distance (lastP);813 final double segmentLength = thisP.distanceToInView(lastP); 819 814 820 final double dx = (double) thisP.x - lastP.x;821 final double dy = (double) thisP.y - lastP.y;815 final double dx = thisP.getInViewX() - lastP.getInViewX(); 816 final double dy = thisP.getInViewY() - lastP.getInViewY(); 822 817 823 818 // pos is the position from the beginning of the current segment 824 819 // where an image should be painted 825 820 double pos = repeat - (currentWayLength % repeat); 826 821 827 822 AffineTransform saveTransform = g.getTransform(); 828 g.translate(lastP. x, lastP.y);823 g.translate(lastP.getInViewX(), lastP.getInViewY()); 829 824 g.rotate(Math.atan2(dy, dx)); 830 825 831 826 // draw the rest of the image from the last segment in case it … … public class StyledMapRenderer extends AbstractMapRenderer { 866 861 if (size <= 0 && !n.isHighlighted()) 867 862 return; 868 863 869 Point p = nc.getPoint(n);864 MapViewPoint p = mapState.getPointFor(n); 870 865 871 866 if (n.isHighlighted()) { 872 drawPointHighlight(p , size);867 drawPointHighlight(p.getInView(), size); 873 868 } 874 869 875 if (size > 1) { 876 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return; 870 if (size > 1 && p.isInView()) { 877 871 int radius = size / 2; 878 872 879 873 if (isInactiveMode || n.isDisabled()) { … … public class StyledMapRenderer extends AbstractMapRenderer { 881 875 } else { 882 876 g.setColor(color); 883 877 } 878 Rectangle2D rect = new Rectangle2D.Double(p.getInViewX()-radius-1, p.getInViewY()-radius-1, size + 1, size + 1); 884 879 if (fill) { 885 g.fill Rect(p.x-radius-1, p.y-radius-1, size + 1, size + 1);880 g.fill(rect); 886 881 } else { 887 g.draw Rect(p.x-radius-1, p.y-radius-1, size, size);882 g.draw(rect); 888 883 } 889 884 } 890 885 } 891 886 887 /** 888 * Draw the icon for a given node. 889 * @param n The node 890 * @param img The icon to draw at the node position 891 * @param disabled 892 * @param selected 893 * @param member 894 * @param theta 895 */ 892 896 public void drawNodeIcon(Node n, MapImage img, boolean disabled, boolean selected, boolean member, double theta) { 893 Point p = nc.getPoint(n);897 MapViewPoint p = mapState.getPointFor(n); 894 898 895 final int w = img.getWidth(), h = img.getHeight(); 899 int w = img.getWidth(); 900 int h = img.getHeight(); 896 901 if (n.isHighlighted()) { 897 drawPointHighlight(p , Math.max(w, h));902 drawPointHighlight(p.getInView(), Math.max(w, h)); 898 903 } 899 904 900 905 float alpha = img.getAlphaFloat(); 901 906 907 Graphics2D temporaryGraphics = (Graphics2D) g.create(); 902 908 if (!Utils.equalsEpsilon(alpha, 1f)) { 903 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));909 temporaryGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 904 910 } 905 g.rotate(theta, p.x, p.y); 906 g.drawImage(img.getImage(disabled), p.x - w/2 + img.offsetX, p.y - h/2 + img.offsetY, nc); 907 g.rotate(-theta, p.x, p.y); 908 g.setPaintMode(); 911 912 double x = p.getInViewX(); 913 double y = p.getInViewY(); 914 temporaryGraphics.translate(-x, -y); 915 temporaryGraphics.rotate(theta); 916 temporaryGraphics.drawImage(img.getImage(disabled), w/2 + img.offsetX, h/2 + img.offsetY, nc); 909 917 if (selected || member) { 910 918 Color color; 911 919 if (disabled) { … … public class StyledMapRenderer extends AbstractMapRenderer { 916 924 color = relationSelectedColor; 917 925 } 918 926 g.setColor(color); 919 g.draw Rect(p.x - w/2 + img.offsetX - 2, p.y - h/2 + img.offsetY - 2, w + 4, h + 4);927 g.draw(new Rectangle2D.Double(x - w/2 + img.offsetX - 2, y - h/2 + img.offsetY - 2, w + 4, h + 4)); 920 928 } 921 929 } 922 930 931 /** 932 * Draw the symbol and possibly a highlight marking on a given node. 933 * @param n The position to draw the symbol on 934 * @param s The symbol to draw 935 * @param fillColor The color to fill the symbol with 936 * @param strokeColor The color to use for the outer corner of the symbol 937 */ 923 938 public void drawNodeSymbol(Node n, Symbol s, Color fillColor, Color strokeColor) { 924 Point p = nc.getPoint(n); 925 int radius = s.size / 2; 939 MapViewPoint p = mapState.getPointFor(n); 926 940 927 941 if (n.isHighlighted()) { 928 drawPointHighlight(p , s.size);942 drawPointHighlight(p.getInView(), s.size); 929 943 } 930 944 931 if (fillColor != null) { 932 g.setColor(fillColor); 933 switch (s.symbol) { 934 case SQUARE: 935 g.fillRect(p.x - radius, p.y - radius, s.size, s.size); 936 break; 937 case CIRCLE: 938 g.fillOval(p.x - radius, p.y - radius, s.size, s.size); 939 break; 940 case TRIANGLE: 941 g.fillPolygon(buildPolygon(p, radius, 3, Math.PI / 2)); 942 break; 943 case PENTAGON: 944 g.fillPolygon(buildPolygon(p, radius, 5, Math.PI / 2)); 945 break; 946 case HEXAGON: 947 g.fillPolygon(buildPolygon(p, radius, 6)); 948 break; 949 case HEPTAGON: 950 g.fillPolygon(buildPolygon(p, radius, 7, Math.PI / 2)); 951 break; 952 case OCTAGON: 953 g.fillPolygon(buildPolygon(p, radius, 8, Math.PI / 8)); 954 break; 955 case NONAGON: 956 g.fillPolygon(buildPolygon(p, radius, 9, Math.PI / 2)); 957 break; 958 case DECAGON: 959 g.fillPolygon(buildPolygon(p, radius, 10)); 960 break; 961 default: 962 throw new AssertionError(); 945 if (fillColor != null || strokeColor != null) { 946 Shape shape = s.buildShapeAround(p.getInViewX(), p.getInViewY()); 947 948 if (fillColor != null) { 949 g.setColor(fillColor); 950 g.fill(shape); 963 951 } 964 } 965 if (s.stroke != null) { 966 g.setStroke(s.stroke); 967 g.setColor(strokeColor); 968 switch (s.symbol) { 969 case SQUARE: 970 g.drawRect(p.x - radius, p.y - radius, s.size - 1, s.size - 1); 971 break; 972 case CIRCLE: 973 g.drawOval(p.x - radius, p.y - radius, s.size - 1, s.size - 1); 974 break; 975 case TRIANGLE: 976 g.drawPolygon(buildPolygon(p, radius, 3, Math.PI / 2)); 977 break; 978 case PENTAGON: 979 g.drawPolygon(buildPolygon(p, radius, 5, Math.PI / 2)); 980 break; 981 case HEXAGON: 982 g.drawPolygon(buildPolygon(p, radius, 6)); 983 break; 984 case HEPTAGON: 985 g.drawPolygon(buildPolygon(p, radius, 7, Math.PI / 2)); 986 break; 987 case OCTAGON: 988 g.drawPolygon(buildPolygon(p, radius, 8, Math.PI / 8)); 989 break; 990 case NONAGON: 991 g.drawPolygon(buildPolygon(p, radius, 9, Math.PI / 2)); 992 break; 993 case DECAGON: 994 g.drawPolygon(buildPolygon(p, radius, 10)); 995 break; 996 default: 997 throw new AssertionError(); 952 if (s.stroke != null) { 953 g.setStroke(s.stroke); 954 g.setColor(strokeColor); 955 g.draw(shape); 956 g.setStroke(new BasicStroke()); 998 957 } 999 g.setStroke(new BasicStroke());1000 958 } 1001 959 } 1002 960 … … public class StyledMapRenderer extends AbstractMapRenderer { 1010 968 * @param clr The color to use for drawing the text. 1011 969 */ 1012 970 public void drawOrderNumber(Node n1, Node n2, int orderNumber, Color clr) { 1013 Point p1 = nc.getPoint(n1);1014 Point p2 = nc.getPoint(n2);971 MapViewPoint p1 = mapState.getPointFor(n1); 972 MapViewPoint p2 = mapState.getPointFor(n2); 1015 973 drawOrderNumber(p1, p2, orderNumber, clr); 1016 974 } 1017 975 … … public class StyledMapRenderer extends AbstractMapRenderer { 1021 979 * @param path path to draw 1022 980 * @param line line style 1023 981 */ 1024 private void drawPathHighlight( GeneralPathpath, BasicStroke line) {982 private void drawPathHighlight(Path2D path, BasicStroke line) { 1025 983 if (path == null) 1026 984 return; 1027 985 g.setColor(highlightColorTransparent); … … public class StyledMapRenderer extends AbstractMapRenderer { 1040 998 * @param p point 1041 999 * @param size highlight size 1042 1000 */ 1043 private void drawPointHighlight(Point p, int size) {1001 private void drawPointHighlight(Point2D p, int size) { 1044 1002 g.setColor(highlightColorTransparent); 1045 1003 int s = size + highlightPointRadius; 1046 1004 if (useWiderHighlight) s += widerHighlight; 1047 1005 while (s >= size) { 1048 1006 int r = (int) Math.floor(s/2d); 1049 g.fill RoundRect(p.x-r, p.y-r, s, s, r, r);1007 g.fill(new RoundRectangle2D.Double(p.getX()-r, p.getY()-r, s, s, r, r)); 1050 1008 s -= highlightStep; 1051 1009 } 1052 1010 } … … public class StyledMapRenderer extends AbstractMapRenderer { 1235 1193 } 1236 1194 1237 1195 /** 1196 * A half segment that can be used to place text on it. Used in the drawTextOnPath algorithm. 1197 * @author Michael Zangl 1198 * @since xxx 1199 */ 1200 private static class HalfSegment { 1201 /** 1202 * start point of half segment (as length along the way) 1203 */ 1204 final double start; 1205 /** 1206 * end point of half segment (as length along the way) 1207 */ 1208 final double end; 1209 /** 1210 * quality factor (off screen / partly on screen / fully on screen) 1211 */ 1212 final double quality; 1213 1214 /** 1215 * Create a new half segment 1216 * @param start The start along the way 1217 * @param end The end of the segment 1218 * @param quality A quality factor. 1219 */ 1220 public HalfSegment(double start, double end, double quality) { 1221 super(); 1222 this.start = start; 1223 this.end = end; 1224 this.quality = quality; 1225 } 1226 1227 @Override 1228 public String toString() { 1229 return "HalfSegment [start=" + start + ", end=" + end + ", quality=" + quality + "]"; 1230 } 1231 } 1232 1233 /** 1238 1234 * Draws a text along a given way. 1239 1235 * @param way The way to draw the text on. 1240 1236 * @param text The text definition (font/.../text content) to draw. … … public class StyledMapRenderer extends AbstractMapRenderer { 1251 1247 1252 1248 Rectangle bounds = g.getClipBounds(); 1253 1249 1254 Polygon poly = new Polygon(); 1255 Point lastPoint = null; 1256 Iterator<Node> it = way.getNodes().iterator(); 1257 double pathLength = 0; 1258 long dx, dy; 1250 List<MapViewPoint> points = way.getNodes().stream().map(mapState::getPointFor).collect(Collectors.toList()); 1259 1251 1260 1252 // find half segments that are long enough to draw text on (don't draw text over the cross hair in the center of each segment) 1261 List<Double> longHalfSegmentStart = new ArrayList<>(); // start point of half segment (as length along the way) 1262 List<Double> longHalfSegmentEnd = new ArrayList<>(); // end point of half segment (as length along the way) 1263 List<Double> longHalfsegmentQuality = new ArrayList<>(); // quality factor (off screen / partly on screen / fully on screen) 1264 1265 while (it.hasNext()) { 1266 Node n = it.next(); 1267 Point p = nc.getPoint(n); 1268 poly.addPoint(p.x, p.y); 1253 List<HalfSegment> longHalfSegment = new ArrayList<>(); 1269 1254 1270 if (lastPoint != null) { 1271 dx = (long) p.x - lastPoint.x; 1272 dy = (long) p.y - lastPoint.y; 1273 double segmentLength = Math.sqrt(dx*dx + dy*dy); 1274 if (segmentLength > 2*(rec.getWidth()+4)) { 1275 Point center = new Point((lastPoint.x + p.x)/2, (lastPoint.y + p.y)/2); 1276 double q = 0; 1277 if (bounds != null) { 1278 if (bounds.contains(lastPoint) && bounds.contains(center)) { 1279 q = 2; 1280 } else if (bounds.contains(lastPoint) || bounds.contains(center)) { 1281 q = 1; 1282 } 1283 } 1284 longHalfSegmentStart.add(pathLength); 1285 longHalfSegmentEnd.add(pathLength + segmentLength / 2); 1286 longHalfsegmentQuality.add(q); 1287 1288 q = 0; 1289 if (bounds != null) { 1290 if (bounds.contains(center) && bounds.contains(p)) { 1291 q = 2; 1292 } else if (bounds.contains(center) || bounds.contains(p)) { 1293 q = 1; 1294 } 1295 } 1296 longHalfSegmentStart.add(pathLength + segmentLength / 2); 1297 longHalfSegmentEnd.add(pathLength + segmentLength); 1298 longHalfsegmentQuality.add(q); 1299 } 1300 pathLength += segmentLength; 1301 } 1302 lastPoint = p; 1303 } 1255 double pathLength = computePath(2 * (rec.getWidth() + 4), bounds, points, longHalfSegment); 1304 1256 1305 1257 if (rec.getWidth() > pathLength) 1306 1258 return; 1307 1259 1308 1260 double t1, t2; 1309 1261 1310 if (!longHalfSegmentStart.isEmpty()) { 1311 if (way.getNodesCount() == 2) { 1312 // For 2 node ways, the two half segments are exactly the same size and distance from the center. 1313 // Prefer the first one for consistency. 1314 longHalfsegmentQuality.set(0, longHalfsegmentQuality.get(0) + 0.5); 1315 } 1316 1317 // find the long half segment that is closest to the center of the way 1318 // candidates with higher quality value are preferred 1319 double bestStart = Double.NaN; 1320 double bestEnd = Double.NaN; 1321 double bestDistanceToCenter = Double.MAX_VALUE; 1322 double bestQuality = -1; 1323 for (int i = 0; i < longHalfSegmentStart.size(); i++) { 1324 double start = longHalfSegmentStart.get(i); 1325 double end = longHalfSegmentEnd.get(i); 1326 double dist = Math.abs(0.5 * (end + start) - 0.5 * pathLength); 1327 if (longHalfsegmentQuality.get(i) > bestQuality 1328 || (dist < bestDistanceToCenter && Utils.equalsEpsilon(longHalfsegmentQuality.get(i), bestQuality))) { 1329 bestStart = start; 1330 bestEnd = end; 1331 bestDistanceToCenter = dist; 1332 bestQuality = longHalfsegmentQuality.get(i); 1333 } 1334 } 1335 double remaining = bestEnd - bestStart - rec.getWidth(); // total space left and right from the text 1262 if (!longHalfSegment.isEmpty()) { 1263 // find the segment with the best quality. If there are several with best quality, the one close to the center is prefered. 1264 HalfSegment best = longHalfSegment.stream().max( 1265 Comparator.comparingDouble( segment -> 1266 segment.quality - 1e-5 * Math.abs(0.5 * (segment.end + segment.start) - 0.5 * pathLength) 1267 )).get(); 1268 double remaining = best.end - best.start - rec.getWidth(); // total space left and right from the text 1336 1269 // The space left and right of the text should be distributed 20% - 80% (towards the center), 1337 1270 // but the smaller space should not be less than 7 px. 1338 1271 // However, if the total remaining space is less than 14 px, then distribute it evenly. 1339 1272 double smallerSpace = Math.min(Math.max(0.2 * remaining, 7), 0.5 * remaining); 1340 if ((best End + bestStart)/2 < pathLength/2) {1341 t2 = best End - smallerSpace;1273 if ((best.end + best.start)/2 < pathLength/2) { 1274 t2 = best.end - smallerSpace; 1342 1275 t1 = t2 - rec.getWidth(); 1343 1276 } else { 1344 t1 = best Start + smallerSpace;1277 t1 = best.start + smallerSpace; 1345 1278 t2 = t1 + rec.getWidth(); 1346 1279 } 1347 1280 } else { … … public class StyledMapRenderer extends AbstractMapRenderer { 1352 1285 t1 /= pathLength; 1353 1286 t2 /= pathLength; 1354 1287 1355 double[] p1 = pointAt(t1, po ly, pathLength);1356 double[] p2 = pointAt(t2, po ly, pathLength);1288 double[] p1 = pointAt(t1, points, pathLength); 1289 double[] p2 = pointAt(t2, points, pathLength); 1357 1290 1358 1291 if (p1 == null || p2 == null) 1359 1292 return; … … public class StyledMapRenderer extends AbstractMapRenderer { 1381 1314 for (int i = 0; i < gv.getNumGlyphs(); ++i) { 1382 1315 Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D(); 1383 1316 double t = tStart + offsetSign * (gvOffset + rect.getX() + rect.getWidth()/2) / pathLength; 1384 double[] p = pointAt(t, po ly, pathLength);1317 double[] p = pointAt(t, points, pathLength); 1385 1318 if (p != null) { 1386 1319 AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]); 1387 1320 trfm.rotate(p[2]+angleOffset); … … public class StyledMapRenderer extends AbstractMapRenderer { 1401 1334 } 1402 1335 } 1403 1336 1337 private double computePath(double minSegmentLength, Rectangle bounds, List<MapViewPoint> points, 1338 List<HalfSegment> longHalfSegment) { 1339 MapViewPoint lastPoint = points.get(0); 1340 double pathLength = 0; 1341 for (MapViewPoint p : points.subList(1, points.size())) { 1342 double segmentLength = p.distanceToInView(lastPoint); 1343 if (segmentLength > minSegmentLength) { 1344 Point2D center = new Point2D.Double((lastPoint.getInViewX() + p.getInViewX())/2, (lastPoint.getInViewY() + p.getInViewY())/2); 1345 double q = computeQuality(bounds, lastPoint, center); 1346 // prefer the first one for quality equality. 1347 longHalfSegment.add(new HalfSegment(pathLength, pathLength + segmentLength / 2, q)); 1348 1349 q = 0; 1350 if (bounds != null) { 1351 if (bounds.contains(center) && bounds.contains(p.getInView())) { 1352 q = 2; 1353 } else if (bounds.contains(center) || bounds.contains(p.getInView())) { 1354 q = 1; 1355 } 1356 } 1357 longHalfSegment.add(new HalfSegment(pathLength + segmentLength / 2, pathLength + segmentLength, q)); 1358 } 1359 pathLength += segmentLength; 1360 lastPoint = p; 1361 } 1362 return pathLength; 1363 } 1364 1365 private static double computeQuality(Rectangle bounds, MapViewPoint p1, Point2D p2) { 1366 double q = 0; 1367 if (bounds != null) { 1368 if (bounds.contains(p1.getInView())) { 1369 q += 1; 1370 } 1371 if (bounds.contains(p2)) { 1372 q += 1; 1373 } 1374 } 1375 return q; 1376 } 1377 1404 1378 /** 1405 1379 * draw way. This method allows for two draw styles (line using color, dashes using dashedColor) to be passed. 1406 1380 * @param way The way to draw … … public class StyledMapRenderer extends AbstractMapRenderer { 1420 1394 boolean showOrientation, boolean showHeadArrowOnly, 1421 1395 boolean showOneway, boolean onewayReversed) { 1422 1396 1423 GeneralPath path = new GeneralPath();1424 GeneralPath orientationArrows = showOrientation ? new GeneralPath() : null;1425 GeneralPath onewayArrows = showOneway ? new GeneralPath() : null;1426 GeneralPath onewayArrowsCasing = showOneway ? new GeneralPath() : null;1397 MapPath2D path = new MapPath2D(); 1398 MapPath2D orientationArrows = showOrientation ? new MapPath2D() : null; 1399 MapPath2D onewayArrows = showOneway ? new MapPath2D() : null; 1400 MapPath2D onewayArrowsCasing = showOneway ? new MapPath2D() : null; 1427 1401 Rectangle bounds = g.getClipBounds(); 1428 1402 if (bounds != null) { 1429 1403 // avoid arrow heads at the border 1430 1404 bounds.grow(100, 100); 1431 1405 } 1432 1406 1433 double wayLength = 0;1434 Point lastPoint = null;1435 1407 boolean initialMoveToNeeded = true; 1436 1408 List<Node> wayNodes = way.getNodes(); 1437 1409 if (wayNodes.size() < 2) return; … … public class StyledMapRenderer extends AbstractMapRenderer { 1447 1419 highlightSegs = new GeneralPath(); 1448 1420 } 1449 1421 1450 Point p1 = nc.getPoint(ws.getFirstNode());1451 Point p2 = nc.getPoint(ws.getSecondNode());1452 highlightSegs.moveTo(p1. x, p1.y);1453 highlightSegs.lineTo(p2. x, p2.y);1422 Point2D p1 = mapState.getPointFor(ws.getFirstNode()).getInView(); 1423 Point2D p2 = mapState.getPointFor(ws.getSecondNode()).getInView(); 1424 highlightSegs.moveTo(p1.getX(), p1.getY()); 1425 highlightSegs.lineTo(p2.getX(), p2.getY()); 1454 1426 } 1455 1427 1456 1428 drawPathHighlight(highlightSegs, line); 1457 1429 } 1458 1430 1459 Iterator<Point> it = new OffsetIterator(wayNodes, offset); 1431 MapViewPoint lastPoint = null; 1432 double wayLength = 0; 1433 Iterator<MapViewPoint> it = new OffsetIterator(wayNodes, offset); 1460 1434 while (it.hasNext()) { 1461 Point p = it.next();1435 MapViewPoint p = it.next(); 1462 1436 if (lastPoint != null) { 1463 Point p1 = lastPoint; 1464 Point p2 = p; 1465 1466 /** 1467 * Do custom clipping to work around openjdk bug. It leads to 1468 * drawing artefacts when zooming in a lot. (#4289, #4424) 1469 * (Looks like int overflow.) 1470 */ 1471 LineClip clip = new LineClip(p1, p2, bounds); 1472 if (clip.execute()) { 1473 if (!p1.equals(clip.getP1())) { 1474 p1 = clip.getP1(); 1475 path.moveTo(p1.x, p1.y); 1476 } else if (initialMoveToNeeded) { 1477 initialMoveToNeeded = false; 1478 path.moveTo(p1.x, p1.y); 1479 } 1480 p2 = clip.getP2(); 1481 path.lineTo(p2.x, p2.y); 1437 MapViewPoint p1 = lastPoint; 1438 MapViewPoint p2 = p; 1482 1439 1483 /* draw arrow */ 1484 if (showHeadArrowOnly ? !it.hasNext() : showOrientation) { 1485 final double segmentLength = p1.distance(p2); 1486 if (segmentLength != 0) { 1487 final double l = (10. + line.getLineWidth()) / segmentLength; 1488 1489 final double sx = l * (p1.x - p2.x); 1490 final double sy = l * (p1.y - p2.y); 1440 if (initialMoveToNeeded) { 1441 initialMoveToNeeded = false; 1442 path.moveTo(p1); 1443 } 1444 path.lineTo(p2); 1491 1445 1492 orientationArrows.moveTo(p2.x + cosPHI * sx - sinPHI * sy, p2.y + sinPHI * sx + cosPHI * sy); 1493 orientationArrows.lineTo(p2.x, p2.y); 1494 orientationArrows.lineTo(p2.x + cosPHI * sx + sinPHI * sy, p2.y - sinPHI * sx + cosPHI * sy); 1495 } 1496 } 1497 if (showOneway) { 1498 final double segmentLength = p1.distance(p2); 1499 if (segmentLength != 0) { 1500 final double nx = (p2.x - p1.x) / segmentLength; 1501 final double ny = (p2.y - p1.y) / segmentLength; 1502 1503 final double interval = 60; 1504 // distance from p1 1505 double dist = interval - (wayLength % interval); 1506 1507 while (dist < segmentLength) { 1508 for (int i = 0; i < 2; ++i) { 1509 double onewaySize = i == 0 ? 3d : 2d; 1510 GeneralPath onewayPath = i == 0 ? onewayArrowsCasing : onewayArrows; 1511 1512 // scale such that border is 1 px 1513 final double fac = -(onewayReversed ? -1 : 1) * onewaySize * (1 + sinPHI) / (sinPHI * cosPHI); 1514 final double sx = nx * fac; 1515 final double sy = ny * fac; 1516 1517 // Attach the triangle at the incenter and not at the tip. 1518 // Makes the border even at all sides. 1519 final double x = p1.x + nx * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI)); 1520 final double y = p1.y + ny * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI)); 1521 1522 onewayPath.moveTo(x, y); 1523 onewayPath.lineTo(x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy); 1524 onewayPath.lineTo(x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy); 1525 onewayPath.lineTo(x, y); 1526 } 1527 dist += interval; 1528 } 1446 /* draw arrow */ 1447 if (showHeadArrowOnly ? !it.hasNext() : showOrientation) { 1448 //TODO: Cache 1449 ArrowPaintHelper drawHelper = new ArrowPaintHelper(PHI, 10 + line.getLineWidth()); 1450 drawHelper.paintArrowAt(orientationArrows, p2, p1); 1451 } 1452 if (showOneway) { 1453 final double segmentLength = p1.distanceToInView(p2); 1454 if (segmentLength != 0) { 1455 final double nx = (p2.getInViewX() - p1.getInViewX()) / segmentLength; 1456 final double ny = (p2.getInViewY() - p1.getInViewY()) / segmentLength; 1457 1458 final double interval = 60; 1459 // distance from p1 1460 double dist = interval - (wayLength % interval); 1461 1462 while (dist < segmentLength) { 1463 appenOnewayPath(onewayReversed, p1, nx, ny, dist, 3d, onewayArrowsCasing); 1464 appenOnewayPath(onewayReversed, p1, nx, ny, dist, 2d, onewayArrows); 1465 dist += interval; 1529 1466 } 1530 wayLength += segmentLength;1531 1467 } 1468 wayLength += segmentLength; 1532 1469 } 1533 1470 } 1534 1471 lastPoint = p; … … public class StyledMapRenderer extends AbstractMapRenderer { 1539 1476 displaySegments(path, orientationArrows, onewayArrows, onewayArrowsCasing, color, line, dashes, dashedColor); 1540 1477 } 1541 1478 1479 private void appenOnewayPath(boolean onewayReversed, MapViewPoint p1, double nx, double ny, double dist, double onewaySize, Path2D onewayPath) { 1480 // scale such that border is 1 px 1481 final double fac = -(onewayReversed ? -1 : 1) * onewaySize * (1 + sinPHI) / (sinPHI * cosPHI); 1482 final double sx = nx * fac; 1483 final double sy = ny * fac; 1484 1485 // Attach the triangle at the incenter and not at the tip. 1486 // Makes the border even at all sides. 1487 final double x = p1.getInViewX() + nx * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI)); 1488 final double y = p1.getInViewY() + ny * (dist + (onewayReversed ? -1 : 1) * (onewaySize / sinPHI)); 1489 1490 onewayPath.moveTo(x, y); 1491 onewayPath.lineTo(x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy); 1492 onewayPath.lineTo(x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy); 1493 onewayPath.lineTo(x, y); 1494 } 1495 1542 1496 /** 1543 1497 * Gets the "circum". This is the distance on the map in meters that 100 screen pixels represent. 1544 1498 * @return The "circum" … … public class StyledMapRenderer extends AbstractMapRenderer { 1728 1682 return null; 1729 1683 } 1730 1684 1685 /** 1686 * Test if the area is visible 1687 * @param area The area, interpreted in east/north space. 1688 * @return true if it is visible. 1689 */ 1731 1690 private boolean isAreaVisible(Path2D.Double area) { 1732 1691 Rectangle2D bounds = area.getBounds2D(); 1733 1692 if (bounds.isEmpty()) return false; 1734 Point2D p = nc.getPoint2D(new EastNorth(bounds.getX(), bounds.getY()));1735 if (p.get X() > nc.getWidth()) return false;1736 if (p.get Y() < 0) return false;1737 p = nc.getPoint2D(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));1738 if (p.get X() < 0) return false;1739 if (p.get Y() > nc.getHeight()) return false;1693 MapViewPoint p = mapState.getPointFor(new EastNorth(bounds.getX(), bounds.getY())); 1694 if (p.getInViewX() > mapState.getViewWidth()) return false; 1695 if (p.getInViewY() < 0) return false; 1696 p = mapState.getPointFor(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight())); 1697 if (p.getInViewX() < 0) return false; 1698 if (p.getInViewY() > mapState.getViewHeight()) return false; 1740 1699 return true; 1741 1700 } 1742 1701 … … public class StyledMapRenderer extends AbstractMapRenderer { 1752 1711 return showNames; 1753 1712 } 1754 1713 1755 private static double[] pointAt(double t, Polygonpoly, double pathLength) {1714 private static double[] pointAt(double t, List<MapViewPoint> poly, double pathLength) { 1756 1715 double totalLen = t * pathLength; 1757 1716 double curLen = 0; 1758 longdx, dy;1717 double dx, dy; 1759 1718 double segLen; 1760 1719 1761 1720 // Yes, it is inefficient to iterate from the beginning for each glyph. 1762 1721 // Can be optimized if it turns out to be slow. 1763 for (int i = 1; i < poly. npoints; ++i) {1764 dx = (long) poly.xpoints[i] - poly.xpoints[i-1];1765 dy = (long) poly.ypoints[i] - poly.ypoints[i-1];1722 for (int i = 1; i < poly.size(); ++i) { 1723 dx = poly.get(i).getInViewX() - poly.get(i - 1).getInViewX(); 1724 dy = poly.get(i).getInViewY() - poly.get(i - 1).getInViewY(); 1766 1725 segLen = Math.sqrt(dx*dx + dy*dy); 1767 1726 if (totalLen > curLen + segLen) { 1768 1727 curLen += segLen; 1769 1728 continue; 1770 1729 } 1771 1730 return new double[] { 1772 poly. xpoints[i-1]+(totalLen - curLen)/segLen*dx,1773 poly. ypoints[i-1]+(totalLen - curLen)/segLen*dy,1731 poly.get(i - 1).getInViewX() + (totalLen - curLen) / segLen * dx, 1732 poly.get(i - 1).getInViewY() + (totalLen - curLen) / segLen * dy, 1774 1733 Math.atan2(dy, dx)}; 1775 1734 } 1776 1735 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 8b5b530..3ea8916 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 10 11 import org.openstreetmap.josm.Main; 11 12 import org.openstreetmap.josm.data.osm.Node; … … import org.openstreetmap.josm.gui.mappaint.MultiCascade; 21 22 import org.openstreetmap.josm.gui.mappaint.StyleElementList; 22 23 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProvider; 23 24 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.SimpleBoxProvider; 25 import org.openstreetmap.josm.gui.mappaint.styleelement.Symbol.SymbolShape; 24 26 import org.openstreetmap.josm.gui.util.RotationAngle; 25 27 import org.openstreetmap.josm.tools.CheckParameterUtil; 26 28 import org.openstreetmap.josm.tools.Utils; … … import org.openstreetmap.josm.tools.Utils; 31 33 public class NodeElement extends StyleElement { 32 34 public final MapImage mapImage; 33 35 public final RotationAngle mapImageAngle; 36 /** 37 * The symbol that should be used for drawing this node. 38 */ 34 39 public final Symbol symbol; 35 40 36 public enum SymbolShape { SQUARE, CIRCLE, TRIANGLE, PENTAGON, HEXAGON, HEPTAGON, OCTAGON, NONAGON, DECAGON }37 38 public static class Symbol {39 public SymbolShape symbol;40 public int size;41 public Stroke stroke;42 public Color strokeColor;43 public Color fillColor;44 45 public Symbol(SymbolShape symbol, int size, Stroke stroke, Color strokeColor, Color fillColor) {46 if (stroke != null && strokeColor == null)47 throw new IllegalArgumentException("Stroke given without color");48 if (stroke == null && fillColor == null)49 throw new IllegalArgumentException("Either a stroke or a fill color must be given");50 this.symbol = symbol;51 this.size = size;52 this.stroke = stroke;53 this.strokeColor = strokeColor;54 this.fillColor = fillColor;55 }56 57 @Override58 public boolean equals(Object obj) {59 if (obj == null || getClass() != obj.getClass())60 return false;61 final Symbol other = (Symbol) obj;62 return symbol == other.symbol &&63 size == other.size &&64 Objects.equals(stroke, other.stroke) &&65 Objects.equals(strokeColor, other.strokeColor) &&66 Objects.equals(fillColor, other.fillColor);67 }68 69 @Override70 public int hashCode() {71 return Objects.hash(symbol, size, stroke, strokeColor, fillColor);72 }73 74 @Override75 public String toString() {76 return "symbol=" + symbol + " size=" + size +77 (stroke != null ? " stroke=" + stroke + " strokeColor=" + strokeColor : "") +78 (fillColor != null ? " fillColor=" + fillColor : "");79 }80 }81 82 41 private static final String[] ICON_KEYS = {ICON_IMAGE, ICON_WIDTH, ICON_HEIGHT, ICON_OPACITY, ICON_OFFSET_X, ICON_OFFSET_Y}; 83 42 84 43 public static final NodeElement SIMPLE_NODE_ELEMSTYLE; … … public class NodeElement extends StyleElement { 182 141 mapImage.offsetX = Math.round(offsetXF); 183 142 mapImage.offsetY = Math.round(offsetYF); 184 143 185 mapImage.alpha = Math.min(255, Math.max(0, Integer.valueOf(Main.pref.getInteger("mappaint.icon-image-alpha", 255))));144 mapImage.alpha = Math.min(255, Math.max(0, Main.pref.getInteger("mappaint.icon-image-alpha", 255))); 186 145 Integer pAlpha = Utils.color_float2int(c.get(keys[ICON_OPACITY_IDX], null, float.class)); 187 146 if (pAlpha != null) { 188 147 mapImage.alpha = pAlpha; … … public class NodeElement extends StyleElement { 192 151 193 152 private static Symbol createSymbol(Environment env) { 194 153 Cascade c = env.mc.getCascade(env.layer); 195 Cascade cDef = env.mc.getCascade("default");196 154 197 SymbolShape shape;198 155 Keyword shapeKW = c.get("symbol-shape", null, Keyword.class); 199 156 if (shapeKW == null) 200 157 return null; 201 if ("square".equals(shapeKW.val)) { 202 shape = SymbolShape.SQUARE; 203 } else if ("circle".equals(shapeKW.val)) { 204 shape = SymbolShape.CIRCLE; 205 } else if ("triangle".equals(shapeKW.val)) { 206 shape = SymbolShape.TRIANGLE; 207 } else if ("pentagon".equals(shapeKW.val)) { 208 shape = SymbolShape.PENTAGON; 209 } else if ("hexagon".equals(shapeKW.val)) { 210 shape = SymbolShape.HEXAGON; 211 } else if ("heptagon".equals(shapeKW.val)) { 212 shape = SymbolShape.HEPTAGON; 213 } else if ("octagon".equals(shapeKW.val)) { 214 shape = SymbolShape.OCTAGON; 215 } else if ("nonagon".equals(shapeKW.val)) { 216 shape = SymbolShape.NONAGON; 217 } else if ("decagon".equals(shapeKW.val)) { 218 shape = SymbolShape.DECAGON; 219 } else 158 Optional<SymbolShape> shape = SymbolShape.forName(shapeKW.val); 159 if (!shape.isPresent()) { 220 160 return null; 161 } 221 162 163 Cascade cDef = env.mc.getCascade("default"); 222 164 Float sizeOnDefault = cDef.get("symbol-size", null, Float.class); 223 165 if (sizeOnDefault != null && sizeOnDefault <= 0) { 224 166 sizeOnDefault = null; … … public class NodeElement extends StyleElement { 266 208 } 267 209 } 268 210 269 return new Symbol(shape , Math.round(size), stroke, strokeColor, fillColor);211 return new Symbol(shape.get(), Math.round(size), stroke, strokeColor, fillColor); 270 212 } 271 213 272 214 @Override … … public class NodeElement extends StyleElement { 278 220 painter.drawNodeIcon(n, mapImage, painter.isInactiveMode() || n.isDisabled(), selected, member, 279 221 mapImageAngle == null ? 0.0 : mapImageAngle.getRotationAngle(primitive)); 280 222 } else if (symbol != null) { 281 Color fillColor = symbol.fillColor; 282 if (fillColor != null) { 283 if (painter.isInactiveMode() || n.isDisabled()) { 284 fillColor = settings.getInactiveColor(); 285 } else if (defaultSelectedHandling && selected) { 286 fillColor = settings.getSelectedColor(fillColor.getAlpha()); 287 } else if (member) { 288 fillColor = settings.getRelationSelectedColor(fillColor.getAlpha()); 289 } 290 } 291 Color strokeColor = symbol.strokeColor; 292 if (strokeColor != null) { 293 if (painter.isInactiveMode() || n.isDisabled()) { 294 strokeColor = settings.getInactiveColor(); 295 } else if (defaultSelectedHandling && selected) { 296 strokeColor = settings.getSelectedColor(strokeColor.getAlpha()); 297 } else if (member) { 298 strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha()); 299 } 300 } 301 painter.drawNodeSymbol(n, symbol, fillColor, strokeColor); 223 paintWithSymbol(settings, painter, selected, member, n); 302 224 } else { 303 225 Color color; 304 226 boolean isConnection = n.isConnectionNode(); … … public class NodeElement extends StyleElement { 341 263 } 342 264 } 343 265 266 private void paintWithSymbol(MapPaintSettings settings, StyledMapRenderer painter, boolean selected, boolean member, 267 Node n) { 268 Color fillColor = symbol.fillColor; 269 if (fillColor != null) { 270 if (painter.isInactiveMode() || n.isDisabled()) { 271 fillColor = settings.getInactiveColor(); 272 } else if (defaultSelectedHandling && selected) { 273 fillColor = settings.getSelectedColor(fillColor.getAlpha()); 274 } else if (member) { 275 fillColor = settings.getRelationSelectedColor(fillColor.getAlpha()); 276 } 277 } 278 Color strokeColor = symbol.strokeColor; 279 if (strokeColor != null) { 280 if (painter.isInactiveMode() || n.isDisabled()) { 281 strokeColor = settings.getInactiveColor(); 282 } else if (defaultSelectedHandling && selected) { 283 strokeColor = settings.getSelectedColor(strokeColor.getAlpha()); 284 } else if (member) { 285 strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha()); 286 } 287 } 288 painter.drawNodeSymbol(n, symbol, fillColor, strokeColor); 289 } 290 344 291 public BoxProvider getBoxProvider() { 345 292 if (mapImage != null) 346 293 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 }