001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.awt.geom.Line2D;
005import java.lang.reflect.Constructor;
006import java.lang.reflect.InvocationTargetException;
007import java.util.Arrays;
008import java.util.Objects;
009
010import org.openstreetmap.josm.tools.Logging;
011
012/**
013 * A segment consisting of 2 consecutive nodes out of a way.
014 * @author Taylor Smock
015 * @param <N> The node type
016 * @param <W> The way type
017 * @since 17862
018 */
019public class IWaySegment<N extends INode, W extends IWay<N>> implements Comparable<IWaySegment<N, W>> {
020
021    private final W way;
022    private final int lowerIndex;
023
024    /**
025     * Returns the way.
026     * @return the way
027     */
028    public W getWay() {
029        return way;
030    }
031
032    /**
033     * Returns the index of the first of the 2 nodes in the way.
034     * @return index of the first of the 2 nodes in the way
035     * @see #getUpperIndex()
036     * @see #getFirstNode()
037     */
038    public int getLowerIndex() {
039        return lowerIndex;
040    }
041
042    /**
043     * Returns the index of the second of the 2 nodes in the way.
044     * @return the index of the second of the 2 nodes in the way
045     * @see #getLowerIndex()
046     * @see #getSecondNode()
047     */
048    public int getUpperIndex() {
049        return lowerIndex + 1;
050    }
051
052    /**
053     * Constructs a new {@code IWaySegment}.
054     * @param w The way
055     * @param i The node lower index
056     * @throws IllegalArgumentException in case of invalid index
057     */
058    public IWaySegment(W w, int i) {
059        way = w;
060        lowerIndex = i;
061        if (i < 0 || i >= w.getNodesCount() - 1) {
062            throw new IllegalArgumentException(toString());
063        }
064    }
065
066    /**
067     * Determines if the segment is usable (node not deleted).
068     * @return {@code true} if the segment is usable
069     * @since 17986
070     */
071    public boolean isUsable() {
072        return getUpperIndex() < way.getNodesCount();
073    }
074
075    /**
076     * Returns the first node of the way segment.
077     * @return the first node
078     */
079    public N getFirstNode() {
080        return way.getNode(getLowerIndex());
081    }
082
083    /**
084     * Returns the second (last) node of the way segment.
085     * @return the second node
086     */
087    public N getSecondNode() {
088        return way.getNode(getUpperIndex());
089    }
090
091    /**
092     * Determines and returns the way segment for the given way and node pair.
093     * @param <N> type of node
094     * @param <W> type of way
095     * @param way way
096     * @param first first node
097     * @param second second node
098     * @return way segment
099     * @throws IllegalArgumentException if the node pair is not part of way
100     */
101    public static <N extends INode, W extends IWay<N>> IWaySegment<N, W> forNodePair(W way, N first, N second) {
102        int endIndex = way.getNodesCount() - 1;
103        while (endIndex > 0) {
104            final int indexOfFirst = way.getNodes().subList(0, endIndex).lastIndexOf(first);
105            if (second.equals(way.getNode(indexOfFirst + 1))) {
106                return new IWaySegment<>(way, indexOfFirst);
107            }
108            endIndex--;
109        }
110        throw new IllegalArgumentException("Node pair is not part of way!");
111    }
112
113    /**
114     * Returns this way segment as complete way.
115     * @return the way segment as {@code Way}
116     * @throws IllegalAccessException See {@link Constructor#newInstance}
117     * @throws IllegalArgumentException See {@link Constructor#newInstance}
118     * @throws InstantiationException See {@link Constructor#newInstance}
119     * @throws InvocationTargetException See {@link Constructor#newInstance}
120     * @throws NoSuchMethodException See {@link Class#getConstructor}
121     */
122    public W toWay()
123      throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
124        // If the number of nodes is 2, then don't bother creating a new way
125        if (this.way.getNodes().size() == 2) {
126            return this.way;
127        }
128        // Since the way determines the generic class, this.way.getClass() is always Class<W>, assuming
129        // that way remains the defining element for the type, and remains final.
130        @SuppressWarnings("unchecked")
131        Class<W> clazz = (Class<W>) this.way.getClass();
132        Constructor<W> constructor;
133        W w;
134        try {
135            // Check for clone constructor
136            constructor = clazz.getConstructor(clazz);
137            w = constructor.newInstance(this.way);
138        } catch (NoSuchMethodException e) {
139            Logging.trace(e);
140            constructor = clazz.getConstructor();
141            w = constructor.newInstance();
142        }
143
144        w.setNodes(Arrays.asList(getFirstNode(), getSecondNode()));
145        return w;
146    }
147
148    @Override
149    public boolean equals(Object o) {
150        if (this == o) return true;
151        if (o == null || getClass() != o.getClass()) return false;
152        IWaySegment<?, ?> that = (IWaySegment<?, ?>) o;
153        return lowerIndex == that.lowerIndex &&
154          Objects.equals(way, that.way);
155    }
156
157    @Override
158    public int hashCode() {
159        return Objects.hash(way, lowerIndex);
160    }
161
162    @Override
163    public int compareTo(IWaySegment o) {
164        final W thisWay;
165        final IWay<?> otherWay;
166        try {
167            thisWay = toWay();
168            otherWay = o == null ? null : o.toWay();
169        } catch (ReflectiveOperationException e) {
170            Logging.error(e);
171            return -1;
172        }
173        return o == null ? -1 : (equals(o) ? 0 : thisWay.compareTo(otherWay));
174    }
175
176    /**
177     * Checks whether this segment crosses other segment
178     *
179     * @param s2 The other segment
180     * @return true if both segments crosses
181     */
182    public boolean intersects(IWaySegment<?, ?> s2) {
183        if (getFirstNode().equals(s2.getFirstNode()) || getSecondNode().equals(s2.getSecondNode()) ||
184          getFirstNode().equals(s2.getSecondNode()) || getSecondNode().equals(s2.getFirstNode()))
185            return false;
186
187        return Line2D.linesIntersect(
188          getFirstNode().getEastNorth().east(), getFirstNode().getEastNorth().north(),
189          getSecondNode().getEastNorth().east(), getSecondNode().getEastNorth().north(),
190          s2.getFirstNode().getEastNorth().east(), s2.getFirstNode().getEastNorth().north(),
191          s2.getSecondNode().getEastNorth().east(), s2.getSecondNode().getEastNorth().north());
192    }
193
194    /**
195     * Checks whether this segment and another way segment share the same points
196     * @param s2 The other segment
197     * @return true if other way segment is the same or reverse
198     */
199    public boolean isSimilar(IWaySegment<?, ?> s2) {
200        return (getFirstNode().equals(s2.getFirstNode()) && getSecondNode().equals(s2.getSecondNode()))
201          || (getFirstNode().equals(s2.getSecondNode()) && getSecondNode().equals(s2.getFirstNode()));
202    }
203
204    @Override
205    public String toString() {
206        return "IWaySegment [way=" + way.getUniqueId() + ", lowerIndex=" + lowerIndex + ']';
207    }
208}