Changeset 11606 in josm


Ignore:
Timestamp:
2017-02-25T00:47:49+01:00 (2 months ago)
Author:
Don-vip
Message:

fix #14374 - automatic tag conflict resolution of source for French cadastre and Canadian CanVec (patch from Tyndare, modified)

Location:
trunk
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java

    r10791 r11606  
    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);
  • trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolutionUtil.java

    r8855 r11606  
    33
    44import java.util.ArrayList;
     5import java.util.Arrays;
    56import java.util.Collection;
    6 
     7import java.util.Collections;
     8import java.util.HashMap;
     9import java.util.LinkedHashSet;
     10import java.util.List;
     11import java.util.Set;
     12import java.util.TreeSet;
     13import java.util.regex.Pattern;
     14import java.util.regex.PatternSyntaxException;
     15import java.util.stream.Collectors;
     16
     17import org.openstreetmap.josm.Main;
     18import org.openstreetmap.josm.data.Preferences.pref;
    719import org.openstreetmap.josm.data.osm.OsmPrimitive;
    820import org.openstreetmap.josm.data.osm.Tag;
    921import org.openstreetmap.josm.data.osm.TagCollection;
    10 import org.openstreetmap.josm.data.osm.TigerUtils;
     22import org.openstreetmap.josm.tools.Pair;
    1123
    1224/**
     
    1527 */
    1628public final class TagConflictResolutionUtil {
     29
     30    /** The OSM key 'source' */
     31    private static final String KEY_SOURCE = "source";
     32
     33    /** The group identifier for French Cadastre choices */
     34    private static final String GRP_FR_CADASTRE = "FR:cadastre";
     35
     36    /** The group identifier for Canadian CANVEC choices */
     37    private static final String GRP_CA_CANVEC = "CA:canvec";
     38
     39    /**
     40     * Default preferences for the list of AutomaticCombine tag conflict resolvers.
     41     */
     42    private static final Collection<AutomaticCombine> defaultAutomaticTagConflictCombines = Arrays.asList(
     43        new AutomaticCombine("tiger:tlid", "US TIGER tlid", false, ":", "Integer"),
     44        new AutomaticCombine("tiger:(?!tlid$).*", "US TIGER not tlid", true, ":", "String")
     45    );
     46
     47    /**
     48     * Default preferences for the list of AutomaticChoice tag conflict resolvers.
     49     */
     50    private static final Collection<AutomaticChoice> defaultAutomaticTagConflictChoices = Arrays.asList(
     51        /* "source" "FR:cadastre" - https://wiki.openstreetmap.org/wiki/FR:WikiProject_France/Cadastre
     52         * List of choices for the "source" tag of data exported from the French cadastre,
     53         * which ends by the exported year generating many conflicts.
     54         * The generated score begins with the year number to select the most recent one.
     55         */
     56        new AutomaticChoice(KEY_SOURCE, GRP_FR_CADASTRE, "FR cadastre source, manual value", true,
     57                "cadastre", "0"),
     58        new AutomaticChoice(KEY_SOURCE, GRP_FR_CADASTRE, "FR cadastre source, initial format", true,
     59                "extraction vectorielle v1 cadastre-dgi-fr source : Direction G[eé]n[eé]rale des Imp[oô]ts"
     60                + " - Cadas\\. Mise [aà] jour : (2[0-9]{3})",
     61                "$1 1"),
     62        new AutomaticChoice(KEY_SOURCE, GRP_FR_CADASTRE, "FR cadastre source, last format", true,
     63                "(?:cadastre-dgi-fr source : )?Direction G[eé]n[eé]rale des (?:Imp[oô]ts|Finances Publiques)"
     64                + " - Cadas(?:tre)?(?:\\.| ;) [Mm]ise [aà] jour : (2[0-9]{3})",
     65                "$1 2"),
     66        /* "source" "CA:canvec" - https://wiki.openstreetmap.org/wiki/CanVec
     67         * List of choices for the "source" tag of data exported from Natural Resources Canada (NRCan)
     68         */
     69        new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, initial value", true,
     70                "CanVec_Import_2009", "00"),
     71        new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, 4.0/6.0 value", true,
     72                "CanVec ([1-9]).0 - NRCan", "0$1"),
     73        new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, 7.0/8.0 value", true,
     74                "NRCan-CanVec-([1-9]).0", "0$1"),
     75        new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, 10.0/12.0 value", true,
     76                "NRCan-CanVec-(1[012]).0", "$1")
     77    );
     78
     79    private static volatile Collection<AutomaticTagConflictResolver> automaticTagConflictResolvers;
    1780
    1881    private TagConflictResolutionUtil() {
     
    61124
    62125    /**
    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     }
    74 
    75     /**
    76126     * Completes tags in the tag collection <code>tc</code> with the empty value
    77127     * for each tag. If the empty value is present the tag conflict resolution dialog
     
    89139        }
    90140    }
     141
     142    /**
     143     * Automatically resolve some tag conflicts.
     144     * The list of automatic resolution is taken from the preferences.
     145     * @param tc the tag collection
     146     * @since 11606
     147     */
     148    public static void applyAutomaticTagConflictResolution(TagCollection tc) {
     149        applyAutomaticTagConflictResolution(tc, getAutomaticTagConflictResolvers());
     150    }
     151
     152    /**
     153     * Get the AutomaticTagConflictResolvers configured in the Preferences or the default ones.
     154     * @return the configured AutomaticTagConflictResolvers.
     155     * @since 11606
     156     */
     157    public static Collection<AutomaticTagConflictResolver> getAutomaticTagConflictResolvers() {
     158        if (automaticTagConflictResolvers == null) {
     159            Collection<AutomaticCombine> automaticTagConflictCombines =
     160                    Main.pref.getListOfStructs(
     161                            "automatic-tag-conflict-resolution.combine",
     162                            defaultAutomaticTagConflictCombines, AutomaticCombine.class);
     163            Collection<AutomaticChoiceGroup> automaticTagConflictChoiceGroups =
     164                    AutomaticChoiceGroup.groupChoices(Main.pref.getListOfStructs(
     165                            "automatic-tag-conflict-resolution.choice",
     166                            defaultAutomaticTagConflictChoices, AutomaticChoice.class));
     167            // Use a tmp variable to fully construct the collection before setting
     168            // the volatile variable automaticTagConflictResolvers.
     169            ArrayList<AutomaticTagConflictResolver> tmp = new ArrayList<>();
     170            tmp.addAll(automaticTagConflictCombines);
     171            tmp.addAll(automaticTagConflictChoiceGroups);
     172            automaticTagConflictResolvers = tmp;
     173        }
     174        return Collections.unmodifiableCollection(automaticTagConflictResolvers);
     175    }
     176
     177    /**
     178     * An automatic tag conflict resolver interface.
     179     * @since 11606
     180     */
     181    interface AutomaticTagConflictResolver {
     182        /**
     183         * Check if this resolution apply to the given Tag key.
     184         * @param key The Tag key to match.
     185         * @return true if this automatic resolution apply to the given Tag key.
     186         */
     187        boolean matchesKey(String key);
     188
     189        /**
     190         * Try to resolve a conflict between a set of values for a Tag
     191         * @param values the set of conflicting values for the Tag.
     192         * @return the resolved value or null if resolution was not possible.
     193         */
     194        String resolve(Set<String> values);
     195    }
     196
     197    /**
     198     * Automatically resolve some given conflicts using the given resolvers.
     199     * @param tc the tag collection.
     200     * @param resolvers the list of automatic tag conflict resolvers to apply.
     201     * @since 11606
     202     */
     203    public static void applyAutomaticTagConflictResolution(TagCollection tc,
     204            Collection<AutomaticTagConflictResolver> resolvers) {
     205        for (String key: tc.getKeysWithMultipleValues()) {
     206            for (AutomaticTagConflictResolver resolver : resolvers) {
     207                try {
     208                    if (resolver.matchesKey(key)) {
     209                        String result = resolver.resolve(tc.getValues(key));
     210                        if (result != null) {
     211                            tc.setUniqueForKey(key, result);
     212                            break;
     213                        }
     214                    }
     215                } catch (PatternSyntaxException e) {
     216                    // Can happen if a particular resolver has an invalid regular expression pattern
     217                    // but it should not stop the other automatic tag conflict resolution.
     218                    Main.error(e);
     219                }
     220            }
     221        }
     222    }
     223
     224    /**
     225     * Preference for automatic tag-conflict resolver by combining the tag values using a separator.
     226     * @since 11606
     227     */
     228    public static class AutomaticCombine implements AutomaticTagConflictResolver {
     229
     230        /** The Tag key to match */
     231        @pref public String key;
     232
     233        /** A free description */
     234        @pref public String description = "";
     235
     236        /** If regular expression must be used to match the Tag key or the value. */
     237        @pref public boolean isRegex;
     238
     239        /** The separator to use to combine the values. */
     240        @pref public String separator = ";";
     241
     242        /** If the combined values must be sorted.
     243         * Possible values:
     244         * <ul>
     245         * <li> Integer - Sort using Integer natural order.</li>
     246         * <li> String - Sort using String natural order.</li>
     247         * <li> * - No ordering.</li>
     248         * </ul>
     249         */
     250        @pref public String sort;
     251
     252        /** Default constructor. */
     253        public AutomaticCombine() {
     254            // needed for instantiation from Preferences
     255        }
     256
     257        /** Instantiate an automatic tag-conflict resolver which combining the values using a separator.
     258         * @param key The Tag key to match.
     259         * @param description A free description.
     260         * @param isRegex If regular expression must be used to match the Tag key or the value.
     261         * @param separator The separator to use to combine the values.
     262         * @param sort If the combined values must be sorted.
     263         */
     264        public AutomaticCombine(String key, String description, boolean isRegex, String separator, String sort) {
     265            this.key = key;
     266            this.description = description;
     267            this.isRegex = isRegex;
     268            this.separator = separator;
     269            this.sort = sort;
     270        }
     271
     272        @Override
     273        public boolean matchesKey(String k) {
     274            if (isRegex) {
     275                return Pattern.matches(this.key, k);
     276            } else {
     277                return this.key.equals(k);
     278            }
     279        }
     280
     281        Set<String> instantiateSortedSet() {
     282            if ("String".equals(sort)) {
     283                return new TreeSet<>();
     284            } else if ("Integer".equals(sort)) {
     285                return new TreeSet<>((String v1, String v2) -> Long.valueOf(v1).compareTo(Long.valueOf(v2)));
     286            } else {
     287                return new LinkedHashSet<>();
     288            }
     289        }
     290
     291        @Override
     292        public String resolve(Set<String> values) {
     293            Set<String> results = instantiateSortedSet();
     294            for (String value: values) {
     295                for (String part: value.split(Pattern.quote(separator))) {
     296                    results.add(part);
     297                }
     298            }
     299            return String.join(separator, results);
     300        }
     301
     302        @Override
     303        public String toString() {
     304            return AutomaticCombine.class.getSimpleName()
     305                    + "(key='" + key + "', description='" + description + "', isRegex="
     306                    + isRegex + ", separator='" + separator + "', sort='" + sort + "')";
     307        }
     308    }
     309
     310    /**
     311     * Preference for a particular choice from a group for automatic tag conflict resolution.
     312     * {@code AutomaticChoice}s are grouped into {@link AutomaticChoiceGroup}.
     313     * @since 11606
     314     */
     315    public static class AutomaticChoice {
     316
     317        /** The Tag key to match. */
     318        @pref public String key;
     319
     320        /** The name of the {link AutomaticChoice group} this choice belongs to. */
     321        @pref public String group;
     322
     323        /** A free description. */
     324        @pref public String description = "";
     325
     326        /** If regular expression must be used to match the Tag key or the value. */
     327        @pref public boolean isRegex;
     328
     329        /** The Tag value to match. */
     330        @pref public String value;
     331
     332        /**
     333         * The score to give to this choice in order to choose the best value
     334         * Natural String ordering is used to identify the best score.
     335         */
     336        @pref public String score;
     337
     338        /** Default constructor. */
     339        public AutomaticChoice() {
     340            // needed for instantiation from Preferences
     341        }
     342
     343        /**
     344         * Instantiate a particular choice from a group for automatic tag conflict resolution.
     345         * @param key The Tag key to match.
     346         * @param group The name of the {link AutomaticChoice group} this choice belongs to.
     347         * @param description A free description.
     348         * @param isRegex If regular expression must be used to match the Tag key or the value.
     349         * @param value The Tag value to match.
     350         * @param score The score to give to this choice in order to choose the best value.
     351         */
     352        public AutomaticChoice(String key, String group, String description, boolean isRegex, String value, String score) {
     353            this.key = key;
     354            this.group = group;
     355            this.description = description;
     356            this.isRegex = isRegex;
     357            this.value = value;
     358            this.score = score;
     359        }
     360
     361        /**
     362         * Check if this choice match the given Tag value.
     363         * @param v the Tag value to match.
     364         * @return true if this choice correspond to the given tag value.
     365         */
     366        public boolean matchesValue(String v) {
     367            if (isRegex) {
     368                return Pattern.matches(this.value, v);
     369            } else {
     370                return this.value.equals(v);
     371            }
     372        }
     373
     374        /**
     375         * Return the score associated to this choice for the given Tag value.
     376         * For the result to be valid the given tag value must {@link #matchesValue(String) match} this choice.
     377         * @param v the Tag value of which to get the score.
     378         * @return the score associated to the given Tag value.
     379         * @throws PatternSyntaxException if the regular expression syntax is invalid
     380         */
     381        public String computeScoreFromValue(String v) {
     382            if (isRegex) {
     383                return v.replaceAll("^" + this.value + "$", this.score);
     384            } else {
     385                return this.score;
     386            }
     387        }
     388
     389        @Override
     390        public String toString() {
     391            return AutomaticChoice.class.getSimpleName()
     392                    + "(key='" + key + "', group='" + group + "', description='" + description
     393                    + "', isRegex=" + isRegex + ", value='" + value + "', score='" + score + "')";
     394        }
     395    }
     396
     397    /**
     398     * Preference for an automatic tag conflict resolver which choose from
     399     * a group of possible {@link AutomaticChoice choice} values.
     400     * @since 11606
     401     */
     402    public static class AutomaticChoiceGroup implements AutomaticTagConflictResolver {
     403
     404        /** The Tag key to match. */
     405        @pref public String key;
     406
     407        /** The name of the group. */
     408        final String group;
     409
     410        /** If regular expression must be used to match the Tag key. */
     411        @pref public boolean isRegex;
     412
     413        /** The list of choice to choose from. */
     414        final List<AutomaticChoice> choices;
     415
     416        /** Instantiate an automatic tag conflict resolver which choose from
     417         * a given list of {@link AutomaticChoice choice} values.
     418         *
     419         * @param key The Tag key to match.
     420         * @param group The name of the group.
     421         * @param isRegex If regular expression must be used to match the Tag key.
     422         * @param choices The list of choice to choose from.
     423         */
     424        public AutomaticChoiceGroup(String key, String group, boolean isRegex, List<AutomaticChoice> choices) {
     425            this.key = key;
     426            this.group = group;
     427            this.isRegex = isRegex;
     428            this.choices = choices;
     429        }
     430
     431        /**
     432         * Group a given list of {@link AutomaticChoice} by the Tag key and the choice group name.
     433         * @param choices the list of {@link AutomaticChoice choices} to group.
     434         * @return the resulting list of group.
     435         */
     436        public static Collection<AutomaticChoiceGroup> groupChoices(Collection<AutomaticChoice> choices) {
     437            HashMap<Pair<String, String>, AutomaticChoiceGroup> results = new HashMap<>();
     438            for (AutomaticChoice choice: choices) {
     439                Pair<String, String> id = new Pair<>(choice.key, choice.group);
     440                AutomaticChoiceGroup group = results.get(id);
     441                if (group == null) {
     442                    boolean isRegex = choice.isRegex && !Pattern.quote(choice.key).equals(choice.key);
     443                    group = new AutomaticChoiceGroup(choice.key, choice.group, isRegex, new ArrayList<>());
     444                    results.put(id, group);
     445                }
     446                group.choices.add(choice);
     447            }
     448            return results.values();
     449        }
     450
     451        @Override
     452        public boolean matchesKey(String k) {
     453            if (isRegex) {
     454                return Pattern.matches(this.key, k);
     455            } else {
     456                return this.key.equals(k);
     457            }
     458        }
     459
     460        @Override
     461        public String resolve(Set<String> values) {
     462            String bestScore = "";
     463            String bestValue = "";
     464            for (String value : values) {
     465                String score = null;
     466                for (AutomaticChoice choice : choices) {
     467                    if (choice.matchesValue(value)) {
     468                        score = choice.computeScoreFromValue(value);
     469                    }
     470                }
     471                if (score == null) {
     472                    // This value is not matched in this group
     473                    // so we can not choose from this group for this key.
     474                    return null;
     475                }
     476                if (score.compareTo(bestScore) >= 0) {
     477                    bestScore = score;
     478                    bestValue = value;
     479                }
     480            }
     481            return bestValue;
     482        }
     483
     484        @Override
     485        public String toString() {
     486            Collection<String> stringChoices = choices.stream().map(AutomaticChoice::toString).collect(Collectors.toCollection(ArrayList::new));
     487            return AutomaticChoiceGroup.class.getSimpleName() + "(key='" + key + "', group='" + group +
     488                    "', isRegex=" + isRegex + ", choices=(\n  " + String.join(",\n  ", stringChoices) + "))";
     489        }
     490    }
    91491}
Note: See TracChangeset for help on using the changeset viewer.