001 // License: GPL. See LICENSE file for details. 002 package org.openstreetmap.josm.data.validation; 003 004 import java.awt.Color; 005 import java.awt.Graphics; 006 import java.awt.Point; 007 import java.util.Collection; 008 import java.util.Collections; 009 import java.util.List; 010 import java.util.TreeSet; 011 import java.util.ArrayList; 012 013 import org.openstreetmap.josm.command.Command; 014 import org.openstreetmap.josm.data.osm.Node; 015 import org.openstreetmap.josm.data.osm.OsmPrimitive; 016 import org.openstreetmap.josm.data.osm.Relation; 017 import org.openstreetmap.josm.data.osm.Way; 018 import org.openstreetmap.josm.data.osm.WaySegment; 019 import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor; 020 import org.openstreetmap.josm.gui.MapView; 021 022 /** 023 * Validation error 024 * @author frsantos 025 */ 026 public class TestError { 027 /** is this error on the ignore list */ 028 private Boolean ignored = false; 029 /** Severity */ 030 private Severity severity; 031 /** The error message */ 032 private String message; 033 /** Deeper error description */ 034 private String description; 035 private String description_en; 036 /** The affected primitives */ 037 private Collection<? extends OsmPrimitive> primitives; 038 /** The primitives to be highlighted */ 039 private Collection<?> highlighted; 040 /** The tester that raised this error */ 041 private Test tester; 042 /** Internal code used by testers to classify errors */ 043 private int code; 044 /** If this error is selected */ 045 private boolean selected; 046 047 /** 048 * Constructors 049 * @param tester The tester 050 * @param severity The severity of this error 051 * @param message The error message 052 * @param primitive The affected primitive 053 * @param primitives The affected primitives 054 * @param code The test error reference code 055 */ 056 public TestError(Test tester, Severity severity, String message, String description, String description_en, 057 int code, Collection<? extends OsmPrimitive> primitives, Collection<?> highlighted) { 058 this.tester = tester; 059 this.severity = severity; 060 this.message = message; 061 this.description = description; 062 this.description_en = description_en; 063 this.primitives = primitives; 064 this.highlighted = highlighted; 065 this.code = code; 066 } 067 068 public TestError(Test tester, Severity severity, String message, int code, Collection<? extends OsmPrimitive> primitives, 069 Collection<?> highlighted) { 070 this(tester, severity, message, null, null, code, primitives, highlighted); 071 } 072 073 public TestError(Test tester, Severity severity, String message, String description, String description_en, 074 int code, Collection<? extends OsmPrimitive> primitives) { 075 this(tester, severity, message, description, description_en, code, primitives, primitives); 076 } 077 078 public TestError(Test tester, Severity severity, String message, int code, Collection<? extends OsmPrimitive> primitives) { 079 this(tester, severity, message, null, null, code, primitives, primitives); 080 } 081 082 public TestError(Test tester, Severity severity, String message, int code, OsmPrimitive primitive) { 083 this(tester, severity, message, null, null, code, Collections.singletonList(primitive), Collections 084 .singletonList(primitive)); 085 } 086 087 public TestError(Test tester, Severity severity, String message, String description, String description_en, 088 int code, OsmPrimitive primitive) { 089 this(tester, severity, message, description, description_en, code, Collections.singletonList(primitive)); 090 } 091 092 /** 093 * Gets the error message 094 * @return the error message 095 */ 096 public String getMessage() { 097 return message; 098 } 099 100 /** 101 * Gets the error message 102 * @return the error description 103 */ 104 public String getDescription() { 105 return description; 106 } 107 108 /** 109 * Sets the error message 110 * @param message The error message 111 */ 112 public void setMessage(String message) { 113 this.message = message; 114 } 115 116 /** 117 * Gets the list of primitives affected by this error 118 * @return the list of primitives affected by this error 119 */ 120 public Collection<? extends OsmPrimitive> getPrimitives() { 121 return primitives; 122 } 123 124 /** 125 * Gets the list of primitives affected by this error and are selectable 126 * @return the list of selectable primitives affected by this error 127 */ 128 public Collection<? extends OsmPrimitive> getSelectablePrimitives() { 129 List<OsmPrimitive> selectablePrimitives = new ArrayList<OsmPrimitive>(primitives.size()); 130 for (OsmPrimitive o : primitives) { 131 if (o.isSelectable()) { 132 selectablePrimitives.add(o); 133 } 134 } 135 return selectablePrimitives; 136 } 137 138 139 /** 140 * Sets the list of primitives affected by this error 141 * @param primitives the list of primitives affected by this error 142 */ 143 144 public void setPrimitives(List<OsmPrimitive> primitives) { 145 this.primitives = primitives; 146 } 147 148 /** 149 * Gets the severity of this error 150 * @return the severity of this error 151 */ 152 public Severity getSeverity() { 153 return severity; 154 } 155 156 /** 157 * Sets the severity of this error 158 * @param severity the severity of this error 159 */ 160 public void setSeverity(Severity severity) { 161 this.severity = severity; 162 } 163 164 /** 165 * Sets the ignore state for this error 166 */ 167 public String getIgnoreState() { 168 Collection<String> strings = new TreeSet<String>(); 169 String ignorestring = getIgnoreSubGroup(); 170 for (OsmPrimitive o : primitives) { 171 // ignore data not yet uploaded 172 if (o.isNew()) 173 return null; 174 String type = "u"; 175 if (o instanceof Way) { 176 type = "w"; 177 } else if (o instanceof Relation) { 178 type = "r"; 179 } else if (o instanceof Node) { 180 type = "n"; 181 } 182 strings.add(type + "_" + o.getId()); 183 } 184 for (String o : strings) { 185 ignorestring += ":" + o; 186 } 187 return ignorestring; 188 } 189 190 public String getIgnoreSubGroup() { 191 String ignorestring = getIgnoreGroup(); 192 if (description_en != null) { 193 ignorestring += "_" + description_en; 194 } 195 return ignorestring; 196 } 197 198 public String getIgnoreGroup() { 199 return Integer.toString(code); 200 } 201 202 public void setIgnored(boolean state) { 203 ignored = state; 204 } 205 206 public Boolean getIgnored() { 207 return ignored; 208 } 209 210 /** 211 * Gets the tester that raised this error 212 * @return the tester that raised this error 213 */ 214 public Test getTester() { 215 return tester; 216 } 217 218 /** 219 * Gets the code 220 * @return the code 221 */ 222 public int getCode() { 223 return code; 224 } 225 226 /** 227 * Returns true if the error can be fixed automatically 228 * 229 * @return true if the error can be fixed 230 */ 231 public boolean isFixable() { 232 return tester != null && tester.isFixable(this); 233 } 234 235 /** 236 * Fixes the error with the appropriate command 237 * 238 * @return The command to fix the error 239 */ 240 public Command getFix() { 241 if (tester == null || !tester.isFixable(this)) 242 return null; 243 244 return tester.fixError(this); 245 } 246 247 /** 248 * Paints the error on affected primitives 249 * 250 * @param g The graphics 251 * @param mv The MapView 252 */ 253 public void paint(Graphics g, MapView mv) { 254 if (!ignored) { 255 PaintVisitor v = new PaintVisitor(g, mv); 256 visitHighlighted(v); 257 } 258 } 259 260 @SuppressWarnings("unchecked") 261 public void visitHighlighted(ValidatorVisitor v) { 262 for (Object o : highlighted) { 263 if (o instanceof OsmPrimitive) { 264 v.visit((OsmPrimitive) o); 265 } else if (o instanceof WaySegment) { 266 v.visit((WaySegment) o); 267 } else if (o instanceof List<?>) { 268 v.visit((List<Node>)o); 269 } 270 } 271 } 272 273 /** 274 * Visitor that highlights the primitives affected by this error 275 * @author frsantos 276 */ 277 class PaintVisitor extends AbstractVisitor implements ValidatorVisitor { 278 /** The graphics */ 279 private final Graphics g; 280 /** The MapView */ 281 private final MapView mv; 282 283 /** 284 * Constructor 285 * @param g The graphics 286 * @param mv The Mapview 287 */ 288 public PaintVisitor(Graphics g, MapView mv) { 289 this.g = g; 290 this.mv = mv; 291 } 292 293 @Override 294 public void visit(OsmPrimitive p) { 295 if (p.isUsable()) { 296 p.visit(this); 297 } 298 } 299 300 /** 301 * Draws a circle around the node 302 * @param n The node 303 * @param color The circle color 304 */ 305 public void drawNode(Node n, Color color) { 306 Point p = mv.getPoint(n); 307 g.setColor(color); 308 if (selected) { 309 g.fillOval(p.x - 5, p.y - 5, 10, 10); 310 } else { 311 g.drawOval(p.x - 5, p.y - 5, 10, 10); 312 } 313 } 314 315 public void drawSegment(Point p1, Point p2, Color color) { 316 g.setColor(color); 317 318 double t = Math.atan2(p2.x - p1.x, p2.y - p1.y); 319 double cosT = Math.cos(t); 320 double sinT = Math.sin(t); 321 int deg = (int) Math.toDegrees(t); 322 if (selected) { 323 int[] x = new int[] { (int) (p1.x + 5 * cosT), (int) (p2.x + 5 * cosT), 324 (int) (p2.x - 5 * cosT), (int) (p1.x - 5 * cosT) }; 325 int[] y = new int[] { (int) (p1.y - 5 * sinT), (int) (p2.y - 5 * sinT), 326 (int) (p2.y + 5 * sinT), (int) (p1.y + 5 * sinT) }; 327 g.fillPolygon(x, y, 4); 328 g.fillArc(p1.x - 5, p1.y - 5, 10, 10, deg, 180); 329 g.fillArc(p2.x - 5, p2.y - 5, 10, 10, deg, -180); 330 } else { 331 g.drawLine((int) (p1.x + 5 * cosT), (int) (p1.y - 5 * sinT), 332 (int) (p2.x + 5 * cosT), (int) (p2.y - 5 * sinT)); 333 g.drawLine((int) (p1.x - 5 * cosT), (int) (p1.y + 5 * sinT), 334 (int) (p2.x - 5 * cosT), (int) (p2.y + 5 * sinT)); 335 g.drawArc(p1.x - 5, p1.y - 5, 10, 10, deg, 180); 336 g.drawArc(p2.x - 5, p2.y - 5, 10, 10, deg, -180); 337 } 338 } 339 340 /** 341 * Draws a line around the segment 342 * 343 * @param s The segment 344 * @param color The color 345 */ 346 public void drawSegment(Node n1, Node n2, Color color) { 347 drawSegment(mv.getPoint(n1), mv.getPoint(n2), color); 348 } 349 350 /** 351 * Draw a small rectangle. 352 * White if selected (as always) or red otherwise. 353 * 354 * @param n The node to draw. 355 */ 356 @Override 357 public void visit(Node n) { 358 if (isNodeVisible(n)) { 359 drawNode(n, severity.getColor()); 360 } 361 } 362 363 @Override 364 public void visit(Way w) { 365 visit(w.getNodes()); 366 } 367 368 @Override 369 public void visit(WaySegment ws) { 370 if (ws.lowerIndex < 0 || ws.lowerIndex + 1 >= ws.way.getNodesCount()) 371 return; 372 Node a = ws.way.getNodes().get(ws.lowerIndex), b = ws.way.getNodes().get(ws.lowerIndex + 1); 373 if (isSegmentVisible(a, b)) { 374 drawSegment(a, b, severity.getColor()); 375 } 376 } 377 378 @Override 379 public void visit(Relation r) { 380 /* No idea how to draw a relation. */ 381 } 382 383 /** 384 * Checks if the given node is in the visible area. 385 * @param n The node to check for visibility 386 * @return true if the node is visible 387 */ 388 protected boolean isNodeVisible(Node n) { 389 Point p = mv.getPoint(n); 390 return !((p.x < 0) || (p.y < 0) || (p.x > mv.getWidth()) || (p.y > mv.getHeight())); 391 } 392 393 /** 394 * Checks if the given segment is in the visible area. 395 * NOTE: This will return true for a small number of non-visible 396 * segments. 397 * @param ls The segment to check 398 * @return true if the segment is visible 399 */ 400 protected boolean isSegmentVisible(Node n1, Node n2) { 401 Point p1 = mv.getPoint(n1); 402 Point p2 = mv.getPoint(n2); 403 if ((p1.x < 0) && (p2.x < 0)) 404 return false; 405 if ((p1.y < 0) && (p2.y < 0)) 406 return false; 407 if ((p1.x > mv.getWidth()) && (p2.x > mv.getWidth())) 408 return false; 409 if ((p1.y > mv.getHeight()) && (p2.y > mv.getHeight())) 410 return false; 411 return true; 412 } 413 414 @Override 415 public void visit(List<Node> nodes) { 416 Node lastN = null; 417 for (Node n : nodes) { 418 if (lastN == null) { 419 lastN = n; 420 continue; 421 } 422 if (n.isDrawable() && isSegmentVisible(lastN, n)) { 423 drawSegment(lastN, n, severity.getColor()); 424 } 425 lastN = n; 426 } 427 } 428 } 429 430 /** 431 * Sets the selection flag of this error 432 * @param selected if this error is selected 433 */ 434 public void setSelected(boolean selected) { 435 this.selected = selected; 436 } 437 }