source: josm/trunk/src/org/openstreetmap/josm/actions/SimplifyWayAction.java @ 5241

Revision 4982, 10.3 KB checked in by stoecker, 3 months ago (diff)

see #7226 - patch by akks (fixed a bit) - fix shortcut deprecations

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.HashSet;
14import java.util.LinkedList;
15import java.util.List;
16
17import javax.swing.JOptionPane;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.command.ChangeCommand;
21import org.openstreetmap.josm.command.Command;
22import org.openstreetmap.josm.command.DeleteCommand;
23import org.openstreetmap.josm.command.SequenceCommand;
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.HelpAwareOptionPane;
29import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
30import org.openstreetmap.josm.gui.help.HelpUtil;
31import org.openstreetmap.josm.tools.ImageProvider;
32import org.openstreetmap.josm.tools.Shortcut;
33
34public class SimplifyWayAction extends JosmAction {
35    public SimplifyWayAction() {
36        super(tr("Simplify Way"), "simplify", tr("Delete unnecessary nodes from a way."), Shortcut.registerShortcut("tools:simplify", tr("Tool: {0}", tr("Simplify Way")),
37                KeyEvent.VK_Y, Shortcut.SHIFT), true);
38        putValue("help", ht("/Action/SimplifyWay"));
39    }
40
41    protected boolean confirmWayWithNodesOutsideBoundingBox(List<? extends OsmPrimitive> primitives) {
42        System.out.println(primitives);
43        return DeleteCommand.checkAndConfirmOutlyingDelete(Main.map.mapView.getEditLayer(), primitives, null);
44    }
45
46    protected void alertSelectAtLeastOneWay() {
47        HelpAwareOptionPane.showOptionDialog(
48                Main.parent,
49                tr("Please select at least one way to simplify."),
50                tr("Warning"),
51                JOptionPane.WARNING_MESSAGE,
52                HelpUtil.ht("/Action/SimplifyWay#SelectAWayToSimplify")
53                );
54    }
55
56    protected boolean confirmSimplifyManyWays(int numWays) {
57        ButtonSpec[] options = new ButtonSpec[] {
58                new ButtonSpec(
59                        tr("Yes"),
60                        ImageProvider.get("ok"),
61                        tr("Simplify all selected ways"),
62                        null
63                        ),
64                        new ButtonSpec(
65                                tr("Cancel"),
66                                ImageProvider.get("cancel"),
67                                tr("Cancel operation"),
68                                null
69                                )
70        };
71        int ret = HelpAwareOptionPane.showOptionDialog(
72                Main.parent,
73                tr(
74                        "The selection contains {0} ways. Are you sure you want to simplify them all?",
75                        numWays
76                        ),
77                        tr("Simplify ways?"),
78                        JOptionPane.WARNING_MESSAGE,
79                        null, // no special icon
80                        options,
81                        options[0],
82                        HelpUtil.ht("/Action/SimplifyWay#ConfirmSimplifyAll")
83                );
84        return ret == 0;
85    }
86
87    public void actionPerformed(ActionEvent e) {
88        DataSet ds = getCurrentDataSet();
89        ds.beginUpdate();
90        try
91        {
92            List<Way> ways = OsmPrimitive.getFilteredList(ds.getSelected(), Way.class);
93            if (ways.isEmpty()) {
94                alertSelectAtLeastOneWay();
95                return;
96            } else if (!confirmWayWithNodesOutsideBoundingBox(ways))
97                return;
98            else if (ways.size() > 10) {
99                if (!confirmSimplifyManyWays(ways.size()))
100                    return;
101            }
102
103            Collection<Command> allCommands = new LinkedList<Command>();
104            for (Way way: ways) {
105                SequenceCommand simplifyCommand = simplifyWay(way, ds);
106                if (simplifyCommand == null) {
107                    continue;
108                }
109                allCommands.add(simplifyCommand);
110            }
111            if (allCommands.isEmpty()) return;
112            SequenceCommand rootCommand = new SequenceCommand(
113                    trn("Simplify {0} way", "Simplify {0} ways", allCommands.size(), allCommands.size()),
114                    allCommands
115                    );
116            Main.main.undoRedo.add(rootCommand);
117        } finally {
118            ds.endUpdate();
119        }
120        Main.map.repaint();
121    }
122
123    /**
124     * Replies true if <code>node</code> is a required node which can't be removed
125     * in order to simplify the way.
126     *
127     * @param way the way to be simplified
128     * @param node the node to check
129     * @return true if <code>node</code> is a required node which can't be removed
130     * in order to simplify the way.
131     */
132    protected boolean isRequiredNode(Way way, Node node) {
133        boolean isRequired =  Collections.frequency(way.getNodes(), node) > 1;
134        if (! isRequired) {
135            List<OsmPrimitive> parents = new LinkedList<OsmPrimitive>();
136            parents.addAll(node.getReferrers());
137            parents.remove(way);
138            isRequired = !parents.isEmpty();
139        }
140        if (!isRequired) {
141            isRequired = node.isTagged();
142        }
143        return isRequired;
144    }
145
146    /**
147     * Simplifies a way
148     *
149     * @param w the way to simplify
150     */
151    public SequenceCommand simplifyWay(Way w, DataSet ds) {
152        double threshold = Double.parseDouble(Main.pref.get("simplify-way.max-error", "3"));
153        int lower = 0;
154        int i = 0;
155        List<Node> newNodes = new ArrayList<Node>(w.getNodesCount());
156        while(i < w.getNodesCount()){
157            if (isRequiredNode(w,w.getNode(i))) {
158                // copy a required node to the list of new nodes. Simplify not
159                // possible
160                newNodes.add(w.getNode(i));
161                i++;
162                lower++;
163                continue;
164            }
165            i++;
166            // find the longest sequence of not required nodes ...
167            while(i<w.getNodesCount() && !isRequiredNode(w,w.getNode(i))) {
168                i++;
169            }
170            // ... and simplify them
171            buildSimplifiedNodeList(w.getNodes(), lower, Math.min(w.getNodesCount()-1, i), threshold,newNodes);
172            lower=i;
173            i++;
174        }
175
176        HashSet<Node> delNodes = new HashSet<Node>();
177        delNodes.addAll(w.getNodes());
178        delNodes.removeAll(newNodes);
179
180        if (delNodes.isEmpty()) return null;
181
182        Collection<Command> cmds = new LinkedList<Command>();
183        Way newWay = new Way(w);
184        newWay.setNodes(newNodes);
185        cmds.add(new ChangeCommand(w, newWay));
186        cmds.add(new DeleteCommand(delNodes));
187        ds.clearSelection(delNodes);
188        return new SequenceCommand(trn("Simplify Way (remove {0} node)", "Simplify Way (remove {0} nodes)", delNodes.size(), delNodes.size()), cmds);
189    }
190
191    /**
192     * Builds the simplified list of nodes for a way segment given by a lower index <code>from</code>
193     * and an upper index <code>to</code>
194     *
195     * @param wnew the way to simplify
196     * @param from the lower index
197     * @param to the upper index
198     * @param threshold
199     */
200    protected void buildSimplifiedNodeList(List<Node> wnew, int from, int to, double threshold, List<Node> simplifiedNodes) {
201
202        Node fromN = wnew.get(from);
203        Node toN = wnew.get(to);
204
205        // Get max xte
206        int imax = -1;
207        double xtemax = 0;
208        for (int i = from + 1; i < to; i++) {
209            Node n = wnew.get(i);
210            double xte = Math.abs(EARTH_RAD
211                    * xtd(fromN.getCoor().lat() * Math.PI / 180, fromN.getCoor().lon() * Math.PI / 180, toN.getCoor().lat() * Math.PI
212                            / 180, toN.getCoor().lon() * Math.PI / 180, n.getCoor().lat() * Math.PI / 180, n.getCoor().lon() * Math.PI
213                            / 180));
214            if (xte > xtemax) {
215                xtemax = xte;
216                imax = i;
217            }
218        }
219
220        if (imax != -1 && xtemax >= threshold) {
221            // Segment cannot be simplified - try shorter segments
222            buildSimplifiedNodeList(wnew, from, imax,threshold,simplifiedNodes);
223            //simplifiedNodes.add(wnew.get(imax));
224            buildSimplifiedNodeList(wnew, imax, to, threshold,simplifiedNodes);
225        } else {
226            // Simplify segment
227            if (simplifiedNodes.isEmpty() || simplifiedNodes.get(simplifiedNodes.size()-1) != fromN) {
228                simplifiedNodes.add(fromN);
229            }
230            if (fromN != toN) {
231                simplifiedNodes.add(toN);
232            }
233        }
234    }
235
236    public static final double EARTH_RAD = 6378137.0;
237
238    /* From Aviaton Formulary v1.3
239     * http://williams.best.vwh.net/avform.htm
240     */
241    public static double dist(double lat1, double lon1, double lat2, double lon2) {
242        return 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2) + Math.cos(lat1) * Math.cos(lat2)
243                * Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
244    }
245
246    public static double course(double lat1, double lon1, double lat2, double lon2) {
247        return Math.atan2(Math.sin(lon1 - lon2) * Math.cos(lat2), Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
248                * Math.cos(lat2) * Math.cos(lon1 - lon2))
249                % (2 * Math.PI);
250    }
251
252    public static double xtd(double lat1, double lon1, double lat2, double lon2, double lat3, double lon3) {
253        double dist_AD = dist(lat1, lon1, lat3, lon3);
254        double crs_AD = course(lat1, lon1, lat3, lon3);
255        double crs_AB = course(lat1, lon1, lat2, lon2);
256        return Math.asin(Math.sin(dist_AD) * Math.sin(crs_AD - crs_AB));
257    }
258
259    @Override
260    protected void updateEnabledState() {
261        if (getCurrentDataSet() == null) {
262            setEnabled(false);
263        } else {
264            updateEnabledState(getCurrentDataSet().getSelected());
265        }
266    }
267
268    @Override
269    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
270        setEnabled(selection != null && !selection.isEmpty());
271    }
272}
Note: See TracBrowser for help on using the repository browser.