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