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

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

see #15229 - move corrector package to actions.corrector

  • Property svn:eol-style set to native
File size: 11.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions.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 FORWARD_BACKWARD, UP_DOWN
174 );
175
176 /**
177 * Tests whether way can be reversed without semantic change, i.e., whether tags have to be changed.
178 * Looks for keys like oneway, oneway:bicycle, cycleway:right:oneway, left/right.
179 * @param way way to test
180 * @return false if tags should be changed to keep semantic, true otherwise.
181 */
182 public static boolean isReversible(Way way) {
183 for (Tag tag : TagCollection.from(way)) {
184 if (!tag.equals(TagSwitcher.apply(tag))) {
185 return false;
186 }
187 }
188 return true;
189 }
190
191 /**
192 * Returns the subset of irreversible ways.
193 * @param ways all ways
194 * @return the subset of irreversible ways
195 * @see #isReversible(Way)
196 */
197 public static List<Way> irreversibleWays(List<Way> ways) {
198 List<Way> newWays = new ArrayList<>(ways);
199 for (Way way : ways) {
200 if (isReversible(way)) {
201 newWays.remove(way);
202 }
203 }
204 return newWays;
205 }
206
207 /**
208 * Inverts sign of a numeric value.
209 * @param value numeric value
210 * @return opposite numeric value
211 */
212 public static String invertNumber(String value) {
213 Pattern pattern = Pattern.compile("^([+-]?)(\\d.*)$", Pattern.CASE_INSENSITIVE);
214 Matcher matcher = pattern.matcher(value);
215 if (!matcher.matches()) return value;
216 String sign = matcher.group(1);
217 String rest = matcher.group(2);
218 sign = "-".equals(sign) ? "" : "-";
219 return sign + rest;
220 }
221
222 static List<TagCorrection> getTagCorrections(Tagged way) {
223 List<TagCorrection> tagCorrections = new ArrayList<>();
224 for (Map.Entry<String, String> entry : way.getKeys().entrySet()) {
225 final String key = entry.getKey();
226 final String value = entry.getValue();
227 Tag newTag = TagSwitcher.apply(key, value);
228 String newKey = newTag.getKey();
229 String newValue = newTag.getValue();
230
231 boolean needsCorrection = !key.equals(newKey);
232 if (way.get(newKey) != null && way.get(newKey).equals(newValue)) {
233 needsCorrection = false;
234 }
235 if (!value.equals(newValue)) {
236 needsCorrection = true;
237 }
238
239 if (needsCorrection) {
240 tagCorrections.add(new TagCorrection(key, value, newKey, newValue));
241 }
242 }
243 return tagCorrections;
244 }
245
246 static List<RoleCorrection> getRoleCorrections(Way oldway) {
247 List<RoleCorrection> roleCorrections = new ArrayList<>();
248
249 Collection<OsmPrimitive> referrers = oldway.getReferrers();
250 for (OsmPrimitive referrer: referrers) {
251 if (!(referrer instanceof Relation)) {
252 continue;
253 }
254 Relation relation = (Relation) referrer;
255 int position = 0;
256 for (RelationMember member : relation.getMembers()) {
257 if (!member.getMember().hasEqualSemanticAttributes(oldway)
258 || !member.hasRole()) {
259 position++;
260 continue;
261 }
262
263 final String newRole = COMBINED_SWITCHERS.apply(member.getRole());
264 if (!member.getRole().equals(newRole)) {
265 roleCorrections.add(new RoleCorrection(relation, position, member, newRole));
266 }
267
268 position++;
269 }
270 }
271 return roleCorrections;
272 }
273
274 static Map<OsmPrimitive, List<TagCorrection>> getTagCorrectionsMap(Way way) {
275 Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap = new HashMap<>();
276 List<TagCorrection> tagCorrections = getTagCorrections(way);
277 if (!tagCorrections.isEmpty()) {
278 tagCorrectionsMap.put(way, tagCorrections);
279 }
280 for (Node node : way.getNodes()) {
281 final List<TagCorrection> corrections = getTagCorrections(node);
282 if (!corrections.isEmpty()) {
283 tagCorrectionsMap.put(node, corrections);
284 }
285 }
286 return tagCorrectionsMap;
287 }
288
289 @Override
290 public Collection<Command> execute(Way oldway, Way way) throws UserCancelException {
291 Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap = getTagCorrectionsMap(way);
292
293 Map<OsmPrimitive, List<RoleCorrection>> roleCorrectionMap = new HashMap<>();
294 List<RoleCorrection> roleCorrections = getRoleCorrections(oldway);
295 if (!roleCorrections.isEmpty()) {
296 roleCorrectionMap.put(way, roleCorrections);
297 }
298
299 return applyCorrections(oldway.getDataSet(), tagCorrectionsMap, roleCorrectionMap,
300 tr("When reversing this way, the following changes are suggested in order to maintain data consistency."));
301 }
302
303 private static boolean ignoreKeyForCorrection(String key) {
304 for (Pattern ignoredKey : IGNORED_KEYS) {
305 if (ignoredKey.matcher(key).matches()) {
306 return true;
307 }
308 }
309 return false;
310 }
311}
Note: See TracBrowser for help on using the repository browser.