Merge branch 'master' of ssh://github.com/osmandapp/Osmand into js_voice_routing

This commit is contained in:
PaulStets 2018-08-07 08:51:19 +03:00
commit 7d808a38a9
42 changed files with 1074 additions and 665 deletions

View file

@ -1731,6 +1731,7 @@ public class BinaryMapIndexReader {
searchResults = new ArrayList<T>();
cacheCoordinates.clear();
cacheTypes.clear();
stringTable = null;
land = false;
ocean = false;
numberOfVisitedObjects = 0;

View file

@ -436,7 +436,7 @@ public class BinaryMapTransportReaderAdapter {
}
}
dataObject.setId(did);
dataObject.setLocation(MapUtils.getLatitudeFromTile(BinaryMapIndexReader.TRANSPORT_STOP_ZOOM, dy), MapUtils.getLongitudeFromTile(BinaryMapIndexReader.TRANSPORT_STOP_ZOOM, dx));
dataObject.setLocation(BinaryMapIndexReader.TRANSPORT_STOP_ZOOM, dx, dy);
return dataObject;
}
@ -461,7 +461,7 @@ public class BinaryMapTransportReaderAdapter {
req.cacheTypes.clear();
TransportStop dataObject = new TransportStop();
dataObject.setLocation(MapUtils.getLatitudeFromTile(BinaryMapIndexReader.TRANSPORT_STOP_ZOOM, y), MapUtils.getLongitudeFromTile(BinaryMapIndexReader.TRANSPORT_STOP_ZOOM, x));
dataObject.setLocation(BinaryMapIndexReader.TRANSPORT_STOP_ZOOM, x, y);
dataObject.setFileOffset(shift);
while(true){
int t = codedIS.readTag();

View file

@ -142,7 +142,7 @@ public class GeocodingUtilities {
public List<GeocodingResult> reverseGeocodingSearch(RoutingContext ctx, double lat, double lon, boolean allowEmptyNames) throws IOException {
RoutePlannerFrontEnd rp = new RoutePlannerFrontEnd(false);
RoutePlannerFrontEnd rp = new RoutePlannerFrontEnd();
List<GeocodingResult> lst = new ArrayList<GeocodingUtilities.GeocodingResult>();
List<RouteSegmentPoint> listR = new ArrayList<BinaryRoutePlanner.RouteSegmentPoint>();
rp.findRouteSegment(lat, lon, ctx, listR);

View file

@ -1,8 +1,16 @@
package net.osmand.data;
import java.util.ArrayList;
import java.util.List;
import gnu.trove.list.array.TIntArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.osmand.osm.edit.Node;
import net.osmand.osm.edit.Way;
import net.osmand.util.MapUtils;
@ -14,6 +22,7 @@ public class TransportRoute extends MapObject {
private Integer dist = null;
private String color;
private List<Way> forwardWays;
public static final double SAME_STOP = 25;
public TransportRoute(){
}
@ -26,6 +35,110 @@ public class TransportRoute extends MapObject {
return forwardWays;
}
public void mergeForwardWays() {
boolean changed = true;
// combine as many ways as possible
while (changed) {
changed = false;
Iterator<Way> it = forwardWays.iterator();
while (it.hasNext() && !changed) {
// scan to merge with the next segment
double d = SAME_STOP;
Way w = it.next();
Way toCombine = null;
boolean reverseOriginal = false;
boolean reverseCombine = false;
for (int i = 0; i < forwardWays.size(); i++) {
Way combine = forwardWays.get(i);
if (combine == w) {
continue;
}
double distAttachAfter = MapUtils.getDistance(w.getFirstNode().getLatLon(), combine.getLastNode().getLatLon());
double distReverseAttachAfter = MapUtils.getDistance(w.getLastNode().getLatLon(), combine.getLastNode()
.getLatLon());
double distAttachAfterReverse = MapUtils.getDistance(w.getFirstNode().getLatLon(), combine.getFirstNode().getLatLon());
if (distAttachAfter < d) {
toCombine = combine;
reverseOriginal = false;
reverseCombine = false;
d = distAttachAfter;
} else if (distReverseAttachAfter < d) {
toCombine = combine;
reverseOriginal = true;
reverseCombine = false;
d = distReverseAttachAfter;
} else if (distAttachAfterReverse < d) {
toCombine = combine;
reverseOriginal = false;
reverseCombine = true;
d = distAttachAfterReverse;
}
}
if (toCombine != null) {
if(reverseCombine) {
toCombine.reverseNodes();
}
if(reverseOriginal) {
w.reverseNodes();
}
for (int i = 1; i < w.getNodes().size(); i++) {
toCombine.addNode(w.getNodes().get(i));
}
it.remove();
changed = true;
}
}
}
if (forwardStops.size() > 0) {
// resort ways to stops order
final Map<Way, int[]> orderWays = new HashMap<Way, int[]>();
for (Way w : forwardWays) {
int[] pair = new int[] { 0, 0 };
Node firstNode = w.getFirstNode();
TransportStop st = forwardStops.get(0);
double firstDistance = MapUtils.getDistance(st.getLocation(), firstNode.getLatitude(),
firstNode.getLongitude());
Node lastNode = w.getLastNode();
double lastDistance = MapUtils.getDistance(st.getLocation(), lastNode.getLatitude(),
lastNode.getLongitude());
for (int i = 1; i < forwardStops.size(); i++) {
st = forwardStops.get(i);
double firstd = MapUtils.getDistance(st.getLocation(), firstNode.getLatitude(),
firstNode.getLongitude());
double lastd = MapUtils.getDistance(st.getLocation(), lastNode.getLatitude(),
lastNode.getLongitude());
if (firstd < firstDistance) {
pair[0] = i;
firstDistance = firstd;
}
if (lastd < lastDistance) {
pair[1] = i;
lastDistance = lastd;
}
}
orderWays.put(w, pair);
if(pair[0] > pair[1]) {
w.reverseNodes();
}
}
if(orderWays.size() > 1) {
Collections.sort(forwardWays, new Comparator<Way>() {
@Override
public int compare(Way o1, Way o2) {
int[] is1 = orderWays.get(o1);
int[] is2 = orderWays.get(o2);
int i1 = is1 != null ? Math.min(is1[0], is1[1]) : 0;
int i2 = is2 != null ? Math.min(is2[0], is2[1]) : 0;
return Integer.compare(i1, i2);
}
});
}
}
}
public String getColor() {
return color;
}

View file

@ -1,9 +1,14 @@
package net.osmand.data;
import net.osmand.util.MapUtils;
public class TransportStop extends MapObject {
private int[] referencesToRoutes = null;
private Amenity amenity;
public int distance;
public int x31;
public int y31;
public TransportStop(){
}
@ -23,4 +28,15 @@ public class TransportStop extends MapObject {
public void setAmenity(Amenity amenity) {
this.amenity = amenity;
}
@Override
public void setLocation(double latitude, double longitude) {
super.setLocation(latitude, longitude);
}
public void setLocation(int zoom, int dx, int dy) {
x31 = dx << (31 - zoom);
y31 = dy << (31 - zoom);
setLocation(MapUtils.getLatitudeFromTile(zoom, dy), MapUtils.getLongitudeFromTile(zoom, dx));
}
}

View file

@ -1,580 +0,0 @@
package net.osmand.router;
import gnu.trove.map.hash.TLongObjectHashMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
import net.osmand.PlatformUtil;
import net.osmand.binary.RouteDataObject;
import net.osmand.osm.MapRenderingTypes;
import net.osmand.router.BinaryRoutePlanner.FinalRouteSegment;
import net.osmand.router.BinaryRoutePlanner.RouteSegment;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
public class BinaryRoutePlannerOld {
public static boolean PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST = true;
private static final int REVERSE_WAY_RESTRICTION_ONLY = 1024;
private static final int STANDARD_ROAD_IN_QUEUE_OVERHEAD = 900;
protected static final Log log = PlatformUtil.getLog(BinaryRoutePlannerOld.class);
private static final int ROUTE_POINTS = 11;
private static double squareRootDist(int x1, int y1, int x2, int y2) {
return MapUtils.squareRootDist31(x1, y1, x2, y2);
// return measuredDist(x1, y1, x2, y2);
}
/**
* Calculate route between start.segmentEnd and end.segmentStart (using A* algorithm)
* return list of segments
*/
void searchRouteInternal(final RoutingContext ctx, RouteSegment start, RouteSegment end) throws IOException, InterruptedException {
// measure time
ctx.timeToLoad = 0;
ctx.visitedSegments = 0;
ctx.timeToCalculate = System.nanoTime();
if(ctx.config.initialDirection != null) {
ctx.firstRoadId = (start.getRoad().id << ROUTE_POINTS) + start.getSegmentStart();
double plusDir = start.getRoad().directionRoute(start.getSegmentStart(), true);
double diff = plusDir - ctx.config.initialDirection;
if(Math.abs(MapUtils.alignAngleDifference(diff)) <= Math.PI / 3) {
ctx.firstRoadDirection = 1;
} else if(Math.abs(MapUtils.alignAngleDifference(diff - Math.PI)) <= Math.PI / 3) {
ctx.firstRoadDirection = -1;
}
}
// Initializing priority queue to visit way segments
Comparator<RouteSegment> segmentsComparator = new Comparator<RouteSegment>(){
@Override
public int compare(RouteSegment o1, RouteSegment o2) {
return ctx.roadPriorityComparator(o1.distanceFromStart, o1.distanceToEnd, o2.distanceFromStart, o2.distanceToEnd);
}
};
Comparator<RouteSegment> nonHeuristicSegmentsComparator = new Comparator<RouteSegment>(){
@Override
public int compare(RouteSegment o1, RouteSegment o2) {
return roadPriorityComparator(o1.distanceFromStart, o1.distanceToEnd, o2.distanceFromStart, o2.distanceToEnd, 0.5);
}
};
PriorityQueue<RouteSegment> graphDirectSegments = new PriorityQueue<RouteSegment>(50, segmentsComparator);
PriorityQueue<RouteSegment> graphReverseSegments = new PriorityQueue<RouteSegment>(50, segmentsComparator);
// Set to not visit one segment twice (stores road.id << X + segmentStart)
TLongObjectHashMap<RouteSegment> visitedDirectSegments = new TLongObjectHashMap<RouteSegment>();
TLongObjectHashMap<RouteSegment> visitedOppositeSegments = new TLongObjectHashMap<RouteSegment>();
boolean runRecalculation = ctx.previouslyCalculatedRoute != null && ctx.previouslyCalculatedRoute.size() > 0
&& ctx.config.recalculateDistance != 0;
if (runRecalculation) {
RouteSegment previous = null;
List<RouteSegmentResult> rlist = new ArrayList<RouteSegmentResult>();
float distanceThreshold = ctx.config.recalculateDistance;
float threshold = 0;
for(RouteSegmentResult rr : ctx.previouslyCalculatedRoute) {
threshold += rr.getDistance();
if(threshold > distanceThreshold) {
rlist.add(rr);
}
}
runRecalculation = rlist.size() > 0;
if (rlist.size() > 0) {
for (RouteSegmentResult rr : rlist) {
RouteSegment segment = new RouteSegment(rr.getObject(), rr.getEndPointIndex());
if (previous != null) {
previous.setParentRoute(segment);
previous.setParentSegmentEnd(rr.getStartPointIndex());
long t = (rr.getObject().getId() << ROUTE_POINTS) + segment.getSegmentStart();
visitedOppositeSegments.put(t, segment);
}
previous = segment;
}
end = previous;
}
}
// for start : f(start) = g(start) + h(start) = 0 + h(start) = h(start)
int targetEndX = end.road.getPoint31XTile(end.getSegmentStart());
int targetEndY = end.road.getPoint31YTile(end.getSegmentStart());
int startX = start.road.getPoint31XTile(start.getSegmentStart());
int startY = start.road.getPoint31YTile(start.getSegmentStart());
float estimatedDistance = (float) h(ctx, targetEndX, targetEndY, startX, startY);
end.distanceToEnd = start.distanceToEnd = estimatedDistance;
graphDirectSegments.add(start);
graphReverseSegments.add(end);
// Extract & analyze segment with min(f(x)) from queue while final segment is not found
boolean inverse = false;
boolean init = false;
PriorityQueue<RouteSegment> graphSegments;
if(inverse) {
graphSegments = graphReverseSegments;
} else {
graphSegments = graphDirectSegments;
}
while (!graphSegments.isEmpty()) {
RouteSegment segment = graphSegments.poll();
ctx.visitedSegments++;
// for debug purposes
if (ctx.visitor != null) {
// ctx.visitor.visitSegment(segment, true);
}
updateCalculationProgress(ctx, graphDirectSegments, graphReverseSegments);
boolean routeFound = false;
if (!inverse) {
routeFound = processRouteSegment(ctx, false, graphDirectSegments, visitedDirectSegments, targetEndX, targetEndY,
segment, visitedOppositeSegments);
} else {
routeFound = processRouteSegment(ctx, true, graphReverseSegments, visitedOppositeSegments, startX, startY, segment,
visitedDirectSegments);
}
if (graphReverseSegments.isEmpty() || graphDirectSegments.isEmpty() || routeFound) {
break;
}
if(runRecalculation) {
// nothing to do
inverse = false;
} else if (!init) {
inverse = !inverse;
init = true;
} else if (ctx.planRouteIn2Directions()) {
inverse = nonHeuristicSegmentsComparator.compare(graphDirectSegments.peek(), graphReverseSegments.peek()) > 0;
if (graphDirectSegments.size() * 1.3 > graphReverseSegments.size()) {
inverse = true;
} else if (graphDirectSegments.size() < 1.3 * graphReverseSegments.size()) {
inverse = false;
}
} else {
// different strategy : use onedirectional graph
inverse = ctx.getPlanRoadDirection() < 0;
}
if (inverse) {
graphSegments = graphReverseSegments;
} else {
graphSegments = graphDirectSegments;
}
// check if interrupted
if(ctx.calculationProgress != null && ctx.calculationProgress.isCancelled) {
throw new InterruptedException("Route calculation interrupted");
}
}
println("Result is found");
printDebugMemoryInformation(ctx, graphDirectSegments, graphReverseSegments, visitedDirectSegments, visitedOppositeSegments);
}
private void updateCalculationProgress(final RoutingContext ctx, PriorityQueue<RouteSegment> graphDirectSegments,
PriorityQueue<RouteSegment> graphReverseSegments) {
if(ctx.calculationProgress != null) {
ctx.calculationProgress.reverseSegmentQueueSize = graphReverseSegments.size();
ctx.calculationProgress.directSegmentQueueSize = graphDirectSegments.size();
RouteSegment dirPeek = graphDirectSegments.peek();
if(dirPeek != null) {
ctx.calculationProgress.distanceFromBegin =
Math.max(dirPeek.distanceFromStart, ctx.calculationProgress.distanceFromBegin);
}
RouteSegment revPeek = graphReverseSegments.peek();
if(revPeek != null) {
ctx.calculationProgress.distanceFromEnd =
Math.max(revPeek.distanceFromStart, ctx.calculationProgress.distanceFromEnd);
}
}
}
private double h(final RoutingContext ctx, int targetEndX, int targetEndY,
int startX, int startY) {
double distance = squareRootDist(startX, startY, targetEndX, targetEndY);
return distance / ctx.getRouter().getMaxDefaultSpeed();
}
protected static double h(RoutingContext ctx, double distToFinalPoint, RouteSegment next) {
return distToFinalPoint / ctx.getRouter().getMaxDefaultSpeed();
}
private static void println(String logMsg) {
// log.info(logMsg);
System.out.println(logMsg);
}
private static void printInfo(String logMsg) {
log.warn(logMsg);
}
public void printDebugMemoryInformation(RoutingContext ctx, PriorityQueue<RouteSegment> graphDirectSegments, PriorityQueue<RouteSegment> graphReverseSegments,
TLongObjectHashMap<RouteSegment> visitedDirectSegments,TLongObjectHashMap<RouteSegment> visitedOppositeSegments) {
printInfo("Time to calculate : " + (System.nanoTime() - ctx.timeToCalculate) / 1e6 + ", time to load : " + ctx.timeToLoad / 1e6 + ", time to load headers : " + ctx.timeToLoadHeaders / 1e6);
int maxLoadedTiles = Math.max(ctx.maxLoadedTiles, ctx.getCurrentlyLoadedTiles());
printInfo("Current loaded tiles : " + ctx.getCurrentlyLoadedTiles() + ", maximum loaded tiles " + maxLoadedTiles);
printInfo("Loaded tiles " + ctx.loadedTiles + " (distinct "+ctx.distinctLoadedTiles+ "), unloaded tiles " + ctx.unloadedTiles +
", loaded more than once same tiles "
+ ctx.loadedPrevUnloadedTiles );
printInfo("Visited roads, " + ctx.visitedSegments + ", relaxed roads " + ctx.relaxedSegments);
if (graphDirectSegments != null && graphReverseSegments != null) {
printInfo("Priority queues sizes : " + graphDirectSegments.size() + "/" + graphReverseSegments.size());
}
if (visitedDirectSegments != null && visitedOppositeSegments != null) {
printInfo("Visited segments sizes: " + visitedDirectSegments.size() + "/" + visitedOppositeSegments.size());
}
}
private boolean processRouteSegment(final RoutingContext ctx, boolean reverseWaySearch,
PriorityQueue<RouteSegment> graphSegments, TLongObjectHashMap<RouteSegment> visitedSegments, int targetEndX, int targetEndY,
RouteSegment segment, TLongObjectHashMap<RouteSegment> oppositeSegments) throws IOException {
// Always start from segmentStart (!), not from segmentEnd
// It makes difference only for the first start segment
// Middle point will always be skipped from observation considering already visited
final RouteDataObject road = segment.road;
final int middle = segment.getSegmentStart();
float obstaclePlusTime = 0;
float obstacleMinusTime = 0;
// This is correct way of checking but it has problem with relaxing strategy
// long ntf = (segment.road.getId() << ROUTE_POINTS) + segment.segmentStart;
// visitedSegments.put(ntf, segment);
// if (oppositeSegments.contains(ntf) && oppositeSegments.get(ntf) != null) {
// RouteSegment opposite = oppositeSegments.get(ntf);
// if (opposite.segmentStart == segment.segmentStart) {
// if (reverseWaySearch) {
// reverse : segment.parentSegmentEnd - segment.parentRoute
// } else {
// reverse : opposite.parentSegmentEnd - oppositie.parentRoute
// }
// return true;
// }
// }
// 0. mark route segment as visited
long nt = (road.getId() << ROUTE_POINTS) + middle;
// avoid empty segments to connect but mark the point as visited
visitedSegments.put(nt, null);
int oneway = ctx.getRouter().isOneWay(road);
boolean minusAllowed;
boolean plusAllowed;
if(ctx.firstRoadId == nt) {
if(ctx.firstRoadDirection < 0) {
obstaclePlusTime += 500;
} else if(ctx.firstRoadDirection > 0) {
obstacleMinusTime += 500;
}
}
if (!reverseWaySearch) {
minusAllowed = oneway <= 0;
plusAllowed = oneway >= 0;
} else {
minusAllowed = oneway >= 0;
plusAllowed = oneway <= 0;
}
// +/- diff from middle point
int d = plusAllowed ? 1 : -1;
if(segment.parentRoute != null) {
if(plusAllowed && middle < segment.getRoad().getPointsLength() - 1) {
obstaclePlusTime = (float) ctx.getRouter().calculateTurnTime(segment, segment.getRoad().getPointsLength() - 1,
segment.parentRoute, segment.parentSegmentEnd);
}
if(minusAllowed && middle > 0) {
obstacleMinusTime = (float) ctx.getRouter().calculateTurnTime(segment, 0,
segment.parentRoute, segment.parentSegmentEnd);
}
}
// Go through all point of the way and find ways to continue
// ! Actually there is small bug when there is restriction to move forward on way (it doesn't take into account)
float posSegmentDist = 0;
float negSegmentDist = 0;
while (minusAllowed || plusAllowed) {
// 1. calculate point not equal to middle
// (algorithm should visit all point on way if it is not oneway)
int segmentEnd = middle + d;
boolean positive = d > 0;
if (!minusAllowed && d > 0) {
d++;
} else if (!plusAllowed && d < 0) {
d--;
} else {
if (d <= 0) {
d = -d + 1;
} else {
d = -d;
}
}
if (segmentEnd < 0) {
minusAllowed = false;
continue;
}
if (segmentEnd >= road.getPointsLength()) {
plusAllowed = false;
continue;
}
// if we found end point break cycle
long nts = (road.getId() << ROUTE_POINTS) + segmentEnd;
visitedSegments.put(nts, segment);
// 2. calculate point and try to load neighbor ways if they are not loaded
int x = road.getPoint31XTile(segmentEnd);
int y = road.getPoint31YTile(segmentEnd);
if(positive) {
posSegmentDist += squareRootDist(x, y,
road.getPoint31XTile(segmentEnd - 1), road.getPoint31YTile(segmentEnd - 1));
} else {
negSegmentDist += squareRootDist(x, y,
road.getPoint31XTile(segmentEnd + 1), road.getPoint31YTile(segmentEnd + 1));
}
// 2.1 calculate possible obstacle plus time
if(positive){
double obstacle = ctx.getRouter().defineRoutingObstacle(road, segmentEnd);
if (obstacle < 0) {
plusAllowed = false;
continue;
}
obstaclePlusTime += obstacle;
} else {
double obstacle = ctx.getRouter().defineRoutingObstacle(road, segmentEnd);
if (obstacle < 0) {
minusAllowed = false;
continue;
}
obstacleMinusTime += obstacle;
}
// int overhead = 0;
// could be expensive calculation
int overhead = (ctx.visitedSegments - ctx.relaxedSegments ) *
STANDARD_ROAD_IN_QUEUE_OVERHEAD;
if(overhead > ctx.config.memoryLimitation * 0.95){
throw new OutOfMemoryError("There is no enough memory " + ctx.config.memoryLimitation/(1<<20) + " Mb");
}
RouteSegment next = ctx.loadRouteSegment(x, y, ctx.config.memoryLimitation - overhead);
// 3. get intersected ways
if (next != null) {
// TO-DO U-Turn
if((next == segment || next.road.id == road.id) && next.next == null) {
// simplification if there is no real intersection
continue;
}
// Using A* routing algorithm
// g(x) - calculate distance to that point and calculate time
float priority = ctx.getRouter().defineSpeedPriority(road);
float speed = ctx.getRouter().defineRoutingSpeed(road) * priority;
if (speed == 0) {
speed = ctx.getRouter().getMinDefaultSpeed() * priority;
}
float distOnRoadToPass = positive? posSegmentDist : negSegmentDist;
float distStartObstacles = segment.distanceFromStart + ( positive ? obstaclePlusTime : obstacleMinusTime) +
distOnRoadToPass / speed;
float distToFinalPoint = (float) squareRootDist(x, y, targetEndX, targetEndY);
boolean routeFound = processIntersections(ctx, graphSegments, visitedSegments, oppositeSegments,
distStartObstacles, distToFinalPoint, segment, segmentEnd, next, reverseWaySearch);
if(routeFound){
return routeFound;
}
}
}
return false;
}
private boolean proccessRestrictions(RoutingContext ctx, RouteDataObject road, RouteSegment inputNext, boolean reverseWay) {
ctx.segmentsToVisitPrescripted.clear();
ctx.segmentsToVisitNotForbidden.clear();
boolean exclusiveRestriction = false;
RouteSegment next = inputNext;
if (!reverseWay && road.getRestrictionLength() == 0) {
return false;
}
if(!ctx.getRouter().restrictionsAware()) {
return false;
}
while (next != null) {
int type = -1;
if (!reverseWay) {
for (int i = 0; i < road.getRestrictionLength(); i++) {
if (road.getRestrictionId(i) == next.road.id) {
type = road.getRestrictionType(i);
break;
}
}
} else {
for (int i = 0; i < next.road.getRestrictionLength(); i++) {
int rt = next.road.getRestrictionType(i);
long restrictedTo = next.road.getRestrictionId(i);
if (restrictedTo == road.id) {
type = rt;
break;
}
// Check if there is restriction only to the other than current road
if (rt == MapRenderingTypes.RESTRICTION_ONLY_RIGHT_TURN || rt == MapRenderingTypes.RESTRICTION_ONLY_LEFT_TURN
|| rt == MapRenderingTypes.RESTRICTION_ONLY_STRAIGHT_ON) {
// check if that restriction applies to considered junk
RouteSegment foundNext = inputNext;
while (foundNext != null) {
if (foundNext.getRoad().id == restrictedTo) {
break;
}
foundNext = foundNext.next;
}
if (foundNext != null) {
type = REVERSE_WAY_RESTRICTION_ONLY; // special constant
}
}
}
}
if (type == REVERSE_WAY_RESTRICTION_ONLY) {
// next = next.next; continue;
} else if (type == -1 && exclusiveRestriction) {
// next = next.next; continue;
} else if (type == MapRenderingTypes.RESTRICTION_NO_LEFT_TURN || type == MapRenderingTypes.RESTRICTION_NO_RIGHT_TURN
|| type == MapRenderingTypes.RESTRICTION_NO_STRAIGHT_ON || type == MapRenderingTypes.RESTRICTION_NO_U_TURN) {
// next = next.next; continue;
} else if (type == -1) {
// case no restriction
ctx.segmentsToVisitNotForbidden.add(next);
} else {
// case exclusive restriction (only_right, only_straight, ...)
// 1. in case we are going backward we should not consider only_restriction
// as exclusive because we have many "in" roads and one "out"
// 2. in case we are going forward we have one "in" and many "out"
if (!reverseWay) {
exclusiveRestriction = true;
ctx.segmentsToVisitNotForbidden.clear();
ctx.segmentsToVisitPrescripted.add(next);
} else {
ctx.segmentsToVisitNotForbidden.add(next);
}
}
next = next.next;
}
ctx.segmentsToVisitPrescripted.addAll(ctx.segmentsToVisitNotForbidden);
return true;
}
private boolean processIntersections(RoutingContext ctx, PriorityQueue<RouteSegment> graphSegments,
TLongObjectHashMap<RouteSegment> visitedSegments, TLongObjectHashMap<RouteSegment> oppositeSegments,
float distFromStart, float distToFinalPoint,
RouteSegment segment, int segmentEnd, RouteSegment inputNext,
boolean reverseWay) {
boolean thereAreRestrictions = proccessRestrictions(ctx, segment.road, inputNext, reverseWay);
Iterator<RouteSegment> nextIterator = null;
if (thereAreRestrictions) {
nextIterator = ctx.segmentsToVisitPrescripted.iterator();
}
// Calculate possible ways to put into priority queue
RouteSegment next = inputNext;
boolean hasNext = nextIterator == null || nextIterator.hasNext();
while (hasNext) {
if (nextIterator != null) {
next = nextIterator.next();
}
long nts = (next.road.getId() << ROUTE_POINTS) + next.getSegmentStart();
// 1. Check if opposite segment found so we can stop calculations
if (oppositeSegments.contains(nts) && oppositeSegments.get(nts) != null) {
// restrictions checked
RouteSegment opposite = oppositeSegments.get(nts);
// additional check if opposite way not the same as current one
if (next.getSegmentStart() != segmentEnd ||
opposite.getRoad().getId() != segment.getRoad().getId()) {
FinalRouteSegment frs = new FinalRouteSegment(segment.getRoad(), segment.getSegmentStart());
float distStartObstacles = segment.distanceFromStart;
frs.setParentRoute(segment.getParentRoute());
frs.setParentSegmentEnd(segment.getParentSegmentEnd());
frs.reverseWaySearch = reverseWay;
frs.distanceFromStart = opposite.distanceFromStart + distStartObstacles;
RouteSegment op = new RouteSegment(segment.getRoad(), segmentEnd);
op.setParentRoute(opposite);
op.setParentSegmentEnd(next.getSegmentStart());
frs.distanceToEnd = 0;
frs.opposite = op;
ctx.finalRouteSegment = frs;
return true;
}
}
// road.id could be equal on roundabout, but we should accept them
boolean alreadyVisited = visitedSegments.contains(nts);
if (!alreadyVisited) {
float distanceToEnd = (float) h(ctx, distToFinalPoint, next);
if (next.parentRoute == null
|| ctx.roadPriorityComparator(next.distanceFromStart, next.distanceToEnd, distFromStart, distanceToEnd) > 0) {
if (next.parentRoute != null) {
// already in queue remove it
if (!graphSegments.remove(next)) {
// exist in different queue!
next = new RouteSegment(next.getRoad(), next.getSegmentStart());
}
}
next.distanceFromStart = distFromStart;
next.distanceToEnd = distanceToEnd;
// put additional information to recover whole route after
next.setParentRoute(segment);
next.setParentSegmentEnd(segmentEnd);
graphSegments.add(next);
}
if (ctx.visitor != null) {
// ctx.visitor.visitSegment(next, false);
}
} else {
// the segment was already visited! We need to follow better route if it exists
// that is very strange situation and almost exception (it can happen when we underestimate distnceToEnd)
if (distFromStart < next.distanceFromStart && next.road.id != segment.road.id) {
// That code is incorrect (when segment is processed itself,
// then it tries to make wrong u-turn) -
// this situation should be very carefully checked in future (seems to be fixed)
// System.out.println(segment.getRoad().getName() + " " + next.getRoad().getName());
// System.out.println(next.distanceFromStart + " ! " + distFromStart);
next.distanceFromStart = distFromStart;
next.setParentRoute(segment);
next.setParentSegmentEnd(segmentEnd);
if (ctx.visitor != null) {
// ctx.visitor.visitSegment(next, next.getSegmentStart(), false);
}
}
}
// iterate to next road
if (nextIterator == null) {
next = next.next;
hasNext = next != null;
} else {
hasNext = nextIterator.hasNext();
}
}
return false;
}
/*public */static int roadPriorityComparator(double o1DistanceFromStart, double o1DistanceToEnd,
double o2DistanceFromStart, double o2DistanceToEnd, double heuristicCoefficient ) {
// f(x) = g(x) + h(x) --- g(x) - distanceFromStart, h(x) - distanceToEnd (not exact)
return Double.compare(o1DistanceFromStart + heuristicCoefficient * o1DistanceToEnd,
o2DistanceFromStart + heuristicCoefficient * o2DistanceToEnd);
}
}

View file

@ -1,6 +1,14 @@
package net.osmand.router;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import net.osmand.NativeLibrary;
import net.osmand.PlatformUtil;
import net.osmand.binary.BinaryMapIndexReader;
@ -15,24 +23,15 @@ import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
public class RoutePlannerFrontEnd {
private boolean useOldVersion;
protected static final Log log = PlatformUtil.getLog(RoutePlannerFrontEnd.class);
public boolean useSmartRouteRecalculation = true;
public RoutePlannerFrontEnd(boolean useOldVersion) {
this.useOldVersion = useOldVersion;
public RoutePlannerFrontEnd() {
}
public enum RouteCalculationMode {
BASE,
NORMAL,
@ -345,11 +344,7 @@ public class RoutePlannerFrontEnd {
} else {
refreshProgressDistance(ctx);
// Split into 2 methods to let GC work in between
if (useOldVersion) {
new BinaryRoutePlannerOld().searchRouteInternal(ctx, start, end);
} else {
ctx.finalRouteSegment = new BinaryRoutePlanner().searchRouteInternal(ctx, start, end, recalculationEnd);
}
ctx.finalRouteSegment = new BinaryRoutePlanner().searchRouteInternal(ctx, start, end, recalculationEnd);
// 4. Route is found : collect all segments and prepare result
return new RouteResultPreparation().prepareResult(ctx, ctx.finalRouteSegment);
}

View file

@ -197,9 +197,6 @@ public class RoutingContext {
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,

View file

@ -28,7 +28,6 @@ public class TestRouting {
public static boolean TEST_WO_HEURISTIC = false;
public static boolean TEST_BOTH_DIRECTION = false;
public static NativeLibrary lib = null;
public static boolean oldRouting = false;
private static String vehicle = "car";
@ -203,7 +202,7 @@ public class TestRouting {
return;
}
RoutingConfiguration rconfig = config.build(vehicle, MEMORY_TEST_LIMIT);
RoutePlannerFrontEnd router = new RoutePlannerFrontEnd(oldRouting);
RoutePlannerFrontEnd router = new RoutePlannerFrontEnd();
RoutingContext ctx = router.buildRoutingContext(rconfig,
lib, rs);
String skip = parser.getAttributeValue("", "skip_comment");
@ -308,7 +307,7 @@ public class TestRouting {
long ts = System.currentTimeMillis();
Builder config = RoutingConfiguration.getDefault();
RoutingConfiguration rconfig = config.build(vehicle, MEMORY_TEST_LIMIT);
RoutePlannerFrontEnd router = new RoutePlannerFrontEnd(oldRouting);
RoutePlannerFrontEnd router = new RoutePlannerFrontEnd();
RoutingContext ctx = router.buildRoutingContext(rconfig, lib, rs);
RouteSegment startSegment = router.findRouteSegment(startLat, startLon, ctx, null);
RouteSegment endSegment = router.findRouteSegment(endLat, endLon, ctx, null);

View file

@ -0,0 +1,608 @@
package net.osmand.router;
import gnu.trove.iterator.TIntIterator;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapIndexReader.SearchRequest;
import net.osmand.data.LatLon;
import net.osmand.data.TransportRoute;
import net.osmand.data.TransportStop;
import net.osmand.osm.edit.Node;
import net.osmand.osm.edit.Way;
import net.osmand.util.MapUtils;
public class TransportRoutePlanner {
public static void main(String[] args) throws IOException {
File fl = new File(System.getProperty("maps.dir"), "Netherlands_noord-holland_europe_2.obf");
RandomAccessFile raf = new RandomAccessFile(fl, "r");
BinaryMapIndexReader reader = new BinaryMapIndexReader(raf, fl);
LatLon start = new LatLon(52.28094, 4.853248);
// LatLon end = new LatLon(52.320988, 4.87256);
LatLon end = new LatLon(52.349308, 4.9017425);
TransportRoutingConfiguration cfg = new TransportRoutingConfiguration();
cfg.maxNumberOfChanges = 3;
cfg.walkRadius = 1500;
// cfg.walkChangeRadius = 500;
TransportRoutingContext ctx = new TransportRoutingContext(cfg, reader);
TransportRoutePlanner planner = new TransportRoutePlanner();
planner.buildRoute(ctx, start, end);
}
public List<TransportRouteResult> buildRoute(TransportRoutingContext ctx, LatLon start, LatLon end) throws IOException {
ctx.startCalcTime = System.currentTimeMillis();
List<TransportRouteSegment> startStops = ctx.getTransportStops(start);
List<TransportRouteSegment> endStops = ctx.getTransportStops(end);
TLongObjectHashMap<TransportRouteSegment> endSegments = new TLongObjectHashMap<TransportRouteSegment>();
for(TransportRouteSegment s : endStops) {
endSegments.put(s.getId(), s);
}
PriorityQueue<TransportRouteSegment> queue = new PriorityQueue<TransportRouteSegment>(new SegmentsComparator(ctx));
for(TransportRouteSegment r : startStops){
r.walkDist = (float) MapUtils.getDistance(r.getLocation(), start);
r.distFromStart = r.walkDist / ctx.cfg.walkSpeed;
queue.add(r);
}
double finishTime = ctx.cfg.maxRouteTime;
List<TransportRouteSegment> results = new ArrayList<TransportRouteSegment>();
while (!queue.isEmpty()) {
TransportRouteSegment segment = queue.poll();
TransportRouteSegment ex = ctx.visitedSegments.get(segment.getId());
if(ex != null) {
if(ex.distFromStart > segment.distFromStart) {
System.err.println(String.format("%.1f (%s) > %.1f (%s)", ex.distFromStart, ex, segment.distFromStart, segment));
}
continue;
}
ctx.visitedRoutesCount++;
ctx.visitedSegments.put(segment.getId(), segment);
if (segment.getDepth() > ctx.cfg.maxNumberOfChanges) {
continue;
}
if (segment.distFromStart > finishTime + ctx.cfg.finishTimeSeconds) {
break;
}
long segmentId = segment.getId();
TransportRouteSegment finish = null;
double minDist = 0;
double travelDist = 0;
double travelTime = 0;
TransportStop prevStop = segment.getStop(segment.segStart);
List<TransportRouteSegment> sgms = new ArrayList<TransportRouteSegment>();
for (int ind = 1 + segment.segStart; ind < segment.getLength(); ind++) {
segmentId ++;
ctx.visitedSegments.put(segmentId, segment);
TransportStop stop = segment.getStop(ind);
// could be geometry size
double segmentDist = MapUtils.getDistance(prevStop.getLocation(), stop.getLocation());
travelDist += segmentDist;
travelTime += ctx.cfg.stopTime + segmentDist / ctx.cfg.travelSpeed;
sgms.clear();
sgms = ctx.getTransportStops(stop.x31, stop.y31, true, sgms);
for (TransportRouteSegment sgm : sgms) {
if (segment.wasVisited(sgm)) {
continue;
}
TransportRouteSegment rrs = new TransportRouteSegment(sgm);
rrs.parentRoute = segment;
rrs.parentStop = ind;
rrs.walkDist = MapUtils.getDistance(rrs.getLocation(), stop.getLocation());
rrs.parentTravelTime = travelTime;
rrs.parentTravelDist = travelDist;
double walkTime = rrs.walkDist / ctx.cfg.walkSpeed + ctx.cfg.changeTime;
rrs.distFromStart = segment.distFromStart + travelTime + walkTime;
queue.add(rrs);
}
TransportRouteSegment f = endSegments.get(segmentId);
double distToEnd = MapUtils.getDistance(stop.getLocation(), end);
if (f != null && distToEnd < ctx.cfg.walkRadius) {
if (finish == null || minDist > distToEnd) {
minDist = distToEnd;
finish = new TransportRouteSegment(f);
finish.parentRoute = segment;
finish.parentStop = ind;
finish.walkDist = distToEnd;
finish.parentTravelTime = travelTime;
finish.parentTravelDist = travelDist;
double walkTime = distToEnd / ctx.cfg.walkSpeed;
finish.distFromStart = segment.distFromStart + travelTime + walkTime;
}
}
prevStop = stop;
}
if (finish != null) {
if (finishTime > finish.distFromStart) {
finishTime = finish.distFromStart;
}
if(finish.distFromStart < finishTime + ctx.cfg.finishTimeSeconds) {
results.add(finish);
}
}
}
return prepareResults(ctx, results);
}
private List<TransportRouteResult> prepareResults(TransportRoutingContext ctx, List<TransportRouteSegment> results) {
Collections.sort(results, new SegmentsComparator(ctx));
List<TransportRouteResult> lst = new ArrayList<TransportRouteResult>();
System.out.println("FIX !!! " + ctx.wrongLoadedWays + " " + ctx.loadedWays + " " +
(ctx.loadTime / (1000 * 1000)) + " ms");
System.out.println(String.format("Calculated %.1f seconds, found %d results, visited %d routes, loaded %d tiles (%d ms read, %d ms total),",
(System.currentTimeMillis() - ctx.startCalcTime) / 1000.0, results.size(), ctx.visitedRoutesCount,
ctx.quadTree.size(), ctx.readTime / (1000 * 1000), ctx.loadTime / (1000 * 1000)));
for(TransportRouteSegment res : results) {
TransportRouteResult route = new TransportRouteResult(ctx);
route.routeTime = res.distFromStart;
route.finishWalkDist = res.walkDist;
TransportRouteSegment p = res;
while (p != null) {
if (p.parentRoute != null) {
TransportRouteResultSegment sg = new TransportRouteResultSegment(p.parentRoute.road,
p.parentRoute.segStart, p.parentStop, p.parentRoute.walkDist);
route.segments.add(0, sg);
}
p = p.parentRoute;
}
// test if faster routes fully included
boolean include = false;
for(TransportRouteResult s : lst) {
if(includeRoute(s, route)) {
include = true;
break;
}
}
if(!include) {
lst.add(route);
System.out.println(route.toString());
} else {
// System.err.println(route.toString());
}
}
return lst;
}
private boolean includeRoute(TransportRouteResult fastRoute, TransportRouteResult testRoute) {
if(testRoute.segments.size() < fastRoute.segments.size()) {
return false;
}
int j = 0;
for(int i = 0; i < fastRoute.segments.size(); i++, j++) {
TransportRouteResultSegment fs = fastRoute.segments.get(i);
while(j < testRoute.segments.size()) {
TransportRouteResultSegment ts = testRoute.segments.get(j);
if(fs.route.getId().longValue() != ts.route.getId().longValue()) {
j++;
} else {
break;
}
}
if(j >= testRoute.segments.size()) {
return false;
}
}
return true;
}
private static class SegmentsComparator implements Comparator<TransportRouteSegment> {
public SegmentsComparator(TransportRoutingContext ctx) {
}
@Override
public int compare(TransportRouteSegment o1, TransportRouteSegment o2) {
return Double.compare(o1.distFromStart, o2.distFromStart);
}
}
public static class TransportRouteResultSegment {
public final TransportRoute route;
public final int start;
public final int end;
public final double walkDist ;
public TransportRouteResultSegment(TransportRoute route, int start, int end, double walkDist) {
this.route = route;
this.start = start;
this.end = end;
this.walkDist = walkDist;
}
public TransportStop getStart() {
return route.getForwardStops().get(start);
}
public TransportStop getEnd() {
return route.getForwardStops().get(end);
}
public List<Way> getGeometry() {
List<Way> list = new ArrayList<Way>();
route.mergeForwardWays();
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<Node>();
for(int i = 0; i < fw.size() ; i++) {
List<Node> nodes = fw.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();
}
res.add(n);
if(MapUtils.getDistance(en, n.getLatitude(), n.getLongitude()) < minEnd) {
endInd = res.size();
minEnd = MapUtils.getDistance(en, n.getLatitude(), n.getLongitude());
}
}
}
Way way = new Way(-1);
if (res.isEmpty()) {
for (int i = start; i <= end; i++) {
LatLon l = getStop(i).getLocation();
Node n = new Node(l.getLatitude(), l.getLongitude(), -1);
way.addNode(n);
}
list.add(way);
} else {
for(int k = 0; k < res.size() && k < endInd; k++) {
way.addNode(res.get(k));
}
}
list.add(way);
return list;
}
public double getTravelDist() {
double d = 0;
for (int k = start; k < end; k++) {
d += MapUtils.getDistance(route.getForwardStops().get(k).getLocation(),
route.getForwardStops().get(k + 1).getLocation());
}
return d;
}
public TransportStop getStop(int i) {
return route.getForwardStops().get(i);
}
}
public static class TransportRouteResult {
List<TransportRouteResultSegment> segments = new ArrayList<TransportRouteResultSegment>(4);
double finishWalkDist;
double routeTime;
private final TransportRoutingConfiguration cfg;
public TransportRouteResult(TransportRoutingContext ctx) {
cfg = ctx.cfg;
}
public List<TransportRouteResultSegment> getSegments() {
return segments;
}
public double getWalkDist() {
double d = finishWalkDist;
for (TransportRouteResultSegment s : segments) {
d += s.walkDist;
}
return d;
}
public double getRouteTime() {
return routeTime;
}
public int getStops() {
int stops = 0;
for(TransportRouteResultSegment s : segments) {
stops += (s.end - s.start);
}
return stops;
}
public double getTravelDist() {
double d = 0;
for (TransportRouteResultSegment s : segments) {
d += s.getTravelDist();
}
return d;
}
public double getTravelTime() {
return getTravelDist() / cfg.travelSpeed + cfg.stopTime * getStops() +
cfg.changeTime * getChanges();
}
public double getWalkTime() {
return getWalkDist() / cfg.walkSpeed;
}
public int getChanges() {
return segments.size() - 1;
}
@Override
public String toString() {
StringBuilder bld = new StringBuilder();
bld.append(String.format("Route %d stops, %d changes, %.2f min: %.2f m (%.1f min) to walk, %.2f m (%.1f min) to travel\n",
getStops(), getChanges(), routeTime / 60, getWalkDist(), getWalkTime() / 60.0,
getTravelDist(), getTravelTime() / 60.0));
for(int i = 0; i < segments.size(); i++) {
TransportRouteResultSegment s = segments.get(i);
bld.append(String.format(" %d. %s: walk %.1f m to '%s' and travel to '%s' by %s %d stops \n",
i + 1, s.route.getRef(), s.walkDist, s.getStart().getName(), s.getEnd().getName(), s.route.getName(), (s.end - s.start)));
}
bld.append(String.format(" F. Walk %.1f m to reach your destination", finishWalkDist));
return bld.toString();
}
}
public static class TransportRouteSegment {
final int segStart;
final TransportRoute road;
private static final int SHIFT = 10; // assume less than 1024 stops
TransportRouteSegment parentRoute = null;
int parentStop;
double parentTravelTime; // travel time
double parentTravelDist; // inaccurate
// walk distance to start route location (or finish in case last segment)
double walkDist = 0;
// main field accumulated all time spent from beginning of journey
double distFromStart = 0;
public TransportRouteSegment(TransportRoute road, int stopIndex) {
this.road = road;
this.segStart = (short) stopIndex;
}
public TransportRouteSegment(TransportRouteSegment c) {
this.road = c.road;
this.segStart = c.segStart;
}
public boolean wasVisited(TransportRouteSegment rrs) {
if (rrs.road.getId().longValue() == road.getId().longValue()) {
return true;
}
if(parentRoute != null) {
return parentRoute.wasVisited(rrs);
}
return false;
}
public TransportStop getStop(int i) {
return road.getForwardStops().get(i);
}
public LatLon getLocation() {
return road.getForwardStops().get(segStart).getLocation();
}
public int getLength() {
return road.getForwardStops().size();
}
public long getId() {
long l = road.getId() << SHIFT;
if(l < 0 ) {
throw new IllegalStateException("too long id " + road.getId());
}
if(segStart >= (1 << SHIFT)) {
throw new IllegalStateException("too many stops " + road.getId() + " " + segStart);
}
return l + segStart;
}
public int getDepth() {
if(parentRoute != null) {
return parentRoute.getDepth() + 1;
}
return 1;
}
@Override
public String toString() {
return String.format("Route: %s, stop: %s", road.getName(), road.getForwardStops().get(segStart).getName());
}
}
public static class TransportRoutingContext {
public RouteCalculationProgress calculationProgress;
public TLongObjectHashMap<TransportRouteSegment> visitedSegments = new TLongObjectHashMap<TransportRouteSegment>();
public TransportRoutingConfiguration cfg;
public TLongObjectHashMap<List<TransportRouteSegment>> quadTree;
public final Map<BinaryMapIndexReader, TIntObjectHashMap<TransportRoute>> map =
new LinkedHashMap<BinaryMapIndexReader, TIntObjectHashMap<TransportRoute>>();
// stats
public long startCalcTime;
public int visitedRoutesCount;
public int wrongLoadedWays;
public int loadedWays;
public long loadTime;
public long readTime;
private final int walkRadiusIn31;
private final int walkChangeRadiusIn31;
public TransportRoutingContext(TransportRoutingConfiguration cfg, BinaryMapIndexReader... readers) {
this.cfg = cfg;
walkRadiusIn31 = (int) (cfg.walkRadius / MapUtils.getTileDistanceWidth(31));
walkChangeRadiusIn31 = (int) (cfg.walkChangeRadius / MapUtils.getTileDistanceWidth(31));
quadTree = new TLongObjectHashMap<List<TransportRouteSegment>>();
for (BinaryMapIndexReader r : readers) {
map.put(r, new TIntObjectHashMap<TransportRoute>());
}
}
public List<TransportRouteSegment> getTransportStops(LatLon loc) throws IOException {
int y = MapUtils.get31TileNumberY(loc.getLatitude());
int x = MapUtils.get31TileNumberX(loc.getLongitude());
return getTransportStops(x, y, false, new ArrayList<TransportRouteSegment>());
}
public List<TransportRouteSegment> getTransportStops(int x, int y, boolean change, List<TransportRouteSegment> res) throws IOException {
return loadNativeTransportStops(x, y, change, res);
}
private List<TransportRouteSegment> loadNativeTransportStops(int sx, int sy, boolean change, List<TransportRouteSegment> res) throws IOException {
long nanoTime = System.nanoTime();
int d = change ? walkChangeRadiusIn31 : walkRadiusIn31;
int lx = (sx - d ) >> (31 - cfg.ZOOM_TO_LOAD_TILES);
int rx = (sx + d ) >> (31 - cfg.ZOOM_TO_LOAD_TILES);
int ty = (sy - d ) >> (31 - cfg.ZOOM_TO_LOAD_TILES);
int by = (sy + d ) >> (31 - cfg.ZOOM_TO_LOAD_TILES);
for(int x = lx; x <= rx; x++) {
for(int y = ty; y <= by; y++) {
int tileId = x << (cfg.ZOOM_TO_LOAD_TILES + 1) + y;
List<TransportRouteSegment> list = quadTree.get(tileId);
if(list == null) {
list = loadTile(x, y);
quadTree.put(tileId, list);
}
for(TransportRouteSegment r : list) {
TransportStop st = r.getStop(r.segStart);
if (Math.abs(st.x31 - sx) > walkRadiusIn31 || Math.abs(st.y31 - sy) > walkRadiusIn31) {
wrongLoadedWays++;
} else {
loadedWays++;
res.add(r);
}
}
}
}
loadTime += System.nanoTime() - nanoTime;
return res;
}
private List<TransportRouteSegment> loadTile(int x, int y) throws IOException {
long nanoTime = System.nanoTime();
List<TransportRouteSegment> lst = new ArrayList<TransportRouteSegment>();
int pz = (31 - cfg.ZOOM_TO_LOAD_TILES);
SearchRequest<TransportStop> sr = BinaryMapIndexReader.buildSearchTransportRequest(x << pz, (x + 1) << pz,
y << pz, (y + 1) << pz, -1, null);
TIntArrayList allPoints = new TIntArrayList();
TIntArrayList allPointsUnique = new TIntArrayList();
// should it be global?
TLongObjectHashMap<TransportStop> loadedTransportStops = new TLongObjectHashMap<TransportStop>();
for(BinaryMapIndexReader r : map.keySet()) {
sr.clearSearchResults();
List<TransportStop> stops = r.searchTransportIndex(sr);
for(TransportStop s : stops) {
if(!loadedTransportStops.contains(s.getId())) {
loadedTransportStops.put(s.getId(), s);
allPoints.addAll(s.getReferencesToRoutes());
}
}
makeUnique(allPoints, allPointsUnique);
if(allPointsUnique.size() > 0) {
loadTransportSegments(allPointsUnique, r, stops, lst);
}
}
readTime += System.nanoTime() - nanoTime;
return lst;
}
private void loadTransportSegments(TIntArrayList allPointsUnique, BinaryMapIndexReader r,
List<TransportStop> stops, List<TransportRouteSegment> lst) throws IOException {
TIntObjectHashMap<TransportRoute> routes = r.getTransportRoutes(allPointsUnique.toArray());
map.get(r).putAll(routes);
for(TransportStop s : stops) {
for (int ref : s.getReferencesToRoutes()) {
TransportRoute route = routes.get(ref);
if (route != null) {
int stopIndex = -1;
double dist = TransportRoute.SAME_STOP;
for (int k = 0; k < route.getForwardStops().size(); k++) {
TransportStop st = route.getForwardStops().get(k);
double d = MapUtils.getDistance(st.getLocation(), s.getLocation());
if (d < dist) {
stopIndex = k;
dist = d;
}
}
if (stopIndex != -1) {
TransportRouteSegment segment = new TransportRouteSegment(route, stopIndex);
lst.add(segment);
} else {
System.err.println("missing");
}
}
}
}
}
private void makeUnique(TIntArrayList allPoints, TIntArrayList allPointsUnique) {
allPoints.sort();
int p = 0;
TIntIterator it = allPoints.iterator();
while(it.hasNext()) {
int nxt = it.next();
if(p != nxt) {
allPointsUnique.add(nxt);
p = nxt;
}
}
}
}
}

View file

@ -0,0 +1,26 @@
package net.osmand.router;
public class TransportRoutingConfiguration {
public int ZOOM_TO_LOAD_TILES = 14;
public int walkRadius = 1500; // ? 3000
public int walkChangeRadius = 300;
public double walkSpeed = 3.6 / 3.6; // m/s
public double travelSpeed = 36 / 3.6; // m/s
public int stopTime = 30;
public int changeTime = 300;
public int maxNumberOfChanges = 5;
public int finishTimeSeconds = 1200;
public int maxRouteTime = 60 * 60 * 1000; // 1000 hours
}

View file

@ -59,7 +59,7 @@ public class RouteResultPreparationTest {
RandomAccessFile raf = new RandomAccessFile(fl, "r");
fe = new RoutePlannerFrontEnd(false);
fe = new RoutePlannerFrontEnd();
RoutingConfiguration.Builder builder = RoutingConfiguration.getDefault();
Map<String, String> params = new LinkedHashMap<String, String>();
params.put("car", "true");

View file

@ -60,7 +60,7 @@ public class RouteTestingTest {
public void testRouting() throws Exception {
String fl = "src/test/resources/Routing_test.obf";
RandomAccessFile raf = new RandomAccessFile(fl, "r");
RoutePlannerFrontEnd fe = new RoutePlannerFrontEnd(false);
RoutePlannerFrontEnd fe = new RoutePlannerFrontEnd();
BinaryMapIndexReader[] binaryMapIndexReaders = { new BinaryMapIndexReader(raf, new File(fl)) };
RoutingConfiguration.Builder builder = RoutingConfiguration.getDefault();

View file

@ -11,7 +11,7 @@
<application
android:name="net.osmand.telegram.TelegramApplication"
android:allowBackup="true"
android:icon="@mipmap/product_icon"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="unspecified"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@ -17,17 +18,21 @@
android:paddingBottom="@dimen/list_view_bottom_padding"
android:scrollbars="vertical"/>
<Button
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/open_osmand_btn"
style="@style/DialogActionButtonActive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="@dimen/content_padding_big"
android:background="@drawable/img_shadow_fab"
android:drawableLeft="@drawable/ic_action_osmand_plus"
android:drawablePadding="@dimen/content_padding_standard"
android:drawablePadding="@dimen/content_padding_half"
android:drawableStart="@drawable/ic_action_osmand_plus"
android:text="@string/open_osmand"/>
android:gravity="center"
android:paddingLeft="32dp"
android:paddingRight="32dp"
android:text="@string/open_osmand"
android:textColor="@color/white"
app:typeface="@string/font_roboto_medium"/>
</FrameLayout>

View file

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -8,6 +8,7 @@ import org.drinkless.td.libcore.telegram.Client.ResultHandler
import org.drinkless.td.libcore.telegram.TdApi
import org.drinkless.td.libcore.telegram.TdApi.AuthorizationState
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
@ -30,6 +31,19 @@ class TelegramHelper private constructor() {
private const val DEVICE_PREFIX = "Device: "
private const val LOCATION_PREFIX = "Location: "
private const val FEW_SECONDS_AGO = "few seconds ago"
private const val SECONDS_AGO_SUFFIX = " seconds ago"
private const val MINUTES_AGO_SUFFIX = " minutes ago"
private const val HOURS_AGO_SUFFIX = " hours ago"
private const val UTC_FORMAT_SUFFIX = " UTC"
private val UTC_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
private val UTC_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
// min and max values for the Telegram API
const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 61
const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 60 * 60 * 24 - 1 // one day
@ -714,11 +728,27 @@ class TelegramHelper private constructor() {
private fun parseOsmAndBotLocation(message: TdApi.Message): MessageOsmAndBotLocation {
val messageLocation = message.content as TdApi.MessageLocation
val res = MessageOsmAndBotLocation()
res.name = getOsmAndBotDeviceName(message)
res.lat = messageLocation.location.latitude
res.lon = messageLocation.location.longitude
return res
return MessageOsmAndBotLocation().apply {
name = getOsmAndBotDeviceName(message)
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
val date = message.editDate
lastUpdated = if (date != 0) {
date
} else {
message.date
}
}
}
private fun parseOsmAndBotLocationContent(oldContent:MessageOsmAndBotLocation, content: TdApi.MessageContent): MessageOsmAndBotLocation {
val messageLocation = content as TdApi.MessageLocation
return MessageOsmAndBotLocation().apply {
name = oldContent.name
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = (System.currentTimeMillis() / 1000).toInt()
}
}
private fun parseOsmAndBotLocation(text: String): MessageOsmAndBotLocation {
@ -732,8 +762,15 @@ class TelegramHelper private constructor() {
val locStr = s.removePrefix(LOCATION_PREFIX)
try {
val (latS, lonS) = locStr.split(" ")
val updatedS = locStr.substring(locStr.indexOf("("), locStr.length)
val timeSecs = parseTime(updatedS.removePrefix("(").removeSuffix(")"))
res.lat = latS.dropLast(1).toDouble()
res.lon = lonS.toDouble()
if (timeSecs < MESSAGE_ACTIVE_TIME_SEC) {
res.lastUpdated = (System.currentTimeMillis() / 1000 - timeSecs).toInt()
} else {
res.lastUpdated = timeSecs
}
} catch (e: Exception) {
e.printStackTrace()
}
@ -743,6 +780,40 @@ class TelegramHelper private constructor() {
return res
}
private fun parseTime(timeS: String): Int {
try {
when {
timeS.endsWith(FEW_SECONDS_AGO) -> return 5
timeS.endsWith(SECONDS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(SECONDS_AGO_SUFFIX)
return locStr.toInt()
}
timeS.endsWith(MINUTES_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(MINUTES_AGO_SUFFIX)
val minutes = locStr.toInt()
return minutes * 60
}
timeS.endsWith(HOURS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(HOURS_AGO_SUFFIX)
val hours = locStr.toInt()
return hours * 60 * 60
}
timeS.endsWith(UTC_FORMAT_SUFFIX) -> {
val locStr = timeS.removeSuffix(UTC_FORMAT_SUFFIX)
val (latS, lonS) = locStr.split(" ")
val date = UTC_DATE_FORMAT.parse(latS)
val time = UTC_TIME_FORMAT.parse(lonS)
val res = date.time + time.time
return res.toInt()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return 0
}
class MessageOsmAndBotLocation : TdApi.MessageContent() {
var name: String = ""
@ -751,6 +822,8 @@ class TelegramHelper private constructor() {
internal set
var lon: Double = Double.NaN
internal set
var lastUpdated: Int = 0
internal set
override fun getConstructor() = -1
@ -966,7 +1039,7 @@ class TelegramHelper private constructor() {
parseOsmAndBotLocation(newContent.text.text)
} else if (newContent is TdApi.MessageLocation &&
(isOsmAndBot(message.senderUserId) || isOsmAndBot(message.viaBotUserId))) {
parseOsmAndBotLocation(message)
parseOsmAndBotLocationContent(message.content as MessageOsmAndBotLocation, newContent)
} else {
newContent
}

View file

@ -52,6 +52,10 @@ object TelegramUiHelper {
placeholderId = R.drawable.img_user_picture
}
val type = chat.type
val message = messages.firstOrNull()
if (message != null) {
res.lastUpdated = message.editDate
}
if (type is TdApi.ChatTypePrivate || type is TdApi.ChatTypeSecret) {
val userId = getUserIdFromChatType(type)
val chatWithBot = helper.isBot(userId)
@ -59,7 +63,7 @@ object TelegramUiHelper {
res.chatWithBot = chatWithBot
if (!chatWithBot) {
res.userId = userId
val content = messages.firstOrNull()?.content
val content = message?.content
if (content is TdApi.MessageLocation) {
res.latLon = LatLon(content.location.latitude, content.location.longitude)
}
@ -108,6 +112,7 @@ object TelegramUiHelper {
name = content.name
latLon = LatLon(content.lat, content.lon)
placeholderId = R.drawable.img_user_picture
lastUpdated = content.lastUpdated
}
} else {
null
@ -135,6 +140,7 @@ object TelegramUiHelper {
photoPath = helper.getUserPhotoPath(user)
placeholderId = R.drawable.img_user_picture
userId = message.senderUserId
lastUpdated = message.editDate
}
}
@ -152,6 +158,8 @@ object TelegramUiHelper {
internal set
var userId: Int = 0
internal set
var lastUpdated: Int = 0
internal set
abstract fun canBeOpenedOnMap(): Boolean

View file

@ -10,8 +10,8 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import net.osmand.Location
@ -33,6 +33,7 @@ import org.drinkless.td.libcore.telegram.TdApi
private const val CHAT_VIEW_TYPE = 0
private const val LOCATION_ITEM_VIEW_TYPE = 1
private const val LOCATION_TIMEOUT_TO_BE_STALE = 60 * 15 // 15 minutes
class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessagesListener,
FullInfoUpdatesListener, TelegramLocationListener, TelegramCompassListener {
@ -47,6 +48,8 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
private lateinit var adapter: LiveNowListAdapter
private lateinit var locationViewCache: UpdateLocationViewCache
private lateinit var openOsmAndBtn: View
private var location: Location? = null
private var heading: Float? = null
private var locationUiUpdateAllowed: Boolean = true
@ -66,13 +69,19 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
locationUiUpdateAllowed = newState == RecyclerView.SCROLL_STATE_IDLE
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> animateOpenOsmAndBtn(false)
RecyclerView.SCROLL_STATE_IDLE -> animateOpenOsmAndBtn(true)
}
}
})
}
mainView.findViewById<Button>(R.id.open_osmand_btn).setOnClickListener {
val intent = activity?.packageManager?.getLaunchIntentForPackage(OsmandAidlHelper.OSMAND_PACKAGE_NAME)
if (intent != null) {
startActivity(intent)
openOsmAndBtn = mainView.findViewById<View>(R.id.open_osmand_btn).apply {
setOnClickListener {
activity?.packageManager?.getLaunchIntentForPackage(OsmandAidlHelper.OSMAND_PACKAGE_NAME)
?.also { intent ->
startActivity(intent)
}
}
}
return mainView
@ -230,6 +239,16 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
}
}
private fun animateOpenOsmAndBtn(show: Boolean) {
val scale = if (show) 1f else 0f
openOsmAndBtn.animate()
.scaleX(scale)
.scaleY(scale)
.setDuration(200)
.setInterpolator(LinearInterpolator())
.start()
}
inner class LiveNowListAdapter : RecyclerView.Adapter<BaseViewHolder>() {
private val menuList =
@ -282,7 +301,7 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
}
if (location != null && item.latLon != null) {
holder.locationViewContainer?.visibility = View.VISIBLE
// TODO: locationViewCache.outdatedLocation
locationViewCache.outdatedLocation = System.currentTimeMillis() / 1000 - item.lastUpdated > LOCATION_TIMEOUT_TO_BE_STALE
app.uiUtils.updateLocationView(
holder.directionIcon,
holder.distanceText,

View file

@ -118,10 +118,6 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
}
}
})
telegramHelper.listener = this
if (!telegramHelper.isInit()) {
telegramHelper.init()
}
if (osmandAidlHelper.isOsmandBound() && !osmandAidlHelper.isOsmandConnected()) {
osmandAidlHelper.connectOsmand()
@ -143,6 +139,11 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
super.onResume()
paused = false
telegramHelper.listener = this
if (!telegramHelper.isInit()) {
telegramHelper.init()
}
app.locationProvider.checkIfLastKnownLocationIsValid()
if (AndroidUtils.isLocationPermissionAvailable(this)) {

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><resources>
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="use_fluorescent_overlays">Sovrapposizioni fluorescenti</string>
<string name="use_fluorescent_overlays_descr">Usa colori fluorescenti per visualizzare tracce e percorsi.</string>
<string name="offline_edition">Modifiche offline</string>
@ -2555,8 +2556,8 @@ Rappresenta l\'area: %1$s x %2$s</string>
<string name="full_version_thanks">Grazie per avere acquistato la versione completa di OsmAnd!</string>
<string name="fonts_header">Caratteri della mappa</string>
<string name="right_side_navigation">Guida a destra</string>
<string name="depth_contour_descr">Insieme di mappe che contengono le linee isoipse marine e i punti nautici.</string>
<string name="sea_depth_thanks">Grazie per avere acquistato le isoipse nautiche!</string>
<string name="depth_contour_descr">Mappe linee isoipse marine e i punti nautici.</string>
<string name="sea_depth_thanks">Grazie per avere acquistato le \'Linee isoipse nautiche\'</string>
<string name="index_item_depth_contours_osmand_ext">Isoipse nautiche</string>
<string name="index_item_depth_points_southern_hemisphere">Punti nautici sotto il livello del mare dell\'emisfero Australe</string>
<string name="index_item_depth_points_northern_hemisphere">Punti nautici sotto il livello del mare dell\'emisfero boreale</string>
@ -2919,7 +2920,7 @@ Rappresenta l\'area: %1$s x %2$s</string>
<string name="optional_point_name">Nome del punto facoltativo</string>
<string name="transport_nearby_routes_within">Percorsi vicini entro</string>
<string name="transport_nearby_routes">Entro</string>
<string name="enter_the_file_name">Immetti il nome del file.</string>
<string name="enter_the_file_name">Digit il nome del file.</string>
<string name="map_import_error">Errore importazione mappa</string>
<string name="map_imported_successfully">Mappa importata</string>
<string name="winter_and_ski_renderer">Inverno e sci</string>
@ -3078,4 +3079,32 @@ Rappresenta l\'area: %1$s x %2$s</string>
\n"</string>
<string name="quick_action_edit_actions">Modifica le azioni</string>
<string name="error_notification_desc">Per favore invia una schermata di questa notifica a support@osmand.net</string>
</resources>
<string name="poi_cannot_be_found">Il nodo o il percorso non è stato trovato.</string>
<string name="search_no_results_feedback">Nessun risultato di ricerca?
\nDacci un ritorno</string>
<string name="release_3_1">• Navigazione: corretta la barra di avanzamento, veloce inversione dei punti d\'inizio e fine percorso
\n
\n • Marcatori mappa: corretto la visualizzazione o meno dei gruppi, la possibilità di non visualizzare i marcatori dalla mappa
\n
\n • Modifiche OSM: possibilità di modificare elementi non puntuali e segmenti, correzione di commenti mancanti nelle note, backup delle modifiche
\n
\n • Migliorati Wikipedia e predizione Wikivoyage, aggiornati file già disponibili
\n
\n • Menu contestuale: corretti il colore degli scudetti nella modalità notturna e le dimensioni dei menu addizionali
\n
\n • Navigazione marittima: supporto ai fiumi navigabili
\n
\n • Altre correzioni di errori
\n
\n</string>
<string name="commiting_way">Sottoponi la via…</string>
<string name="increase_search_radius_to">Incrementa il raggio di ricerca a %1$s</string>
<string name="send_search_query_description">Invieremo le tue ricerche: <b>\"%1$s\"</b>, così come la tua localizzazione.<br/><br/> Non raccogliamo informazioni personali, abbiamo solo bisogno dei dati delle ricerche per migliorare l\'algoritmo di ricerca.<br/></string>
<string name="send_search_query">Inviare i dati delle ricerche?</string>
<string name="shared_string_world">Mondo</string>
<string name="point_deleted">Punto %1$s eliminato</string>
<string name="coord_input_edit_point">Modifica il punto</string>
<string name="coord_input_add_point">Aggiungi punto</string>
<string name="coord_input_save_as_track">Salva come traccia</string>
<string name="coord_input_save_as_track_descr">Hai aggiunto %1$s punti. Digita il nome del file e tappa su \"Salva\".</string>
</resources>

View file

@ -3008,7 +3008,7 @@
<string name="how_to_open_link">Hvordan åpne lenken?</string>
<string name="read_wikipedia_offline">Les Wikipedia frakoblet</string>
<string name="download_all">Last ned alt</string>
<string name="shared_string_restart">Omstart</string>
<string name="shared_string_restart">Programomstart</string>
<string name="purchase_cancelled_dialog_title">Du har kansellert ditt OsmAnd Live-abonnement</string>
<string name="purchase_cancelled_dialog_descr">Forny abonnement for å fortsette å bruke alle funksjonene:</string>
@ -3023,7 +3023,7 @@
<string name="shared_string_gpx_files">GPX-filer</string>
<string name="get_osmand_live">Skaff deg OsmAnd Live for å låse opp alle funksjoner: Daglige kartoppdateringer med ubegrensede nedlastinger, alle programtilleggene, Wikipedia, Wikivoyage og mye mer.</string>
<string name="quick_action_edit_actions">Rediger handlinger</string>
<string name="error_notification_desc">Send skjermavbildning av denne merknaden til support@osmand.net</string>
<string name="error_notification_desc">Send en skjeravbildning av dette varselet til support@osmand.net</string>
<string name="coord_input_edit_point">Rediger punkt</string>
<string name="coord_input_add_point">Legg til punkt</string>
<string name="coord_input_save_as_track">Lagre som spor</string>
@ -3037,4 +3037,19 @@
<string name="thank_you_for_feedback">Takk for din tilbakemelding</string>
<string name="search_no_results_feedback">Resultatløst?
\nGi oss tilbakemelding</string>
<string name="poi_cannot_be_found">Fant ikke node eller vei.</string>
<string name="release_3_1">• Navigering: Fiks for fremdriftsindikator, raskt bytte av start og sluttpunkt på ruten
\n
\n• Kartmarkører: Fiks for å slå av og på grupper, mulighet til å skjule markører fra kartet
\n
\n• OSM-rediger: Mulighet til å redigere koder for ikke-punktsobjekter og veier, fiks for manglende kommentarer på notater, sikkerhetskopiering av endringer
\n
\n• Forbedret Wikipedia- og Wikivoyage -fortolkning, oppdaterte filer er allerede tilgjengelige
\n
\n• Kontekstmeny: Fiks for transportskjoldsfarge i nattmodus, fiks for tilleggsmenystørrelser
\n
\n• Båtnavigasjon: Støtte for vannvei-fairway
\n
\n• Andre feilrettinger
\n</string>
</resources>

View file

@ -1661,7 +1661,7 @@
<string name="poi_toilets_access_community">Acesso aos banheiros: comunidade</string>
<string name="poi_toilets_access_public">Acesso aos banheiros: público</string>
<string name="poi_diaper_yes">Trocador de fralda</string>
<string name="poi_diaper_yes">Com trocador de fralda</string>
<string name="poi_diaper_no">Sem trocador de fralda</string>
<string name="poi_diaper_room">Sala para trocar fralda</string>

View file

@ -17,12 +17,12 @@
<string name="poi_service_general">Одржавање</string>
<string name="poi_information_contents">Садржаји</string>
<string name="poi_clock_option">Додатно</string>
<string name="poi_scout_camp">Камп скаута</string>
<string name="poi_scout_camp">Камп извиђача</string>
<string name="poi_resort_type">Тип</string>
<string name="poi_piste_difficulty">Тежина стазе</string>
<string name="poi_piste_grooming">Равнање стазе</string>
<string name="poi_theatre_genre">Жанр</string>
<string name="poi_outdoor_seating">Седење напољу</string>
<string name="poi_outdoor_seating">Спољашња клупа</string>
<string name="poi_fee">Накнада</string>
<string name="poi_smoking">Пушење</string>
<string name="poi_delivery">Достава</string>
@ -1492,4 +1492,55 @@
<string name="poi_denomination_dutch_reformed">Холандски реформисти</string>
<string name="poi_denomination_apostolic">Апостолска црква</string>
<string name="poi_denomination_reform">Реформисти</string>
<string name="poi_fuel_avia_type">Врста горива (авиа)</string>
<string name="poi_tactile_paving">Додирна поставка</string>
<string name="poi_brushless">Без четкица</string>
<string name="poi_observatory_designation">Одредиште</string>
<string name="poi_pharmacy_dispensing">Апотека</string>
<string name="poi_denomination">Деноминација</string>
<string name="poi_backcountry">Рурални део</string>
<string name="poi_beauty_salon_service">Услуга</string>
<string name="poi_diet">Храна</string>
<string name="poi_health_specialty">Специјалитет здрави</string>
<string name="poi_power_supply">Извор напајања</string>
<string name="poi_motorcycle_services">Услуге</string>
<string name="poi_operational_status">Функционални статус</string>
<string name="poi_water_supply_type">Тип доставе воде</string>
<string name="poi_water_place_access">Место приступа води</string>
<string name="poi_checkpoint_type">Тип контролног места</string>
<string name="poi_shop">Продавница</string>
<string name="poi_shop_food">Продавница потребштина и маркет</string>
<string name="poi_emergency">Хитна помоћ</string>
<string name="poi_emergency_infrastructure">Инфраструктура хитне помоћи</string>
<string name="poi_node_networks">Пешачки/бициклистички мрежни чворови</string>
<string name="poi_traffic_enforcement">Ограничење саобраћаја</string>
<string name="poi_man_made">Направљено ручно</string>
<string name="poi_transport_construction">Саобраћајна конструкција</string>
<string name="poi_water_supply">Достава воде</string>
<string name="poi_landuse">Коришћење земљишта</string>
<string name="poi_accomodation">Смештај</string>
<string name="poi_entertainment">Забавно</string>
<string name="poi_service">Услуга</string>
<string name="poi_craft">Радионица</string>
<string name="poi_convenience">Продавница потребштина</string>
<string name="poi_deli">Продавница деликатеса</string>
<string name="poi_greengrocer">Продавница зеленила</string>
<string name="poi_ice_cream">Простор са сладоледом</string>
<string name="poi_pastry">Продавница пецива</string>
<string name="poi_dairy">Млекара</string>
<string name="poi_vending_machine">Аутомат продаје</string>
<string name="poi_bag">Продавница торби</string>
<string name="poi_bathroom_furnishing">Намештај за купатило</string>
<string name="poi_bed">Продавница кревета</string>
<string name="poi_charity">Продавница прилога</string>
<string name="poi_dive">Ронилачка опрема</string>
<string name="poi_doityourself">Продавница за кућно унапређење</string>
<string name="poi_fishing">Опрема за пецање</string>
<string name="poi_free_flying">Продавница ствари слободног летења</string>
<string name="poi_furniture">Продавница намештаја</string>
<string name="poi_garden_centre">Баштенски центар</string>
<string name="poi_garden_furniture">Продавница баштенског намештаја</string>
<string name="poi_shop_yes">Општа продавница</string>
<string name="poi_glaziery">Стаклара</string>
</resources>

View file

@ -2978,7 +2978,7 @@
\nЈавите нам</string>
<string name="release_3_1">• Навођење: Исправка у траци прогреса, брза замена почетне и крајње тачке пута
\n
\n • Ознаке на карти: fix turn on/off groups, ability to hide markers from the map
\n • Ознаке на карти: исправи укључење/искључење група, способност скривања маркера са мапе
\n
\n • OSM измене: Могућност измена ознака за објекте и путеве који нису обичне тачке, исправка недостајућих коментара на белешкама, прављење резервне копије измена
\n

View file

@ -136,10 +136,10 @@ public class CurrentPositionHelper {
}
RoutingConfiguration cfg = app.getDefaultRoutingConfig().build(p, 10,
new HashMap<String, String>());
ctx = new RoutePlannerFrontEnd(false).buildRoutingContext(cfg, null, rs);
ctx = new RoutePlannerFrontEnd().buildRoutingContext(cfg, null, rs);
RoutingConfiguration defCfg = app.getDefaultRoutingConfig().build("geocoding", 10,
new HashMap<String, String>());
defCtx = new RoutePlannerFrontEnd(false).buildRoutingContext(defCfg, null, rs);
defCtx = new RoutePlannerFrontEnd().buildRoutingContext(defCfg, null, rs);
} else {
ctx = null;
defCtx = null;

View file

@ -756,6 +756,7 @@ public class MapContextMenu extends MenuTitleController implements StateChangedL
}
}
@Nullable
public WeakReference<MapContextMenuFragment> findMenuFragment() {
Fragment fragment = mapActivity != null
? mapActivity.getSupportFragmentManager().findFragmentByTag(MapContextMenuFragment.TAG) : null;

View file

@ -12,6 +12,7 @@ import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.view.ContextThemeWrapper;
@ -89,6 +90,7 @@ public class MenuBuilder {
protected List<Amenity> nearestWiki = new ArrayList<>();
private List<OsmandPlugin> menuPlugins = new ArrayList<>();
private List<TransportStopRoute> routes = new ArrayList<>();
@Nullable
private CardsRowBuilder onlinePhotoCardsRow;
private List<AbstractCard> onlinePhotoCards;
@ -416,6 +418,9 @@ public class MenuBuilder {
}
private void startLoadingImages() {
if (onlinePhotoCardsRow == null) {
return;
}
onlinePhotoCards = new ArrayList<>();
onlinePhotoCardsRow.setProgressCard();
execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(),
@ -433,7 +438,9 @@ public class MenuBuilder {
if (cardList.size() == 0) {
cards.add(new NoImagesCard(mapActivity));
}
onlinePhotoCardsRow.setCards(cards);
if (onlinePhotoCardsRow != null) {
onlinePhotoCardsRow.setCards(cards);
}
onlinePhotoCards = cards;
}
}

View file

@ -44,15 +44,16 @@ public class MapMultiSelectionMenu extends BaseMenuController {
this.pointDescription = pointDescription;
this.object = object;
this.mapActivity = mapActivity;
if (mapActivity != null) {
init();
}
init();
}
protected void init() {
controller = MenuController.getMenuController(mapActivity, latLon, pointDescription, object, MenuType.MULTI_LINE);
controller.setActive(true);
initTitle();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
controller = MenuController.getMenuController(mapActivity, latLon, pointDescription, object, MenuType.MULTI_LINE);
controller.setActive(true);
initTitle();
}
}
protected void deinit() {
@ -160,7 +161,10 @@ public class MapMultiSelectionMenu extends BaseMenuController {
private void clearMenu() {
clearSelectedObjects();
objects.clear();
getMapActivity().refreshMap();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.refreshMap();
}
}
public void show(LatLon latLon, Map<Object, IContextMenuProvider> selectedObjects) {
@ -170,8 +174,11 @@ public class MapMultiSelectionMenu extends BaseMenuController {
this.latLon = latLon;
createCollection(selectedObjects);
updateNightMode();
MapMultiSelectionMenuFragment.showInstance(getMapActivity());
getMapActivity().refreshMap();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
MapMultiSelectionMenuFragment.showInstance(mapActivity);
mapActivity.refreshMap();
}
}
public boolean isVisible() {
@ -179,8 +186,14 @@ public class MapMultiSelectionMenu extends BaseMenuController {
return fragment != null;
}
@Nullable
public Fragment getFragmentByTag() {
return getMapActivity().getSupportFragmentManager().findFragmentByTag(MapMultiSelectionMenuFragment.TAG);
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
return mapActivity.getSupportFragmentManager().findFragmentByTag(MapMultiSelectionMenuFragment.TAG);
} else {
return null;
}
}
public void hide() {
@ -199,8 +212,11 @@ public class MapMultiSelectionMenu extends BaseMenuController {
public void openContextMenu(@NonNull MenuObject menuObject) {
IContextMenuProvider provider = selectedObjects.remove(menuObject.getObject());
hide();
ContextMenuLayer contextMenuLayer = getMapActivity().getMapLayers().getContextMenuLayer();
contextMenuLayer.showContextMenu(menuObject.getLatLon(), menuObject.getPointDescription(), menuObject.getObject(), provider);
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
ContextMenuLayer contextMenuLayer = mapActivity.getMapLayers().getContextMenuLayer();
contextMenuLayer.showContextMenu(menuObject.getLatLon(), menuObject.getPointDescription(), menuObject.getObject(), provider);
}
}
private void clearSelectedObjects() {

View file

@ -658,7 +658,7 @@ public class RouteProvider {
protected RouteCalculationResult findVectorMapsRoute(final RouteCalculationParams params, boolean calcGPXRoute) throws IOException {
BinaryMapIndexReader[] files = params.ctx.getResourceManager().getRoutingMapFiles();
RoutePlannerFrontEnd router = new RoutePlannerFrontEnd(false);
RoutePlannerFrontEnd router = new RoutePlannerFrontEnd();
OsmandSettings settings = params.ctx.getSettings();
router.setUseFastRecalculation(settings.USE_FAST_RECALCULATION.get());

View file

@ -10,6 +10,7 @@ import android.graphics.PointF;
import android.os.Build;
import android.os.Vibrator;
import android.support.annotation.DimenRes;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v4.util.Pair;
import android.view.MotionEvent;
@ -32,12 +33,15 @@ import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.mapcontextmenu.MapContextMenuFragment;
import net.osmand.plus.mapcontextmenu.other.MapMultiSelectionMenu;
import net.osmand.plus.measurementtool.MeasurementToolLayer;
import net.osmand.plus.quickaction.QuickAction;
import net.osmand.plus.quickaction.QuickActionFactory;
import net.osmand.plus.quickaction.QuickActionRegistry;
import net.osmand.plus.quickaction.QuickActionsWidget;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@ -381,15 +385,20 @@ public class MapQuickActionLayer extends OsmandMapLayer implements QuickActionRe
}
private void setupQuickActionBtnVisibility() {
MapContextMenu contextMenu = mapActivity.getContextMenu();
MapMultiSelectionMenu multiSelectionMenu = contextMenu.getMultiSelectionMenu();
WeakReference<MapContextMenuFragment> contextMenuMenuFragmentRef = contextMenu.findMenuFragment();
MapContextMenuFragment contextMenuMenuFragment = contextMenuMenuFragmentRef != null ? contextMenuMenuFragmentRef.get() : null;
Fragment multiMenuFragment = multiSelectionMenu.getFragmentByTag();
boolean hideQuickButton = !isLayerOn ||
contextMenuLayer.isInChangeMarkerPositionMode() ||
contextMenuLayer.isInGpxDetailsMode() ||
measurementToolLayer.isInMeasurementMode() ||
mapMarkersLayer.isInPlanRouteMode() ||
mapActivity.getContextMenu().isVisible() && !mapActivity.getContextMenu().findMenuFragment().get().isRemoving() ||
mapActivity.getContextMenu().isVisible() && mapActivity.getContextMenu().findMenuFragment().get().isAdded() ||
mapActivity.getContextMenu().getMultiSelectionMenu().isVisible() && mapActivity.getContextMenu().getMultiSelectionMenu().getFragmentByTag().isAdded() ||
mapActivity.getContextMenu().getMultiSelectionMenu().isVisible() && !mapActivity.getContextMenu().getMultiSelectionMenu().getFragmentByTag().isRemoving();
contextMenu.isVisible() && contextMenuMenuFragment != null && !contextMenuMenuFragment.isRemoving() ||
contextMenu.isVisible() && contextMenuMenuFragment != null && contextMenuMenuFragment.isAdded() ||
multiSelectionMenu.isVisible() && multiMenuFragment != null && multiMenuFragment.isAdded() ||
multiSelectionMenu.isVisible() && multiMenuFragment != null && !multiMenuFragment.isRemoving();
quickActionButton.setVisibility(hideQuickButton ? View.GONE : View.VISIBLE);
}

View file

@ -117,7 +117,7 @@ public class CurrentPositionHelper {
if (rs.length > 0) {
RoutingConfiguration defCfg = RoutingConfiguration.getDefault().build("geocoding", 10,
new HashMap<String, String>());
defCtx = new RoutePlannerFrontEnd(false).buildRoutingContext(defCfg, null, rs);
defCtx = new RoutePlannerFrontEnd().buildRoutingContext(defCfg, null, rs);
} else {
defCtx = null;
}