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

Last change on this file since 12518 was 12412, checked in by michael2402, 7 years ago

Fix #14946: Set the right data set for tag corrections when reversing a way.

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