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

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

javadoc

  • Property svn:eol-style set to native
File size: 11.2 KB
RevLine 
[729]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;
[764]7import java.util.Collection;
8import java.util.HashMap;
9import java.util.List;
[8404]10import java.util.Locale;
[764]11import java.util.Map;
[11061]12import java.util.function.Function;
[729]13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
[1000]16import org.openstreetmap.josm.command.Command;
[10125]17import org.openstreetmap.josm.data.correction.RoleCorrection;
18import org.openstreetmap.josm.data.correction.TagCorrection;
[11061]19import org.openstreetmap.josm.data.osm.Node;
[764]20import org.openstreetmap.josm.data.osm.OsmPrimitive;
[729]21import org.openstreetmap.josm.data.osm.OsmUtils;
[1000]22import org.openstreetmap.josm.data.osm.Relation;
23import org.openstreetmap.josm.data.osm.RelationMember;
[5787]24import org.openstreetmap.josm.data.osm.Tag;
25import org.openstreetmap.josm.data.osm.TagCollection;
[9539]26import org.openstreetmap.josm.data.osm.Tagged;
[729]27import org.openstreetmap.josm.data.osm.Way;
[8919]28import org.openstreetmap.josm.tools.UserCancelException;
[729]29
[3210]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 */
[729]38public class ReverseWayTagCorrector extends TagCorrector<Way> {
39
[5787]40 private static final String SEPARATOR = "[:_]";
[729]41
[8512]42 private static Pattern getPatternFor(String s) {
[5787]43 return getPatternFor(s, false);
44 }
[2676]45
[8512]46 private static Pattern getPatternFor(String s, boolean exactMatch) {
[5787]47 if (exactMatch) {
48 return Pattern.compile("(^)(" + s + ")($)");
49 } else {
50 return Pattern.compile("(^|.*" + SEPARATOR + ")(" + s + ")(" + SEPARATOR + ".*|$)",
51 Pattern.CASE_INSENSITIVE);
52 }
53 }
[6069]54
[12537]55 private static final Collection<Pattern> IGNORED_KEYS = new ArrayList<>();
[5787]56 static {
57 for (String s : OsmPrimitive.getUninterestingKeys()) {
[12537]58 IGNORED_KEYS.add(getPatternFor(s));
[5787]59 }
60 for (String s : new String[]{"name", "ref", "tiger:county"}) {
[12537]61 IGNORED_KEYS.add(getPatternFor(s, false));
[5787]62 }
63 for (String s : new String[]{"tiger:county", "turn:lanes", "change:lanes", "placement"}) {
[12537]64 IGNORED_KEYS.add(getPatternFor(s, true));
[5787]65 }
66 }
[6069]67
[11061]68 private interface IStringSwitcher extends Function<String, String> {
[5787]69
[11061]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
[1169]85 private final String a;
86 private final String b;
[5777]87 private final Pattern pattern;
[729]88
[8836]89 StringSwitcher(String a, String b) {
[1000]90 this.a = a;
91 this.b = b;
[8846]92 this.pattern = getPatternFor(a + '|' + b);
[1169]93 }
[764]94
[11061]95 @Override
[1169]96 public String apply(String text) {
[5777]97 Matcher m = pattern.matcher(text);
[764]98
[1169]99 if (m.lookingAt()) {
[8404]100 String leftRight = m.group(2).toLowerCase(Locale.ENGLISH);
[764]101
[1169]102 StringBuilder result = new StringBuilder();
[8379]103 result.append(text.substring(0, m.start(2)))
104 .append(leftRight.equals(a) ? b : a)
105 .append(text.substring(m.end(2)));
[764]106
[1169]107 return result.toString();
108 }
109 return text;
110 }
111 }
[764]112
[5787]113 /**
114 * Reverses a given tag.
115 * @since 5787
116 */
[6987]117 public static final class TagSwitcher {
[6069]118
[6986]119 private TagSwitcher() {
120 // Hide implicit public constructor for utility class
121 }
122
[5787]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 */
[8374]128 public static Tag apply(final Tag tag) {
[5787]129 return apply(tag.getKey(), tag.getValue());
130 }
[6069]131
[5787]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 */
[8374]138 public static Tag apply(final String key, final String value) {
[5787]139 String newKey = key;
140 String newValue = value;
141
142 if (key.startsWith("oneway") || key.endsWith("oneway")) {
143 if (OsmUtils.isReversed(value)) {
[12187]144 newValue = OsmUtils.TRUE_VALUE;
[5787]145 } else if (OsmUtils.isTrue(value)) {
[12187]146 newValue = OsmUtils.REVERSE_VALUE;
[5787]147 }
[11061]148 newKey = COMBINED_SWITCHERS.apply(key);
149 } else if (key.startsWith("incline") || key.endsWith("incline")) {
[5787]150 newValue = UP_DOWN.apply(value);
151 if (newValue.equals(value)) {
152 newValue = invertNumber(value);
153 }
[11061]154 } else if (key.startsWith("direction") || key.endsWith("direction")) {
155 newValue = COMBINED_SWITCHERS.apply(value);
[5787]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)) {
[11061]160 newKey = COMBINED_SWITCHERS.apply(key);
161 newValue = COMBINED_SWITCHERS.apply(value);
[5787]162 }
163 return new Tag(newKey, newValue);
164 }
165 }
[6069]166
[5787]167 private static final StringSwitcher FORWARD_BACKWARD = new StringSwitcher("forward", "backward");
168 private static final StringSwitcher UP_DOWN = new StringSwitcher("up", "down");
[11061]169 private static final IStringSwitcher COMBINED_SWITCHERS = IStringSwitcher.combined(
[5787]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
[11061]175 );
[764]176
[5599]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.
[8470]180 * @param way way to test
[5599]181 * @return false if tags should be changed to keep semantic, true otherwise.
182 */
[2573]183 public static boolean isReversible(Way way) {
[5787]184 for (Tag tag : TagCollection.from(way)) {
185 if (!tag.equals(TagSwitcher.apply(tag))) {
186 return false;
[5599]187 }
[2573]188 }
189 return true;
190 }
191
[13158]192 /**
193 * Returns the subset of irreversible ways.
194 * @param ways all ways
195 * @return the subset of irreversible ways
196 * @see #isReversible(Way)
197 */
[2573]198 public static List<Way> irreversibleWays(List<Way> ways) {
[7005]199 List<Way> newWays = new ArrayList<>(ways);
[2573]200 for (Way way : ways) {
201 if (isReversible(way)) {
202 newWays.remove(way);
203 }
204 }
205 return newWays;
206 }
207
[13158]208 /**
209 * Inverts sign of a numeric value.
210 * @param value numeric value
211 * @return opposite numeric value
212 */
[5787]213 public static String invertNumber(String value) {
[2683]214 Pattern pattern = Pattern.compile("^([+-]?)(\\d.*)$", Pattern.CASE_INSENSITIVE);
215 Matcher matcher = pattern.matcher(value);
216 if (!matcher.matches()) return value;
217 String sign = matcher.group(1);
218 String rest = matcher.group(2);
[6990]219 sign = "-".equals(sign) ? "" : "-";
[2683]220 return sign + rest;
221 }
222
[9539]223 static List<TagCorrection> getTagCorrections(Tagged way) {
[7005]224 List<TagCorrection> tagCorrections = new ArrayList<>();
[11061]225 for (Map.Entry<String, String> entry : way.getKeys().entrySet()) {
226 final String key = entry.getKey();
227 final String value = entry.getValue();
[5787]228 Tag newTag = TagSwitcher.apply(key, value);
229 String newKey = newTag.getKey();
230 String newValue = newTag.getValue();
[764]231
[3413]232 boolean needsCorrection = !key.equals(newKey);
233 if (way.get(newKey) != null && way.get(newKey).equals(newValue)) {
234 needsCorrection = false;
235 }
236 if (!value.equals(newValue)) {
237 needsCorrection = true;
238 }
239
240 if (needsCorrection) {
[2683]241 tagCorrections.add(new TagCorrection(key, value, newKey, newValue));
[1169]242 }
243 }
[9539]244 return tagCorrections;
245 }
[1000]246
[9539]247 static List<RoleCorrection> getRoleCorrections(Way oldway) {
[7005]248 List<RoleCorrection> roleCorrections = new ArrayList<>();
[1000]249
[2611]250 Collection<OsmPrimitive> referrers = oldway.getReferrers();
251 for (OsmPrimitive referrer: referrers) {
[8443]252 if (!(referrer instanceof Relation)) {
[2611]253 continue;
254 }
[8510]255 Relation relation = (Relation) referrer;
[1614]256 int position = 0;
[1925]257 for (RelationMember member : relation.getMembers()) {
[1938]258 if (!member.getMember().hasEqualSemanticAttributes(oldway)
[1930]259 || !member.hasRole()) {
[1614]260 position++;
[1169]261 continue;
[1614]262 }
[1000]263
[11061]264 final String newRole = COMBINED_SWITCHERS.apply(member.getRole());
265 if (!member.getRole().equals(newRole)) {
[2683]266 roleCorrections.add(new RoleCorrection(relation, position, member, newRole));
[1762]267 }
[1614]268
269 position++;
[1169]270 }
271 }
[9539]272 return roleCorrections;
273 }
274
[11061]275 static Map<OsmPrimitive, List<TagCorrection>> getTagCorrectionsMap(Way way) {
[9539]276 Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap = new HashMap<>();
[12187]277 List<TagCorrection> tagCorrections = getTagCorrections(way);
[9539]278 if (!tagCorrections.isEmpty()) {
279 tagCorrectionsMap.put(way, tagCorrections);
280 }
[11061]281 for (Node node : way.getNodes()) {
282 final List<TagCorrection> corrections = getTagCorrections(node);
283 if (!corrections.isEmpty()) {
284 tagCorrectionsMap.put(node, corrections);
285 }
286 }
287 return tagCorrectionsMap;
288 }
[9539]289
[11061]290 @Override
291 public Collection<Command> execute(Way oldway, Way way) throws UserCancelException {
292 Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap = getTagCorrectionsMap(way);
293
[9539]294 Map<OsmPrimitive, List<RoleCorrection>> roleCorrectionMap = new HashMap<>();
295 List<RoleCorrection> roleCorrections = getRoleCorrections(oldway);
[2683]296 if (!roleCorrections.isEmpty()) {
297 roleCorrectionMap.put(way, roleCorrections);
298 }
[1169]299
[12412]300 return applyCorrections(oldway.getDataSet(), tagCorrectionsMap, roleCorrectionMap,
[6326]301 tr("When reversing this way, the following changes are suggested in order to maintain data consistency."));
[1169]302 }
[4345]303
[5787]304 private static boolean ignoreKeyForCorrection(String key) {
[12537]305 for (Pattern ignoredKey : IGNORED_KEYS) {
[5787]306 if (ignoredKey.matcher(key).matches()) {
307 return true;
308 }
309 }
310 return false;
[4345]311 }
[729]312}
Note: See TracBrowser for help on using the repository browser.