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:
Brad Leege
2015-09-09 14:59:42 -05:00
30 changed files with 1175 additions and 261 deletions

View File

@@ -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) {

View File

@@ -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'
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
</resources>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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',

View 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

View File

@@ -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

View File

@@ -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"

View 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 */

View File

@@ -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

View File

@@ -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

View File

@@ -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&apos;s location.</string>
<key>UILaunchStoryboardName</key>
<string>Default</string>
<key>UIRequiredDeviceCapabilities</key>

View File

@@ -32,7 +32,6 @@
'./MBXAppDelegate.m',
'./MBXViewController.h',
'./MBXViewController.mm',
'../../platform/darwin/settings_nsuserdefaults.mm',
],
'xcode_settings': {

View 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

View File

@@ -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
View File

@@ -0,0 +1 @@
#include <mbgl/map/camera.hpp>

View File

@@ -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);
}

View File

@@ -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));
}
};

View File

@@ -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);