package org.openstreetmap.josm.plugins.contourmerge; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.validation.constraints.NotNull; import org.apache.commons.lang3.Validate; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.Way; import lombok.EqualsAndHashCode; /** * <p>A <strong>WaySlice</strong> is a sub sequence of a ways sequence of * nodes.</p> * */ @EqualsAndHashCode(doNotUseGetters = false) public class WaySlice { //static private final Logger logger = Logger.getLogger(WaySlice.class.getName()); private final Way w; private final int start; private final int end; private boolean inDirection = true; /** * <p>Creates a new way slice for the way {@code w}. It consists of the * nodes at the positions <code>[start, start+1, ..., end]</code>.</p> * * @param w the way. Must not be null. * @param start the index of the start node. 0 <= start < w.getNodeCount(). * start < end * @param end the index of the end node. 0 <= end < w.getNodeCount(). * start < end * @throws IllegalArgumentException thrown if one of the arguments * isn't valid */ public WaySlice(@NotNull Way w, int start, int end){ Validate.notNull(w); Validate.isTrue(start >= 0 && start < w.getNodesCount(), "start out of range, got {0}", start); Validate.isTrue(end >= 0 && end < w.getNodesCount(), "end out of range, got {0}", start); Validate.isTrue(start < end, "expected start < end, got start={0}, end={1}", start, end); this.w = w; this.start = start; this.end = end; } /** * <p>Creates a new way slice for the way {@code w}.</p> * * <p>If {@code inDirection==true}, it consists of the nodes at the * positions <code>[start, start+1, ..., end]</code>.</p> * * <p>If {@code inDirection==false} <strong>and w is * {@link Way#isClosed() closed}</strong>, it consists of the nodes at the * positions <code>[end, end+1,...,0,1,...,start]</code>.</p> * @param w the way. Must not be null. * @param start the index of the start node. 0 <= start < w.getNodeCount(). * start < end * @param end the index of the end node. 0 <= end < w.getNodeCount(). * start < end * @param inDirection true, this way slice is given by the nodes * <code>[start, ..., end]</code>; false, if * is given by the nodes <code>[end,..,0,..,start]</code> * (provided the way is closed) * @throws IllegalArgumentException thrown if a precondition is violated */ public WaySlice(@NotNull Way w, int start, int end, boolean inDirection){ this(w,start,end); if (!inDirection){ Validate.isTrue(w.isClosed(), "inDirection=false only supported provided w is closed"); } Validate.isTrue( ! (w.isClosed() && start == 0 && end == w.getNodesCount() -1), "for a closed way, start and end must not both refer to the " + "shared 'join'-node" ); this.inDirection = inDirection; } /** * Replies the way this is a slice of. * * @return the way this is a slice of. */ public Way getWay() { return w; } /** * Replies the index of the first node of this way slice. * * @return the index of the first node of this way slice */ public int getStart() { return start; } /** * Replies the index of the last node of this way slice. * * @return the index of the first node of this way slice */ public int getEnd() { return end; } /** * Replies true, if this way slice has the same direction and the * parent way. Replies false, if it has the opposite direction. * * @return */ public boolean isInDirection() { return inDirection; } public Node getStartNode(){ return w.getNode(start); } public Node getEndNode() { return w.getNode(end); } /** * <p>Replies the lower node idx of the node from which this way slice is * torn off.</p> * * * <strong>Example</strong> * <pre> * n0 ------------- n1 ---------- n2 --------- n3 -------------- n4 * start end * ^-- this is the lower position where the way slice [n1,n2,n3] is * torn off * ==> the method replies 0 * </pre> * * <p>Replies -1, if there is no such index.</p> * * <strong>Example</strong> * <pre> * n0 ------------- n1 ---------- n2 --------- n3 -------------- n4 * start end * The way slice starts at the first node of an open way => there is * no node where the way slice is torn off * ==> the method replies -1 * </pre> * * @return */ public int getStartTearOffIdx() { if (! w.isClosed()) { // an open way return start > 0 ? start - 1 : -1; } else { // a closed way if (isInDirection()){ int lower = start - 1; if (lower < 0) lower = w.getNodesCount() - 2; return lower == end ? -1 : lower; } else { int lower = end - 1; if (lower < 0) lower = w.getNodesCount() - 2; return lower == start ? -1 : lower; } } } /** * <p>Replies the lower node from which this way slice is torn off, see * {@link #getStartTearOffIdx()} for more details.</p> * @return */ public Node getStartTearOffNode() { int i = getStartTearOffIdx(); return i==-1 ? null : w.getNode(i); } /** * <p>Replies the upper node idx of the node from which this way slice is * torn off.</p> * * <strong>Example</strong> * <pre> * n0 ------------- n1 ---------- n2 --------- n3 -------------- n4 * start===================== end * ^ * | * this is the upper position where the way slice [n1,n2,n3] is torn off * ==> the method replies 4 * </pre> * * <p>Replies -1, if there is no such index.</p> * <strong>Example</strong> * <pre> * n0 ------------- n1 ---------- n2 --------- n3 -------------- n4 * start =================== end * The way slice ends at the last node of an open way => there is * no node where the way slice is torn off * ==> the method replies -1 * </pre> * * @return */ public int getEndTearOffIdx() { if (! w.isClosed()) { // an open way return end < w.getNodesCount()-1 ? end + 1 : -1; } else { // a closed way if (inDirection) { int upper = end + 1; if (upper >= w.getNodesCount()-1) upper = 0; return upper == start ? -1 : upper; } else { int upper = start + 1; if (upper >= w.getNodesCount()-1) upper = 0; return upper == end ? -1 : upper; } } } /** * <p>Replies the upper node from which this way slice is torn off, see * {@link #getEndTearOffIdx()} for more details.</p> * @return */ public Node getEndTearOffNode() { int i = getEndTearOffIdx(); return i == -1 ? null : w.getNode(i); } /** * <p>Replies the number of way segments in this way slice.</p> * * @return the number of way segments in this way slice */ public int getNumSegments() { if (inDirection) return end - start; return start + (w.getNodesCount() - 1 - end); // for closed ways } /** * <p>Replies the opposite way slice, or null, if this way slice doesn't have * an opposite way slice, because it is a way slice in an open way.</p> * * @return the oposite way slice */ public WaySlice getOpositeSlice(){ if (!w.isClosed()) return null; return new WaySlice(w, start, end, !inDirection); } /** * <p>Replies a clone of the underlying way, where the nodes given by * this way slice are replaced with the nodes in {@code newNodes}.</code> * * @param newNodes the new nodes. Ignored if null. * @return the cloned way with the new nodes */ public Way replaceNodes(List<Node> newNodes) { Way nw = new Way(w); if (newNodes == null || newNodes.isEmpty()) return nw; if (!w.isClosed()) { List<Node> oldNodes = new ArrayList<>(w.getNodes()); for (int i=start; i<= end;i++) oldNodes.remove(start); oldNodes.addAll(start, newNodes); nw.setNodes(oldNodes); } else { List<Node> oldNodes = new ArrayList<>(w.getNodes()); if (inDirection) { if (start == 0)oldNodes.remove(oldNodes.size()-1); for (int i=start; i<= end;i++)oldNodes.remove(start); oldNodes.addAll(start, newNodes); if (start == 0) oldNodes.add(newNodes.get(0)); nw.setNodes(oldNodes); } else { int upper = oldNodes.size()-1; for (int i=end; i<=upper; i++) oldNodes.remove(end); for (int i=0; i<=start;i++) oldNodes.remove(0); oldNodes.addAll(0, newNodes); // make sure the new way is closed again oldNodes.add(newNodes.get(0)); nw.setNodes(oldNodes); } } return nw; } /** * <p>Replies the list of nodes, always starting at the start index, * following the nodes in the appropriate direction to the end index.</p> * * @return the list of nodes */ public List<Node> getNodes(){ List<Node> nodes = new ArrayList<>(); if (!w.isClosed()) { nodes.addAll(w.getNodes().subList(start, end+1)); } else { if (inDirection) { nodes.addAll(w.getNodes().subList(start, end+1)); } else { // do not add the last node which is the join node common // to the node at index 0 for (int i=end; i<=w.getNodesCount()-2;i++) { nodes.add(w.getNode(i)); } for (int i=0; i <= start; i++) nodes.add(w.getNode(i)); } } return nodes; } /** * Replies true if this way slice participates in at least one sling. * Here's an example of such a sling. * <pre> * 5 * | * | * 1======2======3=======4 * | | * | | * 7-------6 * </pre> * <ul> * <li>the nodes [3,4,6,7] form a sling in the way. Note that the way * itself is not <em>closed</em>, * {@link Way#isClosed() isClosed()} will return false.</li> * <li>the way slice [1,2,3,4] <strong>does</strong> participate in a * sling</li> * <li>the way slice [1,2] <strong>doesn't</strong> participate in * a sling</li> * </ul> * * @return true if this way slice participates in at least one sling. */ protected boolean hasSlings() { Set<Node> nodeSet = new HashSet<>(); if (w.isClosed()){ if (isInDirection()) { for (int i=start; i<=end; i++){ nodeSet.add(w.getNode(i)); } } else { /* A way slice including the common start/end node of a closed * way. * Make sure we look only once at the common start/end node. */ for (int i=start; i > 0 /* don't add the the start node */; i--){ nodeSet.add(w.getNode(i)); } for (int i=w.getNodesCount()-1 /* add the end node */; i >= end; i--){ nodeSet.add(w.getNode(i)); } } } else { for (int i=start; i<=end; i++){ nodeSet.add(w.getNode(i)); } } /* * make sure each node in way slice occurs exactly once in the way. * This ensures that the way slice is not participating in any slings. */ Set<Node> seen = new HashSet<>(); for (int i=0; i< (w.isClosed() ? w.getNodesCount()-1 : w.getNodesCount()); i++) { Node n = w.getNode(i); if (seen.contains(n)) return true; if (nodeSet.contains(n)) seen.add(n); } return false; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("<way-slice ").append("way=").append(w.getPrimitiveId()) .append(", start=").append(start) .append(", end=").append(end) .append(", isInDirection=").append(isInDirection()) .append(">"); return sb.toString(); } }