source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/OverlappingWays.java@ 14501

Last change on this file since 14501 was 14467, checked in by GerdP, 5 years ago

fix #17041 Let OverlappingWays also report ways where exactly one segment overlaps

  • Property svn:eol-style set to native
File size: 8.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.data.validation.tests.CrossingWays.HIGHWAY;
5import static org.openstreetmap.josm.data.validation.tests.CrossingWays.RAILWAY;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.util.ArrayList;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17import java.util.TreeSet;
18
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.OsmUtils;
22import org.openstreetmap.josm.data.osm.Relation;
23import org.openstreetmap.josm.data.osm.Way;
24import org.openstreetmap.josm.data.osm.WaySegment;
25import org.openstreetmap.josm.data.preferences.ListProperty;
26import org.openstreetmap.josm.data.validation.Severity;
27import org.openstreetmap.josm.data.validation.Test;
28import org.openstreetmap.josm.data.validation.TestError;
29import org.openstreetmap.josm.gui.progress.ProgressMonitor;
30import org.openstreetmap.josm.tools.MultiMap;
31import org.openstreetmap.josm.tools.Pair;
32
33/**
34 * Tests if there are overlapping ways.
35 *
36 * @author frsantos
37 * @since 3669
38 */
39public class OverlappingWays extends Test {
40
41 /** Bag of all way segments */
42 private MultiMap<Pair<Node, Node>, WaySegment> nodePairs;
43
44 protected static final int OVERLAPPING_HIGHWAY = 101;
45 protected static final int OVERLAPPING_RAILWAY = 102;
46 protected static final int OVERLAPPING_WAY = 103;
47 protected static final int OVERLAPPING_HIGHWAY_AREA = 111;
48 protected static final int OVERLAPPING_RAILWAY_AREA = 112;
49 protected static final int OVERLAPPING_WAY_AREA = 113;
50 protected static final int DUPLICATE_WAY_SEGMENT = 121;
51
52 protected static final ListProperty IGNORED_KEYS = new ListProperty(
53 "overlapping-ways.ignored-keys", Arrays.asList(
54 "barrier", "building", "building:part", "historic:building", "demolished:building",
55 "removed:building", "disused:building", "abandoned:building", "proposed:building", "man_made"));
56
57 /** Constructor */
58 public OverlappingWays() {
59 super(tr("Overlapping ways"),
60 tr("This test checks that a connection between two nodes "
61 + "is not used by more than one way."));
62 }
63
64 @Override
65 public void startTest(ProgressMonitor monitor) {
66 super.startTest(monitor);
67 nodePairs = new MultiMap<>(1000);
68 }
69
70 private static boolean parentMultipolygonConcernsArea(OsmPrimitive p) {
71 for (Relation r : OsmPrimitive.getFilteredList(p.getReferrers(), Relation.class)) {
72 if (r.concernsArea()) {
73 return true;
74 }
75 }
76 return false;
77 }
78
79 @Override
80 public void endTest() {
81 Map<List<Way>, Set<WaySegment>> seenWays = new HashMap<>(500);
82
83 Collection<TestError> preliminaryErrors = new ArrayList<>();
84 for (Set<WaySegment> duplicated : nodePairs.values()) {
85 int ways = duplicated.size();
86
87 if (ways > 1) {
88 List<OsmPrimitive> prims = new ArrayList<>();
89 List<Way> currentWays = new ArrayList<>();
90 Collection<WaySegment> highlight;
91 int highway = 0;
92 int railway = 0;
93 int area = 0;
94
95 for (WaySegment ws : duplicated) {
96 if (ws.way.hasKey(HIGHWAY)) {
97 highway++;
98 } else if (ws.way.hasKey(RAILWAY)) {
99 railway++;
100 }
101 Boolean ar = OsmUtils.getOsmBoolean(ws.way.get("area"));
102 if (ar != null && ar) {
103 area++;
104 }
105 if (ws.way.concernsArea() || parentMultipolygonConcernsArea(ws.way)) {
106 area++;
107 ways--;
108 }
109
110 prims.add(ws.way);
111 currentWays.add(ws.way);
112 }
113 // These ways not seen before
114 // If two or more of the overlapping ways are highways or railways mark a separate error
115 if ((highlight = seenWays.get(currentWays)) == null) {
116 String errortype;
117 int type;
118
119 if (area > 0) {
120 if (ways == 0 || duplicated.size() == area) {
121 continue; // We previously issued an annoying "Areas share segment" warning
122 } else if (highway == ways) {
123 errortype = tr("Highways share segment with area");
124 type = OVERLAPPING_HIGHWAY_AREA;
125 } else if (railway == ways) {
126 errortype = tr("Railways share segment with area");
127 type = OVERLAPPING_RAILWAY_AREA;
128 } else {
129 errortype = tr("Ways share segment with area");
130 type = OVERLAPPING_WAY_AREA;
131 }
132 } else if (highway == ways) {
133 errortype = tr("Overlapping highways");
134 type = OVERLAPPING_HIGHWAY;
135 } else if (railway == ways) {
136 errortype = tr("Overlapping railways");
137 type = OVERLAPPING_RAILWAY;
138 } else {
139 errortype = tr("Overlapping ways");
140 type = OVERLAPPING_WAY;
141 }
142
143 Severity severity = type < OVERLAPPING_HIGHWAY_AREA ? Severity.WARNING : Severity.OTHER;
144 preliminaryErrors.add(TestError.builder(this, severity, type)
145 .message(errortype)
146 .primitives(prims)
147 .highlightWaySegments(duplicated)
148 .build());
149 seenWays.put(currentWays, duplicated);
150 } else { /* way seen, mark highlight layer only */
151 highlight.addAll(duplicated);
152 }
153 }
154 }
155
156 // see ticket #9598 - only report if at least 3 segments are shared, except for overlapping ways, i.e warnings (see #9820)
157 for (TestError error : preliminaryErrors) {
158 if (error.getSeverity() == Severity.WARNING || error.getHighlighted().size() / error.getPrimitives().size() >= 3) {
159 boolean ignore = false;
160 for (String ignoredKey : IGNORED_KEYS.get()) {
161 if (error.getPrimitives().stream().anyMatch(p -> p.hasKey(ignoredKey))) {
162 ignore = true;
163 break;
164 }
165 }
166 if (!ignore) {
167 errors.add(error);
168 }
169 }
170 }
171
172 super.endTest();
173 nodePairs = null;
174 }
175
176 protected static Set<WaySegment> checkDuplicateWaySegment(Way w) {
177 // test for ticket #4959
178 Set<WaySegment> segments = new TreeSet<>((o1, o2) -> {
179 final List<Node> n1 = Arrays.asList(o1.getFirstNode(), o1.getSecondNode());
180 final List<Node> n2 = Arrays.asList(o2.getFirstNode(), o2.getSecondNode());
181 Collections.sort(n1);
182 Collections.sort(n2);
183 final int first = n1.get(0).compareTo(n2.get(0));
184 final int second = n1.get(1).compareTo(n2.get(1));
185 return first != 0 ? first : second;
186 });
187 final Set<WaySegment> duplicateWaySegments = new HashSet<>();
188
189 for (int i = 0; i < w.getNodesCount() - 1; i++) {
190 final WaySegment segment = new WaySegment(w, i);
191 final boolean wasInSet = !segments.add(segment);
192 if (wasInSet) {
193 duplicateWaySegments.add(segment);
194 }
195 }
196 return duplicateWaySegments;
197 }
198
199 @Override
200 public void visit(Way w) {
201
202 final Set<WaySegment> duplicateWaySegment = checkDuplicateWaySegment(w);
203 if (!duplicateWaySegment.isEmpty()) {
204 errors.add(TestError.builder(this, Severity.ERROR, DUPLICATE_WAY_SEGMENT)
205 .message(tr("Way contains segment twice"))
206 .primitives(w)
207 .highlightWaySegments(duplicateWaySegment)
208 .build());
209 return;
210 }
211
212 Node lastN = null;
213 int i = -2;
214 for (Node n : w.getNodes()) {
215 i++;
216 if (lastN == null) {
217 lastN = n;
218 continue;
219 }
220 nodePairs.put(Pair.sort(new Pair<>(lastN, n)),
221 new WaySegment(w, i));
222 lastN = n;
223 }
224 }
225}
Note: See TracBrowser for help on using the repository browser.