From 230a8e7cc5db3b68ce7f34af5d37b08741e148e1 Mon Sep 17 00:00:00 2001 From: Pascal Barth Date: Mon, 10 Jun 2024 17:05:26 +0200 Subject: [PATCH] WIP maplibre component --- .../src/proj/CoordinateSystem.ts | 2 +- packages/mapviewer/.env.development | 2 +- packages/mapviewer/.env.integration | 2 +- .../mapviewer/src/api/layers/layers.api.js | 17 ++- packages/mapviewer/src/config/map.config.js | 4 +- .../src/config/vectortiles.config.js | 4 +- .../mapviewer/src/modules/map/MapModule.vue | 8 ++ .../maplibre/MaplibreGeoJSONLayer.vue | 49 +++++++ .../maplibre/MaplibreInternalLayer.vue | 76 ++++++++++ .../map/components/maplibre/MaplibreMap.vue | 135 ++++++++++++++++++ .../components/maplibre/MaplibreWMSLayer.vue | 73 ++++++++++ .../components/maplibre/MaplibreWMTSLayer.vue | 52 +++++++ .../utils/addLayerToMaplibre-mixins.js | 20 +++ ...formGeoadminGeoJSONStyleIntoMapboxStyle.js | 22 +++ .../utils/useAddRasterLayer.composable.js | 96 +++++++++++++ .../map/components/toolbox/GeolocButton.vue | 1 - .../map/components/toolbox/Toggle3dButton.vue | 1 - .../map/components/toolbox/ZoomButtons.vue | 1 - .../menu/components/debug/DebugToolbar.vue | 27 +++- .../modules/menu/components/menu/MenuTray.vue | 3 +- packages/mapviewer/src/setup-fontawesome.js | 2 + packages/mapviewer/src/store/debug.store.js | 5 + .../src/store/plugins/app-readiness.plugin.js | 1 + packages/mapviewer/src/views/MapView.vue | 7 +- 24 files changed, 594 insertions(+), 16 deletions(-) create mode 100644 packages/mapviewer/src/modules/map/components/maplibre/MaplibreGeoJSONLayer.vue create mode 100644 packages/mapviewer/src/modules/map/components/maplibre/MaplibreInternalLayer.vue create mode 100644 packages/mapviewer/src/modules/map/components/maplibre/MaplibreMap.vue create mode 100644 packages/mapviewer/src/modules/map/components/maplibre/MaplibreWMSLayer.vue create mode 100644 packages/mapviewer/src/modules/map/components/maplibre/MaplibreWMTSLayer.vue create mode 100644 packages/mapviewer/src/modules/map/components/maplibre/utils/addLayerToMaplibre-mixins.js create mode 100644 packages/mapviewer/src/modules/map/components/maplibre/utils/transformGeoadminGeoJSONStyleIntoMapboxStyle.js create mode 100644 packages/mapviewer/src/modules/map/components/maplibre/utils/useAddRasterLayer.composable.js diff --git a/packages/geoadmin-coordinates/src/proj/CoordinateSystem.ts b/packages/geoadmin-coordinates/src/proj/CoordinateSystem.ts index 890b0c9957..8728833cbc 100644 --- a/packages/geoadmin-coordinates/src/proj/CoordinateSystem.ts +++ b/packages/geoadmin-coordinates/src/proj/CoordinateSystem.ts @@ -13,7 +13,7 @@ import CoordinateSystemBounds from '@/proj/CoordinateSystemBounds' * could lead to initialization errors (even when initializing the constants before importing the * class). Thus we declare them here, at the root class of the coordinates systems. */ -export const STANDARD_ZOOM_LEVEL_1_25000_MAP: number = 15.5 +export const STANDARD_ZOOM_LEVEL_1_25000_MAP: number = 8 export const SWISS_ZOOM_LEVEL_1_25000_MAP: number = 8 /** diff --git a/packages/mapviewer/.env.development b/packages/mapviewer/.env.development index 7837c9089e..e7d8e9ea04 100644 --- a/packages/mapviewer/.env.development +++ b/packages/mapviewer/.env.development @@ -9,5 +9,5 @@ VITE_API_SERVICES_BASE_URL=https://sys-map.dev.bgdi.ch/api/ VITE_API_SERVICE_KML_BASE_URL=https://sys-public.dev.bgdi.ch/ VITE_APP_API_SERVICE_SHORTLINK_BASE_URL=https://sys-s.dev.bgdi.ch/ VITE_APP_3D_TILES_BASE_URL=https://sys-3d.dev.bgdi.ch/ -VITE_APP_VECTORTILES_BASE_URL=https://sys-verctortiles.dev.bgdi.ch/ +VITE_APP_VECTORTILES_BASE_URL=https://sys-vectortiles.dev.bgdi.ch/ VITE_APP_SERVICE_PROXY_BASE_URL=https://sys-proxy.dev.bgdi.ch/ diff --git a/packages/mapviewer/.env.integration b/packages/mapviewer/.env.integration index 7fe354eb6b..e76b6455b5 100644 --- a/packages/mapviewer/.env.integration +++ b/packages/mapviewer/.env.integration @@ -8,5 +8,5 @@ VITE_API_SERVICES_BASE_URL=https://sys-map.int.bgdi.ch/api/ VITE_API_SERVICE_KML_BASE_URL=https://sys-public.int.bgdi.ch/ VITE_APP_API_SERVICE_SHORTLINK_BASE_URL=https://sys-s.int.bgdi.ch/ VITE_APP_3D_TILES_BASE_URL=https://sys-3d.int.bgdi.ch/ -VITE_APP_VECTORTILES_BASE_URL=https://sys-verctortiles.int.bgdi.ch/ +VITE_APP_VECTORTILES_BASE_URL=https://sys-vectortiles.int.bgdi.ch/ VITE_APP_SERVICE_PROXY_BASE_URL=https://sys-proxy.int.bgdi.ch/ diff --git a/packages/mapviewer/src/api/layers/layers.api.js b/packages/mapviewer/src/api/layers/layers.api.js index e55e4db814..9dc219a9e1 100644 --- a/packages/mapviewer/src/api/layers/layers.api.js +++ b/packages/mapviewer/src/api/layers/layers.api.js @@ -10,8 +10,9 @@ import GeoAdminWMSLayer from '@/api/layers/GeoAdminWMSLayer.class' import GeoAdminWMTSLayer from '@/api/layers/GeoAdminWMTSLayer.class' import LayerTimeConfig from '@/api/layers/LayerTimeConfig.class' import LayerTimeConfigEntry from '@/api/layers/LayerTimeConfigEntry.class' -import { getApi3BaseUrl } from '@/config/baseUrl.config' +import { getApi3BaseUrl, getVectorTilesBaseUrl } from '@/config/baseUrl.config' import { DEFAULT_GEOADMIN_MAX_WMTS_RESOLUTION } from '@/config/map.config' +import { VECTOR_LIGHT_BASE_MAP_STYLE_ID } from '@/config/vectortiles.config.js' // API file that covers the backend endpoint http://api3.geo.admin.ch/rest/services/all/MapServer/layersConfig @@ -260,3 +261,17 @@ export const loadLayersConfigFromBackend = (lang) => { } }) } + +export function loadVectorTileStyle() { + return new Promise((resolve, reject) => { + axios + .get(`${getVectorTilesBaseUrl()}styles/${VECTOR_LIGHT_BASE_MAP_STYLE_ID}/style.json`) + .then((response) => { + resolve(response.data) + }) + .catch((err) => { + log.error('Unable to load vector tile style', err) + reject(err) + }) + }) +} diff --git a/packages/mapviewer/src/config/map.config.js b/packages/mapviewer/src/config/map.config.js index ef56810e97..7d01121db4 100644 --- a/packages/mapviewer/src/config/map.config.js +++ b/packages/mapviewer/src/config/map.config.js @@ -1,11 +1,11 @@ -import { LV95 } from '@geoadmin/coordinates' +import { WEBMERCATOR } from '@geoadmin/coordinates' /** * Default projection to be used throughout the application * * @type {CoordinateSystem} */ -export const DEFAULT_PROJECTION = LV95 +export const DEFAULT_PROJECTION = WEBMERCATOR /** * Default tile size to use when requesting WMS tiles with our internal WMSs (512px) diff --git a/packages/mapviewer/src/config/vectortiles.config.js b/packages/mapviewer/src/config/vectortiles.config.js index 311743fdd4..7142518080 100644 --- a/packages/mapviewer/src/config/vectortiles.config.js +++ b/packages/mapviewer/src/config/vectortiles.config.js @@ -5,7 +5,7 @@ * * @type {string} */ -export const VECTOR_LIGHT_BASE_MAP_STYLE_ID = 'ch.swisstopo.leichte-basiskarte_world.vt' +export const VECTOR_LIGHT_BASE_MAP_STYLE_ID = 'ch.swisstopo.basemap.vt' /** * Imagery base map style ID @@ -14,4 +14,4 @@ export const VECTOR_LIGHT_BASE_MAP_STYLE_ID = 'ch.swisstopo.leichte-basiskarte_w * * @type {string} */ -export const VECTOR_TILES_IMAGERY_STYLE_ID = 'ch.swisstopo.leichte-basiskarte-imagery_world.vt' +export const VECTOR_TILES_IMAGERY_STYLE_ID = 'ch.swisstopo.imagerybasemap.vt' diff --git a/packages/mapviewer/src/modules/map/MapModule.vue b/packages/mapviewer/src/modules/map/MapModule.vue index 96424e2630..22c8cee7be 100644 --- a/packages/mapviewer/src/modules/map/MapModule.vue +++ b/packages/mapviewer/src/modules/map/MapModule.vue @@ -5,7 +5,9 @@ import { useStore } from 'vuex' import CompareSlider from './components/CompareSlider.vue' import LocationPopup from './components/LocationPopup.vue' import WarningRibbon from './components/WarningRibbon.vue' + const CesiumMap = defineAsyncComponent(() => import('./components/cesium/CesiumMap.vue')) +const MapLibreMap = defineAsyncComponent(() => import('./components/maplibre/MaplibreMap.vue')) const OpenLayersMap = defineAsyncComponent( () => import('./components/openlayers/OpenLayersMap.vue') ) @@ -13,6 +15,7 @@ const OpenLayersMap = defineAsyncComponent( const store = useStore() const is3DActive = computed(() => store.state.cesium.active) +const showMapLibre = computed(() => store.state.debug.showMapLibre) const displayLocationPopup = computed( () => store.state.map.locationPopupCoordinates && !store.state.ui.embed @@ -33,6 +36,11 @@ const isCompareSliderActive = computed(() => { + + + + + diff --git a/packages/mapviewer/src/modules/map/components/maplibre/MaplibreGeoJSONLayer.vue b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreGeoJSONLayer.vue new file mode 100644 index 0000000000..57aac1dd93 --- /dev/null +++ b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreGeoJSONLayer.vue @@ -0,0 +1,49 @@ + + \ No newline at end of file diff --git a/packages/mapviewer/src/modules/map/components/maplibre/MaplibreInternalLayer.vue b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreInternalLayer.vue new file mode 100644 index 0000000000..f93f0edfd3 --- /dev/null +++ b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreInternalLayer.vue @@ -0,0 +1,76 @@ + + + diff --git a/packages/mapviewer/src/modules/map/components/maplibre/MaplibreMap.vue b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreMap.vue new file mode 100644 index 0000000000..5ec4ade0d6 --- /dev/null +++ b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreMap.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/packages/mapviewer/src/modules/map/components/maplibre/MaplibreWMSLayer.vue b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreWMSLayer.vue new file mode 100644 index 0000000000..1eeb752f3e --- /dev/null +++ b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreWMSLayer.vue @@ -0,0 +1,73 @@ + + + diff --git a/packages/mapviewer/src/modules/map/components/maplibre/MaplibreWMTSLayer.vue b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreWMTSLayer.vue new file mode 100644 index 0000000000..0b299f4b6d --- /dev/null +++ b/packages/mapviewer/src/modules/map/components/maplibre/MaplibreWMTSLayer.vue @@ -0,0 +1,52 @@ + + + diff --git a/packages/mapviewer/src/modules/map/components/maplibre/utils/addLayerToMaplibre-mixins.js b/packages/mapviewer/src/modules/map/components/maplibre/utils/addLayerToMaplibre-mixins.js new file mode 100644 index 0000000000..e7de283908 --- /dev/null +++ b/packages/mapviewer/src/modules/map/components/maplibre/utils/addLayerToMaplibre-mixins.js @@ -0,0 +1,20 @@ +import { randomIntBetween } from '@geoadmin/numbers' + +const Z_INDEX_FOR_LABELS_IN_STYLE = 121 + +const addLayerToMaplibreMixin = { + inject: ['getMapLibreMap'], + data() { + return { + layerStyle: null, + layerSource: null, + sourceId: `source-${this.layerId ? this.layerId : randomIntBetween(0, 100000)}`, + } + }, + computed: { + zIndexForThisLayer: function () { + return Z_INDEX_FOR_LABELS_IN_STYLE + (this.zIndex === -1 ? 0 : this.zIndex) + }, + }, +} +export default addLayerToMaplibreMixin diff --git a/packages/mapviewer/src/modules/map/components/maplibre/utils/transformGeoadminGeoJSONStyleIntoMapboxStyle.js b/packages/mapviewer/src/modules/map/components/maplibre/utils/transformGeoadminGeoJSONStyleIntoMapboxStyle.js new file mode 100644 index 0000000000..9efa5c8381 --- /dev/null +++ b/packages/mapviewer/src/modules/map/components/maplibre/utils/transformGeoadminGeoJSONStyleIntoMapboxStyle.js @@ -0,0 +1,22 @@ + +export default function transformGeoadminGeoJSONStyleIntoMapboxStyle(geoadminStyle) { + if (geoadminStyle.values) { + let mapboxStyle = { + "circle-radius": 8, + "circle-color": ["match", ["get", geoadminStyle.property]], + "circle-stroke-color": "#FFFFFF", + "circle-stroke-width": 1 + }; + + geoadminStyle.values.forEach(({value, vectorOptions}) => { + mapboxStyle["circle-color"].push(value, vectorOptions.fill.color); + }); + // default value + mapboxStyle["circle-color"].push("#eee"); + return mapboxStyle; + } else if (geoadminStyle.ranges) { + return geoadminStyle.ranges; + } else { + return geoadminStyle; + } +} diff --git a/packages/mapviewer/src/modules/map/components/maplibre/utils/useAddRasterLayer.composable.js b/packages/mapviewer/src/modules/map/components/maplibre/utils/useAddRasterLayer.composable.js new file mode 100644 index 0000000000..c1faad2ce2 --- /dev/null +++ b/packages/mapviewer/src/modules/map/components/maplibre/utils/useAddRasterLayer.composable.js @@ -0,0 +1,96 @@ +import log from '@geoadmin/log' +import { inject, onBeforeUnmount, onMounted, toValue, watch } from 'vue' + +export default function useAddTileLayer(layer, sourceId, source, opacity, previousLayerId) { + const getMapLibreMap = inject('getMapLibreMap', () => undefined, true) + + addLayerAndSourceToMap(toValue(layer), toValue(sourceId), toValue(source)) + + onMounted(() => { + addLayerAndSourceToMap(toValue(layer), toValue(sourceId), toValue(source)) + }) + + onBeforeUnmount(() => { + removeLayerAndSourceFromMap(toValue(layer)) + }) + + watch( + () => toValue(previousLayerId), + () => { + removeLayerFromMap(toValue(layer)) + addSourceToMap(toValue(sourceId), toValue(source)) + addLayerToMap(toValue(layer), toValue(previousLayerId)) + } + ) + watch( + () => toValue(source), + () => { + removeLayerAndSourceFromMap(toValue(layer)) + addLayerAndSourceToMap(toValue(layer), toValue(sourceId), toValue(source)) + }, + { + deep: true, + } + ) + watch(opacity, () => { + const mapLibreMap = getMapLibreMap() + if (mapLibreMap?.getLayer(toValue(layer).id)) { + mapLibreMap.setPaintProperty(toValue(layer).id, 'raster-opacity', opacity.value) + } + }) + + function addSourceToMap(sourceId, source) { + const mapLibreMap = getMapLibreMap() + if (!mapLibreMap || !sourceId || !source || !!mapLibreMap.getSource(sourceId)) { + return false + } + mapLibreMap.addSource(sourceId, source) + return true + } + + function addLayerToMap(layer, previousLayerId) { + const mapLibreMap = getMapLibreMap() + if (!mapLibreMap || !layer) { + return + } + if (toValue(previousLayerId)) { + mapLibreMap.addLayer(layer, toValue(previousLayerId)) + } else { + const mapLibreLayers = mapLibreMap.getStyle().layers + // Find the index of the first symbol layer in the map style + const firstSymbolLayer = mapLibreLayers.find((layer) => layer.type === 'symbol') + mapLibreMap.addLayer(layer, firstSymbolLayer?.id) + } + mapLibreMap.setPaintProperty(layer.id, 'raster-opacity', opacity.value) + } + + function addLayerAndSourceToMap(layer, sourceId, source) { + if (addSourceToMap(sourceId, source)) { + addLayerToMap(layer, previousLayerId) + } else { + log.error('Could not add source to map', sourceId, source) + } + } + + function removeSourceFromMap() { + const mapLibreMap = getMapLibreMap() + if (toValue(sourceId) && mapLibreMap.getSource(toValue(sourceId))) { + mapLibreMap.removeSource(toValue(sourceId)) + } + } + + function removeLayerFromMap(layer) { + const mapLibreMap = getMapLibreMap() + if (!mapLibreMap || !layer) { + return + } + if (mapLibreMap.getLayer(layer.id)) { + mapLibreMap.removeLayer(layer.id) + } + } + + function removeLayerAndSourceFromMap(layer) { + removeLayerFromMap(layer) + removeSourceFromMap() + } +} diff --git a/packages/mapviewer/src/modules/map/components/toolbox/GeolocButton.vue b/packages/mapviewer/src/modules/map/components/toolbox/GeolocButton.vue index 3d93df7777..109c1e9547 100644 --- a/packages/mapviewer/src/modules/map/components/toolbox/GeolocButton.vue +++ b/packages/mapviewer/src/modules/map/components/toolbox/GeolocButton.vue @@ -70,7 +70,6 @@ function toggleGeolocation() {