Ticket #23555: 23555.patch

File 23555.patch, 16.3 KB (added by taylor.smock, 2 years ago)

Add enum for different tag merge strategies. Notably only ASK is actually used right now (everything else is effectively KEEP_NON_CONFLICTING right now). See https://github.com/JOSM/conflation/pull/19 for the conflation plugin.

  • core/src/org/openstreetmap/josm/actions/MergeNodesAction.java

    Subject: [PATCH] #23555: Replace geometry update
    ---
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/core/src/org/openstreetmap/josm/actions/MergeNodesAction.java b/core/src/org/openstreetmap/josm/actions/MergeNodesAction.java
    a b  
    293293     * @since 12689
    294294     */
    295295    public static Command mergeNodes(Collection<Node> nodes, Node targetLocationNode) {
     296        return mergeNodes(nodes, targetLocationNode, CombinePrimitiveResolverDialog.Strategy.ASK);
     297    }
     298
     299    /**
     300     * Merges the nodes in {@code nodes} at the specified node's location.
     301     *
     302     * @param nodes the collection of nodes. Ignored if null.
     303     * @param targetLocationNode this node's location will be used for the targetNode.
     304     * @param strategy The strategy to use for tag conflicts
     305     * @return The command necessary to run in order to perform action, or {@code null} if there is nothing to do
     306     * @throws IllegalArgumentException if {@code layer} is null
     307     * @since xxx
     308     */
     309    public static Command mergeNodes(Collection<Node> nodes, Node targetLocationNode, CombinePrimitiveResolverDialog.Strategy strategy) {
    296310        if (nodes == null) {
    297311            return null;
    298312        }
     
    302316        if (targetNode == null) {
    303317            return null;
    304318        }
    305         return mergeNodes(nodes, targetNode, targetLocationNode);
     319        return mergeNodes(nodes, targetNode, targetLocationNode, strategy);
    306320    }
    307321
    308322    /**
     
    315329     * @throws IllegalArgumentException if layer is null
    316330     */
    317331    public static Command mergeNodes(Collection<Node> nodes, Node targetNode, Node targetLocationNode) {
     332        return mergeNodes(nodes, targetNode, targetLocationNode, CombinePrimitiveResolverDialog.Strategy.ASK);
     333    }
     334
     335    /**
     336     * Merges the nodes in <code>nodes</code> onto one of the nodes.
     337     *
     338     * @param nodes the collection of nodes. Ignored if null.
     339     * @param targetNode the target node the collection of nodes is merged to. Must not be null.
     340     * @param targetLocationNode this node's location will be used for the targetNode.
     341     * @param strategy The strategy to use when there are tag conflicts
     342     * @return The command necessary to run in order to perform action, or {@code null} if there is nothing to do
     343     * @throws IllegalArgumentException if layer is null
     344     * @since xxx
     345     */
     346    public static Command mergeNodes(Collection<Node> nodes, Node targetNode, Node targetLocationNode,
     347                                     CombinePrimitiveResolverDialog.Strategy strategy) {
    318348        CheckParameterUtil.ensureParameterNotNull(targetNode, "targetNode");
    319349        if (nodes == null) {
    320350            return null;
     
    346376                    cmds.add(new ChangeCommand(targetNode, newTargetNode));
    347377                }
    348378            }
    349             cmds.addAll(CombinePrimitiveResolverDialog.launchIfNecessary(nodeTags, nodes, Collections.singleton(targetNode)));
     379            cmds.addAll(CombinePrimitiveResolverDialog.launchIfNecessary(nodeTags, nodes, Collections.singleton(targetNode), strategy));
    350380            if (!nodesToDelete.isEmpty()) {
    351381                cmds.add(new DeleteCommand(nodesToDelete));
    352382            }
  • core/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/core/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java b/core/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java
    a b  
    8484 */
    8585public class CombinePrimitiveResolverDialog extends JDialog {
    8686
     87    /**
     88     * The strategy to use when combining tags
     89     */
     90    public enum Strategy {
     91        /** Keep the tags from the target when there are conflicts */
     92        KEEP_TARGET,
     93        /** Keep the tags from the source when there are conflicts */
     94        KEEP_SOURCE,
     95        /** Keep the tags from both the source and the target when there are conflicts */
     96        KEEP_BOTH,
     97        /** Keep all non-conflicting tags */
     98        KEEP_NON_CONFLICTING,
     99        /** Ask if there are any conflicts, including when one objects has tags the other object does not */
     100        ASK
     101    }
     102
    87103    private AutoAdjustingSplitPane spTagConflictTypes;
    88104    private final TagConflictResolverModel modelTagConflictResolver;
    89105    protected TagConflictResolver pnlTagConflictResolver;
     
    486502            final TagCollection tagsOfPrimitives,
    487503            final Collection<? extends OsmPrimitive> primitives,
    488504            final Collection<? extends OsmPrimitive> targetPrimitives) throws UserCancelException {
     505        return launchIfNecessary(tagsOfPrimitives, primitives, targetPrimitives, Strategy.ASK);
     506    }
    489507
     508    /**
     509     * Replies the list of {@link Command commands} needed to resolve specified conflicts,
     510     * by displaying if necessary a {@link CombinePrimitiveResolverDialog} to the user.
     511     * This dialog will allow the user to choose conflict resolution actions.
     512     * <p>
     513     * Non-expert users are informed first of the meaning of these operations, allowing them to cancel.
     514     *
     515     * @param tagsOfPrimitives The tag collection of the primitives to be combined.
     516     *                         Should generally be equal to {@code TagCollection.unionOfAllPrimitives(primitives)}
     517     * @param primitives The primitives to be combined
     518     * @param targetPrimitives The primitives the collection of primitives are merged or combined to.
     519     * @pararm strategy The strategy to use when merging primitives
     520     * @return The list of {@link Command commands} needed to apply resolution actions.
     521     * @throws UserCancelException If the user cancelled a dialog.
     522     * @since xxx
     523     */
     524    public static List<Command> launchIfNecessary(
     525            final TagCollection tagsOfPrimitives,
     526            final Collection<? extends OsmPrimitive> primitives,
     527            final Collection<? extends OsmPrimitive> targetPrimitives,
     528            Strategy strategy) throws UserCancelException {
    490529        CheckParameterUtil.ensureParameterNotNull(tagsOfPrimitives, "tagsOfPrimitives");
    491530        CheckParameterUtil.ensureParameterNotNull(primitives, "primitives");
    492531        CheckParameterUtil.ensureParameterNotNull(targetPrimitives, "targetPrimitives");
     
    518557
    519558        tagModel.populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues(), false);
    520559        relModel.populate(parentRelations, primitives, false);
    521         if (Config.getPref().getBoolean("combine-conflict-precise", true)) {
     560        if (Strategy.ASK == strategy && Config.getPref().getBoolean("combine-conflict-precise", true)) {
    522561            tagModel.prepareDefaultTagDecisions(getResolvableKeys(tagsOfPrimitives.getKeys(), primitives));
    523562        } else {
    524563            tagModel.prepareDefaultTagDecisions(false);
  • plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/ReplaceGeometryUtils.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/ReplaceGeometryUtils.java b/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/replacegeometry/ReplaceGeometryUtils.java
    a b  
    8181     * @return (in case of success) a command to update the geometry of fist object and remove the other
    8282     */
    8383    public static ReplaceGeometryCommand buildReplaceCommand(OsmPrimitive subjectObject, OsmPrimitive referenceSubject) {
     84        return buildReplaceCommand(subjectObject, referenceSubject, CombinePrimitiveResolverDialog.Strategy.ASK);
     85    }
     86
     87    /**
     88     * Replace subjectObject geometry with referenceObject geometry and merge tags
     89     * and relation memberships.
     90     * @param subjectObject object to modify
     91     * @param referenceSubject object that gives new geometry and is removed
     92     * @param strategy The strategy to use when resolving conflicts
     93     * @return (in case of success) a command to update the geometry of fist object and remove the other
     94     */
     95    public static ReplaceGeometryCommand buildReplaceCommand(OsmPrimitive subjectObject, OsmPrimitive referenceSubject,
     96                                                             CombinePrimitiveResolverDialog.Strategy strategy) {
    8497        if (subjectObject instanceof Node && referenceSubject instanceof Node) {
    85             return buildReplaceNodeCommand((Node) subjectObject, (Node) referenceSubject);
     98            return buildReplaceNodeCommand((Node) subjectObject, (Node) referenceSubject, strategy);
    8699        } else if (subjectObject instanceof Way && referenceSubject instanceof Way) {
    87             return buildReplaceWayCommand((Way) subjectObject, (Way) referenceSubject);
     100            return buildReplaceWayCommand((Way) subjectObject, (Way) referenceSubject, strategy);
    88101        } else if (subjectObject instanceof Node) {
    89             return buildUpgradeNodeCommand((Node) subjectObject, referenceSubject);
     102            return buildUpgradeNodeCommand((Node) subjectObject, referenceSubject, strategy);
    90103        } else if (referenceSubject instanceof Node) {
    91104            // TODO: fix this illogical reversal?
    92             return buildUpgradeNodeCommand((Node) referenceSubject, subjectObject);
     105            return buildUpgradeNodeCommand((Node) referenceSubject, subjectObject, strategy);
    93106        } else {
    94107            throw new IllegalArgumentException(
    95108                    tr("This tool can only replace a node, upgrade a node to a way or a multipolygon, or replace a way with a way."));
     
    120133     * @return command to replace node or null if user cancelled
    121134     */
    122135    public static ReplaceGeometryCommand buildReplaceNodeCommand(Node subjectNode, Node referenceNode) {
     136        return buildUpgradeNodeCommand(subjectNode, referenceNode, CombinePrimitiveResolverDialog.Strategy.ASK);
     137    }
     138
     139    /**
     140     * Replace a node with another node (similar to MergeNodesAction)
     141     * @param subjectNode node to be replaced
     142     * @param referenceNode node with greater spatial quality
     143     * @param strategy The strategy to use when resolving conflicts
     144     * @return command to replace node or null if user cancelled
     145     */
     146    public static ReplaceGeometryCommand buildReplaceNodeCommand(Node subjectNode, Node referenceNode,
     147                                                                 CombinePrimitiveResolverDialog.Strategy strategy) {
    123148        if (!subjectNode.getParentWays().isEmpty()) {
    124149            throw new ReplaceGeometryException(tr("Node belongs to way(s), cannot replace."));
    125150        }
    126151        // FIXME: handle different layers
    127152        List<Command> commands = new ArrayList<>();
    128153        Command c = MergeNodesAction.mergeNodes(
    129             Arrays.asList(subjectNode, referenceNode), referenceNode);
     154            Arrays.asList(subjectNode, referenceNode), referenceNode, strategy);
    130155        if (c == null) {
    131156            // User cancelled
    132157            return null;
     
    146171     * @return command to replace
    147172     */
    148173    public static ReplaceGeometryCommand buildUpgradeNodeCommand(Node subjectNode, OsmPrimitive referenceObject) {
     174        return buildUpgradeNodeCommand(subjectNode, referenceObject, CombinePrimitiveResolverDialog.Strategy.ASK);
     175    }
     176
     177    /**
     178     * Upgrade a node to a way or multipolygon
     179     *
     180     * @param subjectNode node to be replaced
     181     * @param referenceObject object with greater spatial quality
     182     * @param strategy The strategy to use when resolving tag conflicts
     183     * @return command to replace
     184     */
     185    public static ReplaceGeometryCommand buildUpgradeNodeCommand(Node subjectNode, OsmPrimitive referenceObject,
     186                                                                 CombinePrimitiveResolverDialog.Strategy strategy) {
    149187        if (!subjectNode.getParentWays().isEmpty()) {
    150188            throw new ReplaceGeometryException(tr("Node belongs to way(s), cannot replace."));
    151189        }
    152190
    153         if (referenceObject instanceof Relation && !((Relation) referenceObject).isMultipolygon()) {
     191        if (referenceObject instanceof Relation && !referenceObject.isMultipolygon()) {
    154192            throw new ReplaceGeometryException(tr("Relation is not a multipolygon, cannot be used as a replacement."));
    155193        }
    156194
     
    180218            }
    181219        }
    182220
    183         List<Command> commands = new ArrayList<>();
     221        List<Command> commands;
    184222        AbstractMap<String, String> nodeTags = subjectNode.getKeys();
    185223
    186224        // merge tags
    187225        try {
    188             commands.addAll(getTagConflictResolutionCommands(subjectNode, referenceObject));
     226            commands = new ArrayList<>(getTagConflictResolutionCommands(subjectNode, referenceObject, strategy));
    189227        } catch (UserCancelException e) {
    190228            // user cancelled tag merge dialog
    191229            return null;
     
    266304     * @return Command to replace geometry or null if user cancelled
    267305     */
    268306    public static ReplaceGeometryCommand buildReplaceWayCommand(Way subjectWay, Way referenceWay) {
     307        return buildReplaceWayCommand(subjectWay, referenceWay, CombinePrimitiveResolverDialog.Strategy.ASK);
     308    }
    269309
     310    /**
     311     * Replace geometry of subjectWay by that of referenceWay. Tries to keep the history of nodes.
     312     * @param subjectWay way to modify
     313     * @param referenceWay way to remove
     314     * @param strategy The strategy to use when resolving conflicts
     315     * @return Command to replace geometry or null if user cancelled
     316     */
     317    public static ReplaceGeometryCommand buildReplaceWayCommand(Way subjectWay, Way referenceWay,
     318                                                                CombinePrimitiveResolverDialog.Strategy strategy) {
    270319        Area a = MainApplication.getLayerManager().getEditDataSet().getDataSourceArea();
    271320        if (!isInArea(subjectWay, a) || !isInArea(referenceWay, a)) {
    272321            throw new ReplaceGeometryException(tr("The ways must be entirely within the downloaded area."));
     
    281330
    282331        // merge tags
    283332        try {
    284             commands.addAll(getTagConflictResolutionCommands(referenceWay, subjectWay));
     333            commands.addAll(getTagConflictResolutionCommands(referenceWay, subjectWay, strategy));
    285334        } catch (UserCancelException e) {
    286335            // user cancelled tag merge dialog
    287336            Logging.trace(e);
     
    472521     *
    473522     * @param source object tags are merged from
    474523     * @param target object tags are merged to
     524     * @param strategy The strategy to use when resolving conflicts
    475525     * @return The list of {@link Command commands} needed to apply resolution actions.
    476526     * @throws UserCancelException If the user cancelled a dialog.
    477527     */
    478     static List<Command> getTagConflictResolutionCommands(OsmPrimitive source, OsmPrimitive target) throws UserCancelException {
     528    static List<Command> getTagConflictResolutionCommands(OsmPrimitive source, OsmPrimitive target,
     529                                                          CombinePrimitiveResolverDialog.Strategy strategy) throws UserCancelException {
    479530        Collection<OsmPrimitive> primitives = Arrays.asList(source, target);
    480531        // launch a conflict resolution dialog, if necessary
    481532        return CombinePrimitiveResolverDialog.launchIfNecessary(
    482                 TagCollection.unionOfAllPrimitives(primitives), primitives, Collections.singleton(target));
     533                TagCollection.unionOfAllPrimitives(primitives), primitives, Collections.singleton(target), strategy);
    483534    }
    484535
    485536    /**