Merge pull request #8978 from osmandapp/route_merge

Route merge
This commit is contained in:
vshcherb 2020-05-17 13:24:16 +02:00 committed by GitHub
commit 36ae49de65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 8132 additions and 2732 deletions

View file

@ -3,6 +3,7 @@ package net.osmand.binary;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.WireFormat;
import net.osmand.Collator;
@ -26,6 +27,7 @@ import net.osmand.binary.OsmandOdb.OsmAndMapIndex.MapRootLevel;
import net.osmand.data.Amenity;
import net.osmand.data.Building;
import net.osmand.data.City;
import net.osmand.data.IncompleteTransportRoute;
import net.osmand.data.LatLon;
import net.osmand.data.MapObject;
import net.osmand.data.Street;
@ -109,7 +111,8 @@ public class BinaryMapIndexReader {
/*private*/ List<TransportIndex> transportIndexes = new ArrayList<TransportIndex>();
/*private*/ List<RouteRegion> routingIndexes = new ArrayList<RouteRegion>();
/*private*/ List<BinaryIndexPart> indexes = new ArrayList<BinaryIndexPart>();
TLongObjectHashMap<IncompleteTransportRoute> incompleteTransportRoutes = null;
protected CodedInputStream codedIS;
private final BinaryMapTransportReaderAdapter transportAdapter;
@ -2634,5 +2637,18 @@ public class BinaryMapIndexReader {
}
}
public TLongObjectHashMap<IncompleteTransportRoute> getIncompleteTransportRoutes() throws InvalidProtocolBufferException, IOException {
if (incompleteTransportRoutes == null) {
incompleteTransportRoutes = new TLongObjectHashMap<>();
for (TransportIndex ti : transportIndexes) {
if (ti.incompleteRoutesLength > 0) {
transportAdapter.readIncompleteRoutesList(incompleteTransportRoutes, ti.incompleteRoutesLength,
ti.incompleteRoutesOffset);
}
}
}
return incompleteTransportRoutes;
}
}

View file

