package net.osmand.binary; import gnu.trove.iterator.TLongObjectIterator; import gnu.trove.list.array.TIntArrayList; import gnu.trove.list.array.TLongArrayList; import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.map.hash.TLongObjectHashMap; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.List; import net.osmand.PlatformUtil; import net.osmand.ResultMatcher; import net.osmand.binary.BinaryMapIndexReader.SearchRequest; import net.osmand.binary.OsmandOdb.IdTable; import net.osmand.binary.OsmandOdb.OsmAndRoutingIndex.RouteDataBlock; import net.osmand.binary.OsmandOdb.OsmAndRoutingIndex.RouteDataBox; import net.osmand.binary.OsmandOdb.OsmAndRoutingIndex.RouteEncodingRule; import net.osmand.binary.OsmandOdb.RestrictionData; import net.osmand.binary.OsmandOdb.RouteData; import net.osmand.util.MapUtils; import net.osmand.util.OpeningHoursParser; import org.apache.commons.logging.Log; import com.google.protobuf.CodedInputStream; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.WireFormat; public class BinaryMapRouteReaderAdapter { protected static final Log LOG = PlatformUtil.getLog(BinaryMapRouteReaderAdapter.class); private static final int SHIFT_COORDINATES = 4; private static class RouteTypeCondition { String condition = ""; OpeningHoursParser.OpeningHours hours = null; float floatValue; } public static class RouteTypeRule { private final static int ACCESS = 1; private final static int ONEWAY = 2; private final static int HIGHWAY_TYPE = 3; private final static int MAXSPEED = 4; private final static int ROUNDABOUT = 5; public final static int TRAFFIC_SIGNALS = 6; public final static int RAILWAY_CROSSING = 7; private final static int LANES = 8; private final String t; private final String v; private int intValue; private float floatValue; private int type; private List conditions = null; public RouteTypeRule(String t, String v) { this.t = t.intern(); if("true".equals(v)) { v = "yes"; } if("false".equals(v)) { v = "no"; } this.v = v == null? null : v.intern(); try { analyze(); } catch(RuntimeException e) { System.err.println("Error analyzing tag/value = " + t + "/" +v); throw e; } } public String getTag() { return t; } public String getValue(){ return v; } public boolean roundabout(){ return type == ROUNDABOUT; } public int getType() { return type; } public boolean conditional() { return conditions != null; } public int onewayDirection(){ if(type == ONEWAY){ return intValue; } return 0; } public float maxSpeed(){ if(type == MAXSPEED){ if(conditions != null) { Calendar i = Calendar.getInstance(); i.setTimeInMillis(System.currentTimeMillis()); for(RouteTypeCondition c : conditions) { if(c.hours != null && c.hours.isOpenedForTime(i)) { return c.floatValue; } } } return floatValue; } return -1; } public int lanes(){ if(type == LANES){ return intValue; } return -1; } public String highwayRoad(){ if(type == HIGHWAY_TYPE){ return v; } return null; } private void analyze() { if(t.equalsIgnoreCase("oneway")){ type = ONEWAY; if("-1".equals(v) || "reverse".equals(v)) { intValue = -1; } else if("1".equals(v) || "yes".equals(v)) { intValue = 1; } else { intValue = 0; } } else if(t.equalsIgnoreCase("highway") && "traffic_signals".equals(v)){ type = TRAFFIC_SIGNALS; } else if(t.equalsIgnoreCase("railway") && ("crossing".equals(v) || "level_crossing".equals(v))){ type = RAILWAY_CROSSING; } else if(t.equalsIgnoreCase("roundabout") && v != null){ type = ROUNDABOUT; } else if(t.equalsIgnoreCase("junction") && "roundabout".equalsIgnoreCase(v)){ type = ROUNDABOUT; } else if(t.equalsIgnoreCase("highway") && v != null){ type = HIGHWAY_TYPE; } else if(t.startsWith("access") && v != null){ type = ACCESS; } else if(t.equalsIgnoreCase("maxspeed:conditional") && v != null){ conditions = new ArrayList(); String[] cts = v.split(";"); for(String c : cts) { int ch = c.indexOf('@'); if (ch > 0) { RouteTypeCondition cond = new RouteTypeCondition(); cond.floatValue = RouteDataObject.parseSpeed(c.substring(0, ch), 0); cond.condition = c.substring(ch + 1).trim(); if (cond.condition.startsWith("(") && cond.condition.endsWith(")")) { cond.condition = cond.condition.substring(1, cond.condition.length() - 1).trim(); } cond.hours = OpeningHoursParser.parseOpenedHours(cond.condition); conditions.add(cond); } } type = MAXSPEED; } else if(t.equalsIgnoreCase("maxspeed") && v != null){ type = MAXSPEED; floatValue = RouteDataObject.parseSpeed(v, 0); } else if (t.equalsIgnoreCase("lanes") && v != null) { intValue = -1; int i = 0; type = LANES; while (i < v.length() && Character.isDigit(v.charAt(i))) { i++; } if (i > 0) { intValue = Integer.parseInt(v.substring(0, i)); } } } } public static class RouteRegion extends BinaryIndexPart { public int regionsRead; List subregions = new ArrayList(); List basesubregions = new ArrayList(); List routeEncodingRules = new ArrayList(); int nameTypeRule = -1; int refTypeRule = -1; int destinationTypeRule = -1; public RouteTypeRule quickGetEncodingRule(int id) { return routeEncodingRules.get(id); } private void initRouteEncodingRule(int id, String tags, String val) { while (routeEncodingRules.size() <= id) { routeEncodingRules.add(null); } routeEncodingRules.set(id, new RouteTypeRule(tags, val)); if (tags.equals("name")) { nameTypeRule = id; } else if (tags.equals("ref")) { refTypeRule = id; } else if (tags.equals("destination")) { destinationTypeRule = id; } } public List getSubregions(){ return subregions; } public List getBaseSubregions(){ return basesubregions; } public double getLeftLongitude() { double l = 180; for(RouteSubregion s : subregions) { l = Math.min(l, MapUtils.get31LongitudeX(s.left)); } return l; } public double getRightLongitude() { double l = -180; for(RouteSubregion s : subregions) { l = Math.max(l, MapUtils.get31LongitudeX(s.right)); } return l; } public double getBottomLatitude() { double l = 90; for(RouteSubregion s : subregions) { l = Math.min(l, MapUtils.get31LatitudeY(s.bottom)); } return l; } public double getTopLatitude() { double l = -90; for(RouteSubregion s : subregions) { l = Math.max(l, MapUtils.get31LatitudeY(s.top)); } return l; } public boolean contains(int x31, int y31) { for(RouteSubregion s : subregions) { if(s.left <= x31 && s.right >= x31 && s.top <= y31 && s.bottom >= y31) { return true; } } return false; } } // Used in C++ public static class RouteSubregion { private final static int INT_SIZE = 4; public final RouteRegion routeReg; public RouteSubregion(RouteSubregion copy) { this.routeReg = copy.routeReg; this.left = copy.left; this.right = copy.right; this.top = copy.top; this.bottom = copy.bottom; this.filePointer = copy.filePointer; this.length = copy.length; } public RouteSubregion(RouteRegion routeReg) { this.routeReg = routeReg; } public int length; public int filePointer; public int left; public int right; public int top; public int bottom; public int shiftToData; public List subregions = null; public List dataObjects = null; public int getEstimatedSize(){ int shallow = 7 * INT_SIZE + 4*3; if (subregions != null) { shallow += 8; for (RouteSubregion s : subregions) { shallow += s.getEstimatedSize(); } } return shallow; } public int countSubregions(){ int cnt = 1; if (subregions != null) { for (RouteSubregion s : subregions) { cnt += s.countSubregions(); } } return cnt; } } private CodedInputStream codedIS; private final BinaryMapIndexReader map; protected BinaryMapRouteReaderAdapter(BinaryMapIndexReader map){ this.codedIS = map.codedIS; this.map = map; } private void skipUnknownField(int t) throws IOException { map.skipUnknownField(t); } private int readInt() throws IOException { return map.readInt(); } protected void readRouteIndex(RouteRegion region) throws IOException { int routeEncodingRule =1; while(true){ int t = codedIS.readTag(); int tag = WireFormat.getTagFieldNumber(t); switch (tag) { case 0: return; case OsmandOdb.OsmAndRoutingIndex.NAME_FIELD_NUMBER : region.name = codedIS.readString(); break; case OsmandOdb.OsmAndRoutingIndex.RULES_FIELD_NUMBER: { int len = codedIS.readInt32(); int oldLimit = codedIS.pushLimit(len); readRouteEncodingRule(region, routeEncodingRule++); codedIS.skipRawBytes(codedIS.getBytesUntilLimit()); codedIS.popLimit(oldLimit); } break; case OsmandOdb.OsmAndRoutingIndex.ROOTBOXES_FIELD_NUMBER : case OsmandOdb.OsmAndRoutingIndex.BASEMAPBOXES_FIELD_NUMBER :{ RouteSubregion subregion = new RouteSubregion(region); subregion.length = readInt(); subregion.filePointer = codedIS.getTotalBytesRead(); int oldLimit = codedIS.pushLimit(subregion.length); readRouteTree(subregion, null, 0, true); if(tag == OsmandOdb.OsmAndRoutingIndex.ROOTBOXES_FIELD_NUMBER) { region.subregions.add(subregion); } else { region.basesubregions.add(subregion); } codedIS.skipRawBytes(codedIS.getBytesUntilLimit()); codedIS.popLimit(oldLimit); break; } case OsmandOdb.OsmAndRoutingIndex.BLOCKS_FIELD_NUMBER : { // Finish reading file! codedIS.skipRawBytes(codedIS.getBytesUntilLimit()); break; } default: skipUnknownField(t); break; } } } private RouteDataObject readRouteDataObject(RouteRegion reg, int pleftx, int ptopy) throws IOException { RouteDataObject o = new RouteDataObject(reg); TIntArrayList pointsX = new TIntArrayList(); TIntArrayList pointsY = new TIntArrayList(); TIntArrayList types = new TIntArrayList(); List globalpointTypes = new ArrayList(); while (true) { int ts = codedIS.readTag(); int tags = WireFormat.getTagFieldNumber(ts); switch (tags) { case 0: o.pointsX = pointsX.toArray(); o.pointsY = pointsY.toArray(); o.types = types.toArray(); if(globalpointTypes.size() > 0){ o.pointTypes = new int[globalpointTypes.size()][]; for(int k=0; k 0) { types.add(codedIS.readRawVarint32()); } codedIS.popLimit(oldLimit); break; case RouteData.STRINGNAMES_FIELD_NUMBER: o.names = new TIntObjectHashMap(); int sizeL = codedIS.readRawVarint32(); int old = codedIS.pushLimit(sizeL); while (codedIS.getBytesUntilLimit() > 0) { int stag = codedIS.readRawVarint32(); int pId = codedIS.readRawVarint32(); o.names.put(stag, ((char)pId)+""); } codedIS.popLimit(old); break; case RouteData.POINTS_FIELD_NUMBER: len = codedIS.readRawVarint32(); oldLimit = codedIS.pushLimit(len); int px = pleftx >> SHIFT_COORDINATES; int py = ptopy >> SHIFT_COORDINATES; while(codedIS.getBytesUntilLimit() > 0){ int x = (codedIS.readSInt32() ) + px; int y = (codedIS.readSInt32() ) + py; pointsX.add(x << SHIFT_COORDINATES); pointsY.add(y << SHIFT_COORDINATES); px = x; py = y; } codedIS.popLimit(oldLimit); break; case RouteData.POINTTYPES_FIELD_NUMBER: len = codedIS.readRawVarint32(); oldLimit = codedIS.pushLimit(len); while (codedIS.getBytesUntilLimit() > 0) { int pointInd = codedIS.readRawVarint32(); TIntArrayList pointTypes = new TIntArrayList(); int lens = codedIS.readRawVarint32(); int oldLimits = codedIS.pushLimit(lens); while (codedIS.getBytesUntilLimit() > 0) { pointTypes.add(codedIS.readRawVarint32()); } codedIS.popLimit(oldLimits); while (pointInd >= globalpointTypes.size()) { globalpointTypes.add(null); } globalpointTypes.set(pointInd, pointTypes); } codedIS.popLimit(oldLimit); break; case RouteData.ROUTEID_FIELD_NUMBER: o.id = codedIS.readInt32(); break; default: skipUnknownField(ts); break; } } } private void readRouteTreeData(RouteSubregion routeTree, TLongArrayList idTables, TLongObjectHashMap restrictions) throws IOException { routeTree.dataObjects = new ArrayList(); idTables.clear(); restrictions.clear(); List stringTable = null; while(true){ int t = codedIS.readTag(); int tag = WireFormat.getTagFieldNumber(t); switch (tag) { case 0: TLongObjectIterator it = restrictions.iterator(); while (it.hasNext()) { it.advance(); int from = (int) it.key(); RouteDataObject fromr = routeTree.dataObjects.get(from); fromr.restrictions = new long[it.value().size()]; for (int k = 0; k < fromr.restrictions.length; k++) { int to = (int) (it.value().get(k) >> RouteDataObject.RESTRICTION_SHIFT); long valto = (idTables.get(to) << RouteDataObject.RESTRICTION_SHIFT) | ((long) it.value().get(k) & RouteDataObject.RESTRICTION_MASK); fromr.restrictions[k] = valto; } } for (RouteDataObject o : routeTree.dataObjects) { if (o != null) { if (o.id < idTables.size()) { o.id = idTables.get((int) o.id); } if (o.names != null && stringTable != null) { int[] keys = o.names.keys(); for (int j = 0; j < keys.length; j++) { o.names.put(keys[j], stringTable.get(o.names.get(keys[j]).charAt(0))); } } } } return; case RouteDataBlock.DATAOBJECTS_FIELD_NUMBER : int length = codedIS.readRawVarint32(); int oldLimit = codedIS.pushLimit(length); RouteDataObject obj = readRouteDataObject(routeTree.routeReg, routeTree.left, routeTree.top); while(obj.id >= routeTree.dataObjects.size()) { routeTree.dataObjects.add(null); } routeTree.dataObjects.set((int) obj.id,obj); codedIS.popLimit(oldLimit); break; case RouteDataBlock.IDTABLE_FIELD_NUMBER : long routeId = 0; length = codedIS.readRawVarint32(); oldLimit = codedIS.pushLimit(length); idLoop : while(true){ int ts = codedIS.readTag(); int tags = WireFormat.getTagFieldNumber(ts); switch (tags) { case 0: break idLoop; case IdTable.ROUTEID_FIELD_NUMBER : routeId += codedIS.readSInt64(); idTables.add(routeId); break; default: skipUnknownField(ts); break; } } codedIS.popLimit(oldLimit); break; case RouteDataBlock.RESTRICTIONS_FIELD_NUMBER : length = codedIS.readRawVarint32(); oldLimit = codedIS.pushLimit(length); long from = 0; long to = 0; long type = 0; idLoop : while(true){ int ts = codedIS.readTag(); int tags = WireFormat.getTagFieldNumber(ts); switch (tags) { case 0: break idLoop; case RestrictionData.FROM_FIELD_NUMBER : from = codedIS.readInt32(); break; case RestrictionData.TO_FIELD_NUMBER : to = codedIS.readInt32(); break; case RestrictionData.TYPE_FIELD_NUMBER : type = codedIS.readInt32(); break; default: skipUnknownField(ts); break; } } if(!restrictions.containsKey(from)) { restrictions.put(from, new TLongArrayList()); } restrictions.get(from).add((to << RouteDataObject.RESTRICTION_SHIFT) + type); codedIS.popLimit(oldLimit); break; case RouteDataBlock.STRINGTABLE_FIELD_NUMBER : length = codedIS.readRawVarint32(); oldLimit = codedIS.pushLimit(length); stringTable = map.readStringTable(); // codedIS.skipRawBytes(codedIS.getBytesUntilLimit()); codedIS.popLimit(oldLimit); break; default: skipUnknownField(t); break; } } } private void readRouteEncodingRule(RouteRegion index, int id) throws IOException { String tags = null; String val = null; while(true){ int t = codedIS.readTag(); int tag = WireFormat.getTagFieldNumber(t); switch (tag) { case 0: index.initRouteEncodingRule(id, tags, val); return; case RouteEncodingRule.VALUE_FIELD_NUMBER : val = codedIS.readString().intern(); break; case RouteEncodingRule.TAG_FIELD_NUMBER : tags = codedIS.readString().intern(); break; case RouteEncodingRule.ID_FIELD_NUMBER : id = codedIS.readUInt32(); break; default: skipUnknownField(t); break; } } } private RouteSubregion readRouteTree(RouteSubregion thisTree, RouteSubregion parentTree, int depth, boolean readCoordinates) throws IOException { boolean readChildren = depth != 0; if(readChildren) { thisTree.subregions = new ArrayList(); } thisTree.routeReg.regionsRead++; while(true){ int t = codedIS.readTag(); int tag = WireFormat.getTagFieldNumber(t); switch (tag) { case 0: return thisTree; case RouteDataBox.LEFT_FIELD_NUMBER : int i = codedIS.readSInt32(); if (readCoordinates) { thisTree.left = i + (parentTree != null ? parentTree.left : 0); } break; case RouteDataBox.RIGHT_FIELD_NUMBER : i = codedIS.readSInt32(); if (readCoordinates) { thisTree.right = i + (parentTree != null ? parentTree.right : 0); } break; case RouteDataBox.TOP_FIELD_NUMBER : i = codedIS.readSInt32(); if (readCoordinates) { thisTree.top = i + (parentTree != null ? parentTree.top : 0); } break; case RouteDataBox.BOTTOM_FIELD_NUMBER : i = codedIS.readSInt32(); if (readCoordinates) { thisTree.bottom = i + (parentTree != null ? parentTree.bottom : 0); } break; case RouteDataBox.SHIFTTODATA_FIELD_NUMBER : thisTree.shiftToData = readInt(); if(!readChildren) { // usually 0 thisTree.subregions = new ArrayList(); readChildren = true; } break; case RouteDataBox.BOXES_FIELD_NUMBER : if(readChildren){ RouteSubregion subregion = new RouteSubregion(thisTree.routeReg); subregion.length = readInt(); subregion.filePointer = codedIS.getTotalBytesRead(); int oldLimit = codedIS.pushLimit(subregion.length); readRouteTree(subregion, thisTree, depth - 1, true); thisTree.subregions.add(subregion); codedIS.popLimit(oldLimit); codedIS.seek(subregion.filePointer + subregion.length); } else { codedIS.seek(thisTree.filePointer + thisTree.length); // skipUnknownField(t); } break; default: skipUnknownField(t); break; } } } public void initRouteTypesIfNeeded(SearchRequest req, List list) throws IOException { for (RouteSubregion rs : list) { if (req.intersects(rs.left, rs.top, rs.right, rs.bottom)) { initRouteRegion(rs.routeReg); } } } public void initRouteRegion(RouteRegion routeReg) throws IOException, InvalidProtocolBufferException { if (routeReg.routeEncodingRules.isEmpty()) { codedIS.seek(routeReg.filePointer); int oldLimit = codedIS.pushLimit(routeReg.length); readRouteIndex(routeReg); codedIS.popLimit(oldLimit); } } public List loadRouteRegionData(RouteSubregion rs) throws IOException { TLongArrayList idMap = new TLongArrayList(); TLongObjectHashMap restrictionMap = new TLongObjectHashMap(); if (rs.dataObjects == null) { codedIS.seek(rs.filePointer + rs.shiftToData); int limit = codedIS.readRawVarint32(); int oldLimit = codedIS.pushLimit(limit); readRouteTreeData(rs, idMap, restrictionMap); codedIS.popLimit(oldLimit); } List res = rs.dataObjects; rs.dataObjects = null; return res; } public void loadRouteRegionData(List toLoad, ResultMatcher matcher) throws IOException { Collections.sort(toLoad, new Comparator() { @Override public int compare(RouteSubregion o1, RouteSubregion o2) { int p1 = o1.filePointer + o1.shiftToData; int p2 = o2.filePointer + o2.shiftToData; return p1 == p2 ? 0 : (p1 < p2 ? -1 : 1); } }); TLongArrayList idMap = new TLongArrayList(); TLongObjectHashMap restrictionMap = new TLongObjectHashMap(); for (RouteSubregion rs : toLoad) { if (rs.dataObjects == null) { codedIS.seek(rs.filePointer + rs.shiftToData); int limit = codedIS.readRawVarint32(); int oldLimit = codedIS.pushLimit(limit); readRouteTreeData(rs, idMap, restrictionMap); codedIS.popLimit(oldLimit); } for (RouteDataObject ro : rs.dataObjects) { if (ro != null) { matcher.publish(ro); } } // free objects rs.dataObjects = null; } } public List searchRouteRegionTree(SearchRequest req, List list, List toLoad) throws IOException { for (RouteSubregion rs : list) { if (req.intersects(rs.left, rs.top, rs.right, rs.bottom)) { if (rs.subregions == null) { codedIS.seek(rs.filePointer); int old = codedIS.pushLimit(rs.length); readRouteTree(rs, null, req.contains(rs.left, rs.top, rs.right, rs.bottom) ? -1 : 1, false); codedIS.popLimit(old); } searchRouteRegionTree(req, rs.subregions, toLoad); if (rs.shiftToData != 0) { toLoad.add(rs); } } } return toLoad; } public List loadInteresectedPoints(SearchRequest req, List list, List toLoad) throws IOException { for (RouteSubregion rs : list) { if (req.intersects(rs.left, rs.top, rs.right, rs.bottom)) { if (rs.subregions == null) { codedIS.seek(rs.filePointer); int old = codedIS.pushLimit(rs.length); readRouteTree(rs, null, req.contains(rs.left, rs.top, rs.right, rs.bottom) ? -1 : 1, false); codedIS.popLimit(old); } searchRouteRegionTree(req, rs.subregions, toLoad); if (rs.shiftToData != 0) { toLoad.add(rs); } } } return toLoad; } }