source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolutionUtil.java@ 13809

Last change on this file since 13809 was 13809, checked in by Don-vip, 6 years ago

define InterestingTags functions in IPrimitive

  • Property svn:eol-style set to native
File size: 20.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.tags;
3
4import java.util.ArrayList;
5import java.util.Arrays;
6import java.util.Collection;
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.data.StructUtils;
18import org.openstreetmap.josm.data.StructUtils.StructEntry;
19import org.openstreetmap.josm.data.osm.AbstractPrimitive;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Tag;
22import org.openstreetmap.josm.data.osm.TagCollection;
23import org.openstreetmap.josm.spi.preferences.Config;
24import org.openstreetmap.josm.tools.JosmRuntimeException;
25import org.openstreetmap.josm.tools.Logging;
26import org.openstreetmap.josm.tools.Pair;
27
28/**
29 * Collection of utility methods for tag conflict resolution
30 *
31 */
32public final class TagConflictResolutionUtil {
33
34 /** The OSM key 'source' */
35 private static final String KEY_SOURCE = "source";
36
37 /** The group identifier for French Cadastre choices */
38 private static final String GRP_FR_CADASTRE = "FR:cadastre";
39
40 /** The group identifier for Canadian CANVEC choices */
41 private static final String GRP_CA_CANVEC = "CA:canvec";
42
43 /**
44 * Default preferences for the list of AutomaticCombine tag conflict resolvers.
45 */
46 private static final Collection<AutomaticCombine> defaultAutomaticTagConflictCombines = Arrays.asList(
47 new AutomaticCombine("tiger:tlid", "US TIGER tlid", false, ":", "Integer"),
48 new AutomaticCombine("tiger:(?!tlid$).*", "US TIGER not tlid", true, ":", "String")
49 );
50
51 /**
52 * Default preferences for the list of AutomaticChoice tag conflict resolvers.
53 */
54 private static final Collection<AutomaticChoice> defaultAutomaticTagConflictChoices = Arrays.asList(
55 /* "source" "FR:cadastre" - https://wiki.openstreetmap.org/wiki/FR:WikiProject_France/Cadastre
56 * List of choices for the "source" tag of data exported from the French cadastre,
57 * which ends by the exported year generating many conflicts.
58 * The generated score begins with the year number to select the most recent one.
59 */
60 new AutomaticChoice(KEY_SOURCE, GRP_FR_CADASTRE, "FR cadastre source, manual value", true,
61 "cadastre", "0"),
62 new AutomaticChoice(KEY_SOURCE, GRP_FR_CADASTRE, "FR cadastre source, initial format", true,
63 "extraction vectorielle v1 cadastre-dgi-fr source : Direction G[eé]n[eé]rale des Imp[oô]ts"
64 + " - Cadas\\. Mise [aà] jour : (2[0-9]{3})",
65 "$1 1"),
66 new AutomaticChoice(KEY_SOURCE, GRP_FR_CADASTRE, "FR cadastre source, last format", true,
67 "(?:cadastre-dgi-fr source : )?Direction G[eé]n[eé]rale des (?:Imp[oô]ts|Finances Publiques)"
68 + " - Cadas(?:tre)?(?:\\.| ;) [Mm]ise [aà] jour : (2[0-9]{3})",
69 "$1 2"),
70 /* "source" "CA:canvec" - https://wiki.openstreetmap.org/wiki/CanVec
71 * List of choices for the "source" tag of data exported from Natural Resources Canada (NRCan)
72 */
73 new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, initial value", true,
74 "CanVec_Import_2009", "00"),
75 new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, 4.0/6.0 value", true,
76 "CanVec ([1-9]).0 - NRCan", "0$1"),
77 new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, 7.0/8.0 value", true,
78 "NRCan-CanVec-([1-9]).0", "0$1"),
79 new AutomaticChoice(KEY_SOURCE, GRP_CA_CANVEC, "CA canvec source, 10.0/12.0 value", true,
80 "NRCan-CanVec-(1[012]).0", "$1")
81 );
82
83 private static volatile Collection<AutomaticTagConflictResolver> automaticTagConflictResolvers;
84
85 private TagConflictResolutionUtil() {
86 // no constructor, just static utility methods
87 }
88
89 /**
90 * Normalizes the tags in the tag collection <code>tc</code> before resolving tag conflicts.
91 *
92 * Removes irrelevant tags like "created_by".
93 *
94 * For tags which are not present on at least one of the merged nodes, the empty value ""
95 * is added to the list of values for this tag, but only if there are at least two
96 * primitives with tags, and at least one tagged primitive do not have this tag.
97 *
98 * @param tc the tag collection
99 * @param merged the collection of merged primitives
100 */
101 public static void normalizeTagCollectionBeforeEditing(TagCollection tc, Collection<? extends OsmPrimitive> merged) {
102 // remove irrelevant tags
103 //
104 for (String key : AbstractPrimitive.getDiscardableKeys()) {
105 tc.removeByKey(key);
106 }
107
108 Collection<OsmPrimitive> taggedPrimitives = new ArrayList<>();
109 for (OsmPrimitive p: merged) {
110 if (p.isTagged()) {
111 taggedPrimitives.add(p);
112 }
113 }
114 if (taggedPrimitives.size() <= 1)
115 return;
116
117 for (String key: tc.getKeys()) {
118 // make sure the empty value is in the tag set if a tag is not present
119 // on all merged nodes
120 //
121 for (OsmPrimitive p: taggedPrimitives) {
122 if (p.get(key) == null) {
123 tc.add(new Tag(key, "")); // add a tag with key and empty value
124 }
125 }
126 }
127 }
128
129 /**
130 * Completes tags in the tag collection <code>tc</code> with the empty value
131 * for each tag. If the empty value is present the tag conflict resolution dialog
132 * will offer an option for removing the tag and not only options for selecting
133 * one of the current values of the tag.
134 *
135 * @param tc the tag collection
136 */
137 public static void completeTagCollectionForEditing(TagCollection tc) {
138 for (String key: tc.getKeys()) {
139 // make sure the empty value is in the tag set such that we can delete the tag
140 // in the conflict dialog if necessary
141 tc.add(new Tag(key, ""));
142 }
143 }
144
145 /**
146 * Automatically resolve some tag conflicts.
147 * The list of automatic resolution is taken from the preferences.
148 * @param tc the tag collection
149 * @since 11606
150 */
151 public static void applyAutomaticTagConflictResolution(TagCollection tc) {
152 try {
153 applyAutomaticTagConflictResolution(tc, getAutomaticTagConflictResolvers());
154 } catch (JosmRuntimeException e) {
155 Logging.log(Logging.LEVEL_ERROR, "Unable to automatically resolve tag conflicts", e);
156 }
157 }
158
159 /**
160 * Get the AutomaticTagConflictResolvers configured in the Preferences or the default ones.
161 * @return the configured AutomaticTagConflictResolvers.
162 * @since 11606
163 */
164 public static Collection<AutomaticTagConflictResolver> getAutomaticTagConflictResolvers() {
165 if (automaticTagConflictResolvers == null) {
166 Collection<AutomaticCombine> automaticTagConflictCombines = StructUtils.getListOfStructs(
167 Config.getPref(),
168 "automatic-tag-conflict-resolution.combine",
169 defaultAutomaticTagConflictCombines, AutomaticCombine.class);
170 Collection<AutomaticChoiceGroup> automaticTagConflictChoiceGroups =
171 AutomaticChoiceGroup.groupChoices(StructUtils.getListOfStructs(
172 Config.getPref(),
173 "automatic-tag-conflict-resolution.choice",
174 defaultAutomaticTagConflictChoices, AutomaticChoice.class));
175 // Use a tmp variable to fully construct the collection before setting
176 // the volatile variable automaticTagConflictResolvers.
177 ArrayList<AutomaticTagConflictResolver> tmp = new ArrayList<>();
178 tmp.addAll(automaticTagConflictCombines);
179 tmp.addAll(automaticTagConflictChoiceGroups);
180 automaticTagConflictResolvers = tmp;
181 }
182 return Collections.unmodifiableCollection(automaticTagConflictResolvers);
183 }
184
185 /**
186 * An automatic tag conflict resolver interface.
187 * @since 11606
188 */
189 interface AutomaticTagConflictResolver {
190 /**
191 * Check if this resolution apply to the given Tag key.
192 * @param key The Tag key to match.
193 * @return true if this automatic resolution apply to the given Tag key.
194 */
195 boolean matchesKey(String key);
196
197 /**
198 * Try to resolve a conflict between a set of values for a Tag
199 * @param values the set of conflicting values for the Tag.
200 * @return the resolved value or null if resolution was not possible.
201 */
202 String resolve(Set<String> values);
203 }
204
205 /**
206 * Automatically resolve some given conflicts using the given resolvers.
207 * @param tc the tag collection.
208 * @param resolvers the list of automatic tag conflict resolvers to apply.
209 * @since 11606
210 */
211 public static void applyAutomaticTagConflictResolution(TagCollection tc,
212 Collection<AutomaticTagConflictResolver> resolvers) {
213 for (String key: tc.getKeysWithMultipleValues()) {
214 for (AutomaticTagConflictResolver resolver : resolvers) {
215 try {
216 if (resolver.matchesKey(key)) {
217 String result = resolver.resolve(tc.getValues(key));
218 if (result != null) {
219 tc.setUniqueForKey(key, result);
220 break;
221 }
222 }
223 } catch (PatternSyntaxException e) {
224 // Can happen if a particular resolver has an invalid regular expression pattern
225 // but it should not stop the other automatic tag conflict resolution.
226 Logging.error(e);
227 }
228 }
229 }
230 }
231
232 /**
233 * Preference for automatic tag-conflict resolver by combining the tag values using a separator.
234 * @since 11606
235 */
236 public static class AutomaticCombine implements AutomaticTagConflictResolver {
237
238 /** The Tag key to match */
239 @StructEntry public String key;
240
241 /** A free description */
242 @StructEntry public String description = "";
243
244 /** If regular expression must be used to match the Tag key or the value. */
245 @StructEntry public boolean isRegex;
246
247 /** The separator to use to combine the values. */
248 @StructEntry public String separator = ";";
249
250 /** If the combined values must be sorted.
251 * Possible values:
252 * <ul>
253 * <li> Integer - Sort using Integer natural order.</li>
254 * <li> String - Sort using String natural order.</li>
255 * <li> * - No ordering.</li>
256 * </ul>
257 */
258 @StructEntry public String sort;
259
260 /** Default constructor. */
261 public AutomaticCombine() {
262 // needed for instantiation from Preferences
263 }
264
265 /** Instantiate an automatic tag-conflict resolver which combining the values using a separator.
266 * @param key The Tag key to match.
267 * @param description A free description.
268 * @param isRegex If regular expression must be used to match the Tag key or the value.
269 * @param separator The separator to use to combine the values.
270 * @param sort If the combined values must be sorted.
271 */
272 public AutomaticCombine(String key, String description, boolean isRegex, String separator, String sort) {
273 this.key = key;
274 this.description = description;
275 this.isRegex = isRegex;
276 this.separator = separator;
277 this.sort = sort;
278 }
279
280 @Override
281 public boolean matchesKey(String k) {
282 if (isRegex) {
283 return Pattern.matches(this.key, k);
284 } else {
285 return this.key.equals(k);
286 }
287 }
288
289 Set<String> instantiateSortedSet() {
290 if ("String".equals(sort)) {
291 return new TreeSet<>();
292 } else if ("Integer".equals(sort)) {
293 return new TreeSet<>((String v1, String v2) -> Long.valueOf(v1).compareTo(Long.valueOf(v2)));
294 } else {
295 return new LinkedHashSet<>();
296 }
297 }
298
299 @Override
300 public String resolve(Set<String> values) {
301 Set<String> results = instantiateSortedSet();
302 for (String value: values) {
303 for (String part: value.split(Pattern.quote(separator))) {
304 results.add(part);
305 }
306 }
307 return String.join(separator, results);
308 }
309
310 @Override
311 public String toString() {
312 return AutomaticCombine.class.getSimpleName()
313 + "(key='" + key + "', description='" + description + "', isRegex="
314 + isRegex + ", separator='" + separator + "', sort='" + sort + "')";
315 }
316 }
317
318 /**
319 * Preference for a particular choice from a group for automatic tag conflict resolution.
320 * {@code AutomaticChoice}s are grouped into {@link AutomaticChoiceGroup}.
321 * @since 11606
322 */
323 public static class AutomaticChoice {
324
325 /** The Tag key to match. */
326 @StructEntry public String key;
327
328 /** The name of the {link AutomaticChoice group} this choice belongs to. */
329 @StructEntry public String group;
330
331 /** A free description. */
332 @StructEntry public String description = "";
333
334 /** If regular expression must be used to match the Tag key or the value. */
335 @StructEntry public boolean isRegex;
336
337 /** The Tag value to match. */
338 @StructEntry public String value;
339
340 /**
341 * The score to give to this choice in order to choose the best value
342 * Natural String ordering is used to identify the best score.
343 */
344 @StructEntry public String score;
345
346 /** Default constructor. */
347 public AutomaticChoice() {
348 // needed for instantiation from Preferences
349 }
350
351 /**
352 * Instantiate a particular choice from a group for automatic tag conflict resolution.
353 * @param key The Tag key to match.
354 * @param group The name of the {link AutomaticChoice group} this choice belongs to.
355 * @param description A free description.
356 * @param isRegex If regular expression must be used to match the Tag key or the value.
357 * @param value The Tag value to match.
358 * @param score The score to give to this choice in order to choose the best value.
359 */
360 public AutomaticChoice(String key, String group, String description, boolean isRegex, String value, String score) {
361 this.key = key;
362 this.group = group;
363 this.description = description;
364 this.isRegex = isRegex;
365 this.value = value;
366 this.score = score;
367 }
368
369 /**
370 * Check if this choice match the given Tag value.
371 * @param v the Tag value to match.
372 * @return true if this choice correspond to the given tag value.
373 */
374 public boolean matchesValue(String v) {
375 if (isRegex) {
376 return Pattern.matches(this.value, v);
377 } else {
378 return this.value.equals(v);
379 }
380 }
381
382 /**
383 * Return the score associated to this choice for the given Tag value.
384 * For the result to be valid the given tag value must {@link #matchesValue(String) match} this choice.
385 * @param v the Tag value of which to get the score.
386 * @return the score associated to the given Tag value.
387 * @throws PatternSyntaxException if the regular expression syntax is invalid
388 */
389 public String computeScoreFromValue(String v) {
390 if (isRegex) {
391 return v.replaceAll("^" + this.value + "$", this.score);
392 } else {
393 return this.score;
394 }
395 }
396
397 @Override
398 public String toString() {
399 return AutomaticChoice.class.getSimpleName()
400 + "(key='" + key + "', group='" + group + "', description='" + description
401 + "', isRegex=" + isRegex + ", value='" + value + "', score='" + score + "')";
402 }
403 }
404
405 /**
406 * Preference for an automatic tag conflict resolver which choose from
407 * a group of possible {@link AutomaticChoice choice} values.
408 * @since 11606
409 */
410 public static class AutomaticChoiceGroup implements AutomaticTagConflictResolver {
411
412 /** The Tag key to match. */
413 @StructEntry public String key;
414
415 /** The name of the group. */
416 final String group;
417
418 /** If regular expression must be used to match the Tag key. */
419 @StructEntry public boolean isRegex;
420
421 /** The list of choice to choose from. */
422 final List<AutomaticChoice> choices;
423
424 /** Instantiate an automatic tag conflict resolver which choose from
425 * a given list of {@link AutomaticChoice choice} values.
426 *
427 * @param key The Tag key to match.
428 * @param group The name of the group.
429 * @param isRegex If regular expression must be used to match the Tag key.
430 * @param choices The list of choice to choose from.
431 */
432 public AutomaticChoiceGroup(String key, String group, boolean isRegex, List<AutomaticChoice> choices) {
433 this.key = key;
434 this.group = group;
435 this.isRegex = isRegex;
436 this.choices = choices;
437 }
438
439 /**
440 * Group a given list of {@link AutomaticChoice} by the Tag key and the choice group name.
441 * @param choices the list of {@link AutomaticChoice choices} to group.
442 * @return the resulting list of group.
443 */
444 public static Collection<AutomaticChoiceGroup> groupChoices(Collection<AutomaticChoice> choices) {
445 HashMap<Pair<String, String>, AutomaticChoiceGroup> results = new HashMap<>();
446 for (AutomaticChoice choice: choices) {
447 Pair<String, String> id = new Pair<>(choice.key, choice.group);
448 AutomaticChoiceGroup group = results.get(id);
449 if (group == null) {
450 boolean isRegex = choice.isRegex && !Pattern.quote(choice.key).equals(choice.key);
451 group = new AutomaticChoiceGroup(choice.key, choice.group, isRegex, new ArrayList<>());
452 results.put(id, group);
453 }
454 group.choices.add(choice);
455 }
456 return results.values();
457 }
458
459 @Override
460 public boolean matchesKey(String k) {
461 if (isRegex) {
462 return Pattern.matches(this.key, k);
463 } else {
464 return this.key.equals(k);
465 }
466 }
467
468 @Override
469 public String resolve(Set<String> values) {
470 String bestScore = "";
471 String bestValue = "";
472 for (String value : values) {
473 String score = null;
474 for (AutomaticChoice choice : choices) {
475 if (choice.matchesValue(value)) {
476 score = choice.computeScoreFromValue(value);
477 }
478 }
479 if (score == null) {
480 // This value is not matched in this group
481 // so we can not choose from this group for this key.
482 return null;
483 }
484 if (score.compareTo(bestScore) >= 0) {
485 bestScore = score;
486 bestValue = value;
487 }
488 }
489 return bestValue;
490 }
491
492 @Override
493 public String toString() {
494 Collection<String> stringChoices = choices.stream().map(AutomaticChoice::toString).collect(Collectors.toCollection(ArrayList::new));
495 return AutomaticChoiceGroup.class.getSimpleName() + "(key='" + key + "', group='" + group +
496 "', isRegex=" + isRegex + ", choices=(\n " + String.join(",\n ", stringChoices) + "))";
497 }
498 }
499}
Note: See TracBrowser for help on using the repository browser.