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

Last change on this file since 10621 was 10125, checked in by Don-vip, 8 years ago

refactor classes from corrector package, add javadoc

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