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

Last change on this file since 9997 was 9539, checked in by simon04, 8 years ago

fix #10290 - Improve ReverseWayNoTagCorrector

It evaluates its own directional tags together with those defined at
OsmPrimitive#directionKeys minus the ones that can be corrected using
ReverseWayTagCorrector.

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