From b456c41b341e7f1bd6c60c08abc4b0ab816770e6 Mon Sep 17 00:00:00 2001 From: Victor Shcherb Date: Sat, 16 May 2020 16:33:59 +0200 Subject: [PATCH] Update transport rotues --- .../osmand/router/TransportRoutePlanner.java | 272 +++++++----------- 1 file changed, 100 insertions(+), 172 deletions(-) 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 4fc7f69141..0143e17b64 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java @@ -8,10 +8,12 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.PriorityQueue; + import gnu.trove.iterator.TIntIterator; import gnu.trove.list.array.TIntArrayList; import gnu.trove.map.hash.TIntObjectHashMap; @@ -941,31 +943,33 @@ public class TransportRoutePlanner { } 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); - List allWays = getAllWays(incompleteRoutes); //TODO check ways for right order? Artifacts during drawing. // 2. Get array of segments: - List> segments = parseRoutePartsToSegments(incompleteRoutes); - Collections.sort(segments, compareSegsBySize); + LinkedList> stopSegments = parseRoutePartsToSegments(incompleteRoutes); // 3. Merge segments and remove excess missingStops (when they are closer then MISSING_STOP_SEARCH_RADIUS): - // 4. Check for missingStops. If they present in the middle/there more then one segment - we have a hole in the map data - List> mergedSegments = combineSegments(segments); + // + 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 - // 5. Create combined TransportRoute and return it - if (mergedSegments.size() > 1 || mergedSegments.size() == 0) { - // TODO sort incomplete routes (and remove missingStops) and merge into one? - return route; - } else { - return new TransportRoute(route, mergedSegments.get(0), allWays); + // TODO + List finalList = new ArrayList(); + for(List s : mergedSegments) { + finalList.addAll(s); } + // 5. Create combined TransportRoute and return it + return new TransportRoute(route, finalList, allWays); } private List getAllWays(List parts) { @@ -976,181 +980,105 @@ public class TransportRoutePlanner { return w; } - Comparator> compareSegsBySize = new Comparator>() { - public int compare(List s1, List s2) { - return -Integer.compare(s1.size(), s2.size()); - } - }; - private List> combineSegments(List> segments) { - List> rawSegments = segments; - - List> partsToDelete = new ArrayList>(); - List> partsToReturn = new ArrayList>(); - List base; - int startSize = 0; - - Iterator> segItr = rawSegments.iterator(); - while (segItr.hasNext()) { - startSize = rawSegments.size(); - partsToDelete.clear(); - partsToReturn.clear(); - base = segItr.next(); - segItr.remove(); - - TransportStop firstStopMissing = base.get(0).isMissingStop() ? base.get(0) : null; - TransportStop lastStopMissing = base.get(base.size() - 1).isMissingStop() ? base.get(base.size() - 1) - : null; - - for (int i = 0; i < segments.size(); i++) { - // compare every other piece of route with base (largest part so far) - // and if it has common stops or close missing stops try to combine - List candidate = rawSegments.get(i); - TransportStop cmss = candidate.get(0).isMissingStop() ? candidate.get(0) : null; - TransportStop cmse = candidate.get(candidate.size() - 1).isMissingStop() - ? candidate.get(candidate.size() - 1) - : null; - int csStopCount = candidate.size(); - if (cmss != null) { - csStopCount--; - } - if (cmse != null) { - csStopCount--; - } - int csSameStopsCount = 0; - for (TransportStop s : base) { - if (!s.isMissingStop()) { - for (TransportStop cs : candidate) { - if (!cs.isMissingStop() && s.getId().equals(cs.getId())) { - csSameStopsCount++; - } - } - } - } - if (csStopCount == csSameStopsCount) { - // all stops of candidate inside base, delete candidate - partsToDelete.add(candidate); - continue; - - } else { - if (csSameStopsCount > 0 && firstStopMissing != null && lastStopMissing == null - && cmse != null) { - // parts intersecting and we know what sides to connect, attach to start - base = mergeSegments(base, candidate, false); - - } else if (csSameStopsCount > 0 && lastStopMissing != null && firstStopMissing == null - && cmss != null) { - // parts intersecting and we know what sides to connect, attach to end - base = mergeSegments(base, candidate, true); - - } else { - // check for missing stops in candidate and attach accordingly - double distStartToEnd = MISSING_STOP_SEARCH_RADIUS + 1; - double distEndToStart = MISSING_STOP_SEARCH_RADIUS + 1; - if (cmss != null && lastStopMissing != null) { - distStartToEnd = MapUtils.getDistance(cmss.getLocation(), - lastStopMissing.getLocation()); - } - if (cmse != null && firstStopMissing != null) { - distEndToStart = MapUtils.getDistance(cmse.getLocation(), - firstStopMissing.getLocation()); - } - if (distStartToEnd < distEndToStart && distStartToEnd <= MISSING_STOP_SEARCH_RADIUS) { - base = mergeSegments(base, candidate, true); - } else if (distEndToStart < distStartToEnd && distEndToStart <= MISSING_STOP_SEARCH_RADIUS) { - base = mergeSegments(base, candidate, false); - } else { - if (csSameStopsCount == 0) { - // it's OK, we should look for other parts first - partsToReturn.add(candidate); - System.out.println("Candidate is not connected to Base, continue search"); - } else { - // it's not OK, if there is intersecting stops and too long distance between - // missingStops, there is some error in data - System.out.println("MERGING ERROR. THERE IS SOMETHING WRONG WITH DATA"); - } - } - } - partsToDelete.add(candidate); - } - } - for (List p : partsToDelete) { - rawSegments.remove(p); - } - rawSegments.addAll(partsToReturn); - rawSegments.add(base); - //Check if all is merged: - if (rawSegments.size() == 1) { - break; - } - //If we still have several segments, but after iteration they number didn't dwindle, - //check if we still could merge some of them or do we have a hole in the data - boolean hasValidCandidate = false; - if (rawSegments.size() == startSize) { - for (int i = 0; i < rawSegments.size()-1; i++) { - TransportStop ms = rawSegments.get(i).get(0).isMissingStop() ? rawSegments.get(i).get(0) : null; - TransportStop me = rawSegments.get(i).get(rawSegments.get(i).size()-1).isMissingStop() - ? rawSegments.get(i).get(rawSegments.get(i).size()-1) : null; - for (int j = 1; j < rawSegments.size(); j++) { - TransportStop cms = rawSegments.get(j).get(0).isMissingStop() ? rawSegments.get(j).get(0) : null; - TransportStop cme = rawSegments.get(j).get(rawSegments.get(j).size()-1).isMissingStop() - ? rawSegments.get(j).get(rawSegments.get(j).size()-1) : null; - if (ms != null && cme != null && MapUtils.getDistance(ms.getLocation(), cme.getLocation()) <= MISSING_STOP_SEARCH_RADIUS) { - hasValidCandidate = true; - } - if (me != null && cms != null && MapUtils.getDistance(me.getLocation(), cms.getLocation()) <= MISSING_STOP_SEARCH_RADIUS) { - hasValidCandidate = true; - } - //we has at least one valid pair of segments for merging - if (hasValidCandidate) { - break; - } + + 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; } } } - //if we could not merge any more segments - break; - if (rawSegments.size() == 1 || (rawSegments.size() == startSize && !hasValidCandidate)) { - 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; + for(;commonStopFirst < firstSegment.size(); commonStopFirst++) { + for(; commonStopSecond < segmentToMerge.size(); commonStopSecond++ ) { + long lid1 = firstSegment.get(commonStopFirst).getId(); + long lid2 = segmentToMerge.get(commonStopSecond).getId(); + if(lid1 > 0 && lid2 == lid1) { + break; + } } } - return rawSegments; + if(commonStopFirst < firstSegment.size()) { + // we've found common stop so we can merge based on stops + // merge last part first + if(firstSegment.size() - commonStopFirst < segmentToMerge.size() - commonStopSecond) { + 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) { + 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 List mergeSegments(List base, List candidate, boolean forward) { - List result; - if (forward) { - result = new ArrayList<>(base.subList(0, base.size()-1)); - for (int i = 1; i < candidate.size(); i++) { - if (!result.contains(candidate.get(i))) { - result.add(candidate.get(i)); - } - } - } else { - result = new ArrayList<>(candidate.subList(0, candidate.size()-1)); - for (int i = 1; i < base.size(); i++) { - if (!result.contains(base.get(i))) { - result.add(base.get(i)); - } - } - } - return result; - } - private List> parseRoutePartsToSegments(List routeParts) { - List> segs = new ArrayList>(); + private LinkedList> parseRoutePartsToSegments(List routeParts) { + LinkedList> segs = new LinkedList>(); + // here we assume that missing stops come in pairs + // TODO check generation that are doubles for (TransportRoute part : routeParts) { - List newSeg = new ArrayList(); + List newSeg = new ArrayList(); for (TransportStop s : part.getForwardStops()) { + newSeg.add(s); if (s.isMissingStop()) { - if (newSeg.isEmpty()) { - newSeg.add(s); - } else { - newSeg.add(s); + if (!newSeg.isEmpty()) { segs.add(newSeg); newSeg = new ArrayList(); } - } else { - newSeg.add(s); } } if (!newSeg.isEmpty()) {