Merge branch 'master' into 2244-texture-view
# Conflicts: # android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java
This commit is contained in:
@@ -1227,6 +1227,14 @@ jobject JNICALL nativeLatLngForPixel(JNIEnv *env, jobject obj, jlong nativeMapVi
|
||||
return ret;
|
||||
}
|
||||
|
||||
jdouble JNICALL nativeGetTopOffsetPixelsForAnnotationSymbol(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring symbolName) {
|
||||
mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetTopOffsetPixelsForAnnotationSymbol");
|
||||
assert(nativeMapViewPtr != 0);
|
||||
NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr);
|
||||
return nativeMapView->getMap().getTopOffsetPixelsForAnnotationSymbol(std_string_from_jstring(env, symbolName));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
@@ -1708,6 +1716,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
reinterpret_cast<void *>(&nativePixelForLatLng)},
|
||||
{"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;",
|
||||
reinterpret_cast<void *>(&nativeLatLngForPixel)},
|
||||
{"nativeGetTopOffsetPixelsForAnnotationSymbol", "(JLjava/lang/String;)D",
|
||||
reinterpret_cast<void *>(&nativeGetTopOffsetPixelsForAnnotationSymbol)},
|
||||
};
|
||||
|
||||
if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) {
|
||||
|
||||
@@ -28,9 +28,9 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-annotations:23.0.0'
|
||||
compile 'com.android.support:support-v4:23.0.0'
|
||||
compile 'com.android.support:design:23.0.0'
|
||||
compile 'com.android.support:support-annotations:23.0.1'
|
||||
compile 'com.android.support:support-v4:23.0.1'
|
||||
compile 'com.android.support:design:23.0.1'
|
||||
compile 'com.squareup.okhttp:okhttp:2.4.0'
|
||||
compile 'com.mapzen.android:lost:1.0.1'
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ package com.mapbox.mapboxgl.annotations;
|
||||
|
||||
import com.mapbox.mapboxgl.views.MapView;
|
||||
|
||||
|
||||
public abstract class Annotation {
|
||||
public abstract class Annotation implements Comparable<Annotation> {
|
||||
|
||||
/**
|
||||
* The annotation id
|
||||
@@ -11,7 +10,7 @@ public abstract class Annotation {
|
||||
* Internal C++ id is stored as unsigned int.
|
||||
*/
|
||||
protected long id = -1; // -1 unless added to a MapView
|
||||
private MapView mapView;
|
||||
protected MapView mapView;
|
||||
|
||||
float alpha = 1.0f;
|
||||
boolean visible = true;
|
||||
@@ -60,4 +59,35 @@ public abstract class Annotation {
|
||||
// public void setZIndex(float zIndex) {
|
||||
//
|
||||
// }
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (o instanceof Annotation) {
|
||||
Annotation comp = (Annotation) o;
|
||||
return id == comp.id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Annotation annotation) {
|
||||
|
||||
if (annotation == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (id < annotation.getId()) {
|
||||
return 1;
|
||||
} else if (id > annotation.getId()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Equal
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
package com.mapbox.mapboxgl.annotations;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import com.mapbox.mapboxgl.geometry.LatLng;
|
||||
import com.mapbox.mapboxgl.views.MapView;
|
||||
|
||||
/**
|
||||
* A tooltip view
|
||||
*/
|
||||
public class InfoWindow {
|
||||
|
||||
private Marker boundMarker;
|
||||
private MapView mMapView;
|
||||
private boolean mIsVisible;
|
||||
protected View mView;
|
||||
|
||||
static int mTitleId = 0;
|
||||
static int mDescriptionId = 0;
|
||||
static int mSubDescriptionId = 0;
|
||||
static int mImageId = 0;
|
||||
|
||||
public InfoWindow(int layoutResId, MapView mapView) {
|
||||
mMapView = mapView;
|
||||
mIsVisible = false;
|
||||
Context context = mapView.getContext();
|
||||
LayoutInflater inflater =
|
||||
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mView = inflater.inflate(layoutResId, (ViewGroup) mapView, false);
|
||||
|
||||
if (mTitleId == 0) {
|
||||
setResIds(mapView.getContext());
|
||||
}
|
||||
|
||||
// default behavior: close it when clicking on the tooltip:
|
||||
setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent e) {
|
||||
if (e.getAction() == MotionEvent.ACTION_UP) {
|
||||
close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public InfoWindow(View view, MapView mapView) {
|
||||
mMapView = mapView;
|
||||
mIsVisible = false;
|
||||
ViewGroup parent = (ViewGroup) mapView.getParent();
|
||||
Context context = mapView.getContext();
|
||||
LayoutInflater inflater =
|
||||
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mView = view;
|
||||
|
||||
// default behavior: close it when clicking on the tooltip:
|
||||
setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent e) {
|
||||
if (e.getAction() == MotionEvent.ACTION_UP) {
|
||||
close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* open the window at the specified position.
|
||||
*
|
||||
* @param object the graphical object on which is hooked the view
|
||||
* @param position to place the window on the map
|
||||
* @param offsetX (&offsetY) the offset of the view to the position, in pixels.
|
||||
* This allows to offset the view from the object position.
|
||||
* @return this infowindow
|
||||
*/
|
||||
public InfoWindow open(Marker object, LatLng position, int offsetX, int offsetY) {
|
||||
onOpen(object);
|
||||
MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT);
|
||||
mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||
|
||||
PointF coords = mMapView.toScreenLocation(position);
|
||||
double y = mMapView.getTopOffsetPixelsForAnnotationSymbol(object.sprite);
|
||||
y = y * mMapView.getScreenDensity();
|
||||
|
||||
// Flip y coordinate as Android view origin is upper left corner
|
||||
coords.y = mMapView.getHeight() - coords.y;
|
||||
lp.leftMargin = (int) coords.x - (mView.getMeasuredWidth() / 2);
|
||||
// Add y because it's a negative value
|
||||
lp.topMargin = (int) coords.y - mView.getMeasuredHeight() + (int) y;
|
||||
|
||||
close(); //if it was already opened
|
||||
mMapView.addView(mView, lp);
|
||||
mIsVisible = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close this InfoWindow if it is visible, otherwise don't do anything.
|
||||
*
|
||||
* @return this info window
|
||||
*/
|
||||
public InfoWindow close() {
|
||||
if (mIsVisible) {
|
||||
mIsVisible = false;
|
||||
((ViewGroup) mView.getParent()).removeView(mView);
|
||||
// this.boundMarker.blur();
|
||||
setBoundMarker(null);
|
||||
onClose();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Android view. This allows to set its content.
|
||||
*
|
||||
* @return the Android view
|
||||
*/
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mapView this InfoWindow is bound to
|
||||
*
|
||||
* @return the mapView
|
||||
*/
|
||||
public MapView getMapView() {
|
||||
return mMapView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the view that is displayed when the InfoWindow opens.
|
||||
* This retrieves data from overlayItem and shows it in the tooltip.
|
||||
*
|
||||
* @param overlayItem the tapped overlay item
|
||||
*/
|
||||
public void onOpen(Marker overlayItem) {
|
||||
String title = overlayItem.getTitle();
|
||||
((TextView) mView.findViewById(mTitleId /*R.id.title*/)).setText(title);
|
||||
String snippet = overlayItem.getSnippet();
|
||||
((TextView) mView.findViewById(mDescriptionId /*R.id.description*/)).setText(snippet);
|
||||
|
||||
/*
|
||||
//handle sub-description, hiding or showing the text view:
|
||||
TextView subDescText = (TextView) mView.findViewById(mSubDescriptionId);
|
||||
String subDesc = overlayItem.getSubDescription();
|
||||
if ("".equals(subDesc)) {
|
||||
subDescText.setVisibility(View.GONE);
|
||||
} else {
|
||||
subDescText.setText(subDesc);
|
||||
subDescText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public void onClose() {
|
||||
//by default, do nothing
|
||||
}
|
||||
|
||||
public InfoWindow setBoundMarker(Marker aBoundMarker) {
|
||||
this.boundMarker = aBoundMarker;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Marker getBoundMarker() {
|
||||
return boundMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a context, set the resource ids for the layout
|
||||
* of the InfoWindow.
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
private static void setResIds(Context context) {
|
||||
String packageName = context.getPackageName(); //get application package name
|
||||
mTitleId = context.getResources().getIdentifier("id/infowindow_title", null, packageName);
|
||||
mDescriptionId =
|
||||
context.getResources().getIdentifier("id/infowindow_description", null, packageName);
|
||||
mSubDescriptionId = context.getResources()
|
||||
.getIdentifier("id/infowindow_subdescription", null, packageName);
|
||||
mImageId = context.getResources().getIdentifier("id/infowindow_image", null, packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to override default touch events handling on InfoWindow (ie, close automatically)
|
||||
*
|
||||
* @param listener New View.OnTouchListener to use
|
||||
*/
|
||||
public void setOnTouchListener(View.OnTouchListener listener) {
|
||||
mView.setOnTouchListener(listener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.mapbox.mapboxgl.annotations;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
public class InfoWindowTipView extends View {
|
||||
|
||||
private Paint mPaint;
|
||||
private Path mPath;
|
||||
|
||||
public InfoWindowTipView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mPath = new Path();
|
||||
|
||||
this.mPaint = new Paint();
|
||||
this.mPaint.setColor(Color.WHITE);
|
||||
this.mPaint.setAntiAlias(true);
|
||||
this.mPaint.setStrokeWidth(0.0f);
|
||||
this.mPaint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
int height = getMeasuredHeight();
|
||||
int width = getMeasuredWidth();
|
||||
|
||||
mPath.rewind();
|
||||
mPath.moveTo((width / 2) - height, 0);
|
||||
mPath.lineTo((width / 2) + height, 0);
|
||||
mPath.lineTo((width / 2), height);
|
||||
mPath.lineTo((width / 2) - height, 0);
|
||||
canvas.drawPath(mPath, this.mPaint);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.mapbox.mapboxgl.annotations;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.view.View;
|
||||
import com.mapbox.mapboxgl.geometry.LatLng;
|
||||
|
||||
import com.mapbox.mapboxgl.views.R;
|
||||
|
||||
public class Marker extends Annotation {
|
||||
|
||||
@@ -16,10 +18,16 @@ public class Marker extends Annotation {
|
||||
String snippet;
|
||||
String sprite = "default_marker";
|
||||
String title;
|
||||
private InfoWindow infoWindow = null;
|
||||
|
||||
private boolean infoWindowShown = false;
|
||||
|
||||
public Marker() {}
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public Marker() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* If two markers have the same LatLng, they are equal.
|
||||
@@ -41,6 +49,10 @@ public class Marker extends Annotation {
|
||||
return alpha;
|
||||
}
|
||||
|
||||
public Point getAnchor() {
|
||||
return new Point((int)anchorU, (int)anchorV);
|
||||
}
|
||||
|
||||
public float getAnchorU() {
|
||||
return anchorU;
|
||||
}
|
||||
@@ -66,7 +78,7 @@ public class Marker extends Annotation {
|
||||
}
|
||||
|
||||
public void hideInfoWindow() {
|
||||
//TODO hideInfoWindow
|
||||
infoWindow.close();
|
||||
infoWindowShown = false;
|
||||
}
|
||||
|
||||
@@ -82,33 +94,33 @@ public class Marker extends Annotation {
|
||||
return infoWindowShown;
|
||||
}
|
||||
|
||||
void setAnchor(float u, float v) {
|
||||
public void setAnchor(float u, float v) {
|
||||
this.anchorU = u;
|
||||
this.anchorV = v;
|
||||
}
|
||||
|
||||
void setDraggable(boolean draggable) {
|
||||
public void setDraggable(boolean draggable) {
|
||||
this.draggable = draggable;
|
||||
}
|
||||
|
||||
void setFlat(boolean flat) {
|
||||
public void setFlat(boolean flat) {
|
||||
this.flat = flat;
|
||||
}
|
||||
|
||||
void setInfoWindowAnchor(float u, float v) {
|
||||
public void setInfoWindowAnchor(float u, float v) {
|
||||
infoWindowAnchorU = u;
|
||||
infoWindowAnchorV = v;
|
||||
}
|
||||
|
||||
void setPosition(LatLng latlng) {
|
||||
public void setPosition(LatLng latlng) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
void setRotation(float rotation) {
|
||||
public void setRotation(float rotation) {
|
||||
this.rotation = rotation;
|
||||
}
|
||||
|
||||
void setSnippet(String snippet) {
|
||||
public void setSnippet(String snippet) {
|
||||
this.snippet = snippet;
|
||||
}
|
||||
|
||||
@@ -120,18 +132,55 @@ public class Marker extends Annotation {
|
||||
*
|
||||
* @param sprite
|
||||
*/
|
||||
void setSprite(String sprite) {
|
||||
public void setSprite(String sprite) {
|
||||
this.sprite = sprite;
|
||||
}
|
||||
|
||||
void setTitle(String title) {
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
void showInfoWindow() {
|
||||
public void showInfoWindow() {
|
||||
|
||||
if (!isVisible() || mapView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getInfoWindow().open(this, getPosition(), (int) anchorU, (int) anchorV);
|
||||
getInfoWindow().setBoundMarker(this);
|
||||
infoWindowShown = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to set a custom OnTouchListener for the InfoWindow.
|
||||
* By default the InfoWindow will close on touch.
|
||||
* @param listener Custom OnTouchListener
|
||||
*/
|
||||
public void setInfoWindowOnTouchListener(View.OnTouchListener listener) {
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
getInfoWindow().setOnTouchListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common internal InfoWindow initialization method
|
||||
* @return InfoWindow for Marker
|
||||
*/
|
||||
private InfoWindow getInfoWindow() {
|
||||
if (infoWindow == null) {
|
||||
infoWindow = new InfoWindow(R.layout.infowindow, mapView);
|
||||
}
|
||||
return infoWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
super.setVisible(visible);
|
||||
if (!visible && infoWindowShown) {
|
||||
hideInfoWindow();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Method in Google Maps Android API
|
||||
// public int hashCode()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.mapbox.mapboxgl.views;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@@ -11,6 +10,7 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.GeomagneticField;
|
||||
import android.hardware.Sensor;
|
||||
@@ -27,7 +27,6 @@ import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.GestureDetectorCompat;
|
||||
import android.support.v4.view.ScaleGestureDetectorCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.Gravity;
|
||||
import android.view.ScaleGestureDetector;
|
||||
@@ -100,6 +99,8 @@ public class MapView extends FrameLayout implements LocationListener {
|
||||
* Every annotation that has been added to the map.
|
||||
*/
|
||||
private List<Annotation> mAnnotations = new ArrayList<>();
|
||||
private List<Annotation> mAnnotationsNearLastTap = new ArrayList<>();
|
||||
private Annotation mSelectedAnnotation = null;
|
||||
|
||||
//
|
||||
// Instance members
|
||||
@@ -479,6 +480,9 @@ public class MapView extends FrameLayout implements LocationListener {
|
||||
private void removeAnnotationsWithId(long annotationId){
|
||||
for (Iterator<Annotation> iterator = mAnnotations.iterator(); iterator.hasNext();) {
|
||||
Annotation annotation = iterator.next();
|
||||
if (annotation instanceof Marker) {
|
||||
((Marker)annotation).hideInfoWindow();
|
||||
}
|
||||
if (annotation.getId() == annotationId) {
|
||||
iterator.remove();
|
||||
}
|
||||
@@ -486,6 +490,9 @@ public class MapView extends FrameLayout implements LocationListener {
|
||||
}
|
||||
|
||||
public void removeAnnotation(Annotation annotation) {
|
||||
if (annotation instanceof Marker) {
|
||||
((Marker)annotation).hideInfoWindow();
|
||||
}
|
||||
long id = annotation.getId();
|
||||
mNativeMapView.removeAnnotation(id);
|
||||
mAnnotations.remove(annotation);
|
||||
@@ -499,8 +506,12 @@ public class MapView extends FrameLayout implements LocationListener {
|
||||
public void removeAnnotations() {
|
||||
long[] ids = new long[mAnnotations.size()];
|
||||
for(int i = 0; i < mAnnotations.size(); i++) {
|
||||
long id = mAnnotations.get(i).getId();
|
||||
Annotation annotation = mAnnotations.get(i);
|
||||
long id = annotation.getId();
|
||||
ids[i] = id;
|
||||
if (annotation instanceof Marker) {
|
||||
((Marker)annotation).hideInfoWindow();
|
||||
}
|
||||
}
|
||||
mNativeMapView.removeAnnotations(ids);
|
||||
mAnnotations.clear();
|
||||
@@ -523,9 +534,6 @@ public class MapView extends FrameLayout implements LocationListener {
|
||||
annotations.add(annotation);
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < annotations.size(); i++) {
|
||||
Log.d(TAG, "tapped: " + Long.toString(annotations.get(i).getId()));
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
@@ -731,6 +739,18 @@ public class MapView extends FrameLayout implements LocationListener {
|
||||
return new PointF(point.x * mScreenDensity, point.y * mScreenDensity);
|
||||
}
|
||||
|
||||
public double getTopOffsetPixelsForAnnotationSymbol(@NonNull String symbolName) {
|
||||
return mNativeMapView.getTopOffsetPixelsForAnnotationSymbol(symbolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common Screen Density
|
||||
* @return Screen Density
|
||||
*/
|
||||
public float getScreenDensity() {
|
||||
return mScreenDensity;
|
||||
}
|
||||
|
||||
//
|
||||
// Lifecycle events
|
||||
//
|
||||
@@ -1026,32 +1046,104 @@ public class MapView extends FrameLayout implements LocationListener {
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
// Cancel any animation
|
||||
mNativeMapView.cancelTransitions();
|
||||
|
||||
// Select or deselect point annotations
|
||||
PointF tapPoint = new PointF(e.getX(), e.getY());
|
||||
|
||||
float toleranceWidth = 60 * mScreenDensity;
|
||||
float toleranceHeight = 80 * mScreenDensity;
|
||||
|
||||
PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3);
|
||||
PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3);
|
||||
|
||||
LatLng sw = fromScreenLocation(bl);
|
||||
LatLng ne = fromScreenLocation(tr);
|
||||
|
||||
BoundingBox bbox = new BoundingBox(ne, sw);
|
||||
|
||||
List<Annotation> annotations = getAnnotationsInBounds(bbox);
|
||||
|
||||
performClick();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called for single taps after a delay
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
return false;
|
||||
// Open / Close InfoWindow
|
||||
|
||||
float x = e.getX();
|
||||
float y = e.getY();
|
||||
|
||||
// flip y direction vertically to match core GL
|
||||
y = getHeight() - y;
|
||||
|
||||
PointF tapPoint = new PointF(x, y);
|
||||
|
||||
float toleranceWidth = 40 * mScreenDensity;
|
||||
float toleranceHeight = 60 * mScreenDensity;
|
||||
|
||||
RectF tapRect = new RectF(tapPoint.x - toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3,
|
||||
tapPoint.x + toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3);
|
||||
|
||||
List<LatLng> corners = Arrays.asList(
|
||||
fromScreenLocation(new PointF(tapRect.left, tapRect.bottom)),
|
||||
fromScreenLocation(new PointF(tapRect.left, tapRect.top)),
|
||||
fromScreenLocation(new PointF(tapRect.right, tapRect.top)),
|
||||
fromScreenLocation(new PointF(tapRect.right, tapRect.bottom))
|
||||
);
|
||||
|
||||
BoundingBox tapBounds = BoundingBox.fromLatLngs(corners);
|
||||
|
||||
List<Annotation> nearbyAnnotations = getAnnotationsInBounds(tapBounds);
|
||||
|
||||
long newSelectedAnnotationID = -1;
|
||||
|
||||
if (nearbyAnnotations.size() > 0) {
|
||||
|
||||
// there is at least one nearby annotation; select one
|
||||
//
|
||||
// first, sort for comparison and iteration
|
||||
Collections.sort(nearbyAnnotations);
|
||||
|
||||
if (nearbyAnnotations == mAnnotationsNearLastTap)
|
||||
{
|
||||
// the selection candidates haven't changed; cycle through them
|
||||
if (mSelectedAnnotation != null && (mSelectedAnnotation.getId() == mAnnotationsNearLastTap.get(mAnnotationsNearLastTap.size() - 1).getId()))
|
||||
{
|
||||
// the selected annotation is the last in the set; cycle back to the first
|
||||
// note: this could be the selected annotation if only one in set
|
||||
newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId();
|
||||
}
|
||||
else if (mSelectedAnnotation != null)
|
||||
{
|
||||
// otherwise increment the selection through the candidates
|
||||
long currentID = mSelectedAnnotation.getId();
|
||||
long result = mAnnotationsNearLastTap.indexOf(mSelectedAnnotation);
|
||||
newSelectedAnnotationID = mAnnotationsNearLastTap.get((int) result + 1).getId();
|
||||
}
|
||||
else
|
||||
{
|
||||
// no current selection; select the first one
|
||||
newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// start tracking a new set of nearby annotations
|
||||
mAnnotationsNearLastTap = nearbyAnnotations;
|
||||
|
||||
// select the first one
|
||||
newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId();
|
||||
}
|
||||
|
||||
} else {
|
||||
// there are no nearby annotations; deselect if necessary
|
||||
newSelectedAnnotationID = -1;
|
||||
}
|
||||
|
||||
if (newSelectedAnnotationID >= 0) {
|
||||
|
||||
for (Annotation annotation : mAnnotations) {
|
||||
if (annotation instanceof Marker) {
|
||||
if (annotation.getId() == newSelectedAnnotationID) {
|
||||
if (mSelectedAnnotation == null || annotation.getId() != mSelectedAnnotation.getId()) {
|
||||
selectAnnotation(annotation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// deselect any selected annotation
|
||||
if (mSelectedAnnotation != null) {
|
||||
deselectAnnotation();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called for a long press
|
||||
@@ -1485,7 +1577,7 @@ public class MapView extends FrameLayout implements LocationListener {
|
||||
|
||||
// Called for events that don't fit the other handlers
|
||||
// such as mouse scroll events, mouse moves, joystick, trackpad
|
||||
@Override @TargetApi(12)
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
// Mouse events
|
||||
//if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // this is not available before API 18
|
||||
@@ -1891,5 +1983,39 @@ public class MapView extends FrameLayout implements LocationListener {
|
||||
mGpsMarker.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (change.equals(MapChange.MapChangeRegionWillChange) || change.equals(MapChange.MapChangeRegionWillChangeAnimated)) {
|
||||
deselectAnnotation();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void selectAnnotation(Annotation annotation) {
|
||||
|
||||
if (annotation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (annotation == mSelectedAnnotation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (annotation instanceof Marker) {
|
||||
// Need to deselect any currently selected annotation first
|
||||
deselectAnnotation();
|
||||
|
||||
((Marker)annotation).showInfoWindow();
|
||||
mSelectedAnnotation = annotation;
|
||||
}
|
||||
}
|
||||
|
||||
private void deselectAnnotation() {
|
||||
if (mSelectedAnnotation != null && mSelectedAnnotation instanceof Marker) {
|
||||
Marker marker = (Marker) mSelectedAnnotation;
|
||||
if (marker.isInfoWindowShown()) {
|
||||
marker.hideInfoWindow();
|
||||
mSelectedAnnotation = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.mapbox.mapboxgl.views;
|
||||
import android.graphics.PointF;
|
||||
import android.view.Surface;
|
||||
|
||||
import com.mapbox.mapboxgl.annotations.Annotation;
|
||||
import com.mapbox.mapboxgl.annotations.Marker;
|
||||
import com.mapbox.mapboxgl.annotations.Polygon;
|
||||
import com.mapbox.mapboxgl.annotations.Polyline;
|
||||
@@ -11,8 +10,6 @@ import com.mapbox.mapboxgl.geometry.BoundingBox;
|
||||
import com.mapbox.mapboxgl.geometry.LatLng;
|
||||
import com.mapbox.mapboxgl.geometry.LatLngZoom;
|
||||
import com.mapbox.mapboxgl.geometry.ProjectedMeters;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// Class that wraps the native methods for convenience
|
||||
@@ -424,6 +421,10 @@ class NativeMapView {
|
||||
return nativeLatLngForPixel(mNativeMapViewPtr, pixel);
|
||||
}
|
||||
|
||||
public double getTopOffsetPixelsForAnnotationSymbol(String symbolName) {
|
||||
return nativeGetTopOffsetPixelsForAnnotationSymbol(mNativeMapViewPtr, symbolName);
|
||||
}
|
||||
|
||||
//
|
||||
// Callbacks
|
||||
//
|
||||
@@ -606,4 +607,6 @@ class NativeMapView {
|
||||
private native PointF nativePixelForLatLng(long nativeMapViewPtr, LatLng latLng);
|
||||
|
||||
private native LatLng nativeLatLngForPixel(long nativeMapViewPtr, PointF pixel);
|
||||
|
||||
private native double nativeGetTopOffsetPixelsForAnnotationSymbol(long nativeMapViewPtr, String symbolName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:background="@color/white"
|
||||
android:padding="5dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/infowindow_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="18sp"
|
||||
android:maxEms="17"
|
||||
android:layout_gravity="left"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/infoWindowTitle"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/infowindow_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="14sp"
|
||||
android:maxEms="17"
|
||||
android:text="@string/infoWindowDescription"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/infowindow_subdescription"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="12sp"
|
||||
android:maxEms="17"
|
||||
android:text="@string/infoWindowAddress"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
|
||||
<com.mapbox.mapboxgl.annotations.InfoWindowTipView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="white">#FFFFFF</color>
|
||||
<color name="black">#000000</color>
|
||||
</resources>
|
||||
@@ -1,4 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="compassContentDescription">Map compass. Click to reset the map rotation to North.</string>
|
||||
<string name="infoWindowTitle">Title</string>
|
||||
<string name="infoWindowDescription">Description</string>
|
||||
<string name="infoWindowAddress">Address</string>
|
||||
</resources>
|
||||
|
||||
@@ -82,10 +82,10 @@ dependencies {
|
||||
compile(project(':MapboxGLAndroidSDK')) {
|
||||
transitive = true
|
||||
}
|
||||
compile 'com.android.support:support-annotations:23.0.0'
|
||||
compile 'com.android.support:support-v4:23.0.0'
|
||||
compile 'com.android.support:appcompat-v7:23.0.0'
|
||||
compile 'com.android.support:design:23.0.0'
|
||||
compile 'com.android.support:support-annotations:23.0.1'
|
||||
compile 'com.android.support:support-v4:23.0.1'
|
||||
compile 'com.android.support:appcompat-v7:23.0.1'
|
||||
compile 'com.android.support:design:23.0.1'
|
||||
compile('com.crashlytics.sdk.android:crashlytics:2.5.1@aar') {
|
||||
transitive = true;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.mapbox.mapboxgl.annotations.Marker;
|
||||
import com.mapbox.mapboxgl.annotations.MarkerOptions;
|
||||
import com.mapbox.mapboxgl.annotations.PolygonOptions;
|
||||
import com.mapbox.mapboxgl.annotations.PolylineOptions;
|
||||
@@ -32,6 +34,7 @@ import io.fabric.sdk.android.Fabric;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
@@ -121,6 +124,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
mMapView.addMarker(new MarkerOptions()
|
||||
.position(position)
|
||||
.title("Dropped Pin")
|
||||
.snippet(new DecimalFormat("#.#####").format(position.getLatitude()) + ", " +
|
||||
new DecimalFormat("#.#####").format(position.getLongitude()))
|
||||
.sprite("default_marker"));
|
||||
}
|
||||
});
|
||||
@@ -392,10 +397,18 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private void addMarkers() {
|
||||
LatLng backLot = new LatLng(38.649441, -121.369064);
|
||||
mMapView.addMarker(new MarkerOptions()
|
||||
final Marker marker = mMapView.addMarker(new MarkerOptions()
|
||||
.position(backLot)
|
||||
.title("Back Lot")
|
||||
.snippet("The back lot behind my house"));
|
||||
marker.setInfoWindowOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||
Toast.makeText(getApplicationContext(), "Custom Info Touch Listener!!", Toast.LENGTH_SHORT).show();
|
||||
marker.hideInfoWindow();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
LatLng cheeseRoom = new LatLng(38.531577,-122.010646);
|
||||
mMapView.addMarker(new MarkerOptions()
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
'../include/mbgl/ios/Mapbox.h',
|
||||
'../platform/ios/MGLMapboxEvents.h',
|
||||
'../platform/ios/MGLMapboxEvents.m',
|
||||
'../include/mbgl/ios/MGLMapCamera.h',
|
||||
'../platform/ios/MGLMapCamera.mm',
|
||||
'../include/mbgl/ios/MGLMapView.h',
|
||||
'../include/mbgl/ios/MGLMapView+IBAdditions.h',
|
||||
'../platform/ios/MGLMapView.mm',
|
||||
|
||||
36
include/mbgl/ios/MGLMapCamera.h
Normal file
36
include/mbgl/ios/MGLMapCamera.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#import "Mapbox.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** An `MGLMapCamera` object represents a viewpoint from which the user observes some point on an `MGLMapView`. */
|
||||
@interface MGLMapCamera : NSObject <NSSecureCoding, NSCopying>
|
||||
|
||||
/** Coordinate at the center of the map view. */
|
||||
@property (nonatomic) CLLocationCoordinate2D centerCoordinate;
|
||||
|
||||
/** Heading measured in degrees clockwise from true north. */
|
||||
@property (nonatomic) CLLocationDirection heading;
|
||||
|
||||
/** Pitch toward the horizon measured in degrees, with 0 degrees resulting in a two-dimensional map. */
|
||||
@property (nonatomic) CGFloat pitch;
|
||||
|
||||
/** Meters above ground level. */
|
||||
@property (nonatomic) CLLocationDistance altitude;
|
||||
|
||||
/** Returns a new camera with all properties set to 0. */
|
||||
+ (instancetype)camera;
|
||||
|
||||
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||
fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
|
||||
eyeAltitude:(CLLocationDistance)eyeAltitude;
|
||||
|
||||
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||
fromDistance:(CLLocationDistance)distance
|
||||
pitch:(CGFloat)pitch
|
||||
heading:(CLLocationDirection)heading;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,4 +1,5 @@
|
||||
#import "MGLGeometry.h"
|
||||
#import "MGLMapCamera.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
@@ -6,6 +7,7 @@
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class MGLAnnotationImage;
|
||||
@class MGLMapCamera;
|
||||
@class MGLUserLocation;
|
||||
@class MGLPolyline;
|
||||
@class MGLPolygon;
|
||||
@@ -136,6 +138,8 @@ IB_DESIGNABLE
|
||||
* @param animated Specify `YES` if you want the map view to animate scrolling and zooming to the new location or `NO` if you want the map to display the new location immediately. */
|
||||
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated;
|
||||
|
||||
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated;
|
||||
|
||||
/** The coordinate bounds visible in the receiver’s viewport.
|
||||
*
|
||||
* Changing the value of this property updates the receiver immediately. If you want to animate the change, call `setVisibleCoordinateBounds:animated:` instead. */
|
||||
@@ -181,19 +185,19 @@ IB_DESIGNABLE
|
||||
/** Resets the map rotation to a northern heading. */
|
||||
- (IBAction)resetNorth;
|
||||
|
||||
/** The pitch of the map (measured in degrees).
|
||||
*
|
||||
* The default value `0` shows a completely flat map. Maximum value is `60`. */
|
||||
@property (nonatomic) double pitch;
|
||||
/** A camera representing the current viewpoint of the map. */
|
||||
@property (nonatomic, copy) MGLMapCamera *camera;
|
||||
|
||||
/** Changes the pitch of the map.
|
||||
* @param pitch The pitch of the map (measured in degrees) relative to top-down.
|
||||
*
|
||||
* Changing the pitch tilts the map without changing the current center coordinate or zoom level. */
|
||||
- (void)setPitch:(double)pitch;
|
||||
/** Moves the viewpoint to a different location with respect to the map with an optional transition animation.
|
||||
* @param camera The new viewpoint.
|
||||
* @param animated Specify `YES` if you want the map view to animate the change to the new viewpoint or `NO` if you want the map to display the new viewpoint immediately. */
|
||||
- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated;
|
||||
|
||||
/** Resets the map pitch to head-on. */
|
||||
- (IBAction)resetPitch;
|
||||
/** Moves the viewpoint to a different location with respect to the map with an optional transition duration and timing function.
|
||||
* @param camera The new viewpoint.
|
||||
* @param duration The amount of time, measured in seconds, that the transition animation should take. Specify `0` to jump to the new viewpoint instantaneously.
|
||||
* @param function A timing function used for the animation. Set this parameter to `nil` for a transition that matches most system animations. If the duration is `0`, this parameter is ignored. */
|
||||
- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function;
|
||||
|
||||
#pragma mark - Converting Map Coordinates
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#import "MGLAccountManager.h"
|
||||
#import "MGLAnnotation.h"
|
||||
#import "MGLAnnotationImage.h"
|
||||
#import "MGLMapCamera.h"
|
||||
#import "MGLGeometry.h"
|
||||
#import "MGLMapView.h"
|
||||
#import "MGLMultiPoint.h"
|
||||
|
||||
22
include/mbgl/map/camera.hpp
Normal file
22
include/mbgl/map/camera.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef MBGL_MAP_CAMERA
|
||||
#define MBGL_MAP_CAMERA
|
||||
|
||||
#include <mbgl/util/geo.hpp>
|
||||
#include <mbgl/util/optional.hpp>
|
||||
#include <mbgl/util/chrono.hpp>
|
||||
#include <mbgl/util/unitbezier.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
struct CameraOptions {
|
||||
mapbox::util::optional<LatLng> center;
|
||||
mapbox::util::optional<double> zoom;
|
||||
mapbox::util::optional<double> angle;
|
||||
mapbox::util::optional<double> pitch;
|
||||
mapbox::util::optional<Duration> duration;
|
||||
mapbox::util::optional<mbgl::util::UnitBezier> easing;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MBGL_MAP_CAMERA */
|
||||
@@ -2,6 +2,7 @@
|
||||
#define MBGL_MAP_MAP
|
||||
|
||||
#include <mbgl/util/chrono.hpp>
|
||||
#include <mbgl/map/camera.hpp>
|
||||
#include <mbgl/map/update.hpp>
|
||||
#include <mbgl/map/mode.hpp>
|
||||
#include <mbgl/util/geo.hpp>
|
||||
@@ -94,6 +95,10 @@ public:
|
||||
void cancelTransitions();
|
||||
void setGestureInProgress(bool);
|
||||
|
||||
// Camera
|
||||
void jumpTo(CameraOptions options);
|
||||
void easeTo(CameraOptions options);
|
||||
|
||||
// Position
|
||||
void moveBy(double dx, double dy, const Duration& = Duration::zero());
|
||||
void setLatLng(LatLng latLng, const Duration& = Duration::zero());
|
||||
@@ -107,8 +112,8 @@ public:
|
||||
void setZoom(double zoom, const Duration& = Duration::zero());
|
||||
double getZoom() const;
|
||||
void setLatLngZoom(LatLng latLng, double zoom, const Duration& = Duration::zero());
|
||||
void fitBounds(LatLngBounds bounds, EdgeInsets padding, const Duration& duration = Duration::zero());
|
||||
void fitBounds(AnnotationSegment segment, EdgeInsets padding, const Duration& duration = Duration::zero());
|
||||
CameraOptions cameraForLatLngBounds(LatLngBounds bounds, EdgeInsets padding);
|
||||
CameraOptions cameraForLatLngs(std::vector<LatLng> latLngs, EdgeInsets padding);
|
||||
void resetZoom();
|
||||
double getMinZoom() const;
|
||||
double getMaxZoom() const;
|
||||
@@ -121,7 +126,7 @@ public:
|
||||
void resetNorth();
|
||||
|
||||
// Pitch
|
||||
void setPitch(double pitch);
|
||||
void setPitch(double pitch, const Duration& = Duration::zero());
|
||||
double getPitch() const;
|
||||
|
||||
// Size
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#import <mbgl/ios/Mapbox.h>
|
||||
|
||||
#import <mbgl/platform/darwin/settings_nsuserdefaults.hpp>
|
||||
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
static UIColor *const kTintColor = [UIColor colorWithRed:0.120 green:0.550 blue:0.670 alpha:1.000];
|
||||
@@ -26,10 +24,20 @@ static NSUInteger const kStyleVersion = 8;
|
||||
|
||||
@implementation MBXViewController
|
||||
|
||||
mbgl::Settings_NSUserDefaults *settings = nullptr;
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [MBXViewController class])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
|
||||
@"userTrackingMode": @(MGLUserTrackingModeNone),
|
||||
@"showsUserLocation": @NO,
|
||||
@"debug": @NO,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
@@ -38,6 +46,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationDidEnterBackgroundNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState:) name:UIApplicationWillEnterForegroundNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationWillTerminateNotification object:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -49,7 +58,6 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
|
||||
|
||||
self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds];
|
||||
self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
self.mapView.showsUserLocation = YES;
|
||||
self.mapView.delegate = self;
|
||||
[self.view addSubview:self.mapView];
|
||||
|
||||
@@ -74,36 +82,42 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
|
||||
target:self
|
||||
action:@selector(locateUser)];
|
||||
|
||||
settings = new mbgl::Settings_NSUserDefaults();
|
||||
[self restoreState:nil];
|
||||
}
|
||||
|
||||
- (void)saveState:(__unused NSNotification *)notification
|
||||
{
|
||||
if (self.mapView && settings)
|
||||
if (self.mapView)
|
||||
{
|
||||
settings->longitude = self.mapView.centerCoordinate.longitude;
|
||||
settings->latitude = self.mapView.centerCoordinate.latitude;
|
||||
settings->zoom = self.mapView.zoomLevel;
|
||||
settings->bearing = self.mapView.direction;
|
||||
settings->pitch = self.mapView.pitch;
|
||||
settings->debug = self.mapView.isDebugActive;
|
||||
settings->userTrackingMode = self.mapView.userTrackingMode;
|
||||
settings->showsUserLocation = self.mapView.showsUserLocation;
|
||||
settings->save();
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSData *archivedCamera = [NSKeyedArchiver archivedDataWithRootObject:self.mapView.camera];
|
||||
[defaults setObject:archivedCamera forKey:@"camera"];
|
||||
[defaults setInteger:self.mapView.userTrackingMode forKey:@"userTrackingMode"];
|
||||
[defaults setBool:self.mapView.showsUserLocation forKey:@"showsUserLocation"];
|
||||
[defaults setBool:self.mapView.debugActive forKey:@"debug"];
|
||||
[defaults synchronize];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)restoreState:(__unused NSNotification *)notification
|
||||
{
|
||||
if (self.mapView && settings) {
|
||||
settings->load();
|
||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(settings->latitude, settings->longitude) zoomLevel:settings->zoom animated:NO];
|
||||
self.mapView.direction = settings->bearing;
|
||||
self.mapView.pitch = settings->pitch;
|
||||
self.mapView.userTrackingMode = settings->userTrackingMode;
|
||||
self.mapView.showsUserLocation = settings->showsUserLocation;
|
||||
[self.mapView setDebugActive:settings->debug];
|
||||
if (self.mapView) {
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSData *archivedCamera = [defaults objectForKey:@"camera"];
|
||||
MGLMapCamera *camera = archivedCamera ? [NSKeyedUnarchiver unarchiveObjectWithData:archivedCamera] : nil;
|
||||
if (camera)
|
||||
{
|
||||
self.mapView.camera = camera;
|
||||
}
|
||||
NSInteger uncheckedTrackingMode = [defaults integerForKey:@"userTrackingMode"];
|
||||
if (uncheckedTrackingMode >= 0 &&
|
||||
(NSUInteger)uncheckedTrackingMode >= MGLUserTrackingModeNone &&
|
||||
(NSUInteger)uncheckedTrackingMode <= MGLUserTrackingModeFollowWithCourse)
|
||||
{
|
||||
self.mapView.userTrackingMode = (MGLUserTrackingMode)uncheckedTrackingMode;
|
||||
}
|
||||
self.mapView.showsUserLocation = [defaults boolForKey:@"showsUserLocation"];
|
||||
self.mapView.debugActive = [defaults boolForKey:@"debug"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,12 +347,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
if (settings)
|
||||
{
|
||||
[self saveState:nil];
|
||||
delete settings;
|
||||
settings = nullptr;
|
||||
}
|
||||
[self saveState:nil];
|
||||
}
|
||||
|
||||
#pragma mark - MGLMapViewDelegate
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>(c) 2014 Mapbox</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>The map will be centered on the user's location.</string>
|
||||
<string>The map will display the user's location.</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>Default</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
'./MBXAppDelegate.m',
|
||||
'./MBXViewController.h',
|
||||
'./MBXViewController.mm',
|
||||
'../../platform/darwin/settings_nsuserdefaults.mm',
|
||||
],
|
||||
|
||||
'xcode_settings': {
|
||||
|
||||
101
platform/ios/MGLMapCamera.mm
Normal file
101
platform/ios/MGLMapCamera.mm
Normal file
@@ -0,0 +1,101 @@
|
||||
#import "MGLMapCamera.h"
|
||||
|
||||
#include <mbgl/util/projection.hpp>
|
||||
|
||||
@implementation MGLMapCamera
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (instancetype)camera
|
||||
{
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||
fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
|
||||
eyeAltitude:(CLLocationDistance)eyeAltitude
|
||||
{
|
||||
mbgl::LatLng centerLatLng = mbgl::LatLng(centerCoordinate.latitude, centerCoordinate.longitude);
|
||||
mbgl::LatLng eyeLatLng = mbgl::LatLng(eyeCoordinate.latitude, eyeCoordinate.longitude);
|
||||
|
||||
mbgl::ProjectedMeters centerMeters = mbgl::Projection::projectedMetersForLatLng(centerLatLng);
|
||||
mbgl::ProjectedMeters eyeMeters = mbgl::Projection::projectedMetersForLatLng(eyeLatLng);
|
||||
CLLocationDirection heading = std::atan((centerMeters.northing - eyeMeters.northing) /
|
||||
(centerMeters.easting - eyeMeters.easting));
|
||||
|
||||
double groundDistance = std::hypot(centerMeters.northing - eyeMeters.northing,
|
||||
centerMeters.easting - eyeMeters.easting);
|
||||
CGFloat pitch = std::atan(eyeAltitude / groundDistance);
|
||||
|
||||
return [[self alloc] initWithCenterCoordinate:centerCoordinate
|
||||
altitude:eyeAltitude
|
||||
pitch:pitch
|
||||
heading:heading];
|
||||
}
|
||||
|
||||
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||
fromDistance:(CLLocationDistance)distance
|
||||
pitch:(CGFloat)pitch
|
||||
heading:(CLLocationDirection)heading
|
||||
{
|
||||
return [[self alloc] initWithCenterCoordinate:centerCoordinate
|
||||
altitude:distance
|
||||
pitch:(CGFloat)pitch
|
||||
heading:heading];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||
altitude:(CLLocationDistance)altitude
|
||||
pitch:(CGFloat)pitch
|
||||
heading:(CLLocationDirection)heading
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_centerCoordinate = centerCoordinate;
|
||||
_altitude = altitude;
|
||||
_pitch = pitch;
|
||||
_heading = heading;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"],
|
||||
[coder decodeDoubleForKey:@"centerLongitude"]);
|
||||
_altitude = [coder decodeDoubleForKey:@"altitude"];
|
||||
_pitch = [coder decodeDoubleForKey:@"pitch"];
|
||||
_heading = [coder decodeDoubleForKey:@"heading"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
[coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"];
|
||||
[coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"];
|
||||
[coder encodeDouble:_altitude forKey:@"altitude"];
|
||||
[coder encodeDouble:_pitch forKey:@"pitch"];
|
||||
[coder encodeDouble:_heading forKey:@"heading"];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(nullable NSZone *)zone
|
||||
{
|
||||
return [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate
|
||||
altitude:_altitude
|
||||
pitch:_pitch
|
||||
heading:_heading];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<MKMapCamera %p centerCoordinate:%f, %f altitude:%.0fm heading:%.0f° pitch:%.0f°>",
|
||||
self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -48,6 +48,9 @@ NSUInteger const MGLStyleVersion = 8;
|
||||
const NSTimeInterval MGLAnimationDuration = 0.3;
|
||||
const CGSize MGLAnnotationUpdateViewportOutset = {150, 150};
|
||||
const CGFloat MGLMinimumZoom = 3;
|
||||
const CGFloat MGLMinimumPitch = 0;
|
||||
const CGFloat MGLMaximumPitch = 60;
|
||||
const CLLocationDegrees MGLAngularFieldOfView = M_PI / 6.;
|
||||
|
||||
NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey";
|
||||
NSString *const MGLAnnotationSymbolKey = @"MGLAnnotationSymbolKey";
|
||||
@@ -67,6 +70,18 @@ CLLocationDegrees MGLDegreesFromRadians(CGFloat radians)
|
||||
return radians * 180 / M_PI;
|
||||
}
|
||||
|
||||
mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function)
|
||||
{
|
||||
if ( ! function)
|
||||
{
|
||||
function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
|
||||
}
|
||||
float p1[2], p2[2];
|
||||
[function getControlPointAtIndex:0 values:p1];
|
||||
[function getControlPointAtIndex:1 values:p2];
|
||||
return { p1[0], p1[1], p2[0], p2[1] };
|
||||
}
|
||||
|
||||
#pragma mark - Private -
|
||||
|
||||
@interface MGLMapView () <UIGestureRecognizerDelegate, GLKViewDelegate, CLLocationManagerDelegate, UIActionSheetDelegate>
|
||||
@@ -368,7 +383,10 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
|
||||
|
||||
// set initial position
|
||||
//
|
||||
_mbglMap->setLatLngZoom(mbgl::LatLng(0, 0), _mbglMap->getMinZoom());
|
||||
mbgl::CameraOptions options;
|
||||
options.center = mbgl::LatLng(0, 0);
|
||||
options.zoom = _mbglMap->getMinZoom();
|
||||
_mbglMap->jumpTo(options);
|
||||
_pendingLatitude = NAN;
|
||||
_pendingLongitude = NAN;
|
||||
|
||||
@@ -1347,12 +1365,10 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
|
||||
else if (twoFingerDrag.state == UIGestureRecognizerStateBegan || twoFingerDrag.state == UIGestureRecognizerStateChanged)
|
||||
{
|
||||
CGFloat gestureDistance = CGPoint([twoFingerDrag translationInView:twoFingerDrag.view]).y;
|
||||
double currentPitch = _mbglMap->getPitch();
|
||||
double minPitch = 0;
|
||||
double maxPitch = 60.0;
|
||||
double slowdown = 20.0;
|
||||
CGFloat currentPitch = _mbglMap->getPitch();
|
||||
CGFloat slowdown = 20.0;
|
||||
|
||||
double pitchNew = fmax(fmin(currentPitch - (gestureDistance / slowdown), maxPitch), minPitch);
|
||||
CGFloat pitchNew = mbgl::util::clamp(currentPitch - (gestureDistance / slowdown), MGLMinimumPitch, MGLMaximumPitch);
|
||||
|
||||
_mbglMap->setPitch(pitchNew);
|
||||
}
|
||||
@@ -1530,7 +1546,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCenterCoordinate
|
||||
{
|
||||
return [NSSet setWithObjects:@"latitude", @"longitude", nil];
|
||||
return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil];
|
||||
}
|
||||
|
||||
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated preservingTracking:(BOOL)tracking
|
||||
@@ -1542,13 +1558,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
|
||||
|
||||
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated
|
||||
{
|
||||
CGFloat duration = (animated ? MGLAnimationDuration : 0);
|
||||
|
||||
_mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(coordinate),
|
||||
fmaxf(_mbglMap->getZoom(), self.currentMinimumZoom),
|
||||
secondsAsDuration(duration));
|
||||
|
||||
[self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
|
||||
[self setCenterCoordinate:coordinate zoomLevel:self.zoomLevel animated:animated];
|
||||
}
|
||||
|
||||
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||
@@ -1562,43 +1572,58 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
|
||||
}
|
||||
|
||||
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated
|
||||
{
|
||||
[self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:self.direction animated:animated];
|
||||
}
|
||||
|
||||
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated
|
||||
{
|
||||
self.userTrackingMode = MGLUserTrackingModeNone;
|
||||
|
||||
CGFloat duration = (animated ? MGLAnimationDuration : 0);
|
||||
[self _setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated];
|
||||
}
|
||||
|
||||
_mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(centerCoordinate), zoomLevel, secondsAsDuration(duration));
|
||||
- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated
|
||||
{
|
||||
mbgl::CameraOptions options;
|
||||
options.center = MGLLatLngFromLocationCoordinate2D(centerCoordinate);
|
||||
options.zoom = fmaxf(zoomLevel, self.currentMinimumZoom);
|
||||
if (direction >= 0)
|
||||
{
|
||||
options.angle = MGLRadiansFromDegrees(-direction);
|
||||
}
|
||||
if (animated)
|
||||
{
|
||||
options.duration = secondsAsDuration(MGLAnimationDuration);
|
||||
options.easing = MGLUnitBezierForMediaTimingFunction(nil);
|
||||
}
|
||||
_mbglMap->easeTo(options);
|
||||
|
||||
[self unrotateIfNeededAnimated:animated];
|
||||
|
||||
[self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
|
||||
}
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingZoomLevel
|
||||
{
|
||||
return [NSSet setWithObject:@"camera"];
|
||||
}
|
||||
|
||||
- (double)zoomLevel
|
||||
{
|
||||
return _mbglMap->getZoom();
|
||||
}
|
||||
|
||||
- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated
|
||||
{
|
||||
self.userTrackingMode = MGLUserTrackingModeNone;
|
||||
|
||||
CGFloat duration = (animated ? MGLAnimationDuration : 0);
|
||||
|
||||
_mbglMap->setLatLngZoom(_mbglMap->getLatLng(),
|
||||
fmaxf(zoomLevel, self.currentMinimumZoom),
|
||||
secondsAsDuration(duration));
|
||||
|
||||
[self unrotateIfNeededAnimated:animated];
|
||||
|
||||
[self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
|
||||
}
|
||||
|
||||
- (void)setZoomLevel:(double)zoomLevel
|
||||
{
|
||||
[self setZoomLevel:zoomLevel animated:NO];
|
||||
}
|
||||
|
||||
- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated
|
||||
{
|
||||
[self setCenterCoordinate:self.centerCoordinate zoomLevel:zoomLevel animated:animated];
|
||||
}
|
||||
|
||||
MGLCoordinateBounds MGLCoordinateBoundsFromLatLngBounds(mbgl::LatLngBounds latLngBounds)
|
||||
{
|
||||
return MGLCoordinateBoundsMake(MGLLocationCoordinate2DFromLatLng(latLngBounds.sw),
|
||||
@@ -1639,11 +1664,34 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord
|
||||
animated:animated];
|
||||
}
|
||||
|
||||
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated
|
||||
{
|
||||
CLLocationCoordinate2D coordinates[] = {
|
||||
{bounds.ne.latitude, bounds.sw.longitude},
|
||||
bounds.sw,
|
||||
{bounds.sw.latitude, bounds.ne.longitude},
|
||||
bounds.ne,
|
||||
};
|
||||
[self setVisibleCoordinates:coordinates
|
||||
count:sizeof(coordinates) / sizeof(coordinates[0])
|
||||
edgePadding:insets
|
||||
direction:direction
|
||||
animated:animated];
|
||||
}
|
||||
|
||||
- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated
|
||||
{
|
||||
[self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:self.direction animated:animated];
|
||||
}
|
||||
|
||||
- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated
|
||||
{
|
||||
[self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil];
|
||||
}
|
||||
|
||||
- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function
|
||||
{
|
||||
// NOTE: does not disrupt tracking mode
|
||||
CGFloat duration = animated ? MGLAnimationDuration : 0;
|
||||
|
||||
[self willChangeValueForKey:@"visibleCoordinateBounds"];
|
||||
mbgl::EdgeInsets mbglInsets = {insets.top, insets.left, insets.bottom, insets.right};
|
||||
mbgl::AnnotationSegment segment;
|
||||
@@ -1652,12 +1700,27 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord
|
||||
{
|
||||
segment.push_back({coordinates[i].latitude, coordinates[i].longitude});
|
||||
}
|
||||
_mbglMap->fitBounds(segment, mbglInsets, secondsAsDuration(duration));
|
||||
mbgl::CameraOptions options = _mbglMap->cameraForLatLngs(segment, mbglInsets);
|
||||
if (direction >= 0)
|
||||
{
|
||||
options.angle = MGLRadiansFromDegrees(-direction);
|
||||
}
|
||||
if (duration > 0)
|
||||
{
|
||||
options.duration = secondsAsDuration(duration);
|
||||
options.easing = MGLUnitBezierForMediaTimingFunction(function);
|
||||
}
|
||||
_mbglMap->easeTo(options);
|
||||
[self didChangeValueForKey:@"visibleCoordinateBounds"];
|
||||
|
||||
[self unrotateIfNeededAnimated:animated];
|
||||
[self unrotateIfNeededAnimated:duration > 0];
|
||||
|
||||
[self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
|
||||
[self notifyMapChange:(duration > 0 ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
|
||||
}
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingDirection
|
||||
{
|
||||
return [NSSet setWithObject:@"camera"];
|
||||
}
|
||||
|
||||
- (CLLocationDirection)direction
|
||||
@@ -1683,23 +1746,139 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord
|
||||
[self setDirection:direction animated:NO];
|
||||
}
|
||||
|
||||
- (double)pitch
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingPitch
|
||||
{
|
||||
return _mbglMap->getPitch();
|
||||
return [NSSet setWithObject:@"camera"];
|
||||
}
|
||||
|
||||
- (void)setPitch:(double)pitch
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCamera
|
||||
{
|
||||
// constrain pitch to between 0º and 60º
|
||||
//
|
||||
_mbglMap->setPitch(fmax(fmin(pitch, 60), 0));
|
||||
return [NSSet setWithObjects:@"longitude", @"latitude", @"centerCoordinate", @"zoomLevel", @"direction", nil];
|
||||
}
|
||||
|
||||
- (MGLMapCamera *)camera
|
||||
{
|
||||
CGRect frame = self.frame;
|
||||
CGPoint edgePoint;
|
||||
// Constrain by the shorter of the two axes.
|
||||
if (frame.size.width > frame.size.height) // landscape
|
||||
{
|
||||
edgePoint = CGPointMake(0, frame.size.height / 2.);
|
||||
}
|
||||
else // portrait
|
||||
{
|
||||
edgePoint = CGPointMake(frame.size.width / 2., 0);
|
||||
}
|
||||
CLLocationCoordinate2D edgeCoordinate = [self convertPoint:edgePoint toCoordinateFromView:self];
|
||||
mbgl::ProjectedMeters edgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(edgeCoordinate));
|
||||
|
||||
//[self notifyMapChange:(mbgl::MapChangeRegionDidChange)];
|
||||
// Because we constrain the zoom level vertically in portrait orientation,
|
||||
// the visible medial span is affected by pitch: the distance from the
|
||||
// center point to the near edge is less than than distance from the center
|
||||
// point to the far edge. Average the two distances.
|
||||
mbgl::ProjectedMeters nearEdgeMeters;
|
||||
if (frame.size.width > frame.size.height)
|
||||
{
|
||||
nearEdgeMeters = edgeMeters;
|
||||
}
|
||||
else
|
||||
{
|
||||
CGPoint nearEdgePoint = CGPointMake(frame.size.width / 2., frame.size.height);
|
||||
CLLocationCoordinate2D nearEdgeCoordinate = [self convertPoint:nearEdgePoint toCoordinateFromView:self];
|
||||
nearEdgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(nearEdgeCoordinate));
|
||||
}
|
||||
|
||||
// The opposite side is the distance between the center and one edge.
|
||||
CLLocationCoordinate2D centerCoordinate = self.centerCoordinate;
|
||||
mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(centerCoordinate));
|
||||
CLLocationDistance centerToEdge = std::hypot(centerMeters.easting - edgeMeters.easting,
|
||||
centerMeters.northing - edgeMeters.northing);
|
||||
CLLocationDistance centerToNearEdge = std::hypot(centerMeters.easting - nearEdgeMeters.easting,
|
||||
centerMeters.northing - nearEdgeMeters.northing);
|
||||
CLLocationDistance altitude = (centerToEdge + centerToNearEdge) / 2 / std::tan(MGLAngularFieldOfView / 2.);
|
||||
|
||||
CGFloat pitch = _mbglMap->getPitch();
|
||||
|
||||
return [MGLMapCamera cameraLookingAtCenterCoordinate:centerCoordinate
|
||||
fromDistance:altitude
|
||||
pitch:pitch
|
||||
heading:self.direction];
|
||||
}
|
||||
|
||||
- (void)resetPitch
|
||||
- (void)setCamera:(MGLMapCamera *)camera
|
||||
{
|
||||
[self setPitch:0];
|
||||
[self setCamera:camera animated:NO];
|
||||
}
|
||||
|
||||
- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated
|
||||
{
|
||||
[self setCamera:camera withDuration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil];
|
||||
}
|
||||
|
||||
- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function
|
||||
{
|
||||
// The opposite side is the distance between the center and one edge.
|
||||
mbgl::LatLng centerLatLng = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate);
|
||||
mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(centerLatLng);
|
||||
CLLocationDistance centerToEdge = camera.altitude * std::tan(MGLAngularFieldOfView / 2.);
|
||||
|
||||
double angle = -1;
|
||||
if (camera.heading >= 0)
|
||||
{
|
||||
angle = MGLRadiansFromDegrees(-camera.heading);
|
||||
}
|
||||
double pitch = -1;
|
||||
if (camera.pitch >= 0)
|
||||
{
|
||||
pitch = MGLRadiansFromDegrees(mbgl::util::clamp(camera.pitch, MGLMinimumPitch, MGLMaximumPitch));
|
||||
}
|
||||
|
||||
// Make a visible bounds that extends in the constrained direction (the
|
||||
// shorter of the two axes).
|
||||
CGRect frame = self.frame;
|
||||
mbgl::LatLng sw, ne;
|
||||
if (frame.size.width > frame.size.height) // landscape
|
||||
{
|
||||
sw = _mbglMap->latLngForProjectedMeters({
|
||||
centerMeters.northing - centerToEdge * std::sin(angle),
|
||||
centerMeters.easting - centerToEdge * std::cos(angle),
|
||||
});
|
||||
ne = _mbglMap->latLngForProjectedMeters({
|
||||
centerMeters.northing + centerToEdge * std::sin(angle),
|
||||
centerMeters.easting + centerToEdge * std::cos(angle),
|
||||
});
|
||||
}
|
||||
else // portrait
|
||||
{
|
||||
sw = _mbglMap->latLngForProjectedMeters({
|
||||
centerMeters.northing - centerToEdge * std::cos(-angle) + centerToEdge * std::cos(-angle) * std::sin(pitch) / 2,
|
||||
centerMeters.easting - centerToEdge * std::sin(-angle) + centerToEdge * std::sin(-angle) * std::sin(pitch) / 2,
|
||||
});
|
||||
ne = _mbglMap->latLngForProjectedMeters({
|
||||
centerMeters.northing + centerToEdge * std::cos(-angle) - centerToEdge * std::cos(-angle) * std::sin(pitch) / 2,
|
||||
centerMeters.easting + centerToEdge * std::sin(-angle) - centerToEdge * std::sin(-angle) * std::sin(pitch) / 2,
|
||||
});
|
||||
}
|
||||
|
||||
// Fit the viewport to the bounds. Correct the center in case pitch should
|
||||
// cause the visual center to lie above the screen center.
|
||||
mbgl::CameraOptions options = _mbglMap->cameraForLatLngs({ sw, ne }, {});
|
||||
options.center = centerLatLng;
|
||||
|
||||
if (camera.heading >= 0)
|
||||
{
|
||||
options.angle = angle;
|
||||
}
|
||||
if (pitch >= 0)
|
||||
{
|
||||
options.pitch = pitch;
|
||||
}
|
||||
if (duration > 0)
|
||||
{
|
||||
options.duration = secondsAsDuration(duration);
|
||||
options.easing = MGLUnitBezierForMediaTimingFunction(function);
|
||||
}
|
||||
_mbglMap->easeTo(options);
|
||||
}
|
||||
|
||||
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view
|
||||
@@ -2482,6 +2661,12 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
|
||||
[self.delegate mapView:self didUpdateUserLocation:self.userLocation];
|
||||
}
|
||||
}
|
||||
|
||||
CLLocationDirection course = self.userLocation.location.course;
|
||||
if (course < 0 || self.userTrackingMode != MGLUserTrackingModeFollowWithCourse)
|
||||
{
|
||||
course = -1;
|
||||
}
|
||||
|
||||
if (self.userTrackingMode != MGLUserTrackingModeNone)
|
||||
{
|
||||
@@ -2496,7 +2681,7 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
|
||||
{
|
||||
// at sufficient detail, just re-center the map; don't zoom
|
||||
//
|
||||
[self setCenterCoordinate:self.userLocation.location.coordinate animated:YES preservingTracking:YES];
|
||||
[self _setCenterCoordinate:self.userLocation.location.coordinate zoomLevel:self.zoomLevel direction:course animated:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2526,17 +2711,11 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
|
||||
desiredSouthWest.longitude != actualSouthWest.longitude)
|
||||
{
|
||||
// assumes we won't disrupt tracking mode
|
||||
[self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) animated:YES];
|
||||
[self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) edgePadding:UIEdgeInsetsZero direction:course animated:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CLLocationDirection course = self.userLocation.location.course;
|
||||
if (course >= 0 && self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
|
||||
{
|
||||
_mbglMap->setBearing(course);
|
||||
}
|
||||
|
||||
self.userLocationAnnotationView.haloLayer.hidden = ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate) ||
|
||||
newLocation.horizontalAccuracy > 10;
|
||||
@@ -3088,7 +3267,7 @@ class MBGLView : public mbgl::View
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLatitude
|
||||
{
|
||||
return [NSSet setWithObject:@"centerCoordinate"];
|
||||
return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
|
||||
}
|
||||
|
||||
- (double)latitude
|
||||
@@ -3114,7 +3293,7 @@ class MBGLView : public mbgl::View
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLongitude
|
||||
{
|
||||
return [NSSet setWithObject:@"centerCoordinate"];
|
||||
return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
|
||||
}
|
||||
|
||||
- (double)longitude
|
||||
|
||||
1
src/mbgl/map/camera.cpp
Normal file
1
src/mbgl/map/camera.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include <mbgl/map/camera.hpp>
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <mbgl/util/projection.hpp>
|
||||
#include <mbgl/util/thread.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -114,6 +115,18 @@ void Map::setGestureInProgress(bool inProgress) {
|
||||
update(Update::Repaint);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
void Map::jumpTo(CameraOptions options) {
|
||||
transform->jumpTo(options);
|
||||
update(Update::Repaint);
|
||||
}
|
||||
|
||||
void Map::easeTo(CameraOptions options) {
|
||||
transform->easeTo(options);
|
||||
update(options.zoom ? Update::Zoom : Update::Repaint);
|
||||
}
|
||||
|
||||
#pragma mark - Position
|
||||
|
||||
void Map::moveBy(double dx, double dy, const Duration& duration) {
|
||||
@@ -131,9 +144,11 @@ LatLng Map::getLatLng() const {
|
||||
}
|
||||
|
||||
void Map::resetPosition() {
|
||||
transform->setAngle(0);
|
||||
transform->setLatLng(LatLng(0, 0));
|
||||
transform->setZoom(0);
|
||||
CameraOptions options;
|
||||
options.angle = 0;
|
||||
options.center = LatLng(0, 0);
|
||||
options.zoom = 0;
|
||||
transform->jumpTo(options);
|
||||
update(Update::Zoom);
|
||||
}
|
||||
|
||||
@@ -168,25 +183,26 @@ void Map::setLatLngZoom(LatLng latLng, double zoom, const Duration& duration) {
|
||||
update(Update::Zoom);
|
||||
}
|
||||
|
||||
void Map::fitBounds(LatLngBounds bounds, EdgeInsets padding, const Duration& duration) {
|
||||
CameraOptions Map::cameraForLatLngBounds(LatLngBounds bounds, EdgeInsets padding) {
|
||||
AnnotationSegment segment = {
|
||||
{bounds.ne.latitude, bounds.sw.longitude},
|
||||
bounds.sw,
|
||||
{bounds.sw.latitude, bounds.ne.longitude},
|
||||
bounds.ne,
|
||||
};
|
||||
fitBounds(segment, padding, duration);
|
||||
return cameraForLatLngs(segment, padding);
|
||||
}
|
||||
|
||||
void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, const Duration& duration) {
|
||||
if (segment.empty()) {
|
||||
return;
|
||||
CameraOptions Map::cameraForLatLngs(std::vector<LatLng> latLngs, EdgeInsets padding) {
|
||||
CameraOptions options;
|
||||
if (latLngs.empty()) {
|
||||
return options;
|
||||
}
|
||||
|
||||
// Calculate the bounds of the possibly rotated shape with respect to the viewport.
|
||||
vec2<> nePixel = {-INFINITY, -INFINITY};
|
||||
vec2<> swPixel = {INFINITY, INFINITY};
|
||||
for (LatLng latLng : segment) {
|
||||
for (LatLng latLng : latLngs) {
|
||||
vec2<> pixel = pixelForLatLng(latLng);
|
||||
swPixel.x = std::min(swPixel.x, pixel.x);
|
||||
nePixel.x = std::max(nePixel.x, pixel.x);
|
||||
@@ -214,7 +230,9 @@ void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, const Duratio
|
||||
vec2<> centerPixel = (paddedNEPixel + paddedSWPixel) * 0.5;
|
||||
LatLng centerLatLng = latLngForPixel(centerPixel);
|
||||
|
||||
setLatLngZoom(centerLatLng, zoom, duration);
|
||||
options.center = centerLatLng;
|
||||
options.zoom = zoom;
|
||||
return options;
|
||||
}
|
||||
|
||||
void Map::resetZoom() {
|
||||
@@ -270,8 +288,8 @@ void Map::resetNorth() {
|
||||
|
||||
#pragma mark - Pitch
|
||||
|
||||
void Map::setPitch(double pitch) {
|
||||
transform->setPitch(std::min(pitch, 60.0) * M_PI / 180);
|
||||
void Map::setPitch(double pitch, const Duration& duration) {
|
||||
transform->setPitch(util::clamp(pitch, 0., 60.) * M_PI / 180, duration);
|
||||
update(Update::Repaint);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <mbgl/map/camera.hpp>
|
||||
#include <mbgl/map/transform.hpp>
|
||||
#include <mbgl/map/view.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
@@ -53,6 +54,38 @@ bool Transform::resize(const std::array<uint16_t, 2> size) {
|
||||
|
||||
#pragma mark - Position
|
||||
|
||||
void Transform::jumpTo(const CameraOptions options) {
|
||||
CameraOptions jumpOptions = options;
|
||||
jumpOptions.duration.reset();
|
||||
easeTo(jumpOptions);
|
||||
}
|
||||
|
||||
void Transform::easeTo(CameraOptions options) {
|
||||
LatLng latLng = options.center ? *options.center : getLatLng();
|
||||
double zoom = options.zoom ? *options.zoom : getZoom();
|
||||
double angle = options.angle ? *options.angle : getAngle();
|
||||
if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude) || std::isnan(zoom)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double new_scale = std::pow(2.0, zoom);
|
||||
|
||||
const double s = new_scale * util::tileSize;
|
||||
state.Bc = s / 360;
|
||||
state.Cc = s / util::M2PI;
|
||||
|
||||
const double m = 1 - 1e-15;
|
||||
const double f = std::fmin(std::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m);
|
||||
|
||||
double xn = -latLng.longitude * state.Bc;
|
||||
double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f));
|
||||
|
||||
options.center.reset();
|
||||
options.zoom.reset();
|
||||
options.angle.reset();
|
||||
_easeTo(options, new_scale, angle, xn, yn);
|
||||
}
|
||||
|
||||
void Transform::moveBy(const double dx, const double dy, const Duration& duration) {
|
||||
if (std::isnan(dx) || std::isnan(dy)) {
|
||||
return;
|
||||
@@ -62,71 +95,30 @@ void Transform::moveBy(const double dx, const double dy, const Duration& duratio
|
||||
}
|
||||
|
||||
void Transform::_moveBy(const double dx, const double dy, const Duration& duration) {
|
||||
|
||||
double x = state.x + std::cos(state.angle) * dx + std::sin( state.angle) * dy;
|
||||
double y = state.y + std::cos(state.angle) * dy + std::sin(-state.angle) * dx;
|
||||
|
||||
state.constrain(state.scale, y);
|
||||
|
||||
if (duration == Duration::zero()) {
|
||||
view.notifyMapChange(MapChangeRegionWillChange);
|
||||
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
|
||||
view.notifyMapChange(MapChangeRegionDidChange);
|
||||
} else {
|
||||
view.notifyMapChange(MapChangeRegionWillChangeAnimated);
|
||||
|
||||
const double startX = state.x;
|
||||
const double startY = state.y;
|
||||
state.panning = true;
|
||||
|
||||
startTransition(
|
||||
[=](double t) {
|
||||
state.x = util::interpolate(startX, x, t);
|
||||
state.y = util::interpolate(startY, y, t);
|
||||
view.notifyMapChange(MapChangeRegionIsChanging);
|
||||
return Update::Repaint;
|
||||
},
|
||||
[=] {
|
||||
state.panning = false;
|
||||
view.notifyMapChange(MapChangeRegionDidChangeAnimated);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
CameraOptions options;
|
||||
options.duration = duration;
|
||||
_easeTo(options, state.scale, state.angle, x, y);
|
||||
}
|
||||
|
||||
void Transform::setLatLng(const LatLng latLng, const Duration& duration) {
|
||||
if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double m = 1 - 1e-15;
|
||||
const double f = ::fmin(::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m);
|
||||
|
||||
double xn = -latLng.longitude * state.Bc;
|
||||
double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f));
|
||||
|
||||
_setScaleXY(state.scale, xn, yn, duration);
|
||||
CameraOptions options;
|
||||
options.center = latLng;
|
||||
options.duration = duration;
|
||||
easeTo(options);
|
||||
}
|
||||
|
||||
void Transform::setLatLngZoom(const LatLng latLng, const double zoom, const Duration& duration) {
|
||||
if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude) || std::isnan(zoom)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double new_scale = std::pow(2.0, zoom);
|
||||
|
||||
const double s = new_scale * util::tileSize;
|
||||
state.Bc = s / 360;
|
||||
state.Cc = s / util::M2PI;
|
||||
|
||||
const double m = 1 - 1e-15;
|
||||
const double f = ::fmin(::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m);
|
||||
|
||||
double xn = -latLng.longitude * state.Bc;
|
||||
double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f));
|
||||
|
||||
_setScaleXY(new_scale, xn, yn, duration);
|
||||
CameraOptions options;
|
||||
options.center = latLng;
|
||||
options.zoom = zoom;
|
||||
options.duration = duration;
|
||||
easeTo(options);
|
||||
}
|
||||
|
||||
|
||||
@@ -206,13 +198,27 @@ void Transform::_setScale(double new_scale, double cx, double cy, const Duration
|
||||
|
||||
void Transform::_setScaleXY(const double new_scale, const double xn, const double yn,
|
||||
const Duration& duration) {
|
||||
CameraOptions options;
|
||||
options.duration = duration;
|
||||
_easeTo(options, new_scale, state.angle, xn, yn);
|
||||
}
|
||||
|
||||
void Transform::_easeTo(CameraOptions options, const double new_scale, const double new_angle, const double xn, const double yn) {
|
||||
Update update = state.scale == new_scale ? Update::Repaint : Update::Zoom;
|
||||
double scale = new_scale;
|
||||
double x = xn;
|
||||
double y = yn;
|
||||
|
||||
state.constrain(scale, y);
|
||||
|
||||
double angle = _normalizeAngle(new_angle, state.angle);
|
||||
state.angle = _normalizeAngle(state.angle, angle);
|
||||
double pitch = options.pitch ? *options.pitch : state.pitch;
|
||||
|
||||
if (duration == Duration::zero()) {
|
||||
if (!options.duration) {
|
||||
options.duration = Duration::zero();
|
||||
}
|
||||
if (!options.duration || *options.duration == Duration::zero()) {
|
||||
view.notifyMapChange(MapChangeRegionWillChange);
|
||||
|
||||
state.scale = scale;
|
||||
@@ -221,18 +227,28 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl
|
||||
const double s = state.scale * util::tileSize;
|
||||
state.Bc = s / 360;
|
||||
state.Cc = s / util::M2PI;
|
||||
|
||||
state.angle = angle;
|
||||
state.pitch = pitch;
|
||||
|
||||
view.notifyMapChange(MapChangeRegionDidChange);
|
||||
} else {
|
||||
view.notifyMapChange(MapChangeRegionWillChangeAnimated);
|
||||
|
||||
const double startS = state.scale;
|
||||
const double startA = state.angle;
|
||||
const double startP = state.pitch;
|
||||
const double startX = state.x;
|
||||
const double startY = state.y;
|
||||
state.panning = true;
|
||||
state.scaling = true;
|
||||
state.rotating = true;
|
||||
|
||||
startTransition(
|
||||
[=](double t) {
|
||||
util::UnitBezier ease = options.easing ? *options.easing : util::UnitBezier(0, 0, 0.25, 1);
|
||||
return ease.solve(t, 0.001);
|
||||
},
|
||||
[=](double t) {
|
||||
state.scale = util::interpolate(startS, scale, t);
|
||||
state.x = util::interpolate(startX, x, t);
|
||||
@@ -240,14 +256,17 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl
|
||||
const double s = state.scale * util::tileSize;
|
||||
state.Bc = s / 360;
|
||||
state.Cc = s / util::M2PI;
|
||||
state.angle = util::wrap(util::interpolate(startA, angle, t), -M_PI, M_PI);
|
||||
state.pitch = util::interpolate(startP, pitch, t);
|
||||
view.notifyMapChange(MapChangeRegionIsChanging);
|
||||
return Update::Zoom;
|
||||
return update;
|
||||
},
|
||||
[=] {
|
||||
state.panning = false;
|
||||
state.scaling = false;
|
||||
state.rotating = false;
|
||||
view.notifyMapChange(MapChangeRegionDidChangeAnimated);
|
||||
}, duration);
|
||||
}, *options.duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +326,7 @@ void Transform::setAngle(const double new_angle, const double cx, const double c
|
||||
_moveBy(dx, dy, Duration::zero());
|
||||
}
|
||||
|
||||
_setAngle(new_angle, Duration::zero());
|
||||
_setAngle(new_angle);
|
||||
|
||||
if (cx >= 0 && cy >= 0) {
|
||||
_moveBy(-dx, -dy, Duration::zero());
|
||||
@@ -315,32 +334,10 @@ void Transform::setAngle(const double new_angle, const double cx, const double c
|
||||
}
|
||||
|
||||
void Transform::_setAngle(double new_angle, const Duration& duration) {
|
||||
double angle = _normalizeAngle(new_angle, state.angle);
|
||||
state.angle = _normalizeAngle(state.angle, angle);
|
||||
|
||||
if (duration == Duration::zero()) {
|
||||
view.notifyMapChange(MapChangeRegionWillChange);
|
||||
|
||||
state.angle = angle;
|
||||
|
||||
view.notifyMapChange(MapChangeRegionDidChange);
|
||||
} else {
|
||||
view.notifyMapChange(MapChangeRegionWillChangeAnimated);
|
||||
|
||||
const double startA = state.angle;
|
||||
state.rotating = true;
|
||||
|
||||
startTransition(
|
||||
[=](double t) {
|
||||
state.angle = util::wrap(util::interpolate(startA, angle, t), -M_PI, M_PI);
|
||||
view.notifyMapChange(MapChangeRegionIsChanging);
|
||||
return Update::Repaint;
|
||||
},
|
||||
[=] {
|
||||
state.rotating = false;
|
||||
view.notifyMapChange(MapChangeRegionDidChangeAnimated);
|
||||
}, duration);
|
||||
}
|
||||
CameraOptions options;
|
||||
options.angle = new_angle;
|
||||
options.duration = duration;
|
||||
easeTo(options);
|
||||
}
|
||||
|
||||
double Transform::getAngle() const {
|
||||
@@ -349,8 +346,11 @@ double Transform::getAngle() const {
|
||||
|
||||
#pragma mark - Pitch
|
||||
|
||||
void Transform::setPitch(double pitch) {
|
||||
state.pitch = pitch;
|
||||
void Transform::setPitch(double pitch, const Duration& duration) {
|
||||
CameraOptions options;
|
||||
options.pitch = pitch;
|
||||
options.duration = duration;
|
||||
easeTo(options);
|
||||
}
|
||||
|
||||
double Transform::getPitch() const {
|
||||
@@ -359,7 +359,8 @@ double Transform::getPitch() const {
|
||||
|
||||
#pragma mark - Transition
|
||||
|
||||
void Transform::startTransition(std::function<Update(double)> frame,
|
||||
void Transform::startTransition(std::function<double(double)> easing,
|
||||
std::function<Update(double)> frame,
|
||||
std::function<void()> finish,
|
||||
const Duration& duration) {
|
||||
if (transitionFinishFn) {
|
||||
@@ -369,7 +370,7 @@ void Transform::startTransition(std::function<Update(double)> frame,
|
||||
transitionStart = Clock::now();
|
||||
transitionDuration = duration;
|
||||
|
||||
transitionFrameFn = [frame, this](const TimePoint now) {
|
||||
transitionFrameFn = [easing, frame, this](const TimePoint now) {
|
||||
float t = std::chrono::duration<float>(now - transitionStart) / transitionDuration;
|
||||
if (t >= 1.0) {
|
||||
Update result = frame(1.0);
|
||||
@@ -378,8 +379,7 @@ void Transform::startTransition(std::function<Update(double)> frame,
|
||||
transitionFinishFn = nullptr;
|
||||
return result;
|
||||
} else {
|
||||
util::UnitBezier ease(0, 0, 0.25, 1);
|
||||
return frame(ease.solve(t, 0.001));
|
||||
return frame(easing(t));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define MBGL_MAP_TRANSFORM
|
||||
|
||||
#include <mbgl/map/transform_state.hpp>
|
||||
#include <mbgl/map/camera.hpp>
|
||||
#include <mbgl/util/chrono.hpp>
|
||||
#include <mbgl/map/update.hpp>
|
||||
#include <mbgl/util/geo.hpp>
|
||||
@@ -22,6 +23,9 @@ public:
|
||||
// Map view
|
||||
bool resize(std::array<uint16_t, 2> size);
|
||||
|
||||
void jumpTo(const CameraOptions options);
|
||||
void easeTo(const CameraOptions options);
|
||||
|
||||
// Position
|
||||
void moveBy(double dx, double dy, const Duration& = Duration::zero());
|
||||
void setLatLng(LatLng latLng, const Duration& = Duration::zero());
|
||||
@@ -42,7 +46,7 @@ public:
|
||||
double getAngle() const;
|
||||
|
||||
// Pitch
|
||||
void setPitch(double pitch);
|
||||
void setPitch(double pitch, const Duration& = Duration::zero());
|
||||
double getPitch() const;
|
||||
|
||||
// Transitions
|
||||
@@ -60,13 +64,16 @@ private:
|
||||
void _moveBy(double dx, double dy, const Duration& = Duration::zero());
|
||||
void _setScale(double scale, double cx, double cy, const Duration& = Duration::zero());
|
||||
void _setScaleXY(double new_scale, double xn, double yn, const Duration& = Duration::zero());
|
||||
void _easeTo(CameraOptions options, const double new_scale, const double new_angle,
|
||||
const double xn, const double yn);
|
||||
void _setAngle(double angle, const Duration& = Duration::zero());
|
||||
|
||||
View &view;
|
||||
|
||||
TransformState state;
|
||||
|
||||
void startTransition(std::function<Update(double)> frame,
|
||||
void startTransition(std::function<double(double)> easing,
|
||||
std::function<Update(double)> frame,
|
||||
std::function<void()> finish,
|
||||
const Duration& duration);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user