package net.osmand.plus.base; import android.content.Context; import android.support.v4.util.Pair; import android.view.WindowManager; import net.osmand.Location; import net.osmand.StateChangedListener; import net.osmand.ValueHolder; import net.osmand.binary.BinaryMapDataObject; import net.osmand.data.LatLon; import net.osmand.data.RotatedTileBox; import net.osmand.map.IMapLocationListener; import net.osmand.map.WorldRegion; import net.osmand.plus.MapMarkersHelper; import net.osmand.plus.MapMarkersHelper.MapMarkerChangedListener; import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmAndLocationProvider; import net.osmand.plus.OsmAndLocationProvider.OsmAndCompassListener; import net.osmand.plus.OsmAndLocationProvider.OsmAndLocationListener; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandSettings; 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.views.AnimateDraggingMapThread; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.util.MapUtils; import java.io.IOException; public class MapViewTrackingUtilities implements OsmAndLocationListener, IMapLocationListener, OsmAndCompassListener, IRouteInformationListener, MapMarkerChangedListener { private static final int AUTO_FOLLOW_MSG_ID = OsmAndConstants.UI_HANDLER_LOCATION_SERVICE + 4; private long lastTimeAutoZooming = 0; private boolean sensorRegistered = false; private OsmandMapTileView mapView; private DashboardOnMap dashboard; private MapContextMenu contextMenu; private OsmandSettings settings; private OsmandApplication app; private boolean isMapLinkedToLocation = true; private boolean followingMode; private boolean routePlanningMode; private boolean showViewAngle = false; private boolean isUserZoomed = false; private String locationProvider; private boolean showRouteFinishDialog = false; private Location myLocation; private Float heading; private boolean drivingRegionUpdated = false; private boolean movingToMyLocation = false; public MapViewTrackingUtilities(OsmandApplication app){ this.app = app; settings = app.getSettings(); myLocation = app.getLocationProvider().getLastKnownLocation(); app.getLocationProvider().addLocationListener(this); app.getLocationProvider().addCompassListener(this); addTargetPointListener(app); addMapMarkersListener(app); app.getRoutingHelper().addListener(this); } public void resetDrivingRegionUpdate() { drivingRegionUpdated = false; } private void addTargetPointListener(OsmandApplication app) { app.getTargetPointsHelper().addListener(new StateChangedListener<Void>() { @Override public void stateChanged(Void change) { if(mapView != null) { mapView.refreshMap(); } } }); } private void addMapMarkersListener(OsmandApplication app) { app.getMapMarkersHelper().addListener(this); } @Override public void onMapMarkerChanged(MapMarkersHelper.MapMarker mapMarker) { } @Override public void onMapMarkersChanged() { if (mapView != null) { mapView.refreshMap(); } } public void setMapView(OsmandMapTileView mapView) { this.mapView = mapView; if(mapView != null) { WindowManager wm = (WindowManager) app.getSystemService(Context.WINDOW_SERVICE); int orientation = wm.getDefaultDisplay().getOrientation(); app.getLocationProvider().updateScreenOrientation(orientation); mapView.setMapLocationListener(this); } } public Location getMyLocation() { return myLocation; } public Float getHeading() { return heading; } public String getLocationProvider() { return locationProvider; } @Override public void updateCompassValue(float val) { heading = val; if (mapView != null) { if (settings.ROTATE_MAP.get() == OsmandSettings.ROTATE_MAP_COMPASS && !routePlanningMode) { if (Math.abs(MapUtils.degreesDiff(mapView.getRotate(), -val)) > 1) { mapView.setRotate(-val); } } else if (showViewAngle) { mapView.refreshMap(); } } if(dashboard != null) { dashboard.updateCompassValue(val); } if(contextMenu != null) { contextMenu.updateCompassValue(val); } } public void setDashboard(DashboardOnMap dashboard) { this.dashboard = dashboard; } public void setContextMenu(MapContextMenu contextMenu) { this.contextMenu = contextMenu; } public boolean isMovingToMyLocation() { return movingToMyLocation; } @Override public void updateLocation(Location location) { myLocation = location; showViewAngle = false; if (location != null) { locationProvider = location.getProvider(); if (settings.DRIVING_REGION_AUTOMATIC.get() && !drivingRegionUpdated) { try { BinaryMapDataObject o = app.getRegions().findBinaryMapDataObject( new LatLon(location.getLatitude(), location.getLongitude())); if (o != null) { String fullName = app.getRegions().getFullName(o); WorldRegion worldRegion = app.getRegions().getRegionData(fullName); if (worldRegion != null) { app.setupDrivingRegion(worldRegion); } } drivingRegionUpdated = true; } catch (IOException e) { // ignore } } } if (mapView != null) { RotatedTileBox tb = mapView.getCurrentRotatedTileBox(); if (isMapLinkedToLocation() && location != null) { Pair<Integer, Double> zoom = null; Float rotation = null; if (settings.AUTO_ZOOM_MAP.get()) { zoom = autozoom(location); } int currentMapRotation = settings.ROTATE_MAP.get(); boolean smallSpeedForCompass = isSmallSpeedForCompass(location); boolean smallSpeedForAnimation = isSmallSpeedForAnimation(location); // boolean virtualBearing = fMode && settings.SNAP_TO_ROAD.get(); showViewAngle = (!location.hasBearing() || smallSpeedForCompass) && (tb != null && tb.containsLatLon(location.getLatitude(), location.getLongitude())); if (currentMapRotation == OsmandSettings.ROTATE_MAP_BEARING) { if (location.hasBearing() && !smallSpeedForCompass) { // special case when bearing equals to zero (we don't change anything) if (location.getBearing() != 0f) { rotation = -location.getBearing(); } } } else if(currentMapRotation == OsmandSettings.ROTATE_MAP_COMPASS) { showViewAngle = routePlanningMode; // disable compass rotation in that mode } registerUnregisterSensor(location); if (settings.ANIMATE_MY_LOCATION.get() && !smallSpeedForAnimation && !movingToMyLocation) { mapView.getAnimatedDraggingThread().startMoving( location.getLatitude(), location.getLongitude(), zoom, rotation, false); } else { if (zoom != null) { mapView.getAnimatedDraggingThread().startZooming(zoom.first, zoom.second, false); } if (rotation != null) { mapView.setRotate(rotation); } mapView.setLatLon(location.getLatitude(), location.getLongitude()); } } else if(location != null) { showViewAngle = (!location.hasBearing() || isSmallSpeedForCompass(location)) && (tb != null && tb.containsLatLon(location.getLatitude(), location.getLongitude())); registerUnregisterSensor(location); } RoutingHelper routingHelper = app.getRoutingHelper(); followingMode = routingHelper.isFollowingMode(); if(routePlanningMode != routingHelper.isRoutePlanningMode()) { switchToRoutePlanningMode(); } // When location is changed we need to refresh map in order to show movement! mapView.refreshMap(); } if(dashboard != null) { dashboard.updateMyLocation(location); } if(contextMenu != null) { contextMenu.updateMyLocation(location); } } public static boolean isSmallSpeedForCompass(Location location) { return !location.hasSpeed() || location.getSpeed() < 0.5; } public static boolean isSmallSpeedForAnimation(Location location) { return !location.hasSpeed() || location.getSpeed() < 1.5; } public boolean isShowViewAngle() { return showViewAngle; } public void switchToRoutePlanningMode() { RoutingHelper routingHelper = app.getRoutingHelper(); routePlanningMode = routingHelper.isRoutePlanningMode(); updateSettings(); if(!routePlanningMode && followingMode) { backToLocationImpl(); } } public void updateSettings(){ if (mapView != null) { if (settings.ROTATE_MAP.get() == OsmandSettings.ROTATE_MAP_NONE || routePlanningMode) { mapView.setRotate(0); } mapView.setMapPosition(settings.ROTATE_MAP.get() == OsmandSettings.ROTATE_MAP_BEARING && !routePlanningMode && !settings.CENTER_POSITION_ON_MAP.get() ? OsmandSettings.BOTTOM_CONSTANT : OsmandSettings.CENTER_CONSTANT); } registerUnregisterSensor(app.getLocationProvider().getLastKnownLocation()); } private void registerUnregisterSensor(net.osmand.Location location) { int currentMapRotation = settings.ROTATE_MAP.get(); boolean registerCompassListener = ((showViewAngle || contextMenu != null) && location != null) || (currentMapRotation == OsmandSettings.ROTATE_MAP_COMPASS && !routePlanningMode); // show point view only if gps enabled if(sensorRegistered != registerCompassListener) { app.getLocationProvider().registerOrUnregisterCompassListener(registerCompassListener); } } private float defineZoomFromSpeed(RotatedTileBox tb, float speed) { if (speed < 7f / 3.6) { return 0; } double visibleDist = tb.getDistance(tb.getCenterPixelX(), 0, tb.getCenterPixelX(), tb.getCenterPixelY()); float time = 75f; // > 83 km/h show 75 seconds if (speed < 83f / 3.6) { time = 60f; } time /= settings.AUTO_ZOOM_MAP_SCALE.get().coefficient; double distToSee = speed * time; float zoomDelta = (float) (Math.log(visibleDist / distToSee) / Math.log(2.0f)); // check if 17, 18 is correct? return zoomDelta; } public Pair<Integer, Double> autozoom(Location location) { if (location.hasSpeed()) { long now = System.currentTimeMillis(); final RotatedTileBox tb = mapView.getCurrentRotatedTileBox(); float zdelta = defineZoomFromSpeed(tb, location.getSpeed()); if (Math.abs(zdelta) >= 0.5/*?Math.sqrt(0.5)*/) { // prevent ui hysteresis (check time interval for autozoom) if (zdelta >= 2) { // decrease a bit zdelta -= 1; } else if (zdelta <= -2) { // decrease a bit zdelta += 1; } double targetZoom = Math.min(tb.getZoom() + tb.getZoomFloatPart() + zdelta, settings.AUTO_ZOOM_MAP_SCALE.get().maxZoom); int threshold = settings.AUTO_FOLLOW_ROUTE.get(); if (now - lastTimeAutoZooming > 4500 && (now - lastTimeAutoZooming > threshold || !isUserZoomed)) { isUserZoomed = false; lastTimeAutoZooming = now; // double settingsZoomScale = Math.log(mapView.getSettingsMapDensity()) / Math.log(2.0f); // double zoomScale = Math.log(tb.getMapDensity()) / Math.log(2.0f); // double complexZoom = tb.getZoom() + zoomScale + zdelta; // round to 0.33 targetZoom = Math.round(targetZoom * 3) / 3f; int newIntegerZoom = (int)Math.round(targetZoom); double zPart = targetZoom - newIntegerZoom; return new Pair<>(newIntegerZoom, zPart); } } } return null; } public void backToLocationImpl() { if (mapView != null) { OsmAndLocationProvider locationProvider = app.getLocationProvider(); if (!isMapLinkedToLocation()) { setMapLinkedToLocation(true); if (locationProvider.getLastKnownLocation() != null) { net.osmand.Location lastKnownLocation = locationProvider.getLastKnownLocation(); AnimateDraggingMapThread thread = mapView.getAnimatedDraggingThread(); int fZoom = mapView.getZoom() < 15 ? 15 : mapView.getZoom(); movingToMyLocation = true; thread.startMoving(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude(), fZoom, false, new Runnable() { @Override public void run() { movingToMyLocation = false; } }); } mapView.refreshMap(); } if (locationProvider.getLastKnownLocation() == null) { app.showToastMessage(R.string.unknown_location); } } } private void backToLocationWithDelay(int delay) { app.runMessageInUIThreadAndCancelPrevious(AUTO_FOLLOW_MSG_ID, new Runnable() { @Override public void run() { if (mapView != null && !isMapLinkedToLocation() && contextMenu == null) { app.showToastMessage(R.string.auto_follow_location_enabled); backToLocationImpl(); } } }, delay * 1000); } public boolean isMapLinkedToLocation(){ return isMapLinkedToLocation; } public void setMapLinkedToLocation(boolean isMapLinkedToLocation) { if (!isMapLinkedToLocation) { int autoFollow = settings.AUTO_FOLLOW_ROUTE.get(); if (autoFollow > 0 && app.getRoutingHelper().isFollowingMode() && !routePlanningMode) { backToLocationWithDelay(autoFollow); } } this.isMapLinkedToLocation = isMapLinkedToLocation; } @Override public void locationChanged(double newLatitude, double newLongitude, Object source) { // when user start dragging setMapLinkedToLocation(false); } public void switchRotateMapMode(){ String rotMode = app.getString(R.string.rotate_map_none_opt); if (settings.ROTATE_MAP.get() == OsmandSettings.ROTATE_MAP_NONE && mapView.getRotate() != 0) { // reset manual rotation } else { int vl = (settings.ROTATE_MAP.get() + 1) % 3; settings.ROTATE_MAP.set(vl); if (settings.ROTATE_MAP.get() == OsmandSettings.ROTATE_MAP_BEARING) { rotMode = app.getString(R.string.rotate_map_bearing_opt); } else if (settings.ROTATE_MAP.get() == OsmandSettings.ROTATE_MAP_COMPASS) { rotMode = app.getString(R.string.rotate_map_compass_opt); } } rotMode = app.getString(R.string.rotate_map_to_bearing) + ":\n" + rotMode; app.showShortToastMessage(rotMode); updateSettings(); if(mapView != null) { mapView.refreshMap(); } } @Override public void newRouteIsCalculated(boolean newRoute, ValueHolder<Boolean> showToast) { } @Override public void routeWasCancelled() { } @Override public void routeWasFinished() { showRouteFinishDialog = (mapView == null); } public boolean getShowRouteFinishDialog() { return showRouteFinishDialog; } public void setShowRouteFinishDialog(boolean showRouteFinishDialog) { this.showRouteFinishDialog = showRouteFinishDialog; } public void setZoomTime(long time) { lastTimeAutoZooming = time; isUserZoomed = true; } }