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.HashSet;
007import java.util.Set;
008
009import org.openstreetmap.josm.data.osm.Node;
010import org.openstreetmap.josm.data.osm.Way;
011import org.openstreetmap.josm.data.validation.Severity;
012import org.openstreetmap.josm.data.validation.Test;
013import org.openstreetmap.josm.data.validation.TestError;
014import org.openstreetmap.josm.data.validation.tests.CrossingWays.SelfCrossing;
015
016/**
017 * Checks for self-intersecting ways.
018 */
019public class SelfIntersectingWay extends Test {
020
021    protected static final int SELF_INTERSECT = 401;
022
023    /**
024     * Constructs a new {@code SelfIntersectingWay} test.
025     */
026    public SelfIntersectingWay() {
027        super(tr("Self-intersecting ways"),
028                tr("This test checks for ways " +
029                        "that contain some of their nodes more than once."));
030    }
031
032    @Override
033    public void visit(Way w) {
034        int last = w.getNodesCount();
035        if (last < 2)
036            return;
037        Set<Node> nodes = new HashSet<>();
038        nodes.add(w.firstNode());
039        int countFirst = 0;
040        int countLast = 0;
041        for (int i = 1; i < last; i++) {
042            Node n = w.getNode(i);
043            if (nodes.contains(n)) {
044                boolean ok = false;
045                if (n == w.firstNode()) {
046                    if (countFirst++ == 0)
047                        ok = true;
048                } else if (i + 1 == last) {
049                    if (countLast++ == 0)
050                        ok = true;
051                }
052                if (!ok || countFirst + countLast > 1) {
053                    errors.add(TestError.builder(this, Severity.WARNING, SELF_INTERSECT)
054                            .message(tr("Self-intersecting ways"))
055                            .primitives(w)
056                            .highlight(n)
057                            .build());
058                    break;
059                }
060            } else {
061                nodes.add(n);
062            }
063        }
064    }
065
066    /**
067     * Check if the given way is self-intersecting
068     * @param way the way to check
069     * @return {@code true} if way contains some nodes more than once
070     * @see SelfCrossing
071     * @since 17386
072     */
073    public static boolean isSelfIntersecting(Way way) {
074        SelfIntersectingWay test = new SelfIntersectingWay();
075        test.visit(way);
076        return !test.errors.isEmpty();
077    }
078}