package de.blau.android.osm; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; /** * Logic for reversing direction dependent tags, this is one of the more arcane things about the OSM data model * @author simon * */ class Reverse { /** * Return the direction dependent tags and associated values * oneway, *:left, *:right, *:backward, *:forward * Probably we should check for issues with relation membership too * @return */ public static Map<String, String> getDirectionDependentTags(OsmElement e) { Map<String, String> result = null; Map<String, String> tags = e.getTags(); if (tags != null) { for (String key : tags.keySet()) { String value = tags.get(key); if ("oneway".equals(key) || "incline".equals(key) || "turn".equals(key) || "turn:lanes".equals(key) || "direction".equals(key) || key.endsWith(":left") || key.endsWith(":right") || key.endsWith(":backward") || key.endsWith(":forward") || key.contains(":forward:") || key.contains(":backward:") || key.contains(":right:") || key.contains(":left:") || value.equals("right") || value.equals("left") || value.equals("forward") || value.equals("backward")) { if (result == null) { result = new TreeMap<String, String>(); } result.put(key, value); } } } return result; } /** * Return a list of (route) relations that this way is a member of with a direction dependent role * @return */ public static List<Relation> getRelationsWithDirectionDependentRoles(OsmElement e) { ArrayList<Relation> result = null; ArrayList<Relation> parents = e.getParentRelations(); if (parents != null) { for (Relation r:parents) { String t = r.getTagWithKey(Tags.KEY_TYPE); if (t != null && Tags.VALUE_ROUTE.equals(t)) { RelationMember rm = r.getMember(Way.NAME, e.getOsmId()); if (rm != null && ("forward".equals(rm.getRole()) || "backward".equals(rm.getRole()))) { if (result == null) { result = new ArrayList<Relation>(); } result.add(r); } } } } return result; } /** * Reverse the role of this element in any relations it is in (currently only relevant for routes) * @param relations */ public static void reverseRoleDirection(OsmElement e, List<Relation> relations) { if (relations != null) { for (Relation r:relations) { for (RelationMember rm:r.getAllMembers(e)) { if (rm.role != null && "forward".equals(rm.role)) { rm.setRole("backward"); continue; } if (rm.role != null && "backward".equals(rm.role)) { rm.setRole("forward"); continue; } } } } } private static String reverseCardinalDirection(final String value) throws NumberFormatException { String tmpVal = ""; for (int i=0;i<value.length();i++) { switch (value.toUpperCase(Locale.US).charAt(i)) { case 'N': tmpVal = tmpVal + 'S'; break; case 'W': tmpVal = tmpVal + 'E'; break; case 'S': tmpVal = tmpVal + 'N'; break; case 'E': tmpVal = tmpVal + 'W'; break; default: throw new NumberFormatException(); } } return tmpVal; } private static String reverseDirection(final String value) { if (value.equals("up")) { return "down"; } else if (value.equals("down")) { return "up"; } else { if (value.endsWith("°")) { //degrees try { String tmpVal = value.substring(0,value.length()-1); return floatToString(((Float.valueOf(tmpVal)+180.0f) % 360.0f)) + "°"; } catch (NumberFormatException nex) { // oops put back original values return value; } } else if (value.matches("-?\\d+(\\.\\d+)?")) { //degrees without degree symbol try { return floatToString(((Float.valueOf(value)+180.0f) % 360.0f)); } catch (NumberFormatException nex) { // oops put back original values return value; } } else { // cardinal directions try { return reverseCardinalDirection(value); } catch (NumberFormatException fex) { return value; } } } } private static String reverseTurnLanes(final String value) { String tmpValue = ""; for (String s:value.split("\\|")) { String tmpValue2 = ""; for (String s2:s.split(";")) { if (s2.indexOf("right") >= 0) { s2 = s2.replace("right", "left"); } else if (s.indexOf("left") >= 0) { s2 = s2.replace("left", "right"); } if (tmpValue2.equals("")) { tmpValue2 = s2; } else { tmpValue2 = s2 + ";" + tmpValue2; // reverse order } } if (tmpValue.equals("")) { tmpValue = tmpValue2; } else { tmpValue = tmpValue2 + "|" + tmpValue; // reverse order } } return tmpValue; } private static String reverseIncline(final String value) { String tmpVal; if (value.equals("up")) { return "down"; } else if (value.equals("down")) { return "up"; } else { try { if (value.endsWith("°")) { //degrees tmpVal = value.substring(0,value.length()-1); return floatToString((Float.valueOf(tmpVal)*-1)) + "°"; } else if (value.endsWith("%")) { // percent{ tmpVal = value.substring(0,value.length()-1); return floatToString((Float.valueOf(tmpVal)*-1)) + "%"; } else { return floatToString((Float.valueOf(value)*-1)); } } catch (NumberFormatException nex) { // oops put back original values return value; } } } private static String reverseOneway(final String value) { if (value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("true") || value.equals("1")) { return "-1"; } else if (value.equalsIgnoreCase("reverse") || value.equals("-1")) { return "yes"; } return value; } /** * Reverse the direction dependent tags and save them to tags * Note this code in its original version ran in to complexity limits on Android 2.2 (and probably older). Eliminating if .. else if constructs seems to have * resolved this * @param tags Map of all direction dependent tags * @param reverseOneway if false don't change the value of the oneway tag if present */ public static void reverseDirectionDependentTags(OsmElement e, Map<String, String> dirTags, boolean reverseOneway) { if (e.getTags() == null) { return; } Map<String, String> tags = new TreeMap<String,String>(e.getTags()); for (String key : dirTags.keySet()) { if (!key.equals("oneway") || reverseOneway) { String value = tags.get(key).trim(); tags.remove(key); // if (key.equals("oneway")) { tags.put(key, reverseOneway(value)); continue; } if (key.equals("direction")) { tags.put(key, reverseDirection(value)); continue; } if (key.equals("incline")) { tags.put(key, reverseIncline(value)); continue; } if (key.equals("turn:lanes") || key.equals("turn")) { // turn:lane:forward/backward doesn't need to be handled special tags.put(key,reverseTurnLanes(value)); continue; } if (key.endsWith(":left")) { // this would be more elegant in a loop tags.put(key.substring(0, key.length()-5) + ":right", value); continue; } if (key.endsWith(":right")) { tags.put(key.substring(0, key.length()-6) + ":left", value); continue; } if (key.endsWith(":backward")) { tags.put(key.substring(0, key.length()-9) + ":forward", value); continue; } if (key.endsWith(":forward")) { tags.put(key.substring(0, key.length()-8) + ":backward", value); continue; } if (key.indexOf(":forward:") >= 0) { tags.put(key.replace(":forward:", ":backward:"), value); continue; } if (key.indexOf(":backward:") >= 0) { tags.put(key.replace(":backward:", ":forward:"), value); continue; } if (key.indexOf(":right:") >= 0) { tags.put(key.replace(":right:", ":left:"), value); continue; } if (key.indexOf(":left:") >= 0) { tags.put(key.replace(":left:", ":right:"), value); continue; } if (value.equals("right")) { // doing this for all values is probably dangerous tags.put(key, "left"); continue; } if (value.equals("left")) { tags.put(key, "right"); continue; } if (value.equals("forward")) { tags.put(key, "backward"); continue; } if (value.equals("backward")) { tags.put(key, "forward"); continue; } // can't happen should throw an exception tags.put(key,value); } } e.setTags(tags); } private static String floatToString(float f) { if(f == (int) f) return String.format(Locale.US, "%d",(int)f); else return String.format(Locale.US,"%s",f); } }