UNPKG

ngx-mapbox-gl

Version:

A Angular binding of mapbox-gl-js

725 lines (724 loc) 28.7 kB
import { Injectable, InjectionToken, Injector, NgZone, afterEveryRender, inject, } from '@angular/core'; import { Map, Marker, Popup, } from 'mapbox-gl'; import { AsyncSubject, Subscription } from 'rxjs'; import * as i0 from "@angular/core"; export const MAPBOX_API_KEY = new InjectionToken('MapboxApiKey'); export class MapService { zone = inject(NgZone); MAPBOX_API_KEY = inject(MAPBOX_API_KEY, { optional: true, }); injector = inject(Injector); mapInstance; mapCreated$; mapLoaded$; mapEvents; mapCreated = new AsyncSubject(); mapLoaded = new AsyncSubject(); markersToRemove = []; popupsToRemove = []; imageIdsToRemove = []; subscription = new Subscription(); constructor() { this.mapCreated$ = this.mapCreated.asObservable(); this.mapLoaded$ = this.mapLoaded.asObservable(); } setup(options) { const mapOptions = { ...options.mapOptions, bearing: Array.isArray(options.mapOptions.bearing) ? options.mapOptions.bearing[0] : options.mapOptions.bearing, zoom: Array.isArray(options.mapOptions.zoom) ? options.mapOptions.zoom[0] : options.mapOptions.zoom, pitch: Array.isArray(options.mapOptions.pitch) ? options.mapOptions.pitch[0] : options.mapOptions.pitch, accessToken: options.accessToken || this.MAPBOX_API_KEY || '', }; this.createMap(mapOptions); this.hookEvents(options.mapEvents); this.mapEvents = options.mapEvents; this.mapCreated.next(undefined); this.mapCreated.complete(); // Intentionally emit mapCreate after internal mapCreated event if (options.mapEvents.mapCreate.observed) { this.zone.run(() => { options.mapEvents.mapCreate.emit(this.mapInstance); }); } } destroyMap() { if (this.mapInstance) { this.subscription.unsubscribe(); this.mapInstance.remove(); } } updateProjection(projection) { return this.zone.runOutsideAngular(() => { this.mapInstance.setProjection(projection); }); } updateMinZoom(minZoom) { return this.zone.runOutsideAngular(() => { this.mapInstance.setMinZoom(minZoom); }); } updateMaxZoom(maxZoom) { return this.zone.runOutsideAngular(() => { this.mapInstance.setMaxZoom(maxZoom); }); } updateMinPitch(minPitch) { return this.zone.runOutsideAngular(() => { this.mapInstance.setMinPitch(minPitch); }); } updateMaxPitch(maxPitch) { return this.zone.runOutsideAngular(() => { this.mapInstance.setMaxPitch(maxPitch); }); } updateRenderWorldCopies(status) { return this.zone.runOutsideAngular(() => { this.mapInstance.setRenderWorldCopies(status); }); } updateScrollZoom(status) { return this.zone.runOutsideAngular(() => { status ? this.mapInstance.scrollZoom.enable() : this.mapInstance.scrollZoom.disable(); }); } updateDragRotate(status) { return this.zone.runOutsideAngular(() => { status ? this.mapInstance.dragRotate.enable() : this.mapInstance.dragRotate.disable(); }); } updateTouchPitch(status) { return this.zone.runOutsideAngular(() => { status ? this.mapInstance.touchPitch.enable() : this.mapInstance.touchPitch.disable(); }); } updateTouchZoomRotate(status) { return this.zone.runOutsideAngular(() => { status ? this.mapInstance.touchZoomRotate.enable() : this.mapInstance.touchZoomRotate.disable(); }); } updateDoubleClickZoom(status) { return this.zone.runOutsideAngular(() => { status ? this.mapInstance.doubleClickZoom.enable() : this.mapInstance.doubleClickZoom.disable(); }); } updateKeyboard(status) { return this.zone.runOutsideAngular(() => { status ? this.mapInstance.keyboard.enable() : this.mapInstance.keyboard.disable(); }); } updateDragPan(status) { return this.zone.runOutsideAngular(() => { status ? this.mapInstance.dragPan.enable() : this.mapInstance.dragPan.disable(); }); } updateBoxZoom(status) { return this.zone.runOutsideAngular(() => { status ? this.mapInstance.boxZoom.enable() : this.mapInstance.boxZoom.disable(); }); } updateStyle(style) { return this.zone.runOutsideAngular(() => { this.mapInstance.setStyle(style); }); } updateMaxBounds(maxBounds) { return this.zone.runOutsideAngular(() => { this.mapInstance.setMaxBounds(maxBounds); }); } changeCanvasCursor(cursor) { const canvas = this.mapInstance.getCanvasContainer(); canvas.style.cursor = cursor; } queryRenderedFeatures(pointOrBox, parameters) { return this.mapInstance.queryRenderedFeatures(pointOrBox, parameters); } panTo(center, options) { return this.zone.runOutsideAngular(() => { this.mapInstance.panTo(center, options); }); } move(movingMethod, movingOptions, zoom, center, bearing, pitch) { return this.zone.runOutsideAngular(() => { this.mapInstance[movingMethod]({ ...movingOptions, zoom: zoom != null ? zoom : this.mapInstance.getZoom(), center: center != null ? center : this.mapInstance.getCenter(), bearing: bearing != null ? bearing : this.mapInstance.getBearing(), pitch: pitch != null ? pitch : this.mapInstance.getPitch(), }); }); } addLayer(layer, bindEvents, before) { this.zone.runOutsideAngular(() => { Object.keys(layer.layerOptions).forEach((key) => { const tkey = key; if (layer.layerOptions[tkey] === undefined) { delete layer.layerOptions[tkey]; } }); this.mapInstance.addLayer(layer.layerOptions, before); if (bindEvents) { if (layer.layerEvents.layerClick.observed) { this.mapInstance.on('click', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerClick.emit(evt); }); }); } if (layer.layerEvents.layerDblClick.observed) { this.mapInstance.on('dblclick', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerDblClick.emit(evt); }); }); } if (layer.layerEvents.layerMouseDown.observed) { this.mapInstance.on('mousedown', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerMouseDown.emit(evt); }); }); } if (layer.layerEvents.layerMouseUp.observed) { this.mapInstance.on('mouseup', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerMouseUp.emit(evt); }); }); } if (layer.layerEvents.layerMouseEnter.observed) { this.mapInstance.on('mouseenter', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerMouseEnter.emit(evt); }); }); } if (layer.layerEvents.layerMouseLeave.observed) { this.mapInstance.on('mouseleave', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerMouseLeave.emit(evt); }); }); } if (layer.layerEvents.layerMouseMove.observed) { this.mapInstance.on('mousemove', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerMouseMove.emit(evt); }); }); } if (layer.layerEvents.layerMouseOver.observed) { this.mapInstance.on('mouseover', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerMouseOver.emit(evt); }); }); } if (layer.layerEvents.layerMouseOut.observed) { this.mapInstance.on('mouseout', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerMouseOut.emit(evt); }); }); } if (layer.layerEvents.layerContextMenu.observed) { this.mapInstance.on('contextmenu', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerContextMenu.emit(evt); }); }); } if (layer.layerEvents.layerTouchStart.observed) { this.mapInstance.on('touchstart', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerTouchStart.emit(evt); }); }); } if (layer.layerEvents.layerTouchEnd.observed) { this.mapInstance.on('touchend', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerTouchEnd.emit(evt); }); }); } if (layer.layerEvents.layerTouchCancel.observed) { this.mapInstance.on('touchcancel', layer.layerOptions.id, (evt) => { this.zone.run(() => { layer.layerEvents.layerTouchCancel.emit(evt); }); }); } } }); } removeLayer(layerId) { this.zone.runOutsideAngular(() => { if (this.mapInstance.getLayer(layerId) != null) { this.mapInstance.removeLayer(layerId); } }); } addMarker(marker) { const options = { offset: marker.markersOptions.offset, anchor: marker.markersOptions.anchor, draggable: marker.markersOptions.draggable, rotationAlignment: marker.markersOptions.rotationAlignment, pitchAlignment: marker.markersOptions.pitchAlignment, clickTolerance: marker.markersOptions.clickTolerance, }; Object.keys(options).forEach((key) => { const tkey = key; if (options[tkey] === undefined) { delete options[tkey]; } }); if (marker.markersOptions.element.childNodes.length > 0) { options.element = marker.markersOptions.element; } const markerInstance = new Marker(options); if (marker.markersEvents.markerDragStart.observed) { markerInstance.on('dragstart', (event) => { if (event) { const { target } = event; this.zone.run(() => { marker.markersEvents.markerDragStart.emit(target); }); } }); } /* */ if (marker.markersEvents.markerDrag.observed) { markerInstance.on('drag', (event) => { if (event) { const { target } = event; this.zone.run(() => { marker.markersEvents.markerDrag.emit(target); }); } }); } if (marker.markersEvents.markerDragEnd.observed) { markerInstance.on('dragend', (event) => { if (event) { const { target } = event; this.zone.run(() => { marker.markersEvents.markerDragEnd.emit(target); }); } }); } const lngLat = marker.markersOptions.feature ? marker.markersOptions.feature.geometry.coordinates : marker.markersOptions.lngLat; markerInstance.setLngLat(lngLat); return this.zone.runOutsideAngular(() => { markerInstance.addTo(this.mapInstance); return markerInstance; }); } removeMarker(marker) { this.markersToRemove.push(marker); } createPopup(popup, element) { return this.zone.runOutsideAngular(() => { Object.keys(popup.popupOptions).forEach((key) => { const tkey = key; return (popup.popupOptions[tkey] === undefined && delete popup.popupOptions[tkey]); }); const popupInstance = new Popup(popup.popupOptions); popupInstance.setDOMContent(element); if (popup.popupEvents.popupClose.observed) { popupInstance.on('close', () => { this.zone.run(() => { popup.popupEvents.popupClose.emit(); }); }); } if (popup.popupEvents.popupOpen.observed) { popupInstance.on('open', () => { this.zone.run(() => { popup.popupEvents.popupOpen.emit(); }); }); } return popupInstance; }); } addPopupToMap(popup, lngLat, skipOpenEvent = false) { return this.zone.runOutsideAngular(() => { if (skipOpenEvent && popup._listeners) { delete popup._listeners['open']; } popup.setLngLat(lngLat); popup.addTo(this.mapInstance); }); } addPopupToMarker(marker, popup) { return this.zone.runOutsideAngular(() => { marker.setPopup(popup); }); } removePopupFromMap(popup, skipCloseEvent = false) { if (skipCloseEvent && popup._listeners) { delete popup._listeners['close']; } this.popupsToRemove.push(popup); } removePopupFromMarker(marker) { return this.zone.runOutsideAngular(() => { marker.setPopup(undefined); }); } addControl(control, position) { return this.zone.runOutsideAngular(() => { this.mapInstance.addControl(control, position); }); } removeControl(control) { return this.zone.runOutsideAngular(() => { this.mapInstance.removeControl(control); }); } async loadAndAddImage(imageId, url, options) { return this.zone.runOutsideAngular(() => new Promise((resolve, reject) => { this.mapInstance.loadImage(url, (error, image) => { if (error) { reject(error); return; } if (!image) { reject(new Error('Image not loaded')); return; } this.addImage(imageId, image, options); resolve(); }); })); } addImage(imageId, data, options) { return this.zone.runOutsideAngular(() => { this.mapInstance.addImage(imageId, data, options); }); } removeImage(imageId) { this.imageIdsToRemove.push(imageId); } addSource(sourceId, source) { return this.zone.runOutsideAngular(() => { Object.keys(source).forEach((key) => { const tkey = key; return source[tkey] === undefined && delete source[tkey]; }); this.mapInstance.addSource(sourceId, source); }); } getSource(sourceId) { return this.mapInstance.getSource(sourceId); } removeSource(sourceId) { this.zone.runOutsideAngular(() => { this.findLayersBySourceId(sourceId).forEach((layer) => this.mapInstance.removeLayer(layer.id)); this.mapInstance.removeSource(sourceId); }); } setLayerAllPaintProperty(layerId, paint) { return this.zone.runOutsideAngular(() => { Object.keys(paint).forEach((key) => { const tKey = key; // TODO Check for perf, setPaintProperty only on changed paint props maybe this.mapInstance.setPaintProperty(layerId, tKey, paint[tKey]); }); }); } setLayerAllLayoutProperty(layerId, layout) { return this.zone.runOutsideAngular(() => { Object.keys(layout).forEach((key) => { const tKey = key; // TODO Check for perf, setLayoutProperty only on changed layout props maybe this.mapInstance.setLayoutProperty(layerId, tKey, layout[tKey]); }); }); } setLayerFilter(layerId, filter) { return this.zone.runOutsideAngular(() => { this.mapInstance.setFilter(layerId, filter); }); } setLayerBefore(layerId, beforeId) { return this.zone.runOutsideAngular(() => { this.mapInstance.moveLayer(layerId, beforeId); }); } setLayerZoomRange(layerId, minZoom, maxZoom) { return this.zone.runOutsideAngular(() => { this.mapInstance.setLayerZoomRange(layerId, minZoom ? minZoom : 0, maxZoom ? maxZoom : 20); }); } fitBounds(bounds, options) { return this.zone.runOutsideAngular(() => { this.mapInstance.fitBounds(bounds, options); }); } fitScreenCoordinates(points, bearing, options) { return this.zone.runOutsideAngular(() => { this.mapInstance.fitScreenCoordinates(points[0], points[1], bearing, options); }); } applyChanges() { this.zone.runOutsideAngular(() => { this.removeMarkers(); this.removePopups(); this.removeImages(); }); } createMap(options) { NgZone.assertNotInAngularZone(); Object.keys(options).forEach((key) => { const tkey = key; if (options[tkey] === undefined) { delete options[tkey]; } }); this.mapInstance = new Map(options); afterEveryRender({ write: () => { this.applyChanges(); }, }, { injector: this.injector }); } removeMarkers() { for (const marker of this.markersToRemove) { marker.remove(); } this.markersToRemove = []; } removePopups() { for (const popup of this.popupsToRemove) { popup.remove(); } this.popupsToRemove = []; } removeImages() { for (const imageId of this.imageIdsToRemove) { this.mapInstance.removeImage(imageId); } this.imageIdsToRemove = []; } findLayersBySourceId(sourceId) { const layers = this.mapInstance.getStyle().layers; if (layers == null) { return []; } return layers.filter((l) => 'source' in l ? l.source === sourceId : false); } hookEvents(events) { this.mapInstance.on('load', (evt) => { this.mapLoaded.next(undefined); this.mapLoaded.complete(); this.zone.run(() => { events.mapLoad.emit(evt); }); }); if (events.mapResize.observed) { this.mapInstance.on('resize', (evt) => this.zone.run(() => { events.mapResize.emit(evt); })); } if (events.mapRemove.observed) { this.mapInstance.on('remove', (evt) => this.zone.run(() => { events.mapRemove.emit(evt); })); } if (events.mapMouseDown.observed) { this.mapInstance.on('mousedown', (evt) => this.zone.run(() => { events.mapMouseDown.emit(evt); })); } if (events.mapMouseUp.observed) { this.mapInstance.on('mouseup', (evt) => this.zone.run(() => { events.mapMouseUp.emit(evt); })); } if (events.mapMouseMove.observed) { this.mapInstance.on('mousemove', (evt) => this.zone.run(() => { events.mapMouseMove.emit(evt); })); } if (events.mapClick.observed) { this.mapInstance.on('click', (evt) => this.zone.run(() => { events.mapClick.emit(evt); })); } if (events.mapDblClick.observed) { this.mapInstance.on('dblclick', (evt) => this.zone.run(() => { events.mapDblClick.emit(evt); })); } if (events.mapMouseOver.observed) { this.mapInstance.on('mouseover', (evt) => this.zone.run(() => { events.mapMouseOver.emit(evt); })); } if (events.mapMouseOut.observed) { this.mapInstance.on('mouseout', (evt) => this.zone.run(() => { events.mapMouseOut.emit(evt); })); } if (events.mapContextMenu.observed) { this.mapInstance.on('contextmenu', (evt) => this.zone.run(() => { events.mapContextMenu.emit(evt); })); } if (events.mapTouchStart.observed) { this.mapInstance.on('touchstart', (evt) => this.zone.run(() => { events.mapTouchStart.emit(evt); })); } if (events.mapTouchEnd.observed) { this.mapInstance.on('touchend', (evt) => this.zone.run(() => { events.mapTouchEnd.emit(evt); })); } if (events.mapTouchMove.observed) { this.mapInstance.on('touchmove', (evt) => this.zone.run(() => { events.mapTouchMove.emit(evt); })); } if (events.mapTouchCancel.observed) { this.mapInstance.on('touchcancel', (evt) => this.zone.run(() => { events.mapTouchCancel.emit(evt); })); } if (events.mapWheel.observed) { this.mapInstance.on('wheel', (evt) => this.zone.run(() => { events.mapWheel.emit(evt); })); } if (events.moveStart.observed) { this.mapInstance.on('movestart', (evt) => this.zone.run(() => events.moveStart.emit(evt))); } if (events.move.observed) { this.mapInstance.on('move', (evt) => this.zone.run(() => events.move.emit(evt))); } if (events.moveEnd.observed) { this.mapInstance.on('moveend', (evt) => this.zone.run(() => events.moveEnd.emit(evt))); } if (events.mapDragStart.observed) { this.mapInstance.on('dragstart', (evt) => this.zone.run(() => events.mapDragStart.emit(evt))); } if (events.mapDrag.observed) { this.mapInstance.on('drag', (evt) => this.zone.run(() => events.mapDrag.emit(evt))); } if (events.mapDragEnd.observed) { this.mapInstance.on('dragend', (evt) => this.zone.run(() => events.mapDragEnd.emit(evt))); } if (events.zoomStart.observed) { this.mapInstance.on('zoomstart', () => this.zone.run(() => events.zoomStart.emit())); } if (events.zoomEvt.observed) { this.mapInstance.on('zoom', () => this.zone.run(() => events.zoomEvt.emit())); } if (events.zoomEnd.observed) { this.mapInstance.on('zoomend', () => this.zone.run(() => events.zoomEnd.emit())); } if (events.rotateStart.observed) { this.mapInstance.on('rotatestart', (evt) => this.zone.run(() => events.rotateStart.emit(evt))); } if (events.rotate.observed) { this.mapInstance.on('rotate', (evt) => this.zone.run(() => events.rotate.emit(evt))); } if (events.rotateEnd.observed) { this.mapInstance.on('rotateend', (evt) => this.zone.run(() => events.rotateEnd.emit(evt))); } if (events.pitchStart.observed) { this.mapInstance.on('pitchstart', () => this.zone.run(() => events.pitchStart.emit())); } if (events.pitchEvt.observed) { this.mapInstance.on('pitch', () => this.zone.run(() => events.pitchEvt.emit())); } if (events.pitchEnd.observed) { this.mapInstance.on('pitchend', () => this.zone.run(() => events.pitchEnd.emit())); } if (events.boxZoomStart.observed) { this.mapInstance.on('boxzoomstart', (evt) => this.zone.run(() => events.boxZoomStart.emit(evt))); } if (events.boxZoomEnd.observed) { this.mapInstance.on('boxzoomend', (evt) => this.zone.run(() => events.boxZoomEnd.emit(evt))); } if (events.boxZoomCancel.observed) { this.mapInstance.on('boxzoomcancel', (evt) => this.zone.run(() => events.boxZoomCancel.emit(evt))); } if (events.webGlContextLost.observed) { this.mapInstance.on('webglcontextlost', (evt) => this.zone.run(() => events.webGlContextLost.emit(evt))); } if (events.webGlContextRestored.observed) { this.mapInstance.on('webglcontextrestored', (evt) => this.zone.run(() => events.webGlContextRestored.emit(evt))); } if (events.render.observed) { this.mapInstance.on('render', () => this.zone.run(() => events.render.emit())); } if (events.mapError.observed) { this.mapInstance.on('error', (evt) => this.zone.run(() => events.mapError.emit(evt.error))); } if (events.data.observed) { this.mapInstance.on('data', (evt) => this.zone.run(() => events.data.emit(evt))); } if (events.styleData.observed) { this.mapInstance.on('styledata', (evt) => this.zone.run(() => events.styleData.emit(evt))); } if (events.sourceData.observed) { this.mapInstance.on('sourcedata', (evt) => this.zone.run(() => events.sourceData.emit(evt))); } if (events.dataLoading.observed) { this.mapInstance.on('dataloading', (evt) => this.zone.run(() => events.dataLoading.emit(evt))); } if (events.styleDataLoading.observed) { this.mapInstance.on('styledataloading', (evt) => this.zone.run(() => events.styleDataLoading.emit(evt))); } if (events.sourceDataLoading.observed) { this.mapInstance.on('sourcedataloading', (evt) => this.zone.run(() => events.sourceDataLoading.emit(evt))); } if (events.styleImageMissing.observed) { this.mapInstance.on('styleimagemissing', (evt) => this.zone.run(() => events.styleImageMissing.emit(evt))); } if (events.idle.observed) { this.mapInstance.on('idle', () => this.zone.run(() => events.idle.emit())); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: MapService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: MapService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: MapService, decorators: [{ type: Injectable }], ctorParameters: () => [] }); //# sourceMappingURL=map.service.js.map