source: josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java@ 13840

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

define InterestingTags functions in IPrimitive

  • Property svn:eol-style set to native
File size: 11.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.corrector;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.List;
10import java.util.Locale;
11import java.util.Map;
12import java.util.function.Function;
13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16import org.openstreetmap.josm.command.Command;
17import org.openstreetmap.josm.data.correction.RoleCorrection;
18import org.openstreetmap.josm.data.correction.TagCorrection;
19import org.openstreetmap.josm.data.osm.AbstractPrimitive;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.OsmUtils;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.RelationMember;
25import org.openstreetmap.josm.data.osm.Tag;
26import org.openstreetmap.josm.data.osm.TagCollection;
27import org.openstreetmap.josm.data.osm.Tagged;
28import org.openstreetmap.josm.data.osm.Way;
29import org.openstreetmap.josm.tools.UserCancelException;
30
31/**
32 * A ReverseWayTagCorrector handles necessary corrections of tags
33 * when a way is reversed. E.g. oneway=yes needs to be changed
34 * to oneway=-1 and vice versa.
35 *
36 * The Corrector offers the automatic resolution in an dialog
37 * for the user to confirm.
38 */
39public class ReverseWayTagCorrector extends TagCorrector<Way> {
40
41 private static final String SEPARATOR = "[:_]";
42
43 private static Pattern getPatternFor(String s) {
44 return getPatternFor(s, false);
45 }
46
47 private static Pattern getPatternFor(String s, boolean exactMatch) {
48 if (exactMatch) {
49 return Pattern.compile("(^)(" + s + ")($)");
50 } else {
51 return Pattern.compile("(^|.*" + SEPARATOR + ")(" + s + ")(" + SEPARATOR + ".*|$)",
52 Pattern.CASE_INSENSITIVE);
53 }
54 }
55
56 private static final Collection<Pattern> IGNORED_KEYS = new ArrayList<>();
57 static {
58 for (String s : AbstractPrimitive.getUninterestingKeys()) {
59 IGNORED_KEYS.add(getPatternFor(s));
60 }
61 for (String s : new String[]{"name", "ref", "tiger:county"}) {
62 IGNORED_KEYS.add(getPatternFor(s, false));
63 }
64 for (String s : new String[]{"tiger:county", "turn:lanes", "change:lanes", "placement"}) {
65 IGNORED_KEYS.add(getPatternFor(s, true));
66 }
67 }
68
69 private interface IStringSwitcher extends Function<String, String> {
70
71 static IStringSwitcher combined(IStringSwitcher... switchers) {
72 return key -> {
73 for (IStringSwitcher switcher : switchers) {
74 final String newKey = switcher.apply(key);
75 if (!key.equals(newKey)) {
76 return newKey;
77 }
78 }
79 return key;
80 };
81 }
82 }
83
84 private static class StringSwitcher implements IStringSwitcher {
85
86 private final String a;
87 private final String b;
88 private final Pattern pattern;
89
90 StringSwitcher(String a, String b) {
91 this.a = a;
92 this.b = b;
93 this.pattern = getPatternFor(a + '|' + b);
94 }
95
96 @Override
97 public String apply(String text) {
98 Matcher m = pattern.matcher(text);
99
100 if (m.lookingAt()) {
101 String leftRight = m.group(2).toLowerCase(Locale.ENGLISH);
102
103 StringBuilder result = new StringBuilder();
104 result.append(text.substring(0, m.start(2)))
105 .append(leftRight.equals(a) ? b : a)
106 .append(text.substring(m.end(2)));
107
108 return result.toString();
109 }
110 return text;
111 }
112 }
113
114 /**
115 * Reverses a given tag.
116 * @since 5787
117 */
118 public static final class TagSwitcher {
119
120 private TagSwitcher() {
121 // Hide implicit public constructor for utility class
122 }
123
124 /**
125 * Reverses a given tag.
126 * @param tag The tag to reverse
127 * @return The reversed tag (is equal to <code>tag</code> if no change is needed)
128 */
129 public static Tag apply(final Tag tag) {
130 return apply(tag.getKey(), tag.getValue());
131 }
132
133 /**
134 * Reverses a given tag (key=value).
135 * @param key The tag key
136 * @param value The tag value
137 * @return The reversed tag (is equal to <code>key=value</code> if no change is needed)
138 */
139 public static Tag apply(final String key, final String value) {
140 String newKey = key;
141 String newValue = value;
142
143 if (key.startsWith("oneway") || key.endsWith("oneway")) {
144 if (OsmUtils.isReversed(value)) {
145 newValue = OsmUtils.TRUE_VALUE;
146 } else if (OsmUtils.isTrue(value)) {
147 newValue = OsmUtils.REVERSE_VALUE;
148 }
149 newKey = COMBINED_SWITCHERS.apply(key);
150 } else if (key.startsWith("incline") || key.endsWith("incline")) {
151 newValue = UP_DOWN.apply(value);
152 if (newValue.equals(value)) {
153 newValue = invertNumber(value);
154 }
155 } else if (key.startsWith("direction") || key.endsWith("direction")) {
156 newValue = COMBINED_SWITCHERS.apply(value);
157 } else if (key.endsWith(":forward") || key.endsWith(":backward")) {
158 // Change key but not left/right value (fix #8518)
159 newKey = FORWARD_BACKWARD.apply(key);
160 } else if (!ignoreKeyForCorrection(key)) {
161 newKey = COMBINED_SWITCHERS.apply(key);
162 newValue = COMBINED_SWITCHERS.apply(value);
163 }
164 return new Tag(newKey, newValue);
165 }
166 }
167
168 private static final StringSwitcher FORWARD_BACKWARD = new StringSwitcher("forward", "backward");
169 private static final StringSwitcher UP_DOWN = new StringSwitcher("up", "down");
170 private static final IStringSwitcher COMBINED_SWITCHERS = IStringSwitcher.combined(
171 new StringSwitcher("left", "right"),
172 new StringSwitcher("forwards", "backwards"),
173 new StringSwitcher("east", "west"),
174 new StringSwitcher("north", "south"),
175 FORWARD_BACKWARD, UP_DOWN
176 );
177
178 /**
179 * Tests whether way can be reversed without semantic change, i.e., whether tags have to be changed.
180 * Looks for keys like oneway, oneway:bicycle, cycleway:right:oneway, left/right.
181 * @param way way to test
182 * @return false if tags should be changed to keep semantic, true otherwise.
183 */
184 public static boolean isReversible(Way way) {
185 for (Tag tag : TagCollection.from(way)) {
186 if (!tag.equals(TagSwitcher.apply(tag))) {
187 return false;
188 }
189 }
190 return true;
191 }
192
193 /**
194 * Returns the subset of irreversible ways.
195 * @param ways all ways
196 * @return the subset of irreversible ways
197 * @see #isReversible(Way)
198 */
199 public static List<Way> irreversibleWays(List<Way> ways) {
200 List<Way> newWays = new ArrayList<>(ways);
201 for (Way way : ways) {
202 if (isReversible(way)) {
203 newWays.remove(way);
204 }
205 }
206 return newWays;
207 }
208
209 /**
210 * Inverts sign of a numeric value.
211 * @param value numeric value
212 * @return opposite numeric value
213 */
214 public static String invertNumber(String value) {
215 Pattern pattern = Pattern.compile("^([+-]?)(\\d.*)$", Pattern.CASE_INSENSITIVE);
216 Matcher matcher = pattern.matcher(value);
217 if (!matcher.matches()) return value;
218 String sign = matcher.group(1);
219 String rest = matcher.group(2);
220 sign = "-".equals(sign) ? "" : "-";
221 return sign + rest;
222 }
223
224 static List<TagCorrection> getTagCorrections(Tagged way) {
225 List<TagCorrection> tagCorrections = new ArrayList<>();
226 for (Map.Entry<String, String> entry : way.getKeys().entrySet()) {
227 final String key = entry.getKey();
228 final String value = entry.getValue();
229 Tag newTag = TagSwitcher.apply(key, value);
230 String newKey = newTag.getKey();
231 String newValue = newTag.getValue();
232
233 boolean needsCorrection = !key.equals(newKey);
234 if (way.get(newKey) != null && way.get(newKey).equals(newValue)) {
235 needsCorrection = false;
236 }
237 if (!value.equals(newValue)) {
238 needsCorrection = true;
239 }
240
241 if (needsCorrection) {
242 tagCorrections.add(new TagCorrection(key, value, newKey, newValue));
243 }
244 }
245 return tagCorrections;
246 }
247
248 static List<RoleCorrection> getRoleCorrections(Way oldway) {
249 List<RoleCorrection> roleCorrections = new ArrayList<>();
250
251 Collection<OsmPrimitive> referrers = oldway.getReferrers();
252 for (OsmPrimitive referrer: referrers) {
253 if (!(referrer instanceof Relation)) {
254 continue;
255 }
256 Relation relation = (Relation) referrer;
257 int position = 0;
258 for (RelationMember member : relation.getMembers()) {
259 if (!member.getMember().hasEqualSemanticAttributes(oldway)
260 || !member.hasRole()) {
261 position++;
262 continue;
263 }
264
265 final String newRole = COMBINED_SWITCHERS.apply(member.getRole());
266 if (!member.getRole().equals(newRole)) {
267 roleCorrections.add(new RoleCorrection(relation, position, member, newRole));
268 }
269
270 position++;
271 }
272 }
273 return roleCorrections;
274 }
275
276 static Map<OsmPrimitive, List<TagCorrection>> getTagCorrectionsMap(Way way) {
277 Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap = new HashMap<>();
278 List<TagCorrection> tagCorrections = getTagCorrections(way);
279 if (!tagCorrections.isEmpty()) {
280 tagCorrectionsMap.put(way, tagCorrections);
281 }
282 for (Node node : way.getNodes()) {
283 final List<TagCorrection> corrections = getTagCorrections(node);
284 if (!corrections.isEmpty()) {
285 tagCorrectionsMap.put(node, corrections);
286 }
287 }
288 return tagCorrectionsMap;
289 }
290
291 @Override
292 public Collection<Command> execute(Way oldway, Way way) throws UserCancelException {
293 Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap = getTagCorrectionsMap(way);
294
295 Map<OsmPrimitive, List<RoleCorrection>> roleCorrectionMap = new HashMap<>();
296 List<RoleCorrection> roleCorrections = getRoleCorrections(oldway);
297 if (!roleCorrections.isEmpty()) {
298 roleCorrectionMap.put(way, roleCorrections);
299 }
300
301 return applyCorrections(oldway.getDataSet(), tagCorrectionsMap, roleCorrectionMap,
302 tr("When reversing this way, the following changes are suggested in order to maintain data consistency."));
303 }
304
305 private static boolean ignoreKeyForCorrection(String key) {
306 for (Pattern ignoredKey : IGNORED_KEYS) {
307 if (ignoredKey.matcher(key).matches()) {
308 return true;
309 }
310 }
311 return false;
312 }
313}
Note: See TracBrowser for help on using the repository browser.