package com.mapbox.mapboxsdk.maps; import android.content.Context; import android.graphics.Bitmap; import android.location.Location; import android.os.SystemClock; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v4.util.LongSparseArray; import android.support.v4.util.Pools; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewGroup; import com.mapbox.mapboxsdk.MapboxAccountManager; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions; import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.annotations.InfoWindow; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerOptions; import com.mapbox.mapboxsdk.annotations.MarkerView; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.annotations.Polygon; import com.mapbox.mapboxsdk.annotations.PolygonOptions; import com.mapbox.mapboxsdk.annotations.Polyline; import com.mapbox.mapboxsdk.annotations.PolylineOptions; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.layers.CustomLayer; import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * The general class to interact with in the Android Mapbox SDK. It exposes the entry point for all * methods related to the MapView. You cannot instantiate {@link MapboxMap} object directly, rather, * you must obtain one from the getMapAsync() method on a MapFragment or MapView that you have * added to your application. *

* Note: Similar to a View object, a MapboxMap should only be read and modified from the main thread. *

*/ public class MapboxMap { private MapView mMapView; private UiSettings mUiSettings; private TrackingSettings mTrackingSettings; private MyLocationViewSettings myLocationViewSettings; private Projection mProjection; private CameraPosition mCameraPosition; private boolean mInvalidCameraPosition; private LongSparseArray mAnnotations; private List mSelectedMarkers; private MarkerViewManager mMarkerViewManager; private List mInfoWindows; private MapboxMap.InfoWindowAdapter mInfoWindowAdapter; private boolean mMyLocationEnabled; private boolean mAllowConcurrentMultipleInfoWindows; private MapboxMap.OnMapClickListener mOnMapClickListener; private MapboxMap.OnMapLongClickListener mOnMapLongClickListener; private MapboxMap.OnMarkerClickListener mOnMarkerClickListener; private MapboxMap.OnInfoWindowClickListener mOnInfoWindowClickListener; private MapboxMap.OnInfoWindowLongClickListener mOnInfoWindowLongClickListener; private MapboxMap.OnInfoWindowCloseListener mOnInfoWindowCloseListener; private MapboxMap.OnFlingListener mOnFlingListener; private MapboxMap.OnScrollListener mOnScrollListener; private MapboxMap.OnMyLocationTrackingModeChangeListener mOnMyLocationTrackingModeChangeListener; private MapboxMap.OnMyBearingTrackingModeChangeListener mOnMyBearingTrackingModeChangeListener; private MapboxMap.OnFpsChangedListener mOnFpsChangedListener; private MapboxMap.OnCameraChangeListener mOnCameraChangeListener; private double mMaxZoomLevel = -1; private double mMinZoomLevel = -1; MapboxMap(@NonNull MapView mapView) { mMapView = mapView; mMapView.addOnMapChangedListener(new MapChangeCameraPositionListener()); mUiSettings = new UiSettings(mapView); mTrackingSettings = new TrackingSettings(mMapView, mUiSettings); mProjection = new Projection(mapView); mAnnotations = new LongSparseArray<>(); mSelectedMarkers = new ArrayList<>(); mInfoWindows = new ArrayList<>(); mMarkerViewManager = new MarkerViewManager(this, mapView); } // // MinZoom // /** *

* Sets the minimum zoom level the map can be displayed at. *

* * @param minZoom The new minimum zoom level. */ @UiThread public void setMinZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double minZoom) { if ((minZoom < MapboxConstants.MINIMUM_ZOOM) || (minZoom > MapboxConstants.MAXIMUM_ZOOM)) { Log.e(MapboxConstants.TAG, "Not setting minZoom, value is in unsupported range: " + minZoom); return; } mMinZoomLevel = minZoom; mMapView.setMinZoom(minZoom); } /** *

* Gets the maximum zoom level the map can be displayed at. *

* * @return The minimum zoom level. */ @UiThread public double getMinZoom() { if (mMinZoomLevel == -1) { return mMinZoomLevel = mMapView.getMinZoom(); } return mMinZoomLevel; } // // MaxZoom // /** *

* Sets the maximum zoom level the map can be displayed at. *

