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

Last change on this file since 16438 was 16438, checked in by simon04, 4 years ago

see #19251 - Java 8: use Stream

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