Skip to content

Commit 2f58bf5

Browse files
committed
Update completeness project to support overlay tile server
1 parent 2babb09 commit 2f58bf5

File tree

5 files changed

+372
-137
lines changed

5 files changed

+372
-137
lines changed

src/components/BaseMap.vue

Lines changed: 148 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
<script setup lang="ts" generic="GeoJsonProperties extends GeoJSON.GeoJsonProperties">
22
import 'maplibre-gl/dist/maplibre-gl.css';
33
import { 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';
55
import getBbox from '@turf/bbox';
66
import { 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
812
const BOUNDS_SOURCE_NAME = 'bounds-geojson-source';
913
const BOUNDS_LINE_LAYER_NAME = 'bounds-geojson-line-layer';
1014
const 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+
1221
type 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
2537
const props = defineProps<Props>();
@@ -32,37 +44,52 @@ const emit = defineEmits<{
3244
const mapContainer = shallowRef<HTMLDivElement | null>(null);
3345
const map = shallowRef<Map | null>(null);
3446
const 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
3654
const 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+
4073
const style = computed(() => ({
4174
width: `${props.mapWidth}px`,
42-
height: `${props.mapHeight}px`
75+
height: `${props.mapHeight}px`,
4376
}));
4477
4578
watchEffect(() => {
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+
6693
function clearLastHoveredFeatureState() {
6794
if (isDefined(map.value) && isDefined(lastHoveredBoundFeatureId.value)) {
6895
map.value.removeFeatureState(
@@ -76,43 +103,27 @@ function clearLastHoveredFeatureState() {
76103
}
77104
}
78105
106+
79107
onMounted(() => {
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+
218315
watchEffect((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
326415
onUnmounted(() => {
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

Comments
 (0)