package net.osmand.osm;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.osmand.PlatformUtil;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* reference : http://wiki.openstreetmap.org/wiki/Map_Features
*/
public abstract class MapRenderingTypes {
private static final Log log = PlatformUtil.getLog(MapRenderingTypes.class);
public static final String[] langs = new String[] { "af", "als", "ar", "az", "be", "bg", "bn", "bpy", "br", "bs", "ca", "ceb", "cs", "cy", "da", "de", "el", "eo", "es", "et", "eu", "fa", "fi", "fr", "fy", "ga", "gl", "he", "hi", "hsb",
"hr", "ht", "hu", "hy", "id", "is", "it", "ja", "ka", "ko", "ku", "la", "lb", "lt", "lv", "mk", "ml", "mr", "ms", "nds", "new", "nl", "nn", "no", "nv", "os", "pl", "pms", "pt", "ro", "ru", "sc", "sh", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", "tl", "tr", "uk", "vi", "vo", "zh" };
public final static byte RESTRICTION_NO_RIGHT_TURN = 1;
public final static byte RESTRICTION_NO_LEFT_TURN = 2;
public final static byte RESTRICTION_NO_U_TURN = 3;
public final static byte RESTRICTION_NO_STRAIGHT_ON = 4;
public final static byte RESTRICTION_ONLY_RIGHT_TURN = 5;
public final static byte RESTRICTION_ONLY_LEFT_TURN = 6;
public final static byte RESTRICTION_ONLY_STRAIGHT_ON = 7;
private static char TAG_DELIMETER = '/'; //$NON-NLS-1$
private String resourceName = null;
protected Map<String, MapRulType> types = null;
protected List<MapRulType> typeList = new ArrayList<MapRulType>();
protected MapRulType nameRuleType;
protected MapRulType nameEnRuleType;
public MapRenderingTypes(String fileName){
this.resourceName = fileName;
}
public Map<String, MapRulType> getEncodingRuleTypes(){
checkIfInitNeeded();
return types;
}
protected void checkIfInitNeeded() {
if(types == null) {
types = new LinkedHashMap<String, MapRulType>();
typeList.clear();
nameRuleType = MapRulType.createText("name");
nameRuleType.order = 40;
registerRuleType(nameRuleType);
nameEnRuleType = MapRulType.createText("name:en");
nameEnRuleType.order = 45;
registerRuleType(nameEnRuleType);
init();
}
}
public static Collection<Map<String, String>> splitTagsIntoDifferentObjects(final Map<String, String> tags) {
// check open sea maps tags
boolean split = splitIsNeeded(tags);
if(!split) {
return Collections.singleton(tags);
} else {
return splitOpenSeaMapsTags(tags);
}
}
protected static boolean splitIsNeeded(final Map<String, String> tags) {
boolean seamark = false;
for(String s : tags.keySet()) {
if(s.startsWith("seamark:")) {
seamark = true;
break;
}
}
return seamark;
}
private static Collection<Map<String, String>> splitOpenSeaMapsTags(final Map<String, String> tags) {
Map<String, Map<String, String>> groupByOpenSeamaps = new HashMap<String, Map<String, String>>();
Map<String, String> common = new HashMap<String, String>();
String ATTACHED_KEY = "seamark:attached";
String type = "";
for (String s : tags.keySet()) {
String value = tags.get(s);
if (s.equals("seamark:type")) {
type = value;
common.put(ATTACHED_KEY, openSeaType(value));
} else if (s.startsWith("seamark:")) {
String stype = s.substring("seamark:".length());
int ind = stype.indexOf(':');
if (ind == -1) {
common.put(s, value);
} else {
String group = openSeaType(stype.substring(0, ind));
String add = stype.substring(ind + 1);
if (!groupByOpenSeamaps.containsKey(group)) {
groupByOpenSeamaps.put(group, new HashMap<String, String>());
}
groupByOpenSeamaps.get(group).put("seamark:" + add, value);
}
} else {
common.put(s, value);
}
}
List<Map<String, String>> res = new ArrayList<Map<String,String>>();
for (Entry<String, Map<String, String>> g : groupByOpenSeamaps.entrySet()) {
g.getValue().putAll(common);
g.getValue().put("seamark", g.getKey());
if (openSeaType(type).equals(g.getKey())) {
g.getValue().remove(ATTACHED_KEY);
g.getValue().put("seamark", type);
res.add(0, g.getValue());
} else {
res.add(g.getValue());
}
}
return res;
}
private static String openSeaType(String value) {
if(value.equals("light_major") || value.equals("light_minor")) {
return "light";
}
return value;
}
public MapRulType getTypeByInternalId(int id) {
return typeList.get(id);
}
private String lc(String a) {
if(a != null) {
return a.toLowerCase();
}
return a;
}
protected MapRulType getRuleType(String tag, String val, boolean poi, boolean map) {
Map<String, MapRulType> types = getEncodingRuleTypes();
tag = lc(tag);
val = lc(val);
MapRulType rType = types.get(constructRuleKey(tag, val));
if (rType == null || (!rType.isPOI() && poi) || (!rType.isMap() && map)) {
rType = types.get(constructRuleKey(tag, null));
}
if(rType == null || (!rType.isPOI() && poi) || (!rType.isMap() && map)) {
return null;
} else if(rType.isAdditional() && rType.tagValuePattern.value == null) {
MapRulType parent = rType;
rType = MapRulType.createAdditional(tag, val);
rType.additional = true;
rType.order = parent.order;
rType.map = parent.map;
rType.poi = parent.poi;
rType.onlyPoint = parent.onlyPoint;
rType.namePrefix = parent.namePrefix;
rType = registerRuleType(rType);
}
return rType;
}
public MapRulType getNameRuleType() {
getEncodingRuleTypes();
return nameRuleType;
}
public MapRulType getNameEnRuleType() {
getEncodingRuleTypes();
return nameEnRuleType;
}
protected void init(){
InputStream is;
try {
if(resourceName == null){
is = MapRenderingTypes.class.getResourceAsStream("rendering_types.xml"); //$NON-NLS-1$
} else {
is = new FileInputStream(resourceName);
}
long time = System.currentTimeMillis();
XmlPullParser parser = PlatformUtil.newXMLPullParser();
int tok;
parser.setInput(is, "UTF-8");
MapRulType parentCategory = null;
while ((tok = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (tok == XmlPullParser.START_TAG) {
String name = parser.getName();
if (name.equals("category")) { //$NON-NLS-1$
parentCategory = parseCategoryFromXml(parser);
} else if (name.equals("type")) {
parseAndRegisterTypeFromXML(parser, parentCategory);
} else if (name.equals("routing_type")) {
parseRouteTagFromXML(parser);
} else if (name.equals("entity_convert")) {
parseEntityConvertXML(parser);
}
}
}
log.info("Time to init " + (System.currentTimeMillis() - time)); //$NON-NLS-1$
is.close();
} catch (IOException e) {
log.error("Unexpected error", e); //$NON-NLS-1$
e.printStackTrace();
throw new RuntimeException(e);
} catch (RuntimeException e) {
log.error("Unexpected error", e); //$NON-NLS-1$
e.printStackTrace();
throw e;
} catch (XmlPullParserException e) {
log.error("Unexpected error", e); //$NON-NLS-1$
e.printStackTrace();
throw new RuntimeException(e);
}
}
protected abstract void parseEntityConvertXML(XmlPullParser parser);
protected abstract void parseRouteTagFromXML(XmlPullParser parser);
protected abstract void parseAndRegisterTypeFromXML(XmlPullParser parser, MapRulType parentCategory) ;
protected MapRulType parseBaseRuleType(XmlPullParser parser, MapRulType parentCategory, String tag) {
String value = lc(parser.getAttributeValue("", "value"));
String additional = parser.getAttributeValue("", "additional");
if (value != null && value.length() == 0) { //$NON-NLS-1$
value = null;
}
MapRulType rtype = MapRulType.createMainEntity(tag, value);
if("true".equals(additional)) {
rtype = MapRulType.createAdditional(tag, value);
} else if("text".equals(additional)) {
rtype = MapRulType.createText(tag);
}
rtype.map = "true".equals(parser.getAttributeValue("", "map")) ||
"yes".equals(parser.getAttributeValue("", "map")) || parser.getAttributeValue("", "map") == null;
rtype.poi = "true".equals(parser.getAttributeValue("", "poi")) ||
"yes".equals(parser.getAttributeValue("", "poi")) || parser.getAttributeValue("", "poi") == null;
String order = parser.getAttributeValue("", "order");
if(!Algorithms.isEmpty(order)) {
rtype.order = Integer.parseInt(order);
} else if (parentCategory != null) {
rtype.order = parentCategory.order;
}
rtype.category = parentCategory == null ? null : parentCategory.category;
rtype.onlyPoint = Boolean.parseBoolean(parser.getAttributeValue("", "point")); //$NON-NLS-1$
rtype.relation = Boolean.parseBoolean(parser.getAttributeValue("", "relation")); //$NON-NLS-1$
rtype.relationGroup = Boolean.parseBoolean(parser.getAttributeValue("", "relationGroup")); //$NON-NLS-1$
if (rtype.isMain()) {
rtype.namePrefix = parser.getAttributeValue("", "namePrefix"); //$NON-NLS-1$
if (rtype.namePrefix == null) {
rtype.namePrefix = "";
}
String v = parser.getAttributeValue("", "nameTags");
if (v != null) {
String[] names = v.split(",");
rtype.names = new MapRulType[names.length];
for (int i = 0; i < names.length; i++) {
String tagName = names[i];
if (rtype.namePrefix.length() > 0) {
tagName = rtype.namePrefix + tagName;
}
MapRulType mt = MapRulType.createText(tagName);
mt = registerRuleType(mt);
rtype.names[i] = mt;
}
}
}
return rtype;
}
protected MapRulType registerRuleType(MapRulType rt) {
String tag = rt.tagValuePattern.tag;
String val = rt.tagValuePattern.value;
String keyVal = constructRuleKey(tag, val);
if(types.containsKey(keyVal)){
MapRulType mapRulType = types.get(keyVal);
if(mapRulType.isAdditional() || mapRulType.isText() ) {
rt.id = mapRulType.id;
if(rt.isMain()) {
mapRulType.main = true;
if(rt.minzoom != 0) {
mapRulType.minzoom = Math.max(rt.minzoom, mapRulType.minzoom);
}
if(rt.maxzoom != 0) {
mapRulType.maxzoom = Math.min(rt.maxzoom, mapRulType.maxzoom);
}
}
// types.put(keyVal, rt);
// typeList.set(rt.id, rt);
return mapRulType;
} else {
throw new RuntimeException("Duplicate " + keyVal);
}
} else {
rt.id = types.size();
types.put(keyVal, rt);
typeList.add(rt);
return rt;
}
}
protected MapRulType parseCategoryFromXml(XmlPullParser parser) {
MapRulType rtype = new MapRulType();
rtype.category = parser.getAttributeValue("", "name");
if (!Algorithms.isEmpty(parser.getAttributeValue("", "order"))) {
rtype.order = Integer.parseInt(parser.getAttributeValue("", "order"));
}
return rtype;
}
protected static String constructRuleKey(String tag, String val) {
if(val == null || val.length() == 0){
return tag;
}
return tag + TAG_DELIMETER + val;
}
protected static String getTagKey(String tagValue) {
int i = tagValue.indexOf(TAG_DELIMETER);
if(i >= 0){
return tagValue.substring(0, i);
}
return tagValue;
}
protected static String getValueKey(String tagValue) {
int i = tagValue.indexOf(TAG_DELIMETER);
if(i >= 0){
return tagValue.substring(i + 1);
}
return null;
}
protected static class TagValuePattern {
protected String tag;
protected String value;
protected int substrSt = 0;
protected int substrEnd = 0;
protected TagValuePattern(String t, String v) {
this.tag = t;
this.value = v;
if(tag == null && value == null) {
throw new IllegalStateException("Tag/value null should be handled differently");
}
if(tag == null) {
throw new UnsupportedOperationException();
}
}
public boolean isApplicable(Map<String, String> e ){
if(value == null) {
return e.get(tag) != null;
}
return value.equals(e.get(tag));
}
@Override
public String toString() {
return "tag="+tag + " val=" +value;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((tag == null) ? 0 : tag.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TagValuePattern other = (TagValuePattern) obj;
if (tag == null) {
if (other.tag != null)
return false;
} else if (!tag.equals(other.tag))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
public static class MapRulType {
protected MapRulType[] names;
protected TagValuePattern tagValuePattern;
protected boolean additional;
protected boolean additionalText;
protected boolean main;
protected int order = 50;
protected String category = null;
protected boolean relation;
protected boolean relationGroup;
// creation of only section
protected boolean map = true;
protected boolean poi = true;
// Needed only for map rules
protected int minzoom;
protected int maxzoom;
protected boolean onlyPoint;
protected String namePrefix ="";
// inner id
protected int id = -1;
protected int freq;
protected int targetId ;
protected int targetPoiId = -1;
private MapRulType(){
}
public boolean isPOI(){
return poi;
}
public boolean isMap(){
return map;
}
public int getOrder() {
return order;
}
public static MapRulType createMainEntity(String tag, String value) {
MapRulType rt = new MapRulType();
rt.tagValuePattern = new TagValuePattern(tag, value);
rt.main = true;
return rt;
}
public static MapRulType createText(String tag) {
MapRulType rt = new MapRulType();
rt.additionalText = true;
rt.minzoom = 2;
rt.maxzoom = 31;
rt.tagValuePattern = new TagValuePattern(tag, null);
return rt;
}
public static MapRulType createAdditional(String tag, String value) {
MapRulType rt = new MapRulType();
rt.additional = true;
rt.minzoom = 2;
rt.maxzoom = 31;
rt.tagValuePattern = new TagValuePattern(tag, value);
return rt;
}
public String getTag() {
return tagValuePattern.tag;
}
public int getTargetId() {
return targetId;
}
public int getTargetPoiId() {
return targetPoiId;
}
public void setTargetPoiId(int catId, int valueId) {
if(catId <= 31) {
this.targetPoiId = (valueId << 6) | (catId << 1) ;
} else {
if(catId > (1 << 15)) {
throw new IllegalArgumentException("Refer source code");
}
this.targetPoiId = (valueId << 16) | (catId << 1) | 1;
}
}
public int getInternalId() {
return id;
}
public void setTargetId(int targetId) {
this.targetId = targetId;
}
public String getValue() {
return tagValuePattern.value;
}
public int getMinzoom() {
return minzoom;
}
public boolean isAdditional() {
return additional;
}
public boolean isAdditionalOrText() {
return additional || additionalText;
}
public boolean isMain() {
return main;
}
public boolean isText() {
return additionalText;
}
public boolean isOnlyPoint() {
return onlyPoint;
}
public boolean isRelation() {
return relation;
}
public boolean isRelationGroup() {
return relationGroup;
}
public int getFreq() {
return freq;
}
public int updateFreq(){
return ++freq;
}
@Override
public String toString() {
return getTag() + " " + getValue();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MapRulType other = (MapRulType) obj;
if (id != other.id || id < 0)
return false;
return true;
}
}
public static String getRestrictionValue(int i) {
switch (i) {
case RESTRICTION_NO_RIGHT_TURN:
return "NO_RIGHT_TURN".toLowerCase();
case RESTRICTION_NO_LEFT_TURN:
return "NO_LEFT_TURN".toLowerCase();
case RESTRICTION_NO_U_TURN:
return "NO_U_TURN".toLowerCase();
case RESTRICTION_NO_STRAIGHT_ON:
return "NO_STRAIGHT_ON".toLowerCase();
case RESTRICTION_ONLY_RIGHT_TURN:
return "ONLY_RIGHT_TURN".toLowerCase();
case RESTRICTION_ONLY_LEFT_TURN:
return "ONLY_LEFT_TURN".toLowerCase();
case RESTRICTION_ONLY_STRAIGHT_ON:
return "ONLY_STRAIGHT_ON".toLowerCase();
}
return "unkonwn";
}
}