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 }