// License: GPL. For details, see LICENSE file.
package terracer;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import javax.swing.JOptionPane;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.command.ChangePropertyCommand;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.command.SequenceCommand;
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.tools.Shortcut;
/**
* Tool to reverse the house numbers in a terrace.
*
* Useful for when you're using the Terracer tool and the house numbers come out
* in the wrong direction, or when someone has added house numbers in the wrong
* direction anyway.
*
* Finds all connected ways which have a building=* tag on them in order (breadth
* first search) and then changes the tags to be the reverse of the order in which
* they were found.
*/
public class ReverseTerraceAction extends JosmAction {
public ReverseTerraceAction() {
super(tr("Reverse a terrace"),
"reverse_terrace",
tr("Reverses house numbers on a terrace."),
Shortcut.registerShortcut("tools:ReverseTerrace",
tr("Tool: {0}", tr("Reverse a Terrace")),
KeyEvent.VK_V, Shortcut.ALT_CTRL_SHIFT),
true);
}
/**
* Breadth-first searches based on the selection while the selection is a way
* with a building=* tag and then applies the addr:housenumber tag in reverse
* order.
*/
public void actionPerformed(ActionEvent e) {
Collection<Way> selectedWays = Main.getLayerManager().getEditDataSet().getSelectedWays();
// Set to keep track of all the nodes that have been visited - that is: if
// we encounter them again we will not follow onto the connected ways.
HashSet<Node> visitedNodes = new HashSet<>();
// Set to keep track of the ways the algorithm has seen, but not yet visited.
// Since when a way is visited all of its nodes are marked as visited, there
// is no need to keep a visitedWays set.
HashSet<Way> front = new HashSet<>();
// Find the first or last way from the teracced houses.
// It should be connected to exactly one other way.
for (Way w : selectedWays) {
int conn = 0;
for (Way v : selectedWays) {
if (w.equals(v)) continue;
if (!Collections.disjoint(w.getNodes(), v.getNodes())) {
++conn;
}
}
if (conn == 1) {
front.add(w);
break;
}
}
if (front.isEmpty()) {
JOptionPane.showMessageDialog(Main.parent,
tr("Cannot reverse!"));
return;
}
// This is like a visitedWays set, but in a linear order.
LinkedList<Way> orderedWays = new LinkedList<>();
// And the tags to reverse on the orderedWays.
LinkedList<String> houseNumbers = new LinkedList<>();
while (front.size() > 0) {
// Java apparently doesn't have useful methods to get single items from sets...
Way w = front.iterator().next();
// Visit all the nodes in the way, adding the building's they're members of
// to the front.
for (Node n : w.getNodes()) {
if (!visitedNodes.contains(n)) {
for (OsmPrimitive prim : n.getReferrers()) {
if (prim.keySet().contains("building") && prim instanceof Way) {
front.add((Way) prim);
}
}
visitedNodes.add(n);
}
}
// We've finished visiting this way, so record the attributes we're interested
// in for re-writing.
front.remove(w);
orderedWays.addLast(w);
houseNumbers.addFirst(w.get("addr:housenumber"));
}
Collection<Command> commands = new LinkedList<>();
for (int i = 0; i < orderedWays.size(); ++i) {
commands.add(new ChangePropertyCommand(
orderedWays.get(i),
"addr:housenumber",
houseNumbers.get(i)));
}
Main.main.undoRedo.add(new SequenceCommand(tr("Reverse Terrace"), commands));
Main.getLayerManager().getEditDataSet().setSelected(orderedWays);
}
@Override
protected void updateEnabledState() {
setEnabled(getLayerManager().getEditDataSet() != null);
}
}