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

Last change on this file since 5012 was 5012, checked in by akks, 12 years ago

tried to fix 0-node way validation bug, see #7437

  • Property svn:eol-style set to native
File size: 10.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.LinkedList;
12import java.util.List;
13
14import org.openstreetmap.josm.Main;
15import org.openstreetmap.josm.data.osm.Node;
16import org.openstreetmap.josm.data.osm.OsmPrimitive;
17import org.openstreetmap.josm.data.osm.Relation;
18import org.openstreetmap.josm.data.osm.RelationMember;
19import org.openstreetmap.josm.data.osm.Way;
20import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
21import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay;
22import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;
23import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
24import org.openstreetmap.josm.data.validation.Severity;
25import org.openstreetmap.josm.data.validation.Test;
26import org.openstreetmap.josm.data.validation.TestError;
27import org.openstreetmap.josm.gui.mappaint.AreaElemStyle;
28import org.openstreetmap.josm.gui.mappaint.ElemStyles;
29import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
30
31public class MultipolygonTest extends Test {
32
33 protected static final int WRONG_MEMBER_TYPE = 1601;
34 protected static final int WRONG_MEMBER_ROLE = 1602;
35 protected static final int NON_CLOSED_WAY = 1603;
36 protected static final int MISSING_OUTER_WAY = 1604;
37 protected static final int INNER_WAY_OUTSIDE = 1605;
38 protected static final int CROSSING_WAYS = 1606;
39 protected static final int OUTER_STYLE_MISMATCH = 1607;
40 protected static final int INNER_STYLE_MISMATCH = 1608;
41 protected static final int NOT_CLOSED = 1609;
42 protected static final int NO_STYLE = 1610;
43 protected static final int NO_STYLE_POLYGON = 1611;
44
45 private static ElemStyles styles;
46
47 private final List<List<Node>> nonClosedWays = new ArrayList<List<Node>>();
48
49 private final double SCALE = 1.0; // arbitrary scale - we could test every possible scale, but this should suffice
50
51 public MultipolygonTest() {
52 super(tr("Multipolygon"),
53 tr("This test checks if multipolygons are valid."));
54 }
55
56 @Override
57 public void initialize() throws Exception {
58 styles = MapPaintStyles.getStyles();
59 }
60
61 private List<List<Node>> joinWays(Collection<Way> ways) {
62 List<List<Node>> result = new ArrayList<List<Node>>();
63 List<Way> waysToJoin = new ArrayList<Way>();
64 for (Way way : ways) {
65 if (way.isClosed()) {
66 result.add(way.getNodes());
67 } else {
68 waysToJoin.add(way);
69 }
70 }
71
72 for (JoinedWay jw : Multipolygon.joinWays(waysToJoin)) {
73 if (!jw.isClosed()) {
74 nonClosedWays.add(jw.getNodes());
75 } else {
76 result.add(jw.getNodes());
77 }
78 }
79 return result;
80 }
81
82 private GeneralPath createPath(List<Node> nodes) {
83 GeneralPath result = new GeneralPath();
84 result.moveTo((float) nodes.get(0).getCoor().lat(), (float) nodes.get(0).getCoor().lon());
85 for (int i=1; i<nodes.size(); i++) {
86 Node n = nodes.get(i);
87 result.lineTo((float) n.getCoor().lat(), (float) n.getCoor().lon());
88 }
89 return result;
90 }
91
92 private List<GeneralPath> createPolygons(List<List<Node>> joinedWays) {
93 List<GeneralPath> result = new ArrayList<GeneralPath>();
94 for (List<Node> way : joinedWays) {
95 result.add(createPath(way));
96 }
97 return result;
98 }
99
100 private Intersection getPolygonIntersection(GeneralPath outer, List<Node> inner) {
101 boolean inside = false;
102 boolean outside = false;
103
104 for (Node n : inner) {
105 boolean contains = outer.contains(n.getCoor().lat(), n.getCoor().lon());
106 inside = inside | contains;
107 outside = outside | !contains;
108 if (inside & outside) {
109 return Intersection.CROSSING;
110 }
111 }
112
113 return inside ? Intersection.INSIDE : Intersection.OUTSIDE;
114 }
115
116 @Override
117 public void visit(Way w) {
118 if (!w.isClosed() && ElemStyles.hasAreaElemStyle(w, false)) {
119 List<Node> nodes = w.getNodes();
120 if (nodes.size()<1) return; // fix zero nodes bug
121 errors.add(new TestError(this, Severity.WARNING, tr("Area style way is not closed"), NOT_CLOSED,
122 Collections.singletonList(w), Arrays.asList(nodes.get(0), nodes.get(nodes.size() - 1))));
123 }
124 }
125
126 @Override
127 public void visit(Relation r) {
128 nonClosedWays.clear();
129 if (r.isMultipolygon()) {
130 checkMembersAndRoles(r);
131
132 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r);
133
134 boolean hasOuterWay = false;
135 for (RelationMember m : r.getMembers()) {
136 if ("outer".equals(m.getRole())) {
137 hasOuterWay = true;
138 break;
139 }
140 }
141 if (!hasOuterWay) {
142 errors.add(new TestError(this, Severity.WARNING, tr("No outer way for multipolygon"), MISSING_OUTER_WAY, r));
143 }
144
145 for (RelationMember rm : r.getMembers()) {
146 if (!rm.getMember().isUsable())
147 return; // Rest of checks is only for complete multipolygons
148 }
149
150 List<List<Node>> innerWays = joinWays(polygon.getInnerWays()); // Side effect - sets nonClosedWays
151 List<List<Node>> outerWays = joinWays(polygon.getOuterWays());
152 if (styles != null) {
153
154 AreaElemStyle area = ElemStyles.getAreaElemStyle(r, false);
155 boolean areaStyle = area != null;
156 // If area style was not found for relation then use style of ways
157 if (area == null) {
158 for (Way w : polygon.getOuterWays()) {
159 area = ElemStyles.getAreaElemStyle(w, true);
160 if (area != null) {
161 break;
162 }
163 }
164 if(area == null)
165 errors.add(new TestError(this, Severity.OTHER, tr("No style for multipolygon"), NO_STYLE, r));
166 else
167 errors.add( new TestError(this, Severity.OTHER, tr("No style in multipolygon relation"),
168 NO_STYLE_POLYGON, r));
169 }
170
171 if (area != null) {
172 for (Way wInner : polygon.getInnerWays()) {
173 AreaElemStyle areaInner = ElemStyles.getAreaElemStyle(wInner, false);
174
175 if (areaInner != null && area.equals(areaInner)) {
176 List<OsmPrimitive> l = new ArrayList<OsmPrimitive>();
177 l.add(r);
178 l.add(wInner);
179 errors.add( new TestError(this, Severity.WARNING, tr("Style for inner way equals multipolygon"),
180 INNER_STYLE_MISMATCH, l, Collections.singletonList(wInner)));
181 }
182 }
183 if(!areaStyle) {
184 for (Way wOuter : polygon.getOuterWays()) {
185 AreaElemStyle areaOuter = ElemStyles.getAreaElemStyle(wOuter, false);
186 if (areaOuter != null && !area.equals(areaOuter)) {
187 List<OsmPrimitive> l = new ArrayList<OsmPrimitive>();
188 l.add(r);
189 l.add(wOuter);
190 errors.add(new TestError(this, Severity.WARNING, tr("Style for outer way mismatches"),
191 OUTER_STYLE_MISMATCH, l, Collections.singletonList(wOuter)));
192 }
193 }
194 }
195 }
196 }
197
198 List<Node> openNodes = new LinkedList<Node>();
199 for (List<Node> w : nonClosedWays) {
200 if (w.size()<1) continue;
201 openNodes.add(w.get(0));
202 openNodes.add(w.get(w.size() - 1));
203 }
204 if (!openNodes.isEmpty()) {
205 List<OsmPrimitive> primitives = new LinkedList<OsmPrimitive>();
206 primitives.add(r);
207 primitives.addAll(openNodes);
208 Arrays.asList(openNodes, r);
209 errors.add(new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY,
210 primitives, openNodes));
211 }
212
213 // For painting is used Polygon class which works with ints only. For validation we need more precision
214 List<GeneralPath> outerPolygons = createPolygons(outerWays);
215 for (List<Node> pdInner : innerWays) {
216 boolean outside = true;
217 boolean crossing = false;
218 List<Node> outerWay = null;
219 for (int i=0; i<outerWays.size(); i++) {
220 GeneralPath outer = outerPolygons.get(i);
221 Intersection intersection = getPolygonIntersection(outer, pdInner);
222 outside = outside & intersection == Intersection.OUTSIDE;
223 if (intersection == Intersection.CROSSING) {
224 crossing = true;
225 outerWay = outerWays.get(i);
226 }
227 }
228 if (outside || crossing) {
229 List<List<Node>> highlights = new ArrayList<List<Node>>();
230 highlights.add(pdInner);
231 if (outside) {
232 errors.add(new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), INNER_WAY_OUTSIDE, Collections.singletonList(r), highlights));
233 } else if (crossing) {
234 highlights.add(outerWay);
235 errors.add(new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), CROSSING_WAYS, Collections.singletonList(r), highlights));
236 }
237 }
238 }
239 }
240 }
241
242 private void checkMembersAndRoles(Relation r) {
243 for (RelationMember rm : r.getMembers()) {
244 if (rm.isWay()) {
245 if (!("inner".equals(rm.getRole()) || "outer".equals(rm.getRole()) || !rm.hasRole())) {
246 errors.add(new TestError(this, Severity.WARNING, tr("No useful role for multipolygon member"), WRONG_MEMBER_ROLE, rm.getMember()));
247 }
248 } else {
249 if(!"admin_centre".equals(rm.getRole()))
250 errors.add(new TestError(this, Severity.WARNING, tr("Non-Way in multipolygon"), WRONG_MEMBER_TYPE, rm.getMember()));
251 }
252 }
253 }
254}
Note: See TracBrowser for help on using the repository browser.