Ticket #7278: resolve_tag_conflicts.patch

File resolve_tag_conflicts.patch, 16.2 KB (added by joshdoe, 13 years ago)

adding tag conflict resolution to replace geometry command

  • src/utilsplugin2/UtilsPlugin2.java

     
    11// License: GPL v2 or later. See LICENSE file for details.
    22package utilsplugin2;
    33
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
    46import utilsplugin2.customurl.ChooseURLAction;
    57import utilsplugin2.customurl.OpenPageAction;
    68import utilsplugin2.customurl.UtilsPluginPreferences;
    79
    810import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
    911import java.awt.event.KeyEvent;
     12import java.util.Arrays;
     13import java.util.Collection;
     14import java.util.HashSet;
     15import java.util.Set;
    1016import javax.swing.JMenu;
    1117import javax.swing.JMenuItem;
    1218import utilsplugin2.selection.*;
    1319import utilsplugin2.dumbutils.*;
    1420
    1521import org.openstreetmap.josm.Main;
     22import org.openstreetmap.josm.actions.search.PushbackTokenizer;
     23import org.openstreetmap.josm.actions.search.SearchCompiler;
     24import org.openstreetmap.josm.actions.search.SearchCompiler.*;
     25import org.openstreetmap.josm.data.osm.OsmPrimitive;
     26import org.openstreetmap.josm.data.osm.Relation;
     27import org.openstreetmap.josm.data.osm.Way;
    1628import org.openstreetmap.josm.gui.MainMenu;
    1729import org.openstreetmap.josm.gui.MapFrame;
    1830import org.openstreetmap.josm.plugins.Plugin;
     
    89101       
    90102        selectURL = MainMenu.add(toolsMenu, new ChooseURLAction());
    91103       
    92 
     104        // register search operators
     105        SearchCompiler.addMatchFactory(new UtilsUnaryMatchFactory());
    93106    }
    94107
    95108    @Override
     
    126139    public PreferenceSetting getPreferenceSetting() {
    127140        return new UtilsPluginPreferences();
    128141    }
     142   
     143    public static class UtilsUnaryMatchFactory implements UnaryMatchFactory {
     144        private static Collection<String> keywords = Arrays.asList("inside",
     145                "intersecting", "allintersecting");
     146       
     147        @Override
     148        public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) throws ParseError {
     149            if ("inside".equals(keyword))
     150                return new InsideMatch(matchOperand);
     151            else if ("intersecting".equals(keyword))
     152                return new IntersectingMatch(matchOperand, false);
     153            else if ("allintersecting".equals(keyword))
     154                return new IntersectingMatch(matchOperand, true);
     155            return null;
     156        }
    129157
     158        @Override
     159        public Collection<String> getKeywords() {
     160            return keywords;
     161        }
     162    }
     163
     164    /**
     165     * Matches all objects contained within the match expression.
     166     */
     167    public static class InsideMatch extends UnaryMatch {
     168        private Collection<OsmPrimitive> inside = null;
     169       
     170        public InsideMatch(Match match) {
     171            super(match);
     172            init();
     173        }
     174       
     175        /**
     176         * Find all objects inside areas which match the expression
     177         */
     178        private void init() {
     179            Collection<OsmPrimitive> matchedAreas = new HashSet<OsmPrimitive>();
     180
     181            // find all ways that match the expression
     182            Collection<Way> ways = Main.main.getCurrentDataSet().getWays();
     183            for (Way way : ways) {
     184                if (match.match(way))
     185                    matchedAreas.add(way);
     186            }
     187           
     188            // find all relations that match the expression
     189            Collection<Relation> rels = Main.main.getCurrentDataSet().getRelations();
     190            for (Relation rel : rels) {
     191                if (match.match(rel))
     192                    matchedAreas.add(rel);
     193            }
     194           
     195            inside = NodeWayUtils.selectAllInside(matchedAreas, Main.main.getCurrentDataSet());
     196        }
     197
     198        @Override
     199        public boolean match(OsmPrimitive osm) {
     200            return inside.contains(osm);
     201        }
     202    }
     203   
     204    public static class IntersectingMatch extends UnaryMatch {
     205        private Collection<Way> intersecting = null;
     206       
     207        public IntersectingMatch(Match match, boolean all) {
     208            super(match);
     209            init(all);
     210        }   
     211       
     212        /**
     213         * Find (all) ways intersecting ways which match the expression.
     214         */
     215        private void init(boolean all) {
     216            Collection<Way> matchedWays = new HashSet<Way>();
     217           
     218            // find all ways that match the expression
     219            Collection<Way> allWays = Main.main.getCurrentDataSet().getWays();
     220            for (Way way : allWays) {
     221                if (match.match(way))
     222                    matchedWays.add(way);
     223            }
     224           
     225            Set<Way> newWays = new HashSet<Way>();
     226            if (all)
     227                NodeWayUtils.addWaysIntersectingWaysRecursively(allWays, matchedWays, newWays);
     228            else
     229                NodeWayUtils.addWaysIntersectingWays(allWays, matchedWays, newWays);
     230            intersecting = newWays;
     231        }
     232       
     233        @Override
     234        public boolean match(OsmPrimitive osm) {
     235            return intersecting.contains(osm);
     236        }
     237    }
    130238}
  • src/utilsplugin2/selection/SelectAllInsideAction.java

     
    1414
    1515import org.openstreetmap.josm.Main;
    1616import org.openstreetmap.josm.actions.JosmAction;
    17 import org.openstreetmap.josm.data.osm.Node;
    18 import org.openstreetmap.josm.data.osm.OsmPrimitive;
    19 import org.openstreetmap.josm.data.osm.Relation;
    20 import org.openstreetmap.josm.data.osm.Way;
     17import org.openstreetmap.josm.data.osm.*;
    2118import org.openstreetmap.josm.tools.Shortcut;
    2219
    2320/**
     
    3128                KeyEvent.VK_I, Shortcut.GROUP_EDIT ,KeyEvent.ALT_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK), true);
    3229        putValue("help", ht("/Action/SelectAllInside"));
    3330    }
    34 
     31   
     32    @Override
    3533    public void actionPerformed(ActionEvent e) {
    3634        long t=System.currentTimeMillis();
    37         Set<Way> selectedWays = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Way.class);
    38         Set<Relation> selectedRels = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Relation.class);
    39 
    40         for (Relation r: selectedRels) {
    41             if (!r.isMultipolygon()) selectedRels.remove(r);
    42         }
    43 
    44         Set<Way> newWays = new HashSet<Way>();
    45         Set<Node> newNodes = new HashSet<Node>();
    46         // select ways attached to already selected ways
    47         if (!selectedWays.isEmpty()) {
    48             for (Way w: selectedWays) {
    49                 NodeWayUtils.addAllInsideWay(getCurrentDataSet(),w,newWays,newNodes);
    50             }
    51         }
    52         if (!selectedRels.isEmpty()) {
    53             for (Relation r: selectedRels) {
    54                 NodeWayUtils.addAllInsideMultipolygon(getCurrentDataSet(),r,newWays,newNodes);
    55             }
    56         }
    57         if (!newWays.isEmpty() || !newNodes.isEmpty()) {
    58             getCurrentDataSet().addSelected(newWays);
    59             getCurrentDataSet().addSelected(newNodes);
     35        Collection<OsmPrimitive> insideSelected = NodeWayUtils.selectAllInside(getCurrentDataSet().getSelected(), getCurrentDataSet());
     36       
     37        if (!insideSelected.isEmpty()) {
     38            getCurrentDataSet().addSelected(insideSelected);
    6039        } else{
    6140        JOptionPane.showMessageDialog(Main.parent,
    6241               tr("Nothing found. Please select some closed ways or multipolygons to find all primitives inside them!"),
  • src/utilsplugin2/selection/NodeWayUtils.java

     
    147147     * @param initWays ways to check intersections
    148148     * @param newWays set to place the ways we found
    149149     */
    150     static int addWaysIntersectingWays(Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays) {
     150    public static int addWaysIntersectingWays(Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays) {
    151151        int count=0;
    152152        for (Way w : initWays){
    153153            count+=addWaysIntersectingWay(allWays, w, newWays);
     
    171171        return newNodes.size()-s;
    172172    }
    173173
    174     static void addWaysIntersectingWaysRecursively
     174    public static void addWaysIntersectingWaysRecursively
    175175            (Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays)
    176176    {
    177177            Set<Way> foundWays = new HashSet<Way>();
     
    457457       // System.out.printf("Intersected intercount %d %s\n",interCount, point.toString());
    458458        if (interCount%2 == 1) return 1; else return 0;
    459459    }
     460   
     461    public static Collection<OsmPrimitive> selectAllInside(Collection<OsmPrimitive> selected, DataSet dataset) {
     462        Set<Way> selectedWays = OsmPrimitive.getFilteredSet(selected, Way.class);
     463        Set<Relation> selectedRels = OsmPrimitive.getFilteredSet(selected, Relation.class);
    460464
     465        for (Relation r: selectedRels) {
     466            if (!r.isMultipolygon()) selectedRels.remove(r);
     467        }
     468
     469        Set<Way> newWays = new HashSet<Way>();
     470        Set<Node> newNodes = new HashSet<Node>();
     471        // select ways attached to already selected ways
     472        if (!selectedWays.isEmpty()) {
     473            for (Way w: selectedWays) {
     474                addAllInsideWay(dataset,w,newWays,newNodes);
     475            }
     476        }
     477        if (!selectedRels.isEmpty()) {
     478            for (Relation r: selectedRels) {
     479                addAllInsideMultipolygon(dataset,r,newWays,newNodes);
     480            }
     481        }
     482       
     483        Set<OsmPrimitive> insideSelection = new HashSet<OsmPrimitive>();
     484        if (!newWays.isEmpty() || !newNodes.isEmpty()) {
     485            insideSelection.addAll(newWays);
     486            insideSelection.addAll(newNodes);
     487        }
     488        return insideSelection;
     489    }
     490
     491
    461492}
  • src/utilsplugin2/dumbutils/ReplaceGeometryAction.java

     
    1212import org.openstreetmap.josm.tools.Shortcut;
    1313import java.awt.event.ActionEvent;
    1414import org.openstreetmap.josm.actions.JosmAction;
    15 import org.openstreetmap.josm.data.osm.OsmPrimitive;
     15import org.openstreetmap.josm.data.osm.*;
    1616import org.openstreetmap.josm.gui.DefaultNameFormatter;
     17import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog;
     18import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil;
    1719import static org.openstreetmap.josm.tools.I18n.tr;
    1820
    1921/**
     
    6264            return;
    6365        }
    6466    }
    65    
     67
    6668    public void replaceNodeWithWay(Node node, Way way) {
    6769        if (!node.getReferrers().isEmpty()) {
    6870            JOptionPane.showMessageDialog(Main.parent, tr("Node has referrers, cannot replace with way."), TITLE, JOptionPane.INFORMATION_MESSAGE);
     
    7577            Collection<Node> nodePool = getUnimportantNodes(way);
    7678            nodeToReplace = findNearestNode(node, nodePool);
    7779        }
    78        
     80
    7981        List<Command> commands = new ArrayList<Command>();
    8082        AbstractMap<String, String> nodeTags = (AbstractMap<String, String>) node.getKeys();
    81        
     83
     84        // merge tags
     85        Collection<Command> tagResolutionCommands = getTagConflictResolutionCommands(node, way);
     86        if (tagResolutionCommands == null) {
     87            // user canceled tag merge dialog
     88            return;
     89        }
     90        commands.addAll(tagResolutionCommands);
     91
    8292        // replace sacrificial node in way with node that is being upgraded
    8393        if (nodeToReplace != null) {
    8494            List<Node> wayNodes = way.getNodes();
    8595            int idx = wayNodes.indexOf(nodeToReplace);
    8696            wayNodes.set(idx, node);
    87             if (idx == 0) {
     97            if (idx == 0 && way.isClosed()) {
    8898                // node is at start/end of way
    8999                wayNodes.set(wayNodes.size() - 1, node);
    90100            }
    91101            commands.add(new ChangeNodesCommand(way, wayNodes));
    92102            commands.add(new MoveCommand(node, nodeToReplace.getCoor()));
    93103            commands.add(new DeleteCommand(nodeToReplace));
    94            
     104
    95105            // delete tags from node
    96106            if (!nodeTags.isEmpty()) {
    97107                AbstractMap<String, String> nodeTagsToDelete = new HashMap<String, String>();
     
    105115            commands.add(new DeleteCommand(node));
    106116        }
    107117
    108         // Copy tags from node
    109         // TODO: use merge tag conflict dialog instead
    110         commands.add(new ChangePropertyCommand(Arrays.asList(way), nodeTags));
    111        
    112118        getCurrentDataSet().setSelected(way);
    113        
     119
    114120        Main.main.undoRedo.add(new SequenceCommand(
    115121                tr("Replace geometry for way {0}", way.getDisplayName(DefaultNameFormatter.getInstance())),
    116122                commands));
    117123    }
    118    
     124
    119125    public void replaceWayWithWay(List<Way> selection) {
    120126        boolean overrideNewCheck = false;
    121127        int idxNew = selection.get(0).isNew() ? 0 : 1;
     
    186192        for( Node node : nodeAssoc.keySet() )
    187193            commands.add(new MoveCommand(nodeAssoc.get(node), node.getCoor()));
    188194
    189         // Copy tags from temporary way (source etc.)
    190         for( String key : geometry.keySet() )
    191             commands.add(new ChangePropertyCommand(way, key, geometry.get(key)));
     195        // Merge tags from source to target way
     196        commands.addAll(getTagConflictResolutionCommands(geometry, way));
    192197
    193198        // Remove geometry way from selection
    194199        getCurrentDataSet().clearSelection(geometry);
     
    208213
    209214    /**
    210215     * Create a list of nodes that are not used anywhere except in the way.
     216     *
    211217     * @param way
    212      * @return 
     218     * @return
    213219     */
    214220    public Collection<Node> getUnimportantNodes(Way way) {
    215221        Set<Node> nodePool = new HashSet<Node>();
     
    223229        }
    224230        return nodePool;
    225231    }
    226    
     232
    227233    /**
     234     * Merge tags from source to target object, showing resolution dialog if
     235     * needed.
     236     *
     237     * @param source
     238     * @param target
     239     * @return
     240     */
     241    public List<Command> getTagConflictResolutionCommands(OsmPrimitive source, OsmPrimitive target) {
     242        Collection<OsmPrimitive> primitives = Arrays.asList(source, target);
     243       
     244        Set<RelationToChildReference> relationToNodeReferences = RelationToChildReference.getRelationToChildReferences(primitives);
     245
     246        // build the tag collection
     247        TagCollection tags = TagCollection.unionOfAllPrimitives(primitives);
     248        TagConflictResolutionUtil.combineTigerTags(tags);
     249        TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(tags, primitives);
     250        TagCollection tagsToEdit = new TagCollection(tags);
     251        TagConflictResolutionUtil.completeTagCollectionForEditing(tagsToEdit);
     252
     253        // launch a conflict resolution dialog, if necessary
     254        CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
     255        dialog.getTagConflictResolverModel().populate(tagsToEdit, tags.getKeysWithMultipleValues());
     256        dialog.getRelationMemberConflictResolverModel().populate(relationToNodeReferences);
     257        dialog.setTargetPrimitive(target);
     258        dialog.prepareDefaultDecisions();
     259
     260        // conflict resolution is necessary if there are conflicts in the merged tags
     261        // or if at least one of the merged nodes is referred to by a relation
     262        if (!tags.isApplicableToPrimitive() || relationToNodeReferences.size() > 1) {
     263            dialog.setVisible(true);
     264            if (dialog.isCanceled()) {
     265                return null;
     266            }
     267        }
     268        return dialog.buildResolutionCommands();
     269    }
     270
     271    /**
    228272     * Find node from the collection which is nearest to <tt>node</tt>. Max distance is taken in consideration.
    229273     * @return null if there is no such node.
    230274     */
    231275    private Node findNearestNode( Node node, Collection<Node> nodes ) {
    232276        if( nodes.contains(node) )
    233277            return node;
    234        
     278
    235279        Node nearest = null;
    236280        double distance = MAX_NODE_REPLACEMENT_DISTANCE;
    237281        Point2D coor = node.getCoor();