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        boolean hasway = false;
038        List<OsmPrimitive> r = w.firstNode().getReferrers();
039        for (OsmPrimitive p : r) {
040            if (p != w && p.hasKey(HIGHWAY)) {
041                hasway = true;
042                break;
043            }
044        }
045        if (!hasway) {
046            for (OsmPrimitive p : r) {
047                testForError(w, w.firstNode(), p);
048            }
049        }
050        hasway = false;
051        r = w.lastNode().getReferrers();
052        for (OsmPrimitive p : r) {
053            if (p != w && p.hasKey(HIGHWAY)) {
054                hasway = true;
055                break;
056            }
057        }
058        if (!hasway) {
059            for (OsmPrimitive p : r) {
060                testForError(w, w.lastNode(), p);
061            }
062        }
063    }
064
065    private void testForError(Way w, Node wayNode, OsmPrimitive p) {
066        if (wayNode.isOutsideDownloadArea()
067                || 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", "natural") && ElemStyles.hasAreaElemStyle(p, false);
085    }
086
087    private void addPossibleError(Way w, Node wayNode, OsmPrimitive p, OsmPrimitive area) {
088        // Avoid "legal" cases (see #10655)
089        if (w.hasKey(HIGHWAY) && wayNode.hasTag("leisure", "slipway") && area.hasTag("natural", "water")) {
090            return;
091        }
092        if (wayNode.hasTag("noexit", "yes")) {
093            // Avoid "legal" case (see #17036)
094            return;
095        }
096        errors.add(TestError.builder(this, Severity.WARNING, 2301)
097                .message(tr("Way terminates on Area"))
098                .primitives(w, p)
099                .highlight(wayNode)
100                .build());
101    }
102}