source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java@ 7489

Last change on this file since 7489 was 7486, checked in by Don-vip, 10 years ago

fix #10469 - Way tagged aeroway=taxiway causes warning in validator

  • Property svn:eol-style set to native
File size: 14.2 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.GeneralPath;
7import java.text.MessageFormat;
8import java.util.ArrayList;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.HashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Set;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.actions.CreateMultipolygonAction;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Relation;
22import org.openstreetmap.josm.data.osm.RelationMember;
23import org.openstreetmap.josm.data.osm.Way;
24import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
25import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay;
26import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;
27import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
28import org.openstreetmap.josm.data.validation.OsmValidator;
29import org.openstreetmap.josm.data.validation.Severity;
30import org.openstreetmap.josm.data.validation.Test;
31import org.openstreetmap.josm.data.validation.TestError;
32import org.openstreetmap.josm.gui.DefaultNameFormatter;
33import org.openstreetmap.josm.gui.mappaint.AreaElemStyle;
34import org.openstreetmap.josm.gui.mappaint.ElemStyles;
35import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
36import org.openstreetmap.josm.gui.progress.ProgressMonitor;
37import org.openstreetmap.josm.tools.Pair;
38
39/**
40 * Checks if multipolygons are valid
41 * @since 3669
42 */
43public class MultipolygonTest extends Test {
44
45 protected static final int WRONG_MEMBER_TYPE = 1601;
46 protected static final int WRONG_MEMBER_ROLE = 1602;
47 protected static final int NON_CLOSED_WAY = 1603;
48 protected static final int MISSING_OUTER_WAY = 1604;
49 protected static final int INNER_WAY_OUTSIDE = 1605;
50 protected static final int CROSSING_WAYS = 1606;
51 protected static final int OUTER_STYLE_MISMATCH = 1607;
52 protected static final int INNER_STYLE_MISMATCH = 1608;
53 protected static final int NOT_CLOSED = 1609;
54 protected static final int NO_STYLE = 1610;
55 protected static final int NO_STYLE_POLYGON = 1611;
56
57 private static ElemStyles styles;
58
59 private final List<List<Node>> nonClosedWays = new ArrayList<>();
60 private final Set<String> keysCheckedByAnotherTest = new HashSet<>();
61
62 /**
63 * Constructs a new {@code MultipolygonTest}.
64 */
65 public MultipolygonTest() {
66 super(tr("Multipolygon"),
67 tr("This test checks if multipolygons are valid."));
68 }
69
70 @Override
71 public void initialize() {
72 styles = MapPaintStyles.getStyles();
73 }
74
75 @Override
76 public void startTest(ProgressMonitor progressMonitor) {
77 super.startTest(progressMonitor);
78 keysCheckedByAnotherTest.clear();
79 for (Test t : OsmValidator.getEnabledTests(false)) {
80 if (t instanceof UnclosedWays) {
81 keysCheckedByAnotherTest.addAll(((UnclosedWays)t).getCheckedKeys());
82 break;
83 }
84 }
85 }
86
87 @Override
88 public void endTest() {
89 keysCheckedByAnotherTest.clear();
90 super.endTest();
91 }
92
93 private List<List<Node>> joinWays(Collection<Way> ways) {
94 List<List<Node>> result = new ArrayList<>();
95 List<Way> waysToJoin = new ArrayList<>();
96 for (Way way : ways) {
97 if (way.isClosed()) {
98 result.add(way.getNodes());
99 } else {
100 waysToJoin.add(way);
101 }
102 }
103
104 for (JoinedWay jw : Multipolygon.joinWays(waysToJoin)) {
105 if (!jw.isClosed()) {
106 nonClosedWays.add(jw.getNodes());
107 } else {
108 result.add(jw.getNodes());
109 }
110 }
111 return result;
112 }
113
114 private GeneralPath createPath(List<Node> nodes) {
115 GeneralPath result = new GeneralPath();
116 result.moveTo((float) nodes.get(0).getCoor().lat(), (float) nodes.get(0).getCoor().lon());
117 for (int i=1; i<nodes.size(); i++) {
118 Node n = nodes.get(i);
119 result.lineTo((float) n.getCoor().lat(), (float) n.getCoor().lon());
120 }
121 return result;
122 }
123
124 private List<GeneralPath> createPolygons(List<List<Node>> joinedWays) {
125 List<GeneralPath> result = new ArrayList<>();
126 for (List<Node> way : joinedWays) {
127 result.add(createPath(way));
128 }
129 return result;
130 }
131
132 private Intersection getPolygonIntersection(GeneralPath outer, List<Node> inner) {
133 boolean inside = false;
134 boolean outside = false;
135
136 for (Node n : inner) {
137 boolean contains = outer.contains(n.getCoor().lat(), n.getCoor().lon());
138 inside = inside | contains;
139 outside = outside | !contains;
140 if (inside & outside) {
141 return Intersection.CROSSING;
142 }
143 }
144
145 return inside ? Intersection.INSIDE : Intersection.OUTSIDE;
146 }
147
148 @Override
149 public void visit(Way w) {
150 if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) {
151 List<Node> nodes = w.getNodes();
152 if (nodes.size()<1) return; // fix zero nodes bug
153 for (String key : keysCheckedByAnotherTest) {
154 if (w.hasKey(key)) {
155 return;
156 }
157 }
158 errors.add(new TestError(this, Severity.WARNING, tr("Area style way is not closed"), NOT_CLOSED,
159 Collections.singletonList(w), Arrays.asList(nodes.get(0), nodes.get(nodes.size() - 1))));
160 }
161 }
162
163 @Override
164 public void visit(Relation r) {
165 nonClosedWays.clear();
166 if (r.isMultipolygon()) {
167 checkMembersAndRoles(r);
168
169 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r);
170
171 boolean hasOuterWay = false;
172 for (RelationMember m : r.getMembers()) {
173 if ("outer".equals(m.getRole())) {
174 hasOuterWay = true;
175 break;
176 }
177 }
178 if (!hasOuterWay) {
179 addError(r, new TestError(this, Severity.WARNING, tr("No outer way for multipolygon"), MISSING_OUTER_WAY, r));
180 }
181
182 if (r.hasIncompleteMembers()) {
183 return; // Rest of checks is only for complete multipolygons
184 }
185
186 // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match.
187 final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false);
188 if (newMP != null) {
189 for (RelationMember member : r.getMembers()) {
190 final Collection<RelationMember> memberInNewMP = newMP.b.getMembersFor(Collections.singleton(member.getMember()));
191 if (memberInNewMP != null && !memberInNewMP.isEmpty()) {
192 final String roleInNewMP = memberInNewMP.iterator().next().getRole();
193 if (!member.getRole().equals(roleInNewMP)) {
194 addError(r, new TestError(this, Severity.WARNING, RelationChecker.ROLE_VERIF_PROBLEM_MSG,
195 tr("Role for ''{0}'' should be ''{1}''",
196 member.getMember().getDisplayName(DefaultNameFormatter.getInstance()), roleInNewMP),
197 MessageFormat.format("Role for ''{0}'' should be ''{1}''",
198 member.getMember().getDisplayName(DefaultNameFormatter.getInstance()), roleInNewMP),
199 WRONG_MEMBER_ROLE, Collections.singleton(r), Collections.singleton(member.getMember())));
200 }
201 }
202 }
203 }
204
205 List<List<Node>> innerWays = joinWays(polygon.getInnerWays()); // Side effect - sets nonClosedWays
206 List<List<Node>> outerWays = joinWays(polygon.getOuterWays());
207 if (styles != null) {
208
209 AreaElemStyle area = ElemStyles.getAreaElemStyle(r, false);
210 boolean areaStyle = area != null;
211 // If area style was not found for relation then use style of ways
212 if (area == null) {
213 for (Way w : polygon.getOuterWays()) {
214 area = ElemStyles.getAreaElemStyle(w, true);
215 if (area != null) {
216 break;
217 }
218 }
219 if (!"boundary".equals(r.get("type"))) {
220 if (area == null) {
221 addError(r, new TestError(this, Severity.OTHER, tr("No style for multipolygon"), NO_STYLE, r));
222 } else {
223 addError(r, new TestError(this, Severity.OTHER, tr("No style in multipolygon relation"),
224 NO_STYLE_POLYGON, r));
225 }
226 }
227 }
228
229 if (area != null) {
230 for (Way wInner : polygon.getInnerWays()) {
231 AreaElemStyle areaInner = ElemStyles.getAreaElemStyle(wInner, false);
232
233 if (areaInner != null && area.equals(areaInner)) {
234 List<OsmPrimitive> l = new ArrayList<>();
235 l.add(r);
236 l.add(wInner);
237 addError(r, new TestError(this, Severity.WARNING, tr("Style for inner way equals multipolygon"),
238 INNER_STYLE_MISMATCH, l, Collections.singletonList(wInner)));
239 }
240 }
241 if(!areaStyle) {
242 for (Way wOuter : polygon.getOuterWays()) {
243 AreaElemStyle areaOuter = ElemStyles.getAreaElemStyle(wOuter, false);
244 if (areaOuter != null && !area.equals(areaOuter)) {
245 List<OsmPrimitive> l = new ArrayList<>();
246 l.add(r);
247 l.add(wOuter);
248 addError(r, new TestError(this, Severity.WARNING, tr("Style for outer way mismatches"),
249 OUTER_STYLE_MISMATCH, l, Collections.singletonList(wOuter)));
250 }
251 }
252 }
253 }
254 }
255
256 List<Node> openNodes = new LinkedList<>();
257 for (List<Node> w : nonClosedWays) {
258 if (w.size()<1) continue;
259 openNodes.add(w.get(0));
260 openNodes.add(w.get(w.size() - 1));
261 }
262 if (!openNodes.isEmpty()) {
263 List<OsmPrimitive> primitives = new LinkedList<>();
264 primitives.add(r);
265 primitives.addAll(openNodes);
266 Arrays.asList(openNodes, r);
267 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY,
268 primitives, openNodes));
269 }
270
271 // For painting is used Polygon class which works with ints only. For validation we need more precision
272 List<GeneralPath> outerPolygons = createPolygons(outerWays);
273 for (List<Node> pdInner : innerWays) {
274 boolean outside = true;
275 boolean crossing = false;
276 List<Node> outerWay = null;
277 for (int i=0; i<outerWays.size(); i++) {
278 GeneralPath outer = outerPolygons.get(i);
279 Intersection intersection = getPolygonIntersection(outer, pdInner);
280 outside = outside & intersection == Intersection.OUTSIDE;
281 if (intersection == Intersection.CROSSING) {
282 crossing = true;
283 outerWay = outerWays.get(i);
284 }
285 }
286 if (outside || crossing) {
287 List<List<Node>> highlights = new ArrayList<>();
288 highlights.add(pdInner);
289 if (outside) {
290 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), INNER_WAY_OUTSIDE, Collections.singletonList(r), highlights));
291 } else if (crossing) {
292 highlights.add(outerWay);
293 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), CROSSING_WAYS, Collections.singletonList(r), highlights));
294 }
295 }
296 }
297 }
298 }
299
300 private void checkMembersAndRoles(Relation r) {
301 for (RelationMember rm : r.getMembers()) {
302 if (rm.isWay()) {
303 if (!(rm.hasRole("inner", "outer") || !rm.hasRole())) {
304 addError(r, new TestError(this, Severity.WARNING, tr("No useful role for multipolygon member"), WRONG_MEMBER_ROLE, rm.getMember()));
305 }
306 } else {
307 if (!rm.hasRole("admin_centre", "label", "subarea", "land_area")) {
308 addError(r, new TestError(this, Severity.WARNING, tr("Non-Way in multipolygon"), WRONG_MEMBER_TYPE, rm.getMember()));
309 }
310 }
311 }
312 }
313
314 private void addRelationIfNeeded(TestError error, Relation r) {
315 // Fix #8212 : if the error references only incomplete primitives,
316 // add multipolygon in order to let user select something and fix the error
317 Collection<? extends OsmPrimitive> primitives = error.getPrimitives();
318 if (!primitives.contains(r)) {
319 for (OsmPrimitive p : primitives) {
320 if (!p.isIncomplete()) {
321 return;
322 }
323 }
324 List<OsmPrimitive> newPrimitives = new ArrayList<>(primitives);
325 newPrimitives.add(0, r);
326 error.setPrimitives(newPrimitives);
327 }
328 }
329
330 private void addError(Relation r, TestError error) {
331 addRelationIfNeeded(error, r);
332 errors.add(error);
333 }
334}
Note: See TracBrowser for help on using the repository browser.