001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.awt.geom.Line2D;
005import java.util.Objects;
006
007/**
008 * A segment consisting of 2 consecutive nodes out of a way.
009 */
010public final class WaySegment implements Comparable<WaySegment> {
011
012    /**
013     * The way.
014     */
015    public final Way way;
016
017    /**
018     * The index of one of the 2 nodes in the way.  The other node has the
019     * index <code>lowerIndex + 1</code>.
020     */
021    public final int lowerIndex;
022
023    /**
024     * Constructs a new {@code WaySegment}.
025     * @param w The way
026     * @param i The node lower index
027     * @throws IllegalArgumentException in case of invalid index
028     */
029    public WaySegment(Way w, int i) {
030        way = w;
031        lowerIndex = i;
032        if (i < 0 || i >= w.getNodesCount() - 1) {
033            throw new IllegalArgumentException(toString());
034        }
035    }
036
037    /**
038     * Returns the first node of the way segment.
039     * @return the first node
040     */
041    public Node getFirstNode() {
042        return way.getNode(lowerIndex);
043    }
044
045    /**
046     * Returns the second (last) node of the way segment.
047     * @return the second node
048     */
049    public Node getSecondNode() {
050        return way.getNode(lowerIndex + 1);
051    }
052
053    /**
054     * Determines and returns the way segment for the given way and node pair.
055     * @param way way
056     * @param first first node
057     * @param second second node
058     * @return way segment
059     * @throws IllegalArgumentException if the node pair is not part of way
060     */
061    public static WaySegment forNodePair(Way way, Node first, Node second) {
062        int endIndex = way.getNodesCount() - 1;
063        while (endIndex > 0) {
064            final int indexOfFirst = way.getNodes().subList(0, endIndex).lastIndexOf(first);
065            if (second.equals(way.getNode(indexOfFirst + 1))) {
066                return new WaySegment(way, indexOfFirst);
067            }
068            endIndex--;
069        }
070        throw new IllegalArgumentException("Node pair is not part of way!");
071    }
072
073    /**
074     * Returns this way segment as complete way.
075     * @return the way segment as {@code Way}
076     */
077    public Way toWay() {
078        Way w = new Way();
079        w.addNode(getFirstNode());
080        w.addNode(getSecondNode());
081        return w;
082    }
083
084    @Override
085    public boolean equals(Object o) {
086        if (this == o) return true;
087        if (o == null || getClass() != o.getClass()) return false;
088        WaySegment that = (WaySegment) o;
089        return lowerIndex == that.lowerIndex &&
090                Objects.equals(way, that.way);
091    }
092
093    @Override
094    public int hashCode() {
095        return Objects.hash(way, lowerIndex);
096    }
097
098    @Override
099    public int compareTo(WaySegment o) {
100        return o == null ? -1 : (equals(o) ? 0 : toWay().compareTo(o.toWay()));
101    }
102
103    /**
104     * Checks whether this segment crosses other segment
105     *
106     * @param s2 The other segment
107     * @return true if both segments crosses
108     */
109    public boolean intersects(WaySegment s2) {
110        if (getFirstNode().equals(s2.getFirstNode()) || getSecondNode().equals(s2.getSecondNode()) ||
111                getFirstNode().equals(s2.getSecondNode()) || getSecondNode().equals(s2.getFirstNode()))
112            return false;
113
114        return Line2D.linesIntersect(
115                getFirstNode().getEastNorth().east(), getFirstNode().getEastNorth().north(),
116                getSecondNode().getEastNorth().east(), getSecondNode().getEastNorth().north(),
117                s2.getFirstNode().getEastNorth().east(), s2.getFirstNode().getEastNorth().north(),
118                s2.getSecondNode().getEastNorth().east(), s2.getSecondNode().getEastNorth().north());
119    }
120
121    /**
122     * Checks whether this segment and another way segment share the same points
123     * @param s2 The other segment
124     * @return true if other way segment is the same or reverse
125     */
126    public boolean isSimilar(WaySegment s2) {
127        return (getFirstNode().equals(s2.getFirstNode()) && getSecondNode().equals(s2.getSecondNode()))
128            || (getFirstNode().equals(s2.getSecondNode()) && getSecondNode().equals(s2.getFirstNode()));
129    }
130
131    @Override
132    public String toString() {
133        return "WaySegment [way=" + way.getUniqueId() + ", lowerIndex=" + lowerIndex + ']';
134    }
135}