diff --git a/.eslintignore b/.eslintignore index 46e6b1d6f..371793efb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,5 @@ -/plugin/build -/example -/lib \ No newline at end of file +plugin +example +lib +__tests__ +ios/RCTMGL/index.d.ts \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 859bbf885..a2c067fa3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { root: true, - parser: 'babel-eslint', + parser: '@babel/eslint-parser', plugins: ['react', 'react-native', 'fp', 'import'], env: { jest: true, diff --git a/android/rctmgl/src/main/java-mapboxgl/common/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.java b/android/rctmgl/src/main/java-mapboxgl/common/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.java index 37a166272..9a0c8dd1c 100644 --- a/android/rctmgl/src/main/java-mapboxgl/common/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.java +++ b/android/rctmgl/src/main/java-mapboxgl/common/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.java @@ -95,6 +95,10 @@ public String getString(String key) { return mPayload.getString(key); } + public String getEnumName() { + return mPayload.getString("value").toUpperCase().replaceAll("-","_"); + } + public Double getDouble(String key) { return mPayload.getDouble(key); } diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.kt index 74273bf80..d9c97691f 100644 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.kt +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapView.kt @@ -1,30 +1,34 @@ package com.mapbox.rctmgl.components.mapview import android.content.Context -import com.mapbox.maps.extension.style.layers.getLayer -import com.mapbox.maps.plugin.annotation.generated.PointAnnotationManager -import com.mapbox.maps.plugin.annotation.AnnotationPlugin -import com.mapbox.maps.plugin.annotation.generated.PointAnnotation -import com.mapbox.android.gestures.MoveGestureDetector -import com.mapbox.rctmgl.components.styles.terrain.RCTMGLTerrain -import com.mapbox.maps.plugin.delegates.listeners.OnMapLoadErrorListener -import com.mapbox.maps.extension.observable.eventdata.MapLoadingErrorEventData -import android.graphics.PointF -import com.facebook.react.bridge.WritableMap -import com.facebook.react.bridge.WritableNativeMap import android.graphics.BitmapFactory +import android.graphics.PointF import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableNativeArray +import com.facebook.react.bridge.WritableNativeMap +import com.mapbox.android.gestures.MoveGestureDetector import com.mapbox.geojson.Feature import com.mapbox.geojson.Point import com.mapbox.maps.* -import com.mapbox.maps.plugin.delegates.listeners.OnMapLoadedListener +import com.mapbox.maps.extension.observable.eventdata.MapLoadingErrorEventData import com.mapbox.maps.extension.style.layers.Layer +import com.mapbox.maps.extension.style.layers.generated.* +import com.mapbox.maps.extension.style.layers.getLayer +import com.mapbox.maps.extension.style.utils.unwrap +import com.mapbox.maps.extension.style.layers.properties.generated.Visibility import com.mapbox.maps.plugin.annotation.annotations import com.mapbox.maps.plugin.annotation.generated.OnPointAnnotationClickListener +import com.mapbox.maps.plugin.annotation.generated.PointAnnotation +import com.mapbox.maps.plugin.annotation.generated.PointAnnotationManager import com.mapbox.maps.plugin.annotation.generated.createPointAnnotationManager -import com.mapbox.maps.plugin.compass.compass +import com.mapbox.maps.plugin.delegates.listeners.OnCameraChangeListener +import com.mapbox.maps.plugin.delegates.listeners.OnMapIdleListener +import com.mapbox.maps.plugin.delegates.listeners.OnMapLoadErrorListener +import com.mapbox.maps.plugin.delegates.listeners.OnMapLoadedListener import com.mapbox.maps.plugin.gestures.* import com.mapbox.rctmgl.R import com.mapbox.rctmgl.components.AbstractMapFeature @@ -38,6 +42,7 @@ import com.mapbox.rctmgl.components.mapview.helpers.CameraChangeTracker import com.mapbox.rctmgl.components.styles.layers.RCTLayer import com.mapbox.rctmgl.components.styles.light.RCTMGLLight import com.mapbox.rctmgl.components.styles.sources.RCTSource +import com.mapbox.rctmgl.components.styles.terrain.RCTMGLTerrain import com.mapbox.rctmgl.events.AndroidCallbackEvent import com.mapbox.rctmgl.events.IEvent import com.mapbox.rctmgl.events.MapChangeEvent @@ -46,11 +51,12 @@ import com.mapbox.rctmgl.events.constants.EventTypes import com.mapbox.rctmgl.utils.GeoJSONUtils import com.mapbox.rctmgl.utils.LatLng import com.mapbox.rctmgl.utils.Logger +import com.mapbox.rctmgl.utils.extensions.toReadableArray import org.json.JSONException import org.json.JSONObject -import java.lang.Exception import java.util.* + open class RCTMGLMapView(private val mContext: Context, var mManager: RCTMGLMapViewManager /*, MapboxMapOptions options*/) : MapView(mContext), OnMapClickListener { private val mSources: MutableMap> private val mImages: MutableList @@ -106,6 +112,14 @@ open class RCTMGLMapView(private val mContext: Context, var mManager: RCTMGLMapV }) val _this = this + map.addOnCameraChangeListener(OnCameraChangeListener { cameraChangedEventData -> + handleMapChangedEvent(EventTypes.REGION_IS_CHANGING) + }) + + map.addOnMapIdleListener(OnMapIdleListener { mapIdleEventData -> + sendRegionDidChangeEvent() + }) + val gesturesPlugin: GesturesPlugin = this.gestures gesturesPlugin.addOnMapClickListener(_this) gesturesPlugin.addOnMoveListener(object : OnMoveListener { @@ -620,6 +634,60 @@ open class RCTMGLMapView(private val mContext: Context, var mManager: RCTMGLMapV // } //} + // region Callbacks + + fun getCenter(callbackID: String?) { + var center = mMap!!.cameraState!!.center + val array: WritableArray = WritableNativeArray() + array.pushDouble(center.longitude()) + array.pushDouble(center.latitude()) + val payload: WritableMap = WritableNativeMap() + payload.putArray("center", array) + + val event = AndroidCallbackEvent(this, callbackID, payload) + mManager.handleEvent(event) + } + + fun getZoom(callbackID: String?) { + var zoom = mMap!!.cameraState!!.zoom + + val payload: WritableMap = WritableNativeMap() + payload.putDouble("zoom", zoom) + + val event = AndroidCallbackEvent(this, callbackID, payload) + mManager.handleEvent(event) + } + + private fun getDisplayDensity(): Float { + return mContext.resources.displayMetrics.density + } + + fun getCoordinateFromView(callbackID: String?, pixel: ScreenCoordinate) { + val density: Float = getDisplayDensity() + val screenCoordinate = ScreenCoordinate(pixel.x * density, pixel.y * density) + + val coordinate = mMap!!.coordinateForPixel(pixel) + + val payload: WritableMap = WritableNativeMap() + payload.putArray("coordinateFromView", coordinate.toReadableArray()) + + val event = AndroidCallbackEvent(this, callbackID, payload) + mManager.handleEvent(event) + } + + fun getPointInView(callbackID: String?, coordinate: Point) { + val point = mMap!!.pixelForCoordinate(coordinate) + + val array: WritableArray = WritableNativeArray() + array.pushDouble(point.x) + array.pushDouble(point.y) + val payload: WritableMap = WritableNativeMap() + payload.putArray("pointInView", array) + + val event = AndroidCallbackEvent(this, callbackID, payload) + mManager.handleEvent(event) + } + fun queryTerrainElevation(callbackID: String?, longitude: Double, latitude: Double) { val result = mMap?.getElevation(Point.fromLngLat(longitude, latitude)) val payload: WritableMap = WritableNativeMap() @@ -630,6 +698,51 @@ open class RCTMGLMapView(private val mContext: Context, var mManager: RCTMGLMapV } } + fun match(layer: Layer, sourceId:String, sourceLayerId: String?) : Boolean { + fun match(actSourceId: String, actSourceLayerId: String?) : Boolean { + return (actSourceId == sourceId && ((sourceLayerId == null) || (sourceLayerId == actSourceLayerId))) + } + return when (layer) { + is BackgroundLayer -> false + is LocationIndicatorLayer -> false + is SkyLayer -> false + is CircleLayer -> match(layer.sourceId, layer.sourceLayer) + is FillExtrusionLayer -> match(layer.sourceId, layer.sourceLayer) + is FillLayer -> match(layer.sourceId, layer.sourceLayer) + is HeatmapLayer -> match(layer.sourceId, layer.sourceLayer) + is HillshadeLayer -> match(layer.sourceId, layer.sourceLayer) + is LineLayer -> match(layer.sourceId, layer.sourceLayer) + is RasterLayer -> match(layer.sourceId, layer.sourceLayer) + is SymbolLayer -> match(layer.sourceId, layer.sourceLayer) + else -> { + logE("MapView", "Layer type: $layer.type unknown.") + false + } + } + } + + fun setSourceVisibility( + visible: Boolean, + sourceId: String, + sourceLayerId: String? + ) { + if (mMap == null) { + Logger.e("MapView", "setSourceVisibility, map is null") + return + } + val style = mMap!!.getStyle(); + style!!.styleLayers.forEach { + val layer = style.getLayer(it.id) + if ((layer != null) && match(layer, sourceId, sourceLayerId)) { + layer.visibility( + if (visible) Visibility.VISIBLE else Visibility.NONE + ) + } + } + } + + // endregion + companion object { const val LOG_TAG = "RCTMGLMapView" } diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.java b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.java deleted file mode 100644 index ac194ceb8..000000000 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.java +++ /dev/null @@ -1,366 +0,0 @@ -package com.mapbox.rctmgl.components.mapview; - -import android.util.Log; -import android.view.Gravity; -import android.view.View; - -import com.facebook.react.bridge.NativeArray; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.uimanager.LayoutShadowNode; -import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.annotations.ReactProp; -/* -import com.mapbox.mapboxsdk.gqeometry.LatLngBounds; -import com.mapbox.mapboxsdk.log.Logger; -import com.mapbox.mapboxsdk.maps.MapboxMap; - */ -import com.mapbox.maps.MapboxMap; - -import com.mapbox.rctmgl.components.AbstractEventEmitter; -import com.mapbox.rctmgl.components.annotation.RCTMGLPointAnnotation; -import com.mapbox.rctmgl.events.constants.EventKeys; -/* -import com.mapbox.rctmgl.events.constants.EventKeys; -import com.mapbox.rctmgl.utils.ConvertUtils; -import com.mapbox.rctmgl.utils.ExpressionParser; -import com.mapbox.rctmgl.utils.GeoJSONUtils; -import com.mapbox.geojson.FeatureCollection; -import com.mapbox.geojson.Point; - */ - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; -import java.util.concurrent.RunnableFuture; - -import javax.annotation.Nullable; - -import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread; - -public class RCTMGLMapViewManager extends AbstractEventEmitter { - public static final String LOG_TAG = "RCTMGLMapViewManager"; - public static final String REACT_CLASS = "RCTMGLMapView"; - - private Map mViews; - - public RCTMGLMapViewManager(ReactApplicationContext context) { - super(context); - mViews = new HashMap<>(); - } - - @Override - public String getName() { - return REACT_CLASS; - } - - @Override - public LayoutShadowNode createShadowNodeInstance() { - return new MapShadowNode(this); - } - - @Override - public Class getShadowNodeClass() { - return MapShadowNode.class; - } - - @Override - protected void onAfterUpdateTransaction(RCTMGLMapView mapView) { - super.onAfterUpdateTransaction(mapView); - - if (mapView.getMapboxMap() == null) { - mViews.put(mapView.getId(), mapView); - mapView.init(); - } - } - - @Override - public void addView(RCTMGLMapView mapView, View childView, int childPosition) { - mapView.addFeature(childView, childPosition); - } - - @Override - public int getChildCount(RCTMGLMapView mapView) { - return mapView.getFeatureCount(); - } - - @Override - public View getChildAt(RCTMGLMapView mapView, int index) { - return mapView.getFeatureAt(index); - } - - @Override - public void removeViewAt(RCTMGLMapView mapView, int index) { - mapView.removeFeature(index); - } - - @Override - protected RCTMGLMapView createViewInstance(ThemedReactContext themedReactContext) { - return new RCTMGLMapView(themedReactContext, this/*, null*/); - } - - @Override - public void onDropViewInstance(RCTMGLMapView mapView) { - int reactTag = mapView.getId(); - - if (mViews.containsKey(reactTag)) { - mViews.remove(reactTag); - } - - super.onDropViewInstance(mapView); - } - - public RCTMGLMapView getByReactTag(int reactTag) { - return mViews.get(reactTag); - } - - //region React Props - - @ReactProp(name="styleURL") - public void setStyleURL(RCTMGLMapView mapView, String styleURL) { - mapView.setReactStyleURL(styleURL); - } - - @ReactProp(name="preferredFramesPerSecond") - public void setPreferredFramesPerSecond(RCTMGLMapView mapView, int preferredFramesPerSecond) { - //mapView.setReactPreferredFramesPerSecond(preferredFramesPerSecond); - } - - @ReactProp(name="localizeLabels") - public void setLocalizeLabels(RCTMGLMapView mapView, boolean localizeLabels) { - //mapView.setLocalizeLabels(localizeLabels); - } - - @ReactProp(name="zoomEnabled") - public void setZoomEnabled(RCTMGLMapView mapView, boolean zoomEnabled) { - //mapView.setReactZoomEnabled(zoomEnabled); - } - - @ReactProp(name="scrollEnabled") - public void setScrollEnabled(RCTMGLMapView mapView, boolean scrollEnabled) { - //mapView.setReactScrollEnabled(scrollEnabled); - } - - @ReactProp(name="pitchEnabled") - public void setPitchEnabled(RCTMGLMapView mapView, boolean pitchEnabled) { - //mapView.setReactPitchEnabled(pitchEnabled); - } - - @ReactProp(name="rotateEnabled") - public void setRotateEnabled(RCTMGLMapView mapView, boolean rotateEnabled) { - //mapView.setReactRotateEnabled(rotateEnabled); - } - - @ReactProp(name="attributionEnabled") - public void setAttributionEnabled(RCTMGLMapView mapView, boolean attributionEnabled) { - //mapView.setReactAttributionEnabled(attributionEnabled); - } - - @ReactProp(name="attributionPosition") - public void setAttributionPosition(RCTMGLMapView mapView, @Nullable ReadableMap attributionPosition) { - //mapView.setReactAttributionPosition(attributionPosition); - } - - @ReactProp(name="logoEnabled") - public void setLogoEnabled(RCTMGLMapView mapView, boolean logoEnabled) { - //mapView.setReactLogoEnabled(logoEnabled); - } - - @ReactProp(name="logoPosition") - public void setLogoPosition(RCTMGLMapView mapView, ReadableMap logoPosition) { - //mapView.setReactLogoPosition(logoPosition); - } - - @ReactProp(name="compassEnabled") - public void setCompassEnabled(RCTMGLMapView mapView, boolean compassEnabled) { - //mapView.setReactCompassEnabled(compassEnabled); - } - - @ReactProp(name="compassViewMargins") - public void setCompassViewMargins(RCTMGLMapView mapView, ReadableMap compassViewMargins){ - //mapView.setReactCompassViewMargins(compassViewMargins); - } - - @ReactProp(name="compassViewPosition") - public void setCompassViewPosition(RCTMGLMapView mapView, int compassViewPosition) { - //mapView.setReactCompassViewPosition(compassViewPosition); - } - - @ReactProp(name="contentInset") - public void setContentInset(RCTMGLMapView mapView, ReadableArray array) { - //mapView.setReactContentInset(array); - } - - @ReactProp(name = "tintColor", customType = "Color") - public void setTintColor(RCTMGLMapView mapView, @Nullable Integer tintColor) { - //mapView.setTintColor(tintColor); - } - - //endregion - - //region Custom Events - - @Override - public Map customEvents() { - return MapBuilder.builder() - .put(EventKeys.MAP_CLICK, "onPress") - .put(EventKeys.MAP_LONG_CLICK,"onLongPress") - .put(EventKeys.MAP_ONCHANGE, "onMapChange") - .put(EventKeys.MAP_ON_LOCATION_CHANGE, "onLocationChange") - .put(EventKeys.MAP_USER_TRACKING_MODE_CHANGE, "onUserTrackingModeChange") - .put(EventKeys.MAP_ANDROID_CALLBACK, "onAndroidCallback") - .build(); - } - - //endregion - - //region React Methods - public static final int METHOD_QUERY_FEATURES_POINT = 2; - public static final int METHOD_QUERY_FEATURES_RECT = 3; - public static final int METHOD_VISIBLE_BOUNDS = 4; - public static final int METHOD_GET_POINT_IN_VIEW = 5; - public static final int METHOD_GET_COORDINATE_FROM_VIEW = 6; - public static final int METHOD_TAKE_SNAP = 7; - public static final int METHOD_GET_ZOOM = 8; - public static final int METHOD_GET_CENTER = 9; - public static final int METHOD_SET_HANDLED_MAP_EVENTS = 10; - public static final int METHOD_SHOW_ATTRIBUTION = 11; - public static final int METHOD_SET_SOURCE_VISIBILITY = 12; - public static final int METHOD_QUERY_TERRAIN_ELEVATION = 13; - - @Nullable - @Override - public Map getCommandsMap() { - return MapBuilder.builder() - .put("queryRenderedFeaturesAtPoint", METHOD_QUERY_FEATURES_POINT) - .put("queryRenderedFeaturesInRect", METHOD_QUERY_FEATURES_RECT) - .put("getVisibleBounds", METHOD_VISIBLE_BOUNDS) - .put("getPointInView", METHOD_GET_POINT_IN_VIEW) - .put("getCoordinateFromView", METHOD_GET_COORDINATE_FROM_VIEW) - .put("takeSnap", METHOD_TAKE_SNAP) - .put("getZoom", METHOD_GET_ZOOM) - .put("getCenter", METHOD_GET_CENTER) - .put( "setHandledMapChangedEvents", METHOD_SET_HANDLED_MAP_EVENTS) - .put("showAttribution", METHOD_SHOW_ATTRIBUTION) - .put("setSourceVisibility", METHOD_SET_SOURCE_VISIBILITY) - .put("queryTerrainElevation", METHOD_QUERY_TERRAIN_ELEVATION) - .build(); - } - - @Override - public void receiveCommand(RCTMGLMapView mapView, int commandID, @Nullable ReadableArray args) { - // allows method calls to work with componentDidMount - MapboxMap mapboxMap = mapView.getMapboxMap(); - if (mapboxMap == null) { -// mapView.enqueuePreRenderMapMethod(commandID, args); - return; - } - - switch (commandID) { - case METHOD_QUERY_TERRAIN_ELEVATION: - ReadableArray coords = args.getArray(1); - mapView.queryTerrainElevation(args.getString(0), coords.getDouble(0), coords.getDouble(1)); - } - /* - switch (commandID) { - case METHOD_QUERY_FEATURES_POINT: - mapView.queryRenderedFeaturesAtPoint( - args.getString(0), - ConvertUtils.toPointF(args.getArray(1)), - ExpressionParser.from(args.getArray(2)), - ConvertUtils.toStringList(args.getArray(3))); - break; - case METHOD_QUERY_FEATURES_RECT: - mapView.queryRenderedFeaturesInRect( - args.getString(0), - ConvertUtils.toRectF(args.getArray(1)), - ExpressionParser.from(args.getArray(2)), - ConvertUtils.toStringList(args.getArray(3))); - break; - case METHOD_VISIBLE_BOUNDS: - mapView.getVisibleBounds(args.getString(0)); - break; - case METHOD_GET_POINT_IN_VIEW: - mapView.getPointInView(args.getString(0), GeoJSONUtils.toLatLng(args.getArray(1))); - break; - case METHOD_GET_COORDINATE_FROM_VIEW: - mapView.getCoordinateFromView(args.getString(0), ConvertUtils.toPointF(args.getArray(1))); - break; - case METHOD_TAKE_SNAP: - mapView.takeSnap(args.getString(0), args.getBoolean(1)); - break; - case METHOD_GET_ZOOM: - mapView.getZoom(args.getString(0)); - break; - case METHOD_GET_CENTER: - mapView.getCenter(args.getString(0)); - break; - case METHOD_SET_HANDLED_MAP_EVENTS: - if(args != null) { - ArrayList eventsArray = new ArrayList<>(); - for (int i = 1; i < args.size(); i++) { - eventsArray.add(args.getString(i)); - } - mapView.setHandledMapChangedEvents(eventsArray); - } - break; - case METHOD_SHOW_ATTRIBUTION: - mapView.showAttribution(); - break; - case METHOD_SET_SOURCE_VISIBILITY: - mapView.setSourceVisibility( - args.getBoolean(1), - args.getString(2), - args.getString(3) - ); - - }*/ - } - - //endregion - - private static final class MapShadowNode extends LayoutShadowNode { - private RCTMGLMapViewManager mViewManager; - - public MapShadowNode(RCTMGLMapViewManager viewManager) { - mViewManager = viewManager; - } - - @Override - public void dispose() { - super.dispose(); - diposeNativeMapView(); - } - - /** - * We need this mapview to dispose (calls into nativeMap.destroy) before ReactNative starts tearing down the views in - * onDropViewInstance. - */ - private void diposeNativeMapView() { - final RCTMGLMapView mapView = mViewManager.getByReactTag(getReactTag()); - - if (mapView != null) - { - runOnUiThread(new Runnable() { - @Override - public void run() - { - try - { -// mapView.dispose(); - } - catch (Exception ex) - { - Log.e(LOG_TAG , " disposeNativeMapView() exception destroying map view", ex); - } - } - }); - } - } - } -} - diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.kt new file mode 100644 index 000000000..7cd930123 --- /dev/null +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/mapview/RCTMGLMapViewManager.kt @@ -0,0 +1,311 @@ +package com.mapbox.rctmgl.components.mapview + +import android.util.Log +import android.view.View + +import com.facebook.react.bridge.ReactApplicationContext +import com.mapbox.rctmgl.components.AbstractEventEmitter +import com.facebook.react.uimanager.LayoutShadowNode +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableArray +import com.mapbox.rctmgl.events.constants.EventKeys +import com.mapbox.maps.MapboxMap +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.common.MapBuilder +import com.mapbox.rctmgl.utils.GeoJSONUtils +import com.mapbox.rctmgl.utils.extensions.toCoordinate +import com.mapbox.rctmgl.utils.extensions.toScreenCoordinate +import java.lang.Exception +import java.util.HashMap + +open class RCTMGLMapViewManager(context: ReactApplicationContext?) : + AbstractEventEmitter(context) { + private val mViews: MutableMap + override fun getName(): String { + return REACT_CLASS + } + + override fun createShadowNodeInstance(): LayoutShadowNode { + return MapShadowNode(this) + } + + override fun getShadowNodeClass(): Class { + return MapShadowNode::class.java + } + + override fun onAfterUpdateTransaction(mapView: RCTMGLMapView) { + super.onAfterUpdateTransaction(mapView) + if (mapView.getMapboxMap() == null) { + mViews[mapView.id] = mapView + mapView.init() + } + } + + override fun addView(mapView: RCTMGLMapView?, childView: View?, childPosition: Int) { + mapView!!.addFeature(childView, childPosition) + } + + override fun getChildCount(mapView: RCTMGLMapView?): Int { + return mapView!!.featureCount + } + + override fun getChildAt(mapView: RCTMGLMapView?, index: Int): View? { + return mapView!!.getFeatureAt(index) + } + + override fun removeViewAt(mapView: RCTMGLMapView?, index: Int) { + mapView!!.removeFeature(index) + } + + override fun createViewInstance(themedReactContext: ThemedReactContext): RCTMGLMapView { + return RCTMGLMapView(themedReactContext, this /*, null*/) + } + + override fun onDropViewInstance(mapView: RCTMGLMapView) { + val reactTag = mapView.id + if (mViews.containsKey(reactTag)) { + mViews.remove(reactTag) + } + super.onDropViewInstance(mapView) + } + + fun getByReactTag(reactTag: Int): RCTMGLMapView? { + return mViews[reactTag] + } + + //region React Props + @ReactProp(name = "styleURL") + fun setStyleURL(mapView: RCTMGLMapView, styleURL: String?) { + mapView.setReactStyleURL(styleURL!!) + } + + @ReactProp(name = "preferredFramesPerSecond") + fun setPreferredFramesPerSecond(mapView: RCTMGLMapView?, preferredFramesPerSecond: Int) { + //mapView.setReactPreferredFramesPerSecond(preferredFramesPerSecond); + } + + @ReactProp(name = "localizeLabels") + fun setLocalizeLabels(mapView: RCTMGLMapView?, localizeLabels: Boolean) { + //mapView.setLocalizeLabels(localizeLabels); + } + + @ReactProp(name = "zoomEnabled") + fun setZoomEnabled(mapView: RCTMGLMapView?, zoomEnabled: Boolean) { + //mapView.setReactZoomEnabled(zoomEnabled); + } + + @ReactProp(name = "scrollEnabled") + fun setScrollEnabled(mapView: RCTMGLMapView?, scrollEnabled: Boolean) { + //mapView.setReactScrollEnabled(scrollEnabled); + } + + @ReactProp(name = "pitchEnabled") + fun setPitchEnabled(mapView: RCTMGLMapView?, pitchEnabled: Boolean) { + //mapView.setReactPitchEnabled(pitchEnabled); + } + + @ReactProp(name = "rotateEnabled") + fun setRotateEnabled(mapView: RCTMGLMapView?, rotateEnabled: Boolean) { + //mapView.setReactRotateEnabled(rotateEnabled); + } + + @ReactProp(name = "attributionEnabled") + fun setAttributionEnabled(mapView: RCTMGLMapView?, attributionEnabled: Boolean) { + //mapView.setReactAttributionEnabled(attributionEnabled); + } + + @ReactProp(name = "attributionPosition") + fun setAttributionPosition(mapView: RCTMGLMapView?, attributionPosition: ReadableMap?) { + //mapView.setReactAttributionPosition(attributionPosition); + } + + @ReactProp(name = "logoEnabled") + fun setLogoEnabled(mapView: RCTMGLMapView?, logoEnabled: Boolean) { + //mapView.setReactLogoEnabled(logoEnabled); + } + + @ReactProp(name = "logoPosition") + fun setLogoPosition(mapView: RCTMGLMapView?, logoPosition: ReadableMap?) { + //mapView.setReactLogoPosition(logoPosition); + } + + @ReactProp(name = "compassEnabled") + fun setCompassEnabled(mapView: RCTMGLMapView?, compassEnabled: Boolean) { + //mapView.setReactCompassEnabled(compassEnabled); + } + + @ReactProp(name = "compassViewMargins") + fun setCompassViewMargins(mapView: RCTMGLMapView?, compassViewMargins: ReadableMap?) { + //mapView.setReactCompassViewMargins(compassViewMargins); + } + + @ReactProp(name = "compassViewPosition") + fun setCompassViewPosition(mapView: RCTMGLMapView?, compassViewPosition: Int) { + //mapView.setReactCompassViewPosition(compassViewPosition); + } + + @ReactProp(name = "contentInset") + fun setContentInset(mapView: RCTMGLMapView?, array: ReadableArray?) { + //mapView.setReactContentInset(array); + } + + @ReactProp(name = "tintColor", customType = "Color") + fun setTintColor(mapView: RCTMGLMapView?, tintColor: Int?) { + //mapView.setTintColor(tintColor); + } + + //endregion + //region Custom Events + override fun customEvents(): Map? { + return MapBuilder.builder() + .put(EventKeys.MAP_CLICK, "onPress") + .put(EventKeys.MAP_LONG_CLICK, "onLongPress") + .put(EventKeys.MAP_ONCHANGE, "onMapChange") + .put(EventKeys.MAP_ON_LOCATION_CHANGE, "onLocationChange") + .put(EventKeys.MAP_USER_TRACKING_MODE_CHANGE, "onUserTrackingModeChange") + .put(EventKeys.MAP_ANDROID_CALLBACK, "onAndroidCallback") + .build() + } + + override fun getCommandsMap(): Map? { + return MapBuilder.builder() + .put("queryRenderedFeaturesAtPoint", METHOD_QUERY_FEATURES_POINT) + .put("queryRenderedFeaturesInRect", METHOD_QUERY_FEATURES_RECT) + .put("getVisibleBounds", METHOD_VISIBLE_BOUNDS) + .put("getPointInView", METHOD_GET_POINT_IN_VIEW) + .put("getCoordinateFromView", METHOD_GET_COORDINATE_FROM_VIEW) + .put("takeSnap", METHOD_TAKE_SNAP) + .put("getZoom", METHOD_GET_ZOOM) + .put("getCenter", METHOD_GET_CENTER) + .put("setHandledMapChangedEvents", METHOD_SET_HANDLED_MAP_EVENTS) + .put("showAttribution", METHOD_SHOW_ATTRIBUTION) + .put("setSourceVisibility", METHOD_SET_SOURCE_VISIBILITY) + .put("queryTerrainElevation", METHOD_QUERY_TERRAIN_ELEVATION) + .build() + } + + override fun receiveCommand(mapView: RCTMGLMapView, commandID: Int, args: ReadableArray?) { + // allows method calls to work with componentDidMount + val mapboxMap = mapView.getMapboxMap() + ?: // mapView.enqueuePreRenderMapMethod(commandID, args); + return + when (commandID) { + METHOD_QUERY_TERRAIN_ELEVATION -> { + val coords = args!!.getArray(1) + mapView.queryTerrainElevation( + args.getString(0), + coords.getDouble(0), + coords.getDouble(1) + ) + } + METHOD_GET_ZOOM -> { + mapView.getZoom(args!!.getString(0)); + } + METHOD_GET_CENTER -> { + mapView.getCenter(args!!.getString(0)); + } + METHOD_GET_POINT_IN_VIEW -> { + mapView.getPointInView(args!!.getString(0), args.getArray(1).toCoordinate()) + } + METHOD_GET_COORDINATE_FROM_VIEW -> { + mapView.getCoordinateFromView(args!!.getString(0), args.getArray(1).toScreenCoordinate()); + } + METHOD_SET_SOURCE_VISIBILITY -> { + mapView!!.setSourceVisibility( + args!!.getBoolean(1), + args!!.getString(2), + args!!.getString(3) + ); + } + } + /* + switch (commandID) { + case METHOD_QUERY_FEATURES_POINT: + mapView.queryRenderedFeaturesAtPoint( + args.getString(0), + ConvertUtils.toPointF(args.getArray(1)), + ExpressionParser.from(args.getArray(2)), + ConvertUtils.toStringList(args.getArray(3))); + break; + case METHOD_QUERY_FEATURES_RECT: + mapView.queryRenderedFeaturesInRect( + args.getString(0), + ConvertUtils.toRectF(args.getArray(1)), + ExpressionParser.from(args.getArray(2)), + ConvertUtils.toStringList(args.getArray(3))); + break; + case METHOD_VISIBLE_BOUNDS: + mapView.getVisibleBounds(args.getString(0)); + break; + case METHOD_TAKE_SNAP: + mapView.takeSnap(args.getString(0), args.getBoolean(1)); + break; + case METHOD_SET_HANDLED_MAP_EVENTS: + if(args != null) { + ArrayList eventsArray = new ArrayList<>(); + for (int i = 1; i < args.size(); i++) { + eventsArray.add(args.getString(i)); + } + mapView.setHandledMapChangedEvents(eventsArray); + } + break; + case METHOD_SHOW_ATTRIBUTION: + mapView.showAttribution(); + break; + + + }*/ + } + //endregion + + private class MapShadowNode(private val mViewManager: RCTMGLMapViewManager) : + LayoutShadowNode() { + override fun dispose() { + super.dispose() + diposeNativeMapView() + } + + /** + * We need this mapview to dispose (calls into nativeMap.destroy) before ReactNative starts tearing down the views in + * onDropViewInstance. + */ + private fun diposeNativeMapView() { + val mapView = mViewManager.getByReactTag(reactTag) + if (mapView != null) { + UiThreadUtil.runOnUiThread { + try { +// mapView.dispose(); + } catch (ex: Exception) { + Log.e(LOG_TAG, " disposeNativeMapView() exception destroying map view", ex) + } + } + } + } + } + + companion object { + const val LOG_TAG = "RCTMGLMapViewManager" + const val REACT_CLASS = "RCTMGLMapView" + + //endregion + //region React Methods + const val METHOD_QUERY_FEATURES_POINT = 2 + const val METHOD_QUERY_FEATURES_RECT = 3 + const val METHOD_VISIBLE_BOUNDS = 4 + const val METHOD_GET_POINT_IN_VIEW = 5 + const val METHOD_GET_COORDINATE_FROM_VIEW = 6 + const val METHOD_TAKE_SNAP = 7 + const val METHOD_GET_ZOOM = 8 + const val METHOD_GET_CENTER = 9 + const val METHOD_SET_HANDLED_MAP_EVENTS = 10 + const val METHOD_SHOW_ATTRIBUTION = 11 + const val METHOD_SET_SOURCE_VISIBILITY = 12 + const val METHOD_QUERY_TERRAIN_ELEVATION = 13 + } + + init { + mViews = HashMap() + } +} \ No newline at end of file diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleFactory.java b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleFactory.java index 785c6dbfe..704f25bca 100644 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleFactory.java +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleFactory.java @@ -830,7 +830,7 @@ public static void setFillSortKey(FillLayer layer, RCTMGLStyleValue styleValue) } public static void setVisibility(FillLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setFillAntialias(FillLayer layer, RCTMGLStyleValue styleValue) { @@ -909,7 +909,7 @@ public static void setFillTranslateAnchor(FillLayer layer, RCTMGLStyleValue styl if (styleValue.isExpression()) { layer.fillTranslateAnchor(styleValue.getExpression()); } else { - layer.fillTranslateAnchor(FillTranslateAnchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.fillTranslateAnchor(FillTranslateAnchor.valueOf(styleValue.getEnumName())); } } @@ -937,7 +937,7 @@ public static void setLineCap(LineLayer layer, RCTMGLStyleValue styleValue) { if (styleValue.isExpression()) { layer.lineCap(styleValue.getExpression()); } else { - layer.lineCap(LineCap.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.lineCap(LineCap.valueOf(styleValue.getEnumName())); } } @@ -945,7 +945,7 @@ public static void setLineJoin(LineLayer layer, RCTMGLStyleValue styleValue) { if (styleValue.isExpression()) { layer.lineJoin(styleValue.getExpression()); } else { - layer.lineJoin(LineJoin.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.lineJoin(LineJoin.valueOf(styleValue.getEnumName())); } } @@ -974,7 +974,7 @@ public static void setLineSortKey(LineLayer layer, RCTMGLStyleValue styleValue) } public static void setVisibility(LineLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setLineOpacity(LineLayer layer, RCTMGLStyleValue styleValue) { @@ -1029,7 +1029,7 @@ public static void setLineTranslateAnchor(LineLayer layer, RCTMGLStyleValue styl if (styleValue.isExpression()) { layer.lineTranslateAnchor(styleValue.getExpression()); } else { - layer.lineTranslateAnchor(LineTranslateAnchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.lineTranslateAnchor(LineTranslateAnchor.valueOf(styleValue.getEnumName())); } } @@ -1145,7 +1145,7 @@ public static void setSymbolPlacement(SymbolLayer layer, RCTMGLStyleValue styleV if (styleValue.isExpression()) { layer.symbolPlacement(styleValue.getExpression()); } else { - layer.symbolPlacement(SymbolPlacement.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.symbolPlacement(SymbolPlacement.valueOf(styleValue.getEnumName())); } } @@ -1177,7 +1177,7 @@ public static void setSymbolZOrder(SymbolLayer layer, RCTMGLStyleValue styleValu if (styleValue.isExpression()) { layer.symbolZOrder(styleValue.getExpression()); } else { - layer.symbolZOrder(SymbolZOrder.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.symbolZOrder(SymbolZOrder.valueOf(styleValue.getEnumName())); } } @@ -1209,7 +1209,7 @@ public static void setIconRotationAlignment(SymbolLayer layer, RCTMGLStyleValue if (styleValue.isExpression()) { layer.iconRotationAlignment(styleValue.getExpression()); } else { - layer.iconRotationAlignment(IconRotationAlignment.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.iconRotationAlignment(IconRotationAlignment.valueOf(styleValue.getEnumName())); } } @@ -1225,7 +1225,7 @@ public static void setIconTextFit(SymbolLayer layer, RCTMGLStyleValue styleValue if (styleValue.isExpression()) { layer.iconTextFit(styleValue.getExpression()); } else { - layer.iconTextFit(IconTextFit.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.iconTextFit(IconTextFit.valueOf(styleValue.getEnumName())); } } @@ -1285,7 +1285,7 @@ public static void setIconAnchor(SymbolLayer layer, RCTMGLStyleValue styleValue) if (styleValue.isExpression()) { layer.iconAnchor(styleValue.getExpression()); } else { - layer.iconAnchor(IconAnchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.iconAnchor(IconAnchor.valueOf(styleValue.getEnumName())); } } @@ -1293,7 +1293,7 @@ public static void setIconPitchAlignment(SymbolLayer layer, RCTMGLStyleValue sty if (styleValue.isExpression()) { layer.iconPitchAlignment(styleValue.getExpression()); } else { - layer.iconPitchAlignment(IconPitchAlignment.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.iconPitchAlignment(IconPitchAlignment.valueOf(styleValue.getEnumName())); } } @@ -1301,7 +1301,7 @@ public static void setTextPitchAlignment(SymbolLayer layer, RCTMGLStyleValue sty if (styleValue.isExpression()) { layer.textPitchAlignment(styleValue.getExpression()); } else { - layer.textPitchAlignment(TextPitchAlignment.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.textPitchAlignment(TextPitchAlignment.valueOf(styleValue.getEnumName())); } } @@ -1309,7 +1309,7 @@ public static void setTextRotationAlignment(SymbolLayer layer, RCTMGLStyleValue if (styleValue.isExpression()) { layer.textRotationAlignment(styleValue.getExpression()); } else { - layer.textRotationAlignment(TextRotationAlignment.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.textRotationAlignment(TextRotationAlignment.valueOf(styleValue.getEnumName())); } } @@ -1365,7 +1365,7 @@ public static void setTextJustify(SymbolLayer layer, RCTMGLStyleValue styleValue if (styleValue.isExpression()) { layer.textJustify(styleValue.getExpression()); } else { - layer.textJustify(TextJustify.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.textJustify(TextJustify.valueOf(styleValue.getEnumName())); } } @@ -1389,7 +1389,7 @@ public static void setTextAnchor(SymbolLayer layer, RCTMGLStyleValue styleValue) if (styleValue.isExpression()) { layer.textAnchor(styleValue.getExpression()); } else { - layer.textAnchor(TextAnchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.textAnchor(TextAnchor.valueOf(styleValue.getEnumName())); } } @@ -1437,7 +1437,7 @@ public static void setTextTransform(SymbolLayer layer, RCTMGLStyleValue styleVal if (styleValue.isExpression()) { layer.textTransform(styleValue.getExpression()); } else { - layer.textTransform(TextTransform.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.textTransform(TextTransform.valueOf(styleValue.getEnumName())); } } @@ -1474,7 +1474,7 @@ public static void setTextOptional(SymbolLayer layer, RCTMGLStyleValue styleValu } public static void setVisibility(SymbolLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setIconOpacity(SymbolLayer layer, RCTMGLStyleValue styleValue) { @@ -1577,7 +1577,7 @@ public static void setIconTranslateAnchor(SymbolLayer layer, RCTMGLStyleValue st if (styleValue.isExpression()) { layer.iconTranslateAnchor(styleValue.getExpression()); } else { - layer.iconTranslateAnchor(IconTranslateAnchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.iconTranslateAnchor(IconTranslateAnchor.valueOf(styleValue.getEnumName())); } } @@ -1681,7 +1681,7 @@ public static void setTextTranslateAnchor(SymbolLayer layer, RCTMGLStyleValue st if (styleValue.isExpression()) { layer.textTranslateAnchor(styleValue.getExpression()); } else { - layer.textTranslateAnchor(TextTranslateAnchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.textTranslateAnchor(TextTranslateAnchor.valueOf(styleValue.getEnumName())); } } @@ -1694,7 +1694,7 @@ public static void setCircleSortKey(CircleLayer layer, RCTMGLStyleValue styleVal } public static void setVisibility(CircleLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setCircleRadius(CircleLayer layer, RCTMGLStyleValue styleValue) { @@ -1781,7 +1781,7 @@ public static void setCircleTranslateAnchor(CircleLayer layer, RCTMGLStyleValue if (styleValue.isExpression()) { layer.circleTranslateAnchor(styleValue.getExpression()); } else { - layer.circleTranslateAnchor(CircleTranslateAnchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.circleTranslateAnchor(CircleTranslateAnchor.valueOf(styleValue.getEnumName())); } } @@ -1789,7 +1789,7 @@ public static void setCirclePitchScale(CircleLayer layer, RCTMGLStyleValue style if (styleValue.isExpression()) { layer.circlePitchScale(styleValue.getExpression()); } else { - layer.circlePitchScale(CirclePitchScale.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.circlePitchScale(CirclePitchScale.valueOf(styleValue.getEnumName())); } } @@ -1797,7 +1797,7 @@ public static void setCirclePitchAlignment(CircleLayer layer, RCTMGLStyleValue s if (styleValue.isExpression()) { layer.circlePitchAlignment(styleValue.getExpression()); } else { - layer.circlePitchAlignment(CirclePitchAlignment.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.circlePitchAlignment(CirclePitchAlignment.valueOf(styleValue.getEnumName())); } } @@ -1850,7 +1850,7 @@ public static void setCircleStrokeOpacityTransition(CircleLayer layer, RCTMGLSty } public static void setVisibility(HeatmapLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setHeatmapRadius(HeatmapLayer layer, RCTMGLStyleValue styleValue) { @@ -1918,7 +1918,7 @@ public static void setHeatmapOpacityTransition(HeatmapLayer layer, RCTMGLStyleVa } public static void setVisibility(FillExtrusionLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setFillExtrusionOpacity(FillExtrusionLayer layer, RCTMGLStyleValue styleValue) { @@ -1973,7 +1973,7 @@ public static void setFillExtrusionTranslateAnchor(FillExtrusionLayer layer, RCT if (styleValue.isExpression()) { layer.fillExtrusionTranslateAnchor(styleValue.getExpression()); } else { - layer.fillExtrusionTranslateAnchor(FillExtrusionTranslateAnchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.fillExtrusionTranslateAnchor(FillExtrusionTranslateAnchor.valueOf(styleValue.getEnumName())); } } @@ -2038,7 +2038,7 @@ public static void setFillExtrusionVerticalGradient(FillExtrusionLayer layer, RC } public static void setVisibility(RasterLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setRasterOpacity(RasterLayer layer, RCTMGLStyleValue styleValue) { @@ -2141,7 +2141,7 @@ public static void setRasterResampling(RasterLayer layer, RCTMGLStyleValue style if (styleValue.isExpression()) { layer.rasterResampling(styleValue.getExpression()); } else { - layer.rasterResampling(RasterResampling.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.rasterResampling(RasterResampling.valueOf(styleValue.getEnumName())); } } @@ -2154,7 +2154,7 @@ public static void setRasterFadeDuration(RasterLayer layer, RCTMGLStyleValue sty } public static void setVisibility(HillshadeLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setHillshadeIlluminationDirection(HillshadeLayer layer, RCTMGLStyleValue styleValue) { @@ -2169,7 +2169,7 @@ public static void setHillshadeIlluminationAnchor(HillshadeLayer layer, RCTMGLSt if (styleValue.isExpression()) { layer.hillshadeIlluminationAnchor(styleValue.getExpression()); } else { - layer.hillshadeIlluminationAnchor(HillshadeIlluminationAnchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.hillshadeIlluminationAnchor(HillshadeIlluminationAnchor.valueOf(styleValue.getEnumName())); } } @@ -2238,7 +2238,7 @@ public static void setHillshadeAccentColorTransition(HillshadeLayer layer, RCTMG } public static void setVisibility(BackgroundLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setBackgroundColor(BackgroundLayer layer, RCTMGLStyleValue styleValue) { @@ -2294,14 +2294,14 @@ public static void setBackgroundOpacityTransition(BackgroundLayer layer, RCTMGLS } public static void setVisibility(SkyLayer layer, RCTMGLStyleValue styleValue) { - layer.visibility(Visibility.valueOf(styleValue.getString(VALUE_KEY))); + layer.visibility(Visibility.valueOf(styleValue.getEnumName())); } public static void setSkyType(SkyLayer layer, RCTMGLStyleValue styleValue) { if (styleValue.isExpression()) { layer.skyType(styleValue.getExpression()); } else { - layer.skyType(SkyType.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.skyType(SkyType.valueOf(styleValue.getEnumName())); } } @@ -2381,7 +2381,7 @@ public static void setAnchor(Light layer, RCTMGLStyleValue styleValue) { if (styleValue.isExpression()) { layer.anchor(styleValue.getExpression()); } else { - layer.anchor(Anchor.valueOf(styleValue.getString(VALUE_KEY).toUpperCase())); + layer.anchor(Anchor.valueOf(styleValue.getEnumName())); } } @@ -2389,7 +2389,7 @@ public static void setPosition(Light layer, RCTMGLStyleValue styleValue) { if (styleValue.isExpression()) { layer.position(styleValue.getExpression()); } else { - layer.position(styleValue.getFloatArrayExpression(VALUE_KEY)); + layer.position(styleValue.getLightPosition()); } } diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.java b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.java deleted file mode 100644 index 5e9afa12e..000000000 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.java +++ /dev/null @@ -1,247 +0,0 @@ -package com.mapbox.rctmgl.components.styles; - -import androidx.annotation.NonNull; - -import com.facebook.react.bridge.Dynamic; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableType; -import com.facebook.react.bridge.WritableNativeMap; -import com.google.gson.JsonArray; -import com.mapbox.maps.extension.style.expressions.generated.Expression; -import com.mapbox.maps.extension.style.types.StyleTransition; -import com.mapbox.rctmgl.utils.ConvertUtils; -import com.mapbox.rctmgl.utils.ExpressionParser; -import com.mapbox.rctmgl.utils.ImageEntry; - -import java.util.ArrayList; -import java.util.List; - -public class RCTMGLStyleValue { - - private String mType; - private boolean isExpression; - private Expression mExpression; - private ReadableMap mPayload; - - private String imageURI = ""; - private boolean isAddImage; - private Double imageScale = ImageEntry.defaultScale; - - public static final int InterpolationModeExponential = 100; - public static final int InterpolationModeInterval = 101; - public static final int InterpolationModeCategorical = 102; - public static final int InterpolationModeIdentity = 103; - - public RCTMGLStyleValue(@NonNull ReadableMap config) { - mType = config.getString("styletype"); - mPayload = config.getMap("stylevalue"); - - if ("image".equals(mType)) { - imageScale = ImageEntry.defaultScale; - if ("hashmap".equals(mPayload.getString("type"))) { - ReadableMap map = getMap(); - imageURI = map.getMap("uri").getString("value"); - if (map.getMap("scale") != null) { - imageScale = map.getMap("scale").getDouble("value"); - } - } else if ("string".equals(mPayload.getString("type"))) { - String value = mPayload.getString("value"); - if (value.contains("://")) { - imageURI = value; - } else { - imageURI = null; - isExpression = true; - mExpression = Expression.literal(value); - } - } else { - imageURI = null; - } - isAddImage = imageURI != null; - if (isAddImage) { return; } - } - - Dynamic dynamic = mPayload.getDynamic("value"); - if (dynamic.getType().equals(ReadableType.Array)) { - ReadableArray array = dynamic.asArray(); - if (array.size() > 0 && mPayload.getString("type").equals("array")) { - ReadableMap map = array.getMap(0); - if (map != null && map.getString("type").equals("string")) { - isExpression = true; - mExpression = ExpressionParser.fromTyped(mPayload); - } - } - } - } - - private boolean isTokenizedValue(String value) { - return (value.startsWith("{") && value.endsWith("}")); - } - - public String getType() { - return mType; - } - - public boolean isFunction() { - return mType.equals("function"); - } - - public int getInt(String key) { - return mPayload.getInt(key); - } - - public Expression getIntExpression(String key) { - return Expression.literal(mPayload.getInt(key)); - } - - public String getString(String key) { - return mPayload.getString(key); - } - - public Double getDouble(String key) { - return mPayload.getDouble(key); - } - - public Float getFloat(String key) { - return getDouble(key).floatValue(); - } - - public Dynamic getDynamic(String key) { - return mPayload.getDynamic(key); - } - - public ReadableArray getArray(String key) { - return mPayload.getArray(key); - } - - public Boolean getBoolean(String key) { - return mPayload.getBoolean(key); - } - - /* - public Float[] getFloatArray(String key) { - ReadableArray arr = getArray(key); - - Float[] floatArr = new Float[arr.size()]; - for (int i = 0; i < arr.size(); i++) { - ReadableMap item = arr.getMap(i); - floatArr[i] = (float) item.getDouble("value"); - } - - return floatArr; - } - */ - public List getFloatArray(String key) { - ReadableArray arr = getArray(key); - - ArrayList result = new ArrayList(arr.size()); - for (int i = 0; i < arr.size(); i++) { - ReadableMap item = arr.getMap(i); - result.add(item.getDouble("value")); - } - - - return result; - } - -/* - public String[] getStringArray(String key) { - ReadableArray arr = getArray(key); - - String[] stringArr = new String[arr.size()]; - for (int i = 0; i < arr.size(); i++) { - ReadableMap item = arr.getMap(i); - stringArr[i] = item.getString("value"); - } - - return stringArr; - } */ - - public List getStringArray(String key) { - ReadableArray arr = getArray(key); - - ArrayList result = new ArrayList(arr.size()); - for (int i = 0; i < arr.size(); i++) { - ReadableMap item = arr.getMap(i); - result.add(item.getString("value")); - } - - return result; - } - - public ReadableMap getMap() { - if ("hashmap".equals(mPayload.getString("type"))) { - ReadableArray keyValues = mPayload.getArray("value"); - WritableNativeMap result = new WritableNativeMap(); - for (int i = 0; i < keyValues.size(); i++) { - ReadableArray keyValue = keyValues.getArray(i); - String stringKey = keyValue.getMap(0).getString("value"); - WritableNativeMap value = new WritableNativeMap(); - value.merge(keyValue.getMap(1)); - result.putMap(stringKey, value); - } - return result; - } - - return null; - } - - public ReadableMap getMap(String _key) { - return getMap(); - } - - public Expression getExpression() { - return mExpression; - } - - public Expression getFloatArrayExpression(String key) { - return new Expression(this.getFloatArray(key)); - } - - public boolean isExpression() { - return isExpression; - } - - public boolean shouldAddImage() { - return isAddImage; - } - - public Boolean isImageStringValue() { - return "string".equals(mPayload.getString("type")); - } - - public String getImageStringValue() { - return mPayload.getString("value"); - } - - public String getImageURI() { - return imageURI; - } - - public double getImageScale() { - return imageScale; - } - - - public StyleTransition getTransition() { - if (!mType.equals("transition")) { - return null; - } - ReadableMap config = getMap(RCTMGLStyleFactory.VALUE_KEY); - - boolean enablePlacementTransitions = true; - if (config.hasKey("enablePlacementTransitions")) { - enablePlacementTransitions = config.getMap("enablePlacementTransitions").getBoolean("value"); - } - int duration = 300; - int delay = 0; - if (config.hasKey("duration") && ReadableType.Map.equals(config.getType("duration"))) { - duration = config.getMap("duration").getInt("value"); - } - if (config.hasKey("delay") && ReadableType.Map.equals(config.getType("delay"))) { - duration = config.getMap("delay").getInt("value"); - } - - return new StyleTransition.Builder().duration(duration).delay(delay).build(); - } -} diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.kt new file mode 100644 index 000000000..b88b81f94 --- /dev/null +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/RCTMGLStyleValue.kt @@ -0,0 +1,223 @@ +package com.mapbox.rctmgl.components.styles + +import com.facebook.react.bridge.* +import com.mapbox.bindgen.Value +import com.mapbox.maps.extension.style.types.StyleTransition.Builder +import com.mapbox.rctmgl.utils.ImageEntry +import com.mapbox.maps.extension.style.types.StyleTransition +import com.mapbox.rctmgl.components.styles.RCTMGLStyleFactory +import com.mapbox.maps.extension.style.expressions.generated.Expression +import com.mapbox.maps.extension.style.light.LightPosition +import com.mapbox.rctmgl.utils.ExpressionParser +import java.util.ArrayList + +class RCTMGLStyleValue(config: ReadableMap) { + val type: String? + private var isExpression = false + private var mExpression: Expression? = null + private val mPayload: ReadableMap? + var imageURI: String? = "" + private var isAddImage = false + var imageScale = ImageEntry.defaultScale + private fun isTokenizedValue(value: String): Boolean { + return value.startsWith("{") && value.endsWith("}") + } + + val isFunction: Boolean + get() = type == "function" + + fun getInt(key: String?): Int { + return mPayload!!.getInt(key!!) + } + + fun getIntExpression(key: String?): Expression { + return Expression.literal(mPayload!!.getInt(key!!).toLong()) + } + + fun getString(key: String?): String? { + return mPayload!!.getString(key!!) + } + + fun getEnumName(): String { + return mPayload!!.getString("value")!!.toUpperCase().replace("-", "_") + } + + fun getDouble(key: String?): Double { + return mPayload!!.getDouble(key!!) + } + + fun getFloat(key: String?): Float { + return getDouble(key).toFloat() + } + + fun getDynamic(key: String?): Dynamic { + return mPayload!!.getDynamic(key!!) + } + + fun getArray(key: String?): ReadableArray? { + return mPayload!!.getArray(key!!) + } + + fun getBoolean(key: String?): Boolean { + return mPayload!!.getBoolean(key!!) + } + + /* + public Float[] getFloatArray(String key) { + ReadableArray arr = getArray(key); + + Float[] floatArr = new Float[arr.size()]; + for (int i = 0; i < arr.size(); i++) { + ReadableMap item = arr.getMap(i); + floatArr[i] = (float) item.getDouble("value"); + } + + return floatArr; + } + */ + fun getFloatArray(key: String?): List { + val arr = getArray(key) + val result = ArrayList(arr!!.size()) + for (i in 0 until arr.size()) { + val item = arr.getMap(i) + result.add(item.getDouble("value")) + } + return result + } + + /* + public String[] getStringArray(String key) { + ReadableArray arr = getArray(key); + + String[] stringArr = new String[arr.size()]; + for (int i = 0; i < arr.size(); i++) { + ReadableMap item = arr.getMap(i); + stringArr[i] = item.getString("value"); + } + + return stringArr; + } */ + fun getStringArray(key: String?): List { + val arr = getArray(key) + val result = ArrayList(arr!!.size()) + for (i in 0 until arr.size()) { + val item = arr.getMap(i) + result.add(item.getString("value")) + } + return result + } + + val map: ReadableMap? + get() { + if ("hashmap" == mPayload!!.getString("type")) { + val keyValues = mPayload.getArray("value") + val result = WritableNativeMap() + for (i in 0 until keyValues!!.size()) { + val keyValue = keyValues.getArray(i) + val stringKey = keyValue.getMap(0).getString("value") + val value = WritableNativeMap() + value.merge(keyValue.getMap(1)) + result.putMap(stringKey!!, value) + } + return result + } + return null + } + + fun getMap(_key: String?): ReadableMap? { + return map + } + + fun getExpression(): Expression? { + return mExpression + } + + fun getLightPosition(): LightPosition { + return LightPosition.fromList(getFloatArray("value")) + } + + fun isExpression(): Boolean { + return isExpression + } + + fun shouldAddImage(): Boolean { + return isAddImage + } + + val isImageStringValue: Boolean + get() = "string" == mPayload!!.getString("type") + + fun getImageStringValue(): String? { + return mPayload!!.getString("value") + } + + val transition: StyleTransition? + get() { + if (type != "transition") { + return null + } + val config = getMap(RCTMGLStyleFactory.VALUE_KEY) + var enablePlacementTransitions = true + if (config!!.hasKey("enablePlacementTransitions")) { + enablePlacementTransitions = + config.getMap("enablePlacementTransitions")!!.getBoolean("value") + } + var duration = 300 + val delay = 0 + if (config.hasKey("duration") && ReadableType.Map == config.getType("duration")) { + duration = config.getMap("duration")!!.getInt("value") + } + if (config.hasKey("delay") && ReadableType.Map == config.getType("delay")) { + duration = config.getMap("delay")!!.getInt("value") + } + return Builder().duration(duration.toLong()).delay(delay.toLong()).build() + } + + companion object { + const val InterpolationModeExponential = 100 + const val InterpolationModeInterval = 101 + const val InterpolationModeCategorical = 102 + const val InterpolationModeIdentity = 103 + } + + init { + type = config.getString("styletype") + mPayload = config.getMap("stylevalue") + var isAddImage = false + if ("image" == type) { + imageScale = ImageEntry.defaultScale + if ("hashmap" == mPayload!!.getString("type")) { + val map = map + imageURI = map!!.getMap("uri")!!.getString("value") + if (map.getMap("scale") != null) { + imageScale = map.getMap("scale")!!.getDouble("value") + } + } else if ("string" == mPayload.getString("type")) { + val value = mPayload.getString("value") + if (value!!.contains("://")) { + imageURI = value + } else { + imageURI = null + isExpression = true + mExpression = Expression.literal(value) + } + } else { + imageURI = null + } + isAddImage = imageURI != null + } + if (!isAddImage) { + val dynamic = mPayload!!.getDynamic("value") + if (dynamic.type == ReadableType.Array) { + val array = dynamic.asArray() + if (array.size() > 0 && mPayload.getString("type") == "array") { + val map = array.getMap(0) + if (map != null && map.getString("type") == "string") { + isExpression = true + mExpression = ExpressionParser.fromTyped(mPayload) + } + } + } + } + } +} \ No newline at end of file diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.java b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.java deleted file mode 100644 index 2c67f93f6..000000000 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.java +++ /dev/null @@ -1,319 +0,0 @@ -package com.mapbox.rctmgl.components.styles.sources; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.Size; -import androidx.core.content.res.ResourcesCompat; - -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.WritableNativeMap; -import com.mapbox.bindgen.Expected; -import com.mapbox.bindgen.None; -import com.mapbox.bindgen.Value; -import com.mapbox.geojson.Feature; -import com.mapbox.geojson.FeatureCollection; -import com.mapbox.maps.FeatureExtensionValue; -import com.mapbox.maps.MapboxMap; -import com.mapbox.maps.QueriedFeature; -import com.mapbox.maps.QueryFeatureExtensionCallback; -import com.mapbox.maps.QueryFeaturesCallback; -import com.mapbox.maps.SourceQueryOptions; -import com.mapbox.maps.Style; - -import com.mapbox.maps.extension.style.expressions.generated.Expression; -// import com.mapbox.rctmgl.R; -import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource; -import com.mapbox.maps.plugin.delegates.MapFeatureQueryDelegate; -import com.mapbox.rctmgl.components.mapview.RCTMGLMapView; -import com.mapbox.rctmgl.events.AndroidCallbackEvent; -import com.mapbox.rctmgl.events.FeatureClickEvent; -// import com.mapbox.rctmgl.utils.DownloadMapImageTask; -import com.mapbox.rctmgl.utils.ImageEntry; -import com.mapbox.rctmgl.utils.Logger; - -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class RCTMGLShapeSource extends RCTSource { - private URL mURL; - private RCTMGLShapeSourceManager mManager; - - private String mShape; - - private Boolean mCluster; - private Integer mClusterRadius; - private Integer mClusterMaxZoom; - - private Integer mMaxZoom; - private Integer mBuffer; - private Double mTolerance; - private Boolean mLineMetrics; - - private static Bitmap mImagePlaceholder; - private List> mImages; - private List> mNativeImages; - - public RCTMGLShapeSource(Context context, RCTMGLShapeSourceManager manager) { - super(context); - mManager = manager; - } - - @Override - public void addToMap(final RCTMGLMapView mapView) { - // Wait for style before adding the source to the map - mapView.getMapboxMap().getStyle(new Style.OnStyleLoaded() { - @Override - public void onStyleLoaded(@NonNull Style style) { - MapboxMap map = mapView.getMapboxMap(); - RCTMGLShapeSource.super.addToMap(mapView); - } - }); - } - - @Override - public GeoJsonSource makeSource() { - - GeoJsonSource.Builder builder = new GeoJsonSource.Builder(getID()); - getOptions(builder); - - if (mShape != null) { - builder.data(mShape); - } else { - builder.data(mURL.toString()); - } - - return builder.build(); - } - - public void setURL(URL url) { - mURL = url; - - if (mSource != null && mMapView != null && !mMapView.isDestroyed() ) { - mSource.data(mURL.toString()); - } - } - - public void setShape(String geoJSONStr) { - mShape = geoJSONStr; - - if (mSource != null && mMapView != null && !mMapView.isDestroyed() ) { - mSource.data(mShape); - - Expected result = mMap.getStyle().setStyleSourceProperty(getID(), "data", Value.valueOf(mShape)); - - } - } - - public void setCluster(boolean cluster) { - mCluster = cluster; - } - - public void setClusterRadius(int clusterRadius) { - mClusterRadius = clusterRadius; - } - - public void setClusterMaxZoom(int clusterMaxZoom) { - mClusterMaxZoom = clusterMaxZoom; - } - - public void setMaxZoom(int maxZoom) { - mMaxZoom = maxZoom; - } - - public void setBuffer(int buffer) { - mBuffer = buffer; - } - - public void setTolerance(double tolerance) { - mTolerance = tolerance; - } - - public void setLineMetrics(boolean lineMetrics) { - mLineMetrics = lineMetrics; - } - - public void onPress(OnPressEvent event) { - mManager.handleEvent(FeatureClickEvent.makeShapeSourceEvent(this, event)); - } - - private void getOptions(GeoJsonSource.Builder builder) { - if (mCluster != null) { - builder.cluster(mCluster); - } - - if (mClusterRadius != null) { - builder.clusterRadius(mClusterRadius); - } - - if (mClusterMaxZoom != null) { - builder.clusterMaxZoom(mClusterMaxZoom); - } - - if (mMaxZoom != null) { - builder.maxzoom(mMaxZoom); - } - - if (mBuffer != null) { - builder.buffer(mBuffer); - } - - if (mTolerance != null) { - builder.tolerance(mTolerance.floatValue()); - } - - if (mLineMetrics != null) { - builder.lineMetrics(mLineMetrics); - } - } - - public void querySourceFeatures(String callbackID, - @Nullable Expression filter) { - if (mSource == null) { - WritableMap payload = new WritableNativeMap(); - payload.putString("error", "source is not yet loaded"); - AndroidCallbackEvent event = new AndroidCallbackEvent(this, callbackID, payload); - mManager.handleEvent(event); - return; - } - - RCTMGLShapeSource _this = this; - mMap.querySourceFeatures(getID(), new SourceQueryOptions( - null, // v10todo - filter - ),new QueryFeaturesCallback() { - - @Override - public void run(@NonNull Expected> features) { - if (features.isError()) { - Logger.e("RCTMGLShapeSource", String.format("Error: %s", features.getError())); - } else { - WritableMap payload = new WritableNativeMap(); - - List result = new ArrayList<>(features.getValue().size()); - for (QueriedFeature i : features.getValue()) { - result.add(i.getFeature()); - } - - payload.putString("data", FeatureCollection.fromFeatures(result).toJson()); - - AndroidCallbackEvent event = new AndroidCallbackEvent(_this, callbackID, payload); - mManager.handleEvent(event); - } - } - }); - } - - private void callbackError(String callbackID, String error, String where) { - WritableMap payload = new WritableNativeMap(); - payload.putString("error",where + ": " + error); - AndroidCallbackEvent event = new AndroidCallbackEvent(this, callbackID, payload); - mManager.handleEvent(event); - } - - public void getClusterExpansionZoom(String callbackID, int clusterId) { - if (mSource == null) { - WritableMap payload = new WritableNativeMap(); - payload.putString("error", "source is not yet loaded"); - AndroidCallbackEvent event = new AndroidCallbackEvent(this, callbackID, payload); - mManager.handleEvent(event); - return; - } - - SourceQueryOptions options = new SourceQueryOptions(null, - Expression.eq(Expression.get("cluster_id"), Expression.literal(clusterId)) - ); - - RCTMGLShapeSource _this = this; - - mMap.querySourceFeatures( - getID(), - options, - - new QueryFeaturesCallback() { - @Override - public void run(@NonNull Expected> features) { - if (features.isValue()) { - QueriedFeature cluster = features.getValue().get(0); - mMap.queryFeatureExtensions(getID(), - cluster.getFeature(), - "supercluster", - "expansion-zoom", - null, - new QueryFeatureExtensionCallback() { - @Override - public void run(@NonNull Expected extension) { - if (extension.isValue()) { - Object contents = extension.getValue().getValue().getContents(); - if (contents instanceof Long) { - WritableMap payload = new WritableNativeMap(); - payload.putInt("data", ((Long)contents).intValue()); - - AndroidCallbackEvent event = new AndroidCallbackEvent(_this, callbackID, payload); - mManager.handleEvent(event); - return; - } else { - callbackError(callbackID, "Not a number", "getClusterExpansionZoom/queryFeatureExtensions2"); - return; - } - } else { - callbackError(callbackID, extension.getError(), "getClusterExpansionZoom/queryFeatureExtensions"); - return; - } - } - } - ); - } else { - callbackError(callbackID, features.getError(), "getClusterExpansionZoom/querySourceFeatures"); - return; - } - } - } - ); - } - - - public void getClusterLeaves(String callbackID, int clusterId, int number, int offset) { - SourceQueryOptions options = new SourceQueryOptions(null, - Expression.eq(Expression.get("cluster_id"), Expression.literal(clusterId)) - ); - mMap.querySourceFeatures( - getID(), - options, new QueryFeaturesCallback() { - @Override - public void run(@NonNull Expected> features) { - if (features.isValue()) { - QueriedFeature cluster = features.getValue().get(0); - mMap.queryFeatureExtensions(getID(), cluster.getFeature(), "supercluster", "leaves", null, - new QueryFeatureExtensionCallback() { - @Override - public void run(@NonNull Expected extension) { - if (extension.isValue()) { - List leaves = extension.getValue().getFeatureCollection(); - WritableMap payload = new WritableNativeMap(); - payload.putString("data", FeatureCollection.fromFeatures(leaves).toJson()); - } else { - callbackError(callbackID, features.getError(), "getClusterLeaves/queryFeatureExtensions"); - return; - } - } - } - ); - } else { - callbackError(callbackID, features.getError(), "getClusterLeaves/querySourceFeatures"); - return; - } - } - } - ); - } -} - diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.kt new file mode 100644 index 000000000..7a8fd0a3a --- /dev/null +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSource.kt @@ -0,0 +1,284 @@ +package com.mapbox.rctmgl.components.styles.sources + +import android.content.Context +import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource +import com.mapbox.rctmgl.utils.ImageEntry +import android.graphics.drawable.BitmapDrawable +import com.mapbox.rctmgl.components.mapview.RCTMGLMapView +import com.mapbox.rctmgl.events.FeatureClickEvent +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableNativeMap +import com.mapbox.bindgen.Value +import com.mapbox.geojson.Feature +import com.mapbox.rctmgl.events.AndroidCallbackEvent +import com.mapbox.geojson.FeatureCollection +import com.mapbox.maps.* +import com.mapbox.maps.extension.style.expressions.generated.Expression +import com.mapbox.rctmgl.utils.Logger +import java.net.URL +import java.util.ArrayList +import java.util.HashMap + +class RCTMGLShapeSource(context: Context, private val mManager: RCTMGLShapeSourceManager) : + RCTSource(context) { + private var mURL: URL? = null + private var mShape: String? = null + private var mCluster: Boolean? = null + private var mClusterRadius: Long? = null + private var mClusterMaxZoom: Long? = null + private var mClusterProperties: HashMap? = null + private var mMaxZoom: Long? = null + private var mBuffer: Long? = null + private var mTolerance: Double? = null + private var mLineMetrics: Boolean? = null + private val mImages: List>? = null + private val mNativeImages: List>? = null + override fun addToMap(mapView: RCTMGLMapView) { + // Wait for style before adding the source to the map + mapView.getMapboxMap().getStyle { + val map = mapView.getMapboxMap() + super@RCTMGLShapeSource.addToMap(mapView) + } + } + + override fun makeSource(): GeoJsonSource { + val builder = GeoJsonSource.Builder(iD.toString()) + getOptions(builder) + + builder.data(mShape ?: mURL.toString()) + + return builder.build() + } + + fun setURL(url: URL) { + mURL = url + if (mSource != null && mMapView != null && !mMapView!!.isDestroyed) { + mSource!!.data(mURL.toString()) + } + } + + fun setShape(geoJSONStr: String) { + mShape = geoJSONStr + if (mSource != null && mMapView != null && !mMapView!!.isDestroyed) { + mSource!!.data(mShape!!) + val result = mMap!!.getStyle()!! + .setStyleSourceProperty(iD!!, "data", Value.valueOf(mShape!!)) + } + } + + fun setCluster(cluster: Boolean) { + mCluster = cluster + } + + fun setClusterRadius(clusterRadius: Long) { + mClusterRadius = clusterRadius + } + + fun setClusterMaxZoom(clusterMaxZoom: Long) { + mClusterMaxZoom = clusterMaxZoom + } + + fun setClusterProperties(clusterProperties: HashMap) { + mClusterProperties = clusterProperties + } + + fun setMaxZoom(maxZoom: Long) { + mMaxZoom = maxZoom + } + + fun setBuffer(buffer: Long) { + mBuffer = buffer + } + + fun setTolerance(tolerance: Double) { + mTolerance = tolerance + } + + fun setLineMetrics(lineMetrics: Boolean) { + mLineMetrics = lineMetrics + } + + override fun onPress(event: OnPressEvent?) { + mManager.handleEvent(FeatureClickEvent.makeShapeSourceEvent(this, event)) + } + + private fun getOptions(builder: GeoJsonSource.Builder) { + if (mCluster != null) { + builder.cluster(mCluster!!) + } + if (mClusterRadius != null) { + builder.clusterRadius(mClusterRadius!!) + } + if (mClusterMaxZoom != null) { + builder.clusterMaxZoom(mClusterMaxZoom!!) + } + if (mClusterProperties != null) { + builder.clusterProperties(mClusterProperties!!) + } + if (mMaxZoom != null) { + builder.maxzoom(mMaxZoom!!) + } + if (mBuffer != null) { + builder.buffer(mBuffer!!) + } + if (mTolerance != null) { + builder.tolerance(mTolerance!!) + } + if (mLineMetrics != null) { + builder.lineMetrics(mLineMetrics!!) + } + } + + fun querySourceFeatures( + callbackID: String, + filter: Expression? + ) { + if (mSource == null) { + val payload: WritableMap = WritableNativeMap() + payload.putString("error", "source is not yet loaded") + val event = AndroidCallbackEvent(this, callbackID, payload) + mManager.handleEvent(event) + return + } + val _this = this + mMap!!.querySourceFeatures( + iD!!, SourceQueryOptions( + null, // v10todo + filter!! + ) + ) { features -> + if (features.isError) { + Logger.e("RCTMGLShapeSource", String.format("Error: %s", features.error)) + } else { + val payload: WritableMap = WritableNativeMap() + val result: MutableList = ArrayList( + features.value!!.size + ) + for (i in features.value!!) { + result.add(i.feature) + } + payload.putString("data", FeatureCollection.fromFeatures(result).toJson()) + val event = AndroidCallbackEvent(_this, callbackID, payload) + mManager.handleEvent(event) + } + } + } + + private fun callbackError(callbackID: String, error: String, where: String) { + val payload: WritableMap = WritableNativeMap() + payload.putString("error", "$where: $error") + val event = AndroidCallbackEvent(this, callbackID, payload) + mManager.handleEvent(event) + } + + fun getClusterExpansionZoom(callbackID: String, clusterId: Int) { + if (mSource == null) { + val payload: WritableMap = WritableNativeMap() + payload.putString("error", "source is not yet loaded") + val event = AndroidCallbackEvent(this, callbackID, payload) + mManager.handleEvent(event) + return + } + val options = SourceQueryOptions( + null, + Expression.eq(Expression.get("cluster_id"), Expression.literal(clusterId.toLong())) + ) + val _this = this + mMap!!.querySourceFeatures( + iD!!, + options, + QueryFeaturesCallback { features -> + if (features.isValue) { + val cluster = features.value!![0] + mMap!!.queryFeatureExtensions( + iD!!, + cluster.feature, + "supercluster", + "expansion-zoom", + null, + QueryFeatureExtensionCallback { extension -> + if (extension.isValue) { + val contents = extension.value!!.value!!.contents + if (contents is Long) { + val payload: WritableMap = WritableNativeMap() + payload.putInt("data", contents.toInt()) + val event = AndroidCallbackEvent(_this, callbackID, payload) + mManager.handleEvent(event) + return@QueryFeatureExtensionCallback + } else { + callbackError( + callbackID, + "Not a number", + "getClusterExpansionZoom/queryFeatureExtensions2" + ) + return@QueryFeatureExtensionCallback + } + } else { + callbackError( + callbackID, + extension.error ?: "Unknown error", + "getClusterExpansionZoom/queryFeatureExtensions" + ) + return@QueryFeatureExtensionCallback + } + } + ) + } else { + callbackError( + callbackID, + features.error ?: "Unknown error", + "getClusterExpansionZoom/querySourceFeatures" + ) + return@QueryFeaturesCallback + } + } + ) + } + + fun getClusterLeaves(callbackID: String, clusterId: Int, number: Int, offset: Int) { + val options = SourceQueryOptions( + null, + Expression.eq(Expression.get("cluster_id"), Expression.literal(clusterId.toLong())) + ) + mMap!!.querySourceFeatures( + iD!!, + options, QueryFeaturesCallback { features -> + if (features.isValue) { + val cluster = features.value!![0] + mMap!!.queryFeatureExtensions( + iD!!, cluster.feature, "supercluster", "leaves", null, + QueryFeatureExtensionCallback { extension -> + if (extension.isValue) { + val leaves = extension.value!! + .featureCollection + val payload: WritableMap = WritableNativeMap() + payload.putString( + "data", + FeatureCollection.fromFeatures(leaves!!).toJson() + ) + } else { + callbackError( + callbackID, + features.error ?: "Unknown error", + "getClusterLeaves/queryFeatureExtensions" + ) + return@QueryFeatureExtensionCallback + } + } + ) + } else { + callbackError( + callbackID, + features.error ?: "Unknown error", + "getClusterLeaves/querySourceFeatures" + ) + return@QueryFeaturesCallback + } + } + ) + } + + /*companion object { + private val mImagePlaceholder: Bitmap? = null + }*/ +} diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.java b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.java deleted file mode 100644 index 29ab17787..000000000 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.mapbox.rctmgl.components.styles.sources; - -import android.content.Context; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.media.Image; -import android.util.Log; -import android.view.View; - -import androidx.annotation.Nullable; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.ReadableType; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.ViewGroupManager; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.mapbox.rctmgl.components.AbstractEventEmitter; -// import com.mapbox.rctmgl.components.annotation.RCTMGLCallout; -import com.mapbox.rctmgl.components.mapview.RCTMGLMapView; -import com.mapbox.rctmgl.components.styles.layers.RCTLayer; -import com.mapbox.rctmgl.events.constants.EventKeys; -import com.mapbox.rctmgl.utils.ExpressionParser; -import com.mapbox.rctmgl.utils.ImageEntry; -// import com.mapbox.rctmgl.utils.ResourceUtils; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class RCTMGLShapeSourceManager extends AbstractEventEmitter { - public static final String LOG_TAG = "RCTMGLShapeSourceManager"; - public static final String REACT_CLASS = "RCTMGLShapeSource"; - - private ReactApplicationContext mContext; - - public RCTMGLShapeSourceManager(ReactApplicationContext context) { - super(context); - mContext = context; - } - - @Override - public String getName() { - return REACT_CLASS; - } - - @Override - protected RCTMGLShapeSource createViewInstance(ThemedReactContext reactContext) { - return new RCTMGLShapeSource(reactContext, this); - } - - @Override - public View getChildAt(RCTMGLShapeSource source, int childPosition) { - return source.getLayerAt(childPosition); - } - - @Override - public int getChildCount(RCTMGLShapeSource source) { - return source.getLayerCount(); - } - - @Override - public void addView(RCTMGLShapeSource source, View childView, int childPosition) { - source.addLayer(childView, getChildCount(source)); - } - - @Override - public void removeViewAt(RCTMGLShapeSource source, int childPosition) { - source.removeLayer(childPosition); - } - - @ReactProp(name = "id") - public void setId(RCTMGLShapeSource source, String id) { - source.setID(id); - } - - @ReactProp(name = "url") - public void setURL(RCTMGLShapeSource source, String urlStr) { - try { - source.setURL(new URL(urlStr)); - } catch (MalformedURLException e) { - Log.w(LOG_TAG, e.getLocalizedMessage()); - } - } - - @ReactProp(name = "shape") - public void setGeometry(RCTMGLShapeSource source, String geoJSONStr) { - source.setShape(geoJSONStr); - } - - @ReactProp(name = "cluster") - public void setCluster(RCTMGLShapeSource source, int cluster) { - source.setCluster(cluster == 1); - } - - @ReactProp(name = "clusterRadius") - public void setClusterRadius(RCTMGLShapeSource source, int radius) { - source.setClusterRadius(radius); - } - - @ReactProp(name = "clusterMaxZoomLevel") - public void setClusterMaxZoomLevel(RCTMGLShapeSource source, int clusterMaxZoom) { - source.setClusterMaxZoom(clusterMaxZoom); - } - - @ReactProp(name = "maxZoomLevel") - public void setMaxZoomLevel(RCTMGLShapeSource source, int maxZoom) { - source.setMaxZoom(maxZoom); - } - - @ReactProp(name = "buffer") - public void setBuffer(RCTMGLShapeSource source, int buffer) { - source.setBuffer(buffer); - } - - @ReactProp(name = "tolerance") - public void setTolerance(RCTMGLShapeSource source, double tolerance) { - source.setTolerance(tolerance); - } - - @ReactProp(name = "lineMetrics") - public void setLineMetrics(RCTMGLShapeSource source, boolean lineMetrics) { - source.setLineMetrics(lineMetrics); - } - - @ReactProp(name = "hasPressListener") - public void setHasPressListener(RCTMGLShapeSource source, boolean hasPressListener) { - source.setHasPressListener(hasPressListener); - } - - @ReactProp(name="hitbox") - public void setHitbox(RCTMGLShapeSource source, ReadableMap map) { - source.setHitbox(map); - } - - @Override - public Map customEvents() { - return MapBuilder.builder() - .put(EventKeys.SHAPE_SOURCE_LAYER_CLICK, "onMapboxShapeSourcePress") - .put(EventKeys.MAP_ANDROID_CALLBACK, "onAndroidCallback") - .build(); - } - - //region React Methods - public static final int METHOD_FEATURES = 103; - public static final int METHOD_GET_CLUSTER_EXPANSION_ZOOM = 104; - public static final int METHOD_GET_CLUSTER_LEAVES = 105; - - @Nullable - @Override - public Map getCommandsMap() { - return MapBuilder.builder() - .put("features", METHOD_FEATURES) - .put("getClusterExpansionZoom", METHOD_GET_CLUSTER_EXPANSION_ZOOM) - .put("getClusterLeaves", METHOD_GET_CLUSTER_LEAVES) - .build(); - } - - @Override - public void receiveCommand(RCTMGLShapeSource source, int commandID, @Nullable ReadableArray args) { - switch (commandID) { - case METHOD_FEATURES: - source.querySourceFeatures( - args.getString(0), - ExpressionParser.from(args.getArray(1)) - ); - break; - case METHOD_GET_CLUSTER_EXPANSION_ZOOM: - source.getClusterExpansionZoom(args.getString(0), args.getInt(1)); - break; - case METHOD_GET_CLUSTER_LEAVES: - source.getClusterLeaves( - args.getString(0), - args.getInt(1), - args.getInt(2), - args.getInt((3)) - ); - break; - } - } -} diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.kt new file mode 100644 index 000000000..1a19a44a3 --- /dev/null +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/components/styles/sources/RCTMGLShapeSourceManager.kt @@ -0,0 +1,188 @@ +package com.mapbox.rctmgl.components.styles.sources + +import android.util.Log +import android.view.View +import com.facebook.react.bridge.ReactApplicationContext +import com.mapbox.rctmgl.components.AbstractEventEmitter +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableType +import com.facebook.react.common.MapBuilder +import com.mapbox.bindgen.Value +import com.mapbox.maps.extension.style.expressions.generated.Expression +import com.mapbox.rctmgl.events.constants.EventKeys +import com.mapbox.rctmgl.utils.ExpressionParser +import java.net.MalformedURLException +import java.net.URL +import java.util.ArrayList +import java.util.HashMap + + +class RCTMGLShapeSourceManager(private val mContext: ReactApplicationContext) : + AbstractEventEmitter( + mContext + ) { + override fun getName(): String { + return REACT_CLASS + } + + override fun createViewInstance(reactContext: ThemedReactContext): RCTMGLShapeSource { + return RCTMGLShapeSource(reactContext, this) + } + + override fun getChildAt(source: RCTMGLShapeSource, childPosition: Int): View { + return source.getLayerAt(childPosition) + } + + override fun getChildCount(source: RCTMGLShapeSource): Int { + return source.layerCount + } + + override fun addView(source: RCTMGLShapeSource, childView: View, childPosition: Int) { + source.addLayer(childView, getChildCount(source)) + } + + override fun removeViewAt(source: RCTMGLShapeSource, childPosition: Int) { + source.removeLayer(childPosition) + } + + @ReactProp(name = "id") + fun setId(source: RCTMGLShapeSource, id: String) { + source.iD = id + } + + @ReactProp(name = "url") + fun setURL(source: RCTMGLShapeSource, urlStr: String) { + try { + source.setURL(URL(urlStr)) + } catch (e: MalformedURLException) { + Log.w(LOG_TAG, e.localizedMessage ?: "Unkown URL error") + } + } + + @ReactProp(name = "shape") + fun setGeometry(source: RCTMGLShapeSource, geoJSONStr: String) { + source.setShape(geoJSONStr) + } + + @ReactProp(name = "cluster") + fun setCluster(source: RCTMGLShapeSource, cluster: Int) { + source.setCluster(cluster == 1) + } + + @ReactProp(name = "clusterRadius") + fun setClusterRadius(source: RCTMGLShapeSource, radius: Int) { + source.setClusterRadius(radius.toLong()) + } + + @ReactProp(name = "clusterMaxZoomLevel") + fun setClusterMaxZoomLevel(source: RCTMGLShapeSource, clusterMaxZoom: Int) { + source.setClusterMaxZoom(clusterMaxZoom.toLong()) + } + + @ReactProp(name = "clusterProperties") + fun setClusterProperties(source: RCTMGLShapeSource, map: ReadableMap) { + val properties = HashMap() + val iterator = map.keySetIterator() + while (iterator.hasNextKey()) { + val name = iterator.nextKey() + val expressions = map.getArray(name) + val builder: MutableList = ArrayList() + for (iExp in 0 until expressions!!.size()) { + var argument: Expression + argument = when (expressions.getType(iExp)) { + ReadableType.Array -> ExpressionParser.from( + expressions.getArray(iExp) + )!! + ReadableType.Map -> ExpressionParser.from( + expressions.getMap(iExp) + ) + ReadableType.Boolean -> Expression.literal(expressions.getBoolean(iExp)) + ReadableType.Number -> Expression.literal(expressions.getDouble(iExp)) + else -> Expression.literal(expressions.getString(iExp)) + } + builder.add(argument) + } + properties[name] = Value(builder) + } + source.setClusterProperties(properties) + } + + @ReactProp(name = "maxZoomLevel") + fun setMaxZoomLevel(source: RCTMGLShapeSource, maxZoom: Int) { + source.setMaxZoom(maxZoom.toLong()) + } + + @ReactProp(name = "buffer") + fun setBuffer(source: RCTMGLShapeSource, buffer: Int) { + source.setBuffer(buffer.toLong()) + } + + @ReactProp(name = "tolerance") + fun setTolerance(source: RCTMGLShapeSource, tolerance: Double) { + source.setTolerance(tolerance) + } + + @ReactProp(name = "lineMetrics") + fun setLineMetrics(source: RCTMGLShapeSource, lineMetrics: Boolean) { + source.setLineMetrics(lineMetrics) + } + + @ReactProp(name = "hasPressListener") + fun setHasPressListener(source: RCTMGLShapeSource, hasPressListener: Boolean) { + source.setHasPressListener(hasPressListener) + } + + @ReactProp(name = "hitbox") + fun setHitbox(source: RCTMGLShapeSource, map: ReadableMap) { + source.setHitbox(map) + } + + override fun customEvents(): Map? { + return MapBuilder.builder() + .put(EventKeys.SHAPE_SOURCE_LAYER_CLICK, "onMapboxShapeSourcePress") + .put(EventKeys.MAP_ANDROID_CALLBACK, "onAndroidCallback") + .build() + } + + override fun getCommandsMap(): Map? { + return MapBuilder.builder() + .put("features", METHOD_FEATURES) + .put("getClusterExpansionZoom", METHOD_GET_CLUSTER_EXPANSION_ZOOM) + .put("getClusterLeaves", METHOD_GET_CLUSTER_LEAVES) + .build() + } + + override fun receiveCommand(source: RCTMGLShapeSource, commandID: Int, args: ReadableArray?) { + if (args == null) { + return + } + when (commandID) { + METHOD_FEATURES -> source.querySourceFeatures( + args.getString(0), + ExpressionParser.from(args.getArray(1)) + ) + METHOD_GET_CLUSTER_EXPANSION_ZOOM -> source.getClusterExpansionZoom( + args.getString(0), args.getInt(1) + ) + METHOD_GET_CLUSTER_LEAVES -> source.getClusterLeaves( + args.getString(0), + args.getInt(1), + args.getInt(2), + args.getInt(3) + ) + } + } + + companion object { + const val LOG_TAG = "RCTMGLShapeSourceManager" + const val REACT_CLASS = "RCTMGLShapeSource" + + //region React Methods + const val METHOD_FEATURES = 103 + const val METHOD_GET_CLUSTER_EXPANSION_ZOOM = 104 + const val METHOD_GET_CLUSTER_LEAVES = 105 + } +} diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLLogging.java b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLLogging.java deleted file mode 100644 index 26856ea6a..000000000 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLLogging.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.mapbox.rctmgl.modules; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.mapbox.rctmgl.utils.Logger; - -import android.util.Log; - -@ReactModule(name = RCTMGLLogging.REACT_CLASS) -public class RCTMGLLogging extends ReactContextBaseJavaModule { - public static final String REACT_CLASS = "RCTMGLLogging"; - private ReactApplicationContext mReactContext; - - public RCTMGLLogging(ReactApplicationContext reactApplicationContext) { - super(reactApplicationContext); - mReactContext = reactApplicationContext; - - Logger.setVerbosity(Log.WARN); - Logger.setLoggerDefinition(new Logger.LoggerDefinition() { - @Override - public void v(String tag, String msg) { - Log.v(tag, msg); - onLog("verbose", tag, msg, null); - } - - @Override - public void v(String tag, String msg, Throwable tr) { - Log.v(tag, msg, tr); - onLog("verbose", tag, msg, tr); - } - - @Override - public void d(String tag, String msg) { - Log.d(tag, msg); - onLog("debug", tag, msg, null); - } - - @Override - public void d(String tag, String msg, Throwable tr) { - Log.d(tag, msg, tr); - onLog("debug",tag,msg,tr); - } - - @Override - public void i(String tag, String msg) { - Log.i(tag, msg); - onLog("info", tag, msg, null); - } - - @Override - public void i(String tag, String msg, Throwable tr) { - Log.i(tag, msg, tr); - onLog("info", tag, msg, tr); - } - - @Override - public void w(String tag, String msg) { - Log.w(tag, msg); - onLog("warning", tag, msg, null); - } - - @Override - public void w(String tag, String msg, Throwable tr) { - Log.w(tag, msg, tr); - onLog("warning", tag, msg, tr); - } - - @Override - public void e(String tag, String msg) { - Log.e(tag, msg); - onLog("error", tag, msg, null); - } - - @Override - public void e(String tag, String msg, Throwable tr) { - Log.e(tag, msg, tr); - onLog("error", tag, msg, tr); - } - }); - } - - @Override - public String getName() { - return REACT_CLASS; - } - - @ReactMethod - public void setLogLevel(String level) { - int NONE = 0; - int logLevel = NONE; - switch(level) - { - case "error": - logLevel = Log.ERROR; - break; - case "warning": - logLevel = Log.WARN; - break; - case "info": - logLevel = Log.INFO; - break; - case "debug": - logLevel = Log.DEBUG; - break; - case "verbose": - logLevel = Log.VERBOSE; - break; - default: - logLevel = NONE; - break; - } - Logger.setVerbosity(logLevel); - } - - public void onLog(String level, String tag, String msg, Throwable tr) { - WritableMap event = Arguments.createMap(); - event.putString("message", msg); - event.putString("tag", tag); - event.putString("level", level); - - mReactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("LogEvent", event); - } - -} diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLLogging.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLLogging.kt new file mode 100644 index 000000000..549ed2252 --- /dev/null +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLLogging.kt @@ -0,0 +1,113 @@ +package com.mapbox.rctmgl.modules + +import android.util.Log +import com.facebook.react.bridge.* +import com.facebook.react.module.annotations.ReactModule +import com.mapbox.rctmgl.modules.RCTMGLLogging +import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter +import com.mapbox.rctmgl.utils.Logger +import com.mapbox.rctmgl.utils.Logger.LoggerDefinition + +@ReactModule(name = RCTMGLLogging.REACT_CLASS) +class RCTMGLLogging(private val mReactContext: ReactApplicationContext) : + ReactContextBaseJavaModule( + mReactContext + ) { + override fun getName(): String { + return REACT_CLASS + } + + @ReactMethod + fun addListener(eventName: String?) { + // Set up any upstream listeners or background tasks as necessary + } + + @ReactMethod + fun removeListeners(count: Int?) { + // Remove upstream listeners, stop unnecessary background tasks + } + + @ReactMethod + fun setLogLevel(level: String?) { + val NONE = 0 + var logLevel = NONE + logLevel = when (level) { + "error" -> Log.ERROR + "warning" -> Log.WARN + "info" -> Log.INFO + "debug" -> Log.DEBUG + "verbose" -> Log.VERBOSE + else -> NONE + } + Logger.setVerbosity(logLevel) + } + + fun onLog(level: String?, tag: String?, msg: String?, tr: Throwable?) { + val event = Arguments.createMap() + event.putString("message", msg) + event.putString("tag", tag) + event.putString("level", level) + mReactContext + .getJSModule(RCTDeviceEventEmitter::class.java) + .emit("LogEvent", event) + } + + companion object { + const val REACT_CLASS = "RCTMGLLogging" + } + + init { + Logger.setVerbosity(Log.WARN) + Logger.setLoggerDefinition(object : LoggerDefinition { + override fun v(tag: String, msg: String) { + Log.v(tag, msg) + onLog("verbose", tag, msg, null) + } + + override fun v(tag: String, msg: String, tr: Throwable) { + Log.v(tag, msg, tr) + onLog("verbose", tag, msg, tr) + } + + override fun d(tag: String, msg: String) { + Log.d(tag, msg) + onLog("debug", tag, msg, null) + } + + override fun d(tag: String, msg: String, tr: Throwable) { + Log.d(tag, msg, tr) + onLog("debug", tag, msg, tr) + } + + override fun i(tag: String, msg: String) { + Log.i(tag, msg) + onLog("info", tag, msg, null) + } + + override fun i(tag: String, msg: String, tr: Throwable) { + Log.i(tag, msg, tr) + onLog("info", tag, msg, tr) + } + + override fun w(tag: String, msg: String) { + Log.w(tag, msg) + onLog("warning", tag, msg, null) + } + + override fun w(tag: String, msg: String, tr: Throwable) { + Log.w(tag, msg, tr) + onLog("warning", tag, msg, tr) + } + + override fun e(tag: String, msg: String) { + Log.e(tag, msg) + onLog("error", tag, msg, null) + } + + override fun e(tag: String, msg: String, tr: Throwable) { + Log.e(tag, msg, tr) + onLog("error", tag, msg, tr) + } + }) + } +} \ No newline at end of file diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLModule.java b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLModule.java deleted file mode 100644 index 56b155ba0..000000000 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLModule.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.mapbox.rctmgl.modules; -import android.os.Handler; - -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.common.MapBuilder; - -import com.mapbox.maps.extension.style.layers.properties.generated.LineJoin; - -import com.mapbox.maps.Style; -import com.mapbox.maps.ResourceOptions; -import com.mapbox.maps.ResourceOptionsManager; - -import com.mapbox.rctmgl.components.camera.constants.CameraMode; -import com.mapbox.rctmgl.events.constants.EventTypes; - -import java.util.HashMap; -import java.util.Map; - -import javax.annotation.Nullable; - -@ReactModule(name = RCTMGLModule.REACT_CLASS) -public class RCTMGLModule extends ReactContextBaseJavaModule { - public static final String REACT_CLASS = "RCTMGLModule"; - - private static boolean customHeaderInterceptorAdded = false; - - private Handler mUiThreadHandler; - private ReactApplicationContext mReactContext; - - - @Override - public String getName() { - return REACT_CLASS; - } - - public RCTMGLModule(ReactApplicationContext reactApplicationContext) { - super(reactApplicationContext); - mReactContext = reactApplicationContext; - } - - @Override - @Nullable - public Map getConstants() { - // map style urls - Map styleURLS = new HashMap<>(); - styleURLS.put("Street", Style.MAPBOX_STREETS); - styleURLS.put("Dark", Style.DARK); - styleURLS.put("Light", Style.LIGHT); - styleURLS.put("Outdoors", Style.OUTDOORS); - styleURLS.put("Satellite", Style.SATELLITE); - styleURLS.put("SatelliteStreet", Style.SATELLITE_STREETS); - styleURLS.put("TrafficDay", Style.TRAFFIC_DAY); - styleURLS.put("TrafficNight", Style.TRAFFIC_NIGHT); - - // events - Map eventTypes = new HashMap<>(); - eventTypes.put("MapClick", EventTypes.MAP_CLICK); - eventTypes.put("MapLongClick", EventTypes.MAP_LONG_CLICK); - eventTypes.put("RegionWillChange", EventTypes.REGION_WILL_CHANGE); - eventTypes.put("RegionIsChanging", EventTypes.REGION_IS_CHANGING); - eventTypes.put("RegionDidChange", EventTypes.REGION_DID_CHANGE); - eventTypes.put("UserLocationUpdated", EventTypes.USER_LOCATION_UPDATED); - eventTypes.put("WillStartLoadingMap", EventTypes.WILL_START_LOADING_MAP); - eventTypes.put("DidFinishLoadingMap", EventTypes.DID_FINISH_LOADING_MAP); - eventTypes.put("DidFailLoadingMap", EventTypes.DID_FAIL_LOADING_MAP); - eventTypes.put("WillStartRenderingFrame", EventTypes.WILL_START_RENDERING_FRAME); - eventTypes.put("DidFinishRenderingFrame", EventTypes.DID_FINISH_RENDERING_FRAME); - eventTypes.put("DidFinishRenderingFrameFully", EventTypes.DID_FINISH_RENDERING_FRAME_FULLY); - eventTypes.put("WillStartRenderingMap", EventTypes.WILL_START_RENDERING_MAP); - eventTypes.put("DidFinishRenderingMap", EventTypes.DID_FINISH_RENDERING_MAP); - eventTypes.put("DidFinishRenderingMapFully", EventTypes.DID_FINISH_RENDERING_MAP_FULLY); - eventTypes.put("DidFinishLoadingStyle", EventTypes.DID_FINISH_LOADING_STYLE); - - // style source constants - Map styleSourceConsts = new HashMap<>(); - styleSourceConsts.put("DefaultSourceID", "TODO-defautl id"); //v10todo - - // line layer constants - Map lineJoin = new HashMap<>(); - lineJoin.put("Bevel", LineJoin.BEVEL.getValue()); - lineJoin.put("Round", LineJoin.ROUND.getValue()); - lineJoin.put("Miter", LineJoin.MITER.getValue()); - - // camera modes - Map cameraModes = new HashMap<>(); - cameraModes.put("Flight", CameraMode.FLIGHT); - cameraModes.put("Ease", CameraMode.EASE); - cameraModes.put("Linear", CameraMode.LINEAR); - cameraModes.put("None", CameraMode.NONE); - - // offline region download states - Map offlinePackDownloadStates = new HashMap<>(); - offlinePackDownloadStates.put("Inactive", RCTMGLOfflineModule.INACTIVE_REGION_DOWNLOAD_STATE); - offlinePackDownloadStates.put("Active", RCTMGLOfflineModule.ACTIVE_REGION_DOWNLOAD_STATE); - offlinePackDownloadStates.put("Complete", RCTMGLOfflineModule.COMPLETE_REGION_DOWNLOAD_STATE); - - // offline module callback names - Map offlineModuleCallbackNames = new HashMap<>(); - offlineModuleCallbackNames.put("Error", RCTMGLOfflineModule.OFFLINE_ERROR); - offlineModuleCallbackNames.put("Progress", RCTMGLOfflineModule.OFFLINE_PROGRESS); - - // location module callback names - Map locationModuleCallbackNames = new HashMap<>(); - locationModuleCallbackNames.put("Update", RCTMGLLocationModule.LOCATION_UPDATE); - - return MapBuilder.builder() - .put("StyleURL", styleURLS) - .put("EventTypes", eventTypes) - .put("StyleSource", styleSourceConsts) - .put("CameraModes", cameraModes) - .put("LineJoin", lineJoin) - .put("OfflinePackDownloadState", offlinePackDownloadStates) - .put("OfflineCallbackName", offlineModuleCallbackNames) - .put("LocationCallbackName", locationModuleCallbackNames) - .build(); - - } - - public static String getAccessToken(ReactApplicationContext reactContext) { - return ResourceOptionsManager.Companion.getDefault(reactContext, null).getResourceOptions().getAccessToken(); - } - - @ReactMethod - public void setAccessToken(final String accessToken) { - mReactContext.runOnUiQueueThread(new Runnable() { - @Override - public void run() { - ResourceOptionsManager.Companion.getDefault(getReactApplicationContext(), accessToken); - } - }); - } -} \ No newline at end of file diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLModule.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLModule.kt new file mode 100644 index 000000000..b55f22f3f --- /dev/null +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLModule.kt @@ -0,0 +1,134 @@ +package com.mapbox.rctmgl.modules + +import android.os.Handler +import com.mapbox.maps.extension.style.layers.properties.generated.LineJoin +import com.mapbox.maps.ResourceOptionsManager.Companion.getDefault +import com.facebook.react.module.annotations.ReactModule +import com.mapbox.rctmgl.modules.RCTMGLModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.mapbox.rctmgl.events.constants.EventTypes +import com.mapbox.rctmgl.modules.RCTMGLOfflineModule +import com.mapbox.rctmgl.modules.RCTMGLLocationModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.common.MapBuilder +import com.mapbox.maps.Style +import com.mapbox.rctmgl.components.camera.constants.CameraMode +import java.util.HashMap + +@ReactModule(name = RCTMGLModule.REACT_CLASS) +class RCTMGLModule(private val mReactContext: ReactApplicationContext) : ReactContextBaseJavaModule( + mReactContext +) { + private val mUiThreadHandler: Handler? = null + override fun getName(): String { + return REACT_CLASS + } + + override fun getConstants(): Map? { + // map style urls + val styleURLS: MutableMap = HashMap() + styleURLS["Street"] = Style.MAPBOX_STREETS + styleURLS["Dark"] = Style.DARK + styleURLS["Light"] = Style.LIGHT + styleURLS["Outdoors"] = Style.OUTDOORS + styleURLS["Satellite"] = Style.SATELLITE + styleURLS["SatelliteStreet"] = Style.SATELLITE_STREETS + styleURLS["TrafficDay"] = Style.TRAFFIC_DAY + styleURLS["TrafficNight"] = Style.TRAFFIC_NIGHT + + // tile server + val tileServers: MutableMap = HashMap() + tileServers["Mapbox"] = "mapbox" + + // impl + val impl: MutableMap = HashMap() + impl["Library"] = "mapbox" + + // events + val eventTypes: MutableMap = HashMap() + eventTypes["MapClick"] = EventTypes.MAP_CLICK + eventTypes["MapLongClick"] = EventTypes.MAP_LONG_CLICK + eventTypes["RegionWillChange"] = EventTypes.REGION_WILL_CHANGE + eventTypes["RegionIsChanging"] = EventTypes.REGION_IS_CHANGING + eventTypes["RegionDidChange"] = EventTypes.REGION_DID_CHANGE + eventTypes["UserLocationUpdated"] = EventTypes.USER_LOCATION_UPDATED + eventTypes["WillStartLoadingMap"] = EventTypes.WILL_START_LOADING_MAP + eventTypes["DidFinishLoadingMap"] = EventTypes.DID_FINISH_LOADING_MAP + eventTypes["DidFailLoadingMap"] = EventTypes.DID_FAIL_LOADING_MAP + eventTypes["WillStartRenderingFrame"] = EventTypes.WILL_START_RENDERING_FRAME + eventTypes["DidFinishRenderingFrame"] = EventTypes.DID_FINISH_RENDERING_FRAME + eventTypes["DidFinishRenderingFrameFully"] = EventTypes.DID_FINISH_RENDERING_FRAME_FULLY + eventTypes["WillStartRenderingMap"] = EventTypes.WILL_START_RENDERING_MAP + eventTypes["DidFinishRenderingMap"] = EventTypes.DID_FINISH_RENDERING_MAP + eventTypes["DidFinishRenderingMapFully"] = EventTypes.DID_FINISH_RENDERING_MAP_FULLY + eventTypes["DidFinishLoadingStyle"] = EventTypes.DID_FINISH_LOADING_STYLE + + // style source constants + val styleSourceConsts: MutableMap = HashMap() + styleSourceConsts["DefaultSourceID"] = "TODO-defautl id" //v10todo + + // line layer constants + val lineJoin: MutableMap = HashMap() + lineJoin["Bevel"] = LineJoin.BEVEL.value + lineJoin["Round"] = LineJoin.ROUND.value + lineJoin["Miter"] = LineJoin.MITER.value + + // camera modes + val cameraModes: MutableMap = HashMap() + cameraModes["Flight"] = CameraMode.FLIGHT + cameraModes["Ease"] = CameraMode.EASE + cameraModes["Linear"] = CameraMode.LINEAR + cameraModes["None"] = CameraMode.NONE + + // offline region download states + val offlinePackDownloadStates: MutableMap = HashMap() + offlinePackDownloadStates["Inactive"] = RCTMGLOfflineModule.INACTIVE_REGION_DOWNLOAD_STATE + offlinePackDownloadStates["Active"] = RCTMGLOfflineModule.ACTIVE_REGION_DOWNLOAD_STATE + offlinePackDownloadStates["Complete"] = RCTMGLOfflineModule.COMPLETE_REGION_DOWNLOAD_STATE + + // offline module callback names + val offlineModuleCallbackNames: MutableMap = HashMap() + offlineModuleCallbackNames["Error"] = RCTMGLOfflineModule.OFFLINE_ERROR + offlineModuleCallbackNames["Progress"] = RCTMGLOfflineModule.OFFLINE_PROGRESS + + // location module callback names + val locationModuleCallbackNames: MutableMap = HashMap() + locationModuleCallbackNames["Update"] = RCTMGLLocationModule.LOCATION_UPDATE + return MapBuilder.builder() + .put("StyleURL", styleURLS) + .put("EventTypes", eventTypes) + .put("StyleSource", styleSourceConsts) + .put("CameraModes", cameraModes) + .put("LineJoin", lineJoin) + .put("OfflinePackDownloadState", offlinePackDownloadStates) + .put("OfflineCallbackName", offlineModuleCallbackNames) + .put("LocationCallbackName", locationModuleCallbackNames) + .put("TileServers", tileServers) + .put("Implementation", impl) + .build() + } + + @ReactMethod + fun setAccessToken(accessToken: String?) { + mReactContext.runOnUiQueueThread(Runnable { + getDefault( + reactApplicationContext, accessToken + ) + }) + } + + @ReactMethod + fun setWellKnownTileServer(tileServer: String?) { + // NO-OP + } + + companion object { + const val REACT_CLASS = "RCTMGLModule" + private val customHeaderInterceptorAdded = false + @JvmStatic + fun getAccessToken(reactContext: ReactApplicationContext?): String { + return getDefault((reactContext)!!, null).resourceOptions.accessToken + } + } +} \ No newline at end of file diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLOfflineModule.java b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLOfflineModule.java deleted file mode 100644 index 404e836ba..000000000 --- a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLOfflineModule.java +++ /dev/null @@ -1,684 +0,0 @@ -package com.mapbox.rctmgl.modules; - -import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread; - -import android.os.Handler; -import android.os.Looper; -import android.util.JsonReader; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Dynamic; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableType; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.WritableNativeMap; -import com.facebook.react.module.annotations.ReactModule; -import com.facebook.react.modules.core.RCTNativeAppEventEmitter; -import com.mapbox.bindgen.Expected; -import com.mapbox.bindgen.Value; -import com.mapbox.common.Cancelable; -import com.mapbox.common.NetworkRestriction; -import com.mapbox.common.TileRegion; -import com.mapbox.common.TileRegionCallback; -import com.mapbox.common.TileRegionError; -import com.mapbox.common.TileRegionGeometryCallback; -import com.mapbox.common.TileRegionLoadOptions; -import com.mapbox.common.TileRegionLoadProgress; -import com.mapbox.common.TileRegionLoadProgressCallback; -import com.mapbox.common.TileRegionsCallback; -import com.mapbox.common.TileStore; -import com.mapbox.common.TileStoreOptions; -import com.mapbox.common.TilesetDescriptor; -import com.mapbox.common.ValueConverter; -import com.mapbox.geojson.BoundingBox; -import com.mapbox.geojson.FeatureCollection; - -import com.mapbox.geojson.Geometry; -import com.mapbox.geojson.Point; -import com.mapbox.maps.OfflineManager; -import com.mapbox.maps.ResourceOptions; -import com.mapbox.maps.StylePackLoadOptions; -import com.mapbox.maps.TileStoreUsageMode; -import com.mapbox.maps.TilesetDescriptorOptions; -import com.mapbox.rctmgl.events.IEvent; -import com.mapbox.rctmgl.events.OfflineEvent; -import com.mapbox.rctmgl.events.constants.EventTypes; -import com.mapbox.rctmgl.utils.ConvertUtils; -import com.mapbox.rctmgl.utils.GeoJSONUtils; -import com.mapbox.rctmgl.utils.LatLngBounds; -import com.mapbox.rctmgl.utils.Logger; -import com.mapbox.rctmgl.utils.ReadableMapToValue; -import com.mapbox.turf.TurfMeasurement; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.CountDownLatch; - - -class TileRegionPack { - final static String ACTIVE = "active"; - final static String INACTIVE = "inactive"; - final static String COMPLETE = "complete"; - public String name; - public TileRegionLoadProgress progress; - public String state; - public Cancelable cancelable; - public TileRegionLoadOptions loadOptions; - - TileRegionPack(String name, TileRegionLoadProgress progress, String state) { - this.name = name; - this.progress = progress; - this.state = state; - } -} - -@ReactModule(name = RCTMGLOfflineModule.REACT_CLASS) -public class RCTMGLOfflineModule extends ReactContextBaseJavaModule { - public static final String REACT_CLASS = "RCTMGLOfflineModule"; - - public static final String INACTIVE_REGION_DOWNLOAD_STATE = TileRegionPack.INACTIVE; - public static final String ACTIVE_REGION_DOWNLOAD_STATE = TileRegionPack.ACTIVE; - - public static final String COMPLETE_REGION_DOWNLOAD_STATE = TileRegionPack.COMPLETE; - - public static final String OFFLINE_ERROR = "MapboxOfflineRegionError"; - public static final String OFFLINE_PROGRESS = "MapboxOfflineRegionProgress"; - -// public static final String DEFAULT_STYLE_URL = Style.MAPBOX_STREETS; - public static final Double DEFAULT_MIN_ZOOM_LEVEL = 10.0; - public static final Double DEFAULT_MAX_ZOOM_LEVEL = 20.0; - - public HashMap tileRegionPacks = new HashMap<>(); - - private ReactApplicationContext mReactContext; - private Double mProgressEventThrottle = 300.0; - - static OfflineManager offlineManager = null; - static TileStore tileStore = null; - - public RCTMGLOfflineModule(ReactApplicationContext reactApplicationContext) { - super(reactApplicationContext); - mReactContext = reactApplicationContext; - } - - @Override - public String getName () { - return REACT_CLASS; - } - - static public OfflineManager getOfflineManager(ReactApplicationContext mReactContext) { - if (offlineManager == null) { - offlineManager = new OfflineManager(new ResourceOptions.Builder().accessToken (RCTMGLModule.getAccessToken(mReactContext)).tileStore(getTileStore()).build() ); - } - return offlineManager; - } - - static public TileStore getTileStore() { - if (tileStore == null) { - tileStore = TileStore.create(); - return tileStore; - } - return tileStore; - } - - @ReactMethod - public void createPack(ReadableMap options, final Promise promise) throws JSONException { - final String name = ConvertUtils.getString("name", options, ""); - final OfflineManager offlineManager = RCTMGLOfflineModule.getOfflineManager(mReactContext); - LatLngBounds latLngBounds = getBoundsFromOptions(options); - - TilesetDescriptorOptions descriptorOptions = new TilesetDescriptorOptions.Builder(). - styleURI(options.getString("styleURL")). - minZoom((byte)options.getInt("minZoom")). - maxZoom((byte)options.getInt("maxZoom")).build(); - - TilesetDescriptor tilesetDescriptor = offlineManager.createTilesetDescriptor(descriptorOptions); - ArrayList descriptors = new ArrayList<>(); - descriptors.add(tilesetDescriptor); - - - TileRegionLoadOptions loadOptions = new TileRegionLoadOptions.Builder() - .geometry(GeoJSONUtils.fromLatLngBoundsToPolygon(latLngBounds)) - .descriptors(descriptors) - .metadata(Value.valueOf(options.getString("metadata"))) - .acceptExpired(true) - .networkRestriction(NetworkRestriction.NONE) - .build(); - - String metadataStr = options.getString("metadata"); - JSONObject metadata = new JSONObject(metadataStr); - String id = metadata.getString("name"); - TileRegionPack pack = new TileRegionPack(id, null, TileRegionPack.INACTIVE); - pack.loadOptions = loadOptions; - tileRegionPacks.put(id, pack); - - promise.resolve(fromOfflineRegion(latLngBounds, metadataStr)); - - startPackDownload(pack); - } - - void startPackDownload(TileRegionPack pack) { - RCTMGLOfflineModule _this = this; - pack.cancelable = getTileStore().loadTileRegion(pack.name, pack.loadOptions, new TileRegionLoadProgressCallback() { - @Override - public void run(@NonNull TileRegionLoadProgress progress) { - pack.progress = progress; - pack.state = TileRegionPack.ACTIVE; - _this.sendEvent(_this.makeStatusEvent(pack.name, progress, pack)); - } - }, new TileRegionCallback() { - @Override - public void run(@NonNull Expected region) { - pack.cancelable = null; - if (region.isError()) { - pack.state = TileRegionPack.INACTIVE; - _this.sendEvent(_this.makeErrorEvent(pack.name, "TileRegionError", region.getError().getMessage())); - } else { - pack.state = TileRegionPack.COMPLETE; - _this.sendEvent(_this.makeStatusEvent(pack.name, pack.progress, pack)); - } - } - }); - } - - @ReactMethod - public void getPacks(final Promise promise) { - getTileStore().getAllTileRegions(new TileRegionsCallback() { - @Override - public void run(@NonNull Expected> regions) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (regions.isValue()) { - convertRegionsToJSON(regions.getValue(), promise); - } else { - promise.reject("getPacks", regions.getError().getMessage()); - } - } - }); - - } - }); - } - - private void convertRegionsToJSON(List tileRegions, Promise promise) { - CountDownLatch countDownLatch = new CountDownLatch(tileRegions.size()); - ArrayList errors = new ArrayList<>(); - ArrayList geometries = new ArrayList<>(); - try { - for (TileRegion region : tileRegions) { - getTileStore().getTileRegionGeometry(region.getId(), new TileRegionGeometryCallback() { - @Override - public void run(@NonNull Expected result) { - if (result.isValue()) { - geometries.add(result.getValue()); - } else { - errors.add(result.getError()); - } - countDownLatch.countDown(); - } - }); - } - } catch(Error error) { - Logger.e("OS", "a"); - } - try { - countDownLatch.await(); - WritableArray result = Arguments.createArray(); - for (Geometry geometry: geometries) { - result.pushMap(fromOfflineRegion(geometry)); - } - for (TileRegionError error: errors) { - WritableMap errorMap = Arguments.createMap(); - errorMap.putString("type","error"); - errorMap.putString("message", error.getMessage()); - errorMap.putString("errorType", error.getType().toString()); - result.pushMap(errorMap); - } - promise.resolve( - result - ); - } catch (InterruptedException interruptedException) { - promise.reject(interruptedException); - } - - } - - /* - @ReactMethod - public void invalidateAmbientCache(final Promise promise) { - activateFileSource(); - final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); - offlineManager.invalidateAmbientCache(new OfflineManager.FileSourceCallback() { - @Override - public void onSuccess() { - promise.resolve(null); - } - - @Override - public void onError(String error) { - promise.reject("invalidateAmbientCache", error); - } - }); - } - - @ReactMethod - public void clearAmbientCache(final Promise promise) { - activateFileSource(); - - final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); - - offlineManager.clearAmbientCache(new OfflineManager.FileSourceCallback() { - @Override - public void onSuccess() { - promise.resolve(null); - } - - @Override - public void onError(String error) { - promise.reject("clearAmbientCache", error); - } - }); - } - - @ReactMethod - public void setMaximumAmbientCacheSize(int size, final Promise promise) { - activateFileSource(); - - final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); - - offlineManager.setMaximumAmbientCacheSize(size, new OfflineManager.FileSourceCallback() { - @Override - public void onSuccess() { - promise.resolve(null); - } - - @Override - public void onError(String error) { - promise.reject("setMaximumAmbientCacheSize", error); - } - }); - }*/ - -/* - @ReactMethod - public void resetDatabase(final Promise promise) { - activateFileSource(); - final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); - offlineManager.resetDatabase(new OfflineManager.FileSourceCallback() { - @Override - public void onSuccess() { - promise.resolve(null); - } - - @Override - public void onError(String error) { - promise.reject("resetDatabase", error); - } - }); - }*/ - - @ReactMethod - public void getPackStatus(final String name, final Promise promise) { - TileRegionPack pack = this.tileRegionPacks.get(name); - if (pack != null) { - promise.resolve(makeRegionStatus(name, pack.progress, pack)); - } else { - promise.reject(new Error("Pack not found")); - Logger.w(REACT_CLASS, "getPackStatus - Unknown offline region"); - } - } - - /* - @ReactMethod - public void setPackObserver(final String name, final Promise promise) { - activateFileSource(); - - final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); - - offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() { - @Override - public void onList(OfflineRegion[] offlineRegions) { - OfflineRegion region = getRegionByName(name, offlineRegions); - boolean hasRegion = region != null; - - if (hasRegion) { - setOfflineRegionObserver(name, region); - } - - promise.resolve(hasRegion); - } - - @Override - public void onError(String error) { - promise.reject("setPackObserver", error); - } - }); - }*/ - - /* - @ReactMethod - public void invalidatePack(final String name, final Promise promise) { - activateFileSource(); - - final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); - - offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() { - @Override - public void onList(OfflineRegion[] offlineRegions) { - OfflineRegion region = getRegionByName(name, offlineRegions); - - if (region == null) { - promise.resolve(null); - Log.w(REACT_CLASS, "invalidateRegion - Unknown offline region"); - return; - } - - region.invalidate(new OfflineRegion.OfflineRegionInvalidateCallback() { - @Override - public void onInvalidate() { - promise.resolve(null); - } - - @Override - public void onError(String error) { - promise.reject("invalidateRegion", error); - } - }); - } - - @Override - public void onError(String error) { - promise.reject("invalidateRegion", error); - } - }); - }*/ - - /* - @ReactMethod - public void deletePack(final String name, final Promise promise) { - activateFileSource(); - - final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); - - offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() { - @Override - public void onList(OfflineRegion[] offlineRegions) { - OfflineRegion region = getRegionByName(name, offlineRegions); - - if (region == null) { - promise.resolve(null); - Log.w(REACT_CLASS, "deleteRegion - Unknown offline region"); - return; - } - - // stop download before deleting (https://github.com/mapbox/mapbox-gl-native/issues/12382#issuecomment-431055103) - region.setDownloadState(INACTIVE_REGION_DOWNLOAD_STATE); - - region.delete(new OfflineRegion.OfflineRegionDeleteCallback() { - @Override - public void onDelete() { - promise.resolve(null); - } - - @Override - public void onError(String error) { - promise.reject("deleteRegion", error); - } - }); - } - - @Override - public void onError(String error) { - promise.reject("deleteRegion", error); - } - }); - }*/ - - @ReactMethod - public void pausePackDownload(final String name, final Promise promise) { - TileRegionPack pack = this.tileRegionPacks.get(name); - if (pack != null) { - if (pack.cancelable != null) { - pack.cancelable.cancel(); - pack.cancelable = null; - promise.resolve(null); - } else { - promise.reject("resumeRegionDownload", "Offline region cancelled already"); - } - } else { - promise.reject("resumeRegionDownload", "Unknown offline region"); - } - } - - @ReactMethod - public void resumePackDownload(final String name, final Promise promise) { - TileRegionPack pack = this.tileRegionPacks.get(name); - if (pack != null) { - startPackDownload(pack); - promise.resolve(null); - } else { - promise.reject("resumeRegionDownload", "Unknown offline region"); - } - } -/* - @ReactMethod - public void mergeOfflineRegions(final String path, final Promise promise) { - activateFileSource(); - - final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); - - offlineManager.mergeOfflineRegions(path, new OfflineManager.MergeOfflineRegionsCallback() { - @Override - public void onMerge(OfflineRegion[] offlineRegions) { - promise.resolve(null); - } - - @Override - public void onError(String error) { - promise.reject("mergeOfflineRegions", error); - } - }); - }*/ - - @ReactMethod - public void setTileCountLimit(int tileCountLimit) { - OfflineManager offlineManager = getOfflineManager(mReactContext); - //v10todo - //offlineManager.setOfflineMapboxTileCountLimit(tileCountLimit); - } - - @ReactMethod - public void setProgressEventThrottle(double eventThrottle) { - mProgressEventThrottle = eventThrottle; - } -/* - private OfflineRegionDefinition makeDefinition(LatLngBounds latLngBounds, ReadableMap options) { - return new OfflineTilePyramidRegionDefinition( - ConvertUtils.getString("styleURL", options, DEFAULT_STYLE_URL), - latLngBounds, - ConvertUtils.getDouble("minZoom", options, DEFAULT_MIN_ZOOM_LEVEL), - ConvertUtils.getDouble("maxZoom", options, DEFAULT_MAX_ZOOM_LEVEL), - mReactContext.getResources().getDisplayMetrics().density); - }*/ - - private byte[] getMetadataBytes(String metadata) { - byte[] metadataBytes = null; - - if (metadata == null || metadata.isEmpty()) { - return metadataBytes; - } - - try { - metadataBytes = metadata.getBytes("utf-8"); - } catch (UnsupportedEncodingException e) { - Log.w(REACT_CLASS, e.getLocalizedMessage()); - } - - return metadataBytes; - } - - /* - private void setOfflineRegionObserver(final String name, final OfflineRegion region) { - region.setObserver(new OfflineRegion.OfflineRegionObserver() { - OfflineRegionStatus prevStatus = null; - long timestamp = System.currentTimeMillis(); - - @Override - public void onStatusChanged(OfflineRegionStatus status) { - if (shouldSendUpdate(System.currentTimeMillis(), status)) { - sendEvent(makeStatusEvent(name, status)); - timestamp = System.currentTimeMillis(); - } - prevStatus = status; - } - - @Override - public void onError(OfflineRegionError error) { - sendEvent(makeErrorEvent(name, EventTypes.OFFLINE_ERROR, error.getMessage())); - } - - @Override - public void mapboxTileCountLimitExceeded(long limit) { - String message = String.format(Locale.getDefault(), "Mapbox tile limit exceeded %d", limit); - sendEvent(makeErrorEvent(name, EventTypes.OFFLINE_TILE_LIMIT, message)); - } - - private boolean shouldSendUpdate (long currentTimestamp, OfflineRegionStatus curStatus) { - if (prevStatus == null) { - return false; - } - - if (prevStatus.getDownloadState() != curStatus.getDownloadState()) { - return true; - } - - if (currentTimestamp - timestamp > mProgressEventThrottle) { - return true; - } - - return false; - } - }); - - region.setDownloadState(ACTIVE_REGION_DOWNLOAD_STATE); - }*/ - - private void sendEvent(IEvent event) { - RCTNativeAppEventEmitter eventEmitter = getEventEmitter(); - eventEmitter.emit(event.getKey(), event.toJSON()); - } - - private RCTNativeAppEventEmitter getEventEmitter() { - return mReactContext.getJSModule(RCTNativeAppEventEmitter.class); - } - - private OfflineEvent makeErrorEvent(String regionName, String errorType, String message) { - WritableMap payload = new WritableNativeMap(); - payload.putString("message", message); - payload.putString("name", regionName); - return new OfflineEvent(OFFLINE_ERROR, errorType, payload); - } - - - private OfflineEvent makeStatusEvent(String regionName, TileRegionLoadProgress status, TileRegionPack pack) { - return new OfflineEvent(OFFLINE_PROGRESS, EventTypes.OFFLINE_STATUS, makeRegionStatus(regionName, status, pack)); - } - - private WritableMap makeRegionStatus(String regionName, TileRegionLoadProgress status, TileRegionPack pack) { - WritableMap map = Arguments.createMap(); - double progressPercentage = ((double)status.getCompletedResourceCount()*100.0) / ((double)status.getRequiredResourceCount()); - - map.putString("name", regionName); - map.putString("state", pack.state); - map.putDouble("percentage", progressPercentage); - map.putInt("completedResourceCount", (int)status.getCompletedResourceCount()); - map.putInt("completedResourceSize", (int)status.getCompletedResourceSize()); - map.putInt("erroredResourceCount", (int)status.getErroredResourceCount()); - map.putInt("requiredResourceCount", (int)status.getRequiredResourceCount()); - map.putInt("loadedResourceCount", (int)status.getLoadedResourceCount()); - map.putInt("loadedResourceSize", (int)status.getLoadedResourceSize()); - - return map; - } - - private LatLngBounds getBoundsFromOptions(ReadableMap options) { - String featureCollectionJSONStr = ConvertUtils.getString("bounds", options, "{}"); - FeatureCollection featureCollection = FeatureCollection.fromJson(featureCollectionJSONStr); - return GeoJSONUtils.toLatLngBounds(featureCollection); - } - - private WritableMap fromOfflineRegion(LatLngBounds bounds, String metadataStr) { - WritableMap map = Arguments.createMap(); - map.putArray("bounds", GeoJSONUtils.fromLatLngBounds(bounds)); - map.putString("metadata", metadataStr); - return map; - } - - private WritableMap fromOfflineRegion(Geometry region) { - WritableMap map = Arguments.createMap(); - double[] bbox = TurfMeasurement.bbox(region); - - WritableArray bounds = Arguments.createArray(); - for (double d: bbox) { - bounds.pushDouble(d); - } - map.putArray("bounds", bounds); - map.putMap("geometry", GeoJSONUtils.fromGeometry(region)); - - //map.putString("metadata", new String(region.getMetadata())); - return map; - } -/* - private OfflineRegion getRegionByName(String name, OfflineRegion[] offlineRegions) { - if (name == null || name.isEmpty()) { - return null; - } - - for (OfflineRegion region : offlineRegions) { - boolean isRegion = false; - - try { - byte[] byteMetadata = region.getMetadata(); - - if (byteMetadata != null) { - JSONObject metadata = new JSONObject(new String(byteMetadata)); - isRegion = name.equals(metadata.getString("name")); - } - } catch (JSONException e) { - Log.w(REACT_CLASS, e.getLocalizedMessage()); - } - - if (isRegion) { - return region; - } - } - - return null; - }*/ - - /* - private void activateFileSource() { - FileSource fileSource = FileSource.getInstance(mReactContext); - fileSource.activate(); - }*/ -} diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLOfflineModule.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLOfflineModule.kt new file mode 100644 index 000000000..a9b19e89e --- /dev/null +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/modules/RCTMGLOfflineModule.kt @@ -0,0 +1,643 @@ +package com.mapbox.rctmgl.modules + +import android.util.Log +import com.facebook.react.bridge.* +import com.facebook.react.module.annotations.ReactModule +import com.mapbox.rctmgl.modules.RCTMGLOfflineModule +import com.mapbox.rctmgl.modules.TileRegionPack +import com.mapbox.maps.OfflineManager +import com.mapbox.rctmgl.utils.LatLngBounds +import com.mapbox.maps.TilesetDescriptorOptions +import com.mapbox.rctmgl.utils.GeoJSONUtils +import com.mapbox.bindgen.Expected +import com.mapbox.geojson.Geometry +import com.mapbox.rctmgl.events.IEvent +import com.facebook.react.modules.core.RCTNativeAppEventEmitter +import com.mapbox.bindgen.Value +import com.mapbox.rctmgl.events.OfflineEvent +import com.mapbox.common.* +import com.mapbox.rctmgl.events.constants.EventTypes +import com.mapbox.geojson.FeatureCollection +import com.mapbox.turf.TurfMeasurement +import com.mapbox.maps.ResourceOptions +import com.mapbox.rctmgl.modules.RCTMGLModule +import com.mapbox.rctmgl.utils.ConvertUtils +import com.mapbox.rctmgl.utils.Logger +import org.json.JSONException +import org.json.JSONObject +import java.io.UnsupportedEncodingException +import java.lang.Error +import java.util.ArrayList +import java.util.HashMap +import java.util.concurrent.CountDownLatch + +class TileRegionPack(var name: String, var progress: TileRegionLoadProgress?, var state: String) { + var cancelable: Cancelable? = null + var loadOptions: TileRegionLoadOptions? = null + + companion object { + val ACTIVE = "active" + val INACTIVE = "inactive" + val COMPLETE = "complete" + } +} + +@ReactModule(name = RCTMGLOfflineModule.REACT_CLASS) +class RCTMGLOfflineModule(private val mReactContext: ReactApplicationContext) : + ReactContextBaseJavaModule( + mReactContext + ) { + var tileRegionPacks = HashMap() + private var mProgressEventThrottle = 300.0 + override fun getName(): String { + return REACT_CLASS + } + + @ReactMethod + fun addListener(eventName: String?) { + // Set up any upstream listeners or background tasks as necessary + } + + @ReactMethod + fun removeListeners(count: Int?) { + // Remove upstream listeners, stop unnecessary background tasks + } + + @ReactMethod + @Throws(JSONException::class) + fun createPack(options: ReadableMap, promise: Promise) { + val name = ConvertUtils.getString("name", options, "") + val offlineManager = getOfflineManager(mReactContext) + val latLngBounds = getBoundsFromOptions(options) + val descriptorOptions = TilesetDescriptorOptions.Builder().styleURI( + (options.getString("styleURL"))!! + ).minZoom(options.getInt("minZoom").toByte()).maxZoom(options.getInt("maxZoom").toByte()) + .build() + val tilesetDescriptor = offlineManager!!.createTilesetDescriptor(descriptorOptions) + val descriptors = ArrayList() + descriptors.add(tilesetDescriptor) + val loadOptions = TileRegionLoadOptions.Builder() + .geometry(GeoJSONUtils.fromLatLngBoundsToPolygon(latLngBounds)) + .descriptors(descriptors) + .metadata(Value.valueOf((options.getString("metadata"))!!)) + .acceptExpired(true) + .networkRestriction(NetworkRestriction.NONE) + .build() + val metadataStr = options.getString("metadata") + val metadata = JSONObject(metadataStr) + val id = metadata.getString("name") + val pack = TileRegionPack(id, null, TileRegionPack.INACTIVE) + pack.loadOptions = loadOptions + tileRegionPacks[id] = pack + promise.resolve(fromOfflineRegion(latLngBounds, metadataStr)) + startPackDownload(pack) + } + + fun startPackDownload(pack: TileRegionPack) { + val _this = this + pack.cancelable = getTileStore()!! + .loadTileRegion( + pack.name, + (pack.loadOptions)!!, + TileRegionLoadProgressCallback { progress -> + pack.progress = progress + pack.state = TileRegionPack.ACTIVE + _this.sendEvent(_this.makeStatusEvent(pack.name, progress, pack)) + }, + object : TileRegionCallback { + override fun run(region: Expected) { + pack.cancelable = null + if (region.isError) { + pack.state = TileRegionPack.INACTIVE + _this.sendEvent( + _this.makeErrorEvent( + pack.name, "TileRegionError", region.error!! + .message + ) + ) + } else { + pack.state = TileRegionPack.COMPLETE + _this.sendEvent(_this.makeStatusEvent(pack.name, pack.progress, pack)) + } + } + }) + } + + @ReactMethod + fun getPacks(promise: Promise) { + getTileStore()!!.getAllTileRegions(object : TileRegionsCallback { + override fun run(regions: Expected>) { + UiThreadUtil.runOnUiThread(object : Runnable { + override fun run() { + if (regions.isValue) { + convertRegionsToJSON((regions.value)!!, promise) + } else { + promise.reject("getPacks", regions.error!!.message) + } + } + }) + } + }) + } + + private fun convertRegionsToJSON(tileRegions: List, promise: Promise) { + val countDownLatch = CountDownLatch(tileRegions.size) + val errors = ArrayList() + val geometries = ArrayList() + try { + for (region: TileRegion in tileRegions) { + getTileStore()!! + .getTileRegionGeometry(region.id, object : TileRegionGeometryCallback { + override fun run(result: Expected) { + if (result.isValue) { + geometries.add(result.value) + } else { + errors.add(result.error) + } + countDownLatch.countDown() + } + }) + } + } catch (error: Error) { + Logger.e("OS", "a") + } + try { + countDownLatch.await() + val result = Arguments.createArray() + for (geometry: Geometry? in geometries) { + result.pushMap(fromOfflineRegion(geometry)) + } + for (error: TileRegionError? in errors) { + val errorMap = Arguments.createMap() + errorMap.putString("type", "error") + errorMap.putString("message", error!!.message) + errorMap.putString("errorType", error.type.toString()) + result.pushMap(errorMap) + } + promise.resolve( + result + ) + } catch (interruptedException: InterruptedException) { + promise.reject(interruptedException) + } + } + + /* + @ReactMethod + public void invalidateAmbientCache(final Promise promise) { + activateFileSource(); + final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); + offlineManager.invalidateAmbientCache(new OfflineManager.FileSourceCallback() { + @Override + public void onSuccess() { + promise.resolve(null); + } + + @Override + public void onError(String error) { + promise.reject("invalidateAmbientCache", error); + } + }); + } + + @ReactMethod + public void clearAmbientCache(final Promise promise) { + activateFileSource(); + + final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); + + offlineManager.clearAmbientCache(new OfflineManager.FileSourceCallback() { + @Override + public void onSuccess() { + promise.resolve(null); + } + + @Override + public void onError(String error) { + promise.reject("clearAmbientCache", error); + } + }); + } + + @ReactMethod + public void setMaximumAmbientCacheSize(int size, final Promise promise) { + activateFileSource(); + + final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); + + offlineManager.setMaximumAmbientCacheSize(size, new OfflineManager.FileSourceCallback() { + @Override + public void onSuccess() { + promise.resolve(null); + } + + @Override + public void onError(String error) { + promise.reject("setMaximumAmbientCacheSize", error); + } + }); + }*/ + /* + @ReactMethod + public void resetDatabase(final Promise promise) { + activateFileSource(); + final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); + offlineManager.resetDatabase(new OfflineManager.FileSourceCallback() { + @Override + public void onSuccess() { + promise.resolve(null); + } + + @Override + public void onError(String error) { + promise.reject("resetDatabase", error); + } + }); + }*/ + @ReactMethod + fun getPackStatus(name: String, promise: Promise) { + val pack = tileRegionPacks[name] + if (pack != null) { + promise.resolve(makeRegionStatus(name, pack.progress, pack)) + } else { + promise.reject(Error("Pack not found")) + Logger.w(REACT_CLASS, "getPackStatus - Unknown offline region") + } + } + + /* + @ReactMethod + public void setPackObserver(final String name, final Promise promise) { + activateFileSource(); + + final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); + + offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() { + @Override + public void onList(OfflineRegion[] offlineRegions) { + OfflineRegion region = getRegionByName(name, offlineRegions); + boolean hasRegion = region != null; + + if (hasRegion) { + setOfflineRegionObserver(name, region); + } + + promise.resolve(hasRegion); + } + + @Override + public void onError(String error) { + promise.reject("setPackObserver", error); + } + }); + }*/ + /* + @ReactMethod + public void invalidatePack(final String name, final Promise promise) { + activateFileSource(); + + final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); + + offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() { + @Override + public void onList(OfflineRegion[] offlineRegions) { + OfflineRegion region = getRegionByName(name, offlineRegions); + + if (region == null) { + promise.resolve(null); + Log.w(REACT_CLASS, "invalidateRegion - Unknown offline region"); + return; + } + + region.invalidate(new OfflineRegion.OfflineRegionInvalidateCallback() { + @Override + public void onInvalidate() { + promise.resolve(null); + } + + @Override + public void onError(String error) { + promise.reject("invalidateRegion", error); + } + }); + } + + @Override + public void onError(String error) { + promise.reject("invalidateRegion", error); + } + }); + }*/ + /* + @ReactMethod + public void deletePack(final String name, final Promise promise) { + activateFileSource(); + + final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); + + offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() { + @Override + public void onList(OfflineRegion[] offlineRegions) { + OfflineRegion region = getRegionByName(name, offlineRegions); + + if (region == null) { + promise.resolve(null); + Log.w(REACT_CLASS, "deleteRegion - Unknown offline region"); + return; + } + + // stop download before deleting (https://github.com/mapbox/mapbox-gl-native/issues/12382#issuecomment-431055103) + region.setDownloadState(INACTIVE_REGION_DOWNLOAD_STATE); + + region.delete(new OfflineRegion.OfflineRegionDeleteCallback() { + @Override + public void onDelete() { + promise.resolve(null); + } + + @Override + public void onError(String error) { + promise.reject("deleteRegion", error); + } + }); + } + + @Override + public void onError(String error) { + promise.reject("deleteRegion", error); + } + }); + }*/ + @ReactMethod + fun pausePackDownload(name: String, promise: Promise) { + val pack = tileRegionPacks[name] + if (pack != null) { + if (pack.cancelable != null) { + pack.cancelable!!.cancel() + pack.cancelable = null + promise.resolve(null) + } else { + promise.reject("resumeRegionDownload", "Offline region cancelled already") + } + } else { + promise.reject("resumeRegionDownload", "Unknown offline region") + } + } + + @ReactMethod + fun resumePackDownload(name: String, promise: Promise) { + val pack = tileRegionPacks[name] + if (pack != null) { + startPackDownload(pack) + promise.resolve(null) + } else { + promise.reject("resumeRegionDownload", "Unknown offline region") + } + } + + /* + @ReactMethod + public void mergeOfflineRegions(final String path, final Promise promise) { + activateFileSource(); + + final OfflineManager offlineManager = OfflineManager.getInstance(mReactContext); + + offlineManager.mergeOfflineRegions(path, new OfflineManager.MergeOfflineRegionsCallback() { + @Override + public void onMerge(OfflineRegion[] offlineRegions) { + promise.resolve(null); + } + + @Override + public void onError(String error) { + promise.reject("mergeOfflineRegions", error); + } + }); + }*/ + @ReactMethod + fun setTileCountLimit(tileCountLimit: Int) { + val offlineManager = getOfflineManager(mReactContext) + //v10todo + //offlineManager.setOfflineMapboxTileCountLimit(tileCountLimit); + } + + @ReactMethod + fun setProgressEventThrottle(eventThrottle: Double) { + mProgressEventThrottle = eventThrottle + } + + /* + private OfflineRegionDefinition makeDefinition(LatLngBounds latLngBounds, ReadableMap options) { + return new OfflineTilePyramidRegionDefinition( + ConvertUtils.getString("styleURL", options, DEFAULT_STYLE_URL), + latLngBounds, + ConvertUtils.getDouble("minZoom", options, DEFAULT_MIN_ZOOM_LEVEL), + ConvertUtils.getDouble("maxZoom", options, DEFAULT_MAX_ZOOM_LEVEL), + mReactContext.getResources().getDisplayMetrics().density); + }*/ + private fun getMetadataBytes(metadata: String?): ByteArray? { + var metadataBytes: ByteArray? = null + if (metadata == null || metadata.isEmpty()) { + return metadataBytes + } + try { + metadataBytes = metadata.toByteArray(charset("utf-8")) + } catch (e: UnsupportedEncodingException) { + Log.w(REACT_CLASS, e.localizedMessage) + } + return metadataBytes + } + + /* + private void setOfflineRegionObserver(final String name, final OfflineRegion region) { + region.setObserver(new OfflineRegion.OfflineRegionObserver() { + OfflineRegionStatus prevStatus = null; + long timestamp = System.currentTimeMillis(); + + @Override + public void onStatusChanged(OfflineRegionStatus status) { + if (shouldSendUpdate(System.currentTimeMillis(), status)) { + sendEvent(makeStatusEvent(name, status)); + timestamp = System.currentTimeMillis(); + } + prevStatus = status; + } + + @Override + public void onError(OfflineRegionError error) { + sendEvent(makeErrorEvent(name, EventTypes.OFFLINE_ERROR, error.getMessage())); + } + + @Override + public void mapboxTileCountLimitExceeded(long limit) { + String message = String.format(Locale.getDefault(), "Mapbox tile limit exceeded %d", limit); + sendEvent(makeErrorEvent(name, EventTypes.OFFLINE_TILE_LIMIT, message)); + } + + private boolean shouldSendUpdate (long currentTimestamp, OfflineRegionStatus curStatus) { + if (prevStatus == null) { + return false; + } + + if (prevStatus.getDownloadState() != curStatus.getDownloadState()) { + return true; + } + + if (currentTimestamp - timestamp > mProgressEventThrottle) { + return true; + } + + return false; + } + }); + + region.setDownloadState(ACTIVE_REGION_DOWNLOAD_STATE); + }*/ + private fun sendEvent(event: IEvent) { + val eventEmitter = eventEmitter + eventEmitter.emit(event.key, event.toJSON()) + } + + private val eventEmitter: RCTNativeAppEventEmitter + private get() = mReactContext.getJSModule(RCTNativeAppEventEmitter::class.java) + + private fun makeErrorEvent( + regionName: String, + errorType: String, + message: String + ): OfflineEvent { + val payload: WritableMap = WritableNativeMap() + payload.putString("message", message) + payload.putString("name", regionName) + return OfflineEvent(OFFLINE_ERROR, errorType, payload) + } + + private fun makeStatusEvent( + regionName: String, + status: TileRegionLoadProgress?, + pack: TileRegionPack + ): OfflineEvent { + return OfflineEvent( + OFFLINE_PROGRESS, + EventTypes.OFFLINE_STATUS, + makeRegionStatus(regionName, status, pack) + ) + } + + private fun makeRegionStatus( + regionName: String, + status: TileRegionLoadProgress?, + pack: TileRegionPack + ): WritableMap { + val map = Arguments.createMap() + val progressPercentage = + (status!!.completedResourceCount.toDouble() * 100.0) / (status.requiredResourceCount.toDouble()) + map.putString("name", regionName) + map.putString("state", pack.state) + map.putDouble("percentage", progressPercentage) + map.putInt("completedResourceCount", status.completedResourceCount.toInt()) + map.putInt("completedResourceSize", status.completedResourceSize.toInt()) + map.putInt("erroredResourceCount", status.erroredResourceCount.toInt()) + map.putInt("requiredResourceCount", status.requiredResourceCount.toInt()) + map.putInt("loadedResourceCount", status.loadedResourceCount.toInt()) + map.putInt("loadedResourceSize", status.loadedResourceSize.toInt()) + return map + } + + private fun getBoundsFromOptions(options: ReadableMap): LatLngBounds { + val featureCollectionJSONStr = ConvertUtils.getString("bounds", options, "{}") + val featureCollection = FeatureCollection.fromJson(featureCollectionJSONStr) + return GeoJSONUtils.toLatLngBounds(featureCollection) + } + + private fun fromOfflineRegion(bounds: LatLngBounds, metadataStr: String?): WritableMap { + val map = Arguments.createMap() + map.putArray("bounds", GeoJSONUtils.fromLatLngBounds(bounds)) + map.putString("metadata", metadataStr) + return map + } + + private fun fromOfflineRegion(region: Geometry?): WritableMap { + val map = Arguments.createMap() + val bbox = TurfMeasurement.bbox(region) + val bounds = Arguments.createArray() + for (d: Double in bbox) { + bounds.pushDouble(d) + } + map.putArray("bounds", bounds) + map.putMap("geometry", GeoJSONUtils.fromGeometry(region)) + + //map.putString("metadata", new String(region.getMetadata())); + return map + } /* + private OfflineRegion getRegionByName(String name, OfflineRegion[] offlineRegions) { + if (name == null || name.isEmpty()) { + return null; + } + + for (OfflineRegion region : offlineRegions) { + boolean isRegion = false; + + try { + byte[] byteMetadata = region.getMetadata(); + + if (byteMetadata != null) { + JSONObject metadata = new JSONObject(new String(byteMetadata)); + isRegion = name.equals(metadata.getString("name")); + } + } catch (JSONException e) { + Log.w(REACT_CLASS, e.getLocalizedMessage()); + } + + if (isRegion) { + return region; + } + } + + return null; + }*/ + + /* + private void activateFileSource() { + FileSource fileSource = FileSource.getInstance(mReactContext); + fileSource.activate(); + }*/ + companion object { + const val REACT_CLASS = "RCTMGLOfflineModule" + @JvmField + val INACTIVE_REGION_DOWNLOAD_STATE = TileRegionPack.INACTIVE + @JvmField + val ACTIVE_REGION_DOWNLOAD_STATE = TileRegionPack.ACTIVE + @JvmField + val COMPLETE_REGION_DOWNLOAD_STATE = TileRegionPack.COMPLETE + @JvmField + val OFFLINE_ERROR = "MapboxOfflineRegionError" + @JvmField + val OFFLINE_PROGRESS = "MapboxOfflineRegionProgress" + + // public static final String DEFAULT_STYLE_URL = Style.MAPBOX_STREETS; + val DEFAULT_MIN_ZOOM_LEVEL = 10.0 + val DEFAULT_MAX_ZOOM_LEVEL = 20.0 + var offlineManager: OfflineManager? = null + var _tileStore: TileStore? = null + fun getOfflineManager(mReactContext: ReactApplicationContext?): OfflineManager? { + if (offlineManager == null) { + offlineManager = OfflineManager( + ResourceOptions.Builder() + .accessToken(RCTMGLModule.getAccessToken(mReactContext)).tileStore( + getTileStore() + ).build() + ) + } + return offlineManager + } + + fun getTileStore(): TileStore? { + if (_tileStore == null) { + _tileStore = TileStore.create() + return _tileStore + } + return _tileStore + } + } +} \ No newline at end of file diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/Point.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/Point.kt new file mode 100644 index 000000000..052acabf3 --- /dev/null +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/Point.kt @@ -0,0 +1,13 @@ +package com.mapbox.rctmgl.utils.extensions + +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.WritableNativeArray +import com.mapbox.geojson.Point +import com.mapbox.rctmgl.utils.Logger + +fun Point.toReadableArray() : ReadableArray { + val array = WritableNativeArray() + array.pushDouble(this.longitude()) + array.pushDouble(this.latitude()) + return array +} \ No newline at end of file diff --git a/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/ReadableArray.kt b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/ReadableArray.kt new file mode 100644 index 000000000..ba69db180 --- /dev/null +++ b/android/rctmgl/src/main/java-v10/com/mapbox/rctmgl/utils/extensions/ReadableArray.kt @@ -0,0 +1,23 @@ +package com.mapbox.rctmgl.utils.extensions + +import com.facebook.react.bridge.ReadableArray +import com.mapbox.geojson.Point +import com.mapbox.maps.ScreenCoordinate +import com.mapbox.rctmgl.utils.Logger + +fun ReadableArray.toCoordinate() : Point { + if (this.size() != 2) { + Logger.e("ReadableArray.toCoordinate","Cannot convert $this to point, 2 coordinates are required") + } + return Point.fromLngLat( + getDouble(0), + getDouble(1) + ) +} + +fun ReadableArray.toScreenCoordinate() : ScreenCoordinate { + if (this.size() != 2) { + Logger.e("ReadableArray.toCoordinate","Cannot convert $this to point, 2 coordinates are required") + } + return ScreenCoordinate(getDouble(0), getDouble(1)) +} \ No newline at end of file diff --git a/docs/Camera.md b/docs/Camera.md index 994654a5b..8e45af81c 100644 --- a/docs/Camera.md +++ b/docs/Camera.md @@ -13,12 +13,8 @@ | zoomLevel | `number` | `none` | `false` | The zoom level of the map. | | padding | `CameraPadding` | `none` | `false` | The viewport padding in points. | | animationDuration | `number` | `none` | `false` | The duration the map takes to animate to a new configuration. | -| animationMode | `\| 'flyTo' -\| 'easeTo' -\| 'linearTo' -\| 'moveTo' -\| 'none'` | `none` | `false` | The easing or path the camera uses to animate to a new configuration. | -| followUserMode | `'normal' \| 'compass' \| 'course'` | `none` | `false` | The mode used to track the user location on the map. | +| animationMode | `CameraAnimationMode` | `none` | `false` | The easing or path the camera uses to animate to a new configuration. | +| followUserMode | `UserTrackingMode` | `none` | `false` | The mode used to track the user location on the map. | | followUserLocation | `boolean` | `none` | `false` | Whether the map orientation follows the user location. | | followZoomLevel | `number` | `none` | `false` | The zoom level used when following the user location. | | followPitch | `number` | `none` | `false` | The pitch used when following the user location. | @@ -29,7 +25,7 @@ | defaultSettings | `CameraStop` | `none` | `false` | The configuration that the camera falls back on, if no other values are specified. | | allowUpdates | `boolean` | `none` | `false` | Whether the camera should send any configuration to the native module. Prevents unnecessary tile
fetching and improves performance when the map is not visible. Defaults to `true`. | | triggerKey | `string \| number` | `none` | `false` | Any arbitrary primitive value that, when changed, causes the camera to retry moving to its target
configuration. (Not yet implemented.) | -| onUserTrackingModeChange | `(event:MapboxGLEvent<'usertrackingmodechange',{followUserLocation:boolean;followUserMode:UserTrackingMode\|null;}>,)=>void` | `none` | `false` | Executes when user tracking mode changes. | +| onUserTrackingModeChange | `UserTrackingModeChangeCallback` | `none` | `false` | Executes when user tracking mode changes. | ### methods #### setCamera(config) @@ -50,7 +46,7 @@ camera.current?.setCamera({ ``` -#### fitBounds(ne, sw, paddingConfig, animationDuration) +#### fitBounds(ne, sw, paddingConfig, _animationDuration) Set the camera position to enclose the provided bounds, with optional
padding and duration. @@ -60,7 +56,7 @@ Set the camera position to enclose the provided bounds, with optional
paddin | `ne` | `Position` | `Yes` | Northeast coordinate of bounding box | | `sw` | `Position` | `Yes` | Southwest coordinate of bounding box | | `paddingConfig` | `number \| Array` | `Yes` | The viewport padding, specified as a number (all sides equal), a 2-item array ([vertical, horizontal]), or a 4-item array ([top, right, bottom, left]) | -| `animationDuration` | `number` | `Yes` | The transition duration | +| `_animationDuration` | `n/a` | `Yes` | undefined | @@ -70,15 +66,15 @@ camera.fitBounds([lon, lat], [lon, lat], [20, 0], 1000); ``` -#### flyTo(centerCoordinate, animationDuration) +#### flyTo(_centerCoordinate, _animationDuration) Sets the camera to center around the provided coordinate using a realistic 'travel'
animation, with optional duration. ##### arguments | Name | Type | Required | Description | | ---- | :--: | :------: | :----------: | -| `centerCoordinate` | `Position` | `Yes` | The coordinate to center in the view | -| `animationDuration` | `number` | `Yes` | The transition duration | +| `_centerCoordinate` | `n/a` | `Yes` | undefined | +| `_animationDuration` | `n/a` | `Yes` | undefined | @@ -88,15 +84,15 @@ camera.flyTo([lon, lat], 12000); ``` -#### moveTo(centerCoordinate, animationDuration) +#### moveTo(_centerCoordinate, _animationDuration) Sets the camera to center around the provided coordinate, with optional duration. ##### arguments | Name | Type | Required | Description | | ---- | :--: | :------: | :----------: | -| `centerCoordinate` | `Position` | `Yes` | The coordinate to center in the view | -| `animationDuration` | `number` | `Yes` | The transition duration | +| `_centerCoordinate` | `n/a` | `Yes` | undefined | +| `_animationDuration` | `n/a` | `Yes` | undefined | @@ -106,15 +102,15 @@ camera.moveTo([lon, lat]); ``` -#### zoomTo(zoomLevel, animationDuration) +#### zoomTo(_zoomLevel, _animationDuration) Zooms the camera to the provided level, with optional duration. ##### arguments | Name | Type | Required | Description | | ---- | :--: | :------: | :----------: | -| `zoomLevel` | `number` | `Yes` | The target zoom | -| `animationDuration` | `number` | `Yes` | The transition duration | +| `_zoomLevel` | `n/a` | `Yes` | undefined | +| `_animationDuration` | `n/a` | `Yes` | undefined | diff --git a/docs/MapView.md b/docs/MapView.md index 30eb074be..dd5f3d336 100644 --- a/docs/MapView.md +++ b/docs/MapView.md @@ -1,6 +1,6 @@ ## -### MapView backed by Mapbox Native GL +### MapView backed by Mapbox Native GL. ### props | Prop | Type | Default | Required | Description | @@ -16,7 +16,7 @@ | pitchEnabled | `bool` | `true` | `false` | Enable/Disable pitch on map | | rotateEnabled | `bool` | `true` | `false` | Enable/Disable rotation on map | | attributionEnabled | `bool` | `true` | `false` | The Mapbox terms of service, which governs the use of Mapbox-hosted vector tiles and styles,
[requires](https://www.mapbox.com/help/how-attribution-works/) these copyright notices to accompany any map that features Mapbox-designed styles, OpenStreetMap data, or other Mapbox data such as satellite or terrain data.
If that applies to this map view, do not hide this view or remove any notices from it.

You are additionally [required](https://www.mapbox.com/help/how-mobile-apps-work/#telemetry) to provide users with the option to disable anonymous usage and location sharing (telemetry).
If this view is hidden, you must implement this setting elsewhere in your app. See our website for [Android](https://www.mapbox.com/android-docs/map-sdk/overview/#telemetry-opt-out) and [iOS](https://www.mapbox.com/ios-sdk/#telemetry_opt_out) for implementation details.

Enable/Disable attribution on map. For iOS you need to add MGLMapboxMetricsEnabledSettingShownInApp=YES
to your Info.plist | -| attributionPosition | `custom` | `none` | `false` | Adds attribution offset, e.g. `{top: 8, left: 8}` will put attribution button in top-left corner of the map | +| attributionPosition | `custom` | `none` | `false` | Adds attribution offset, e.g. `{top: 8, left: 8}` will put attribution button in top-left corner of the map. By default on Android, the attribution with information icon (i) will be on the bottom left, while on iOS the mapbox logo will be on bottom left with information icon (i) on bottom right. Read more about mapbox attribution [here](https://docs.mapbox.com/help/getting-started/attribution/) | | tintColor | `union` | `none` | `false` | MapView's tintColor | | logoEnabled | `bool` | `true` | `false` | Enable/Disable the logo on the map. | | logoPosition | `custom` | `none` | `false` | Adds logo offset, e.g. `{top: 8, left: 8}` will put the logo in top-left corner of the map | diff --git a/docs/PointAnnotation.md b/docs/PointAnnotation.md index ae115b7f4..8ac5ff12f 100644 --- a/docs/PointAnnotation.md +++ b/docs/PointAnnotation.md @@ -1,6 +1,6 @@ ## -### PointAnnotation represents a one-dimensional shape located at a single geographical coordinate.

Consider using ShapeSource and SymbolLayer instead, if you have many points and you have static images,
they'll offer much better performance.

If you need interactive views please use MarkerView,
as with PointAnnotation on Android child views are rendered onto a bitmap for better performance. +### PointAnnotation represents a one-dimensional shape located at a single geographical coordinate.

Consider using ShapeSource and SymbolLayer instead, if you have many points and you have static images,
they'll offer much better performance.

If you need interactive views, please use MarkerView, as with PointAnnotation on Android, child views
are rendered onto a bitmap for better performance. ### props | Prop | Type | Default | Required | Description | diff --git a/docs/ShapeSource.md b/docs/ShapeSource.md index 07bf91389..1bd02fbec 100644 --- a/docs/ShapeSource.md +++ b/docs/ShapeSource.md @@ -1,6 +1,6 @@ ## -### ShapeSource is a map content source that supplies vector shapes to be shown on the map.
The shape may be a url or a GeoJSON object +### ShapeSource is a map content source that supplies vector shapes to be shown on the map.
The shape may be a url or a GeoJSON object. ### props | Prop | Type | Default | Required | Description | diff --git a/docs/VectorSource.md b/docs/VectorSource.md index 8992679c3..a47f6ce04 100644 --- a/docs/VectorSource.md +++ b/docs/VectorSource.md @@ -1,6 +1,6 @@ ## -### VectorSource is a map content source that supplies tiled vector data in Mapbox Vector Tile format to be shown on the map.
The location of and metadata about the tiles are defined either by an option dictionary or by an external file that conforms to the TileJSON specification. +### VectorSource is a map content source that supplies tiled vector data in Mapbox Vector Tile format to be shown
on the map. The location of and metadata about the tiles are defined either by an option dictionary or by an
external file that conforms to the TileJSON specification. ### props | Prop | Type | Default | Required | Description | diff --git a/docs/docs.json b/docs/docs.json index ce689f185..451876232 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -262,12 +262,8 @@ "optional": false }, { - "name": "animationDuration", - "description": "The transition duration", - "type": { - "name": "number" - }, - "optional": false + "name": "_animationDuration", + "type": {} } ], "returns": null, @@ -282,20 +278,13 @@ "modifiers": [], "params": [ { - "name": "centerCoordinate", - "description": "The coordinate to center in the view", - "type": { - "name": "Position" - }, - "optional": false + "name": "_centerCoordinate", + "optional": false, + "type": {} }, { - "name": "animationDuration", - "description": "The transition duration", - "type": { - "name": "number" - }, - "optional": false + "name": "_animationDuration", + "type": {} } ], "returns": null, @@ -310,20 +299,13 @@ "modifiers": [], "params": [ { - "name": "centerCoordinate", - "description": "The coordinate to center in the view", - "type": { - "name": "Position" - }, - "optional": false + "name": "_centerCoordinate", + "optional": false, + "type": {} }, { - "name": "animationDuration", - "description": "The transition duration", - "type": { - "name": "number" - }, - "optional": false + "name": "_animationDuration", + "type": {} } ], "returns": null, @@ -338,20 +320,13 @@ "modifiers": [], "params": [ { - "name": "zoomLevel", - "description": "The target zoom", - "type": { - "name": "number" - }, - "optional": false + "name": "_zoomLevel", + "optional": false, + "type": {} }, { - "name": "animationDuration", - "description": "The transition duration", - "type": { - "name": "number" - }, - "optional": false + "name": "_animationDuration", + "type": {} } ], "returns": null, @@ -421,14 +396,14 @@ { "name": "animationMode", "required": false, - "type": "\\| 'flyTo'\n\\| 'easeTo'\n\\| 'linearTo'\n\\| 'moveTo'\n\\| 'none'", + "type": "CameraAnimationMode", "default": "none", "description": "The easing or path the camera uses to animate to a new configuration." }, { "name": "followUserMode", "required": false, - "type": "'normal' \\| 'compass' \\| 'course'", + "type": "UserTrackingMode", "default": "none", "description": "The mode used to track the user location on the map." }, @@ -505,7 +480,7 @@ { "name": "onUserTrackingModeChange", "required": false, - "type": "(event:MapboxGLEvent<'usertrackingmodechange',{followUserLocation:boolean;followUserMode:UserTrackingMode\\|null;}>,)=>void", + "type": "UserTrackingModeChangeCallback", "default": "none", "description": "Executes when user tracking mode changes." } @@ -2256,7 +2231,7 @@ ] }, "MapView": { - "description": "MapView backed by Mapbox Native GL", + "description": "MapView backed by Mapbox Native GL.", "displayName": "MapView", "methods": [ { @@ -2646,7 +2621,7 @@ "required": false, "type": "custom", "default": "none", - "description": "Adds attribution offset, e.g. `{top: 8, left: 8}` will put attribution button in top-left corner of the map" + "description": "Adds attribution offset, e.g. `{top: 8, left: 8}` will put attribution button in top-left corner of the map. By default on Android, the attribution with information icon (i) will be on the bottom left, while on iOS the mapbox logo will be on bottom left with information icon (i) on bottom right. Read more about mapbox attribution [here](https://docs.mapbox.com/help/getting-started/attribution/)" }, { "name": "tintColor", @@ -2992,7 +2967,7 @@ "name": "NativeUserLocation" }, "PointAnnotation": { - "description": "PointAnnotation represents a one-dimensional shape located at a single geographical coordinate.\n\nConsider using ShapeSource and SymbolLayer instead, if you have many points and you have static images,\nthey'll offer much better performance.\n\nIf you need interactive views please use MarkerView,\nas with PointAnnotation on Android child views are rendered onto a bitmap for better performance.", + "description": "PointAnnotation represents a one-dimensional shape located at a single geographical coordinate.\n\nConsider using ShapeSource and SymbolLayer instead, if you have many points and you have static images,\nthey'll offer much better performance.\n\nIf you need interactive views, please use MarkerView, as with PointAnnotation on Android, child views\nare rendered onto a bitmap for better performance.", "displayName": "PointAnnotation", "methods": [ { @@ -3509,7 +3484,7 @@ "name": "RasterSource" }, "ShapeSource": { - "description": "ShapeSource is a map content source that supplies vector shapes to be shown on the map.\nThe shape may be a url or a GeoJSON object", + "description": "ShapeSource is a map content source that supplies vector shapes to be shown on the map.\nThe shape may be a url or a GeoJSON object.", "displayName": "ShapeSource", "methods": [ { @@ -5648,7 +5623,7 @@ "name": "UserLocation" }, "VectorSource": { - "description": "VectorSource is a map content source that supplies tiled vector data in Mapbox Vector Tile format to be shown on the map.\nThe location of and metadata about the tiles are defined either by an option dictionary or by an external file that conforms to the TileJSON specification.", + "description": "VectorSource is a map content source that supplies tiled vector data in Mapbox Vector Tile format to be shown\non the map. The location of and metadata about the tiles are defined either by an option dictionary or by an\nexternal file that conforms to the TileJSON specification.", "displayName": "VectorSource", "methods": [ { diff --git a/example/package.json b/example/package.json index 405f00752..054b3c175 100644 --- a/example/package.json +++ b/example/package.json @@ -70,7 +70,7 @@ "simulator": { "type": "ios.simulator", "device": { - "type": "iPhone 11" + "type": "iPhone SE (2nd generation)" } } }, diff --git a/example/src/examples/Annotations/CustomCallout.tsx b/example/src/examples/Annotations/CustomCallout.tsx index 4a550150e..99a83dfe6 100644 --- a/example/src/examples/Annotations/CustomCallout.tsx +++ b/example/src/examples/Annotations/CustomCallout.tsx @@ -1,7 +1,7 @@ import React, { FC, useState } from 'react'; import MapboxGL, { SymbolLayerStyle } from '@rnmapbox/maps'; -import { Feature } from '@turf/helpers/dist/js'; import { View, Text, ViewStyle, StyleProp, TextStyle } from 'react-native'; +import { Feature, FeatureCollection, Point } from 'geojson'; // @ts-ignore import exampleIcon from '../../assets/pin.png'; @@ -13,7 +13,7 @@ const defaultCamera = { zoomLevel: 17.4, }; -const featureCollection = { +const featureCollection: FeatureCollection = { type: 'FeatureCollection', features: [ { @@ -50,7 +50,7 @@ type CustomCalloutProps = { const CustomCallout: FC = (props) => { const [selectedFeature, setSelectedFeature] = - useState>(); + useState>(); const onPinPress = (e: any): void => { if (selectedFeature) { diff --git a/example/src/examples/V10/CameraAnimation.tsx b/example/src/examples/V10/CameraAnimation.tsx index d450446a3..99a9ed244 100644 --- a/example/src/examples/V10/CameraAnimation.tsx +++ b/example/src/examples/V10/CameraAnimation.tsx @@ -5,6 +5,9 @@ import MapboxGL, { CameraAnimationMode, CameraRef, Camera, + MapView, + ShapeSource, + CircleLayer, } from '@rnmapbox/maps'; import bbox from '@turf/bbox'; import { Feature, LineString, Point, Position } from '@turf/helpers'; @@ -82,6 +85,7 @@ const CameraAnimation = (props: any) => { const [inputKind, setInputKind] = useState<'declarative' | 'imperative'>( 'declarative', ); + const camera = useRef(null); const [animationMode, setAnimationMode] = useState('moveTo'); @@ -271,7 +275,7 @@ const CameraAnimation = (props: any) => { return ( - + { features.map((f) => { const id = JSON.stringify(f.geometry.coordinates); return ( - - + - + ); })} - + diff --git a/index.ts b/index.ts index 88c2c2030..ed19d3637 100644 --- a/index.ts +++ b/index.ts @@ -1,14 +1,74 @@ -import type { ReactNode, SyntheticEvent } from 'react'; -import type { - ViewProps, - ViewStyle, - StyleProp, - TextStyle, - ImageSourcePropType, -} from 'react-native'; import { NativeModules, PermissionsAndroid } from 'react-native'; -import type { Units, BBox, Id } from '@turf/helpers'; +import { + OfflineProgressStatus, + OfflineProgressError, + OfflinePack, + OfflinePackStatus, + OrnamentPosition, + RegionPayload, + MapState, + MapViewProps, + UserLocationProps, + WithExpression, + LightStyle, + Transition, + BackgroundLayerStyle, + CircleLayerStyle, + FillExtrusionLayerStyle, + FillLayerStyle, + LineLayerStyle, + RasterLayerStyle, + TextVariableAnchorValues, + SymbolLayerStyle, + HeatmapLayerStyle, + LightProps, + PointAnnotationProps, + MarkerViewProps, + StyleProps, + CalloutProps, + TileSourceProps, + VectorSourceProps, + ShapeSourceProps, + RasterSourceProps, + LayerBaseProps, + BackgroundLayerProps, + CircleLayerProps, + FillExtrusionLayerProps, + FillLayerProps, + LineLayerProps, + RasterLayerProps, + SymbolLayerProps, + HeatmapLayerProps, + ImagesProps, + ImageSourceProps, + OfflineCreatePackOptions, + SnapshotOptions, + LogLevel, + LogObject, + LogCallback, + Location, + Coordinates, + Padding, + ExpressionName, + ExpressionField, + Expression, + Anchor, + Visibility, + Alignment, + AutoAlignment, + NamedStyles, + MapboxGLEvent, + OnPressEvent, + UnitsOptions, + PositionsOptions, + StyleURLKey, + SkyLayerProps, + SkyLayerStyle, + InterpolationMode, + StyleURL, + CameraAnimationMode, +} from './javascript/types'; import { MapView, Light, @@ -35,15 +95,16 @@ import { MarkerView, Style, } from './javascript/components'; -import Camera, { - CameraAnimationMode, - CameraProps, - CameraRef, - UserTrackingMode, -} from './javascript/components/Camera'; -import locationManager from './javascript/modules/location/locationManager'; -import offlineManager from './javascript/modules/offline/offlineManager'; -import snapshotManager from './javascript/modules/snapshot/snapshotManager'; +import Camera, { CameraProps, CameraRef } from './javascript/components/Camera'; +import locationManager, { + LocationManager, +} from './javascript/modules/location/locationManager'; +import offlineManager, { + OfflineManager, +} from './javascript/modules/offline/offlineManager'; +import snapshotManager, { + SnapshotManager, +} from './javascript/modules/snapshot/snapshotManager'; import { Animated, AnimatedPoint, @@ -56,722 +117,6 @@ import Logger from './javascript/utils/Logger'; import { isAndroid } from './javascript/utils'; import geoUtils from './javascript/utils/geoUtils'; -// Types - -interface OfflineProgressStatus { - name: string; - state: number; - percentage: number; - completedResourceSize: number; - completedTileCount: number; - completedResourceCount: number; - requiredResourceCount: number; - completedTileSize: number; -} - -interface OfflineProgressError { - message: string; - name: string; -} - -interface OfflinePack { - name: string; - bounds: [GeoJSON.Position, GeoJSON.Position]; - metadata: any; - status: () => Promise; - resume: () => Promise; - pause: () => Promise; -} - -interface OfflinePackStatus { - name: string; - state: number; - percentage: number; - completedResourceCount: number; - completedResourceSize: number; - completedTileSize: number; - completedTileCount: number; - requiredResourceCount: number; -} - -type OrnamentPosition = - | { top: number; left: number } - | { top: number; right: number } - | { bottom: number; left: number } - | { bottom: number; right: number }; - -interface RegionPayload { - zoomLevel: number; - heading: number; - animated: boolean; - isUserInteraction: boolean; - visibleBounds: GeoJSON.Position[]; - pitch: number; -} - -/** - * v10 only - experimental - */ -interface MapState { - properties: { - center: GeoJSON.Position; - bounds: { - ne: GeoJSON.Position; - sw: GeoJSON.Position; - }; - zoom: number; - heading: number; - pitch: number; - }; - gestures: { - isGestureActive: boolean; - isAnimatingFromGesture: boolean; - }; -} - -interface MapViewProps extends ViewProps { - animated?: boolean; - userTrackingMode?: UserTrackingMode; - userLocationVerticalAlignment?: number; - contentInset?: Array; - style?: StyleProp; - styleURL?: string; - styleJSON?: string; - preferredFramesPerSecond?: number; - localizeLabels?: boolean; - zoomEnabled?: boolean; - scrollEnabled?: boolean; - pitchEnabled?: boolean; - rotateEnabled?: boolean; - attributionEnabled?: boolean; - attributionPosition?: OrnamentPosition; - logoEnabled?: boolean; - logoPosition?: OrnamentPosition; - compassEnabled?: boolean; - compassPosition?: OrnamentPosition; - compassViewPosition?: number; - compassViewMargins?: Point; - scaleBarEnabled?: boolean; - scaleBarPosition?: OrnamentPosition; - surfaceView?: boolean; - regionWillChangeDebounceTime?: number; - regionDidChangeDebounceTime?: number; - tintColor?: string; - - onPress?: (feature: GeoJSON.Feature) => void; - onLongPress?: (feature: GeoJSON.Feature) => void; - onRegionWillChange?: ( - feature: GeoJSON.Feature, - ) => void; - onRegionIsChanging?: ( - feature: GeoJSON.Feature, - ) => void; - onRegionDidChange?: ( - feature: GeoJSON.Feature, - ) => void; - onCameraChanged?: (state: MapState) => void; - onMapIdle?: (state: MapState) => void; - onUserLocationUpdate?: (feature: Location) => void; - onWillStartLoadingMap?: () => void; - onDidFinishLoadingMap?: () => void; - onDidFailLoadingMap?: () => void; - onWillStartRenderingFrame?: () => void; - onDidFinishRenderingFrame?: () => void; - onDidFinishRenderingFrameFully?: () => void; - onWillStartRenderingMap?: () => void; - onDidFinishRenderingMap?: () => void; - onDidFinishRenderingMapFully?: () => void; - onDidFinishLoadingStyle?: () => void; - onUserTrackingModeChange?: () => void; -} - -interface UserLocationProps { - androidRenderMode?: 'normal' | 'compass' | 'gps'; - animated?: boolean; - children?: ReactNode; - minDisplacement?: number; - onPress?: () => void; - onUpdate?: (location: Location) => void; - renderMode?: 'normal' | 'native'; - showsUserHeadingIndicator?: boolean; - visible?: boolean; -} - -type WithExpression = { - [P in keyof T]: T[P] | Expression; -}; - -interface LightStyle { - anchor?: Alignment | Expression; - position?: GeoJSON.Position | Expression; - positionTransition?: Transition | Expression; - color?: string | Expression; - colorTransition?: Transition | Expression; - intensity?: number | Expression; - intensityTransition?: Transition | Expression; -} - -interface Transition { - duration: number; - delay: number; -} - -interface BackgroundLayerStyle { - visibility?: Visibility | Expression; - backgroundColor?: string | Expression; - backgroundColorTransition?: Transition | Expression; - backgroundPattern?: string | Expression; - backgroundPatternTransition?: Transition | Expression; - backgroundOpacity?: number | Expression; - backgroundOpacityTransition?: Transition | Expression; -} - -interface CircleLayerStyle { - visibility?: Visibility | Expression; - circleRadius?: number | Expression; - circleRadiusTransition?: Transition | Expression; - circleColor?: string | Expression; - circleColorTransition?: Transition | Expression; - circleBlur?: number | Expression; - circleBlurTransition?: Transition | Expression; - circleOpacity?: number | Expression; - circleOpacityTransition?: Transition | Expression; - circleTranslate?: Array | Expression; - circleTranslateTransition?: Transition | Expression; - circleTranslateAnchor?: Alignment | Expression; - circlePitchScale?: Alignment | Expression; - circlePitchAlignment?: Alignment | Expression; - circleStrokeWidth?: number | Expression; - circleStrokeWidthTransition?: Transition | Expression; - circleStrokeColor?: string | Expression; - circleStrokeColorTransition?: Transition | Expression; - circleStrokeOpacity?: number | Expression; - circleStrokeOpacityTransition?: Transition | Expression; -} - -interface FillExtrusionLayerStyle { - visibility?: Visibility | Expression; - fillExtrusionOpacity?: number | Expression; - fillExtrusionOpacityTransition?: Transition | Expression; - fillExtrusionColor?: string | Expression; - fillExtrusionColorTransition?: Transition | Expression; - fillExtrusionTranslate?: Array | Expression; - fillExtrusionTranslateTransition?: Transition | Expression; - fillExtrusionTranslateAnchor?: Alignment | Expression; - fillExtrusionPattern?: string | Expression; - fillExtrusionPatternTransition?: Transition | Expression; - fillExtrusionHeight?: number | Expression; - fillExtrusionHeightTransition?: Transition | Expression; - fillExtrusionBase?: number | Expression; - fillExtrusionBaseTransition?: Transition | Expression; -} - -interface FillLayerStyle { - visibility?: Visibility | Expression; - fillAntialias?: boolean | Expression; - fillOpacity?: number | Expression; - fillExtrusionOpacityTransition?: Transition | Expression; - fillColor?: string | Expression; - fillColorTransition?: Transition | Expression; - fillOutlineColor?: string | Expression; - fillOutlineColorTransition?: Transition | Expression; - fillTranslate?: Array | Expression; - fillTranslateTransition?: Transition | Expression; - fillTranslateAnchor?: Alignment | Expression; - fillPattern?: string | Expression; - fillPatternTransition?: Transition | Expression; -} - -interface LineLayerStyle { - lineCap?: 'butt' | 'round' | 'square' | Expression; - lineJoin?: 'bevel' | 'round' | 'miter' | Expression; - lineMiterLimit?: number | Expression; - lineRoundLimit?: number | Expression; - visibility?: Visibility | Expression; - lineOpacity?: number | Expression; - lineOpacityTransition?: Transition | Expression; - lineColor?: string | Expression; - lineColorTransition?: Transition | Expression; - lineTranslate?: Array | Expression; - lineTranslateTransition?: Transition | Expression; - lineTranslateAnchor?: Alignment | Expression; - lineWidth?: number | Expression; - lineWidthTransition?: Transition | Expression; - lineGapWidth?: number | Expression; - lineGapWidthTransition?: Transition | Expression; - lineOffset?: number | Expression; - lineOffsetTransition?: Transition | Expression; - lineBlur?: number | Expression; - lineBlurTransition?: Transition | Expression; - lineDasharray?: Array | Expression; - lineDasharrayTransition?: Transition | Expression; - linePattern?: string | Expression; - linePatternTransition?: Transition | Expression; -} - -interface RasterLayerStyle { - visibility?: Visibility | Expression; - rasterOpacity?: number | Expression; - rasterOpacityTransition?: Transition | Expression; - rasterHueRotate?: Expression; - rasterHueRotateTransition?: Transition | Expression; - rasterBrightnessMin?: number | Expression; - rasterBrightnessMinTransition?: Transition | Expression; - rasterBrightnessMax?: number | Expression; - rasterBrightnessMaxTransition?: Transition | Expression; - rasterSaturation?: number | Expression; - rasterSaturationTransition?: Transition | Expression; - rasterContrast?: number | Expression; - rasterContrastTransition?: Transition | Expression; - rasterFadeDuration?: number | Expression; -} - -type TextVariableAnchorValues = - | 'center' - | 'left' - | 'right' - | 'top' - | 'bottom' - | 'top-left' - | 'top-right' - | 'bottom-left' - | 'bottom-right'; - -interface SymbolLayerStyle { - symbolPlacement?: 'point' | 'line' | Expression; - symbolSpacing?: number | Expression; - symbolAvoidEdges?: boolean | Expression; - symbolSortKey?: number | Expression; - symbolZOrder?: 'auto' | 'viewport-y' | 'source' | Expression; - iconAllowOverlap?: boolean | Expression; - iconIgnorePlacement?: boolean | Expression; - iconOptional?: boolean | Expression; - iconRotationAlignment?: AutoAlignment | Expression; - iconSize?: number | Expression; - iconTextFit?: 'none' | 'width' | 'height' | 'both' | Expression; - iconTextFitPadding?: Array | Expression; - iconImage?: string | Expression; - iconRotate?: number | Expression; - iconPadding?: number | Expression; - iconKeepUpright?: boolean | Expression; - iconOffset?: Array | Expression; - iconAnchor?: Anchor | Expression; - iconPitchAlignment?: AutoAlignment | Expression; - textPitchAlignment?: AutoAlignment | Expression; - textRotationAlignment?: AutoAlignment | Expression; - textField?: string | Expression; - textFont?: Array | Expression; - textSize?: number | Expression; - textMaxWidth?: number | Expression; - textLineHeight?: number | Expression; - textLetterSpacing?: number | Expression; - textJustify?: 'left' | 'center' | 'right' | Expression; - textAnchor?: Anchor | Expression; - textMaxAngle?: number | Expression; - textRotate?: number | Expression; - textPadding?: number | Expression; - textKeepUpright?: boolean | Expression; - textTransform?: 'none' | 'uppercase' | 'lowercase' | Expression; - textOffset?: Array | Expression; - textAllowOverlap?: boolean | Expression; - textIgnorePlacement?: boolean | Expression; - textOptional?: boolean | Expression; - textVariableAnchor?: Array; - textRadialOffset?: number | Expression; - visibility?: Visibility | Expression; - iconOpacity?: number | Expression; - iconOpacityTransition?: Transition | Expression; - iconColor?: string | Expression; - iconColorTransition?: Transition | Expression; - iconHaloColor?: string | Expression; - iconHaloColorTransition?: Transition | Expression; - iconHaloWidth?: number | Expression; - iconHaloWidthTransition?: Transition | Expression; - iconHaloBlur?: number | Expression; - iconHaloBlurTransition?: Transition | Expression; - iconTranslate?: Array | Expression; - iconTranslateTransition?: Transition | Expression; - iconTranslateAnchor?: Alignment | Expression; - textOpacity?: number | Expression; - textOpacityTransition?: Transition | Expression; - textColor?: string | Expression; - textColorTransition?: Transition | Expression; - textHaloColor?: string | Expression; - textHaloColorTransition?: Transition | Expression; - textHaloWidth?: number | Expression; - textHaloWidthTransition?: Transition | Expression; - textHaloBlur?: number | Expression; - textHaloBlurTransition?: Transition | Expression; - textTranslate?: Array | Expression; - textTranslateTransition?: Transition | Expression; - textTranslateAnchor?: Alignment | Expression; -} - -interface HeatmapLayerStyle { - visibility?: Visibility | Expression; - heatmapRadius?: number | Expression; - heatmapRadiusTransition?: Transition | Expression; - heatmapWeight?: number | Expression; - heatmapIntensity?: number | Expression; - heatmapIntensityTransition?: Transition | Expression; - heatmapColor?: string | Expression; - heatmapOpacity?: number | Expression; - heatmapOpacityTransition?: Transition | Expression; -} - -interface SkyLayerStyle { - skyType: string | Expression; - skyAtmosphereSun?: Array | Expression; - skyAtmosphereSunIntensity: number | Expression; -} - -interface SkyLayerProps extends LayerBaseProps { - id: string; - style?: StyleProp; -} - -interface Point { - x: number; - y: number; -} - -interface LightProps extends Omit { - style?: LightStyle; -} - -interface PointAnnotationProps { - id: string; - title?: string; - snippet?: string; - selected?: boolean; - draggable?: boolean; - coordinate: GeoJSON.Position; - anchor?: Point; - onSelected?: () => void; - onDeselected?: () => void; - onDragStart?: () => void; - onDrag?: () => void; - onDragEnd?: () => void; -} - -type MarkerViewProps = PointAnnotationProps; - -interface StyleProps { - json: any; -} - -interface CalloutProps extends Omit { - title?: string; - style?: StyleProp>; - containerStyle?: StyleProp>; - contentStyle?: StyleProp>; - tipStyle?: StyleProp>; - textStyle?: StyleProp>; -} - -interface TileSourceProps extends ViewProps { - id: string; - url?: string; - tileUrlTemplates?: Array; - minZoomLevel?: number; - maxZoomLevel?: number; -} - -interface VectorSourceProps extends TileSourceProps { - onPress?: (event: OnPressEvent) => void; - hitbox?: { - width: number; - height: number; - }; -} - -interface ShapeSourceProps extends ViewProps { - id: string; - url?: string; - shape?: - | GeoJSON.GeometryCollection - | GeoJSON.Feature - | GeoJSON.FeatureCollection - | GeoJSON.Geometry; - cluster?: boolean; - clusterRadius?: number; - clusterMaxZoomLevel?: number; - clusterProperties?: object; - maxZoomLevel?: number; - buffer?: number; - tolerance?: number; - lineMetrics?: boolean; - images?: { assets?: string[] } & { [key: string]: ImageSourcePropType }; - onPress?: (event: OnPressEvent) => void; - hitbox?: { - width: number; - height: number; - }; -} - -interface RasterSourceProps extends TileSourceProps { - tileSize?: number; -} - -interface LayerBaseProps extends Omit { - id: string; - sourceID?: string; - sourceLayerID?: string; - aboveLayerID?: string; - belowLayerID?: string; - layerIndex?: number; - filter?: Expression; - minZoomLevel?: number; - maxZoomLevel?: number; -} - -interface BackgroundLayerProps extends LayerBaseProps { - style?: StyleProp; -} - -interface CircleLayerProps extends LayerBaseProps { - style?: StyleProp; -} - -interface FillExtrusionLayerProps extends Omit { - id: string; - style?: StyleProp; -} - -interface FillLayerProps extends LayerBaseProps { - style?: StyleProp; -} - -interface LineLayerProps extends LayerBaseProps { - style?: StyleProp; -} - -interface RasterLayerProps extends LayerBaseProps { - style?: StyleProp; -} - -interface SymbolLayerProps extends LayerBaseProps { - style?: StyleProp; -} - -interface HeatmapLayerProps extends LayerBaseProps { - style?: StyleProp; -} - -interface ImagesProps extends ViewProps { - images?: { assets?: string[] } & { [key: string]: ImageSourcePropType }; - nativeAssetImages?: string[]; - onImageMissing?: (imageKey: string) => void; -} - -interface ImageSourceProps extends ViewProps { - id: string; - url?: number | string; - coordinates: [ - GeoJSON.Position, - GeoJSON.Position, - GeoJSON.Position, - GeoJSON.Position, - ]; -} - -interface OfflineCreatePackOptions { - name?: string; - styleURL?: string; - bounds?: [GeoJSON.Position, GeoJSON.Position]; - minZoom?: number; - maxZoom?: number; - metadata?: any; -} - -interface SnapshotOptions { - centerCoordinate?: GeoJSON.Position; - width?: number; - height?: number; - zoomLevel?: number; - pitch?: number; - heading?: number; - styleURL?: string; - writeToDisk?: boolean; -} - -type LogLevel = 'error' | 'warning' | 'info' | 'debug' | 'verbose'; - -interface LogObject { - level: LogLevel; - message: string; - tag: string; -} - -type LogCallback = (object: LogObject) => void; - -interface Location { - coords: Coordinates; - timestamp?: number; -} - -interface Coordinates { - /** - * The heading (measured in degrees) relative to true north. - * Heading is used to describe the direction the device is pointing to (the value of the compass). - * Note that on Android this is incorrectly reporting the course value as mentioned in issue https://github.com/rnmapbox/maps/issues/1213 - * and will be corrected in a future update. - */ - heading?: number; - - /** - * The direction in which the device is traveling, measured in degrees and relative to due north. - * The course refers to the direction the device is actually moving (not the same as heading). - */ - course?: number; - - /** - * The instantaneous speed of the device, measured in meters per second. - */ - speed?: number; - - /** - * The latitude in degrees. - */ - latitude: number; - - /** - * The longitude in degrees. - */ - longitude: number; - - /** - * The radius of uncertainty for the location, measured in meters. - */ - accuracy?: number; - - /** - * The altitude, measured in meters. - */ - altitude?: number; -} - -type Padding = number | [number, number] | [number, number, number, number]; - -// prettier-ignore -type ExpressionName = - // Types - | 'array' | 'boolean' | 'collator' | 'format' | 'image' | 'literal' | 'number' | 'number-format' | 'object' | 'string' - | 'to-boolean' | 'to-color' | 'to-number' | 'to-string' | 'typeof' - // Feature data - | 'accumulated' | 'feature-state' | 'geometry-type' | 'id' | 'line-progress' | 'properties' - // Lookup - | 'at' | 'get' | 'has' | 'in' | 'index-of' | 'length' | 'slice' - // Decision - | '!' | '!=' | '<' | '<=' | '==' | '>' | '>=' | 'all' | 'any' | 'case' | 'match' | 'coalesce' | 'within' - // Ramps, scales, curves - | 'interpolate' | 'interpolate-hcl' | 'interpolate-lab' | 'step' - // Variable binding - | 'let' | 'var' - // String - | 'concat' | 'downcase' | 'is-supported-script' | 'resolved-locale' | 'upcase' - // Color - | 'rgb' | 'rgba' | 'to-rgba' - // Math - | '-' | '*' | '/' | '%' | '^' | '+' | 'abs' | 'acos' | 'asin' | 'atan' | 'ceil' | 'cos' | 'distance' | 'e' - | 'floor' | 'ln' | 'ln2' | 'log10' | 'log2' | 'max' | 'min' | 'pi' | 'round' | 'sin' | 'sqrt' | 'tan' - // Zoom, Heatmap - | 'zoom' | 'heatmap-density'; - -type ExpressionField = - | string - | number - | boolean - | Expression - | ExpressionField[] - | { [key: string]: ExpressionField }; - -type Expression = [ExpressionName, ...ExpressionField[]]; - -type Anchor = - | 'center' - | 'left' - | 'right' - | 'top' - | 'bottom' - | 'top-left' - | 'top-right' - | 'bottom-left' - | 'bottom-right'; -type Visibility = 'visible' | 'none'; -type Alignment = 'map' | 'viewport'; -type AutoAlignment = Alignment | 'auto'; - -type NamedStyles = { - [P in keyof T]: - | SymbolLayerStyle - | RasterLayerStyle - | LineLayerStyle - | FillLayerStyle - | FillExtrusionLayerStyle - | CircleLayerStyle - | BackgroundLayerStyle; -}; - -type MapboxGLEvent< - T extends string, - P = GeoJSON.Feature, - V = Element, -> = SyntheticEvent; - -type OnPressEvent = { - features: Array; - coordinates: { - latitude: number; - longitude: number; - }; - point: { - x: number; - y: number; - }; -}; - -interface UnitsOptions { - units?: Units; -} - -interface PositionsOptions { - bbox?: BBox; - id?: Id; -} - -type StyleURLKey = - | 'Street' - | 'Dark' - | 'Light' - | 'Outdoors' - | 'Satellite' - | 'SatelliteStreet' - | 'TrafficDay' - | 'TrafficNight'; - -// Enums - -enum InterpolationMode { - Exponential = 0, - Categorical = 1, - Interval = 2, - Identity = 3, -} - -const StyleURL: Record = { - Street: 'mapbox://styles/mapbox/streets-v11', - Dark: 'mapbox://styles/mapbox/dark-v10', - Light: 'mapbox://styles/mapbox/light-v10', - Outdoors: 'mapbox://styles/mapbox/outdoors-v11', - Satellite: 'mapbox://styles/mapbox/satellite-v9', - SatelliteStreet: 'mapbox://styles/mapbox/satellite-streets-v11', - TrafficDay: 'mapbox://styles/mapbox/navigation-preview-day-v4', - TrafficNight: 'mapbox://styles/mapbox/navigation-preview-night-v4', -}; - -// Methods - const requestAndroidLocationPermissions = async function () { if (isAndroid()) { const res = await PermissionsAndroid.requestMultiple([ @@ -797,54 +142,8 @@ const requestAndroidLocationPermissions = async function () { throw new Error('You should only call this method on Android!'); }; -// Export - -export { - // Components - MapView, - Light, - PointAnnotation, - Annotation, - Callout, - UserLocation, - Camera, - VectorSource, - ShapeSource, - RasterSource, - RasterDemSource, - ImageSource, - Images, - FillLayer, - FillExtrusionLayer, - HeatmapLayer, - LineLayer, - CircleLayer, - SkyLayer, - SymbolLayer, - RasterLayer, - BackgroundLayer, - Terrain, - locationManager, - offlineManager, - snapshotManager, - MarkerView, - Animated, - AnimatedPoint, - AnimatedShape, - AnimatedCoordinatesArray, - AnimatedExtractCoordinateFromArray, - AnimatedRouteCoordinatesArray, - Style, - Logger, - // Enums - InterpolationMode, - StyleURL, - // Methods - requestAndroidLocationPermissions, -}; - export type { - // Types + /** Types */ CameraProps, CameraRef, CameraAnimationMode, @@ -869,7 +168,6 @@ export type { TextVariableAnchorValues, SymbolLayerStyle, HeatmapLayerStyle, - Point, LightProps, PointAnnotationProps, MarkerViewProps, @@ -915,9 +213,60 @@ export type { SkyLayerStyle, }; -const MapboxGL = { - ...NativeModules.MGLModule, - // Components +type NamedExportsType = { + /** Components */ + MapView: typeof MapView; + Light: typeof Light; + PointAnnotation: typeof PointAnnotation; + Annotation: typeof Annotation; + Callout: typeof Callout; + UserLocation: typeof UserLocation; + Camera: typeof Camera; + VectorSource: typeof VectorSource; + ShapeSource: typeof ShapeSource; + RasterSource: typeof RasterSource; + RasterDemSource: typeof RasterDemSource; + ImageSource: typeof ImageSource; + Images: typeof Images; + FillLayer: typeof FillLayer; + FillExtrusionLayer: typeof FillExtrusionLayer; + HeatmapLayer: typeof HeatmapLayer; + LineLayer: typeof LineLayer; + CircleLayer: typeof CircleLayer; + SkyLayer: typeof SkyLayer; + SymbolLayer: typeof SymbolLayer; + RasterLayer: typeof RasterLayer; + BackgroundLayer: typeof BackgroundLayer; + Terrain: typeof Terrain; + MarkerView: typeof MarkerView; + Animated: typeof Animated; + AnimatedPoint: typeof AnimatedPoint; + AnimatedShape: typeof AnimatedShape; + AnimatedCoordinatesArray: typeof AnimatedCoordinatesArray; + AnimatedExtractCoordinateFromArray: typeof AnimatedExtractCoordinateFromArray; + AnimatedRouteCoordinatesArray: typeof AnimatedRouteCoordinatesArray; + Style: typeof Style; + Logger: typeof Logger; + locationManager: LocationManager; + offlineManager: OfflineManager; + snapshotManager: SnapshotManager; + /** Enums */ + InterpolationMode: typeof InterpolationMode; + StyleURL: typeof StyleURL; + /** Methods */ + requestAndroidLocationPermissions: typeof requestAndroidLocationPermissions; + /** Native */ + removeCustomHeader(headerName: string): void; + addCustomHeader(headerName: string, headerValue: string): void; + setAccessToken(accessToken: string | null): void; + setWellKnownTileServer(tileServer: string): void; + getAccessToken(): Promise; + setTelemetryEnabled(telemetryEnabled: boolean): void; + setConnected(connected: boolean): void; +}; + +export { + /** Components */ MapView, Light, PointAnnotation, @@ -941,9 +290,50 @@ const MapboxGL = { RasterLayer, BackgroundLayer, Terrain, + MarkerView, + Animated, + AnimatedPoint, + AnimatedShape, + AnimatedCoordinatesArray, + AnimatedExtractCoordinateFromArray, + AnimatedRouteCoordinatesArray, + Style, + Logger, locationManager, offlineManager, snapshotManager, + /** Enums */ + InterpolationMode, + StyleURL, + /** Methods */ + requestAndroidLocationPermissions, +}; + +const MapboxGL: NamedExportsType = { + /** Components */ + MapView, + Light, + PointAnnotation, + Annotation, + Callout, + UserLocation, + Camera, + VectorSource, + ShapeSource, + RasterSource, + RasterDemSource, + ImageSource, + Images, + FillLayer, + FillExtrusionLayer, + HeatmapLayer, + LineLayer, + CircleLayer, + SkyLayer, + SymbolLayer, + RasterLayer, + BackgroundLayer, + Terrain, MarkerView, Animated, AnimatedPoint, @@ -953,13 +343,18 @@ const MapboxGL = { AnimatedRouteCoordinatesArray, Style, Logger, - // Classes + locationManager, + offlineManager, + snapshotManager, + /** Classes */ geoUtils, - // Enums + /** Enums */ InterpolationMode, StyleURL, - // Methods + /** Methods */ requestAndroidLocationPermissions, + /** Native */ + ...NativeModules.MGLModule, }; export default MapboxGL; diff --git a/ios/RCTMGL-v10/RCTMGLMapView.swift b/ios/RCTMGL-v10/RCTMGLMapView.swift index a0d48723b..39bef43f3 100644 --- a/ios/RCTMGL-v10/RCTMGLMapView.swift +++ b/ios/RCTMGL-v10/RCTMGLMapView.swift @@ -86,16 +86,16 @@ open class RCTMGLMapView : MapView { } } - @objc public override func layoutSubviews() { - super.layoutSubviews() - if let camera = reactCamera { - if (isPendingInitialLayout) { - isPendingInitialLayout = false; - - camera.initialLayout() - } - } - } +// @objc public override func layoutSubviews() { +// super.layoutSubviews() +// if let camera = reactCamera { +// if (isPendingInitialLayout) { +// isPendingInitialLayout = false; +// +// camera.initialLayout() +// } +// } +// } public override func updateConstraints() { super.updateConstraints() diff --git a/ios/RCTMGL-v10/RCTMGLShapeSource.swift b/ios/RCTMGL-v10/RCTMGLShapeSource.swift index bc65ae079..73fc0a620 100644 --- a/ios/RCTMGL-v10/RCTMGLShapeSource.swift +++ b/ios/RCTMGL-v10/RCTMGLShapeSource.swift @@ -22,6 +22,7 @@ class RCTMGLShapeSource : RCTMGLSource { @objc var cluster : NSNumber? @objc var clusterRadius : NSNumber? @objc var clusterMaxZoomLevel : NSNumber? + @objc var clusterProperties : [String: [Any]]?; @objc var maxZoomLevel : NSNumber? @objc var buffer : NSNumber? @@ -61,6 +62,19 @@ class RCTMGLShapeSource : RCTMGLSource { result.clusterMaxZoom = clusterMaxZoomLevel.doubleValue } + do { + if let clusterProperties = clusterProperties { + result.clusterProperties = try clusterProperties.mapValues { (params : [Any]) in + let data = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) + let decodedExpression = try JSONDecoder().decode(Expression.self, from: data) + + return decodedExpression + } + } + } catch { + Logger.log(level: .error, message: "RCTMGLShapeSource.parsing clusterProperties failed", error: error) + } + if let maxZoomLevel = maxZoomLevel { result.maxzoom = maxZoomLevel.doubleValue } diff --git a/ios/RCTMGL-v10/RCTMGLShapeSourceManager.m b/ios/RCTMGL-v10/RCTMGLShapeSourceManager.m index 248c0ab09..2186063a6 100644 --- a/ios/RCTMGL-v10/RCTMGLShapeSourceManager.m +++ b/ios/RCTMGL-v10/RCTMGLShapeSourceManager.m @@ -10,6 +10,7 @@ @interface RCT_EXTERN_MODULE(RCTMGLShapeSourceManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(cluster, NSNumber) RCT_EXPORT_VIEW_PROPERTY(clusterRadius, NSNumber) RCT_EXPORT_VIEW_PROPERTY(clusterMaxZoomLevel, NSNumber) +RCT_EXPORT_VIEW_PROPERTY(clusterProperties, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(maxZoomLevel, NSNumber) RCT_EXPORT_VIEW_PROPERTY(buffer, NSNumber) RCT_EXPORT_VIEW_PROPERTY(tolerance, NSNumber) diff --git a/ios/RCTMGL-v10/RCTMGLStyleValue.swift b/ios/RCTMGL-v10/RCTMGLStyleValue.swift index 921ad16be..0d873bd98 100644 --- a/ios/RCTMGL-v10/RCTMGLStyleValue.swift +++ b/ios/RCTMGL-v10/RCTMGLStyleValue.swift @@ -118,6 +118,24 @@ class RCTMGLStyleValue { return value } + else if type == "hashmap" { + guard let values = from["value"] as? [[Any]] else { + fatalError("Value for hashmap should be array of array") + } + + let result = values.map { items -> (String,Any) in + let key = items[0] + let value = items[1] + guard let key = key as? String else { + fatalError("First item should be a string key") + } + guard let value = value as? [String:Any] else { + fatalError("Value should be an array of dicts") + } + return (key,convert(value)) + } + return Dictionary(uniqueKeysWithValues: result) + } else if type == "number" { guard let value = from["value"] else { fatalError("Value for number should not be nil") diff --git a/ios/RCTMGL-v10/RCTMGLTerrain.swift b/ios/RCTMGL-v10/RCTMGLTerrain.swift index cbe38af82..416af68e4 100644 --- a/ios/RCTMGL-v10/RCTMGLTerrain.swift +++ b/ios/RCTMGL-v10/RCTMGLTerrain.swift @@ -55,7 +55,7 @@ class RCTMGLTerrain : UIView, RCTMGLMapComponent, RCTMGLSourceConsumer { do { try style.setTerrain(terrain) } catch { - Logger.log(level:.error, message: "Failed to create terrain: \(terrain)") + Logger.log(level:.error, message: "Failed to create terrain: \(terrain)", error: error) } } @@ -89,6 +89,6 @@ class RCTMGLTerrain : UIView, RCTMGLMapComponent, RCTMGLSourceConsumer { } func removeFromMap(_ map: RCTMGLMapView, style: Style) { - try! style.setTerrain(properties: [:]) + style.removeTerrain() } } diff --git a/ios/RCTMGL/MGLModule.m b/ios/RCTMGL/MGLModule.m index 2d3acfdf7..76a9b7340 100644 --- a/ios/RCTMGL/MGLModule.m +++ b/ios/RCTMGL/MGLModule.m @@ -12,6 +12,8 @@ #import "CameraMode.h" #import "RCTMGLSource.h" #import "MGLCustomHeaders.h" +#import + @import Mapbox; @implementation MGLModule @@ -27,12 +29,22 @@ + (BOOL)requiresMainQueueSetup { // style urls NSMutableDictionary *styleURLS = [[NSMutableDictionary alloc] init]; + // tile servers + NSMutableDictionary *tileServers = + [[NSMutableDictionary alloc] init]; + // impl + const NSMutableDictionary* impl = [[NSMutableDictionary alloc] init]; #ifdef RNMBGL_USE_MAPLIBRE for (MGLDefaultStyle* style in [MGLStyle predefinedStyles]) { [styleURLS setObject:[style.url absoluteString] forKey:style.name]; } [styleURLS setObject:[[MGLStyle defaultStyleURL] absoluteString] forKey:@"Default"]; + [tileServers setObject:@"mapbox" forKey:@"Mapbox"]; + [tileServers setObject:@"maplibre" forKey:@"MapLibre"]; + [tileServers setObject:@"maptiler" forKey:@"MapTiler"]; + [impl setObject:@"maplibre" forKey:@"Library"]; + #else [styleURLS setObject:[MGLStyle.streetsStyleURL absoluteString] forKey:@"Street"]; [styleURLS setObject:[MGLStyle.darkStyleURL absoluteString] forKey:@"Dark"]; @@ -40,6 +52,8 @@ + (BOOL)requiresMainQueueSetup [styleURLS setObject:[MGLStyle.outdoorsStyleURL absoluteString] forKey:@"Outdoors"]; [styleURLS setObject:[MGLStyle.satelliteStyleURL absoluteString] forKey:@"Satellite"]; [styleURLS setObject:[MGLStyle.satelliteStreetsStyleURL absoluteString] forKey:@"SatelliteStreet"]; + [tileServers setObject:@"mapbox" forKey:@"Mapbox"] + [impl setObject:@"mapbox-gl" forKey:@"Library"]; #endif // event types @@ -214,6 +228,8 @@ + (BOOL)requiresMainQueueSetup return @{ @"StyleURL": styleURLS, + @"TileServers": tileServers, + @"Implementation": impl, @"EventTypes": eventTypes, @"UserTrackingModes": userTrackingModes, @"UserLocationVerticalAlignment": userLocationVerticalAlignment, @@ -257,6 +273,32 @@ + (BOOL)requiresMainQueueSetup #endif } +RCT_EXPORT_METHOD(setWellKnownTileServer:(NSString*)tileServer) +{ +#ifdef RNMBGL_USE_MAPLIBRE + MGLWellKnownTileServer server = MGLMapLibre; + + if ([tileServer isEqualToString:@"maplibre"]) { + server = MGLMapLibre; + } else if ([tileServer isEqualToString:@"mapbox"]) { + server = MGLMapbox; + } else if ([tileServer isEqualToString:@"maptiler"]) { + server = MGLMapTiler; + } else { + RCTLogError(@"setWellKnownTileServer: %@ should be one of maplibre,mapbox,maptiler", tileServer); + return; + } + + [MGLSettings useWellKnownTileServer: server]; +#else + if ([tileServer isEqualToString:@"mapbox"]) { + // nothing to do + } else { + RCTLogError(@"setWellKnownTileServer: %@ should be mapbox", tileServer); + } +#endif +} + RCT_EXPORT_METHOD(addCustomHeader:(NSString *)headerName forHeaderValue:(NSString *) headerValue) { [MGLCustomHeaders.sharedInstance addHeader:headerValue forHeaderName:headerName]; diff --git a/javascript/components/Camera.tsx b/javascript/components/Camera.tsx index 7a8bef6dd..fa969671c 100644 --- a/javascript/components/Camera.tsx +++ b/javascript/components/Camera.tsx @@ -9,8 +9,12 @@ import React, { import { NativeModules, requireNativeComponent } from 'react-native'; import { Position } from '@turf/helpers'; +import { + UserTrackingMode, + UserTrackingModeChangeCallback, + CameraAnimationMode, +} from '../types'; import geoUtils from '../utils/geoUtils'; -import { MapboxGLEvent } from '../..'; const NativeModule = NativeModules.MGLModule; @@ -37,25 +41,6 @@ const nativeAnimationMode = ( export const NATIVE_MODULE_NAME = 'RCTMGLCamera'; -export type CameraAnimationMode = - | 'flyTo' - | 'easeTo' - | 'linearTo' - | 'moveTo' - | 'none'; - -export type UserTrackingMode = 'normal' | 'compass' | 'course'; - -export type UserTrackingModeChangeCallback = ( - event: MapboxGLEvent< - 'usertrackingmodechange', - { - followUserLocation: boolean; - followUserMode: UserTrackingMode | null; - } - >, -) => void; - export interface CameraStop { /** Allows static check of the data type. For internal use only. */ readonly type?: 'CameraStop'; @@ -368,9 +353,9 @@ const Camera = (props: CameraProps, ref: React.ForwardedRef) => { ne, sw, paddingConfig = 0, - animationDuration = 0, + _animationDuration = 0, ) => { - let padding = { + let _padding = { paddingTop: 0, paddingBottom: 0, paddingLeft: 0, @@ -379,14 +364,14 @@ const Camera = (props: CameraProps, ref: React.ForwardedRef) => { if (typeof paddingConfig === 'object') { if (paddingConfig.length === 2) { - padding = { + _padding = { paddingTop: paddingConfig[0], paddingBottom: paddingConfig[0], paddingLeft: paddingConfig[1], paddingRight: paddingConfig[1], }; } else if (paddingConfig.length === 4) { - padding = { + _padding = { paddingTop: paddingConfig[0], paddingBottom: paddingConfig[2], paddingLeft: paddingConfig[3], @@ -394,7 +379,7 @@ const Camera = (props: CameraProps, ref: React.ForwardedRef) => { }; } } else if (typeof paddingConfig === 'number') { - padding = { + _padding = { paddingTop: paddingConfig, paddingBottom: paddingConfig, paddingLeft: paddingConfig, @@ -408,46 +393,46 @@ const Camera = (props: CameraProps, ref: React.ForwardedRef) => { ne, sw, }, - padding, - animationDuration, + padding: _padding, + animationDuration: _animationDuration, animationMode: 'easeTo', }); }; const fitBounds = useCallback(_fitBounds, [setCamera]); const _flyTo: CameraRef['flyTo'] = ( - centerCoordinate, - animationDuration = 2000, + _centerCoordinate, + _animationDuration = 2000, ) => { setCamera({ type: 'CameraStop', - centerCoordinate, - animationDuration, + centerCoordinate: _centerCoordinate, + animationDuration: _animationDuration, }); }; const flyTo = useCallback(_flyTo, [setCamera]); const _moveTo: CameraRef['moveTo'] = ( - centerCoordinate, - animationDuration = 0, + _centerCoordinate, + _animationDuration = 0, ) => { setCamera({ type: 'CameraStop', - centerCoordinate, - animationDuration, + centerCoordinate: _centerCoordinate, + animationDuration: _animationDuration, animationMode: 'easeTo', }); }; const moveTo = useCallback(_moveTo, [setCamera]); const _zoomTo: CameraRef['zoomTo'] = ( - zoomLevel, - animationDuration = 2000, + _zoomLevel, + _animationDuration = 2000, ) => { setCamera({ type: 'CameraStop', - zoomLevel, - animationDuration, + zoomLevel: _zoomLevel, + animationDuration: _animationDuration, animationMode: 'flyTo', }); }; diff --git a/javascript/components/CircleLayer.js b/javascript/components/CircleLayer.js index 432b667e7..41cdf93f9 100644 --- a/javascript/components/CircleLayer.js +++ b/javascript/components/CircleLayer.js @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { NativeModules, requireNativeComponent } from 'react-native'; import { viewPropTypes } from '../utils'; import { CircleLayerStyleProp } from '../utils/styleMap'; +import { CircleLayerProps } from '../types'; import AbstractLayer from './AbstractLayer'; @@ -13,6 +14,8 @@ export const NATIVE_MODULE_NAME = 'RCTMGLCircleLayer'; /** * CircleLayer is a style layer that renders one or more filled circles on the map. + * + * @extends {Component} */ class CircleLayer extends AbstractLayer { static propTypes = { diff --git a/javascript/components/MapView.js b/javascript/components/MapView.js index 3914b8570..0478668f9 100644 --- a/javascript/components/MapView.js +++ b/javascript/components/MapView.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { View, @@ -19,6 +19,7 @@ import { } from '../utils'; import { getFilter } from '../utils/filterUtils'; import Logger from '../utils/Logger'; +import { MapViewProps } from '../types'; import NativeBridgeComponent from './NativeBridgeComponent'; @@ -40,9 +41,11 @@ const styles = StyleSheet.create({ const defaultStyleURL = MapboxGL.StyleURL.Street; /** - * MapView backed by Mapbox Native GL + * MapView backed by Mapbox Native GL. + * + * @extends {Component} */ -class MapView extends NativeBridgeComponent(React.Component) { +class MapView extends NativeBridgeComponent(Component) { static propTypes = { ...viewPropTypes, @@ -120,7 +123,7 @@ class MapView extends NativeBridgeComponent(React.Component) { attributionEnabled: PropTypes.bool, /** - * Adds attribution offset, e.g. `{top: 8, left: 8}` will put attribution button in top-left corner of the map + * Adds attribution offset, e.g. `{top: 8, left: 8}` will put attribution button in top-left corner of the map. By default on Android, the attribution with information icon (i) will be on the bottom left, while on iOS the mapbox logo will be on bottom left with information icon (i) on bottom right. Read more about mapbox attribution [here](https://docs.mapbox.com/help/getting-started/attribution/) */ attributionPosition: ornamentPositionPropType, diff --git a/javascript/components/MarkerView.js b/javascript/components/MarkerView.js index 53b25f3ee..c7e43dc5a 100644 --- a/javascript/components/MarkerView.js +++ b/javascript/components/MarkerView.js @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { Component, PureComponent } from 'react'; import PropTypes from 'prop-types'; import { Platform, NativeModules, requireNativeComponent } from 'react-native'; import { toJSONString, viewPropTypes } from '../utils'; import { makePoint } from '../utils/geoUtils'; +import { MarkerViewProps } from '../types'; import PointAnnotation from './PointAnnotation'; @@ -18,8 +19,10 @@ export const NATIVE_MODULE_NAME = 'RCTMGLMarkerView'; * . * This is based on [MakerView plugin](https://docs.mapbox.com/android/plugins/overview/markerview/) on Android * and PointAnnotation on iOS. + * + * @extends {Component} */ -class MarkerView extends React.PureComponent { +class MarkerView extends PureComponent { static propTypes = { ...viewPropTypes, diff --git a/javascript/components/PointAnnotation.js b/javascript/components/PointAnnotation.js index 97aca8a00..4cbfc3216 100644 --- a/javascript/components/PointAnnotation.js +++ b/javascript/components/PointAnnotation.js @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { requireNativeComponent, StyleSheet, Platform } from 'react-native'; import { toJSONString, isFunction, viewPropTypes } from '../utils'; import { makePoint } from '../utils/geoUtils'; +import { PointAnnotationProps } from '../types'; import NativeBridgeComponent from './NativeBridgeComponent'; @@ -23,8 +24,10 @@ const styles = StyleSheet.create({ * Consider using ShapeSource and SymbolLayer instead, if you have many points and you have static images, * they'll offer much better performance. * - * If you need interactive views please use MarkerView, - * as with PointAnnotation on Android child views are rendered onto a bitmap for better performance. + * If you need interactive views, please use MarkerView, as with PointAnnotation on Android, child views + * are rendered onto a bitmap for better performance. + * + * @extends {Component} */ class PointAnnotation extends NativeBridgeComponent(React.PureComponent) { static propTypes = { diff --git a/javascript/components/ShapeSource.js b/javascript/components/ShapeSource.js index 42f359c2b..c711e2479 100644 --- a/javascript/components/ShapeSource.js +++ b/javascript/components/ShapeSource.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { NativeModules, requireNativeComponent } from 'react-native'; @@ -11,6 +11,7 @@ import { isAndroid, } from '../utils'; import { copyPropertiesAsDeprecated } from '../utils/deprecation'; +import { ShapeSourceProps } from '../types'; import AbstractSource from './AbstractSource'; import NativeBridgeComponent from './NativeBridgeComponent'; @@ -21,7 +22,9 @@ export const NATIVE_MODULE_NAME = 'RCTMGLShapeSource'; /** * ShapeSource is a map content source that supplies vector shapes to be shown on the map. - * The shape may be a url or a GeoJSON object + * The shape may be a url or a GeoJSON object. + * + * @extends {Component} */ class ShapeSource extends NativeBridgeComponent(AbstractSource) { static NATIVE_ASSETS_KEY = 'assets'; diff --git a/javascript/components/UserLocation.js b/javascript/components/UserLocation.js index 4e7ebdfdd..bf9ab3451 100644 --- a/javascript/components/UserLocation.js +++ b/javascript/components/UserLocation.js @@ -1,7 +1,8 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import locationManager from '../modules/location/locationManager'; +import { UserLocationProps } from '../types'; import Annotation from './annotations/Annotation'; import CircleLayer from './CircleLayer'; @@ -53,7 +54,10 @@ export const normalIcon = (showsUserHeadingIndicator, heading) => [ : []), ]; -class UserLocation extends React.Component { +/** + * @extends {Component} + */ +class UserLocation extends Component { static propTypes = { /** * Whether location icon is animated between updates diff --git a/javascript/components/VectorSource.js b/javascript/components/VectorSource.js index cd8faa51d..d7b1f6339 100644 --- a/javascript/components/VectorSource.js +++ b/javascript/components/VectorSource.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { NativeModules, requireNativeComponent } from 'react-native'; @@ -10,6 +10,7 @@ import { } from '../utils'; import { getFilter } from '../utils/filterUtils'; import { copyPropertiesAsDeprecated } from '../utils/deprecation'; +import { VectorSourceProps } from '../types'; import AbstractSource from './AbstractSource'; import NativeBridgeComponent from './NativeBridgeComponent'; @@ -19,8 +20,11 @@ const MapboxGL = NativeModules.MGLModule; export const NATIVE_MODULE_NAME = 'RCTMGLVectorSource'; /** - * VectorSource is a map content source that supplies tiled vector data in Mapbox Vector Tile format to be shown on the map. - * The location of and metadata about the tiles are defined either by an option dictionary or by an external file that conforms to the TileJSON specification. + * VectorSource is a map content source that supplies tiled vector data in Mapbox Vector Tile format to be shown + * on the map. The location of and metadata about the tiles are defined either by an option dictionary or by an + * external file that conforms to the TileJSON specification. + * + * @extends {Component} */ class VectorSource extends NativeBridgeComponent(AbstractSource) { static propTypes = { diff --git a/javascript/components/index.d.ts b/javascript/components/index.d.ts index b9c587afb..c5f6eb5c3 100644 --- a/javascript/components/index.d.ts +++ b/javascript/components/index.d.ts @@ -1,6 +1,6 @@ +import { Component } from 'react'; import { Properties } from '@turf/helpers'; import { FeatureCollection, Geometry, Feature } from 'geojson'; -import type { Component } from 'react'; import { ViewPropTypes } from 'react-native'; import { @@ -25,10 +25,9 @@ import { SymbolLayerProps, UserLocationProps, VectorSourceProps, -} from '.'; +} from '../types'; export class MapView extends Component { - longitude: number; getPointInView(coordinate: GeoJSON.Position): Promise; getCoordinateFromView(point: GeoJSON.Position): Promise; getVisibleBounds(): Promise; diff --git a/javascript/modules/offline/OfflinePack.d.ts b/javascript/modules/offline/OfflinePack.d.ts new file mode 100644 index 000000000..b6e2de444 --- /dev/null +++ b/javascript/modules/offline/OfflinePack.d.ts @@ -0,0 +1,7 @@ +import { Component } from 'react'; + +export class OfflinePack extends Component { + status(): void; + resume(): void; + pause(): void; +} diff --git a/javascript/types/index.ts b/javascript/types/index.ts new file mode 100644 index 000000000..fa95a331c --- /dev/null +++ b/javascript/types/index.ts @@ -0,0 +1,743 @@ +import { Units, Id, BBox } from '@turf/helpers'; +import React, { ReactNode, SyntheticEvent } from 'react'; +import { + ViewProps, + StyleProp, + ViewStyle, + TextStyle, + ImageSourcePropType, +} from 'react-native'; + +export type CameraAnimationMode = + | 'flyTo' + | 'easeTo' + | 'linearTo' + | 'moveTo' + | 'none'; + +export type UserTrackingMode = 'normal' | 'compass' | 'course'; + +export type UserTrackingModeChangeCallback = ( + event: MapboxGLEvent< + 'usertrackingmodechange', + { + followUserLocation: boolean; + followUserMode: UserTrackingMode | null; + } + >, +) => void; + +export interface OfflineProgressStatus { + name: string; + state: number; + percentage: number; + completedResourceSize: number; + completedTileCount: number; + completedResourceCount: number; + requiredResourceCount: number; + completedTileSize: number; +} + +export interface OfflineProgressError { + message: string; + name: string; +} + +export interface OfflinePack { + name: string; + bounds: [GeoJSON.Position, GeoJSON.Position]; + metadata: any; + status: () => Promise; + resume: () => Promise; + pause: () => Promise; +} + +export interface OfflinePackStatus { + name: string; + state: number; + percentage: number; + completedResourceCount: number; + completedResourceSize: number; + completedTileSize: number; + completedTileCount: number; + requiredResourceCount: number; +} + +export type OrnamentPosition = + | { top: number; left: number } + | { top: number; right: number } + | { bottom: number; left: number } + | { bottom: number; right: number }; + +export interface RegionPayload { + zoomLevel: number; + heading: number; + animated: boolean; + isUserInteraction: boolean; + visibleBounds: GeoJSON.Position[]; + pitch: number; +} + +/** + * v10 only - experimental + */ +export interface MapState { + properties: { + center: GeoJSON.Position; + bounds: { + ne: GeoJSON.Position; + sw: GeoJSON.Position; + }; + zoom: number; + heading: number; + pitch: number; + }; + gestures: { + isGestureActive: boolean; + isAnimatingFromGesture: boolean; + }; +} + +export interface MapViewProps extends ViewProps { + animated?: boolean; + userTrackingMode?: UserTrackingMode; + userLocationVerticalAlignment?: number; + contentInset?: Array; + style?: StyleProp; + styleURL?: string; + styleJSON?: string; + preferredFramesPerSecond?: number; + localizeLabels?: boolean; + zoomEnabled?: boolean; + scrollEnabled?: boolean; + pitchEnabled?: boolean; + rotateEnabled?: boolean; + attributionEnabled?: boolean; + attributionPosition?: OrnamentPosition; + logoEnabled?: boolean; + logoPosition?: OrnamentPosition; + compassEnabled?: boolean; + compassPosition?: OrnamentPosition; + compassViewPosition?: number; + compassViewMargins?: Point; + scaleBarEnabled?: boolean; + scaleBarPosition?: OrnamentPosition; + surfaceView?: boolean; + regionWillChangeDebounceTime?: number; + regionDidChangeDebounceTime?: number; + tintColor?: string; + + onPress?: (feature: GeoJSON.Feature) => void; + onLongPress?: (feature: GeoJSON.Feature) => void; + onRegionWillChange?: ( + feature: GeoJSON.Feature, + ) => void; + onRegionIsChanging?: ( + feature: GeoJSON.Feature, + ) => void; + onRegionDidChange?: ( + feature: GeoJSON.Feature, + ) => void; + onCameraChanged?: (state: MapState) => void; + onMapIdle?: (state: MapState) => void; + onUserLocationUpdate?: (feature: Location) => void; + onWillStartLoadingMap?: () => void; + onDidFinishLoadingMap?: () => void; + onDidFailLoadingMap?: () => void; + onWillStartRenderingFrame?: () => void; + onDidFinishRenderingFrame?: () => void; + onDidFinishRenderingFrameFully?: () => void; + onWillStartRenderingMap?: () => void; + onDidFinishRenderingMap?: () => void; + onDidFinishRenderingMapFully?: () => void; + onDidFinishLoadingStyle?: () => void; + onUserTrackingModeChange?: () => void; +} + +export interface UserLocationProps { + androidRenderMode?: 'normal' | 'compass' | 'gps'; + animated?: boolean; + children?: ReactNode; + minDisplacement?: number; + onPress?: () => void; + onUpdate?: (location: Location) => void; + renderMode?: 'normal' | 'native'; + showsUserHeadingIndicator?: boolean; + visible?: boolean; +} + +export type WithExpression = { + [P in keyof T]: T[P] | Expression; +}; + +export interface LightStyle { + anchor?: Alignment | Expression; + position?: GeoJSON.Position | Expression; + positionTransition?: Transition | Expression; + color?: string | Expression; + colorTransition?: Transition | Expression; + intensity?: number | Expression; + intensityTransition?: Transition | Expression; +} + +export interface Transition { + duration: number; + delay: number; +} + +export interface BackgroundLayerStyle { + visibility?: Visibility | Expression; + backgroundColor?: string | Expression; + backgroundColorTransition?: Transition | Expression; + backgroundPattern?: string | Expression; + backgroundPatternTransition?: Transition | Expression; + backgroundOpacity?: number | Expression; + backgroundOpacityTransition?: Transition | Expression; +} + +export interface CircleLayerStyle { + visibility?: Visibility | Expression; + circleRadius?: number | Expression; + circleRadiusTransition?: Transition | Expression; + circleColor?: string | Expression; + circleColorTransition?: Transition | Expression; + circleBlur?: number | Expression; + circleBlurTransition?: Transition | Expression; + circleOpacity?: number | Expression; + circleOpacityTransition?: Transition | Expression; + circleTranslate?: Array | Expression; + circleTranslateTransition?: Transition | Expression; + circleTranslateAnchor?: Alignment | Expression; + circlePitchScale?: Alignment | Expression; + circlePitchAlignment?: Alignment | Expression; + circleStrokeWidth?: number | Expression; + circleStrokeWidthTransition?: Transition | Expression; + circleStrokeColor?: string | Expression; + circleStrokeColorTransition?: Transition | Expression; + circleStrokeOpacity?: number | Expression; + circleStrokeOpacityTransition?: Transition | Expression; +} + +export interface FillExtrusionLayerStyle { + visibility?: Visibility | Expression; + fillExtrusionOpacity?: number | Expression; + fillExtrusionOpacityTransition?: Transition | Expression; + fillExtrusionColor?: string | Expression; + fillExtrusionColorTransition?: Transition | Expression; + fillExtrusionTranslate?: Array | Expression; + fillExtrusionTranslateTransition?: Transition | Expression; + fillExtrusionTranslateAnchor?: Alignment | Expression; + fillExtrusionPattern?: string | Expression; + fillExtrusionPatternTransition?: Transition | Expression; + fillExtrusionHeight?: number | Expression; + fillExtrusionHeightTransition?: Transition | Expression; + fillExtrusionBase?: number | Expression; + fillExtrusionBaseTransition?: Transition | Expression; +} + +export interface FillLayerStyle { + visibility?: Visibility | Expression; + fillAntialias?: boolean | Expression; + fillOpacity?: number | Expression; + fillExtrusionOpacityTransition?: Transition | Expression; + fillColor?: string | Expression; + fillColorTransition?: Transition | Expression; + fillOutlineColor?: string | Expression; + fillOutlineColorTransition?: Transition | Expression; + fillTranslate?: Array | Expression; + fillTranslateTransition?: Transition | Expression; + fillTranslateAnchor?: Alignment | Expression; + fillPattern?: string | Expression; + fillPatternTransition?: Transition | Expression; +} + +export interface LineLayerStyle { + lineCap?: 'butt' | 'round' | 'square' | Expression; + lineJoin?: 'bevel' | 'round' | 'miter' | Expression; + lineMiterLimit?: number | Expression; + lineRoundLimit?: number | Expression; + visibility?: Visibility | Expression; + lineOpacity?: number | Expression; + lineOpacityTransition?: Transition | Expression; + lineColor?: string | Expression; + lineColorTransition?: Transition | Expression; + lineTranslate?: Array | Expression; + lineTranslateTransition?: Transition | Expression; + lineTranslateAnchor?: Alignment | Expression; + lineWidth?: number | Expression; + lineWidthTransition?: Transition | Expression; + lineGapWidth?: number | Expression; + lineGapWidthTransition?: Transition | Expression; + lineOffset?: number | Expression; + lineOffsetTransition?: Transition | Expression; + lineBlur?: number | Expression; + lineBlurTransition?: Transition | Expression; + lineDasharray?: Array | Expression; + lineDasharrayTransition?: Transition | Expression; + linePattern?: string | Expression; + linePatternTransition?: Transition | Expression; +} + +export interface RasterLayerStyle { + visibility?: Visibility | Expression; + rasterOpacity?: number | Expression; + rasterOpacityTransition?: Transition | Expression; + rasterHueRotate?: Expression; + rasterHueRotateTransition?: Transition | Expression; + rasterBrightnessMin?: number | Expression; + rasterBrightnessMinTransition?: Transition | Expression; + rasterBrightnessMax?: number | Expression; + rasterBrightnessMaxTransition?: Transition | Expression; + rasterSaturation?: number | Expression; + rasterSaturationTransition?: Transition | Expression; + rasterContrast?: number | Expression; + rasterContrastTransition?: Transition | Expression; + rasterFadeDuration?: number | Expression; +} + +export type TextVariableAnchorValues = + | 'center' + | 'left' + | 'right' + | 'top' + | 'bottom' + | 'top-left' + | 'top-right' + | 'bottom-left' + | 'bottom-right'; + +export interface SymbolLayerStyle { + symbolPlacement?: 'point' | 'line' | Expression; + symbolSpacing?: number | Expression; + symbolAvoidEdges?: boolean | Expression; + symbolSortKey?: number | Expression; + symbolZOrder?: 'auto' | 'viewport-y' | 'source' | Expression; + iconAllowOverlap?: boolean | Expression; + iconIgnorePlacement?: boolean | Expression; + iconOptional?: boolean | Expression; + iconRotationAlignment?: AutoAlignment | Expression; + iconSize?: number | Expression; + iconTextFit?: 'none' | 'width' | 'height' | 'both' | Expression; + iconTextFitPadding?: Array | Expression; + iconImage?: string | Expression; + iconRotate?: number | Expression; + iconPadding?: number | Expression; + iconKeepUpright?: boolean | Expression; + iconOffset?: Array | Expression; + iconAnchor?: Anchor | Expression; + iconPitchAlignment?: AutoAlignment | Expression; + textPitchAlignment?: AutoAlignment | Expression; + textRotationAlignment?: AutoAlignment | Expression; + textField?: string | Expression; + textFont?: Array | Expression; + textSize?: number | Expression; + textMaxWidth?: number | Expression; + textLineHeight?: number | Expression; + textLetterSpacing?: number | Expression; + textJustify?: 'left' | 'center' | 'right' | Expression; + textAnchor?: Anchor | Expression; + textMaxAngle?: number | Expression; + textRotate?: number | Expression; + textPadding?: number | Expression; + textKeepUpright?: boolean | Expression; + textTransform?: 'none' | 'uppercase' | 'lowercase' | Expression; + textOffset?: Array | Expression; + textAllowOverlap?: boolean | Expression; + textIgnorePlacement?: boolean | Expression; + textOptional?: boolean | Expression; + textVariableAnchor?: Array; + textRadialOffset?: number | Expression; + visibility?: Visibility | Expression; + iconOpacity?: number | Expression; + iconOpacityTransition?: Transition | Expression; + iconColor?: string | Expression; + iconColorTransition?: Transition | Expression; + iconHaloColor?: string | Expression; + iconHaloColorTransition?: Transition | Expression; + iconHaloWidth?: number | Expression; + iconHaloWidthTransition?: Transition | Expression; + iconHaloBlur?: number | Expression; + iconHaloBlurTransition?: Transition | Expression; + iconTranslate?: Array | Expression; + iconTranslateTransition?: Transition | Expression; + iconTranslateAnchor?: Alignment | Expression; + textOpacity?: number | Expression; + textOpacityTransition?: Transition | Expression; + textColor?: string | Expression; + textColorTransition?: Transition | Expression; + textHaloColor?: string | Expression; + textHaloColorTransition?: Transition | Expression; + textHaloWidth?: number | Expression; + textHaloWidthTransition?: Transition | Expression; + textHaloBlur?: number | Expression; + textHaloBlurTransition?: Transition | Expression; + textTranslate?: Array | Expression; + textTranslateTransition?: Transition | Expression; + textTranslateAnchor?: Alignment | Expression; +} + +export interface HeatmapLayerStyle { + visibility?: Visibility | Expression; + heatmapRadius?: number | Expression; + heatmapRadiusTransition?: Transition | Expression; + heatmapWeight?: number | Expression; + heatmapIntensity?: number | Expression; + heatmapIntensityTransition?: Transition | Expression; + heatmapColor?: string | Expression; + heatmapOpacity?: number | Expression; + heatmapOpacityTransition?: Transition | Expression; +} + +export interface SkyLayerStyle { + skyType: string | Expression; + skyAtmosphereSun?: Array | Expression; + skyAtmosphereSunIntensity: number | Expression; +} + +export interface SkyLayerProps extends LayerBaseProps { + id: string; + style?: StyleProp; +} + +export interface Point { + x: number; + y: number; +} + +export interface LightProps extends Omit { + style?: LightStyle; +} + +export interface PointAnnotationProps { + id: string; + title?: string; + snippet?: string; + selected?: boolean; + draggable?: boolean; + coordinate: GeoJSON.Position; + anchor?: Point; + onSelected?: () => void; + onDeselected?: () => void; + onDragStart?: () => void; + onDrag?: () => void; + onDragEnd?: () => void; +} + +export type MarkerViewProps = PointAnnotationProps; + +export interface StyleProps { + json: any; +} + +export interface CalloutProps extends Omit { + title?: string; + style?: StyleProp>; + containerStyle?: StyleProp>; + contentStyle?: StyleProp>; + tipStyle?: StyleProp>; + textStyle?: StyleProp>; +} + +export interface TileSourceProps extends ViewProps { + id: string; + url?: string; + tileUrlTemplates?: Array; + minZoomLevel?: number; + maxZoomLevel?: number; +} + +export interface VectorSourceProps extends TileSourceProps { + onPress?: (event: OnPressEvent) => void; + hitbox?: { + width: number; + height: number; + }; +} + +export interface ShapeSourceProps extends ViewProps { + id: string; + url?: string; + shape?: + | GeoJSON.GeometryCollection + | GeoJSON.Feature + | GeoJSON.FeatureCollection + | GeoJSON.Geometry; + cluster?: boolean; + clusterRadius?: number; + clusterMaxZoomLevel?: number; + clusterProperties?: object; + maxZoomLevel?: number; + buffer?: number; + tolerance?: number; + lineMetrics?: boolean; + images?: { assets?: string[] } & { [key: string]: ImageSourcePropType }; + onPress?: (event: OnPressEvent) => void; + hitbox?: { + width: number; + height: number; + }; +} + +export interface RasterSourceProps extends TileSourceProps { + tileSize?: number; +} + +export interface LayerBaseProps extends Omit { + id: string; + sourceID?: string; + sourceLayerID?: string; + aboveLayerID?: string; + belowLayerID?: string; + layerIndex?: number; + filter?: Expression; + minZoomLevel?: number; + maxZoomLevel?: number; +} + +export interface BackgroundLayerProps extends LayerBaseProps { + style?: StyleProp; +} + +export interface CircleLayerProps extends LayerBaseProps { + style?: StyleProp; +} + +export interface FillExtrusionLayerProps extends Omit { + id: string; + style?: StyleProp; +} + +export interface FillLayerProps extends LayerBaseProps { + style?: StyleProp; +} + +export interface LineLayerProps extends LayerBaseProps { + style?: StyleProp; +} + +export interface RasterLayerProps extends LayerBaseProps { + style?: StyleProp; +} + +export interface SymbolLayerProps extends LayerBaseProps { + style?: StyleProp; +} + +export interface HeatmapLayerProps extends LayerBaseProps { + style?: StyleProp; +} + +export interface ImagesProps extends ViewProps { + images?: { assets?: string[] } & { [key: string]: ImageSourcePropType }; + nativeAssetImages?: string[]; + onImageMissing?: (imageKey: string) => void; +} + +export interface ImageSourceProps extends ViewProps { + id: string; + url?: number | string; + coordinates: [ + GeoJSON.Position, + GeoJSON.Position, + GeoJSON.Position, + GeoJSON.Position, + ]; +} + +export interface OfflineCreatePackOptions { + name?: string; + styleURL?: string; + bounds?: [GeoJSON.Position, GeoJSON.Position]; + minZoom?: number; + maxZoom?: number; + metadata?: any; +} + +export interface SnapshotOptions { + centerCoordinate?: GeoJSON.Position; + width?: number; + height?: number; + zoomLevel?: number; + pitch?: number; + heading?: number; + styleURL?: string; + writeToDisk?: boolean; +} + +export type LogLevel = 'error' | 'warning' | 'info' | 'debug' | 'verbose'; + +export interface LogObject { + level: LogLevel; + message: string; + tag: string; +} + +export type LogCallback = (object: LogObject) => void; + +export interface Location { + coords: Coordinates; + timestamp?: number; +} + +export interface Coordinates { + /** + * The heading (measured in degrees) relative to true north. + * Heading is used to describe the direction the device is pointing to (the value of the compass). + * Note that on Android this is incorrectly reporting the course value as mentioned in issue https://github.com/rnmapbox/maps/issues/1213 + * and will be corrected in a future update. + */ + heading?: number; + + /** + * The direction in which the device is traveling, measured in degrees and relative to due north. + * The course refers to the direction the device is actually moving (not the same as heading). + */ + course?: number; + + /** + * The instantaneous speed of the device, measured in meters per second. + */ + speed?: number; + + /** + * The latitude in degrees. + */ + latitude: number; + + /** + * The longitude in degrees. + */ + longitude: number; + + /** + * The radius of uncertainty for the location, measured in meters. + */ + accuracy?: number; + + /** + * The altitude, measured in meters. + */ + altitude?: number; +} + +export type Padding = + | number + | [number, number] + | [number, number, number, number]; + +// prettier-ignore +export type ExpressionName = + // Types + | 'array' | 'boolean' | 'collator' | 'format' | 'image' | 'literal' | 'number' | 'number-format' | 'object' | 'string' + | 'to-boolean' | 'to-color' | 'to-number' | 'to-string' | 'typeof' + // Feature data + | 'accumulated' | 'feature-state' | 'geometry-type' | 'id' | 'line-progress' | 'properties' + // Lookup + | 'at' | 'get' | 'has' | 'in' | 'index-of' | 'length' | 'slice' + // Decision + | '!' | '!=' | '<' | '<=' | '==' | '>' | '>=' | 'all' | 'any' | 'case' | 'match' | 'coalesce' | 'within' + // Ramps, scales, curves + | 'interpolate' | 'interpolate-hcl' | 'interpolate-lab' | 'step' + // Variable binding + | 'let' | 'var' + // String + | 'concat' | 'downcase' | 'is-supported-script' | 'resolved-locale' | 'upcase' + // Color + | 'rgb' | 'rgba' | 'to-rgba' + // Math + | '-' | '*' | '/' | '%' | '^' | '+' | 'abs' | 'acos' | 'asin' | 'atan' | 'ceil' | 'cos' | 'distance' | 'e' + | 'floor' | 'ln' | 'ln2' | 'log10' | 'log2' | 'max' | 'min' | 'pi' | 'round' | 'sin' | 'sqrt' | 'tan' + // Zoom, Heatmap + | 'zoom' | 'heatmap-density'; + +export type ExpressionField = + | string + | number + | boolean + | Expression + | ExpressionField[] + | { [key: string]: ExpressionField }; + +export type Expression = [ExpressionName, ...ExpressionField[]]; + +export type Anchor = + | 'center' + | 'left' + | 'right' + | 'top' + | 'bottom' + | 'top-left' + | 'top-right' + | 'bottom-left' + | 'bottom-right'; +export type Visibility = 'visible' | 'none'; +export type Alignment = 'map' | 'viewport'; +export type AutoAlignment = Alignment | 'auto'; + +export type NamedStyles = { + [P in keyof T]: + | SymbolLayerStyle + | RasterLayerStyle + | LineLayerStyle + | FillLayerStyle + | FillExtrusionLayerStyle + | CircleLayerStyle + | BackgroundLayerStyle; +}; + +export type MapboxGLEvent< + T extends string, + P = GeoJSON.Feature, + V = Element, +> = SyntheticEvent; + +export type OnPressEvent = { + features: Array; + coordinates: { + latitude: number; + longitude: number; + }; + point: { + x: number; + y: number; + }; +}; + +export interface UnitsOptions { + units?: Units; +} + +export interface PositionsOptions { + bbox?: BBox; + id?: Id; +} + +export type StyleURLKey = + | 'Street' + | 'Dark' + | 'Light' + | 'Outdoors' + | 'Satellite' + | 'SatelliteStreet' + | 'TrafficDay' + | 'TrafficNight'; + +// Enums + +export enum InterpolationMode { + Exponential = 0, + Categorical = 1, + Interval = 2, + Identity = 3, +} + +export const StyleURL: Record = { + Street: 'mapbox://styles/mapbox/streets-v11', + Dark: 'mapbox://styles/mapbox/dark-v10', + Light: 'mapbox://styles/mapbox/light-v10', + Outdoors: 'mapbox://styles/mapbox/outdoors-v11', + Satellite: 'mapbox://styles/mapbox/satellite-v9', + SatelliteStreet: 'mapbox://styles/mapbox/satellite-streets-v11', + TrafficDay: 'mapbox://styles/mapbox/navigation-preview-day-v4', + TrafficNight: 'mapbox://styles/mapbox/navigation-preview-night-v4', +}; diff --git a/javascript/utils/animated/AnimatedPoint.js b/javascript/utils/animated/AnimatedPoint.js index bf4d44065..6c2e64d79 100644 --- a/javascript/utils/animated/AnimatedPoint.js +++ b/javascript/utils/animated/AnimatedPoint.js @@ -9,6 +9,9 @@ const DEFAULT_POINT = { type: 'Point', coordinates: DEFAULT_COORD }; let uniqueID = 0; +/** + * @extends {Component} + */ export class AnimatedPoint extends AnimatedWithChildren { constructor(point = DEFAULT_POINT) { super(); @@ -42,6 +45,9 @@ export class AnimatedPoint extends AnimatedWithChildren { this.latitude.flattenOffset(); } + /** + * @param {((value: GeoJSON.Point) => void)=} cb Callback that fires with current value. + */ stopAnimation(cb) { this.longitude.stopAnimation(); this.latitude.stopAnimation(); @@ -51,6 +57,9 @@ export class AnimatedPoint extends AnimatedWithChildren { } } + /** + * @param {((value: GeoJSON.Point) => void)=} cb Callback that fires with current value. + */ addListener(cb) { uniqueID += 1; const id = `${String(uniqueID)}-${String(Date.now())}`; @@ -69,12 +78,19 @@ export class AnimatedPoint extends AnimatedWithChildren { return id; } + /** + * @param {number} id + */ removeListener(id) { this.longitude.removeListener(this._listeners[id].longitude); this.latitude.removeListener(this._listeners[id].latitude); delete this._listeners[id]; } + /** + * @param {{coordinates: GeoJSON.Position} & Omit} config + * @returns {Animated.CompositeAnimation} + */ spring(config = { coordinates: DEFAULT_COORD }) { return Animated.parallel([ Animated.spring(this.longitude, { @@ -90,6 +106,10 @@ export class AnimatedPoint extends AnimatedWithChildren { ]); } + /** + * @param {{coordinates: GeoJSON.Position} & Omit} config + * @returns {Animated.CompositeAnimation} + */ timing(config = { coordinates: DEFAULT_COORD }) { return Animated.parallel([ Animated.timing(this.longitude, { @@ -105,6 +125,9 @@ export class AnimatedPoint extends AnimatedWithChildren { ]); } + /** + * @returns {GeoJSON.Point} + */ __getValue() { return { type: 'Point', diff --git a/javascript/utils/animated/index.d.ts b/javascript/utils/animated/index.d.ts index 2596e1070..a61f8d3fe 100644 --- a/javascript/utils/animated/index.d.ts +++ b/javascript/utils/animated/index.d.ts @@ -1,22 +1,23 @@ import RN from 'react-native'; -export class Animated {} // TODO - -export class AnimatedPoint { - constructor(point?: GeoJSON.Point); - longitude: RN.Animated.Value; - latitude: RN.Animated.Value; - setValue: (point: GeoJSON.Point) => void; - setOffset: (point: GeoJSON.Point) => void; - flattenOffset: () => void; - stopAnimation: (cb?: () => GeoJSON.Point) => void; - addListener: (cb?: () => GeoJSON.Point) => void; - removeListener: (id: string) => void; - spring: (config: Record) => RN.Animated.CompositeAnimation; - timing: (config: Record) => RN.Animated.CompositeAnimation; -} - +export class AnimatedPoint {} export class AnimatedShape {} // TODO export class AnimatedCoordinatesArray {} // TODO export class AnimatedExtractCoordinateFromArray {} // TODO export class AnimatedRouteCoordinatesArray {} // TODO + +export const Animated = { + ShapeSource: RN.AnimatedComponent, + ImageSource: RN.AnimatedComponent, + FillLayer: RN.AnimatedComponent, + FillExtrusionLayer: RN.AnimatedComponent, + LineLayer: RN.AnimatedComponent, + CircleLayer: RN.AnimatedComponent, + SymbolLayer: RN.AnimatedComponent, + RasterLayer: RN.AnimatedComponent, + BackgroundLayer: RN.AnimatedComponent, + CoordinatesArray: RN.AnimatedComponent, + RouteCoordinatesArray: RN.AnimatedComponent, + Shape: RN.AnimatedComponent, + ExtractCoordinateFromArray: RN.AnimatedComponent, +}; diff --git a/javascript/utils/geoUtils.d.ts b/javascript/utils/geoUtils.d.ts index f6dee7c85..72dadb764 100644 --- a/javascript/utils/geoUtils.d.ts +++ b/javascript/utils/geoUtils.d.ts @@ -8,7 +8,7 @@ import { Properties, } from '@turf/helpers'; -import { PositionsOptions, UnitsOptions } from '../..'; +import { PositionsOptions, UnitsOptions } from '../types'; export function makePoint

( coordinates: Position, diff --git a/javascript/utils/index.d.ts b/javascript/utils/index.d.ts index 478a81e62..d2cb0c9a9 100644 --- a/javascript/utils/index.d.ts +++ b/javascript/utils/index.d.ts @@ -1 +1,7 @@ export function isAndroid(): boolean; +export function runNativeCommand( + _nativeModuleName: string, + methodName: string, + nativeRef: any, + args: any[], +): any; diff --git a/package.json b/package.json index 6a8927a43..b09c6f2c2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@rnmapbox/maps", "description": "A Mapbox react native module for creating custom maps", - "version": "10.0.0-beta.8", + "version": "10.0.0-beta.9", "publishConfig": { "access": "public" }, @@ -53,7 +53,6 @@ "@turf/helpers": "6.5.0", "@turf/length": "6.5.0", "@turf/nearest-point-on-line": "6.5.0", - "@types/geojson": "^7946.0.7", "debounce": "^1.2.0" }, "devDependencies": { @@ -64,6 +63,7 @@ "@react-native-community/eslint-config": "^3.0.1", "@sinonjs/fake-timers": "^8.0.1", "@testing-library/react-native": "^8.0.0", + "@types/geojson": "^7946.0.7", "@types/node": "^17.0.34", "@types/react": "^18.0.9", "@types/react-native": "^0.67.7", @@ -73,10 +73,11 @@ "documentation": "13.2.5", "ejs": "^3.1.3", "ejs-lint": "^1.1.0", - "eslint": "^7.32.0", + "eslint": "^8.1.0", "eslint-config-prettier": "^8.5.0", + "eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-fp": "^2.3.0", - "eslint-plugin-import": "2.25.3", + "eslint-plugin-import": "2.26.0", "eslint-plugin-react-native": "^4.0.0", "expo-module-scripts": "^2.0.0", "husky": "4.3.8", diff --git a/scripts/autogenHelpers/DocJSONBuilder.js b/scripts/autogenHelpers/DocJSONBuilder.js index 7a4d67b70..ba59578fa 100644 --- a/scripts/autogenHelpers/DocJSONBuilder.js +++ b/scripts/autogenHelpers/DocJSONBuilder.js @@ -61,7 +61,13 @@ class DocJSONBuilder { component.name = name; - // styles + // Main description + component.description = component.description.replace( + /(\n*)(@\w+) (\{.*\})/g, + '', + ); + + // Styles if (this._styledLayers[name] && this._styledLayers[name].properties) { component.styles = []; @@ -81,7 +87,6 @@ class DocJSONBuilder { expression: prop.expression, transition: prop.transition, }; - if (prop.type === 'enum') { docStyle.values = Object.keys(prop.doc.values).map((value) => { return { value, doc: prop.doc.values[value].doc }; @@ -265,6 +270,7 @@ class DocJSONBuilder { }); fileName = fileName.replace(fileExtensionsRegex, ''); results[fileName] = parsed; + this.postprocess(results[fileName], fileName); next(); diff --git a/scripts/templates/RCTMGLStyleFactoryv10.java.ejs b/scripts/templates/RCTMGLStyleFactoryv10.java.ejs index ffde49143..c0cc8f80b 100644 --- a/scripts/templates/RCTMGLStyleFactoryv10.java.ejs +++ b/scripts/templates/RCTMGLStyleFactoryv10.java.ejs @@ -72,7 +72,7 @@ public class RCTMGLStyleFactory { <%_ for (const prop of layer.properties) { _%> public static void set<%- pascelCase(prop.name) -%>(<%- getLayerType(layer, 'android') -%> layer, RCTMGLStyleValue styleValue) { <%_ if (prop.name === 'visibility') { _%> - layer.<%- prop.name %>(<%- pascelCase(prop.name) %>.valueOf(styleValue.getString(VALUE_KEY))); + layer.<%- prop.name %>(<%- pascelCase(prop.name) %>.valueOf(styleValue.getEnumName())); <%_ } else if (prop.type === 'resolvedImage') { _%> if (styleValue.isExpression()) { if (styleValue.isImageStringValue()) { @@ -88,11 +88,11 @@ public class RCTMGLStyleFactory { layer.<%- prop.name -%>(styleValue.getExpression()); } else { <%_ if (prop.type === 'enum') { _%> - layer.<%- prop.name -%>(<%- pascelCase(prop.name) %>.valueOf(<%- androidGetConfigType(androidInputType(prop.type, prop.value), prop) -%>.toUpperCase())); + layer.<%- prop.name -%>(<%- pascelCase(prop.name) %>.valueOf(styleValue.getEnumName())); <%_ } else if (prop.name === 'lineGradient' || prop.name === 'heatmapColor' || prop.name === 'skyGradient') { _%> layer.<%- prop.name -%>(styleValue.getIntExpression(VALUE_KEY)); <%_ } else if (prop.name === 'position') { _%> - layer.<%- prop.name -%>(styleValue.getFloatArrayExpression(VALUE_KEY)); + layer.<%- prop.name -%>(styleValue.getLightPosition()); <%_ } else { _%> layer.<%- prop.name -%>(<%- androidGetConfigType(androidInputType(prop.type, prop.value), prop) -%>); <%_ } _%> diff --git a/tsconfig.json b/tsconfig.json index a32692e8c..0fa32ada8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "lib": [ "esnext" ], - "module": "esnext", + "module": "ES2015", "moduleResolution": "node", "target": "esnext", "allowUnreachableCode": false, @@ -22,17 +22,11 @@ "resolveJsonModule": true, "skipLibCheck": true, "strict": true, - "paths": { - "types": [ - "types/index.d.ts" - ] - }, "declaration": true }, "include": [ "index.ts", "javascript/**/*", - "types/**/*" ], "exclude": [ "lib/**/*", diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index 8b878788c..000000000 --- a/types/index.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Component } from 'react'; - -import { InterpolationMode, NamedStyles } from '..'; - -export function removeCustomHeader(headerName: string): void; -export function addCustomHeader(headerName: string, headerValue: string): void; -export function setAccessToken(accessToken: string | null): void; -export function getAccessToken(): Promise; -export function setTelemetryEnabled(telemetryEnabled: boolean): void; -export function setConnected(connected: boolean): void; - -export class StyleSheet extends Component { - static create | NamedStyles>(styles: T): T; - camera( - stops: { [key: number]: string }, - interpolationMode?: InterpolationMode, - ): void; - source( - stops: { [key: number]: string }, - attributeName: string, - interpolationMode?: InterpolationMode, - ): void; - composite( - stops: { [key: number]: string }, - attributeName: string, - interpolationMode?: InterpolationMode, - ): void; - - identity(attributeName: string): number; -} - -export class OfflinePack extends Component { - status(): void; - resume(): void; - pause(): void; -}