@goongmaps/goong-map-react-native
Version:
A Goong GL react native module for creating custom maps
314 lines (275 loc) • 9.66 kB
JavaScript
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;