UNPKG

@goongmaps/goong-map-react-native

Version:

A Goong GL react native module for creating custom maps

314 lines (275 loc) 9.66 kB
import {NativeModules, NativeEventEmitter} from 'react-native'; import {isUndefined, isFunction, isAndroid} from '../../utils'; import OfflineCreatePackOptions from './OfflineCreatePackOptions'; import OfflinePack from './OfflinePack'; const GoongSDK = NativeModules.MGLModule; const GoongSDKOfflineManager = NativeModules.MGLOfflineModule; export const OfflineModuleEventEmitter = new NativeEventEmitter( GoongSDKOfflineManager, ); /** * OfflineManager implements a singleton (shared object) that manages offline packs. * All of this class’s instance methods are asynchronous, reflecting the fact that offline resources are stored in a database. * The shared object maintains a canonical collection of offline packs. */ class OfflineManager { constructor() { this._hasInitialized = false; this._offlinePacks = {}; this._progressListeners = {}; this._errorListeners = {}; this._onProgress = this._onProgress.bind(this); this._onError = this._onError.bind(this); } /** * Creates and registers an offline pack that downloads the resources needed to use the given region offline. * * @example * * const progressListener = (offlineRegion, status) => console.log(offlineRegion, status); * const errorListener = (offlineRegion, err) => console.log(offlineRegion, err); * * await GoongSDK.offlineManager.createPack({ * name: 'offlinePack', * styleURL: '...', * minZoom: 14, * maxZoom: 20, * bounds: [[neLng, neLat], [swLng, swLat]] * }, progressListener, errorListener) * * @param {OfflineCreatePackOptions} options Create options for a offline pack that specifices zoom levels, style url, and the region to download. * @param {Callback=} progressListener Callback that listens for status events while downloading the offline resource. * @param {Callback=} errorListener Callback that listens for status events while downloading the offline resource. * @return {void} */ async createPack(options, progressListener, errorListener) { await this._initialize(); const packOptions = new OfflineCreatePackOptions(options); if (this._offlinePacks[packOptions.name]) { throw new Error( `Offline pack with name ${packOptions.name} already exists.`, ); } this.subscribe(packOptions.name, progressListener, errorListener); const nativeOfflinePack = await GoongSDKOfflineManager.createPack( packOptions, ); this._offlinePacks[packOptions.name] = new OfflinePack(nativeOfflinePack); } /** * Unregisters the given offline pack and allows resources that are no longer required by any remaining packs to be potentially freed. * * @example * await GoongSDK.offlineManager.deletePack('packName') * * @param {String} name Name of the offline pack. * @return {void} */ async deletePack(name) { if (!name) { return; } await this._initialize(); const offlinePack = this._offlinePacks[name]; if (offlinePack) { await GoongSDKOfflineManager.deletePack(name); delete this._offlinePacks[name]; } } /** * Deletes the existing database, which includes both the ambient cache and offline packs, then reinitializes it. * * @example * await GoongSDK.offlineManager.resetDatabase(); * * @return {void} */ async resetDatabase() { await this._initialize(); await GoongSDKOfflineManager.resetDatabase(); } /** * Retrieves all the current offline packs that are stored in the database. * * @example * const offlinePacks = await GoongSDK.offlineManager.getPacks(); * * @return {Array<OfflinePack>} */ async getPacks() { await this._initialize(); return Object.keys(this._offlinePacks).map( name => this._offlinePacks[name], ); } /** * Retrieves an offline pack that is stored in the database by name. * * @example * const offlinePack = await GoongSDK.offlineManager.getPack(); * * @param {String} name Name of the offline pack. * @return {OfflinePack} */ async getPack(name) { await this._initialize(); return this._offlinePacks[name]; } /** * Sideloads offline db * * @example * await GoongSDK.offlineManager.mergeOfflineRegions(path); * * @param {String} path Path to offline tile db on file system. * @return {void} */ async mergeOfflineRegions(path) { await this._initialize(); return GoongSDKOfflineManager.mergeOfflineRegions(path); } /** * Sets the maximum number of Mapbox-hosted tiles that may be downloaded and stored on the current device. * The Mapbox Terms of Service prohibits changing or bypassing this limit without permission from Mapbox. * * @example * GoongSDK.offlineManager.setTileCountLimit(1000); * * @param {Number} limit Map tile limit count. * @return {void} */ setTileCountLimit(limit) { GoongSDKOfflineManager.setTileCountLimit(limit); } /** * Sets the value at which download status events will be sent over the React Native bridge. * These events happening very very fast default is 500ms. * * @example * GoongSDK.setProgressEventThrottle(500); * * @param {Number} throttleValue event throttle value in ms. * @return {void} */ setProgressEventThrottle(throttleValue) { GoongSDKOfflineManager.setProgressEventThrottle(throttleValue); } /** * Subscribe to download status/error events for the requested offline pack. * Note that createPack calls this internally if listeners are provided. * * @example * const progressListener = (offlinePack, status) => console.log(offlinePack, status) * const errorListener = (offlinePack, err) => console.log(offlinePack, err) * GoongSDK.offlineManager.subscribe('packName', progressListener, errorListener) * * @param {String} packName Name of the offline pack. * @param {Callback} progressListener Callback that listens for status events while downloading the offline resource. * @param {Callback} errorListener Callback that listens for status events while downloading the offline resource. * @return {void} */ async subscribe(packName, progressListener, errorListener) { const totalProgressListeners = Object.keys(this._progressListeners).length; if (isFunction(progressListener)) { if (totalProgressListeners === 0) { OfflineModuleEventEmitter.addListener( GoongSDK.OfflineCallbackName.Progress, this._onProgress, ); } this._progressListeners[packName] = progressListener; } const totalErrorListeners = Object.keys(this._errorListeners).length; if (isFunction(errorListener)) { if (totalErrorListeners === 0) { OfflineModuleEventEmitter.addListener( GoongSDK.OfflineCallbackName.Error, this._onError, ); } this._errorListeners[packName] = errorListener; } // we need to manually set the pack observer on Android // if we're resuming a pack download instead of going thru the create flow if (isAndroid() && this._offlinePacks[packName]) { try { // manually set a listener, since listeners are only set on create flow await GoongSDKOfflineManager.setPackObserver(packName); } catch (e) { console.log('Unable to set pack observer', e); // eslint-disable-line } } } /** * Unsubscribes any listeners associated with the offline pack. * It's a good idea to call this on componentWillUnmount. * * @example * GoongSDK.offlineManager.unsubscribe('packName') * * @param {String} packName Name of the offline pack. * @return {void} */ unsubscribe(packName) { delete this._progressListeners[packName]; delete this._errorListeners[packName]; if (Object.keys(this._progressListeners).length === 0) { OfflineModuleEventEmitter.removeListener( GoongSDK.OfflineCallbackName.Progress, this._onProgress, ); } if (Object.keys(this._errorListeners).length === 0) { OfflineModuleEventEmitter.removeListener( GoongSDK.OfflineCallbackName.Error, this._onError, ); } } _initialize() { return new Promise(async (resolve, reject) => { if (this._hasInitialized) { return resolve(true); } try { const nativeOfflinePacks = await GoongSDKOfflineManager.getPacks(); for (const nativeOfflinePack of nativeOfflinePacks) { const offlinePack = new OfflinePack(nativeOfflinePack); this._offlinePacks[offlinePack.name] = offlinePack; } } catch (e) { reject(e); return; } this._hasInitialized = true; return resolve(true); }); } _onProgress(e) { const {name, state} = e.payload; if (!this._hasListeners(name, this._progressListeners)) { return; } const pack = this._offlinePacks[name]; this._progressListeners[name](pack, e.payload); // cleanup listeners now that they are no longer needed if (state === GoongSDK.OfflinePackDownloadState.Complete) { this.unsubscribe(name); } } _onError(e) { const {name} = e.payload; if (!this._hasListeners(name, this._errorListeners)) { return; } const pack = this._offlinePacks[name]; this._errorListeners[name](pack, e.payload); } _hasListeners(name, listenerMap) { return ( !isUndefined(this._offlinePacks[name]) && isFunction(listenerMap[name]) ); } } const offlineManager = new OfflineManager(); export default offlineManager;