-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathMapLibreLayer.ts
More file actions
123 lines (105 loc) · 3.6 KB
/
MapLibreLayer.ts
File metadata and controls
123 lines (105 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import {Map as MapLibreMap} from 'maplibre-gl';
import type {MapOptions, QueryRenderedFeaturesOptions} from 'maplibre-gl';
import type {Map} from 'ol';
import Layer from 'ol/layer/Layer.js';
import type {Options as LayerOptions} from 'ol/layer/Layer.js';
import type {EventsKey} from 'ol/events.js';
import BaseEvent from 'ol/events/Event.js';
import {unByKey} from 'ol/Observable.js';
import {Source} from 'ol/source.js';
import MapLibreLayerRenderer from './MapLibreLayerRenderer.js';
import getMapLibreAttributions from './getMapLibreAttributions.js';
export type MapLibreOptions = Omit<MapOptions, 'container'>;
/**
* Receives the zoom level from the OpenLayers view
* and can transform it for MapLibre in case the projection
* system used by OL isn't using a standard Mercator pyramid.
*
* This enables the MapLibreLayer to work in Mercator zooms even if OL
* is using a localized projection (such as LV95 for Switzerland)
*/
export type MapLibreLayerTranslateZoomFunction = (zoom: number) => number;
export type MapLibreLayerOptions = LayerOptions & {
mapLibreOptions: MapLibreOptions;
queryRenderedFeaturesOptions?: QueryRenderedFeaturesOptions;
translateZoom?: MapLibreLayerTranslateZoomFunction
};
export default class MapLibreLayer extends Layer {
mapLibreMap?: MapLibreMap;
loaded: boolean = false;
private olListenersKeys: EventsKey[] = [];
constructor(options: MapLibreLayerOptions) {
super({
source: new Source({
attributions: () => {
return getMapLibreAttributions(this.mapLibreMap);
},
}),
...options,
});
}
override disposeInternal() {
unByKey(this.olListenersKeys);
this.loaded = false;
if (this.mapLibreMap) {
// Some asynchronous repaints are triggered even if the MapLibreMap has been removed,
// to avoid display of errors we set an empty function.
this.mapLibreMap.triggerRepaint = () => {};
this.mapLibreMap.remove();
}
super.disposeInternal();
}
override setMapInternal(map: Map) {
super.setMapInternal(map);
if (map) {
this.loadMapLibreMap();
} else {
// TODO: I'm not sure if it's the right call
this.dispose();
}
}
private loadMapLibreMap() {
this.loaded = false;
const map = this.getMapInternal();
if (map) {
this.olListenersKeys.push(
map.on('change:target', this.loadMapLibreMap.bind(this)),
);
}
if (!map?.getTargetElement()) {
return;
}
if (!this.getVisible()) {
// On next change of visibility we load the map
this.olListenersKeys.push(
this.once('change:visible', this.loadMapLibreMap.bind(this)),
);
return;
}
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.width = '100%';
container.style.height = '100%';
const mapLibreOptions = this.get('mapLibreOptions') as MapLibreOptions;
this.mapLibreMap = new MapLibreMap(
Object.assign({}, mapLibreOptions, {
container: container,
attributionControl: false,
interactive: false,
trackResize: false,
}),
);
this.mapLibreMap.on('sourcedata', () => {
this.getSource()?.refresh(); // Refresh attribution
});
this.mapLibreMap.once('load', () => {
this.loaded = true;
this.getRenderer().ready = true
this.dispatchEvent(new BaseEvent('load'));
});
}
override createRenderer(): MapLibreLayerRenderer {
const translateZoom = this.get('translateZoom') as MapLibreLayerTranslateZoomFunction | undefined;
return new MapLibreLayerRenderer(this, translateZoom);
}
}