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

Last change on this file since 8870 was 8870, checked in by Don-vip, 9 years ago

sonar - squid:S2325 - "private" methods that don't access instance data should be "static"

  • Property svn:eol-style set to native
File size: 14.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.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 protected static final int OUTER_STYLE = 1613;
57
58 private static volatile ElemStyles styles;
59
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 static GeneralPath createPath(List<Node> nodes) {
94 GeneralPath result = new GeneralPath();
95 result.moveTo((float) nodes.get(0).getCoor().lat(), (float) nodes.get(0).getCoor().lon());
96 for (int i = 1; i < nodes.size(); i++) {
97 Node n = nodes.get(i);
98 result.lineTo((float) n.getCoor().lat(), (float) n.getCoor().lon());
99 }
100 return result;
101 }
102
103 private List<GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) {
104 List<GeneralPath> result = new ArrayList<>();
105 for (Multipolygon.PolyData way : joinedWays) {
106 result.add(createPath(way.getNodes()));
107 }
108 return result;
109 }
110
111 private static Intersection getPolygonIntersection(GeneralPath outer, List<Node> inner) {
112 boolean inside = false;
113 boolean outside = false;
114
115 for (Node n : inner) {
116 boolean contains = outer.contains(n.getCoor().lat(), n.getCoor().lon());
117 inside = inside | contains;
118 outside = outside | !contains;
119 if (inside & outside) {
120 return Intersection.CROSSING;
121 }
122 }
123
124 return inside ? Intersection.INSIDE : Intersection.OUTSIDE;
125 }
126
127 @Override
128 public void visit(Way w) {
129 if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) {
130 List<Node> nodes = w.getNodes();
131 if (nodes.isEmpty()) return; // fix zero nodes bug
132 for (String key : keysCheckedByAnotherTest) {
133 if (w.hasKey(key)) {
134 return;
135 }
136 }
137 errors.add(new TestError(this, Severity.WARNING, tr("Area style way is not closed"), NOT_CLOSED,
138 Collections.singletonList(w), Arrays.asList(nodes.get(0), nodes.get(nodes.size() - 1))));
139 }
140 }
141
142 @Override
143 public void visit(Relation r) {
144 if (r.isMultipolygon()) {
145 checkMembersAndRoles(r);
146
147 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r);
148
149 boolean hasOuterWay = false;
150 for (RelationMember m : r.getMembers()) {
151 if ("outer".equals(m.getRole())) {
152 hasOuterWay = true;
153 break;
154 }
155 }
156 if (!hasOuterWay) {
157 addError(r, new TestError(this, Severity.WARNING, tr("No outer way for multipolygon"), MISSING_OUTER_WAY, r));
158 }
159
160 if (r.hasIncompleteMembers()) {
161 return; // Rest of checks is only for complete multipolygons
162 }
163
164 // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match.
165 final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false);
166 if (newMP != null) {
167 for (RelationMember member : r.getMembers()) {
168 final Collection<RelationMember> memberInNewMP = newMP.b.getMembersFor(Collections.singleton(member.getMember()));
169 if (memberInNewMP != null && !memberInNewMP.isEmpty()) {
170 final String roleInNewMP = memberInNewMP.iterator().next().getRole();
171 if (!member.getRole().equals(roleInNewMP)) {
172 addError(r, new TestError(this, Severity.WARNING, RelationChecker.ROLE_VERIF_PROBLEM_MSG,
173 tr("Role for ''{0}'' should be ''{1}''",
174 member.getMember().getDisplayName(DefaultNameFormatter.getInstance()), roleInNewMP),
175 MessageFormat.format("Role for ''{0}'' should be ''{1}''",
176 member.getMember().getDisplayName(DefaultNameFormatter.getInstance()), roleInNewMP),
177 WRONG_MEMBER_ROLE, Collections.singleton(r), Collections.singleton(member.getMember())));
178 }
179 }
180 }
181 }
182
183 if (styles != null && !"boundary".equals(r.get("type"))) {
184 AreaElemStyle area = ElemStyles.getAreaElemStyle(r, false);
185 boolean areaStyle = area != null;
186 // If area style was not found for relation then use style of ways
187 if (area == null) {
188 for (Way w : polygon.getOuterWays()) {
189 area = ElemStyles.getAreaElemStyle(w, true);
190 if (area != null) {
191 break;
192 }
193 }
194 if (area == null) {
195 addError(r, new TestError(this, Severity.OTHER, tr("No area style for multipolygon"), NO_STYLE, r));
196 } else {
197 /* old style multipolygon - solve: copy tags from outer way to multipolygon */
198 addError(r, new TestError(this, Severity.WARNING,
199 trn("Multipolygon relation should be tagged with area tags and not the outer way",
200 "Multipolygon relation should be tagged with area tags and not the outer ways",
201 polygon.getOuterWays().size()),
202 NO_STYLE_POLYGON, r));
203 }
204 }
205
206 if (area != null) {
207 for (Way wInner : polygon.getInnerWays()) {
208 AreaElemStyle areaInner = ElemStyles.getAreaElemStyle(wInner, false);
209
210 if (areaInner != null && area.equals(areaInner)) {
211 List<OsmPrimitive> l = new ArrayList<>();
212 l.add(r);
213 l.add(wInner);
214 addError(r, new TestError(this, Severity.OTHER,
215 tr("With the currently used mappaint style the style for inner way equals the multipolygon style"),
216 INNER_STYLE_MISMATCH, l, Collections.singletonList(wInner)));
217 }
218 }
219 for (Way wOuter : polygon.getOuterWays()) {
220 AreaElemStyle areaOuter = ElemStyles.getAreaElemStyle(wOuter, false);
221 if (areaOuter != null) {
222 List<OsmPrimitive> l = new ArrayList<>();
223 l.add(r);
224 l.add(wOuter);
225 if (!area.equals(areaOuter)) {
226 addError(r, new TestError(this, Severity.WARNING, !areaStyle ? tr("Style for outer way mismatches")
227 : tr("With the currently used mappaint style(s) the style for outer way mismatches polygon"),
228 OUTER_STYLE_MISMATCH, l, Collections.singletonList(wOuter)));
229 } else if (areaStyle) { /* style on outer way of multipolygon, but equal to polygon */
230 addError(r, new TestError(this, Severity.WARNING, tr("Area style on outer way"), OUTER_STYLE,
231 l, Collections.singletonList(wOuter)));
232 }
233 }
234 }
235 }
236 }
237
238 List<Node> openNodes = polygon.getOpenEnds();
239 if (!openNodes.isEmpty()) {
240 List<OsmPrimitive> primitives = new LinkedList<>();
241 primitives.add(r);
242 primitives.addAll(openNodes);
243 Arrays.asList(openNodes, r);
244 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY,
245 primitives, openNodes));
246 }
247
248 // For painting is used Polygon class which works with ints only. For validation we need more precision
249 List<GeneralPath> outerPolygons = createPolygons(polygon.getOuterPolygons());
250 for (Multipolygon.PolyData pdInner : polygon.getInnerPolygons()) {
251 boolean outside = true;
252 boolean crossing = false;
253 Multipolygon.PolyData outerWay = null;
254 for (int i = 0; i < polygon.getOuterPolygons().size(); i++) {
255 GeneralPath outer = outerPolygons.get(i);
256 Intersection intersection = getPolygonIntersection(outer, pdInner.getNodes());
257 outside = outside & intersection == Intersection.OUTSIDE;
258 if (intersection == Intersection.CROSSING) {
259 crossing = true;
260 outerWay = polygon.getOuterPolygons().get(i);
261 }
262 }
263 if (outside || crossing) {
264 List<List<Node>> highlights = new ArrayList<>();
265 highlights.add(pdInner.getNodes());
266 if (outside) {
267 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"),
268 INNER_WAY_OUTSIDE, Collections.singletonList(r), highlights));
269 } else {
270 highlights.add(outerWay.getNodes());
271 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
272 CROSSING_WAYS, Collections.singletonList(r), highlights));
273 }
274 }
275 }
276 }
277 }
278
279 private void checkMembersAndRoles(Relation r) {
280 for (RelationMember rm : r.getMembers()) {
281 if (rm.isWay()) {
282 if (!(rm.hasRole("inner", "outer") || !rm.hasRole())) {
283 addError(r, new TestError(this, Severity.WARNING, tr("No useful role for multipolygon member"),
284 WRONG_MEMBER_ROLE, rm.getMember()));
285 }
286 } else {
287 if (!rm.hasRole("admin_centre", "label", "subarea", "land_area")) {
288 addError(r, new TestError(this, Severity.WARNING, tr("Non-Way in multipolygon"), WRONG_MEMBER_TYPE, rm.getMember()));
289 }
290 }
291 }
292 }
293
294 private static void addRelationIfNeeded(TestError error, Relation r) {
295 // Fix #8212 : if the error references only incomplete primitives,
296 // add multipolygon in order to let user select something and fix the error
297 Collection<? extends OsmPrimitive> primitives = error.getPrimitives();
298 if (!primitives.contains(r)) {
299 for (OsmPrimitive p : primitives) {
300 if (!p.isIncomplete()) {
301 return;
302 }
303 }
304 List<OsmPrimitive> newPrimitives = new ArrayList<OsmPrimitive>(primitives);
305 newPrimitives.add(0, r);
306 error.setPrimitives(newPrimitives);
307 }
308 }
309
310 private void addError(Relation r, TestError error) {
311 addRelationIfNeeded(error, r);
312 errors.add(error);
313 }
314}
Note: See TracBrowser for help on using the repository browser.