UNPKG

@benedictstrube/ui-mapbox

Version:

Interactive, thoroughly customizable maps powered by vector tiles and OpenGL.

1,164 lines 114 kB
import { request } from '@nativescript-community/perms'; import { AndroidApplication, Application, Color, File, Http, Image, ImageSource, Trace, Utils, knownFolders, path } from '@nativescript/core'; import { ExpressionParser } from './expression/expression-parser'; import { Layer, LayerFactory } from './layers/layer-factory'; import { CLog, CLogTypes, MapStyle, MapboxCommon, MapboxTraceCategory, MapboxViewBase, telemetryProperty } from './common'; export * from './common'; function _getLocation(loc) { if (loc === null) { return null; } else { return { location: { lat: loc.getLatitude(), lng: loc.getLongitude() }, speed: loc.getSpeed() }; } } export function setLogLevel(level) { const Logger = com.mapbox.mapboxsdk.log.Logger; let loggingLevel; switch (level) { case 'none': loggingLevel = Logger.NONE; break; case 'info': loggingLevel = Logger.INFO; break; case 'debug': loggingLevel = Logger.DEBUG; break; case 'verbose': loggingLevel = Logger.VERBOSE; break; case 'fault': case 'error': loggingLevel = Logger.ERROR; break; } Logger.setVerbosity(loggingLevel); } export class MapboxView extends MapboxViewBase { constructor() { super(); this.settings = null; this.initialized = false; if (Trace.isEnabled()) { CLog(CLogTypes.info, 'constructor(): building new MapboxView object.'); } } setConfig(settings) { if (settings.zoomLevel && !settings.center) { settings.center = { lat: 48.858093, lng: 2.294694 }; } this.settings = settings; } getNativeMapView() { return this.nativeMapView; } getMapboxApi() { return this.mapbox; } createNativeView() { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'createNativeView(): top'); } const nativeView = new android.widget.FrameLayout(this._context); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'createNativeView(): bottom'); } return nativeView; } onLoaded() { super.onLoaded(); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'onLoaded()'); } if (!this.initialized) { this.initMap(); this.initialized = true; } } initNativeView() { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initNativeView(): top'); } this.nativeView.owner = this; Application.android.on(AndroidApplication.activityPausedEvent, this.onPause, this); Application.android.on(AndroidApplication.activityResumedEvent, this.onResume, this); super.initNativeView(); } disposeNativeView() { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'disposeNativeView(): top'); } this.nativeView.owner = null; Application.android.off(AndroidApplication.activityPausedEvent, this.onPause, this); Application.android.off(AndroidApplication.activityResumedEvent, this.onResume, this); if (this.mapbox) { this.mapbox.destroy(); } super.disposeNativeView(); } initMap() { if (Trace.isEnabled()) { CLog(CLogTypes.info, "MapboxView:initMap(): top - accessToken is '" + this.config.accessToken + "'", this.config); } if (!this.nativeMapView && ((this.config && this.config.accessToken) || (this.settings && this.settings.accessToken))) { this.mapbox = new Mapbox(this); const options = { context: this._context, parentView: this.nativeView, onLocationPermissionGranted: (event) => { this.notify({ eventName: MapboxViewBase.locationPermissionGrantedEvent, object: this, map: this, android: this.nativeMapView }); }, onLocationPermissionDenied: (event) => { this.notify({ eventName: MapboxViewBase.locationPermissionDeniedEvent, object: this, map: this, android: this.nativeMapView }); }, onMapReady: (map) => { if (this.telemetry === false) { try { com.mapbox.mapboxsdk.Mapbox.getTelemetry().setUserTelemetryRequestState(false); console.error('telemtry disabled!'); } catch (err) { console.error('telemtry', err); } } if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initMap(): onMapReady event - calling notify with the MapboxViewBase.mapReadyEvent'); } if (this.hasListeners(MapboxViewBase.mapReadyEvent)) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initMap(): onMapReady has listeners.'); } } else { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initMap(): onMapReady DOES NOT HAVE listeners.'); } } this.notify({ eventName: MapboxViewBase.mapReadyEvent, object: this, map: this, android: this.nativeMapView }); }, onScrollEvent: (event) => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initMap(): onScrollEvent event'); } this.notify({ eventName: MapboxViewBase.scrollEvent, object: this, event, map: this, android: this.nativeMapView }); }, onMoveBeginEvent: (event) => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initMap(): onMoveBeginEvent event'); } this.notify({ eventName: MapboxViewBase.moveBeginEvent, object: this, event, map: this, android: this.nativeMapView }); }, onMoveEndEvent: (event) => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initMap(): onMoveEndEvent event'); } this.notify({ eventName: MapboxViewBase.moveEndEvent, object: this, event, map: this, android: this.nativeMapView }); } }; if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initMap(): this.config is:', this.config); } if (!this.settings) { this.settings = Mapbox.merge(this.config, Mapbox.defaults); } else { this.settings = Mapbox.merge(this.settings, Mapbox.defaults); } this.settings = Mapbox.merge(this.settings, options); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initMap(): before show.'); } this.mapbox.show(this.settings); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'initMap(): bottom.'); } } } [telemetryProperty.setNative](value) { if (com.mapbox.mapboxsdk.Mapbox.getTelemetry()) { com.mapbox.mapboxsdk.Mapbox.getTelemetry().setUserTelemetryRequestState(value); } } } export class Mapbox extends MapboxCommon { constructor(view) { super(view); this._accessToken = ''; this.circleManager = null; this.lineManager = null; this.symbolManager = null; this._markers = []; this._polylines = []; this._polygons = []; this.lines = []; this.eventCallbacks = {}; this._markerIconDownloadCache = []; this.iconCache = {}; if (Trace.isEnabled()) { CLog(CLogTypes.info, 'constructor(): building new Mapbox object.'); } this.eventCallbacks['click'] = []; if (Trace.isEnabled()) { CLog(CLogTypes.info, 'constructor(): end of Mapbox constructor.'); } } setMapboxViewInstance(mapboxViewInstance) { } show(options) { return new Promise((resolve, reject) => { try { const settings = Mapbox.merge(options, Mapbox.defaults); const showIt = () => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show()'); } if (settings.accessToken === undefined) { reject('mapbox_accesstoken_missing'); return; } if (this._mapboxViewInstance) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): view already created. Removing it.'); } const viewGroup = this._mapboxViewInstance.getParent(); if (viewGroup !== null) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): view already created. Removing _mapboxViewInstance child of view parent.'); } viewGroup.removeView(this._mapboxViewInstance); } } this._accessToken = settings.accessToken; let context = Application.android.context; if (settings.context) { context = settings.context; } com.mapbox.mapboxsdk.Mapbox.getInstance(context, this._accessToken); const mapboxMapOptions = this._getMapboxMapOptions(settings); this._mapboxViewInstance = new com.mapbox.mapboxsdk.maps.MapView(context, mapboxMapOptions); this._mapboxViewInstance.onCreate(null); if (Trace.isEnabled()) { this.onDidFailLoadingMapListener = new com.mapbox.mapboxsdk.maps.MapView.OnDidFailLoadingMapListener({ onDidFailLoadingMap: (error) => CLog(CLogTypes.error, 'Mapbox::show(): failed to load map:', error) }); this._mapboxViewInstance.addOnDidFailLoadingMapListener(this.onDidFailLoadingMapListener); } if (Trace.isEnabled()) { this.onDidFinishLoadingMapListener = new com.mapbox.mapboxsdk.maps.MapView.OnDidFinishLoadingMapListener({ onDidFinishLoadingMap: () => CLog(CLogTypes.info, 'show(): finished loading map') }); this._mapboxViewInstance.addOnDidFinishLoadingMapListener(this.onDidFinishLoadingMapListener); } this.onMapReadyCallback = new com.mapbox.mapboxsdk.maps.OnMapReadyCallback({ onMapReady: (mbMap) => { this._mapboxMapInstance = mbMap; if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): onMapReady() with instance:', this._mapboxMapInstance); } this.setMapStyle(settings.style).then((style) => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): style loaded.'); } this.initEventHandlerShim(settings, this._mapboxViewInstance); this._addMarkers(settings.markers, this._mapboxViewInstance); if (settings.showUserLocation) { this.requestFineLocationPermission() .then(() => { this.showUserLocationMarker(settings.locationComponentOptions); if (settings.onLocationPermissionGranted) { settings.onLocationPermissionGranted(this._mapboxMapInstance); } }) .catch((err) => { if (settings.onLocationPermissionDenied) { settings.onLocationPermissionDenied(this._mapboxMapInstance); } }); } if (settings.onMapReady) { settings.onMapReady(this._mapboxMapInstance); } resolve({ android: this._mapboxViewInstance }); }); } }); this._mapboxViewInstance.getMapAsync(this.onMapReadyCallback); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): after getMapAsync()'); } if (settings.parentView) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): adding map to passed in view'); } settings.parentView.addView(this._mapboxViewInstance); } else if (settings.container) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): adding map to passed in container'); } context = Application.android.foregroundActivity; if (!context) { context = Application.android.startActivity; } const mapViewLayout = new android.widget.FrameLayout(context); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): before adding mapboxViewInstance to FrameLayout'); } mapViewLayout.addView(this._mapboxViewInstance); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): before adding FrameLayout to container'); } settings.container.addChild(mapViewLayout); } if (Trace.isEnabled()) { CLog(CLogTypes.info, 'show(): showIt() bottom'); } }; setTimeout(showIt, settings.delay ? settings.delay : 200); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.show: ' + ex); } reject(ex); } }); } hide() { return new Promise((resolve, reject) => { try { if (this._mapboxViewInstance) { const viewGroup = this._mapboxViewInstance.getParent(); if (viewGroup !== null) { viewGroup.setVisibility(android.view.View.INVISIBLE); } } resolve(); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.hide: ' + ex); } reject(ex); } }); } unhide() { return new Promise((resolve, reject) => { try { if (this._mapboxViewInstance) { this._mapboxViewInstance.getParent().setVisibility(android.view.View.VISIBLE); resolve(); } else { reject('No map found'); } } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.unhide: ' + ex); } reject(ex); } }); } destroy(nativeMap) { return new Promise(async (resolve, reject) => { this.clearEventListeners(); this.iconFactory = null; if (Trace.isEnabled()) { CLog(CLogTypes.info, 'destroy(): destroying mapbox view.'); } if (this.lineManager) { this.lineManager.onDestroy(); this.lineManager = null; } if (this.circleManager) { this.circleManager.onDestroy(); this.circleManager = null; } if (this.symbolManager) { this.symbolManager.onDestroy(); this.symbolManager = null; } if (this._locationComponent) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'destroy(): Location marker not disabled before destroy() called.'); } await this.hideUserLocationMarker(); } if (this._mapboxViewInstance) { const viewGroup = this._mapboxViewInstance.getParent(); if (viewGroup !== null) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'destroy(): removing _mapboxViewInstance view.'); } viewGroup.removeView(this._mapboxViewInstance); } this._mapboxViewInstance.onPause(); this._mapboxViewInstance.onStop(); this._mapboxViewInstance.destroyDrawingCache(); this._mapboxViewInstance.onDestroy(); this._mapboxViewInstance = null; this._mapboxMapInstance = null; } resolve(); }); } clearEventListeners() { if (this.onDidFailLoadingMapListener) { this._mapboxViewInstance.removeOnDidFailLoadingMapListener(this.onDidFailLoadingMapListener); this.onDidFailLoadingMapListener = null; } if (this.onDidFinishLoadingMapListener) { this._mapboxViewInstance.removeOnDidFinishLoadingMapListener(this.onDidFinishLoadingMapListener); this.onDidFinishLoadingMapListener = null; } if (this.onDidFinishLoadingStyleListener) { this._mapboxViewInstance.removeOnDidFinishLoadingStyleListener(this.onDidFinishLoadingStyleListener); this.onDidFinishLoadingStyleListener = null; } if (this.onAnnotationClickListener) { this.lineManager.removeClickListener(this.onAnnotationClickListener); this.onAnnotationClickListener = null; } if (this.onMarkerClickListener) { this._mapboxMapInstance.setOnMarkerClickListener(null); this.onMarkerClickListener = null; } if (this.onInfoWindowClickListener) { this._mapboxMapInstance.setOnInfoWindowClickListener(null); this.onInfoWindowClickListener = null; } if (this.onDidFailLoadingMapListener) { this._mapboxViewInstance.removeOnDidFailLoadingMapListener(this.onDidFailLoadingMapListener); this.onDidFailLoadingMapListener = null; } if (this.onMapClickListener) { this._mapboxMapInstance.removeOnMapClickListener(this.onMapClickListener); this.onMapClickListener = null; } if (this.onMapLongClickListener) { this._mapboxMapInstance.removeOnMapLongClickListener(this.onMapLongClickListener); this.onMapLongClickListener = null; } if (this.onMoveListener) { this._mapboxMapInstance.removeOnMoveListener(this.onMoveListener); this.onMoveListener = null; } if (this.onScrollListener) { this._mapboxMapInstance.removeOnMoveListener(this.onScrollListener); this.onScrollListener = null; } if (this.onFlingListener) { this._mapboxMapInstance.removeOnFlingListener(this.onFlingListener); this.onFlingListener = null; } if (this.onCameraMoveListener) { this._mapboxMapInstance.removeOnCameraMoveListener(this.onCameraMoveListener); this.onCameraMoveListener = null; } if (this.onCameraMoveCancelListener) { this._mapboxMapInstance.removeOnCameraMoveCancelListener(this.onCameraMoveCancelListener); this.onCameraMoveCancelListener = null; } if (this.onCameraIdleListener) { this._mapboxMapInstance.removeOnCameraIdleListener(this.onCameraIdleListener); this.onCameraIdleListener = null; } if (this.onLocationClickListener) { this._locationComponent.removeOnLocationClickListener(this.onLocationClickListener); this.onLocationClickListener = null; } } async onStart(nativeMap) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'onStart()'); } this._mapboxViewInstance.onStart(); } async onResume(nativeMapViewInstance) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'onResume()'); } this._mapboxViewInstance.onResume(); } async onPause(nativeMapViewInstance) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'onPause()'); } this._mapboxViewInstance.onPause(); } async onStop(nativeMap) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'onStop()'); } this._mapboxViewInstance.onStop(); } async onLowMemory(nativeMap) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'onLowMemory()'); } this._mapboxViewInstance.onLowMemory(); } async onDestroy(nativeMap) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'onDestroy()'); } this._mapboxViewInstance.onDestroy(); } initEventHandlerShim(settings, mapboxNativeViewInstance) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Mapbox:initEventHandlerShim(): top'); } this.setOnMapClickListener((point) => this.checkForClickEvent(point), mapboxNativeViewInstance); this.setOnMoveBeginListener((point) => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Mapbox:initEventHandlerShim(): moveBegin:', point); } if (typeof settings.onMoveBeginEvent != 'undefined') { settings.onMoveBeginEvent(point); } }, mapboxNativeViewInstance); this.setOnMoveEndListener((point) => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Mapbox:initEventHandlerShim(): moveEnd:', point); } if (typeof settings.onMoveEndEvent != 'undefined') { settings.onMoveEndEvent(point); } }, mapboxNativeViewInstance); this.setOnScrollListener((point) => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Mapbox:initEventHandlerShim(): move:', point); } if (typeof settings.onScrollEvent != 'undefined') { settings.onScrollEvent(point); } }, mapboxNativeViewInstance); } onMapEvent(eventName, id, callback, nativeMapView) { if (typeof this.eventCallbacks[eventName] == 'undefined') { this.eventCallbacks[eventName] = []; } this.eventCallbacks[eventName].push({ id, callback }); } offMapEvent(eventName, id, nativeMapView) { if (typeof this.eventCallbacks[eventName] == 'undefined') { return; } this.eventCallbacks[eventName] = this.eventCallbacks[eventName].filter((entry) => entry.id !== id); } checkForClickEvent(point, nativeMap) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Mapbox:checkForClickEvent(): got click event with point:', JSON.stringify(point)); } this.eventCallbacks['click'] && this.eventCallbacks['click'].forEach((eventListener) => { this.queryRenderedFeatures({ point, layers: [eventListener.id] }).then((response) => { if (response.length > 0) { eventListener.callback(response); } }); }); this.view && this.view.notify({ eventName: 'mapClick', object: this.view, point }); return false; } handleLineClickEvent(clickOverlay) { const lineEntry = this.lines.find((entry) => { if (Trace.isEnabled()) { CLog(CLogTypes.info, "Mapbox:handleLineClickEvent(): checking lineEntry clickOverlay id '" + entry.clickOverlay + "' against clickOverlay '" + clickOverlay + "'"); } return entry.clickOverlay === clickOverlay; }); if (!lineEntry) { console.error('Mapbox:handleLineClick(): click on overlay without an underlying line layer'); return false; } for (let x = 0; x < this.eventCallbacks['click'].length; x++) { const entry = this.eventCallbacks['click'][x]; if (Trace.isEnabled()) { CLog(CLogTypes.info, "Mapbox:handleLineClickEvent(): checking entry id '" + entry.id + "' against lineEnty id '" + lineEntry.id + "'"); } if (entry.id === lineEntry.id) { if (Trace.isEnabled()) { CLog(CLogTypes.info, "Mapbox:handleLineClickEvent(): calling callback for '" + entry.id + "'"); } return entry.callback(lineEntry); } } return false; } hasFineLocationPermission() { return new Promise((resolve, reject) => { try { resolve(this._fineLocationPermissionGranted()); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.hasFineLocationPermission: ' + ex); } reject(ex); } }); } async requestFineLocationPermission() { return request('location'); } setMapStyle(style, nativeMapViewInstance) { return new Promise((resolve, reject) => { try { const mapStyle = this._getMapStyle(style); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'setMapStyle(): with style:', style); } this.onDidFinishLoadingStyleListener = new com.mapbox.mapboxsdk.maps.MapView.OnDidFinishLoadingStyleListener({ onDidFinishLoadingStyle: () => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Mapbox:setMapStyle(): style loaded'); } const nMapbox = this._mapboxMapInstance; const nMapView = this._mapboxViewInstance; const nStyle = nMapbox.getStyle(); this.lineManager = new com.mapbox.mapboxsdk.plugins.annotation.LineManager(nMapView, nMapbox, nStyle); this.onAnnotationClickListener = new com.mapbox.mapboxsdk.plugins.annotation.OnAnnotationClickListener({ onAnnotationClick: (line) => { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Mapbox:setMapStyle(): click on line:', line); } this.handleLineClickEvent(line); return true; } }); this.lineManager.addClickListener(this.onAnnotationClickListener); resolve(); } }); this._mapboxViewInstance.addOnDidFinishLoadingStyleListener(this.onDidFinishLoadingStyleListener); this.onDidFailLoadingMapListener = new com.mapbox.mapboxsdk.maps.MapView.OnDidFailLoadingMapListener({ onDidFailLoadingMap: (error) => { if (Trace.isEnabled()) { CLog(CLogTypes.error, 'Mapbox:setMapStyle(): style failed', mapStyle, error); } reject(error); } }); this._mapboxViewInstance.addOnDidFailLoadingMapListener(this.onDidFailLoadingMapListener); const builder = new com.mapbox.mapboxsdk.maps.Style.Builder(); this._mapboxMapInstance.setStyle(builder.fromUri(mapStyle)); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.error, 'Error in mapbox.setMapStyle', style, ex); } reject(ex); } }); } async getImage(imageId, nativeMap) { return new Promise((resolve, reject) => { const theMap = nativeMap || this._mapboxMapInstance; if (!theMap) { reject('No map has been loaded'); return; } try { const nativeImage = theMap.getStyle().getImage(imageId); const img = new ImageSource(nativeImage); resolve(img); } catch (ex) { reject('Error during getImage: ' + ex); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.getImage: ' + ex); } throw ex; } }); } async addImage(imageId, image, nativeMap) { return new Promise((resolve, reject) => { const theMap = nativeMap || this._mapboxMapInstance; if (!theMap) { reject('No map has been loaded'); return; } if (!image.startsWith('res://')) { image = path.join(knownFolders.currentApp().path, image.replace('~/', '')); } const img = ImageSource.fromFileOrResourceSync(image); try { theMap.getStyle().addImage(imageId, img.android); resolve(); } catch (ex) { reject('Error during addImage: ' + ex); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.addImage: ' + ex); } throw ex; } }); } async removeImage(imageId, nativeMap) { return new Promise((resolve, reject) => { const theMap = nativeMap || this._mapboxMapInstance; if (!theMap) { reject('No map has been loaded'); return; } try { theMap.getStyle().removeImage(imageId); resolve(); } catch (ex) { reject('Error during removeImage: ' + ex); if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.removeImage: ' + ex); } throw ex; } }); } async addMarkers(markers, nativeMap) { try { this._addMarkers(markers, this._mapboxViewInstance); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.addMarkers: ' + ex); } throw ex; } } async removeMarkers(ids, nativeMap) { try { this._removeMarkers(ids, this._mapboxViewInstance); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.removeMarkers: ' + ex); } throw ex; } } _addMarkers(markers, nativeMap) { if (!markers) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'No markers passed'); } return; } if (!Array.isArray(markers)) { if (Trace.isEnabled()) { CLog(CLogTypes.info, "markers must be passed as an Array: [{title:'foo'}]"); } return; } if (!this._mapboxMapInstance) { return; } if (!this.onMarkerClickListener) { this.onMarkerClickListener = new com.mapbox.mapboxsdk.maps.MapboxMap.OnMarkerClickListener({ onMarkerClick: (marker) => { const cachedMarker = this._getClickedMarkerDetails(marker); if (cachedMarker && cachedMarker.onTap) { cachedMarker.onTap(cachedMarker); } return false; } }); this._mapboxMapInstance.setOnMarkerClickListener(this.onMarkerClickListener); } if (!this.onInfoWindowClickListener) { this.onInfoWindowClickListener = new com.mapbox.mapboxsdk.maps.MapboxMap.OnInfoWindowClickListener({ onInfoWindowClick: (marker) => { const cachedMarker = this._getClickedMarkerDetails(marker); if (cachedMarker && cachedMarker.onCalloutTap) { cachedMarker.onCalloutTap(cachedMarker); } return true; } }); this._mapboxMapInstance.setOnInfoWindowClickListener(this.onInfoWindowClickListener); } if (!this.iconFactory) { this.iconFactory = com.mapbox.mapboxsdk.annotations.IconFactory.getInstance(Application.android.context); } const iconFactory = this.iconFactory; this._downloadMarkerImages(markers).then((updatedMarkers) => { for (const m in updatedMarkers) { const marker = updatedMarkers[m]; this._markers.push(marker); const markerOptions = new com.mapbox.mapboxsdk.annotations.MarkerOptions(); markerOptions.setTitle(marker.title); markerOptions.setSnippet(marker.subtitle); markerOptions.setPosition(new com.mapbox.mapboxsdk.geometry.LatLng(parseFloat(marker.lat), parseFloat(marker.lng))); if (marker.icon) { if (marker.icon.startsWith('res://')) { let cached = this.iconCache[marker.iconPath]; if (!cached) { const resourcename = marker.icon.substring(6); const res = Utils.ad.getApplicationContext().getResources(); const identifier = res.getIdentifier(resourcename, 'drawable', Utils.ad.getApplication().getPackageName()); if (identifier !== 0) { cached = this.iconCache[marker.iconPath] = iconFactory.fromResource(identifier); } } if (cached) { markerOptions.setIcon(cached); } else { console.log(`No icon found for this device density for icon ' ${marker.icon}'. Falling back to the default icon.`); } } else if (marker.icon.startsWith('http')) { if (marker.iconDownloaded !== null) { markerOptions.setIcon(iconFactory.fromBitmap(marker.iconDownloaded)); } } else { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Please use res://resourcename, http(s)://imageurl or iconPath to use a local path'); } } } else if (marker.iconPath) { let cached = this.iconCache[marker.iconPath]; if (!cached) { const iconFullPath = path.join(knownFolders.currentApp().path, marker.iconPath.replace('~/', '')); if (File.exists(iconFullPath)) { cached = this.iconCache[marker.iconPath] = iconFactory.fromPath(iconFullPath); } } if (cached) { markerOptions.setIcon(cached); } else { console.log(`Marker icon not found, using the default instead. Requested path: '" + ${marker.iconPath}'.`); } } marker.android = this._mapboxMapInstance.addMarker(markerOptions); if (marker.selected) { this._mapboxMapInstance.selectMarker(marker.android); } marker.update = (newSettings) => { for (const m in this._markers) { const _marker = this._markers[m]; if (marker.id === _marker.id) { if (newSettings.onTap !== undefined) { _marker.onTap = newSettings.onTap; } if (newSettings.onCalloutTap !== undefined) { _marker.onCalloutTap = newSettings.onCalloutTap; } if (newSettings.title !== undefined) { _marker.title = newSettings.title; _marker.android.setTitle(newSettings.title); } if (newSettings.subtitle !== undefined) { _marker.subtitle = newSettings.title; _marker.android.setSnippet(newSettings.subtitle); } if (newSettings.lat && newSettings.lng) { _marker.lat = newSettings.lat; _marker.lng = newSettings.lng; _marker.android.setPosition(new com.mapbox.mapboxsdk.geometry.LatLng(parseFloat(newSettings.lat), parseFloat(newSettings.lng))); } if (newSettings.selected) { this._mapboxMapInstance.selectMarker(_marker.android); } } } }; } }); } _removeMarkers(ids, nativeMap) { if (!this._mapboxMapInstance) { return; } for (const m in this._markers) { const marker = this._markers[m]; if (!ids || (marker && marker.id && ids.indexOf(marker.id) > -1)) { if (marker && marker.android) { this._mapboxMapInstance.removeAnnotation(marker.android); } } } if (ids) { this._markers = this._markers.filter((marker) => ids.indexOf(marker.id) === -1); } else { this._markers = []; } } setCenter(options, nativeMap) { return new Promise((resolve, reject) => { try { const cameraPosition = new com.mapbox.mapboxsdk.camera.CameraPosition.Builder().target(new com.mapbox.mapboxsdk.geometry.LatLng(options.lat, options.lng)).build(); if (options.animated === true) { const newCameraPosition = com.mapbox.mapboxsdk.camera.CameraUpdateFactory.newCameraPosition(cameraPosition); this._mapboxMapInstance.animateCamera(newCameraPosition, 1000, null); } else { this._mapboxMapInstance.setCameraPosition(cameraPosition); } resolve(); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.setCenter: ' + ex); } reject(ex); } }); } getCenter(nativeMap) { return new Promise((resolve, reject) => { try { const coordinate = this._mapboxMapInstance.getCameraPosition().target; resolve({ lat: coordinate.getLatitude(), lng: coordinate.getLongitude() }); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.getCenter: ' + ex); } reject(ex); } }); } setZoomLevel(options, nativeMap) { return new Promise((resolve, reject) => { try { const animated = options.animated === undefined || options.animated; const level = options.level; if (level >= 0 && level <= 20) { const cameraUpdate = com.mapbox.mapboxsdk.camera.CameraUpdateFactory.zoomTo(level); if (animated) { this._mapboxMapInstance.easeCamera(cameraUpdate); } else { this._mapboxMapInstance.moveCamera(cameraUpdate); } resolve(); } else { reject('invalid zoomlevel, use any double value from 0 to 20 (like 8.3)'); } } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.setZoomLevel: ' + ex); } reject(ex); } }); } getZoomLevel(nativeMap) { return new Promise((resolve, reject) => { try { const level = this._mapboxMapInstance.getCameraPosition().zoom; resolve(level); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.getZoomLevel: ' + ex); } reject(ex); } }); } setTilt(options, nativeMap) { return new Promise((resolve, reject) => { try { const tilt = options.tilt ? options.tilt : 30; const cameraPositionBuilder = new com.mapbox.mapboxsdk.camera.CameraPosition.Builder().tilt(tilt); const cameraUpdate = com.mapbox.mapboxsdk.camera.CameraUpdateFactory.newCameraPosition(cameraPositionBuilder.build()); const durationMs = options.duration ? options.duration : 5000; this._mapboxMapInstance.easeCamera(cameraUpdate, durationMs); setTimeout(() => { resolve(); }, durationMs); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.setTilt: ' + ex); } reject(ex); } }); } getTilt(nativeMap) { return new Promise((resolve, reject) => { try { const tilt = this._mapboxMapInstance.getCameraPosition().tilt; resolve(tilt); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.getTilt: ' + ex); } reject(ex); } }); } getUserLocation() { return new Promise((resolve, reject) => { try { const loc = this._locationComponent ? this._locationComponent.getLastKnownLocation() : null; if (loc === null) { reject('Location not available'); } else { resolve(_getLocation(loc)); } } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.getUserLocation: ' + ex); } reject(ex); } }); } queryRenderedFeatures(options) { return new Promise((resolve, reject) => { try { if (options.point === undefined) { reject("Please set the 'point' parameter"); return; } if (!options) { options = {}; } const mapboxPoint = new com.mapbox.mapboxsdk.geometry.LatLng(options.point.lat, options.point.lng); const screenLocation = this._mapboxMapInstance.getProjection().toScreenLocation(mapboxPoint); if (this._mapboxMapInstance.queryRenderedFeatures) { const queryFilter = options.filter ? ExpressionParser.parseJson(options.filter) : null; const features = this._mapboxMapInstance.queryRenderedFeatures(screenLocation, queryFilter, options.layers); const result = []; for (let i = 0; i < features.size(); i++) { const feature = features.get(i); result.push(JSON.parse(feature.toJson())); } resolve(result); } else { reject('Feature not supported by this Mapbox version'); } } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.queryRenderedFeatures: ' + ex); } reject(ex); } }); } querySourceFeatures(sourceId, options) { return new Promise((resolve, reject) => { try { if (!options) { options = {}; } const source = this._mapboxMapInstance.getStyle().getSource(sourceId); if (!source) { throw new Error(`Source with id "${sourceId}" not found.`); } let features; const queryFilter = options.filter ? ExpressionParser.parseJson(options.filter) : null; if (source instanceof com.mapbox.mapboxsdk.style.sources.GeoJsonSource) { features = source.querySourceFeatures(queryFilter); } else if (source instanceof com.mapbox.mapboxsdk.style.sources.VectorSource) { if (!options.sourceLayer) { throw new Error('The option "sourceLayer" is required for vector sources.'); } features = source.querySourceFeatures([options.sourceLayer], queryFilter); } else { throw new Error('Only sources from type "vector" or "geojson" are supported.'); } const result = []; for (let i = 0; i < features.size(); i++) { const feature = features.get(i); result.push(JSON.parse(feature.toJson())); } resolve(result); } catch (ex) { if (Trace.isEnabled()) { CLog(CLogTypes.info, 'Error in mapbox.querySourceFeatures: ' + ex); } reject(ex); } }); } addPolygon(options, nativeMap) { return new Promise((resolve, reject) => { try { const points = options.points; if (points === undefined) { reject("Please set the 'points' parameter"); return; } const polygonOptions = new com.mapbox.mapboxsdk.annotations.PolygonOptions(); for (const p in points) { const point = points[p]; polygonOptions.add(new com.mapbox.mapboxsdk.geometry.LatLng(point.lat, point.lng)); } polygonOptions.fillColor(Mapbox.getAndroidColor(options.fillColor)); polygonOptions.alpha(options.fillOpacity === undefined ? 1 : options.fillOpacity); if (options.strokeColor) { polygonOptions.strokeColor(Mapbox.getAnd