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

Last change on this file since 4682 was 4682, checked in by simon04, 12 years ago

fix #2746 - add validation: Way connected to Area

  • Property svn:eol-style set to native
File size: 10.7 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 errors.add(new TestError(this, Severity.WARNING, tr("Area style way is not closed"), NOT_CLOSED,
121 Collections.singletonList(w), Arrays.asList(nodes.get(0), nodes.get(nodes.size() - 1))));
122 }
123 }
124
125 @Override
126 public void visit(Relation r) {
127 nonClosedWays.clear();
128 if (r.isMultipolygon()) {
129 checkMembersAndRoles(r);
130
131 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r);
132
133 boolean hasOuterWay = false;
134 for (RelationMember m : r.getMembers()) {
135 if ("outer".equals(m.getRole())) {
136 hasOuterWay = true;
137 break;
138 }
139 }
140 if (!hasOuterWay) {
141 errors.add(new TestError(this, Severity.WARNING, tr("No outer way for multipolygon"), MISSING_OUTER_WAY, r));
142 }
143
144 for (RelationMember rm : r.getMembers()) {
145 if (!rm.getMember().isUsable())
146 return; // Rest of checks is only for complete multipolygons
147 }
148
149 List<List<Node>> innerWays = joinWays(polygon.getInnerWays()); // Side effect - sets nonClosedWays
150 List<List<Node>> outerWays = joinWays(polygon.getOuterWays());
151 if (styles != null) {
152
153 AreaElemStyle area = ElemStyles.getAreaElemStyle(r, false);
154 boolean areaStyle = area != null;
155 // If area style was not found for relation then use style of ways
156 if (area == null) {
157 for (Way w : polygon.getOuterWays()) {
158 area = ElemStyles.getAreaElemStyle(w, true);
159 if (area != null) {
160 break;
161 }
162 }
163 if(area == null)
164 errors.add(new TestError(this, Severity.OTHER, tr("No style for multipolygon"), NO_STYLE, r));
165 else
166 errors.add( new TestError(this, Severity.OTHER, tr("No style in multipolygon relation"),
167 NO_STYLE_POLYGON, r));
168 }
169
170 if (area != null) {
171 for (Way wInner : polygon.getInnerWays()) {
172 AreaElemStyle areaInner = ElemStyles.getAreaElemStyle(wInner, false);
173
174 if (areaInner != null && area.equals(areaInner)) {
175 List<OsmPrimitive> l = new ArrayList<OsmPrimitive>();
176 l.add(r);
177 l.add(wInner);
178 errors.add( new TestError(this, Severity.WARNING, tr("Style for inner way equals multipolygon"),
179 INNER_STYLE_MISMATCH, l, Collections.singletonList(wInner)));
180 }
181 }
182 if(!areaStyle) {
183 for (Way wOuter : polygon.getOuterWays()) {
184 AreaElemStyle areaOuter = ElemStyles.getAreaElemStyle(wOuter, false);
185 if (areaOuter != null && !area.equals(areaOuter)) {
186 List<OsmPrimitive> l = new ArrayList<OsmPrimitive>();
187 l.add(r);
188 l.add(wOuter);
189 errors.add(new TestError(this, Severity.WARNING, tr("Style for outer way mismatches"),
190 OUTER_STYLE_MISMATCH, l, Collections.singletonList(wOuter)));
191 }
192 }
193 }
194 }
195 }
196
197 List<Node> openNodes = new LinkedList<Node>();
198 for (List<Node> w : nonClosedWays) {
199 openNodes.add(w.get(0));
200 openNodes.add(w.get(w.size() - 1));
201 }
202 if (!openNodes.isEmpty()) {
203 List<OsmPrimitive> primitives = new LinkedList<OsmPrimitive>();
204 primitives.add(r);
205 primitives.addAll(openNodes);
206 Arrays.asList(openNodes, r);
207 errors.add(new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY,
208 primitives, openNodes));
209 }
210
211 // For painting is used Polygon class which works with ints only. For validation we need more precision
212 List<GeneralPath> outerPolygons = createPolygons(outerWays);
213 for (List<Node> pdInner : innerWays) {
214 boolean outside = true;
215 boolean crossing = false;
216 List<Node> outerWay = null;
217 for (int i=0; i<outerWays.size(); i++) {
218 GeneralPath outer = outerPolygons.get(i);
219 Intersection intersection = getPolygonIntersection(outer, pdInner);
220 outside = outside & intersection == Intersection.OUTSIDE;
221 if (intersection == Intersection.CROSSING) {
222 crossing = true;
223 outerWay = outerWays.get(i);
224 }
225 }
226 if (outside || crossing) {
227 List<List<Node>> highlights = new ArrayList<List<Node>>();
228 highlights.add(pdInner);
229 if (outside) {
230 errors.add(new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), INNER_WAY_OUTSIDE, Collections.singletonList(r), highlights));
231 } else if (crossing) {
232 highlights.add(outerWay);
233 errors.add(new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), CROSSING_WAYS, Collections.singletonList(r), highlights));
234 }
235 }
236 }
237 }
238 }
239
240 private void checkMembersAndRoles(Relation r) {
241 for (RelationMember rm : r.getMembers()) {
242 if (rm.isWay()) {
243 if (!("inner".equals(rm.getRole()) || "outer".equals(rm.getRole()) || !rm.hasRole())) {
244 errors.add(new TestError(this, Severity.WARNING, tr("No useful role for multipolygon member"), WRONG_MEMBER_ROLE, rm.getMember()));
245 }
246 } else {
247 if(!"admin_centre".equals(rm.getRole()))
248 errors.add(new TestError(this, Severity.WARNING, tr("Non-Way in multipolygon"), WRONG_MEMBER_TYPE, rm.getMember()));
249 }
250 }
251 }
252}
Note: See TracBrowser for help on using the repository browser.