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    Set<Node> outerNodes;
024    /**
025     * endpoints of selected ways
026     */
027    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     * Add a way endpoint to nodes, outerNodes
050     *
051     * @param node a way endpoint
052     */
053    private void addNodes(Node node) {
054        if (node == null) return;
055        else if (!nodes.add(node))
056            outerNodes.remove(node);
057        else
058            outerNodes.add(node);
059    }
060
061    /**
062     * Add the endpoints of the way to nodes, outerNodes
063     *
064     * @param way a way whose endpoints are added
065     */
066    private void addNodes(Way way) {
067        addNodes(way.firstNode());
068        addNodes(way.lastNode());
069    }
070
071    /**
072     * Find out if the selection can be extended
073     *
074     * @return true if the selection can be extended
075     */
076    public boolean canExtend() {
077        return outerNodes != null && !outerNodes.isEmpty();
078    }
079
080    /**
081     * Finds out if the current selection can be extended.
082     *
083     * @param selection current selection (ways and others)
084     * @param node      perimeter node from which to extend the selection
085     * @return a way by which to extend the selection, or null
086     */
087    private static Way findWay(Collection<OsmPrimitive> selection, Node node) {
088        Way foundWay = null;
089
090        for (Way way : OsmPrimitive.getFilteredList(node.getReferrers(),
091                Way.class)) {
092            if (way.getNodesCount() < 2 || !way.isFirstLastNode(node)
093                    || selection.contains(way))
094                continue;
095
096            /* A previously unselected way was found that is connected
097            to the node. */
098            if (foundWay != null)
099                /* This is not the only qualifying way. There is a
100                branch at the node, and we cannot extend the selection. */
101                return null;
102
103            /* Remember the first found qualifying way. */
104            foundWay = way;
105        }
106
107        /* Return the only way found, or null if none was found. */
108        return foundWay;
109    }
110
111    /**
112     * Finds out if the current selection can be extended.
113     * <p>
114     * The members outerNodes, nodes must have been initialized.
115     * How to update these members when extending the selection, @see extend().
116     * </p>
117     * @param selection current selection
118     * @return a way by which to extend the selection, or null
119     */
120    private Way findWay(Collection<OsmPrimitive> selection) {
121        for (Node node : outerNodes) {
122            Way way = findWay(selection, node);
123            if (way != null)
124                return way;
125        }
126
127        return null;
128    }
129
130    /**
131     * Extend the current selection
132     *
133     * @param data the data set in which to extend the selection
134     */
135    public void extend(DataSet data) {
136        Collection<OsmPrimitive> currentSelection;
137        LinkedList<OsmPrimitive> selection;
138        boolean selectionChanged = false;
139        Way way;
140
141        if (!canExtend())
142            return;
143
144        currentSelection = data.getSelected();
145
146        way = findWay(currentSelection);
147
148        if (way == null)
149            return;
150
151        selection = new LinkedList<>();
152        for (OsmPrimitive primitive : currentSelection)
153            selection.add(primitive);
154
155        do {
156            if (!selection.add(way))
157                break;
158
159            selectionChanged = true;
160            addNodes(way);
161
162            way = findWay(selection);
163        } while (way != null);
164
165        if (selectionChanged)
166            data.setSelected(selection, true);
167    }
168}