From 962bdeb31387a99622c7b382c497c53678a666da Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Sun, 3 Nov 2024 13:43:16 +0800 Subject: [PATCH] feat: improve updateCamera() * add support for 'duration' and 'bounds' * return promise for camera animation --- src/BasicLeafletRenderer.mjs | 28 ++++++++++++++++++++++----- src/BasicMaplibreRenderer.mjs | 23 +++++++++++++++------- src/BasicOpenlayersRenderer.mjs | 34 +++++++++++++++++---------------- 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/src/BasicLeafletRenderer.mjs b/src/BasicLeafletRenderer.mjs index d77c07e..5616601 100644 --- a/src/BasicLeafletRenderer.mjs +++ b/src/BasicLeafletRenderer.mjs @@ -169,13 +169,31 @@ const Renderer = class extends defaultExport { } } - updateCamera (options, animation) { - const latLon = L.latLng(options.center[1], options.center[0]) - if (animation) { - this.map.flyTo(latLon, options.zoom) + async updateCamera ({ center, zoom, bounds, animation, padding, duration }) { + const latLon = center ? L.latLng(center[1], center[0]) : this.map.getCenter() + const options = { + animate: animation ?? false, + padding: [padding, padding], + duration: (duration ?? 250) / 1000, + } + + if (bounds) { + const [[w, s], [e, n]] = bounds + const latLngBounds = new this.L.LatLngBounds([[s, w], [n, e]]) + if (!latLngBounds.isValid()) { + throw new Error('Bounds are not valid.') + } + const target = this.map._getBoundsCenterZoom(latLngBounds, options) + this.map.flyTo(target.center, target.zoom, options) + } else if (animation) { + this.map.flyTo(latLon, zoom ?? this.map.getZoom(), options) } else { - this.map.setView(latLon, options.zoom) + this.map.setView(latLon, zoom) } + + return new Promise(resolve => { + setTimeout(resolve, duration ?? 0) + }) } project ([lng, lat]) { diff --git a/src/BasicMaplibreRenderer.mjs b/src/BasicMaplibreRenderer.mjs index a0a12ab..fb91f82 100644 --- a/src/BasicMaplibreRenderer.mjs +++ b/src/BasicMaplibreRenderer.mjs @@ -5,7 +5,7 @@ import { renderByScriptTargetWith, } from './mapclay.mjs' /* eslint-disable-next-line no-unused-vars */ -import * as M from 'maplibre-gl' +import maplibregl from 'maplibre-gl' import { addProtocols } from 'maplibre-gl-vector-text-protocol' import { TerraDrawMapLibreGLAdapter } from 'terra-draw' loadCSS('https://unpkg.com/maplibre-gl@4.5.2/dist/maplibre-gl.css') @@ -190,16 +190,25 @@ const Renderer = class extends defaultExport { } } - updateCamera (options, useAnimation) { - if (useAnimation) { + async updateCamera ({ bounds, center, zoom, animation, ...others }, useAnimation) { + if (bounds) { + this.map.fitBounds(bounds, { linear: true, ...others }) + } else if (animation || useAnimation) { this.map.flyTo({ - center: options.center, - zoom: options.zoom, + center: center ?? this.map.getCenter(), + zoom: zoom ?? this.map.getZoom(), + ...others, }) } else { - this.map.setCenter(options.center) - this.map.setZoom(options.zoom) + this.map.setCenter(center) + this.map.setZoom(zoom) } + + return new Promise(resolve => { + this.map.on('zoomend', () => { + resolve('zoomend') + }) + }) } project ([lng, lat]) { diff --git a/src/BasicOpenlayersRenderer.mjs b/src/BasicOpenlayersRenderer.mjs index f4b10c8..a063c94 100644 --- a/src/BasicOpenlayersRenderer.mjs +++ b/src/BasicOpenlayersRenderer.mjs @@ -184,20 +184,20 @@ const Renderer = class extends defaultExport { return element } - async addTileData ({ map, data }) { + async addTileData ({ map, data, ol }) { const tileData = data.filter(record => record.type === 'tile') const styleDatum = tileData.filter(datum => datum.type === 'style')[0] if (!styleDatum && tileData.length === 0) { const baseLayer = new layer.Tile({ - source: new source.OSM(), + source: new ol.source.OSM(), title: 'OSM Carto', }) map.addLayer(baseLayer) } else { tileData.forEach(datum => { const tileLayer = new layer.Tile({ - source: new source.XYZ({ url: datum.url }), + source: new ol.source.XYZ({ url: datum.url }), title: datum.title ? datum.title : 'Anonymous', }) map.addLayer(tileLayer) @@ -245,23 +245,26 @@ const Renderer = class extends defaultExport { // } } - updateCamera (options, useAnimation) { + async updateCamera ({ center: lonLat, zoom, bounds, duration, padding }) { const map = this.map const view = map.getView() - const xy = this.ol.proj.fromLonLat(options.center, this.crs) - if (useAnimation) { - flyTo( - map, - { center: xy, zoom: options.zoom }, - () => null, - ) + const center = lonLat ? this.ol.proj.fromLonLat(lonLat, this.crs) : view.getCenter() + + if (bounds) { + const boundsTransformed = bounds + .map(lonLat => this.ol.proj.fromLonLat(lonLat, this.crs)) + view.fit(boundsTransformed.flat(), { duration, padding: Array(4).fill(padding) }) } else { view.animate({ - center: options.center, - zoom: options.zoom, - duration: 300, + center, + zoom: zoom ?? view.getZoom(), + duration, }) } + + return new Promise(resolve => { + setTimeout(resolve, duration ?? 0) + }) } project = ([x, y]) => @@ -280,8 +283,7 @@ const Renderer = class extends defaultExport { } // Pan map to a specific location -function flyTo (map, status, done) { - const duration = 2500 +function flyTo (map, status, done, { duration = 2500 } = {}) { const view = map.getView() const nextZoom = status.zoom ? status.zoom : view.getZoom() const nextCenter = status.center ? status.center : view.center