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 if (!partialSelection) 050 return; 051 052 // Test all way segments around this node. 053 // If there is an error in the unchanged part of the way, we do not need to warn the user about it. 054 for (Way way : n.getParentWays()) { 055 if (ignoreWay(way)) { 056 continue; 057 } 058 // Do not simply use index of - a node may be in a way multiple times 059 for (int i = 0; i < way.getNodesCount(); i++) { 060 if (n == way.getNode(i)) { 061 if (i > 0) { 062 visitWaySegment(way, i - 1); 063 } 064 if (i < way.getNodesCount() - 1) { 065 visitWaySegment(way, i); 066 } 067 } 068 } 069 } 070 } 071 072 @Override 073 public void visit(Way w) { 074 if (ignoreWay(w)) { 075 return; 076 } 077 visitedWays.add(w); 078 079 testWay(w); 080 } 081 082 private void testWay(Way w) { 083 for (int i = 0; i < w.getNodesCount() - 1; i++) { 084 visitWaySegment(w, i); 085 } 086 } 087 088 private boolean ignoreWay(Way w) { 089 return w.hasKey("boundary") || w.hasTag("route", "ferry") || w.hasTag("bay", "fjord") 090 || w.hasTag("natural", "strait") || visitedWays.contains(w); 091 } 092 093 private void visitWaySegment(Way w, int i) { 094 LatLon coor1 = w.getNode(i).getCoor(); 095 LatLon coor2 = w.getNode(i + 1).getCoor(); 096 097 if (coor1 != null && coor2 != null) { 098 Double length = coor1.greatCircleDistance(coor2); 099 if (length > maxlength) { 100 addErrorForSegment(new WaySegment(w, i), length / 1000.0); 101 } 102 } 103 } 104 105 private void addErrorForSegment(WaySegment waySegment, Double length) { 106 if (reported.add(waySegment)) { 107 errors.add(TestError.builder(this, Severity.WARNING, LONG_SEGMENT) 108 .message(tr("Long segments"), marktr("Very long segment of {0} kilometers"), length.intValue()) 109 .primitives(waySegment.getWay()) 110 .highlightWaySegments(Collections.singleton(waySegment)) 111 .build()); 112 } 113 } 114 115 @Override 116 public void startTest(ProgressMonitor monitor) { 117 super.startTest(monitor); 118 maxlength = Config.getPref().getInt("validator.maximum.segment.length", 15_000); 119 reported = new HashSet<>(); 120 visitedWays = new HashSet<>(); 121 } 122 123 @Override 124 public void endTest() { 125 super.endTest(); 126 // free memory 127 visitedWays = null; 128 reported = null; 129 } 130 131 @Override 132 public boolean isPrimitiveUsable(OsmPrimitive p) { 133 return p.isUsable() && (isUsableWay(p) || isUsableNode(p)); 134 } 135 136 private static boolean isUsableNode(OsmPrimitive p) { 137 // test changed nodes - ways referred by them may not be checked automatically. 138 return p instanceof Node && ((Node) p).isLatLonKnown(); 139 } 140 141 private static boolean isUsableWay(OsmPrimitive p) { 142 // test only Ways with at least 2 nodes 143 return p instanceof Way && ((Way) p).getNodesCount() > 1; 144 } 145}