11<script setup lang="ts" generic =" GeoJsonProperties extends GeoJSON .GeoJsonProperties " >
22import ' maplibre-gl/dist/maplibre-gl.css' ;
33import { Map } from ' maplibre-gl' ;
4- import { shallowRef , onMounted , onUnmounted , markRaw , watchEffect , onBeforeUnmount , computed } from ' vue' ;
4+ import { shallowRef , onMounted , onUnmounted , markRaw , watchEffect , onBeforeUnmount , computed , watch } from ' vue' ;
55import getBbox from ' @turf/bbox' ;
66import { isDefined , isNotDefined , listToMap } from ' @togglecorp/fujs' ;
7+ import type { OverlayTileServer , TileServer } from ' @/utils/types' ;
8+
9+ const BASE_SOURCE_NAME = ' base-source' ;
10+ const BASE_RASTER_LAYER_NAME = ' base-raster-layer' ;
711
812const BOUNDS_SOURCE_NAME = ' bounds-geojson-source' ;
913const BOUNDS_LINE_LAYER_NAME = ' bounds-geojson-line-layer' ;
1014const BOUNDS_FILL_LAYER_NAME = ' bounds-geojson-fill-layer' ;
1115
16+ const OVERLAY_SOURCE_NAME = ' overlay-source' ;
17+ const OVERLAY_RASTER_LAYER_NAME = ' overlay-raster-layer' ;
18+ const OVERLAY_LINE_LAYER_NAME = ' overlay-line-layer' ;
19+ const OVERLAY_FILL_LAYER_NAME = ' overlay-fill-layer' ;
20+
1221type Props = {
1322 geoJson: GeoJSON .GeoJSON <GeoJSON .Geometry , GeoJsonProperties >;
23+ tileSize: number ;
1424 mapWidth: number ;
1525 mapHeight: number ;
1626 mapState: {
@@ -20,6 +30,8 @@ type Props = {
2030 value: any ,
2131 }[]
2232 }[];
33+ tileServer: TileServer ;
34+ overlayTileServer: OverlayTileServer ;
2335}
2436
2537const props = defineProps <Props >();
@@ -32,37 +44,52 @@ const emit = defineEmits<{
3244const mapContainer = shallowRef <HTMLDivElement | null >(null );
3345const map = shallowRef <Map | null >(null );
3446const lastHoveredBoundFeatureId = shallowRef <number | string >();
47+ const updateBoundsRef = shallowRef ();
48+
49+ function getUpdatedUrl(url : string ) {
50+ // NOTE: maplibre uses `quadkey`
51+ return url .replace (' quad_key' , ' quadkey' );
52+ }
3553
3654const bounds = computed (() => (
37- getBbox (props .geoJson ) as [number , number , number , number ]
55+ getBbox (props .geoJson ) as [number , number , number , number ]
3856));
3957
58+ function updateBounds() {
59+ window .clearTimeout (updateBoundsRef .value );
60+ updateBoundsRef .value = window .setTimeout (() => {
61+ if (isDefined (map .value )) {
62+ map .value .fitBounds (
63+ bounds .value ,
64+ {
65+ padding: 0 ,
66+ duration: 200 ,
67+ },
68+ );
69+ }
70+ }, 200 );
71+ }
72+
4073const style = computed (() => ({
4174 width: ` ${props .mapWidth }px ` ,
42- height: ` ${props .mapHeight }px `
75+ height: ` ${props .mapHeight }px ` ,
4376}));
4477
4578watchEffect (() => {
46- if (isDefined (map .value ) && isDefined (props .mapHeight ) && isDefined (props .mapWidth )) {
47- const tileSize = Math .round (props .mapHeight / 3 );
48-
49- const baseTileSource = map .value .getSource (' base-tile-source' );
79+ if (isDefined (map .value )) {
80+ const baseTileSource = map .value .getSource (BASE_SOURCE_NAME );
5081 if (isDefined (baseTileSource )) {
51- baseTileSource .tileSize = tileSize ;
82+ baseTileSource .tileSize = props .tileSize ;
83+ updateBounds ();
5284 }
53-
54- /*
55- map.value.fitBounds(
56- bounds.value,
57- {
58- duration: 0,
59- padding: 0,
60- },
61- );
62- */
6385 }
6486});
6587
88+ watch (style , () => {
89+ updateBounds ();
90+ });
91+
92+
6693function clearLastHoveredFeatureState() {
6794 if (isDefined (map .value ) && isDefined (lastHoveredBoundFeatureId .value )) {
6895 map .value .removeFeatureState (
@@ -76,43 +103,27 @@ function clearLastHoveredFeatureState() {
76103 }
77104}
78105
106+
79107onMounted (() => {
80108 if (mapContainer .value ) {
81109 const mapValue = markRaw (new Map ({
82110 container: mapContainer .value ,
83111 style: {
84112 version: 8 ,
85113 sources: {
86- ' base-tile-source ' : {
114+ [ BASE_SOURCE_NAME ] : {
87115 type: ' raster' ,
88- tiles: [' https://ecn.t0.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=7505 ' ],
116+ tiles: [getUpdatedUrl ( props . tileServer . url ) ],
89117 tileSize: 256 ,
90- attribution: ' © 2019 Microsoft Corporation, Earthstar Geographics SIO' ,
91- maxzoom: 19 ,
92- },
93- ' overlay-tile-source' : {
94- type: ' vector' ,
95- tiles: [' https://vector.osm.org/shortbread_v1/{z}/{x}/{y}.mvt' ],
96- attribution: ' Map data from OpenStreetMap' ,
97- minzoom: 0 ,
98- maxzoom: 14 ,
118+ attribution: props .tileServer .credits ,
119+ // maxzoom: 19,
99120 },
100121 },
101122 layers: [
102123 {
103- id: ' base-tile-layer ' ,
124+ id: BASE_RASTER_LAYER_NAME ,
104125 type: ' raster' ,
105- source: ' base-tile-source' ,
106- },
107- {
108- id: ' overlay-tile-layer' ,
109- type: ' line' ,
110- source: ' overlay-tile-source' ,
111- ' source-layer' : ' buildings' ,
112- paint: {
113- ' line-color' : ' #ffff00' ,
114- ' line-width' : 2 ,
115- },
126+ source: BASE_SOURCE_NAME ,
116127 },
117128 ],
118129 },
@@ -215,6 +226,92 @@ onBeforeUnmount(() => {
215226 }
216227});
217228
229+ function removeLayerSafe(layerName : string ) {
230+ if (isDefined (map .value )) {
231+ if (map .value .getLayer (layerName )) {
232+ map .value .removeLayer (layerName );
233+ }
234+ }
235+ }
236+
237+ function removeSourceSafe(sourceName : string ) {
238+ if (isDefined (map .value )) {
239+ if (map .value .getSource (sourceName )) {
240+ map .value .removeSource (sourceName );
241+ }
242+ }
243+ }
244+
245+ watchEffect ((onCleanup ) => {
246+ if (isDefined (map .value )) {
247+ const {
248+ overlayTileServer,
249+ } = props ;
250+
251+ if (overlayTileServer .type === ' raster' ) {
252+ const { raster } = overlayTileServer ;
253+
254+ map .value .addSource (
255+ OVERLAY_SOURCE_NAME ,
256+ {
257+ type: ' raster' ,
258+ tiles: [getUpdatedUrl (raster .url )],
259+ attribution: raster .credits ,
260+ },
261+ );
262+
263+ map .value .addLayer ({
264+ id: OVERLAY_RASTER_LAYER_NAME ,
265+ type: ' raster' ,
266+ source: OVERLAY_SOURCE_NAME
267+ });
268+ } else {
269+ const { vector } = overlayTileServer ;
270+ map .value .addSource (
271+ OVERLAY_SOURCE_NAME ,
272+ {
273+ type: ' vector' ,
274+ tiles: [vector .tileServer .url ],
275+ attribution: vector .tileServer .credits ,
276+ minzoom: vector .tileServer .maxZoom ,
277+ maxzoom: vector .tileServer .maxZoom ,
278+ },
279+ );
280+
281+ map .value .addLayer ({
282+ id: OVERLAY_LINE_LAYER_NAME ,
283+ type: ' line' ,
284+ source: OVERLAY_SOURCE_NAME ,
285+ ' source-layer' : vector .tileServer .sourceLayer ,
286+ paint: {
287+ ' line-color' : vector .lineColor ,
288+ ' line-width' : vector .lineWidth ,
289+ ' line-opacity' : vector .lineOpacity ,
290+ ' line-dasharray' : vector .lineDasharray ,
291+ },
292+ });
293+
294+ map .value .addLayer ({
295+ id: OVERLAY_FILL_LAYER_NAME ,
296+ type: ' fill' ,
297+ source: OVERLAY_SOURCE_NAME ,
298+ ' source-layer' : vector .tileServer .sourceLayer ,
299+ paint: {
300+ ' fill-color' : vector .fillColor ,
301+ ' fill-opacity' : vector .fillOpacity ,
302+ },
303+ });
304+ }
305+ }
306+
307+ onCleanup (() => {
308+ removeLayerSafe (OVERLAY_RASTER_LAYER_NAME );
309+ removeLayerSafe (OVERLAY_LINE_LAYER_NAME );
310+ removeLayerSafe (OVERLAY_FILL_LAYER_NAME );
311+ removeSourceSafe (OVERLAY_SOURCE_NAME );
312+ });
313+ });
314+
218315watchEffect ((onCleanup ) => {
219316 if (isDefined (map .value )) {
220317 map .value .addSource (BOUNDS_SOURCE_NAME , {
@@ -291,17 +388,9 @@ watchEffect((onCleanup) => {
291388 }
292389
293390 onCleanup (() => {
294- if (isDefined (map .value )) {
295- if (map .value .getLayer (BOUNDS_LINE_LAYER_NAME )) {
296- map .value .removeLayer (BOUNDS_LINE_LAYER_NAME );
297- }
298- if (map .value .getLayer (BOUNDS_FILL_LAYER_NAME )) {
299- map .value .removeLayer (BOUNDS_FILL_LAYER_NAME );
300- }
301- if (map .value .getSource (BOUNDS_SOURCE_NAME )) {
302- map .value .removeSource (BOUNDS_SOURCE_NAME );
303- }
304- }
391+ removeLayerSafe (BOUNDS_LINE_LAYER_NAME );
392+ removeLayerSafe (BOUNDS_FILL_LAYER_NAME );
393+ removeSourceSafe (BOUNDS_SOURCE_NAME );
305394 });
306395});
307396
@@ -324,37 +413,24 @@ watchEffect(() => {
324413});
325414
326415onUnmounted (() => {
416+ window .clearTimeout (updateBoundsRef .value );
327417 map .value ?.remove ();
328418});
329419 </script >
330420
331421<template >
332- <div
333- class =" map-wrapper"
334- :style =" style"
335- >
336- <a href =" https://www.maptiler.com" class =" watermark" >
337- <img src =" https://api.maptiler.com/resources/logo.svg" alt =" MapTiler logo" />
338- </a >
339- <div class =" map" ref =" mapContainer" />
422+ <div class =" map-wrapper" >
423+ <div
424+ :style =" style"
425+ ref =" mapContainer"
426+ />
340427 </div >
341428</template >
342429
343430<style scoped>
344431.map-wrapper {
345432 position : relative ;
433+ background-color : gray ;
346434 isolation : isolate ;
347435}
348-
349- .map {
350- width : 100% ;
351- height : 100% ;
352- }
353-
354- .watermark {
355- position : absolute ;
356- left : 10px ;
357- bottom : 10px ;
358- z-index : 1 ;
359- }
360436 </style >
0 commit comments