Ticket #14374: applyAutomaticTagConflictResolution-updated.diff

File applyAutomaticTagConflictResolution-updated.diff, 21.1 KB (added by Tyndare, 9 years ago)

PATCH (updated)

  • core/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java

    cd josm && svn diff core plugins
     
    475475        CheckParameterUtil.ensureParameterNotNull(targetPrimitives, "targetPrimitives");
    476476
    477477        final TagCollection completeWayTags = new TagCollection(tagsOfPrimitives);
    478         TagConflictResolutionUtil.combineTigerTags(completeWayTags);
     478        TagConflictResolutionUtil.applyAutomaticTagConflictResolution(completeWayTags);
    479479        TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(completeWayTags, primitives);
    480480        final TagCollection tagsToEdit = new TagCollection(completeWayTags);
    481481        TagConflictResolutionUtil.completeTagCollectionForEditing(tagsToEdit);
  • core/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolutionUtil.java

     
    22package org.openstreetmap.josm.gui.conflict.tags;
    33
    44import java.util.ArrayList;
     5import java.util.Arrays;
    56import java.util.Collection;
     7import java.util.HashMap;
     8import java.util.LinkedHashSet;
     9import java.util.List;
     10import java.util.Set;
     11import java.util.TreeSet;
     12import java.util.regex.Pattern;
     13import java.util.stream.Collectors;
    614
     15import org.openstreetmap.josm.Main;
     16import org.openstreetmap.josm.data.Preferences.pref;
    717import org.openstreetmap.josm.data.osm.OsmPrimitive;
    818import org.openstreetmap.josm.data.osm.Tag;
    919import org.openstreetmap.josm.data.osm.TagCollection;
    10 import org.openstreetmap.josm.data.osm.TigerUtils;
     20import org.openstreetmap.josm.tools.Pair;
    1121
    1222/**
    1323 * Collection of utility methods for tag conflict resolution
     
    5969        }
    6070    }
    6171
    62     /**
    63      * Combines tags from TIGER data
    64      *
    65      * @param tc the tag collection
    66      */
    67     public static void combineTigerTags(TagCollection tc) {
    68         for (String key: tc.getKeys()) {
    69             if (TigerUtils.isTigerTag(key)) {
    70                 tc.setUniqueForKey(key, TigerUtils.combineTags(tc.getValues(key)));
    71             }
    72         }
    73     }
    7472
    7573    /**
    7674     * Completes tags in the tag collection <code>tc</code> with the empty value
     
    8886            tc.add(new Tag(key, ""));
    8987        }
    9088    }
     89
     90
     91    /**
     92     * Automatically resolve some tag conflicts.
     93     * The list of automatic resolution is taken from the preferences.
     94     * @param tc the tag collection
     95     */
     96    public static void applyAutomaticTagConflictResolution(TagCollection tc) {
     97        applyAutomaticTagConflictResolution(tc, getAutomaticTagConflictResolvers());
     98    }
     99
     100
     101    /**
     102     * Default preferences for the list of AutomaticCombine tag conflict resolvers.
     103     */
     104    public static final Collection<AutomaticCombine> defaultAutomaticTagConflictCombines = Arrays.asList(
     105            new AutomaticCombine("tiger:tlid", "US TIGER tlid", false, ":", "Integer"),
     106            new AutomaticCombine("tiger:(?!tlid$).*", "US TIGER not tlid", true, ":", "String")
     107    );
     108
     109
     110    /**
     111     * Default preferences for the list of AutomaticChoice tag conflict resolvers.
     112     */
     113    public static final Collection<AutomaticChoice> defaultAutomaticTagConflictChoices = Arrays.asList(
     114            /* "source" "FR:cadastre"
     115             * List of choices for the "source" tag of data exported from the French cadastre,
     116             * which ends by the exported year generating many conflicts.
     117             * The generated score begins with the year number to select the most recent one.
     118             */
     119            new AutomaticChoice("source", "FR:cadastre", "FR cadastre source, manual value", true,
     120                    "cadastre", "0"),
     121            new AutomaticChoice("source", "FR:cadastre", "FR cadastre source, initial format", true,
     122                    "extraction vectorielle v1 cadastre-dgi-fr source : Direction G[eé]n[eé]rale des Imp[ôo]ts"
     123                    + " - Cadas\\. Mise [àa] jour : ([0-9]{4})",
     124                    "$1 1"),
     125            new AutomaticChoice("source", "FR:cadastre", "FR cadastre source, old format", true,
     126                    "cadastre-dgi-fr source : Direction G[eé]n[eé]rale des Imp[ôo]ts - Cadastre\\. Mise [àa] jour : ([0-9]{4})",
     127                    "$1 2"),
     128            new AutomaticChoice("source", "FR:cadastre", "FR cadastre source, last format", true,
     129                    "cadastre-dgi-fr source : Direction G[eé]n[ée]rale des Finances Publiques - Cadastre\\. Mise [aà] jour : ([0-9]{4})",
     130                    "$1 3")
     131    );
     132
     133
     134    /**
     135     * Get the AutomaticTagConflictResolvers configured in the Preferences or the default ones.
     136     * @return the configured AutomaticTagConflictResolvers.
     137     */
     138    public static Collection<AutomaticTagConflictResolver> getAutomaticTagConflictResolvers() {
     139        if (automaticTagConflictResolvers == null) {
     140            Collection<AutomaticCombine> automaticTagConflictCombines =
     141                    Main.pref.getListOfStructs(
     142                            "automatic-tag-conflict-resolution.combine",
     143                            defaultAutomaticTagConflictCombines, AutomaticCombine.class);
     144            Collection<AutomaticChoiceGroup> automaticTagConflictChoiceGroups =
     145                    AutomaticChoiceGroup.groupChoices(Main.pref.getListOfStructs(
     146                            "automatic-tag-conflict-resolution.choice",
     147                            defaultAutomaticTagConflictChoices, AutomaticChoice.class));
     148            // Use a tmp variable to fully construct the collection before setting
     149            // the volatile variable automaticTagConflictResolvers.
     150            ArrayList<AutomaticTagConflictResolver> tmp = new ArrayList<>();
     151            tmp.addAll(automaticTagConflictCombines);
     152            tmp.addAll(automaticTagConflictChoiceGroups);
     153            automaticTagConflictResolvers = tmp;
     154        }
     155        return automaticTagConflictResolvers;
     156    }
     157
     158    private static volatile Collection<AutomaticTagConflictResolver> automaticTagConflictResolvers;
     159
     160
     161    /**
     162     * An automatic tag conflict resolver interface.
     163     */
     164    interface AutomaticTagConflictResolver {
     165        /**
     166         * Check if this resolution apply to the given Tag key.
     167         * @param key The Tag key to match.
     168         * @return true if this automatic resolution apply to the given Tag key.
     169         */
     170        boolean matchesKey(String key);
     171
     172        /**
     173         * Try to resolve a conflict between a set of values for a Tag
     174         * @param values the set of conflicting values for the Tag.
     175         * @return the resolved value or null if resolution was not possible.
     176         */
     177        String resolve(Set<String> values);
     178    }
     179
     180
     181    /**
     182     * Automatically resolve some given conflicts using the given resolvers.
     183     * @param tc the tag collection.
     184     * @param resolvers the list of automatic tag conflict resolvers to apply.
     185     */
     186    public static void applyAutomaticTagConflictResolution(TagCollection tc,
     187            Collection<AutomaticTagConflictResolver> resolvers) {
     188        for (String key: tc.getKeysWithMultipleValues()) {
     189            for (AutomaticTagConflictResolver resolver: resolvers) {
     190                try {
     191                    if (resolver.matchesKey(key)) {
     192                        String result = resolver.resolve(tc.getValues(key));
     193                        if (result != null) {
     194                            tc.setUniqueForKey(key, result);
     195                            break;
     196                        }
     197                    }
     198                } catch (RuntimeException e) {
     199                    // Can happen for instance if a particular resolver has an invalid regular
     200                    // expression pattern (java.util.regex.PatternSyntaxException)
     201                    // but it should not stop the other automatic tag conflict resolution.
     202                    Main.error(e);
     203                }
     204            }
     205        }
     206    }
     207
     208
     209    /**
     210     * Preference for automatic tag-conflict resolver by combining the tag values
     211     * using a separator.
     212     */
     213    public static class AutomaticCombine implements AutomaticTagConflictResolver {
     214
     215        /** The Tag key to match */
     216        @pref public String key;
     217
     218        /** A free description */
     219        @pref public String description = "";
     220
     221        /** If regular expression must be used to match the Tag key or the value. */
     222        @pref public boolean isRegex = false;
     223
     224        /** The separator to use to combine the values. */
     225        @pref public String separator = ";";
     226
     227        /** If the combined values must be sorted.
     228         * Possible values:
     229         * <ul>
     230         * <li> Integer - Sort using Integer natural order.</li>
     231         * <li> String - Sort using String natural order.</li>
     232         * <li> * - No ordering.</li>
     233         * </ul>
     234         */
     235        @pref public String sort;
     236
     237        /** Default constructor needed for instantiation from Preferences */
     238        public AutomaticCombine() {}
     239
     240        /** Instantiate an automatic tag-conflict resolver which combining the values using a separator.
     241         * @param key The Tag key to match.
     242         * @param description A free description.
     243         * @param isRegex If regular expression must be used to match the Tag key or the value.
     244         * @param separator The separator to use to combine the values.
     245         * @param sort If the combined values must be sorted.
     246         */
     247        public AutomaticCombine(String key, String description, boolean isRegex, String separator, String sort) {
     248            this.key = key;
     249            this.description = description;
     250            this.isRegex = isRegex;
     251            this.separator = separator;
     252            this.sort = sort;
     253        }
     254
     255        @Override
     256        public boolean matchesKey(String k) {
     257            if (isRegex) {
     258                return Pattern.matches(this.key, k);
     259            } else {
     260                return this.key.equals(k);
     261            }
     262        }
     263
     264        Set<String> instantiateSortedSet() {
     265            if ("String".equals(sort)) {
     266                return new TreeSet<>();
     267            } else if ("Integer".equals(sort)) {
     268                return new TreeSet<>((String v1, String v2) -> Long.valueOf(v1).compareTo(Long.valueOf(v2)));
     269            } else {
     270                return new LinkedHashSet<>();
     271            }
     272        }
     273
     274        @Override
     275        public String resolve(Set<String> values) {
     276            Set<String> results = instantiateSortedSet();
     277            for (String value: values) {
     278                for (String part: value.split(Pattern.quote(separator))) {
     279                    results.add(part);
     280                }
     281            }
     282            return String.join(separator, results);
     283        }
     284
     285        @Override
     286        public String toString() {
     287            return AutomaticCombine.class.getSimpleName()
     288                    + "(key='" + key + "', description='" + description + "', isRegex="
     289                    + isRegex + ", separator='" + separator + "', sort='" + sort + "')";
     290        }
     291    }
     292
     293    /**
     294     * Preference for a particular choice from a group for automatic tag conflict resolution.
     295     * AutomaticChoice are grouped into {@link AutomaticChoiceGroup}.
     296     */
     297    public static class AutomaticChoice {
     298
     299        /** The Tag key to match. */
     300        @pref public String key;
     301
     302        /** The name of the {link AutomaticChoice group} this choice belongs to. */
     303        @pref public String group;
     304
     305        /** A free description. */
     306        @pref public String description = "";
     307
     308        /** If regular expression must be used to match the Tag key or the value. */
     309        @pref public boolean isRegex = false;
     310
     311        /** The Tag value to match. */
     312        @pref public String value;
     313
     314        /**
     315         * The score to give to this choice in order to choose the best value
     316         * Natural String ordering is used to identify the best score.
     317         */
     318        @pref public String score;
     319
     320        /** Default constructor needed for instantiation from Preferences */
     321        public AutomaticChoice() {}
     322
     323        /**
     324         * Instantiate a particular choice from a group for automatic tag conflict resolution.
     325         * @param key The Tag key to match.
     326         * @param group The name of the {link AutomaticChoice group} this choice belongs to.
     327         * @param description A free description.
     328         * @param isRegex If regular expression must be used to match the Tag key or the value.
     329         * @param value The Tag value to match.
     330         * @param score The score to give to this choice in order to choose the best value.
     331         */
     332        public AutomaticChoice(String key, String group, String description, boolean isRegex, String value, String score) {
     333            this.key = key;
     334            this.group = group;
     335            this.description = description;
     336            this.isRegex = isRegex;
     337            this.value = value;
     338            this.score = score;
     339        }
     340
     341        /**
     342         * Check if this choice match the given Tag value.
     343         * @param v the Tag value to match.
     344         * @return true if this choice correspond to the given tag value.
     345         */
     346        public boolean matchesValue(String v) {
     347            if (isRegex) {
     348                return Pattern.matches(this.value, v);
     349            } else {
     350                return this.value.equals(v);
     351            }
     352        }
     353
     354        /**
     355         * Return the score associated to this choice for the given Tag value.
     356         * For the result to be valid the given tag value must {@link #matchesValue(String) match} this choice.
     357         * @param v the Tag value of which to get the score.
     358         * @return the score associated to the given Tag value.
     359         */
     360        public String computeScoreFromValue(String v) {
     361            if (isRegex) {
     362                return v.replaceAll("^" + this.value + "$", this.score);
     363            } else {
     364                return this.score;
     365            }
     366        }
     367
     368        @Override
     369        public String toString() {
     370            return AutomaticChoice.class.getSimpleName()
     371                    + "(key='" + key + "', group='" + group + "', description='" + description
     372                    + "', isRegex=" + isRegex + ", value='" + value + "', score='" + score + "')";
     373        }
     374    }
     375
     376
     377    /**
     378     * Preference for an automatic tag conflict resolver which choose from
     379     * a group of possible {@link AutomaticChoice choice} values.
     380     */
     381    public static class AutomaticChoiceGroup implements AutomaticTagConflictResolver {
     382
     383        /** The Tag key to match. */
     384        @pref public String key;
     385
     386        /** The name of the group. */
     387        public String group;
     388
     389        /** If regular expression must be used to match the Tag key. */
     390        @pref public boolean isRegex = false;
     391
     392        /** The list of choice to choose from. */
     393        public List<AutomaticChoice> choices;
     394
     395        /** Instantiate an automatic tag conflict resolver which choose from
     396         * a given list of {@link AutomaticChoice choice} values.
     397         *
     398         * @param key The Tag key to match.
     399         * @param group The name of the group.
     400         * @param isRegex If regular expression must be used to match the Tag key.
     401         * @param choices The list of choice to choose from.
     402         */
     403        public AutomaticChoiceGroup(String key, String group, boolean isRegex, List<AutomaticChoice> choices) {
     404            this.key = key;
     405            this.group = group;
     406            this.isRegex = isRegex;
     407            this.choices = choices;
     408        }
     409
     410        /**
     411         * Group a given list of {@link AutomaticChoice} by the Tag key and the choice group name.
     412         * @param choices the list of {@link AutomaticChoice choices} to group.
     413         * @return the resulting list of group.
     414         */
     415        public static Collection<AutomaticChoiceGroup> groupChoices(Collection<AutomaticChoice> choices) {
     416            HashMap<Pair<String, String>, AutomaticChoiceGroup> results = new HashMap<>();
     417            for (AutomaticChoice choice: choices) {
     418                Pair<String, String> id = new Pair<>(choice.key, choice.group);
     419                AutomaticChoiceGroup group = results.get(id);
     420                if (group == null) {
     421                    boolean isRegex = choice.isRegex && !Pattern.quote(choice.key).equals(choice.key);
     422                    group = new AutomaticChoiceGroup(choice.key, choice.group, isRegex, new ArrayList<>());
     423                    results.put(id, group);
     424                }
     425                group.choices.add(choice);
     426            }
     427            return results.values();
     428        }
     429
     430        @Override
     431        public boolean matchesKey(String k) {
     432            if (isRegex) {
     433                return Pattern.matches(this.key, k);
     434            } else {
     435                return this.key.equals(k);
     436            }
     437        }
     438
     439        @Override
     440        public String resolve(Set<String> values) {
     441            String bestScore = "";
     442            String bestValue = "";
     443            for (String value: values) {
     444                String score = null;
     445                for (AutomaticChoice choice: choices) {
     446                    if (choice.matchesValue(value)) {
     447                        score = choice.computeScoreFromValue(value);
     448                    }
     449                }
     450                if (score == null) {
     451                    // This value is not matched in this group
     452                    // so we can not choose from this group for this key.
     453                    return null;
     454                }
     455                if (score.compareTo(bestScore) >= 0) {
     456                    bestScore = score;
     457                    bestValue = value;
     458                }
     459            }
     460            return bestValue;
     461        }
     462
     463        @Override
     464        public String toString() {
     465            Collection<String> stringChoices = choices.stream().map(AutomaticChoice::toString).collect(Collectors.toCollection(ArrayList::new));
     466            return AutomaticChoiceGroup.class.getSimpleName()
     467                    + "(key='" + key + "', group='" + group +
     468                    "', isRegex=" + isRegex + ", choices=(\n  "
     469                    + String.join(",\n  ", stringChoices) + "))";
     470        }
     471
     472    }
     473
    91474}
  • plugins/merge-overlap/src/mergeoverlap/MergeOverlapAction.java

     
    11package mergeoverlap;
    22
    3 import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags;
     3import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.applyAutomaticTagConflictResolution;
    44import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing;
    55import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing;
    66import static org.openstreetmap.josm.tools.I18n.tr;
     
    543543        modifiedTargetWay.setNodes(path);
    544544
    545545        TagCollection completeWayTags = new TagCollection(wayTags);
    546         combineTigerTags(completeWayTags);
     546        applyAutomaticTagConflictResolution(completeWayTags);
    547547        normalizeTagCollectionBeforeEditing(completeWayTags, ways);
    548548        TagCollection tagsToEdit = new TagCollection(completeWayTags);
    549549        completeTagCollectionForEditing(tagsToEdit);
  • plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/datasets/WayCombiner.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.plugins.opendata.core.datasets;
    33
    4 import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags;
     4import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.applyAutomaticTagConflictResolution;
    55import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing;
    66import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing;
    77
     
    125125        modifiedTargetWay.setNodes(path);
    126126
    127127        TagCollection completeWayTags = new TagCollection(wayTags);
    128         combineTigerTags(completeWayTags);
     128        applyAutomaticTagConflictResolution(completeWayTags);
    129129        normalizeTagCollectionBeforeEditing(completeWayTags, ways);
    130130        TagCollection tagsToEdit = new TagCollection(completeWayTags);
    131131        completeTagCollectionForEditing(tagsToEdit);