ReactNativeMapboxGLModule.java 19.11 KiB
package com.mapbox.reactnativemapboxgl;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.support.annotation.MainThread;
import android.support.annotation.UiThread;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
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.ReadableNativeMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.mapbox.mapboxsdk.MapboxAccountManager;
import com.mapbox.mapboxsdk.constants.MyLocationTracking;
import com.mapbox.mapboxsdk.constants.MyBearingTracking;
import com.mapbox.mapboxsdk.constants.Style;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.offline.OfflineManager;
import com.mapbox.mapboxsdk.offline.OfflineRegion;
import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition;
import com.mapbox.mapboxsdk.offline.OfflineRegionError;
import com.mapbox.mapboxsdk.offline.OfflineRegionStatus;
import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition;
import com.mapbox.mapboxsdk.telemetry.MapboxEventManager;
import javax.annotation.Nullable;
public class ReactNativeMapboxGLModule extends ReactContextBaseJavaModule {
    private static final String TAG = ReactNativeMapboxGLModule.class.getSimpleName();
    private ReactApplicationContext context;
    private ReactNativeMapboxGLPackage aPackage;
    Handler mainHandler;
    private int throttleInterval = 300;
    private static boolean initialized = false;
    public ReactNativeMapboxGLModule(ReactApplicationContext reactContext, ReactNativeMapboxGLPackage thePackage) {
        super(reactContext);
        this.mainHandler = new Handler(reactContext.getApplicationContext().getMainLooper());
        this.context = reactContext;
        this.aPackage = thePackage;
        Log.d(TAG, "Context " + context);
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
Log.d(TAG, "reactContext " + reactContext); } @Override public String getName() { return "MapboxGLManager"; } static private ArrayList<Integer> serializeTracking(int locationTracking, int bearingTracking) { ArrayList<Integer> result = new ArrayList<Integer>(); result.add(locationTracking); result.add(bearingTracking); return result; } public static final int[] locationTrackingModes = new int[] { MyLocationTracking.TRACKING_NONE, MyLocationTracking.TRACKING_FOLLOW, MyLocationTracking.TRACKING_FOLLOW, MyLocationTracking.TRACKING_FOLLOW }; public static final int[] bearingTrackingModes = new int[] { MyBearingTracking.NONE, MyBearingTracking.NONE, MyBearingTracking.GPS, MyBearingTracking.COMPASS }; @Override public @Nullable Map<String, Object> getConstants() { HashMap<String, Object> constants = new HashMap<String, Object>(); HashMap<String, Object> userTrackingMode = new HashMap<String, Object>(); HashMap<String, Object> mapStyles = new HashMap<String, Object>(); HashMap<String, Object> userLocationVerticalAlignment = new HashMap<String, Object>(); // User tracking constants userTrackingMode.put("none", 0); userTrackingMode.put("follow", 1); userTrackingMode.put("followWithCourse", 2); userTrackingMode.put("followWithHeading", 3); // Style constants mapStyles.put("light", Style.LIGHT); mapStyles.put("dark", Style.DARK); mapStyles.put("streets", Style.MAPBOX_STREETS); mapStyles.put("emerald", Style.EMERALD); mapStyles.put("satellite", Style.SATELLITE); mapStyles.put("hybrid", Style.SATELLITE_STREETS); // These need to be here for compatibility, even if they're not supported on Android userLocationVerticalAlignment.put("center", 0); userLocationVerticalAlignment.put("top", 1); userLocationVerticalAlignment.put("bottom", 2); // Other constants constants.put("unknownResourceCount", Long.MAX_VALUE); constants.put("metricsEnabled", MapboxEventManager.getMapboxEventManager().isTelemetryEnabled()); constants.put("userTrackingMode", userTrackingMode); constants.put("mapStyles", mapStyles); constants.put("userLocationVerticalAlignment", userLocationVerticalAlignment); return constants; } // Access Token @ReactMethod
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
public void setAccessToken(String accessToken) { if (accessToken == null || accessToken.length() == 0 || accessToken.equals("your-mapbox.com-access-token")) { throw new JSApplicationIllegalArgumentException("Invalid access token. Register to mapbox.com and request an access token, then pass it to setAccessToken()"); } if (initialized) { String oldToken = MapboxAccountManager.getInstance().getAccessToken(); if (!oldToken.equals(accessToken)) { throw new JSApplicationIllegalArgumentException("Mapbox access token cannot be initialized twice with different values"); } return; } initialized = true; MapboxAccountManager.start(context.getApplicationContext(), accessToken); initializeOfflinePacks(); } // Metrics @ReactMethod public void setMetricsEnabled(boolean value) { MapboxEventManager.getMapboxEventManager().setTelemetryEnabled(value); } // Offline packs // Offline pack events and initialization class OfflineRegionProgressObserver implements OfflineRegion.OfflineRegionObserver { ReactNativeMapboxGLModule module; OfflineRegion region; OfflineRegionStatus status; String name; boolean recentlyUpdated = false; boolean throttled = true; boolean invalid = false; OfflineRegionProgressObserver(ReactNativeMapboxGLModule module, OfflineRegion region, String name) { this.module = module; this.region = region; if (name == null) { this.name = getOfflineRegionName(region); } else { this.name = name; } } void fireUpdateEvent() { if (invalid) { return; } recentlyUpdated = true; WritableMap event = serializeOfflineRegionStatus(region, this.status); module.getReactApplicationContext().getJSModule(RCTNativeAppEventEmitter.class) .emit("MapboxOfflineProgressDidChange", event); module.mainHandler.postDelayed(new Runnable() { @Override public void run() { recentlyUpdated = false; if (throttled) { throttled = false; fireUpdateEvent(); } } }, throttleInterval); } @Override public void onStatusChanged(OfflineRegionStatus status) { if (invalid) { return; }
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
this.status = status; if (!recentlyUpdated) { fireUpdateEvent(); } else { throttled = true; } } @Override public void onError(OfflineRegionError error) { if (invalid) { return; } WritableMap event = Arguments.createMap(); event.putString("name", getOfflineRegionName(region)); event.putString("error", error.toString()); module.getReactApplicationContext().getJSModule(RCTNativeAppEventEmitter.class) .emit("MapboxOfflineError", event); } @Override public void mapboxTileCountLimitExceeded(long limit) { if (invalid) { return; } WritableMap event = Arguments.createMap(); event.putString("name", getOfflineRegionName(region)); event.putDouble("maxTiles", limit); module.getReactApplicationContext().getJSModule(RCTNativeAppEventEmitter.class) .emit("MapboxOfflineMaxAllowedTiles", event); } public void invalidate() { invalid = true; } } private int uninitializedObserverCount = -1; private ArrayList<OfflineRegionProgressObserver> offlinePackObservers = new ArrayList<>(); private ArrayList<Promise> offlinePackListingRequests = new ArrayList<>(); void flushListingRequests() { WritableArray result = _getOfflinePacks(); for (Promise promise : offlinePackListingRequests) { promise.resolve(result); } offlinePackListingRequests.clear(); } class OfflineRegionsInitialRequest implements OfflineManager.ListOfflineRegionsCallback { ReactNativeMapboxGLModule module; OfflineRegionsInitialRequest(ReactNativeMapboxGLModule module) { this.module = module; } @Override public void onList(OfflineRegion[] offlineRegions) { uninitializedObserverCount = offlineRegions.length; for (OfflineRegion region : offlineRegions) { final OfflineRegionProgressObserver observer = new OfflineRegionProgressObserver(module, region, null); offlinePackObservers.add(observer); region.setObserver(observer); region.setDownloadState(OfflineRegion.STATE_ACTIVE); region.getStatus(new OfflineRegion.OfflineRegionStatusCallback() { @Override public void onStatus(OfflineRegionStatus status) { observer.onStatusChanged(status); uninitializedObserverCount--;
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
if (uninitializedObserverCount == 0) { flushListingRequests(); } } @Override public void onError(String error) { Log.e(context.getApplicationContext().getPackageName(), error); } }); } } @Override public void onError(String error) { Log.e(module.getReactApplicationContext().getPackageName(), error); } } void initializeOfflinePacks() { final ReactNativeMapboxGLModule _this = this; mainHandler.post(new Runnable() { @Override public void run() { OfflineManager.getInstance(context.getApplicationContext()).listOfflineRegions( new OfflineRegionsInitialRequest(_this) ); } }); } // Offline pack utils static WritableMap serializeOfflineRegionStatus(OfflineRegion region, OfflineRegionStatus status) { WritableMap result = Arguments.createMap(); try { ByteArrayInputStream bis = new ByteArrayInputStream(region.getMetadata()); ObjectInputStream ois = new ObjectInputStream(bis); result.putString("name", (String)ois.readObject()); result.putString("metadata", (String)ois.readObject()); ois.close(); } catch (Throwable e) { e.printStackTrace(); } result.putInt("countOfBytesCompleted", (int)status.getCompletedResourceSize()); result.putInt("countOfResourcesCompleted", (int)status.getCompletedResourceCount()); result.putInt("countOfResourcesExpected", (int)status.getRequiredResourceCount()); result.putInt("maximumResourcesExpected", (int)status.getRequiredResourceCount()); return result; } static String getOfflineRegionName(OfflineRegion region) { try { ByteArrayInputStream bis = new ByteArrayInputStream(region.getMetadata()); ObjectInputStream ois = new ObjectInputStream(bis); String name = (String)ois.readObject(); ois.close(); return name; } catch (Throwable e) { e.printStackTrace(); return null; } } // Offline pack listing
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
WritableArray _getOfflinePacks() { WritableArray result = Arguments.createArray(); for (OfflineRegionProgressObserver observer : offlinePackObservers) { result.pushMap(serializeOfflineRegionStatus(observer.region, observer.status)); } return result; } @ReactMethod public void getOfflinePacks(final Promise promise) { mainHandler.post(new Runnable() { @Override public void run() { promise.resolve(_getOfflinePacks()); } }); } // Offline pack insertion @ReactMethod public void addOfflinePack(ReadableMap options, final Promise promise) { if (!options.hasKey("name")) { promise.reject(new JSApplicationIllegalArgumentException("addOfflinePack(): name is required.")); return; } if (!options.hasKey("minZoomLevel")) { promise.reject(new JSApplicationIllegalArgumentException("addOfflinePack(): minZoomLevel is required.")); return; } if (!options.hasKey("maxZoomLevel")) { promise.reject(new JSApplicationIllegalArgumentException("addOfflinePack(): maxZoomLevel is required.")); return; } if (!options.hasKey("bounds")) { promise.reject(new JSApplicationIllegalArgumentException("addOfflinePack(): bounds is required.")); return; } if (!options.hasKey("styleURL")) { promise.reject(new JSApplicationIllegalArgumentException("addOfflinePack(): styleURL is required.")); return; } if (!options.hasKey("type")) { promise.reject(new JSApplicationIllegalArgumentException("addOfflinePack(): type is required.")); return; } if (!options.getString("type").equals("bbox")) { promise.reject(new JSApplicationIllegalArgumentException("addOfflinePack(): Offline pack type " + options.getString("type") + " not supported. Only \"bbox\" is currently supported.")); return; } float pixelRatio = context.getResources().getDisplayMetrics().density; pixelRatio = pixelRatio < 1.5f ? 1.0f : 2.0f; ReadableArray boundsArray = options.getArray("bounds"); LatLngBounds bounds = new LatLngBounds.Builder() .include(new LatLng(boundsArray.getDouble(0), boundsArray.getDouble(1))) .include(new LatLng(boundsArray.getDouble(2), boundsArray.getDouble(3))) .build(); final OfflineTilePyramidRegionDefinition regionDef = new OfflineTilePyramidRegionDefinition( options.getString("styleURL"), bounds, options.getDouble("minZoomLevel"), options.getDouble("maxZoomLevel"), pixelRatio );