package net.osmand.router; import gnu.trove.iterator.TIntObjectIterator; import gnu.trove.iterator.TLongIterator; import gnu.trove.map.TLongObjectMap; import gnu.trove.map.hash.TLongObjectHashMap; import gnu.trove.set.hash.TLongHashSet; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import net.osmand.NativeLibrary; import net.osmand.NativeLibrary.NativeRouteSearchResult; import net.osmand.PlatformUtil; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.binary.BinaryMapIndexReader.SearchRequest; import net.osmand.binary.BinaryMapRouteReaderAdapter; import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteRegion; import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteSubregion; import net.osmand.binary.RouteDataObject; import net.osmand.router.BinaryRoutePlanner.FinalRouteSegment; import net.osmand.router.BinaryRoutePlanner.RouteSegment; import net.osmand.router.BinaryRoutePlanner.RouteSegmentVisitor; import net.osmand.router.RoutePlannerFrontEnd.RouteCalculationMode; import org.apache.commons.logging.Log; public class RoutingContext { public static final boolean SHOW_GC_SIZE = false; private final static Log log = PlatformUtil.getLog(RoutingContext.class); public static final int OPTION_NO_LOAD = 0; public static final int OPTION_SMART_LOAD = 1; public static final int OPTION_IN_MEMORY_LOAD = 2; // Final context variables public final RoutingConfiguration config; public final RouteCalculationMode calculationMode; public final NativeLibrary nativeLib; public final Map> map = new LinkedHashMap>(); public final Map reverseMap = new LinkedHashMap(); // 1. Initial variables public int startX; public int startY; public int targetX; public int targetY; // deprecated public long firstRoadId; public int firstRoadDirection; public RouteCalculationProgress calculationProgress; public boolean leftSideNavigation; public List previouslyCalculatedRoute; public PrecalculatedRouteDirection precalculatedRouteDirection; // 2. Routing memory cache (big objects) TLongObjectHashMap> indexedSubregions = new TLongObjectHashMap>(); TLongObjectHashMap> tileRoutes = new TLongObjectHashMap>(); // Needs to be a sorted array list . Another option to use hashmap but it will be more memory expensive List subregionTiles = new ArrayList(); // 3. Warm object caches ArrayList segmentsToVisitPrescripted = new ArrayList(5); ArrayList segmentsToVisitNotForbidden = new ArrayList(5); // 5. debug information (package accessor) public TileStatistics global = new TileStatistics(); // updated by route planner in bytes public int memoryOverhead = 0; long timeNanoToCalcDeviation = 0; long timeToLoad = 0; long timeToLoadHeaders = 0; long timeToFindInitialSegments = 0; long timeToCalculate = 0; int distinctLoadedTiles = 0; int maxLoadedTiles = 0; int loadedPrevUnloadedTiles = 0; int unloadedTiles = 0; public float routingTime = 0; public int loadedTiles = 0; public int visitedSegments = 0; public int relaxedSegments = 0; // callback of processing segments RouteSegmentVisitor visitor = null; // old planner public FinalRouteSegment finalRouteSegment; public RoutingContext(RoutingContext cp) { this.config = cp.config; this.map.putAll(cp.map); this.calculationMode = cp.calculationMode; this.leftSideNavigation = cp.leftSideNavigation; this.reverseMap.putAll(cp.reverseMap); this.nativeLib = cp.nativeLib; // copy local data and clear caches for(RoutingSubregionTile tl : subregionTiles) { if(tl.isLoaded()) { subregionTiles.add(tl); for (RouteSegment rs : tl.routes.valueCollection()) { RouteSegment s = rs; while (s != null) { s.parentRoute = null; s.parentSegmentEnd = 0; s.distanceFromStart = 0; s.distanceToEnd = 0; s = s.next; } } } } } public RoutingContext(RoutingConfiguration config, NativeLibrary nativeLibrary, BinaryMapIndexReader[] map) { this(config, nativeLibrary, map, null); } public RoutingContext(RoutingConfiguration config, NativeLibrary nativeLibrary, BinaryMapIndexReader[] map, RouteCalculationMode calcMode) { this.calculationMode = calcMode; for (BinaryMapIndexReader mr : map) { List rr = mr.getRoutingIndexes(); List subregions = new ArrayList(); for (RouteRegion r : rr) { List subregs = calcMode == RouteCalculationMode.BASE ? r.getBaseSubregions() : r.getSubregions(); for (RouteSubregion rs : subregs) { subregions.add(new RouteSubregion(rs)); } this.reverseMap.put(r, mr); } this.map.put(mr, subregions); } this.config = config; this.nativeLib = nativeLibrary; } public RouteSegmentVisitor getVisitor() { return visitor; } public int getCurrentlyLoadedTiles() { int cnt = 0; for(RoutingSubregionTile t : this.subregionTiles){ if(t.isLoaded()) { cnt++; } } return cnt; } public int getCurrentEstimatedSize(){ return global.size; } public void setVisitor(RouteSegmentVisitor visitor) { this.visitor = visitor; } public void setRouter(GeneralRouter router) { config.router = router; } public void setHeuristicCoefficient(float heuristicCoefficient) { config.heuristicCoefficient = heuristicCoefficient; } public VehicleRouter getRouter() { return config.router; } public boolean planRouteIn2Directions() { return config.planRoadDirection == 0; } public int getPlanRoadDirection() { return config.planRoadDirection; } public void setPlanRoadDirection(int planRoadDirection) { config.planRoadDirection = planRoadDirection; } public int roadPriorityComparator(double o1DistanceFromStart, double o1DistanceToEnd, double o2DistanceFromStart, double o2DistanceToEnd) { return BinaryRoutePlanner.roadPriorityComparator(o1DistanceFromStart, o1DistanceToEnd, o2DistanceFromStart, o2DistanceToEnd, config.heuristicCoefficient); } public void registerRouteDataObject(RouteDataObject o ) { if(!getRouter().acceptLine(o)){ return; } for(int k = 0; k routes = tileRoutes.get(tileId); if(routes == null){ routes = new ArrayList(); tileRoutes.put(tileId, routes); } if(!routes.contains(o)){ routes.add(o); } } } public void unloadAllData() { unloadAllData(null); } public void unloadAllData(RoutingContext except) { for (RoutingSubregionTile tl : subregionTiles) { if (tl.isLoaded()) { if(except == null || except.searchSubregionTile(tl.subregion) < 0){ tl.unload(); unloadedTiles ++; global.size -= tl.tileStatistics.size; } } } subregionTiles.clear(); tileRoutes.clear(); indexedSubregions.clear(); } private int searchSubregionTile(RouteSubregion subregion){ RoutingSubregionTile key = new RoutingSubregionTile(subregion); long now = System.nanoTime(); int ind = Collections.binarySearch(subregionTiles, key, new Comparator() { @Override public int compare(RoutingSubregionTile o1, RoutingSubregionTile o2) { if(o1.subregion.left == o2.subregion.left) { return 0; } return o1.subregion.left < o2.subregion.left ? 1 : -1; } }); if (ind >= 0) { for (int i = ind; i <= subregionTiles.size(); i++) { if (i == subregionTiles.size() || subregionTiles.get(i).subregion.left > subregion.left) { ind = -i - 1; return ind; } if (subregionTiles.get(i).subregion == subregion) { return i; } } } timeToLoadHeaders += (System.nanoTime() - now); return ind; } public void newRoutingPoints() { int middleX = startX / 2 + targetX / 2; int middleY = startY / 2 + targetY; List dataObjects = new ArrayList(); loadTileData(middleX, middleY, 17, dataObjects); System.out.println("Size of data objects " + dataObjects.size()); } public RouteSegment loadRouteSegment(int x31, int y31, int memoryLimit) { long tileId = getRoutingTile(x31, y31, memoryLimit, OPTION_SMART_LOAD); TLongObjectHashMap excludeDuplications = new TLongObjectHashMap(); RouteSegment original = null; if (tileRoutes.containsKey(tileId)) { List routes = tileRoutes.get(tileId); if (routes != null) { for (RouteDataObject ro : routes) { for (int i = 0; i < ro.pointsX.length; i++) { if (ro.getPoint31XTile(i) == x31 && ro.getPoint31YTile(i) == y31) { long id = calcRouteId(ro, i); if (excludeDuplications.contains(id)) { continue; } excludeDuplications.put(id, ro); RouteSegment segment = new RouteSegment(ro, i); segment.next = original; original = segment; } } } } } List subregions = indexedSubregions.get(tileId); if (subregions != null) { for (RoutingSubregionTile rs : subregions) { original = rs.loadRouteSegment(x31, y31, this, excludeDuplications, original); } } return original; } public void loadSubregionTile(final RoutingSubregionTile ts, boolean loadObjectsInMemory) { boolean wasUnloaded = ts.isUnloaded(); int ucount = ts.getUnloadCont(); if (nativeLib == null) { long now = System.nanoTime(); try { BinaryMapIndexReader reader = reverseMap.get(ts.subregion.routeReg); ts.setLoadedNonNative(); List res = reader.loadRouteIndexData(ts.subregion); // System.out.println(ts.subregion.shiftToData + " " + res); for(RouteDataObject ro : res){ if(ro != null && config.router.acceptLine(ro)) { ts.add(ro); } } } catch (IOException e) { throw new RuntimeException("Loading data exception", e); } timeToLoad += (System.nanoTime() - now); } else { long now = System.nanoTime(); NativeRouteSearchResult ns = nativeLib.loadRouteRegion(ts.subregion, loadObjectsInMemory); // System.out.println(ts.subregion.shiftToData + " " + Arrays.toString(ns.objects)); ts.setLoadedNative(ns, this); timeToLoad += (System.nanoTime() - now); } loadedTiles++; if (wasUnloaded) { if(ucount == 1) { loadedPrevUnloadedTiles++; } } else { if(global != null) { global.allRoutes += ts.tileStatistics.allRoutes; global.coordinates += ts.tileStatistics.coordinates; } distinctLoadedTiles++; } global.size += ts.tileStatistics.size; } private List loadTileHeaders(final int x31, final int y31) { final int zoomToLoad = 31 - config.ZOOM_TO_LOAD_TILES; int tileX = x31 >> zoomToLoad; int tileY = y31 >> zoomToLoad; return loadTileHeaders(zoomToLoad, tileX, tileY); } public void checkOldRoutingFiles(BinaryMapIndexReader key) { if(calculationMode == RouteCalculationMode.BASE && key.getDateCreated() < 1390172400000l) { // new SimpleDateFormat("dd-MM-yyyy").parse("20-01-2014").getTime() System.err.println("Old routing file : " + key.getDateCreated() + " " + new Date(key.getDateCreated())); String map = ""; for (RouteRegion r : key.getRoutingIndexes()) { map = r.getName(); } throw new RuntimeException("Update map '"+map+ "' !"); } } public void checkOldRoutingFiles(int x31, int y31) { for (Entry> r : map.entrySet()) { BinaryMapIndexReader reader = r.getKey(); for(RouteRegion reg : reader.getRoutingIndexes()) { if(reg.contains(x31, y31)) { checkOldRoutingFiles(reader); break; } } } } public List loadTileHeaders(final int zoomToLoadM31, int tileX, int tileY) { SearchRequest request = BinaryMapIndexReader.buildSearchRouteRequest(tileX << zoomToLoadM31, (tileX + 1) << zoomToLoadM31, tileY << zoomToLoadM31, (tileY + 1) << zoomToLoadM31, null); List collection = null; for (Entry> r : map.entrySet()) { // NOTE: load headers same as we do in non-native (it is not native optimized) try { if (r.getValue().size() > 0) { long now = System.nanoTime(); // int rg = r.getValue().get(0).routeReg.regionsRead; List subregs = r.getKey().searchRouteIndexTree(request, r.getValue()); if(subregs.size() > 0) { checkOldRoutingFiles(r.getKey()); } for (RouteSubregion sr : subregs) { int ind = searchSubregionTile(sr); RoutingSubregionTile found; if (ind < 0) { found = new RoutingSubregionTile(sr); subregionTiles.add(-(ind + 1), found); } else { found = subregionTiles.get(ind); } if (collection == null) { collection = new ArrayList(4); } collection.add(found); } timeToLoadHeaders += (System.nanoTime() - now); } } catch (IOException e) { throw new RuntimeException("Loading data exception", e); } } return collection; } public void loadTileData(int x31, int y31, int zoomAround, final List toFillIn) { int t = config.ZOOM_TO_LOAD_TILES - zoomAround; int coordinatesShift = (1 << (31 - config.ZOOM_TO_LOAD_TILES)); if(t <= 0) { t = 1; coordinatesShift = (1 << (31 - zoomAround)); } else { t = 1 << t; } TLongHashSet ts = new TLongHashSet(); long now = System.nanoTime(); for(int i = -t; i <= t; i++) { for(int j = -t; j <= t; j++) { ts.add(getRoutingTile(x31 +i*coordinatesShift, y31 + j*coordinatesShift, 0, OPTION_IN_MEMORY_LOAD)); } } TLongIterator it = ts.iterator(); while(it.hasNext()){ getAllObjects(it.next(), toFillIn); } timeToFindInitialSegments += (System.nanoTime() - now); } @SuppressWarnings("unused") private long getRoutingTile(int x31, int y31, int memoryLimit, int loadOptions){ // long now = System.nanoTime(); long xloc = x31 >> (31 - config.ZOOM_TO_LOAD_TILES); long yloc = y31 >> (31 - config.ZOOM_TO_LOAD_TILES); long tileId = (xloc << config.ZOOM_TO_LOAD_TILES) + yloc; if (loadOptions != OPTION_NO_LOAD) { if( memoryLimit == 0){ memoryLimit = config.memoryLimitation; } if (getCurrentEstimatedSize() > 0.9 * memoryLimit) { int sz1 = getCurrentEstimatedSize(); long h1 = 0; if (SHOW_GC_SIZE && sz1 > 0.7 * memoryLimit) { runGCUsedMemory(); h1 = runGCUsedMemory(); } int clt = getCurrentlyLoadedTiles(); long us1 = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); unloadUnusedTiles(memoryLimit); if (h1 != 0 && getCurrentlyLoadedTiles() != clt) { int sz2 = getCurrentEstimatedSize(); runGCUsedMemory(); long h2 = runGCUsedMemory(); float mb = (1 << 20); log.warn("Unload tiles : estimated " + (sz1 - sz2) / mb + " ?= " + (h1 - h2) / mb + " actual"); log.warn("Used after " + h2 / mb + " of " + Runtime.getRuntime().totalMemory() / mb + " max " + maxMemory() / mb); } else { float mb = (1 << 20); int sz2 = getCurrentEstimatedSize(); log.warn("Unload tiles : occupied before " + sz1 / mb + " Mb - now " + sz2 / mb + "MB " + memoryLimit/mb + " limit MB " + config.memoryLimitation/mb); long us2 = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); log.warn("Used memory before " + us1 / mb + "after " + us1 / mb + " of max " + maxMemory() / mb); } } if (!indexedSubregions.containsKey(tileId)) { List collection = loadTileHeaders(x31, y31); indexedSubregions.put(tileId, collection); } List subregions = indexedSubregions.get(tileId); if (subregions != null) { for (RoutingSubregionTile ts : subregions) { if (!ts.isLoaded()) { loadSubregionTile(ts, loadOptions == OPTION_IN_MEMORY_LOAD); } } } } // timeToLoad += (System.nanoTime() - now); return tileId; } private long maxMemory() { // AVIAN FIXME // return Runtime.getRuntime().maxMemory(); return 0; } public boolean checkIfMemoryLimitCritical(int memoryLimit) { return getCurrentEstimatedSize() > 0.9 * memoryLimit; } public void unloadUnusedTiles(int memoryLimit) { float desirableSize = memoryLimit * 0.7f; List list = new ArrayList(subregionTiles.size() / 2); int loaded = 0; for(RoutingSubregionTile t : subregionTiles) { if(t.isLoaded()) { list.add(t); loaded++; } } maxLoadedTiles = Math.max(maxLoadedTiles, getCurrentlyLoadedTiles()); Collections.sort(list, new Comparator() { private int pow(int base, int pw) { int r = 1; for (int i = 0; i < pw; i++) { r *= base; } return r; } @Override public int compare(RoutingSubregionTile o1, RoutingSubregionTile o2) { int v1 = (o1.access + 1) * pow(10, o1.getUnloadCont() -1); int v2 = (o2.access + 1) * pow(10, o2.getUnloadCont() -1); return v1 < v2 ? -1 : (v1 == v2 ? 0 : 1); } }); int i = 0; while(getCurrentEstimatedSize() >= desirableSize && (list.size() - i) > loaded / 5 && i < list.size()) { RoutingSubregionTile unload = list.get(i); i++; // System.out.println("Unload " + unload); unload.unload(); unloadedTiles ++; global.size -= unload.tileStatistics.size; // tile could be cleaned from routing tiles and deleted from whole list } for(RoutingSubregionTile t : subregionTiles) { t.access /= 3; } } private void getAllObjects(long tileId, final List toFillIn) { TLongObjectHashMap excludeDuplications = new TLongObjectHashMap(); if (tileRoutes.containsKey(tileId)) { List routes = tileRoutes.get(tileId); if (routes != null) { for (RouteDataObject ro : routes) { if (!excludeDuplications.contains(ro.id)) { excludeDuplications.put(ro.id, ro); toFillIn.add(ro); } } } } List subregions = indexedSubregions.get(tileId); if (subregions != null) { for (RoutingSubregionTile rs : subregions) { rs.loadAllObjects(toFillIn, this, excludeDuplications); } } } protected static long runGCUsedMemory() { Runtime runtime = Runtime.getRuntime(); long usedMem1 = runtime.totalMemory() - runtime.freeMemory(); long usedMem2 = Long.MAX_VALUE; int cnt = 4; while (cnt-- >= 0) { for (int i = 0; (usedMem1 < usedMem2) && (i < 1000); ++i) { // AVIAN FIXME runtime.runFinalization(); runtime.gc(); Thread.yield(); usedMem2 = usedMem1; usedMem1 = runtime.totalMemory() - runtime.freeMemory(); } } return usedMem1; } private static long calcRouteId(RouteDataObject o, int ind) { return (o.getId() << 10) + ind; } public static class RoutingSubregionTile { public final RouteSubregion subregion; // make it without get/set for fast access public int access; public TileStatistics tileStatistics = new TileStatistics(); private NativeRouteSearchResult searchResult = null; private int isLoaded = 0; private TLongObjectMap routes = null; public RoutingSubregionTile(RouteSubregion subregion) { this.subregion = subregion; } public TLongObjectMap getRoutes() { return routes; } public void loadAllObjects(final List toFillIn, RoutingContext ctx, TLongObjectHashMap excludeDuplications) { if(routes != null) { Iterator it = routes.valueCollection().iterator(); while(it.hasNext()){ RouteSegment rs = it.next(); while(rs != null){ RouteDataObject ro = rs.road; if (!excludeDuplications.contains(ro.id)) { excludeDuplications.put(ro.id, ro); toFillIn.add(ro); } rs = rs.next; } } } else if(searchResult != null) { RouteDataObject[] objects = searchResult.objects; if(objects != null) { for(RouteDataObject ro : objects) { if (ro != null && !excludeDuplications.contains(ro.id)) { excludeDuplications.put(ro.id, ro); toFillIn.add(ro); } } } } } private RouteSegment loadRouteSegment(int x31, int y31, RoutingContext ctx, TLongObjectHashMap excludeDuplications, RouteSegment original) { if(searchResult == null && routes == null) { return original; } access++; if (searchResult == null) { long l = (((long) x31) << 31) + (long) y31; RouteSegment segment = routes.get(l); while (segment != null) { RouteDataObject ro = segment.road; RouteDataObject toCmp = excludeDuplications.get(calcRouteId(ro, segment.getSegmentStart())); if (toCmp == null || toCmp.getPointsLength() < ro.getPointsLength()) { excludeDuplications.put(calcRouteId(ro, segment.getSegmentStart()), ro); RouteSegment s = new RouteSegment(ro, segment.getSegmentStart()); s.next = original; original = s; } segment = segment.next; } return original; } // Native use case long nanoTime = System.nanoTime(); RouteDataObject[] res = ctx.nativeLib.getDataObjects(searchResult, x31, y31); ctx.timeToLoad += (System.nanoTime() - nanoTime); if (res != null) { for (RouteDataObject ro : res) { boolean accept = ro != null; if (ctx != null) { accept = ctx.getRouter().acceptLine(ro); } if (accept) { for (int i = 0; i < ro.pointsX.length; i++) { if (ro.getPoint31XTile(i) == x31 && ro.getPoint31YTile(i) == y31) { RouteDataObject toCmp = excludeDuplications.get(calcRouteId(ro, i)); if (toCmp == null || toCmp.getPointsLength() < ro.getPointsLength()) { RouteSegment segment = new RouteSegment(ro, i); segment.next = original; original = segment; excludeDuplications.put(calcRouteId(ro, i), ro); } } } } } } return original; } public boolean isLoaded() { return isLoaded > 0; } public int getUnloadCont(){ return Math.abs(isLoaded); } public boolean isUnloaded() { return isLoaded < 0; } public void unload() { if(isLoaded == 0) { this.isLoaded = -1; } else { isLoaded = -Math.abs(isLoaded); } if(searchResult != null) { searchResult.deleteNativeResult(); } searchResult = null; routes = null; } public void setLoadedNonNative(){ isLoaded = Math.abs(isLoaded) + 1; routes = new TLongObjectHashMap(); tileStatistics = new TileStatistics(); } public void add(RouteDataObject ro) { tileStatistics.addObject(ro); for (int i = 0; i < ro.pointsX.length; i++) { int x31 = ro.getPoint31XTile(i); int y31 = ro.getPoint31YTile(i); long l = (((long) x31) << 31) + (long) y31; RouteSegment segment = new RouteSegment(ro, i); if (!routes.containsKey(l)) { routes.put(l, segment); } else { RouteSegment orig = routes.get(l); while (orig.next != null) { orig = orig.next; } orig.next = segment; } } } public void setLoadedNative(NativeRouteSearchResult r, RoutingContext ctx) { isLoaded = Math.abs(isLoaded) + 1; tileStatistics = new TileStatistics(); if (r.objects != null) { searchResult = null; routes = new TLongObjectHashMap(); for (RouteDataObject ro : r.objects) { if (ro != null && ctx.config.router.acceptLine(ro)) { add(ro); } } } else { searchResult = r; tileStatistics.size += 100; } } } static int getEstimatedSize(RouteDataObject o) { // calculate size int sz = 0; sz += 8 + 4; // overhead if (o.names != null) { sz += 12; TIntObjectIterator it = o.names.iterator(); while(it.hasNext()) { it.advance(); String vl = it.value(); sz += 12 + vl.length(); } sz += 12 + o.names.size() * 25; } sz += 8; // id // coordinates sz += (8 + 4 + 4 * o.getPointsLength()) * 4; sz += o.types == null ? 4 : (8 + 4 + 4 * o.types.length); sz += o.restrictions == null ? 4 : (8 + 4 + 8 * o.restrictions.length); sz += 4; if (o.pointTypes != null) { sz += 8 + 4 * o.pointTypes.length; for (int i = 0; i < o.pointTypes.length; i++) { sz += 4; if (o.pointTypes[i] != null) { sz += 8 + 8 * o.pointTypes[i].length; } } } // Standard overhead? return (int) (sz * 3.5); } protected static class TileStatistics { public int size = 0; public int allRoutes = 0; public int coordinates = 0; @Override public String toString() { return "All routes " + allRoutes + " size " + (size / 1024f) + " KB coordinates " + coordinates + " ratio coord " + (((float)size) / coordinates) + " ratio routes " + (((float)size) / allRoutes); } public void addObject(RouteDataObject o) { allRoutes++; coordinates += o.getPointsLength() * 2; size += getEstimatedSize(o); } } public BinaryMapIndexReader[] getMaps() { return map.keySet().toArray(new BinaryMapIndexReader[map.size()]); } }