diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 912f1178c7..bd6c3d36f8 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -10,6 +10,7 @@ - For wording and consistency, please note https://osmand.net/help-online?id=technical-articles#Creating_a_Consistent_User_Experience Thx - Hardy --> + Public transport Select road on the map or from the list below that you want to avoid during navigation: Show along the route Simulate navigation diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index e3e071b9cd..51bd3b1aa0 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -44,6 +44,7 @@ import net.osmand.plus.render.RendererRegistry; import net.osmand.plus.resources.ResourceManager; import net.osmand.plus.routepreparationmenu.RoutingOptionsHelper; import net.osmand.plus.routing.RoutingHelper; +import net.osmand.plus.routing.TransportRoutingHelper; import net.osmand.plus.search.QuickSearchHelper; import net.osmand.plus.views.corenative.NativeCoreContext; import net.osmand.plus.voice.CommandPlayer; @@ -458,6 +459,7 @@ public class AppInitializer implements IProgress { app.applyTheme(app); app.inAppPurchaseHelper = startupInit(new InAppPurchaseHelper(app), InAppPurchaseHelper.class); app.poiTypes = startupInit(MapPoiTypes.getDefaultNoInit(), MapPoiTypes.class); + app.transportRoutingHelper = startupInit(new TransportRoutingHelper(app), TransportRoutingHelper.class); app.routingHelper = startupInit(new RoutingHelper(app), RoutingHelper.class); app.resourceManager = startupInit(new ResourceManager(app), ResourceManager.class); app.daynightHelper = startupInit(new DayNightHelper(app), DayNightHelper.class); diff --git a/OsmAnd/src/net/osmand/plus/ApplicationMode.java b/OsmAnd/src/net/osmand/plus/ApplicationMode.java index dac0b648ab..7c26428b85 100644 --- a/OsmAnd/src/net/osmand/plus/ApplicationMode.java +++ b/OsmAnd/src/net/osmand/plus/ApplicationMode.java @@ -57,6 +57,9 @@ public class ApplicationMode { public static final ApplicationMode TRAIN = create(R.string.app_mode_train, "train").speed(25f, 40). carLocation().icon(R.drawable.map_action_train, R.drawable.ic_action_train).reg(); + public static final ApplicationMode PUBLIC_TRANSPORT = create(R.string.app_mode_public_transport, "public_transport"). + icon(R.drawable.map_action_bus_dark, R.drawable.ic_action_bus_dark).reg(); + static { ApplicationMode[] exceptDefault = new ApplicationMode[]{CAR, PEDESTRIAN, BICYCLE, BOAT, AIRCRAFT, BUS, TRAIN}; ApplicationMode[] exceptPedestrianAndDefault = new ApplicationMode[]{CAR, BICYCLE, BOAT, AIRCRAFT, BUS, TRAIN}; diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index 55748624a2..4be50587b7 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -57,6 +57,7 @@ import net.osmand.plus.render.RendererRegistry; import net.osmand.plus.resources.ResourceManager; import net.osmand.plus.routepreparationmenu.RoutingOptionsHelper; import net.osmand.plus.routing.RoutingHelper; +import net.osmand.plus.routing.TransportRoutingHelper; import net.osmand.plus.search.QuickSearchHelper; import net.osmand.plus.voice.CommandPlayer; import net.osmand.plus.wikivoyage.data.TravelDbHelper; @@ -106,6 +107,7 @@ public class OsmandApplication extends MultiDexApplication { PoiFiltersHelper poiFilters; MapPoiTypes poiTypes; RoutingHelper routingHelper; + TransportRoutingHelper transportRoutingHelper; FavouritesDbHelper favorites; CommandPlayer player; GpxSelectionHelper selectedGpxHelper; @@ -402,6 +404,10 @@ public class OsmandApplication extends MultiDexApplication { return routingHelper; } + public TransportRoutingHelper getTransportRoutingHelper() { + return transportRoutingHelper; + } + public RoutingOptionsHelper getRoutingOptionsHelper() { return routingOptionsHelper; } diff --git a/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java b/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java index 4abacf44ed..c49e349c54 100644 --- a/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java +++ b/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java @@ -379,8 +379,8 @@ public class TargetPointsHelper { } public void updateRouteAndRefresh(boolean updateRoute) { - if(updateRoute && ( routingHelper.isRouteBeingCalculated() || routingHelper.isRouteCalculated() || - routingHelper.isFollowingMode() || routingHelper.isRoutePlanningMode())) { + if(updateRoute && (routingHelper.isPublicTransportMode() || routingHelper.isRouteBeingCalculated() || + routingHelper.isRouteCalculated() || routingHelper.isFollowingMode() || routingHelper.isRoutePlanningMode())) { updateRoutingHelper(); } updateListeners(); @@ -388,15 +388,14 @@ public class TargetPointsHelper { private void updateRoutingHelper() { LatLon start = settings.getPointToStart(); - Location lastKnownLocation = ctx.getLocationProvider().getLastKnownLocation(); + LatLon finish = settings.getPointToNavigate(); List is = getIntermediatePointsLatLonNavigation(); + Location lastKnownLocation = ctx.getLocationProvider().getLastKnownLocation(); if((routingHelper.isFollowingMode() && lastKnownLocation != null) || start == null) { - routingHelper.setFinalAndCurrentLocation(settings.getPointToNavigate(), - is, lastKnownLocation); + routingHelper.setFinalAndCurrentLocation(finish, is, lastKnownLocation); } else { Location loc = wrap(start); - routingHelper.setFinalAndCurrentLocation(settings.getPointToNavigate(), - is, loc); + routingHelper.setFinalAndCurrentLocation(finish, is, loc); } } diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 86168b5947..155522dc84 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -110,9 +110,11 @@ import net.osmand.plus.measurementtool.MeasurementToolFragment; import net.osmand.plus.measurementtool.NewGpxData; import net.osmand.plus.render.RendererRegistry; import net.osmand.plus.resources.ResourceManager; +import net.osmand.plus.routing.IRouteInformationListener; import net.osmand.plus.routing.RoutingHelper; -import net.osmand.plus.routing.RoutingHelper.IRouteInformationListener; import net.osmand.plus.routing.RoutingHelper.RouteCalculationProgressCallback; +import net.osmand.plus.routing.TransportRoutingHelper; +import net.osmand.plus.routing.TransportRoutingHelper.TransportRouteCalculationProgressCallback; import net.osmand.plus.search.QuickSearchDialogFragment; import net.osmand.plus.search.QuickSearchDialogFragment.QuickSearchTab; import net.osmand.plus.search.QuickSearchDialogFragment.QuickSearchType; @@ -439,7 +441,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven private void createProgressBarForRouting() { final ProgressBar pb = (ProgressBar) findViewById(R.id.map_horizontal_progress); - app.getRoutingHelper().setProgressBar(new RouteCalculationProgressCallback() { + final RouteCalculationProgressCallback progressCallback = new RouteCalculationProgressCallback() { @Override public void start() { @@ -501,6 +503,25 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven dashboardOnMap.routeCalculationFinished(); pb.setVisibility(View.GONE); } + }; + + app.getRoutingHelper().setProgressBar(progressCallback); + + app.getTransportRoutingHelper().setProgressBar(new TransportRouteCalculationProgressCallback() { + @Override + public void start() { + progressCallback.start(); + } + + @Override + public void updateProgress(int progress) { + progressCallback.updateProgress(progress); + } + + @Override + public void finish() { + progressCallback.finish(); + } }); } diff --git a/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java b/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java index e655960eea..af25adabeb 100644 --- a/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java +++ b/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java @@ -25,7 +25,7 @@ import net.osmand.plus.R; import net.osmand.plus.dashboard.DashboardOnMap; import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.routing.RoutingHelper; -import net.osmand.plus.routing.RoutingHelper.IRouteInformationListener; +import net.osmand.plus.routing.IRouteInformationListener; import net.osmand.plus.views.AnimateDraggingMapThread; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.util.MapUtils; diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java index 9704c9f38d..bd395ee44c 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java @@ -80,7 +80,7 @@ import net.osmand.plus.mapillary.MapillaryPlugin.MapillaryFirstDialogFragment; import net.osmand.plus.osmedit.OsmNotesMenu; import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.routing.RoutingHelper; -import net.osmand.plus.routing.RoutingHelper.IRouteInformationListener; +import net.osmand.plus.routing.IRouteInformationListener; import net.osmand.plus.srtmplugin.ContourLinesMenu; import net.osmand.plus.srtmplugin.HillshadeMenu; import net.osmand.plus.srtmplugin.SRTMPlugin; diff --git a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java index 8e3e85ae94..a5ed90a0d9 100644 --- a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java +++ b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java @@ -108,7 +108,8 @@ public class ResourceManager { TRANSPORT, ADDRESS, QUICK_SEARCH, - ROUTING + ROUTING, + TRANSPORT_ROUTING } public static class BinaryMapReaderResource { @@ -998,7 +999,20 @@ public class ResourceManager { } return readers.toArray(new BinaryMapIndexReader[readers.size()]); } - + + public BinaryMapIndexReader[] getTransportRoutingMapFiles() { + List readers = new ArrayList<>(fileReaders.size()); + for(BinaryMapReaderResource r : fileReaders.values()) { + if(r.isUseForRouting()) { + BinaryMapIndexReader reader = r.getReader(BinaryMapReaderResourceType.TRANSPORT_ROUTING); + if (reader != null) { + readers.add(reader); + } + } + } + return readers.toArray(new BinaryMapIndexReader[readers.size()]); + } + public BinaryMapIndexReader[] getQuickSearchFiles() { List readers = new ArrayList<>(fileReaders.size()); for(BinaryMapReaderResource r : fileReaders.values()) { diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/MapRouteInfoMenu.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/MapRouteInfoMenu.java index e8e53147b3..ace98d95fb 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/MapRouteInfoMenu.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/MapRouteInfoMenu.java @@ -70,7 +70,7 @@ import net.osmand.plus.mapmarkers.MapMarkerSelectionFragment; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.routing.RouteDirectionInfo; import net.osmand.plus.routing.RoutingHelper; -import net.osmand.plus.routing.RoutingHelper.IRouteInformationListener; +import net.osmand.plus.routing.IRouteInformationListener; import net.osmand.plus.views.MapControlsLayer; import net.osmand.router.GeneralRouter; diff --git a/OsmAnd/src/net/osmand/plus/routing/IRouteInformationListener.java b/OsmAnd/src/net/osmand/plus/routing/IRouteInformationListener.java new file mode 100644 index 0000000000..917e2081c5 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/routing/IRouteInformationListener.java @@ -0,0 +1,12 @@ +package net.osmand.plus.routing; + +import net.osmand.ValueHolder; + +public interface IRouteInformationListener { + + void newRouteIsCalculated(boolean newRoute, ValueHolder showToast); + + void routeWasCancelled(); + + void routeWasFinished(); +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java b/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java index df11e56b6e..9a5f4abe91 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java +++ b/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java @@ -36,21 +36,12 @@ public class RoutingHelper { private static final org.apache.commons.logging.Log log = PlatformUtil.getLog(RoutingHelper.class); - public interface IRouteInformationListener { - - void newRouteIsCalculated(boolean newRoute, ValueHolder showToast); - - void routeWasCancelled(); - - void routeWasFinished(); - } - private static final float POSITION_TOLERANCE = 60; - - private List> listeners = new LinkedList>(); + private List> listeners = new LinkedList<>(); private OsmandApplication app; + private TransportRoutingHelper transportRoutingHelper; private boolean isFollowingMode = false; private boolean isRoutePlanningMode = false; @@ -99,9 +90,13 @@ public class RoutingHelper { settings = context.getSettings(); voiceRouter = new VoiceRouter(this, settings); provider = new RouteProvider(); + transportRoutingHelper = context.getTransportRoutingHelper(); setAppMode(settings.APPLICATION_MODE.get()); } + public TransportRoutingHelper getTransportRoutingHelper() { + return transportRoutingHelper; + } public boolean isFollowingMode() { return isFollowingMode; @@ -160,8 +155,6 @@ public class RoutingHelper { this.isRoutePlanningMode = isRoutePlanningMode; } - - public synchronized void setFinalAndCurrentLocation(LatLon finalLocation, List intermediatePoints, Location currentLocation){ RouteCalculationResult previousRoute = route; clearCurrentRoute(finalLocation, intermediatePoints); @@ -201,6 +194,7 @@ public class RoutingHelper { this.lastProjection = null; setFollowingMode(false); } + transportRoutingHelper.clearCurrentRoute(newFinalLocation); } private synchronized void finishCurrentRoute() { @@ -265,7 +259,8 @@ public class RoutingHelper { } public void addListener(IRouteInformationListener l){ - listeners.add(new WeakReference(l)); + listeners.add(new WeakReference<>(l)); + transportRoutingHelper.addListener(l); } public boolean removeListener(IRouteInformationListener lt){ @@ -278,6 +273,7 @@ public class RoutingHelper { return true; } } + transportRoutingHelper.removeListener(lt); return false; } @@ -305,7 +301,11 @@ public class RoutingHelper { private Location setCurrentLocation(Location currentLocation, boolean returnUpdatedLocation, RouteCalculationResult previousRoute, boolean targetPointsChanged) { Location locationProjection = currentLocation; - if (finalLocation == null || currentLocation == null) { + if (isPublicTransportMode() && currentLocation != null) { + transportRoutingHelper.setFinalAndCurrentLocation(finalLocation, + new LatLon(currentLocation.getLatitude(), currentLocation.getLongitude())); + } + if (finalLocation == null || currentLocation == null || isPublicTransportMode()) { isDeviatedFromRoute = false; return locationProjection; } @@ -567,7 +567,7 @@ public class RoutingHelper { private boolean identifyUTurnIsNeeded(Location currentLocation, float posTolerance) { - if (finalLocation == null || currentLocation == null || !route.isCalculated()) { + if (finalLocation == null || currentLocation == null || !route.isCalculated() || isPublicTransportMode()) { return false; } boolean isOffRoute = false; @@ -664,7 +664,7 @@ public class RoutingHelper { app.runInUIThread(new Runnable() { @Override public void run() { - ValueHolder showToast = new ValueHolder(); + ValueHolder showToast = new ValueHolder<>(); showToast.value = true; Iterator> it = listeners.iterator(); while (it.hasNext()) { @@ -937,7 +937,18 @@ public class RoutingHelper { public void recalculateRouteDueToSettingsChange() { clearCurrentRoute(finalLocation, intermediatePoints); - recalculateRouteInBackground(lastFixedLocation, finalLocation, intermediatePoints, currentGPXRoute, route, true, false); + if (isPublicTransportMode()) { + Location start = lastFixedLocation; + LatLon finish = finalLocation; + if (start != null && finish != null) { + transportRoutingHelper.setFinalAndCurrentLocation(finish, + new LatLon(start.getLatitude(), start.getLongitude())); + } else { + transportRoutingHelper.recalculateRouteDueToSettingsChange(); + } + } else { + recalculateRouteInBackground(lastFixedLocation, finalLocation, intermediatePoints, currentGPXRoute, route, true, false); + } } private void recalculateRouteInBackground(final Location start, final LatLon end, final List intermediates, @@ -1060,6 +1071,9 @@ public class RoutingHelper { void finish(); } + public boolean isPublicTransportMode() { + return mode == ApplicationMode.PUBLIC_TRANSPORT; + } public boolean isRouteBeingCalculated(){ return currentRunningJob instanceof RouteRecalculationThread; diff --git a/OsmAnd/src/net/osmand/plus/routing/TransportRoutingHelper.java b/OsmAnd/src/net/osmand/plus/routing/TransportRoutingHelper.java new file mode 100644 index 0000000000..47c73a0ca3 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/routing/TransportRoutingHelper.java @@ -0,0 +1,358 @@ +package net.osmand.plus.routing; + +import android.support.annotation.NonNull; + +import net.osmand.PlatformUtil; +import net.osmand.ValueHolder; +import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.data.LatLon; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.R; +import net.osmand.plus.routing.RouteProvider.RouteService; +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.TransportRoutePlanner.TransportRoutingContext; +import net.osmand.router.TransportRoutingConfiguration; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import static net.osmand.plus.notifications.OsmandNotification.NotificationType.NAVIGATION; + +public class TransportRoutingHelper { + + private static final org.apache.commons.logging.Log log = PlatformUtil.getLog(TransportRoutingHelper.class); + + private List> listeners = new LinkedList<>(); + + private OsmandApplication app; + + private List routes; + private int currentRoute; + + private LatLon startLocation; + private LatLon endLocation; + private boolean useSchedule; + + private Thread currentRunningJob; + private String lastRouteCalcError; + private String lastRouteCalcErrorShort; + private long lastTimeEvaluatedRoute = 0; + + private TransportRouteCalculationProgressCallback progressRoute; + + public TransportRoutingHelper(@NonNull OsmandApplication app) { + this.app = app; + } + + public LatLon getStartLocation() { + return startLocation; + } + + public LatLon getEndLocation() { + return endLocation; + } + + public boolean isUseSchedule() { + return useSchedule; + } + + public void setUseSchedule(boolean useSchedule) { + this.useSchedule = useSchedule; + } + + public int getCurrentRoute() { + return currentRoute; + } + + public List getRoutes() { + return routes; + } + + public void setCurrentRoute(int currentRoute) { + this.currentRoute = currentRoute; + } + + public void addListener(IRouteInformationListener l){ + listeners.add(new WeakReference<>(l)); + } + + public boolean removeListener(IRouteInformationListener lt){ + Iterator> it = listeners.iterator(); + while(it.hasNext()) { + WeakReference ref = it.next(); + IRouteInformationListener l = ref.get(); + if(l == null || lt == l) { + it.remove(); + return true; + } + } + return false; + } + + public void recalculateRouteDueToSettingsChange() { + clearCurrentRoute(endLocation); + recalculateRouteInBackground(startLocation, endLocation); + } + + private void recalculateRouteInBackground(LatLon start, LatLon end) { + if (start == null || end == null) { + return; + } + TransportRouteCalculationParams params = new TransportRouteCalculationParams(); + params.start = start; + params.end = end; + params.useSchedule = useSchedule; + params.type = RouteService.OSMAND; + params.ctx = app; + params.calculationProgress = new RouteCalculationProgress(); + + startRouteCalculationThread(params); + } + + private void startRouteCalculationThread(TransportRouteCalculationParams params) { + synchronized (this) { + final Thread prevRunningJob = currentRunningJob; + RouteRecalculationThread newThread = + new RouteRecalculationThread("Calculating public transport route", params); + currentRunningJob = newThread; + startProgress(params); + updateProgress(params); + if (prevRunningJob != null) { + newThread.setWaitPrevJob(prevRunningJob); + } + currentRunningJob.start(); + } + } + + public void setProgressBar(TransportRouteCalculationProgressCallback progressRoute) { + this.progressRoute = progressRoute; + } + + private void startProgress(final TransportRouteCalculationParams params) { + final TransportRouteCalculationProgressCallback progressRoute = this.progressRoute; + if (progressRoute != null) { + progressRoute.start(); + } + } + + private void updateProgress(final TransportRouteCalculationParams params) { + final TransportRouteCalculationProgressCallback progressRoute = this.progressRoute; + if (progressRoute != null) { + app.runInUIThread(new Runnable() { + + @Override + public void run() { + RouteCalculationProgress calculationProgress = params.calculationProgress; + if (isRouteBeingCalculated()) { + float pr = calculationProgress.getLinearProgress(); + progressRoute.updateProgress((int) pr); + Thread t = currentRunningJob; + if (t instanceof RouteRecalculationThread && ((RouteRecalculationThread) t).params != params) { + // different calculation started + } else { + updateProgress(params); + } + } else { + progressRoute.finish(); + } + } + }, 300); + } + } + + public boolean isRouteBeingCalculated() { + return currentRunningJob instanceof RouteRecalculationThread; + } + + private void setNewRoute(final List res) { + app.runInUIThread(new Runnable() { + @Override + public void run() { + ValueHolder showToast = new ValueHolder<>(); + showToast.value = true; + Iterator> it = listeners.iterator(); + while (it.hasNext()) { + WeakReference ref = it.next(); + IRouteInformationListener l = ref.get(); + if (l == null) { + it.remove(); + } else { + l.newRouteIsCalculated(true, showToast); + } + } + if (showToast.value && OsmandPlugin.isDevelopment()) { + String msg = "Public transport routes calculated: " + res.size(); + app.showToastMessage(msg); + } + } + }); + } + + public synchronized void setFinalAndCurrentLocation(LatLon finalLocation, LatLon currentLocation) { + clearCurrentRoute(finalLocation); + // to update route + setCurrentLocation(currentLocation); + } + + public synchronized void clearCurrentRoute(LatLon newFinalLocation) { + routes = null; + app.getWaypointHelper().setNewRoute(new RouteCalculationResult("")); + app.runInUIThread(new Runnable() { + @Override + public void run() { + Iterator> it = listeners.iterator(); + while (it.hasNext()) { + WeakReference ref = it.next(); + IRouteInformationListener l = ref.get(); + if (l == null) { + it.remove(); + } else { + l.routeWasCancelled(); + } + } + } + }); + this.endLocation = newFinalLocation; + if (currentRunningJob instanceof RouteRecalculationThread) { + ((RouteRecalculationThread) currentRunningJob).stopCalculation(); + } + } + + private void setCurrentLocation(LatLon currentLocation) { + if (endLocation == null || currentLocation == null) { + return; + } + startLocation = currentLocation; + recalculateRouteInBackground(currentLocation, endLocation); + } + + private void showMessage(final String msg) { + app.runInUIThread(new Runnable() { + @Override + public void run() { + app.showToastMessage(msg); + } + }); + } + + public interface TransportRouteCalculationProgressCallback { + + void start(); + + void updateProgress(int progress); + + void finish(); + } + + public static class TransportRouteCalculationParams { + + public LatLon start; + public LatLon end; + + public OsmandApplication ctx; + public RouteService type; + public boolean useSchedule; + public RouteCalculationProgress calculationProgress; + public TransportRouteCalculationResultListener resultListener; + + public interface TransportRouteCalculationResultListener { + void onRouteCalculated(List route); + } + } + + private class RouteRecalculationThread extends Thread { + + private final TransportRouteCalculationParams params; + private Thread prevRunningJob; + + public RouteRecalculationThread(String name, TransportRouteCalculationParams params) { + super(name); + this.params = params; + if (params.calculationProgress == null) { + params.calculationProgress = new RouteCalculationProgress(); + } + } + + public void stopCalculation() { + params.calculationProgress.isCancelled = true; + } + + private List calculateRouteImpl(TransportRouteCalculationParams params) throws IOException { + RoutingConfiguration.Builder config = params.ctx.getDefaultRoutingConfig(); + BinaryMapIndexReader[] files = params.ctx.getResourceManager().getTransportRoutingMapFiles(); + + TransportRoutingConfiguration cfg = new TransportRoutingConfiguration(config); + cfg.useSchedule = params.useSchedule; + TransportRoutePlanner planner = new TransportRoutePlanner(); + TransportRoutingContext ctx = new TransportRoutingContext(cfg, files); + return planner.buildRoute(ctx, params.start, params.end); + } + + @Override + public void run() { + synchronized (TransportRoutingHelper.this) { + currentRunningJob = this; + } + if (prevRunningJob != null) { + while (prevRunningJob.isAlive()) { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + // ignore + } + } + synchronized (TransportRoutingHelper.this) { + currentRunningJob = this; + } + } + + List res = null; + String error = null; + try { + res = calculateRouteImpl(params); + } catch (IOException e) { + error = e.getMessage(); + log.error(e); + } + if (params.calculationProgress.isCancelled) { + synchronized (TransportRoutingHelper.this) { + currentRunningJob = null; + } + return; + } + synchronized (TransportRoutingHelper.this) { + routes = res; + if (res != null) { + if (params.resultListener != null) { + params.resultListener.onRouteCalculated(res); + } + } + currentRunningJob = null; + } + if (res != null) { + setNewRoute(res); + } else if (error != null) { + lastRouteCalcError = app.getString(R.string.error_calculating_route) + ":\n" + error; + lastRouteCalcErrorShort = app.getString(R.string.error_calculating_route); + showMessage(lastRouteCalcError); + } else { + lastRouteCalcError = app.getString(R.string.empty_route_calculated); + lastRouteCalcErrorShort = app.getString(R.string.empty_route_calculated); + showMessage(lastRouteCalcError); + } + app.getNotificationHelper().refreshNotification(NAVIGATION); + lastTimeEvaluatedRoute = System.currentTimeMillis(); + } + + public void setWaitPrevJob(Thread prevRunningJob) { + this.prevRunningJob = prevRunningJob; + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/views/OsmandMapLayer.java b/OsmAnd/src/net/osmand/plus/views/OsmandMapLayer.java index 767e0cd10e..e024b0dfd4 100644 --- a/OsmAnd/src/net/osmand/plus/views/OsmandMapLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/OsmandMapLayer.java @@ -13,6 +13,7 @@ import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.os.AsyncTask; import android.support.annotation.NonNull; +import android.util.Pair; import android.view.MotionEvent; import net.osmand.binary.BinaryMapIndexReader; @@ -137,14 +138,25 @@ public abstract class OsmandMapLayer { return x >= lx && x <= rx && y >= ty && y <= by; } - public int calculatePath(RotatedTileBox tb, TIntArrayList xs, TIntArrayList ys, Path path) { + List> paths = new ArrayList<>(); + int res = calculatePath(tb, xs, ys, null, paths); + if (paths.size() > 0) { + path.addPath(paths.get(0).first); + } + return res; + } + + public int calculatePath(RotatedTileBox tb, TIntArrayList xs, TIntArrayList ys, List colors, List> paths) { boolean segmentStarted = false; int prevX = xs.get(0); int prevY = ys.get(0); int height = tb.getPixHeight(); int width = tb.getPixWidth(); int cnt = 0; + boolean hasColors = colors != null && colors.size() == xs.size(); + int color = hasColors ? colors.get(0) : 0; + Path path = new Path(); boolean prevIn = isIn(prevX, prevY, 0, 0, width, height); for (int i = 1; i < xs.size(); i++) { int currX = xs.get(i); @@ -186,6 +198,21 @@ public abstract class OsmandMapLayer { prevIn = currIn; prevX = currX; prevY = currY; + + if (hasColors) { + int newColor = colors.get(i); + if (color != newColor) { + paths.add(new Pair<>(path, color)); + path = new Path(); + if (segmentStarted) { + path.moveTo(currX, currY); + } + color = newColor; + } + } + } + if (!path.isEmpty()) { + paths.add(new Pair<>(path, color)); } return cnt; } @@ -383,6 +410,8 @@ public abstract class OsmandMapLayer { protected static class RenderingLineAttributes { protected int cachedHash; public Paint paint; + public Paint customColorPaint; + public int customColor = 0; public int defaultWidth = 0; public int defaultColor = 0; public boolean isPaint2; @@ -402,6 +431,7 @@ public abstract class OsmandMapLayer { public RenderingLineAttributes(String renderingAttribute) { this.renderingAttribute = renderingAttribute; paint = initPaint(); + customColorPaint = new Paint(paint); paint2 = initPaint(); paint3 = initPaint(); paint_1 = initPaint(); @@ -462,6 +492,7 @@ public abstract class OsmandMapLayer { if (paint.getStrokeWidth() == 0 && defaultWidth != 0) { paint.setStrokeWidth(defaultWidth); } + customColorPaint = new Paint(paint); } return true; } @@ -486,7 +517,12 @@ public abstract class OsmandMapLayer { if (isShadowPaint) { canvas.drawPath(path, shadowPaint); } - canvas.drawPath(path, paint); + if (customColor != 0) { + customColorPaint.setColor(customColor); + canvas.drawPath(path, customColorPaint); + } else { + canvas.drawPath(path, paint); + } if (isPaint2) { canvas.drawPath(path, paint2); } diff --git a/OsmAnd/src/net/osmand/plus/views/POIMapLayer.java b/OsmAnd/src/net/osmand/plus/views/POIMapLayer.java index f03da3dac1..f475fedef8 100644 --- a/OsmAnd/src/net/osmand/plus/views/POIMapLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/POIMapLayer.java @@ -39,7 +39,7 @@ import net.osmand.plus.activities.MapActivity; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.render.RenderingIcons; import net.osmand.plus.routing.RoutingHelper; -import net.osmand.plus.routing.RoutingHelper.IRouteInformationListener; +import net.osmand.plus.routing.IRouteInformationListener; import net.osmand.plus.views.MapTextLayer.MapTextProvider; import net.osmand.util.Algorithms; diff --git a/OsmAnd/src/net/osmand/plus/views/RouteLayer.java b/OsmAnd/src/net/osmand/plus/views/RouteLayer.java index dd7349b754..3ce17b7be9 100644 --- a/OsmAnd/src/net/osmand/plus/views/RouteLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/RouteLayer.java @@ -12,11 +12,16 @@ import android.graphics.PointF; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.support.annotation.ColorInt; +import android.util.Pair; import net.osmand.Location; import net.osmand.data.LatLon; import net.osmand.data.QuadRect; 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.GPXUtilities.WptPt; import net.osmand.plus.R; import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu; @@ -24,9 +29,15 @@ import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu.TrackChartPoints; import net.osmand.plus.routing.RouteCalculationResult; import net.osmand.plus.routing.RouteDirectionInfo; import net.osmand.plus.routing.RoutingHelper; +import net.osmand.plus.routing.TransportRoutingHelper; +import net.osmand.plus.transport.TransportStopRoute; +import net.osmand.plus.transport.TransportStopType; +import net.osmand.router.TransportRoutePlanner.TransportRouteResult; +import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment; import net.osmand.util.MapUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -42,11 +53,10 @@ public class RouteLayer extends OsmandMapLayer { private OsmandMapTileView view; private final RoutingHelper helper; + private final TransportRoutingHelper transportHelper; // keep array lists created private List actionPoints = new ArrayList(); - private Path path; - // cache private Bitmap coloredArrowUp; private Bitmap actionArrow; @@ -61,10 +71,12 @@ public class RouteLayer extends OsmandMapLayer { private TrackChartPoints trackChartPoints; private RenderingLineAttributes attrs; + private boolean nightMode; - public RouteLayer(RoutingHelper helper){ + public RouteLayer(RoutingHelper helper) { this.helper = helper; + this.transportHelper = helper.getTransportRoutingHelper(); } public void setTrackChartPoints(TrackDetailsMenu.TrackChartPoints trackChartPoints) { @@ -73,8 +85,7 @@ public class RouteLayer extends OsmandMapLayer { private void initUI() { actionArrow = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_action_arrow, null); - path = new Path(); - + paintIcon = new Paint(); paintIcon.setFilterBitmap(true); paintIcon.setAntiAlias(true); @@ -119,7 +130,9 @@ public class RouteLayer extends OsmandMapLayer { @Override public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { - if (helper.getFinalLocation() != null && helper.getRoute().isCalculated()) { + if ((helper.isPublicTransportMode() && transportHelper.getRoutes() != null) || + (helper.getFinalLocation() != null && helper.getRoute().isCalculated())) { + updateAttrs(settings, tileBox); if(coloredArrowUp == null) { @@ -178,6 +191,7 @@ public class RouteLayer extends OsmandMapLayer { paintIconAction.setColorFilter(new PorterDuffColorFilter(attrs.paint3.getColor(), Mode.MULTIPLY)); paintIcon.setColorFilter(new PorterDuffColorFilter(attrs.paint2.getColor(), Mode.MULTIPLY)); } + nightMode = settings != null && settings.isNightMode(); } private void drawXAxisPoints(Canvas canvas, RotatedTileBox tileBox) { @@ -413,24 +427,79 @@ public class RouteLayer extends OsmandMapLayer { private class RouteSimplificationGeometry { RouteCalculationResult route; + TransportRouteResult transportRoute; double mapDensity; TreeMap zooms = new TreeMap<>(); - List locations = Collections.emptyList(); - + List locations = Collections.emptyList(); + TreeMap colorsMap = new TreeMap<>(); + // cache arrays TIntArrayList tx = new TIntArrayList(); TIntArrayList ty = new TIntArrayList(); List angles = new ArrayList<>(); List distances = new ArrayList<>(); - + List colors = new ArrayList<>(); + + private int getColor(int index) { + List list = new ArrayList<>(colorsMap.keySet()); + for (int i = list.size() -1; i >= 0; i--) { + int c = list.get(i); + if (c <= index) { + return colorsMap.get(c); + } + } + return attrs.paint.getColor(); + } + public void updateRoute(RotatedTileBox tb, RouteCalculationResult route) { if(tb.getMapDensity() != mapDensity || this.route != route) { this.route = route; - if(route == null) { + if (route == null) { locations = Collections.emptyList(); } else { locations = route.getImmutableAllLocations(); } + colorsMap.clear(); + this.mapDensity = tb.getMapDensity(); + zooms.clear(); + } + } + + public void updateTransportRoute(RotatedTileBox tb, TransportRouteResult route) { + if (tb.getMapDensity() != mapDensity || this.transportRoute != route) { + this.transportRoute = route; + if (route == null) { + locations = Collections.emptyList(); + colorsMap.clear(); + } else { + LatLon start = transportHelper.getStartLocation(); + LatLon end = transportHelper.getEndLocation(); + List list = new ArrayList<>(); + List cols = new ArrayList<>(); + calculateTransportResult(start, end, route, list, cols); + List locs = new ArrayList<>(); + TreeMap colsMap = new TreeMap<>(); + int i = 0; + int k = 0; + if (list.size() > 0) { + for (Way w : list) { + colsMap.put(k, cols.get(i++)); + //Location loc = new Location(""); + //loc.setLatitude(w.getLatitude()); + //loc.setLongitude(w.getLongitude()); + //locs.add(loc); + for (Node n : w.getNodes()) { + Location ln = new Location(""); + ln.setLatitude(n.getLatitude()); + ln.setLongitude(n.getLongitude()); + locs.add(ln); + k++; + } + } + } + locations = locs; + colorsMap = colsMap; + } this.mapDensity = tb.getMapDensity(); zooms.clear(); } @@ -452,14 +521,13 @@ public class RouteLayer extends OsmandMapLayer { TByteArrayList simplification = geometryZoom.getSimplifyPoints(); List odistances = geometryZoom.getDistances(); - - clearArrays(); + int color = attrs.paint.getColor(); boolean previousVisible = false; if (lastProjection != null) { if (leftLongitude <= lastProjection.getLongitude() && lastProjection.getLongitude() <= rightLongitude && bottomLatitude <= lastProjection.getLatitude() && lastProjection.getLatitude() <= topLatitude) { - addLocation(tb, lastProjection, tx, ty, angles, distances, 0); + addLocation(tb, lastProjection, color, tx, ty, angles, distances, 0, colors); previousVisible = true; } } @@ -467,7 +535,8 @@ public class RouteLayer extends OsmandMapLayer { int previous = -1; for (int i = currentRoute; i < routeNodes.size(); i++) { Location ls = routeNodes.get(i); - if(simplification.getQuick(i) == 0) { + color = getColor(i); + if (simplification.getQuick(i) == 0 && !colorsMap.containsKey(i)) { continue; } if (leftLongitude <= ls.getLongitude() && ls.getLongitude() <= rightLongitude && bottomLatitude <= ls.getLatitude() @@ -481,23 +550,23 @@ public class RouteLayer extends OsmandMapLayer { } else if (lastProjection != null) { lt = lastProjection; } - addLocation(tb, lt, tx, ty, angles, distances, 0); // first point + addLocation(tb, lt, color, tx, ty, angles, distances, 0, colors); // first point } - addLocation(tb, ls, tx, ty, angles, distances, dist); + addLocation(tb, ls, color, tx, ty, angles, distances, dist, colors); previousVisible = true; } else if (previousVisible) { - addLocation(tb, ls, tx, ty, angles, distances, previous == -1 ? 0 : odistances.get(i)); + addLocation(tb, ls, color, tx, ty, angles, distances, previous == -1 ? 0 : odistances.get(i), colors); double distToFinish = 0; for(int ki = i + 1; ki < odistances.size(); ki++) { distToFinish += odistances.get(ki); } - drawRouteSegment(tb, canvas, tx, ty, angles, distances, distToFinish); + drawRouteSegment(tb, canvas, tx, ty, angles, distances, distToFinish, colors); previousVisible = false; clearArrays(); } previous = i; } - drawRouteSegment(tb, canvas, tx, ty, angles, distances, 0); + drawRouteSegment(tb, canvas, tx, ty, angles, distances, 0, colors); } private void clearArrays() { @@ -505,10 +574,11 @@ public class RouteLayer extends OsmandMapLayer { ty.clear(); distances.clear(); angles.clear(); + colors.clear(); } - private void addLocation(RotatedTileBox tb, Location ls, TIntArrayList tx, TIntArrayList ty, - List angles, List distances, double dist) { + private void addLocation(RotatedTileBox tb, Location ls, int color, TIntArrayList tx, TIntArrayList ty, + List angles, List distances, double dist, List colors) { float x = tb.getPixXFromLatLon(ls.getLatitude(), ls.getLongitude()); float y = tb.getPixYFromLatLon(ls.getLatitude(), ls.getLongitude()); float px = x; @@ -531,21 +601,26 @@ public class RouteLayer extends OsmandMapLayer { ty.add((int) y); angles.add(angle); distances.add(distSegment); + colors.add(color); } } RouteSimplificationGeometry routeGeometry = new RouteSimplificationGeometry(); private void drawRouteSegment(RotatedTileBox tb, Canvas canvas, TIntArrayList tx, TIntArrayList ty, - List angles, List distances, double distToFinish) { - if(tx.size() < 2) { + List angles, List distances, double distToFinish, List colors) { + if (tx.size() < 2) { return; } try { - path.reset(); + List> paths = new ArrayList<>(); canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY()); - calculatePath(tb, tx, ty, path); - attrs.drawPath(canvas, path); + calculatePath(tb, tx, ty, colors, paths); + for (Pair pc : paths) { + attrs.customColor = pc.second; + attrs.drawPath(canvas, pc.first); + } + attrs.customColor = 0; if (tb.getZoomAnimation() == 0) { drawArrowsOverPath(canvas, tb, tx, ty, angles, distances, coloredArrowUp, distToFinish); } @@ -555,16 +630,32 @@ public class RouteLayer extends OsmandMapLayer { } public void drawLocations(RotatedTileBox tb, Canvas canvas, double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude) { - RouteCalculationResult route = helper.getRoute(); - routeGeometry.updateRoute(tb, route); - routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude, - helper.getLastProjection(), route == null ? 0 : route.getCurrentRoute()); - List rd = helper.getRouteDirections(); - Iterator it = rd.iterator(); - if (tb.getZoom() >= 14) { - List actionPoints = calculateActionPoints(topLatitude, leftLongitude, bottomLatitude, rightLongitude, helper.getLastProjection(), - helper.getRoute().getRouteLocations(), helper.getRoute().getCurrentRoute(), it, tb.getZoom()); - drawAction(tb, canvas, actionPoints); + if (helper.isPublicTransportMode()) { + int currentRoute = transportHelper.getCurrentRoute(); + List routes = transportHelper.getRoutes(); + TransportRouteResult route = routes != null && routes.size() > currentRoute ? routes.get(currentRoute) : null; + routeGeometry.updateRoute(tb, null); + routeGeometry.updateTransportRoute(tb, route); + if (route != null) { + LatLon start = transportHelper.getStartLocation(); + Location startLocation = new Location("transport"); + startLocation.setLatitude(start.getLatitude()); + startLocation.setLongitude(start.getLongitude()); + routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude, startLocation, 0); + } + } else { + RouteCalculationResult route = helper.getRoute(); + routeGeometry.updateTransportRoute(tb, null); + routeGeometry.updateRoute(tb, route); + routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude, + helper.getLastProjection(), route == null ? 0 : route.getCurrentRoute()); + List rd = helper.getRouteDirections(); + Iterator it = rd.iterator(); + if (tb.getZoom() >= 14) { + List actionPoints = calculateActionPoints(topLatitude, leftLongitude, bottomLatitude, rightLongitude, helper.getLastProjection(), + helper.getRoute().getRouteLocations(), helper.getRoute().getCurrentRoute(), it, tb.getZoom()); + drawAction(tb, canvas, actionPoints); + } } } @@ -710,7 +801,49 @@ public class RouteLayer extends OsmandMapLayer { return false; } + private void calculateTransportResult(LatLon start, LatLon end, TransportRouteResult r, List res, List colors) { + if (r != null) { + LatLon p = start; + for (TransportRouteResultSegment s : r.getSegments()) { + LatLon floc = s.getStart().getLocation(); + addRouteWalk(p, floc, res, colors); + List geometry = s.getGeometry(); + res.addAll(geometry); + addColors(s.route, geometry.size(), colors); + p = s.getEnd().getLocation(); + } + addRouteWalk(p, end, res, colors); + } + } - + private void addRouteWalk(LatLon s, LatLon e, List res, List colors) { + double dist = MapUtils.getDistance(s, e); + if (dist > 50) { + Way way = new Way(-1); + way.putTag(OSMSettings.OSMTagKey.NAME.getValue(), String.format("Walk %.1f m", dist)); + way.addNode(new Node(s.getLatitude(), s.getLongitude(), -1)); + way.addNode(new Node(e.getLatitude(), e.getLongitude(), -1)); + res.add(way); + addColors(null, 1, colors); + } + } + private void addColors(TransportRoute route, int count, List colors) { + int color; + if (route == null) { + color = attrs.paint.getColor(); + } else { + TransportStopRoute r = new TransportStopRoute(); + r.type = TransportStopType.findType(route.getType()); + r.route = route; + color = r.getColor(helper.getApplication(), nightMode); + } + addColors(color, count, colors); + } + + private void addColors(int color, int count, List colors) { + Integer[] integers = new Integer[count]; + Arrays.fill(integers, color); + colors.addAll(Arrays.asList(integers)); + } }