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.Objects; 007import java.util.Set; 008import java.util.TreeSet; 009 010import org.openstreetmap.josm.data.osm.DataSet; 011import org.openstreetmap.josm.data.osm.Node; 012import org.openstreetmap.josm.data.osm.OsmPrimitive; 013import org.openstreetmap.josm.data.osm.Way; 014import org.openstreetmap.josm.tools.Utils; 015 016/** 017 * Auxiliary class for the {@link SelectNonBranchingWaySequencesAction}. 018 * 019 * @author Marko Mäkelä 020 */ 021public class SelectNonBranchingWaySequences { 022 /** 023 * outer endpoints of selected ways 024 */ 025 private final Set<Node> outerNodes; 026 /** 027 * endpoints of selected ways 028 */ 029 private final Set<Node> nodes; 030 031 /** 032 * Creates a way selection 033 * 034 * @param ways selection a selection of ways 035 */ 036 public SelectNonBranchingWaySequences(final Collection<Way> ways) { 037 if (ways.isEmpty()) { 038 // The selection cannot be extended. 039 outerNodes = null; 040 nodes = null; 041 } else { 042 nodes = new TreeSet<>(); 043 outerNodes = new TreeSet<>(); 044 045 for (Way way : ways) { 046 addNodes(way); 047 } 048 } 049 } 050 051 /** 052 * Add a way endpoint to nodes, outerNodes 053 * 054 * @param node a way endpoint 055 */ 056 private void addNodes(Node node) { 057 if (node == null) return; 058 else if (!nodes.add(node)) 059 outerNodes.remove(node); 060 else 061 outerNodes.add(node); 062 } 063 064 /** 065 * Add the endpoints of the way to nodes, outerNodes 066 * 067 * @param way a way whose endpoints are added 068 */ 069 private void addNodes(Way way) { 070 addNodes(way.firstNode()); 071 addNodes(way.lastNode()); 072 } 073 074 /** 075 * Find out if the selection can be extended 076 * 077 * @return true if the selection can be extended 078 */ 079 public boolean canExtend() { 080 return !Utils.isEmpty(outerNodes); 081 } 082 083 /** 084 * Finds out if the current selection can be extended. 085 * 086 * @param selection current selection (ways and others) 087 * @param node perimeter node from which to extend the selection 088 * @return a way by which to extend the selection, or null 089 */ 090 private static Way findWay(Collection<OsmPrimitive> selection, Node node) { 091 Way foundWay = null; 092 093 for (Way way : node.getParentWays()) { 094 if (way.getNodesCount() < 2 || !way.isFirstLastNode(node) 095 || !way.isSelectable() 096 || selection.contains(way)) 097 continue; 098 099 /* A previously unselected way was found that is connected 100 to the node. */ 101 if (foundWay != null) 102 /* This is not the only qualifying way. There is a 103 branch at the node, and we cannot extend the selection. */ 104 return null; 105 106 /* Remember the first found qualifying way. */ 107 foundWay = way; 108 } 109 110 /* Return the only way found, or null if none was found. */ 111 return foundWay; 112 } 113 114 /** 115 * Finds out if the current selection can be extended. 116 * <p> 117 * The members outerNodes, nodes must have been initialized. 118 * How to update these members when extending the selection, @see extend(). 119 * </p> 120 * @param selection current selection 121 * @return a way by which to extend the selection, or null 122 */ 123 private Way findWay(Collection<OsmPrimitive> selection) { 124 return outerNodes.stream() 125 .map(node -> findWay(selection, node)) 126 .filter(Objects::nonNull) 127 .findFirst().orElse(null); 128 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}