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}