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

Last change on this file since 7570 was 7570, checked in by bastiK, 10 years ago

see #10529 - remove duplicate test (same as NO_STYLE_POLYGON)

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