001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.corrector;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.util.ArrayList;
007 import java.util.Arrays;
008 import java.util.Collection;
009 import java.util.HashMap;
010 import java.util.List;
011 import java.util.Map;
012 import java.util.regex.Matcher;
013 import java.util.regex.Pattern;
014
015 import org.openstreetmap.josm.command.Command;
016 import org.openstreetmap.josm.data.osm.OsmPrimitive;
017 import org.openstreetmap.josm.data.osm.OsmUtils;
018 import org.openstreetmap.josm.data.osm.Relation;
019 import org.openstreetmap.josm.data.osm.RelationMember;
020 import org.openstreetmap.josm.data.osm.Way;
021
022 /**
023 * A ReverseWayTagCorrector handles necessary corrections of tags
024 * when a way is reversed. E.g. oneway=yes needs to be changed
025 * to oneway=-1 and vice versa.
026 *
027 * The Corrector offers the automatic resolution in an dialog
028 * for the user to confirm.
029 */
030
031 public class ReverseWayTagCorrector extends TagCorrector<Way> {
032
033 private static class PrefixSuffixSwitcher {
034
035 private static final String SEPARATOR = "[:_]?";
036
037 private final String a;
038 private final String b;
039 private final Pattern startPattern;
040 private final Pattern endPattern;
041
042 public PrefixSuffixSwitcher(String a, String b) {
043 this.a = a;
044 this.b = b;
045 startPattern = Pattern.compile(
046 "^(" + a + "|" + b + ")(" + SEPARATOR + "|$)",
047 Pattern.CASE_INSENSITIVE);
048 endPattern = Pattern.compile("^.*" +
049 SEPARATOR + "(" + a + "|" + b + ")$",
050 Pattern.CASE_INSENSITIVE);
051 }
052
053 public String apply(String text) {
054 Matcher m = startPattern.matcher(text);
055 if (!m.lookingAt()) {
056 m = endPattern.matcher(text);
057 }
058
059 if (m.lookingAt()) {
060 String leftRight = m.group(1).toLowerCase();
061
062 StringBuilder result = new StringBuilder();
063 result.append(text.substring(0, m.start(1)));
064 result.append(leftRight.equals(a) ? b : a);
065 result.append(text.substring(m.end(1)));
066
067 return result.toString();
068 }
069 return text;
070 }
071 }
072
073 private static PrefixSuffixSwitcher[] prefixSuffixSwitchers =
074 new PrefixSuffixSwitcher[] {
075 new PrefixSuffixSwitcher("left", "right"),
076 new PrefixSuffixSwitcher("forward", "backward"),
077 new PrefixSuffixSwitcher("forwards", "backwards"),
078 new PrefixSuffixSwitcher("up", "down"),
079 new PrefixSuffixSwitcher("east", "west"),
080 new PrefixSuffixSwitcher("north", "south"),
081 };
082
083 private static ArrayList<String> reversibleTags = new ArrayList<String>(
084 Arrays.asList(new String[] {"oneway", "incline", "direction"}));
085
086 public static boolean isReversible(Way way) {
087 for (String key : way.keySet()) {
088 if (reversibleTags.contains(key)) return false;
089 for (PrefixSuffixSwitcher prefixSuffixSwitcher : prefixSuffixSwitchers) {
090 if (!key.equals(prefixSuffixSwitcher.apply(key))) return false;
091 }
092 }
093
094 return true;
095 }
096
097 public static List<Way> irreversibleWays(List<Way> ways) {
098 List<Way> newWays = new ArrayList<Way>(ways);
099 for (Way way : ways) {
100 if (isReversible(way)) {
101 newWays.remove(way);
102 }
103 }
104 return newWays;
105 }
106
107 public String invertNumber(String value) {
108 Pattern pattern = Pattern.compile("^([+-]?)(\\d.*)$", Pattern.CASE_INSENSITIVE);
109 Matcher matcher = pattern.matcher(value);
110 if (!matcher.matches()) return value;
111 String sign = matcher.group(1);
112 String rest = matcher.group(2);
113 sign = sign.equals("-") ? "" : "-";
114 return sign + rest;
115 }
116
117 @Override
118 public Collection<Command> execute(Way oldway, Way way) throws UserCancelException {
119 Map<OsmPrimitive, List<TagCorrection>> tagCorrectionsMap =
120 new HashMap<OsmPrimitive, List<TagCorrection>>();
121
122 ArrayList<TagCorrection> tagCorrections = new ArrayList<TagCorrection>();
123 for (String key : way.keySet()) {
124 String newKey = key;
125 String value = way.get(key);
126 String newValue = value;
127
128 if (key.equals("oneway")) {
129 if (OsmUtils.isReversed(value)) {
130 newValue = OsmUtils.trueval;
131 } else if (OsmUtils.isTrue(value)) {
132 newValue = OsmUtils.reverseval;
133 }
134 } else if (key.equals("incline") || key.equals("direction")) {
135 PrefixSuffixSwitcher switcher = new PrefixSuffixSwitcher("up", "down");
136 newValue = switcher.apply(value);
137 if (newValue.equals(value)) {
138 newValue = invertNumber(value);
139 }
140 } else if (!ignoreKeyForPrefixSuffixCorrection(key)) {
141 for (PrefixSuffixSwitcher prefixSuffixSwitcher : prefixSuffixSwitchers) {
142 newKey = prefixSuffixSwitcher.apply(key);
143 if (!key.equals(newKey)) {
144 break;
145 }
146 newValue = prefixSuffixSwitcher.apply(value);
147 if (!value.equals(newValue)) {
148 break;
149 }
150 }
151 }
152
153 boolean needsCorrection = !key.equals(newKey);
154 if (way.get(newKey) != null && way.get(newKey).equals(newValue)) {
155 needsCorrection = false;
156 }
157 if (!value.equals(newValue)) {
158 needsCorrection = true;
159 }
160
161 if (needsCorrection) {
162 tagCorrections.add(new TagCorrection(key, value, newKey, newValue));
163 }
164 }
165 if (!tagCorrections.isEmpty()) {
166 tagCorrectionsMap.put(way, tagCorrections);
167 }
168
169 Map<OsmPrimitive, List<RoleCorrection>> roleCorrectionMap =
170 new HashMap<OsmPrimitive, List<RoleCorrection>>();
171 ArrayList<RoleCorrection> roleCorrections = new ArrayList<RoleCorrection>();
172
173 Collection<OsmPrimitive> referrers = oldway.getReferrers();
174 for (OsmPrimitive referrer: referrers) {
175 if (! (referrer instanceof Relation)) {
176 continue;
177 }
178 Relation relation = (Relation)referrer;
179 int position = 0;
180 for (RelationMember member : relation.getMembers()) {
181 if (!member.getMember().hasEqualSemanticAttributes(oldway)
182 || !member.hasRole()) {
183 position++;
184 continue;
185 }
186
187 boolean found = false;
188 String newRole = null;
189 for (PrefixSuffixSwitcher prefixSuffixSwitcher : prefixSuffixSwitchers) {
190 newRole = prefixSuffixSwitcher.apply(member.getRole());
191 if (!newRole.equals(member.getRole())) {
192 found = true;
193 break;
194 }
195 }
196
197 if (found) {
198 roleCorrections.add(new RoleCorrection(relation, position, member, newRole));
199 }
200
201 position++;
202 }
203 }
204 if (!roleCorrections.isEmpty()) {
205 roleCorrectionMap.put(way, roleCorrections);
206 }
207
208 return applyCorrections(tagCorrectionsMap, roleCorrectionMap,
209 tr("When reversing this way, the following changes to properties "
210 + "of the way and its nodes are suggested in order "
211 + "to maintain data consistency."));
212 }
213
214 private static boolean ignoreKeyForPrefixSuffixCorrection(String key) {
215 return key.contains("name") || key.equals("tiger:county")
216 || key.equalsIgnoreCase("fixme") || key.startsWith("note");
217 }
218 }