summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps')
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java125
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java11
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java36
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java13
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java15
5 files changed, 181 insertions, 19 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
index cf2d20179f..2b6c1fdd01 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java
@@ -8,6 +8,8 @@ import android.graphics.PointF;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.util.Pair;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
@@ -26,6 +28,7 @@ import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.constants.TelemetryConstants;
import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.utils.MathUtils;
import java.util.ArrayList;
@@ -34,6 +37,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import static com.mapbox.mapboxsdk.constants.MapboxConstants.ANIMATION_DURATION_FLING_BASE;
import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.REASON_API_ANIMATION;
import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE;
@@ -42,6 +46,9 @@ import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener.RE
*/
final class MapGestureDetector {
+ @VisibleForTesting
+ static final int BOUND_REPEL_RATIO = 1000;
+
private final Transform transform;
private final Projection projection;
private final UiSettings uiSettings;
@@ -108,6 +115,18 @@ final class MapGestureDetector {
}
}
+ @VisibleForTesting
+ MapGestureDetector(Transform transform, Projection projection, UiSettings uiSettings,
+ AnnotationManager annotationManager, CameraChangeDispatcher cameraChangeDispatcher,
+ AndroidGesturesManager androidGesturesManager) {
+ this.annotationManager = annotationManager;
+ this.transform = transform;
+ this.projection = projection;
+ this.uiSettings = uiSettings;
+ this.cameraChangeDispatcher = cameraChangeDispatcher;
+ this.gesturesManager = androidGesturesManager;
+ }
+
private void initializeGestureListeners(@NonNull Context context, boolean attachDefaultListeners) {
if (attachDefaultListeners) {
StandardGestureListener standardGestureListener = new StandardGestureListener();
@@ -412,11 +431,42 @@ final class MapGestureDetector {
// tilt results in a bigger translation, limiting input for #5281
double tilt = transform.getTilt();
double tiltFactor = 1.5 + ((tilt != 0) ? (tilt / 10) : 0);
- double offsetX = velocityX / tiltFactor / screenDensity;
- double offsetY = velocityY / tiltFactor / screenDensity;
+ float offsetX = (float) (velocityX / tiltFactor / screenDensity);
+ float offsetY = (float) (velocityY / tiltFactor / screenDensity);
+
+ double animationTimeDivider = 1;
+
+ LatLngBounds bounds = transform.getLatLngBounds();
+ if (bounds != null) {
+ // camera movement is limited by bounds, calculate the acceptable offset that doesn't cross the bounds
+ Pair<Float, Float> offset = findBoundsLimitedOffset(projection, transform, bounds, -offsetX, -offsetY);
+ if (offset != null) {
+ float targetOffsetX = -offset.first;
+ float targetOffsetY = -offset.second;
+
+ // check how much did we cut from the original offset and shorten animation time by the same ratio
+ float offsetDiffRatioX = 0f;
+ if (targetOffsetX != 0) {
+ offsetDiffRatioX = offsetX / targetOffsetX;
+ }
+ float offsetDiffRatioY = 0f;
+ if (targetOffsetY != 0) {
+ offsetDiffRatioY = offsetY / targetOffsetY;
+ }
+
+ // pick the highest ratio as a divider
+ animationTimeDivider = offsetDiffRatioX > offsetDiffRatioY ? offsetDiffRatioX : offsetDiffRatioY;
+ // default to 1 if necessary
+ animationTimeDivider = animationTimeDivider > 1 ? animationTimeDivider : 1;
+
+ offsetX = targetOffsetX;
+ offsetY = targetOffsetY;
+ }
+ }
- // calculate animation time based on displacement
- long animationTime = (long) (velocityXY / 7 / tiltFactor + MapboxConstants.ANIMATION_DURATION_FLING_BASE);
+ // calculate animation time based on displacement and make it shorter if we've hit the bound
+ long animationTime =
+ (long) ((velocityXY / 7 / tiltFactor + ANIMATION_DURATION_FLING_BASE) / animationTimeDivider);
// update transformation
transform.moveBy(offsetX, offsetY, animationTime);
@@ -445,6 +495,16 @@ final class MapGestureDetector {
// dispatching camera start event only when the movement actually occurred
cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_GESTURE);
+ LatLngBounds bounds = transform.getLatLngBounds();
+ if (bounds != null) {
+ // camera movement is limited by bounds, calculate the acceptable offset that doesn't cross the bounds
+ Pair<Float, Float> offset = findBoundsLimitedOffset(projection, transform, bounds, distanceX, distanceY);
+ if (offset != null) {
+ distanceX = offset.first;
+ distanceY = offset.second;
+ }
+ }
+
// Scroll the map
transform.moveBy(-distanceX, -distanceY, 0 /*no duration*/);
@@ -914,6 +974,63 @@ final class MapGestureDetector {
return mapZoom >= MapboxConstants.MINIMUM_ZOOM && mapZoom <= MapboxConstants.MAXIMUM_ZOOM;
}
+ /**
+ * Finds acceptable camera offset based on the bounds that are limiting the camera movement.
+ * <p>
+ * To find the offset, we're hit testing all bounding box edges against the segment that is created from the current
+ * screen center and a target calculated from the provided offset.
+ * Because we are converting LatLng to unwrapped screen coordinates, we are going to find only one collision, or none,
+ * and return an acceptable offset based on the distance from the original point to bounds intersection,
+ * or the original target.
+ */
+ @Nullable
+ @VisibleForTesting
+ static Pair<Float, Float> findBoundsLimitedOffset(@NonNull Projection projection,
+ @NonNull Transform transform,
+ @NonNull LatLngBounds bounds,
+ float offsetX, float offsetY) {
+ // because core returns number with big precision, LatLngBounds#contains can fail when using raw values
+ LatLng coreScreenCenter = transform.getCenterCoordinate(false);
+ LatLng screenCenter = new LatLng(
+ MathUtils.round(coreScreenCenter.getLatitude(), 6),
+ MathUtils.round(coreScreenCenter.getLongitude(), 6));
+ if (!bounds.contains(screenCenter) || bounds.equals(LatLngBounds.world())) {
+ // return when screen center is not within bounds or the camera is not limited
+ return null;
+ }
+
+ PointF referencePixel = projection.toScreenLocationRaw(screenCenter);
+ PointF nwPixel = projection.toScreenLocationRaw(bounds.getNorthWest());
+ PointF nePixel = projection.toScreenLocationRaw(bounds.getNorthEast());
+ PointF sePixel = projection.toScreenLocationRaw(bounds.getSouthEast());
+ PointF swPixel = projection.toScreenLocationRaw(bounds.getSouthWest());
+
+ Pair[] edges = new Pair[] {
+ new Pair<>(nwPixel, nePixel), // north edge
+ new Pair<>(nePixel, sePixel), // east edge
+ new Pair<>(sePixel, swPixel), // south edge
+ new Pair<>(swPixel, nwPixel) // west edge
+ };
+
+ // to prevent the screen center from being permanently attracted to the bound we need to push the origin
+ // so that it isn't a part of the bound's edge segment, otherwise, we'd have a constant collision
+ PointF boundsCenter = projection.toScreenLocationRaw(bounds.getCenter());
+ PointF toCenterVector = new PointF(boundsCenter.x - referencePixel.x, boundsCenter.y - referencePixel.y);
+ referencePixel.offset(toCenterVector.x / BOUND_REPEL_RATIO, toCenterVector.y / BOUND_REPEL_RATIO);
+ PointF target = new PointF(referencePixel.x + offsetX, referencePixel.y + offsetY);
+
+ for (Pair edge : edges) {
+ PointF intersection =
+ MathUtils.findSegmentsIntersection(referencePixel, target, (PointF) edge.first, (PointF) edge.second);
+ if (intersection != null) {
+ // returning a limited offset vector
+ return new Pair<>(intersection.x - referencePixel.x, intersection.y - referencePixel.y);
+ }
+ }
+ // no collisions, returning the original offset vector
+ return new Pair<>(target.x - referencePixel.x, target.y - referencePixel.y);
+ }
+
void notifyOnMapClickListeners(@NonNull PointF tapPoint) {
for (MapboxMap.OnMapClickListener listener : onMapClickListenerList) {
if (listener.onMapClick(projection.fromScreenLocation(tapPoint))) {
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java
index cf5961a313..e9b6e5b5ef 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java
@@ -6,6 +6,7 @@ import android.graphics.RectF;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.annotations.Marker;
@@ -63,9 +64,12 @@ interface NativeMap {
void setLatLng(@NonNull LatLng latLng, long duration);
- LatLng getLatLng();
+ LatLng getLatLng(boolean padded);
+
+ void setLatLngBounds(@Nullable LatLngBounds latLngBounds);
- void setLatLngBounds(@NonNull LatLngBounds latLngBounds);
+ @NonNull
+ LatLngBounds getLatLngBounds();
void setVisibleCoordinateBounds(@NonNull LatLng[] coordinates, @NonNull RectF padding,
double direction, long duration);
@@ -194,6 +198,9 @@ interface NativeMap {
@NonNull
PointF pixelForLatLng(@NonNull LatLng latLng);
+ @NonNull
+ PointF pixelForLatLngRaw(@NonNull LatLng latLng);
+
LatLng latLngForPixel(@NonNull PointF pixel);
//
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
index 10942d521c..ed06dc605c 100755
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
@@ -10,6 +10,7 @@ import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
+
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.LibraryLoader;
@@ -214,13 +215,22 @@ final class NativeMapView implements NativeMap {
}
@Override
- public void setLatLngBounds(LatLngBounds latLngBounds) {
+ public void setLatLngBounds(@Nullable LatLngBounds latLngBounds) {
if (checkState("setLatLngBounds")) {
return;
}
nativeSetLatLngBounds(latLngBounds);
}
+ @NonNull
+ @Override
+ public LatLngBounds getLatLngBounds() {
+ if (checkState("getLatLngBounds")) {
+ return LatLngBounds.world();
+ }
+ return nativeGetLatLngBounds();
+ }
+
@Override
public void cancelTransitions() {
if (checkState("cancelTransitions")) {
@@ -254,11 +264,11 @@ final class NativeMapView implements NativeMap {
}
@Override
- public LatLng getLatLng() {
+ public LatLng getLatLng(boolean padded) {
if (checkState("")) {
return new LatLng();
}
- return nativeGetLatLng();
+ return nativeGetLatLng(padded);
}
@Override
@@ -665,6 +675,17 @@ final class NativeMapView implements NativeMap {
}
@Override
+ @NonNull
+ public PointF pixelForLatLngRaw(@NonNull LatLng latLng) {
+ if (checkState("pixelForLatLngRaw")) {
+ return new PointF();
+ }
+ PointF pointF = nativePixelForLatLngRaw(latLng.getLatitude(), latLng.getLongitude());
+ pointF.set(pointF.x * pixelRatio, pointF.y * pixelRatio);
+ return pointF;
+ }
+
+ @Override
public LatLng latLngForPixel(@NonNull PointF pixel) {
if (checkState("latLngForPixel")) {
return new LatLng();
@@ -1095,6 +1116,9 @@ final class NativeMapView implements NativeMap {
private native void nativeSetLatLngBounds(LatLngBounds latLngBounds);
@Keep
+ private native LatLngBounds nativeGetLatLngBounds();
+
+ @Keep
private native void nativeCancelTransitions();
@Keep
@@ -1108,7 +1132,7 @@ final class NativeMapView implements NativeMap {
@NonNull
@Keep
- private native LatLng nativeGetLatLng();
+ private native LatLng nativeGetLatLng(boolean padded);
@NonNull
@Keep
@@ -1242,6 +1266,10 @@ final class NativeMapView implements NativeMap {
@NonNull
@Keep
+ private native PointF nativePixelForLatLngRaw(double lat, double lon);
+
+ @NonNull
+ @Keep
private native LatLng nativeLatLngForPixel(float x, float y);
@Keep
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java
index cbf5426012..dbeabe2be6 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java
@@ -3,6 +3,7 @@ package com.mapbox.mapboxsdk.maps;
import android.graphics.PointF;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
+
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.constants.GeometryConstants;
import com.mapbox.mapboxsdk.geometry.LatLng;
@@ -257,6 +258,18 @@ public class Projection {
return nativeMapView.pixelForLatLng(location);
}
+ /**
+ * Similarly to {@link #toScreenLocation(LatLng)} this method returns a screen location of a coordinate, however,
+ * the values are always relative to the screens center and do not unwrap when crossing the antimeridian.
+ *
+ * @param location A LatLng on the map to convert to a screen location.
+ * @return A Point representing the screen location in screen pixels, always relative to screen's center.
+ */
+ @NonNull
+ PointF toScreenLocationRaw(@NonNull LatLng location) {
+ return nativeMapView.pixelForLatLngRaw(location);
+ }
+
float getHeight() {
return nativeMapView.getHeight();
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java
index c40994d7ca..2d2302912d 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java
@@ -11,6 +11,7 @@ import com.mapbox.mapboxsdk.camera.CameraUpdate;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.log.Logger;
import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener;
@@ -248,13 +249,9 @@ final class Transform implements MapView.OnCameraDidChangeListener {
nativeMap.setBearing(bearing, focalX, focalY, duration);
}
-
- //
- // LatLng / CenterCoordinate
- //
-
- LatLng getLatLng() {
- return nativeMap.getLatLng();
+ @Nullable
+ LatLngBounds getLatLngBounds() {
+ return nativeMap.getLatLngBounds();
}
//
@@ -273,8 +270,8 @@ final class Transform implements MapView.OnCameraDidChangeListener {
// Center coordinate
//
- LatLng getCenterCoordinate() {
- return nativeMap.getLatLng();
+ LatLng getCenterCoordinate(boolean padded) {
+ return nativeMap.getLatLng(padded);
}
void setCenterCoordinate(LatLng centerCoordinate) {