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.Arrays;
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;
017import org.openstreetmap.josm.tools.Predicates;
018import org.openstreetmap.josm.tools.Utils;
019
020/**
021 * Checks for ways connected to areas.
022 * @since 4682
023 */
024public class WayConnectedToArea extends Test {
025
026    /**
027     * Constructs a new {@code WayConnectedToArea} test.
028     */
029    public WayConnectedToArea() {
030        super(tr("Way connected to Area"), tr("Checks for ways connected to areas."));
031    }
032
033    @Override
034    public void visit(Way w) {
035        if (!w.isUsable() || w.isClosed() || !w.hasKey("highway")) {
036            return;
037        }
038
039        boolean hasway = false;
040        List<OsmPrimitive> r = w.firstNode().getReferrers();
041        for (OsmPrimitive p : r) {
042            if(p != w && p.hasKey("highway")) {
043                hasway = true;
044                break;
045            }
046        }
047        if (!hasway) {
048            for (OsmPrimitive p : r) {
049                testForError(w, w.firstNode(), p);
050            }
051        }
052        hasway = false;
053        r = w.lastNode().getReferrers();
054        for (OsmPrimitive p : r) {
055            if(p != w && p.hasKey("highway")) {
056                hasway = true;
057                break;
058            }
059        }
060        if (!hasway) {
061            for (OsmPrimitive p : r) {
062                testForError(w, w.lastNode(), p);
063            }
064        }
065    }
066
067    private void testForError(Way w, Node wayNode, OsmPrimitive p) {
068        if (wayNode.isOutsideDownloadArea()) {
069            return;
070        } else if (Utils.exists(wayNode.getReferrers(), Predicates.hasTag("route", "ferry"))) {
071            return;
072        } else if (isArea(p)) {
073            addPossibleError(w, wayNode, p, p);
074        } else {
075            for (OsmPrimitive r : p.getReferrers()) {
076                if (r instanceof Relation
077                        && r.hasTag("type", "multipolygon")
078                        && isArea(r)) {
079                    addPossibleError(w, wayNode, p, r);
080                    break;
081                }
082            }
083        }
084    }
085
086    private boolean isArea(OsmPrimitive p) {
087        return (p.hasKey("landuse") || p.hasKey("natural"))
088                && ElemStyles.hasAreaElemStyle(p, false);
089    }
090
091    private void addPossibleError(Way w, Node wayNode, OsmPrimitive p, OsmPrimitive area) {
092        // Avoid "legal" cases (see #10655)
093        if (w.hasKey("highway") && wayNode.hasTag("leisure", "slipway") && area.hasTag("natural", "water")) {
094            return;
095        }
096        errors.add(new TestError(this, Severity.WARNING,
097                tr("Way terminates on Area"), 2301,
098                Arrays.asList(w, p),
099                Arrays.asList(wayNode)));
100    }
101}