diff --git a/examples/demo.js b/examples/demo.js index b4b7ed2..9541cdb 100644 --- a/examples/demo.js +++ b/examples/demo.js @@ -33,6 +33,10 @@ window.map = new Map({ }), }); +window.map.on('rendercomplete', () => { + console.log('rendercomplete'); +}); + window.map.on('singleclick', (evt) => { const features = window.map.getFeaturesAtPixel(evt.pixel); features.forEach((feature) => { diff --git a/package-lock.json b/package-lock.json index fdc2a12..261b0d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -664,7 +664,6 @@ "integrity": "sha512-b9ll4jaFYfXSv6NZAOJ2P0uuyT/Doel7ho2AHLSUz2thtcL6HEb2+qdV2f9wriVvbEoPAj9VuSOgNc0t0f5iMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.1", "@parcel/cache": "2.16.3", @@ -2681,7 +2680,6 @@ "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", @@ -2893,7 +2891,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3048,7 +3045,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3347,7 +3343,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -5114,7 +5109,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5684,7 +5678,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5865,3 +5858,4 @@ } } } + diff --git a/src/MapLibreLayerRenderer.ts b/src/MapLibreLayerRenderer.ts index 9175e4d..761fc4e 100644 --- a/src/MapLibreLayerRenderer.ts +++ b/src/MapLibreLayerRenderer.ts @@ -1,4 +1,4 @@ -import type {MapGeoJSONFeature} from 'maplibre-gl'; +import type {Map, MapGeoJSONFeature} from 'maplibre-gl'; import type {QueryRenderedFeaturesOptions} from 'maplibre-gl'; import type {FrameState} from 'ol/Map.js'; import {toDegrees} from 'ol/math.js'; @@ -12,7 +12,7 @@ import type {Geometry} from 'ol/geom.js'; import {SimpleGeometry} from 'ol/geom.js'; import type {Pixel} from 'ol/pixel.js'; import type MapLibreLayer from './MapLibreLayer.js'; -import type { MapLibreLayerTranslateZoomFunction } from './MapLibreLayer.js' +import type {MapLibreLayerTranslateZoomFunction} from './MapLibreLayer.js'; const VECTOR_TILE_FEATURE_PROPERTY = 'vectorTileFeature'; @@ -24,16 +24,34 @@ const formats: { }), }; + +function sameSize(map: Map, frameState: FrameState): boolean { + return ( + map.transform.width === Math.floor(frameState.size[0]) && + map.transform.height === Math.floor(frameState.size[1]) + ); +} + + /** * This class is a renderer for MapLibre Layer to be able to use the native ol * functionalities like map.getFeaturesAtPixel or map.hasFeatureAtPixel. */ export default class MapLibreLayerRenderer extends LayerRenderer { - private readonly translateZoom: MapLibreLayerTranslateZoomFunction | undefined - - constructor(layer: MapLibreLayer, translateZoom: MapLibreLayerTranslateZoomFunction | undefined) { - super(layer) - this.translateZoom = translateZoom + readonly translateZoom: + | MapLibreLayerTranslateZoomFunction + | undefined; + + ignoreNextRender: boolean; + + constructor( + layer: MapLibreLayer, + translateZoom: MapLibreLayerTranslateZoomFunction | undefined + ) { + super(layer); + this.translateZoom = translateZoom; + this.setIsReady = this.setIsReady.bind(this); + this.ignoreNextRender = false; } getFeaturesAtCoordinate( @@ -70,17 +88,31 @@ export default class MapLibreLayerRenderer extends LayerRenderer const layer = this.getLayer(); const {mapLibreMap} = layer; const map = layer.getMapInternal(); + if (!layer || !map || !mapLibreMap) { return null; } + mapLibreMap.off('idle', this.setIsReady); + + + // When the browser is zoomed it could happens that the renderFrame call for readyness + // in setIsReady is called with a different size than the one of the mapLibreMap, + // so we need to render. + if (this.ready && this.ignoreNextRender && sameSize(mapLibreMap, frameState)) { + this.ignoreNextRender = false; + return mapLibreMap.getContainer(); + } + + this.ready = false; + this.ignoreNextRender = false; const mapLibreCanvas = mapLibreMap.getCanvas(); const {viewState} = frameState; // adjust view parameters in MapLibre mapLibreMap.jumpTo({ center: toLonLat(viewState.center, viewState.projection) as [number, number], - zoom: (this.translateZoom ? this.translateZoom(viewState.zoom) : viewState.zoom) - 1 , + zoom: (this.translateZoom ? this.translateZoom(viewState.zoom) : viewState.zoom) - 1, bearing: toDegrees(-viewState.rotation), }); @@ -89,14 +121,17 @@ export default class MapLibreLayerRenderer extends LayerRenderer mapLibreCanvas.style.opacity = opacity; } + if (!mapLibreCanvas.isConnected) { // The canvas is not connected to the DOM, request a map rendering at the next animation frame // to set the canvas size. map.render(); - } else if (!sameSize(mapLibreCanvas, frameState)) { + } else if (!sameSize(mapLibreMap, frameState)) { mapLibreMap.resize(); } + mapLibreMap.once('idle', this.setIsReady); + mapLibreMap.redraw(); return mapLibreMap.getContainer(); @@ -113,7 +148,7 @@ export default class MapLibreLayerRenderer extends LayerRenderer coordinate: Coordinate, _frameState: FrameState, hitTolerance: number, - callback: FeatureCallback, + callback: FeatureCallback ): Feature | undefined { const features = this.getFeaturesAtCoordinate(coordinate, hitTolerance); let result; @@ -182,11 +217,12 @@ export default class MapLibreLayerRenderer extends LayerRenderer } return olFeature; } -} -function sameSize(canvas: HTMLCanvasElement, frameState: FrameState): boolean { - return ( - canvas.width === Math.floor(frameState.size[0] * frameState.pixelRatio) && - canvas.height === Math.floor(frameState.size[1] * frameState.pixelRatio) - ); + setIsReady() { + if (!this.ready) { + this.ready = true; + this.ignoreNextRender = true; + this.getLayer().changed(); + } + } } diff --git a/src/getMapLibreAttributions.ts b/src/getMapLibreAttributions.ts index c96b197..c3bfc12 100644 --- a/src/getMapLibreAttributions.ts +++ b/src/getMapLibreAttributions.ts @@ -13,10 +13,11 @@ const getMapLibreAttributions = (map: MapLibreMap | undefined): string[] => { if (!style) { return []; } - const {sourceCaches} = style; + // @ts-expect-error - sourceCaches exists in maplibre-gl < 5.11.0 and tileManagers in maplibre-gl >= 5.11.0 + const {sourceCaches, tileManagers} = style; let copyrights: string[] = []; - - Object.values(sourceCaches).forEach( + const caches: Record Source}> = tileManagers || sourceCaches || {}; + Object.values(caches).forEach( (value: {used: boolean; getSource: () => Source}) => { if (value.used) { const {attribution} = value.getSource();