diff --git a/OsmAnd-java/src/main/java/net/osmand/data/TransportStop.java b/OsmAnd-java/src/main/java/net/osmand/data/TransportStop.java index b7c3687b31..6f5b17ca75 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/TransportStop.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/TransportStop.java @@ -22,8 +22,6 @@ public class TransportStop extends MapObject { public int y31; private List exits; private List routes = null; - private LinkedHashMap referencesToRoutesMap; - private TransportStopAggregated transportStopAggregated; public TransportStop() {} @@ -35,19 +33,6 @@ public class TransportStop extends MapObject { public boolean isMissingStop() { return MISSING_STOP_NAME.equals(getName()); } - - public LinkedHashMap getReferencesToRoutesMap() { - return referencesToRoutesMap; - } - - public void putReferencesToRoutes(String repositoryFileName, int[] referencesToRoutes) { - LinkedHashMap referencesToRoutesMap = this.referencesToRoutesMap; - if (referencesToRoutesMap == null) { - referencesToRoutesMap = new LinkedHashMap<>(); - this.referencesToRoutesMap = referencesToRoutesMap; - } - referencesToRoutesMap.put(repositoryFileName, referencesToRoutes); - } public void setRoutes(List routes) { this.routes = routes; @@ -115,10 +100,6 @@ public class TransportStop extends MapObject { return !isDeleted() && referencesToRoutes != null && referencesToRoutes.length > 0; } - public boolean hasReferencesToRoutesMap() { - return !isDeleted() && referencesToRoutesMap != null && !referencesToRoutesMap.isEmpty(); - } - public Amenity getAmenity() { if (transportStopAggregated != null) { return transportStopAggregated.getAmenity(); diff --git a/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java b/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java index 4cdcfc0a26..c9d471a497 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java @@ -2,6 +2,7 @@ package net.osmand.router; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -30,12 +31,15 @@ import net.osmand.data.TransportStop; import net.osmand.data.TransportStopExit; import net.osmand.osm.edit.Node; import net.osmand.osm.edit.Way; +import net.osmand.router.TransportRoutePlanner.TransportRouteResult; +import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment; +import net.osmand.router.TransportRoutePlanner.TransportRouteSegment; import net.osmand.util.MapUtils; public class TransportRoutePlanner { private static final boolean MEASURE_TIME = false; - private static final int MISSING_STOP_SEARCH_RADIUS = 15000; + private static final int MIN_DIST_STOP_TO_GEOMETRY = 150; public static final long GEOMETRY_WAY_ID = -1; public static final long STOPS_WAY_ID = -2; @@ -440,7 +444,7 @@ public class TransportRoutePlanner { Node ln = startInd.way.getLastNode(); Node fn = endInd.way.getFirstNode(); // HERE we need to check other ways for continuation - if (ln != null && fn != null && MapUtils.getDistance(ln.getLatLon(), fn.getLatLon()) < MISSING_STOP_SEARCH_RADIUS) { + if (ln != null && fn != null && MapUtils.getDistance(ln.getLatLon(), fn.getLatLon()) < TransportStopsRouteReader.MISSING_STOP_SEARCH_RADIUS) { validContinuation = true; } else { validContinuation = false; @@ -752,9 +756,7 @@ public class TransportRoutePlanner { // Here we don't limit files by bbox, so it could be an issue while searching for multiple unused files // Incomplete routes usually don't need more files than around Max-BBOX of start/end, // so here an improvement could be introduced - public final Map> routeMap = - new LinkedHashMap>(); - + final TransportStopsRouteReader transportStopsReader; public int finishTimeSeconds; // stats @@ -779,9 +781,7 @@ public class TransportRoutePlanner { walkChangeRadiusIn31 = (int) (cfg.walkChangeRadius / MapUtils.getTileDistanceWidth(31)); quadTree = new TLongObjectHashMap>(); this.library = library; - for (BinaryMapIndexReader r : readers) { - routeMap.put(r, new TIntObjectHashMap()); - } + transportStopsReader = new TransportStopsRouteReader(Arrays.asList(readers)); } public List getTransportStops(LatLon loc) throws IOException { @@ -831,369 +831,11 @@ public class TransportRoutePlanner { int pz = (31 - cfg.ZOOM_TO_LOAD_TILES); SearchRequest sr = BinaryMapIndexReader.buildSearchTransportRequest(x << pz, (x + 1) << pz, y << pz, (y + 1) << pz, -1, null); - - // could be global ? - TLongObjectHashMap loadedTransportStops = new TLongObjectHashMap(); - - for (BinaryMapIndexReader r : routeMap.keySet()) { - sr.clearSearchResults(); - List stops = r.searchTransportIndex(sr); - - TIntArrayList routesToLoad = mergeTransportStops(r, loadedTransportStops, stops); - - TIntObjectHashMap loadedRoutes = routeMap.get(r); -// localFileRoutes.clear(); - TIntObjectHashMap localFileRoutes = new TIntObjectHashMap<>(); //reference, route - loadRoutes(r, localFileRoutes, loadedRoutes, routesToLoad); - - for (TransportStop stop : stops) { - // skip missing stops - if (stop.isMissingStop()) { - continue; - } - long stopId = stop.getId(); - TransportStop multifileStop = loadedTransportStops.get(stopId); - int[] rrs = stop.getReferencesToRoutes(); - // clear up so it won't be used because there is multi file stop - stop.setReferencesToRoutes(null); - if (rrs != null && !multifileStop.isDeleted()) { - for (int rr : rrs) { - TransportRoute route = localFileRoutes.get(rr); - if (route == null) { - System.err.println( - String.format("Something went wrong by loading combined route %d for stop %s", - rr, stop)); - } else { - TransportRoute combinedRoute = getCombinedRoute(route); - if (multifileStop == stop || (!multifileStop.hasRoute(combinedRoute.getId()) && - !multifileStop.isRouteDeleted(combinedRoute.getId()))) { - // duplicates won't be added - multifileStop.addRouteId(combinedRoute.getId()); - multifileStop.addRoute(combinedRoute); - } - } - } - } - } - } - // There should go stops with complete routes: - loadTransportSegments(loadedTransportStops.valueCollection(), lst); - + Collection stops = transportStopsReader.readMergedTransportStops(sr); + loadTransportSegments(stops, lst); readTime += System.nanoTime() - nanoTime; return lst; } - - - public TIntArrayList mergeTransportStops(BinaryMapIndexReader reader, TLongObjectHashMap loadedTransportStops, - List stops) throws IOException { - TIntArrayList routesToLoad = new TIntArrayList(); - Iterator it = stops.iterator(); - TIntArrayList localRoutesToLoad = new TIntArrayList(); - while (it.hasNext()) { - TransportStop stop = it.next(); - long stopId = stop.getId(); - localRoutesToLoad.clear(); - TransportStop multifileStop = loadedTransportStops.get(stopId); - long[] routesIds = stop.getRoutesIds(); - long[] delRIds = stop.getDeletedRoutesIds(); - if (multifileStop == null) { - loadedTransportStops.put(stopId, stop); - multifileStop = stop; - if (!stop.isDeleted()) { - localRoutesToLoad.addAll(stop.getReferencesToRoutes()); - } - } else if (multifileStop.isDeleted()){ - // stop has nothing to load, so not needed - it.remove(); - } else { - if (delRIds != null) { - for (long deletedRouteId : delRIds) { - multifileStop.addDeletedRouteId(deletedRouteId); - } - } - if (routesIds != null && routesIds.length > 0) { - int[] refs = stop.getReferencesToRoutes(); - for (int i = 0; i < routesIds.length; i++) { - long routeId = routesIds[i]; - if (!multifileStop.hasRoute(routeId) && !multifileStop.isRouteDeleted(routeId)) { - localRoutesToLoad.add(refs[i]); - } - } - } else { - if (stop.hasReferencesToRoutes()) { - // old format - localRoutesToLoad.addAll(stop.getReferencesToRoutes()); - } else { - // stop has noting to load, so not needed - it.remove(); - } - } - } - routesToLoad.addAll(localRoutesToLoad); - multifileStop.putReferencesToRoutes(reader.getFile().getName(), localRoutesToLoad.toArray()); //add valid stop and references to routes - } - return routesToLoad; - } - - private void loadRoutes(BinaryMapIndexReader reader, TIntObjectHashMap localFileRoutes, - TIntObjectHashMap loadedRoutes, TIntArrayList routesToLoad) throws IOException { - // load/combine routes - if (routesToLoad.size() > 0) { - routesToLoad.sort(); - TIntArrayList referencesToLoad = new TIntArrayList(); - TIntIterator itr = routesToLoad.iterator(); - int p = routesToLoad.get(0) + 1; // different - while (itr.hasNext()) { - int nxt = itr.next(); - if (p != nxt) { - if (localFileRoutes != null && loadedRoutes != null && loadedRoutes.contains(nxt)) { //check if - localFileRoutes.put(nxt, loadedRoutes.get(nxt)); - } else { - referencesToLoad.add(nxt); - } - } - } - if (localFileRoutes != null && loadedRoutes != null) { - reader.loadTransportRoutes(referencesToLoad.toArray(), localFileRoutes); - loadedRoutes.putAll(localFileRoutes); - } - } - } - - private TransportRoute getCombinedRoute(TransportRoute route) throws IOException { - if (!route.isIncomplete()) { - return route; - } - TransportRoute c = combinedRoutesCache.get(route.getId()); - if (c == null) { - c = combineRoute(route); - combinedRoutesCache.put(route.getId(), c); - } - return c; - } - - private TransportRoute combineRoute(TransportRoute route) throws IOException { - // 1. Get all available route parts; - List incompleteRoutes = findIncompleteRouteParts(route); - if (incompleteRoutes == null) { - return route; - } - // here could be multiple overlays between same points - // It's better to remove them especially identical segments - List allWays = getAllWays(incompleteRoutes); - - - // 2. Get array of segments (each array size > 1): - LinkedList> stopSegments = parseRoutePartsToSegments(incompleteRoutes); - - // 3. Merge segments and remove excess missingStops (when they are closer then MISSING_STOP_SEARCH_RADIUS): - // + Check for missingStops. If they present in the middle/there more then one segment - we have a hole in the map data - List> mergedSegments = combineSegmentsOfSameRoute(stopSegments); - - // 4. Now we need to properly sort segments, proper sorting is minimizing distance between stops - // So it is salesman problem, we have this solution at TspAnt, but if we know last or first segment we can solve it straightforward - List firstSegment = null; - List lastSegment = null; - for(List l : mergedSegments) { - if(!l.get(0).isMissingStop()) { - firstSegment = l; - } - if(!l.get(l.size() - 1).isMissingStop()) { - lastSegment = l; - } - } - List> sortedSegments = new ArrayList>(); - if(firstSegment != null) { - sortedSegments.add(firstSegment); - mergedSegments.remove(firstSegment); - while(!mergedSegments.isEmpty()) { - List last = sortedSegments.get(sortedSegments.size() - 1); - List add = findAndDeleteMinDistance(last.get(last.size() - 1).getLocation(), mergedSegments, true); - sortedSegments.add(add); - } - - } else if(lastSegment != null) { - sortedSegments.add(lastSegment); - mergedSegments.remove(lastSegment); - while(!mergedSegments.isEmpty()) { - List first = sortedSegments.get(0); - List add = findAndDeleteMinDistance(first.get(0).getLocation(), mergedSegments, false); - sortedSegments.add(0, add); - } - } else { - sortedSegments = mergedSegments; - } - List finalList = new ArrayList(); - for(List s : sortedSegments) { - finalList.addAll(s); - } - // 5. Create combined TransportRoute and return it - return new TransportRoute(route, finalList, allWays); - } - - private List findAndDeleteMinDistance(LatLon location, List> mergedSegments, - boolean attachToBegin) { - int ind = attachToBegin ? 0 : mergedSegments.get(0).size() - 1; - double minDist = MapUtils.getDistance(mergedSegments.get(0).get(ind).getLocation(), location); - int minInd = 0; - for(int i = 1; i < mergedSegments.size(); i++) { - ind = attachToBegin ? 0 : mergedSegments.get(i).size() - 1; - double dist = MapUtils.getDistance(mergedSegments.get(i).get(ind).getLocation(), location); - if(dist < minDist) { - minInd = i; - } - } - return mergedSegments.remove(minInd); - } - - private List getAllWays(List parts) { - List w = new ArrayList(); - for (TransportRoute t : parts) { - w.addAll(t.getForwardWays()); - } - return w; - } - - - - private List> combineSegmentsOfSameRoute(LinkedList> segments) { - List> resultSegments = new ArrayList>(); - while (!segments.isEmpty()) { - List firstSegment = segments.poll(); - boolean merged = true; - while (merged) { - merged = false; - Iterator> it = segments.iterator(); - while (it.hasNext()) { - List segmentToMerge = it.next(); - merged = tryToMerge(firstSegment, segmentToMerge); - - if (merged) { - it.remove(); - break; - } - } - } - resultSegments.add(firstSegment); - } - return resultSegments; - } - - private boolean tryToMerge(List firstSegment, List segmentToMerge) { - if(firstSegment.size() < 2 || segmentToMerge.size() < 2) { - return false; - } - // 1st we check that segments overlap by stop - int commonStopFirst = 0; - int commonStopSecond = 0; - boolean found = false; - for(;commonStopFirst < firstSegment.size(); commonStopFirst++) { - for(commonStopSecond = 0; commonStopSecond < segmentToMerge.size() && !found; commonStopSecond++) { - long lid1 = firstSegment.get(commonStopFirst).getId(); - long lid2 = segmentToMerge.get(commonStopSecond).getId(); - if(lid1 > 0 && lid2 == lid1) { - found = true; - break; - } - } - if(found) { - // important to increment break inside loop - break; - } - } - if(found && commonStopFirst < firstSegment.size()) { - // we've found common stop so we can merge based on stops - // merge last part first - int leftPartFirst = firstSegment.size() - commonStopFirst; - int leftPartSecond = segmentToMerge.size() - commonStopSecond; - if(leftPartFirst < leftPartSecond || (leftPartFirst == leftPartSecond && - firstSegment.get(firstSegment.size() - 1).isMissingStop())) { - while(firstSegment.size() > commonStopFirst) { - firstSegment.remove(firstSegment.size() - 1); - } - for(int i = commonStopSecond; i < segmentToMerge.size(); i++) { - firstSegment.add(segmentToMerge.get(i)); - } - } - // merge first part - if(commonStopFirst < commonStopSecond || (commonStopFirst == commonStopSecond && - firstSegment.get(0).isMissingStop())) { - for(int i = 0; i < commonStopFirst; i++) { - firstSegment.remove(0); - } - for(int i = commonStopSecond; i >= 0; i--) { - firstSegment.add(0, segmentToMerge.get(i)); - } - } - return true; - - } - // no common stops, so try to connect to the end or beginning - // beginning - boolean merged = false; - if (MapUtils.getDistance(firstSegment.get(0).getLocation(), - segmentToMerge.get(segmentToMerge.size() - 1).getLocation()) < MISSING_STOP_SEARCH_RADIUS) { - firstSegment.remove(0); - for(int i = segmentToMerge.size() - 2; i >= 0; i--) { - firstSegment.add(0, segmentToMerge.get(i)); - } - merged = true; - } else if(MapUtils.getDistance(firstSegment.get(firstSegment.size() - 1).getLocation(), - segmentToMerge.get(0).getLocation()) < MISSING_STOP_SEARCH_RADIUS) { - firstSegment.remove(firstSegment.size() - 1); - for(int i = 1; i < segmentToMerge.size(); i++) { - firstSegment.add(segmentToMerge.get(i)); - } - merged = true; - } - return merged; - } - - - - private LinkedList> parseRoutePartsToSegments(List routeParts) { - LinkedList> segs = new LinkedList>(); - // here we assume that missing stops come in pairs - // we don't add segments with 1 stop cause they are irrelevant further - for (TransportRoute part : routeParts) { - List newSeg = new ArrayList(); - for (TransportStop s : part.getForwardStops()) { - newSeg.add(s); - if (s.isMissingStop()) { - if (newSeg.size() > 1) { - segs.add(newSeg); - newSeg = new ArrayList(); - } - } - } - if (newSeg.size() > 1) { - segs.add(newSeg); - } - } - return segs; - } - - private List findIncompleteRouteParts(TransportRoute baseRoute) throws IOException { - List allRoutes = null; - for (BinaryMapIndexReader bmir : routeMap.keySet()) { - // here we could limit routeMap indexes by only certain bbox around start / end (check comment on field) - IncompleteTransportRoute ptr = bmir.getIncompleteTransportRoutes().get(baseRoute.getId()); - if (ptr != null) { - TIntArrayList lst = new TIntArrayList(); - while(ptr != null) { - lst.add(ptr.getRouteOffset()); - ptr = ptr.getNextLinkedRoute(); - } - if(lst.size() > 0) { - if(allRoutes == null) { - allRoutes = new ArrayList(); - } - allRoutes.addAll(bmir.getTransportRoutes(lst.toArray()).valueCollection()); - } - } - } - return allRoutes; - } private void loadTransportSegments(Collection stops, List lst) throws IOException { for(TransportStop s : stops) { @@ -1216,7 +858,7 @@ public class TransportRoutePlanner { } } if (stopIndex != -1) { - if (cfg.useSchedule) { + if (cfg != null && cfg.useSchedule) { loadScheduleRouteSegment(lst, route, stopIndex); } else { TransportRouteSegment segment = new TransportRouteSegment(route, stopIndex); @@ -1230,7 +872,7 @@ public class TransportRoutePlanner { } } } - + private void loadScheduleRouteSegment(List lst, TransportRoute route, int stopIndex) { if(route.getSchedule() != null) { TIntArrayList ti = route.getSchedule().tripIntervals; @@ -1255,10 +897,10 @@ public class TransportRoutePlanner { } } } - + public static List convertToTransportRoutingResult(NativeTransportRoutingResult[] res, - TransportRoutingConfiguration cfg) { - //cache for converted TransportRoutes: + TransportRoutingConfiguration cfg) { + // cache for converted TransportRoutes: TLongObjectHashMap convertedRoutesCache = new TLongObjectHashMap<>(); TLongObjectHashMap convertedStopsCache = new TLongObjectHashMap<>(); @@ -1292,8 +934,8 @@ public class TransportRoutePlanner { } private static TransportRoute convertTransportRoute(NativeTransportRoute nr, - TLongObjectHashMap convertedRoutesCache, - TLongObjectHashMap convertedStopsCache) { + TLongObjectHashMap convertedRoutesCache, + TLongObjectHashMap convertedStopsCache) { TransportRoute r = new TransportRoute(); r.setId(nr.id); r.setLocation(nr.routeLat, nr.routeLon); @@ -1312,10 +954,10 @@ public class TransportRoutePlanner { r.setDist(nr.dist); r.setColor(nr.color); - if (nr.intervals != null && nr.intervals.length > 0 && nr.avgStopIntervals !=null + if (nr.intervals != null && nr.intervals.length > 0 && nr.avgStopIntervals != null && nr.avgStopIntervals.length > 0 && nr.avgWaitIntervals != null && nr.avgWaitIntervals.length > 0) { - r.setSchedule(new TransportSchedule(new TIntArrayList(nr.intervals), - new TIntArrayList(nr.avgStopIntervals), new TIntArrayList(nr.avgWaitIntervals))); + r.setSchedule(new TransportSchedule(new TIntArrayList(nr.intervals), new TIntArrayList(nr.avgStopIntervals), + new TIntArrayList(nr.avgWaitIntervals))); } for (int i = 0; i < nr.waysIds.length; i++) { @@ -1333,7 +975,7 @@ public class TransportRoutePlanner { } private static List convertTransportStops(NativeTransportStop[] nstops, - TLongObjectHashMap convertedStopsCache) { + TLongObjectHashMap convertedStopsCache) { List stops = new ArrayList<>(); for (NativeTransportStop ns : nstops) { if (convertedStopsCache != null && convertedStopsCache.get(ns.id) != null) { @@ -1360,16 +1002,11 @@ public class TransportRoutePlanner { if (ns.pTStopExit_refs != null && ns.pTStopExit_refs.length > 0) { for (int i = 0; i < ns.pTStopExit_refs.length; i++) { - s.addExit(new TransportStopExit(ns.pTStopExit_x31s[i], - ns.pTStopExit_y31s[i], ns.pTStopExit_refs[i])); + s.addExit( + new TransportStopExit(ns.pTStopExit_x31s[i], ns.pTStopExit_y31s[i], ns.pTStopExit_refs[i])); } } - if (ns.referenceToRoutesKeys != null && ns.referenceToRoutesKeys.length > 0) { - for (int i = 0; i < ns.referenceToRoutesKeys.length; i++) { - s.putReferencesToRoutes(ns.referenceToRoutesKeys[i], ns.referenceToRoutesVals[i]); - } - } if (convertedStopsCache == null) { convertedStopsCache = new TLongObjectHashMap<>(); } @@ -1380,4 +1017,7 @@ public class TransportRoutePlanner { } return stops; } + + + } diff --git a/OsmAnd-java/src/main/java/net/osmand/router/TransportStopsRouteReader.java b/OsmAnd-java/src/main/java/net/osmand/router/TransportStopsRouteReader.java new file mode 100644 index 0000000000..5693e1ba43 --- /dev/null +++ b/OsmAnd-java/src/main/java/net/osmand/router/TransportStopsRouteReader.java @@ -0,0 +1,409 @@ +package net.osmand.router; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import gnu.trove.iterator.TIntIterator; +import gnu.trove.list.array.TIntArrayList; +import gnu.trove.map.hash.TIntObjectHashMap; +import gnu.trove.map.hash.TLongObjectHashMap; +import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.binary.BinaryMapIndexReader.SearchRequest; +import net.osmand.data.IncompleteTransportRoute; +import net.osmand.data.LatLon; +import net.osmand.data.TransportRoute; +import net.osmand.data.TransportStop; +import net.osmand.osm.edit.Way; +import net.osmand.util.MapUtils; + +public class TransportStopsRouteReader { + public static final int MISSING_STOP_SEARCH_RADIUS = 15000; + TLongObjectHashMap combinedRoutesCache = new TLongObjectHashMap(); + Map> routesFilesCache = new LinkedHashMap>(); + + + public TransportStopsRouteReader(Collection fls) { + for(BinaryMapIndexReader r : fls) { + routesFilesCache.put(r, new TIntObjectHashMap()); + } + } + + public Collection readMergedTransportStops(SearchRequest sr) throws IOException { + // TODO could be global ? + TLongObjectHashMap loadedTransportStops = new TLongObjectHashMap(); + + for (BinaryMapIndexReader r : routesFilesCache.keySet()) { + sr.clearSearchResults(); + List stops = r.searchTransportIndex(sr); + + TIntArrayList routesToLoad = mergeTransportStops(r, loadedTransportStops, stops); + + TIntObjectHashMap loadedRoutes = routesFilesCache.get(r); +// TODO localFileRoutes.clear(); + TIntObjectHashMap localFileRoutes = new TIntObjectHashMap<>(); //reference, route + loadRoutes(r, localFileRoutes, loadedRoutes, routesToLoad); + + for (TransportStop stop : stops) { + // skip missing stops + if (stop.isMissingStop()) { + continue; + } + long stopId = stop.getId(); + TransportStop multifileStop = loadedTransportStops.get(stopId); + int[] rrs = stop.getReferencesToRoutes(); + // clear up so it won't be used because there is multi file stop + stop.setReferencesToRoutes(null); + if (rrs != null && !multifileStop.isDeleted()) { + for (int rr : rrs) { + TransportRoute route = localFileRoutes.get(rr); + if (route == null) { + System.err.println( + String.format("Something went wrong by loading combined route %d for stop %s", + rr, stop)); + } else { + TransportRoute combinedRoute = getCombinedRoute(route); + if (multifileStop == stop || (!multifileStop.hasRoute(combinedRoute.getId()) && + !multifileStop.isRouteDeleted(combinedRoute.getId()))) { + // duplicates won't be added + multifileStop.addRouteId(combinedRoute.getId()); + multifileStop.addRoute(combinedRoute); + } + } + } + } + } + } + // There should go stops with complete routes: + return loadedTransportStops.valueCollection(); + + } + + + public TIntArrayList mergeTransportStops(BinaryMapIndexReader reader, + TLongObjectHashMap loadedTransportStops, List stops) throws IOException { + TIntArrayList routesToLoad = new TIntArrayList(); + Iterator it = stops.iterator(); + TIntArrayList localRoutesToLoad = new TIntArrayList(); + while (it.hasNext()) { + TransportStop stop = it.next(); + long stopId = stop.getId(); + localRoutesToLoad.clear(); + TransportStop multifileStop = loadedTransportStops.get(stopId); + long[] routesIds = stop.getRoutesIds(); + long[] delRIds = stop.getDeletedRoutesIds(); + if (multifileStop == null) { + loadedTransportStops.put(stopId, stop); + multifileStop = stop; + if (!stop.isDeleted()) { + localRoutesToLoad.addAll(stop.getReferencesToRoutes()); + } + } else if (multifileStop.isDeleted()) { + // stop has nothing to load, so not needed + it.remove(); + } else { + if (delRIds != null) { + for (long deletedRouteId : delRIds) { + multifileStop.addDeletedRouteId(deletedRouteId); + } + } + if (routesIds != null && routesIds.length > 0) { + int[] refs = stop.getReferencesToRoutes(); + for (int i = 0; i < routesIds.length; i++) { + long routeId = routesIds[i]; + if (!multifileStop.hasRoute(routeId) && !multifileStop.isRouteDeleted(routeId)) { + localRoutesToLoad.add(refs[i]); + } + } + } else { + if (stop.hasReferencesToRoutes()) { + // old format + localRoutesToLoad.addAll(stop.getReferencesToRoutes()); + } else { + // stop has noting to load, so not needed + it.remove(); + } + } + } + routesToLoad.addAll(localRoutesToLoad); + // // add valid + // stop and + // references + // to routes + // TODO should be here add route not references +// multifileStop.putReferencesToRoutes(reader.getFile().getName(), localRoutesToLoad.toArray()); + } + return routesToLoad; + } + + public void loadRoutes(BinaryMapIndexReader reader, TIntObjectHashMap localFileRoutes, + TIntObjectHashMap loadedRoutes, TIntArrayList routesToLoad) throws IOException { + // load/combine routes + if (routesToLoad.size() > 0) { + routesToLoad.sort(); + TIntArrayList referencesToLoad = new TIntArrayList(); + TIntIterator itr = routesToLoad.iterator(); + int prev = routesToLoad.get(0) - 1; // different + while (itr.hasNext()) { + int nxt = itr.next(); + if (prev != nxt) { + if (localFileRoutes != null && loadedRoutes != null && loadedRoutes.contains(nxt)) { // check if + localFileRoutes.put(nxt, loadedRoutes.get(nxt)); + } else { + referencesToLoad.add(nxt); + } + } + } + if (localFileRoutes != null && loadedRoutes != null) { + reader.loadTransportRoutes(referencesToLoad.toArray(), localFileRoutes); + loadedRoutes.putAll(localFileRoutes); + } + } + } + + private TransportRoute getCombinedRoute(TransportRoute route) throws IOException { + if (!route.isIncomplete()) { + return route; + } + TransportRoute c = combinedRoutesCache.get(route.getId()); + if (c == null) { + c = combineRoute(route); + combinedRoutesCache.put(route.getId(), c); + } + return c; + } + + private TransportRoute combineRoute(TransportRoute route) throws IOException { + // 1. Get all available route parts; + List incompleteRoutes = findIncompleteRouteParts(route); + if (incompleteRoutes == null) { + return route; + } + // here could be multiple overlays between same points + // It's better to remove them especially identical segments + List allWays = getAllWays(incompleteRoutes); + + // 2. Get array of segments (each array size > 1): + LinkedList> stopSegments = parseRoutePartsToSegments(incompleteRoutes); + + // 3. Merge segments and remove excess missingStops (when they are closer then MISSING_STOP_SEARCH_RADIUS): + // + Check for missingStops. If they present in the middle/there more then one segment - we have a hole in the + // map data + List> mergedSegments = combineSegmentsOfSameRoute(stopSegments); + + // 4. Now we need to properly sort segments, proper sorting is minimizing distance between stops + // So it is salesman problem, we have this solution at TspAnt, but if we know last or first segment we can solve + // it straightforward + List firstSegment = null; + List lastSegment = null; + for (List l : mergedSegments) { + if (!l.get(0).isMissingStop()) { + firstSegment = l; + } + if (!l.get(l.size() - 1).isMissingStop()) { + lastSegment = l; + } + } + List> sortedSegments = new ArrayList>(); + if (firstSegment != null) { + sortedSegments.add(firstSegment); + mergedSegments.remove(firstSegment); + while (!mergedSegments.isEmpty()) { + List last = sortedSegments.get(sortedSegments.size() - 1); + List add = findAndDeleteMinDistance(last.get(last.size() - 1).getLocation(), + mergedSegments, true); + sortedSegments.add(add); + } + + } else if (lastSegment != null) { + sortedSegments.add(lastSegment); + mergedSegments.remove(lastSegment); + while (!mergedSegments.isEmpty()) { + List first = sortedSegments.get(0); + List add = findAndDeleteMinDistance(first.get(0).getLocation(), mergedSegments, false); + sortedSegments.add(0, add); + } + } else { + sortedSegments = mergedSegments; + } + List finalList = new ArrayList(); + for (List s : sortedSegments) { + finalList.addAll(s); + } + // 5. Create combined TransportRoute and return it + return new TransportRoute(route, finalList, allWays); + } + + private List findAndDeleteMinDistance(LatLon location, List> mergedSegments, + boolean attachToBegin) { + int ind = attachToBegin ? 0 : mergedSegments.get(0).size() - 1; + double minDist = MapUtils.getDistance(mergedSegments.get(0).get(ind).getLocation(), location); + int minInd = 0; + for (int i = 1; i < mergedSegments.size(); i++) { + ind = attachToBegin ? 0 : mergedSegments.get(i).size() - 1; + double dist = MapUtils.getDistance(mergedSegments.get(i).get(ind).getLocation(), location); + if (dist < minDist) { + minInd = i; + } + } + return mergedSegments.remove(minInd); + } + + private List getAllWays(List parts) { + List w = new ArrayList(); + for (TransportRoute t : parts) { + w.addAll(t.getForwardWays()); + } + return w; + } + + private List> combineSegmentsOfSameRoute(LinkedList> segments) { + List> resultSegments = new ArrayList>(); + while (!segments.isEmpty()) { + List firstSegment = segments.poll(); + boolean merged = true; + while (merged) { + merged = false; + Iterator> it = segments.iterator(); + while (it.hasNext()) { + List segmentToMerge = it.next(); + merged = tryToMerge(firstSegment, segmentToMerge); + + if (merged) { + it.remove(); + break; + } + } + } + resultSegments.add(firstSegment); + } + return resultSegments; + } + + private boolean tryToMerge(List firstSegment, List segmentToMerge) { + if (firstSegment.size() < 2 || segmentToMerge.size() < 2) { + return false; + } + // 1st we check that segments overlap by stop + int commonStopFirst = 0; + int commonStopSecond = 0; + boolean found = false; + for (; commonStopFirst < firstSegment.size(); commonStopFirst++) { + for (commonStopSecond = 0; commonStopSecond < segmentToMerge.size() && !found; commonStopSecond++) { + long lid1 = firstSegment.get(commonStopFirst).getId(); + long lid2 = segmentToMerge.get(commonStopSecond).getId(); + if (lid1 > 0 && lid2 == lid1) { + found = true; + break; + } + } + if (found) { + // important to increment break inside loop + break; + } + } + if (found && commonStopFirst < firstSegment.size()) { + // we've found common stop so we can merge based on stops + // merge last part first + int leftPartFirst = firstSegment.size() - commonStopFirst; + int leftPartSecond = segmentToMerge.size() - commonStopSecond; + if (leftPartFirst < leftPartSecond + || (leftPartFirst == leftPartSecond && firstSegment.get(firstSegment.size() - 1).isMissingStop())) { + while (firstSegment.size() > commonStopFirst) { + firstSegment.remove(firstSegment.size() - 1); + } + for (int i = commonStopSecond; i < segmentToMerge.size(); i++) { + firstSegment.add(segmentToMerge.get(i)); + } + } + // merge first part + if (commonStopFirst < commonStopSecond + || (commonStopFirst == commonStopSecond && firstSegment.get(0).isMissingStop())) { + for (int i = 0; i < commonStopFirst; i++) { + firstSegment.remove(0); + } + for (int i = commonStopSecond; i >= 0; i--) { + firstSegment.add(0, segmentToMerge.get(i)); + } + } + return true; + + } + // no common stops, so try to connect to the end or beginning + // beginning + boolean merged = false; + if (MapUtils.getDistance(firstSegment.get(0).getLocation(), + segmentToMerge.get(segmentToMerge.size() - 1).getLocation()) < MISSING_STOP_SEARCH_RADIUS) { + firstSegment.remove(0); + for (int i = segmentToMerge.size() - 2; i >= 0; i--) { + firstSegment.add(0, segmentToMerge.get(i)); + } + merged = true; + } else if (MapUtils.getDistance(firstSegment.get(firstSegment.size() - 1).getLocation(), + segmentToMerge.get(0).getLocation()) < MISSING_STOP_SEARCH_RADIUS) { + firstSegment.remove(firstSegment.size() - 1); + for (int i = 1; i < segmentToMerge.size(); i++) { + firstSegment.add(segmentToMerge.get(i)); + } + merged = true; + } + return merged; + } + + private LinkedList> parseRoutePartsToSegments(List routeParts) { + LinkedList> segs = new LinkedList>(); + // here we assume that missing stops come in pairs + // we don't add segments with 1 stop cause they are irrelevant further + for (TransportRoute part : routeParts) { + List newSeg = new ArrayList(); + for (TransportStop s : part.getForwardStops()) { + newSeg.add(s); + if (s.isMissingStop()) { + if (newSeg.size() > 1) { + segs.add(newSeg); + newSeg = new ArrayList(); + } + } + } + if (newSeg.size() > 1) { + segs.add(newSeg); + } + } + return segs; + } + + private List findIncompleteRouteParts(TransportRoute baseRoute) throws IOException { + List allRoutes = null; + for (BinaryMapIndexReader bmir : routesFilesCache.keySet()) { + // here we could limit routeMap indexes by only certain bbox around start / end (check comment on field) + IncompleteTransportRoute ptr = bmir.getIncompleteTransportRoutes().get(baseRoute.getId()); + if (ptr != null) { + TIntArrayList lst = new TIntArrayList(); + while (ptr != null) { + lst.add(ptr.getRouteOffset()); + ptr = ptr.getNextLinkedRoute(); + } + if (lst.size() > 0) { + if (allRoutes == null) { + allRoutes = new ArrayList(); + } + allRoutes.addAll(bmir.getTransportRoutes(lst.toArray()).valueCollection()); + } + } + } + return allRoutes; + } + + + + + + + +} diff --git a/OsmAnd/res/drawable/img_feature_purchased.xml b/OsmAnd/res/drawable/img_feature_purchased.xml index e82f078224..d734fd179d 100644 --- a/OsmAnd/res/drawable/img_feature_purchased.xml +++ b/OsmAnd/res/drawable/img_feature_purchased.xml @@ -1,19 +1,14 @@ - + - - - - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/wikivoyage_search_history_placeholder.xml b/OsmAnd/res/drawable/wikivoyage_search_history_placeholder.xml index e19bdcc293..8fd9d1ba9b 100644 --- a/OsmAnd/res/drawable/wikivoyage_search_history_placeholder.xml +++ b/OsmAnd/res/drawable/wikivoyage_search_history_placeholder.xml @@ -1,23 +1,12 @@ - - - - - - + + android:top="2dp" /> - - - + \ No newline at end of file diff --git a/OsmAnd/res/drawable/wikivoyage_search_placeholder.xml b/OsmAnd/res/drawable/wikivoyage_search_placeholder.xml index 356e581f1d..727c4f99a9 100644 --- a/OsmAnd/res/drawable/wikivoyage_search_placeholder.xml +++ b/OsmAnd/res/drawable/wikivoyage_search_placeholder.xml @@ -2,22 +2,20 @@ - + + android:height="28dp" /> + + android:tint="@color/icon_color_default_light" + android:top="2dp" /> - - - + \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/TransportStopController.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/TransportStopController.java index 8198833bd7..deeb139024 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/TransportStopController.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/TransportStopController.java @@ -159,7 +159,7 @@ public class TransportStopController extends MenuController { private void addTransportStopRoutes(OsmandApplication app, List stops, List routes, boolean useEnglishNames) { for (TransportStop tstop : stops) { - if (tstop.hasReferencesToRoutesMap()) { + if (!tstop.isDeleted()) { addRoutes(app, routes, useEnglishNames, tstop, transportStop, (int) MapUtils.getDistance(tstop.getLocation(), transportStop.getLocation())); } } diff --git a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java index 055f9811d5..1badf2616d 100644 --- a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java +++ b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java @@ -48,6 +48,7 @@ import net.osmand.plus.resources.AsyncLoadingThread.TileLoadDownloadRequest; import net.osmand.plus.srtmplugin.SRTMPlugin; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.router.TransportRoutePlanner.TransportRoutingContext; +import net.osmand.router.TransportStopsRouteReader; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; @@ -208,7 +209,7 @@ public class ResourceManager { private final Map addressMap = new ConcurrentHashMap(); protected final Map amenityRepositories = new ConcurrentHashMap(); // protected final Map routingMapFiles = new ConcurrentHashMap(); - protected final Map transportRepositories = new ConcurrentHashMap(); + protected final Map transportRepositories = new ConcurrentHashMap(); protected final Map indexFileNames = new ConcurrentHashMap(); protected final Map basemapFileNames = new ConcurrentHashMap(); @@ -742,7 +743,7 @@ public class ResourceManager { addressMap.put(f.getName(), rarb); } if (mapReader.hasTransportData()) { - transportRepositories.put(f.getName(), new TransportIndexRepositoryBinary(resource)); + transportRepositories.put(f.getName(), resource); } // disable osmc for routing temporarily due to some bugs if (mapReader.containsRouteData() && (!f.getParentFile().equals(liveDir) || @@ -989,51 +990,31 @@ public class ResourceManager { ////////////////////////////////////////////// Working with transport //////////////////////////////////////////////// - public LinkedHashMap getTransportRepositories() { + private List getTransportRepositories(double topLat, double leftLon, double bottomLat, double rightLon) { List fileNames = new ArrayList<>(transportRepositories.keySet()); Collections.sort(fileNames, Algorithms.getStringVersionComparator()); - LinkedHashMap res = new LinkedHashMap<>(); + List res = new ArrayList<>(); for (String fileName : fileNames) { - TransportIndexRepository r = transportRepositories.get(fileName); - if (r != null) { - res.put(fileName, r); + BinaryMapReaderResource r = transportRepositories.get(fileName); + if (r != null && r.isUseForPublicTransport() && + r.getShallowReader().containTransportData(topLat, leftLon, bottomLat, rightLon)) { + res.add(r.getReader(BinaryMapReaderResourceType.TRANSPORT)); } } return res; } - public List searchTransportRepositories(double latitude, double longitude) { - List repos = new ArrayList<>(); - for (TransportIndexRepository index : getTransportRepositories().values()) { - if (index.isUseForPublicTransport() && index.checkContains(latitude,longitude)) { - repos.add(index); - } - } - return repos; - } public List searchTransportSync(double topLat, double leftLon, double bottomLat, double rightLon, ResultMatcher matcher) throws IOException { - List repos = new ArrayList<>(); - TLongObjectHashMap loadedTransportStops = new TLongObjectHashMap<>(); - for (TransportIndexRepository index : getTransportRepositories().values()) { - if (index.isUseForPublicTransport() && index.checkContains(topLat, leftLon, bottomLat, rightLon)) { - repos.add(index); - } - } - if (!repos.isEmpty()) { - for (TransportIndexRepository r : repos) { - List stops = new ArrayList<>(); - r.searchTransportStops(topLat, leftLon, bottomLat, rightLon, -1, stops, matcher); - BinaryMapIndexReader reader = ((TransportIndexRepositoryBinary) r).getOpenFile(); - if (reader != null) { - TransportRoutingContext.mergeTransportStops(reader, loadedTransportStops, stops, null, null); - } - } - } + TransportStopsRouteReader readers = + new TransportStopsRouteReader(getTransportRepositories(topLat, leftLon, bottomLat, rightLon)); List stops = new ArrayList<>(); - for (TransportStop s : loadedTransportStops.valueCollection()) { - if (!s.isDeleted()) { + BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchTransportRequest(MapUtils.get31TileNumberX(leftLon), + MapUtils.get31TileNumberX(rightLon), MapUtils.get31TileNumberY(topLat), + MapUtils.get31TileNumberY(bottomLat), -1, stops); + for (TransportStop s : readers.readMergedTransportStops(req)) { + if (!s.isDeleted() && !s.isMissingStop()) { stops.add(s); } } @@ -1041,19 +1022,11 @@ public class ResourceManager { } public List getRoutesForStop(TransportStop stop) { - List routes = new ArrayList<>(); - LinkedHashMap repositories = getTransportRepositories(); - LinkedHashMap referencesToRoutes = stop.getReferencesToRoutesMap(); - if (referencesToRoutes != null) { - for (Entry refs : referencesToRoutes.entrySet()) { - TransportIndexRepository r = repositories.get(refs.getKey()); - if (r != null) { - List rr = r.getRoutesForReferences(refs.getValue()); - routes.addAll(rr); - } - } + List rts = stop.getRoutes(); + if(rts != null) { + return rts; } - return routes; + return Collections.emptyList(); } ////////////////////////////////////////////// Working with map //////////////////////////////////////////////// diff --git a/OsmAnd/src/net/osmand/plus/resources/TransportIndexRepository.java b/OsmAnd/src/net/osmand/plus/resources/TransportIndexRepository.java deleted file mode 100644 index 54b4408b8e..0000000000 --- a/OsmAnd/src/net/osmand/plus/resources/TransportIndexRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.osmand.plus.resources; - -import net.osmand.ResultMatcher; -import net.osmand.data.TransportRoute; -import net.osmand.data.TransportStop; - -import java.util.List; - -public interface TransportIndexRepository { - - public boolean checkContains(double latitude, double longitude); - - public boolean checkContains(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude); - - public boolean acceptTransportStop(TransportStop stop); - - public void searchTransportStops(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, - int limit, List stops, ResultMatcher matcher); - - public List getRoutesForStop(TransportStop stop); - - public List getRoutesForReferences(int[] referencesToRoutes); - - public boolean isUseForPublicTransport(); -} diff --git a/OsmAnd/src/net/osmand/plus/resources/TransportIndexRepositoryBinary.java b/OsmAnd/src/net/osmand/plus/resources/TransportIndexRepositoryBinary.java deleted file mode 100644 index 596b936062..0000000000 --- a/OsmAnd/src/net/osmand/plus/resources/TransportIndexRepositoryBinary.java +++ /dev/null @@ -1,112 +0,0 @@ -package net.osmand.plus.resources; - -import androidx.annotation.Nullable; - -import net.osmand.PlatformUtil; -import net.osmand.ResultMatcher; -import net.osmand.binary.BinaryMapIndexReader; -import net.osmand.data.TransportRoute; -import net.osmand.data.TransportStop; -import net.osmand.plus.resources.ResourceManager.BinaryMapReaderResource; -import net.osmand.plus.resources.ResourceManager.BinaryMapReaderResourceType; -import net.osmand.util.Algorithms; -import net.osmand.util.MapUtils; - -import org.apache.commons.logging.Log; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public class TransportIndexRepositoryBinary implements TransportIndexRepository { - private static final Log log = PlatformUtil.getLog(TransportIndexRepositoryBinary.class); - private BinaryMapReaderResource resource; - - public TransportIndexRepositoryBinary(BinaryMapReaderResource resource) { - this.resource = resource; - } - - @Nullable - public BinaryMapIndexReader getOpenFile() { - return resource.getReader(BinaryMapReaderResourceType.TRANSPORT); - } - - @Override - public boolean checkContains(double latitude, double longitude) { - BinaryMapIndexReader shallowReader = resource.getShallowReader(); - return shallowReader != null && shallowReader.containTransportData(latitude, longitude); - } - @Override - public boolean checkContains(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude) { - BinaryMapIndexReader shallowReader = resource.getShallowReader(); - return shallowReader != null && shallowReader.containTransportData(topLatitude, leftLongitude, bottomLatitude, rightLongitude); - } - - @Override - public synchronized void searchTransportStops(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, - int limit, List stops, ResultMatcher matcher) { - long now = System.currentTimeMillis(); - try { - BinaryMapIndexReader reader = getOpenFile(); - if (reader != null) { - reader.searchTransportIndex(BinaryMapIndexReader.buildSearchTransportRequest(MapUtils.get31TileNumberX(leftLongitude), - MapUtils.get31TileNumberX(rightLongitude), MapUtils.get31TileNumberY(topLatitude), - MapUtils.get31TileNumberY(bottomLatitude), limit, stops)); - if (log.isDebugEnabled()) { - log.debug(String.format("Search for %s done in %s ms found %s.", //$NON-NLS-1$ - topLatitude + " " + leftLongitude, System.currentTimeMillis() - now, stops.size())); //$NON-NLS-1$ - } - } - } catch (IOException e) { - log.error("Disk error ", e); //$NON-NLS-1$ - } - } - - @Override - public synchronized List getRoutesForStop(TransportStop stop) { - return getRoutesForReferences(stop.getReferencesToRoutes()); - } - - @Override - public List getRoutesForReferences(int[] referencesToRoutes) { - try { - BinaryMapIndexReader reader = getOpenFile(); - if (reader != null) { - Collection res = reader.getTransportRoutes(referencesToRoutes).valueCollection(); - if (res != null) { - List lst = new ArrayList<>(res); - Collections.sort(lst, new Comparator() { - @Override - public int compare(TransportRoute o1, TransportRoute o2) { - int i1 = Algorithms.extractFirstIntegerNumber(o1.getRef()); - int i2 = Algorithms.extractFirstIntegerNumber(o2.getRef()); - int r = Algorithms.compare(i1, i2); - if (r == 0) { - r = Algorithms.compare(o1.getName(), o2.getName()); - } - return r; - } - }); - return lst; - } - } - } catch (IOException e) { - log.error("Disk error ", e); //$NON-NLS-1$ - } - return Collections.emptyList(); - } - - @Override - public boolean acceptTransportStop(TransportStop stop) { - BinaryMapIndexReader shallowReader = resource.getShallowReader(); - return shallowReader != null && shallowReader.transportStopBelongsTo(stop); - } - - @Override - public boolean isUseForPublicTransport() { - return resource.isUseForPublicTransport(); - } -}