Ignore:
Timestamp:
2011-03-02T22:34:51+01:00 (13 years ago)
Author:
mzdila
Message:

support for removing nodes on the shared ways

File:
1 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/simplifyarea/src/sk/zdila/josm/plugin/simplify/SimplifyAreaAction.java

    r25030 r25488  
    1010import java.awt.event.ActionEvent;
    1111import java.awt.event.KeyEvent;
     12import java.util.ArrayList;
    1213import java.util.Collection;
    1314import java.util.HashMap;
     
    1617import java.util.List;
    1718import java.util.Map;
     19import java.util.Map.Entry;
     20import java.util.Set;
    1821
    1922import javax.swing.JOptionPane;
     
    4447    public SimplifyAreaAction() {
    4548        super(tr("Simplify Area"), "simplify", tr("Delete unnecessary nodes from an area."),
    46                 Shortcut.registerShortcut("tools:simplifyArea", tr("Tool: {0}", tr("Simplify Area")), KeyEvent.VK_A, Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
    47     }
     49                Shortcut.registerShortcut("tools:simplifyArea", tr("Tool: {0}", tr("Simplify Area")), KeyEvent.VK_F, Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
     50    }
     51
    4852
    4953
     
    119123            }
    120124        }
    121         final List<Way> ways = OsmPrimitive.getFilteredList(selection, Way.class);
     125        final Collection<Way> ways = OsmPrimitive.getFilteredSet(selection, Way.class);
    122126        if (ways.isEmpty()) {
    123127            alertSelectAtLeastOneWay();
     
    129133        }
    130134
    131         final Collection<Command> allCommands = new LinkedList<Command>();
     135        final List<Node> nodesToDelete = new ArrayList<Node>(); // can contain duplicate instances
     136
    132137        for (final Way way : ways) {
    133             final SequenceCommand simplifyCommand = simplifyWay(way);
    134             if (simplifyCommand == null) {
    135                 continue;
    136             }
    137             allCommands.add(simplifyCommand);
     138            addNodesToDelete(nodesToDelete, way);
     139        }
     140
     141        class Count {
     142            int count;
     143        }
     144
     145        final Map<Node, Count> nodeCountMap = new HashMap<Node, Count>();
     146        for (final Node node : nodesToDelete) {
     147            Count count = nodeCountMap.get(node);
     148            if (count == null) {
     149                count = new Count();
     150                nodeCountMap.put(node, count);
     151            }
     152            count.count++;
     153        }
     154
     155        final Collection<Node> nodesReallyToRemove = new ArrayList<Node>();
     156
     157        for (final Entry<Node, Count> entry : nodeCountMap.entrySet()) {
     158            final Node node = entry.getKey();
     159            final int count = entry.getValue().count;
     160
     161            if (!node.isTagged() && node.getReferrers().size() == count) {
     162                nodesReallyToRemove.add(node);
     163            }
     164        }
     165
     166        final Collection<Command> allCommands = new ArrayList<Command>();
     167
     168        if (!nodesReallyToRemove.isEmpty()) {
     169            for (final Way way : ways) {
     170                final List<Node> nodes = way.getNodes();
     171                final boolean closed = nodes.get(0).equals(nodes.get(nodes.size() - 1));
     172                if (closed) {
     173                    nodes.remove(nodes.size() - 1);
     174                }
     175
     176                if (nodes.removeAll(nodesReallyToRemove)) {
     177                    if (closed) {
     178                        nodes.add(nodes.get(0));
     179                    }
     180
     181                    final Way newWay = new Way(way);
     182                    newWay.setNodes(nodes);
     183                    allCommands.add(new ChangeCommand(way, newWay));
     184                }
     185            }
     186
     187            allCommands.add(new DeleteCommand(nodesReallyToRemove));
     188        }
     189
     190        final Collection<Command> avgCommands = averageNearbyNodes(ways, nodesReallyToRemove);
     191        if (avgCommands != null) {
     192            allCommands.add(new SequenceCommand("average nearby nodes", avgCommands));
    138193        }
    139194
     
    146201
    147202
    148     /**
    149      * Replies true if <code>node</code> is a required node which can't be removed in order to simplify the way.
    150      *
    151      * @param way
    152      *            the way to be simplified
    153      * @param node
    154      *            the node to check
    155      * @return true if <code>node</code> is a required node which can't be removed in order to simplify the way.
    156      */
    157     private boolean isRequiredNode(final Way way, final Node node) {
    158         final List<OsmPrimitive> parents = new LinkedList<OsmPrimitive>(node.getReferrers());
    159         parents.remove(way);
    160         return !parents.isEmpty() || node.isTagged();
    161     }
    162 
    163 
    164     /**
    165      * Simplifies a way
    166      *
    167      * @param w
    168      *            the way to simplify
    169      */
    170     private SequenceCommand simplifyWay(final Way w) {
     203    // average nearby nodes
     204    private Collection<Command> averageNearbyNodes(final Collection<Way> ways, final Collection<Node> nodesAlreadyDeleted) {
     205        final double mergeThreshold = Main.pref.getDouble("simplify-area.merge.threshold", 0.2);
     206
     207        final Map<Node, LatLon> coordMap = new HashMap<Node, LatLon>();
     208        for (final Way way : ways) {
     209            for (final Node n : way.getNodes()) {
     210                coordMap.put(n, n.getCoor());
     211            }
     212        }
     213
     214        coordMap.keySet().removeAll(nodesAlreadyDeleted);
     215
     216        for (final Way w : ways) {
     217            final List<Node> nodes = w.getNodes();
     218
     219            final Node lastNode = nodes.get(nodes.size() - 1);
     220            final boolean closed = nodes.get(0).equals(lastNode);
     221            if (closed) {
     222                nodes.remove(lastNode);
     223            }
     224
     225            nodes.retainAll(coordMap.keySet()); // removes already deleted nodes
     226
     227            while (true) {
     228                double minDist = Double.MAX_VALUE;
     229                Node node1 = null;
     230                Node node2 = null;
     231
     232                // find smallest distance
     233                for (int i = 0, len = nodes.size(); i <= len; i++) {
     234                    final Node n1 = nodes.get(i % len);
     235                    final Node n2 = nodes.get((i + 1) % len);
     236
     237                    if (n1.isTagged() || n2.isTagged()) {
     238                        continue;
     239                    }
     240
     241                    // test if both nodes are on the same ways
     242                    final List<OsmPrimitive> referrers = n1.getReferrers();
     243                    if (!ways.containsAll(referrers)) {
     244                        continue;
     245                    }
     246
     247                    final List<OsmPrimitive> referrers2 = n2.getReferrers();
     248                    if (!ways.containsAll(referrers2)) {
     249                        continue;
     250                    }
     251
     252                    // test if both nodes have same parents
     253                    if (!referrers.containsAll(referrers2) || !referrers2.containsAll(referrers)) {
     254                        continue;
     255                    }
     256
     257                    final double dist = coordMap.get(n1).greatCircleDistance(coordMap.get(n2));
     258                    if (dist < minDist && dist < mergeThreshold) {
     259                        minDist = dist;
     260                        node1 = n1;
     261                        node2 = n2;
     262                    }
     263                }
     264
     265                if (node1 == null || node2 == null) {
     266                    break;
     267                }
     268
     269                final LatLon coord = coordMap.get(node1).getCenter(coordMap.get(node2));
     270                coordMap.put(node1, coord);
     271
     272                nodes.remove(node2);
     273                coordMap.remove(node2);
     274            }
     275        }
     276
     277
     278        final Collection<Command> commands = new ArrayList<Command>();
     279        final Set<Node> nodesToDelete2 = new HashSet<Node>();
     280        for (final Way way : ways) {
     281            final List<Node> nodesToDelete = way.getNodes();
     282            nodesToDelete.removeAll(nodesAlreadyDeleted);
     283            if (nodesToDelete.removeAll(coordMap.keySet())) {
     284                nodesToDelete2.addAll(nodesToDelete);
     285                final Way newWay = new Way(way);
     286                final List<Node> nodes = way.getNodes();
     287                final boolean closed = nodes.get(0).equals(nodes.get(nodes.size() - 1));
     288                if (closed) {
     289                    nodes.remove(nodes.size() - 1);
     290                }
     291                nodes.retainAll(coordMap.keySet());
     292                if (closed) {
     293                    nodes.add(nodes.get(0));
     294                }
     295
     296                newWay.setNodes(nodes);
     297                commands.add(new ChangeCommand(way, newWay));
     298            }
     299        }
     300
     301        if (!nodesToDelete2.isEmpty()) {
     302            commands.add(new DeleteCommand(nodesToDelete2));
     303        }
     304
     305        for (final Entry<Node, LatLon> entry : coordMap.entrySet()) {
     306            final Node node = entry.getKey();
     307            final LatLon coord = entry.getValue();
     308            if (!node.getCoor().equals(coord)) {
     309                commands.add(new MoveCommand(node, coord));
     310            }
     311        }
     312
     313        return commands;
     314    }
     315
     316
     317    private void addNodesToDelete(final Collection<Node> nodesToDelete, final Way w) {
     318//        System.out.println("---------------- " + w.getId());
     319
    171320        final double angleThreshold = Main.pref.getDouble("simplify-area.angle.threshold", 10);
    172321        final double angleFactor = Main.pref.getDouble("simplify-area.angle.factor", 1.0);
    173         final double mergeThreshold = Main.pref.getDouble("simplify-area.merge.threshold", 0.2);
    174322        final double areaThreshold = Main.pref.getDouble("simplify-area.area.threshold", 5.0);
    175323        final double areaFactor = Main.pref.getDouble("simplify-area.area.factor", 1.0);
     
    181329
    182330        if (size == 0) {
    183             return null;
     331            return;
    184332        }
    185333
     
    192340        // remove nodes within threshold
    193341
     342        final List<Double> weightList = new ArrayList<Double>(nodes.size()); // weight cache
     343        for (int i = 0; i < nodes.size(); i++) {
     344            weightList.add(null);
     345        }
     346
    194347        while (true) {
     348//            System.out.println(nodes.size());
    195349            Node prevNode = null;
    196350            LatLon coord1 = null;
     
    201355
    202356            for (int i = 0, len = nodes.size() + (closed ? 2 : 1); i < len; i++) {
    203                 final Node n = nodes.get(i % nodes.size());
     357                final int index = i % nodes.size();
     358
     359                final Node n = nodes.get(index);
    204360                final LatLon coord3 = n.getCoor();
    205361
    206362                if (coord1 != null) {
    207                     final double angleWeight = computeConvectAngle(coord1, coord2, coord3) / angleThreshold;
    208                     final double areaWeight = computeArea(coord1, coord2, coord3) / areaThreshold;
    209                     final double distanceWeight = Math.abs(crossTrackError(coord1, coord2, coord3)) / distanceThreshold;
    210 
    211                     final double weight = isRequiredNode(w, prevNode) ||
    212                     !closed && i == len - 1 || // don't remove last node of the not closed way
    213                     angleWeight > 1.0 || areaWeight > 1.0 || distanceWeight > 1.0 ? Double.MAX_VALUE :
    214                         angleWeight * angleFactor + areaWeight * areaFactor + distanceWeight * distanceFactor;
     363                    final double weight;
     364
     365                    if (weightList.get(index) == null) {
     366                        final double angleWeight = computeConvectAngle(coord1, coord2, coord3) / angleThreshold;
     367                        final double areaWeight = computeArea(coord1, coord2, coord3) / areaThreshold;
     368                        final double distanceWeight = Math.abs(crossTrackError(coord1, coord2, coord3)) / distanceThreshold;
     369
     370                        weight = /*isRequiredNode(w, prevNode, minUse) ||*/
     371                        !closed && i == len - 1 || // don't remove last node of the not closed way
     372                        angleWeight > 1.0 || areaWeight > 1.0 || distanceWeight > 1.0 ? Double.MAX_VALUE :
     373                            angleWeight * angleFactor + areaWeight * areaFactor + distanceWeight * distanceFactor;
     374
     375                        weightList.set(index, weight);
     376                    } else {
     377                        weight = weightList.get(index);
     378                    }
    215379
    216380                    if (weight < minWeight) {
     
    229393            }
    230394
    231             nodes.remove(bestMatch);
    232         }
    233 
    234 
    235         // average nearby nodes
    236 
    237         final Map<Node, LatLon> coordMap = new HashMap<Node, LatLon>();
    238         for (final Node n : nodes) {
    239             coordMap.put(n, n.getCoor());
    240         }
    241 
    242         final Map<Node, MoveCommand> moveCommandList = new HashMap<Node, MoveCommand>();
    243 
    244         while (true) {
    245             double minDist = Double.MAX_VALUE;
    246             Node node1 = null;
    247             Node node2 = null;
    248 
    249             for (int i = 0, len = nodes.size() + (closed ? 2 : 1); i < len; i++) {
    250                 final Node n1 = nodes.get(i % nodes.size());
    251                 final Node n2 = nodes.get((i + 1) % nodes.size());
    252 
    253                 if (isRequiredNode(w, n1) || isRequiredNode(w, n2)) {
    254                     continue;
    255                 }
    256 
    257                 final double dist = coordMap.get(n1).greatCircleDistance(coordMap.get(n2));
    258                 if (dist < minDist && dist < mergeThreshold) {
    259                     minDist = dist;
    260                     node1 = n1;
    261                     node2 = n2;
    262                 }
    263             }
    264 
    265             if (node1 == null || node2 == null) {
    266                 break;
    267             }
    268 
    269 
    270             final LatLon coord = coordMap.get(node1).getCenter(coordMap.get(node2));
    271             coordMap.put(node1, coord);
    272             moveCommandList.put(node1, new MoveCommand(node1, coord));
    273 
    274             nodes.remove(node2);
    275             coordMap.remove(node2);
    276             moveCommandList.remove(node2);
    277         }
    278 
    279 
    280         if (closed) {
    281             nodes.add(nodes.get(0)); // set end node ( = start node)
     395            final int index = nodes.indexOf(bestMatch);
     396
     397            final int size2 = nodes.size();
     398            weightList.set((index - 1 + size2) % size2, null);
     399            weightList.set((index + 1 + size2) % size2, null);
     400            weightList.remove(index);
     401            nodes.remove(index);
    282402        }
    283403
     
    285405        delNodes.removeAll(nodes);
    286406
    287         if (delNodes.isEmpty()) {
    288             return null;
    289         }
    290 
    291         final Collection<Command> cmds = new LinkedList<Command>();
    292         final Way newWay = new Way(w);
    293         newWay.setNodes(nodes);
    294 
    295         cmds.addAll(moveCommandList.values());
    296         cmds.add(new ChangeCommand(w, newWay));
    297         cmds.add(new DeleteCommand(delNodes));
    298         return new SequenceCommand(trn("Simplify Area (remove {0} node)", "Simplify Area (remove {0} nodes)", delNodes.size(), delNodes.size()), cmds);
     407        nodesToDelete.addAll(delNodes);
    299408    }
    300409
Note: See TracChangeset for help on using the changeset viewer.