// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.utilsplugin2.selection; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Set; import javax.swing.JOptionPane; import org.openstreetmap.josm.actions.JosmAction; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.Notification; import org.openstreetmap.josm.tools.Shortcut; /** * Select all connected ways for a street if one way is selected (determine by name/ref), * select highway ways between two selected ways. * * @author zverik */ public class SelectHighwayAction extends JosmAction { public SelectHighwayAction() { super(tr("Select Highway"), "selecthighway", tr("Select highway for the name/ref given"), Shortcut.registerShortcut("tools:selecthighway", tr("Tool: {0}", "Select Highway"), KeyEvent.VK_W, Shortcut.ALT_CTRL), true); } @Override public void actionPerformed(ActionEvent e) { DataSet ds = getLayerManager().getEditDataSet(); List<Way> selectedWays = OsmPrimitive.getFilteredList(ds.getSelected(), Way.class); if (selectedWays.size() == 1) { ds.setSelected(selectNamedRoad(selectedWays.get(0))); } else if (selectedWays.size() == 2) { ds.setSelected(selectHighwayBetween(selectedWays.get(0), selectedWays.get(1))); } else { new Notification( tr("Please select one or two ways for this action") ).setIcon(JOptionPane.WARNING_MESSAGE).show(); } } private Set<Way> selectNamedRoad(Way firstWay) { Set<Way> newWays = new HashSet<>(); String key = firstWay.hasKey("name") ? "name" : "ref"; if (firstWay.hasKey(key)) { String value = firstWay.get(key); Queue<Node> nodeQueue = new LinkedList<>(); nodeQueue.add(firstWay.firstNode()); while (!nodeQueue.isEmpty()) { Node node = nodeQueue.remove(); for (Way p : OsmPrimitive.getFilteredList(node.getReferrers(), Way.class)) { if (!newWays.contains(p) && p.hasKey(key) && p.get(key).equals(value)) { newWays.add(p); nodeQueue.add(p.firstNode().equals(node) ? p.lastNode() : p.firstNode()); } } } } return newWays; } private Set<Way> selectHighwayBetween(Way firstWay, Way lastWay) { int minRank = Math.min(getHighwayRank(firstWay), getHighwayRank(lastWay)); HighwayTree firstTree = new HighwayTree(firstWay, minRank); HighwayTree secondTree = new HighwayTree(lastWay, minRank); Way intersection = firstTree.getIntersection(secondTree); while (intersection == null && (firstTree.canMoveOn() || secondTree.canMoveOn())) { firstTree.processNextLevel(); secondTree.processNextLevel(); intersection = firstTree.getIntersection(secondTree); } Set<Way> newWays = new HashSet<>(); newWays.addAll(firstTree.getPath(intersection)); newWays.addAll(secondTree.getPath(intersection)); return newWays; } private static int getHighwayRank(OsmPrimitive way) { if (!way.hasKey("highway")) return 0; String highway = way.get("highway"); if (highway.equals("path") || highway.equals("footway") || highway.equals("cycleway")) return 1; else if (highway.equals("track") || highway.equals("service")) return 2; else if (highway.equals("unclassified") || highway.equals("residential")) return 3; else if (highway.equals("tertiary") || highway.equals("tertiary_link")) return 4; else if (highway.equals("secondary") || highway.equals("secondary_link")) return 5; else if (highway.equals("primary") || highway.equals("primary_link")) return 6; else if (highway.equals("trunk") || highway.equals("trunk_link") || highway.equals("motorway") || highway.equals("motorway_link")) return 7; return 0; } @Override protected void updateEnabledState() { updateEnabledStateOnCurrentSelection(); } @Override protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { if (selection == null) { setEnabled(false); return; } int count = 0, rank = 100; for (OsmPrimitive p : selection) { if (p instanceof Way) { count++; rank = Math.min(rank, getHighwayRank(p)); } } setEnabled(count == 1 || (count == 2 && rank > 0)); } private static class HighwayTree { private List<Way> tree; private List<Integer> refs; private List<Node> nodesToCheck; private List<Integer> nodeRefs; private int minHighwayRank; HighwayTree(Way from, int minHighwayRank) { tree = new ArrayList<>(1); refs = new ArrayList<>(1); tree.add(from); refs.add(Integer.valueOf(-1)); this.minHighwayRank = minHighwayRank; nodesToCheck = new ArrayList<>(2); nodeRefs = new ArrayList<>(2); nodesToCheck.add(from.firstNode()); nodesToCheck.add(from.lastNode()); nodeRefs.add(Integer.valueOf(0)); nodeRefs.add(Integer.valueOf(0)); } public void processNextLevel() { List<Node> newNodes = new ArrayList<>(); List<Integer> newIdx = new ArrayList<>(); for (int i = 0; i < nodesToCheck.size(); i++) { Node node = nodesToCheck.get(i); Integer nodeRef = nodeRefs.get(i); for (Way way : OsmPrimitive.getFilteredList(node.getReferrers(), Way.class)) { if ((way.firstNode().equals(node) || way.lastNode().equals(node)) && !tree.contains(way) && suits(way)) { tree.add(way); refs.add(nodeRef); Node newNode = way.firstNode().equals(node) ? way.lastNode() : way.firstNode(); newNodes.add(newNode); newIdx.add(Integer.valueOf(tree.size() - 1)); } } } nodesToCheck = newNodes; nodeRefs = newIdx; } private boolean suits(Way w) { return getHighwayRank(w) >= minHighwayRank; } public boolean canMoveOn() { return !nodesToCheck.isEmpty() && tree.size() < 10000; } public Way getIntersection(HighwayTree other) { for (Way w : other.tree) { if (tree.contains(w)) return w; } return null; } public List<Way> getPath(Way to) { if (to == null) return Collections.singletonList(tree.get(0)); int pos = tree.indexOf(to); if (pos < 0) throw new ArrayIndexOutOfBoundsException("Way " + to + " is not in the tree."); List<Way> result = new ArrayList<>(1); while (pos >= 0) { result.add(tree.get(pos)); pos = refs.get(pos); } return result; } } }