package net.osmand.plus; import android.annotation.SuppressLint; import android.content.Context; import net.osmand.Location; import net.osmand.StateChangedListener; import net.osmand.data.LatLon; import net.osmand.data.LocationPoint; import net.osmand.data.PointDescription; import net.osmand.plus.GeocodingLookupService.AddressLookupRequest; import net.osmand.plus.routing.RouteProvider.RouteService; import net.osmand.plus.routing.RoutingHelper; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; import java.util.ArrayList; import java.util.List; public class TargetPointsHelper { private List<TargetPoint> intermediatePoints = new ArrayList<>(); private TargetPoint pointToNavigate = null; private TargetPoint pointToStart = null; private OsmandSettings settings; private RoutingHelper routingHelper; private List<StateChangedListener<Void>> listeners = new ArrayList<>(); private List<TargetPointChangedListener> pointListeners = new ArrayList<>(); private OsmandApplication ctx; private AddressLookupRequest startPointRequest; private AddressLookupRequest targetPointRequest; public interface TargetPointChangedListener { void onTargetPointChanged(TargetPoint targetPoint); } public static class TargetPoint implements LocationPoint { public LatLon point; private PointDescription pointDescription; public int index; public boolean intermediate; public boolean start; public TargetPoint(LatLon point, PointDescription name) { this.point = point; this.pointDescription = name; } public TargetPoint(LatLon point, PointDescription name, int index) { this.point = point; this.pointDescription = name; this.index = index; this.intermediate = true; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TargetPoint targetPoint = (TargetPoint) o; if (start != targetPoint.start) return false; if (intermediate != targetPoint.intermediate) return false; if (index != targetPoint.index) return false; return point.equals(targetPoint.point); } @Override public int hashCode() { int result = point.hashCode(); result = 31 * result + index; result = 31 * result + (start ? 10 : 20); result = 31 * result + (intermediate ? 100 : 200); return result; } @SuppressLint("StringFormatInvalid") public PointDescription getPointDescription(Context ctx) { if (!intermediate) { return new PointDescription(PointDescription.POINT_TYPE_TARGET, ctx.getString(R.string.destination_point, ""), getOnlyName()); } else { return new PointDescription(PointDescription.POINT_TYPE_TARGET, (index + 1) + ". " + ctx.getString(R.string.intermediate_point, ""), getOnlyName()); } } public PointDescription getOriginalPointDescription() { return pointDescription; } public String getOnlyName() { return pointDescription == null ? "" : pointDescription.getName(); } public boolean isSearchingAddress(Context ctx) { return pointDescription != null && pointDescription.isSearchingAddress(ctx); } public static TargetPoint create(LatLon point, PointDescription name) { if(point != null) { return new TargetPoint(point, name); } return null; } public static TargetPoint createStartPoint(LatLon point, PointDescription name) { if (point != null) { TargetPoint target = new TargetPoint(point, name); target.start = true; return target; } return null; } public double getLatitude() { return point.getLatitude(); } public double getLongitude() { return point.getLongitude(); } @Override public int getColor() { return 0; } @Override public boolean isVisible() { return false; } } public TargetPointsHelper(OsmandApplication ctx){ this.ctx = ctx; this.settings = ctx.getSettings(); this.routingHelper = ctx.getRoutingHelper(); readFromSettings(); } public void lookupAddessAll() { lookupAddressForPointToNavigate(); lookupAddessForStartPoint(); for(TargetPoint targetPoint : intermediatePoints) { lookupAddressForIntermediatePoint(targetPoint); } } private void readFromSettings() { pointToNavigate = TargetPoint.create(settings.getPointToNavigate(), settings.getPointNavigateDescription()); pointToStart = TargetPoint.createStartPoint(settings.getPointToStart(), settings.getStartPointDescription()); intermediatePoints.clear(); List<LatLon> ips = settings.getIntermediatePoints(); List<String> desc = settings.getIntermediatePointDescriptions(ips.size()); for(int i = 0; i < ips.size(); i++) { final TargetPoint targetPoint = new TargetPoint(ips.get(i), PointDescription.deserializeFromString(desc.get(i), ips.get(i)), i); intermediatePoints.add(targetPoint); } if (!ctx.isApplicationInitializing()) { lookupAddessAll(); } } private void lookupAddressForIntermediatePoint(final TargetPoint targetPoint) { if (targetPoint != null && targetPoint.pointDescription.isSearchingAddress(ctx)) { cancelPointAddressRequests(targetPoint.point); AddressLookupRequest lookupRequest = new AddressLookupRequest(targetPoint.point, new GeocodingLookupService.OnAddressLookupResult() { @Override public void geocodingDone(String address) { for (TargetPoint p : intermediatePoints) { if (p.point.equals(targetPoint.point)) { p.pointDescription.setName(address); settings.updateIntermediatePoint(p.point.getLatitude(), p.point.getLongitude(), p.pointDescription); updateRouteAndRefresh(false); updateTargetPoint(p); break; } } } }, null); ctx.getGeocodingLookupService().lookupAddress(lookupRequest); } } private void lookupAddessForStartPoint() { if (pointToStart != null && pointToStart.isSearchingAddress(ctx) && (startPointRequest == null || !startPointRequest.getLatLon().equals(pointToStart.point))) { cancelStartPointAddressRequest(); startPointRequest = new AddressLookupRequest(pointToStart.point, new GeocodingLookupService.OnAddressLookupResult() { @Override public void geocodingDone(String address) { startPointRequest = null; if (pointToStart != null) { pointToStart.pointDescription.setName(address); settings.setPointToStart(pointToStart.point.getLatitude(), pointToStart.point.getLongitude(), pointToStart.pointDescription); updateRouteAndRefresh(false); updateTargetPoint(pointToStart); } } }, null); ctx.getGeocodingLookupService().lookupAddress(startPointRequest); } } private void lookupAddressForPointToNavigate() { if (pointToNavigate != null && pointToNavigate.isSearchingAddress(ctx) && (targetPointRequest == null || !targetPointRequest.getLatLon().equals(pointToNavigate.point))) { cancelTargetPointAddressRequest(); targetPointRequest = new AddressLookupRequest(pointToNavigate.point, new GeocodingLookupService.OnAddressLookupResult() { @Override public void geocodingDone(String address) { targetPointRequest = null; if (pointToNavigate != null) { pointToNavigate.pointDescription.setName(address); settings.setPointToNavigate(pointToNavigate.point.getLatitude(), pointToNavigate.point.getLongitude(), pointToNavigate.pointDescription); updateRouteAndRefresh(false); updateTargetPoint(pointToNavigate); } } }, null); ctx.getGeocodingLookupService().lookupAddress(targetPointRequest); } } public TargetPoint getPointToNavigate() { return pointToNavigate; } public TargetPoint getPointToStart() { return pointToStart; } public PointDescription getStartPointDescription(){ return settings.getStartPointDescription(); } public List<TargetPoint> getIntermediatePoints() { return intermediatePoints; } public List<TargetPoint> getIntermediatePointsNavigation() { List<TargetPoint> intermediatePoints = new ArrayList<>(); if (settings.USE_INTERMEDIATE_POINTS_NAVIGATION.get()) { for (TargetPoint t : this.intermediatePoints) { intermediatePoints.add(t); } } return intermediatePoints; } public List<LatLon> getIntermediatePointsLatLon() { List<LatLon> intermediatePointsLatLon = new ArrayList<>(); for (TargetPoint t : this.intermediatePoints) { intermediatePointsLatLon.add(t.point); } return intermediatePointsLatLon; } public List<LatLon> getIntermediatePointsLatLonNavigation() { List<LatLon> intermediatePointsLatLon = new ArrayList<>(); if (settings.USE_INTERMEDIATE_POINTS_NAVIGATION.get()) { for (TargetPoint t : this.intermediatePoints) { intermediatePointsLatLon.add(t.point); } } return intermediatePointsLatLon; } public List<TargetPoint> getAllPoints() { List<TargetPoint> res = new ArrayList<>(); if(pointToStart != null) { res.add(pointToStart); } res.addAll(this.intermediatePoints); if(pointToNavigate != null) { res.add(pointToNavigate); } return res; } public List<TargetPoint> getIntermediatePointsWithTarget() { List<TargetPoint> res = new ArrayList<>(); res.addAll(this.intermediatePoints); if(pointToNavigate != null) { res.add(pointToNavigate); } return res; } public TargetPoint getFirstIntermediatePoint(){ if(intermediatePoints.size() > 0) { return intermediatePoints.get(0); } return null; } public void restoreTargetPoints(boolean updateRoute) { settings.restoreTargetPoints(); readFromSettings(); updateRouteAndRefresh(updateRoute); } /** * Clear the local and persistent waypoints list and destination. */ public void removeAllWayPoints(boolean updateRoute, boolean clearBackup){ cancelStartPointAddressRequest(); cancelTargetPointAddressRequest(); cancelAllIntermediatePointsAddressRequests(); settings.clearIntermediatePoints(); settings.clearPointToNavigate(); settings.clearPointToStart(); if (clearBackup) { settings.backupTargetPoints(); } pointToNavigate = null; pointToStart = null; intermediatePoints.clear(); readFromSettings(); updateRouteAndRefresh(updateRoute); } /** * Move an intermediate waypoint to the destination. */ public void makeWayPointDestination(boolean updateRoute, int index){ TargetPoint targetPoint = intermediatePoints.remove(index); cancelTargetPointAddressRequest(); cancelPointAddressRequests(targetPoint.point); pointToNavigate = targetPoint; settings.setPointToNavigate(pointToNavigate.getLatitude(), pointToNavigate.getLongitude(), pointToNavigate.pointDescription); pointToNavigate.intermediate = false; settings.deleteIntermediatePoint(index); lookupAddressForPointToNavigate(); updateRouteAndRefresh(updateRoute); } public void removeWayPoint(boolean updateRoute, int index){ if (index < 0) { cancelTargetPointAddressRequest(); settings.clearPointToNavigate(); pointToNavigate = null; int sz = intermediatePoints.size(); if (sz > 0) { settings.deleteIntermediatePoint(sz - 1); pointToNavigate = intermediatePoints.remove(sz - 1); pointToNavigate.intermediate = false; settings.setPointToNavigate(pointToNavigate.getLatitude(), pointToNavigate.getLongitude(), pointToNavigate.pointDescription); lookupAddressForPointToNavigate(); } } else { settings.deleteIntermediatePoint(index); TargetPoint targetPoint = intermediatePoints.remove(index); cancelPointAddressRequests(targetPoint.point); int ind = 0; for(TargetPoint tp : intermediatePoints) { tp.index = ind++; } } updateRouteAndRefresh(updateRoute); } public void updateRouteAndRefresh(boolean updateRoute) { if(updateRoute && ( routingHelper.isRouteBeingCalculated() || routingHelper.isRouteCalculated() || routingHelper.isFollowingMode() || routingHelper.isRoutePlanningMode())) { updateRoutingHelper(); } updateListeners(); } private void updateRoutingHelper() { LatLon start = settings.getPointToStart(); Location lastKnownLocation = ctx.getLocationProvider().getLastKnownLocation(); List<LatLon> is = getIntermediatePointsLatLonNavigation(); if((routingHelper.isFollowingMode() && lastKnownLocation != null) || start == null) { routingHelper.setFinalAndCurrentLocation(settings.getPointToNavigate(), is, lastKnownLocation); } else { Location loc = wrap(start); routingHelper.setFinalAndCurrentLocation(settings.getPointToNavigate(), is, loc); } } private Location wrap(LatLon l) { if(l == null) { return null; } Location loc = new Location("map"); loc.setLatitude(l.getLatitude()); loc.setLongitude(l.getLongitude()); return loc; } private Location wrap(TargetPoint l) { if(l == null) { return null; } Location loc = new Location("map"); loc.setLatitude(l.getLatitude()); loc.setLongitude(l.getLongitude()); return loc; } public void addListener(StateChangedListener<Void> l) { listeners.add(l); } private void updateListeners() { for(StateChangedListener<Void> l : listeners) { l.stateChanged(null); } } public void clearPointToNavigate(boolean updateRoute) { cancelTargetPointAddressRequest(); cancelAllIntermediatePointsAddressRequests(); settings.clearPointToNavigate(); settings.clearIntermediatePoints(); intermediatePoints.clear(); readFromSettings(); updateRouteAndRefresh(updateRoute); } public void clearStartPoint(boolean updateRoute) { cancelStartPointAddressRequest(); settings.clearPointToStart(); readFromSettings(); updateRouteAndRefresh(updateRoute); } public void reorderAllTargetPoints(List<TargetPoint> point, boolean updateRoute) { cancelTargetPointAddressRequest(); cancelAllIntermediatePointsAddressRequests(); settings.clearPointToNavigate(); if (point.size() > 0) { List<TargetPoint> subList = point.subList(0, point.size() - 1); ArrayList<String> names = new ArrayList<>(subList.size()); ArrayList<LatLon> ls = new ArrayList<>(subList.size()); for(int i = 0; i < subList.size(); i++) { names.add(PointDescription.serializeToString(subList.get(i).pointDescription)); ls.add(subList.get(i).point); } settings.saveIntermediatePoints(ls, names); TargetPoint p = point.get(point.size() - 1); settings.setPointToNavigate(p.getLatitude(), p.getLongitude(), p.pointDescription); } else { settings.clearIntermediatePoints(); } readFromSettings(); updateRouteAndRefresh(updateRoute); } public boolean hasTooLongDistanceToNavigate() { if(settings.ROUTER_SERVICE.get() != RouteService.OSMAND) { return false; } Location current = routingHelper.getLastProjection(); double dist = 400000; if (ApplicationMode.BICYCLE.isDerivedRoutingFrom(routingHelper.getAppMode()) && settings.getCustomRoutingBooleanProperty("height_obstacles", false).getModeValue(routingHelper.getAppMode())) { dist = 50000; } List<TargetPoint> list = getIntermediatePointsWithTarget(); if(!list.isEmpty()) { if(current != null && MapUtils.getDistance(list.get(0).point, current.getLatitude(), current.getLongitude()) > dist) { return true; } for(int i = 1; i < list.size(); i++) { if(MapUtils.getDistance(list.get(i-1).point, list.get(i).point) > dist) { return true; } } } return false; } public void navigateToPoint(LatLon point, boolean updateRoute, int intermediate){ navigateToPoint(point, updateRoute, intermediate, null); } public void navigateToPoint(final LatLon point, boolean updateRoute, int intermediate, PointDescription historyName){ if(point != null){ final PointDescription pointDescription; if (historyName == null) { pointDescription = new PointDescription(PointDescription.POINT_TYPE_LOCATION, ""); } else { pointDescription = historyName; } if (pointDescription.isLocation() && Algorithms.isEmpty(pointDescription.getName())) { pointDescription.setName(PointDescription.getSearchAddressStr(ctx)); } if(intermediate < 0 || intermediate > intermediatePoints.size()) { if(intermediate > intermediatePoints.size()) { final TargetPoint pn = getPointToNavigate(); if(pn != null) { settings.insertIntermediatePoint(pn.getLatitude(), pn.getLongitude(), pn.pointDescription, intermediatePoints.size()); } } settings.setPointToNavigate(point.getLatitude(), point.getLongitude(), pointDescription); } else { settings.insertIntermediatePoint(point.getLatitude(), point.getLongitude(), pointDescription, intermediate); } } else { cancelTargetPointAddressRequest(); cancelAllIntermediatePointsAddressRequests(); settings.clearPointToNavigate(); settings.clearIntermediatePoints(); } readFromSettings(); updateRouteAndRefresh(updateRoute); } public void setStartPoint(final LatLon startPoint, boolean updateRoute, PointDescription name) { if(startPoint != null) { final PointDescription pointDescription; if (name == null) { pointDescription = new PointDescription(PointDescription.POINT_TYPE_LOCATION, ""); } else { pointDescription = name; } if (pointDescription.isLocation() && Algorithms.isEmpty(pointDescription.getName())) { pointDescription.setName(PointDescription.getSearchAddressStr(ctx)); } settings.setPointToStart(startPoint.getLatitude(), startPoint.getLongitude(), pointDescription); } else { settings.clearPointToStart(); } readFromSettings(); updateRouteAndRefresh(updateRoute); } public boolean checkPointToNavigateShort(){ if(pointToNavigate == null){ ctx.showShortToastMessage(R.string.mark_final_location_first); return false; } return true; } public Location getPointToStartLocation() { return wrap(getPointToStart()); } private void cancelStartPointAddressRequest() { if (startPointRequest != null) { ctx.getGeocodingLookupService().cancel(startPointRequest); startPointRequest = null; } } private void cancelTargetPointAddressRequest() { if (targetPointRequest != null) { ctx.getGeocodingLookupService().cancel(targetPointRequest); targetPointRequest = null; } } private void cancelAllIntermediatePointsAddressRequests() { List<LatLon> intermediatePointsLatLon = getIntermediatePointsLatLon(); for (LatLon latLon : intermediatePointsLatLon) { cancelPointAddressRequests(latLon); } } private void cancelPointAddressRequests(LatLon latLon) { if (latLon != null) { ctx.getGeocodingLookupService().cancel(latLon); } } public void addPointListener(TargetPointChangedListener l) { if (!pointListeners.contains(l)) { pointListeners.add(l); } } public void removePointListener(TargetPointChangedListener l) { pointListeners.remove(l); } private void updateTargetPoint(TargetPoint targetPoint) { for (TargetPointChangedListener l : pointListeners) { l.onTargetPointChanged(targetPoint); } } }