001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer.validation; 003 004import java.awt.Color; 005import java.awt.Graphics2D; 006import java.awt.Point; 007import java.util.HashSet; 008import java.util.List; 009import java.util.Objects; 010import java.util.Set; 011 012import org.openstreetmap.josm.data.coor.LatLon; 013import org.openstreetmap.josm.data.osm.Node; 014import org.openstreetmap.josm.data.osm.OsmPrimitive; 015import org.openstreetmap.josm.data.osm.Relation; 016import org.openstreetmap.josm.data.osm.Way; 017import org.openstreetmap.josm.data.osm.WaySegment; 018import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor; 019import org.openstreetmap.josm.data.validation.TestError; 020import org.openstreetmap.josm.data.validation.ValidatorVisitor; 021import org.openstreetmap.josm.gui.MapView; 022import org.openstreetmap.josm.gui.draw.MapViewPath; 023import org.openstreetmap.josm.gui.draw.SymbolShape; 024import org.openstreetmap.josm.tools.Utils; 025 026/** 027 * Visitor that highlights the primitives affected by an error 028 * @author frsantos 029 * @since 5671 030 * @since 12823 (moved from {@code data.validation} package) 031 */ 032public class PaintVisitor implements OsmPrimitiveVisitor, ValidatorVisitor { 033 /** The graphics */ 034 private final Graphics2D g; 035 /** The MapView */ 036 private final MapView mv; 037 038 /** The severity color */ 039 private Color color; 040 /** Is the error selected ? */ 041 private boolean selected; 042 043 private final Set<PaintedPoint> paintedPoints = new HashSet<>(); 044 private final Set<PaintedSegment> paintedSegments = new HashSet<>(); 045 046 /** 047 * Constructor 048 * @param g The graphics 049 * @param mv The Mapview 050 */ 051 public PaintVisitor(Graphics2D g, MapView mv) { 052 this.g = g; 053 this.mv = mv; 054 } 055 056 protected static class PaintedPoint { 057 protected final LatLon p1; 058 protected final Color color; 059 060 public PaintedPoint(LatLon p1, Color color) { 061 this.p1 = p1; 062 this.color = color; 063 } 064 065 @Override 066 public int hashCode() { 067 return Objects.hash(p1, color); 068 } 069 070 @Override 071 public boolean equals(Object obj) { 072 if (this == obj) return true; 073 if (obj == null || getClass() != obj.getClass()) return false; 074 PaintedPoint that = (PaintedPoint) obj; 075 return Objects.equals(p1, that.p1) && 076 Objects.equals(color, that.color); 077 } 078 } 079 080 protected static class PaintedSegment extends PaintedPoint { 081 private final LatLon p2; 082 083 public PaintedSegment(LatLon p1, LatLon p2, Color color) { 084 super(p1, color); 085 this.p2 = p2; 086 } 087 088 @Override 089 public int hashCode() { 090 return Objects.hash(super.hashCode(), p2); 091 } 092 093 @Override 094 public boolean equals(Object obj) { 095 if (this == obj) return true; 096 if (obj == null || getClass() != obj.getClass()) return false; 097 if (!super.equals(obj)) return false; 098 PaintedSegment that = (PaintedSegment) obj; 099 return Objects.equals(p2, that.p2); 100 } 101 } 102 103 @Override 104 public void visit(TestError error) { 105 if (error != null && !error.isIgnored()) { 106 color = error.getSeverity().getColor(); 107 selected = error.isSelected(); 108 error.visitHighlighted(this); 109 } 110 } 111 112 @Override 113 public void visit(OsmPrimitive p) { 114 if (p.isUsable()) { 115 p.accept(this); 116 } 117 } 118 119 /** 120 * Draws a circle around the node 121 * @param n The node 122 * @param color The circle color 123 */ 124 protected void drawNode(Node n, Color color) { 125 PaintedPoint pp = new PaintedPoint(n.getCoor(), color); 126 127 if (!paintedPoints.contains(pp)) { 128 MapViewPath circle = new MapViewPath(mv.getState()).shapeAround(n, SymbolShape.CIRCLE, 10); 129 130 if (selected) { 131 g.setColor(getHighlightColor(color)); 132 g.fill(circle); 133 } 134 g.setColor(color); 135 g.draw(circle); 136 paintedPoints.add(pp); 137 } 138 } 139 140 /** 141 * Draws a line around the segment 142 * 143 * @param p1 The first point of segment 144 * @param p2 The second point of segment 145 * @param color The color 146 */ 147 protected void drawSegment(Point p1, Point p2, Color color) { 148 149 double t = Math.atan2((double) p2.x - p1.x, (double) p2.y - p1.y); 150 double cosT = 5 * Math.cos(t); 151 double sinT = 5 * Math.sin(t); 152 int deg = (int) Utils.toDegrees(t); 153 if (selected) { 154 g.setColor(getHighlightColor(color)); 155 int[] x = new int[] {(int) (p1.x + cosT), (int) (p2.x + cosT), 156 (int) (p2.x - cosT), (int) (p1.x - cosT)}; 157 int[] y = new int[] {(int) (p1.y - sinT), (int) (p2.y - sinT), 158 (int) (p2.y + sinT), (int) (p1.y + sinT)}; 159 g.fillPolygon(x, y, 4); 160 g.fillArc(p1.x - 5, p1.y - 5, 10, 10, deg, 180); 161 g.fillArc(p2.x - 5, p2.y - 5, 10, 10, deg, -180); 162 } 163 g.setColor(color); 164 g.drawLine((int) (p1.x + cosT), (int) (p1.y - sinT), 165 (int) (p2.x + cosT), (int) (p2.y - sinT)); 166 g.drawLine((int) (p1.x - cosT), (int) (p1.y + sinT), 167 (int) (p2.x - cosT), (int) (p2.y + sinT)); 168 g.drawArc(p1.x - 5, p1.y - 5, 10, 10, deg, 180); 169 g.drawArc(p2.x - 5, p2.y - 5, 10, 10, deg, -180); 170 } 171 172 /** 173 * Draws a line around the segment 174 * 175 * @param n1 The first node of segment 176 * @param n2 The second node of segment 177 * @param color The color 178 */ 179 protected void drawSegment(Node n1, Node n2, Color color) { 180 if (n1.isDrawable() && n2.isDrawable() && isSegmentVisible(n1, n2)) { 181 PaintedSegment ps = new PaintedSegment(n1.getCoor(), n2.getCoor(), color); 182 if (!paintedSegments.contains(ps)) { 183 drawSegment(mv.getPoint(n1), mv.getPoint(n2), color); 184 paintedSegments.add(ps); 185 } 186 } 187 } 188 189 /** 190 * Draw a small rectangle. 191 * White if selected (as always) or red otherwise. 192 * 193 * @param n The node to draw. 194 */ 195 @Override 196 public void visit(Node n) { 197 if (n.isDrawable() && isNodeVisible(n)) { 198 drawNode(n, color); 199 } 200 } 201 202 @Override 203 public void visit(Way w) { 204 visit(w.getNodes()); 205 } 206 207 @Override 208 public void visit(WaySegment ws) { 209 if (ws.lowerIndex < 0 || ws.lowerIndex + 1 >= ws.way.getNodesCount()) 210 return; 211 drawSegment(ws.getFirstNode(), ws.getSecondNode(), color); 212 } 213 214 @Override 215 public void visit(Relation r) { 216 /* No idea how to draw a relation. */ 217 } 218 219 /** 220 * Checks if the given node is in the visible area. 221 * @param n The node to check for visibility 222 * @return true if the node is visible 223 */ 224 protected boolean isNodeVisible(Node n) { 225 Point p = mv.getPoint(n); 226 return !((p.x < 0) || (p.y < 0) || (p.x > mv.getWidth()) || (p.y > mv.getHeight())); 227 } 228 229 /** 230 * Checks if the given segment is in the visible area. 231 * NOTE: This will return true for a small number of non-visible segments. 232 * @param n1 The first point of the segment to check 233 * @param n2 The second point of the segment to check 234 * @return {@code true} if the segment is visible 235 */ 236 protected boolean isSegmentVisible(Node n1, Node n2) { 237 Point p1 = mv.getPoint(n1); 238 Point p2 = mv.getPoint(n2); 239 return (p1.x >= 0 || p2.x >= 0) 240 && (p1.y >= 0 || p2.y >= 0) 241 && (p1.x <= mv.getWidth() || p2.x <= mv.getWidth()) 242 && (p1.y <= mv.getHeight() || p2.y <= mv.getHeight()); 243 } 244 245 @Override 246 public void visit(List<Node> nodes) { 247 Node lastN = null; 248 for (Node n : nodes) { 249 if (lastN == null) { 250 lastN = n; 251 continue; 252 } 253 drawSegment(lastN, n, color); 254 lastN = n; 255 } 256 } 257 258 /** 259 * Gets the color to draw highlight markers with. 260 * @param color severity color 261 * @return The color. 262 */ 263 private static Color getHighlightColor(Color color) { 264 return new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) (color.getAlpha() * .4)); 265 } 266 267 /** 268 * Clears the internal painted objects collections. 269 */ 270 public void clearPaintedObjects() { 271 paintedPoints.clear(); 272 paintedSegments.clear(); 273 } 274}