001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import java.util.Collection; 005import java.util.LinkedList; 006import java.util.Set; 007import java.util.TreeSet; 008 009import org.openstreetmap.josm.data.osm.DataSet; 010import org.openstreetmap.josm.data.osm.Node; 011import org.openstreetmap.josm.data.osm.OsmPrimitive; 012import org.openstreetmap.josm.data.osm.Way; 013 014/** 015 * Auxiliary class for the {@link SelectNonBranchingWaySequencesAction}. 016 * 017 * @author Marko Mäkelä 018 */ 019public class SelectNonBranchingWaySequences { 020 /** 021 * outer endpoints of selected ways 022 */ 023 private Set<Node> outerNodes; 024 /** 025 * endpoints of selected ways 026 */ 027 private Set<Node> nodes; 028 029 /** 030 * Creates a way selection 031 * 032 * @param ways selection a selection of ways 033 */ 034 public SelectNonBranchingWaySequences(final Collection<Way> ways) { 035 if (ways.isEmpty()) { 036 // The selection cannot be extended. 037 outerNodes = null; 038 nodes = null; 039 } else { 040 nodes = new TreeSet<>(); 041 outerNodes = new TreeSet<>(); 042 043 for (Way way : ways) { 044 addNodes(way); 045 } 046 } 047 } 048 049 /** 050 * Add a way endpoint to nodes, outerNodes 051 * 052 * @param node a way endpoint 053 */ 054 private void addNodes(Node node) { 055 if (node == null) return; 056 else if (!nodes.add(node)) 057 outerNodes.remove(node); 058 else 059 outerNodes.add(node); 060 } 061 062 /** 063 * Add the endpoints of the way to nodes, outerNodes 064 * 065 * @param way a way whose endpoints are added 066 */ 067 private void addNodes(Way way) { 068 addNodes(way.firstNode()); 069 addNodes(way.lastNode()); 070 } 071 072 /** 073 * Find out if the selection can be extended 074 * 075 * @return true if the selection can be extended 076 */ 077 public boolean canExtend() { 078 return outerNodes != null && !outerNodes.isEmpty(); 079 } 080 081 /** 082 * Finds out if the current selection can be extended. 083 * 084 * @param selection current selection (ways and others) 085 * @param node perimeter node from which to extend the selection 086 * @return a way by which to extend the selection, or null 087 */ 088 private static Way findWay(Collection<OsmPrimitive> selection, Node node) { 089 Way foundWay = null; 090 091 for (Way way : node.getParentWays()) { 092 if (way.getNodesCount() < 2 || !way.isFirstLastNode(node) 093 || !way.isSelectable() 094 || selection.contains(way)) 095 continue; 096 097 /* A previously unselected way was found that is connected 098 to the node. */ 099 if (foundWay != null) 100 /* This is not the only qualifying way. There is a 101 branch at the node, and we cannot extend the selection. */ 102 return null; 103 104 /* Remember the first found qualifying way. */ 105 foundWay = way; 106 } 107 108 /* Return the only way found, or null if none was found. */ 109 return foundWay; 110 } 111 112 /** 113 * Finds out if the current selection can be extended. 114 * <p> 115 * The members outerNodes, nodes must have been initialized. 116 * How to update these members when extending the selection, @see extend(). 117 * </p> 118 * @param selection current selection 119 * @return a way by which to extend the selection, or null 120 */ 121 private Way findWay(Collection<OsmPrimitive> selection) { 122 for (Node node : outerNodes) { 123 Way way = findWay(selection, node); 124 if (way != null) 125 return way; 126 } 127 128 return null; 129 } 130 131 /** 132 * Extend the current selection 133 * 134 * @param data the data set in which to extend the selection 135 */ 136 public void extend(DataSet data) { 137 if (!canExtend()) 138 return; 139 140 Collection<OsmPrimitive> currentSelection = data.getSelected(); 141 142 Way way = findWay(currentSelection); 143 144 if (way == null) 145 return; 146 147 boolean selectionChanged = false; 148 Collection<OsmPrimitive> selection = new LinkedList<>(currentSelection); 149 150 do { 151 if (!selection.add(way)) 152 break; 153 154 selectionChanged = true; 155 addNodes(way); 156 157 way = findWay(selection); 158 } while (way != null); 159 160 if (selectionChanged) 161 data.setSelected(selection); 162 } 163}