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}