Changeset 27997 in osm


Ignore:
Timestamp:
2012-03-06T03:29:53+01:00 (12 years ago)
Author:
joshdoe
Message:

utilsplugin2: extract static methods from ReplaceGeometryAction to ReplaceGeometryUtils

Location:
applications/editors/josm/plugins
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/conflation/src/org/openstreetmap/josm/plugins/conflation/ConflationToggleDialog.java

    r27971 r27997  
    3232import static org.openstreetmap.josm.tools.I18n.tr;
    3333import org.openstreetmap.josm.tools.Shortcut;
    34 import utilsplugin2.dumbutils.ReplaceGeometryAction;
     34import utilsplugin2.dumbutils.ReplaceGeometryUtils;
    3535
    3636public class ConflationToggleDialog extends ToggleDialog
     
    206206        @Override
    207207        public void actionPerformed(ActionEvent e) {
    208             ReplaceGeometryAction rg = new ReplaceGeometryAction();
    209208            ConflationCandidate c = conflationLayer.getSelectedCandidate();
    210             if (rg.replace(c.getReferenceObject(), c.getSubjectObject())) {
     209            if (ReplaceGeometryUtils.replace(c.getReferenceObject(), c.getSubjectObject())) {
    211210                candidates.remove(c);
    212211            }
  • applications/editors/josm/plugins/utilsplugin2/src/utilsplugin2/dumbutils/ReplaceGeometryAction.java

    r27973 r27997  
    33import java.awt.event.ActionEvent;
    44import java.awt.event.KeyEvent;
    5 import java.awt.geom.Area;
    6 import java.awt.geom.Point2D;
    7 import java.util.*;
     5import java.util.ArrayList;
     6import java.util.Collection;
     7import java.util.List;
    88import javax.swing.JOptionPane;
    99import org.openstreetmap.josm.Main;
    1010import org.openstreetmap.josm.actions.JosmAction;
    11 import org.openstreetmap.josm.actions.MergeNodesAction;
    12 import org.openstreetmap.josm.command.*;
    13 import org.openstreetmap.josm.data.osm.Node;
    1411import org.openstreetmap.josm.data.osm.OsmPrimitive;
    15 import org.openstreetmap.josm.data.osm.Relation;
    16 import org.openstreetmap.josm.data.osm.RelationMember;
    17 import org.openstreetmap.josm.data.osm.RelationToChildReference;
    18 import org.openstreetmap.josm.data.osm.TagCollection;
    19 import org.openstreetmap.josm.data.osm.Way;
    20 import org.openstreetmap.josm.gui.DefaultNameFormatter;
    21 import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog;
    22 import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil;
    2312import static org.openstreetmap.josm.tools.I18n.tr;
    2413import org.openstreetmap.josm.tools.Shortcut;
     
    5746       
    5847        try {
    59             replaceWithNew(firstObject, secondObject);
     48            ReplaceGeometryUtils.replaceWithNew(firstObject, secondObject);
    6049        } catch (IllegalArgumentException ex) {
    6150            JOptionPane.showMessageDialog(Main.parent,
     
    6352        }
    6453         
    65     }
    66    
    67     /**
    68      * Replace new or uploaded object with new object
    69      *
    70      * @param firstObject
    71      * @param secondObject
    72      * @return
    73      */
    74     public boolean replaceWithNew(OsmPrimitive firstObject, OsmPrimitive secondObject) {
    75         if (firstObject instanceof Node && secondObject instanceof Node) {
    76             return replaceNodeWithNew((Node) firstObject, (Node) secondObject);
    77         } else if (firstObject instanceof Way && secondObject instanceof Way) {
    78             return replaceWayWithNew(Arrays.asList((Way) firstObject, (Way) secondObject));
    79         } else if (firstObject instanceof Node) {
    80             return upgradeNode((Node) firstObject, secondObject);
    81         } else if (secondObject instanceof Node) {
    82             return upgradeNode((Node) secondObject, firstObject);
    83         } else {
    84             throw new IllegalArgumentException(tr("This tool can only replace a node, upgrade a node to a way or a multipolygon, or replace a way with a way."));
    85         }
    86     }
    87    
    88     /**
    89      * Replace subjectObject geometry with referenceObject geometry and merge tags
    90      * and relation memberships.
    91      *
    92      * @param subjectObject
    93      * @param referenceSubject
    94      * @return
    95      */
    96     public boolean replace(OsmPrimitive subjectObject, OsmPrimitive referenceSubject) {
    97         if (subjectObject instanceof Node && referenceSubject instanceof Node) {
    98             return replaceNode((Node) subjectObject, (Node) referenceSubject);
    99         } else if (subjectObject instanceof Way && referenceSubject instanceof Way) {
    100             return replaceWay((Way) subjectObject, (Way) referenceSubject);
    101         } else if (subjectObject instanceof Node) {
    102             return upgradeNode((Node) subjectObject, referenceSubject);
    103         } else if (referenceSubject instanceof Node) {
    104             // TODO: fix this illogical reversal?
    105             return upgradeNode((Node) referenceSubject, subjectObject);
    106         } else {
    107             throw new IllegalArgumentException(tr("This tool can only replace a node, upgrade a node to a way or a multipolygon, or replace a way with a way."));
    108         }
    109     }
    110 
    111     /**
    112      * Replace a new or uploaded node with a new node
    113      * @param firstNode
    114      * @param secondNode
    115      * @return
    116      */
    117     public boolean replaceNodeWithNew(Node firstNode, Node secondNode) {
    118         if (firstNode.isNew() && !secondNode.isNew())
    119             return replaceNode(secondNode, firstNode);
    120         else if (!firstNode.isNew() && secondNode.isNew())
    121             return replaceNode(firstNode, secondNode);
    122         else
    123             // both nodes are new OR uploaded, act like MergeNodes, moving first
    124             // node to second
    125             return replaceNode(firstNode, secondNode);
    126     }
    127    
    128     /**
    129      * Replace a node with another node (similar to MergeNodesAction)
    130      *
    131      * @param subjectNode
    132      * @param referenceNode
    133      * @return
    134      */
    135     public boolean replaceNode(Node subjectNode, Node referenceNode) {
    136         if (!OsmPrimitive.getFilteredList(subjectNode.getReferrers(), Way.class).isEmpty()) {
    137             JOptionPane.showMessageDialog(Main.parent, tr("Node belongs to way(s), cannot replace."),
    138                     TITLE, JOptionPane.INFORMATION_MESSAGE);
    139             return false;
    140         }
    141         // FIXME: handle different layers
    142         List<Command> commands = new ArrayList<Command>();
    143         commands.add(MergeNodesAction.mergeNodes(Main.main.getEditLayer(), Arrays.asList(subjectNode, referenceNode), referenceNode));
    144 
    145         Main.main.undoRedo.add(new SequenceCommand(
    146                 tr("Replace geometry for node {0}", subjectNode.getDisplayName(DefaultNameFormatter.getInstance())),
    147                 commands));
    148         return true;
    149     }
    150    
    151     /**
    152      * Upgrade a node to a way or multipolygon
    153      *
    154      * @param subjectNode node to be replaced
    155      * @param referenceObject object with greater spatial quality
    156      */
    157     public boolean upgradeNode(Node subjectNode, OsmPrimitive referenceObject) {
    158         if (!OsmPrimitive.getFilteredList(subjectNode.getReferrers(), Way.class).isEmpty()) {
    159             JOptionPane.showMessageDialog(Main.parent, tr("Node belongs to way(s), cannot replace."),
    160                     TITLE, JOptionPane.INFORMATION_MESSAGE);
    161             return false;
    162         }
    163 
    164         if (referenceObject instanceof Relation && !((Relation) referenceObject).isMultipolygon()) {
    165             JOptionPane.showMessageDialog(Main.parent, tr("Relation is not a multipolygon, cannot be used as a replacement."),
    166                     TITLE, JOptionPane.INFORMATION_MESSAGE);
    167             return false;
    168         }
    169 
    170         Node nodeToReplace = null;
    171         // see if we need to replace a node in the replacement way to preserve connection in history
    172         if (!subjectNode.isNew()) {
    173             // Prepare a list of nodes that are not important
    174             Collection<Node> nodePool = new HashSet<Node>();
    175             if (referenceObject instanceof Way) {
    176                 nodePool.addAll(getUnimportantNodes((Way) referenceObject));
    177             } else if (referenceObject instanceof Relation) {
    178                 for (RelationMember member : ((Relation) referenceObject).getMembers()) {
    179                     if ((member.getRole().equals("outer") || member.getRole().equals("inner"))
    180                             && member.isWay()) {
    181                         // TODO: could consider more nodes, such as nodes that are members of other ways,
    182                         // just need to replace occurences in all referrers
    183                         nodePool.addAll(getUnimportantNodes(member.getWay()));
    184                     }
    185                 }
    186             } else {
    187                 assert false;
    188             }
    189             nodeToReplace = findNearestNode(subjectNode, nodePool);
    190         }
    191 
    192         List<Command> commands = new ArrayList<Command>();
    193         AbstractMap<String, String> nodeTags = (AbstractMap<String, String>) subjectNode.getKeys();
    194 
    195         // merge tags
    196         Collection<Command> tagResolutionCommands = getTagConflictResolutionCommands(subjectNode, referenceObject);
    197         if (tagResolutionCommands == null) {
    198             // user canceled tag merge dialog
    199             return false;
    200         }
    201         commands.addAll(tagResolutionCommands);
    202 
    203         // replace sacrificial node in way with node that is being upgraded
    204         if (nodeToReplace != null) {
    205             // node should only have one parent, a way
    206             Way parentWay = (Way) nodeToReplace.getReferrers().get(0);
    207             List<Node> wayNodes = parentWay.getNodes();
    208             int idx = wayNodes.indexOf(nodeToReplace);
    209             wayNodes.set(idx, subjectNode);
    210             if (idx == 0 && parentWay.isClosed()) {
    211                 // node is at start/end of way
    212                 wayNodes.set(wayNodes.size() - 1, subjectNode);
    213             }
    214             commands.add(new ChangeNodesCommand(parentWay, wayNodes));
    215             commands.add(new MoveCommand(subjectNode, nodeToReplace.getCoor()));
    216             commands.add(new DeleteCommand(nodeToReplace));
    217 
    218             // delete tags from node
    219             if (!nodeTags.isEmpty()) {
    220                 for (String key : nodeTags.keySet()) {
    221                     commands.add(new ChangePropertyCommand(subjectNode, key, null));
    222                 }
    223 
    224             }
    225         } else {
    226             // no node to replace, so just delete the original node
    227             commands.add(new DeleteCommand(subjectNode));
    228         }
    229 
    230         getCurrentDataSet().setSelected(referenceObject);
    231 
    232         Main.main.undoRedo.add(new SequenceCommand(
    233                 tr("Replace geometry for node {0}", subjectNode.getDisplayName(DefaultNameFormatter.getInstance())),
    234                 commands));
    235         return true;
    236     }
    237    
    238     public boolean replaceWayWithNew(List<Way> selection) {
    239         // determine which way will be replaced and which will provide the geometry
    240         boolean overrideNewCheck = false;
    241         int idxNew = selection.get(0).isNew() ? 0 : 1;
    242         if( selection.get(1-idxNew).isNew() ) {
    243             // if both are new, select the one with all the DB nodes
    244             boolean areNewNodes = false;
    245             for (Node n : selection.get(0).getNodes()) {
    246                 if (n.isNew()) {
    247                     areNewNodes = true;
    248                 }
    249             }
    250             idxNew = areNewNodes ? 0 : 1;
    251             overrideNewCheck = true;
    252             for (Node n : selection.get(1 - idxNew).getNodes()) {
    253                 if (n.isNew()) {
    254                     overrideNewCheck = false;
    255                 }
    256             }
    257         }
    258         Way referenceWay = selection.get(idxNew);
    259         Way subjectWay = selection.get(1 - idxNew);
    260        
    261         if( !overrideNewCheck && (subjectWay.isNew() || !referenceWay.isNew()) ) {
    262             JOptionPane.showMessageDialog(Main.parent,
    263                     tr("Please select one way that exists in the database and one new way with correct geometry."),
    264                     TITLE, JOptionPane.WARNING_MESSAGE);
    265             return false;
    266         }
    267         return replaceWay(subjectWay, referenceWay);
    268     }
    269    
    270     public static boolean replaceWay(Way subjectWay, Way referenceWay) {
    271 
    272         Area a = getCurrentDataSet().getDataSourceArea();
    273         if (!isInArea(subjectWay, a) || !isInArea(referenceWay, a)) {
    274             JOptionPane.showMessageDialog(Main.parent,
    275                     tr("The ways must be entirely within the downloaded area."),
    276                     TITLE, JOptionPane.WARNING_MESSAGE);
    277             return false;
    278         }
    279        
    280         if (hasImportantNode(referenceWay, subjectWay)) {
    281             JOptionPane.showMessageDialog(Main.parent,
    282                     tr("The way to be replaced cannot have any nodes with properties or relation memberships unless they belong to both ways."),
    283                     TITLE, JOptionPane.WARNING_MESSAGE);
    284             return false;
    285         }
    286 
    287         List<Command> commands = new ArrayList<Command>();
    288                
    289         // merge tags
    290         Collection<Command> tagResolutionCommands = getTagConflictResolutionCommands(referenceWay, subjectWay);
    291         if (tagResolutionCommands == null) {
    292             // user canceled tag merge dialog
    293             return false;
    294         }
    295         commands.addAll(tagResolutionCommands);
    296        
    297         // Prepare a list of nodes that are not used anywhere except in the way
    298         Collection<Node> nodePool = getUnimportantNodes(subjectWay);
    299 
    300         // And the same for geometry, list nodes that can be freely deleted
    301         Set<Node> geometryPool = new HashSet<Node>();
    302         for( Node node : referenceWay.getNodes() ) {
    303             List<OsmPrimitive> referrers = node.getReferrers();
    304             if( node.isNew() && !node.isDeleted() && referrers.size() == 1
    305                     && referrers.get(0).equals(referenceWay) && !subjectWay.containsNode(node)
    306                     && !hasInterestingKey(node))
    307                 geometryPool.add(node);
    308         }
    309 
    310         // Find new nodes that are closest to the old ones, remove matching old ones from the pool
    311         Map<Node, Node> nodeAssoc = new HashMap<Node, Node>();
    312         for( Node n : geometryPool ) {
    313             Node nearest = findNearestNode(n, nodePool);
    314             if( nearest != null ) {
    315                 nodeAssoc.put(n, nearest);
    316                 nodePool.remove(nearest);
    317             }
    318         }
    319 
    320         // Now that we have replacement list, move all unused new nodes to nodePool (and delete them afterwards)
    321         for( Node n : geometryPool )
    322             if( nodeAssoc.containsKey(n) )
    323                 nodePool.add(n);
    324 
    325         // And prepare a list of nodes with all the replacements
    326         List<Node> geometryNodes = referenceWay.getNodes();
    327         for( int i = 0; i < geometryNodes.size(); i++ )
    328             if( nodeAssoc.containsKey(geometryNodes.get(i)) )
    329                 geometryNodes.set(i, nodeAssoc.get(geometryNodes.get(i)));
    330 
    331         // Now do the replacement
    332         commands.add(new ChangeNodesCommand(subjectWay, geometryNodes));
    333 
    334         // Move old nodes to new positions
    335         for( Node node : nodeAssoc.keySet() )
    336             commands.add(new MoveCommand(nodeAssoc.get(node), node.getCoor()));
    337 
    338         // Remove geometry way from selection
    339         getCurrentDataSet().clearSelection(referenceWay);
    340 
    341         // And delete old geometry way
    342         commands.add(new DeleteCommand(referenceWay));
    343 
    344         // Delete nodes that are not used anymore
    345         if( !nodePool.isEmpty() )
    346             commands.add(new DeleteCommand(nodePool));
    347 
    348         // Two items in undo stack: change original way and delete geometry way
    349         Main.main.undoRedo.add(new SequenceCommand(
    350                 tr("Replace geometry for way {0}", subjectWay.getDisplayName(DefaultNameFormatter.getInstance())),
    351                 commands));
    352         return true;
    353     }
    354 
    355     /**
    356      * Create a list of nodes that are not used anywhere except in the way.
    357      *
    358      * @param way
    359      * @return
    360      */
    361     protected static Collection<Node> getUnimportantNodes(Way way) {
    362         Set<Node> nodePool = new HashSet<Node>();
    363         for (Node n : way.getNodes()) {
    364             List<OsmPrimitive> referrers = n.getReferrers();
    365             if (!n.isDeleted() && referrers.size() == 1 && referrers.get(0).equals(way)
    366                     && !hasInterestingKey(n)) {
    367                 nodePool.add(n);
    368             }
    369         }
    370         return nodePool;
    371     }
    372    
    373     /**
    374      * Checks if a way has at least one important node (e.g. interesting tag,
    375      * role membership), and thus cannot be safely modified.
    376      *
    377      * @param way
    378      * @return
    379      */
    380     protected static boolean hasImportantNode(Way geometry, Way way) {
    381         for (Node n : way.getNodes()) {
    382             // if original and replacement way share a node, it's safe to replace
    383             if (geometry.containsNode(n)) {
    384                 continue;
    385             }
    386             //TODO: if way is connected to other ways, warn or disallow?
    387             for (OsmPrimitive o : n.getReferrers()) {
    388                 if (o instanceof Relation) {
    389                     return true;
    390                 }
    391             }
    392             if (hasInterestingKey(n)) {
    393                 return true;
    394             }
    395         }
    396         return false;
    397     }
    398    
    399     protected static boolean hasInterestingKey(OsmPrimitive object) {
    400         for (String key : object.getKeys().keySet()) {
    401             if (!OsmPrimitive.isUninterestingKey(key)) {
    402                 return true;
    403             }
    404         }
    405         return false;
    406     }
    407 
    408     protected static boolean isInArea(Node node, Area area) {
    409         if (node.isNewOrUndeleted() || area == null || area.contains(node.getCoor())) {
    410             return true;
    411         }
    412         return false;
    413     }
    414    
    415     protected static boolean isInArea(Way way, Area area) {
    416         if (area == null) {
    417             return true;
    418         }
    419 
    420         for (Node n : way.getNodes()) {
    421             if (!isInArea(n, area)) {
    422                 return false;
    423             }
    424         }
    425 
    426         return true;
    427     }
    428    
    429      /**
    430      * Merge tags from source to target object, showing resolution dialog if
    431      * needed.
    432      *
    433      * @param source object tags are merged from
    434      * @param target object tags are merged to
    435      * @return
    436      */
    437     protected static List<Command> getTagConflictResolutionCommands(OsmPrimitive source, OsmPrimitive target) {
    438         Collection<OsmPrimitive> primitives = Arrays.asList(source, target);
    439        
    440         Set<RelationToChildReference> relationToNodeReferences = RelationToChildReference.getRelationToChildReferences(primitives);
    441 
    442         // build the tag collection
    443         TagCollection tags = TagCollection.unionOfAllPrimitives(primitives);
    444         TagConflictResolutionUtil.combineTigerTags(tags);
    445         TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(tags, primitives);
    446         TagCollection tagsToEdit = new TagCollection(tags);
    447         TagConflictResolutionUtil.completeTagCollectionForEditing(tagsToEdit);
    448 
    449         // launch a conflict resolution dialog, if necessary
    450         CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
    451         dialog.getTagConflictResolverModel().populate(tagsToEdit, tags.getKeysWithMultipleValues());
    452         dialog.getRelationMemberConflictResolverModel().populate(relationToNodeReferences);
    453         dialog.setTargetPrimitive(target);
    454         dialog.prepareDefaultDecisions();
    455 
    456         // conflict resolution is necessary if there are conflicts in the merged tags
    457         // or if at least one of the merged nodes is referred to by a relation
    458         if (!tags.isApplicableToPrimitive() || relationToNodeReferences.size() > 1) {
    459             dialog.setVisible(true);
    460             if (dialog.isCanceled()) {
    461                 return null;
    462             }
    463         }
    464         return dialog.buildResolutionCommands();
    465     }
    466 
    467    
    468     /**
    469      * Find node from the collection which is nearest to <tt>node</tt>. Max distance is taken in consideration.
    470      * @return null if there is no such node.
    471      */
    472     protected static Node findNearestNode( Node node, Collection<Node> nodes ) {
    473         if( nodes.contains(node) )
    474             return node;
    475        
    476         Node nearest = null;
    477         // TODO: use meters instead of degrees, but do it fast
    478         double distance = Double.parseDouble(Main.pref.get("utilsplugin2.replace-geometry.max-distance", "1"));
    479         Point2D coor = node.getCoor();
    480 
    481         for( Node n : nodes ) {
    482             double d = n.getCoor().distance(coor);
    483             if( d < distance ) {
    484                 distance = d;
    485                 nearest = n;
    486             }
    487         }
    488         return nearest;
    48954    }
    49055
Note: See TracChangeset for help on using the changeset viewer.