Ticket #7037: 7037v0.1.diff

File 7037v0.1.diff, 8.4 KB (added by Bjoeni, 6 years ago)
  • src/org/openstreetmap/josm/actions/AverageWaysAction.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.actions;
     3
     4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
     5import static org.openstreetmap.josm.tools.I18n.tr;
     6
     7import java.awt.event.ActionEvent;
     8import java.awt.event.KeyEvent;
     9import java.util.ArrayList;
     10import java.util.Collection;
     11import java.util.Comparator;
     12import java.util.List;
     13import java.util.stream.Collectors;
     14
     15import javax.swing.JOptionPane;
     16import javax.swing.SwingUtilities;
     17
     18import org.openstreetmap.josm.command.Command;
     19import org.openstreetmap.josm.command.DeleteCommand;
     20import org.openstreetmap.josm.command.MoveCommand;
     21import org.openstreetmap.josm.command.SequenceCommand;
     22import org.openstreetmap.josm.data.UndoRedoHandler;
     23import org.openstreetmap.josm.data.coor.LatLon;
     24import org.openstreetmap.josm.data.osm.DataSet;
     25import org.openstreetmap.josm.data.osm.Node;
     26import org.openstreetmap.josm.data.osm.OsmPrimitive;
     27import org.openstreetmap.josm.data.osm.Way;
     28import org.openstreetmap.josm.gui.Notification;
     29import org.openstreetmap.josm.tools.Logging;
     30import org.openstreetmap.josm.tools.Shortcut;
     31
     32/**
     33 * Finds the median between two or more ways
     34 * @since xxx
     35 */
     36public class AverageWaysAction extends JosmAction {
     37
     38    /**
     39     * Constructs a new {@link AverageWaysAction}
     40     */
     41    public AverageWaysAction() {
     42        super(tr("Average Ways"), "average", tr("Find the median between two ways"), Shortcut.registerShortcut(
     43                "tools:average", tr("Tool: {0}", tr("Average Ways")), KeyEvent.VK_C, Shortcut.SHIFT), true);
     44        setHelpId(ht("/Action/AverageWays")); //TODO
     45    }
     46
     47    @Override
     48    public void actionPerformed(ActionEvent arg0) {
     49        DataSet ds = getLayerManager().getEditDataSet();
     50        ds.update(() -> {
     51            List<Way> ways = ds.getSelectedWays().stream().filter(p -> !p.isIncomplete()).collect(Collectors.toList());
     52            if (ways.size() < 2) {
     53                alertSelectAtLeastTwoWays();
     54                return;
     55            } else if (!confirmWayWithNodesOutsideBoundingBox(ways)) {
     56                return;
     57            }
     58
     59            averageWays(ways);
     60
     61        });
     62    }
     63
     64    private void averageWays(List<Way> ways) {
     65        Way refWay = ways.stream().max(Comparator.comparing(Way::getNodesCount)).get();
     66        AverageWay avgWay = new AverageWay(refWay);
     67        ways.remove(refWay);
     68        double tolerance = 2e-4; //?
     69        int checkBefore = 10;
     70        int checkAfter = 20;
     71        int total = 0;
     72        boolean checkAll = false;
     73        for (Way way : ways) {
     74            int offset = 0;
     75            for (int r = 0; r < avgWay.size(); r++) {
     76                AverageNode avgWayNode = avgWay.get(r);
     77                double minDist = tolerance;
     78                double lastDist = 0;
     79                double distanceCounter = 0;
     80                Node closestNode = null;
     81                List<Node> wayNodes = way.getNodes();
     82                for (int n = offset; n != offset - 1; n++) {
     83                    total++;
     84                    if (n > wayNodes.size() - 1) {
     85                        if (offset <= 1) {
     86                            break;
     87                        } else {
     88                            n = 0;
     89                        }
     90                    }
     91                    Node thisWayNode = wayNodes.get(n);
     92
     93                    double dist = thisWayNode.getCoor().distance(avgWayNode.getRefNode().getCoor());
     94                    if (dist < minDist) {
     95                        minDist = dist;
     96                        closestNode = thisWayNode;
     97                        offset = Math.max(0, n - checkBefore);
     98                    } else if (!checkAll //flag to force checking all points
     99                            && r != 0 //make sure the whole way is checked at least once
     100                            && closestNode != null //we found a node closer than tolerance
     101                            && lastDist < dist) {
     102                        //we had a close match earlier, but now we're moving away
     103                        distanceCounter++;
     104                        if (distanceCounter > checkAfter) {
     105                            break;
     106                        }
     107                    }
     108                    lastDist = dist;
     109                }
     110                if (closestNode != null)
     111                    avgWayNode.addNode(closestNode);
     112            }
     113        }
     114        List<Command> commands = new ArrayList<>(avgWay.getMoveCommands());
     115        commands.add(new DeleteCommand(ways));
     116        commands.addAll(ways.stream().map(Way::getNodes).map(DeleteCommand::new).collect(Collectors.toList()));
     117        UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Average Ways"), commands));
     118        Logging.debug(total + " iterations");
     119    }
     120
     121    class AverageWay extends ArrayList<AverageNode> {
     122
     123        public AverageWay(Way refWay) {
     124            addAll(refWay.getNodes().stream().map(AverageNode::new).collect(Collectors.toList()));
     125        }
     126
     127        public List<MoveCommand> getMoveCommands() {
     128            return stream().map(n -> new MoveCommand(n.getRefNode(), n.getCoor())).collect(Collectors.toList());
     129        }
     130
     131    }
     132
     133    class AverageNode {
     134        private final Node refNode;
     135        private List<Node> nodes = new ArrayList<>();
     136
     137        public AverageNode(Node refNode) {
     138            this.refNode = refNode;
     139        }
     140
     141        public void addNode(Node node) {
     142            nodes.add(node);
     143        }
     144
     145        public Node getRefNode() {
     146            return refNode;
     147        }
     148
     149        public LatLon getCoor() {
     150            double lat = refNode.getCoor().lat();
     151            double lon = refNode.getCoor().lon();
     152            for (Node node : nodes) {
     153                lat += node.getCoor().lat();
     154                lon += node.getCoor().lon();
     155            }
     156            lat /= (nodes.size() + 1);
     157            lon /= (nodes.size() + 1);
     158            return new LatLon(lat, lon);
     159        }
     160
     161    }
     162
     163    protected boolean confirmWayWithNodesOutsideBoundingBox(List<? extends OsmPrimitive> primitives) {
     164        return DeleteAction.checkAndConfirmOutlyingDelete(primitives, null);
     165    }
     166
     167    protected void alertSelectAtLeastTwoWays() {
     168        SwingUtilities.invokeLater(() -> new Notification(tr("Please select at least two ways to average."))
     169                .setIcon(JOptionPane.WARNING_MESSAGE).setDuration(Notification.TIME_SHORT)
     170                .setHelpTopic(ht("/Action/AverageWays")).show());
     171    }
     172
     173    @Override
     174    protected void updateEnabledState() {
     175        updateEnabledStateOnCurrentSelection();
     176    }
     177
     178    @Override
     179    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
     180        updateEnabledStateOnModifiableSelection(selection);
     181    }
     182
     183}
  • src/org/openstreetmap/josm/gui/MainMenu.java

     
    3434import org.openstreetmap.josm.actions.AlignInLineAction;
    3535import org.openstreetmap.josm.actions.AutoScaleAction;
    3636import org.openstreetmap.josm.actions.AutoScaleAction.AutoScaleMode;
     37import org.openstreetmap.josm.actions.AverageWaysAction;
    3738import org.openstreetmap.josm.actions.ChangesetManagerToggleAction;
    3839import org.openstreetmap.josm.actions.CloseChangesetAction;
    3940import org.openstreetmap.josm.actions.CombineWayAction;
     
    263264    public final ReverseWayAction reverseWay = new ReverseWayAction();
    264265    /** Tools / Simplify Way */
    265266    public final SimplifyWayAction simplifyWay = new SimplifyWayAction();
     267    /** Tools / Average Way */
     268    public final AverageWaysAction averageWays = new AverageWaysAction();
    266269    /** Tools / Align Nodes in Circle */
    267270    public final AlignInCircleAction alignInCircle = new AlignInCircleAction();
    268271    /** Tools / Align Nodes in Line */
     
    857860        toolsMenu.addSeparator();
    858861        add(toolsMenu, reverseWay);
    859862        add(toolsMenu, simplifyWay);
     863        add(toolsMenu, averageWays);
    860864        toolsMenu.addSeparator();
    861865        add(toolsMenu, alignInCircle);
    862866        add(toolsMenu, alignInLine);