package net.osmand.binary; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import net.osmand.Algoritms; import net.osmand.binary.OsmandOdb.CityIndex; import net.osmand.binary.OsmandOdb.InteresectedStreets; import net.osmand.binary.OsmandOdb.MapEncodingRule; import net.osmand.binary.OsmandOdb.OsmAndTransportIndex; import net.osmand.binary.OsmandOdb.PostcodeIndex; import net.osmand.binary.OsmandOdb.StreetIndex; import net.osmand.binary.OsmandOdb.StreetIntersection; import net.osmand.binary.OsmandOdb.TransportRoute; import net.osmand.binary.OsmandOdb.TransportRouteStop; import net.osmand.data.Building; import net.osmand.data.City; import net.osmand.data.MapObject; import net.osmand.data.Street; import net.osmand.data.TransportStop; import net.osmand.data.index.IndexConstants; import net.osmand.osm.LatLon; import net.osmand.osm.MapUtils; import net.osmand.osm.Node; import net.osmand.osm.MapRenderingTypes.MapRulType; import net.sf.junidecode.Junidecode; import com.google.protobuf.CodedOutputStream; import com.google.protobuf.WireFormat; import com.google.protobuf.WireFormat.FieldType; public class BinaryMapIndexWriter { private RandomAccessFile raf; private CodedOutputStream codedOutStream; protected static final int SHIFT_COORDINATES = 5; private static class Bounds { public Bounds(int leftX, int rightX, int topY, int bottomY) { super(); this.bottomY = bottomY; this.leftX = leftX; this.rightX = rightX; this.topY = topY; } int leftX = 0; int rightX = 0; int topY = 0; int bottomY = 0; } private Stack stackBounds = new Stack(); // needed for map tree private Stack stackBaseIds = new Stack(); private Stack> stackStringTable = new Stack>(); // internal constants to track state of index writing private Stack state = new Stack(); private Stack stackSizes = new Stack(); private final static int OSMAND_STRUCTURE_INIT = 1; private final static int MAP_INDEX_INIT = 2; private final static int MAP_ROOT_LEVEL_INIT = 3; private final static int MAP_TREE = 4; private final static int ADDRESS_INDEX_INIT = 5; private final static int CITY_INDEX_INIT = 6; private final static int POSTCODES_INDEX_INIT = 7; private final static int VILLAGES_INDEX_INIT = 8; private final static int TRANSPORT_INDEX_INIT = 9; private final static int TRANSPORT_STOPS_TREE = 10; private final static int TRANSPORT_ROUTES = 11; public BinaryMapIndexWriter(final RandomAccessFile raf) throws IOException{ this.raf = raf; codedOutStream = CodedOutputStream.newInstance(new OutputStream() { @Override public void write(int b) throws IOException { raf.write(b); } @Override public void write(byte[] b) throws IOException { raf.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { raf.write(b, off, len); } }); codedOutStream.writeInt32(OsmandOdb.OsmAndStructure.VERSION_FIELD_NUMBER, IndexConstants.BINARY_MAP_VERSION); state.push(OSMAND_STRUCTURE_INIT); } public void finishWriting(){ } private void preserveInt32Size() throws IOException { codedOutStream.flush(); stackSizes.push(raf.getFilePointer()); codedOutStream.writeFixed32NoTag(0); } private int writeInt32Size() throws IOException{ codedOutStream.flush(); long filePointer = raf.getFilePointer(); Long old = stackSizes.pop(); int length = (int) (filePointer - old - 4); raf.seek(old); raf.writeInt(length); raf.seek(filePointer); return length; } public void startWriteMapIndex(String name) throws IOException{ pushState(MAP_INDEX_INIT, OSMAND_STRUCTURE_INIT); codedOutStream.writeTag(OsmandOdb.OsmAndStructure.MAPINDEX_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); preserveInt32Size(); if(name != null){ codedOutStream.writeString(OsmandOdb.OsmAndMapIndex.NAME_FIELD_NUMBER, name); } } public void endWriteMapIndex() throws IOException{ popState(MAP_INDEX_INIT); int len = writeInt32Size(); System.out.println("MAP INDEX SIZE : " + len); } public void startWriteMapLevelIndex(int minZoom, int maxZoom, int leftX, int rightX, int topY, int bottomY) throws IOException{ pushState(MAP_ROOT_LEVEL_INIT, MAP_INDEX_INIT); codedOutStream.writeTag(OsmandOdb.OsmAndMapIndex.LEVELS_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); preserveInt32Size(); codedOutStream.writeInt32(OsmandOdb.MapRootLevel.MAXZOOM_FIELD_NUMBER, maxZoom); codedOutStream.writeInt32(OsmandOdb.MapRootLevel.MINZOOM_FIELD_NUMBER, minZoom); codedOutStream.writeInt32(OsmandOdb.MapRootLevel.LEFT_FIELD_NUMBER, leftX); codedOutStream.writeInt32(OsmandOdb.MapRootLevel.RIGHT_FIELD_NUMBER, rightX); codedOutStream.writeInt32(OsmandOdb.MapRootLevel.TOP_FIELD_NUMBER, topY); codedOutStream.writeInt32(OsmandOdb.MapRootLevel.BOTTOM_FIELD_NUMBER, bottomY); stackBounds.push(new Bounds(leftX, rightX, topY, bottomY)); } public void endWriteMapLevelIndex() throws IOException{ popState(MAP_ROOT_LEVEL_INIT); stackBounds.pop(); writeInt32Size(); } public void writeMapEncodingRules(Map types) throws IOException{ checkPeekState(MAP_INDEX_INIT); codedOutStream.flush(); long fp = raf.getFilePointer(); MapEncodingRule.Builder builder = OsmandOdb.MapEncodingRule.newBuilder(); for(String tag : types.keySet()){ MapRulType rule = types.get(tag); int type = rule.getType(null); int subType = rule.getSubType(null); if(type != 0 && subType != 0){ builder.setTag(tag).setType(type).setSubtype(subType).setMinZoom(rule.getMinZoom(null)); builder = OsmandOdb.MapEncodingRule.newBuilder(); } for(String val : types.get(tag).getValuesSet()){ type = rule.getType(val); subType = rule.getSubType(val); builder.setTag(tag).setValue(val).setType(type).setSubtype(subType).setMinZoom(rule.getMinZoom(null)); codedOutStream.writeMessage(OsmandOdb.OsmAndMapIndex.RULES_FIELD_NUMBER, builder.build()); builder = OsmandOdb.MapEncodingRule.newBuilder(); } } codedOutStream.flush(); long newfp = raf.getFilePointer(); System.out.println("RENDERING SCHEMA takes " + (newfp - fp)); } public void startMapTreeElement(int leftX, int rightX, int topY, int bottomY) throws IOException{ startMapTreeElement(-1L, leftX, rightX, topY, bottomY); } public void startMapTreeElement(long baseId, int leftX, int rightX, int topY, int bottomY) throws IOException{ checkPeekState(MAP_ROOT_LEVEL_INIT, MAP_TREE); if(state.peek() == MAP_ROOT_LEVEL_INIT){ codedOutStream.writeTag(OsmandOdb.MapRootLevel.ROOT_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); } else { codedOutStream.writeTag(OsmandOdb.MapTree.SUBTREES_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); } state.push(MAP_TREE); preserveInt32Size(); Bounds bounds = stackBounds.peek(); codedOutStream.writeSInt32(OsmandOdb.MapTree.LEFT_FIELD_NUMBER, leftX - bounds.leftX); codedOutStream.writeSInt32(OsmandOdb.MapTree.RIGHT_FIELD_NUMBER, rightX - bounds.rightX); codedOutStream.writeSInt32(OsmandOdb.MapTree.TOP_FIELD_NUMBER, topY - bounds.topY); codedOutStream.writeSInt32(OsmandOdb.MapTree.BOTTOM_FIELD_NUMBER, bottomY - bounds.bottomY); stackBounds.push(new Bounds(leftX, rightX, topY, bottomY)); stackBaseIds.push(baseId); stackStringTable.push(null); } public void endWriteMapTreeElement() throws IOException{ popState(MAP_TREE); stackBounds.pop(); Long l = stackBaseIds.pop(); if(l != -1){ codedOutStream.writeTag(OsmandOdb.MapTree.BASEID_FIELD_NUMBER, WireFormat.FieldType.UINT64.getWireType()); codedOutStream.writeUInt64NoTag(l); } Map map = stackStringTable.peek(); if(map != null){ int i = 0; int size = 0; for(String s : map.keySet()){ Integer integer = map.get(s); if(integer != i){ throw new IllegalStateException(); } i++; size += CodedOutputStream.computeStringSize(OsmandOdb.StringTable.S_FIELD_NUMBER, s); } codedOutStream.writeTag(OsmandOdb.MapTree.STRINGTABLE_FIELD_NUMBER, WireFormat.FieldType.MESSAGE.getWireType()); STRING_TABLE_SIZE += CodedOutputStream.computeTagSize(OsmandOdb.MapTree.STRINGTABLE_FIELD_NUMBER) + CodedOutputStream.computeRawVarint32Size(size) + size; codedOutStream.writeRawVarint32(size); for(String s : map.keySet()){ codedOutStream.writeString(OsmandOdb.StringTable.S_FIELD_NUMBER, s); } } writeInt32Size(); } // debug data about size of map index public static int COORDINATES_SIZE = 0; public static int COORDINATES_COUNT = 0; public static int ID_SIZE = 0; public static int TYPES_SIZE = 0; public static int MAP_DATA_SIZE = 0; public static int STRING_TABLE_SIZE = 0; protected static int codeCoordinateDifference(int x, int px){ // shift absolute coordinates first and get truncated return (x >> SHIFT_COORDINATES) - (px >> SHIFT_COORDINATES); } public void writeMapData(long id, byte[] nodes, byte[] types, String name, int highwayAttributes, byte[] restrictions) throws IOException{ assert state.peek() == MAP_TREE; Bounds bounds = stackBounds.peek(); if(stackBaseIds.peek() == -1){ stackBaseIds.pop(); stackBaseIds.push(id); } // calculate size int sizeCoordinates = 0; int allSize = 0; int px = bounds.leftX; int py = bounds.topY; for(int i=0; i< nodes.length / 8; i++){ int x = Algoritms.parseIntFromBytes(nodes, i * 8); int y = Algoritms.parseIntFromBytes(nodes, i * 8 + 4); sizeCoordinates += CodedOutputStream.computeSInt32SizeNoTag(codeCoordinateDifference(x, px)); sizeCoordinates += CodedOutputStream.computeSInt32SizeNoTag(codeCoordinateDifference(y, py)); px = x; py = y; COORDINATES_COUNT += 2; } allSize += CodedOutputStream.computeRawVarint32Size(sizeCoordinates) + CodedOutputStream.computeTagSize(OsmandOdb.MapData.COORDINATES_FIELD_NUMBER) + sizeCoordinates; // DEBUG COORDINATES_SIZE += allSize; allSize += CodedOutputStream.computeTagSize(OsmandOdb.MapData.TYPES_FIELD_NUMBER); allSize += CodedOutputStream.computeRawVarint32Size(types.length); allSize += types.length; // DEBUG TYPES_SIZE += CodedOutputStream.computeTagSize(OsmandOdb.MapData.TYPES_FIELD_NUMBER) + CodedOutputStream.computeRawVarint32Size(types.length) + types.length; allSize += CodedOutputStream.computeSInt64Size(OsmandOdb.MapData.ID_FIELD_NUMBER, id - stackBaseIds.peek()); // DEBUG ID_SIZE += CodedOutputStream.computeSInt64Size(OsmandOdb.MapData.ID_FIELD_NUMBER, id - stackBaseIds.peek()); int nameId = 0; if(name != null){ if(stackStringTable.peek() == null) { stackStringTable.pop(); stackStringTable.push(new LinkedHashMap()); } Map map = stackStringTable.peek(); if(map.containsKey(name)) { nameId = map.get(name); } else { nameId = map.size(); map.put(name, nameId); } allSize += CodedOutputStream.computeUInt32Size(OsmandOdb.MapData.STRINGID_FIELD_NUMBER, nameId); } int restrictionsSize = 0; if(restrictions.length > 0){ allSize += CodedOutputStream.computeTagSize(OsmandOdb.MapData.RESTRICTIONS_FIELD_NUMBER); for (int i = 0; i < restrictions.length / 8; i++) { long l = Algoritms.parseLongFromBytes(restrictions, i * 8) - stackBaseIds.peek(); restrictionsSize += CodedOutputStream.computeSInt64SizeNoTag(l); } allSize += CodedOutputStream.computeRawVarint32Size(restrictionsSize); allSize += restrictionsSize; } if(highwayAttributes != 0){ allSize += CodedOutputStream.computeInt32Size(OsmandOdb.MapData.HIGHWAYMETA_FIELD_NUMBER, highwayAttributes); } // DEBUG MAP_DATA_SIZE += allSize; // writing data codedOutStream.writeTag(OsmandOdb.MapTree.LEAFS_FIELD_NUMBER, WireFormat.FieldType.MESSAGE.getWireType()); codedOutStream.writeRawVarint32(allSize); codedOutStream.writeTag(OsmandOdb.MapData.COORDINATES_FIELD_NUMBER, WireFormat.FieldType.BYTES.getWireType()); codedOutStream.writeRawVarint32(sizeCoordinates); px = bounds.leftX; py = bounds.topY; for (int i = 0; i < nodes.length / 8; i++) { int x = Algoritms.parseIntFromBytes(nodes, i * 8); int y = Algoritms.parseIntFromBytes(nodes, i * 8 + 4); codedOutStream.writeSInt32NoTag(codeCoordinateDifference(x, px)); codedOutStream.writeSInt32NoTag(codeCoordinateDifference(y, py)); px = x; py = y; } codedOutStream.writeTag(OsmandOdb.MapData.TYPES_FIELD_NUMBER, WireFormat.FieldType.BYTES.getWireType()); codedOutStream.writeRawVarint32(types.length); codedOutStream.writeRawBytes(types); codedOutStream.writeSInt64(OsmandOdb.MapData.ID_FIELD_NUMBER, id - stackBaseIds.peek()); if(name != null){ codedOutStream.writeUInt32(OsmandOdb.MapData.STRINGID_FIELD_NUMBER, nameId); } if(restrictions.length > 0){ codedOutStream.writeTag(OsmandOdb.MapData.RESTRICTIONS_FIELD_NUMBER, WireFormat.FieldType.BYTES.getWireType()); codedOutStream.writeRawVarint32(restrictionsSize); for (int i = 0; i < restrictions.length / 8; i++) { long l = Algoritms.parseLongFromBytes(restrictions, i * 8) - stackBaseIds.peek(); codedOutStream.writeSInt64NoTag(l); } } if(highwayAttributes != 0){ codedOutStream.writeInt32(OsmandOdb.MapData.HIGHWAYMETA_FIELD_NUMBER, highwayAttributes); } } public void startWriteAddressIndex(String name) throws IOException { pushState(ADDRESS_INDEX_INIT, OSMAND_STRUCTURE_INIT); codedOutStream.writeTag(OsmandOdb.OsmAndStructure.ADDRESSINDEX_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); preserveInt32Size(); codedOutStream.writeString(OsmandOdb.OsmAndAddressIndex.NAME_FIELD_NUMBER, name); } public void endWriteAddressIndex() throws IOException { popState(ADDRESS_INDEX_INIT); int len = writeInt32Size(); System.out.println("ADDRESS INDEX SIZE : " + len); } private boolean checkEnNameToWrite(MapObject obj){ if(obj.getEnName() == null){ return false; } return !obj.getEnName().equals(Junidecode.unidecode(obj.getName())); } public void writeCityIndex(City city, List streets, Map> wayNodes) throws IOException { if(city.getType() == City.CityType.CITY || city.getType() == City.CityType.TOWN){ checkPeekState(CITY_INDEX_INIT); } else { checkPeekState(VILLAGES_INDEX_INIT); } CityIndex.Builder cityInd = OsmandOdb.CityIndex.newBuilder(); cityInd.setCityType(city.getType().ordinal()); cityInd.setId(city.getId()); cityInd.setName(city.getName()); if(checkEnNameToWrite(city)){ cityInd.setNameEn(city.getEnName()); } int cx = MapUtils.get31TileNumberX(city.getLocation().getLongitude()); int cy = MapUtils.get31TileNumberY(city.getLocation().getLatitude()); cityInd.setX(cx); cityInd.setY(cy); if(wayNodes != null){ InteresectedStreets.Builder sbuilders = OsmandOdb.InteresectedStreets.newBuilder(); Map> reverseMap = new LinkedHashMap>(); for (int i = 0; i < streets.size(); i++) { streets.get(i).setIndexInCity(i); for (Node n : wayNodes.get(streets.get(i))) { if(!reverseMap.containsKey(n.getId())){ reverseMap.put(n.getId(), new LinkedHashSet(3)); } reverseMap.get(n.getId()).add(i); } } Set checkedStreets = new LinkedHashSet(); for (int i = 0; i < streets.size(); i++) { Street s1 = streets.get(i); checkedStreets.clear(); for(Node intersection : wayNodes.get(s1)){ for(Integer j : reverseMap.get(intersection.getId())){ if(i >= j || checkedStreets.contains(j)){ continue; } checkedStreets.add(j); StreetIntersection.Builder builder = OsmandOdb.StreetIntersection.newBuilder(); builder.setIntersectedStreet1(i); builder.setIntersectedStreet2(j); int sx = MapUtils.get31TileNumberX(intersection.getLongitude()); int sy = MapUtils.get31TileNumberY(intersection.getLatitude()); builder.setIntersectedX((sx - cx) >> 7); builder.setIntersectedY((sy - cy) >> 7); sbuilders.addIntersections(builder.build()); } } } cityInd.setIntersections(sbuilders.build()); } for(Street s : streets){ StreetIndex streetInd = createStreetAndBuildings(s, cx, cy, null); cityInd.addStreets(streetInd); } codedOutStream.writeMessage(OsmandOdb.CitiesIndex.CITIES_FIELD_NUMBER, cityInd.build()); } public void startCityIndexes(boolean villages) throws IOException { pushState(villages ? VILLAGES_INDEX_INIT : CITY_INDEX_INIT, ADDRESS_INDEX_INIT); codedOutStream.writeTag(villages ? OsmandOdb.OsmAndAddressIndex.VILLAGES_FIELD_NUMBER : OsmandOdb.OsmAndAddressIndex.CITIES_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); preserveInt32Size(); } public void endCityIndexes(boolean villages) throws IOException { popState(villages ? VILLAGES_INDEX_INIT : CITY_INDEX_INIT); int length = writeInt32Size(); System.out.println("CITIES size " + length + " " + villages); } public void startPostcodes() throws IOException { pushState(POSTCODES_INDEX_INIT, ADDRESS_INDEX_INIT); codedOutStream.writeTag(OsmandOdb.OsmAndAddressIndex.POSTCODES_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); preserveInt32Size(); } public void endPostcodes() throws IOException { popState(POSTCODES_INDEX_INIT); int postcodes = writeInt32Size(); System.out.println("POSTCODES size " + postcodes); } public void writePostcode(String postcode, Collection streets) throws IOException { checkPeekState(POSTCODES_INDEX_INIT); if(streets.isEmpty()){ return; } postcode = postcode.toUpperCase(); LatLon loc = streets.iterator().next().getLocation(); PostcodeIndex.Builder post = OsmandOdb.PostcodeIndex.newBuilder(); post.setPostcode(postcode); int cx = MapUtils.get31TileNumberX(loc.getLongitude()); int cy = MapUtils.get31TileNumberY(loc.getLatitude()); post.setX(cx); post.setY(cy); for(Street s : streets){ StreetIndex streetInd = createStreetAndBuildings(s, cx, cy, postcode); post.addStreets(streetInd); } codedOutStream.writeMessage(OsmandOdb.PostcodesIndex.POSTCODES_FIELD_NUMBER, post.build()); } protected StreetIndex createStreetAndBuildings(Street street, int cx, int cy, String postcodeFilter) throws IOException { checkPeekState(CITY_INDEX_INIT, VILLAGES_INDEX_INIT, POSTCODES_INDEX_INIT); boolean inCity = state.peek() == CITY_INDEX_INIT || state.peek() == VILLAGES_INDEX_INIT; StreetIndex.Builder streetBuilder = OsmandOdb.StreetIndex.newBuilder(); streetBuilder.setName(street.getName()); if(checkEnNameToWrite(street)){ streetBuilder.setNameEn(street.getEnName()); } streetBuilder.setId(street.getId()); int sx = MapUtils.get31TileNumberX(street.getLocation().getLongitude()); int sy = MapUtils.get31TileNumberY(street.getLocation().getLatitude()); streetBuilder.setX((sx - cx) >> 7); streetBuilder.setY((sy - cy) >> 7); for(Building b : street.getBuildings()){ if(postcodeFilter != null && !postcodeFilter.equalsIgnoreCase(b.getPostcode())){ continue; } OsmandOdb.BuildingIndex.Builder bbuilder= OsmandOdb.BuildingIndex.newBuilder(); int bx = MapUtils.get31TileNumberX(b.getLocation().getLongitude()); int by = MapUtils.get31TileNumberY(b.getLocation().getLatitude()); bbuilder.setX((bx - sx) >> 7); bbuilder.setY((by - sy) >> 7); bbuilder.setId(b.getId()); bbuilder.setName(b.getName()); if(checkEnNameToWrite(b)){ bbuilder.setNameEn(b.getEnName()); } if(inCity && b.getPostcode() != null){ bbuilder.setPostcode(b.getPostcode()); } streetBuilder.addBuildings(bbuilder.build()); } return streetBuilder.build(); } public void startWriteTransportRoutes() throws IOException { pushState(TRANSPORT_ROUTES, TRANSPORT_INDEX_INIT); codedOutStream.writeTag(OsmandOdb.OsmAndTransportIndex.ROUTES_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); preserveInt32Size(); } public void endWriteTransportRoutes() throws IOException { popState(TRANSPORT_ROUTES); writeInt32Size(); } private int registerString(Map stringTable, String s) { if(s == null){ s = ""; } if (stringTable.containsKey(s)) { return stringTable.get(s); } int size = stringTable.size(); stringTable.put(s, size); return size; } public void startWriteTransportIndex(String name) throws IOException { pushState(TRANSPORT_INDEX_INIT, OSMAND_STRUCTURE_INIT); codedOutStream.writeTag(OsmandOdb.OsmAndStructure.TRANSPORTINDEX_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); stackBounds.push(new Bounds(0, 0, 0, 0)); // for transport stops tree preserveInt32Size(); if(name != null){ codedOutStream.writeString(OsmandOdb.OsmAndTransportIndex.NAME_FIELD_NUMBER, name); } } public void endWriteTransportIndex() throws IOException { popState(TRANSPORT_INDEX_INIT); int len = writeInt32Size(); stackBounds.pop(); System.out.println("TRANSPORT INDEX SIZE : " + len); } public void writeTransportRoute(long idRoute, String routeName, String routeEnName, String ref, String operator, String type, int dist, List directStops, List reverseStops, Map stringTable, Map transportRoutesRegistry) throws IOException { checkPeekState(TRANSPORT_ROUTES); TransportRoute.Builder tRoute = OsmandOdb.TransportRoute.newBuilder(); tRoute.setRef(ref); tRoute.setOperator(registerString(stringTable, operator)); tRoute.setType(registerString(stringTable, type)); tRoute.setId(idRoute); tRoute.setName(registerString(stringTable, routeName)); tRoute.setDistance(dist); if(routeEnName != null){ tRoute.setNameEn(registerString(stringTable, routeEnName)); } for (int i = 0; i < 2; i++) { List stops = i == 0 ? directStops : reverseStops; long id = 0; int x24 = 0; int y24 = 0; for (TransportStop st : stops) { TransportRouteStop.Builder tStop = OsmandOdb.TransportRouteStop.newBuilder(); tStop.setId(st.getId() - id); id = st.getId(); int x = (int) MapUtils.getTileNumberX(24, st.getLocation().getLongitude()); int y = (int) MapUtils.getTileNumberY(24, st.getLocation().getLatitude()); tStop.setDx(x - x24); tStop.setDy(y - y24); x24 = x; y24 = y; tStop.setName(registerString(stringTable, st.getName())); if (st.getEnName() != null) { tStop.setNameEn(registerString(stringTable, st.getEnName())); } if (i == 0) { tRoute.addDirectStops(tStop.build()); } else { tRoute.addReverseStops(tStop.build()); } } } codedOutStream.writeTag(OsmandOdb.TransportRoutes.ROUTES_FIELD_NUMBER, FieldType.MESSAGE.getWireType()); codedOutStream.flush(); if(transportRoutesRegistry != null){ transportRoutesRegistry.put(idRoute, raf.getFilePointer()); } codedOutStream.writeMessageNoTag(tRoute.build()); } public void startTransportTreeElement(int leftX, int rightX, int topY, int bottomY) throws IOException { checkPeekState(TRANSPORT_STOPS_TREE, TRANSPORT_INDEX_INIT); if(state.peek() == TRANSPORT_STOPS_TREE){ codedOutStream.writeTag(OsmandOdb.TransportStopsTree.SUBTREES_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); } else { codedOutStream.writeTag(OsmandOdb.OsmAndTransportIndex.STOPS_FIELD_NUMBER, WireFormat.WIRETYPE_FIXED32_LENGTH_DELIMITED); } state.push(TRANSPORT_STOPS_TREE); preserveInt32Size(); Bounds bounds = stackBounds.peek(); codedOutStream.writeSInt32(OsmandOdb.TransportStopsTree.LEFT_FIELD_NUMBER, leftX - bounds.leftX); codedOutStream.writeSInt32(OsmandOdb.TransportStopsTree.RIGHT_FIELD_NUMBER, rightX - bounds.rightX); codedOutStream.writeSInt32(OsmandOdb.TransportStopsTree.TOP_FIELD_NUMBER, topY - bounds.topY); codedOutStream.writeSInt32(OsmandOdb.TransportStopsTree.BOTTOM_FIELD_NUMBER, bottomY - bounds.bottomY); stackBounds.push(new Bounds(leftX, rightX, topY, bottomY)); stackBaseIds.push(-1L); } public void endWriteTransportTreeElement() throws IOException { Long baseId = stackBaseIds.pop(); if(baseId >= 0){ codedOutStream.writeUInt64(OsmandOdb.TransportStopsTree.BASEID_FIELD_NUMBER, baseId); } popState(TRANSPORT_STOPS_TREE); stackBounds.pop(); writeInt32Size(); } public long getFilePointer() throws IOException { codedOutStream.flush(); return raf.getFilePointer(); } public void writeTransportStop(long id, int x24, int y24, String name, String nameEn, Map stringTable, List routes) throws IOException { checkPeekState(TRANSPORT_STOPS_TREE); Bounds bounds = stackBounds.peek(); if (stackBaseIds.peek() == -1) { stackBaseIds.pop(); stackBaseIds.push(id); } codedOutStream.writeTag(OsmandOdb.TransportStopsTree.LEAFS_FIELD_NUMBER, WireFormat.FieldType.MESSAGE.getWireType()); codedOutStream.flush(); long fp = raf.getFilePointer(); OsmandOdb.TransportStop.Builder ts = OsmandOdb.TransportStop.newBuilder(); ts.setName(registerString(stringTable, name)); if(nameEn != null){ ts.setNameEn(registerString(stringTable, nameEn)); } ts.setDx(x24 - bounds.leftX); ts.setDy(y24 - bounds.topY); ts.setId(id -stackBaseIds.peek()); for(Long i : routes){ ts.addRoutes((int)(fp - i)); } codedOutStream.writeMessageNoTag(ts.build()); } public void writeTransportStringTable(Map stringTable) throws IOException { checkPeekState(TRANSPORT_INDEX_INIT); // expect linked hash map int i = 0; OsmandOdb.StringTable.Builder st = OsmandOdb.StringTable.newBuilder(); for(String s : stringTable.keySet()){ if(stringTable.get(s) != i++){ throw new IllegalStateException(); } st.addS(s); } codedOutStream.writeMessage(OsmAndTransportIndex.STRINGTABLE_FIELD_NUMBER, st.build()); } private void pushState(int push, int peek){ if(state.peek() != peek){ throw new IllegalStateException("expected " + peek+ " != "+ state.peek()); } state.push(push); } private void checkPeekState(int... states) { for (int i = 0; i < states.length; i++) { if (states[i] == state.peek()) { return; } } throw new IllegalStateException("Note expected state : " + state.peek()); } private void popState(int state){ Integer st = this.state.pop(); if(st != state){ throw new IllegalStateException("expected " + state + " != "+ st); } } public void flush() throws IOException { codedOutStream.flush(); } public void close() throws IOException{ checkPeekState(OSMAND_STRUCTURE_INIT); codedOutStream.writeInt32(OsmandOdb.OsmAndStructure.VERSIONCONFIRM_FIELD_NUMBER, IndexConstants.BINARY_MAP_VERSION); codedOutStream.flush(); } }