Refactor route / gpx geometry

This commit is contained in:
max-klaus 2020-08-04 21:36:14 +03:00
parent 1e22187dde
commit 151f2f31c6
79 changed files with 3029 additions and 2502 deletions

View file

@ -2,27 +2,16 @@ package net.osmand.router;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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.Locale;
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;
@ -31,9 +20,6 @@ import net.osmand.data.TransportStop;
import net.osmand.data.TransportStopExit;
import net.osmand.osm.edit.Node;
import net.osmand.osm.edit.Way;
import net.osmand.router.TransportRoutePlanner.TransportRouteResult;
import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment;
import net.osmand.router.TransportRoutePlanner.TransportRouteSegment;
import net.osmand.util.MapUtils;
public class TransportRoutePlanner {
@ -224,11 +210,10 @@ public class TransportRoutePlanner {
}
}
private List<TransportRouteResult> prepareResults(TransportRoutingContext ctx, List<TransportRouteSegment> results) {
Collections.sort(results, new SegmentsComparator(ctx));
List<TransportRouteResult> lst = new ArrayList<TransportRouteResult>();
System.out.println(String.format("Calculated %.1f seconds, found %d results, visited %d routes / %d stops, loaded %d tiles (%d ms read, %d ms total), loaded ways %d (%d wrong)",
System.out.println(String.format(Locale.US, "Calculated %.1f seconds, found %d results, visited %d routes / %d stops, loaded %d tiles (%d ms read, %d ms total), loaded ways %d (%d wrong)",
(System.currentTimeMillis() - ctx.startCalcTime) / 1000.0, results.size(),
ctx.visitedRoutesCount, ctx.visitedStops,
ctx.quadTree.size(), ctx.readTime / (1000 * 1000), ctx.loadTime / (1000 * 1000),
@ -314,7 +299,6 @@ public class TransportRoutePlanner {
}
}
public static class TransportRouteResultSegment {
private static final boolean DISPLAY_FULL_SEGMENT_ROUTE = false;
@ -486,157 +470,13 @@ public class TransportRoutePlanner {
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 TransportRouteResult(TransportRoutingConfiguration cfg) {
this.cfg = cfg;
}
public List<TransportRouteResultSegment> getSegments() {
return segments;
}
public void setFinishWalkDist(double finishWalkDist) {
this.finishWalkDist = finishWalkDist;
}
public void setRouteTime(double routeTime) {
this.routeTime = routeTime;
}
public void addSegment(TransportRouteResultSegment seg) {
segments.add(seg);
}
public double getWalkDist() {
double d = finishWalkDist;
for (TransportRouteResultSegment s : segments) {
d += s.walkDist;
}
return d;
}
public double getFinishWalkDist() {
return finishWalkDist;
}
public double getWalkSpeed() {
return cfg.walkSpeed;
}
public double getRouteTime() {
return routeTime;
}
public int getStops() {
int stops = 0;
for(TransportRouteResultSegment s : segments) {
stops += (s.end - s.start);
}
return stops;
}
public boolean isRouteStop(TransportStop stop) {
for(TransportRouteResultSegment s : segments) {
if (s.getTravelStops().contains(stop)) {
return true;
}
}
return false;
}
public TransportRouteResultSegment getRouteStopSegment(TransportStop stop) {
for(TransportRouteResultSegment s : segments) {
if (s.getTravelStops().contains(stop)) {
return s;
}
}
return null;
}
public double getTravelDist() {
double d = 0;
for (TransportRouteResultSegment s : segments) {
d += s.getTravelDist();
}
return d;
}
public double getTravelTime() {
double t = 0;
for (TransportRouteResultSegment s : segments) {
if (cfg.useSchedule) {
TransportSchedule sts = s.route.getSchedule();
for (int k = s.start; k < s.end; k++) {
t += sts.getAvgStopIntervals()[k] * 10;
}
} else {
t += cfg.getBoardingTime();
t += s.getTravelTime();
}
}
return t;
}
public double getWalkTime() {
return getWalkDist() / cfg.walkSpeed;
}
public double getChangeTime() {
return cfg.getChangeTime();
}
public double getBoardingTime() {
return cfg.getBoardingTime();
}
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);
String time = "";
String arriveTime = "";
if(s.depTime != -1) {
time = String.format("at %s", formatTransporTime(s.depTime));
}
int aTime = s.getArrivalTime();
if(aTime != -1) {
arriveTime = String.format("and arrive at %s", formatTransporTime(aTime));
}
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));
return bld.toString();
}
}
public static String formatTransporTime(int i) {
public static String formatTransportTime(int i) {
int h = i / 60 / 6;
int mh = i - h * 60 * 6;
int m = mh / 6;
int s = (mh - m * 6) * 10;
String tm = String.format("%02d:%02d:%02d ", h, m, s);
return tm;
return String.format(Locale.US, "%02d:%02d:%02d ", h, m, s);
}
public static class TransportRouteSegment {
@ -653,13 +493,9 @@ public class TransportRoutePlanner {
double parentTravelDist; // travel distance for parent route (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;
@ -677,8 +513,7 @@ public class TransportRoutePlanner {
this.segStart = c.segStart;
this.departureTime = c.departureTime;
}
public boolean wasVisited(TransportRouteSegment rrs) {
if (rrs.road.getId().longValue() == road.getId().longValue() &&
rrs.departureTime == departureTime) {
@ -690,22 +525,18 @@ public class TransportRoutePlanner {
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();
@ -727,7 +558,6 @@ public class TransportRoutePlanner {
return l ;
}
public int getDepth() {
if(parentRoute != null) {
return parentRoute.getDepth() + 1;
@ -738,165 +568,10 @@ public class TransportRoutePlanner {
@Override
public String toString() {
return String.format("Route: %s, stop: %s %s", road.getName(), road.getForwardStops().get(segStart).getName(),
departureTime == -1 ? "" : formatTransporTime(departureTime) );
}
}
public static class TransportRoutingContext {
public NativeLibrary library;
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
final TransportStopsRouteReader transportStopsReader;
public int finishTimeSeconds;
// stats
public long startCalcTime;
public int visitedRoutesCount;
public int visitedStops;
public int wrongLoadedWays;
public int loadedWays;
public long loadTime;
public long readTime;
private final int walkRadiusIn31;
private final int walkChangeRadiusIn31;
public TransportRoutingContext(TransportRoutingConfiguration cfg, NativeLibrary library, BinaryMapIndexReader... readers) {
this.cfg = cfg;
walkRadiusIn31 = (int) (cfg.walkRadius / MapUtils.getTileDistanceWidth(31));
walkChangeRadiusIn31 = (int) (cfg.walkChangeRadius / MapUtils.getTileDistanceWidth(31));
quadTree = new TLongObjectHashMap<List<TransportRouteSegment>>();
this.library = library;
transportStopsReader = new TransportStopsRouteReader(Arrays.asList(readers));
}
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++) {
long tileId = (((long)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);
Collection<TransportStop> stops = transportStopsReader.readMergedTransportStops(sr);
loadTransportSegments(stops, lst);
readTime += System.nanoTime() - nanoTime;
return lst;
}
private void loadTransportSegments(Collection<TransportStop> stops, List<TransportRouteSegment> lst) throws IOException {
for(TransportStop s : stops) {
if (s.isDeleted() || s.getRoutes() == null) {
continue;
}
for (TransportRoute route : s.getRoutes()) {
int stopIndex = -1;
double dist = TransportRoute.SAME_STOP;
for (int k = 0; k < route.getForwardStops().size(); k++) {
TransportStop st = route.getForwardStops().get(k);
if(st.getId().longValue() == s.getId().longValue() ) {
stopIndex = k;
break;
}
double d = MapUtils.getDistance(st.getLocation(), s.getLocation());
if (d < dist) {
stopIndex = k;
dist = d;
}
}
if (stopIndex != -1) {
if (cfg != null && cfg.useSchedule) {
loadScheduleRouteSegment(lst, route, stopIndex);
} else {
TransportRouteSegment segment = new TransportRouteSegment(route, stopIndex);
lst.add(segment);
}
} else {
System.err.println(String.format("Routing error: missing stop '%s' in route '%s' id: %d",
s.toString(), route.getRef(), route.getId() / 2));
}
}
}
}
private void loadScheduleRouteSegment(List<TransportRouteSegment> lst, TransportRoute route, int stopIndex) {
if(route.getSchedule() != null) {
TIntArrayList ti = route.getSchedule().tripIntervals;
int cnt = ti.size();
int t = 0;
// improve by using exact data
int stopTravelTime = 0;
TIntArrayList avgStopIntervals = route.getSchedule().avgStopIntervals;
for (int i = 0; i < stopIndex; i++) {
if (avgStopIntervals.size() > i) {
stopTravelTime += avgStopIntervals.getQuick(i);
}
}
for(int i = 0; i < cnt; i++) {
t += ti.getQuick(i);
int startTime = t + stopTravelTime;
if(startTime >= cfg.scheduleTimeOfDay && startTime <= cfg.scheduleTimeOfDay + cfg.scheduleMaxTime ) {
TransportRouteSegment segment = new TransportRouteSegment(route, stopIndex, startTime);
lst.add(segment);
}
}
}
departureTime == -1 ? "" : formatTransportTime(departureTime) );
}
}
public static List<TransportRouteResult> convertToTransportRoutingResult(NativeTransportRoutingResult[] res,
TransportRoutingConfiguration cfg) {
// cache for converted TransportRoutes:

View file

@ -0,0 +1,151 @@
package net.osmand.router;
import net.osmand.data.TransportSchedule;
import net.osmand.data.TransportStop;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class TransportRouteResult {
List<TransportRoutePlanner.TransportRouteResultSegment> segments = new ArrayList<TransportRoutePlanner.TransportRouteResultSegment>(4);
double finishWalkDist;
double routeTime;
private final TransportRoutingConfiguration cfg;
public TransportRouteResult(TransportRoutingContext ctx) {
cfg = ctx.cfg;
}
public TransportRouteResult(TransportRoutingConfiguration cfg) {
this.cfg = cfg;
}
public List<TransportRoutePlanner.TransportRouteResultSegment> getSegments() {
return segments;
}
public void setFinishWalkDist(double finishWalkDist) {
this.finishWalkDist = finishWalkDist;
}
public void setRouteTime(double routeTime) {
this.routeTime = routeTime;
}
public void addSegment(TransportRoutePlanner.TransportRouteResultSegment seg) {
segments.add(seg);
}
public double getWalkDist() {
double d = finishWalkDist;
for (TransportRoutePlanner.TransportRouteResultSegment s : segments) {
d += s.walkDist;
}
return d;
}
public double getFinishWalkDist() {
return finishWalkDist;
}
public double getWalkSpeed() {
return cfg.walkSpeed;
}
public double getRouteTime() {
return routeTime;
}
public int getStops() {
int stops = 0;
for(TransportRoutePlanner.TransportRouteResultSegment s : segments) {
stops += (s.end - s.start);
}
return stops;
}
public boolean isRouteStop(TransportStop stop) {
for(TransportRoutePlanner.TransportRouteResultSegment s : segments) {
if (s.getTravelStops().contains(stop)) {
return true;
}
}
return false;
}
public TransportRoutePlanner.TransportRouteResultSegment getRouteStopSegment(TransportStop stop) {
for(TransportRoutePlanner.TransportRouteResultSegment s : segments) {
if (s.getTravelStops().contains(stop)) {
return s;
}
}
return null;
}
public double getTravelDist() {
double d = 0;
for (TransportRoutePlanner.TransportRouteResultSegment s : segments) {
d += s.getTravelDist();
}
return d;
}
public double getTravelTime() {
double t = 0;
for (TransportRoutePlanner.TransportRouteResultSegment s : segments) {
if (cfg.useSchedule) {
TransportSchedule sts = s.route.getSchedule();
for (int k = s.start; k < s.end; k++) {
t += sts.getAvgStopIntervals()[k] * 10;
}
} else {
t += cfg.getBoardingTime();
t += s.getTravelTime();
}
}
return t;
}
public double getWalkTime() {
return getWalkDist() / cfg.walkSpeed;
}
public double getChangeTime() {
return cfg.getChangeTime();
}
public double getBoardingTime() {
return cfg.getBoardingTime();
}
public int getChanges() {
return segments.size() - 1;
}
@Override
public String toString() {
StringBuilder bld = new StringBuilder();
bld.append(String.format(Locale.US, "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++) {
TransportRoutePlanner.TransportRouteResultSegment s = segments.get(i);
String time = "";
String arriveTime = "";
if(s.depTime != -1) {
time = String.format("at %s", TransportRoutePlanner.formatTransportTime(s.depTime));
}
int aTime = s.getArrivalTime();
if(aTime != -1) {
arriveTime = String.format("and arrive at %s", TransportRoutePlanner.formatTransportTime(aTime));
}
bld.append(String.format(Locale.US, " %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));
return bld.toString();
}
}

View file

@ -0,0 +1,171 @@
package net.osmand.router;
import net.osmand.NativeLibrary;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.data.LatLon;
import net.osmand.data.TransportRoute;
import net.osmand.data.TransportStop;
import net.osmand.router.TransportRoutePlanner.TransportRouteSegment;
import net.osmand.util.MapUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TLongObjectHashMap;
public class TransportRoutingContext {
public NativeLibrary library;
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
final TransportStopsRouteReader transportStopsReader;
public int finishTimeSeconds;
// stats
public long startCalcTime;
public int visitedRoutesCount;
public int visitedStops;
public int wrongLoadedWays;
public int loadedWays;
public long loadTime;
public long readTime;
private final int walkRadiusIn31;
private final int walkChangeRadiusIn31;
public TransportRoutingContext(TransportRoutingConfiguration cfg, NativeLibrary library, BinaryMapIndexReader... readers) {
this.cfg = cfg;
walkRadiusIn31 = (int) (cfg.walkRadius / MapUtils.getTileDistanceWidth(31));
walkChangeRadiusIn31 = (int) (cfg.walkChangeRadius / MapUtils.getTileDistanceWidth(31));
quadTree = new TLongObjectHashMap<List<TransportRouteSegment>>();
this.library = library;
transportStopsReader = new TransportStopsRouteReader(Arrays.asList(readers));
}
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++) {
long tileId = (((long)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);
BinaryMapIndexReader.SearchRequest<TransportStop> sr = BinaryMapIndexReader.buildSearchTransportRequest(x << pz, (x + 1) << pz,
y << pz, (y + 1) << pz, -1, null);
Collection<TransportStop> stops = transportStopsReader.readMergedTransportStops(sr);
loadTransportSegments(stops, lst);
readTime += System.nanoTime() - nanoTime;
return lst;
}
private void loadTransportSegments(Collection<TransportStop> stops, List<TransportRouteSegment> lst) throws IOException {
for(TransportStop s : stops) {
if (s.isDeleted() || s.getRoutes() == null) {
continue;
}
for (TransportRoute route : s.getRoutes()) {
int stopIndex = -1;
double dist = TransportRoute.SAME_STOP;
for (int k = 0; k < route.getForwardStops().size(); k++) {
TransportStop st = route.getForwardStops().get(k);
if(st.getId().longValue() == s.getId().longValue() ) {
stopIndex = k;
break;
}
double d = MapUtils.getDistance(st.getLocation(), s.getLocation());
if (d < dist) {
stopIndex = k;
dist = d;
}
}
if (stopIndex != -1) {
if (cfg != null && cfg.useSchedule) {
loadScheduleRouteSegment(lst, route, stopIndex);
} else {
TransportRouteSegment segment = new TransportRouteSegment(route, stopIndex);
lst.add(segment);
}
} else {
System.err.println(String.format(Locale.US, "Routing error: missing stop '%s' in route '%s' id: %d",
s.toString(), route.getRef(), route.getId() / 2));
}
}
}
}
private void loadScheduleRouteSegment(List<TransportRouteSegment> lst, TransportRoute route, int stopIndex) {
if(route.getSchedule() != null) {
TIntArrayList ti = route.getSchedule().tripIntervals;
int cnt = ti.size();
int t = 0;
// improve by using exact data
int stopTravelTime = 0;
TIntArrayList avgStopIntervals = route.getSchedule().avgStopIntervals;
for (int i = 0; i < stopIndex; i++) {
if (avgStopIntervals.size() > i) {
stopTravelTime += avgStopIntervals.getQuick(i);
}
}
for(int i = 0; i < cnt; i++) {
t += ti.getQuick(i);
int startTime = t + stopTravelTime;
if(startTime >= cfg.scheduleTimeOfDay && startTime <= cfg.scheduleTimeOfDay + cfg.scheduleMaxTime ) {
TransportRouteSegment segment = new TransportRouteSegment(route, stopIndex, startTime);
lst.add(segment);
}
}
}
}
}

View file

@ -18,8 +18,8 @@ import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.views.AidlMapLayer;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.layers.AidlMapLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget;
import net.osmand.util.Algorithms;

View file

@ -76,8 +76,8 @@ import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.OsmAndAppCustomization;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.SettingsHelper;
import net.osmand.plus.views.AidlMapLayer;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.layers.AidlMapLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget;

View file

@ -147,9 +147,9 @@ import net.osmand.plus.settings.fragments.ProfileAppearanceFragment;
import net.osmand.plus.track.TrackAppearanceFragment;
import net.osmand.plus.views.AddGpxPointBottomSheetHelper.NewGpxPoint;
import net.osmand.plus.views.AnimateDraggingMapThread;
import net.osmand.plus.views.MapControlsLayer;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.MapQuickActionLayer;
import net.osmand.plus.views.layers.MapControlsLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.layers.MapQuickActionLayer;
import net.osmand.plus.views.OsmAndMapLayersView;
import net.osmand.plus.views.OsmAndMapSurfaceView;
import net.osmand.plus.views.OsmandMapLayer;

View file

@ -68,7 +68,7 @@ import net.osmand.plus.routing.RouteProvider.GPXRouteParamsBuilder;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.settings.fragments.BaseSettingsFragment;
import net.osmand.plus.views.BaseMapLayer;
import net.osmand.plus.views.MapControlsLayer;
import net.osmand.plus.views.layers.MapControlsLayer;
import net.osmand.plus.views.MapTileLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.wikipedia.WikipediaDialogFragment;

View file

@ -41,24 +41,24 @@ import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin;
import net.osmand.plus.render.MapVectorLayer;
import net.osmand.plus.render.RenderingIcons;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.views.ContextMenuLayer;
import net.osmand.plus.views.DownloadedRegionsLayer;
import net.osmand.plus.views.FavouritesLayer;
import net.osmand.plus.views.GPXLayer;
import net.osmand.plus.views.ImpassableRoadsLayer;
import net.osmand.plus.views.MapControlsLayer;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.MapMarkersLayer;
import net.osmand.plus.views.MapQuickActionLayer;
import net.osmand.plus.views.MapTextLayer;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.plus.views.layers.DownloadedRegionsLayer;
import net.osmand.plus.views.layers.FavouritesLayer;
import net.osmand.plus.views.layers.GPXLayer;
import net.osmand.plus.views.layers.ImpassableRoadsLayer;
import net.osmand.plus.views.layers.MapControlsLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.layers.MapMarkersLayer;
import net.osmand.plus.views.layers.MapQuickActionLayer;
import net.osmand.plus.views.layers.MapTextLayer;
import net.osmand.plus.views.MapTileLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.POIMapLayer;
import net.osmand.plus.views.PointLocationLayer;
import net.osmand.plus.views.PointNavigationLayer;
import net.osmand.plus.views.RouteLayer;
import net.osmand.plus.views.RulerControlLayer;
import net.osmand.plus.views.TransportStopsLayer;
import net.osmand.plus.views.layers.POIMapLayer;
import net.osmand.plus.views.layers.PointLocationLayer;
import net.osmand.plus.views.layers.PointNavigationLayer;
import net.osmand.plus.views.layers.RouteLayer;
import net.osmand.plus.views.layers.RulerControlLayer;
import net.osmand.plus.views.layers.TransportStopsLayer;
import net.osmand.plus.views.mapwidgets.MapWidgetRegistry;
import java.util.ArrayList;

View file

@ -17,8 +17,8 @@ import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.audionotes.AudioVideoNotesPlugin.Recording;
import net.osmand.plus.base.PointImageDrawable;
import net.osmand.plus.views.ContextMenuLayer;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.util.Algorithms;

View file

@ -67,7 +67,7 @@ import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.myplaces.FavoritesActivity;
import net.osmand.plus.quickaction.QuickActionType;
import net.osmand.plus.settings.fragments.BaseSettingsFragment;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.widgetstates.WidgetState;

View file

@ -13,8 +13,8 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.MapActivityLayers;
import net.osmand.plus.base.ContextMenuFragment.ContextMenuFragmentListener;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.views.MapControlsLayer;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.layers.MapControlsLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.widgets.RulerWidget;

View file

@ -82,8 +82,8 @@ import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.srtmplugin.ContourLinesMenu;
import net.osmand.plus.srtmplugin.SRTMPlugin;
import net.osmand.plus.srtmplugin.TerrainFragment;
import net.osmand.plus.views.DownloadedRegionsLayer;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.layers.DownloadedRegionsLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.MapWidgetRegistry;
import net.osmand.plus.wikipedia.WikipediaPoiMenu;

View file

@ -15,7 +15,7 @@ import net.osmand.plus.activities.ContributionVersionActivity;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.dashboard.tools.DashFragmentData;
import net.osmand.plus.settings.fragments.BaseSettingsFragment;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget;

View file

@ -35,7 +35,7 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.routing.RoutingHelper.RouteSegmentSearchResult;
import net.osmand.plus.views.ContextMenuLayer;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.router.RouteSegmentResult;
import net.osmand.router.RoutingConfiguration;
import net.osmand.util.Algorithms;

View file

@ -51,7 +51,7 @@ import net.osmand.plus.mapcontextmenu.AdditionalActionsBottomSheetDialogFragment
import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.views.ContextMenuLayer;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType;

View file

@ -77,11 +77,11 @@ import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.views.AnimateDraggingMapThread;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.TransportStopsLayer;
import net.osmand.plus.views.layers.TransportStopsLayer;
import net.osmand.plus.views.controls.HorizontalSwipeConfirm;
import net.osmand.plus.views.controls.SingleTapConfirm;
import net.osmand.plus.widgets.style.CustomTypefaceSpan;
import net.osmand.router.TransportRoutePlanner.TransportRouteResult;
import net.osmand.router.TransportRouteResult;
import net.osmand.util.Algorithms;
import java.util.ArrayList;

View file

@ -55,8 +55,8 @@ import net.osmand.plus.mapcontextmenu.builders.cards.NoImagesCard;
import net.osmand.plus.mapcontextmenu.controllers.TransportStopController;
import net.osmand.plus.render.RenderingIcons;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.views.POIMapLayer;
import net.osmand.plus.views.TransportStopsLayer;
import net.osmand.plus.views.layers.POIMapLayer;
import net.osmand.plus.views.layers.TransportStopsLayer;
import net.osmand.plus.widgets.TextViewEx;
import net.osmand.plus.widgets.tools.ClickableSpanTouchListener;
import net.osmand.util.Algorithms;

View file

@ -75,7 +75,7 @@ import net.osmand.plus.osmedit.OsmPoint;
import net.osmand.plus.parkingpoint.ParkingPositionMenuController;
import net.osmand.plus.resources.ResourceManager;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.views.DownloadedRegionsLayer.DownloadMapObject;
import net.osmand.plus.views.layers.DownloadedRegionsLayer.DownloadMapObject;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType;
import net.osmand.util.Algorithms;

View file

@ -38,7 +38,7 @@ import net.osmand.plus.osmedit.OsmEditingPlugin;
import net.osmand.plus.poi.PoiUIFilter;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.OsmandSettings.MetricsConstants;
import net.osmand.plus.views.POIMapLayer;
import net.osmand.plus.views.layers.POIMapLayer;
import net.osmand.plus.widgets.TextViewEx;
import net.osmand.plus.widgets.tools.ClickableSpanTouchListener;
import net.osmand.plus.wikipedia.WikiArticleHelper;

View file

@ -23,7 +23,7 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.mapcontextmenu.MenuBuilder;
import net.osmand.plus.mapcontextmenu.CollapsableView;
import net.osmand.plus.views.POIMapLayer;
import net.osmand.plus.views.layers.POIMapLayer;
import net.osmand.plus.widgets.TextViewEx;
import net.osmand.util.Algorithms;

View file

@ -32,8 +32,8 @@ import net.osmand.plus.liveupdates.LiveUpdatesHelper;
import net.osmand.plus.mapcontextmenu.MenuController;
import net.osmand.plus.mapcontextmenu.builders.MapDataMenuBuilder;
import net.osmand.plus.srtmplugin.SRTMPlugin;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.DownloadedRegionsLayer.DownloadMapObject;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.layers.DownloadedRegionsLayer.DownloadMapObject;
import net.osmand.util.Algorithms;
import java.io.File;

View file

@ -17,7 +17,7 @@ import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.mapcontextmenu.MenuBuilder;
import net.osmand.plus.mapcontextmenu.MenuController;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.views.TransportStopsLayer;
import net.osmand.plus.views.layers.TransportStopsLayer;
import java.util.List;

View file

@ -11,8 +11,8 @@ import net.osmand.plus.mapcontextmenu.BaseMenuController;
import net.osmand.plus.mapcontextmenu.MenuController;
import net.osmand.plus.mapcontextmenu.MenuController.MenuType;
import net.osmand.plus.mapcontextmenu.MenuTitleController;
import net.osmand.plus.views.ContextMenuLayer;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import java.util.Collections;
import java.util.Comparator;

View file

@ -44,7 +44,7 @@ import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.views.GPXLayer;
import net.osmand.plus.views.layers.GPXLayer;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
import net.osmand.util.MapUtils;

View file

@ -31,7 +31,7 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.MapActivityLayers;
import net.osmand.plus.base.BottomSheetDialogFragment;
import net.osmand.plus.dashboard.DashboardOnMap;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.MapTileLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.MapWidgetRegistry.MapWidgetRegInfo;

View file

@ -26,7 +26,7 @@ import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin;
import net.osmand.plus.resources.ResourceManager;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.MapTileLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.util.MapUtils;

View file

@ -57,7 +57,7 @@ import net.osmand.plus.measurementtool.SnapToRoadBottomSheetDialogFragment.SnapT
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.views.MapMarkersLayer;
import net.osmand.plus.views.layers.MapMarkersLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;

View file

@ -76,9 +76,8 @@ import net.osmand.plus.measurementtool.command.RemovePointCommand;
import net.osmand.plus.measurementtool.command.ReorderPointCommand;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.views.MapControlsLayer;
import net.osmand.plus.views.layers.MapControlsLayer;
import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarView;

View file

@ -16,10 +16,11 @@ import net.osmand.data.RotatedTileBox;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.R;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.views.ContextMenuLayer;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.Renderable;
import net.osmand.plus.views.layers.geometry.GeometryWay;
import net.osmand.util.MapUtils;
import java.util.ArrayList;
@ -246,7 +247,7 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
ty.add(locY);
}
calculatePath(tb, tx, ty, path);
GeometryWay.calculatePath(tb, tx, ty, path);
canvas.drawPath(path, lineAttrs.paint);
}

View file

@ -43,7 +43,7 @@ import net.osmand.plus.dashboard.tools.DashFragmentData;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.fragments.BaseSettingsFragment;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget;

View file

@ -30,7 +30,7 @@ import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.osmedit.OsmBugsUtil.OsmBugResult;
import net.osmand.plus.osmedit.OsmPoint.Action;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.util.Algorithms;

View file

@ -19,7 +19,7 @@ import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.PointImageDrawable;
import net.osmand.plus.render.RenderingIcons;
import net.osmand.plus.views.ContextMenuLayer;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;

View file

@ -37,7 +37,7 @@ import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.OsmandSettings.CommonPreference;
import net.osmand.plus.views.AnimateDraggingMapThread;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget;

View file

@ -40,7 +40,7 @@ import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.views.MapQuickActionLayer;
import net.osmand.plus.views.layers.MapQuickActionLayer;
import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback;
import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback.OnItemMoveCallback;
import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback.UnmovableItem;

View file

@ -56,9 +56,9 @@ import net.osmand.plus.routing.RouteDirectionInfo;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.routing.TransportRoutingHelper;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.views.MapControlsLayer;
import net.osmand.plus.views.layers.MapControlsLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.router.TransportRoutePlanner.TransportRouteResult;
import net.osmand.router.TransportRouteResult;
import net.osmand.util.Algorithms;
import java.io.File;

View file

@ -103,7 +103,7 @@ import net.osmand.plus.search.QuickSearchHelper;
import net.osmand.plus.widgets.TextViewExProgress;
import net.osmand.router.GeneralRouter;
import net.osmand.router.GeneralRouter.RoutingParameter;
import net.osmand.router.TransportRoutePlanner.TransportRouteResult;
import net.osmand.router.TransportRouteResult;
import net.osmand.search.SearchUICore.SearchResultCollection;
import net.osmand.search.core.SearchResult;
import net.osmand.util.MapUtils;

View file

@ -29,7 +29,7 @@ import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.routing.TransportRoutingHelper;
import net.osmand.plus.widgets.TextViewExProgress;
import net.osmand.router.TransportRoutePlanner.TransportRouteResult;
import net.osmand.router.TransportRouteResult;
import net.osmand.util.MapUtils;
import java.util.List;

View file

@ -90,7 +90,7 @@ import net.osmand.render.RenderingRulesStorage;
import net.osmand.router.RouteSegmentResult;
import net.osmand.router.RouteStatisticsHelper;
import net.osmand.router.RouteStatisticsHelper.RouteStatistics;
import net.osmand.router.TransportRoutePlanner.TransportRouteResult;
import net.osmand.router.TransportRouteResult;
import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment;
import net.osmand.util.Algorithms;

View file

@ -30,7 +30,7 @@ import net.osmand.plus.routing.TransportRoutingHelper;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.widgets.FlowLayout;
import net.osmand.plus.widgets.style.CustomTypefaceSpan;
import net.osmand.router.TransportRoutePlanner.TransportRouteResult;
import net.osmand.router.TransportRouteResult;
import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment;
import net.osmand.util.Algorithms;

View file

@ -27,9 +27,9 @@ import net.osmand.router.GeneralRouter;
import net.osmand.router.RouteCalculationProgress;
import net.osmand.router.RoutingConfiguration;
import net.osmand.router.TransportRoutePlanner;
import net.osmand.router.TransportRoutePlanner.TransportRouteResult;
import net.osmand.router.TransportRouteResult;
import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment;
import net.osmand.router.TransportRoutePlanner.TransportRoutingContext;
import net.osmand.router.TransportRoutingContext;
import net.osmand.router.TransportRoutingConfiguration;
import net.osmand.router.NativeTransportRoutingResult;
import net.osmand.util.MapUtils;

View file

@ -19,6 +19,7 @@ import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.mapcontextmenu.editors.RtePtEditor;
import net.osmand.plus.mapcontextmenu.editors.WptPtEditor;
import net.osmand.plus.mapcontextmenu.editors.WptPtEditor.OnDismissListener;
import net.osmand.plus.views.layers.ContextMenuLayer;
public class AddGpxPointBottomSheetHelper implements OnDismissListener {
private final View view;

View file

@ -12,6 +12,7 @@ import net.osmand.data.PointDescription;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.views.layers.ContextMenuLayer;
public class MoveMarkerBottomSheetHelper {
private final View mView;

View file

@ -1,12 +1,9 @@
package net.osmand.plus.views;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Join;
@ -16,15 +13,12 @@ import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.util.Pair;
import android.view.MotionEvent;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.core.graphics.ColorUtils;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.data.Amenity;
@ -36,18 +30,15 @@ import net.osmand.osm.PoiCategory;
import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.render.OsmandRenderer;
import net.osmand.plus.render.OsmandRenderer.RenderingContext;
import net.osmand.render.RenderingRuleSearchRequest;
import net.osmand.render.RenderingRulesStorage;
import net.osmand.util.MapAlgorithms;
import net.osmand.util.MapUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -144,485 +135,14 @@ public abstract class OsmandMapLayer {
}
}
protected boolean isIn(float x, float y, int lx, int ty, int rx, int by) {
return x >= lx && x <= rx && y >= ty && y <= by;
}
public int calculatePath(RotatedTileBox tb, List<Float> xs, List<Float> ys, Path path) {
List<Pair<Path, GeometryWayStyle>> paths = new ArrayList<>();
int res = calculatePath(tb, xs, ys, null, paths);
if (paths.size() > 0) {
path.addPath(paths.get(0).first);
}
return res;
}
public static class GeometryWayContext {
private Context ctx;
private float density;
private boolean nightMode;
private Paint paintIcon;
private Paint paintIconCustom;
private RenderingLineAttributes attrs;
private RenderingLineAttributes attrsPT;
private RenderingLineAttributes attrsW;
private Bitmap arrowBitmap;
private Bitmap walkArrowBitmap;
private Bitmap anchorBitmap;
private Map<Pair<Integer, Drawable>, Bitmap> stopBitmapsCache = new HashMap<>();
private Map<Integer, Bitmap> stopSmallBitmapsCache = new HashMap<>();
public GeometryWayContext(Context ctx, float density) {
this.ctx = ctx;
this.density = density;
paintIcon = new Paint();
paintIcon.setFilterBitmap(true);
paintIcon.setAntiAlias(true);
paintIcon.setColor(Color.BLACK);
paintIcon.setStrokeWidth(1f * density);
paintIconCustom = new Paint();
paintIconCustom.setFilterBitmap(true);
paintIconCustom.setAntiAlias(true);
paintIconCustom.setColor(Color.BLACK);
paintIconCustom.setStrokeWidth(1f * density);
attrsW = new RenderingLineAttributes("walkingRouteLine");
attrsW.defaultWidth = (int) (12 * density);
attrsW.defaultWidth3 = (int) (7 * density);
attrsW.defaultColor = ctx.getResources().getColor(R.color.nav_track_walk_fill);
attrsW.paint3.setStrokeCap(Cap.BUTT);
attrsW.paint3.setColor(Color.WHITE);
attrsW.paint2.setStrokeCap(Cap.BUTT);
attrsW.paint2.setColor(Color.BLACK);
arrowBitmap = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.map_route_direction_arrow, null);
}
public OsmandApplication getApp() {
return (OsmandApplication) ctx.getApplicationContext();
}
public float getDensity() {
return density;
}
public boolean isNightMode() {
return nightMode;
}
private int calculateHash(Object... o) {
return Arrays.hashCode(o);
}
public void updatePaints(boolean nightMode,
@NonNull RenderingLineAttributes attrs,
@NonNull RenderingLineAttributes attrsPT,
@NonNull RenderingLineAttributes attrsW) {
this.attrs = attrs;
this.attrsPT = attrsPT;
this.attrsW = attrsW;
paintIcon.setColorFilter(new PorterDuffColorFilter(attrs.paint2.getColor(), Mode.MULTIPLY));
this.nightMode = nightMode;
recreateBitmaps();
}
private boolean hasAttrs() {
return attrs != null && attrsPT != null && attrsW != null;
}
public int getStrokeColor(int sourceColor) {
return ColorUtils.blendARGB(sourceColor, Color.BLACK, 0.6f);
}
private void recreateBitmaps() {
if (hasAttrs()) {
float walkCircleH = attrsW.paint.getStrokeWidth() * 1.33f;
float walkCircleW = attrsW.paint.getStrokeWidth();
float walkCircleRadius = attrsW.paint.getStrokeWidth() / 2f;
float routeShieldRadius = attrsPT.paint3.getStrokeWidth() / 2f;
// create anchor bitmap
float margin = 2f * density;
float width = routeShieldRadius * 2 + margin * 2;
float height = routeShieldRadius * 2 + margin * 2;
Bitmap bitmap = Bitmap.createBitmap((int)width, (int)height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1f * density);
float x = width / 2;
float y = height / 2;
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(x, y, routeShieldRadius, paint);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(x, y, routeShieldRadius, paint);
anchorBitmap = bitmap;
// create walk arrow bitmap
width = walkCircleW + margin * 2;
height = walkCircleH + margin * 2;
bitmap = Bitmap.createBitmap((int)width, (int)height, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1f * density);
RectF rect = new RectF(margin, margin, width - margin, height - margin);
paint.setColor(attrsW.paint.getColor());
paint.setStyle(Paint.Style.FILL);
canvas.drawRoundRect(rect, walkCircleRadius, walkCircleRadius, paint);
paint.setColor(getStrokeColor(paint.getColor()));
paint.setStyle(Paint.Style.STROKE);
canvas.drawRoundRect(rect, walkCircleRadius, walkCircleRadius, paint);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(1f * density);
paint.setAlpha(200);
canvas.drawBitmap(arrowBitmap, width / 2 - arrowBitmap.getWidth() / 2f, height / 2 - arrowBitmap.getHeight() / 2f, paint);
walkArrowBitmap = bitmap;
stopBitmapsCache = new HashMap<>();
}
}
public Paint getPaintIcon() {
return paintIcon;
}
public Paint getPaintIconCustom() {
return paintIconCustom;
}
public Bitmap getArrowBitmap() {
return arrowBitmap;
}
public Bitmap getWalkArrowBitmap() {
return walkArrowBitmap;
}
public Bitmap getAnchorBitmap() {
return anchorBitmap;
}
public Bitmap getStopShieldBitmap(int color, Drawable stopDrawable) {
Bitmap bmp = stopBitmapsCache.get(new Pair<>(color, stopDrawable));
if (bmp == null) {
int fillColor = UiUtilities.getContrastColor(getApp(), color, true);
int strokeColor = getStrokeColor(color);
float routeShieldRadius = attrsPT.paint3.getStrokeWidth() / 2;
float routeShieldCornerRadius = 3 * density;
float margin = 2f * density;
float width = routeShieldRadius * 2 + margin * 2;
float height = routeShieldRadius * 2 + margin * 2;
bmp = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1f * density);
RectF rect = new RectF(margin, margin, width - margin, height - margin);
paint.setColor(fillColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawRoundRect(rect, routeShieldCornerRadius, routeShieldCornerRadius, paint);
paint.setColor(strokeColor);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRoundRect(rect, routeShieldCornerRadius, routeShieldCornerRadius, paint);
if (stopDrawable != null) {
stopDrawable.setColorFilter(new PorterDuffColorFilter(strokeColor, Mode.SRC_IN));
float marginBitmap = 1f * density;
rect.inset(marginBitmap, marginBitmap);
stopDrawable.setBounds(0, 0, (int) rect.width(), (int) rect.height());
canvas.translate(rect.left, rect.top);
stopDrawable.draw(canvas);
}
stopBitmapsCache.put(new Pair<>(color, stopDrawable), bmp);
}
return bmp;
}
public Bitmap getStopSmallShieldBitmap(int color) {
Bitmap bmp = stopSmallBitmapsCache.get(color);
if (bmp == null) {
int fillColor = UiUtilities.getContrastColor(getApp(), color, true);
int strokeColor = getStrokeColor(color);
float routeShieldRadius = attrsPT.paint3.getStrokeWidth() / 4;
float margin = 3f * density;
float width = routeShieldRadius * 2 + margin * 2;
float height = routeShieldRadius * 2 + margin * 2;
bmp = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1f * density);
paint.setColor(fillColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(width / 2, height / 2, routeShieldRadius, paint);
paint.setColor(strokeColor);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(width / 2, height / 2, routeShieldRadius, paint);
stopSmallBitmapsCache.put(color, bmp);
}
return bmp;
}
}
public abstract static class GeometryWayStyle {
private GeometryWayContext context;
protected Integer color;
public GeometryWayStyle(GeometryWayContext context) {
this.context = context;
}
public GeometryWayStyle(GeometryWayContext context, Integer color) {
this.context = context;
this.color = color;
}
public GeometryWayContext getContext() {
return context;
}
public Context getCtx() {
return context.ctx;
}
public Integer getColor() {
return color;
}
public Integer getStrokeColor() {
return context.getStrokeColor(color);
}
public Integer getPointColor() {
return null;
}
public boolean isNightMode() {
return context.nightMode;
}
public boolean hasAnchors() {
return false;
}
public boolean hasPathLine() {
return true;
}
public boolean isTransportLine() {
return false;
}
public boolean isWalkLine() {
return false;
}
public abstract Bitmap getPointBitmap();
public boolean hasPaintedPointBitmap() {
return false;
}
@Override
public int hashCode() {
return (color != null ? color.hashCode() : 0) + (context.nightMode ? 1231 : 1237) + (hasAnchors() ? 12310 : 12370);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof GeometryWayStyle)) {
return false;
}
GeometryWayStyle o = (GeometryWayStyle) other;
if (color != null && o.color != null) {
return color.equals(o.color);
}
return color == null && o.color == null;
}
}
public static class PathPoint {
float x;
float y;
double angle;
GeometryWayStyle style;
private Matrix matrix = new Matrix();
PathPoint(float x, float y, double angle, GeometryWayStyle style) {
this.x = x;
this.y = y;
this.angle = angle;
this.style = style;
}
protected Matrix getMatrix() {
return matrix;
}
void draw(Canvas canvas, GeometryWayContext context) {
if (style != null && style.getPointBitmap() != null) {
Bitmap bitmap = style.getPointBitmap();
Integer pointColor = style.getPointColor();
float paintH2 = bitmap.getHeight() / 2f;
float paintW2 = bitmap.getWidth() / 2f;
matrix.reset();
matrix.postRotate((float) angle, paintW2, paintH2);
matrix.postTranslate(x - paintW2, y - paintH2);
if (pointColor != null) {
Paint paint = context.getPaintIconCustom();
paint.setColorFilter(new PorterDuffColorFilter(pointColor, Mode.SRC_IN));
canvas.drawBitmap(bitmap, matrix, paint);
} else {
if (style.hasPaintedPointBitmap()) {
Paint paint = context.getPaintIconCustom();
paint.setColorFilter(null);
canvas.drawBitmap(bitmap, matrix, paint);
} else {
canvas.drawBitmap(bitmap, matrix, context.getPaintIcon());
}
}
}
}
}
protected void addLocation(RotatedTileBox tb, double latitude, double longitude, GeometryWayStyle style,
List<Float> tx, List<Float> ty, List<Double> angles, List<Double> distances,
double dist, List<GeometryWayStyle> styles) {
float x = tb.getPixXFromLatLon(latitude, longitude);
float y = tb.getPixYFromLatLon(latitude, longitude);
float px = x;
float py = y;
int previous = tx.size() - 1;
if (previous >= 0) {
px = tx.get(previous);
py = ty.get(previous);
}
double angle = 0;
if (px != x || py != y) {
double angleRad = Math.atan2(y - py, x - px);
angle = (angleRad * 180 / Math.PI) + 90f;
}
double distSegment = Math.sqrt((y - py) * (y - py) + (x - px) * (x - px));
if (dist != 0) {
distSegment = dist;
}
tx.add(x);
ty.add(y);
angles.add(angle);
distances.add(distSegment);
styles.add(style);
}
public int calculatePath(RotatedTileBox tb, List<Float> xs, List<Float> ys, List<GeometryWayStyle> styles, List<Pair<Path, GeometryWayStyle>> paths) {
boolean segmentStarted = false;
float prevX = xs.get(0);
float prevY = ys.get(0);
int height = tb.getPixHeight();
int width = tb.getPixWidth();
int cnt = 0;
boolean hasStyles = styles != null && styles.size() == xs.size();
GeometryWayStyle style = hasStyles ? styles.get(0) : null;
Path path = new Path();
boolean prevIn = isIn(prevX, prevY, 0, 0, width, height);
for (int i = 1; i < xs.size(); i++) {
float currX = xs.get(i);
float currY = ys.get(i);
boolean currIn = isIn(currX, currY, 0, 0, width, height);
boolean draw = false;
if (prevIn && currIn) {
draw = true;
} else {
long intersection = MapAlgorithms.calculateIntersection((int)currX, (int)currY, (int)prevX, (int)prevY, 0, width, height, 0);
if (intersection != -1) {
if (prevIn && (i == 1)) {
cnt++;
path.moveTo(prevX, prevY);
segmentStarted = true;
}
prevX = (int) (intersection >> 32);
prevY = (int) (intersection & 0xffffffff);
draw = true;
}
if (i == xs.size() - 1 && !currIn) {
long inter = MapAlgorithms.calculateIntersection((int)prevX, (int)prevY, (int)currX, (int)currY, 0, width, height, 0);
if (inter != -1) {
currX = (int) (inter >> 32);
currY = (int) (inter & 0xffffffff);
}
}
}
if (draw) {
if (!segmentStarted) {
cnt++;
path.moveTo(prevX, prevY);
segmentStarted = true;
}
path.lineTo(currX, currY);
} else {
segmentStarted = false;
}
prevIn = currIn;
prevX = currX;
prevY = currY;
if (hasStyles) {
GeometryWayStyle newStyle = styles.get(i);
if (!style.equals(newStyle)) {
paths.add(new Pair<>(path, style));
path = new Path();
if (segmentStarted) {
path.moveTo(currX, currY);
}
style = newStyle;
}
}
}
if (!path.isEmpty()) {
paths.add(new Pair<>(path, style));
}
return cnt;
}
@NonNull
public QuadTree<QuadRect> initBoundIntersections(RotatedTileBox tileBox) {
public static QuadTree<QuadRect> initBoundIntersections(RotatedTileBox tileBox) {
QuadRect bounds = new QuadRect(0, 0, tileBox.getPixWidth(), tileBox.getPixHeight());
bounds.inset(-bounds.width() / 4, -bounds.height() / 4);
return new QuadTree<>(bounds, 4, 0.6f);
}
public boolean intersects(QuadTree<QuadRect> boundIntersections, float x, float y, float width, float height) {
public static boolean intersects(QuadTree<QuadRect> boundIntersections, float x, float y, float width, float height) {
List<QuadRect> result = new ArrayList<>();
QuadRect visibleRect = calculateRect(x, y, width, height);
boundIntersections.queryInBox(new QuadRect(visibleRect.left, visibleRect.top, visibleRect.right, visibleRect.bottom), result);
@ -650,7 +170,7 @@ public abstract class OsmandMapLayer {
return new QuadRect(leftLongitude - lon, topLatitude + lat, rightLongitude + lon, bottomLatitude - lat);
}
public QuadRect calculateRect(float x, float y, float width, float height) {
public static QuadRect calculateRect(float x, float y, float width, float height) {
QuadRect rf;
double left = x - width / 2.0d;
double top = y - height / 2.0d;
@ -852,7 +372,7 @@ public abstract class OsmandMapLayer {
}
protected static class RenderingLineAttributes {
public static class RenderingLineAttributes {
protected int cachedHash;
public Paint paint;
public Paint customColorPaint;

View file

@ -49,6 +49,7 @@ import net.osmand.plus.helpers.TwoFingerTapDetector;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.views.MultiTouchSupport.MultiTouchZoomListener;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.render.RenderingRuleSearchRequest;
import net.osmand.render.RenderingRuleStorageProperties;
import net.osmand.render.RenderingRulesStorage;

View file

@ -2,6 +2,7 @@ package net.osmand.plus.views;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import androidx.annotation.NonNull;
@ -9,6 +10,9 @@ import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.views.layers.geometry.GeometryWay;
import net.osmand.plus.views.layers.geometry.GpxGeometryWay;
import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.List;
@ -62,6 +66,8 @@ public class Renderable {
protected AsynchronousResampler culler = null; // The currently active resampler
protected Paint paint = null; // MUST be set by 'updateLocalPaint' before use
protected GpxGeometryWay geometryWay;
public RenderableSegment(List<WptPt> points, double segmentSize) {
this.points = points;
this.segmentSize = segmentSize;
@ -73,12 +79,20 @@ public class Renderable {
paint = new Paint(p);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStyle(Paint.Style.FILL);
paint.setStyle(Paint.Style.STROKE);
}
paint.setColor(p.getColor());
paint.setStrokeWidth(p.getStrokeWidth());
}
public GpxGeometryWay getGeometryWay() {
return geometryWay;
}
public void setGeometryWay(GpxGeometryWay geometryWay) {
this.geometryWay = geometryWay;
}
protected abstract void startCuller(double newZoom);
protected void drawSingleSegment(double zoom, Paint p, Canvas canvas, RotatedTileBox tileBox) {}
@ -95,45 +109,54 @@ public class Renderable {
culled = cull;
}
public List<WptPt> getPointsForDrawing() {
return culled.isEmpty() ? points : culled;
}
public void drawGeometry(Canvas canvas, RotatedTileBox tileBox, QuadRect quadRect, int arrowColor, int trackColor, float trackWidth) {
if (geometryWay != null) {
List<WptPt> points = getPointsForDrawing();
if (!Algorithms.isEmpty(points)) {
geometryWay.setTrackStyleParams(arrowColor, trackColor, trackWidth);
geometryWay.updatePoints(tileBox, points);
geometryWay.drawSegments(tileBox, canvas, quadRect.top, quadRect.left, quadRect.bottom, quadRect.right, null, 0);
}
}
}
protected void draw(List<WptPt> pts, Paint p, Canvas canvas, RotatedTileBox tileBox) {
if (pts.size() > 1) {
updateLocalPaint(p);
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
QuadRect tileBounds = tileBox.getLatLonBounds();
WptPt lastPt = pts.get(0);
float lastx = 0;
float lasty = 0;
boolean reCalculateLastXY = true;
int size = pts.size();
for (int i = 1; i < size; i++) {
boolean recalculateLastXY = true;
Path path = new Path();
for (int i = 1; i < pts.size(); i++) {
WptPt pt = pts.get(i);
if (Math.min(pt.lon, lastPt.lon) < tileBounds.right && Math.max(pt.lon, lastPt.lon) > tileBounds.left
&& Math.min(pt.lat, lastPt.lat) < tileBounds.top && Math.max(pt.lat, lastPt.lat) > tileBounds.bottom) {
if (reCalculateLastXY) {
lastx = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
lasty = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
reCalculateLastXY = false;
if (recalculateLastXY) {
recalculateLastXY = false;
float lastX = tileBox.getPixXFromLatLon(lastPt.lat, lastPt.lon);
float lastY = tileBox.getPixYFromLatLon(lastPt.lat, lastPt.lon);
if (!path.isEmpty()) {
canvas.drawPath(path, paint);
}
path.reset();
path.moveTo(lastX, lastY);
}
float x = tileBox.getPixXFromLatLon(pt.lat, pt.lon);
float y = tileBox.getPixYFromLatLon(pt.lat, pt.lon);
canvas.drawLine(lastx, lasty, x, y, paint);
lastx = x;
lasty = y;
path.lineTo(x, y);
} else {
reCalculateLastXY = true;
recalculateLastXY = true;
}
lastPt = pt;
}
if (!path.isEmpty()) {
canvas.drawPath(path, paint);
}
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.content.res.Resources;
import android.graphics.Bitmap;
@ -28,7 +28,9 @@ import net.osmand.plus.settings.backend.OsmandSettings.CommonPreference;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.widgets.tools.CropCircleTransformation;
import java.io.IOException;

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.Manifest;
import android.content.Context;
@ -66,7 +66,11 @@ import net.osmand.plus.render.MapRenderRepositories;
import net.osmand.plus.render.NativeOsmandLibrary;
import net.osmand.plus.routepreparationmenu.ChooseRouteFragment;
import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu;
import net.osmand.plus.views.AddGpxPointBottomSheetHelper;
import net.osmand.plus.views.AddGpxPointBottomSheetHelper.NewGpxPoint;
import net.osmand.plus.views.MoveMarkerBottomSheetHelper;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.corenative.NativeCoreContext;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.content.Context;
import android.graphics.Canvas;
@ -34,8 +34,11 @@ import net.osmand.plus.download.ui.DownloadMapToolbarController;
import net.osmand.plus.mapcontextmenu.MapContextMenu;
import net.osmand.plus.mapcontextmenu.other.MapMultiSelectionMenu;
import net.osmand.plus.resources.ResourceManager;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProviderSelection;
import net.osmand.plus.views.MapTileLayer;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProviderSelection;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType;
import net.osmand.util.Algorithms;
@ -200,12 +203,12 @@ public class DownloadedRegionsLayer extends OsmandMapLayer implements IContextMe
return;
}
//make sure no maps are loaded for the location
checkMapToDownload(tileBox, data.results);
checkMapToDownload(tileBox, data.getResults());
// draw objects
if (osmandRegions.isInitialized() && zoom >= ZOOM_TO_SHOW_SELECTION_ST && zoom < ZOOM_TO_SHOW_SELECTION) {
final List<BinaryMapDataObject> currentObjects = new LinkedList<>();
if (data.results != null) {
currentObjects.addAll(data.results);
if (data.getResults() != null) {
currentObjects.addAll(data.getResults());
}
final List<BinaryMapDataObject> selectedObjects = new LinkedList<>(this.selectedObjects);
@ -457,7 +460,7 @@ public class DownloadedRegionsLayer extends OsmandMapLayer implements IContextMe
StringBuilder filter = new StringBuilder();
int zoom = view.getZoom();
RotatedTileBox queriedBox = data.getQueriedBox();
final List<BinaryMapDataObject> currentObjects = data.results;
final List<BinaryMapDataObject> currentObjects = data.getResults();
if (osmandRegions.isInitialized() && queriedBox != null) {
if(zoom >= ZOOM_TO_SHOW_MAP_NAMES && Math.abs(queriedBox.getZoom() - zoom) <= ZOOM_THRESHOLD &&
currentObjects != null){
@ -578,12 +581,12 @@ public class DownloadedRegionsLayer extends OsmandMapLayer implements IContextMe
private void getWorldRegionFromPoint(RotatedTileBox tb, PointF point, List<? super DownloadMapObject> dataObjects) {
int zoom = tb.getZoom();
if (zoom >= ZOOM_TO_SHOW_SELECTION_ST && zoom < ZOOM_TO_SHOW_SELECTION
&& data.results != null && osmandRegions.isInitialized()) {
&& data.getResults() != null && osmandRegions.isInitialized()) {
LatLon pointLatLon = tb.getLatLonFromPixel(point.x, point.y);
int point31x = MapUtils.get31TileNumberX(pointLatLon.getLongitude());
int point31y = MapUtils.get31TileNumberY(pointLatLon.getLatitude());
List<BinaryMapDataObject> result = new LinkedList<>(data.results);
List<BinaryMapDataObject> result = new LinkedList<>(data.getResults());
Iterator<BinaryMapDataObject> it = result.iterator();
while (it.hasNext()) {
BinaryMapDataObject o = it.next();

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.graphics.Canvas;
import android.graphics.PointF;
@ -22,8 +22,10 @@ import net.osmand.plus.MapMarkersHelper.MapMarker;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.base.PointImageDrawable;
import net.osmand.plus.views.ContextMenuLayer.ApplyMovedObjectCallback;
import net.osmand.plus.views.MapTextLayer.MapTextProvider;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.layers.ContextMenuLayer.ApplyMovedObjectCallback;
import net.osmand.plus.views.layers.MapTextLayer.MapTextProvider;
import java.util.ArrayList;
import java.util.List;

View file

@ -1,11 +1,9 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
@ -50,13 +48,17 @@ import net.osmand.plus.mapcontextmenu.controllers.SelectedGpxMenuController.Sele
import net.osmand.plus.mapcontextmenu.other.TrackChartPoints;
import net.osmand.plus.render.OsmandRenderer;
import net.osmand.plus.render.OsmandRenderer.RenderingContext;
import net.osmand.plus.render.RenderingIcons;
import net.osmand.plus.settings.backend.OsmandSettings.CommonPreference;
import net.osmand.plus.track.SaveGpxAsyncTask;
import net.osmand.plus.track.TrackDrawInfo;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.ContextMenuLayer.IMoveObjectProvider;
import net.osmand.plus.views.MapTextLayer.MapTextProvider;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.Renderable;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.layers.ContextMenuLayer.IMoveObjectProvider;
import net.osmand.plus.views.layers.MapTextLayer.MapTextProvider;
import net.osmand.plus.views.layers.geometry.GpxGeometryWay;
import net.osmand.plus.views.layers.geometry.GpxGeometryWayContext;
import net.osmand.render.RenderingRuleProperty;
import net.osmand.render.RenderingRuleSearchRequest;
import net.osmand.render.RenderingRulesStorage;
@ -79,8 +81,6 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
private static final Log log = PlatformUtil.getLog(GPXLayer.class);
private static final double TOUCH_RADIUS_MULTIPLIER = 1.5;
private static final double DIRECTION_ARROW_DISTANCE_MULTIPLIER = 10.0;
private static final float DIRECTION_ARROW_CIRCLE_MULTIPLIER = 1.5f;
private static final int DEFAULT_WIDTH_MULTIPLIER = 7;
private static final int START_ZOOM = 7;
@ -117,8 +117,8 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
private Paint paintTextIcon;
private Bitmap arrowBitmap;
private GeometryWayContext wayContext;
private GpxGeometryWayContext wayContext;
private GpxGeometryWay wayGeometry;
private OsmandRenderer osmandRenderer;
@ -214,12 +214,8 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
defPointColor = ContextCompat.getColor(view.getApplication(), R.color.gpx_color_point);
grayColor = ContextCompat.getColor(view.getApplication(), R.color.color_favorite_gray);
wayContext = new GeometryWayContext(view.getContext(), view.getDensity());
Paint paint = wayContext.getPaintIcon();
paint.setStrokeCap(Cap.ROUND);
arrowBitmap = RenderingIcons.getBitmapFromVectorDrawable(view.getContext(), R.drawable.mm_special_arrow_up);
wayContext = new GpxGeometryWayContext(view.getContext(), view.getDensity());
wayGeometry = new GpxGeometryWay(wayContext);
}
@Override
@ -423,33 +419,12 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
QuadRect correctedQuadRect = getCorrectedQuadRect(tileBox.getLatLonBounds());
String width = getTrackWidthName(selectedGpxFile.getGpxFile(), "");
float trackWidth = getTrackWidth(width, defaultTrackWidth);
int color = getTrackColor(selectedGpxFile.getGpxFile(), cachedColor);
int contrastColor = UiUtilities.getContrastColor(view.getApplication(), color, false);
GeometryWayStyle arrowsWayStyle = new GeometryArrowsWayStyle(wayContext, arrowBitmap, contrastColor, color, trackWidth);
int trackColor = getTrackColor(selectedGpxFile.getGpxFile(), cachedColor);
int arrowColor = UiUtilities.getContrastColor(view.getApplication(), trackColor, false);
for (TrkSegment segment : selectedGpxFile.getPointsToDisplay()) {
List<Float> tx = new ArrayList<>();
List<Float> ty = new ArrayList<>();
List<Double> distances = new ArrayList<>();
List<Double> angles = new ArrayList<>();
List<GeometryWayStyle> styles = new ArrayList<>();
boolean previousVisible = false;
List<WptPt> points = segment.points;
if (points.size() > 1) {
for (int i = 0; i < points.size(); i++) {
WptPt pt = points.get(i);
if (correctedQuadRect.left <= pt.getLongitude()
&& pt.getLongitude() <= correctedQuadRect.right
&& correctedQuadRect.bottom <= pt.getLatitude()
&& pt.getLatitude() <= correctedQuadRect.top) {
addLocation(tileBox, pt.getLatitude(), pt.getLongitude(), null, tx, ty, angles, distances, 0, styles);
previousVisible = true;
} else if (previousVisible) {
addLocation(tileBox, pt.getLatitude(), pt.getLongitude(), null, tx, ty, angles, distances, 0, styles);
previousVisible = false;
}
}
drawArrowsOverPath(tx, ty, angles, distances, canvas, tileBox, arrowsWayStyle);
if (segment.renderer instanceof Renderable.RenderableSegment) {
((Renderable.RenderableSegment) segment.renderer)
.drawGeometry(canvas, tileBox, correctedQuadRect, arrowColor, trackColor, trackWidth);
}
}
}
@ -457,122 +432,6 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
}
}
private void drawArrowsOverPath(List<Float> tx, List<Float> ty, List<Double> angles, List<Double> distances,
Canvas canvas, RotatedTileBox tb, GeometryWayStyle wayStyle) {
int pixHeight = tb.getPixHeight();
int pixWidth = tb.getPixWidth();
int left = -pixWidth / 4;
int right = pixWidth + pixWidth / 4;
int top = -pixHeight / 4;
int bottom = pixHeight + pixHeight / 4;
double zoomCoef = tb.getZoomAnimation() > 0 ? (Math.pow(2, tb.getZoomAnimation() + tb.getZoomFloatPart())) : 1f;
double pxStep = arrowBitmap.getHeight() * DIRECTION_ARROW_DISTANCE_MULTIPLIER * zoomCoef;
double dist = 0;
List<ArrowPathPoint> arrows = new ArrayList<>();
for (int i = tx.size() - 2; i >= 0; i--) {
float px = tx.get(i);
float py = ty.get(i);
float x = tx.get(i + 1);
float y = ty.get(i + 1);
double angle = angles.get(i + 1);
double distSegment = distances.get(i + 1);
if (distSegment == 0) {
continue;
}
if (dist >= pxStep) {
dist = 0;
}
double percent = 1 - (pxStep - dist) / distSegment;
dist += distSegment;
while (dist >= pxStep) {
double pdx = (x - px) * percent;
double pdy = (y - py) * percent;
float iconx = (float) (px + pdx);
float icony = (float) (py + pdy);
if (isIn(iconx, icony, left, top, right, bottom)) {
arrows.add(new ArrowPathPoint(iconx, icony, angle, wayStyle));
}
dist -= pxStep;
percent -= pxStep / distSegment;
}
}
for (int i = arrows.size() - 1; i >= 0; i--) {
PathPoint a = arrows.get(i);
a.draw(canvas, wayContext);
}
}
private static class ArrowPathPoint extends PathPoint {
ArrowPathPoint(float x, float y, double angle, GeometryWayStyle style) {
super(x, y, angle, style);
}
@Override
void draw(Canvas canvas, GeometryWayContext context) {
if (style instanceof GeometryArrowsWayStyle) {
GeometryArrowsWayStyle arrowsWayStyle = (GeometryArrowsWayStyle) style;
float arrowWidth = style.getPointBitmap().getWidth();
if (arrowWidth > arrowsWayStyle.getTrackWidth()) {
Paint paint = context.getPaintIcon();
paint.setColor(arrowsWayStyle.getTrackColor());
paint.setStrokeWidth(arrowWidth * DIRECTION_ARROW_CIRCLE_MULTIPLIER);
canvas.drawPoint(x, y, paint);
}
}
super.draw(canvas, context);
}
}
private static class GeometryArrowsWayStyle extends GeometryWayStyle {
private Bitmap arrowBitmap;
protected int pointColor;
protected int trackColor;
protected float trackWidth;
GeometryArrowsWayStyle(GeometryWayContext context, Bitmap arrowBitmap, int arrowColor, int trackColor, float trackWidth) {
super(context);
this.arrowBitmap = arrowBitmap;
this.pointColor = arrowColor;
this.trackColor = trackColor;
this.trackWidth = trackWidth;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!super.equals(other)) {
return false;
}
return other instanceof GeometryArrowsWayStyle;
}
@Override
public Bitmap getPointBitmap() {
return arrowBitmap;
}
@Override
public Integer getPointColor() {
return pointColor;
}
public int getTrackColor() {
return trackColor;
}
public float getTrackWidth() {
return trackWidth;
}
}
private void drawSelectedFilesStartEndPoints(Canvas canvas, RotatedTileBox tileBox, List<SelectedGpxFile> selectedGPXFiles) {
if (tileBox.getZoom() >= START_ZOOM) {
for (SelectedGpxFile selectedGpxFile : selectedGPXFiles) {
@ -781,11 +640,14 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM
String width = getTrackWidthName(selectedGpxFile.getGpxFile(), "");
int color = getTrackColor(selectedGpxFile.getGpxFile(), ts.getColor(cachedColor));
if (ts.renderer == null && !ts.points.isEmpty()) {
Renderable.RenderableSegment renderer;
if (currentTrack) {
ts.renderer = new Renderable.CurrentTrack(ts.points);
renderer = new Renderable.CurrentTrack(ts.points);
} else {
ts.renderer = new Renderable.StandardTrack(ts.points, 17.2);
renderer = new Renderable.StandardTrack(ts.points, 17.2);
}
ts.renderer = renderer;
renderer.setGeometryWay(new GpxGeometryWay(wayContext));
}
updatePaints(color, width, selectedGpxFile.isRoutePoints(), currentTrack, settings, tileBox);
if (ts.renderer instanceof Renderable.RenderableSegment) {

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -21,7 +21,9 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.AvoidSpecificRoads;
import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo;
import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidSpecificRoadsCallback;
import net.osmand.plus.views.ContextMenuLayer.ApplyMovedObjectCallback;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.layers.ContextMenuLayer.ApplyMovedObjectCallback;
import java.util.List;
import java.util.Map;

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.Manifest;
import android.animation.Animator;
@ -65,6 +65,8 @@ import net.osmand.plus.settings.backend.OsmAndAppCustomization;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.OsmandSettings.CommonPreference;
import net.osmand.plus.settings.backend.OsmandSettings.LayerTransparencySeekbarMode;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.corenative.NativeCoreContext;
import java.util.ArrayList;

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.graphics.Canvas;
@ -18,6 +18,8 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.mapcontextmenu.other.TrackChartPoints;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.mapwidgets.LanesControl;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopCoordinatesView;

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -41,9 +41,13 @@ import net.osmand.plus.R;
import net.osmand.plus.TargetPointsHelper.TargetPoint;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.MapViewTrackingUtilities;
import net.osmand.plus.views.ContextMenuLayer.ApplyMovedObjectCallback;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProviderSelection;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.Renderable;
import net.osmand.plus.views.layers.ContextMenuLayer.ApplyMovedObjectCallback;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProviderSelection;
import net.osmand.plus.views.layers.geometry.GeometryWay;
import net.osmand.plus.views.mapwidgets.MapMarkersWidgetsFactory;
import net.osmand.util.MapUtils;
@ -295,7 +299,7 @@ public class MapMarkersLayer extends OsmandMapLayer implements IContextMenuProvi
tx.add(markerX);
ty.add(markerY);
calculatePath(tileBox, tx, ty, linePath);
GeometryWay.calculatePath(tileBox, tx, ty, linePath);
PathMeasure pm = new PathMeasure(linePath, false);
float[] pos = new float[2];
pm.getPosTan(pm.getLength() / 2, pos, null);

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@ -41,12 +41,14 @@ import net.osmand.plus.quickaction.QuickActionRegistry;
import net.osmand.plus.quickaction.QuickActionsWidget;
import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import static net.osmand.plus.views.ContextMenuLayer.VIBRATE_SHORT;
import static net.osmand.plus.views.layers.ContextMenuLayer.VIBRATE_SHORT;
/**
* Created by okorsun on 23.12.16.

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.content.res.Resources;
import android.graphics.Canvas;
@ -19,6 +19,8 @@ import java.util.TreeMap;
import gnu.trove.set.hash.TIntHashSet;
import net.osmand.plus.R;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
public class MapTextLayer extends OsmandMapLayer {

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.app.Dialog;
import android.content.Context;
@ -38,7 +38,9 @@ import net.osmand.plus.poi.PoiUIFilter;
import net.osmand.plus.render.RenderingIcons;
import net.osmand.plus.routing.IRouteInformationListener;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.views.MapTextLayer.MapTextProvider;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.layers.MapTextLayer.MapTextProvider;
import net.osmand.util.Algorithms;
import java.util.ArrayList;

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -26,6 +26,8 @@ import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.base.MapViewTrackingUtilities;
import net.osmand.plus.profiles.ProfileIconColors;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import org.apache.commons.logging.Log;

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.content.res.Resources;
import android.graphics.Bitmap;
@ -20,7 +20,9 @@ import net.osmand.plus.R;
import net.osmand.plus.TargetPointsHelper;
import net.osmand.plus.TargetPointsHelper.TargetPoint;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
import java.util.List;

View file

@ -0,0 +1,642 @@
package net.osmand.plus.views.layers;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.LayerDrawable;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import net.osmand.AndroidUtils;
import net.osmand.Location;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox;
import net.osmand.data.TransportStop;
import net.osmand.plus.R;
import net.osmand.plus.mapcontextmenu.other.TrackChartPoints;
import net.osmand.plus.profiles.LocationIcon;
import net.osmand.plus.routing.RouteCalculationResult;
import net.osmand.plus.routing.RouteDirectionInfo;
import net.osmand.plus.routing.RouteProvider;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.routing.TransportRoutingHelper;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.layers.geometry.PublicTransportGeometryWay;
import net.osmand.plus.views.layers.geometry.PublicTransportGeometryWayContext;
import net.osmand.plus.views.layers.geometry.RouteGeometryWay;
import net.osmand.plus.views.layers.geometry.RouteGeometryWayContext;
import net.osmand.router.TransportRouteResult;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.IContextMenuProvider {
private OsmandMapTileView view;
private final RoutingHelper helper;
private final TransportRoutingHelper transportHelper;
// keep array lists created
private List<Location> actionPoints = new ArrayList<Location>();
// cache
private Bitmap actionArrow;
private Paint paintIconAction;
private Paint paintGridOuterCircle;
private Paint paintGridCircle;
private LayerDrawable selectedPoint;
private TrackChartPoints trackChartPoints;
private RenderingLineAttributes attrs;
private RenderingLineAttributes attrsPT;
private RenderingLineAttributes attrsW;
private boolean nightMode;
private RouteGeometryWayContext routeWayContext;
private PublicTransportGeometryWayContext publicTransportWayContext;
private RouteGeometryWay routeGeometry;
private PublicTransportGeometryWay publicTransportRouteGeometry;
private LayerDrawable projectionIcon;
public RouteLayer(RoutingHelper helper) {
this.helper = helper;
this.transportHelper = helper.getTransportRoutingHelper();
}
public RoutingHelper getHelper() {
return helper;
}
public void setTrackChartPoints(TrackChartPoints trackChartPoints) {
this.trackChartPoints = trackChartPoints;
}
private void initUI() {
float density = view.getDensity();
actionArrow = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_action_arrow, null);
paintIconAction = new Paint();
paintIconAction.setFilterBitmap(true);
paintIconAction.setAntiAlias(true);
attrs = new RenderingLineAttributes("route");
attrs.defaultWidth = (int) (12 * density);
attrs.defaultWidth3 = (int) (7 * density);
attrs.defaultColor = view.getResources().getColor(R.color.nav_track);
attrs.paint3.setStrokeCap(Cap.BUTT);
attrs.paint3.setColor(Color.WHITE);
attrs.paint2.setStrokeCap(Cap.BUTT);
attrs.paint2.setColor(Color.BLACK);
attrsPT = new RenderingLineAttributes("publicTransportLine");
attrsPT.defaultWidth = (int) (12 * density);
attrsPT.defaultWidth3 = (int) (7 * density);
attrsPT.defaultColor = view.getResources().getColor(R.color.nav_track);
attrsPT.paint3.setStrokeCap(Cap.BUTT);
attrsPT.paint3.setColor(Color.WHITE);
attrsPT.paint2.setStrokeCap(Cap.BUTT);
attrsPT.paint2.setColor(Color.BLACK);
attrsW = new RenderingLineAttributes("walkingRouteLine");
attrsW.defaultWidth = (int) (12 * density);
attrsW.defaultWidth3 = (int) (7 * density);
attrsW.defaultColor = view.getResources().getColor(R.color.nav_track_walk_fill);
attrsW.paint3.setStrokeCap(Cap.BUTT);
attrsW.paint3.setColor(Color.WHITE);
attrsW.paint2.setStrokeCap(Cap.BUTT);
attrsW.paint2.setColor(Color.BLACK);
routeWayContext = new RouteGeometryWayContext(view.getContext(), density);
routeWayContext.updatePaints(nightMode, attrs);
routeGeometry = new RouteGeometryWay(routeWayContext);
publicTransportWayContext = new PublicTransportGeometryWayContext(view.getContext(), density);
publicTransportWayContext.updatePaints(nightMode, attrs, attrsPT, attrsW);
publicTransportRouteGeometry = new PublicTransportGeometryWay(publicTransportWayContext);
selectedPoint = (LayerDrawable) AppCompatResources.getDrawable(view.getContext(), R.drawable.map_location_default);
paintGridCircle = new Paint();
paintGridCircle.setStyle(Paint.Style.FILL_AND_STROKE);
paintGridCircle.setAntiAlias(true);
paintGridCircle.setColor(attrs.defaultColor);
paintGridCircle.setAlpha(255);
paintGridOuterCircle = new Paint();
paintGridOuterCircle.setStyle(Paint.Style.FILL_AND_STROKE);
paintGridOuterCircle.setAntiAlias(true);
paintGridOuterCircle.setColor(Color.WHITE);
paintGridOuterCircle.setAlpha(204);
}
@Override
public void initLayer(OsmandMapTileView view) {
this.view = view;
initUI();
}
@Override
public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {
if ((helper.isPublicTransportMode() && transportHelper.getRoutes() != null) ||
(helper.getFinalLocation() != null && helper.getRoute().isCalculated())) {
updateAttrs(settings, tileBox);
int w = tileBox.getPixWidth();
int h = tileBox.getPixHeight();
Location lastProjection = helper.getLastProjection();
final RotatedTileBox cp ;
if(lastProjection != null &&
tileBox.containsLatLon(lastProjection.getLatitude(), lastProjection.getLongitude())){
cp = tileBox.copy();
cp.increasePixelDimensions(w /2, h);
} else {
cp = tileBox;
}
final QuadRect latlonRect = cp.getLatLonBounds();
final QuadRect correctedQuadRect = getCorrectedQuadRect(latlonRect);
drawLocations(tileBox, canvas, correctedQuadRect.top, correctedQuadRect.left, correctedQuadRect.bottom, correctedQuadRect.right);
if (trackChartPoints != null) {
canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
drawXAxisPoints(canvas, tileBox);
LatLon highlightedPoint = trackChartPoints.getHighlightedPoint();
if (highlightedPoint != null
&& highlightedPoint.getLatitude() >= latlonRect.bottom
&& highlightedPoint.getLatitude() <= latlonRect.top
&& highlightedPoint.getLongitude() >= latlonRect.left
&& highlightedPoint.getLongitude() <= latlonRect.right) {
float x = tileBox.getPixXFromLatLon(highlightedPoint.getLatitude(), highlightedPoint.getLongitude());
float y = tileBox.getPixYFromLatLon(highlightedPoint.getLatitude(), highlightedPoint.getLongitude());
selectedPoint.setBounds((int) x - selectedPoint.getIntrinsicWidth() / 2,
(int) y - selectedPoint.getIntrinsicHeight() / 2,
(int) x + selectedPoint.getIntrinsicWidth() / 2,
(int) y + selectedPoint.getIntrinsicHeight() / 2);
selectedPoint.draw(canvas);
}
canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
}
}
}
private void updateAttrs(DrawSettings settings, RotatedTileBox tileBox) {
boolean updatePaints = attrs.updatePaints(view.getApplication(), settings, tileBox);
attrs.isPaint3 = false;
attrs.isPaint2 = false;
attrsPT.updatePaints(view.getApplication(), settings, tileBox);
attrsPT.isPaint3 = false;
attrsPT.isPaint2 = false;
attrsW.updatePaints(view.getApplication(), settings, tileBox);
attrsPT.isPaint3 = false;
attrsPT.isPaint2 = false;
nightMode = settings != null && settings.isNightMode();
if (updatePaints) {
paintIconAction.setColorFilter(new PorterDuffColorFilter(attrs.paint3.getColor(), Mode.MULTIPLY));
routeWayContext.updatePaints(nightMode, attrs);
publicTransportWayContext.updatePaints(nightMode, attrs, attrsPT, attrsW);
}
}
private void drawXAxisPoints(Canvas canvas, RotatedTileBox tileBox) {
QuadRect latLonBounds = tileBox.getLatLonBounds();
List<LatLon> xAxisPoints = trackChartPoints.getXAxisPoints();
if (xAxisPoints != null) {
float r = 3 * tileBox.getDensity();
float density = (float) Math.ceil(tileBox.getDensity());
float outerRadius = r + 2 * density;
float innerRadius = r + density;
QuadRect prevPointRect = null;
for (int i = 0; i < xAxisPoints.size(); i++) {
LatLon axisPoint = xAxisPoints.get(i);
if (axisPoint.getLatitude() >= latLonBounds.bottom
&& axisPoint.getLatitude() <= latLonBounds.top
&& axisPoint.getLongitude() >= latLonBounds.left
&& axisPoint.getLongitude() <= latLonBounds.right) {
float x = tileBox.getPixXFromLatLon(axisPoint.getLatitude(), axisPoint.getLongitude());
float y = tileBox.getPixYFromLatLon(axisPoint.getLatitude(), axisPoint.getLongitude());
QuadRect pointRect = new QuadRect(x - outerRadius, y - outerRadius, x + outerRadius, y + outerRadius);
if (prevPointRect == null || !QuadRect.intersects(prevPointRect, pointRect)) {
canvas.drawCircle(x, y, outerRadius, paintGridOuterCircle);
canvas.drawCircle(x, y, innerRadius, paintGridCircle);
prevPointRect = pointRect;
}
}
}
}
}
@Override
public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) {}
private void drawAction(RotatedTileBox tb, Canvas canvas, List<Location> actionPoints) {
if (actionPoints.size() > 0) {
canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
try {
Path pth = new Path();
Matrix matrix = new Matrix();
boolean first = true;
int x = 0, px = 0, py = 0, y = 0;
for (int i = 0; i < actionPoints.size(); i++) {
Location o = actionPoints.get(i);
if (o == null) {
first = true;
canvas.drawPath(pth, attrs.paint3);
double angleRad = Math.atan2(y - py, x - px);
double angle = (angleRad * 180 / Math.PI) + 90f;
double distSegment = Math.sqrt((y - py) * (y - py) + (x - px) * (x - px));
if (distSegment == 0) {
continue;
}
// int len = (int) (distSegment / pxStep);
float pdx = x - px;
float pdy = y - py;
float scale = attrs.paint3.getStrokeWidth() / ( actionArrow.getWidth() / 2.25f);
float scaledWidth = actionArrow.getWidth();
matrix.reset();
matrix.postTranslate(0, -actionArrow.getHeight() / 2f);
matrix.postRotate((float) angle, actionArrow.getWidth() / 2f, 0);
if (scale > 1.0f) {
matrix.postScale(scale, scale);
scaledWidth *= scale;
}
matrix.postTranslate(px + pdx - scaledWidth/ 2f, py + pdy);
canvas.drawBitmap(actionArrow, matrix, paintIconAction);
} else {
px = x;
py = y;
x = (int) tb.getPixXFromLatLon(o.getLatitude(), o.getLongitude());
y = (int) tb.getPixYFromLatLon(o.getLatitude(), o.getLongitude());
if (first) {
pth.reset();
pth.moveTo(x, y);
first = false;
} else {
pth.lineTo(x, y);
}
}
}
} finally {
canvas.rotate(tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
}
}
}
private void drawProjectionPoint(Canvas canvas, double[] projectionXY) {
if (projectionIcon == null) {
helper.getSettings().getApplicationMode().getLocationIcon();
projectionIcon = (LayerDrawable) AppCompatResources.getDrawable(view.getContext(), LocationIcon.DEFAULT.getIconId());
}
int locationX = (int) projectionXY[0];
int locationY = (int) projectionXY[1];
projectionIcon.setBounds(locationX - projectionIcon.getIntrinsicWidth() / 2,
locationY - projectionIcon.getIntrinsicHeight() / 2,
locationX + projectionIcon.getIntrinsicWidth() / 2,
locationY + projectionIcon.getIntrinsicHeight() / 2);
projectionIcon.draw(canvas);
}
@ColorInt
public int getRouteLineColor(boolean night) {
updateAttrs(new DrawSettings(night), view.getCurrentRotatedTileBox());
return attrs.paint.getColor();
}
public void drawLocations(RotatedTileBox tb, Canvas canvas, double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude) {
if (helper.isPublicTransportMode()) {
int currentRoute = transportHelper.getCurrentRoute();
List<TransportRouteResult> routes = transportHelper.getRoutes();
TransportRouteResult route = routes != null && routes.size() > currentRoute ? routes.get(currentRoute) : null;
routeGeometry.clearRoute();
publicTransportRouteGeometry.updateRoute(tb, route);
if (route != null) {
LatLon start = transportHelper.getStartLocation();
Location startLocation = new Location("transport");
startLocation.setLatitude(start.getLatitude());
startLocation.setLongitude(start.getLongitude());
publicTransportRouteGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude,
startLocation, 0);
}
} else {
RouteCalculationResult route = helper.getRoute();
boolean directTo = route.getRouteService() == RouteProvider.RouteService.DIRECT_TO;
boolean straight = route.getRouteService() == RouteProvider.RouteService.STRAIGHT;
publicTransportRouteGeometry.clearRoute();
routeGeometry.updateRoute(tb, route);
if (directTo) {
routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude,
null, 0);
} else if (straight) {
routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude,
helper.getLastFixedLocation(), route.getCurrentStraightAngleRoute());
} else {
routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude,
helper.getLastProjection(), route.getCurrentStraightAngleRoute());
}
List<RouteDirectionInfo> rd = helper.getRouteDirections();
Iterator<RouteDirectionInfo> it = rd.iterator();
if (!directTo && tb.getZoom() >= 14) {
List<Location> actionPoints = calculateActionPoints(topLatitude, leftLongitude, bottomLatitude, rightLongitude, helper.getLastProjection(),
helper.getRoute().getRouteLocations(), helper.getRoute().getCurrentRoute(), it, tb.getZoom());
drawAction(tb, canvas, actionPoints);
}
if (directTo) {
//add projection point on original route
double[] projectionOnRoute = calculateProjectionOnRoutePoint(
helper.getRoute().getImmutableAllLocations(), helper, tb);
if (projectionOnRoute != null) {
drawProjectionPoint(canvas, projectionOnRoute);
}
}
}
}
private double[] calculateProjectionOnRoutePoint(List<Location> routeNodes, RoutingHelper helper, RotatedTileBox box) {
double[] projectionXY = null;
Location ll = helper.getLastFixedLocation();
RouteCalculationResult route = helper.getRoute();
List<Location> locs = route.getImmutableAllLocations();
int cr = route.getCurrentRoute();
int locIndex = locs.size() - 1;
if(route.getIntermediatePointsToPass() > 0) {
locIndex = route.getIndexOfIntermediate(route.getIntermediatePointsToPass() - 1);
}
if(ll != null && cr > 0 && cr < locs.size() && locIndex >= 0 && locIndex < locs.size()) {
Location loc1 = locs.get(cr - 1);
Location loc2 = locs.get(cr);
double distLeft = route.getDistanceFromPoint(cr) - route.getDistanceFromPoint(locIndex);
double baDist = route.getDistanceFromPoint(cr - 1) - route.getDistanceFromPoint(cr);
Location target = locs.get(locIndex);
double dTarget = ll.distanceTo(target);
final int aX = box.getPixXFromLonNoRot(loc1.getLongitude());
final int aY = box.getPixYFromLatNoRot(loc1.getLatitude());
final int bX = box.getPixXFromLonNoRot(loc2.getLongitude());
final int bY = box.getPixYFromLatNoRot(loc2.getLatitude());
if(baDist != 0) {
double CF = (dTarget - distLeft) / baDist;
double rX = bX - CF * (bX - aX);
double rY = bY - CF * (bY - aY);
projectionXY = new double[] {rX, rY};
}
}
if(projectionXY != null) {
double distanceLoc2Proj = MapUtils.getSqrtDistance((int)projectionXY[0], (int) projectionXY[1],
box.getPixXFromLonNoRot(ll.getLongitude()), box.getPixYFromLatNoRot(ll.getLatitude()));
boolean visible = box.containsPoint((float) projectionXY[0], (float) projectionXY[1], 20.0f)
&& distanceLoc2Proj > AndroidUtils.dpToPx(view.getContext(), 52) / 2.0;
if (visible) {
return projectionXY;
}
}
return null;
}
private List<Location> calculateActionPoints(double topLatitude, double leftLongitude, double bottomLatitude,
double rightLongitude, Location lastProjection, List<Location> routeNodes, int cd,
Iterator<RouteDirectionInfo> it, int zoom) {
RouteDirectionInfo nf = null;
double DISTANCE_ACTION = 35;
if(zoom >= 17) {
DISTANCE_ACTION = 15;
} else if (zoom == 15) {
DISTANCE_ACTION = 70;
} else if (zoom < 15) {
DISTANCE_ACTION = 110;
}
double actionDist = 0;
Location previousAction = null;
List<Location> actionPoints = this.actionPoints;
actionPoints.clear();
int prevFinishPoint = -1;
for (int routePoint = 0; routePoint < routeNodes.size(); routePoint++) {
Location loc = routeNodes.get(routePoint);
if(nf != null) {
int pnt = nf.routeEndPointOffset == 0 ? nf.routePointOffset : nf.routeEndPointOffset;
if(pnt < routePoint + cd ) {
nf = null;
}
}
while (nf == null && it.hasNext()) {
nf = it.next();
int pnt = nf.routeEndPointOffset == 0 ? nf.routePointOffset : nf.routeEndPointOffset;
if (pnt < routePoint + cd) {
nf = null;
}
}
boolean action = nf != null && (nf.routePointOffset == routePoint + cd ||
(nf.routePointOffset <= routePoint + cd && routePoint + cd <= nf.routeEndPointOffset));
if(!action && previousAction == null) {
// no need to check
continue;
}
boolean visible = leftLongitude <= loc.getLongitude() && loc.getLongitude() <= rightLongitude && bottomLatitude <= loc.getLatitude()
&& loc.getLatitude() <= topLatitude;
if(action && !visible && previousAction == null) {
continue;
}
if (!action) {
// previousAction != null
float dist = loc.distanceTo(previousAction);
actionDist += dist;
if (actionDist >= DISTANCE_ACTION) {
actionPoints.add(calculateProjection(1 - (actionDist - DISTANCE_ACTION) / dist, previousAction, loc));
actionPoints.add(null);
prevFinishPoint = routePoint;
previousAction = null;
actionDist = 0;
} else {
actionPoints.add(loc);
previousAction = loc;
}
} else {
// action point
if (previousAction == null) {
addPreviousToActionPoints(actionPoints, lastProjection, routeNodes, DISTANCE_ACTION,
prevFinishPoint, routePoint, loc);
}
actionPoints.add(loc);
previousAction = loc;
prevFinishPoint = -1;
actionDist = 0;
}
}
if(previousAction != null) {
actionPoints.add(null);
}
return actionPoints;
}
private void addPreviousToActionPoints(List<Location> actionPoints, Location lastProjection, List<Location> routeNodes, double DISTANCE_ACTION,
int prevFinishPoint, int routePoint, Location loc) {
// put some points in front
int ind = actionPoints.size();
Location lprevious = loc;
double dist = 0;
for (int k = routePoint - 1; k >= -1; k--) {
Location l = k == -1 ? lastProjection : routeNodes.get(k);
float locDist = lprevious.distanceTo(l);
dist += locDist;
if (dist >= DISTANCE_ACTION) {
if (locDist > 1) {
actionPoints.add(ind,
calculateProjection(1 - (dist - DISTANCE_ACTION) / locDist, lprevious, l));
}
break;
} else {
actionPoints.add(ind, l);
lprevious = l;
}
if (prevFinishPoint == k) {
if (ind >= 2) {
actionPoints.remove(ind - 2);
actionPoints.remove(ind - 2);
}
break;
}
}
}
private Location calculateProjection(double part, Location lp, Location l) {
Location p = new Location(l);
p.setLatitude(lp.getLatitude() + part * (l.getLatitude() - lp.getLatitude()));
p.setLongitude(lp.getLongitude() + part * (l.getLongitude() - lp.getLongitude()));
return p;
}
@Override
public void destroyLayer() {
}
@Override
public boolean drawInScreenPixels() {
return false;
}
@Override
public boolean onLongPressEvent(PointF point, RotatedTileBox tileBox) {
return false;
}
@Override
public boolean onSingleTap(PointF point, RotatedTileBox tileBox) {
return false;
}
private int getRadiusPoi(RotatedTileBox tb){
final double zoom = tb.getZoom();
int r;
if(zoom <= 15) {
r = 8;
} else if(zoom <= 16) {
r = 10;
} else if(zoom <= 17) {
r = 14;
} else {
r = 18;
}
return (int) (r * tb.getDensity());
}
@Nullable
private List<TransportStop> getRouteTransportStops() {
return helper.isPublicTransportMode() ? publicTransportRouteGeometry.getDrawer().getRouteTransportStops() : null;
}
private void getFromPoint(RotatedTileBox tb, PointF point, List<? super TransportStop> res, @NonNull List<TransportStop> routeTransportStops) {
int ex = (int) point.x;
int ey = (int) point.y;
final int rp = getRadiusPoi(tb);
int radius = rp * 3 / 2;
try {
for (int i = 0; i < routeTransportStops.size(); i++) {
TransportStop n = routeTransportStops.get(i);
if (n.getLocation() == null) {
continue;
}
int x = (int) tb.getPixXFromLatLon(n.getLocation().getLatitude(), n.getLocation().getLongitude());
int y = (int) tb.getPixYFromLatLon(n.getLocation().getLatitude(), n.getLocation().getLongitude());
if (Math.abs(x - ex) <= radius && Math.abs(y - ey) <= radius) {
radius = rp;
res.add(n);
}
}
} catch (IndexOutOfBoundsException e) {
// ignore
}
}
@Override
public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List<Object> res, boolean unknownLocation) {
List<TransportStop> routeTransportStops = getRouteTransportStops();
if (!Algorithms.isEmpty(routeTransportStops)) {
getFromPoint(tileBox, point, res, routeTransportStops);
}
}
@Override
public LatLon getObjectLocation(Object o) {
if (o instanceof TransportStop){
return ((TransportStop)o).getLocation();
}
return null;
}
@Override
public PointDescription getObjectName(Object o) {
if (o instanceof TransportStop){
return new PointDescription(PointDescription.POINT_TYPE_TRANSPORT_STOP, view.getContext().getString(R.string.transport_Stop),
((TransportStop)o).getName());
}
return null;
}
@Override
public boolean disableSingleTap() {
return false;
}
@Override
public boolean disableLongPressOnMap() {
return false;
}
@Override
public boolean isObjectClickable(Object o) {
return false;
}
@Override
public boolean runExclusiveAction(@Nullable Object o, boolean unknownLocation) {
return false;
}
}

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -34,6 +34,9 @@ import net.osmand.plus.settings.backend.OsmandSettings.AngularConstants;
import net.osmand.plus.settings.backend.OsmandSettings.RulerMode;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.layers.geometry.GeometryWay;
import net.osmand.util.MapUtils;
import java.util.ArrayList;
@ -428,7 +431,7 @@ public class RulerControlLayer extends OsmandMapLayer {
tx.add(currX);
ty.add(currY);
calculatePath(tb, tx, ty, linePath);
GeometryWay.calculatePath(tb, tx, ty, linePath);
float dist = (float) MapUtils.getDistance(touchPointLatLon, currLoc.getLatitude(), currLoc.getLongitude());
String text = OsmAndFormatter.getFormattedDistance(dist, app);

View file

@ -1,4 +1,4 @@
package net.osmand.plus.views;
package net.osmand.plus.views.layers;
import android.content.Context;
import android.graphics.Canvas;
@ -27,6 +27,9 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.render.RenderingIcons;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.transport.TransportStopType;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.layers.geometry.GeometryWay;
import java.io.IOException;
import java.util.ArrayList;
@ -197,7 +200,7 @@ public class TransportStopsLayer extends OsmandMapLayer implements ContextMenuLa
tx.add(x);
ty.add(y);
}
calculatePath(tb, tx, ty, path);
GeometryWay.calculatePath(tb, tx, ty, path);
}
}
attrs.drawPath(canvas, path);

View file

@ -0,0 +1,450 @@
package net.osmand.plus.views.layers.geometry;
import android.graphics.Canvas;
import android.graphics.Path;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.Location;
import net.osmand.data.RotatedTileBox;
import net.osmand.util.MapAlgorithms;
import net.osmand.util.MapUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import gnu.trove.list.array.TByteArrayList;
public abstract class GeometryWay<T extends GeometryWayContext, D extends GeometryWayDrawer<T>> {
private double mapDensity;
private T context;
private D drawer;
private TreeMap<Integer, PathGeometryZoom> zooms = new TreeMap<>();
private GeometryWayProvider locationProvider;
private Map<Integer, GeometryWayStyle<?>> styleMap = Collections.emptyMap();
// cache arrays
private List<Float> tx = new ArrayList<>();
private List<Float> ty = new ArrayList<>();
private List<Double> angles = new ArrayList<>();
private List<Double> distances = new ArrayList<>();
private List<GeometryWayStyle<?>> styles = new ArrayList<>();
public interface GeometryWayProvider {
double getLatitude(int index);
double getLongitude(int index);
int getSize();
}
private static class GeometryWayLocationProvider implements GeometryWayProvider {
private List<Location> locations;
public GeometryWayLocationProvider(@NonNull List<Location> locations) {
this.locations = locations;
}
@Override
public double getLatitude(int index) {
return locations.get(index).getLatitude();
}
@Override
public double getLongitude(int index) {
return locations.get(index).getLongitude();
}
@Override
public int getSize() {
return locations.size();
}
}
public GeometryWay(T context, D drawer) {
this.context = context;
this.drawer = drawer;
}
private GeometryWayStyle<?> getStyle(int index, GeometryWayStyle<?> defaultWayStyle) {
List<Integer> list = new ArrayList<>(styleMap.keySet());
for (int i = list.size() - 1; i >= 0; i--) {
int c = list.get(i);
if (c <= index) {
return styleMap.get(c);
}
}
return defaultWayStyle;
}
public T getContext() {
return context;
}
public D getDrawer() {
return drawer;
}
public double getMapDensity() {
return mapDensity;
}
@Nullable
public GeometryWayProvider getLocationProvider() {
return locationProvider;
}
protected void updateWay(@NonNull GeometryWayProvider locationProvider, @NonNull RotatedTileBox tb) {
updateWay(locationProvider, null, tb);
}
protected void updateWay(@NonNull GeometryWayProvider locationProvider, @Nullable Map<Integer, GeometryWayStyle<?>> styleMap, @NonNull RotatedTileBox tb) {
this.locationProvider = locationProvider;
this.styleMap = styleMap == null ? Collections.<Integer, GeometryWayStyle<?>>emptyMap() : styleMap;
this.mapDensity = tb.getMapDensity();
this.zooms.clear();
}
public void updateWay(@NonNull List<Location> locations, @NonNull RotatedTileBox tb) {
updateWay(locations, null, tb);
}
public void updateWay(@NonNull List<Location> locations, @Nullable Map<Integer, GeometryWayStyle<?>> styleMap, @NonNull RotatedTileBox tb) {
this.locationProvider = new GeometryWayLocationProvider(locations);
this.styleMap = styleMap == null ? Collections.<Integer, GeometryWayStyle<?>>emptyMap() : styleMap;
this.mapDensity = tb.getMapDensity();
this.zooms.clear();
}
public void clearWay() {
locationProvider = null;
styleMap = Collections.emptyMap();
zooms.clear();
}
private PathGeometryZoom getGeometryZoom(RotatedTileBox tb) {
int zoom = tb.getZoom();
PathGeometryZoom zm = zooms.size() > zoom ? zooms.get(zoom) : null;
if (zm == null) {
zm = new PathGeometryZoom(locationProvider, tb, tb.getZoom() < context.getSimplificationZoom());
zooms.put(zoom, zm);
}
return zm;
}
@NonNull
public abstract GeometryWayStyle<?> getDefaultWayStyle();
public Location getNextVisiblePoint() {
return null;
}
public void drawSegments(RotatedTileBox tb, Canvas canvas, double topLatitude, double leftLongitude,
double bottomLatitude, double rightLongitude, Location lastProjection, int startLocationIndex) {
if (locationProvider == null || locationProvider.getSize() == 0) {
return;
}
PathGeometryZoom geometryZoom = getGeometryZoom(tb);
TByteArrayList simplification = geometryZoom.getSimplifyPoints();
List<Double> odistances = geometryZoom.getDistances();
clearArrays();
GeometryWayStyle<?> defaultWayStyle = getDefaultWayStyle();
GeometryWayStyle<?> style = defaultWayStyle;
boolean previousVisible = false;
if (lastProjection != null) {
previousVisible = addPoint(tb, topLatitude, leftLongitude, bottomLatitude, rightLongitude, style, previousVisible, lastProjection);
}
Location nextVisiblePoint = getNextVisiblePoint();
if (nextVisiblePoint != null) {
previousVisible = addPoint(tb, topLatitude, leftLongitude, bottomLatitude, rightLongitude, style, previousVisible, nextVisiblePoint);
}
GeometryWayProvider locationProvider = this.locationProvider;
int previous = -1;
for (int i = startLocationIndex; i < locationProvider.getSize(); i++) {
style = getStyle(i, defaultWayStyle);
if (simplification.getQuick(i) == 0 && !styleMap.containsKey(i)) {
continue;
}
double lat = locationProvider.getLatitude(i);
double lon = locationProvider.getLongitude(i);
if (leftLongitude <= lon && lon <= rightLongitude && bottomLatitude <= lat
&& lat <= topLatitude) {
double dist = previous == -1 ? 0 : odistances.get(i);
if (!previousVisible) {
double prevLat = Double.NaN;
double prevLon = Double.NaN;
if (previous != -1) {
prevLat = locationProvider.getLatitude(previous);
prevLon = locationProvider.getLongitude(previous);
} else if (lastProjection != null) {
prevLat = lastProjection.getLatitude();
prevLon = lastProjection.getLongitude();
}
if (!Double.isNaN(prevLat) && !Double.isNaN(prevLon)) {
addLocation(tb, prevLat, prevLon, style, tx, ty, angles, distances, dist, styles); // first point
}
}
addLocation(tb, lat, lon, style, tx, ty, angles, distances, dist, styles);
previousVisible = true;
} else if (previousVisible) {
addLocation(tb, lat, lon, style, tx, ty, angles, distances, previous == -1 ? 0 : odistances.get(i), styles);
double distToFinish = 0;
for (int ki = i + 1; ki < odistances.size(); ki++) {
distToFinish += odistances.get(ki);
}
drawRouteSegment(tb, canvas, tx, ty, angles, distances, distToFinish, styles);
previousVisible = false;
clearArrays();
}
previous = i;
}
drawRouteSegment(tb, canvas, tx, ty, angles, distances, 0, styles);
}
private void addLocation(RotatedTileBox tb, double latitude, double longitude, GeometryWayStyle<?> style,
List<Float> tx, List<Float> ty, List<Double> angles, List<Double> distances,
double dist, List<GeometryWayStyle<?>> styles) {
float x = tb.getPixXFromLatLon(latitude, longitude);
float y = tb.getPixYFromLatLon(latitude, longitude);
float px = x;
float py = y;
int previous = tx.size() - 1;
if (previous >= 0) {
px = tx.get(previous);
py = ty.get(previous);
}
double angle = 0;
if (px != x || py != y) {
double angleRad = Math.atan2(y - py, x - px);
angle = (angleRad * 180 / Math.PI) + 90f;
}
double distSegment = dist != 0 ? dist : Math.sqrt((y - py) * (y - py) + (x - px) * (x - px));
tx.add(x);
ty.add(y);
angles.add(angle);
distances.add(distSegment);
styles.add(style);
}
private boolean addPoint(RotatedTileBox tb, double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, GeometryWayStyle<?> style, boolean previousVisible, Location lastPoint) {
if (leftLongitude <= lastPoint.getLongitude() && lastPoint.getLongitude() <= rightLongitude
&& bottomLatitude <= lastPoint.getLatitude() && lastPoint.getLatitude() <= topLatitude) {
addLocation(tb, lastPoint.getLatitude(), lastPoint.getLongitude(), style, tx, ty, angles, distances, 0, styles);
previousVisible = true;
}
return previousVisible;
}
private void clearArrays() {
tx.clear();
ty.clear();
distances.clear();
angles.clear();
styles.clear();
}
public static boolean isIn(float x, float y, int lx, int ty, int rx, int by) {
return x >= lx && x <= rx && y >= ty && y <= by;
}
public static int calculatePath(RotatedTileBox tb, List<Float> xs, List<Float> ys, Path path) {
List<Pair<Path, GeometryWayStyle<?>>> paths = new ArrayList<>();
int res = calculatePath(tb, xs, ys, null, paths);
if (paths.size() > 0) {
path.addPath(paths.get(0).first);
}
return res;
}
public static int calculatePath(RotatedTileBox tb, List<Float> xs, List<Float> ys, List<GeometryWayStyle<?>> styles, List<Pair<Path, GeometryWayStyle<?>>> paths) {
boolean segmentStarted = false;
float prevX = xs.get(0);
float prevY = ys.get(0);
int height = tb.getPixHeight();
int width = tb.getPixWidth();
int cnt = 0;
boolean hasStyles = styles != null && styles.size() == xs.size();
GeometryWayStyle<?> style = hasStyles ? styles.get(0) : null;
Path path = new Path();
boolean prevIn = isIn(prevX, prevY, 0, 0, width, height);
for (int i = 1; i < xs.size(); i++) {
float currX = xs.get(i);
float currY = ys.get(i);
boolean currIn = isIn(currX, currY, 0, 0, width, height);
boolean draw = false;
if (prevIn && currIn) {
draw = true;
} else {
long intersection = MapAlgorithms.calculateIntersection((int) currX, (int) currY, (int) prevX, (int) prevY, 0, width, height, 0);
if (intersection != -1) {
if (prevIn && (i == 1)) {
cnt++;
path.moveTo(prevX, prevY);
segmentStarted = true;
}
prevX = (int) (intersection >> 32);
prevY = (int) (intersection & 0xffffffff);
draw = true;
}
if (i == xs.size() - 1 && !currIn) {
long inter = MapAlgorithms.calculateIntersection((int) prevX, (int) prevY, (int) currX, (int) currY, 0, width, height, 0);
if (inter != -1) {
currX = (int) (inter >> 32);
currY = (int) (inter & 0xffffffff);
}
}
}
if (draw) {
if (!segmentStarted) {
cnt++;
path.moveTo(prevX, prevY);
segmentStarted = true;
}
path.lineTo(currX, currY);
} else {
segmentStarted = false;
}
prevIn = currIn;
prevX = currX;
prevY = currY;
if (hasStyles) {
GeometryWayStyle<?> newStyle = styles.get(i);
if (!style.equals(newStyle)) {
paths.add(new Pair<Path, GeometryWayStyle<?>>(path, style));
path = new Path();
if (segmentStarted) {
path.moveTo(currX, currY);
}
style = newStyle;
}
}
}
if (!path.isEmpty()) {
paths.add(new Pair<Path, GeometryWayStyle<?>>(path, style));
}
return cnt;
}
private void drawRouteSegment(RotatedTileBox tb, Canvas canvas, List<Float> tx, List<Float> ty,
List<Double> angles, List<Double> distances, double distToFinish, List<GeometryWayStyle<?>> styles) {
if (tx.size() < 2) {
return;
}
try {
boolean hasPathLine = false;
for (GeometryWayStyle<?> style : styles) {
if (style.hasPathLine()) {
hasPathLine = true;
break;
}
}
if (hasPathLine) {
List<Pair<Path, GeometryWayStyle<?>>> paths = new ArrayList<>();
canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
calculatePath(tb, tx, ty, styles, paths);
for (Pair<Path, GeometryWayStyle<?>> pc : paths) {
GeometryWayStyle<?> style = pc.second;
if (style.hasPathLine()) {
drawer.drawPath(canvas, pc.first, style);
}
}
context.clearCustomColor();
}
drawer.drawArrowsOverPath(canvas, tb, tx, ty, angles, distances, distToFinish, styles);
} finally {
canvas.rotate(tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
}
}
private static class PathGeometryZoom {
private static final float EPSILON_IN_DPI = 2;
private final TByteArrayList simplifyPoints;
private List<Double> distances;
private List<Double> angles;
public PathGeometryZoom(GeometryWayProvider locationProvider, RotatedTileBox tb, boolean simplify) {
// this.locations = locations;
tb = new RotatedTileBox(tb);
tb.setZoomAndAnimation(tb.getZoom(), 0, tb.getZoomFloatPart());
int size = locationProvider.getSize();
simplifyPoints = new TByteArrayList(size);
distances = new ArrayList<>(size);
angles = new ArrayList<>(size);
if (simplify) {
simplifyPoints.fill(0, size, (byte) 0);
if (size > 0) {
simplifyPoints.set(0, (byte) 1);
}
double distInPix = (tb.getDistance(0, 0, tb.getPixWidth(), 0) / tb.getPixWidth());
double cullDistance = (distInPix * (EPSILON_IN_DPI * Math.max(1, tb.getDensity())));
cullRamerDouglasPeucker(simplifyPoints, locationProvider, 0, size - 1, cullDistance);
} else {
simplifyPoints.fill(0, size, (byte) 1);
}
int previousIndex = -1;
for (int i = 0; i < size; i++) {
double d = 0;
double angle = 0;
if (simplifyPoints.get(i) > 0) {
if (previousIndex > -1) {
float x = tb.getPixXFromLatLon(locationProvider.getLatitude(i), locationProvider.getLongitude(i));
float y = tb.getPixYFromLatLon(locationProvider.getLatitude(i), locationProvider.getLongitude(i));
float px = tb.getPixXFromLatLon(locationProvider.getLatitude(previousIndex), locationProvider.getLongitude(previousIndex));
float py = tb.getPixYFromLatLon(locationProvider.getLatitude(previousIndex), locationProvider.getLongitude(previousIndex));
d = Math.sqrt((y - py) * (y - py) + (x - px) * (x - px));
if (px != x || py != y) {
double angleRad = Math.atan2(y - py, x - px);
angle = (angleRad * 180 / Math.PI) + 90f;
}
}
previousIndex = i;
}
distances.add(d);
angles.add(angle);
}
}
public List<Double> getDistances() {
return distances;
}
public List<Double> getAngles() {
return angles;
}
private void cullRamerDouglasPeucker(TByteArrayList survivor, GeometryWayProvider locationProvider,
int start, int end, double epsillon) {
double dmax = Double.NEGATIVE_INFINITY;
int index = -1;
for (int i = start + 1; i < end; i++) {
double d = MapUtils.getOrthogonalDistance(locationProvider.getLatitude(i), locationProvider.getLongitude(i),
locationProvider.getLatitude(start), locationProvider.getLongitude(start),
locationProvider.getLatitude(end), locationProvider.getLongitude(end));
if (d > dmax) {
dmax = d;
index = i;
}
}
if (dmax > epsillon) {
cullRamerDouglasPeucker(survivor, locationProvider, start, index, epsillon);
cullRamerDouglasPeucker(survivor, locationProvider, index, end, epsillon);
} else {
survivor.set(end, (byte) 1);
}
}
public TByteArrayList getSimplifyPoints() {
return simplifyPoints;
}
}
}

View file

@ -0,0 +1,124 @@
package net.osmand.plus.views.layers.geometry;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.core.graphics.ColorUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.render.RenderingIcons;
import net.osmand.plus.views.OsmandMapLayer.RenderingLineAttributes;
public abstract class GeometryWayContext {
public static final int DEFAULT_SIMPLIFICATION_ZOOM = 6;
private Context ctx;
private float density;
private boolean nightMode;
private int simplificationZoom = DEFAULT_SIMPLIFICATION_ZOOM;
private Paint paintIcon;
private Paint paintIconCustom;
private RenderingLineAttributes attrs;
private Bitmap arrowBitmap;
public GeometryWayContext(Context ctx, float density) {
this.ctx = ctx;
this.density = density;
paintIcon = new Paint();
paintIcon.setFilterBitmap(true);
paintIcon.setAntiAlias(true);
paintIcon.setColor(Color.BLACK);
paintIcon.setStrokeWidth(1f * density);
paintIconCustom = new Paint();
paintIconCustom.setFilterBitmap(true);
paintIconCustom.setAntiAlias(true);
paintIconCustom.setColor(Color.BLACK);
paintIconCustom.setStrokeWidth(1f * density);
arrowBitmap = RenderingIcons.getBitmapFromVectorDrawable(ctx, getArrowBitmapResId());
}
public OsmandApplication getApp() {
return (OsmandApplication) ctx.getApplicationContext();
}
public Context getCtx() {
return ctx;
}
public RenderingLineAttributes getAttrs() {
return attrs;
}
public float getDensity() {
return density;
}
public boolean isNightMode() {
return nightMode;
}
public int getSimplificationZoom() {
return simplificationZoom;
}
public void setSimplificationZoom(int simplificationZoom) {
this.simplificationZoom = simplificationZoom;
}
@DrawableRes
protected abstract int getArrowBitmapResId();
public void updatePaints(boolean nightMode, @NonNull RenderingLineAttributes attrs) {
this.attrs = attrs;
paintIcon.setColorFilter(new PorterDuffColorFilter(attrs.paint2.getColor(), PorterDuff.Mode.MULTIPLY));
this.nightMode = nightMode;
recreateBitmapsInternal();
}
protected boolean hasAttrs() {
return attrs != null;
}
protected void recreateBitmaps() {
}
private void recreateBitmapsInternal() {
if (hasAttrs()) {
recreateBitmaps();
}
}
public void clearCustomColor() {
attrs.customColor = 0;
}
public int getStrokeColor(int sourceColor) {
return ColorUtils.blendARGB(sourceColor, Color.BLACK, 0.6f);
}
public Paint getPaintIcon() {
return paintIcon;
}
public Paint getPaintIconCustom() {
return paintIconCustom;
}
public Bitmap getArrowBitmap() {
return arrowBitmap;
}
}

View file

@ -0,0 +1,140 @@
package net.osmand.plus.views.layers.geometry;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import net.osmand.data.RotatedTileBox;
import java.util.ArrayList;
import java.util.List;
public class GeometryWayDrawer<T extends GeometryWayContext> {
private T context;
public GeometryWayDrawer(T context) {
this.context = context;
}
public T getContext() {
return context;
}
public void drawArrowsOverPath(Canvas canvas, RotatedTileBox tb, List<Float> tx, List<Float> ty,
List<Double> angles, List<Double> distances, double distPixToFinish, List<GeometryWayStyle<?>> styles) {
List<PathPoint> arrows = new ArrayList<>();
int h = tb.getPixHeight();
int w = tb.getPixWidth();
int left = -w / 4;
int right = w + w / 4;
int top = - h/4;
int bottom = h + h/4;
boolean hasStyles = styles != null && styles.size() == tx.size();
double zoomCoef = tb.getZoomAnimation() > 0 ? (Math.pow(2, tb.getZoomAnimation() + tb.getZoomFloatPart())) : 1f;
Bitmap arrow = context.getArrowBitmap();
int arrowHeight = arrow.getHeight();
double pxStep = arrowHeight * 4f * zoomCoef;
double pxStepRegular = arrowHeight * 4f * zoomCoef;
double dist = 0;
if (distPixToFinish != 0) {
dist = distPixToFinish - pxStep * ((int) (distPixToFinish / pxStep)); // dist < 1
}
for (int i = tx.size() - 2; i >= 0; i --) {
GeometryWayStyle<?> style = hasStyles ? styles.get(i) : null;
float px = tx.get(i);
float py = ty.get(i);
float x = tx.get(i + 1);
float y = ty.get(i + 1);
double distSegment = distances.get(i + 1);
double angle = angles.get(i + 1);
if (distSegment == 0) {
continue;
}
pxStep = style != null ? style.getPointStepPx(zoomCoef) : pxStepRegular;
if (dist >= pxStep) {
dist = 0;
}
double percent = 1 - (pxStep - dist) / distSegment;
dist += distSegment;
while (dist >= pxStep) {
double pdx = (x - px) * percent;
double pdy = (y - py) * percent;
float iconx = (float) (px + pdx);
float icony = (float) (py + pdy);
if (GeometryWay.isIn(iconx, icony, left, top, right, bottom)) {
arrows.add(getArrowPathPoint(iconx, icony, style, angle));
}
dist -= pxStep;
percent -= pxStep / distSegment;
}
}
for (int i = arrows.size() - 1; i >= 0; i--) {
PathPoint a = arrows.get(i);
if (!tb.isZoomAnimated() || a.style.isVisibleWhileZooming()) {
a.draw(canvas, context);
}
}
}
protected PathPoint getArrowPathPoint(float iconx, float icony, GeometryWayStyle<?> style, double angle) {
return new PathPoint(iconx, icony, angle, style);
}
public void drawPath(Canvas canvas, Path path, GeometryWayStyle<?> style) {
context.getAttrs().customColor = style.getColor();
context.getAttrs().drawPath(canvas, path);
}
public static class PathPoint {
float x;
float y;
double angle;
GeometryWayStyle<?> style;
private Matrix matrix = new Matrix();
public PathPoint(float x, float y, double angle, GeometryWayStyle<?> style) {
this.x = x;
this.y = y;
this.angle = angle;
this.style = style;
}
protected Matrix getMatrix() {
return matrix;
}
void draw(Canvas canvas, GeometryWayContext context) {
if (style != null && style.getPointBitmap() != null) {
Bitmap bitmap = style.getPointBitmap();
Integer pointColor = style.getPointColor();
float paintH2 = bitmap.getHeight() / 2f;
float paintW2 = bitmap.getWidth() / 2f;
matrix.reset();
matrix.postRotate((float) angle, paintW2, paintH2);
matrix.postTranslate(x - paintW2, y - paintH2);
if (pointColor != null) {
Paint paint = context.getPaintIconCustom();
paint.setColorFilter(new PorterDuffColorFilter(pointColor, PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, matrix, paint);
} else {
if (style.hasPaintedPointBitmap()) {
Paint paint = context.getPaintIconCustom();
paint.setColorFilter(null);
canvas.drawBitmap(bitmap, matrix, paint);
} else {
canvas.drawBitmap(bitmap, matrix, context.getPaintIcon());
}
}
}
}
}
}

View file

@ -0,0 +1,83 @@
package net.osmand.plus.views.layers.geometry;
import android.content.Context;
import android.graphics.Bitmap;
public abstract class GeometryWayStyle<T extends GeometryWayContext> {
private T context;
protected Integer color;
public GeometryWayStyle(T context) {
this.context = context;
}
public GeometryWayStyle(T context, Integer color) {
this.context = context;
this.color = color;
}
public T getContext() {
return context;
}
public Context getCtx() {
return context.getCtx();
}
public Integer getColor() {
return color;
}
public Integer getStrokeColor() {
return context.getStrokeColor(color);
}
public Integer getPointColor() {
return null;
}
public boolean isNightMode() {
return context.isNightMode();
}
public boolean hasPathLine() {
return true;
}
public boolean isVisibleWhileZooming() {
return false;
}
public double getPointStepPx(double zoomCoef) {
Bitmap arrow = context.getArrowBitmap();
int arrowHeight = arrow.getHeight();
return arrowHeight * 4f * zoomCoef;
}
public abstract Bitmap getPointBitmap();
public boolean hasPaintedPointBitmap() {
return false;
}
@Override
public int hashCode() {
return (color != null ? color.hashCode() : 0) + (context.isNightMode() ? 1231 : 1237);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof GeometryWayStyle)) {
return false;
}
GeometryWayStyle<?> o = (GeometryWayStyle<?>) other;
if (color != null && o.color != null) {
return color.equals(o.color);
}
return color == null && o.color == null;
}
}

View file

@ -0,0 +1,138 @@
package net.osmand.plus.views.layers.geometry;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.data.RotatedTileBox;
import java.util.List;
public class GpxGeometryWay extends GeometryWay<GpxGeometryWayContext, GeometryWayDrawer<GpxGeometryWayContext>> {
private List<WptPt> points;
private float trackWidth;
private int trackColor;
private int arrowColor;
private static class GeometryWayWptPtProvider implements GeometryWayProvider {
private List<WptPt> points;
public GeometryWayWptPtProvider(@NonNull List<WptPt> points) {
this.points = points;
}
@Override
public double getLatitude(int index) {
return points.get(index).getLatitude();
}
@Override
public double getLongitude(int index) {
return points.get(index).getLongitude();
}
@Override
public int getSize() {
return points.size();
}
}
public GpxGeometryWay(GpxGeometryWayContext context) {
super(context, new GpxGeometryWayDrawer(context));
}
public void setTrackStyleParams(int arrowColor, int trackColor, float trackWidth) {
this.arrowColor = arrowColor;
this.trackColor = trackColor;
this.trackWidth = trackWidth;
}
@NonNull
@Override
public GeometryWayStyle<?> getDefaultWayStyle() {
return new GeometryArrowsStyle(getContext(), arrowColor, trackColor, trackWidth);
}
public void updatePoints(RotatedTileBox tb, List<WptPt> points) {
if (tb.getMapDensity() != getMapDensity() || this.points != points) {
this.points = points;
if (points != null) {
updateWay(new GeometryWayWptPtProvider(points), tb);
} else {
clearWay();
}
}
}
public void clearPoints() {
if (points != null) {
points = null;
clearWay();
}
}
public static class GeometryArrowsStyle extends GeometryWayStyle<GpxGeometryWayContext> {
private static final double DIRECTION_ARROW_DISTANCE_MULTIPLIER = 10.0;
private Bitmap arrowBitmap;
protected int pointColor;
protected int trackColor;
protected float trackWidth;
GeometryArrowsStyle(GpxGeometryWayContext context, int arrowColor, int trackColor, float trackWidth) {
this(context, null, arrowColor, trackColor, trackWidth);
}
GeometryArrowsStyle(GpxGeometryWayContext context, Bitmap arrowBitmap, int arrowColor, int trackColor, float trackWidth) {
super(context);
this.arrowBitmap = arrowBitmap;
this.pointColor = arrowColor;
this.trackColor = trackColor;
this.trackWidth = trackWidth;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!super.equals(other)) {
return false;
}
return other instanceof GeometryArrowsStyle;
}
@Override
public boolean hasPathLine() {
return false;
}
@Override
public Bitmap getPointBitmap() {
return arrowBitmap != null ? arrowBitmap : getContext().getArrowBitmap();
}
@Override
public Integer getPointColor() {
return pointColor;
}
public int getTrackColor() {
return trackColor;
}
public float getTrackWidth() {
return trackWidth;
}
@Override
public double getPointStepPx(double zoomCoef) {
return getPointBitmap().getHeight() * DIRECTION_ARROW_DISTANCE_MULTIPLIER * zoomCoef;
}
}
}

View file

@ -0,0 +1,20 @@
package net.osmand.plus.views.layers.geometry;
import android.content.Context;
import android.graphics.Paint;
import net.osmand.plus.R;
public class GpxGeometryWayContext extends GeometryWayContext {
public GpxGeometryWayContext(Context ctx, float density) {
super(ctx, density);
Paint paint = getPaintIcon();
paint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected int getArrowBitmapResId() {
return R.drawable.mm_special_arrow_up;
}
}

View file

@ -0,0 +1,48 @@
package net.osmand.plus.views.layers.geometry;
import android.graphics.Canvas;
import android.graphics.Paint;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.views.layers.geometry.GeometryWayDrawer.PathPoint;
import net.osmand.plus.views.layers.geometry.GpxGeometryWay.GeometryArrowsStyle;
import java.util.ArrayList;
import java.util.List;
public class GpxGeometryWayDrawer extends GeometryWayDrawer<GpxGeometryWayContext> {
private static final float DIRECTION_ARROW_CIRCLE_MULTIPLIER = 1.5f;
public GpxGeometryWayDrawer(GpxGeometryWayContext context) {
super(context);
}
@Override
protected PathPoint getArrowPathPoint(float iconx, float icony, GeometryWayStyle<?> style, double angle) {
return new ArrowPathPoint(iconx, icony, angle, style);
}
private static class ArrowPathPoint extends PathPoint {
ArrowPathPoint(float x, float y, double angle, GeometryWayStyle<?> style) {
super(x, y, angle, style);
}
@Override
void draw(Canvas canvas, GeometryWayContext context) {
if (style instanceof GeometryArrowsStyle) {
GeometryArrowsStyle arrowsWayStyle = (GeometryArrowsStyle) style;
float arrowWidth = style.getPointBitmap().getWidth();
if (arrowWidth > arrowsWayStyle.getTrackWidth()) {
Paint paint = context.getPaintIcon();
paint.setColor(arrowsWayStyle.getTrackColor());
paint.setStrokeWidth(arrowWidth * DIRECTION_ARROW_CIRCLE_MULTIPLIER);
canvas.drawPoint(x, y, paint);
}
}
super.draw(canvas, context);
}
}
}

View file

@ -0,0 +1,302 @@
package net.osmand.plus.views.layers.geometry;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import net.osmand.Location;
import net.osmand.data.LatLon;
import net.osmand.data.RotatedTileBox;
import net.osmand.data.TransportRoute;
import net.osmand.osm.edit.Node;
import net.osmand.osm.edit.OSMSettings;
import net.osmand.osm.edit.Way;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.render.RenderingIcons;
import net.osmand.plus.routing.RouteCalculationResult;
import net.osmand.plus.routing.TransportRoutingHelper;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.transport.TransportStopType;
import net.osmand.router.TransportRoutePlanner;
import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment;
import net.osmand.router.TransportRouteResult;
import net.osmand.util.MapUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
public class PublicTransportGeometryWay extends GeometryWay<PublicTransportGeometryWayContext, PublicTransportGeometryWayDrawer> {
private TransportRoutingHelper transportHelper;
private TransportRouteResult route;
public PublicTransportGeometryWay(PublicTransportGeometryWayContext context) {
super(context, new PublicTransportGeometryWayDrawer(context));
this.transportHelper = context.getApp().getTransportRoutingHelper();
}
@NonNull
@Override
public GeometryWayStyle<PublicTransportGeometryWayContext> getDefaultWayStyle() {
return new GeometryWalkWayStyle(getContext());
}
public void updateRoute(RotatedTileBox tb, TransportRouteResult route) {
if (tb.getMapDensity() != getMapDensity() || this.route != route) {
this.route = route;
List<Location> locations;
Map<Integer, GeometryWayStyle<?>> styleMap;
if (route == null) {
locations = Collections.emptyList();
styleMap = Collections.emptyMap();
} else {
LatLon start = transportHelper.getStartLocation();
LatLon end = transportHelper.getEndLocation();
List<Way> list = new ArrayList<>();
List<GeometryWayStyle<?>> styles = new ArrayList<>();
calculateTransportResult(start, end, route, list, styles);
List<Location> locs = new ArrayList<>();
Map<Integer, GeometryWayStyle<?>> stlMap = new TreeMap<>();
int i = 0;
int k = 0;
if (list.size() > 0) {
for (Way w : list) {
stlMap.put(k, styles.get(i++));
for (Node n : w.getNodes()) {
Location ln = new Location("");
ln.setLatitude(n.getLatitude());
ln.setLongitude(n.getLongitude());
locs.add(ln);
k++;
}
}
}
locations = locs;
styleMap = stlMap;
}
updateWay(locations, styleMap, tb);
}
}
public void clearRoute() {
if (route != null) {
route = null;
clearWay();
}
}
private void calculateTransportResult(LatLon start, LatLon end, TransportRouteResult r, List<Way> res, List<GeometryWayStyle<?>> styles) {
if (r != null) {
LatLon p = start;
TransportRouteResultSegment prev = null;
for (TransportRouteResultSegment s : r.getSegments()) {
LatLon floc = s.getStart().getLocation();
addRouteWalk(prev, s, p, floc, res, styles);
List<Way> geometry = s.getGeometry();
res.addAll(geometry);
addStyle(s, geometry, styles);
p = s.getEnd().getLocation();
prev = s;
}
addRouteWalk(prev, null, p, end, res, styles);
}
}
private void addRouteWalk(TransportRouteResultSegment s1, TransportRouteResultSegment s2,
LatLon start, LatLon end, List<Way> res, List<GeometryWayStyle<?>> styles) {
final RouteCalculationResult walkingRouteSegment = transportHelper.getWalkingRouteSegment(s1, s2);
if (walkingRouteSegment != null && walkingRouteSegment.getRouteLocations().size() > 0) {
final List<Location> routeLocations = walkingRouteSegment.getRouteLocations();
Way way = new Way(TransportRoutePlanner.GEOMETRY_WAY_ID);
way.putTag(OSMSettings.OSMTagKey.NAME.getValue(), String.format(Locale.US, "Walk %d m", walkingRouteSegment.getWholeDistance()));
for (Location l : routeLocations) {
way.addNode(new Node(l.getLatitude(), l.getLongitude(), -1));
}
res.add(way);
addStyle(null, Collections.singletonList(way), styles);
} else {
double dist = MapUtils.getDistance(start, end);
Way way = new Way(TransportRoutePlanner.GEOMETRY_WAY_ID);
way.putTag(OSMSettings.OSMTagKey.NAME.getValue(), String.format(Locale.US, "Walk %.1f m", dist));
way.addNode(new Node(start.getLatitude(), start.getLongitude(), -1));
way.addNode(new Node(end.getLatitude(), end.getLongitude(), -1));
res.add(way);
addStyle(null, Collections.singletonList(way), styles);
}
}
private void addStyle(TransportRouteResultSegment segment, List<Way> geometry, List<GeometryWayStyle<?>> styles) {
PublicTransportGeometryWayContext context = getContext();
GeometryWayStyle<?> style;
Way w = geometry.get(0);
if (segment == null || segment.route == null) {
style = new GeometryWalkWayStyle(context);
} else if (w.getId() == TransportRoutePlanner.GEOMETRY_WAY_ID) {
style = new GeometryTransportWayStyle(context, segment);
} else {
style = new TransportStopsWayStyle(context, segment);
}
for (int i = 0; i < geometry.size(); i++) {
styles.add(style);
}
}
public static class GeometryWalkWayStyle extends PublicTransportGeometryWayStyle {
GeometryWalkWayStyle(PublicTransportGeometryWayContext context) {
super(context);
}
@Override
public boolean hasPathLine() {
return false;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!super.equals(other)) {
return false;
}
return other instanceof GeometryWalkWayStyle;
}
public Bitmap getPointBitmap() {
return getContext().getWalkArrowBitmap();
}
@Override
public boolean hasPaintedPointBitmap() {
return true;
}
@Override
public double getPointStepPx(double zoomCoef) {
return getPointBitmap().getHeight() * 1.2f * zoomCoef;
}
@Override
public boolean isVisibleWhileZooming() {
return true;
}
}
public static class GeometryAnchorWayStyle extends GeometryWayStyle<PublicTransportGeometryWayContext> {
GeometryAnchorWayStyle(PublicTransportGeometryWayContext context) {
super(context);
}
@Override
public boolean hasPathLine() {
return false;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!super.equals(other)) {
return false;
}
return other instanceof GeometryAnchorWayStyle;
}
public Bitmap getPointBitmap() {
return getContext().getAnchorBitmap();
}
@Override
public boolean hasPaintedPointBitmap() {
return true;
}
}
public static class TransportStopsWayStyle extends GeometryTransportWayStyle {
TransportStopsWayStyle(PublicTransportGeometryWayContext context, TransportRouteResultSegment segment) {
super(context, segment);
OsmandApplication app = (OsmandApplication) getCtx().getApplicationContext();
this.color = ContextCompat.getColor(app, R.color.icon_color_default_light);
this.pointColor = UiUtilities.getContrastColor(app, color, true);
}
}
public static class GeometryTransportWayStyle extends GeometryWayStyle<PublicTransportGeometryWayContext> {
private TransportRouteResultSegment segment;
private Drawable stopDrawable;
protected Integer pointColor;
GeometryTransportWayStyle(PublicTransportGeometryWayContext context, TransportRouteResultSegment segment) {
super(context);
this.segment = segment;
TransportStopRoute r = new TransportStopRoute();
TransportRoute route = segment.route;
r.type = TransportStopType.findType(route.getType());
r.route = route;
OsmandApplication app = (OsmandApplication) getCtx().getApplicationContext();
this.color = r.getRouteColor(app, isNightMode());
this.pointColor = UiUtilities.getContrastColor(app, color, true);
TransportStopType type = TransportStopType.findType(route.getType());
if (type == null) {
type = TransportStopType.findType("bus");
}
if (type != null) {
stopDrawable = RenderingIcons.getDrawableIcon(getCtx(), type.getResName(), false);
}
}
public TransportRouteResultSegment getSegment() {
return segment;
}
public TransportRoute getRoute() {
return segment.route;
}
@Override
public Bitmap getPointBitmap() {
return getContext().getArrowBitmap();
}
@Override
public Integer getPointColor() {
return pointColor;
}
public Bitmap getStopBitmap() {
return getContext().getStopShieldBitmap(color, stopDrawable);
}
public Bitmap getStopSmallBitmap() {
return getContext().getStopSmallShieldBitmap(color);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!super.equals(other)) {
return false;
}
if (!(other instanceof GeometryTransportWayStyle)) {
return false;
}
return getRoute() == ((GeometryTransportWayStyle) other).getRoute();
}
}
}

View file

@ -0,0 +1,207 @@
package net.osmand.plus.views.layers.geometry;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.Pair;
import androidx.annotation.NonNull;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.views.OsmandMapLayer.RenderingLineAttributes;
import java.util.HashMap;
import java.util.Map;
public class PublicTransportGeometryWayContext extends GeometryWayContext {
private RenderingLineAttributes attrsPT;
private RenderingLineAttributes attrsW;
private Bitmap walkArrowBitmap;
private Bitmap anchorBitmap;
private Map<Pair<Integer, Drawable>, Bitmap> stopBitmapsCache = new HashMap<>();
private Map<Integer, Bitmap> stopSmallBitmapsCache = new HashMap<>();
public PublicTransportGeometryWayContext(Context ctx, float density) {
super(ctx, density);
}
@Override
protected int getArrowBitmapResId() {
return R.drawable.map_route_direction_arrow;
}
public RenderingLineAttributes getAttrsPT() {
return attrsPT;
}
public RenderingLineAttributes getAttrsW() {
return attrsW;
}
public void updatePaints(boolean nightMode, @NonNull RenderingLineAttributes attrs,
@NonNull RenderingLineAttributes attrsPT, @NonNull RenderingLineAttributes attrsW) {
this.attrsPT = attrsPT;
this.attrsW = attrsW;
updatePaints(nightMode, attrs);
}
public Bitmap getWalkArrowBitmap() {
return walkArrowBitmap;
}
public Bitmap getAnchorBitmap() {
return anchorBitmap;
}
@Override
protected boolean hasAttrs() {
return super.hasAttrs() && attrsPT != null && attrsW != null;
}
@Override
protected void recreateBitmaps() {
super.recreateBitmaps();
float walkCircleH = attrsW.paint.getStrokeWidth() * 1.33f;
float walkCircleW = attrsW.paint.getStrokeWidth();
float walkCircleRadius = attrsW.paint.getStrokeWidth() / 2f;
float routeShieldRadius = attrsPT.paint3.getStrokeWidth() / 2f;
// create anchor bitmap
float density = getDensity();
float margin = 2f * density;
float width = routeShieldRadius * 2 + margin * 2;
float height = routeShieldRadius * 2 + margin * 2;
Bitmap bitmap = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1f * density);
float x = width / 2;
float y = height / 2;
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(x, y, routeShieldRadius, paint);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(x, y, routeShieldRadius, paint);
anchorBitmap = bitmap;
// create walk arrow bitmap
width = walkCircleW + margin * 2;
height = walkCircleH + margin * 2;
bitmap = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1f * density);
RectF rect = new RectF(margin, margin, width - margin, height - margin);
paint.setColor(attrsW.paint.getColor());
paint.setStyle(Paint.Style.FILL);
canvas.drawRoundRect(rect, walkCircleRadius, walkCircleRadius, paint);
paint.setColor(getStrokeColor(paint.getColor()));
paint.setStyle(Paint.Style.STROKE);
canvas.drawRoundRect(rect, walkCircleRadius, walkCircleRadius, paint);
Bitmap arrowBitmap = getArrowBitmap();
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(1f * density);
paint.setAlpha(200);
canvas.drawBitmap(arrowBitmap, width / 2 - arrowBitmap.getWidth() / 2f, height / 2 - arrowBitmap.getHeight() / 2f, paint);
walkArrowBitmap = bitmap;
stopBitmapsCache = new HashMap<>();
}
@Override
public void clearCustomColor() {
super.clearCustomColor();
attrsPT.customColor = 0;
}
public Bitmap getStopShieldBitmap(int color, Drawable stopDrawable) {
Bitmap bmp = stopBitmapsCache.get(new Pair<>(color, stopDrawable));
if (bmp == null) {
int fillColor = UiUtilities.getContrastColor(getApp(), color, true);
int strokeColor = getStrokeColor(color);
float density = getDensity();
float routeShieldRadius = attrsPT.paint3.getStrokeWidth() / 2;
float routeShieldCornerRadius = 3 * density;
float margin = 2f * density;
float width = routeShieldRadius * 2 + margin * 2;
float height = routeShieldRadius * 2 + margin * 2;
bmp = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1f * density);
RectF rect = new RectF(margin, margin, width - margin, height - margin);
paint.setColor(fillColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawRoundRect(rect, routeShieldCornerRadius, routeShieldCornerRadius, paint);
paint.setColor(strokeColor);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRoundRect(rect, routeShieldCornerRadius, routeShieldCornerRadius, paint);
if (stopDrawable != null) {
stopDrawable.setColorFilter(new PorterDuffColorFilter(strokeColor, PorterDuff.Mode.SRC_IN));
float marginBitmap = 1f * density;
rect.inset(marginBitmap, marginBitmap);
stopDrawable.setBounds(0, 0, (int) rect.width(), (int) rect.height());
canvas.translate(rect.left, rect.top);
stopDrawable.draw(canvas);
}
stopBitmapsCache.put(new Pair<>(color, stopDrawable), bmp);
}
return bmp;
}
public Bitmap getStopSmallShieldBitmap(int color) {
Bitmap bmp = stopSmallBitmapsCache.get(color);
if (bmp == null) {
int fillColor = UiUtilities.getContrastColor(getApp(), color, true);
int strokeColor = getStrokeColor(color);
float routeShieldRadius = attrsPT.paint3.getStrokeWidth() / 4;
float density = getDensity();
float margin = 3f * density;
float width = routeShieldRadius * 2 + margin * 2;
float height = routeShieldRadius * 2 + margin * 2;
bmp = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1f * density);
paint.setColor(fillColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(width / 2, height / 2, routeShieldRadius, paint);
paint.setColor(strokeColor);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(width / 2, height / 2, routeShieldRadius, paint);
stopSmallBitmapsCache.put(color, bmp);
}
return bmp;
}
}

View file

@ -0,0 +1,222 @@
package net.osmand.plus.views.layers.geometry;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import net.osmand.data.QuadRect;
import net.osmand.data.QuadTree;
import net.osmand.data.RotatedTileBox;
import net.osmand.data.TransportStop;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapLayer.RenderingLineAttributes;
import net.osmand.plus.views.layers.geometry.PublicTransportGeometryWay.GeometryAnchorWayStyle;
import net.osmand.plus.views.layers.geometry.PublicTransportGeometryWay.GeometryTransportWayStyle;
import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class PublicTransportGeometryWayDrawer extends GeometryWayDrawer<PublicTransportGeometryWayContext> {
private List<TransportStop> routeTransportStops = new ArrayList<>();
public PublicTransportGeometryWayDrawer(PublicTransportGeometryWayContext context) {
super(context);
}
public List<TransportStop> getRouteTransportStops() {
return routeTransportStops;
}
@Override
public void drawPath(Canvas canvas, Path path, GeometryWayStyle<?> style) {
if (style instanceof GeometryTransportWayStyle) {
RenderingLineAttributes attrsPT = getContext().getAttrsPT();
attrsPT.customColor = style.getStrokeColor();
attrsPT.customColorPaint.setStrokeWidth(attrsPT.paint2.getStrokeWidth());
attrsPT.drawPath(canvas, path);
attrsPT.customColorPaint.setStrokeWidth(attrsPT.paint.getStrokeWidth());
attrsPT.customColor = style.getColor();
attrsPT.drawPath(canvas, path);
} else {
super.drawPath(canvas, path, style);
}
}
@Override
public void drawArrowsOverPath(Canvas canvas, RotatedTileBox tb, List<Float> tx, List<Float> ty,
List<Double> angles, List<Double> distances, double distPixToFinish, List<GeometryWayStyle<?>> styles) {
PublicTransportGeometryWayContext context = getContext();
List<PathPoint> arrows = new ArrayList<>();
List<PathAnchor> anchors = new ArrayList<>();
List<PathTransportStop> stops = new ArrayList<>();
Set<GeometryTransportWayStyle> transportWaysStyles = new HashSet<>();
GeometryAnchorWayStyle anchorWayStyle = new GeometryAnchorWayStyle(context);
int h = tb.getPixHeight();
int w = tb.getPixWidth();
int left = -w / 4;
int right = w + w / 4;
int top = - h/4;
int bottom = h + h/4;
boolean hasStyles = styles != null && styles.size() == tx.size();
double zoomCoef = tb.getZoomAnimation() > 0 ? (Math.pow(2, tb.getZoomAnimation() + tb.getZoomFloatPart())) : 1f;
Bitmap arrow = context.getArrowBitmap();
int arrowHeight = arrow.getHeight();
double pxStep = arrowHeight * 4f * zoomCoef;
double pxStepRegular = arrowHeight * 4f * zoomCoef;
double dist = 0;
if (distPixToFinish != 0) {
dist = distPixToFinish - pxStep * ((int) (distPixToFinish / pxStep)); // dist < 1
}
GeometryWayStyle<?> prevStyle = null;
for (int i = tx.size() - 2; i >= 0; i --) {
GeometryWayStyle<?> style = hasStyles ? styles.get(i) : null;
float px = tx.get(i);
float py = ty.get(i);
float x = tx.get(i + 1);
float y = ty.get(i + 1);
double distSegment = distances.get(i + 1);
double angle = angles.get(i + 1);
if (distSegment == 0) {
continue;
}
pxStep = style != null ? style.getPointStepPx(zoomCoef) : pxStepRegular;
boolean transportStyle = style instanceof GeometryTransportWayStyle;
if (style != null && !style.equals(prevStyle) && (prevStyle != null || transportStyle)) {
prevStyle = style;
anchors.add(new PathAnchor(x, y, anchorWayStyle));
dist = 0;
}
if (transportStyle) {
transportWaysStyles.add((GeometryTransportWayStyle) style);
}
if (dist >= pxStep) {
dist = 0;
}
double percent = 1 - (pxStep - dist) / distSegment;
dist += distSegment;
while (dist >= pxStep) {
double pdx = (x - px) * percent;
double pdy = (y - py) * percent;
float iconx = (float) (px + pdx);
float icony = (float) (py + pdy);
if (GeometryWay.isIn(iconx, icony, left, top, right, bottom)) {
arrows.add(new PathPoint(iconx, icony, angle, style));
}
dist -= pxStep;
percent -= pxStep / distSegment;
}
}
List<TransportStop> routeTransportStops = new ArrayList<>();
for (GeometryTransportWayStyle style : transportWaysStyles) {
List<TransportStop> transportStops = style.getRoute().getForwardStops();
TransportRouteResultSegment segment = style.getSegment();
int start = segment.start;
int end = segment.end;
for (int i = start; i <= end; i++) {
TransportStop stop = transportStops.get(i);
double lat = stop.getLocation().getLatitude();
double lon = stop.getLocation().getLongitude();
float x = tb.getPixXFromLatLon(lat, lon);
float y = tb.getPixYFromLatLon(lat, lon);
if (GeometryWay.isIn(x, y, left, top, right, bottom)) {
if (i != start && i != end) {
stops.add(new PathTransportStop(x, y, style));
}
routeTransportStops.add(transportStops.get(i));
}
}
}
this.routeTransportStops = routeTransportStops;
for (int i = arrows.size() - 1; i >= 0; i--) {
PathPoint a = arrows.get(i);
if (!tb.isZoomAnimated() || a.style.isVisibleWhileZooming()) {
a.draw(canvas, context);
}
}
for (int i = anchors.size() - 1; i >= 0; i--) {
PathAnchor anchor = anchors.get(i);
anchor.draw(canvas, context);
}
if (stops.size() > 0) {
QuadTree<QuadRect> boundIntersections = OsmandMapLayer.initBoundIntersections(tb);
List<PathTransportStop> fullObjects = new ArrayList<>();
Bitmap stopBitmap = null;
float iconSize = 1f;
for (int i = stops.size() - 1; i >= 0; i--) {
PathTransportStop stop = stops.get(i);
if (stopBitmap == null) {
stopBitmap = stop.getTransportWayStyle().getStopBitmap();
iconSize = stopBitmap.getWidth() * 3 / 2.5f;
}
float x = stop.x;
float y = stop.y;
if (OsmandMapLayer.intersects(boundIntersections, x, y, iconSize, iconSize)) {
stop.setSmallPoint(true);
stop.draw(canvas, context);
} else {
stop.setSmallPoint(false);
fullObjects.add(stop);
}
}
for (PathTransportStop stop : fullObjects) {
stop.draw(canvas, context);
}
}
}
public static class PathAnchor extends PathPoint {
PathAnchor(float x, float y, GeometryAnchorWayStyle style) {
super(x, y, 0, style);
}
}
public static class PathTransportStop extends PathPoint {
private boolean smallPoint;
public boolean isSmallPoint() {
return smallPoint;
}
public void setSmallPoint(boolean smallPoint) {
this.smallPoint = smallPoint;
}
PathTransportStop(float x, float y, GeometryTransportWayStyle style) {
super(x, y, 0, style);
}
GeometryTransportWayStyle getTransportWayStyle() {
return (GeometryTransportWayStyle) style;
}
@Override
void draw(Canvas canvas, GeometryWayContext context) {
Bitmap stopBitmap = smallPoint ?
getTransportWayStyle().getStopSmallBitmap() : getTransportWayStyle().getStopBitmap();
float paintH2 = stopBitmap.getHeight() / 2f;
float paintW2 = stopBitmap.getWidth() / 2f;
Matrix matrix = getMatrix();
matrix.reset();
matrix.postRotate(0f, paintW2, paintH2);
matrix.postTranslate(x - paintW2, y - paintH2);
Paint paint = context.getPaintIconCustom();
paint.setColorFilter(null);
canvas.drawBitmap(stopBitmap, matrix, paint);
}
}
}

View file

@ -0,0 +1,12 @@
package net.osmand.plus.views.layers.geometry;
public abstract class PublicTransportGeometryWayStyle extends GeometryWayStyle<PublicTransportGeometryWayContext> {
public PublicTransportGeometryWayStyle(PublicTransportGeometryWayContext context) {
super(context);
}
public PublicTransportGeometryWayStyle(PublicTransportGeometryWayContext context, Integer color) {
super(context, color);
}
}

View file

@ -0,0 +1,73 @@
package net.osmand.plus.views.layers.geometry;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import net.osmand.Location;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.routing.RouteCalculationResult;
import net.osmand.plus.routing.RoutingHelper;
import java.util.Collections;
import java.util.List;
public class RouteGeometryWay extends GeometryWay<RouteGeometryWayContext, GeometryWayDrawer<RouteGeometryWayContext>> {
private RoutingHelper helper;
private RouteCalculationResult route;
public RouteGeometryWay(RouteGeometryWayContext context) {
super(context, new GeometryWayDrawer<>(context));
this.helper = context.getApp().getRoutingHelper();
}
@NonNull
@Override
public GeometryWayStyle<RouteGeometryWayContext> getDefaultWayStyle() {
return new GeometrySolidWayStyle(getContext(), getContext().getAttrs().paint.getColor());
}
public void updateRoute(RotatedTileBox tb, RouteCalculationResult route) {
if (tb.getMapDensity() != getMapDensity() || this.route != route) {
this.route = route;
List<Location> locations = route != null ? route.getImmutableAllLocations() : Collections.<Location>emptyList();
updateWay(locations, tb);
}
}
public void clearRoute() {
if (route != null) {
route = null;
clearWay();
}
}
@Override
public Location getNextVisiblePoint() {
return helper.getRoute().getCurrentStraightAnglePoint();
}
private static class GeometrySolidWayStyle extends GeometryWayStyle<RouteGeometryWayContext> {
GeometrySolidWayStyle(RouteGeometryWayContext context, Integer color) {
super(context, color);
}
@Override
public Bitmap getPointBitmap() {
return getContext().getArrowBitmap();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!super.equals(other)) {
return false;
}
return other instanceof GeometrySolidWayStyle;
}
}
}

View file

@ -0,0 +1,17 @@
package net.osmand.plus.views.layers.geometry;
import android.content.Context;
import net.osmand.plus.R;
public class RouteGeometryWayContext extends GeometryWayContext {
public RouteGeometryWayContext(Context ctx, float density) {
super(ctx, density);
}
@Override
protected int getArrowBitmapResId() {
return R.drawable.map_route_direction_arrow;
}
}

View file

@ -67,7 +67,7 @@ import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.OsmandSettings.RulerMode;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.RulerControlLayer;
import net.osmand.plus.views.layers.RulerControlLayer;
import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget;
import net.osmand.render.RenderingRuleSearchRequest;
import net.osmand.render.RenderingRulesStorage;

View file

@ -24,8 +24,8 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.dialogs.ConfigureMapMenu;
import net.osmand.plus.mapmarkers.DirectionIndicationDialogFragment;
import net.osmand.plus.quickaction.QuickActionListFragment;
import net.osmand.plus.views.MapInfoLayer;
import net.osmand.plus.views.MapQuickActionLayer;
import net.osmand.plus.views.layers.MapInfoLayer;
import net.osmand.plus.views.layers.MapQuickActionLayer;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget;
import net.osmand.plus.views.mapwidgets.widgetstates.WidgetState;

View file

@ -31,7 +31,7 @@ import net.osmand.plus.search.QuickSearchListAdapter;
import net.osmand.plus.search.listitems.QuickSearchBannerListItem;
import net.osmand.plus.search.listitems.QuickSearchFreeBannerListItem;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.views.DownloadedRegionsLayer;
import net.osmand.plus.views.layers.DownloadedRegionsLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.search.core.ObjectType;
import net.osmand.search.core.SearchPhrase;