From 64644f2399780e4f231756339b961d077ab01f71 Mon Sep 17 00:00:00 2001 From: Merge angular migration to maplibre/mapbox refactor Date: Thu, 12 Dec 2024 18:23:16 +0100 Subject: [PATCH] Externalise the map interface methods to an angular service --- .../lib/pipes/get-value/get-value.module.ts | 2 + .../src/lib/arlas-map-logic.service.ts | 91 ++++- .../src/lib/arlas-map.component.html | 4 +- .../arlas-map/src/lib/arlas-map.component.ts | 250 +++++-------- .../src/lib/basemaps/basemap.component.ts | 72 +--- .../src/lib/basemaps/basemap.service.ts | 12 +- .../arlas-map/src/lib/draw/AbstractDraw.ts | 14 - .../lib/map-import/map-import.component.ts | 2 +- .../src/lib/map/AbstractArlasMapGL.ts | 334 ++---------------- .../src/lib/map/interface/map.interface.ts | 144 +------- .../src/lib/map/service/arlas-map.service.ts | 72 +--- projects/arlas-map/src/lib/map/tools.ts | 42 +++ .../src/lib/arlas-map-logic.service.ts | 119 +++++++ .../src/lib/arlas-mapbox.service.ts | 154 ++++++-- .../lib/basemaps/mapbox-basemap.service.ts | 61 +++- .../arlas-mapbox/src/lib/map/ArlasMapboxGL.ts | 223 +----------- projects/arlas-mapbox/src/public-api.ts | 1 + .../src/lib/arlas-map-logic.service.ts | 164 +++------ .../src/lib/arlas-maplibre.service.ts | 80 +++-- .../lib/basemaps/mapgl-basemap.component.ts | 7 +- .../lib/basemaps/maplibre-basemap.service.ts | 58 ++- .../src/lib/map/ArlasMaplibreGL.ts | 212 ++--------- projects/arlas-maplibre/src/public-api.ts | 3 +- 23 files changed, 786 insertions(+), 1335 deletions(-) create mode 100644 projects/arlas-map/src/lib/map/tools.ts create mode 100644 projects/arlas-mapbox/src/lib/arlas-map-logic.service.ts diff --git a/projects/arlas-components/src/lib/pipes/get-value/get-value.module.ts b/projects/arlas-components/src/lib/pipes/get-value/get-value.module.ts index ada1cc63..a851e179 100644 --- a/projects/arlas-components/src/lib/pipes/get-value/get-value.module.ts +++ b/projects/arlas-components/src/lib/pipes/get-value/get-value.module.ts @@ -16,11 +16,13 @@ * specific language governing permissions and limitations * under the License. */ +import { CommonModule } from '@angular/common'; import { GetValuePipe } from './get-value.pipe'; import { NgModule } from '@angular/core'; @NgModule({ imports: [ + CommonModule ], declarations: [ GetValuePipe diff --git a/projects/arlas-map/src/lib/arlas-map-logic.service.ts b/projects/arlas-map/src/lib/arlas-map-logic.service.ts index 639c39b7..1e35f724 100644 --- a/projects/arlas-map/src/lib/arlas-map-logic.service.ts +++ b/projects/arlas-map/src/lib/arlas-map-logic.service.ts @@ -49,7 +49,7 @@ export abstract class ArlasMapFunctionalService { public updateLabelSources(labelSourceId: string, data: FeatureCollection, map: AbstractArlasMapGL) { if (labelSourceId) { const source = this.mapService.getSource(labelSourceId, map); - this.mapService.setDataToGeojsonSource(labelSourceId, source); + this.mapService.setDataToGeojsonSource(source, data); } } @@ -76,7 +76,6 @@ export abstract class ArlasMapFunctionalService { */ public initVisualisationSet(visualisationSetsConfig: VisualisationSetConfig[]) { if (visualisationSetsConfig) { - console.log('_initVisualisationSet'); visualisationSetsConfig.forEach(visu => { this.visualisationsSets.visualisations.set(visu.name, new Set(visu.layers)); this.visualisationsSets.status.set(visu.name, visu.enabled); @@ -86,7 +85,6 @@ export abstract class ArlasMapFunctionalService { public initMapLayers(mapLayers: MapLayers, map: AbstractArlasMapGL) { if (mapLayers) { - console.log('init maplayers'); this.setLayersMap(mapLayers as MapLayers); } } @@ -117,7 +115,9 @@ export abstract class ArlasMapFunctionalService { if (!!mapLayers.externalEventLayers) { mapLayers.layers .filter(layer => mapLayers.externalEventLayers.map(e => e.id).indexOf(layer.id) >= 0) - .forEach(l => this.mapService.addLayer(map, l.id)); + .forEach(l => { + this.mapService.addLayer(map, l); + }); } } @@ -153,7 +153,7 @@ export abstract class ArlasMapFunctionalService { const isCollectionCompatible = (!collection || (!!collection && (fullLayer.source as string).includes(collection))); if (isCollectionCompatible) { const originalLayerId = layer.id.replace('arlas-' + visibilityEvent.toString() + '-', ''); - const originalLayer = this.mapService.getLayer(map, originalLayerId); + const originalLayer = this.mapService.getAllLayers(map).find(l => l.id === originalLayerId); if (!!originalLayer) { originalLayerIsVisible = this.mapService.isLayerVisible(originalLayer); } @@ -180,7 +180,6 @@ export abstract class ArlasMapFunctionalService { } } - public selectFeatures(mapLayers: MapLayers, map: AbstractArlasMapGL, elementToSelect: Array) { if (elementToSelect) { const ids = elementToSelect.length > 0 ? @@ -214,4 +213,84 @@ export abstract class ArlasMapFunctionalService { } + public updateLayoutVisibility(visualisationName: string, visualisationSetsConfig: VisualisationSetConfig[], map: AbstractArlasMapGL) { + const visuStatus = !this.visualisationsSets.status.get(visualisationName); + visualisationSetsConfig.find(v => v.name === visualisationName).enabled = visuStatus; + if (!visuStatus) { + const layersSet = new Set(this.visualisationsSets.visualisations.get(visualisationName)); + this.visualisationsSets.visualisations.forEach((ls, v) => { + if (v !== visualisationName) { + ls.forEach(ll => { + if (layersSet && layersSet.has(ll)) { + layersSet.delete(ll); + } + }); + } + }); + layersSet.forEach(ll => { + this.mapService.setLayerVisibility(ll, false, map); + }); + } + this.visualisationsSets.status.set(visualisationName, visuStatus); + const layers = new Set(); + this.visualisationsSets.visualisations.forEach((ls, v) => { + if (this.visualisationsSets.status.get(v)) { + ls.forEach(l => { + layers.add(l); + this.mapService.setLayerVisibility(l, true, map); + }); + } + }); + return layers; + } + + public updateVisibility(visibilityStatus: Map, visualisationSetsConfig: VisualisationSetConfig[], map: AbstractArlasMapGL) { + visibilityStatus.forEach((visibilityStatus, l) => { + let layerInVisualisations = false; + if (!visibilityStatus) { + visualisationSetsConfig.forEach(v => { + const ls = new Set(v.layers); + if (!layerInVisualisations) { + layerInVisualisations = ls.has(l); + } + }); + if (layerInVisualisations) { + this.mapService.setLayerVisibility(l, false, map); + } + } else { + let oneVisualisationEnabled = false; + visualisationSetsConfig.forEach(v => { + const ls = new Set(v.layers); + if (!layerInVisualisations) { + layerInVisualisations = ls.has(l); + } + if (ls.has(l) && v.enabled) { + oneVisualisationEnabled = true; + this.mapService.setLayerVisibility(l, true, map); + } + }); + if (!oneVisualisationEnabled && layerInVisualisations) { + this.mapService.setLayerVisibility(l, false, map); + } + } + }); + } + + + public findVisualisationSetLayer(visuName: string, visualisationSetsConfig: VisualisationSetConfig[]) { + return visualisationSetsConfig.find(v => v.name === visuName).layers; + } + public setVisualisationSetLayers(visuName: string, layers: string[], visualisationSetsConfig: VisualisationSetConfig[]) { + const f = visualisationSetsConfig.find(v => v.name === visuName); + if (f) { + f.layers = layers; + } + } + + public abstract updateMapStyle(map: AbstractArlasMapGL, l: any, ids: Array, sourceName: string): void; + + public abstract getVisibleIdsFilter(map: AbstractArlasMapGL, layer: any, ids: Array); + + + } \ No newline at end of file diff --git a/projects/arlas-map/src/lib/arlas-map.component.html b/projects/arlas-map/src/lib/arlas-map.component.html index 1ff761c6..b9731de5 100644 --- a/projects/arlas-map/src/lib/arlas-map.component.html +++ b/projects/arlas-map/src/lib/arlas-map.component.html @@ -21,8 +21,8 @@
drag_indicator
- diff --git a/projects/arlas-map/src/lib/arlas-map.component.ts b/projects/arlas-map/src/lib/arlas-map.component.ts index d4a87b5b..93b1a3a2 100644 --- a/projects/arlas-map/src/lib/arlas-map.component.ts +++ b/projects/arlas-map/src/lib/arlas-map.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, HostListener, Input, OnInit, Output, SimpleCha import { marker } from '@biesbjerg/ngx-translate-extract-marker'; import { Feature, FeatureCollection, Geometry, Polygon, polygon } from '@turf/helpers'; import { BasemapStyle } from './basemaps/basemap.config'; -import { ArlasMapOffset, AbstractArlasMapGL, ElementIdentifier, MapConfig, ZOOM_IN, ZOOM_OUT, RESET_BEARING } from './map/AbstractArlasMapGL'; +import { ArlasMapOffset, AbstractArlasMapGL, ElementIdentifier, MapConfig, ZOOM_IN, ZOOM_OUT, RESET_BEARING, CROSS_LAYER_PREFIX } from './map/AbstractArlasMapGL'; import { IconConfig, ControlPosition, DrawControlsOption } from './map/model/controls'; import { AoiDimensions, BboxDrawCommand } from './draw/draw.models'; import { LegendData } from './legend/legend.config'; @@ -34,8 +34,9 @@ import centroid from '@turf/centroid'; import { ARLAS_VSET } from './map/model/layers'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { OnMoveResult } from './map/model/map'; -import { MapLayerMouseEvent, MapMouseEvent } from './map/model/events'; +import { MapMouseEvent } from './map/model/events'; import { ArlasMapFunctionalService } from './arlas-map-logic.service'; +import { latLngToWKT } from './map/tools'; @Component({ selector: 'arlas-map', @@ -77,7 +78,7 @@ export class ArlasMapComponent implements OnInit { public nbPolygonVertice = 0; /** Number of clicks while drawing !! How is it different from the var above ??? */ public drawClickCounter = 0; - /** !! TODO description */ + /** List of drawn polygons centroid */ public polygonlabeldata: FeatureCollection = Object.assign({}, this.emptyData); /** Whether the list of basemaps is shown. */ public showBasemapsList = false; @@ -106,12 +107,9 @@ export class ArlasMapComponent implements OnInit { private drawBboxSubscription: Subscription; - /** ------------------------------------------------------- VISUAL SEPERATOR ----------------------------------------- */ - - /** ANGULAR INPUTS */ /** @description Html identifier given to the map container (it's a div ;))*/ @@ -253,16 +251,9 @@ export class ArlasMapComponent implements OnInit { @Input() public visualisationSetsConfig: Array; - - - /** ------------------------------------------------------- VISUAL SEPERATOR ----------------------------------------- */ - - - - /** ANGULAR OUTPUTS */ @@ -277,14 +268,14 @@ export class ArlasMapComponent implements OnInit { /** @description @deprecated Emits the event of moving the map. */ @Output() public onMove: EventEmitter = new EventEmitter(); - /** @description Emits the visualisations !!! TODO description !!! */ + /** @description Emits the visible visualisations ids */ @Output() public visualisations: EventEmitter> = new EventEmitter(); - /** @description Emits the event of clicking on a feature. !!! TODO : fix spelling !!!*/ - @Output() public onFeatureClic = new EventEmitter<{ features: Array>; point: [number, number]; }>(); + /** @description Emits the event of clicking on a feature. */ + @Output() public onFeatureClick = new EventEmitter<{ features: Array>; point: [number, number]; }>(); - /** @description Emits the event of hovering feature. !!! TODO : fix spelling !!! */ - @Output() public onFeatureOver = new EventEmitter<{ features: Array>; point: [number, number]; } | {}>(); + /** @description Emits the event of hovering feature. */ + @Output() public onFeatureHover = new EventEmitter<{ features: Array>; point: [number, number]; } | {}>(); /** @description Emits the geojson of an aoi added to the map. */ @Output() public onAoiChanged: EventEmitter> = new EventEmitter(); @@ -298,7 +289,7 @@ export class ArlasMapComponent implements OnInit { /** @description Emits which layers are displayed in the Legend. */ @Output() public legendVisibiltyStatus: Subject> = new Subject(); - /** @description !!! todo !!! */ + /** @description Notifies that the user wants to download the selected layer */ @Output() public downloadSourceEmitter: Subject<{ layerId: string; layerName: string; @@ -307,25 +298,15 @@ export class ArlasMapComponent implements OnInit { downloadType: string; }> = new Subject(); - - protected ICONS_BASE_PATH = 'assets/icons/'; - /** ------------------------------------------------------- VISUAL SEPERATOR - INIT ----------------------------------------- */ - - - - - - public constructor(private http: HttpClient, private drawService: MapboxAoiDrawService, private basemapService: BasemapService, private _snackBar: MatSnackBar, private translate: TranslateService, protected mapService: ArlasMapService, protected mapFunctionalService: ArlasMapFunctionalService) { - console.log('ummm'); this.aoiEditSubscription = this.drawService.editAoi$.subscribe(ae => this.onAoiEdit.emit(ae)); this.drawBboxSubscription = this.drawService.drawBbox$.subscribe({ next: (bboxDC: BboxDrawCommand) => { @@ -337,12 +318,10 @@ export class ArlasMapComponent implements OnInit { public ngOnInit() { this.offlineBasemapChangeSubscription = this.basemapService.protomapBasemapAdded$.subscribe(() => this.reorderLayers()); - console.log('ummm init'); } public ngAfterViewInit() { /** init values */ - console.log('ummm after'); if (!this.initCenter) { this.initCenter = [0, 0]; } @@ -414,9 +393,6 @@ export class ArlasMapComponent implements OnInit { } } - - - /** ------------------------------------------------------- VISUAL SEPERATOR - MAP ----------------------------------------- */ /** If transformRequest' @Input was not set, set a default value : a function that maintains the same url */ @@ -426,8 +402,8 @@ export class ArlasMapComponent implements OnInit { } } - /** TODO comment */ - public defaultOnZoom(e) { + /** Zooms on clicked feature from map event e. */ + public zoomOnClick(e) { if (e.features[0].properties.cluster_id !== undefined) { // TODO: should check the this.index is set with good value const expansionZoom = this.index.getClusterExpansionZoom(e.features[0].properties.cluster_id); @@ -453,22 +429,23 @@ export class ArlasMapComponent implements OnInit { } protected queryRender(e, map: AbstractArlasMapGL,) { - const hasCrossOrDrawLayer = map.hasCrossOrDrawLayer(e); + const hasCrossOrDrawLayer = this.mapService.queryFeatures(e, this.map, CROSS_LAYER_PREFIX); if (!this.isDrawingBbox && !this.isDrawingPolygon && !this.isDrawingCircle && !this.isInSimpleDrawMode && !hasCrossOrDrawLayer) { - map.onEvent(e); + this.onFeatureClick.next({ features: e.features, point: [e.lngLat.lng, e.lngLat.lat] }); } } - /** - * Adds the custom icons given in the component's input - */ + /** Adds the custom icons given in the component's input */ public addIcons() { - this.icons.forEach(icon => { - const iconName = icon.path.split('.')[0]; - const iconPath = this.ICONS_BASE_PATH + icon.path; - const iconErrorMessage = 'The icon "' + this.ICONS_BASE_PATH + icon.path + '" is not found'; - this.mapService.addImage(iconName, iconPath, this.map, iconErrorMessage, { 'sdf': icon.recolorable }); - }); + if (this.icons) { + this.icons.forEach(icon => { + const iconName = icon.path.split('.')[0]; + const iconPath = this.ICONS_BASE_PATH + icon.path; + const iconErrorMessage = 'The icon "' + this.ICONS_BASE_PATH + icon.path + '" is not found'; + this.mapService.addImage(iconName, iconPath, this.map, iconErrorMessage, { 'sdf': icon.recolorable }); + }); + + } } public declareMap() { @@ -480,32 +457,8 @@ export class ArlasMapComponent implements OnInit { margePanForTest: this.margePanForTest, offset: this.offset, wrapLatLng: this.wrapLatLng, - mapLayers: this.mapLayers, maxWidthScale: this.maxWidthScale, unitScale: this.unitScale, - mapLayersEventBind: { - zoomOnClick: [{ event: 'click', fn: this.defaultOnZoom }], - onHover: [ - { - event: 'mousemove', - fn: (e) => { - this.onFeatureOver.next({ features: e.features, point: [e.lngLat.lng, e.lngLat.lat] }); - } - }, - { - event: 'mouseleave', - fn: (e) => { - this.onFeatureOver.next({}); - } - } - ], - emitOnClick: [ - { - event: 'click', - fn: this.queryRender - }], - }, - customEventBind: (m: AbstractArlasMapGL) => this.mapService.getCustomEventsToDrawLayers(m), mapProviderOptions: { container: this.id, style: this.basemapService.getInitStyle(this.basemapService.basemaps.getSelected()), @@ -541,15 +494,7 @@ export class ArlasMapComponent implements OnInit { } } }; - console.log('declare'); this.map = this.mapService.createMap(config); - this.map.eventEmitter$.subscribe({ - next: (e: MapLayerMouseEvent) => { - if (e.type === 'click') { - this.onFeatureClic.next({ features: e.features, point: [e.lngLat.lng, e.lngLat.lat] }); - } - } - }) fromEvent(window, 'beforeunload').subscribe(() => { this.onMapClosed.next(this.map.getMapExtend()); }); @@ -579,8 +524,6 @@ export class ArlasMapComponent implements OnInit { this.draw.setMode('DRAW_RADIUS_CIRCLE', 'draw_radius_circle'); this.draw.setMode('DRAW_STRIP', 'draw_strip'); this.draw.setMode('DIRECT_STRIP', 'direct_strip'); - - // TODO : to have to add event override const drawControlConfig: DrawControlsOption = { draw: { control: this.draw }, addGeoBox: { @@ -597,6 +540,7 @@ export class ArlasMapComponent implements OnInit { } }; this.map.initDrawControls(drawControlConfig); + this.drawService.setDraw(this.draw); /** * The other on load initialisation releated with the map are in @@ -608,7 +552,6 @@ export class ArlasMapComponent implements OnInit { */ this.map.onCustomEvent('beforeOnLoadInit', () => { - // TODO: should change the this.basemapService.declareProtomapProtocol(this.map); this.basemapService.addProtomapBasemap(this.map); this.addIcons(); @@ -616,18 +559,16 @@ export class ArlasMapComponent implements OnInit { this.mapFunctionalService.declareBasemapSources(this.mapSources, this.map); this.mapFunctionalService.declareLabelSources('', this.polygonlabeldata, this.map); this.mapFunctionalService.addArlasDataLayers(this.visualisationSetsConfig, this.mapLayers, this.map); - + this.bindLayerEvents(); }); - this.map.on('load', () => { - + this.mapService.onMapEvent('load', this.map, () => { this.draw.changeMode('static'); if (this.mapLayers !== null) { this.visibilityUpdater.subscribe(visibilityStatus => { - this.map.updateVisibility(visibilityStatus); + this.mapFunctionalService.updateVisibility(visibilityStatus, this.visualisationSetsConfig, this.map); }); } - this.canvas = this.map.getCanvasContainer(); this.canvas.addEventListener('mousedown', this.mousedown, true); this.draw.on('draw.create', (e) => { @@ -670,7 +611,6 @@ export class ArlasMapComponent implements OnInit { }; this.draw.onDrawOnClick((e) => { - console.log('the fuck', e) if (this.drawClickCounter === 0) { window.addEventListener('mousemove', mouseMoveForDraw); } @@ -679,12 +619,12 @@ export class ArlasMapComponent implements OnInit { this.draw.onDrawOnStart((e) => { window.removeEventListener('mousemove', mouseMoveForDraw); this.drawClickCounter = 0; - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); }); this.draw.onDrawOnStop((e) => { window.removeEventListener('mousemove', mouseMoveForDraw); this.drawClickCounter = 0; - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); }); this.draw.onDrawInvalidGeometry((e) => { @@ -708,18 +648,16 @@ export class ArlasMapComponent implements OnInit { this.draw.add(currentFeature); } this.openInvalidGeometrySnackBar(); - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); }); this.draw.onDrawEditSaveInitialFeature((edition) => { - console.log('onDrawEditSaveInitialFeature', edition) this.savedEditFeature = Object.assign({}, edition.feature); this.savedEditFeature.coordinates = [[]]; edition.feature.coordinates[0].forEach(c => this.savedEditFeature.coordinates[0].push(c)); }); this.draw.onDrawSelectionchange((e) => { - console.log('onDrawSelectionchange', e) this.drawSelectionChanged = true; if (e.features.length > 0) { this.isDrawSelected = true; @@ -740,7 +678,7 @@ export class ArlasMapComponent implements OnInit { this.isDrawingStrip = false; this.isInSimpleDrawMode = false; this.draw.changeMode('static'); - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); } }); this.draw.onDrawModeChange((e) => { @@ -753,7 +691,7 @@ export class ArlasMapComponent implements OnInit { if (e.mode === 'simple_select') { this.isInSimpleDrawMode = true; } else if (e.mode === 'static') { - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); } else if (e.mode === 'direct_select') { const selectedFeatures = this.draw.getSelectedFeatures(); const selectedIds = this.draw.getSelectedIds(); @@ -780,12 +718,12 @@ export class ArlasMapComponent implements OnInit { } } else { this.isInSimpleDrawMode = false; - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); } } }); - this.map.on('click', (e) => { + this.mapService.onMapEvent('click', this.map, (e) => { if (this.isDrawingCircle) { return; } @@ -829,22 +767,22 @@ export class ArlasMapComponent implements OnInit { this.onMapLoaded.next(true); }); - this.map.onMoveEnd().subscribe((moveResult => { + this.map.onMoveEnd(this.mapFunctionalService.visualisationsSets).subscribe((moveResult => { this.onMove.next(moveResult); })); // Mouse events - this.map.on('mousedown', (e: MapMouseEvent) => { + this.mapService.onMapEvent('mousedown', this.map, (e: MapMouseEvent) => { this.drawService.startBboxDrawing(); }); - this.map.on('mouseup', (e: MapMouseEvent) => { + this.mapService.onMapEvent('mouseup', this.map, (e: MapMouseEvent) => { this.drawService.stopBboxDrawing(); }); - this.map.on('mousemove', (e: MapMouseEvent) => { + this.mapService.onMapEvent('mousemove', this.map, (e: MapMouseEvent) => { const lngLat = e.lngLat; if (this.isDrawingBbox || this.isDrawingPolygon) { - this.map.setCursorStyle('crosshair'); + this.mapService.setMapCursor(this.map, 'crosshair'); this.map.movelngLat = lngLat; } if (this.drawService.bboxEditionState.isDrawing) { @@ -879,39 +817,56 @@ export class ArlasMapComponent implements OnInit { if (!!this.redrawSource) { this.redrawSource.subscribe(sd => { - this.map.redrawSource(sd.source, sd.data); + this.mapService.setDataToGeojsonSource(this.mapService.getSource(sd.source, this.map), { + 'type': 'FeatureCollection', + 'features': sd.data + }); }); } } + public bindLayerEvents() { + this.mapLayers.events.zoomOnClick.forEach(layerId => { + this.mapService.onLayerEvent('click', this.map, layerId, (e) => this.zoomOnClick(e)); + }); + this.mapLayers.events.onHover.forEach(layerId => { + this.mapService.onLayerEvent('mousemove', this.map, layerId, (e) => + this.onFeatureHover.next({ features: e.features, point: [e.lngLat.lng, e.lngLat.lat] })); + this.mapService.onLayerEvent('mouseleave', this.map, layerId, (e) => + this.onFeatureHover.next({})); + }); + this.mapLayers.events.emitOnClick.forEach(layerId => { + this.mapService.onLayerEvent('click', this.map, layerId, (e) => + this.queryRender(e, this.map)); + }); - - - - - - + const drawPolygonLayers = [ + 'gl-draw-polygon-stroke-inactive', + 'gl-draw-polygon-stroke-active', + 'gl-draw-polygon-stroke-static' + ].map(layer => ['.cold', '.hot'] + .map(id => layer.concat(id))) + .reduce((p, ac) => ac.concat(p), []); + + drawPolygonLayers.forEach(layerId => { + this.mapService.onLayerEvent('mousemove', this.map, layerId, (e) => + this.mapService.setMapCursor(this.map, 'pointer')); + this.mapService.onLayerEvent('mouseleave', this.map, layerId, (e) => + this.mapService.setMapCursor(this.map, '')); + }); + } /** ------------------------------------------------------- VISUAL SEPERATOR - LAYERS ----------------------------------------- */ - - - - /** Sets the layers order according to the order of `visualisationSetsConfig` list*/ public reorderLayers() { this.mapFunctionalService.reorderLayers(this.visualisationSetsConfig, this.map); } - - /** ------------------------------------------------------- VISUAL SEPERATOR - DRAWING ----------------------------------------- */ - - - private mousedown = (e) => { // Continue the rest of the function if we add a geobox. if (!this.isDrawingBbox) { @@ -952,7 +907,7 @@ export class ArlasMapComponent implements OnInit { const f = this.mapService.getPointFromScreen(e, this.canvas); document.removeEventListener('mousemove', this.mousemove); document.removeEventListener('mouseup', this.mouseup); - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); this.map.enableDragPan(); // Capture xy coordinates if (this.start.x !== f.x && this.start.y !== f.y) { @@ -984,7 +939,6 @@ export class ArlasMapComponent implements OnInit { } } - /** * Emits the newly drawn bbox. It completes the drawBbox event emitted by the drawService. * @param east @@ -1026,7 +980,7 @@ export class ArlasMapComponent implements OnInit { /** @description Displays the geobox */ public addGeoBox() { - this.map.setCursorStyle('crosshair'); + this.mapService.setMapCursor(this.map, 'crosshair'); this.drawService.enableBboxEdition(); this.isDrawingBbox = true; } @@ -1035,7 +989,7 @@ export class ArlasMapComponent implements OnInit { * @description Removes all the aois if none of them is selected. Otherwise it removes the selected one only */ public removeAois() { - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); this.isDrawingBbox = false; this.deleteSelectedItem(); } @@ -1077,7 +1031,7 @@ export class ArlasMapComponent implements OnInit { public getAllPolygon(mode: 'wkt' | 'geojson') { let polygon; if (mode === 'wkt') { - polygon = this.latLngToWKT(this.draw.getAll().features.filter(f => this.drawService.isPolygon(f) || + polygon = latLngToWKT(this.draw.getAll().features.filter(f => this.drawService.isPolygon(f) || this.drawService.isCircle(f)).map(f => cleanCoords(f))); } else { polygon = { @@ -1096,7 +1050,7 @@ export class ArlasMapComponent implements OnInit { public getSelectedPolygon(mode: 'wkt' | 'geojson') { let polygon; if (mode === 'wkt') { - polygon = this.latLngToWKT(this.draw.getSelected().features.filter(f => this.drawService.isPolygon(f) || + polygon = latLngToWKT(this.draw.getSelected().features.filter(f => this.drawService.isPolygon(f) || this.drawService.isCircle(f))); } else { polygon = { @@ -1138,11 +1092,11 @@ export class ArlasMapComponent implements OnInit { @HostListener('document:keydown', ['$event']) public handleKeyboardEvent(event: KeyboardEvent) { if (event.key === 'Escape' && this.isDrawingBbox) { - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); this.isDrawingBbox = false; document.removeEventListener('mousemove', this.mousemove); document.removeEventListener('mouseup', this.mouseup); - this.map.setCursorStyle(''); + this.mapService.setMapCursor(this.map, ''); if (this.box) { this.box.parentNode.removeChild(this.box); this.box = undefined; @@ -1166,12 +1120,11 @@ export class ArlasMapComponent implements OnInit { } public emitVisualisations(visualisationName: string) { - const layers = this.map.updateLayoutVisibility(visualisationName); + const layers = this.mapFunctionalService.updateLayoutVisibility(visualisationName, this.visualisationSetsConfig, this.map); this.visualisations.emit(layers); this.reorderLayers(); } - // Todo: replace layer any by unique type public downloadLayerSource(downaload: { layer: any; downloadType: string; }): void { const downlodedSource = { layerId: downaload.layer.id, @@ -1185,15 +1138,15 @@ export class ArlasMapComponent implements OnInit { /** puts the visualisation set list in the new order after dropping */ public drop(event: CdkDragDrop) { - moveItemInArray(this.map.visualisationSetsConfig, event.previousIndex, event.currentIndex); + moveItemInArray(this.visualisationSetsConfig, event.previousIndex, event.currentIndex); this.reorderLayers(); } /** puts the layers list in the new order after dropping */ public dropLayer(event: CdkDragDrop, visuName: string) { - const layers = Array.from(this.map.findVisualisationSetLayer(visuName)); + const layers = Array.from(this.mapFunctionalService.findVisualisationSetLayer(visuName, this.visualisationSetsConfig)); moveItemInArray(layers, event.previousIndex, event.currentIndex); - this.map.setVisualisationSetLayers(visuName, layers); + this.mapFunctionalService.setVisualisationSetLayers(visuName, layers, this.visualisationSetsConfig); this.reorderLayers(); } @@ -1215,7 +1168,7 @@ export class ArlasMapComponent implements OnInit { } public selectFeaturesByCollection(features: Array, collection: string) { - this.map.selectFeaturesByCollection(features, collection); + this.mapFunctionalService.selectFeaturesByCollection(this.mapLayers, this.map, features, collection); } public hideBasemapSwitcher() { @@ -1233,41 +1186,12 @@ export class ArlasMapComponent implements OnInit { this.map.setCenter(lngLat); } - // TODO: put into utils class. - private latLngToWKT(features) { - let wktType = 'POLYGON[###]'; - if (features.length > 1) { - wktType = 'MULTIPOLYGON([###])'; - } - - let polygons = ''; - features.forEach((feat, indexFeature) => { - if (feat) { - const currentFeat: Array = feat.geometry.coordinates; - polygons += (indexFeature === 0 ? '' : ',') + '(('; - currentFeat[0].forEach((coord, index) => { - polygons += (index === 0 ? '' : ',') + coord[0] + ' ' + coord[1]; - }); - polygons += '))'; - } - }); - - let wkt = ''; - if (polygons !== '') { - wkt = wktType.replace('[###]', polygons); - } - return wkt; - } - private highlightFeature(featureToHightLight: { isleaving: boolean; elementidentifier: ElementIdentifier; }) { - this.map.highlightFeature(featureToHightLight); + this.mapFunctionalService.highlightFeature(this.mapLayers, this.map, featureToHightLight); } private selectFeatures(elementToSelect: Array) { - this.map.selectFeatures(elementToSelect); + this.mapFunctionalService.selectFeatures(this.mapLayers, this.map, elementToSelect); } - - - } diff --git a/projects/arlas-map/src/lib/basemaps/basemap.component.ts b/projects/arlas-map/src/lib/basemaps/basemap.component.ts index bb0cea82..f2006397 100644 --- a/projects/arlas-map/src/lib/basemaps/basemap.component.ts +++ b/projects/arlas-map/src/lib/basemaps/basemap.component.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { Subject } from 'rxjs/internal/Subject'; import { AbstractArlasMapGL } from '../map/AbstractArlasMapGL'; import { ArlasMapSource } from '../map/model/sources'; @@ -26,14 +26,16 @@ import { BasemapService } from './basemap.service'; import { BasemapStyle } from './basemap.config'; import { ArlasMapService } from '../map/service/arlas-map.service'; import { ArlasMapFunctionalService } from '../arlas-map-logic.service'; +import { takeUntil } from 'rxjs'; @Component({ selector: 'arlas-basemap', templateUrl: './basemap.component.html', styleUrls: ['./basemap.component.scss'] }) -export class BasemapComponent implements OnInit { - protected LOCAL_STORAGE_BASEMAPS = 'arlas_last_base_map'; +export class BasemapComponent implements OnInit, OnDestroy { + + private _onDestroy$ = new Subject(); @Input() public map: AbstractArlasMapGL; @Input() public mapSources: Array>; @@ -44,7 +46,13 @@ export class BasemapComponent implements OnInit { public showList = false; public basemaps: ArlasBasemaps; - public constructor(protected basemapService: BasemapService, protected mapFunctionalService: ArlasMapFunctionalService) { } + public constructor(protected basemapService: BasemapService, + protected mapFunctionalService: ArlasMapFunctionalService, + protected mapService: ArlasMapService) { + + this.basemapService.basemapChanged$.pipe(takeUntil(this._onDestroy$)).subscribe(() => this.basemapChanged.emit()); + + } public ngOnInit(): void { this.initBasemaps(); @@ -81,59 +89,13 @@ export class BasemapComponent implements OnInit { public setBaseMapStyle(newBasemap: BasemapStyle) { if (this.map) { - this.setStyle(this.basemaps.getSelected().styleFile as any, newBasemap); + this.basemapService.setBasemap(this.basemaps.getSelected().styleFile as any, newBasemap, this.map, this.mapSources); } } - // TODO: s to any try to find a good type or interface for all layer - /** Set mapbox new style. - * !!NOTE: mapbox setStyle removes all added layers from the map; thus the following description : - * This method saves all the currently added layers to the map, applies the 'map.setStyle' and adds all the saved layers afterwards. - */ - public setStyle(s: any, newBasemap: BasemapStyle) { - const selectedBasemapLayersSet = new Set(); - // TODO: Array to any try to find a good type or interface for all layer - const layers: Array = this.map.getLayers(); - const sources = this.map.getStyle().sources; - if (s.layers) { - s.layers.forEach(l => selectedBasemapLayersSet.add(l.id)); - } - // TODO: Array to any try to find a good type or interface for all layer - const layersToSave = new Array(); - const sourcesToSave = new Array>(); - layers.filter((l: any) => !selectedBasemapLayersSet.has(l.id) && !!l.source).forEach(l => { - layersToSave.push(l); - if (sourcesToSave.filter(ms => ms.id === l.source.toString()).length === 0) { - sourcesToSave.push({ id: l.source.toString(), source: sources[l.source.toString()] }); - } - }); - const sourcesToSaveSet = new Set(); - sourcesToSave.forEach(mapSource => sourcesToSaveSet.add(mapSource.id)); - if (this.mapSources) { - this.mapSources.forEach(mapSource => { - if (!sourcesToSaveSet.has(mapSource.id)) { - sourcesToSave.push(mapSource); - } - }); - } - const initStyle = this.basemapService.getInitStyle(newBasemap); - this.map.setStyle(initStyle).once('styledata', () => { - setTimeout(() => { - /** the timeout fixes a mapboxgl bug related to layer placement*/ - this.mapFunctionalService.declareBasemapSources(sourcesToSave, this.map); - layersToSave.forEach(l => { - if (!this.map.getLayer(l.id)) { - this.map.addLayer(l); - } - }); - localStorage.setItem(this.LOCAL_STORAGE_BASEMAPS, JSON.stringify(newBasemap)); - this.basemaps.setSelected(newBasemap); - if (newBasemap.type === 'protomap') { - this.basemapService.addProtomapBasemap(this.map); - this.basemapService.notifyProtomapAddition(); - } - this.basemapChanged.emit(); - }, 0); - }); + public ngOnDestroy() { + this._onDestroy$.next(true); + this._onDestroy$.complete(); } + } diff --git a/projects/arlas-map/src/lib/basemaps/basemap.service.ts b/projects/arlas-map/src/lib/basemaps/basemap.service.ts index 7780c788..035fb9f9 100644 --- a/projects/arlas-map/src/lib/basemaps/basemap.service.ts +++ b/projects/arlas-map/src/lib/basemaps/basemap.service.ts @@ -31,6 +31,11 @@ import { ArlasMapService } from '../map/service/arlas-map.service'; export abstract class BasemapService { protected POWERED_BY_ARLAS = ' Powered by ARLAS.'; + protected LOCAL_STORAGE_BASEMAPS = 'arlas_last_base_map'; + + + protected basemapChangedSource: Subject = new Subject(); + public basemapChanged$ = this.basemapChangedSource.asObservable(); public basemaps: ArlasBasemaps; protected protomapBasemapAddedSource = new Subject(); @@ -53,10 +58,8 @@ export abstract class BasemapService { protected addProtomapLayerToMap(map: AbstractArlasMapGL, styleFile: any) { styleFile.layers.forEach(l => { - if (!!map.getLayer(l.id)) { - map.removeLayer(l.id); - } - map.addLayer(l as any); + this.mapService.removeLayer(map, l.id); + this.mapService.addLayer(map, l); }); } @@ -92,4 +95,5 @@ export abstract class BasemapService { public abstract getInitStyle(selected: BasemapStyle): any; public abstract fetchSources$(): Observable; protected abstract getStyleFile(b: BasemapStyle): Observable; + public abstract setBasemap(s: any, newBasemap: BasemapStyle, map: AbstractArlasMapGL, options?: any); } diff --git a/projects/arlas-map/src/lib/draw/AbstractDraw.ts b/projects/arlas-map/src/lib/draw/AbstractDraw.ts index d8af6567..373a4970 100644 --- a/projects/arlas-map/src/lib/draw/AbstractDraw.ts +++ b/projects/arlas-map/src/lib/draw/AbstractDraw.ts @@ -49,17 +49,12 @@ export class AbstractDraw implements DrawEventsInterface { public enabled: boolean; public drawProvider: MapboxDraw; public constructor(config: any, enabled: boolean, map: AbstractArlasMapGL) { - console.log('init draw'); const modes = MapboxDraw.modes; this.config = JSON.parse(JSON.stringify(config)); this.config.modes = Object.assign(modes, config.modes); - console.log('Draw config', this.config); this.drawProvider = new MapboxDraw(this.config); - console.log('then'); this.arlasMap = map; this.enabled = enabled; - - } public onAdd(map) { @@ -75,7 +70,6 @@ export class AbstractDraw implements DrawEventsInterface { } public setMode(drawModes: DrawModes, replaceMode: any) { - console.log(drawModes); this.drawProvider.modes[drawModes] = replaceMode; } @@ -144,20 +138,13 @@ export class AbstractDraw implements DrawEventsInterface { } public getMode(modes: DrawModes) { - console.log(modes); - console.log(this.drawProvider.modes); return this.drawProvider.modes[modes]; } - /** - * class wrapper - */ - public on(event: DrawEvents, func: (e) => void): void { this.arlasMap.on(event, func); } - public add(features: any) { this.drawProvider.add(features); } @@ -166,7 +153,6 @@ export class AbstractDraw implements DrawEventsInterface { return this.drawProvider.get(featureId); } - public delete(ids: string | Array): AbstractDraw { this.drawProvider.delete(ids); return this; diff --git a/projects/arlas-map/src/lib/map-import/map-import.component.ts b/projects/arlas-map/src/lib/map-import/map-import.component.ts index f8f00e99..ead92dc7 100644 --- a/projects/arlas-map/src/lib/map-import/map-import.component.ts +++ b/projects/arlas-map/src/lib/map-import/map-import.component.ts @@ -513,7 +513,7 @@ export class MapImportComponent { /** *************/ public clearPolygons() { // Clean source of imported polygons - const labelSource = this.mapComponent.map.getSource(this.SOURCE_NAME_POLYGON_LABEL); + const labelSource = this.mapService.getSource(this.SOURCE_NAME_POLYGON_LABEL, this.mapComponent.map); this.featureIndex = 0; this.mapComponent.onAoiChanged.next(this.emptyData); if (labelSource !== undefined) { diff --git a/projects/arlas-map/src/lib/map/AbstractArlasMapGL.ts b/projects/arlas-map/src/lib/map/AbstractArlasMapGL.ts index b53b813f..dbcb6234 100644 --- a/projects/arlas-map/src/lib/map/AbstractArlasMapGL.ts +++ b/projects/arlas-map/src/lib/map/AbstractArlasMapGL.ts @@ -18,8 +18,8 @@ */ import { FeatureCollection } from '@turf/helpers'; -import { ARLAS_ID, ExternalEvent, FILLSTROKE_LAYER_PREFIX, MapLayers, SCROLLABLE_ARLAS_ID } from './model/layers'; -import { fromEvent, map, Observable, Subject, Subscription } from 'rxjs'; +import { ARLAS_ID, FILLSTROKE_LAYER_PREFIX, MapLayers, SCROLLABLE_ARLAS_ID } from './model/layers'; +import { fromEvent, map, Observable, Subscription } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { ArlasMapSource } from './model/sources'; @@ -29,8 +29,6 @@ import { MapInterface } from './interface/map.interface'; import { MapExtent } from './model/extent'; import { marker } from '@biesbjerg/ngx-translate-extract-marker'; import { LngLat, OnMoveResult } from './model/map'; -import { MapLayerMouseEvent } from './model/events'; - export interface ElementIdentifier { @@ -57,13 +55,7 @@ export interface MapConfig { margePanForTest: number; wrapLatLng: boolean; offset: ArlasMapOffset; - mapLayers: MapLayers; - mapLayersEventBind: { - onHover: MapEventBinds[]; - emitOnClick: MapEventBinds[]; - zoomOnClick: MapEventBinds[]; - }; - customEventBind: (map: AbstractArlasMapGL) => BindLayerToEvent[]; + mapProviderOptions?: T; maxWidthScale?: number; unitScale?: string; @@ -108,8 +100,6 @@ export abstract class AbstractArlasMapGL implements MapInterface { * ex: endlnglat will have a type Maplibre.Pointlike/ Mapbox.Point */ - private eventEmitter: Subject = new Subject(); - public eventEmitter$ = this.eventEmitter.asObservable(); public abstract startlngLat: LngLat; public abstract endlngLat: LngLat; public abstract movelngLat: LngLat; @@ -122,25 +112,14 @@ export abstract class AbstractArlasMapGL implements MapInterface { // @Override protected _mapLayers: MapLayers; // todo: find common type protected _controls: ControlsOption; - public visualisationSetsConfig: Array; protected _icons: Array; public mapSources: Array>; // todo: find common type protected _maxWidthScale?: number; protected _unitScale?: string; - public abstract layersMap: Map; // todo: find common type - public firstDrawLayer: string; protected _emptyData: FeatureCollection = { 'type': 'FeatureCollection', 'features': [] }; - public polygonlabeldata = Object.assign({}, this._emptyData); - public visualisationsSets: { - visualisations: Map>; - status: Map; - } = { - visualisations: new Map(), - status: new Map() - }; public currentLat: string; public currentLng: string; public readonly POLYGON_LABEL_SOURCE = 'polygon_label'; @@ -178,105 +157,25 @@ export abstract class AbstractArlasMapGL implements MapInterface { this._margePanForTest = config.margePanForTest; this._displayCurrentCoordinates = config.displayCurrentCoordinates ?? false; this._wrapLatLng = config.wrapLatLng ?? true; - this._mapLayers = config.mapLayers; this._controls = config.controls; this._fitBoundsPadding = config.fitBoundsPadding ?? 10; this._maxWidthScale = config.maxWidthScale; this._unitScale = config.unitScale; - this.init(config); } - public setMinZoom(minZoom?: number): this { - throw new Error('Method not implemented.'); - } + public abstract on(type: string, listener: (ev: any) => void): this; - public setMaxZoom(maxZoom?: number): this { - throw new Error('Method not implemented.'); - } - public project(lnglat: unknown): unknown { - throw new Error('Method not implemented.'); - } - public unproject(point: unknown): unknown { - throw new Error('Method not implemented.'); - } - public queryRenderedFeatures(pointOrBox?: unknown, options?: { layers?: string[]; filter?: any[]; }): any[] { - throw new Error('Method not implemented.'); - } - public setFilter(layer: string, filter?: boolean | any[], options?: unknown): this { - throw new Error('Method not implemented.'); - } - public getLight(): unknown { - throw new Error('Method not implemented.'); - } - public setFeatureState(feature: unknown, state: { [key: string]: any; }): void { - throw new Error('Method not implemented.'); - } - public getFeatureState(feature: unknown): { [key: string]: any; } { - throw new Error('Method not implemented.'); - } - public removeFeatureState(target: unknown, key?: string): void { - throw new Error('Method not implemented.'); - } - public getContainer(): HTMLElement { - throw new Error('Method not implemented.'); - } - public getCanvasContainer(): HTMLElement { - throw new Error('Method not implemented.'); - } - public getCanvas(): HTMLCanvasElement { - throw new Error('Method not implemented.'); - } - public getCenter(): unknown { - throw new Error('Method not implemented.'); - } - public setCenter(center: unknown, unknown?: unknown): this { - throw new Error('Method not implemented.'); - } - public panTo(lnglat: unknown, options?: unknown, unknown?: unknown): this { - throw new Error('Method not implemented.'); - } - public getZoom(): number { - throw new Error('Method not implemented.'); - } - public setZoom(zoom: number, unknown?: unknown): this { - throw new Error('Method not implemented.'); - } - public getBearing(): number { - throw new Error('Method not implemented.'); - } - public setBearing(bearing: number, unknown?: unknown): this { - throw new Error('Method not implemented.'); - } - public rotateTo(bearing: number, options?: unknown, unknown?: unknown): this { - throw new Error('Method not implemented.'); - } - public setPitch(pitch: number, unknown?: unknown): this { - throw new Error('Method not implemented.'); - } - public cameraForBounds(bounds: unknown, options?: unknown): unknown { - throw new Error('Method not implemented.'); - } - public fitBounds(bounds: unknown, options?: unknown, unknown?: unknown): this { - throw new Error('Method not implemented.'); - } - public hasImage(id: string): boolean { - throw new Error('Method not implemented.'); - } + public abstract getZoom(): number; - public on(type: T, layer: string, listener: (ev: unknown) => void): this; - public on(type: T, listener: (ev: unknown) => void): this; - public on(type: string, listener: (ev: any) => void): this; - public on(type: unknown, layer: unknown, listener?: unknown): this { - throw new Error('Method not implemented.'); - } - public once(type: T, layer: string, listener: (ev: unknown) => void): this; - public once(type: T, listener: (ev: unknown) => void): this; - public once(type: string, listener: (ev: any) => void): this; - public once(type: unknown, layer: unknown, listener?: unknown): this { - throw new Error('Method not implemented.'); - } + public abstract fitBounds(bounds: unknown, options?: unknown, unknown?: unknown): this; + public abstract getCanvasContainer(): HTMLElement; + public abstract queryRenderedFeatures(pointOrBox?: unknown, options?: { layers?: string[]; filter?: any[]; }): any[]; + + public abstract setCenter(center: unknown, unknown?: unknown): this; + + public abstract setFilter(layer: string, filter?: boolean | any[], options?: unknown): this; protected init(config: MapConfig): void { try { this._initMapProvider(config); @@ -291,23 +190,13 @@ export abstract class AbstractArlasMapGL implements MapInterface { protected _initOnLoad() { this.onLoad(() => { this.evented.dispatchEvent(new Event('beforeOnLoadInit')); - console.log('on load call'); this._updateBounds(); this._updateZoom(); - this.firstDrawLayer = this.getColdOrHotLayers()[0]; - this._initMapLayers(this); - this._bindCustomEvent(this); - // Fit bounds on current bounds to emit init position in moveend bus this.getMapProvider().fitBounds(this.getBounds()); - this._initVisualisationSet(); }); } - onEvent(e) { - this.eventEmitter.next(e); - } - - onCustomEvent(event: string, loadFn: () => void) { + public onCustomEvent(event: string, loadFn: () => void) { this.evented.addEventListener(event, loadFn); } @@ -330,35 +219,7 @@ export abstract class AbstractArlasMapGL implements MapInterface { this._updateOnMoveEnd(); } - protected _initMapLayers(map: AbstractArlasMapGL) { - if (this._mapLayers) { - console.log('init maplayers'); - this.setLayersMap(this._mapLayers as MapLayers); - this._addExternalEventLayers(); - - this.bindLayersToMapEvent( - map, - this._mapLayers.events.zoomOnClick, - this.config.mapLayersEventBind.zoomOnClick - ); - - this.bindLayersToMapEvent( - map, - this.config.mapLayers.events.emitOnClick, - this.config.mapLayersEventBind.emitOnClick - ); - - this.bindLayersToMapEvent( - map, - this.config.mapLayers.events.onHover, - this.config.mapLayersEventBind.onHover - ); - } - } - protected _updateBounds(): void { - console.log('_updateBounds call'); - this._west = this.getWestBounds(); this._south = this.getSouthBounds(); this._east = this.getEastBounds(); @@ -436,26 +297,10 @@ export abstract class AbstractArlasMapGL implements MapInterface { this._eventSubscription.push(sub); } - protected _bindCustomEvent(map: AbstractArlasMapGL) { - if (this.config.customEventBind) { - console.log('bind custom event'); - this.config.customEventBind(map).forEach(element => - this.bindLayersToMapEvent(map, element.layers, element.mapEventBinds) - ); - } - } - - protected _initVisualisationSet() { - if (this.visualisationSetsConfig) { - console.log('_initVisualisationSet'); - this.visualisationSetsConfig.forEach(visu => { - this.visualisationsSets.visualisations.set(visu.name, new Set(visu.layers)); - this.visualisationsSets.status.set(visu.name, visu.enabled); - }); - } - } - - public onMoveEnd(cb?: () => void) { + public onMoveEnd(visualisationsSets: { + visualisations: Map>; + status: Map; + }, cb?: () => void) { return this._moveEnd$ .pipe(map(_ => { this._updateBounds(); @@ -463,21 +308,21 @@ export abstract class AbstractArlasMapGL implements MapInterface { if (cb) { cb(); } - return this._getMoveEnd(); + return this._getMoveEnd(visualisationsSets); })); } protected abstract _initMapProvider(BaseMapGlConfig): void; protected abstract _initControls(): void; - protected abstract _getMoveEnd(): OnMoveResult; + protected abstract _getMoveEnd(visualisationsSets: { + visualisations: Map>; + status: Map; + }): OnMoveResult; public abstract initDrawControls(config: DrawControlsOption): void; - public abstract bindLayersToMapEvent(map: AbstractArlasMapGL, layers: string[] | Set, binds: MapEventBinds[]): void; public abstract calcOffsetPoint(): any; - public abstract redrawSource(id: string, data): void; public abstract getColdOrHotLayers(); - public abstract addVisualisation(visualisation: VisualisationSetConfig, layers: Array, sources: Array>): void; public abstract paddedFitBounds(bounds: any, options?: any); public abstract geometryToBound(geom: any, paddingPercentage?: number): unknown; public abstract enableDragPan(): void; @@ -488,7 +333,6 @@ export abstract class AbstractArlasMapGL implements MapInterface { public abstract getSouthBounds(): any; public abstract getSouthWestBounds(): any; public abstract getEastBounds(): any; - public abstract setCursorStyle(cursor: string): void; public abstract getMapProvider(): any; public abstract getMapExtend(): MapExtent; public abstract onLoad(fn: () => void): void; @@ -500,150 +344,16 @@ export abstract class AbstractArlasMapGL implements MapInterface { public abstract paddedBounds(npad: number, spad: number, epad: number, wpad: number, map: any, SW, NE): LngLat[]; - public abstract getLayers(): any; public abstract addControl(control: any, position?: ControlPosition, eventOverride?: { event: string; fn: (e?) => void; }); - public abstract setLayersMap(mapLayers: MapLayers, layers?: Array); - - - protected setStrokeLayoutVisibility(layerId: string, visibility: string): void { - const layer = this.layersMap.get(layerId); - if (layer.type === 'fill') { - const strokeId = layer.id.replace(ARLAS_ID, FILLSTROKE_LAYER_PREFIX); - const strokeLayer = this.layersMap.get(strokeId); - if (!!strokeLayer) { - this.getMapProvider().setLayoutProperty(strokeId, 'visibility', visibility); - } - } - } - - protected setScrollableLayoutVisibility(layerId: string, visibility: string): void { - const layer = this.layersMap.get(layerId); - const scrollableId = layer.id.replace(ARLAS_ID, SCROLLABLE_ARLAS_ID); - const scrollbaleLayer = this.layersMap.get(scrollableId); - if (!!scrollbaleLayer) { - this.getMapProvider().setLayoutProperty(scrollableId, 'visibility', visibility); - } - } - - public addLayerInWritePlaceIfNotExist(layerId: string): void { - const layer = this.layersMap.get(layerId); - if (layer !== undefined && layer.id === layerId) { - /** Add the layer if it is not already added */ - if (this.getMapProvider().getLayer(layerId) === undefined) { - if (this.firstDrawLayer && this.firstDrawLayer.length > 0) { - /** draw layers must be on the top of the layers */ - this.getMapProvider().addLayer(layer, this.firstDrawLayer); - } else { - this.getMapProvider().addLayer(layer); - } - } - } else { - throw new Error('The layer `' + layerId + '` is not declared in `mapLayers.layers`'); - } - } - - - public updateLayoutVisibility(visualisationName: string) { - const visuStatus = !this.visualisationsSets.status.get(visualisationName); - this.visualisationSetsConfig.find(v => v.name === visualisationName).enabled = visuStatus; - if (!visuStatus) { - const layersSet = new Set(this.visualisationsSets.visualisations.get(visualisationName)); - this.visualisationsSets.visualisations.forEach((ls, v) => { - if (v !== visualisationName) { - ls.forEach(ll => { - if (layersSet && layersSet.has(ll)) { - layersSet.delete(ll); - } - }); - } - }); - layersSet.forEach(ll => { - this.disableLayoutVisibility(ll); - }); - } - this.visualisationsSets.status.set(visualisationName, visuStatus); - const layers = new Set(); - this.visualisationsSets.visualisations.forEach((ls, v) => { - if (this.visualisationsSets.status.get(v)) { - ls.forEach(l => { - layers.add(l); - this.enableLayoutVisibility(l); - }); - } - }); - return layers; - } - - public updateVisibility(visibilityStatus: Map) { - visibilityStatus.forEach((visibilityStatus, l) => { - let layerInVisualisations = false; - if (!visibilityStatus) { - this.visualisationSetsConfig.forEach(v => { - const ls = new Set(v.layers); - if (!layerInVisualisations) { - layerInVisualisations = ls.has(l); - } - }); - if (layerInVisualisations) { - this.disableLayoutVisibility(l); - } - } else { - let oneVisualisationEnabled = false; - this.visualisationSetsConfig.forEach(v => { - const ls = new Set(v.layers); - if (!layerInVisualisations) { - layerInVisualisations = ls.has(l); - } - if (ls.has(l) && v.enabled) { - oneVisualisationEnabled = true; - this.enableLayoutVisibility(l); - } - }); - if (!oneVisualisationEnabled && layerInVisualisations) { - this.disableLayoutVisibility(l); - } - } - }); - } - - - - public hasCrossOrDrawLayer(e: any): boolean { - const features = this.queryRenderedFeatures(e.point); - return (!!features && !!features.find(f => f && f.layer && f.layer.id && f.layer.id.startsWith(CROSS_LAYER_PREFIX))); - } - - - - public disableLayoutVisibility(layer: string) { - this.getMapProvider().setLayoutProperty(layer, 'visibility', 'none'); - this.setStrokeLayoutVisibility(layer, 'none'); - this.setScrollableLayoutVisibility(layer, 'none'); - } - - public enableLayoutVisibility(layer: string) { - this.setLayoutProperty(layer, 'visibility', 'visible'); - this.setStrokeLayoutVisibility(layer, 'visible'); - this.setScrollableLayoutVisibility(layer, 'visible'); - } public setLayoutProperty(layer: string, name: string, value: any, options?: any) { this.getMapProvider().setLayoutProperty(layer, name, value, options); return this; } - public findVisualisationSetLayer(visuName: string) { - return this.visualisationSetsConfig.find(v => v.name === visuName).layers; - } - public setVisualisationSetLayers(visuName: string, layers: string[]) { - const f = this.visualisationSetsConfig.find(v => v.name === visuName); - if (f) { - f.layers = layers; - } - } public unsubscribeEvents() { this._eventSubscription.forEach(s => s.unsubscribe()); diff --git a/projects/arlas-map/src/lib/map/interface/map.interface.ts b/projects/arlas-map/src/lib/map/interface/map.interface.ts index f71df982..9a83a74b 100644 --- a/projects/arlas-map/src/lib/map/interface/map.interface.ts +++ b/projects/arlas-map/src/lib/map/interface/map.interface.ts @@ -34,140 +34,10 @@ */ export interface MapInterface { - addControl( - control: unknown, - position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left', - ): this; - - resize(eventData?: unknown): this; - - getBounds(): unknown; - - getMaxBounds(): unknown | null; - - setMaxBounds(unknown?: unknown): this; - - setMinZoom(minZoom?: number | null): this; - - getMinZoom(): number; - - setMaxZoom(maxZoom?: number | null): this; - - getMaxZoom(): number; - - project(lnglat: unknown): unknown; - - unproject(point: unknown): unknown; - - /** - * Returns an array of GeoJSON Feature objects representing visible features that satisfy the query parameters. - * - * The properties value of each returned feature object contains the properties of - * its source feature. For GeoJSON sources, only string and numeric property values are supported - * (i.e. null, Array, and Object values are not supported). - * - * Each feature includes top-level layer, source, and sourceLayer properties. - * The layer property is an object representing the style layer to which the feature belongs. - * Layout and paint properties in this object contain values which are fully evaluated for the given zoom - * level and feature. - * - * Only features that are currently rendered are included. Some features will not be included, like: - * - * - Features from layers whose visibility property is "none". - * - Features from layers whose zoom range excludes the current zoom level. - * - Symbol features that have been hidden due to text or icon collision. - * - * Features from all other layers are included, - * including features that may have no visible contribution to the rendered result; - * for example, because the layer's opacity or color alpha component is set to 0. - * - * The topmost rendered feature appears first in the returned array, - * and subsequent features are sorted by descending z-order. - * Features that are rendered multiple times (due to wrapping across the antimeridian at low zoom levels) - * are returned only once (though subject to the following caveat). - * - * Because features come from tiled vector data or GeoJSON data that is converted to tiles internally, - * feature geometries may be split or duplicated across tile boundaries and, as a result, - * features may appear multiple times in query results. For example, suppose there is - * a highway running through the bounding rectangle of a query. - * The results of the query will be those parts of the highway that lie within the map tiles covering the bounding - * rectangle, even if the highway extends into other tiles, and the portion of the highway within each map tile will - * be returned as a separate feature. Similarly, a point feature near a tile boundary may appear in multiple tiles - * due to tile buffering. - * - * @param pointOrBox The geometry of the query region: either a single point or - * southwest and northeast points describing a bounding box. Omitting this parameter - * (i.e. calling Map#queryRenderedFeatures with zero arguments, or with only a options argument) - * is equivalent to passing a bounding box encompassing the entire map viewport. - * @param options - */ - queryRenderedFeatures( - pointOrBox?: unknown, - options?: { layers?: string[] | undefined; filter?: any[] | undefined; } & unknown, - ): unknown[]; - - setFilter(layer: string, filter?: any[] | boolean | null, options?: unknown | null): this; - - setLayoutProperty(layer: string, name: string, value: any, options?: unknown): this; - - getLight(): unknown; - - setFeatureState( - feature: unknown, - state: { [key: string]: any; }, - ): void; - - getFeatureState(feature: unknown): { [key: string]: any; }; - - removeFeatureState(target: unknown, key?: string): void; - - getContainer(): HTMLElement; - - getCanvasContainer(): HTMLElement; - - getCanvas(): HTMLCanvasElement; - - getCenter(): unknown; - - setCenter(center: unknown, unknown?: unknown): this; - - panTo(lnglat: unknown, options?: unknown, unknown?: unknown): this; - - getZoom(): number; - - setZoom(zoom: number, unknown?: unknown): this; - - - getBearing(): number; - - setBearing(bearing: number, unknown?: unknown): this; - - rotateTo(bearing: number, options?: unknown, unknown?: unknown): this; - - getPitch(): number; - - setPitch(pitch: number, unknown?: unknown): this; - - cameraForBounds(bounds: unknown, options?: unknown): unknown | undefined; - - fitBounds(bounds: unknown, options?: unknown, unknown?: unknown): this; - - - on( - type: T, - layer: string, - listener: (ev: unknown) => void, - ): this; - on(type: T, listener: (ev: unknown) => void): this; - on(type: string, listener: (ev: any) => void): this; - - once( - type: T, - layer: string, - listener: (ev: unknown) => void, - ): this; - once(type: T, listener: (ev: unknown) => void): this; - once(type: string, listener: (ev: any) => void): this; - - } - \ No newline at end of file + addControl( + control: unknown, + position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left', + ): this; + + getZoom(): number; +} diff --git a/projects/arlas-map/src/lib/map/service/arlas-map.service.ts b/projects/arlas-map/src/lib/map/service/arlas-map.service.ts index 5361347a..2e025a2b 100644 --- a/projects/arlas-map/src/lib/map/service/arlas-map.service.ts +++ b/projects/arlas-map/src/lib/map/service/arlas-map.service.ts @@ -22,66 +22,7 @@ export abstract class ArlasMapService { public abstract boundsToString(bounds: any): string; - public abstract updateMapStyle(map: AbstractArlasMapGL, l: any, ids: Array, sourceName: string): void; - - public getVisibleIdsFilter(map: AbstractArlasMapGL, layer: any, ids: Array) { - const lFilter = map.layersMap.get(layer).filter; - const filters = []; - if (lFilter) { - lFilter.forEach(f => { - filters.push(f); - }); - } - if (filters.length === 0) { - filters.push('all'); - } - filters.push([ - 'match', - ['get', 'id'], - Array.from(new Set(ids)), - true, - false - ]); - return filters; - } - - /** Binds custom interaction of : 'mouseleave' and 'mouseenter' events - * to all draw layers - */ - public getCustomEventsToDrawLayers(map: AbstractArlasMapGL) { - const drawPolygonLayers = [ - 'gl-draw-polygon-stroke-inactive', - 'gl-draw-polygon-stroke-active', - 'gl-draw-polygon-stroke-static' - ].map(layer => ['.cold', '.hot'] - .map(id => layer.concat(id))) - .reduce((p, ac) => ac.concat(p), []); - return [ - { - layers: drawPolygonLayers, - mapEventBinds: - [{ - event: 'mousemove', - fn: (e) => { - map.setCursorStyle('pointer'); - } - } - ], - }, - { - layers: drawPolygonLayers, - mapEventBinds: [ - { - event: 'mouseleave', - fn: (e) => { - map.setCursorStyle(''); - } - } - ] - }, - ] - } - + public abstract getPointFromScreen(e, container: HTMLElement); /** Sets `data` to a Geojson `source` of the map @@ -99,12 +40,14 @@ export abstract class ArlasMapService { */ public abstract addLayer(map: AbstractArlasMapGL, layer: any, beforeId?: string); public abstract addArlasDataLayer(map: AbstractArlasMapGL, layer: any, layersMap: Map, beforeId?: string); - public abstract getLayersFromPattern(map: AbstractArlasMapGL, layersIdPattern: string): any[] + public abstract getLayersFromPattern(map: AbstractArlasMapGL, layersIdPattern: string): any[]; + public abstract getAllLayers(map: AbstractArlasMapGL): any[]; + public abstract hasLayer(map: AbstractArlasMapGL, layer: any); public abstract hasLayersFromPattern(map: AbstractArlasMapGL, layersIdPattern: string); public abstract moveLayer(map: AbstractArlasMapGL, layer: any, beforeId?: string); public abstract moveArlasDataLayer(map: AbstractArlasMapGL, layer: any, layersMap: Map, beforeId?: string); - public abstract onLayerEvent(eventName: any, map: AbstractArlasMapGL, layer: any, fn: () => void); + public abstract onLayerEvent(eventName: any, map: AbstractArlasMapGL, layer: any, fn: (e) => void); public abstract removeLayer(map: AbstractArlasMapGL, layer: any); public abstract removeLayers(map: AbstractArlasMapGL, layers: any) public abstract removeLayersFromPattern(map: AbstractArlasMapGL, layersIdPattern: string); @@ -112,8 +55,11 @@ export abstract class ArlasMapService { public abstract isLayerVisible(layer: any): boolean; public abstract getLayer(map: AbstractArlasMapGL, layerId: string): any; + + public abstract queryFeatures(e: any, map: AbstractArlasMapGL, layersIdPattern: string, options?: any); public abstract hasSource(map: AbstractArlasMapGL, source: any); public abstract getSource(sourceId: string, options: any): any; + public abstract getAllSources(options: any): any; public abstract setSource(sourceId: string, source: any, options: any); public abstract removeSource(map: AbstractArlasMapGL, source: any); @@ -141,6 +87,4 @@ export abstract class ArlasMapService { public abstract createRasterSource(url: string, bounds: number[], maxZoom: number, minZoom: number, tileSize: number): any; - - } diff --git a/projects/arlas-map/src/lib/map/tools.ts b/projects/arlas-map/src/lib/map/tools.ts new file mode 100644 index 00000000..413e5ecb --- /dev/null +++ b/projects/arlas-map/src/lib/map/tools.ts @@ -0,0 +1,42 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +export function latLngToWKT(features) { + let wktType = 'POLYGON[###]'; + if (features.length > 1) { + wktType = 'MULTIPOLYGON([###])'; + } + + let polygons = ''; + features.forEach((feat, indexFeature) => { + if (feat) { + const currentFeat: Array = feat.geometry.coordinates; + polygons += (indexFeature === 0 ? '' : ',') + '(('; + currentFeat[0].forEach((coord, index) => { + polygons += (index === 0 ? '' : ',') + coord[0] + ' ' + coord[1]; + }); + polygons += '))'; + } + }); + + let wkt = ''; + if (polygons !== '') { + wkt = wktType.replace('[###]', polygons); + } + return wkt; +} \ No newline at end of file diff --git a/projects/arlas-mapbox/src/lib/arlas-map-logic.service.ts b/projects/arlas-mapbox/src/lib/arlas-map-logic.service.ts new file mode 100644 index 00000000..5591441d --- /dev/null +++ b/projects/arlas-mapbox/src/lib/arlas-map-logic.service.ts @@ -0,0 +1,119 @@ +import { Injectable } from '@angular/core'; +import { ArlasMapFunctionalService } from 'arlas-map'; +import { ArlasMapboxService } from './arlas-mapbox.service'; +import { FeatureCollection } from '@turf/helpers'; +import { ArlasMapboxGL } from './map/ArlasMapboxGL'; +import { ArlasMapSource } from 'arlas-map'; +import { MapboxSourceType } from './map/model/sources'; +import { MapLayers } from 'arlas-map'; +import { LayerMetadata } from 'arlas-map'; +import { Expression, GeoJSONSource } from 'mapbox-gl'; +import { ArlasAnyLayer } from './map/model/layers'; +import { VisualisationSetConfig } from 'arlas-map'; + +@Injectable({ + providedIn: 'root' +}) +export class MapLogicService extends ArlasMapFunctionalService{ + public dataSources: GeoJSONSource[] = []; + public layersMap: Map; + public constructor(public mapService: ArlasMapboxService) { + super(mapService); + } + + /** Add to map the sources that will host ARLAS data. */ + public declareArlasDataSources(dataSourcesIds: Set, data: FeatureCollection, map: ArlasMapboxGL) { + super.declareArlasDataSources(dataSourcesIds, data, map); + } + + public declareLabelSources(labelSourceId: string, data: FeatureCollection, map: ArlasMapboxGL) { + super.declareLabelSources(labelSourceId, data, map); + } + + public updateLabelSources(labelSourceId: string, data: FeatureCollection, map: ArlasMapboxGL) { + super.updateLabelSources(labelSourceId, data, map); + } + + public declareBasemapSources(basemapSources: Array>, map: ArlasMapboxGL) { + super.declareBasemapSources(basemapSources, map); + } + + public setLayersMap(mapLayers: MapLayers, layers?: Array) { + if (mapLayers) { + const mapLayersCopy = mapLayers; + if (layers) { + mapLayersCopy.layers = mapLayersCopy.layers.concat(layers); + } + const layersMap = new Map(); + mapLayersCopy.layers.forEach(layer => layersMap.set(layer.id, layer)); + this.layersMap = layersMap; + } + } + + public initMapLayers(mapLayers: MapLayers, map: ArlasMapboxGL) { + super.initMapLayers(mapLayers, map); + } + + public updateMapStyle(map: ArlasMapboxGL, l: any, ids: Array, sourceName: string): void { + const layer = this.mapService.getLayer(map, l) as ArlasAnyLayer; + if (!!layer && typeof (layer.source) === 'string' && layer.source.indexOf(sourceName) >= 0) { + if (ids && ids.length > 0) { + // Tests value in camel and kebab case due to an unknown issue on other projects + if ((layer.metadata as LayerMetadata).isScrollableLayer || layer.metadata['is-scrollable-layer']) { + map.setFilter(l, this.getVisibleIdsFilter(l, ids)); + const strokeLayerId = l.replace('_id:', '-fill_stroke-'); + const strokeLayer = this.mapService.getLayer(map, strokeLayerId); + if (!!strokeLayer) { + map.setFilter(strokeLayerId, this.getVisibleIdsFilter(strokeLayerId, ids)); + } + } + } else { + map.setFilter(l, map.layersMap.get(l).filter); + const strokeLayerId = l.replace('_id:', '-fill_stroke-'); + const strokeLayer = this.mapService.getLayer(map, strokeLayerId); + if (!!strokeLayer) { + map.setFilter(strokeLayerId, + map.layersMap.get(strokeLayerId).filter); + } + } + } + } + + public getVisibleIdsFilter(layer: any, ids: Array): Expression[] { + const lFilter = this.layersMap.get(layer).filter as Expression; + const filters = []; + if (lFilter) { + lFilter.forEach(f => { + filters.push(f); + }); + } + if (filters.length === 0) { + filters.push('all'); + } + filters.push([ + 'match', + ['get', 'id'], + Array.from(new Set(ids)), + true, + false + ]); + return filters; + } + + public addVisualisation(visualisation: VisualisationSetConfig, visualisations: VisualisationSetConfig[], layers: Array, + sources: Array>, mapLayers: MapLayers, map: ArlasMapboxGL): void { + sources.forEach((s) => { + if (typeof (s.source) !== 'string') { + map.getMapProvider().addSource(s.id, s.source); + } + }); + visualisations.unshift(visualisation); + this.visualisationsSets.visualisations.set(visualisation.name, new Set(visualisation.layers)); + this.visualisationsSets.status.set(visualisation.name, visualisation.enabled); + layers.forEach(layer => { + this.mapService.addLayer(map, layer); + }); + this.setLayersMap(mapLayers as MapLayers, layers); + this.reorderLayers(visualisations, map); + } +} \ No newline at end of file diff --git a/projects/arlas-mapbox/src/lib/arlas-mapbox.service.ts b/projects/arlas-mapbox/src/lib/arlas-mapbox.service.ts index 2d9cc823..7238f064 100644 --- a/projects/arlas-mapbox/src/lib/arlas-mapbox.service.ts +++ b/projects/arlas-mapbox/src/lib/arlas-mapbox.service.ts @@ -1,16 +1,16 @@ import { Injectable } from '@angular/core'; -import { ARLAS_ID, ArlasMapService, CROSS_LAYER_PREFIX, FILLSTROKE_LAYER_PREFIX, LngLat, SCROLLABLE_ARLAS_ID } from 'arlas-map'; +import { AbstractArlasMapGL, ARLAS_ID, ArlasMapService, CROSS_LAYER_PREFIX, FILLSTROKE_LAYER_PREFIX, LngLat, SCROLLABLE_ARLAS_ID } from 'arlas-map'; import { AnyLayer, AnySourceData, GeoJSONSource, GeoJSONSourceOptions, GeoJSONSourceRaw, LngLatBounds, Point, Popup, RasterLayer, RasterSource, Source, SymbolLayer } from 'mapbox-gl'; import { ArlasMapboxConfig, ArlasMapboxGL } from './map/ArlasMapboxGL'; import { ArlasDraw } from './draw/ArlasDraw'; import { ArlasAnyLayer } from './map/model/layers'; import { FeatureCollection } from '@turf/helpers'; import { MapboxVectorStyle } from './map/model/vector-style'; +import { ExternalEvent } from 'arlas-map'; @Injectable() export class ArlasMapboxService extends ArlasMapService { - constructor() { super(); } @@ -44,36 +44,11 @@ export class ArlasMapboxService extends ArlasMapService { return bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); } - public updateMapStyle(map: ArlasMapboxGL, l: any, ids: Array, sourceName: string): void { - const layer = this.getLayer(map, l); - if (!!layer && typeof (layer.source) === 'string' && layer.source.indexOf(sourceName) >= 0) { - if (ids && ids.length > 0) { - // Tests value in camel and kebab case due to an unknown issue on other projects - if (layer.metadata.isScrollableLayer || layer.metadata['is-scrollable-layer']) { - map.setFilter(l, this.getVisibleIdsFilter(map, l, ids)); - const strokeLayerId = l.replace('_id:', '-fill_stroke-'); - const strokeLayer = map.getLayer(strokeLayerId); - if (!!strokeLayer) { - map.setFilter(strokeLayerId, this.getVisibleIdsFilter(map, strokeLayerId, ids)); - } - } - } else { - map.setFilter(l, map.layersMap.get(l).filter); - const strokeLayerId = l.replace('_id:', '-fill_stroke-'); - const strokeLayer = map.getLayer(strokeLayerId); - if (!!strokeLayer) { - map.setFilter(strokeLayerId, - map.layersMap.get(strokeLayerId).filter); - } - } - } - } - public setDataToGeojsonSource(source: GeoJSONSource, data: FeatureCollection) { if (!!source) { source.setData(data); } - }; + } /** * @override Add an Image to the map. @@ -91,9 +66,7 @@ export class ArlasMapboxService extends ArlasMapService { if (error) { console.warn(errorMessage); } - console.log('adding the imahe') if (!mapboxMap.hasImage(name)) { - console.log(image) mapboxMap.addImage(name, image, opt); } }); @@ -167,7 +140,7 @@ export class ArlasMapboxService extends ArlasMapService { * @param layer * @param fn */ - public onLayerEvent(eventName: 'click' | 'mousemove' | 'mouseleave', map: ArlasMapboxGL, layer: string, fn: () => void): void { + public onLayerEvent(eventName: 'click' | 'mousemove' | 'mouseleave', map: ArlasMapboxGL, layer: string, fn: (e) => void): void { map.getMapProvider().on(eventName, layer, fn); } @@ -202,7 +175,7 @@ export class ArlasMapboxService extends ArlasMapService { * @param layer Layer identifier * @returns the layer object. */ - private getLayer(map: ArlasMapboxGL, layer: string): ArlasAnyLayer { + public getLayer(map: ArlasMapboxGL, layer: string): ArlasAnyLayer { return map.getMapProvider().getLayer(layer) as ArlasAnyLayer; } @@ -219,12 +192,12 @@ export class ArlasMapboxService extends ArlasMapService { if (layer.type === 'fill') { const strokeId = layer.id.replace(ARLAS_ID, FILLSTROKE_LAYER_PREFIX); if (this.hasLayer(map, strokeId)) { - this.setLayerVisibility(strokeId, isVisible, map); + map.getMapProvider().setLayoutProperty(strokeId, 'visibility', isVisible ? 'visible' : 'none'); } } const scrollableId = layer.id.replace(ARLAS_ID, SCROLLABLE_ARLAS_ID); if (!layer.id.startsWith(SCROLLABLE_ARLAS_ID) && this.hasLayer(map, scrollableId)) { - this.setLayerVisibility(scrollableId, isVisible, map); + map.getMapProvider().setLayoutProperty(scrollableId, 'visibility', isVisible ? 'visible' : 'none'); } } } @@ -426,5 +399,118 @@ export class ArlasMapboxService extends ArlasMapService { map.getMapProvider().flyTo({ center: [lng, lat], zoom }); } + public filterGeojsonData(map: ArlasMapboxGL, layerId: string, filter: any) { + map.getMapProvider().setFilter(layerId, filter); + } + + public queryFeatures(e: any, map: ArlasMapboxGL, layersIdPattern: string, options: any) { + map.getMapProvider().queryRenderedFeatures(e.point, options).filter(f => !!f.layer && !!f.layer.id && f.layer.id.includes(layersIdPattern)); + }; + + + public isLayerVisible(layer: ArlasAnyLayer): boolean { + return layer.layout.visibility === 'visible'; + } + + public getSource(sourceId: string, map: ArlasMapboxGL) { + return map.getMapProvider().getSource(sourceId); + } + + public addArlasDataLayer(map: ArlasMapboxGL, layer: ArlasAnyLayer, arlasDataLayers: Map, before?: string) { + const scrollableId = layer.id.replace(ARLAS_ID, SCROLLABLE_ARLAS_ID); + const scrollableLayer = arlasDataLayers.get(scrollableId); + if (!!scrollableLayer) { + this.addLayer(map, scrollableLayer, before); + } + this.addLayer(map, layer, before); + /** add stroke layer if the layer is a fill */ + if (layer.type === 'fill') { + const strokeId = layer.id.replace(ARLAS_ID, FILLSTROKE_LAYER_PREFIX); + const strokeLayer = arlasDataLayers.get(strokeId); + if (!!strokeLayer) { + this.addLayer(map, strokeLayer, before); + } + } + } + + /** + * @override Mapbox implementation. + * Checks if there are any layers respecting the given id patters + * @param map Map instance. + * @param layersIdPattern Identifiers pattern. + * @returns + */ + public getLayersFromPattern(map: ArlasMapboxGL, layersIdPattern: string): ArlasAnyLayer[] { + return map.getMapProvider().getStyle().layers.filter(l => l.id.includes(layersIdPattern)) as ArlasAnyLayer[]; + } + + public getAllLayers(map: ArlasMapboxGL): ArlasAnyLayer[] { + return map.getMapProvider().getStyle().layers as ArlasAnyLayer[]; + } + + /** + * @override Mapbox implementation. + * Moves the given layer to the top in map instance OR optionnaly before a layer. + * @param map Map + * @param layer Layer to add to the map + * @param before Identifier of an already added layer. The given Layer (second param) is moved under this 'before' layer. + */ + public moveLayer(map: ArlasMapboxGL, layer: string, before?: string) { + if (this.hasLayer(map, layer)) { + map.getMapProvider().moveLayer(layer, before); + } else { + console.warn(`The layer ${layer} is not added to the map`); + } + } + + public moveArlasDataLayer(map: ArlasMapboxGL, layerId: string, arlasDataLayers: Map, before?: string) { + const layer = arlasDataLayers.get(layerId); + const scrollableId = layer.id.replace(ARLAS_ID, SCROLLABLE_ARLAS_ID); + const scrollableLayer = arlasDataLayers.get(scrollableId); + if (!!scrollableLayer && this.hasLayer(map, scrollableId)) { + this.moveLayer(map, scrollableId); + } + if (!!this.hasLayer(map, layerId)) { + this.moveLayer(map, layerId); + if (layer.type === 'fill') { + const strokeId = layer.id.replace(ARLAS_ID, FILLSTROKE_LAYER_PREFIX); + const strokeLayer = arlasDataLayers.get(strokeId); + if (!!strokeLayer && this.hasLayer(map, strokeId)) { + this.moveLayer(map, strokeId); + } + if (!!strokeLayer && !!strokeLayer.id) { + const selectId = 'arlas-' + ExternalEvent.select.toString() + '-' + strokeLayer.id; + const selectLayer = arlasDataLayers.get(selectId); + if (!!selectLayer && this.hasLayer(map, selectId)) { + this.moveLayer(map, selectId); + } + const hoverId = 'arlas-' + ExternalEvent.hover.toString() + '-' + strokeLayer.id; + const hoverLayer = arlasDataLayers.get(hoverId); + if (!!hoverLayer && this.hasLayer(map, hoverId)) { + this.moveLayer(map, hoverId); + } + } + } + } + const selectId = 'arlas-' + ExternalEvent.select.toString() + '-' + layer.id; + const selectLayer = arlasDataLayers.get(selectId); + if (!!selectLayer && this.hasLayer(map, selectId)) { + this.moveLayer(map, selectId); + } + const hoverId = 'arlas-' + ExternalEvent.hover.toString() + '-' + layer.id; + const hoverLayer = arlasDataLayers.get(hoverId); + if (!!hoverLayer && this.hasLayer(map, hoverId)) { + this.moveLayer(map, hoverId); + } + } + + public getAllSources(map: ArlasMapboxGL) { + return map.getMapProvider().getStyle().sources; + } + + + + + } diff --git a/projects/arlas-mapbox/src/lib/basemaps/mapbox-basemap.service.ts b/projects/arlas-mapbox/src/lib/basemaps/mapbox-basemap.service.ts index 846cdef8..d2d660ce 100644 --- a/projects/arlas-mapbox/src/lib/basemaps/mapbox-basemap.service.ts +++ b/projects/arlas-mapbox/src/lib/basemaps/mapbox-basemap.service.ts @@ -23,15 +23,22 @@ import { MapboxBasemapStyle } from './basemap.config'; import mapboxgl from 'mapbox-gl'; import { catchError, forkJoin, Observable, of, tap } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { BasemapService } from 'arlas-map'; +import { AbstractArlasMapGL, BasemapService, BasemapStyle } from 'arlas-map'; import { ArlasMapboxGL } from '../map/ArlasMapboxGL'; import { CustomProtocol } from '../map/protocols/mapbox-gl-custom-protocol'; import { ArlasMapboxService } from '../arlas-mapbox.service'; +import { MapLogicService } from '../arlas-map-logic.service'; +import { MapboxSourceType } from '../map/model/sources'; +import { ArlasMapSource } from 'arlas-map'; +import { ArlasAnyLayer } from '../map/model/layers'; @Injectable() export class MapboxBasemapService extends BasemapService { + - public constructor(protected http: HttpClient, protected mapService: ArlasMapboxService) { + public constructor(protected http: HttpClient, protected mapService: ArlasMapboxService, + private mapLogicService: MapLogicService + ) { super(http, mapService); } @@ -75,16 +82,13 @@ export class MapboxBasemapService extends BasemapService { const clonedStyleFile: mapboxgl.Style = this.cloneStyleFile(selected); return this.buildInitStyle(clonedStyleFile); } - console.log('iniiit style uuum') return selected.styleFile as mapboxgl.Style; } public fetchSources$(): Observable { const sources$: Observable[] = []; - console.log('fetch') this.basemaps.styles().forEach(s => { - console.log('fetchhh'); sources$.push(this.getStyleFile(s).pipe( tap(sf => { Object.keys(sf.sources).forEach(k => { @@ -98,8 +102,7 @@ export class MapboxBasemapService extends BasemapService { s.styleFile = sf as mapboxgl.Style; }), catchError(() => { - console.log('uuum error !!!') - s.errored = true; + s.errored = true; return of(); }) )); @@ -114,4 +117,48 @@ export class MapboxBasemapService extends BasemapService { return of(b.styleFile); } } + + + public setBasemap(s: any, newBasemap: BasemapStyle, map: ArlasMapboxGL, mapSources: Array>) { + const selectedBasemapLayersSet = new Set(); + const layers: Array = this.mapService.getAllLayers(map); + const sources = this.mapService.getAllSources(map); + if (s.layers) { + s.layers.forEach(l => selectedBasemapLayersSet.add(l.id)); + } + const layersToSave = new Array(); + const sourcesToSave = new Array>(); + layers.filter((l: any) => !selectedBasemapLayersSet.has(l.id) && !!l.source).forEach(l => { + layersToSave.push(l as ArlasAnyLayer); + if (sourcesToSave.filter(ms => ms.id === l.source.toString()).length === 0) { + sourcesToSave.push({ id: l.source.toString(), source: sources[l.source.toString()] as MapboxSourceType }); + } + }); + const sourcesToSaveSet = new Set(); + sourcesToSave.forEach(mapSource => sourcesToSaveSet.add(mapSource.id)); + if (mapSources) { + mapSources.forEach(mapSource => { + if (!sourcesToSaveSet.has(mapSource.id)) { + sourcesToSave.push(mapSource); + } + }); + } + const initStyle = this.getInitStyle(newBasemap); + map.getMapProvider().setStyle(initStyle).once('styledata', () => { + setTimeout(() => { + /** the timeout fixes a mapboxgl bug related to layer placement*/ + this.mapLogicService.declareBasemapSources(sourcesToSave, map); + layersToSave.forEach(l => { + this.mapService.addLayer(map, l); + }); + localStorage.setItem(this.LOCAL_STORAGE_BASEMAPS, JSON.stringify(newBasemap)); + this.basemaps.setSelected(newBasemap); + if (newBasemap.type === 'protomap') { + this.addProtomapBasemap(map); + this.notifyProtomapAddition(); + } + this.basemapChangedSource.next(); + }, 0); + }); + } } diff --git a/projects/arlas-mapbox/src/lib/map/ArlasMapboxGL.ts b/projects/arlas-mapbox/src/lib/map/ArlasMapboxGL.ts index cac4d42b..b32c95bf 100644 --- a/projects/arlas-mapbox/src/lib/map/ArlasMapboxGL.ts +++ b/projects/arlas-mapbox/src/lib/map/ArlasMapboxGL.ts @@ -21,12 +21,10 @@ import { AbstractArlasMapGL, BindLayerToEvent, MapConfig, - MapEventBinds, OnMoveResult, } from 'arlas-map'; import mapboxgl, { AnyLayer, - AnySourceData, Control, IControl, LngLat, @@ -34,32 +32,24 @@ import mapboxgl, { LngLatBoundsLike, MapboxOptions, MapLayerEventType, - Point, - PointLike + Point } from 'mapbox-gl'; import MapboxDraw from '@mapbox/mapbox-gl-draw'; -import { MapLayers, VisualisationSetConfig, - ControlButton, ControlPosition, DrawControlsOption, - MapExtent, ArlasMapSource - } from 'arlas-map'; +import { + MapLayers, ControlButton, ControlPosition, DrawControlsOption, + MapExtent, +} from 'arlas-map'; import { ArlasAnyLayer } from './model/layers'; import { MapBoxControlButton, MapBoxPitchToggle } from './model/controls'; -import { MapboxSourceType } from './model/sources'; import bbox from '@turf/bbox'; - -// todo : rename to mapboxConfig export interface ArlasMapboxConfig extends MapConfig { mapLayers: MapLayers; customEventBind: (map: AbstractArlasMapGL) => BindLayerToEvent[]; - mapLayersEventBind: { - onHover: MapEventBinds[]; - emitOnClick: MapEventBinds[]; - zoomOnClick: MapEventBinds[]; - }; } export class ArlasMapboxGL extends AbstractArlasMapGL { + protected _mapLayers: MapLayers; protected _mapProvider: mapboxgl.Map; // Lat/lng on mousedown (start); mouseup (end) and mousemove (between start and end) @@ -87,6 +77,11 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { return [swWorld, neWorld]; } + public on(type: string, listener: (ev: any) => void): this { + this.getMapProvider().on(type, listener); + return this; + } + protected _initMapProvider(config: ArlasMapboxConfig) { this._mapProvider = new mapboxgl.Map( @@ -114,7 +109,6 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { } public _initControls(): void { - console.log('init controls'); if (this._controls) { if (this._controls.mapAttribution) { this.addControl(new mapboxgl.AttributionControl(this._controls.mapAttribution.config), this._controls.mapAttribution.position); @@ -203,10 +197,6 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { return this; } - public setCursorStyle(cursor: string) { - this.getMapProvider().getCanvas().style.cursor = cursor; - } - public enableDragPan() { this.getMapProvider().dragPan.enable(); } @@ -224,42 +214,11 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { return this.getMapProvider().getStyle().layers; } - public isLayerVisible(layer: ArlasAnyLayer): boolean { - return layer.layout.visibility === 'visible'; - } - public getColdOrHotLayers() { return this.getLayers().map(layer => layer.id) .filter(id => id.indexOf('.cold') >= 0 || id.indexOf('.hot') >= 0); } - public addVisualisation(visualisation: VisualisationSetConfig, layers: Array, - sources: Array>): void { - sources.forEach((s) => { - if (typeof (s.source) !== 'string') { - this.getMapProvider().addSource(s.id, s.source); - } - }); - this.visualisationSetsConfig.unshift(visualisation); - this.visualisationsSets.visualisations.set(visualisation.name, new Set(visualisation.layers)); - this.visualisationsSets.status.set(visualisation.name, visualisation.enabled); - layers.forEach(layer => { - this.addLayer(layer); - }); - - this.setLayersMap(this._mapLayers as MapLayers, layers); - this.reorderLayers(); - } - - protected _addExternalEventLayers() { - if (!!this._mapLayers.externalEventLayers) { - this._mapLayers.layers - .filter(layer => this._mapLayers.externalEventLayers.map(e => e.id).indexOf(layer.id) >= 0) - .forEach(l => this.addLayerInWritePlaceIfNotExist(l.id)); - } - } - - public onLoad(fn: () => void): void { this.getMapProvider().on('load', fn); } @@ -268,7 +227,10 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { return new mapboxgl.Point((this._offset.east + this._offset.west) / 2, (this._offset.north + this._offset.south) / 2); } - protected _getMoveEnd() { + protected _getMoveEnd(visualisationsSets: { + visualisations: Map>; + status: Map; + }) { const offsetPoint = this.calcOffsetPoint(); const centerOffsetPoint = this.getMapProvider().project(this.getMapProvider().getCenter()).add(offsetPoint); const centerOffSetLatLng = this.getMapProvider().unproject(centerOffsetPoint); @@ -296,9 +258,9 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { const rawEastOffset = topRghtOffsetLatLng.lng; const rawNorthOffset = topRghtOffsetLatLng.lat; const visibleLayers = new Set(); - this.visualisationsSets.status.forEach((b, vs) => { + visualisationsSets.status.forEach((b, vs) => { if (b) { - this.visualisationsSets.visualisations.get(vs).forEach(l => visibleLayers.add(l)); + visualisationsSets.visualisations.get(vs).forEach(l => visibleLayers.add(l)); } }); const onMoveData: OnMoveResult = { @@ -350,15 +312,6 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { return onMoveData; } - public bindLayersToMapEvent(map: ArlasMapboxGL, layers: string[] | Set, binds: MapEventBinds[]) { - layers.forEach(layerId => { - binds.forEach(el => { - this.getMapProvider().on(el.event, layerId, (e) => { - el.fn(e, map); - }); - }); - }); - } /** Gets bounds of the given geometry */ public geometryToBound(geometry: any, paddingPercentage?: number): unknown { @@ -409,7 +362,6 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { return this.getBounds().getWest(); } public getNorthBounds() { - console.log('north', this.getBounds().getNorth()) return this.getBounds().getNorth(); } @@ -427,17 +379,6 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { return this.getBounds().getEast(); } - - public addLayer(layer: AnyLayer, before?: string) { - this.getMapProvider().addLayer(layer, before); - return this; - } - - public moveLayer(id: string, before?: string) { - this.getMapProvider().moveLayer(id, before); - return this; - } - public getSource(id: string) { return this._mapProvider.getSource(id); } @@ -460,109 +401,19 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { return this._mapProvider.getCanvasContainer(); } - public queryRenderedFeatures(point) { return this._mapProvider.queryRenderedFeatures(point); } - public project(latLng: LngLat) { - return this._mapProvider.project(latLng); - } - - public unproject(latLng: PointLike) { - return this._mapProvider.unproject(latLng); - } - - - public getCenter() { - return this._mapProvider.getCenter(); - } - - public getLayer(id: string) { - return this._mapProvider.getLayer(id); - } - - public setStyle(style: mapboxgl.Style | string, option?: { diff?: boolean | undefined; localIdeographFontFamily?: string | undefined; }) { - this._mapProvider.setStyle(style, option); - return this; - } - - public removeLayer(id: string) { - this._mapProvider.removeLayer(id); - return this; - } - - public getStyle() { - return this._mapProvider.getStyle(); - } - - public hasImage(id: string): boolean { - return this.getMapProvider().hasImage(id); - } - - public cameraForBounds(bounds: mapboxgl.LngLatBoundsLike, - options?: mapboxgl.CameraForBoundsOptions): mapboxgl.CameraForBoundsResult | undefined { - return this._mapProvider.cameraForBounds(bounds, options); - } - - public getBearing(): number { - return this._mapProvider.getBearing(); - } - - public getCanvas(): HTMLCanvasElement { - return this._mapProvider.getCanvas(); - } - - public getContainer(): HTMLElement { - return this._mapProvider.getContainer(); - } - - public getFeatureState(feature: mapboxgl.FeatureIdentifier | mapboxgl.MapboxGeoJSONFeature): { [p: string]: any; } { - return this._mapProvider.getFeatureState(feature); - } - - public getLight(): mapboxgl.Light { - return this._mapProvider.getLight(); - } - public getMaxBounds(): mapboxgl.LngLatBounds | null { return this._mapProvider.getMaxBounds(); } - public getRenderWorldCopies(): boolean { - return this._mapProvider.getRenderWorldCopies(); - } - - public panTo(lnglat: mapboxgl.LngLatLike, options?: mapboxgl.AnimationOptions, eventdata?: mapboxgl.EventData): this { - this._mapProvider.panTo(lnglat, options, eventdata); - return this; - } - - public removeFeatureState(target: mapboxgl.FeatureIdentifier | mapboxgl.MapboxGeoJSONFeature, key?: string): void { - this._mapProvider.removeFeatureState(target, key); - } - public resize(eventData?: mapboxgl.EventData): this { this._mapProvider.resize(eventData); return this; } - public rotateTo(bearing: number, options?: mapboxgl.AnimationOptions, eventData?: mapboxgl.EventData): this { - this._mapProvider.rotateTo(bearing, options, eventData); - return this; - } - - public setBearing(bearing: number, eventData?: mapboxgl.EventData): this { - this._mapProvider.setBearing(bearing, eventData); - return this; - } - - public setFeatureState(feature: mapboxgl.FeatureIdentifier | mapboxgl.MapboxGeoJSONFeature, state: { - [p: string]: any; - }): void { - this._mapProvider.setFeatureState(feature, state); - } - public setFilter(layer: string, filter?: any[] | boolean | null, options?: mapboxgl.FilterOptions | null): this { this._mapProvider.setFilter(layer, filter, options); return this; @@ -573,51 +424,11 @@ export class ArlasMapboxGL extends AbstractArlasMapGL { return this; } - public setMaxZoom(maxZoom?: number | null): this { - this._mapProvider.setMaxZoom(maxZoom); - return this; - } - - public setMinZoom(minZoom?: number | null): this { - this._mapProvider.setMinZoom(minZoom); - return this; - } - - public setPitch(pitch: number, eventData?: mapboxgl.EventData): this { - this._mapProvider.setPitch(pitch, eventData); - return this; - } - public setZoom(zoom: number, eventData?: mapboxgl.EventData): this { this._mapProvider.setZoom(zoom, eventData); return this; } - public on( - type: T, layer: string, - listener: (ev: (mapboxgl.MapLayerEventType[T] & mapboxgl.EventData)) => void): this; - public on(type: T, listener: (ev: (mapboxgl.MapEventType[T] & mapboxgl.EventData)) => void): this; - public on(type: string, listener: (ev: any) => void): this; - public on(type, layer, listener?): this { - this._mapProvider.on(type, layer, listener); - return this; - } - - public once - (type: T, layer: string, listener: (ev: (mapboxgl.MapLayerEventType[T] & mapboxgl.EventData)) => void): this; - public once(type: T, listener: (ev: (mapboxgl.MapEventType[T] & mapboxgl.EventData)) => void): this; - public once(type: string, listener: (ev: any) => void): this; - public once(type, layer, listener?): this { - this._mapProvider.once(type, layer, listener); - return this; - } - - - public addSource(id: string, source: AnySourceData): this { - this._mapProvider.addSource(id, source); - return this; - } - public getBounds() { return this.getMapProvider().getBounds(); } diff --git a/projects/arlas-mapbox/src/public-api.ts b/projects/arlas-mapbox/src/public-api.ts index 2c2dcc7c..dc3353b5 100644 --- a/projects/arlas-mapbox/src/public-api.ts +++ b/projects/arlas-mapbox/src/public-api.ts @@ -6,3 +6,4 @@ export { MapboxLegendService } from './lib/legend/legend.service'; export * from './lib/arlas-mapbox.service'; export * from './lib/arlas-mapbox.module'; export { MapboxBasemapService } from './lib/basemaps/mapbox-basemap.service'; +export { MapLogicService } from './lib/arlas-map-logic.service'; \ No newline at end of file diff --git a/projects/arlas-maplibre/src/lib/arlas-map-logic.service.ts b/projects/arlas-maplibre/src/lib/arlas-map-logic.service.ts index f647b595..2bc59950 100644 --- a/projects/arlas-maplibre/src/lib/arlas-map-logic.service.ts +++ b/projects/arlas-maplibre/src/lib/arlas-map-logic.service.ts @@ -5,15 +5,16 @@ import { FeatureCollection } from '@turf/helpers'; import { ArlasMaplibreGL } from './map/ArlasMaplibreGL'; import { ArlasMapSource } from 'arlas-map'; import { MaplibreSourceType } from './map/model/sources'; -import { TypedStyleLayer } from 'maplibre-gl'; +import { ExpressionSpecification, FilterSpecification, GeoJSONSourceSpecification, TypedStyleLayer } from 'maplibre-gl'; import { MapLayers } from 'arlas-map'; +import { LayerMetadata } from 'arlas-map'; +import { VisualisationSetConfig } from 'arlas-map'; @Injectable({ providedIn: 'root' }) export class MapLogicService extends ArlasMapFunctionalService{ - /** IMPORTANT NOTE: All the attributes/params that are typed with "any", will have the right type in the implementation. */ - public dataSources: any[] = []; + public dataSources: GeoJSONSourceSpecification[] = []; public layersMap: Map; public constructor(public mapService: ArlasMaplibreService) { super(mapService); @@ -49,120 +50,69 @@ export class MapLogicService extends ArlasMapFunctionalService{ } public initMapLayers(mapLayers: MapLayers, map: ArlasMaplibreGL) { - if (mapLayers) { - this.setLayersMap(mapLayers as MapLayers); - this.addVisualLayers(); - this._addExternalEventLayers(); - - this.bindLayersToMapEvent( - map, - mapLayers.events.zoomOnClick, - this.config.mapLayersEventBind.zoomOnClick - ); - - this.bindLayersToMapEvent( - map, - this.config.mapLayers.events.emitOnClick, - this.config.mapLayersEventBind.emitOnClick - ); - - this.bindLayersToMapEvent( - map, - this.config.mapLayers.events.onHover, - this.config.mapLayersEventBind.onHover - ); - } + super.initMapLayers(mapLayers, map); } - public reorderLayers() { - // parses the visulisation list from bottom in order to put the fist ones first - for (let i = this.visualisationSetsConfig.length - 1; i >= 0; i--) { - const visualisation: VisualisationSetConfig = this.visualisationSetsConfig[i]; - if (!!visualisation.layers && visualisation.enabled) { - for (let j = visualisation.layers.length - 1; j >= 0; j--) { - const l = visualisation.layers[j]; - const layer = this.layersMap.get(l); - const scrollableId = layer.id.replace(ARLAS_ID, SCROLLABLE_ARLAS_ID); - const scrollableLayer = this.layersMap.get(scrollableId); - if (!!scrollableLayer && !!this.getLayer(scrollableId)) { - this.moveLayer(scrollableId); - } - if (!!this.getLayer(l)) { - this.moveLayer(l); - if (layer.type === 'fill') { - const strokeId = layer.id.replace(ARLAS_ID, FILLSTROKE_LAYER_PREFIX); - const strokeLayer = this.layersMap.get(strokeId); - if (!!strokeLayer && !!this.getLayer(strokeId)) { - this.moveLayer(strokeId); - } - if (!!strokeLayer && !!strokeLayer.id) { - const selectId = 'arlas-' + ExternalEvent.select.toString() + '-' + strokeLayer.id; - const selectLayer = this.layersMap.get(selectId); - if (!!selectLayer && !!this.getLayer(selectId)) { - this.moveLayer(selectId); - } - const hoverId = 'arlas-' + ExternalEvent.hover.toString() + '-' + strokeLayer.id; - const hoverLayer = this.layersMap.get(hoverId); - if (!!hoverLayer && !!this.getLayer(hoverId)) { - this.moveLayer(hoverId); - } - } - } - } - const selectId = 'arlas-' + ExternalEvent.select.toString() + '-' + layer.id; - const selectLayer = this.layersMap.get(selectId); - if (!!selectLayer && !!this.getLayer(selectId)) { - this.moveLayer(selectId); - } - const hoverId = 'arlas-' + ExternalEvent.hover.toString() + '-' + layer.id; - const hoverLayer = this.layersMap.get(hoverId); - if (!!hoverLayer && !!this.getLayer(hoverId)) { - this.moveLayer(hoverId); + public updateMapStyle(map: ArlasMaplibreGL, l: any, ids: Array, sourceName: string): void { + const layer = this.mapService.getLayer(map, l) as TypedStyleLayer; + if (!!layer && typeof (layer.source) === 'string' && layer.source.indexOf(sourceName) >= 0) { + if (ids && ids.length > 0) { + // Tests value in camel and kebab case due to an unknown issue on other projects + if ((layer.metadata as LayerMetadata).isScrollableLayer || layer.metadata['is-scrollable-layer']) { + map.setFilter(l, this.getVisibleIdsFilter(l, ids)); + const strokeLayerId = l.replace('_id:', '-fill_stroke-'); + const strokeLayer = this.mapService.getLayer(map, strokeLayerId); + if (!!strokeLayer) { + map.setFilter(strokeLayerId, this.getVisibleIdsFilter(strokeLayerId, ids)); } } + } else { + map.setFilter(l, map.layersMap.get(l).filter); + const strokeLayerId = l.replace('_id:', '-fill_stroke-'); + const strokeLayer = this.mapService.getLayer(map, strokeLayerId); + if (!!strokeLayer) { + map.setFilter(strokeLayerId, + map.layersMap.get(strokeLayerId).filter); + } } } - - this.getColdOrHotLayers().forEach(id => this.moveLayer(id)); } - public updateLayersVisibility(visibilityCondition: boolean, visibilityFilter: Array, visibilityEvent: ExternalEvent, - collection?: string): void { - if (this._mapLayers && this._mapLayers.externalEventLayers) { - this._mapLayers.externalEventLayers.filter(layer => layer.on === visibilityEvent).forEach(layer => { - if (this.getLayer(layer.id) !== undefined) { - let originalLayerIsVisible = false; - const fullLayer = this.layersMap.get(layer.id); - const isCollectionCompatible = (!collection || (!!collection && (fullLayer.source as string).includes(collection))); - if (isCollectionCompatible) { - const originalLayerId = layer.id.replace('arlas-' + visibilityEvent.toString() + '-', ''); - const originalLayer = this.getMapProvider().getStyle().layers.find(l => l.id === originalLayerId); - if (!!originalLayer) { - originalLayerIsVisible = this.isLayerVisible(originalLayer); - } - const layerFilter: Array = []; - const externalEventLayer = this.layersMap.get(layer.id); - if (!!externalEventLayer && !!externalEventLayer.filter) { - externalEventLayer.filter.forEach(f => { - layerFilter.push(f); - }); - } - if (layerFilter.length === 0) { - layerFilter.push('all'); - } - if (visibilityCondition && originalLayerIsVisible) { - layerFilter.push(visibilityFilter); - this.setFilter(layer.id, layerFilter); - this.setLayoutProperty(layer.id, 'visibility', 'visible'); - } else { - this.setFilter(layer.id, (layer as any).filter); - this.setLayoutProperty(layer.id, 'visibility', 'none'); - } - } - } + public getVisibleIdsFilter(layer: any, ids: Array): ExpressionSpecification[] { + const lFilter = this.layersMap.get(layer).filter as ExpressionSpecification; + const filters = []; + if (lFilter) { + lFilter.forEach(f => { + filters.push(f); }); } + if (filters.length === 0) { + filters.push('all'); + } + filters.push([ + 'match', + ['get', 'id'], + Array.from(new Set(ids)), + true, + false + ]); + return filters; } - + public addVisualisation(visualisation: VisualisationSetConfig, visualisations: VisualisationSetConfig[], layers: Array, + sources: Array>, mapLayers: MapLayers, map: ArlasMaplibreGL): void { + sources.forEach((s) => { + if (typeof (s.source) !== 'string') { + map.getMapProvider().addSource(s.id, s.source); + } + }); + visualisations.unshift(visualisation); + this.visualisationsSets.visualisations.set(visualisation.name, new Set(visualisation.layers)); + this.visualisationsSets.status.set(visualisation.name, visualisation.enabled); + layers.forEach(layer => { + this.mapService.addLayer(map, layer); + }); + this.setLayersMap(mapLayers as MapLayers, layers); + this.reorderLayers(visualisations, map); + } } \ No newline at end of file diff --git a/projects/arlas-maplibre/src/lib/arlas-maplibre.service.ts b/projects/arlas-maplibre/src/lib/arlas-maplibre.service.ts index 7042aa4f..edf77187 100644 --- a/projects/arlas-maplibre/src/lib/arlas-maplibre.service.ts +++ b/projects/arlas-maplibre/src/lib/arlas-maplibre.service.ts @@ -1,6 +1,11 @@ import { Injectable } from '@angular/core'; -import { ARLAS_ID, ArlasMapService, FILLSTROKE_LAYER_PREFIX, SCROLLABLE_ARLAS_ID } from 'arlas-map'; -import { AddLayerObject, CanvasSourceSpecification, GeoJSONSource, GeoJSONSourceSpecification, LayerSpecification, LngLatBounds, Point, Popup, RasterLayerSpecification, RasterSourceSpecification, ResourceType, SourceSpecification, SymbolLayerSpecification, TypedStyleLayer } from 'maplibre-gl'; +import { AbstractArlasMapGL, ARLAS_ID, ArlasMapService, FILLSTROKE_LAYER_PREFIX, SCROLLABLE_ARLAS_ID } from 'arlas-map'; +import { + AddLayerObject, CanvasSourceSpecification, GeoJSONSource, + GeoJSONSourceSpecification, LayerSpecification, LngLatBounds, Point, Popup, + RasterLayerSpecification, RasterSourceSpecification, ResourceType, + SourceSpecification, SymbolLayerSpecification, TypedStyleLayer +} from 'maplibre-gl'; import { ArlasMaplibreConfig, ArlasMaplibreGL } from './map/ArlasMaplibreGL'; import { ArlasDraw } from './draw/ArlasDraw'; import { LngLat } from 'arlas-map'; @@ -13,6 +18,8 @@ import { ExternalEvent } from 'arlas-map'; @Injectable() export class ArlasMaplibreService extends ArlasMapService { + + public constructor() { super(); } @@ -47,31 +54,6 @@ export class ArlasMaplibreService extends ArlasMapService { return bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); } - public updateMapStyle(map: ArlasMaplibreGL, l: any, ids: Array, sourceName: string): void { - const layer = this.getLayer(map, l) as TypedStyleLayer; - if (!!layer && typeof (layer.source) === 'string' && layer.source.indexOf(sourceName) >= 0) { - if (ids && ids.length > 0) { - // Tests value in camel and kebab case due to an unknown issue on other projects - if ((layer.metadata as LayerMetadata).isScrollableLayer || layer.metadata['is-scrollable-layer']) { - map.setFilter(l, this.getVisibleIdsFilter(map, l, ids)); - const strokeLayerId = l.replace('_id:', '-fill_stroke-'); - const strokeLayer = this.getLayer(map, strokeLayerId); - if (!!strokeLayer) { - map.setFilter(strokeLayerId, this.getVisibleIdsFilter(map, strokeLayerId, ids)); - } - } - } else { - map.setFilter(l, map.layersMap.get(l).filter); - const strokeLayerId = l.replace('_id:', '-fill_stroke-'); - const strokeLayer = this.getLayer(map, strokeLayerId); - if (!!strokeLayer) { - map.setFilter(strokeLayerId, - map.layersMap.get(strokeLayerId).filter); - } - } - } - } - public setDataToGeojsonSource(source: GeoJSONSource, data: FeatureCollection) { if (!!source) { source.setData(data); @@ -146,6 +128,11 @@ export class ArlasMaplibreService extends ArlasMapService { } }; + + public getAllSources(map: ArlasMaplibreGL) { + return map.getMapProvider().getStyle().sources; + } + /** * @override Maplibre implementation. * Adds the given layer to the map instance, optionnaly before a layer. Otherwise it's added to the top. @@ -173,7 +160,7 @@ export class ArlasMaplibreService extends ArlasMapService { const strokeId = layer.id.replace(ARLAS_ID, FILLSTROKE_LAYER_PREFIX); const strokeLayer = arlasDataLayers.get(strokeId); if (!!strokeLayer) { - this.addLayer(map, scrollableLayer, before); + this.addLayer(map, strokeLayer, before); } } } @@ -234,7 +221,7 @@ export class ArlasMaplibreService extends ArlasMapService { this.moveLayer(map, hoverId); } - + } @@ -246,7 +233,7 @@ export class ArlasMaplibreService extends ArlasMapService { * @param layer * @param fn */ - public onLayerEvent(eventName: 'click' | 'mousemove' | 'mouseleave' | 'mouseenter', map: ArlasMaplibreGL, layer: string, fn: () => void): void { + public onLayerEvent(eventName: 'click' | 'mousemove' | 'mouseleave' | 'mouseenter', map: ArlasMaplibreGL, layer: string, fn: (e) => void): void { map.getMapProvider().on(eventName, layer, fn); } @@ -276,14 +263,21 @@ export class ArlasMaplibreService extends ArlasMapService { } }; + + /** + * @overload * Returns the layer object of the given layer id. * @param map Map instance. * @param layer Layer identifier * @returns the layer object. */ - private getLayer(map: ArlasMaplibreGL, layer: string) { - return map.getMapProvider().getLayer(layer); + public getLayer(map: ArlasMaplibreGL, layer: string): TypedStyleLayer { + return map.getMapProvider().getLayer(layer) as TypedStyleLayer; + } + + public getAllLayers(map: ArlasMaplibreGL): TypedStyleLayer[] { + return map.getMapProvider().getStyle().layers as TypedStyleLayer[]; } /** @@ -299,12 +293,12 @@ export class ArlasMaplibreService extends ArlasMapService { if (layer.type === 'fill') { const strokeId = layer.id.replace(ARLAS_ID, FILLSTROKE_LAYER_PREFIX); if (this.hasLayer(map, strokeId)) { - this.setLayerVisibility(strokeId, isVisible, map); + map.getMapProvider().setLayoutProperty(strokeId, 'visibility', isVisible ? 'visible' : 'none'); } } const scrollableId = layer.id.replace(ARLAS_ID, SCROLLABLE_ARLAS_ID); - if (!layer.id.startsWith(SCROLLABLE_ARLAS_ID) && this.hasLayer(map, scrollableId)) { - this.setLayerVisibility(scrollableId, isVisible, map); + if (!!scrollableId && this.hasLayer(map, scrollableId)) { + map.getMapProvider().setLayoutProperty(scrollableId, 'visibility', isVisible ? 'visible' : 'none'); } } } @@ -429,7 +423,6 @@ export class ArlasMaplibreService extends ArlasMapService { tiles: [url], bounds: bounds as [number, number, number, number], maxzoom: maxZoom, - minzoom: minZoom, tileSize: tileSize } } @@ -520,6 +513,21 @@ export class ArlasMaplibreService extends ArlasMapService { map.getMapProvider().setFilter(layerId, filter); } + public queryFeatures(e: any, map: ArlasMaplibreGL, layersIdPattern: string, options: any) { + map.getMapProvider().queryRenderedFeatures(e.point, options).filter(f => !!f.layer && !!f.layer.id && f.layer.id.includes(layersIdPattern)); + }; + + + public isLayerVisible(layer: LayerSpecification): boolean { + return layer.layout.visibility === 'visible'; + } + + public getSource(sourceId: string, map: ArlasMaplibreGL) { + return map.getMapProvider().getSource(sourceId); + } + + + diff --git a/projects/arlas-maplibre/src/lib/basemaps/mapgl-basemap.component.ts b/projects/arlas-maplibre/src/lib/basemaps/mapgl-basemap.component.ts index 985b7b47..6ef98857 100644 --- a/projects/arlas-maplibre/src/lib/basemaps/mapgl-basemap.component.ts +++ b/projects/arlas-maplibre/src/lib/basemaps/mapgl-basemap.component.ts @@ -24,6 +24,7 @@ import { MaplibreBasemapService } from './maplibre-basemap.service'; import { ArlasMapSource } from 'arlas-map'; import { MaplibreSourceType } from '../map/model/sources'; import { ArlasMapFunctionalService } from 'arlas-map'; +import { ArlasMaplibreService } from '../arlas-maplibre.service'; @Component({ selector: 'arlas-maplibre-basemap', @@ -33,7 +34,9 @@ import { ArlasMapFunctionalService } from 'arlas-map'; export class MaplibreBasemapComponent extends BasemapComponent implements OnInit { @Input() public map: ArlasMaplibreGL; @Input() public mapSources: Array>; - public constructor(protected basemapService: MaplibreBasemapService, protected mapFunctionalService: ArlasMapFunctionalService) { - super(basemapService, mapFunctionalService); + public constructor(protected basemapService: MaplibreBasemapService, + protected mapFunctionalService: ArlasMapFunctionalService, + protected mapService: ArlasMaplibreService) { + super(basemapService, mapFunctionalService, mapService); } } diff --git a/projects/arlas-maplibre/src/lib/basemaps/maplibre-basemap.service.ts b/projects/arlas-maplibre/src/lib/basemaps/maplibre-basemap.service.ts index 2d7055e2..38218b1b 100644 --- a/projects/arlas-maplibre/src/lib/basemaps/maplibre-basemap.service.ts +++ b/projects/arlas-maplibre/src/lib/basemaps/maplibre-basemap.service.ts @@ -22,23 +22,26 @@ import * as pmtiles from 'pmtiles'; import { MapLibreBasemapStyle } from './basemap.config'; import { catchError, forkJoin, Observable, of, tap } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import maplibre, { GetResourceResponse, RequestParameters, VectorSourceSpecification } from 'maplibre-gl'; -import { BackgroundLayerSpecification } from '@maplibre/maplibre-gl-style-spec'; -import { BasemapService } from 'arlas-map'; +import maplibre, { AddLayerObject, RequestParameters, TypedStyleLayer } from 'maplibre-gl'; +import { BackgroundLayerSpecification, LayerSpecification } from '@maplibre/maplibre-gl-style-spec'; +import { BasemapService, BasemapStyle } from 'arlas-map'; import { ArlasMaplibreGL } from '../map/ArlasMaplibreGL'; import { ArlasMaplibreService } from '../arlas-maplibre.service'; +import { ArlasMapSource } from 'arlas-map'; +import { MapLogicService } from '../arlas-map-logic.service'; +import { MaplibreSourceType } from '../map/model/sources'; @Injectable({ providedIn: 'root' }) export class MaplibreBasemapService extends BasemapService { - - public constructor(protected http: HttpClient, protected mapService: ArlasMaplibreService) { + public constructor(protected http: HttpClient, protected mapService: ArlasMaplibreService, + private mapLogicService: MapLogicService + ) { super(http, mapService); } - public addProtomapBasemap(map: ArlasMaplibreGL) { const selectedBasemap = this.basemaps.getSelected(); if (selectedBasemap.type === 'protomap') { @@ -120,4 +123,47 @@ export class MaplibreBasemapService extends BasemapService { return of(b.styleFile); } } + + public setBasemap(s: any, newBasemap: BasemapStyle, map: ArlasMaplibreGL, mapSources: Array>) { + const selectedBasemapLayersSet = new Set(); + const layers: Array = this.mapService.getAllLayers(map); + const sources = this.mapService.getAllSources(map); + if (s.layers) { + s.layers.forEach(l => selectedBasemapLayersSet.add(l.id)); + } + const layersToSave = new Array(); + const sourcesToSave = new Array>(); + layers.filter((l: any) => !selectedBasemapLayersSet.has(l.id) && !!l.source).forEach(l => { + layersToSave.push(l as AddLayerObject); + if (sourcesToSave.filter(ms => ms.id === l.source.toString()).length === 0) { + sourcesToSave.push({ id: l.source.toString(), source: sources[l.source.toString()] as MaplibreSourceType }); + } + }); + const sourcesToSaveSet = new Set(); + sourcesToSave.forEach(mapSource => sourcesToSaveSet.add(mapSource.id)); + if (mapSources) { + mapSources.forEach(mapSource => { + if (!sourcesToSaveSet.has(mapSource.id)) { + sourcesToSave.push(mapSource); + } + }); + } + const initStyle = this.getInitStyle(newBasemap); + map.getMapProvider().setStyle(initStyle).once('styledata', () => { + setTimeout(() => { + /** the timeout fixes a mapboxgl bug related to layer placement*/ + this.mapLogicService.declareBasemapSources(sourcesToSave, map); + layersToSave.forEach(l => { + this.mapService.addLayer(map, l); + }); + localStorage.setItem(this.LOCAL_STORAGE_BASEMAPS, JSON.stringify(newBasemap)); + this.basemaps.setSelected(newBasemap); + if (newBasemap.type === 'protomap') { + this.addProtomapBasemap(map); + this.notifyProtomapAddition(); + } + this.basemapChangedSource.next(); + }, 0); + }); + } } diff --git a/projects/arlas-maplibre/src/lib/map/ArlasMaplibreGL.ts b/projects/arlas-maplibre/src/lib/map/ArlasMaplibreGL.ts index 75e52171..9f0aa621 100644 --- a/projects/arlas-maplibre/src/lib/map/ArlasMaplibreGL.ts +++ b/projects/arlas-maplibre/src/lib/map/ArlasMaplibreGL.ts @@ -18,56 +18,48 @@ */ import maplibregl, { - AddLayerObject, - AnimationOptions, - CameraForBoundsOptions, - CanvasSourceSpecification, - CenterZoomBearing, ControlPosition, - EaseToOptions, - FeatureIdentifier, FitBoundsOptions, - FlyToOptions, IControl, LngLat, LngLatBounds, - LngLatLike, MapGeoJSONFeature, MapLayerEventType, MapOptions, Point, PointLike, QueryRenderedFeaturesOptions, - SourceSpecification, - StyleImageInterface, StyleSetterOptions, - StyleSpecification, TypedStyleLayer } from 'maplibre-gl'; -import { AbstractArlasMapGL, BindLayerToEvent, MapConfig, MapEventBinds, OnMoveResult } from 'arlas-map'; +import { AbstractArlasMapGL, BindLayerToEvent, MapConfig, OnMoveResult } from 'arlas-map'; import { MapLayers } from 'arlas-map'; import { MaplibreControlButton, MaplibrePitchToggle } from './model/controls'; import { ControlButton, DrawControlsOption } from 'arlas-map'; -import { VisualisationSetConfig } from 'arlas-map'; import { MapExtent } from 'arlas-map'; -import { ArlasMapSource } from 'arlas-map'; -import { MaplibreSourceType } from './model/sources'; import bbox from '@turf/bbox'; - - export interface ArlasMaplibreConfig extends MapConfig { mapLayers: MapLayers; customEventBind: (map: AbstractArlasMapGL) => BindLayerToEvent[]; - mapLayersEventBind: { - onHover: MapEventBinds[]; - emitOnClick: MapEventBinds[]; - zoomOnClick: MapEventBinds[]; - }; } export class ArlasMaplibreGL extends AbstractArlasMapGL { + public setCenter(lngLat: [number, number]) { + this._mapProvider.setCenter(lngLat); + return this; + } + + public getCanvasContainer() { + return this._mapProvider.getCanvasContainer(); + } + + public resize(eventData?: unknown): this { + this._mapProvider.resize(eventData); + return this; + } + protected _mapLayers: MapLayers; protected _mapProvider: maplibregl.Map; public endlngLat: maplibregl.LngLat; @@ -80,7 +72,6 @@ export class ArlasMaplibreGL extends AbstractArlasMapGL { } protected _initMapProvider(config: ArlasMaplibreConfig) { - console.log('init map provider'); this._mapProvider = new maplibregl.Map( config.mapProviderOptions ); @@ -110,7 +101,16 @@ export class ArlasMaplibreGL extends AbstractArlasMapGL { return [swWorld, neWorld]; } - protected _getMoveEnd() { + + public on(type: string, listener: (ev: any) => void): this { + this.getMapProvider().on(type, listener); + return this; + } + + protected _getMoveEnd(visualisationsSets: { + visualisations: Map>; + status: Map; + }) { const offsetPoint = this.calcOffsetPoint(); const centerOffsetPoint = this.getMapProvider().project(this.getMapProvider().getCenter()).add(offsetPoint); const centerOffSetLatLng = this.getMapProvider().unproject(centerOffsetPoint); @@ -138,9 +138,9 @@ export class ArlasMaplibreGL extends AbstractArlasMapGL { const rawEastOffset = topRghtOffsetLatLng.lng; const rawNorthOffset = topRghtOffsetLatLng.lat; const visibleLayers = new Set(); - this.visualisationsSets.status.forEach((b, vs) => { + visualisationsSets.status.forEach((b, vs) => { if (b) { - this.visualisationsSets.visualisations.get(vs).forEach(l => visibleLayers.add(l)); + visualisationsSets.visualisations.get(vs).forEach(l => visibleLayers.add(l)); } }); const onMoveData: OnMoveResult = { @@ -193,7 +193,6 @@ export class ArlasMaplibreGL extends AbstractArlasMapGL { } protected _initControls(): void { - console.log('init controls', this._controls); if (this._controls) { if (this._controls.mapAttribution) { this.addControl(new maplibregl.AttributionControl(this._controls.mapAttribution.config), this._controls.mapAttribution.position); @@ -238,24 +237,12 @@ export class ArlasMaplibreGL extends AbstractArlasMapGL { ); } - - public bindLayersToMapEvent(map: ArlasMaplibreGL, layers: string[] | Set, binds: MapEventBinds[]) { - layers.forEach(layerId => { - binds.forEach(el => { - this.getMapProvider().on(el.event, layerId, (e) => { - el.fn(e, map); - }); - }); - }); - } - public addControl(control: IControl, position?: ControlPosition, eventOverride?: { event: string; fn: (e?) => void; }); public addControl(control: IControl, position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'): this; public addControl(control: IControl, position?: ControlPosition | 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left', eventOverride?: { event: string; fn: (e?) => void; }) { - console.log(control); this.getMapProvider().addControl(control, position); if (control instanceof ControlButton && eventOverride) { control.btn[eventOverride.event] = () => eventOverride.fn(); @@ -264,24 +251,9 @@ export class ArlasMaplibreGL extends AbstractArlasMapGL { } - - // TODO : should fix any in source - public addVisualisation(visualisation: VisualisationSetConfig, layers: Array, - sources: Array>): void { - sources.forEach((s) => { - if (typeof (s.source) !== 'string') { - this.getMapProvider().addSource(s.id, s.source); - } - }); - this.visualisationSetsConfig.unshift(visualisation); - this.visualisationsSets.visualisations.set(visualisation.name, new Set(visualisation.layers)); - this.visualisationsSets.status.set(visualisation.name, visualisation.enabled); - layers.forEach(layer => { - this.addLayer(layer); - }); - // TODO : should fix any in source - this.setLayersMap(this._mapLayers as MapLayers, layers); - this.reorderLayers(); + public setFilter(layer: string, filter?: boolean | any[], options?: StyleSetterOptions): this { + this._mapProvider.setFilter(layer, filter as any, options); + return this; } public disableDragPan(): void { @@ -416,143 +388,27 @@ export class ArlasMaplibreGL extends AbstractArlasMapGL { this.fitBounds(bounds, paddedOptions); } - public redrawSource(id: string, data) { - if (this.getSource(id) !== undefined) { - (this.getSource(id) as maplibregl.GeoJSONSource).setData({ - 'type': 'FeatureCollection', - 'features': data - }); - } - } - - public resize(eventData?: unknown): this { - this._mapProvider.resize(eventData); - return this; - } - - public setCursorStyle(cursor: string): void { - this.getMapProvider().getCanvas().style.cursor = cursor; - } - - public setLayersMap(mapLayers: MapLayers, layers?: Array) { - if (mapLayers) { - const mapLayersCopy = mapLayers; - if (layers) { - mapLayersCopy.layers = mapLayersCopy.layers.concat(layers); - } - const layersMap = new Map(); - mapLayersCopy.layers.forEach(layer => layersMap.set(layer.id, layer)); - this.layersMap = layersMap; - } - } public setMaxBounds(lnglatbounds?: maplibregl.LngLatBoundsLike): this { this._mapProvider.setMaxBounds(lnglatbounds); return this; } - public setMinZoom(minZoom?: number): this { - this._mapProvider.setMinZoom(minZoom); - return this; - } - public setMaxZoom(maxZoom?: number): this { - this._mapProvider.setMaxZoom(maxZoom); - return this; - } - public project(lngLat: maplibregl.LngLat): unknown { - return this._mapProvider.project(lngLat); - } - public unproject(point: maplibregl.PointLike): unknown { - return this._mapProvider.unproject(point); - } + public queryRenderedFeatures(pointOrBox?: PointLike | [PointLike, PointLike], options?: { layers?: string[]; filter?: any[]; }): MapGeoJSONFeature[] { return this._mapProvider.queryRenderedFeatures(pointOrBox, options as QueryRenderedFeaturesOptions); } - public setFilter(layer: string, filter?: boolean | any[], options?: StyleSetterOptions): this { - this._mapProvider.setFilter(layer, filter as any, options); - return this; - } - public getLight() { - return this._mapProvider.getLight(); - } - public setFeatureState(feature: FeatureIdentifier, state: { [key: string]: any; }): void { - this._mapProvider.setFeatureState(feature, state); - } - public getFeatureState(feature: FeatureIdentifier): { [key: string]: any; } { - return this._mapProvider.getFeatureState(feature); - } - public removeFeatureState(target: FeatureIdentifier, key?: string): void { - this._mapProvider.removeFeatureState(target, key); - } - public getContainer(): HTMLElement { - return this._mapProvider.getContainer(); - } - public getCanvasContainer(): HTMLElement { - return this._mapProvider.getCanvasContainer(); - } - public getCanvas(): HTMLCanvasElement { - return this._mapProvider.getCanvas(); - } - public getCenter(): maplibregl.LngLat { - return this._mapProvider.getCenter(); - } - public setCenter(center: LngLatLike, eventData?: any): this { - this._mapProvider.setCenter(center, eventData); - return this; - } - public panTo(lnglat: LngLatLike, options?: AnimationOptions, eventdata?: any): this { - this._mapProvider.panTo(lnglat, options, eventdata); - return this; - } + public getZoom(): number { return this._mapProvider.getZoom(); } - public setZoom(zoom: number, eventdata?: any): this { - this._mapProvider.setZoom(zoom, eventdata); - return this; - } - public getBearing(): number { - return this._mapProvider.getBearing(); - } - public setBearing(bearing: number, eventData?: any): this { - this._mapProvider.setBearing(bearing, eventData); - return this; - } - public rotateTo(bearing: number, options?: AnimationOptions, eventData?: any): this { - this._mapProvider.rotateTo(bearing, options, eventData); - return this; - } - public setPitch(pitch: number, eventData?: any): this { - this._mapProvider.setPitch(pitch, eventData); - return this; - } - public cameraForBounds(bounds: LngLatBounds, options?: CameraForBoundsOptions): CenterZoomBearing { - return this._mapProvider.cameraForBounds(bounds, options); - } + + public fitBounds(bounds: LngLatBounds, options?: FitBoundsOptions, eventData?: any): this { this._mapProvider.fitBounds(bounds, options, eventData); return this; } - public flyTo(options: FlyToOptions, eventData?: any): this { - this._mapProvider.flyTo(options, eventData); - return this; - } - - public on(type: T, layer: string, listener: (ev: unknown) => void): this; - public on(type: T, listener: (ev: unknown) => void): this; - public on(type: string, listener: (ev: any) => void): this; - public on(type, layer, listener?): this { - this._mapProvider.on(type, layer, listener); - return this; - } - public once(type: T, layer: string, listener: (ev: any) => void): this; - public once(type: T, listener: (ev: any) => void): this; - public once(type: string, listener: (ev: any) => void): this; - public once(type, layer, listener?): this { - this._mapProvider.once(type, layer, listener); - return this; - } } diff --git a/projects/arlas-maplibre/src/public-api.ts b/projects/arlas-maplibre/src/public-api.ts index 1220150f..9f93221d 100644 --- a/projects/arlas-maplibre/src/public-api.ts +++ b/projects/arlas-maplibre/src/public-api.ts @@ -6,4 +6,5 @@ export * from './lib/arlas-maplibre.service'; export * from './lib/arlas-maplibre.module'; export { MaplibreLegendService } from './lib/legend/legend.service'; export { MaplibreBasemapService } from './lib/basemaps/maplibre-basemap.service'; -export { MaplibreVectorStyle } from './lib/map/model/vector-style'; \ No newline at end of file +export { MaplibreVectorStyle } from './lib/map/model/vector-style' +export { MapLogicService } from './lib/arlas-map-logic.service'; \ No newline at end of file