* * @param maxZoom The new maximum zoom level. */ @UiThread public void setMaxZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double maxZoom) { if ((maxZoom < MapboxConstants.MINIMUM_ZOOM) || (maxZoom > MapboxConstants.MAXIMUM_ZOOM)) { Log.e(MapboxConstants.TAG, "Not setting maxZoom, value is in unsupported range: " + maxZoom); return; } mMaxZoomLevel = maxZoom; mMapView.setMaxZoom(maxZoom); } /** *

* Gets the maximum zoom level the map can be displayed at. *

* * @return The maximum zoom level. */ @UiThread public double getMaxZoom() { if (mMaxZoomLevel == -1) { return mMaxZoomLevel = mMapView.getMaxZoom(); } return mMaxZoomLevel; } // // UiSettings // /** * Gets the user interface settings for the map. * * @return */ public UiSettings getUiSettings() { return mUiSettings; } // // TrackingSettings // /** * Gets the tracking interface settings for the map. * * @return */ public TrackingSettings getTrackingSettings() { return mTrackingSettings; } // // MyLocationViewSettings // /** * Gets the settings of the user location for the map. */ public MyLocationViewSettings getMyLocationViewSettings() { if (myLocationViewSettings == null) { myLocationViewSettings = new MyLocationViewSettings(mMapView, mMapView.getUserLocationView()); } return myLocationViewSettings; } // // Projection // /** * Get the Projection object that you can use to convert between screen coordinates and latitude/longitude coordinates. */ public Projection getProjection() { return mProjection; } // // Camera API // /** * Gets the current position of the camera. * The CameraPosition returned is a snapshot of the current position, and will not automatically update when the camera moves. * * @return The current position of the Camera. */ public final CameraPosition getCameraPosition() { if (mInvalidCameraPosition) { invalidateCameraPosition(); } return mCameraPosition; } /** * Repositions the camera according to the cameraPosition. * The move is instantaneous, and a subsequent getCameraPosition() will reflect the new position. * See CameraUpdateFactory for a set of updates. * * @param cameraPosition */ public void setCameraPosition(@NonNull CameraPosition cameraPosition) { moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); } /** * Repositions the camera according to the instructions defined in the update. * The move is instantaneous, and a subsequent getCameraPosition() will reflect the new position. * See CameraUpdateFactory for a set of updates. * * @param update The change that should be applied to the camera. */ @UiThread public final void moveCamera(CameraUpdate update) { moveCamera(update, null); } /** * Repositions the camera according to the instructions defined in the update. * The move is instantaneous, and a subsequent getCameraPosition() will reflect the new position. * See CameraUpdateFactory for a set of updates. * * @param update The change that should be applied to the camera. */ @UiThread public final void moveCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) { mCameraPosition = update.getCameraPosition(this); mMapView.jumpTo(mCameraPosition.bearing, mCameraPosition.target, mCameraPosition.tilt, mCameraPosition.zoom); if (callback != null) { callback.onFinish(); } invalidateCameraPosition(); } /** * Gradually move the camera by the default duration, zoom will not be affected unless specified * within {@link CameraUpdate}. If {@link #getCameraPosition()} is called during the animation, * it will return the current location of the camera in flight. * * @param update The change that should be applied to the camera. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ @UiThread public final void easeCamera(CameraUpdate update) { easeCamera(update, MapboxConstants.ANIMATION_DURATION); } /** * Gradually move the camera by a specified duration in milliseconds, zoom will not be affected * unless specified within {@link CameraUpdate}. If {@link #getCameraPosition()} is called * during the animation, it will return the current location of the camera in flight. * * @param update The change that should be applied to the camera. * @param durationMs The duration of the animation in milliseconds. This must be strictly * positive, otherwise an IllegalArgumentException will be thrown. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ @UiThread public final void easeCamera(CameraUpdate update, int durationMs) { easeCamera(update, durationMs, null); } /** * Gradually move the camera by a specified duration in milliseconds, zoom will not be affected * unless specified within {@link CameraUpdate}. A callback can be used to be notified when * easing the camera stops. If {@link #getCameraPosition()} is called during the animation, it * will return the current location of the camera in flight. * * @param update The change that should be applied to the camera. * @param durationMs The duration of the animation in milliseconds. This must be strictly * positive, otherwise an IllegalArgumentException will be thrown. * @param callback An optional callback to be notified from the main thread when the animation * stops. If the animation stops due to its natural completion, the callback * will be notified with onFinish(). If the animation stops due to interruption * by a later camera movement or a user gesture, onCancel() will be called. * Do not update or ease the camera from within onCancel(). * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ @UiThread public final void easeCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { easeCamera(update, durationMs, true, callback); } @UiThread public final void easeCamera(CameraUpdate update, int durationMs, boolean easingInterpolator) { easeCamera(update, durationMs, easingInterpolator, null); } @UiThread public final void easeCamera(CameraUpdate update, int durationMs, boolean easingInterpolator, final MapboxMap.CancelableCallback callback) { mCameraPosition = update.getCameraPosition(this); mMapView.easeTo(mCameraPosition.bearing, mCameraPosition.target, getDurationNano(durationMs), mCameraPosition.tilt, mCameraPosition.zoom, easingInterpolator, new CancelableCallback() { @Override public void onCancel() { if (callback != null) { callback.onCancel(); } invalidateCameraPosition(); } @Override public void onFinish() { if (callback != null) { callback.onFinish(); } invalidateCameraPosition(); } }); } /** * Animate the camera to a new location defined within {@link CameraUpdate} using a transition * animation that evokes powered flight. The animation will last the default amount of time. * During the animation, a call to {@link #getCameraPosition()} returns an intermediate location * of the camera in flight. * * @param update The change that should be applied to the camera. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ @UiThread public final void animateCamera(CameraUpdate update) { animateCamera(update, MapboxConstants.ANIMATION_DURATION, null); } /** * Animate the camera to a new location defined within {@link CameraUpdate} using a transition * animation that evokes powered flight. The animation will last the default amount of time. A * callback can be used to be notified when animating the camera stops. During the animation, a * call to {@link #getCameraPosition()} returns an intermediate location of the camera in flight. * * @param update The change that should be applied to the camera. * @param callback The callback to invoke from the main thread when the animation stops. If the * animation completes normally, onFinish() is called; otherwise, onCancel() is * called. Do not update or animate the camera from within onCancel(). * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ @UiThread public final void animateCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) { animateCamera(update, MapboxConstants.ANIMATION_DURATION, callback); } /** * Animate the camera to a new location defined within {@link CameraUpdate} using a transition * animation that evokes powered flight. The animation will last a specified amount of time * given in milliseconds. During the animation, a call to {@link #getCameraPosition()} returns * an intermediate location of the camera in flight. * * @param update The change that should be applied to the camera. * @param durationMs The duration of the animation in milliseconds. This must be strictly * positive, otherwise an IllegalArgumentException will be thrown. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ @UiThread public final void animateCamera(CameraUpdate update, int durationMs) { animateCamera(update, durationMs, null); } /** * Animate the camera to a new location defined within {@link CameraUpdate} using a transition * animation that evokes powered flight. The animation will last a specified amount of time * given in milliseconds. A callback can be used to be notified when animating the camera stops. * During the animation, a call to {@link #getCameraPosition()} returns an intermediate location * of the camera in flight. * * @param update The change that should be applied to the camera. * @param durationMs The duration of the animation in milliseconds. This must be strictly * positive, otherwise an IllegalArgumentException will be thrown. * @param callback An optional callback to be notified from the main thread when the animation * stops. If the animation stops due to its natural completion, the callback * will be notified with onFinish(). If the animation stops due to interruption * by a later camera movement or a user gesture, onCancel() will be called. * Do not update or animate the camera from within onCancel(). If a callback * isn't required, leave it as null. * @see com.mapbox.mapboxsdk.camera.CameraUpdateFactory for a set of updates. */ @UiThread public final void animateCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { mCameraPosition = update.getCameraPosition(this); mMapView.flyTo(mCameraPosition.bearing, mCameraPosition.target, getDurationNano(durationMs), mCameraPosition.tilt, mCameraPosition.zoom, new CancelableCallback() { @Override public void onCancel() { if (callback != null) { callback.onCancel(); } invalidateCameraPosition(); } @Override public void onFinish() { if (mOnCameraChangeListener != null) { mOnCameraChangeListener.onCameraChange(mCameraPosition); } if (callback != null) { callback.onFinish(); } invalidateCameraPosition(); } }); } /** * Converts milliseconds to nanoseconds * * @param durationMs The time in milliseconds * @return time in nanoseconds */ private long getDurationNano(long durationMs) { return durationMs > 0 ? TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS) : 0; } /** * Invalidates the current camera position by reconstructing it from mbgl */ private void invalidateCameraPosition() { mInvalidCameraPosition = false; CameraPosition cameraPosition = mMapView.invalidateCameraPosition(); if (cameraPosition != null) { mCameraPosition = cameraPosition; } if (mOnCameraChangeListener != null) { mOnCameraChangeListener.onCameraChange(mCameraPosition); } } // // Reset North // /** * Resets the map view to face north. */ public void resetNorth() { mMapView.resetNorth(); } // // Debug // /** * Returns whether the map debug information is currently shown. * * @return If true, map debug information is currently shown. */ @UiThread public boolean isDebugActive() { return mMapView.isDebugActive(); } /** *

* Changes whether the map debug information is shown. *

* The default value is false. * * @param debugActive If true, map debug information is shown. */ @UiThread public void setDebugActive(boolean debugActive) { mMapView.setDebugActive(debugActive); } /** *

* Cycles through the map debug options. *

* The value of {@link MapView#isDebugActive()} reflects whether there are * any map debug options enabled or disabled. * * @see MapView#isDebugActive() */ @UiThread public void cycleDebugOptions() { mMapView.cycleDebugOptions(); } // // Styling // /** *

* Loads a new map style from the specified URL. *

* {@code url} can take the following forms: *
    *
  • {@code Style.*}: load one of the bundled styles in {@link Style}.
  • *
  • {@code mapbox://styles//