diff options
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps')
17 files changed, 1476 insertions, 762 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index 0c77723354..81044a2afa 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -4,6 +4,8 @@ import android.graphics.PointF; import android.graphics.RectF; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.support.annotation.WorkerThread; import android.support.v4.util.LongSparseArray; import com.mapbox.mapboxsdk.annotations.Annotation; @@ -17,6 +19,7 @@ import com.mapbox.mapboxsdk.annotations.Polygon; import com.mapbox.mapboxsdk.annotations.PolygonOptions; import com.mapbox.mapboxsdk.annotations.Polyline; import com.mapbox.mapboxsdk.annotations.PolylineOptions; +import com.mapbox.mapboxsdk.geometry.LatLng; import java.util.ArrayList; import java.util.Collections; @@ -33,27 +36,29 @@ import java.util.List; * com.mapbox.mapboxsdk.annotations. * </p> */ -class AnnotationManager { +class AnnotationManager extends MapThreadExecutor { - private final NativeMapView nativeMapView; private final MapView mapView; private final IconManager iconManager; private final InfoWindowManager infoWindowManager = new InfoWindowManager(); private final MarkerViewManager markerViewManager; + + // FIXME: 08/02/2017 threadsafe collection private final LongSparseArray<Annotation> annotations = new LongSparseArray<>(); private final List<Marker> selectedMarkers = new ArrayList<>(); private MapboxMap mapboxMap; private MapboxMap.OnMarkerClickListener onMarkerClickListener; - AnnotationManager(NativeMapView view, MapView mapView, MarkerViewManager markerViewManager) { - this.nativeMapView = view; + AnnotationManager(NativeMapView view, ThreadExecutor threadExecutor, MapView mapView, + MarkerViewManager markerViewManager) { + super(view, threadExecutor); this.mapView = mapView; - this.iconManager = new IconManager(nativeMapView); + this.iconManager = new IconManager(getNativeMapView()); this.markerViewManager = markerViewManager; if (view != null) { // null checking needed for unit tests - nativeMapView.addOnMapChangedListener(markerViewManager); + getNativeMapView().addOnMapChangedListener(markerViewManager); } } @@ -74,6 +79,7 @@ class AnnotationManager { // Annotations // + @UiThread Annotation getAnnotation(long id) { return annotations.get(id); } @@ -86,6 +92,7 @@ class AnnotationManager { return annotations; } + @UiThread void removeAnnotation(@NonNull Annotation annotation) { if (annotation instanceof Marker) { Marker marker = (Marker) annotation; @@ -94,23 +101,25 @@ class AnnotationManager { markerViewManager.removeMarkerView((MarkerView) marker); } } - long id = annotation.getId(); - if (nativeMapView != null) { - nativeMapView.removeAnnotation(id); - } + final long id = annotation.getId(); + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.removeAnnotation(id); + } + }); annotations.remove(id); } + @UiThread void removeAnnotation(long id) { - if (nativeMapView != null) { - nativeMapView.removeAnnotation(id); - } - annotations.remove(id); + removeAnnotation(annotations.get(id)); } + @UiThread void removeAnnotations(@NonNull List<? extends Annotation> annotationList) { int count = annotationList.size(); - long[] ids = new long[count]; + final long[] ids = new long[count]; for (int i = 0; i < count; i++) { Annotation annotation = annotationList.get(i); if (annotation instanceof Marker) { @@ -123,9 +132,12 @@ class AnnotationManager { ids[i] = annotationList.get(i).getId(); } - if (nativeMapView != null) { - nativeMapView.removeAnnotations(ids); - } + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.removeAnnotations(ids); + } + }); for (long id : ids) { annotations.remove(id); @@ -135,7 +147,7 @@ class AnnotationManager { void removeAnnotations() { Annotation annotation; int count = annotations.size(); - long[] ids = new long[count]; + final long[] ids = new long[count]; for (int i = 0; i < count; i++) { ids[i] = annotations.keyAt(i); annotation = annotations.get(ids[i]); @@ -148,9 +160,12 @@ class AnnotationManager { } } - if (nativeMapView != null) { - nativeMapView.removeAnnotations(ids); - } + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.removeAnnotations(ids); + } + }); annotations.clear(); } @@ -159,53 +174,78 @@ class AnnotationManager { // Markers // - Marker addMarker(@NonNull BaseMarkerOptions markerOptions, @NonNull MapboxMap mapboxMap) { - Marker marker = prepareMarker(markerOptions); - long id = nativeMapView != null ? nativeMapView.addMarker(marker) : 0; - marker.setMapboxMap(mapboxMap); - marker.setId(id); - annotations.put(id, marker); - return marker; - } - - List<Marker> addMarkers(@NonNull List<? extends BaseMarkerOptions> markerOptionsList, @NonNull MapboxMap mapboxMap) { - int count = markerOptionsList.size(); - List<Marker> markers = new ArrayList<>(count); - if (count > 0) { - BaseMarkerOptions markerOptions; - Marker marker; - for (int i = 0; i < count; i++) { - markerOptions = markerOptionsList.get(i); - marker = prepareMarker(markerOptions); - markers.add(marker); + void addMarker(@NonNull final BaseMarkerOptions markerOptions, @NonNull final MapboxMap mapboxMap, + final Callback<Marker> onMarkerResult) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final Marker marker = prepareMarker(markerOptions); + long id = nativeMapView != null ? nativeMapView.addMarker(marker) : 0; + marker.setMapboxMap(mapboxMap); + marker.setId(id); + annotations.put(id, marker); + + queueUiEvent(new Runnable() { + @Override + public void run() { + if (onMarkerResult != null) { + onMarkerResult.onResult(marker); + } + } + }); } + }); + } + + void addMarkers(@NonNull final List<? extends BaseMarkerOptions> markerOptionsList, + @NonNull final MapboxMap mapboxMap, final Callback<List<Marker>> onMarkersAddedResult) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + int count = markerOptionsList.size(); + final List<Marker> markers = new ArrayList<>(count); + if (count > 0) { + BaseMarkerOptions markerOptions; + Marker marker; + for (int i = 0; i < count; i++) { + markerOptions = markerOptionsList.get(i); + marker = prepareMarker(markerOptions); + markers.add(marker); + } - if (markers.size() > 0) { - long[] ids = null; - if (nativeMapView != null) { - ids = nativeMapView.addMarkers(markers); - } + if (markers.size() > 0) { + long[] ids = null; + if (nativeMapView != null) { + ids = nativeMapView.addMarkers(markers); + } - long id = 0; - Marker m; - for (int i = 0; i < markers.size(); i++) { - m = markers.get(i); - m.setMapboxMap(mapboxMap); - if (ids != null) { - id = ids[i]; - } else { - // unit test - id++; + long id = 0; + Marker m; + for (int i = 0; i < markers.size(); i++) { + m = markers.get(i); + m.setMapboxMap(mapboxMap); + if (ids != null) { + id = ids[i]; + } else { + // unit test + id++; + } + m.setId(id); + annotations.put(id, m); + } } - m.setId(id); - annotations.put(id, m); } - + queueUiEvent(new Runnable() { + @Override + public void run() { + onMarkersAddedResult.onResult(markers); + } + }); } - } - return markers; + }); } + @WorkerThread private Marker prepareMarker(BaseMarkerOptions markerOptions) { Marker marker = markerOptions.getMarker(); Icon icon = iconManager.loadIconForMarker(marker); @@ -213,54 +253,75 @@ class AnnotationManager { return marker; } - MarkerView addMarker(@NonNull BaseMarkerViewOptions markerOptions, @NonNull MapboxMap mapboxMap, - @Nullable MarkerViewManager.OnMarkerViewAddedListener onMarkerViewAddedListener) { - final MarkerView marker = prepareViewMarker(markerOptions); - - // add marker to map - marker.setMapboxMap(mapboxMap); - long id = nativeMapView.addMarker(marker); - marker.setId(id); - annotations.put(id, marker); - - if (onMarkerViewAddedListener != null) { - markerViewManager.addOnMarkerViewAddedListener(marker, onMarkerViewAddedListener); - } - markerViewManager.setEnabled(true); - markerViewManager.setWaitingForRenderInvoke(true); - return marker; - } - + @UiThread + void addMarker(@NonNull final BaseMarkerViewOptions markerOptions, @NonNull final MapboxMap mapboxMap, + final Callback<MarkerView> callback) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final MarkerView marker = prepareViewMarker(markerOptions); + + // add marker to map + marker.setMapboxMap(mapboxMap); + long id = nativeMapView.addMarker(marker); + marker.setId(id); + annotations.put(id, marker); + + queueUiEvent(new Runnable() { + @Override + public void run() { + if (callback != null) { + markerViewManager.addOnMarkerViewAddedListener(marker, callback); + } + markerViewManager.setEnabled(true); + markerViewManager.setWaitingForRenderInvoke(true); + } + }); + } + }); + } + + + @UiThread + void addMarkerViews(@NonNull final List<? extends BaseMarkerViewOptions> markerViewOptions, + @NonNull final MapboxMap mapboxMap, final Callback<List<MarkerView>> callback) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + List<MarkerView> markers = new ArrayList<>(); + for (BaseMarkerViewOptions markerViewOption : markerViewOptions) { + // if last marker + if (markerViewOptions.indexOf(markerViewOption) == markerViewOptions.size() - 1) { + // get notified when render occurs to invalidate and draw MarkerViews + markerViewManager.setWaitingForRenderInvoke(true); + } + // add marker to map + MarkerView marker = prepareViewMarker(markerViewOption); + marker.setMapboxMap(mapboxMap); + long id = nativeMapView.addMarker(marker); + marker.setId(id); + annotations.put(id, marker); + markers.add(marker); + } + markerViewManager.setEnabled(true); + markerViewManager.update(); - List<MarkerView> addMarkerViews(@NonNull List<? extends BaseMarkerViewOptions> markerViewOptions, - @NonNull MapboxMap mapboxMap) { - List<MarkerView> markers = new ArrayList<>(); - for (BaseMarkerViewOptions markerViewOption : markerViewOptions) { - // if last marker - if (markerViewOptions.indexOf(markerViewOption) == markerViewOptions.size() - 1) { - // get notified when render occurs to invalidate and draw MarkerViews - markerViewManager.setWaitingForRenderInvoke(true); + if (callback != null) { + callback.onResult(markers); + } } - // add marker to map - MarkerView marker = prepareViewMarker(markerViewOption); - marker.setMapboxMap(mapboxMap); - long id = nativeMapView.addMarker(marker); - marker.setId(id); - annotations.put(id, marker); - markers.add(marker); - } - markerViewManager.setEnabled(true); - markerViewManager.update(); - return markers; + }); } + @WorkerThread private MarkerView prepareViewMarker(BaseMarkerViewOptions markerViewOptions) { MarkerView marker = markerViewOptions.getMarker(); iconManager.loadIconForMarkerView(marker); return marker; } - void updateMarker(@NonNull Marker updatedMarker, @NonNull MapboxMap mapboxMap) { + @UiThread + void updateMarker(@NonNull final Marker updatedMarker, @NonNull final MapboxMap mapboxMap) { if (updatedMarker == null) { return; } @@ -269,16 +330,21 @@ class AnnotationManager { return; } - if (!(updatedMarker instanceof MarkerView)) { - iconManager.ensureIconLoaded(updatedMarker, mapboxMap); - } + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + if (!(updatedMarker instanceof MarkerView)) { + iconManager.ensureIconLoaded(updatedMarker, mapboxMap); + } - nativeMapView.updateMarker(updatedMarker); + nativeMapView.updateMarker(updatedMarker); - int index = annotations.indexOfKey(updatedMarker.getId()); - if (index > -1) { - annotations.setValueAt(index, updatedMarker); - } + int index = annotations.indexOfKey(updatedMarker.getId()); + if (index > -1) { + annotations.setValueAt(index, updatedMarker); + } + } + }); } List<Marker> getMarkers() { @@ -293,10 +359,12 @@ class AnnotationManager { return markers; } + @UiThread void setOnMarkerClickListener(@Nullable MapboxMap.OnMarkerClickListener listener) { onMarkerClickListener = listener; } + @UiThread void selectMarker(@NonNull Marker marker) { if (selectedMarkers.contains(marker)) { return; @@ -320,6 +388,7 @@ class AnnotationManager { selectedMarkers.add(marker); } + @UiThread void deselectMarkers() { if (selectedMarkers.isEmpty()) { return; @@ -339,6 +408,7 @@ class AnnotationManager { selectedMarkers.clear(); } + @UiThread void deselectMarker(@NonNull Marker marker) { if (!selectedMarkers.contains(marker)) { return; @@ -359,113 +429,161 @@ class AnnotationManager { return selectedMarkers; } - @NonNull - List<Marker> getMarkersInRect(@NonNull RectF rectangle) { - // convert Rectangle to be density depedent - float pixelRatio = nativeMapView.getPixelRatio(); - RectF rect = new RectF(rectangle.left / pixelRatio, - rectangle.top / pixelRatio, - rectangle.right / pixelRatio, - rectangle.bottom / pixelRatio); + void getMarkersInRect(@NonNull final RectF rectangle, final Callback<List<Marker>> callback) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + // convert Rectangle to be density depedent + float pixelRatio = nativeMapView.getPixelRatio(); + RectF rect = new RectF(rectangle.left / pixelRatio, + rectangle.top / pixelRatio, + rectangle.right / pixelRatio, + rectangle.bottom / pixelRatio); - long[] ids = nativeMapView.queryPointAnnotations(rect); + long[] ids = nativeMapView.queryPointAnnotations(rect); - List<Long> idsList = new ArrayList<>(ids.length); - for (long id : ids) { - idsList.add(id); - } + List<Long> idsList = new ArrayList<>(ids.length); + for (long id : ids) { + idsList.add(id); + } - List<Marker> annotations = new ArrayList<>(ids.length); - List<Annotation> annotationList = getAnnotations(); - int count = annotationList.size(); - for (int i = 0; i < count; i++) { - Annotation annotation = annotationList.get(i); - if (annotation instanceof com.mapbox.mapboxsdk.annotations.Marker && idsList.contains(annotation.getId())) { - annotations.add((com.mapbox.mapboxsdk.annotations.Marker) annotation); - } - } + final List<Marker> annotations = new ArrayList<>(ids.length); + List<Annotation> annotationList = getAnnotations(); + int count = annotationList.size(); + for (int i = 0; i < count; i++) { + Annotation annotation = annotationList.get(i); + if (annotation instanceof Marker && idsList.contains(annotation.getId())) { + annotations.add((Marker) annotation); + } + } - return new ArrayList<>(annotations); + queueUiEvent(new Runnable() { + @Override + public void run() { + if (callback != null) { + callback.onResult(new ArrayList<>(annotations)); + } + } + }); + } + }); } - List<MarkerView> getMarkerViewsInRect(@NonNull RectF rectangle) { - float pixelRatio = nativeMapView.getPixelRatio(); - RectF rect = new RectF(rectangle.left / pixelRatio, - rectangle.top / pixelRatio, - rectangle.right / pixelRatio, - rectangle.bottom / pixelRatio); + void getMarkerViewsInRect(@NonNull final RectF rectangle, final Callback<List<MarkerView>> callback) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + float pixelRatio = nativeMapView.getPixelRatio(); + RectF rect = new RectF(rectangle.left / pixelRatio, + rectangle.top / pixelRatio, + rectangle.right / pixelRatio, + rectangle.bottom / pixelRatio); - long[] ids = nativeMapView.queryPointAnnotations(rect); + long[] ids = nativeMapView.queryPointAnnotations(rect); - List<Long> idsList = new ArrayList<>(ids.length); - for (long id : ids) { - idsList.add(id); - } + List<Long> idsList = new ArrayList<>(ids.length); + for (long id : ids) { + idsList.add(id); + } - List<MarkerView> annotations = new ArrayList<>(ids.length); - List<Annotation> annotationList = getAnnotations(); - int count = annotationList.size(); - for (int i = 0; i < count; i++) { - Annotation annotation = annotationList.get(i); - if (annotation instanceof MarkerView && idsList.contains(annotation.getId())) { - annotations.add((MarkerView) annotation); - } - } + final List<MarkerView> annotations = new ArrayList<>(ids.length); + List<Annotation> annotationList = getAnnotations(); + int count = annotationList.size(); + for (int i = 0; i < count; i++) { + Annotation annotation = annotationList.get(i); + if (annotation instanceof MarkerView && idsList.contains(annotation.getId())) { + annotations.add((MarkerView) annotation); + } + } - return new ArrayList<>(annotations); + if (callback != null) { + queueUiEvent(new Runnable() { + @Override + public void run() { + callback.onResult(annotations); + } + }); + } + } + }); } // // Polygons // - Polygon addPolygon(@NonNull PolygonOptions polygonOptions, @NonNull MapboxMap mapboxMap) { - Polygon polygon = polygonOptions.getPolygon(); - if (!polygon.getPoints().isEmpty()) { - long id = nativeMapView != null ? nativeMapView.addPolygon(polygon) : 0; - polygon.setId(id); - polygon.setMapboxMap(mapboxMap); - annotations.put(id, polygon); - } - return polygon; - } - - List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList, @NonNull MapboxMap mapboxMap) { - int count = polygonOptionsList.size(); - - Polygon polygon; - List<Polygon> polygons = new ArrayList<>(count); - if (count > 0) { - for (PolygonOptions polygonOptions : polygonOptionsList) { - polygon = polygonOptions.getPolygon(); + void addPolygon(@NonNull final PolygonOptions polygonOptions, @NonNull final MapboxMap mapboxMap, + final Callback<Polygon> listener) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final Polygon polygon = polygonOptions.getPolygon(); if (!polygon.getPoints().isEmpty()) { - polygons.add(polygon); + long id = nativeMapView.addPolygon(polygon); + polygon.setId(id); + polygon.setMapboxMap(mapboxMap); + annotations.put(id, polygon); + queueUiEvent(new Runnable() { + @Override + public void run() { + listener.onResult(polygon); + } + }); } } + }); + } + + void addPolygons(@NonNull final List<PolygonOptions> polygonOptionsList, @NonNull final MapboxMap mapboxMap, + final Callback<List<Polygon>> listCallback) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + int count = polygonOptionsList.size(); + + Polygon polygon; + final List<Polygon> polygons = new ArrayList<>(count); + if (count > 0) { + for (PolygonOptions polygonOptions : polygonOptionsList) { + polygon = polygonOptions.getPolygon(); + if (!polygon.getPoints().isEmpty()) { + polygons.add(polygon); + } + } - long[] ids = null; - if (nativeMapView != null) { - ids = nativeMapView.addPolygons(polygons); - } + long[] ids = null; + if (nativeMapView != null) { + ids = nativeMapView.addPolygons(polygons); + } - long id = 0; - for (int i = 0; i < polygons.size(); i++) { - polygon = polygons.get(i); - polygon.setMapboxMap(mapboxMap); - if (ids != null) { - id = ids[i]; - } else { - // unit test - id++; + long id = 0; + for (int i = 0; i < polygons.size(); i++) { + polygon = polygons.get(i); + polygon.setMapboxMap(mapboxMap); + if (ids != null) { + id = ids[i]; + } else { + // unit test + id++; + } + polygon.setId(id); + annotations.put(id, polygon); + } } - polygon.setId(id); - annotations.put(id, polygon); + + queueUiEvent(new Runnable() { + @Override + public void run() { + if (listCallback != null) { + listCallback.onResult(polygons); + } + } + }); } - } - return polygons; + }); } - void updatePolygon(Polygon polygon) { + void updatePolygon(final Polygon polygon) { if (polygon == null) { return; } @@ -474,12 +592,16 @@ class AnnotationManager { return; } - nativeMapView.updatePolygon(polygon); - - int index = annotations.indexOfKey(polygon.getId()); - if (index > -1) { - annotations.setValueAt(index, polygon); - } + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.updatePolygon(polygon); + int index = annotations.indexOfKey(polygon.getId()); + if (index > -1) { + annotations.setValueAt(index, polygon); + } + } + }); } List<Polygon> getPolygons() { @@ -498,55 +620,75 @@ class AnnotationManager { // Polylines // - Polyline addPolyline(@NonNull PolylineOptions polylineOptions, @NonNull MapboxMap mapboxMap) { - Polyline polyline = polylineOptions.getPolyline(); - if (!polyline.getPoints().isEmpty()) { - long id = nativeMapView != null ? nativeMapView.addPolyline(polyline) : 0; - polyline.setMapboxMap(mapboxMap); - polyline.setId(id); - annotations.put(id, polyline); - } - return polyline; - } - - List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList, @NonNull MapboxMap mapboxMap) { - int count = polylineOptionsList.size(); - Polyline polyline; - List<Polyline> polylines = new ArrayList<>(count); - - if (count > 0) { - for (PolylineOptions options : polylineOptionsList) { - polyline = options.getPolyline(); + void addPolyline(@NonNull final PolylineOptions polylineOptions, @NonNull final MapboxMap mapboxMap, + final Callback<Polyline> listener) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final Polyline polyline = polylineOptions.getPolyline(); if (!polyline.getPoints().isEmpty()) { - polylines.add(polyline); + long id = nativeMapView != null ? nativeMapView.addPolyline(polyline) : 0; + polyline.setMapboxMap(mapboxMap); + polyline.setId(id); + annotations.put(id, polyline); + queueUiEvent(new Runnable() { + @Override + public void run() { + if (listener != null) { + listener.onResult(polyline); + } + } + }); } } + }); + } + + void addPolylines(@NonNull final List<PolylineOptions> polylineOptionsList, @NonNull final MapboxMap mapboxMap, + final Callback<List<Polyline>> listener) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + int count = polylineOptionsList.size(); + Polyline polyline; + final List<Polyline> polylines = new ArrayList<>(count); + + if (count > 0) { + for (PolylineOptions options : polylineOptionsList) { + polyline = options.getPolyline(); + if (!polyline.getPoints().isEmpty()) { + polylines.add(polyline); + } + } - long[] ids = null; - if (nativeMapView != null) { - ids = nativeMapView.addPolylines(polylines); - } + long[] ids = nativeMapView.addPolylines(polylines); + long id = 0; + Polyline p; - long id = 0; - Polyline p; - - for (int i = 0; i < polylines.size(); i++) { - p = polylines.get(i); - p.setMapboxMap(mapboxMap); - if (ids != null) { - id = ids[i]; - } else { - // unit test - id++; + for (int i = 0; i < polylines.size(); i++) { + p = polylines.get(i); + p.setMapboxMap(mapboxMap); + if (ids != null) { + id = ids[i]; + } else { + // unit test + id++; + } + p.setId(id); + annotations.put(id, p); + } } - p.setId(id); - annotations.put(id, p); + queueUiEvent(new Runnable() { + @Override + public void run() { + listener.onResult(polylines); + } + }); } - } - return polylines; + }); } - void updatePolyline(Polyline polyline) { + void updatePolyline(final Polyline polyline) { if (polyline == null) { return; } @@ -555,12 +697,17 @@ class AnnotationManager { return; } - nativeMapView.updatePolyline(polyline); + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.updatePolyline(polyline); - int index = annotations.indexOfKey(polyline.getId()); - if (index > -1) { - annotations.setValueAt(index, polyline); - } + int index = annotations.indexOfKey(polyline.getId()); + if (index > -1) { + annotations.setValueAt(index, polyline); + } + } + }); } List<Polyline> getPolylines() { @@ -604,101 +751,130 @@ class AnnotationManager { void reloadMarkers() { iconManager.reloadIcons(); - int count = annotations.size(); - for (int i = 0; i < count; i++) { - Annotation annotation = annotations.get(i); - if (annotation instanceof Marker) { - Marker marker = (Marker) annotation; - nativeMapView.removeAnnotation(annotation.getId()); - long newId = nativeMapView.addMarker(marker); - marker.setId(newId); + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + int count = annotations.size(); + for (int i = 0; i < count; i++) { + Annotation annotation = annotations.get(i); + if (annotation instanceof Marker) { + Marker marker = (Marker) annotation; + nativeMapView.removeAnnotation(annotation.getId()); + long newId = nativeMapView.addMarker(marker); + marker.setId(newId); + } + } } - } + }); } // // Click event // - boolean onTap(PointF tapPoint, float screenDensity) { + // FIXME: 09/02/2017 cleanup + void onTap(final PointF tapPoint, final UiSettings uiSettings, final Projection projection, + final MapboxMap.OnMapClickListener onMapClickListener) { + float screenDensity = uiSettings.getPixelRatio(); float toleranceSides = 4 * screenDensity; float toleranceTopBottom = 10 * screenDensity; - boolean handledDefaultClick = false; RectF tapRect = new RectF(tapPoint.x - iconManager.getAverageIconWidth() / 2 - toleranceSides, tapPoint.y - iconManager.getAverageIconHeight() / 2 - toleranceTopBottom, tapPoint.x + iconManager.getAverageIconWidth() / 2 + toleranceSides, tapPoint.y + iconManager.getAverageIconHeight() / 2 + toleranceTopBottom); - List<Marker> nearbyMarkers = getMarkersInRect(tapRect); - long newSelectedMarkerId = -1; - - // find a Marker that isn't selected yet - if (nearbyMarkers.size() > 0) { - Collections.sort(nearbyMarkers); - for (Marker nearbyMarker : nearbyMarkers) { - boolean found = false; - for (Marker selectedMarker : selectedMarkers) { - if (selectedMarker.equals(nearbyMarker)) { - found = true; + getMarkersInRect(tapRect, new Callback<List<Marker>>() { + @Override + public void onResult(List<Marker> nearbyMarkers) { + boolean tapHandled = false; + + long newSelectedMarkerId = -1; + boolean handledDefaultClick = false; + + // find a Marker that isn't selected yet + if (nearbyMarkers.size() > 0) { + Collections.sort(nearbyMarkers); + for (Marker nearbyMarker : nearbyMarkers) { + boolean found = false; + for (Marker selectedMarker : selectedMarkers) { + if (selectedMarker.equals(nearbyMarker)) { + found = true; + } + } + if (!found) { + newSelectedMarkerId = nearbyMarker.getId(); + break; + } } } - if (!found) { - newSelectedMarkerId = nearbyMarker.getId(); - break; - } - } - } - // if unselected marker found - if (newSelectedMarkerId >= 0) { - List<Annotation> annotations = getAnnotations(); - int count = annotations.size(); - for (int i = 0; i < count; i++) { - Annotation annotation = annotations.get(i); - if (annotation instanceof Marker) { - if (annotation.getId() == newSelectedMarkerId) { - Marker marker = (Marker) annotation; - - if (marker instanceof MarkerView) { - handledDefaultClick = markerViewManager.onClickMarkerView((MarkerView) marker); - } else { - if (onMarkerClickListener != null) { - // end developer has provided a custom click listener - handledDefaultClick = onMarkerClickListener.onMarkerClick(marker); + // if unselected marker found + if (newSelectedMarkerId >= 0) { + List<Annotation> annotations = getAnnotations(); + int count = annotations.size(); + for (int i = 0; i < count; i++) { + Annotation annotation = annotations.get(i); + if (annotation instanceof Marker) { + if (annotation.getId() == newSelectedMarkerId) { + Marker marker = (Marker) annotation; + + if (marker instanceof MarkerView) { + handledDefaultClick = markerViewManager.onClickMarkerView((MarkerView) marker); + } else { + if (onMarkerClickListener != null) { + // end developer has provided a custom click listener + handledDefaultClick = onMarkerClickListener.onMarkerClick(marker); + } + } + + if (annotation instanceof MarkerView) { + markerViewManager.onClickMarkerView((MarkerView) annotation); + } else { + if (!handledDefaultClick) { + // only select marker if user didn't handle the click event themselves + selectMarker(marker); + } + } + tapHandled = true; } } - - if (annotation instanceof MarkerView) { - markerViewManager.onClickMarkerView((MarkerView) annotation); - } else { - if (!handledDefaultClick) { - // only select marker if user didn't handle the click event themselves - selectMarker(marker); + } + } else if (nearbyMarkers.size() > 0) { + // we didn't find an unselected marker, check if we can close an already open markers + for (Marker nearbyMarker : nearbyMarkers) { + for (Marker selectedMarker : selectedMarkers) { + if (nearbyMarker.equals(selectedMarker)) { + if (onMarkerClickListener != null) { + // end developer has provided a custom click listener + handledDefaultClick = onMarkerClickListener.onMarkerClick(nearbyMarker); + if (!handledDefaultClick) { + deselectMarker(nearbyMarker); + } + } + tapHandled = true; } } - - return true; } } - } - } else if (nearbyMarkers.size() > 0) { - // we didn't find an unselected marker, check if we can close an already open markers - for (Marker nearbyMarker : nearbyMarkers) { - for (Marker selectedMarker : selectedMarkers) { - if (nearbyMarker.equals(selectedMarker)) { - if (onMarkerClickListener != null) { - // end developer has provided a custom click listener - handledDefaultClick = onMarkerClickListener.onMarkerClick(nearbyMarker); - if (!handledDefaultClick) { - deselectMarker(nearbyMarker); + + if (!tapHandled) { + if (uiSettings.isDeselectMarkersOnTap()) { + // deselect any selected marker + deselectMarkers(); + } + + // notify app of map click + if (onMapClickListener != null) { + projection.fromScreenLocation(tapPoint, new Callback<LatLng>() { + @Override + public void onResult(LatLng latLng) { + onMapClickListener.onMapClick(latLng); } - } - return true; + }); } } } - } - return false; + }); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Callback.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Callback.java new file mode 100644 index 0000000000..2b0fd58d78 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Callback.java @@ -0,0 +1,18 @@ +package com.mapbox.mapboxsdk.maps; + +import com.mapbox.mapboxsdk.style.layers.Layer; +import com.mapbox.mapboxsdk.style.sources.Source; + +public interface Callback<T> { + + void onResult(T t); + + interface SourceCallback<U extends Source> { + void onResult(U u); + } + + interface LayerCallback<V extends Layer> { + void onResult(V v); + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java index c9d81a88bc..ca3a4496aa 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java @@ -1,6 +1,7 @@ package com.mapbox.mapboxsdk.maps; import android.graphics.Bitmap; +import android.support.annotation.WorkerThread; import android.util.DisplayMetrics; import com.mapbox.mapboxsdk.annotations.Icon; @@ -92,10 +93,12 @@ class IconManager { return icon; } + @WorkerThread int getTopOffsetPixelsForIcon(Icon icon) { return (int) (nativeMapView.getTopOffsetPixelsForAnnotationSymbol(icon.getId()) * nativeMapView.getPixelRatio()); } + @WorkerThread void loadIcon(Icon icon) { Bitmap bitmap = icon.getBitmap(); String id = icon.getId(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java index 0f4d3197cc..be337b2bb0 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java @@ -100,9 +100,13 @@ final class MapGestureDetector { * @param y coordinate * @return location */ - private Location getLocationFromGesture(float x, float y) { - LatLng latLng = projection.fromScreenLocation(new PointF(x, y)); - return TelemetryUtils.buildLocation(latLng.getLongitude(), latLng.getLatitude()); + private void getLocationFromGesture(float x, float y, final Callback<Location> locationCallback) { + projection.fromScreenLocation(new PointF(x, y), new Callback<LatLng>() { + @Override + public void onResult(LatLng latLng) { + locationCallback.onResult(TelemetryUtils.buildLocation(latLng.getLongitude(), latLng.getLatitude())); + } + }); } /** @@ -139,9 +143,13 @@ final class MapGestureDetector { && uiSettings.isZoomGesturesEnabled(); if (twoTap) { // Confirmed 2nd Finger Down - MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( - getLocationFromGesture(event.getX(), event.getY()), - MapboxEvent.GESTURE_TWO_FINGER_SINGLETAP, transform.getZoom())); + getLocationFromGesture(event.getX(), event.getY(), new Callback<Location>() { + @Override + public void onResult(Location location) { + MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( + location, MapboxEvent.GESTURE_TWO_FINGER_SINGLETAP, transform.getZoom())); + } + }); } break; @@ -170,8 +178,13 @@ final class MapGestureDetector { // Scroll / Pan Has Stopped if (scrollInProgress) { - MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapDragEndEvent( - getLocationFromGesture(event.getX(), event.getY()), transform.getZoom())); + getLocationFromGesture(event.getX(), event.getY(), new Callback<Location>() { + @Override + public void onResult(Location location) { + MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapDragEndEvent( + location, transform.getZoom())); + } + }); scrollInProgress = false; } @@ -242,12 +255,12 @@ final class MapGestureDetector { } @Override - public boolean onDoubleTapEvent(MotionEvent e) { + public boolean onDoubleTapEvent(MotionEvent event) { if (!uiSettings.isZoomGesturesEnabled() || !uiSettings.isDoubleTapGesturesEnabled()) { return false; } - switch (e.getAction()) { + switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: @@ -265,14 +278,18 @@ final class MapGestureDetector { transform.zoom(true, focalPoint.x, focalPoint.y); } else { // Zoom in on gesture - transform.zoom(true, e.getX(), e.getY()); + transform.zoom(true, event.getX(), event.getY()); } break; } - MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( - getLocationFromGesture(e.getX(), e.getY()), - MapboxEvent.GESTURE_DOUBLETAP, transform.getZoom())); + getLocationFromGesture(event.getX(), event.getY(), new Callback<Location>() { + @Override + public void onResult(Location location) { + MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( + location, MapboxEvent.GESTURE_DOUBLETAP, transform.getZoom())); + } + }); return true; } @@ -287,32 +304,28 @@ final class MapGestureDetector { @Override public boolean onSingleTapConfirmed(MotionEvent motionEvent) { PointF tapPoint = new PointF(motionEvent.getX(), motionEvent.getY()); - boolean tapHandled = annotationManager.onTap(tapPoint, uiSettings.getPixelRatio()); - - if (!tapHandled) { - if (uiSettings.isDeselectMarkersOnTap()) { - // deselect any selected marker - annotationManager.deselectMarkers(); - } - - // notify app of map click - if (onMapClickListener != null) { - onMapClickListener.onMapClick(projection.fromScreenLocation(tapPoint)); + annotationManager.onTap(tapPoint, uiSettings, projection, onMapClickListener); + getLocationFromGesture(motionEvent.getX(), motionEvent.getY(), new Callback<Location>() { + @Override + public void onResult(Location location) { + MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( + location, MapboxEvent.GESTURE_SINGLETAP, transform.getZoom())); } - } - - MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( - getLocationFromGesture(motionEvent.getX(), motionEvent.getY()), - MapboxEvent.GESTURE_SINGLETAP, transform.getZoom())); - + }); return true; } @Override public void onLongPress(MotionEvent motionEvent) { if (onMapLongClickListener != null && !quickZoom) { - onMapLongClickListener.onMapLongClick( - projection.fromScreenLocation(new PointF(motionEvent.getX(), motionEvent.getY()))); + projection.fromScreenLocation(new PointF(motionEvent.getX(), motionEvent.getY()), new Callback<LatLng>() { + @Override + public void onResult(LatLng latLng) { + if (onMapLongClickListener != null) { + onMapLongClickListener.onMapLongClick(latLng); + } + } + }); } } @@ -363,9 +376,13 @@ final class MapGestureDetector { public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (!scrollInProgress) { scrollInProgress = true; - MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( - getLocationFromGesture(e1.getX(), e1.getY()), - MapboxEvent.GESTURE_PAN_START, transform.getZoom())); + getLocationFromGesture(e1.getX(), e1.getY(), new Callback<Location>() { + @Override + public void onResult(Location location) { + MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( + location, MapboxEvent.GESTURE_PAN_START, transform.getZoom())); + } + }); } if (!trackingSettings.isScrollGestureCurrentlyEnabled()) { return false; @@ -407,9 +424,13 @@ final class MapGestureDetector { scaleGestureOccurred = true; beginTime = detector.getEventTime(); - MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( - getLocationFromGesture(detector.getFocusX(), detector.getFocusY()), - MapboxEvent.GESTURE_PINCH_START, transform.getZoom())); + getLocationFromGesture(detector.getFocusX(), detector.getFocusY(), new Callback<Location>() { + @Override + public void onResult(Location location) { + MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( + location, MapboxEvent.GESTURE_PINCH_START, transform.getZoom())); + } + }); return true; } @@ -499,9 +520,13 @@ final class MapGestureDetector { } beginTime = detector.getEventTime(); - MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( - getLocationFromGesture(detector.getFocusX(), detector.getFocusY()), - MapboxEvent.GESTURE_ROTATION_START, transform.getZoom())); + getLocationFromGesture(detector.getFocusX(), detector.getFocusY(), new Callback<Location>() { + @Override + public void onResult(Location location) { + MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( + location, MapboxEvent.GESTURE_ROTATION_START, transform.getZoom())); + } + }); return true; } @@ -549,7 +574,7 @@ final class MapGestureDetector { trackingSettings.resetTrackingModesIfRequired(true, true); // Get rotate value - double bearing = transform.getRawBearing(); + double bearing = transform.getBearing(); bearing += detector.getRotationDegreesDelta(); // Rotate the map @@ -580,9 +605,14 @@ final class MapGestureDetector { } beginTime = detector.getEventTime(); - MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( - getLocationFromGesture(detector.getFocusX(), detector.getFocusY()), - MapboxEvent.GESTURE_PITCH_START, transform.getZoom())); + getLocationFromGesture(detector.getFocusX(), detector.getFocusY(), new Callback<Location>() { + @Override + public void onResult(Location location) { + MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapClickEvent( + location, MapboxEvent.GESTURE_PITCH_START, transform.getZoom())); + } + }); + return true; } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapRunnable.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapRunnable.java new file mode 100644 index 0000000000..0268e2cf95 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapRunnable.java @@ -0,0 +1,9 @@ +package com.mapbox.mapboxsdk.maps; + +import android.support.annotation.NonNull; +import android.support.annotation.WorkerThread; + +interface MapRunnable { + @WorkerThread + void execute(@NonNull NativeMapView nativeMapView); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapState.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapState.java new file mode 100644 index 0000000000..90764b709b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapState.java @@ -0,0 +1,139 @@ +package com.mapbox.mapboxsdk.maps; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.UiThread; +import android.text.TextUtils; + +import com.mapbox.mapboxsdk.constants.MapboxConstants; + +class MapState extends MapThreadExecutor implements State { + + private String apiBaseUrl; + private String styleUrl; + private boolean debug; + private long transitionDuration; + private long transitionDelay; + + MapState(@NonNull NativeMapView nativeMapView, @NonNull ThreadExecutor invocation) { + super(nativeMapView, invocation); + } + + @UiThread + void setApiBaseUrl(@NonNull final String apiBaseUrl) { + this.apiBaseUrl = apiBaseUrl; + queueRenderEvent(new MapRunnable() { + @Override + public void execute(NativeMapView nativeMapView) { + nativeMapView.setApiBaseUrl(apiBaseUrl); + } + }); + } + + @UiThread + String getStyleUrl() { + return styleUrl; + } + + @UiThread + void setStyleUrl(final String styleUrl) { + this.styleUrl = styleUrl; + queueRenderEvent(new MapRunnable() { + @Override + public void execute(NativeMapView nativeMapView) { + nativeMapView.setStyleUrl(styleUrl); + } + }); + } + + @UiThread + boolean getDebug() { + return debug; + } + + @UiThread + void setDebug(final boolean debug) { + this.debug = debug; + queueRenderEvent(new MapRunnable() { + @Override + public void execute(NativeMapView nativeMapView) { + nativeMapView.setDebug(debug); + } + }); + } + + @UiThread + void cycleDebugOptions() { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.cycleDebugOptions(); + } + }); + } + + long getTransitionDuration() { + return transitionDuration; + } + + void setTransitionDuration(final long transitionDuration) { + this.transitionDuration = transitionDuration; + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setTransitionDuration(transitionDuration); + } + }); + } + + long getTransitionDelay() { + return transitionDelay; + } + + void setTransitionDelay(final long transitionDelay) { + this.transitionDelay = transitionDelay; + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setTransitionDuration(transitionDelay); + } + }); + } + + @UiThread + public void onSaveInstanceState(Bundle outState) { + outState.putBoolean(MapboxConstants.STATE_DEBUG_ACTIVE, debug); + outState.putString(MapboxConstants.STATE_STYLE_URL, styleUrl); + outState.putString(MapboxConstants.STATE_API_BASE_URL, apiBaseUrl); + outState.putLong(MapboxConstants.STATE_TRANSITION_DELAY, transitionDelay); + outState.putLong(MapboxConstants.STATE_TRANSITION_DURATION, transitionDuration); + } + + @UiThread + public void onRestoreInstanceState(Bundle savedInstanceState) { + boolean debug = savedInstanceState.getBoolean(MapboxConstants.STATE_DEBUG_ACTIVE); + if (debug) { + setDebug(debug); + } + + String styleUrl = savedInstanceState.getString(MapboxConstants.STATE_STYLE_URL); + if (!TextUtils.isEmpty(styleUrl)) { + setStyleUrl(styleUrl); + } + + String apiBaseUrl = savedInstanceState.getString(MapboxConstants.STATE_API_BASE_URL); + if (!TextUtils.isEmpty(apiBaseUrl)) { + setApiBaseUrl(apiBaseUrl); + } + + long transitionDuration = savedInstanceState.getLong(MapboxConstants.STATE_TRANSITION_DURATION); + if (transitionDuration != 0) { + setTransitionDuration(transitionDuration); + } + + long transitionDelay = savedInstanceState.getLong(MapboxConstants.STATE_TRANSITION_DELAY); + if (transitionDelay != 0) { + setTransitionDelay(transitionDelay); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapThreadExecutor.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapThreadExecutor.java new file mode 100644 index 0000000000..24fc29224f --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapThreadExecutor.java @@ -0,0 +1,32 @@ +package com.mapbox.mapboxsdk.maps; + +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.WorkerThread; + +abstract class MapThreadExecutor implements ThreadExecutor { + + private final NativeMapView nativeMapView; + private final ThreadExecutor threadExecutor; + + MapThreadExecutor(@NonNull NativeMapView nativeMapView, @NonNull ThreadExecutor threadExecutor) { + this.nativeMapView = nativeMapView; + this.threadExecutor = threadExecutor; + } + + @MainThread + public void queueRenderEvent(final MapRunnable mapRunnable) { + threadExecutor.queueRenderEvent(mapRunnable); + } + + @WorkerThread + public void queueUiEvent(Runnable runnable) { + threadExecutor.queueUiEvent(runnable); + } + + @NonNull + NativeMapView getNativeMapView() { + return nativeMapView; + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index c124531fa5..e6c08ef021 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -8,7 +8,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Canvas; import android.graphics.PointF; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -21,7 +20,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v7.app.AlertDialog; -import android.text.TextUtils; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -74,6 +72,7 @@ import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY; public class MapView extends FrameLayout { private NativeMapView nativeMapView; + private MapState mapState; private boolean destroyed; private GLSurfaceView glSurfaceView; @@ -141,6 +140,7 @@ public class MapView extends FrameLayout { glSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceView); glSurfaceView.setEGLConfigChooser(8, 8, 8, 0 /** TODO: What alpha value do we need here?? */, 16, 8); glSurfaceView.setEGLContextClientVersion(2); + glSurfaceView.setRenderer(new GLSurfaceView.Renderer() { @Override public void onSurfaceCreated(final GL10 gl, EGLConfig eglConfig) { @@ -149,9 +149,8 @@ public class MapView extends FrameLayout { // Create native Map object nativeMapView = new NativeMapView(MapView.this); - if (TextUtils.isEmpty(nativeMapView.getAccessToken())) { - nativeMapView.setAccessToken(Mapbox.getAccessToken()); - } + nativeMapView.setAccessToken(Mapbox.getAccessToken()); + nativeMapView.setReachability(isConnected()); //Continue configuring the map view on the main thread MapView.this.post(new Runnable() { @@ -191,6 +190,25 @@ public class MapView extends FrameLayout { } protected void onNativeMapViewReady() { + ThreadExecutor threadExecutor = new ThreadExecutor() { + @Override + public void queueRenderEvent(final MapRunnable runnable) { + glSurfaceView.queueEvent(new Runnable() { + @Override + public void run() { + runnable.execute(nativeMapView); + } + }); + } + + @Override + public void queueUiEvent(Runnable runnable) { + glSurfaceView.post(runnable); + } + }; + + + mapState = new MapState(nativeMapView, threadExecutor); // callback for focal point invalidation FocalPointInvalidator focalPoint = new FocalPointInvalidator(compassView); @@ -202,19 +220,19 @@ public class MapView extends FrameLayout { CameraZoomInvalidator zoomInvalidator = new CameraZoomInvalidator(); // setup components for MapboxMap creation - Projection proj = new Projection(nativeMapView); + Projection proj = new Projection(mapState, getWidth(), getHeight()); UiSettings uiSettings = new UiSettings(proj, focalPoint, compassView, attrView, logoView); - TrackingSettings trackingSettings = new TrackingSettings(myLocationView, uiSettings, focalPoint, zoomInvalidator); + TrackingSettings tracking = new TrackingSettings(myLocationView, uiSettings, focalPoint, zoomInvalidator); MyLocationViewSettings myLocationViewSettings = new MyLocationViewSettings(myLocationView, proj, focalPoint); MarkerViewManager markerViewManager = new MarkerViewManager((ViewGroup) findViewById(R.id.markerViewContainer)); - AnnotationManager annotations = new AnnotationManager(nativeMapView, this, markerViewManager); - Transform transform = new Transform(nativeMapView, annotations.getMarkerViewManager(), trackingSettings); - mapboxMap = new MapboxMap(nativeMapView, transform, uiSettings, trackingSettings, myLocationViewSettings, proj, + AnnotationManager annotations = new AnnotationManager(nativeMapView, threadExecutor, this, markerViewManager); + Transform transform = new Transform(nativeMapView, threadExecutor, annotations.getMarkerViewManager(), tracking); + mapboxMap = new MapboxMap(mapState, transform, uiSettings, tracking, myLocationViewSettings, proj, registerTouchListener, annotations); // user input - mapGestureDetector = new MapGestureDetector(getContext(), transform, proj, uiSettings, trackingSettings, annotations); - mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings); + mapGestureDetector = new MapGestureDetector(getContext(), transform, proj, uiSettings, tracking, annotations); + mapKeyListener = new MapKeyListener(transform, tracking, uiSettings); mapZoomButtonController = new MapZoomButtonController(this, uiSettings, transform); // inject widgets with MapboxMap @@ -222,9 +240,6 @@ public class MapView extends FrameLayout { myLocationView.setMapboxMap(mapboxMap); attrView.setOnClickListener(new AttributionOnClickListener(getContext(), transform)); - // notify Map object about current connectivity state - nativeMapView.setReachability(isConnected()); - // initialise MapboxMap mapboxMap.initialise(getContext(), mapboxMapOptions); @@ -249,14 +264,12 @@ public class MapView extends FrameLayout { */ @UiThread public void onCreate(@Nullable Bundle savedInstanceState) { - //nativeMapView.setAccessToken(Mapbox.getAccessToken()); - if (savedInstanceState == null) { MapboxTelemetry.getInstance().pushEvent(MapboxEvent.buildMapLoadEvent()); } else if (savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) { + mapState.onRestoreInstanceState(savedInstanceState); mapboxMap.onRestoreInstanceState(savedInstanceState); } - } /** @@ -269,6 +282,7 @@ public class MapView extends FrameLayout { @UiThread public void onSaveInstanceState(@NonNull Bundle outState) { outState.putBoolean(MapboxConstants.STATE_HAS_SAVED_STATE, true); + mapState.onSaveInstanceState(outState); mapboxMap.onSaveInstanceState(outState); } @@ -403,7 +417,12 @@ public class MapView extends FrameLayout { */ @UiThread public void onLowMemory() { - nativeMapView.onLowMemory(); + glSurfaceView.queueEvent(new Runnable() { + @Override + public void run() { + nativeMapView.onLowMemory(); + } + }); } // Called when debug mode is enabled to update a FPS counter @@ -449,17 +468,12 @@ public class MapView extends FrameLayout { * @param url The URL of the map style * @see Style */ - public void setStyleUrl(@NonNull String url) { + public void setStyleUrl(@NonNull final String url) { if (destroyed) { return; } - // stopgap for https://github.com/mapbox/mapbox-gl-native/issues/6242 - if (TextUtils.isEmpty(nativeMapView.getAccessToken())) { - nativeMapView.setAccessToken(Mapbox.getAccessToken()); - } - - nativeMapView.setStyleUrl(url); + mapState.setStyleUrl(url); } // @@ -485,28 +499,6 @@ public class MapView extends FrameLayout { } } - @Override - public void onDraw(Canvas canvas) { - Timber.i("onDraw"); - super.onDraw(canvas); - if (isInEditMode()) { - return; - } - } - - @Override - protected void onSizeChanged(int width, int height, int oldw, int oldh) { - if (destroyed) { - return; - } - - if (!isInEditMode()) { - if (nativeMapView != null) { - //XXX This should happen through GLSurfaceView#Renderer callbacks nativeMapView.resizeView(width, height); - } - } - } - // // View events // @@ -1005,14 +997,4 @@ public class MapView extends FrameLayout { onMapReadyCallbackList.add(callback); } } - - private class RenderRunnable implements Runnable { - @Override - public void run() { - if (destroyed) { - return; - } - nativeMapView.render(); - } - } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index 69a95457b8..2ea9f422b9 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -6,7 +6,6 @@ import android.graphics.PointF; import android.graphics.RectF; import android.location.Location; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -16,7 +15,6 @@ import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; -import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions; @@ -59,8 +57,7 @@ import timber.log.Timber; */ public final class MapboxMap { - private final NativeMapView nativeMapView; - + private final MapState mapState; private final UiSettings uiSettings; private final TrackingSettings trackingSettings; private final Projection projection; @@ -69,13 +66,12 @@ public final class MapboxMap { private final MyLocationViewSettings myLocationViewSettings; private final OnRegisterTouchListener onRegisterTouchListener; - private MapboxMap.OnFpsChangedListener onFpsChangedListener; - MapboxMap(NativeMapView map, Transform transform, UiSettings ui, TrackingSettings tracking, + MapboxMap(MapState mapState, Transform transform, UiSettings ui, TrackingSettings tracking, MyLocationViewSettings myLocationView, Projection projection, OnRegisterTouchListener listener, AnnotationManager annotations) { - this.nativeMapView = map; + this.mapState = mapState; this.uiSettings = ui; this.trackingSettings = tracking; this.projection = projection; @@ -94,16 +90,23 @@ public final class MapboxMap { // Map configuration setDebugActive(options.getDebugActive()); setApiBaseUrl(options); - setStyleUrl(options); + //setStyleUrl(options); } void onStart() { - nativeMapView.update(); - trackingSettings.onStart(); - if (TextUtils.isEmpty(nativeMapView.getStyleUrl())) { - // if user hasn't loaded a Style yet - nativeMapView.setStyleUrl(Style.MAPBOX_STREETS); + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.update(); + } + }); + + if (TextUtils.isEmpty(mapState.getStyleUrl())) { + Timber.e("Loading default style!!!!"); + mapState.setStyleUrl(Style.MAPBOX_STREETS); } + + trackingSettings.onStart(); } void onStop() { @@ -111,9 +114,7 @@ public final class MapboxMap { } void onSaveInstanceState(Bundle outState) { - outState.putParcelable(MapboxConstants.STATE_CAMERA_POSITION, transform.getCameraPosition()); - outState.putBoolean(MapboxConstants.STATE_DEBUG_ACTIVE, nativeMapView.getDebug()); - outState.putString(MapboxConstants.STATE_STYLE_URL, nativeMapView.getStyleUrl()); + transform.onSaveInstanceState(outState); trackingSettings.onSaveInstanceState(outState); uiSettings.onSaveInstanceState(outState); } @@ -126,12 +127,6 @@ public final class MapboxMap { uiSettings.onRestoreInstanceState(savedInstanceState); trackingSettings.onRestoreInstanceState(savedInstanceState); - nativeMapView.setDebug(savedInstanceState.getBoolean(MapboxConstants.STATE_DEBUG_ACTIVE)); - - final String styleUrl = savedInstanceState.getString(MapboxConstants.STATE_STYLE_URL); - if (!TextUtils.isEmpty(styleUrl)) { - nativeMapView.setStyleUrl(savedInstanceState.getString(MapboxConstants.STATE_STYLE_URL)); - } } /** @@ -157,11 +152,13 @@ public final class MapboxMap { * Called when the user */ void onUpdate() { - CameraPosition cameraPosition = transform.invalidateCameraPosition(); - uiSettings.update(cameraPosition); - // FIXME introduce update method with camera position - trackingSettings.update(); - annotationManager.update(); + CameraPosition cameraPosition = transform.getCameraPosition(); + if (cameraPosition != null) { + uiSettings.update(cameraPosition); + // FIXME introduce update method with camera position + trackingSettings.update(); + annotationManager.update(); + } } // Style @@ -176,7 +173,7 @@ public final class MapboxMap { */ @UiThread public long getTransitionDuration() { - return nativeMapView.getTransitionDuration(); + return mapState.getTransitionDuration(); } /** @@ -186,7 +183,7 @@ public final class MapboxMap { */ @UiThread public void setTransitionDuration(long duration) { - nativeMapView.setTransitionDuration(duration); + mapState.setTransitionDuration(duration); } /** @@ -199,7 +196,7 @@ public final class MapboxMap { */ @UiThread public long getTransitionDelay() { - return nativeMapView.getTransitionDelay(); + return mapState.getTransitionDelay(); } /** @@ -209,13 +206,26 @@ public final class MapboxMap { */ @UiThread public void setTransitionDelay(long delay) { - nativeMapView.setTransitionDelay(delay); + mapState.setTransitionDelay(delay); } - @Nullable + // FIXME: 10/02/2017 javadoc @UiThread - public Layer getLayer(@NonNull String layerId) { - return nativeMapView.getLayer(layerId); + public void getLayer(@NonNull final String layerId, final Callback<Layer> listener) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final Layer layer = nativeMapView.getLayer(layerId); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + if (listener != null) { + listener.onResult(layer); + } + } + }); + } + }); } /** @@ -225,16 +235,29 @@ public final class MapboxMap { * @param <T> the generic attribute of a Layer * @return the casted Layer, null if another type */ - @Nullable @UiThread - public <T extends Layer> T getLayerAs(@NonNull String layerId) { - try { - // noinspection unchecked - return (T) nativeMapView.getLayer(layerId); - } catch (ClassCastException exception) { - Timber.e(String.format("Layer: %s is a different type: %s", layerId, exception)); - return null; - } + public void getLayerAs(@NonNull final String layerId, final Callback.LayerCallback listener) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final Layer layer = nativeMapView.getLayer(layerId); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + if (listener != null) { + try { + // noinspection unchecked + listener.onResult(layer); + } catch (ClassCastException exception) { + Timber.e(String.format("Layer: %s is a different type: %s", layerId, exception)); + // noinspection unchecked + listener.onResult(null); + } + } + } + }); + } + }); } /** @@ -254,10 +277,16 @@ public final class MapboxMap { * @param before the layer id to add this layer before */ @UiThread - public void addLayer(@NonNull Layer layer, String before) { - nativeMapView.addLayer(layer, before); + public void addLayer(@NonNull final Layer layer, final String before) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.addLayer(layer, before); + } + }); } + /** * Removes the layer. Any references to the layer become invalid and should not be used anymore * @@ -265,8 +294,17 @@ public final class MapboxMap { * @throws NoSuchLayerException the exception thrown when layer with layerId doesn't exist */ @UiThread - public void removeLayer(@NonNull String layerId) throws NoSuchLayerException { - nativeMapView.removeLayer(layerId); + public void removeLayer(@NonNull final String layerId) throws NoSuchLayerException { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + try { + nativeMapView.removeLayer(layerId); + } catch (NoSuchLayerException exception) { + // FIXME handle exception + } + } + }); } /** @@ -276,14 +314,33 @@ public final class MapboxMap { * @throws NoSuchLayerException the exeption thrown when the layer doesn't exist */ @UiThread - public void removeLayer(@NonNull Layer layer) throws NoSuchLayerException { - nativeMapView.removeLayer(layer); + public void removeLayer(@NonNull final Layer layer) throws NoSuchLayerException { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + try { + nativeMapView.removeLayer(layer); + } catch (NoSuchLayerException exception) { + // FIXME handle exception + } + } + }); } - @Nullable @UiThread - public Source getSource(@NonNull String sourceId) { - return nativeMapView.getSource(sourceId); + public void getSource(@NonNull final String sourceId, final Callback<Source> listener) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final Source source = nativeMapView.getSource(sourceId); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + listener.onResult(source); + } + }); + } + }); } /** @@ -293,16 +350,26 @@ public final class MapboxMap { * @param <T> the generic type of a Source * @return the casted Source, null if another type */ - @Nullable @UiThread - public <T extends Source> T getSourceAs(@NonNull String sourceId) { - try { - // noinspection unchecked - return (T) nativeMapView.getSource(sourceId); - } catch (ClassCastException exception) { - Timber.e(String.format("Source: %s is a different type: %s", sourceId, exception)); - return null; - } + public void getSourceAs(@NonNull final String sourceId, final Callback.SourceCallback listener) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final Source source = nativeMapView.getSource(sourceId); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + try { + // noinspection unchecked + listener.onResult(source); + } catch (ClassCastException exception) { + Timber.e(String.format("Source: %s is a different type: %s", sourceId, exception)); + listener.onResult(null); + } + } + }); + } + }); } /** @@ -311,8 +378,13 @@ public final class MapboxMap { * @param source the source to add */ @UiThread - public void addSource(@NonNull Source source) { - nativeMapView.addSource(source); + public void addSource(@NonNull final Source source) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.addSource(source); + } + }); } /** @@ -322,8 +394,17 @@ public final class MapboxMap { * @throws NoSuchSourceException the exception thrown when the source with sourceId doesn't exist */ @UiThread - public void removeSource(@NonNull String sourceId) throws NoSuchSourceException { - nativeMapView.removeSource(sourceId); + public void removeSource(@NonNull final String sourceId) throws NoSuchSourceException { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + try { + nativeMapView.removeSource(sourceId); + } catch (NoSuchSourceException exception) { + // FIXME: 10/02/2017 handle exception + } + } + }); } /** @@ -333,8 +414,17 @@ public final class MapboxMap { * @throws NoSuchSourceException the exception thrown when the source with sourceId doesn't exist */ @UiThread - public void removeSource(@NonNull Source source) throws NoSuchSourceException { - nativeMapView.removeSource(source); + public void removeSource(@NonNull final Source source) throws NoSuchSourceException { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + try { + nativeMapView.removeSource(source); + } catch (NoSuchSourceException exception) { + // FIXME: 10/02/2017 handle exception + } + } + }); } /** @@ -344,8 +434,13 @@ public final class MapboxMap { * @param image the pre-multiplied Bitmap */ @UiThread - public void addImage(@NonNull String name, @NonNull Bitmap image) { - nativeMapView.addImage(name, image); + public void addImage(@NonNull final String name, @NonNull final Bitmap image) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.addImage(name, image); + } + }); } /** @@ -354,8 +449,13 @@ public final class MapboxMap { * @param name the name of the image to remove */ @UiThread - public void removeImage(String name) { - nativeMapView.removeImage(name); + public void removeImage(final String name) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.removeImage(name); + } + }); } // @@ -465,6 +565,7 @@ public final class MapboxMap { * * @return the Projection associated with this map */ + @UiThread public Projection getProjection() { return projection; } @@ -479,6 +580,7 @@ public final class MapboxMap { * This invokes the {@link CancelableCallback} for ongoing camera updates. * </p> */ + @UiThread public void cancelTransitions() { transform.cancelTransitions(); } @@ -490,6 +592,7 @@ public final class MapboxMap { * * @return The current position of the Camera. */ + @UiThread public final CameraPosition getCameraPosition() { return transform.getCameraPosition(); } @@ -501,6 +604,7 @@ public final class MapboxMap { * * @param cameraPosition the camera position to set */ + @UiThread public void setCameraPosition(@NonNull CameraPosition cameraPosition) { moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), null); } @@ -527,15 +631,10 @@ public final class MapboxMap { */ @UiThread public final void moveCamera(final CameraUpdate update, final MapboxMap.CancelableCallback callback) { - new Handler().post(new Runnable() { - @Override - public void run() { - transform.moveCamera(MapboxMap.this, update, callback); - // MapChange.REGION_DID_CHANGE_ANIMATED is not called for `jumpTo` - // invalidate camera position to provide OnCameraChange event. - invalidateCameraPosition(); - } - }); + transform.moveCamera(MapboxMap.this, update, callback); + // MapChange.REGION_DID_CHANGE_ANIMATED is not called for `jumpTo` + // invalidate camera position to provide OnCameraChange event. + invalidateCameraPosition(); } /** @@ -633,12 +732,7 @@ public final class MapboxMap { @UiThread public final void easeCamera(final CameraUpdate update, final int durationMs, final boolean easingInterpolator, final MapboxMap.CancelableCallback callback) { - new Handler().post(new Runnable() { - @Override - public void run() { - transform.easeCamera(MapboxMap.this, update, durationMs, easingInterpolator, callback); - } - }); + transform.easeCamera(MapboxMap.this, update, durationMs, easingInterpolator, callback); } /** @@ -709,19 +803,14 @@ public final class MapboxMap { @UiThread public final void animateCamera(final CameraUpdate update, final int durationMs, final MapboxMap.CancelableCallback callback) { - new Handler().post(new Runnable() { - @Override - public void run() { - transform.animateCamera(MapboxMap.this, update, durationMs, callback); - } - }); + transform.animateCamera(MapboxMap.this, update, durationMs, callback); } /** * Invalidates the current camera position by reconstructing it from mbgl */ void invalidateCameraPosition() { - CameraPosition cameraPosition = transform.invalidateCameraPosition(); + CameraPosition cameraPosition = transform.getCameraPosition(); if (cameraPosition != null) { transform.updateCameraPosition(cameraPosition); } @@ -738,19 +827,12 @@ public final class MapboxMap { transform.resetNorth(); } - /** - * Set focal bearing. - */ - public void setFocalBearing(double bearing, float focalX, float focalY, long duration) { - transform.setBearing(bearing, focalX, focalY, duration); - } - public float getHeight() { - return nativeMapView.getHeight(); + return projection.getHeight(); } public float getWidth() { - return nativeMapView.getWidth(); + return projection.getWidth(); } // @@ -764,7 +846,7 @@ public final class MapboxMap { */ @UiThread public boolean isDebugActive() { - return nativeMapView.getDebug(); + return mapState.getDebug(); } /** @@ -777,7 +859,7 @@ public final class MapboxMap { */ @UiThread public void setDebugActive(boolean debugActive) { - nativeMapView.setDebug(debugActive); + mapState.setDebug(debugActive); } /** @@ -791,7 +873,7 @@ public final class MapboxMap { */ @UiThread public void cycleDebugOptions() { - nativeMapView.cycleDebugOptions(); + mapState.cycleDebugOptions(); } // @@ -801,7 +883,7 @@ public final class MapboxMap { private void setApiBaseUrl(@NonNull MapboxMapOptions options) { String apiBaseUrl = options.getApiBaseUrl(); if (!TextUtils.isEmpty(apiBaseUrl)) { - nativeMapView.setApiBaseUrl(apiBaseUrl); + mapState.setApiBaseUrl(apiBaseUrl); } } @@ -840,7 +922,7 @@ public final class MapboxMap { */ @UiThread public void setStyleUrl(@NonNull String url) { - nativeMapView.setStyleUrl(url); + mapState.setStyleUrl(url); } /** @@ -869,13 +951,10 @@ public final class MapboxMap { * * @param options the object containing the style url */ + @UiThread private void setStyleUrl(@NonNull MapboxMapOptions options) { String style = options.getStyle(); if (!TextUtils.isEmpty(style)) { - // stopgap for https://github.com/mapbox/mapbox-gl-native/issues/6242 - if (TextUtils.isEmpty(nativeMapView.getAccessToken())) { - nativeMapView.setAccessToken(Mapbox.getAccessToken()); - } setStyleUrl(style); } } @@ -891,7 +970,7 @@ public final class MapboxMap { @UiThread @NonNull public String getStyleUrl() { - return nativeMapView.getStyleUrl(); + return mapState.getStyleUrl(); } // @@ -909,9 +988,8 @@ public final class MapboxMap { * @return The {@code Marker} that was added to the map. */ @UiThread - @NonNull - public Marker addMarker(@NonNull MarkerOptions markerOptions) { - return annotationManager.addMarker(markerOptions, this); + public void addMarker(@NonNull MarkerOptions markerOptions) { + annotationManager.addMarker(markerOptions, this, null); } /** @@ -925,9 +1003,37 @@ public final class MapboxMap { * @return The {@code Marker} that was added to the map. */ @UiThread - @NonNull - public Marker addMarker(@NonNull BaseMarkerOptions markerOptions) { - return annotationManager.addMarker(markerOptions, this); + public void addMarker(@NonNull MarkerOptions markerOptions, @Nullable Callback<Marker> markerCallback) { + annotationManager.addMarker(markerOptions, this, markerCallback); + } + + + /** + * <p> + * Adds a marker to this map. + * </p> + * The marker's icon is rendered on the map at the location {@code Marker.position}. + * If {@code Marker.title} is defined, the map shows an info box with the marker's title and snippet. + * + * @param markerOptions A marker options object that defines how to render the marker. + */ + @UiThread + public void addMarker(@NonNull BaseMarkerOptions markerOptions) { + annotationManager.addMarker(markerOptions, this, null); + } + + /** + * <p> + * Adds a marker to this map. + * </p> + * The marker's icon is rendered on the map at the location {@code Marker.position}. + * If {@code Marker.title} is defined, the map shows an info box with the marker's title and snippet. + * + * @param markerOptions A marker options object that defines how to render the marker. + */ + @UiThread + public void addMarker(@NonNull BaseMarkerOptions markerOptions, @Nullable Callback<Marker> markerCallback) { + annotationManager.addMarker(markerOptions, this, markerCallback); } /** @@ -942,8 +1048,8 @@ public final class MapboxMap { */ @UiThread @NonNull - public MarkerView addMarker(@NonNull BaseMarkerViewOptions markerOptions) { - return annotationManager.addMarker(markerOptions, this, null); + public void addMarker(@NonNull BaseMarkerViewOptions markerOptions) { + annotationManager.addMarker(markerOptions, this, null); } @@ -959,29 +1065,34 @@ public final class MapboxMap { * @return The {@code Marker} that was added to the map. */ @UiThread - @NonNull - public MarkerView addMarker(@NonNull BaseMarkerViewOptions markerOptions, - final MarkerViewManager.OnMarkerViewAddedListener onMarkerViewAddedListener) { - return annotationManager.addMarker(markerOptions, this, onMarkerViewAddedListener); + public void addMarker(@NonNull BaseMarkerViewOptions markerOptions, + Callback<MarkerView> listener) { + annotationManager.addMarker(markerOptions, this, listener); } /** * FIXME javadoc */ @UiThread - @NonNull - public List<MarkerView> addMarkerViews(@NonNull List<? extends - BaseMarkerViewOptions> markerViewOptions) { - return annotationManager.addMarkerViews(markerViewOptions, this); + public void addMarkerViews(@NonNull List<? extends BaseMarkerViewOptions> markerViewOptions) { + annotationManager.addMarkerViews(markerViewOptions, this, null); } /** * FIXME javadoc */ @UiThread - @NonNull - public List<MarkerView> getMarkerViewsInRect(@NonNull RectF rect) { - return annotationManager.getMarkerViewsInRect(rect); + public void addMarkerViews(@NonNull List<? extends BaseMarkerViewOptions> markerViewOptions, + Callback<List<MarkerView>> callback) { + annotationManager.addMarkerViews(markerViewOptions, this, callback); + } + + /** + * FIXME javadoc + */ + @UiThread + public void getMarkerViewsInRect(@NonNull RectF rect, Callback<List<MarkerView>> listener) { + annotationManager.getMarkerViewsInRect(rect, listener); } /** @@ -995,10 +1106,9 @@ public final class MapboxMap { * @return A list of the {@code Marker}s that were added to the map. */ @UiThread - @NonNull - public List<Marker> addMarkers(@NonNull List<? extends - BaseMarkerOptions> markerOptionsList) { - return annotationManager.addMarkers(markerOptionsList, this); + public void addMarkers(@NonNull List<? extends + BaseMarkerOptions> markerOptionsList, Callback<List<Marker>> listCallback) { + annotationManager.addMarkers(markerOptionsList, this, listCallback); } /** @@ -1020,9 +1130,8 @@ public final class MapboxMap { * @return The {@code Polyine} that was added to the map. */ @UiThread - @NonNull - public Polyline addPolyline(@NonNull PolylineOptions polylineOptions) { - return annotationManager.addPolyline(polylineOptions, this); + public void addPolyline(@NonNull PolylineOptions polylineOptions, Callback<Polyline> listener) { + annotationManager.addPolyline(polylineOptions, this, listener); } /** @@ -1032,9 +1141,8 @@ public final class MapboxMap { * @return A list of the {@code Polyline}s that were added to the map. */ @UiThread - @NonNull - public List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList) { - return annotationManager.addPolylines(polylineOptionsList, this); + public void addPolylines(@NonNull List<PolylineOptions> polylineOptionsList, Callback<List<Polyline>> listener) { + annotationManager.addPolylines(polylineOptionsList, this, listener); } /** @@ -1054,9 +1162,8 @@ public final class MapboxMap { * @return The {@code Polygon} that was added to the map. */ @UiThread - @NonNull - public Polygon addPolygon(@NonNull PolygonOptions polygonOptions) { - return annotationManager.addPolygon(polygonOptions, this); + public void addPolygon(@NonNull PolygonOptions polygonOptions, Callback<Polygon> listener) { + annotationManager.addPolygon(polygonOptions, this, listener); } /** @@ -1066,9 +1173,8 @@ public final class MapboxMap { * @return A list of the {@code Polygon}s that were added to the map. */ @UiThread - @NonNull - public List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList) { - return annotationManager.addPolygons(polygonOptionsList, this); + public void addPolygons(@NonNull List<PolygonOptions> polygonOptionsList, Callback<List<Polygon>> listener) { + annotationManager.addPolygons(polygonOptionsList, this, listener); } @@ -1591,11 +1697,15 @@ public final class MapboxMap { * Takes a snapshot of the map. * * @param callback Callback method invoked when the snapshot is taken. - * @param bitmap A pre-allocated bitmap. */ @UiThread - public void snapshot(@NonNull SnapshotReadyCallback callback) { - nativeMapView.addSnapshotCallback(callback); + public void snapshot(@NonNull final SnapshotReadyCallback callback) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.addSnapshotCallback(callback); + } + }); } /** @@ -1606,10 +1716,20 @@ public final class MapboxMap { * @return the list of feature */ @UiThread - @NonNull - public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, @Nullable String... - layerIds) { - return nativeMapView.queryRenderedFeatures(coordinates, layerIds); + public void queryRenderedFeatures(@NonNull final PointF coordinates, @NonNull final Callback<List<Feature>> callback, + @Nullable final String... layerIds) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final List<Feature> features = nativeMapView.queryRenderedFeatures(coordinates, layerIds); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + callback.onResult(features); + } + }); + } + }); } /** @@ -1620,10 +1740,20 @@ public final class MapboxMap { * @return the list of feature */ @UiThread - @NonNull - public List<Feature> queryRenderedFeatures(@NonNull RectF coordinates, @Nullable String... - layerIds) { - return nativeMapView.queryRenderedFeatures(coordinates, layerIds); + public void queryRenderedFeatures(@NonNull final RectF coordinates, @NonNull final Callback<List<Feature>> callback, + @Nullable final String... layerIds) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final List<Feature> features = nativeMapView.queryRenderedFeatures(coordinates, layerIds); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + callback.onResult(features); + } + }); + } + }); } // @@ -2012,4 +2142,4 @@ public final class MapboxMap { Transform getTransform() { return transform; } -} +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index f5fd886662..c1f6afbda1 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -578,10 +578,15 @@ final class NativeMapView { * * @param bitmap the snapshot as a bitmap */ - protected void onSnapshotReady(Bitmap bitmap) { - if (snapshotReadyCallback != null && bitmap != null) { - snapshotReadyCallback.onSnapshotReady(bitmap); - } + protected void onSnapshotReady(final Bitmap bitmap) { + mapView.post(new Runnable() { + @Override + public void run() { + if (snapshotReadyCallback != null && bitmap != null) { + snapshotReadyCallback.onSnapshotReady(bitmap); + } + } + }); } native void setReachability(boolean status); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java index b7f93cc913..b52184912c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java @@ -15,25 +15,34 @@ import com.mapbox.mapboxsdk.geometry.VisibleRegion; */ public class Projection { - private final NativeMapView nativeMapView; - private int[] contentPadding; + private final MapState mapState; + private final float height; + private final float width; - Projection(@NonNull NativeMapView nativeMapView) { - this.nativeMapView = nativeMapView; - this.contentPadding = new int[] {0, 0, 0, 0}; + private int[] contentPadding = new int[] {0, 0, 0, 0}; + + Projection(@NonNull MapState mapState, float width, float height) { + this.mapState = mapState; + this.width = width; + this.height = height; } void setContentPadding(int[] contentPadding, int[] userLocationViewPadding) { this.contentPadding = contentPadding; - int[] padding = new int[] { + final int[] padding = new int[] { contentPadding[0] + userLocationViewPadding[0], contentPadding[1] + userLocationViewPadding[1], contentPadding[2] + userLocationViewPadding[2], contentPadding[3] + userLocationViewPadding[3] }; - nativeMapView.setContentPadding(padding); + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setContentPadding(padding); + } + }); } int[] getContentPadding() { @@ -54,8 +63,20 @@ public class Projection { * @param latitude The latitude for which to return the value. * @return The distance measured in meters. */ - public double getMetersPerPixelAtLatitude(@FloatRange(from = -90, to = 90) double latitude) { - return nativeMapView.getMetersPerPixelAtLatitude(latitude); + public void getMetersPerPixelAtLatitude(@FloatRange(from = -90, to = 90) final double latitude, + final Callback<Double> listener) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final double metersPerPixel = nativeMapView.getMetersPerPixelAtLatitude(latitude); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + listener.onResult(metersPerPixel); + } + }); + } + }); } /** @@ -67,8 +88,19 @@ public class Projection { * @return The LatLng corresponding to the point on the screen, or null if the ray through * the given screen point does not intersect the ground plane. */ - public LatLng fromScreenLocation(PointF point) { - return nativeMapView.latLngForPixel(point); + public void fromScreenLocation(final PointF point, final Callback<LatLng> listener) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final LatLng latLng = nativeMapView.latLngForPixel(point); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + listener.onResult(latLng); + } + }); + } + }); } /** @@ -77,25 +109,37 @@ public class Projection { * * @return The projection of the viewing frustum in its current state. */ - public VisibleRegion getVisibleRegion() { - LatLngBounds.Builder builder = new LatLngBounds.Builder(); - - float left = contentPadding[0]; - float right = nativeMapView.getWidth() - contentPadding[2]; - float top = contentPadding[1]; - float bottom = nativeMapView.getHeight() - contentPadding[3]; - - LatLng topLeft = fromScreenLocation(new PointF(left, top)); - LatLng topRight = fromScreenLocation(new PointF(right, top)); - LatLng bottomRight = fromScreenLocation(new PointF(right, bottom)); - LatLng bottomLeft = fromScreenLocation(new PointF(left, bottom)); - - builder.include(topLeft) - .include(topRight) - .include(bottomRight) - .include(bottomLeft); - - return new VisibleRegion(topLeft, topRight, bottomLeft, bottomRight, builder.build()); + public void getVisibleRegion(final Callback<VisibleRegion> listener) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + LatLngBounds.Builder builder = new LatLngBounds.Builder(); + + final float left = contentPadding[0]; + float right = nativeMapView.getWidth() - contentPadding[2]; + float top = contentPadding[1]; + float bottom = nativeMapView.getHeight() - contentPadding[3]; + + LatLng topLeft = nativeMapView.latLngForPixel(new PointF(left, top)); + LatLng topRight = nativeMapView.latLngForPixel(new PointF(right, top)); + LatLng bottomRight = nativeMapView.latLngForPixel(new PointF(right, bottom)); + LatLng bottomLeft = nativeMapView.latLngForPixel(new PointF(left, bottom)); + + LatLngBounds bounds = builder.include(topLeft) + .include(topRight) + .include(bottomRight) + .include(bottomLeft) + .build(); + + final VisibleRegion visibleRegion = new VisibleRegion(topLeft, topRight, bottomLeft, bottomRight, bounds); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + listener.onResult(visibleRegion); + } + }); + } + }); } /** @@ -106,15 +150,26 @@ public class Projection { * @param location A LatLng on the map to convert to a screen location. * @return A Point representing the screen location in screen pixels. */ - public PointF toScreenLocation(LatLng location) { - return nativeMapView.pixelForLatLng(location); + public void toScreenLocation(final LatLng location, final Callback<PointF> listener) { + mapState.queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final PointF pointF = nativeMapView.pixelForLatLng(location); + mapState.queueUiEvent(new Runnable() { + @Override + public void run() { + listener.onResult(pointF); + } + }); + } + }); } float getHeight() { - return nativeMapView.getHeight(); + return height; } float getWidth() { - return nativeMapView.getWidth(); + return width; } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/State.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/State.java new file mode 100644 index 0000000000..5835312371 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/State.java @@ -0,0 +1,13 @@ +package com.mapbox.mapboxsdk.maps; + +import android.os.Bundle; +import android.support.annotation.UiThread; + +interface State { + + @UiThread + void onSaveInstanceState(Bundle outState); + + @UiThread + void onRestoreInstanceState(Bundle savedInstanceState); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ThreadExecutor.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ThreadExecutor.java new file mode 100644 index 0000000000..1533ca5733 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ThreadExecutor.java @@ -0,0 +1,12 @@ +package com.mapbox.mapboxsdk.maps; + +import android.support.annotation.UiThread; +import android.support.annotation.WorkerThread; + +interface ThreadExecutor { + @UiThread + void queueRenderEvent(MapRunnable runnable); + + @WorkerThread + void queueUiEvent(Runnable runnable); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java index 97170ca3d4..f2edac63a8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java @@ -1,8 +1,10 @@ package com.mapbox.mapboxsdk.maps; +import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.annotation.WorkerThread; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.camera.CameraPosition; @@ -16,8 +18,6 @@ import java.util.concurrent.TimeUnit; import timber.log.Timber; -import static com.mapbox.mapboxsdk.maps.MapView.REGION_DID_CHANGE_ANIMATED; - /** * Resembles the current Map transformation. * <p> @@ -25,19 +25,24 @@ import static com.mapbox.mapboxsdk.maps.MapView.REGION_DID_CHANGE_ANIMATED; * {@link com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraChangeListener}. * </p> */ -final class Transform implements MapView.OnMapChangedListener { +// FIXME use MapState instead of exending MapThreadExecutor +final class Transform extends MapThreadExecutor implements State { - private final NativeMapView mapView; private final MarkerViewManager markerViewManager; private final TrackingSettings trackingSettings; private final MyLocationView myLocationView; - private CameraPosition cameraPosition; private MapboxMap.CancelableCallback cameraCancelableCallback; private MapboxMap.OnCameraChangeListener onCameraChangeListener; - Transform(NativeMapView mapView, MarkerViewManager markerViewManager, TrackingSettings trackingSettings) { - this.mapView = mapView; + private LatLng centerLatLng; + private double bearing; + private double tilt; + private double zoom; + + Transform(NativeMapView mapView, ThreadExecutor threadExecutor, MarkerViewManager markerViewManager, + TrackingSettings trackingSettings) { + super(mapView, threadExecutor); this.markerViewManager = markerViewManager; this.trackingSettings = trackingSettings; this.myLocationView = trackingSettings.getMyLocationView(); @@ -51,15 +56,43 @@ final class Transform implements MapView.OnMapChangedListener { } // + // State + // + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putDouble(MapboxConstants.STATE_CAMERA_LAT, centerLatLng.getLatitude()); + outState.putDouble(MapboxConstants.STATE_CAMERA_LNG, centerLatLng.getLongitude()); + outState.putDouble(MapboxConstants.STATE_CAMERA_BEARING, bearing); + outState.putDouble(MapboxConstants.STATE_CAMERA_TILT, tilt); + outState.putDouble(MapboxConstants.STATE_CAMERA_ZOOM, zoom); + } + + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + double latitude = savedInstanceState.getDouble(MapboxConstants.STATE_CAMERA_LAT, 0); + double longitude = savedInstanceState.getDouble(MapboxConstants.STATE_CAMERA_LNG, 0); + centerLatLng = new LatLng(latitude, longitude); + bearing = savedInstanceState.getDouble(MapboxConstants.STATE_CAMERA_BEARING, 0); + tilt = savedInstanceState.getDouble(MapboxConstants.STATE_CAMERA_TILT, 0); + zoom = savedInstanceState.getDouble(MapboxConstants.STATE_CAMERA_ZOOM, 0); + // FIXME: 10/02/2017 ACTUALLY RESTORE STATE + } + + // // Camera API // @UiThread public final CameraPosition getCameraPosition() { - if (cameraPosition == null) { - cameraPosition = invalidateCameraPosition(); - } - return cameraPosition; + return new CameraPosition.Builder(centerLatLng, zoom, tilt, bearing).build(); + } + + void setCameraPosition(CameraPosition cameraPostion) { + centerLatLng = cameraPostion.target; + tilt = cameraPostion.tilt; + bearing = cameraPostion.bearing; + zoom = cameraPostion.zoom; } @UiThread @@ -70,87 +103,123 @@ final class Transform implements MapView.OnMapChangedListener { markerViewManager.setTilt((float) position.tilt); } - @Override - public void onMapChanged(@MapView.MapChange int change) { - if (change == REGION_DID_CHANGE_ANIMATED && cameraCancelableCallback != null) { - updateCameraPosition(invalidateCameraPosition()); - if (cameraCancelableCallback != null) { - cameraCancelableCallback.onFinish(); - cameraCancelableCallback = null; + @UiThread + final void moveCamera(final MapboxMap mapboxMap, final CameraUpdate update, + final MapboxMap.CancelableCallback callback) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + CameraPosition cameraPosition = update.getCameraPosition(mapboxMap); + setCameraPosition(cameraPosition); + cancelTransitions(nativeMapView); + nativeMapView.jumpTo(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom); + trackingSettings.resetTrackingModesIfRequired(cameraPosition); + if (callback != null) { + callback.onFinish(); + } } - mapView.removeOnMapChangedListener(this); - } - } + }); - @UiThread - final void moveCamera(MapboxMap mapboxMap, CameraUpdate update, MapboxMap.CancelableCallback callback) { - cameraPosition = update.getCameraPosition(mapboxMap); - trackingSettings.resetTrackingModesIfRequired(cameraPosition); - cancelTransitions(); - mapView.jumpTo(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom); - if (callback != null) { - callback.onFinish(); - } } @UiThread - final void easeCamera(MapboxMap mapboxMap, CameraUpdate update, int durationMs, boolean easingInterpolator, - final MapboxMap.CancelableCallback callback) { - cameraPosition = update.getCameraPosition(mapboxMap); - trackingSettings.resetTrackingModesIfRequired(cameraPosition); - - cancelTransitions(); - if (callback != null) { - cameraCancelableCallback = callback; - mapView.addOnMapChangedListener(this); - } - - mapView.easeTo(cameraPosition.bearing, cameraPosition.target, getDurationNano(durationMs), cameraPosition.tilt, - cameraPosition.zoom, easingInterpolator); + final void easeCamera(final MapboxMap mapboxMap, final CameraUpdate update, final int durationMs, + final boolean easingInterpolator, final MapboxMap.CancelableCallback callback) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + CameraPosition position = update.getCameraPosition(mapboxMap); + trackingSettings.resetTrackingModesIfRequired(position); + + cancelTransitions(nativeMapView); + if (callback != null) { + cameraCancelableCallback = callback; + // nativeMapView.addOnMapChangedListener(Transform.this); + } + + nativeMapView.easeTo(position.bearing, position.target, getDurationNano(durationMs), position.tilt, + position.zoom, easingInterpolator); + } + }); } @UiThread - final void animateCamera(MapboxMap mapboxMap, CameraUpdate update, int durationMs, + final void animateCamera(final MapboxMap mapboxMap, final CameraUpdate update, final int durationMs, final MapboxMap.CancelableCallback callback) { - cameraPosition = update.getCameraPosition(mapboxMap); - trackingSettings.resetTrackingModesIfRequired(cameraPosition); - - cancelTransitions(); - if (callback != null) { - cameraCancelableCallback = callback; - mapView.addOnMapChangedListener(this); - } - - mapView.flyTo(cameraPosition.bearing, cameraPosition.target, getDurationNano(durationMs), cameraPosition.tilt, - cameraPosition.zoom); + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + CameraPosition position = update.getCameraPosition(mapboxMap); + trackingSettings.resetTrackingModesIfRequired(position); + + cancelTransitions(nativeMapView); + if (callback != null) { + cameraCancelableCallback = callback; + // nativeMapView.addOnMapChangedListener(Transform.this); + } + + nativeMapView.flyTo(position.bearing, position.target, getDurationNano(durationMs), position.tilt, + position.zoom); + } + }); } @UiThread - @Nullable - CameraPosition invalidateCameraPosition() { - if (mapView != null) { - cameraPosition = new CameraPosition.Builder(mapView.getCameraValues()).build(); - if (onCameraChangeListener != null) { - onCameraChangeListener.onCameraChange(this.cameraPosition); + void invalidateCameraPosition() { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + final CameraPosition cameraPosition = new CameraPosition.Builder(nativeMapView.getCameraValues()).build(); + queueUiEvent(new Runnable() { + @Override + public void run() { + updateCameraPosition(cameraPosition); + if (onCameraChangeListener != null) { + // post camera change event on ui Thread + onCameraChangeListener.onCameraChange(cameraPosition); + } + } + }); } - } - return cameraPosition; + }); } + @UiThread void cancelTransitions() { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + cancelTransitions(nativeMapView); + } + }); + } + + @WorkerThread + void cancelTransitions(NativeMapView nativeMapView) { if (cameraCancelableCallback != null) { - cameraCancelableCallback.onCancel(); - cameraCancelableCallback = null; + queueUiEvent(new Runnable() { + @Override + public void run() { + cameraCancelableCallback.onCancel(); + cameraCancelableCallback = null; + } + }); } - mapView.cancelTransitions(); + nativeMapView.cancelTransitions(); } @UiThread void resetNorth() { - cancelTransitions(); - mapView.resetNorth(); + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + cancelTransitions(nativeMapView); + nativeMapView.resetNorth(); + } + }); } + @UiThread void setOnCameraChangeListener(@Nullable MapboxMap.OnCameraChangeListener listener) { this.onCameraChangeListener = listener; } @@ -159,151 +228,175 @@ final class Transform implements MapView.OnMapChangedListener { return durationMs > 0 ? TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS) : 0; } - // - // non Camera API - // - - // Zoom in or out - + @UiThread double getZoom() { - return cameraPosition.zoom; + return zoom; } + @UiThread void zoom(boolean zoomIn) { + zoom = zoomIn ? zoom + 1 : zoom - 1; zoom(zoomIn, -1.0f, -1.0f); } - void zoom(boolean zoomIn, float x, float y) { - // Cancel any animation - cancelTransitions(); + @UiThread + void zoom(final boolean zoomIn, final float x, final float y) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + cancelTransitions(nativeMapView); + + if (zoomIn) { + nativeMapView.scaleBy(2.0, x, y, MapboxConstants.ANIMATION_DURATION); + } else { + nativeMapView.scaleBy(0.5, x, y, MapboxConstants.ANIMATION_DURATION); + } + } + }); - if (zoomIn) { - mapView.scaleBy(2.0, x, y, MapboxConstants.ANIMATION_DURATION); - } else { - mapView.scaleBy(0.5, x, y, MapboxConstants.ANIMATION_DURATION); - } } - void setZoom(double zoom) { - mapView.setZoom(zoom); + @UiThread + void setZoom(final double zoom) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setZoom(zoom); + } + }); } - // Direction + @UiThread double getBearing() { - double direction = -mapView.getBearing(); - - while (direction > 360) { - direction -= 360; - } - while (direction < 0) { - direction += 360; - } - - return direction; + return bearing; } - double getRawBearing() { - return mapView.getBearing(); - } - - void setBearing(double bearing) { - if (myLocationView != null) { - myLocationView.setBearing(bearing); - } - mapView.setBearing(bearing); - } - - void setBearing(double bearing, float focalX, float focalY) { + @UiThread + void setBearing(final double bearing) { if (myLocationView != null) { myLocationView.setBearing(bearing); } - mapView.setBearing(bearing, focalX, focalY); + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setBearing(bearing); + } + }); } - void setBearing(double bearing, float focalX, float focalY, long duration) { + @UiThread + void setBearing(final double bearing, final float focalX, final float focalY) { + this.bearing = bearing; if (myLocationView != null) { myLocationView.setBearing(bearing); } - mapView.setBearing(bearing, focalX, focalY, duration); - } - - - // - // LatLng / CenterCoordinate - // - - LatLng getLatLng() { - return mapView.getLatLng(); + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setBearing(bearing, focalX, focalY); + } + }); } // // Pitch / Tilt // + @UiThread double getTilt() { - return mapView.getPitch(); + return tilt; } - void setTilt(Double pitch) { + @UiThread + void setTilt(final Double pitch) { + tilt = pitch; if (myLocationView != null) { myLocationView.setTilt(pitch); } markerViewManager.setTilt(pitch.floatValue()); - mapView.setPitch(pitch, 0); + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setPitch(pitch, 0); + } + }); } // // Center coordinate // - LatLng getCenterCoordinate() { - return mapView.getLatLng(); - } - - void setCenterCoordinate(LatLng centerCoordinate) { - mapView.setLatLng(centerCoordinate); - } - - void setGestureInProgress(boolean gestureInProgress) { - mapView.setGestureInProgress(gestureInProgress); + void setGestureInProgress(final boolean gestureInProgress) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setGestureInProgress(gestureInProgress); + } + }); if (!gestureInProgress) { invalidateCameraPosition(); } } - void zoomBy(double pow, float x, float y) { - mapView.scaleBy(pow, x, y); + void zoomBy(final double pow, final float x, final float y) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.scaleBy(pow, x, y); + } + }); } - void moveBy(double offsetX, double offsetY, long duration) { - mapView.moveBy(offsetX, offsetY, duration); + void moveBy(final double offsetX, final double offsetY, final long duration) { + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.moveBy(offsetX, offsetY, duration); + } + }); } // // Min & Max ZoomLevel // - void setMinZoom(double minZoom) { + private double minZoom; + private double maxZoom; + + void setMinZoom(final double minZoom) { if ((minZoom < MapboxConstants.MINIMUM_ZOOM) || (minZoom > MapboxConstants.MAXIMUM_ZOOM)) { Timber.e("Not setting minZoomPreference, value is in unsupported range: " + minZoom); return; } - mapView.setMinZoom(minZoom); + this.minZoom = minZoom; + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setMinZoom(minZoom); + } + }); } double getMinZoom() { - return mapView.getMinZoom(); + return minZoom; } - void setMaxZoom(double maxZoom) { + void setMaxZoom(final double maxZoom) { if ((maxZoom < MapboxConstants.MINIMUM_ZOOM) || (maxZoom > MapboxConstants.MAXIMUM_ZOOM)) { Timber.e("Not setting maxZoomPreference, value is in unsupported range: " + maxZoom); return; } - mapView.setMaxZoom(maxZoom); + + this.maxZoom = maxZoom; + queueRenderEvent(new MapRunnable() { + @Override + public void execute(@NonNull NativeMapView nativeMapView) { + nativeMapView.setMaxZoom(maxZoom); + } + }); } double getMaxZoom() { - return mapView.getMaxZoom(); + return maxZoom; } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java index ed6ef5199a..b3a7fd787f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java @@ -172,12 +172,14 @@ public final class CompassView extends AppCompatImageView implements Runnable, F final MapboxMap mapboxMap = this.mapboxMap.get(); final CompassView compassView = this.compassView.get(); if (mapboxMap != null && compassView != null) { - PointF focalPoint = compassView.getFocalPoint(); - if (focalPoint != null) { - mapboxMap.setFocalBearing(0, focalPoint.x, focalPoint.y, TIME_MAP_NORTH_ANIMATION); - } else { - mapboxMap.setFocalBearing(0, mapboxMap.getWidth() / 2, mapboxMap.getHeight() / 2, TIME_MAP_NORTH_ANIMATION); - } + //PointF focalPoint = compassView.getFocalPoint(); + //if (focalPoint != null) { + //mapboxMap.setFocalBearing(0, focalPoint.x, focalPoint.y, TIME_MAP_NORTH_ANIMATION); + //} else { + //mapboxMap.setFocalBearing(0, mapboxMap.getWidth() / 2, mapboxMap.getHeight() / 2, TIME_MAP_NORTH_ANIMATION); + //} + // FIXME: 10/02/2017 with animation and focal point + mapboxMap.resetNorth(); compassView.postDelayed(compassView, TIME_WAIT_IDLE + TIME_MAP_NORTH_ANIMATION); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java index c9888c36d2..fdc9f2dcb0 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java @@ -32,6 +32,7 @@ import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.location.LocationSource; +import com.mapbox.mapboxsdk.maps.Callback; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.Projection; import com.mapbox.services.android.telemetry.location.LocationEngine; @@ -60,6 +61,7 @@ public class MyLocationView extends View { private Location location; private long locationUpdateTimestamp; private float previousDirection; + private float metersPerPixel; private float accuracy; private Paint accuracyPaint; @@ -256,7 +258,6 @@ public class MyLocationView extends View { } final PointF pointF = screenLocation; - float metersPerPixel = (float) projection.getMetersPerPixelAtLatitude(location.getLatitude()); float accuracyPixels = (Float) accuracyAnimator.getAnimatedValue() / metersPerPixel / 2; float maxRadius = getWidth() / 2; accuracyPixels = accuracyPixels <= maxRadius ? accuracyPixels : maxRadius; @@ -333,6 +334,15 @@ public class MyLocationView extends View { setBearing(position.bearing); setTilt(position.tilt); } + + if (location != null) { + projection.getMetersPerPixelAtLatitude(location.getLatitude(), new Callback<Double>() { + @Override + public void onResult(Double aDouble) { + metersPerPixel = (float) aDouble.doubleValue(); + } + }); + } } public void onStart() { @@ -837,9 +847,14 @@ public class MyLocationView extends View { @Override void invalidate() { if (latLng != null) { - screenLocation = projection.toScreenLocation(latLng); + projection.toScreenLocation(latLng, new Callback<PointF>() { + @Override + public void onResult(PointF pointF) { + screenLocation = pointF; + MyLocationView.this.invalidate(); + } + }); } - MyLocationView.this.invalidate(); } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java index e9d823ebda..81ae044777 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java @@ -21,7 +21,7 @@ public class MyLocationViewSettings { private FocalPointChangeListener focalPointChangeListener; // - // State + // MapThreadExecutor // private boolean enabled; |