@ -9,6 +9,7 @@ import com.google.protobuf.CodedInputStream;
import com.google.protobuf.WireFormat;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import net.osmand.binary.BinaryMapIndexReader.SearchRequest;
import net.osmand.data.TransportSchedule;
import net.osmand.data.TransportStop;
@ -43,6 +44,8 @@ public class BinaryMapTransportReaderAdapter {
int stopsFileOffset = 0;
int stopsFileLength = 0;
int incompleteRoutesOffset = 0;
int incompleteRoutesLength = 0;
public String getPartName() {
return "Transport";
@ -67,7 +70,8 @@ public class BinaryMapTransportReaderAdapter {
public int getBottom() {
return bottom;
}
IndexStringTable stringTable = null;
}
@ -108,12 +112,19 @@ public class BinaryMapTransportReaderAdapter {
ind.stringTable = st;
codedIS.seek(st.length + st.fileOffset);
break;
case OsmandOdb.OsmAndTransportIndex.INCOMPLETEROUTES_FIELD_NUMBER :
ind.incompleteRoutesLength = codedIS.readRawVarint32();
ind.incompleteRoutesOffset = codedIS.getTotalBytesRead();
codedIS.seek(ind.incompleteRoutesLength + ind.incompleteRoutesOffset);
break;
default:
skipUnknownField(t);
break;
}
}
}
private void readTransportBounds(TransportIndex ind) throws IOException {
while(true){
@ -240,6 +251,79 @@ public class BinaryMapTransportReaderAdapter {
return ((char) i)+"";
}
public void readIncompleteRoutesList(TLongObjectHashMap<net.osmand.data.IncompleteTransportRoute> incompleteRoutes,
int length, int offset) throws IOException {
codedIS.seek(offset);
boolean end = false;
while (!end) {
int t = codedIS.readTag();
int tag = WireFormat.getTagFieldNumber(t);
switch (tag) {
case 0:
end = true;
break;
case OsmandOdb.IncompleteTransportRoutes.ROUTES_FIELD_NUMBER:
int l = codedIS.readRawVarint32();
int olds = codedIS.pushLimit(l);
net.osmand.data.IncompleteTransportRoute ir = readIncompleteRoute();
net.osmand.data.IncompleteTransportRoute itr = incompleteRoutes.get(ir.getRouteId());
if(itr != null) {
itr.setNextLinkedRoute(ir);
} else {
incompleteRoutes.put(ir.getRouteId(), ir);
}
codedIS.popLimit(olds);
break;
default:
skipUnknownField(t);
break;
}
}
}
public net.osmand.data.IncompleteTransportRoute readIncompleteRoute() throws IOException {
net.osmand.data.IncompleteTransportRoute dataObject = new net.osmand.data.IncompleteTransportRoute();
boolean end = false;
while(!end){
int t = codedIS.readTag();
int tag = WireFormat.getTagFieldNumber(t);
switch (tag) {
case 0:
end = true;
break;
case OsmandOdb.IncompleteTransportRoute.ID_FIELD_NUMBER :
dataObject.setRouteId(codedIS.readUInt64());
break;
case OsmandOdb.IncompleteTransportRoute.ROUTEREF_FIELD_NUMBER :
dataObject.setRouteOffset(codedIS.readRawVarint32());
break;
case OsmandOdb.IncompleteTransportRoute.OPERATOR_FIELD_NUMBER :
skipUnknownField(t);
// dataObject.setOperator(regStr(stringTable));
break;
case OsmandOdb.IncompleteTransportRoute.REF_FIELD_NUMBER :
skipUnknownField(t);
// dataObject.setRef(regStr(stringTable));
break;
case OsmandOdb.IncompleteTransportRoute.TYPE_FIELD_NUMBER :
skipUnknownField(t);
// dataObject.setType(regStr(stringTable));
break;
case OsmandOdb.IncompleteTransportRoute.MISSINGSTOPS_FIELD_NUMBER :
// dataObject.getMissingStops().add(codedIS.readSInt32()); //skip for now
skipUnknownField(t);
break;
default:
skipUnknownField(t);
break;
}
}
return dataObject;
}
public net.osmand.data.TransportRoute getTransportRoute(int filePointer, TIntObjectHashMap<String> stringTable,
boolean onlyDescription) throws IOException {
codedIS.seek(filePointer);
@ -394,7 +478,6 @@ public class BinaryMapTransportReaderAdapter {
codedIS.seek(ind.stringTable.fileOffset);
int oldLimit = codedIS.pushLimit(ind.stringTable.length);
int current = 0;
int i = 0;
while (codedIS.getBytesUntilLimit() > 0) {
int t = codedIS.readTag();
int tag = WireFormat.getTagFieldNumber(t);

View file

@ -26,7 +26,6 @@ import net.osmand.binary.OsmandIndex.PoiPart;
import net.osmand.binary.OsmandIndex.RoutingPart;
import net.osmand.binary.OsmandIndex.RoutingSubregion;
import net.osmand.binary.OsmandIndex.TransportPart;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
@ -131,6 +130,10 @@ public class CachedOsmandIndexes {
transport.setBottom(index.getBottom());
transport.setStopsTableLength(index.stopsFileLength);
transport.setStopsTableOffset(index.stopsFileOffset);
// if(index.incompleteRoutesLength > 0) {
transport.setIncompleteRoutesLength(index.incompleteRoutesLength);
transport.setIncompleteRoutesOffset(index.incompleteRoutesOffset);
// }
transport.setStringTableLength(index.stringTable.length);
transport.setStringTableOffset(index.stringTable.fileOffset);
fileIndex.addTransportIndex(transport);
@ -269,6 +272,8 @@ public class CachedOsmandIndexes {
mi.bottom = index.getBottom();
mi.stopsFileLength = index.getStopsTableLength();
mi.stopsFileOffset = index.getStopsTableOffset();
mi.incompleteRoutesLength = index.getIncompleteRoutesLength();
mi.incompleteRoutesOffset = index.getIncompleteRoutesOffset();
mi.stringTable = new IndexStringTable();
mi.stringTable.fileOffset = index.getStringTableOffset();
mi.stringTable.length = index.getStringTableLength();

File diff suppressed because it is too large Load diff

View file

@ -44,10 +44,10 @@ public class DataTileManager<T> {
return x;
}
private void putObjects(int tx, int ty, List<T> r){
if(objects.containsKey(evTile(tx, ty))){
private void putObjects(int tx, int ty, List<T> r) {
if (objects.containsKey(evTile(tx, ty))) {
r.addAll(objects.get(evTile(tx, ty)));
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })

View file

@ -0,0 +1,59 @@
package net.osmand.data;
public class IncompleteTransportRoute {
private long routeId;
private int routeOffset = -1;
private String operator;
private String type;
private String ref;
private IncompleteTransportRoute nextLinkedRoute;
public IncompleteTransportRoute getNextLinkedRoute() {
return nextLinkedRoute;
}
public void setNextLinkedRoute(IncompleteTransportRoute nextLinkedRoute) {
this.nextLinkedRoute = nextLinkedRoute;
}
public long getRouteId() {
return routeId;
}
public void setRouteId(long routeId) {
this.routeId = routeId;
}
public int getRouteOffset() {
return routeOffset;
}
public void setRouteOffset(int routeOffset) {
this.routeOffset = routeOffset;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
}

View file

@ -1,10 +1,5 @@
package net.osmand.data;
import net.osmand.osm.edit.Node;
import net.osmand.osm.edit.Way;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -12,6 +7,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.osmand.osm.edit.Node;
import net.osmand.osm.edit.Way;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
public class TransportRoute extends MapObject {
private List<TransportStop> forwardStops = new ArrayList<TransportStop>();
private String ref;
@ -26,6 +26,20 @@ public class TransportRoute extends MapObject {
public TransportRoute() {
}
public TransportRoute(TransportRoute r, List<TransportStop> forwardStops, List<Way> forwardWay) {
this.name = r.name;
this.enName = r.enName;
this.names = r.names;
this.id = r.id;
this.operator = r.operator;
this.ref = r.ref;
this.type = r.type;
this.color = r.color;
this.schedule = r.schedule;
this.forwardStops = forwardStops;
this.forwardWays = forwardWay;
}
public TransportSchedule getSchedule() {
return schedule;
}
@ -37,7 +51,15 @@ public class TransportRoute extends MapObject {
return schedule;
}
public boolean isIncomplete() {
for (TransportStop s : forwardStops) {
if (s.isMissingStop()) {
return true;
}
}
return false;
}
public List<TransportStop> getForwardStops() {
return forwardStops;
}
@ -70,7 +92,8 @@ public class TransportRoute extends MapObject {
resortWaysToStopsOrder(forwardWays, forwardStops);
}
public static void mergeRouteWays(List<Way> forwardWays) {
// intrusive operation cause it changes ways itself!
private static void mergeRouteWays(List<Way> forwardWays) {
boolean changed = true;
// combine as many ways as possible
while (changed && forwardWays != null) {
@ -137,7 +160,7 @@ public class TransportRoute extends MapObject {
}
}
public static Map<Way, int[]> resortWaysToStopsOrder(List<Way> forwardWays, List<TransportStop> forwardStops) {
private static Map<Way, int[]> resortWaysToStopsOrder(List<Way> forwardWays, List<TransportStop> forwardStops) {
final Map<Way, int[]> orderWays = new HashMap<Way, int[]>();
if (forwardWays != null && forwardStops.size() > 0) {
// resort ways to stops order

View file

@ -12,6 +12,7 @@ import java.util.List;
public class TransportStop extends MapObject {
private static final int DELETED_STOP = -1;
public static final String MISSING_STOP_NAME = "#Missing Stop";
private int[] referencesToRoutes = null;
private long[] deletedRoutesIds;
@ -22,7 +23,7 @@ public class TransportStop extends MapObject {
private List<TransportStopExit> exits;
private List<TransportRoute> routes = null;
private LinkedHashMap<String, int[]> referencesToRoutesMap;
private TransportStopAggregated transportStopAggregated;
public TransportStop() {}
@ -30,7 +31,11 @@ public class TransportStop extends MapObject {
public List<TransportRoute> getRoutes() {
return routes;
}
public boolean isMissingStop() {
return MISSING_STOP_NAME.equals(getName());
}
public LinkedHashMap<String, int[]> getReferencesToRoutesMap() {
return referencesToRoutesMap;
}

View file

@ -54,10 +54,14 @@ public class Relation extends Entity {
}
public void addMember(Long id, EntityType type, String role){
addMember(new EntityId(type, id), role);
}
public void addMember(EntityId id, String role){
if(members == null){
members = new ArrayList<>();
}
members.add(new RelationMember(new EntityId(type, id), role));
members.add(new RelationMember(id, role));
}
public List<RelationMember> getMembers(String role) {
@ -115,7 +119,16 @@ public class Relation extends Entity {
public LatLon getLatLon() {
return null;
}
public void update(RelationMember r, EntityId newEntityId) {
r.entity = null;
r.entityId = newEntityId;
}
public void updateRole(RelationMember r, String newRole) {
r.role = newRole;
}
public boolean remove(EntityId key) {
if(members != null) {
Iterator<RelationMember> it = members.iterator();

View file

@ -5,20 +5,23 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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;
import gnu.trove.map.hash.TLongObjectHashMap;
import net.osmand.NativeLibrary;
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.QuadRect;
import net.osmand.data.TransportRoute;
@ -32,6 +35,8 @@ 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;
@ -375,9 +380,14 @@ public class TransportRoutePlanner {
}
return nodes;
}
private static class SearchNodeInd {
int ind = -1;
Way way = null;
double dist = MIN_DIST_STOP_TO_GEOMETRY;
}
public List<Way> getGeometry() {
List<Way> list = new ArrayList<>();
route.mergeForwardWays();
if (DISPLAY_FULL_SEGMENT_ROUTE) {
System.out.println("TOTAL SEGMENTS: " + route.getForwardWays().size());
@ -386,44 +396,69 @@ public class TransportRoutePlanner {
}
return route.getForwardWays();
}
List<Way> fw = route.getForwardWays();
double minStart = 150;
double minEnd = 150;
LatLon str = getStart().getLocation();
LatLon en = getEnd().getLocation();
int endInd = -1;
List<Node> res = new ArrayList<>();
for (int i = 0; i < fw.size() ; i++) {
List<Node> nodes = fw.get(i).getNodes();
List<Way> ways = route.getForwardWays();
final LatLon startLoc = getStart().getLocation();
final LatLon endLoc = getEnd().getLocation();
SearchNodeInd startInd = new SearchNodeInd();
SearchNodeInd endInd = new SearchNodeInd();
for (int i = 0; i < ways.size() ; i++) {
List<Node> nodes = ways.get(i).getNodes();
for (int j = 0; j < nodes.size(); j++) {
Node n = nodes.get(j);
if (MapUtils.getDistance(str, n.getLatitude(), n.getLongitude()) < minStart) {
minStart = MapUtils.getDistance(str, n.getLatitude(), n.getLongitude());
res.clear();
if (MapUtils.getDistance(startLoc, n.getLatitude(), n.getLongitude()) < startInd.dist) {
startInd.dist = MapUtils.getDistance(startLoc, n.getLatitude(), n.getLongitude());
startInd.ind = j;
startInd.way = ways.get(i);
}
res.add(n);
if (MapUtils.getDistance(en, n.getLatitude(), n.getLongitude()) < minEnd) {
endInd = res.size();
minEnd = MapUtils.getDistance(en, n.getLatitude(), n.getLongitude());
if (MapUtils.getDistance(endLoc, n.getLatitude(), n.getLongitude()) < endInd.dist) {
endInd.dist = MapUtils.getDistance(endLoc, n.getLatitude(), n.getLongitude());
endInd.ind = j;
endInd.way = ways.get(i);
}
}
}
Way way;
if (res.isEmpty() || endInd == -1) {
way = new Way(STOPS_WAY_ID);
for (int i = start; i <= end; i++) {
LatLon l = getStop(i).getLocation();
Node n = new Node(l.getLatitude(), l.getLongitude(), -1);
way.addNode(n);
boolean validOneWay = startInd.way != null && startInd.way == endInd.way && startInd.ind <= endInd.ind;
if (validOneWay) {
Way way = new Way(GEOMETRY_WAY_ID);
for (int k = startInd.ind; k <= endInd.ind; k++) {
way.addNode(startInd.way.getNodes().get(k));
}
} else {
way = new Way(GEOMETRY_WAY_ID);
for(int k = 0; k < res.size() && k < endInd; k++) {
way.addNode(res.get(k));
return Collections.singletonList(way);
}
boolean validContinuation = startInd.way != null && endInd.way != null &&
startInd.way != endInd.way;
if (validContinuation) {
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) {
validContinuation = true;
} else {
validContinuation = false;
}
}
list.add(way);
return list;
if (validContinuation) {
List<Way> two = new ArrayList<Way>();
Way way = new Way(GEOMETRY_WAY_ID);
for (int k = startInd.ind; k < startInd.way.getNodes().size(); k++) {
way.addNode(startInd.way.getNodes().get(k));
}
two.add(way);
way = new Way(GEOMETRY_WAY_ID);
for (int k = 0; k <= endInd.ind; k++) {
way.addNode(endInd.way.getNodes().get(k));
}
two.add(way);
return two;
}
Way way = new Way(STOPS_WAY_ID);
for (int i = start; i <= end; i++) {
LatLon l = getStop(i).getLocation();
Node n = new Node(l.getLatitude(), l.getLongitude(), -1);
way.addNode(n);
}
return Collections.singletonList(way);
}
public double getTravelDist() {
@ -574,8 +609,8 @@ public class TransportRoutePlanner {
if(aTime != -1) {
arriveTime = String.format("and arrive at %s", formatTransporTime(aTime));
}
bld.append(String.format(" %d. %s: walk %.1f m to '%s' and travel %s to '%s' by %s %d stops %s\n",
i + 1, s.route.getRef(), s.walkDist, s.getStart().getName(),
bld.append(String.format(" %d. %s [%d]: walk %.1f m to '%s' and travel %s to '%s' by %s %d stops %s\n",
i + 1, s.route.getRef(), s.route.getId() / 2, s.walkDist, s.getStart().getName(),
time, s.getEnd().getName(),s.route.getName(), (s.end - s.start), arriveTime));
}
bld.append(String.format(" F. Walk %.1f m to reach your destination", finishWalkDist));
@ -701,12 +736,17 @@ public class TransportRoutePlanner {
public RouteCalculationProgress calculationProgress;
public TLongObjectHashMap<TransportRouteSegment> visitedSegments = new TLongObjectHashMap<TransportRouteSegment>();
public TransportRoutingConfiguration cfg;
public TLongObjectHashMap<TransportRoute> combinedRoutesCache = new TLongObjectHashMap<TransportRoute>();
public Map<TransportStop, List<TransportRoute>> missingStopsCache = new HashMap<TransportStop, List<TransportRoute>>();
public TLongObjectHashMap<List<TransportRouteSegment>> quadTree;
// 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<BinaryMapIndexReader, TIntObjectHashMap<TransportRoute>> routeMap =
new LinkedHashMap<BinaryMapIndexReader, TIntObjectHashMap<TransportRoute>>();
// stats
public long startCalcTime;
public int visitedRoutesCount;
@ -723,7 +763,6 @@ public class TransportRoutePlanner {
public TransportRoutingContext(TransportRoutingConfiguration cfg, NativeLibrary library, BinaryMapIndexReader... readers) {
this.cfg = cfg;
walkRadiusIn31 = (int) (cfg.walkRadius / MapUtils.getTileDistanceWidth(31));
@ -785,53 +824,58 @@ public class TransportRoutePlanner {
// could be global ?
TLongObjectHashMap<TransportStop> loadedTransportStops = new TLongObjectHashMap<TransportStop>();
TIntObjectHashMap<TransportRoute> localFileRoutes = new TIntObjectHashMap<>();
TIntObjectHashMap<TransportRoute> localFileRoutes = new TIntObjectHashMap<>(); //reference, route
for (BinaryMapIndexReader r : routeMap.keySet()) {
sr.clearSearchResults();
List<TransportStop> stops = r.searchTransportIndex(sr);
localFileRoutes.clear();
mergeTransportStops(r, loadedTransportStops, stops, localFileRoutes, routeMap.get(r));
for (TransportStop stop : stops) {
// skip missing stops
if (stop.isMissingStop()) {
continue;
}
long stopId = stop.getId();
TransportStop multifileStop = loadedTransportStops.get(stopId);
int[] rrs = stop.getReferencesToRoutes();
if (multifileStop == stop) {
// clear up so it won't be used as it is multi file stop
stop.setReferencesToRoutes(null);
} else {
// add other routes
stop.setReferencesToRoutes(null);
}
// clear up so it won't be used as it 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 route %d for stop %s", rr, stop));
} else if (multifileStop == stop ||
(!multifileStop.hasRoute(route.getId()) &&
!multifileStop.isRouteDeleted(route.getId()))) {
// duplicates won't be added
multifileStop.addRouteId(route.getId());
multifileStop.addRoute(route);
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);
readTime += System.nanoTime() - nanoTime;
return lst;
}
public static List<TransportStop> mergeTransportStops(BinaryMapIndexReader reader,
TLongObjectHashMap<TransportStop> loadedTransportStops,
List<TransportStop> stops,
TIntObjectHashMap<TransportRoute> localFileRoutes,
TIntObjectHashMap<TransportRoute> loadedRoutes) throws IOException {
TIntObjectHashMap<TransportRoute> loadedRoutes
) throws IOException {
TIntArrayList routesToLoad = new TIntArrayList();
TIntArrayList localRoutesToLoad = new TIntArrayList();
Iterator<TransportStop> it = stops.iterator();
@ -849,7 +893,7 @@ public class TransportRoutePlanner {
localRoutesToLoad.addAll(stop.getReferencesToRoutes());
}
} else if (multifileStop.isDeleted()){
// stop has noting to load, so not needed
// stop has nothing to load, so not needed
it.remove();
} else {
if (delRIds != null) {
@ -876,10 +920,10 @@ public class TransportRoutePlanner {
}
}
routesToLoad.addAll(localRoutesToLoad);
multifileStop.putReferencesToRoutes(reader.getFile().getName(), localRoutesToLoad.toArray());
multifileStop.putReferencesToRoutes(reader.getFile().getName(), localRoutesToLoad.toArray()); //add valid stop and references to routes
}
// load routes
// load/combine routes
if (routesToLoad.size() > 0) {
routesToLoad.sort();
TIntArrayList referencesToLoad = new TIntArrayList();
@ -888,7 +932,7 @@ public class TransportRoutePlanner {
while (itr.hasNext()) {
int nxt = itr.next();
if (p != nxt) {
if (localFileRoutes != null && loadedRoutes != null && loadedRoutes.contains(nxt)) {
if (localFileRoutes != null && loadedRoutes != null && loadedRoutes.contains(nxt)) { //check if
localFileRoutes.put(nxt, loadedRoutes.get(nxt));
} else {
referencesToLoad.add(nxt);
@ -903,7 +947,232 @@ public class TransportRoutePlanner {
return stops;
}
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<TransportRoute> 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<Way> allWays = getAllWays(incompleteRoutes);
// 2. Get array of segments (each array size > 1):
LinkedList<List<TransportStop>> 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<List<TransportStop>> 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<TransportStop> firstSegment = null;
List<TransportStop> lastSegment = null;
for(List<TransportStop> l : mergedSegments) {
if(!l.get(0).isMissingStop()) {
firstSegment = l;
}
if(!l.get(l.size() - 1).isMissingStop()) {
lastSegment = l;
}
}
List<List<TransportStop>> sortedSegments = new ArrayList<List<TransportStop>>();
if(firstSegment != null) {
sortedSegments.add(firstSegment);
mergedSegments.remove(firstSegment);
while(!mergedSegments.isEmpty()) {
List<TransportStop> last = sortedSegments.get(sortedSegments.size() - 1);
List<TransportStop> 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<TransportStop> first = sortedSegments.get(0);
List<TransportStop> add = findAndDeleteMinDistance(first.get(0).getLocation(), mergedSegments, false);
sortedSegments.add(0, add);
}
} else {
sortedSegments = mergedSegments;
}
List<TransportStop> finalList = new ArrayList<TransportStop>();
for(List<TransportStop> s : sortedSegments) {
finalList.addAll(s);
}
// 5. Create combined TransportRoute and return it
return new TransportRoute(route, finalList, allWays);
}
private List<TransportStop> findAndDeleteMinDistance(LatLon location, List<List<TransportStop>> 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<Way> getAllWays(List<TransportRoute> parts) {
List<Way> w = new ArrayList<Way>();
for (TransportRoute t : parts) {
w.addAll(t.getForwardWays());
}
return w;
}
private List<List<TransportStop>> combineSegmentsOfSameRoute(LinkedList<List<TransportStop>> segments) {
List<List<TransportStop>> resultSegments = new ArrayList<List<TransportStop>>();
while (!segments.isEmpty()) {
List<TransportStop> firstSegment = segments.poll();
boolean merged = true;
while (merged) {
merged = false;
Iterator<List<TransportStop>> it = segments.iterator();
while (it.hasNext()) {
List<TransportStop> segmentToMerge = it.next();
merged = tryToMerge(firstSegment, segmentToMerge);
if (merged) {
it.remove();
break;
}
}
}
resultSegments.add(firstSegment);
}
return resultSegments;
}
private boolean tryToMerge(List<TransportStop> firstSegment, List<TransportStop> 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;
}
}
}
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 LinkedList<List<TransportStop>> parseRoutePartsToSegments(List<TransportRoute> routeParts) {
LinkedList<List<TransportStop>> segs = new LinkedList<List<TransportStop>>();
// here we assume that missing stops come in pairs <A, B, C, MISSING, MISSING, D, E...>
// we don't add segments with 1 stop cause they are irrelevant further
for (TransportRoute part : routeParts) {
List<TransportStop> newSeg = new ArrayList<TransportStop>();
for (TransportStop s : part.getForwardStops()) {
newSeg.add(s);
if (s.isMissingStop()) {
if (newSeg.size() > 1) {
segs.add(newSeg);
newSeg = new ArrayList<TransportStop>();
}
}
}
if (newSeg.size() > 1) {
segs.add(newSeg);
}
}
return segs;
}
private List<TransportRoute> findIncompleteRouteParts(TransportRoute baseRoute) throws IOException {
List<TransportRoute> 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<TransportRoute>();
}
allRoutes.addAll(bmir.getTransportRoutes(lst.toArray()).valueCollection());
}
}
}
return allRoutes;
}
private void loadTransportSegments(Collection<TransportStop> stops, List<TransportRouteSegment> lst) throws IOException {
for(TransportStop s : stops) {
if (s.isDeleted() || s.getRoutes() == null) {
@ -1089,5 +1358,4 @@ public class TransportRoutePlanner {
}
return stops;
}
}