Changeset 5076 in osm for applications/editors/josm/plugins/utilsplugin/src/UtilsPlugin/SimplifyWayAction.java
- Timestamp:
- 2007-10-19T00:13:15+02:00 (17 years ago)
- Location:
- applications/editors/josm/plugins/utilsplugin/src
- Files:
-
- 2 added
- 1 copied
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/utilsplugin/src/UtilsPlugin/SimplifyWayAction.java
r5075 r5076 5 5 import java.awt.event.ActionEvent; 6 6 import java.util.ArrayList; 7 import java.util.Arrays;8 7 import java.util.Collection; 9 import java.util.Comparator; 10 import java.util.HashMap; 8 import java.util.Collections; 11 9 import java.util.HashSet; 12 10 import java.util.LinkedList; … … 18 16 import org.openstreetmap.josm.command.DeleteCommand; 19 17 import org.openstreetmap.josm.command.SequenceCommand; 20 import org.openstreetmap.josm.data.SelectionChangedListener;21 18 import org.openstreetmap.josm.data.coor.LatLon; 22 19 import org.openstreetmap.josm.data.osm.Node; 23 20 import org.openstreetmap.josm.data.osm.OsmPrimitive; 24 import org.openstreetmap.josm.data.osm.Segment;25 21 import org.openstreetmap.josm.data.osm.Way; 26 import org.openstreetmap.josm.data.osm.visitor. Visitor;22 import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor; 27 23 28 24 import org.openstreetmap.josm.data.osm.DataSet; 29 25 import org.openstreetmap.josm.actions.JosmAction; 30 26 31 /** 32 * Forgets the selected data, unless it is referenced by something. 33 * 34 * "Forgetting", as opposed to "deleting", means that the data is simply removed from JOSM, and 35 * not tagged as "to be deleted on server". 36 * 37 * - selected WAYS can always be forgotten. 38 * - selected SEGMENTS can be forgotten unless they are referenced by not-forgotten ways. 39 * - selected NODES can be forgotten unless they are referenced by not-forgotten segments. 40 */ 41 42 public class SimplifyWayAction extends JosmAction implements SelectionChangedListener { 43 44 45 private Way selectedWay = null; 46 47 /** 48 * Create a new SimplifyWayAction. 27 public class SimplifyWayAction extends JosmAction { 28 public SimplifyWayAction() { 29 super(tr("Simplify Way"), "simplify", 30 tr("Delete unnecessary nodes from a way."), 0, 0, true); 31 } 32 33 public void actionPerformed(ActionEvent e) { 34 Collection<OsmPrimitive> selection = Main.ds.getSelected(); 35 36 if (selection.size() == 1 && selection.iterator().next() instanceof Way) { 37 simplifyWay((Way) selection.iterator().next()); 38 } 39 } 40 41 public void simplifyWay(Way w) { 42 double threshold = Double.parseDouble( 43 Main.pref.get("simplify-way.max-error", "50")); 44 45 Way wnew = new Way(w); 46 47 int toI = wnew.nodes.size() - 1; 48 for (int i = wnew.nodes.size() - 1; i >= 0; i--) { 49 CollectBackReferencesVisitor backRefsV = 50 new CollectBackReferencesVisitor(Main.ds, false); 51 backRefsV.visit(wnew.nodes.get(i)); 52 boolean used = false; 53 if (backRefsV.data.size() == 1) { 54 used = Collections.frequency( 55 w.nodes, wnew.nodes.get(i)) > 1; 56 } else { 57 backRefsV.data.remove(w); 58 used = !backRefsV.data.isEmpty(); 59 } 60 61 if (used) { 62 if (toI - i >= 2) { 63 ArrayList<Node> ns = new ArrayList<Node>(); 64 simplifyWayRange(wnew, i, toI, ns, threshold); 65 for (int j = toI-1; j > i; j--) wnew.nodes.remove(j); 66 wnew.nodes.addAll(i+1, ns); 67 } 68 toI = i; 69 } 70 } 71 72 HashSet<Node> delNodes = new HashSet<Node>(); 73 delNodes.addAll(w.nodes); 74 delNodes.removeAll(wnew.nodes); 75 76 if (wnew.nodes.size() != w.nodes.size()) { 77 Collection<Command> cmds = new LinkedList<Command>(); 78 cmds.add(new ChangeCommand(w, wnew)); 79 cmds.add(new DeleteCommand(delNodes)); 80 Main.main.undoRedo.add( 81 new SequenceCommand(tr("Simplify Way (remove {0} nodes)", 82 delNodes.size()), 83 cmds)); 84 Main.map.repaint(); 85 } 86 } 87 88 /* 89 * Takes an interval [from,to] and adds nodes from the set (from,to) to 90 * ns. 49 91 */ 50 public SimplifyWayAction() { 51 super(tr("Simplify Way"), "simplify", tr("Delete low-information nodes from a way."), 0, 0, true); 52 try { Main.ds.addSelectionChangedListener(this); } 53 catch( NoSuchMethodError e ) 54 { 55 try { 56 java.lang.reflect.Field f = DataSet.class.getDeclaredField("listeners"); 57 ((Collection<SelectionChangedListener>)f.get(Main.ds)).add(this); 58 // Main.ds.listeners.add(this); 59 } catch (Exception x) { System.out.println( e ); } 60 } 61 } 62 63 /** 64 * Called when the action is executed. 65 */ 66 public void actionPerformed(ActionEvent e) { 67 68 Collection<OsmPrimitive> selection = Main.ds.getSelected(); 69 70 71 Visitor selectVisitor = new Visitor(){ 72 public void visit(Node n) { 73 } 74 public void visit(Segment s) { 75 } 76 public void visit(Way w) { 77 selectedWay = w; 78 } 79 }; 80 81 for (OsmPrimitive p : selection) 82 p.visit(selectVisitor); 83 84 simplifyWay(selectedWay); 85 } 86 87 private class NodeRecord { 88 public boolean keep = false; // whether this node must be kept 89 public Node node; // the node 90 public NodeRecord previous; // the segment leading to this node 91 public double xte; // the cross-track error 92 public NodeRecord next; 93 } 94 95 /** 96 * Simplifies the given way by potentially removing nodes and segments. 97 * 98 * @param way 99 * @return true if simplification was successful (even if way was not changed) 100 * false if simplification was not possible (branching/unordered ways) 101 */ 102 public boolean simplifyWay(Way way) { 103 104 // first build some structures that help us working with this way, assuming 105 // it might be very long, so we want to be efficient. 106 107 // a map holding one NodeRecord object for every node in the way, except 108 // the first node (which is never "simplified" anyway) 109 HashMap<Node,NodeRecord> nodeIndex = new HashMap<Node,NodeRecord>(); 110 111 // a hash set containing all segments in this way, for fast is-in-way checks 112 HashSet<Segment> segmentIndex = new HashSet<Segment>(); 113 114 // in addition to all this, we also have each NodeRecord pointing 115 // to the next one along the way, making a linked list. 116 NodeRecord firstNr = null; 117 118 // fill structures 119 NodeRecord prevNr = null; 120 for (Segment s : way.segments) { 121 if ((prevNr != null) && (!s.from.equals(prevNr.node))) { 122 // error 123 System.out.println("XXX err"); 124 return false; 125 } 126 segmentIndex.add(s); 127 NodeRecord nr = new NodeRecord(); 128 nr.node = s.to; 129 if (prevNr == null) { 130 nr.previous = new NodeRecord(); 131 nr.previous.node = s.from; 132 // set "keep" on first node 133 nr.previous.keep = true; 134 firstNr = nr.previous; 135 firstNr.next = nr; 136 nodeIndex.put(s.from, nr.previous); 137 } else { 138 nr.previous = prevNr; 139 prevNr.next = nr; 140 } 141 nr.xte = 0; 142 nr.next = null; 143 prevNr = nr; 144 nodeIndex.put(s.to, nr); 145 } 146 147 // set "keep" on last node 148 prevNr.keep = true; 149 150 // check the current data set, and mark all nodes that are used by a segment 151 // not exclusively owned by the current way as "untouchable". 152 for (Segment s: Main.ds.segments) { 153 if (s.deleted) continue; 154 if (segmentIndex.contains(s)) continue; // these don't count 155 NodeRecord tmp; 156 tmp = nodeIndex.get(s.from); if (tmp != null) tmp.keep = true; 157 tmp = nodeIndex.get(s.to); if (tmp != null) tmp.keep = true; 158 } 159 160 for (Way w: Main.ds.ways) { 161 if (w.deleted) continue; 162 if (w.equals(way)) continue; // these don't count 163 for (Segment s: w.segments) 164 { 165 NodeRecord tmp; 166 tmp = nodeIndex.get(s.from); if (tmp != null) tmp.keep = true; 167 tmp = nodeIndex.get(s.to); if (tmp != null) tmp.keep = true; 168 } 169 } 170 171 // keep all nodes which have tags other than source and created_by 172 for (NodeRecord nr : nodeIndex.values()) { 173 Collection<String> keyset = nr.node.keySet(); 174 keyset.remove("source"); 175 keyset.remove("created_by"); 176 if (!keyset.isEmpty()) nr.keep = true; 177 } 178 179 // compute cross-track error for all elements. cross-track error is the 180 // distance between a node and the nearest point on a line from the 181 // previous to the next node - that's the error you would introduce 182 // by removing the node. 183 for (NodeRecord r = firstNr; r.next != null; r = r.next) { 184 computeXte(r); 185 } 186 187 boolean stayInLoop = true; 188 double treshold = Double.parseDouble(Main.pref.get("simplify-way.max-error", "0.06")); 189 while(stayInLoop) { 190 NodeRecord[] sorted = new NodeRecord[nodeIndex.size()]; 191 nodeIndex.values().toArray(sorted); 192 Arrays.sort(sorted, new Comparator<NodeRecord>() { 193 public int compare(NodeRecord a, NodeRecord b) { 194 return (a.xte < b.xte) ? -1 : (a.xte > b.xte) ? 1 : 0; 195 } 196 }); 197 198 stayInLoop = false; 199 for (NodeRecord nr : sorted) { 200 if (nr.keep) continue; 201 if (nr.xte < treshold) { 202 // delete this node 203 nodeIndex.remove(nr.node); 204 if (nr == firstNr) { 205 firstNr = nr.next; 206 } else { 207 nr.previous.next = nr.next; 208 } 209 if (nr.next != null) { 210 nr.next.previous = nr.previous; 211 } 212 computeXte(nr.next); 213 computeXte(nr.previous); 214 stayInLoop = true; 215 } 216 break; 217 } 218 } 219 220 Segment currentOriginalSegment = null; 221 Segment currentModifiedSegment = null; 222 Way wayCopy = null; 223 int delCount = 0; 224 Collection<Command> cmds = new LinkedList<Command>(); 225 226 for (Segment s : way.segments) { 227 if (currentOriginalSegment == null) { 228 currentOriginalSegment = s; 229 currentModifiedSegment = s; 230 continue; 231 } 232 233 if (nodeIndex.containsKey(s.from)) { 234 // the current remaining segment's "to" node is not 235 // deleted, so it may stay. 236 if (currentModifiedSegment != currentOriginalSegment) { 237 cmds.add(new ChangeCommand(currentOriginalSegment, currentModifiedSegment)); 238 } 239 currentOriginalSegment = s; 240 currentModifiedSegment = s; 241 } else { 242 // the "to" node is to be deleted; delete segment and 243 // node 244 cmds.add(new DeleteCommand(Arrays.asList(new OsmPrimitive[]{s, s.from}))); 245 delCount ++; 246 if (wayCopy == null) { 247 wayCopy = new Way(way); 248 } 249 wayCopy.segments.remove(s); 250 if (currentModifiedSegment == currentOriginalSegment) { 251 currentModifiedSegment = new Segment(currentOriginalSegment); 252 } 253 currentModifiedSegment.to = s.to; 254 } 255 } 256 if (currentModifiedSegment != currentOriginalSegment) { 257 cmds.add(new ChangeCommand(currentOriginalSegment, currentModifiedSegment)); 258 } 259 260 if (wayCopy != null) { 261 cmds.add(new ChangeCommand(way, wayCopy)); 262 Main.main.editLayer().add(new SequenceCommand(tr("Simplify Way (remove {0} nodes)", delCount), cmds)); 263 } 264 265 return true; 266 267 } 268 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 269 setEnabled(!newSelection.isEmpty()); 270 } 271 272 private static void computeXte(NodeRecord r) { 273 if ((r.previous == null) || (r.next == null)) { 274 r.xte = 0; 275 return; 276 } 277 Node prevNode = r.previous.node; 278 Node nextNode = r.next.node; 279 r.xte = radtomiles(linedist(prevNode.coor.lat(), prevNode.coor.lon(), 280 r.node.coor.lat(), r.node.coor.lon(), 281 nextNode.coor.lat(), nextNode.coor.lon())); 282 } 283 92 public void simplifyWayRange(Way wnew, int from, int to, ArrayList<Node> ns, double thr) { 93 Node fromN = wnew.nodes.get(from), toN = wnew.nodes.get(to); 94 95 int imax = -1; 96 double xtemax = 0; 97 for (int i = from+1; i < to; i++) { 98 Node n = wnew.nodes.get(i); 99 double xte = radtometers(linedist( 100 fromN.coor.lat(), fromN.coor.lon(), 101 n.coor.lat(), n.coor.lon(), 102 toN.coor.lat(), toN.coor.lon())); 103 if (xte > xtemax) { 104 xtemax = xte; 105 imax = i; 106 } 107 } 108 109 if (imax != -1 && xtemax >= thr) { 110 simplifyWayRange(wnew, from, imax, ns, thr); 111 ns.add(wnew.nodes.get(imax)); 112 simplifyWayRange(wnew, imax, to, ns, thr); 113 } 114 } 115 284 116 /* ---------------------------------------------------------------------- 285 117 * Everything below this comment was converted from C to Java by Frederik
Note:
See TracChangeset
for help on using the changeset viewer.