// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.fixAddresses; import static org.openstreetmap.josm.tools.I18n.tr; import java.io.IOException; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.List; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.PleaseWaitRunnable; import org.openstreetmap.josm.io.OsmTransferException; import org.xml.sax.SAXException; /** * The class GuessAddressRunnable scans the data set to find the best guess for a missing address field. * * The guessing procedure itself is implemented by defining "guessers" using the {@link GuessedValueHandler} * class. A guessed field does not modify the corresponding property of {@link OSMAddress} itself, but * adds the guessed value to a shadowed field by calling {@link OSMAddress#setGuessedValue(String, String)}. */ public class GuessAddressRunnable extends PleaseWaitRunnable { private List<OSMAddress> addressesToGuess; private List<IProgressMonitorFinishedListener> finishListeners = new ArrayList<>(); private boolean isRunning = false; private boolean canceled; private GuessedValueHandler[] wayGuessers = new GuessedValueHandler[]{new GuessStreetValueHandler(TagConstants.ADDR_STREET_TAG)}; private GuessedValueHandler[] nodeGuessers = new GuessedValueHandler[]{ new GuessedValueHandler(TagConstants.ADDR_POSTCODE_TAG, 500.0), new GuessedValueHandler(TagConstants.ADDR_CITY_TAG, 5000.0), new GuessedValueHandler(TagConstants.ADDR_STATE_TAG, 5000.0), new GuessedValueHandler(TagConstants.ADDR_COUNTRY_TAG, 5000.0), new GuessedValueHandler(TagConstants.ADDR_CITY_TAG, 2000.0) }; /** * Instantiates a new guess address runnable. * * @param addresses the addresses to guess the values for * @param title the title of progress monitor */ public GuessAddressRunnable(List<OSMAddress> addresses, String title) { super(title != null ? title : tr("Searching")); setAddressEditContainer(addresses); } /** * Sets the address edit container. * * @param nodes the new address edit container */ public void setAddressEditContainer(List<OSMAddress> nodes) { if (isRunning) { throw new ConcurrentModificationException(); } this.addressesToGuess = nodes; } /** * Gets the addresses to guess. * * @return the addresses to guess */ public List<OSMAddress> getAddressesToGuess() { return addressesToGuess; } /** * @return the isRunning */ public boolean isRunning() { return isRunning; } /** * Adds a finish listener. * * @param l the listener to add */ public void addFinishListener(IProgressMonitorFinishedListener l) { finishListeners.add(l); } /** * Removes a finish listener. * * @param l the listener to remove */ public void removeFinishListener(IProgressMonitorFinishedListener l) { finishListeners.remove(l); } /** * Fires the 'finished' event after the thread has done his work. */ protected void fireFinished() { for (IProgressMonitorFinishedListener l : finishListeners) { l.finished(); } // this event is fired only once, then we disconnect all listeners finishListeners.clear(); } @Override protected void cancel() { canceled = true; } @Override protected void finish() { // nothing to do yet } @Override protected void realRun() throws SAXException, IOException, OsmTransferException { if (Main.getLayerManager().getEditDataSet() == null || addressesToGuess == null) return; isRunning = true; canceled = false; // Start progress monitor to guess address values progressMonitor.subTask(tr("Searching") + "..."); try { progressMonitor.setTicksCount(addressesToGuess.size()); List<OSMAddress> shadowCopy = new ArrayList<>(addressesToGuess); for (OSMAddress aNode : shadowCopy) { if (!aNode.needsGuess()) { // nothing to do progressMonitor.worked(1); continue; } // check for cancel if (canceled) { break; } // Update progress monitor progressMonitor.subTask(tr("Guess values for ") + aNode); // Run way-related guessers for (int i = 0; i < wayGuessers.length; i++) { GuessedValueHandler guesser = wayGuessers[i]; guesser.setAddressNode(aNode); // visit osm data for (Way way : Main.getLayerManager().getEditDataSet().getWays()) { if (canceled) { break; } way.accept(guesser); } String guessedVal = guesser.getCurrentValue(); if (guessedVal != null) { aNode.setGuessedValue(guesser.getTag(), guessedVal, guesser.getSourceNode()); } } // Run node-related guessers for (int i = 0; i < nodeGuessers.length; i++) { GuessedValueHandler guesser = nodeGuessers[i]; guesser.setAddressNode(aNode); // visit osm data for (Node node : Main.getLayerManager().getEditDataSet().getNodes()) { if (canceled) { break; } node.accept(guesser); } String guessedVal = guesser.getCurrentValue(); if (guessedVal != null) { aNode.setGuessedValue(guesser.getTag(), guessedVal, guesser.getSourceNode()); } } // report progress progressMonitor.worked(1); } } finally { isRunning = false; fireFinished(); } } private static class GuessStreetValueHandler extends GuessedValueHandler { GuessStreetValueHandler(String tag) { this(tag, null); } GuessStreetValueHandler(String tag, OSMAddress aNode) { super(tag, aNode, 200.0); } @Override public void visit(Node n) { // do nothing } @Override public void visit(Way w) { if (TagUtils.isStreetSupportingHousenumbers(w)) { OSMAddress aNode = getAddressNode(); String newVal = TagUtils.getNameValue(w); if (newVal != null) { double dist = OsmUtils.getMinimumDistanceToWay(aNode.getCoor(), w); if (dist < minDist && dist < getMaxDistance()) { minDist = dist; currentValue = newVal; srcNode = w; } } } } } }