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