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

Last change on this file since 6575 was 6575, checked in by simon04, 10 years ago

fix #7196 - Validator: add multipolygon role check

This is done by creating a new multipolygon using the logics from
"Create multipolygon" and see if the roles match.

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