package com.mapbox.mapboxsdk.location; import android.annotation.SuppressLint; import android.content.Context; import android.hardware.SensorManager; import android.location.Location; import android.os.Looper; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresPermission; import android.support.annotation.StyleRes; import android.support.annotation.VisibleForTesting; import android.view.WindowManager; import com.mapbox.android.core.location.LocationEngine; import com.mapbox.android.core.location.LocationEngineCallback; import com.mapbox.android.core.location.LocationEngineProvider; import com.mapbox.android.core.location.LocationEngineRequest; import com.mapbox.android.core.location.LocationEngineResult; import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.location.modes.CameraMode; import com.mapbox.mapboxsdk.location.modes.RenderMode; import com.mapbox.mapboxsdk.log.Logger; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener; import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener; import com.mapbox.mapboxsdk.maps.MapboxMap.OnMapClickListener; import com.mapbox.mapboxsdk.maps.Style; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_FASTEST_INTERVAL_MILLIS; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_INTERVAL_MILLIS; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_TILT_ANIM_DURATION; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_ZOOM_ANIM_DURATION; /** * The Location Component provides location awareness to your mobile application. Enabling this * component provides a contextual experience to your users by showing an icon representing the users * current location. A few different modes are offered to provide the right context to your users at * the correct time. {@link RenderMode#NORMAL} simply shows the users location on the map * represented as a dot. {@link RenderMode#COMPASS} mode allows you to display an arrow icon * (by default) that points in the direction the device is pointing in. * {@link RenderMode#GPS} can be used in conjunction with our Navigation SDK to * display a larger icon (customized with {@link LocationComponentOptions#gpsDrawable()}) we call the user puck. *
* This component also offers the ability to set a map camera behavior for tracking the user * location. These different {@link CameraMode}s will track, stop tracking the location based on the * mode set with {@link LocationComponent#setCameraMode(int)}. *
* * To get the component object use {@link MapboxMap#getLocationComponent()} and activate it with * {@link #activateLocationComponent(Context, Style)} or one of the overloads. * Then, manage its visibility with {@link #setLocationComponentEnabled(boolean)}. * *
* Using this component requires you to request permission beforehand manually or using * {@link com.mapbox.android.core.permissions.PermissionsManager}. Either * {@code ACCESS_COARSE_LOCATION} or {@code ACCESS_FINE_LOCATION} permissions can be requested for * this component to work as expected. *
* This component offers a default, built-in {@link LocationEngine} with some of the activation methods. * This engine will be obtained by {@link LocationEngineProvider#getBestLocationEngine(Context, boolean)} which defaults * to the {@link com.mapbox.android.core.location.MapboxFusedLocationEngineImpl}. If you'd like to utilize Google * Play Services * for more precise location updates, simply add the Google Play Location Services dependency in your build script. * This will make the default engine the {@link com.mapbox.android.core.location.GoogleLocationEngineImpl} instead. * After a custom engine is passed to the component, or the built-in is initialized, * the location updates are going to be requested with the {@link LocationEngineRequest}, either a default one, * or the one passed during the activation. * When using any engine, requesting/removing the location updates is going to be managed internally. *
* You can also push location updates to the component without any internal engine management. * To achieve that, use {@link #activateLocationComponent(Context, Style, boolean)} with false. * No engine is going to be initialized and you can push location updates with {@link #forceLocationUpdate(Location)}. *
* For location puck animation purposes, like navigation, * we recommend limiting the maximum zoom level of the map for the best user experience. *
* Location Component doesn't support state saving out-of-the-box.
*/
public final class LocationComponent {
private static final String TAG = "Mbgl-LocationComponent";
@NonNull
private final MapboxMap mapboxMap;
private Style style;
private LocationComponentOptions options;
@NonNull
private InternalLocationEngineProvider internalLocationEngineProvider = new InternalLocationEngineProvider();
@Nullable
private LocationEngine locationEngine;
@NonNull
private LocationEngineRequest locationEngineRequest =
new LocationEngineRequest.Builder(DEFAULT_INTERVAL_MILLIS)
.setFastestInterval(DEFAULT_FASTEST_INTERVAL_MILLIS)
.setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
.build();
private LocationEngineCallback
* To get the component object use {@link MapboxMap#getLocationComponent()}.
*/
public LocationComponent(@NonNull MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
}
// used for creating a spy
LocationComponent() {
//noinspection ConstantConditions
mapboxMap = null;
}
@VisibleForTesting
LocationComponent(@NonNull MapboxMap mapboxMap,
@NonNull LocationEngineCallback
* Note: This method will initialize and use an internal {@link LocationEngine} when enabled.
*
* @param context the context
* @param style the proxy object for current map style. More info at {@link Style}
*/
@RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
public void activateLocationComponent(@NonNull Context context, @NonNull Style style) {
activateLocationComponent(context, style,
LocationComponentOptions.createFromAttributes(context, R.style.mapbox_LocationComponent));
}
/**
* This method initializes the component and needs to be called before any other operations are performed.
* Afterwards, you can manage component's visibility by {@link #setLocationComponentEnabled(boolean)}.
*
* @param context the context
* @param style the proxy object for current map style. More info at {@link Style}
* @param useDefaultLocationEngine true if you want to initialize and use the built-in location engine or false if
* there should be no location engine initialized
*/
@RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
public void activateLocationComponent(@NonNull Context context, @NonNull Style style,
boolean useDefaultLocationEngine) {
if (useDefaultLocationEngine) {
activateLocationComponent(context, style, R.style.mapbox_LocationComponent);
} else {
activateLocationComponent(context, style, null, R.style.mapbox_LocationComponent);
}
}
/**
* This method initializes the component and needs to be called before any other operations are performed.
* Afterwards, you can manage component's visibility by {@link #setLocationComponentEnabled(boolean)}.
*
* @param context the context
* @param style the proxy object for current map style. More info at {@link Style}
* @param useDefaultLocationEngine true if you want to initialize and use the built-in location engine or false if
* there should be no location engine initialized
* @param locationEngineRequest the location request
*/
@RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
public void activateLocationComponent(@NonNull Context context, @NonNull Style style,
boolean useDefaultLocationEngine,
@NonNull LocationEngineRequest locationEngineRequest) {
setLocationEngineRequest(locationEngineRequest);
if (useDefaultLocationEngine) {
activateLocationComponent(context, style, R.style.mapbox_LocationComponent);
} else {
activateLocationComponent(context, style, null, R.style.mapbox_LocationComponent);
}
}
/**
* This method initializes the component and needs to be called before any other operations are performed.
* Afterwards, you can manage component's visibility by {@link #setLocationComponentEnabled(boolean)}.
*
* Note: This method will initialize and use an internal {@link LocationEngine} when enabled.
*
* @param context the context
* @param style the proxy object for current map style. More info at {@link Style}
* @param styleRes the LocationComponent style res
*/
@RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
public void activateLocationComponent(@NonNull Context context, @NonNull Style style, @StyleRes int styleRes) {
activateLocationComponent(context, style, LocationComponentOptions.createFromAttributes(context, styleRes));
}
/**
* This method initializes the component and needs to be called before any other operations are performed.
* Afterwards, you can manage component's visibility by {@link #setLocationComponentEnabled(boolean)}.
*
* Note: This method will initialize and use an internal {@link LocationEngine} when enabled.
*
* When camera is transitioning to a new mode, it will reject inputs like {@link #zoomWhileTracking(double)} or
* {@link #tiltWhileTracking(double)}.
* Use {@link OnLocationCameraTransitionListener} to listen for the transition state.
*
*
* When camera is transitioning to a new mode, it will reject inputs like {@link #zoomWhileTracking(double)} or
* {@link #tiltWhileTracking(double)}.
* Use {@link OnLocationCameraTransitionListener} to listen for the transition state.
*
*
*
* Setting this will not impact any other animations schedule with {@link MapboxMap}, gesture animations or
* {@link #zoomWhileTracking(double)}/{@link #tiltWhileTracking(double)}.
*
* Use this setting to limit animation rate of the location puck on higher zoom levels to decrease the stress on
* the device's CPU which can directly improve battery life, without sacrificing UX.
*
* Example usage:
*
* If you're looking for a way to throttle the FPS of the whole map, including other animations and gestures, see
* {@link MapView#setMaximumFps(int)}.
*
* @param maxAnimationFps max location animation FPS
*/
public void setMaxAnimationFps(int maxAnimationFps) {
locationAnimatorCoordinator.setMaxAnimationFps(maxAnimationFps);
}
/**
* Set the location engine to update the current user location.
*
* If {@code null} is passed in, all updates will have to occur through the
* {@link LocationComponent#forceLocationUpdate(Location)} method.
*
* @param locationEngine a {@link LocationEngine} this component should use to handle updates
*/
@SuppressLint("MissingPermission")
public void setLocationEngine(@Nullable LocationEngine locationEngine) {
if (this.locationEngine != null) {
// If internal location engines being used, extra steps need to be taken to deconstruct the instance.
this.locationEngine.removeLocationUpdates(currentLocationEngineListener);
this.locationEngine = null;
}
if (locationEngine != null) {
fastestInterval = locationEngineRequest.getFastestInterval();
this.locationEngine = locationEngine;
if (isLayerReady && isEnabled) {
setLastLocation();
locationEngine.requestLocationUpdates(
locationEngineRequest, currentLocationEngineListener, Looper.getMainLooper());
}
} else {
fastestInterval = 0;
}
}
/**
* Set the location request that's going to be used when requesting location updates.
*
* @param locationEngineRequest the location request
*/
public void setLocationEngineRequest(@NonNull LocationEngineRequest locationEngineRequest) {
this.locationEngineRequest = locationEngineRequest;
// reset internal LocationEngine ref to re-request location updates if needed
setLocationEngine(locationEngine);
}
/**
* Get the location request that's going to be used when requesting location updates.
*/
@NonNull
public LocationEngineRequest getLocationEngineRequest() {
return locationEngineRequest;
}
/**
* Returns the current {@link LocationEngine} being used for updating the user location.
*
* @return the {@link LocationEngine} being used to update the user location
*/
@Nullable
public LocationEngine getLocationEngine() {
return locationEngine;
}
/**
* Sets the compass engine used to provide compass heading values.
*
* @param compassEngine to be used
*/
public void setCompassEngine(@Nullable CompassEngine compassEngine) {
if (this.compassEngine != null) {
updateCompassListenerState(false);
}
this.compassEngine = compassEngine;
updateCompassListenerState(true);
}
/**
* Returns the compass engine used to provide compass heading values.
*
* @return compass engine currently being used
*/
@Nullable
public CompassEngine getCompassEngine() {
return compassEngine;
}
/**
* Get the last know location of the location component.
*
* @return the last known location
*/
@Nullable
@RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
public Location getLastKnownLocation() {
return lastLocation;
}
/**
* Adds a listener that gets invoked when the user clicks the displayed location.
*
* If there are registered location click listeners and the location is clicked,
* only {@link OnLocationClickListener#onLocationComponentClick()} is going to be delivered,
* {@link com.mapbox.mapboxsdk.maps.MapboxMap.OnMapClickListener#onMapClick(LatLng)} is going to be consumed
* and not pushed to the listeners registered after the component's activation.
*
* @param listener The location click listener that is invoked when the
* location is clicked
*/
public void addOnLocationClickListener(@NonNull OnLocationClickListener listener) {
onLocationClickListeners.add(listener);
}
/**
* Removes the passed listener from the current list of location click listeners.
*
* @param listener to be removed
*/
public void removeOnLocationClickListener(@NonNull OnLocationClickListener listener) {
onLocationClickListeners.remove(listener);
}
/**
* Adds a listener that gets invoked when the user long clicks the displayed location.
*
* If there are registered location long click listeners and the location is long clicked,
* only {@link OnLocationLongClickListener#onLocationComponentLongClick()} is going to be delivered,
* {@link com.mapbox.mapboxsdk.maps.MapboxMap.OnMapLongClickListener#onMapLongClick(LatLng)} is going to be consumed
* and not pushed to the listeners registered after the component's activation.
*
* @param listener The location click listener that is invoked when the
* location is clicked
*/
public void addOnLocationLongClickListener(@NonNull OnLocationLongClickListener listener) {
onLocationLongClickListeners.add(listener);
}
/**
* Removes the passed listener from the current list of location long click listeners.
*
* @param listener to be removed
*/
public void removeOnLocationLongClickListener(@NonNull OnLocationLongClickListener listener) {
onLocationLongClickListeners.remove(listener);
}
/**
* Adds a listener that gets invoked when camera tracking state changes.
*
* @param listener Listener that gets invoked when camera tracking state changes.
*/
public void addOnCameraTrackingChangedListener(@NonNull OnCameraTrackingChangedListener listener) {
onCameraTrackingChangedListeners.add(listener);
}
/**
* Removes a listener that gets invoked when camera tracking state changes.
*
* @param listener Listener that gets invoked when camera tracking state changes.
*/
public void removeOnCameraTrackingChangedListener(@NonNull OnCameraTrackingChangedListener listener) {
onCameraTrackingChangedListeners.remove(listener);
}
/**
* Adds a listener that gets invoked when render mode changes.
*
* @param listener Listener that gets invoked when render mode changes.
*/
public void addOnRenderModeChangedListener(@NonNull OnRenderModeChangedListener listener) {
onRenderModeChangedListeners.add(listener);
}
/**
* Removes a listener that gets invoked when render mode changes.
*
* @param listener Listener that gets invoked when render mode changes.
*/
public void removeRenderModeChangedListener(@NonNull OnRenderModeChangedListener listener) {
onRenderModeChangedListeners.remove(listener);
}
/**
* Adds the passed listener that gets invoked when user updates have stopped long enough for the last update
* to be considered stale.
*
* This timeout is set by {@link LocationComponentOptions#staleStateTimeout()}.
*
* @param listener invoked when last update is considered stale
*/
public void addOnLocationStaleListener(@NonNull OnLocationStaleListener listener) {
onLocationStaleListeners.add(listener);
}
/**
* Removes the passed listener from the current list of stale listeners.
*
* @param listener to be removed from the list
*/
public void removeOnLocationStaleListener(@NonNull OnLocationStaleListener listener) {
onLocationStaleListeners.remove(listener);
}
/**
* Internal use.
*/
public void onStart() {
isComponentStarted = true;
onLocationLayerStart();
}
/**
* Internal use.
*/
public void onStop() {
onLocationLayerStop();
isComponentStarted = false;
}
/**
* Internal use.
*/
public void onDestroy() {
}
/**
* Internal use.
*/
public void onStartLoadingMap() {
onLocationLayerStop();
}
/**
* Internal use.
*/
public void onFinishLoadingStyle() {
if (isComponentInitialized) {
style = mapboxMap.getStyle();
locationLayerController.initializeComponents(style, options);
locationCameraController.initializeOptions(options);
onLocationLayerStart();
}
}
@SuppressLint("MissingPermission")
private void onLocationLayerStart() {
if (!isComponentInitialized || !isComponentStarted || mapboxMap.getStyle() == null) {
return;
}
if (!isLayerReady) {
isLayerReady = true;
mapboxMap.addOnCameraMoveListener(onCameraMoveListener);
mapboxMap.addOnCameraIdleListener(onCameraIdleListener);
if (options.enableStaleState()) {
staleStateManager.onStart();
}
}
if (isEnabled) {
if (locationEngine != null) {
try {
locationEngine.requestLocationUpdates(
locationEngineRequest, currentLocationEngineListener, Looper.getMainLooper());
} catch (SecurityException se) {
Logger.e(TAG, "Unable to request location updates", se);
}
}
setCameraMode(locationCameraController.getCameraMode());
setLastLocation();
updateCompassListenerState(true);
setLastCompassHeading();
}
}
private void onLocationLayerStop() {
if (!isComponentInitialized || !isLayerReady || !isComponentStarted) {
return;
}
isLayerReady = false;
locationLayerController.hide();
staleStateManager.onStop();
if (compassEngine != null) {
updateCompassListenerState(false);
}
locationAnimatorCoordinator.cancelAllAnimations();
if (locationEngine != null) {
locationEngine.removeLocationUpdates(currentLocationEngineListener);
}
mapboxMap.removeOnCameraMoveListener(onCameraMoveListener);
mapboxMap.removeOnCameraIdleListener(onCameraIdleListener);
}
private void initialize(@NonNull final Context context, @NonNull Style style,
@NonNull final LocationComponentOptions options) {
if (isComponentInitialized) {
return;
}
if (!style.isFullyLoaded()) {
throw new IllegalStateException("Style is invalid, provide the most recently loaded one.");
}
this.style = style;
this.options = options;
mapboxMap.addOnMapClickListener(onMapClickListener);
mapboxMap.addOnMapLongClickListener(onMapLongClickListener);
LayerSourceProvider sourceProvider = new LayerSourceProvider();
LayerFeatureProvider featureProvider = new LayerFeatureProvider();
LayerBitmapProvider bitmapProvider = new LayerBitmapProvider(context);
locationLayerController = new LocationLayerController(mapboxMap, style, sourceProvider, featureProvider,
bitmapProvider, options, renderModeChangedListener);
locationCameraController = new LocationCameraController(
context, mapboxMap, cameraTrackingChangedListener, options, onCameraMoveInvalidateListener);
locationAnimatorCoordinator = new LocationAnimatorCoordinator(
mapboxMap.getProjection(),
MapboxAnimatorSetProvider.getInstance(),
MapboxAnimatorProvider.getInstance()
);
locationAnimatorCoordinator.setTrackingAnimationDurationMultiplier(options
.trackingAnimationDurationMultiplier());
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
if (windowManager != null && sensorManager != null) {
compassEngine = new LocationComponentCompassEngine(windowManager, sensorManager);
}
staleStateManager = new StaleStateManager(onLocationStaleListener, options);
updateMapWithOptions(options);
setRenderMode(RenderMode.NORMAL);
setCameraMode(CameraMode.NONE);
isComponentInitialized = true;
onLocationLayerStart();
}
private void initializeLocationEngine(@NonNull Context context) {
if (this.locationEngine != null) {
this.locationEngine.removeLocationUpdates(currentLocationEngineListener);
}
setLocationEngine(internalLocationEngineProvider.getBestLocationEngine(context, false));
}
private void updateCompassListenerState(boolean canListen) {
if (compassEngine != null) {
if (!canListen) {
// We shouldn't listen, simply unregistering
removeCompassListener(compassEngine);
return;
}
if (!isComponentInitialized || !isComponentStarted || !isEnabled) {
return;
}
if (locationCameraController.isConsumingCompass() || locationLayerController.isConsumingCompass()) {
// If we have a consumer, and not yet listening, then start listening
if (!isListeningToCompass) {
isListeningToCompass = true;
compassEngine.addCompassListener(compassListener);
}
} else {
// If we have no consumers, stop listening
removeCompassListener(compassEngine);
}
}
}
private void removeCompassListener(@NonNull CompassEngine engine) {
if (isListeningToCompass) {
isListeningToCompass = false;
engine.removeCompassListener(compassListener);
}
}
private void enableLocationComponent() {
isEnabled = true;
onLocationLayerStart();
}
private void disableLocationComponent() {
isEnabled = false;
onLocationLayerStop();
}
private void updateMapWithOptions(@NonNull LocationComponentOptions options) {
int[] padding = options.padding();
if (padding != null) {
mapboxMap.setPadding(
padding[0], padding[1], padding[2], padding[3]
);
}
}
/**
* Updates the user location icon.
*
* @param location the latest user location
*/
private void updateLocation(@Nullable final Location location, boolean fromLastLocation) {
if (location == null) {
return;
} else if (!isLayerReady) {
lastLocation = location;
return;
} else {
long currentTime = SystemClock.elapsedRealtime();
if (currentTime - lastUpdateTime < fastestInterval) {
return;
} else {
lastUpdateTime = currentTime;
}
}
showLocationLayerIfHidden();
if (!fromLastLocation) {
staleStateManager.updateLatestLocationTime();
}
CameraPosition currentCameraPosition = mapboxMap.getCameraPosition();
boolean isGpsNorth = getCameraMode() == CameraMode.TRACKING_GPS_NORTH;
locationAnimatorCoordinator.feedNewLocation(location, currentCameraPosition, isGpsNorth);
updateAccuracyRadius(location, false);
lastLocation = location;
}
private void showLocationLayerIfHidden() {
boolean isLocationLayerHidden = locationLayerController.isHidden();
if (isEnabled && isComponentStarted && isLocationLayerHidden) {
locationLayerController.show();
}
}
private void updateCompassHeading(float heading) {
locationAnimatorCoordinator.feedNewCompassBearing(heading, mapboxMap.getCameraPosition());
}
/**
* If the locationEngine contains a last location value, we use it for the initial location layer
* position.
*/
@SuppressLint("MissingPermission")
private void setLastLocation() {
if (locationEngine != null) {
locationEngine.getLastLocation(lastLocationEngineListener);
} else {
updateLocation(getLastKnownLocation(), true);
}
}
private void setLastCompassHeading() {
updateCompassHeading(compassEngine != null ? compassEngine.getLastHeading() : 0);
}
@SuppressLint("MissingPermission")
private void updateLayerOffsets(boolean forceUpdate) {
CameraPosition position = mapboxMap.getCameraPosition();
if (lastCameraPosition == null || forceUpdate) {
lastCameraPosition = position;
locationLayerController.updateForegroundBearing((float) position.bearing);
locationLayerController.updateForegroundOffset(position.tilt);
updateAccuracyRadius(getLastKnownLocation(), true);
return;
}
if (position.bearing != lastCameraPosition.bearing) {
locationLayerController.updateForegroundBearing((float) position.bearing);
}
if (position.tilt != lastCameraPosition.tilt) {
locationLayerController.updateForegroundOffset(position.tilt);
}
if (position.zoom != lastCameraPosition.zoom) {
updateAccuracyRadius(getLastKnownLocation(), true);
}
lastCameraPosition = position;
}
private void updateAccuracyRadius(Location location, boolean noAnimation) {
locationAnimatorCoordinator.feedNewAccuracyRadius(Utils.calculateZoomLevelRadius(mapboxMap, location), noAnimation);
}
private void updateAnimatorListenerHolders() {
Set
*
*
* @param cameraMode one of the modes found in {@link CameraMode}
*/
public void setCameraMode(@CameraMode.Mode int cameraMode) {
setCameraMode(cameraMode, null);
}
/**
* Sets the camera mode, which determines how the map camera will track the rendered location.
*
*
*
* @param cameraMode one of the modes found in {@link CameraMode}
* @param transitionListener callback that's going to be invoked when the transition animation finishes
*/
public void setCameraMode(@CameraMode.Mode int cameraMode,
@Nullable OnLocationCameraTransitionListener transitionListener) {
locationCameraController.setCameraMode(cameraMode, lastLocation, new CameraTransitionListener(transitionListener));
updateCompassListenerState(true);
}
/**
* Used to reset camera animators and notify listeners when the transition finishes.
*/
private class CameraTransitionListener implements OnLocationCameraTransitionListener {
private final OnLocationCameraTransitionListener externalListener;
private CameraTransitionListener(OnLocationCameraTransitionListener externalListener) {
this.externalListener = externalListener;
}
@Override
public void onLocationCameraTransitionFinished(int cameraMode) {
if (externalListener != null) {
externalListener.onLocationCameraTransitionFinished(cameraMode);
}
reset(cameraMode);
}
@Override
public void onLocationCameraTransitionCanceled(int cameraMode) {
if (externalListener != null) {
externalListener.onLocationCameraTransitionCanceled(cameraMode);
}
reset(cameraMode);
}
private void reset(@CameraMode.Mode int cameraMode) {
locationAnimatorCoordinator.resetAllCameraAnimations(mapboxMap.getCameraPosition(),
cameraMode == CameraMode.TRACKING_GPS_NORTH);
}
}
/**
* Provides the current camera mode being used to track the location or compass updates.
*
* @return the current camera mode
*/
@CameraMode.Mode
public int getCameraMode() {
return locationCameraController.getCameraMode();
}
/**
* Sets the render mode, which determines how the location updates will be rendered on the map.
*
*
*
* @param renderMode one of the modes found in {@link RenderMode}
*/
public void setRenderMode(@RenderMode.Mode int renderMode) {
locationLayerController.setRenderMode(renderMode);
updateLayerOffsets(true);
updateCompassListenerState(true);
}
/**
* Provides the current render mode being used to show
* the location and/or compass updates on the map.
*
* @return the current render mode
*/
@RenderMode.Mode
public int getRenderMode() {
return locationLayerController.getRenderMode();
}
/**
* Returns the current location options being used.
*
* @return the current {@link LocationComponentOptions}
*/
public LocationComponentOptions getLocationComponentOptions() {
return options;
}
/**
* Apply a new component style with a style resource.
*
* @param styleRes a XML style overriding some or all the options
*/
public void applyStyle(@NonNull Context context, @StyleRes int styleRes) {
applyStyle(LocationComponentOptions.createFromAttributes(context, styleRes));
}
/**
* Apply a new component style with location component options.
*
* @param options to update the current style
*/
public void applyStyle(@NonNull final LocationComponentOptions options) {
LocationComponent.this.options = options;
if (mapboxMap.getStyle() != null) {
locationLayerController.applyStyle(options);
locationCameraController.initializeOptions(options);
staleStateManager.setEnabled(options.enableStaleState());
staleStateManager.setDelayTime(options.staleStateTimeout());
locationAnimatorCoordinator.setTrackingAnimationDurationMultiplier(options.trackingAnimationDurationMultiplier());
locationAnimatorCoordinator.setCompassAnimationEnabled(options.compassAnimationEnabled());
locationAnimatorCoordinator.setAccuracyAnimationEnabled(options.accuracyAnimationEnabled());
updateMapWithOptions(options);
}
}
/**
* Zooms to the desired zoom level.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param zoomLevel The desired zoom level.
* @param animationDuration The zoom animation duration.
* @param callback The callback with finish/cancel information
*/
public void zoomWhileTracking(double zoomLevel, long animationDuration,
@Nullable MapboxMap.CancelableCallback callback) {
if (!isLayerReady) {
return;
} else if (getCameraMode() == CameraMode.NONE) {
Logger.e(TAG, String.format("%s%s",
"LocationComponent#zoomWhileTracking method can only be used",
" when a camera mode other than CameraMode#NONE is engaged."));
return;
}
locationAnimatorCoordinator.feedNewZoomLevel(zoomLevel, mapboxMap.getCameraPosition(), animationDuration, callback);
}
/**
* Zooms to the desired zoom level.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param zoomLevel The desired zoom level.
* @param animationDuration The zoom animation duration.
*/
public void zoomWhileTracking(double zoomLevel, long animationDuration) {
zoomWhileTracking(zoomLevel, animationDuration, null);
}
/**
* Zooms to the desired zoom level.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param zoomLevel The desired zoom level.
*/
public void zoomWhileTracking(double zoomLevel) {
zoomWhileTracking(zoomLevel, DEFAULT_TRACKING_ZOOM_ANIM_DURATION, null);
}
/**
* Cancels animation started by {@link #zoomWhileTracking(double, long, MapboxMap.CancelableCallback)}.
*/
public void cancelZoomWhileTrackingAnimation() {
locationAnimatorCoordinator.cancelZoomAnimation();
}
/**
* Tilts the camera.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param tilt The desired camera tilt.
* @param animationDuration The tilt animation duration.
* @param callback The callback with finish/cancel information
*/
public void tiltWhileTracking(double tilt, long animationDuration,
@Nullable MapboxMap.CancelableCallback callback) {
if (!isLayerReady) {
return;
} else if (getCameraMode() == CameraMode.NONE) {
Logger.e(TAG, String.format("%s%s",
"LocationComponent#tiltWhileTracking method can only be used",
" when a camera mode other than CameraMode#NONE is engaged."));
return;
}
locationAnimatorCoordinator.feedNewTilt(tilt, mapboxMap.getCameraPosition(), animationDuration, callback);
}
/**
* Tilts the camera.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param tilt The desired camera tilt.
* @param animationDuration The tilt animation duration.
*/
public void tiltWhileTracking(double tilt, long animationDuration) {
tiltWhileTracking(tilt, animationDuration, null);
}
/**
* Tilts the camera.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param tilt The desired camera tilt.
*/
public void tiltWhileTracking(double tilt) {
tiltWhileTracking(tilt, DEFAULT_TRACKING_TILT_ANIM_DURATION, null);
}
/**
* Cancels animation started by {@link #tiltWhileTracking(double, long, MapboxMap.CancelableCallback)}.
*/
public void cancelTiltWhileTrackingAnimation() {
locationAnimatorCoordinator.cancelTiltAnimation();
}
/**
* Use to either force a location update or to manually control when the user location gets
* updated.
*
* @param location where the location icon is placed on the map
*/
public void forceLocationUpdate(@Nullable Location location) {
updateLocation(location, false);
}
/**
* Set max FPS at which location animators can output updates. The throttling will only impact the location puck
* and camera tracking smooth animations.
*
* {@code
* mapboxMap.addOnCameraIdleListener(new MapboxMap.OnCameraIdleListener() {
* @Override
* public void onCameraIdle() {
* double zoom = mapboxMap.getCameraPosition().zoom;
* int maxAnimationFps;
* if (zoom < 5) {
* maxAnimationFps = 3;
* } else if (zoom < 10) {
* maxAnimationFps = 5;
* } else if (zoom < 15) {
* maxAnimationFps = 7;
* } else if (zoom < 18) {
* maxAnimationFps = 15;
* } else {
* maxAnimationFps = Integer.MAX_VALUE;
* }
* locationComponent.setMaxAnimationFps(maxAnimationFps);
* }
* });
* }
*
*