Ticket #14374: applyAutomaticTagConflictResolution.diff

File applyAutomaticTagConflictResolution.diff, 14.6 KB (added by Tyndare, 8 years ago)

PATCH

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

     
    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);
  • 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 configured tag conflicts.
     93     * @param tc the tag collection
     94     */
     95    public static void applyAutomaticTagConflictResolution(TagCollection tc) {
     96        applyAutomaticTagConflictResolution(tc, getAutomaticTagConflictCombines(), getAutomaticTagConflictChoiceGroups());
     97    }
     98
     99    public static Collection<AutomaticCombine> defaultAutomaticTagConflictCombines = Arrays.asList(
     100            new AutomaticCombine("tiger:tlid", "US TIGER tlid", false, ":", "Integer"),
     101            new AutomaticCombine("tiger:(?!tlid$).*", "US TIGER not tlid", true, ":", "String")
     102    );
     103
     104    public static Collection<AutomaticChoice> defaultAutomaticTagConflictChoices = Arrays.asList(
     105            // Choice for "source" tag of data exported from the French cadastre,
     106            // which ends by the exported year generating many conflicts
     107            // the generated score begins with the year number to select the most recent one.
     108            new AutomaticChoice("source", "FR cadastre source, manual value", true, "FR:cadastre:source",
     109                    "cadastre", "0"),
     110            new AutomaticChoice("source", "FR cadastre source, initial format", true, "FR:cadastre:source",
     111                    "extraction vectorielle v1 cadastre-dgi-fr source : Direction G[eé]n[eé]rale des Imp[ôo]ts - Cadas\\. Mise [àa] jour : ([0-9]{4})",
     112                    "$1 1"),
     113            new AutomaticChoice("source", "FR cadastre source, old format", true, "FR:cadastre:source",
     114                    "cadastre-dgi-fr source : Direction G[eé]n[eé]rale des Imp[ôo]ts - Cadastre\\. Mise [àa] jour : ([0-9]{4})",
     115                    "$1 2"),
     116            new AutomaticChoice("source", "FR cadastre source, last format", true, "FR:cadastre:source",
     117                    "cadastre-dgi-fr source : Direction G[eé]n[ée]rale des Finances Publiques - Cadastre\\. Mise [aà] jour : ([0-9]{4})",
     118                    "$1 3")
     119    );
     120
     121
     122    private static volatile Collection<AutomaticChoiceGroup> automaticTagConflictChoiceGroups;
     123    public static Collection<AutomaticChoiceGroup> getAutomaticTagConflictChoiceGroups() {
     124        if (automaticTagConflictChoiceGroups == null) {
     125            automaticTagConflictChoiceGroups = AutomaticChoiceGroup.groupChoices(Main.pref.getListOfStructs(
     126                            "automatic-tag-conflict-resolution.choice",
     127                            defaultAutomaticTagConflictChoices, AutomaticChoice.class));
     128        }
     129        return automaticTagConflictChoiceGroups;
     130    }
     131
     132
     133    private static volatile Collection<AutomaticCombine> automaticTagConflictCombines;
     134    public static Collection<AutomaticCombine> getAutomaticTagConflictCombines() {
     135        if (automaticTagConflictCombines == null) {
     136            automaticTagConflictCombines = Main.pref.getListOfStructs(
     137                            "automatic-tag-conflict-resolution.combine",
     138                            defaultAutomaticTagConflictCombines, AutomaticCombine.class);
     139        }
     140        return automaticTagConflictCombines;
     141    }
     142
     143
     144    /**
     145     * Automatically resolve some given conflicts.
     146     * @param tc the tag collection.
     147     * @param combines automatic tag combination to apply.
     148     * @param choiceGroups automatic tag choice to apply.
     149     */
     150    public static void applyAutomaticTagConflictResolution(TagCollection tc, Collection<AutomaticCombine> combines, Collection<AutomaticChoiceGroup> choiceGroups) {
     151        keyLoop:
     152        for(String key: tc.getKeysWithMultipleValues()) {
     153            for(AutomaticCombine combine: combines) {
     154                if (combine.matchesKey(key)) {
     155                    tc.setUniqueForKey(key, combine.resolve(tc.getValues(key)));
     156                    continue keyLoop;
     157                }
     158            }
     159            for(AutomaticChoiceGroup choiceGroup: choiceGroups) {
     160                if (choiceGroup.matchesKey(key)) {
     161                    String result = choiceGroup.resolve(tc.getValues(key));
     162                    if (result != null) {
     163                        tc.setUniqueForKey(key, result);
     164                        continue keyLoop;
     165                    }
     166                }
     167            }
     168        }
     169    }
     170
     171
     172    /**
     173     * Preference for automatic tag-conflict resolution by combining the values
     174     * using a separator.
     175     */
     176    public static class AutomaticCombine {
     177
     178        /** The Tag key to match */
     179        @pref public String key;
     180
     181        /** A free description */
     182        @pref public String description;
     183
     184        /** If regular expression must be used to match the Tag key or the value */
     185        @pref public boolean isRegex = false;
     186
     187        /** The separator to use to combine the values */
     188        @pref public String separator = ";";
     189
     190        /** If the combined values must be sorted, possible values are
     191         *  <li>
     192         *  <ul>String - using String natural ordering
     193         *  <ul>Integer- using Integer natural ordering
     194         *  <ul> - whatever else value will keep the original order
     195         *  </li>
     196         */
     197        @pref public String sort;
     198
     199        public AutomaticCombine() {}
     200
     201        public AutomaticCombine(String key, String description, boolean isRegex, String separator, String sort) {
     202            this.key =  key;
     203            this.description = description;
     204            this.isRegex = isRegex;
     205            this.separator = separator;
     206            this.sort = sort;
     207        }
     208
     209        /** Test if this case apply to the given Tag key */
     210        public boolean matchesKey(String k) {
     211            if (isRegex) {
     212                return Pattern.matches(this.key, k);
     213            } else {
     214                return this.key.equals(k);
     215            }
     216        }
     217
     218        Set<String> instantiateSortedSet() {
     219            if ("String".equals(sort)) {
     220                return new TreeSet<String>();
     221            } else if ("Integer".equals(sort)) {
     222                return new TreeSet<String>(
     223                        (String v1, String v2) -> Long.valueOf(v1).compareTo(Long.valueOf(v2)));
     224            } else {
     225                return new LinkedHashSet<String>();
     226            }
     227        }
     228
     229        public String resolve(Set<String> values) {
     230            Set<String> results = instantiateSortedSet();
     231            for (String value: values) {
     232                for(String part: value.split(separator)) {
     233                    results.add(part);
     234                }
     235            }
     236            return String.join(separator, results);
     237        }
     238
     239        @Override
     240        public String toString() {
     241            return AutomaticCombine.class.getSimpleName()
     242                    + "(key='" + key + "', description='" + description + "', isRegex="
     243                    + isRegex + ", separator='" + separator + "', sort='" + sort + "')";
     244        }
     245    }
     246
     247    /**
     248     * Preference for automatic tag conflict resolution by choosing from
     249     * a group of possible values.
     250     * This class represent a particular choice from a group.
     251     */
     252    public static class AutomaticChoice {
     253
     254        /** The Tag key to match */
     255        @pref public String key;
     256        /** A free description */
     257        @pref public String description;
     258
     259        /** If regular expression must be used to match the Tag key or the value */
     260        @pref public boolean isRegex = false;
     261
     262        /** The name of the group */
     263        @pref public String group;
     264
     265        /** The Tag value to match */
     266        @pref public String value;
     267
     268        /** The score to give to this choice in order to choose the best value */
     269        @pref public String score;
     270
     271        public AutomaticChoice() {}
     272
     273        public AutomaticChoice(String key, String description, boolean isRegex, String group, String value, String score) {
     274            this.key =  key;
     275            this.description = description;
     276            this.isRegex = isRegex;
     277            this.group = group;
     278            this.value = value;
     279            this.score = score;
     280        }
     281
     282        public boolean matchValue(String v) {
     283            if (isRegex) {
     284                return Pattern.matches(this.value, v);
     285            } else {
     286                return this.value.equals(v);
     287            }
     288        }
     289
     290        public String computeScoreFromValue(String v) {
     291            if (isRegex) {
     292                return v.replaceAll("^" + this.value + "$", this.score);
     293            } else {
     294                return this.score;
     295            }
     296        }
     297
     298        @Override
     299        public String toString() {
     300            return AutomaticChoice.class.getSimpleName()
     301                    + "(key='" + key + "', description='" + description + "', isRegex="
     302                    + isRegex + ", group='" + group + "', value='" + value + "', score='" + score + "')";
     303        }
     304    }
     305
     306
     307    /**
     308     * Preference for automatic tag conflict resolution by choosing from
     309     * a group of possible values.
     310     */
     311    public static class AutomaticChoiceGroup {
     312
     313        /** The Tag key to match */
     314        @pref public String key;
     315
     316        /** If regular expression must be used to match the Tag key */
     317        @pref public boolean isRegex = false;
     318
     319        /** The name of the group */
     320        public String group;
     321
     322        /** The list of choice to choose from */
     323        public List<AutomaticChoice> choices;
     324
     325        public AutomaticChoiceGroup(String key, boolean isRegex, String group, List<AutomaticChoice> choices) {
     326            this.key =  key;
     327            this.isRegex = isRegex;
     328            this.group = group;
     329            this.choices = choices;
     330        }
     331
     332        public static Collection<AutomaticChoiceGroup> groupChoices(Collection<AutomaticChoice> choices) {
     333            HashMap<Pair<String,String>, AutomaticChoiceGroup> results = new HashMap<Pair<String, String>, AutomaticChoiceGroup>();
     334            for(AutomaticChoice choice: choices) {
     335                Pair<String,String> id = new Pair<String, String>(choice.key, choice.group);
     336                AutomaticChoiceGroup group = results.get(id);
     337                if (group == null) {
     338                    boolean isRegex = choice.isRegex && ! Pattern.quote(choice.key).equals(choice.key);
     339                    group = new AutomaticChoiceGroup(choice.key, isRegex, choice.group, new ArrayList<>());
     340                    results.put(id, group);
     341                }
     342                group.choices.add(choice);
     343            }
     344            return results.values();
     345        }
     346
     347        /** Test if this case apply to the given Tag key */
     348        public boolean matchesKey(String k) {
     349            if (isRegex) {
     350                return Pattern.matches(this.key, k);
     351            } else {
     352                return this.key.equals(k);
     353            }
     354        }
     355        /**
     356         * Choose the most appropriate value for this group, or return null if not possible.
     357         */
     358        public String resolve(Set<String> values) {
     359            String bestScore = "";
     360            String bestValue = "";
     361            for (String value: values) {
     362                String score = null;
     363                for(AutomaticChoice choice: choices) {
     364                    if (choice.matchValue(value)) {
     365                        score = choice.computeScoreFromValue(value);
     366                    }
     367                }
     368                if (score == null) {
     369                    // This value is not matched in this group
     370                    // so we can not choose from this group for this key.
     371                    return null;
     372                }
     373                if (score.compareTo(bestScore) >= 0) {
     374                    bestScore = score;
     375                    bestValue = value;
     376                }
     377            }
     378            return bestValue;
     379        }
     380
     381        @Override
     382        public String toString() {
     383            Collection<String> stringChoices = choices.stream().map(AutomaticChoice::toString).collect(Collectors.toCollection(ArrayList::new));
     384            return AutomaticChoiceGroup.class.getSimpleName()
     385                    + "(key='" + key + "', isRegex="
     386                    + isRegex + ", group='" + group + "', choices=(\n  " + String.join("\n  ", stringChoices) + "))";
     387        }
     388
     389    }
     390
    91391}