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