001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.data.validation.tests.CrossingWays.HIGHWAY; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.util.List; 008 009import org.openstreetmap.josm.data.osm.Node; 010import org.openstreetmap.josm.data.osm.OsmPrimitive; 011import org.openstreetmap.josm.data.osm.Relation; 012import org.openstreetmap.josm.data.osm.Way; 013import org.openstreetmap.josm.data.validation.Severity; 014import org.openstreetmap.josm.data.validation.Test; 015import org.openstreetmap.josm.data.validation.TestError; 016import org.openstreetmap.josm.gui.mappaint.ElemStyles; 017 018/** 019 * Checks for ways connected to areas. 020 * @since 4682 021 */ 022public class WayConnectedToArea extends Test { 023 024 /** 025 * Constructs a new {@code WayConnectedToArea} test. 026 */ 027 public WayConnectedToArea() { 028 super(tr("Way connected to Area"), tr("Checks for ways connected to areas.")); 029 } 030 031 @Override 032 public void visit(Way w) { 033 if (!w.isUsable() || w.isClosed() || !w.hasKey(HIGHWAY)) { 034 return; 035 } 036 037 List<OsmPrimitive> r = w.firstNode().getReferrers(); 038 boolean hasway = r.stream().anyMatch(p -> p != w && p.hasKey(HIGHWAY)); 039 if (!hasway) { 040 for (OsmPrimitive p : r) { 041 testForError(w, w.firstNode(), p); 042 } 043 } 044 r = w.lastNode().getReferrers(); 045 hasway = r.stream().anyMatch(p -> p != w && p.hasKey(HIGHWAY)); 046 if (!hasway) { 047 for (OsmPrimitive p : r) { 048 testForError(w, w.lastNode(), p); 049 } 050 } 051 } 052 053 private void testForError(Way w, Node wayNode, OsmPrimitive p) { 054 if (wayNode.isOutsideDownloadArea() 055 || wayNode.getReferrers().stream().anyMatch(p1 -> p1.hasTag("route", "ferry"))) { 056 return; 057 } else if (isArea(p)) { 058 addPossibleError(w, wayNode, p, p); 059 } else { 060 p.referrers(Relation.class) 061 .filter(r -> r.isMultipolygon() && isArea(r)) 062 .findFirst() 063 .ifPresent(r -> addPossibleError(w, wayNode, p, r)); 064 } 065 } 066 067 private static boolean isArea(OsmPrimitive p) { 068 return p.hasKey("landuse", "natural") && ElemStyles.hasAreaElemStyle(p, false); 069 } 070 071 private void addPossibleError(Way w, Node wayNode, OsmPrimitive p, OsmPrimitive area) { 072 // Avoid "legal" cases (see #10655) 073 if (w.hasKey(HIGHWAY) && wayNode.hasTag("leisure", "slipway") && area.hasTag("natural", "water")) { 074 return; 075 } 076 if (wayNode.hasTag("noexit", "yes")) { 077 // Avoid "legal" case (see #17036) 078 return; 079 } 080 errors.add(TestError.builder(this, Severity.WARNING, 2301) 081 .message(tr("Way terminates on Area")) 082 .primitives(w, p) 083 .highlight(wayNode) 084 .build()); 085 } 086}