Ignore:
Timestamp:
2007-10-19T00:13:15+02:00 (17 years ago)
Author:
gabriel
Message:

utilsplugin: Port to API 0.5.

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  
    55import java.awt.event.ActionEvent;
    66import java.util.ArrayList;
    7 import java.util.Arrays;
    87import java.util.Collection;
    9 import java.util.Comparator;
    10 import java.util.HashMap;
     8import java.util.Collections;
    119import java.util.HashSet;
    1210import java.util.LinkedList;
     
    1816import org.openstreetmap.josm.command.DeleteCommand;
    1917import org.openstreetmap.josm.command.SequenceCommand;
    20 import org.openstreetmap.josm.data.SelectionChangedListener;
    2118import org.openstreetmap.josm.data.coor.LatLon;
    2219import org.openstreetmap.josm.data.osm.Node;
    2320import org.openstreetmap.josm.data.osm.OsmPrimitive;
    24 import org.openstreetmap.josm.data.osm.Segment;
    2521import org.openstreetmap.josm.data.osm.Way;
    26 import org.openstreetmap.josm.data.osm.visitor.Visitor;
     22import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
    2723
    2824import org.openstreetmap.josm.data.osm.DataSet;
    2925import org.openstreetmap.josm.actions.JosmAction;
    3026
    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.
     27public 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.
    4991         */
    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
    284116        /* ----------------------------------------------------------------------
    285117         * Everything below this comment was converted from C to Java by Frederik
Note: See TracChangeset for help on using the changeset viewer.