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 : OsmPrimitive.getFilteredList(node.getReferrers(),
092                Way.class)) {
093            if (way.getNodesCount() < 2 || !way.isFirstLastNode(node)
094                    || !way.isSelectable()
095                    || selection.contains(way))
096                continue;
097
098            /* A previously unselected way was found that is connected
099            to the node. */
100            if (foundWay != null)
101                /* This is not the only qualifying way. There is a
102                branch at the node, and we cannot extend the selection. */
103                return null;
104
105            /* Remember the first found qualifying way. */
106            foundWay = way;
107        }
108
109        /* Return the only way found, or null if none was found. */
110        return foundWay;
111    }
112
113    /**
114     * Finds out if the current selection can be extended.
115     * <p>
116     * The members outerNodes, nodes must have been initialized.
117     * How to update these members when extending the selection, @see extend().
118     * </p>
119     * @param selection current selection
120     * @return a way by which to extend the selection, or null
121     */
122    private Way findWay(Collection<OsmPrimitive> selection) {
123        for (Node node : outerNodes) {
124            Way way = findWay(selection, node);
125            if (way != null)
126                return way;
127        }
128
129        return null;
130    }
131
132    /**
133     * Extend the current selection
134     *
135     * @param data the data set in which to extend the selection
136     */
137    public void extend(DataSet data) {
138        if (!canExtend())
139            return;
140
141        Collection<OsmPrimitive> currentSelection = data.getSelected();
142
143        Way way = findWay(currentSelection);
144
145        if (way == null)
146            return;
147
148        boolean selectionChanged = false;
149        Collection<OsmPrimitive> selection = new LinkedList<>(currentSelection);
150
151        do {
152            if (!selection.add(way))
153                break;
154
155            selectionChanged = true;
156            addNodes(way);
157
158            way = findWay(selection);
159        } while (way != null);
160
161        if (selectionChanged)
162            data.setSelected(selection);
163    }
164}