001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.util.Collections; 008import java.util.HashSet; 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.Way; 015import org.openstreetmap.josm.data.osm.WaySegment; 016import org.openstreetmap.josm.data.validation.Severity; 017import org.openstreetmap.josm.data.validation.Test; 018import org.openstreetmap.josm.data.validation.TestError; 019import org.openstreetmap.josm.gui.progress.ProgressMonitor; 020import org.openstreetmap.josm.spi.preferences.Config; 021 022/** 023 * Checks for very long segments. 024 * 025 * @since 8320 026 */ 027public class LongSegment extends Test { 028 029 /** Long segment error */ 030 protected static final int LONG_SEGMENT = 3501; 031 /** Maximum segment length for this test */ 032 protected int maxlength; 033 /** set of visited ways. Tracking this increases performance when checking single nodes. */ 034 private Set<Way> visitedWays; 035 036 /** set of way segments that have been reported */ 037 protected Set<WaySegment> reported; 038 039 /** 040 * Constructor 041 */ 042 public LongSegment() { 043 super(tr("Long segments"), 044 tr("This tests for long way segments, which are usually errors.")); 045 } 046 047 @Override 048 public void visit(Node n) { 049 // Test all way segments around this node. 050 // If there is an error in the unchanged part of the way, we do not need to warn the user about it. 051 for (Way way : n.getParentWays()) { 052 if (ignoreWay(way)) { 053 continue; 054 } 055 // Do not simply use index of - a node may be in a way multiple times 056 for (int i = 0; i < way.getNodesCount(); i++) { 057 if (n == way.getNode(i)) { 058 if (i > 0) { 059 visitWaySegment(way, i - 1); 060 } 061 if (i < way.getNodesCount() - 1) { 062 visitWaySegment(way, i); 063 } 064 } 065 } 066 } 067 } 068 069 @Override 070 public void visit(Way w) { 071 if (ignoreWay(w)) { 072 return; 073 } 074 visitedWays.add(w); 075 076 testWay(w); 077 } 078 079 private void testWay(Way w) { 080 for (int i = 0; i < w.getNodesCount() - 1; i++) { 081 visitWaySegment(w, i); 082 } 083 } 084 085 private boolean ignoreWay(Way w) { 086 return visitedWays.contains(w) || w.hasTag("route", "ferry") || w.hasTag("bay", "fjord") || w.hasTag("natural", "strait"); 087 } 088 089 private void visitWaySegment(Way w, int i) { 090 LatLon coor1 = w.getNode(i).getCoor(); 091 LatLon coor2 = w.getNode(i + 1).getCoor(); 092 093 if (coor1 != null && coor2 != null) { 094 Double length = coor1.greatCircleDistance(coor2); 095 if (length > maxlength) { 096 addErrorForSegment(new WaySegment(w, i), length / 1000.0); 097 } 098 } 099 } 100 101 private void addErrorForSegment(WaySegment waySegment, Double length) { 102 if (reported.add(waySegment)) { 103 errors.add(TestError.builder(this, Severity.WARNING, LONG_SEGMENT) 104 .message(tr("Long segments"), marktr("Very long segment of {0} kilometers"), length.intValue()) 105 .primitives(waySegment.way) 106 .highlightWaySegments(Collections.singleton(waySegment)) 107 .build()); 108 } 109 } 110 111 @Override 112 public void startTest(ProgressMonitor monitor) { 113 super.startTest(monitor); 114 maxlength = Config.getPref().getInt("validator.maximum.segment.length", 15_000); 115 reported = new HashSet<>(); 116 visitedWays = new HashSet<>(); 117 } 118 119 @Override 120 public void endTest() { 121 super.endTest(); 122 // free memory 123 visitedWays = null; 124 reported = null; 125 } 126 127 @Override 128 public boolean isPrimitiveUsable(OsmPrimitive p) { 129 return p.isUsable() && (isUsableWay(p) || isUsableNode(p)); 130 } 131 132 private static boolean isUsableNode(OsmPrimitive p) { 133 // test changed nodes - ways referred by them may not be checked automatically. 134 return p instanceof Node && p.isDrawable(); 135 } 136 137 private static boolean isUsableWay(OsmPrimitive p) { 138 // test only Ways with at least 2 nodes 139 return p instanceof Way && ((Way) p).getNodesCount() > 1; 140 } 141}