package com.mapbox.mapboxsdk.views; import android.Manifest; import android.app.Activity; import android.app.ActivityManager; import android.app.Dialog; import android.app.Fragment; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PointF; import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.location.Location; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.CallSuper; import android.support.annotation.FloatRange; import android.support.annotation.IntDef; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresPermission; import android.support.annotation.UiThread; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; import android.view.InputDevice; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.view.ViewConfiguration; import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ZoomButtonsController; import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector; import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector; import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.InfoWindow; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerOptions; 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.annotations.IconFactory; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.MathConstants; import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.exceptions.IconBitmapChangedException; import com.mapbox.mapboxsdk.exceptions.InvalidAccessTokenException; import com.mapbox.mapboxsdk.geometry.BoundingBox; import com.mapbox.mapboxsdk.geometry.CoordinateBounds; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngZoom; import com.mapbox.mapboxsdk.layers.CustomLayer; import com.mapbox.mapboxsdk.utils.ApiAccess; import com.mapbox.mapboxsdk.utils.MathUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; /** *

* A {@code MapView} provides an embeddable map interface. * You use this class to display map information and to manipulate the map contents from your application. * You can center the map on a given coordinate, specify the size of the area you want to display, * and style the features of the map to fit your application's use case. *

*

* Use of {@code MapView} requires a Mapbox API access token. * Obtain an access token on the Mapbox account page. *

* Warning: Please note that you are responsible for getting permission to use the map data, * and for ensuring your use adheres to the relevant terms of use. * * @see MapView#setAccessToken(String) */ public final class MapView extends FrameLayout { // // Static members // // Used for logging private static final String TAG = "MapView"; // Used for animation private static final long ANIMATION_DURATION = 300; // Used for saving instance state private static final String STATE_CENTER_LATLNG = "centerLatLng"; private static final String STATE_CENTER_DIRECTION = "centerDirection"; private static final String STATE_ZOOM = "zoomLevel"; private static final String STATE_TILT = "tilt"; private static final String STATE_ZOOM_ENABLED = "zoomEnabled"; private static final String STATE_SCROLL_ENABLED = "scrollEnabled"; private static final String STATE_ROTATE_ENABLED = "rotateEnabled"; private static final String STATE_TILT_ENABLED = "tiltEnabled"; private static final String STATE_ZOOM_CONTROLS_ENABLED = "zoomControlsEnabled"; private static final String STATE_DEBUG_ACTIVE = "debugActive"; private static final String STATE_STYLE_URL = "styleUrl"; private static final String STATE_ACCESS_TOKEN = "accessToken"; private static final String STATE_STYLE_CLASSES = "styleClasses"; private static final String STATE_DEFAULT_TRANSITION_DURATION = "defaultTransitionDuration"; private static final String STATE_MY_LOCATION_ENABLED = "myLocationEnabled"; private static final String STATE_MY_LOCATION_TRACKING_MODE = "myLocationTracking"; private static final String STATE_COMPASS_ENABLED = "compassEnabled"; private static final String STATE_COMPASS_GRAVITY = "compassGravity"; private static final String STATE_COMPASS_MARGIN_LEFT = "compassMarginLeft"; private static final String STATE_COMPASS_MARGIN_TOP = "compassMarginTop"; private static final String STATE_COMPASS_MARGIN_RIGHT = "compassMarginRight"; private static final String STATE_COMPASS_MARGIN_BOTTOM = "compassMarginBottom"; private static final String STATE_LOGO_GRAVITY = "logoGravity"; private static final String STATE_LOGO_MARGIN_LEFT = "logoMarginLeft"; private static final String STATE_LOGO_MARGIN_TOP = "logoMarginTop"; private static final String STATE_LOGO_MARGIN_RIGHT = "logoMarginRight"; private static final String STATE_LOGO_MARGIN_BOTTOM = "logoMarginBottom"; private static final String STATE_LOGO_VISIBILITY = "logoVisibility"; private static final String STATE_ATTRIBUTION_GRAVITY = "attrGravity"; private static final String STATE_ATTRIBUTION_MARGIN_LEFT = "attrMarginLeft"; private static final String STATE_ATTRIBUTION_MARGIN_TOP = "attrMarginTop"; private static final String STATE_ATTRIBUTION_MARGIN_RIGHT = "attrMarginRight"; private static final String STATE_ATTRIBUTION_MARGIN_BOTTOM = "atrrMarginBottom"; private static final String STATE_ATTRIBUTION_VISIBILITY = "atrrVisibility"; // Used for positioning views private static final float DIMENSION_SEVEN_DP = 7f; private static final float DIMENSION_TEN_DP = 10f; private static final float DIMENSION_SIXTEEN_DP = 16f; private static final float DIMENSION_SEVENTYSIX_DP = 76f; // Used to select "Improve this map" link in the attribution dialog // Index into R.arrays.attribution_links private static final int ATTRIBUTION_INDEX_IMPROVE_THIS_MAP = 2; /** * The currently supported maximum zoom level. * * @see MapView#setZoomLevel(double) * @deprecated use #MAXIMUM_ZOOM instead. */ public static final double MAXIMUM_ZOOM_LEVEL = 18.0; /** * The currently supported maximum zoom level. * * @see MapView#setZoom(double) */ public static final double MAXIMUM_ZOOM = 18.0; /** * The currently supported maximum and minimum tilt values. * * @see MapView#setTilt(Double, Long) */ private static final double MINIMUM_TILT = 0; private static final double MAXIMUM_TILT = 60; // // Instance members // // Used to call JNI NativeMapView private NativeMapView mNativeMapView; // Used to track rendering private TextureView mTextureView; // Used to handle DPI scaling private float mScreenDensity = 1.0f; // Touch gesture detectors private GestureDetectorCompat mGestureDetector; private ScaleGestureDetector mScaleGestureDetector; private RotateGestureDetector mRotateGestureDetector; private ShoveGestureDetector mShoveGestureDetector; private boolean mTwoTap = false; private boolean mZoomStarted = false; private boolean mQuickZoom = false; // Shows zoom buttons private ZoomButtonsController mZoomButtonsController; private boolean mZoomControlsEnabled = false; // Used to track trackball long presses private TrackballLongPressTimeOut mCurrentTrackballLongPressTimeOut; // Receives changes to network connectivity private ConnectivityReceiver mConnectivityReceiver; // Used for user location private UserLocationView mUserLocationView; // Used for the compass private CompassView mCompassView; // Used for displaying annotations // Every annotation that has been added to the map private final List mAnnotations = new ArrayList<>(); private List mMarkersNearLastTap = new ArrayList<>(); private List mSelectedMarkers = new ArrayList<>(); private List mInfoWindows = new ArrayList<>(); private InfoWindowAdapter mInfoWindowAdapter; private List mIcons = new ArrayList<>(); // Used for the Mapbox Logo private ImageView mLogoView; // Used for attributions control private ImageView mAttributionsView; // Used to manage MapChange event listeners private List mOnMapChangedListener = new ArrayList<>(); // Used to manage map click event listeners private OnMapClickListener mOnMapClickListener; private OnMapLongClickListener mOnMapLongClickListener; // Used to manage fling and scroll event listeners private OnFlingListener mOnFlingListener; private OnScrollListener mOnScrollListener; // Used to manage marker click event listeners private OnMarkerClickListener mOnMarkerClickListener; private OnInfoWindowClickListener mOnInfoWindowClickListener; // Used to manage FPS change event listeners private OnFpsChangedListener mOnFpsChangedListener; // Used to manage tracking mode changes private OnMyLocationTrackingModeChangeListener mOnMyLocationTrackingModeChangeListener; private OnMyBearingTrackingModeChangeListener mOnMyBearingTrackingModeChangeListener; // // Properties // // These are properties with setters/getters, saved in onSaveInstanceState and XML attributes private boolean mZoomEnabled = true; private boolean mScrollEnabled = true; private boolean mRotateEnabled = true; private boolean mTiltEnabled = true; private boolean mAllowConcurrentMultipleOpenInfoWindows = false; private String mStyleUrl; // // Inner classes // // // Enums // /** * Map change event types. * * @see MapView.OnMapChangedListener#onMapChanged(int) */ @IntDef({REGION_WILL_CHANGE, REGION_WILL_CHANGE_ANIMATED, REGION_IS_CHANGING, REGION_DID_CHANGE, REGION_DID_CHANGE_ANIMATED, WILL_START_LOADING_MAP, DID_FINISH_LOADING_MAP, DID_FAIL_LOADING_MAP, WILL_START_RENDERING_FRAME, DID_FINISH_RENDERING_FRAME, DID_FINISH_RENDERING_FRAME_FULLY_RENDERED, WILL_START_RENDERING_MAP, DID_FINISH_RENDERING_MAP, DID_FINISH_RENDERING_MAP_FULLY_RENDERED }) @Retention(RetentionPolicy.SOURCE) public @interface MapChange { } /** *

* This {@link MapChange} is triggered whenever the currently displayed map region is about to changing * without an animation. *

*

* This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends * with {@link MapView#REGION_DID_CHANGE}. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int REGION_WILL_CHANGE = 0; /** *

* This {@link MapChange} is triggered whenever the currently displayed map region is about to changing * with an animation. *

*

* This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends * with {@link MapView#REGION_DID_CHANGE_ANIMATED}. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int REGION_WILL_CHANGE_ANIMATED = 1; /** *

* This {@link MapChange} is triggered whenever the currently displayed map region is changing. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int REGION_IS_CHANGING = 2; /** *

* This {@link MapChange} is triggered whenever the currently displayed map region finished changing * without an animation. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int REGION_DID_CHANGE = 3; /** *

* This {@link MapChange} is triggered whenever the currently displayed map region finished changing * with an animation. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int REGION_DID_CHANGE_ANIMATED = 4; /** *

* This {@link MapChange} is triggered when the map is about to start loading a new map style. *

*

* This event is followed by {@link MapView#DID_FINISH_LOADING_MAP} or * {@link MapView#DID_FAIL_LOADING_MAP}. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int WILL_START_LOADING_MAP = 5; /** *

* This {@link MapChange} is triggered when the map has successfully loaded a new map style. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int DID_FINISH_LOADING_MAP = 6; /** *

* This {@link MapChange} is currently not implemented. *

*

* This event is triggered when the map has failed to load a new map style. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int DID_FAIL_LOADING_MAP = 7; /** *

* This {@link MapChange} is currently not implemented. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int WILL_START_RENDERING_FRAME = 8; /** *

* This {@link MapChange} is currently not implemented. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int DID_FINISH_RENDERING_FRAME = 9; /** *

* This {@link MapChange} is currently not implemented. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int DID_FINISH_RENDERING_FRAME_FULLY_RENDERED = 10; /** *

* This {@link MapChange} is currently not implemented. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int WILL_START_RENDERING_MAP = 11; /** *

* This {@link MapChange} is currently not implemented. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int DID_FINISH_RENDERING_MAP = 12; /** *

* This {@link MapChange} is currently not implemented. *

* * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener */ public static final int DID_FINISH_RENDERING_MAP_FULLY_RENDERED = 13; // // Interfaces // /** * Interface definition for a callback to be invoked when the map is flinged. * * @see MapView#setOnFlingListener(OnFlingListener) */ public interface OnFlingListener { /** * Called when the map is flinged. */ void onFling(); } /** * Interface definition for a callback to be invoked when the map is scrolled. * * @see MapView#setOnScrollListener(OnScrollListener) */ public interface OnScrollListener { /** * Called when the map is scrolled. */ void onScroll(); } /** * Interface definition for a callback to be invoked on every frame rendered to the map view. * * @see MapView#setOnFpsChangedListener(OnFpsChangedListener) */ public interface OnFpsChangedListener { /** * Called for every frame rendered to the map view. * * @param fps The average number of frames rendered over the last second. */ void onFpsChanged(double fps); } /** * Interface definition for a callback to be invoked when the user clicks on the map view. * * @see MapView#setOnMapClickListener(OnMapClickListener) */ public interface OnMapClickListener { /** * Called when the user clicks on the map view. * * @param point The projected map coordinate the user clicked on. */ void onMapClick(@NonNull LatLng point); } /** * Interface definition for a callback to be invoked when the user long clicks on the map view. * * @see MapView#setOnMapLongClickListener(OnMapLongClickListener) */ public interface OnMapLongClickListener { /** * Called when the user long clicks on the map view. * * @param point The projected map coordinate the user long clicked on. */ void onMapLongClick(@NonNull LatLng point); } /** * Interface definition for a callback to be invoked when the user clicks on a marker. * * @see MapView#setOnMarkerClickListener(OnMarkerClickListener) */ public interface OnMarkerClickListener { /** * Called when the user clicks on a marker. * * @param marker The marker the user clicked on. * @return If true the listener has consumed the event and the info window will not be shown. */ boolean onMarkerClick(@NonNull Marker marker); } /** * Interface definition for a callback to be invoked when the user clicks on an info window. * * @see MapView#setOnInfoWindowClickListener(OnInfoWindowClickListener) */ public interface OnInfoWindowClickListener { /** * Called when the user clicks on an info window. * * @param marker The marker of the info window the user clicked on. * @return If true the listener has consumed the event and the info window will not be closed. */ boolean onMarkerClick(@NonNull Marker marker); } /** * Interface definition for a callback to be invoked when the displayed map view changes. * * @see MapView#addOnMapChangedListener(OnMapChangedListener) * @see MapView.MapChange */ public interface OnMapChangedListener { /** * Called when the displayed map view changes. * * @param change Type of map change event, one of {@link #REGION_WILL_CHANGE}, * {@link #REGION_WILL_CHANGE_ANIMATED}, * {@link #REGION_IS_CHANGING}, * {@link #REGION_DID_CHANGE}, * {@link #REGION_DID_CHANGE_ANIMATED}, * {@link #WILL_START_LOADING_MAP}, * {@link #DID_FAIL_LOADING_MAP}, * {@link #DID_FINISH_LOADING_MAP}, * {@link #WILL_START_RENDERING_FRAME}, * {@link #DID_FINISH_RENDERING_FRAME}, * {@link #DID_FINISH_RENDERING_FRAME_FULLY_RENDERED}, * {@link #WILL_START_RENDERING_MAP}, * {@link #DID_FINISH_RENDERING_MAP}, * {@link #DID_FINISH_RENDERING_MAP_FULLY_RENDERED}. */ void onMapChanged(@MapChange int change); } /** * Interface definition for a callback to be invoked when an info window will be shown. * * @see MapView#setInfoWindowAdapter(InfoWindowAdapter) */ public interface InfoWindowAdapter { /** * Called when an info window will be shown as a result of a marker click. * * @param marker The marker the user clicked on. * @return View to be shown as a info window. If null is returned the default * info window will be shown. */ @Nullable View getInfoWindow(@NonNull Marker marker); } /** * Interface definition for a callback to be invoked when the the My Location dot * (which signifies the user's location) changes location. * * @see MapView#setOnMyLocationChangeListener(OnMyLocationChangeListener) */ public interface OnMyLocationChangeListener { /** * Called when the location of the My Location dot has changed * (be it latitude/longitude, bearing or accuracy). * * @param location The current location of the My Location dot The type of map change event. */ void onMyLocationChange(@Nullable Location location); } /** * Interface definition for a callback to be invoked when the the My Location tracking mode changes. * * @see MapView#setMyLocationTrackingMode(int) */ public interface OnMyLocationTrackingModeChangeListener { /** * Called when the tracking mode of My Location tracking has changed * * @param myLocationTrackingMode the current active location tracking mode */ void onMyLocationTrackingModeChange(@MyLocationTracking.Mode int myLocationTrackingMode); } /** * Interface definition for a callback to be invoked when the the My Location tracking mode changes. * * @see MapView#setMyLocationTrackingMode(int) */ public interface OnMyBearingTrackingModeChangeListener { /** * Called when the tracking mode of My Bearing tracking has changed * * @param myBearingTrackingMode the current active bearing tracking mode */ void onMyBearingTrackingModeChange(@MyBearingTracking.Mode int myBearingTrackingMode); } /** * A callback interface for reporting when a task is complete or cancelled. */ public interface CancelableCallback { /** * Invoked when a task is cancelled. */ void onCancel(); /** * Invoked when a task is complete. */ void onFinish(); } // // Constructors // /** * Simple constructor to use when creating a {@link MapView} from code using the default map style. * * @param context The {@link Context} of the {@link android.app.Activity} * or {@link android.app.Fragment} the {@link MapView} is running in. * @param accessToken Your public Mapbox access token. Used to load map styles and tiles. */ @UiThread public MapView(@NonNull Context context, @NonNull String accessToken) { super(context); if (accessToken == null) { Log.w(TAG, "accessToken was null, so just returning"); return; } initialize(context, null); setAccessToken(accessToken); setStyleUrl(null); } /** * Simple constructor to use when creating a {@link MapView} from code using the provided map style URL. * * @param context The {@link Context} of the {@link android.app.Activity} * or {@link android.app.Fragment} the {@link MapView} is running in. * @param accessToken Your public Mapbox access token. Used to load map styles and tiles. * @param styleUrl A URL to the map style initially displayed. See {@link MapView#setStyleUrl(String)} for possible values. * @see MapView#setStyleUrl(String) */ @UiThread public MapView(@NonNull Context context, @NonNull String accessToken, @NonNull String styleUrl) { super(context); if (accessToken == null) { Log.w(TAG, "accessToken was null, so just returning"); return; } if (styleUrl == null) { Log.w(TAG, "styleUrl was null, so just returning"); return; } initialize(context, null); setAccessToken(accessToken); setStyleUrl(styleUrl); } // Constructor that is called when inflating a view from XML. /** * Do not call from code. */ @UiThread public MapView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); initialize(context, attrs); } // Constructor that is called when inflating a view from XML. /** * Do not call from code. */ @UiThread public MapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initialize(context, attrs); } // // Initialization // // Common initialization code goes here private void initialize(Context context, AttributeSet attrs) { if (context == null) { Log.w(TAG, "context was null, so just returning"); return; } // Inflate content View view = LayoutInflater.from(context).inflate(R.layout.mapview_internal, this); if (!isInEditMode()) { setWillNotDraw(false); } // Reference the TextureView mTextureView = (TextureView) view.findViewById(R.id.textureView); mTextureView.setSurfaceTextureListener(new SurfaceTextureListener()); // Check if we are in Android Studio UI editor to avoid error in layout preview if (isInEditMode()) { return; } // Get the screen's density mScreenDensity = context.getResources().getDisplayMetrics().density; // Get the cache path String cachePath = context.getCacheDir().getAbsolutePath(); String dataPath = context.getFilesDir().getAbsolutePath(); String apkPath = context.getPackageCodePath(); // Create the NativeMapView int availableProcessors = Runtime.getRuntime().availableProcessors(); ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); ActivityManager activityManager = (ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE); activityManager.getMemoryInfo(memoryInfo); long maxMemory = memoryInfo.availMem; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { maxMemory = memoryInfo.totalMem; } mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity, availableProcessors, maxMemory); // Ensure this view is interactable setClickable(true); setLongClickable(true); setFocusable(true); setFocusableInTouchMode(true); requestFocus(); // Touch gesture detectors mGestureDetector = new GestureDetectorCompat(context, new GestureListener()); mGestureDetector.setIsLongpressEnabled(true); mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener()); ScaleGestureDetectorCompat.setQuickScaleEnabled(mScaleGestureDetector, true); mRotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener()); mShoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener()); // Shows the zoom controls if (!context.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) { mZoomControlsEnabled = true; } mZoomButtonsController = new ZoomButtonsController(this); mZoomButtonsController.setZoomSpeed(ANIMATION_DURATION); mZoomButtonsController.setOnZoomListener(new OnZoomListener()); // Check current connection status ConnectivityManager connectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); boolean isConnected = (activeNetwork != null) && activeNetwork.isConnectedOrConnecting(); onConnectivityChanged(isConnected); // Setup user location UI mUserLocationView = (UserLocationView) view.findViewById(R.id.userLocationView); mUserLocationView.setMapView(this); // Setup compass mCompassView = (CompassView) view.findViewById(R.id.compassView); mCompassView.setOnClickListener(new CompassView.CompassClickListener(this)); // Setup Mapbox logo mLogoView = (ImageView) view.findViewById(R.id.logoView); // Setup Attributions control mAttributionsView = (ImageView) view.findViewById(R.id.attributionView); mAttributionsView.setOnClickListener(new AttributionOnClickListener(this)); // Load the attributes TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0); try { double centerLatitude = typedArray.getFloat(R.styleable.MapView_center_latitude, 0.0f); double centerLongitude = typedArray.getFloat(R.styleable.MapView_center_longitude, 0.0f); setLatLng(new LatLng(centerLatitude, centerLongitude)); // need to set zoom level first because of limitation on rotating when zoomed out float zoom = typedArray.getFloat(R.styleable.MapView_zoom, 0.0f); if(zoom != 0.0f){ setZoom(zoom); }else{ setZoomLevel(typedArray.getFloat(R.styleable.MapView_zoom_level, 0.0f)); } setDirection(typedArray.getFloat(R.styleable.MapView_direction, 0.0f)); setZoomEnabled(typedArray.getBoolean(R.styleable.MapView_zoom_enabled, true)); setScrollEnabled(typedArray.getBoolean(R.styleable.MapView_scroll_enabled, true)); setRotateEnabled(typedArray.getBoolean(R.styleable.MapView_rotate_enabled, true)); setTiltEnabled(typedArray.getBoolean(R.styleable.MapView_tilt_enabled, true)); setZoomControlsEnabled(typedArray.getBoolean(R.styleable.MapView_zoom_controls_enabled, isZoomControlsEnabled())); setDebugActive(typedArray.getBoolean(R.styleable.MapView_debug_active, false)); if (typedArray.getString(R.styleable.MapView_style_url) != null) { setStyleUrl(typedArray.getString(R.styleable.MapView_style_url)); } if (typedArray.getString(R.styleable.MapView_access_token) != null) { setAccessToken(typedArray.getString(R.styleable.MapView_access_token)); } if (typedArray.getString(R.styleable.MapView_style_classes) != null) { List styleClasses = Arrays.asList(typedArray .getString(R.styleable.MapView_style_classes).split("\\s*,\\s*")); for (String styleClass : styleClasses) { if (styleClass.length() == 0) { styleClasses.remove(styleClass); } } setStyleClasses(styleClasses); } // Compass setCompassEnabled(typedArray.getBoolean(R.styleable.MapView_compass_enabled, true)); setCompassGravity(typedArray.getInt(R.styleable.MapView_compass_gravity, Gravity.TOP | Gravity.END)); setWidgetMargins(mCompassView, typedArray.getDimension(R.styleable.MapView_compass_margin_left, DIMENSION_TEN_DP) , typedArray.getDimension(R.styleable.MapView_compass_margin_top, DIMENSION_TEN_DP) , typedArray.getDimension(R.styleable.MapView_compass_margin_right, DIMENSION_TEN_DP) , typedArray.getDimension(R.styleable.MapView_compass_margin_bottom, DIMENSION_TEN_DP)); // Logo setLogoVisibility(typedArray.getInt(R.styleable.MapView_logo_visibility, View.VISIBLE)); setLogoGravity(typedArray.getInt(R.styleable.MapView_logo_gravity, Gravity.BOTTOM | Gravity.START)); setWidgetMargins(mLogoView, typedArray.getDimension(R.styleable.MapView_logo_margin_left, DIMENSION_SIXTEEN_DP) , typedArray.getDimension(R.styleable.MapView_logo_margin_top, DIMENSION_SIXTEEN_DP) , typedArray.getDimension(R.styleable.MapView_logo_margin_right, DIMENSION_SIXTEEN_DP) , typedArray.getDimension(R.styleable.MapView_logo_margin_bottom, DIMENSION_SIXTEEN_DP)); // Attribution setAttributionVisibility(typedArray.getInt(R.styleable.MapView_attribution_visibility, View.VISIBLE)); setAttributionGravity(typedArray.getInt(R.styleable.MapView_attribution_gravity, Gravity.BOTTOM)); setWidgetMargins(mAttributionsView, typedArray.getDimension(R.styleable.MapView_attribution_margin_left, DIMENSION_SEVENTYSIX_DP) , typedArray.getDimension(R.styleable.MapView_attribution_margin_top, DIMENSION_SEVEN_DP) , typedArray.getDimension(R.styleable.MapView_attribution_margin_right, DIMENSION_SEVEN_DP) , typedArray.getDimension(R.styleable.MapView_attribution_margin_bottom, DIMENSION_SEVEN_DP)); // User location try { //noinspection ResourceType setMyLocationEnabled(typedArray.getBoolean(R.styleable.MapView_my_location_enabled, false)); }catch (SecurityException ignore){ // User did not accept location permissions } } finally { typedArray.recycle(); } } // // Lifecycle events // /** *

* You must call this method from the parent's {@link android.app.Activity#onCreate(Bundle)} or * {@link android.app.Fragment#onCreate(Bundle)}. *

* You must set a valid access token with {@link MapView#setAccessToken(String)} before you this method * or an exception will be thrown. * * @param savedInstanceState Pass in the parent's savedInstanceState. * @see MapView#setAccessToken(String) */ @UiThread public void onCreate(@Nullable Bundle savedInstanceState) { if (savedInstanceState != null) { setLatLng((LatLng) savedInstanceState.getParcelable(STATE_CENTER_LATLNG)); // need to set zoom level first because of limitation on rotating when zoomed out setZoom(savedInstanceState.getDouble(STATE_ZOOM)); setDirection(savedInstanceState.getDouble(STATE_CENTER_DIRECTION)); setTilt(savedInstanceState.getDouble(STATE_TILT), null); setZoomEnabled(savedInstanceState.getBoolean(STATE_ZOOM_ENABLED)); setScrollEnabled(savedInstanceState.getBoolean(STATE_SCROLL_ENABLED)); setRotateEnabled(savedInstanceState.getBoolean(STATE_ROTATE_ENABLED)); setTiltEnabled(savedInstanceState.getBoolean(STATE_TILT_ENABLED)); setZoomControlsEnabled(savedInstanceState.getBoolean(STATE_ZOOM_CONTROLS_ENABLED)); setDebugActive(savedInstanceState.getBoolean(STATE_DEBUG_ACTIVE)); setStyleUrl(savedInstanceState.getString(STATE_STYLE_URL)); setAccessToken(savedInstanceState.getString(STATE_ACCESS_TOKEN)); List appliedStyleClasses = savedInstanceState.getStringArrayList(STATE_STYLE_CLASSES); if (!appliedStyleClasses.isEmpty()) { setStyleClasses(appliedStyleClasses); } mNativeMapView.setDefaultTransitionDuration( savedInstanceState.getLong(STATE_DEFAULT_TRANSITION_DURATION)); // User location try { //noinspection ResourceType setMyLocationEnabled(savedInstanceState.getBoolean(STATE_MY_LOCATION_ENABLED)); }catch (SecurityException ignore){ // User did not accept location permissions } // Compass setCompassEnabled(savedInstanceState.getBoolean(STATE_COMPASS_ENABLED)); setCompassGravity(savedInstanceState.getInt(STATE_COMPASS_GRAVITY)); setCompassMargins(savedInstanceState.getInt(STATE_COMPASS_MARGIN_LEFT) , savedInstanceState.getInt(STATE_COMPASS_MARGIN_TOP) , savedInstanceState.getInt(STATE_COMPASS_MARGIN_RIGHT) , savedInstanceState.getInt(STATE_COMPASS_MARGIN_BOTTOM)); // Logo setLogoVisibility(savedInstanceState.getInt(STATE_LOGO_VISIBILITY)); setLogoGravity(savedInstanceState.getInt(STATE_LOGO_GRAVITY)); setLogoMargins(savedInstanceState.getInt(STATE_LOGO_MARGIN_LEFT) , savedInstanceState.getInt(STATE_LOGO_MARGIN_TOP) , savedInstanceState.getInt(STATE_LOGO_MARGIN_RIGHT) , savedInstanceState.getInt(STATE_LOGO_MARGIN_BOTTOM)); // Attribution setAttributionVisibility(savedInstanceState.getInt(STATE_ATTRIBUTION_VISIBILITY)); setAttributionGravity(savedInstanceState.getInt(STATE_ATTRIBUTION_GRAVITY)); setAttributionMargins(savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_LEFT) , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_TOP) , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_RIGHT) , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_BOTTOM)); //noinspection ResourceType setMyLocationTrackingMode(savedInstanceState.getInt(STATE_MY_LOCATION_TRACKING_MODE, MyLocationTracking.TRACKING_NONE)); } // Force a check for an access token validateAccessToken(getAccessToken()); // Initialize EGL mNativeMapView.initializeDisplay(); mNativeMapView.initializeContext(); // Add annotation deselection listener addOnMapChangedListener(new OnMapChangedListener() { @Override public void onMapChanged(@MapChange int change) { if (change == DID_FINISH_LOADING_MAP) { reloadIcons(); reloadMarkers(); adjustTopOffsetPixels(); } } }); } /** * You must call this method from the parent's {@link android.app.Activity#onSaveInstanceState(Bundle)} * or {@link android.app.Fragment#onSaveInstanceState(Bundle)}. * * @param outState Pass in the parent's outState. */ @UiThread public void onSaveInstanceState(@NonNull Bundle outState) { if (outState == null) { Log.w(TAG, "outState was null, so just returning"); return; } outState.putParcelable(STATE_CENTER_LATLNG, getLatLng()); // need to set zoom level first because of limitation on rotating when zoomed out outState.putDouble(STATE_ZOOM, getZoom()); outState.putDouble(STATE_CENTER_DIRECTION, getDirection()); outState.putDouble(STATE_TILT, getTilt()); outState.putBoolean(STATE_ZOOM_ENABLED, isZoomEnabled()); outState.putBoolean(STATE_SCROLL_ENABLED, isScrollEnabled()); outState.putBoolean(STATE_ROTATE_ENABLED, isRotateEnabled()); outState.putBoolean(STATE_TILT_ENABLED, isTiltEnabled()); outState.putBoolean(STATE_ZOOM_CONTROLS_ENABLED, isZoomControlsEnabled()); outState.putBoolean(STATE_DEBUG_ACTIVE, isDebugActive()); outState.putString(STATE_STYLE_URL, getStyleUrl()); outState.putString(STATE_ACCESS_TOKEN, getAccessToken()); outState.putStringArrayList(STATE_STYLE_CLASSES, new ArrayList<>(getStyleClasses())); outState.putLong(STATE_DEFAULT_TRANSITION_DURATION, mNativeMapView.getDefaultTransitionDuration()); outState.putBoolean(STATE_MY_LOCATION_ENABLED, isMyLocationEnabled()); outState.putInt(STATE_MY_LOCATION_TRACKING_MODE, mUserLocationView.getMyLocationTrackingMode()); // Compass LayoutParams compassParams = (LayoutParams) mCompassView.getLayoutParams(); outState.putBoolean(STATE_COMPASS_ENABLED, isCompassEnabled()); outState.putInt(STATE_COMPASS_GRAVITY, compassParams.gravity); outState.putInt(STATE_COMPASS_MARGIN_LEFT, compassParams.leftMargin); outState.putInt(STATE_COMPASS_MARGIN_TOP, compassParams.topMargin); outState.putInt(STATE_COMPASS_MARGIN_BOTTOM, compassParams.bottomMargin); outState.putInt(STATE_COMPASS_MARGIN_RIGHT, compassParams.rightMargin); // Logo LayoutParams logoParams = (LayoutParams) mLogoView.getLayoutParams(); outState.putInt(STATE_LOGO_GRAVITY, logoParams.gravity); outState.putInt(STATE_LOGO_MARGIN_LEFT, logoParams.leftMargin); outState.putInt(STATE_LOGO_MARGIN_TOP, logoParams.topMargin); outState.putInt(STATE_LOGO_MARGIN_RIGHT, logoParams.rightMargin); outState.putInt(STATE_LOGO_MARGIN_BOTTOM, logoParams.bottomMargin); outState.putInt(STATE_LOGO_VISIBILITY, mLogoView.getVisibility()); // Attribution LayoutParams attrParams = (LayoutParams) mAttributionsView.getLayoutParams(); outState.putInt(STATE_ATTRIBUTION_GRAVITY, attrParams.gravity); outState.putInt(STATE_ATTRIBUTION_MARGIN_LEFT, attrParams.leftMargin); outState.putInt(STATE_ATTRIBUTION_MARGIN_TOP, attrParams.topMargin); outState.putInt(STATE_ATTRIBUTION_MARGIN_RIGHT, attrParams.rightMargin); outState.putInt(STATE_ATTRIBUTION_MARGIN_BOTTOM, attrParams.bottomMargin); outState.putInt(STATE_ATTRIBUTION_VISIBILITY, mAttributionsView.getVisibility()); } /** * You must call this method from the parent's {@link Activity#onDestroy()} or {@link Fragment#onDestroy()}. */ @UiThread public void onDestroy() { mNativeMapView.terminateContext(); mNativeMapView.terminateDisplay(); mNativeMapView.destroySurface(); mNativeMapView.destroy(); mNativeMapView = null; } /** * You must call this method from the parent's {@link Activity#onStart()} or {@link Fragment#onStart()}. */ @UiThread public void onStart() { mUserLocationView.onStart(); } /** * You must call this method from the parent's {@link Activity#onStop()} or {@link Fragment#onStop()} */ @UiThread public void onStop() { mUserLocationView.onStop(); } /** * You must call this method from the parent's {@link Activity#onPause()} or {@link Fragment#onPause()}. */ @UiThread public void onPause() { // Register for connectivity changes getContext().unregisterReceiver(mConnectivityReceiver); mConnectivityReceiver = null; mUserLocationView.pause(); mNativeMapView.pause(); } /** * You must call this method from the parent's {@link Activity#onResume()} or {@link Fragment#onResume()}. */ @UiThread public void onResume() { // Register for connectivity changes mConnectivityReceiver = new ConnectivityReceiver(); getContext().registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); mNativeMapView.resume(); mNativeMapView.update(); mUserLocationView.resume(); } /** * You must call this method from the parent's {@link Activity#onLowMemory()} or {@link Fragment#onLowMemory()}. */ @UiThread public void onLowMemory() { mNativeMapView.onLowMemory(); } // // Position // /** * Returns the current {@link LatLng} at the center of the map view. * * @return The current center. */ @UiThread @NonNull public LatLng getLatLng() { return mNativeMapView.getLatLng(); } /** *

* Centers the map on a new {@link LatLng} immediately without changing the zoom level. *

*

* The initial {@link LatLng} is (0, 0). *

* If you want to animate the change, use {@link MapView#setLatLng(LatLng, boolean)}. * * @param latLng The new center. * @see MapView#setLatLng(LatLng, boolean) */ @UiThread public void setLatLng(@NonNull LatLng latLng) { setLatLng(latLng, false); } /** *

* Centers the map on a new {@link LatLng} without changing the zoom level and optionally animates the change. *

* The initial {@link LatLng} is (0, 0). * * @param latLng The new center. * @param animated If true, animates the change. If false, immediately changes the map. */ @UiThread public void setLatLng(@NonNull LatLng latLng, boolean animated) { if (latLng == null) { Log.w(TAG, "latLng was null, so just returning"); return; } if (animated) { CameraPosition cameraPosition = new CameraPosition.Builder(getCameraPosition()) .target(latLng) .build(); animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), (int) ANIMATION_DURATION, null); } else { jumpTo(mNativeMapView.getBearing(), latLng, mNativeMapView.getPitch(), mNativeMapView.getZoom()); } } /** *

* Centers the map on a new {@link LatLng} immediately while changing the current zoom level. *

*

* The initial value is a center {@link LatLng} of (0, 0) and a zoom level of 0. *

* If you want to animate the change, use {@link MapView#setLatLng(LatLng, boolean)}. * * @param latLngZoom The new center and zoom level. * @see MapView#setLatLng(LatLngZoom, boolean) */ @UiThread public void setLatLng(@NonNull LatLngZoom latLngZoom) { setLatLng(latLngZoom, false); } /** *

* Centers the map on a new {@link LatLng} while changing the zoom level and optionally animates the change. *

* The initial value is a center {@link LatLng} of (0, 0) and a zoom level of 0. * * @param latLngZoom The new center and zoom level. * @param animated If true, animates the change. If false, immediately changes the map. */ @UiThread public void setLatLng(@NonNull LatLngZoom latLngZoom, boolean animated) { if (latLngZoom == null) { Log.w(TAG, "latLngZoom was null, so just returning"); return; } long duration = animated ? ANIMATION_DURATION : 0; mNativeMapView.cancelTransitions(); mNativeMapView.setLatLngZoom(latLngZoom, duration); } /** * Returns the current coordinate at the center of the map view. * * @return The current coordinate. * @deprecated use {@link #getLatLng()} instead. */ @UiThread @NonNull @Deprecated public LatLng getCenterCoordinate() { return mNativeMapView.getLatLng(); } /** *

* Centers the map on a new coordinate immediately without changing the zoom level. *

*

* The initial coordinate is (0, 0). *

* If you want to animate the change, use {@link MapView#setCenterCoordinate(LatLng, boolean)}. * * @param centerCoordinate The new coordinate. * @see MapView#setCenterCoordinate(LatLng, boolean) * @deprecated use {@link #setLatLng(LatLng)}} instead. */ @UiThread @Deprecated public void setCenterCoordinate(@NonNull LatLng centerCoordinate) { setCenterCoordinate(centerCoordinate, false); } /** *

* Centers the map on a new coordinate without changing the zoom level and optionally animates the change. *

* The initial coordinate is (0, 0). * * @param centerCoordinate The new coordinate. * @param animated If true, animates the change. If false, immediately changes the map. * @deprecated use {@link #setLatLng(LatLng, boolean)}} instead. */ @UiThread @Deprecated public void setCenterCoordinate(@NonNull LatLng centerCoordinate, boolean animated) { if (centerCoordinate == null) { Log.w(TAG, "centerCoordinate was null, so just returning"); return; } if (animated) { CameraPosition cameraPosition = new CameraPosition.Builder(getCameraPosition()) .target(centerCoordinate) .build(); animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), (int) ANIMATION_DURATION, null); } else { jumpTo(mNativeMapView.getBearing(), centerCoordinate, mNativeMapView.getPitch(), mNativeMapView.getZoom()); } } /** *

* Centers the map on a new coordinate immediately while changing the current zoom level. *

*

* The initial value is a center coordinate of (0, 0) and a zoom level of 0. *

* If you want to animate the change, use {@link MapView#setCenterCoordinate(LatLngZoom, boolean)}. * * @param centerCoordinate The new coordinate and zoom level. * @see MapView#setCenterCoordinate(LatLngZoom, boolean) * @deprecated use {@link #setLatLng(LatLngZoom)} instead. */ @UiThread @Deprecated public void setCenterCoordinate(@NonNull LatLngZoom centerCoordinate) { setCenterCoordinate(centerCoordinate, false); } /** *

* Centers the map on a new coordinate while changing the zoom level and optionally animates the change. *

* The initial value is a center coordinate of (0, 0) and a zoom level of 0. * * @param centerCoordinate The new coordinate and zoom level. * @param animated If true, animates the change. If false, immediately changes the map. * @deprecated use {@link #setLatLng(LatLngZoom, boolean)}} instead. */ @UiThread @Deprecated public void setCenterCoordinate(@NonNull LatLngZoom centerCoordinate, boolean animated) { if (centerCoordinate == null) { Log.w(TAG, "centerCoordinate was null, so just returning"); return; } long duration = animated ? ANIMATION_DURATION : 0; mNativeMapView.cancelTransitions(); mNativeMapView.setLatLngZoom(centerCoordinate, duration); } /** * Resets the map to the minimum zoom level, a center coordinate of (0, 0), a true north heading, * and animates the change. */ @UiThread public void resetPosition() { mNativeMapView.cancelTransitions(); mNativeMapView.resetPosition(); } /** * Returns whether the user may scroll around the map. * * @return If true, scrolling is enabled. */ @UiThread public boolean isScrollEnabled() { return mScrollEnabled; } /** *

* Changes whether the user may scroll around the map. *

*

* This setting controls only user interactions with the map. If you set the value to false, * you may still change the map location programmatically. *

* The default value is true. * * @param scrollEnabled If true, scrolling is enabled. */ @UiThread public void setScrollEnabled(boolean scrollEnabled) { this.mScrollEnabled = scrollEnabled; } // // Pitch / Tilt // /** * Gets the current Tilt in degrees of the MapView * * @return tilt in degrees */ public double getTilt() { return mNativeMapView.getPitch(); } /** * Sets the Tilt in degrees of the MapView. * * @param pitch New tilt in degrees * @param duration Animation time in milliseconds. If null then 0 is used, making the animation immediate. */ @FloatRange(from = MINIMUM_TILT, to = MAXIMUM_TILT) public void setTilt(Double pitch, @Nullable Long duration) { long actualDuration = 0; if (duration != null) { actualDuration = duration; } mNativeMapView.setPitch(pitch, actualDuration); } // // Rotation // /** * Returns the current heading of the map relative to true north. * * @return The current heading measured in degrees. */ @UiThread @FloatRange(from = 0, to = 360) public double getDirection() { double direction = -mNativeMapView.getBearing(); while (direction > 360) { direction -= 360; } while (direction < 0) { direction += 360; } return direction; } /** *

* Rotates the map to a new heading relative to true north immediately. *

*
    *
  • The value 0 means that the top edge of the map view will correspond to true north.
  • *
  • The value 90 means the top of the map will point due east.
  • *
  • The value 180 means the top of the map will point due south.
  • *
  • The value 270 means the top of the map will point due west.
  • *
*

* The initial heading is 0. *

* If you want to animate the change, use {@link MapView#setDirection(double, boolean)}. * * @param direction The new heading measured in degrees. * @see MapView#setDirection(double, boolean) */ @UiThread public void setDirection(@FloatRange(from = 0, to = 360) double direction) { setDirection(direction, false); } /** *

* Rotates the map to a new heading relative to true north and optionally animates the change. *

*
    *
  • The value 0 means that the top edge of the map view will correspond to true north.
  • *
  • The value 90 means the top of the map will point due east.
  • *
  • The value 180 means the top of the map will point due south.
  • *
  • The value 270 means the top of the map will point due west.
  • *
* The initial heading is 0. * * @param direction The new heading measured in degrees from true north. * @param animated If true, animates the change. If false, immediately changes the map. */ @UiThread public void setDirection(@FloatRange(from = 0, to = 360) double direction, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; mNativeMapView.cancelTransitions(); // Out of range direactions are normallised in setBearing mNativeMapView.setBearing(-direction, duration); } /** * Resets the map heading to true north and animates the change. */ @UiThread public void resetNorth() { mNativeMapView.cancelTransitions(); mNativeMapView.resetNorth(); } /** * Returns whether the user may rotate the map. * * @return If true, rotating is enabled. */ @UiThread public boolean isRotateEnabled() { return mRotateEnabled; } /** *

* Changes whether the user may rotate the map. *

*

* This setting controls only user interactions with the map. If you set the value to false, * you may still change the map location programmatically. *

* The default value is true. * * @param rotateEnabled If true, rotating is enabled. */ @UiThread public void setRotateEnabled(boolean rotateEnabled) { this.mRotateEnabled = rotateEnabled; } // // Scale // /** * Returns the current zoom level of the map view. * * @return The current zoom. */ @UiThread @FloatRange(from = 0.0, to = MAXIMUM_ZOOM) public double getZoom() { return mNativeMapView.getZoom(); } /** *

* Zooms the map to a new zoom level immediately without changing the center coordinate. *

*

* At zoom level 0, tiles cover the entire world map; * at zoom level 1, tiles cover 1/14 of the world; * at zoom level 2, tiles cover 1/16 of the world, and so on. *

*

* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM}. *

* If you want to animate the change, use {@link MapView#setZoom(double, boolean)}. * * @param zoomLevel The new zoom. * @see MapView#setZoom(double, boolean) * @see MapView#MAXIMUM_ZOOM */ @UiThread public void setZoom(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM) double zoomLevel) { setZoom(zoomLevel, false); setZoom(zoomLevel, false); } /** *

* Zooms the map to a new zoom level and optionally animates the change without changing the center coordinate. *

*

* At zoom level 0, tiles cover the entire world map; * at zoom level 1, tiles cover 1/14 of the world; * at zoom level 2, tiles cover 1/16 of the world, and so on. *

* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM}. * * @param zoomLevel The new zoom level. * @param animated If true, animates the change. If false, immediately changes the map. * @see MapView#MAXIMUM_ZOOM */ @UiThread public void setZoom(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel, boolean animated) { if ((zoomLevel < 0.0) || (zoomLevel > MAXIMUM_ZOOM_LEVEL)) { throw new IllegalArgumentException("zoomLevel is < 0 or > MapView.MAXIMUM_ZOOM_LEVEL"); } long duration = animated ? ANIMATION_DURATION : 0; mNativeMapView.cancelTransitions(); mNativeMapView.setZoom(zoomLevel, duration); } /** * Returns the current zoom level of the map view. * * @return The current zoom level. * @deprecated use {@link #getZoom()} instead. */ @UiThread @FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) @Deprecated public double getZoomLevel() { return mNativeMapView.getZoom(); } /** *

* Zooms the map to a new zoom level immediately without changing the center coordinate. *

*

* At zoom level 0, tiles cover the entire world map; * at zoom level 1, tiles cover 1/14 of the world; * at zoom level 2, tiles cover 1/16 of the world, and so on. *

*

* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM_LEVEL}. *

* If you want to animate the change, use {@link MapView#setZoomLevel(double, boolean)}. * * @param zoomLevel The new coordinate. * @see MapView#setZoomLevel(double, boolean) * @see MapView#MAXIMUM_ZOOM_LEVEL * @deprecated use {@link #setZoom(double)} instead. */ @UiThread @Deprecated public void setZoomLevel(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel) { setZoomLevel(zoomLevel, false); } /** *

* Zooms the map to a new zoom level and optionally animates the change without changing the center coordinate. *

*

* At zoom level 0, tiles cover the entire world map; * at zoom level 1, tiles cover 1/14 of the world; * at zoom level 2, tiles cover 1/16 of the world, and so on. *

* The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM_LEVEL}. * * @param zoomLevel The new coordinate. * @param animated If true, animates the change. If false, immediately changes the map. * @see MapView#MAXIMUM_ZOOM_LEVEL * @deprecated use {@link #setZoom(double, boolean)} instead. */ @UiThread @Deprecated public void setZoomLevel(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel, boolean animated) { if ((zoomLevel < 0.0) || (zoomLevel > MAXIMUM_ZOOM_LEVEL)) { throw new IllegalArgumentException("zoomLevel is < 0 or > MapView.MAXIMUM_ZOOM_LEVEL"); } long duration = animated ? ANIMATION_DURATION : 0; mNativeMapView.cancelTransitions(); mNativeMapView.setZoom(zoomLevel, duration); } /** * Returns whether the user may zoom the map. * * @return If true, zooming is enabled. */ @UiThread public boolean isZoomEnabled() { return mZoomEnabled; } /** *

* Changes whether the user may zoom the map. *

*

* This setting controls only user interactions with the map. If you set the value to false, * you may still change the map location programmatically. *

* The default value is true. * * @param zoomEnabled If true, zooming is enabled. */ @UiThread public void setZoomEnabled(boolean zoomEnabled) { this.mZoomEnabled = zoomEnabled; if (mZoomControlsEnabled && (getVisibility() == View.VISIBLE) && mZoomEnabled) { mZoomButtonsController.setVisible(true); } else { mZoomButtonsController.setVisible(false); } } /** * Gets whether the zoom controls are enabled. * * @return If true, the zoom controls are enabled. */ public boolean isZoomControlsEnabled() { return mZoomControlsEnabled; } /** *

* Sets whether the zoom controls are enabled. * If enabled, the zoom controls are a pair of buttons * (one for zooming in, one for zooming out) that appear on the screen. * When pressed, they cause the camera to zoom in (or out) by one zoom level. * If disabled, the zoom controls are not shown. *

* By default the zoom controls are enabled if the device is only single touch capable; * * @param enabled If true, the zoom controls are enabled. */ public void setZoomControlsEnabled(boolean enabled) { mZoomControlsEnabled = enabled; if (mZoomControlsEnabled && (getVisibility() == View.VISIBLE) && mZoomEnabled) { mZoomButtonsController.setVisible(true); } else { mZoomButtonsController.setVisible(false); } } // Zoom in or out private void zoom(boolean zoomIn) { zoom(zoomIn, -1.0f, -1.0f); } private void zoom(boolean zoomIn, float x, float y) { // Cancel any animation mNativeMapView.cancelTransitions(); if (zoomIn) { mNativeMapView.scaleBy(2.0, x / mScreenDensity, y / mScreenDensity, ANIMATION_DURATION); } else { mNativeMapView.scaleBy(0.5, x / mScreenDensity, y / mScreenDensity, ANIMATION_DURATION); } } // // Tilt // /** * Returns whether the user may tilt the map. * * @return If true, tilting is enabled. */ @UiThread public boolean isTiltEnabled() { return mTiltEnabled; } /** *

* Changes whether the user may tilt the map. *

*

* This setting controls only user interactions with the map. If you set the value to false, * you may still change the map location programmatically. *

* The default value is true. * * @param tiltEnabled If true, tilting is enabled. */ @UiThread public void setTiltEnabled(boolean tiltEnabled) { this.mTiltEnabled = tiltEnabled; } // // 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() { return new CameraPosition(getLatLng(), (float) getZoom(), (float) getTilt(), (float) getBearing()); } /** * Animates the movement of the camera from the current position to the position defined in the update. * During the animation, a call to getCameraPosition() returns an intermediate location of the camera. *

* See CameraUpdateFactory for a set of updates. * * @param update The change that should be applied to the camera. */ @UiThread public final void animateCamera(CameraUpdate update) { animateCamera(update, 1, null); } /** * Animates the movement of the camera from the current position to the position defined in the update and calls an optional callback on completion. * See CameraUpdateFactory for a set of updates. * During the animation, a call to getCameraPosition() returns an intermediate location of the camera. * * @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(). */ @UiThread public final void animateCamera(CameraUpdate update, MapView.CancelableCallback callback) { animateCamera(update, 1, callback); } /** * Moves the map according to the update with an animation over a specified duration, and calls an optional callback on completion. See CameraUpdateFactory for a set of updates. * If 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. The callback should not attempt to move or animate the camera in its cancellation method. If a callback isn't required, leave it as null. */ @UiThread public final void animateCamera(CameraUpdate update, int durationMs, final MapView.CancelableCallback callback) { if (update.getTarget() == null) { Log.w(TAG, "animateCamera with null target coordinate passed in. Will immediately return without animating camera."); return; } mNativeMapView.cancelTransitions(); // Register callbacks early enough if (callback != null) { final MapView view = this; addOnMapChangedListener(new OnMapChangedListener() { @Override public void onMapChanged(@MapChange int change) { if (change == REGION_DID_CHANGE_ANIMATED) { callback.onFinish(); // Clean up after self removeOnMapChangedListener(this); } } }); } // Convert Degrees To Radians double angle = -1; if (update.getBearing() >= 0) { angle = (-update.getBearing()) * MathConstants.DEG2RAD; } double pitch = -1; if (update.getTilt() >= 0) { double dp = MathUtils.clamp(update.getTilt(), MINIMUM_TILT, MAXIMUM_TILT); pitch = dp * MathConstants.DEG2RAD; } double zoom = -1; if (update.getZoom() >= 0) { zoom = update.getZoom(); } long durationNano = 0; if (durationMs > 0) { durationNano = TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS); } flyTo(angle, update.getTarget(), durationNano, pitch, zoom); } /** * Ease the map according to the update with an animation over a specified duration, and calls an optional callback on completion. See CameraUpdateFactory for a set of updates. * If 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. The callback should not attempt to move or animate the camera in its cancellation method. If a callback isn't required, leave it as null. */ @UiThread public final void easeCamera(CameraUpdate update, int durationMs, final MapView.CancelableCallback callback) { if (update.getTarget() == null) { Log.w(TAG, "easeCamera with null target coordinate passed in. Will immediately return without easing camera."); return; } mNativeMapView.cancelTransitions(); // Register callbacks early enough if (callback != null) { final MapView view = this; addOnMapChangedListener(new OnMapChangedListener() { @Override public void onMapChanged(@MapChange int change) { if (change == REGION_DID_CHANGE_ANIMATED) { callback.onFinish(); // Clean up after self removeOnMapChangedListener(this); } } }); } // Convert Degrees To Radians double angle = -1; if (update.getBearing() >= 0) { angle = (-update.getBearing()) * MathConstants.DEG2RAD; } double pitch = -1; if (update.getTilt() >= 0) { double dp = MathUtils.clamp(update.getTilt(), MINIMUM_TILT, MAXIMUM_TILT); pitch = dp * MathConstants.DEG2RAD; } double zoom = -1; if (update.getZoom() >= 0) { zoom = update.getZoom(); } long durationNano = 0; if (durationMs > 0) { durationNano = TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS); } easeTo(angle, update.getTarget(), durationNano, pitch, zoom); } /** * 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) { if (update.getTarget() == null) { Log.w(TAG, "moveCamera with null target coordinate passed in. Will immediately return without moving camera."); return; } mNativeMapView.cancelTransitions(); // Convert Degrees To Radians double angle = -1; if (update.getBearing() >= 0) { angle = (-update.getBearing()) * MathConstants.DEG2RAD; } double pitch = -1; if (update.getTilt() >= 0) { double dp = MathUtils.clamp(update.getTilt(), MINIMUM_TILT, MAXIMUM_TILT); pitch = dp * MathConstants.DEG2RAD; } double zoom = -1; if (update.getZoom() >= 0) { zoom = update.getZoom(); } jumpTo(angle, update.getTarget(), pitch, zoom); } // // InfoWindows // /** * Changes whether the map allows concurrent multiple infowindows to be shown. * * @param allow If true, map allows concurrent multiple infowindows to be shown. */ @UiThread public void setAllowConcurrentMultipleOpenInfoWindows(boolean allow) { this.mAllowConcurrentMultipleOpenInfoWindows = allow; } /** * Returns whether the map allows concurrent multiple infowindows to be shown. * * @return If true, map allows concurrent multiple infowindows to be shown. */ @UiThread public boolean isAllowConcurrentMultipleOpenInfoWindows() { return this.mAllowConcurrentMultipleOpenInfoWindows; } // // Debug // /** * Returns whether the map debug information is currently shown. * * @return If true, map debug information is currently shown. */ @UiThread public boolean isDebugActive() { return mNativeMapView.getDebug(); } /** *

* 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) { mNativeMapView.setDebug(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() { mNativeMapView.cycleDebugOptions(); } // True if map has finished loading the view private boolean isFullyLoaded() { return mNativeMapView.isFullyLoaded(); } // // 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//