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}