Skip to content

Commit

Permalink
[POC] MapLibre layer
Browse files Browse the repository at this point in the history
replaces all BG layers with a VectorTile equivalent
switch to mercator as default projection to make it happen (not possible yet to mix projection systems, needs some work done on the geoblocks/ol-maplibre-layer library)
  • Loading branch information
pakb committed Aug 26, 2024
1 parent 6e614e4 commit 19e149e
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 71 deletions.
2 changes: 1 addition & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -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://vectortiles.geo.admin.ch/
VITE_APP_SERVICE_PROXY_BASE_URL=https://sys-proxy.dev.bgdi.ch/
5 changes: 5 additions & 0 deletions src/api/layers/GeoAdminLayer.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export default class GeoAdminLayer extends AbstractLayer {
* layer
* @param {String | null} layerData.idIn3d The layer ID to be used as substitute for this layer
* when we are showing the 3D map. Will be using the same layer if this is set to null.
* @param {String | null} layerData.idInVectorTile The layer ID to be used as substitute for
* this layer when we are showing the map with vector tiles. Will be using the same layer if
* this is set to null.
* @param {String} layerData.technicalName The ID/name to use when requesting the WMS/WMTS
* backend, this might be different than id, and many layers (with different id) can in fact
* request the same layer, through the same technical name, in the end)
Expand Down Expand Up @@ -75,6 +78,7 @@ export default class GeoAdminLayer extends AbstractLayer {
type = null,
id = null,
idIn3d = null,
idInVectorTile = null,
technicalName = null,
opacity = 1.0,
visible = true,
Expand Down Expand Up @@ -128,6 +132,7 @@ export default class GeoAdminLayer extends AbstractLayer {
this.isHighlightable = isHighlightable
this.topics = topics
this.idIn3d = idIn3d
this.idInVectorTile = idInVectorTile
this.isSpecificFor3D = id.toLowerCase().endsWith('_3d')
this.searchable = searchable
}
Expand Down
17 changes: 10 additions & 7 deletions src/api/layers/GeoAdminVectorLayer.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,26 @@ import { VECTOR_TILE_BASE_URL } from '@/config'
*/
export default class GeoAdminVectorLayer extends GeoAdminLayer {
/**
* @param {string} layerId The ID of this layer
* @param {LayerAttribution[]} extraAttributions Extra attribution in case this vector layer is
* a mix of many sources
* @param {string} vtLayerConfig.id The ID of this layer
* @param {string} vtLayerConfig.vectorStyleId The ID of the style in the VT backend
* @param {LayerAttribution[]} vtLayerConfig.extraAttributions Extra attribution in case this
* vector layer is a mix of many sources
*/
constructor(layerId, extraAttributions = []) {
constructor(vtLayerConfig = {}) {
const { id, vectorStyleId = null, extraAttributions = [] } = vtLayerConfig
super({
name: layerId,
id,
name: id,
type: LayerTypes.VECTOR,
baseUrl: VECTOR_TILE_BASE_URL,
id: layerId,
technicalName: layerId,
technicalName: vectorStyleId,
attributions: [
...extraAttributions,
new LayerAttribution('swisstopo', 'https://www.swisstopo.admin.ch/en/home.html'),
],
isBackground: true,
hasLegend: false,
})
this.vectorStyleId = vectorStyleId
}
}
5 changes: 5 additions & 0 deletions src/api/layers/GeoAdminWMSLayer.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export default class GeoAdminWMSLayer extends GeoAdminLayer {
* @param {String} layerData.id The unique ID of this layer
* @param {String | null} layerData.idIn3d The layer ID to be used as substitute for this layer
* when we are showing the 3D map. Will be using the same layer if this is set to null.
* @param {String | null} layerData.idInVectorTile The layer ID to be used as substitute for
* this layer when we are showing the map with vector tiles. Will be using the same layer if
* this is set to null.
* @param {String} layerData.technicalName The ID/name to use when requesting the WMS backend,
* this might be different than id, and many layers (with different id) can in fact request
* the same layer, through the same technical name, in the end)
Expand Down Expand Up @@ -69,6 +72,7 @@ export default class GeoAdminWMSLayer extends GeoAdminLayer {
name = null,
id = null,
idIn3d = null,
idInVectorTile = null,
technicalName = null,
opacity = 1.0,
visible = true,
Expand All @@ -91,6 +95,7 @@ export default class GeoAdminWMSLayer extends GeoAdminLayer {
type: LayerTypes.WMS,
id,
idIn3d,
idInVectorTile,
technicalName,
opacity,
visible,
Expand Down
5 changes: 5 additions & 0 deletions src/api/layers/GeoAdminWMTSLayer.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export default class GeoAdminWMTSLayer extends GeoAdminLayer {
* @param {String} layerData.id Unique layer ID
* @param {String | null} layerData.idIn3d The layer ID to be used as substitute for this layer
* when we are showing the 3D map. Will be using the same layer if this is set to null.
* @param {String | null} layerData.idInVectorTile The layer ID to be used as substitute for
* this layer when we are showing the map with vector tiles. Will be using the same layer if
* this is set to null.
* @param {String} layerData.technicalName ID to be used in our backend (can be different from
* the id)
* @param {Number} [layerData.opacity=1.0] Opacity value between 0.0 (transparent) and 1.0
Expand Down Expand Up @@ -62,6 +65,7 @@ export default class GeoAdminWMTSLayer extends GeoAdminLayer {
name = null,
id = null,
idIn3d = null,
idInVectorTile = null,
technicalName = null,
opacity = 1.0,
visible = true,
Expand All @@ -88,6 +92,7 @@ export default class GeoAdminWMTSLayer extends GeoAdminLayer {
type: LayerTypes.WMTS,
id,
idIn3d,
idInVectorTile,
technicalName,
opacity,
visible,
Expand Down
4 changes: 2 additions & 2 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// loading and exporting all values from the .env file as ES6 importable variables

import { LV95 } from '@/utils/coordinates/coordinateSystems'
import { LV95, WEBMERCATOR } from '@/utils/coordinates/coordinateSystems'

/**
* Enum that tells for which (deployment) environment the app has been built.
Expand Down Expand Up @@ -32,7 +32,7 @@ export const APP_VERSION = __APP_VERSION__
*
* @type {CoordinateSystem}
*/
export const DEFAULT_PROJECTION = LV95
export const DEFAULT_PROJECTION = WEBMERCATOR

/**
* Adds a slash at the end of the URL if there is none
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ import { useLayerZIndexCalculation } from '@/modules/map/components/common/z-ind
import OpenLayersInternalLayer from '@/modules/map/components/openlayers/OpenLayersInternalLayer.vue'
const store = useStore()
const layersConfig = computed(() => store.state.layers.config)
const currentBackgroundLayer = computed(() => store.state.layers.currentBackgroundLayer)
const vectorTileCounterpart = computed(() =>
layersConfig.value.find((layer) => layer.id === currentBackgroundLayer.value.idInVectorTile)
)
const { getZIndexForLayer } = useLayerZIndexCalculation()
</script>

<template>
<OpenLayersInternalLayer
v-if="currentBackgroundLayer"
:layer-config="currentBackgroundLayer"
:layer-config="vectorTileCounterpart ?? currentBackgroundLayer"
:z-index="getZIndexForLayer(currentBackgroundLayer)"
/>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import OpenLayersKMLLayer from '@/modules/map/components/openlayers/OpenLayersKM
import OpenLayersVectorLayer from '@/modules/map/components/openlayers/OpenLayersVectorLayer.vue'
import OpenLayersWMSLayer from '@/modules/map/components/openlayers/OpenLayersWMSLayer.vue'
import OpenLayersWMTSLayer from '@/modules/map/components/openlayers/OpenLayersWMTSLayer.vue'
import { WEBMERCATOR } from '@/utils/coordinates/coordinateSystems'
const props = defineProps({
layerConfig: {
Expand All @@ -35,7 +34,6 @@ const props = defineProps({
const { layerConfig, parentLayerOpacity, zIndex } = toRefs(props)
const store = useStore()
const projection = computed(() => store.state.position.projection)
const resolution = computed(() => store.getters.resolution)
function shouldAggregateSubLayerBeVisible(subLayer) {
Expand All @@ -56,7 +54,7 @@ function shouldAggregateSubLayerBeVisible(subLayer) {
(see OpenLayersMap main component)
-->
<OpenLayersVectorLayer
v-if="projection.epsg === WEBMERCATOR.epsg && layerConfig.type === LayerTypes.VECTOR"
v-if="layerConfig.type === LayerTypes.VECTOR"
:vector-layer-config="layerConfig"
:parent-layer-opacity="parentLayerOpacity"
:z-index="zIndex"
Expand Down
58 changes: 9 additions & 49 deletions src/modules/map/components/openlayers/OpenLayersVectorLayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@
*/
import { MapLibreLayer } from '@geoblocks/ol-maplibre-layer'
import axios from 'axios'
import { Source } from 'ol/source'
import { computed, inject, toRefs, watch } from 'vue'
import GeoAdminVectorLayer from '@/api/layers/GeoAdminVectorLayer.class'
import { VECTOR_TILES_IMAGERY_STYLE_ID } from '@/config'
import useAddLayerToMap from '@/modules/map/components/openlayers/utils/useAddLayerToMap.composable'
import log from '@/utils/logging'
const props = defineProps({
vectorLayerConfig: {
Expand All @@ -35,65 +33,27 @@ const props = defineProps({
const { vectorLayerConfig, parentLayerOpacity, zIndex } = toRefs(props)
// extracting useful info from what we've linked so far
const layerId = computed(() => vectorLayerConfig.value.id)
const layerId = computed(() => vectorLayerConfig.value.vectorStyleId)
const opacity = computed(() => parentLayerOpacity.value ?? vectorLayerConfig.value.opacity)
const styleUrl = computed(
() => `${vectorLayerConfig.value.baseUrl}styles/${layerId.value}/style.json`
)
const layer = new MapLibreLayer({
id: layerId.value,
opacity: opacity.value,
mapLibreOptions: {
style: styleUrl.value,
},
source: new Source({
attribution: [vectorLayerConfig.value.attribution],
}),
})
setMapLibreStyle(styleUrl.value)
const olMap = inject('olMap')
useAddLayerToMap(layer, olMap, zIndex)
watch(opacity, (newOpacity) => layer.setOpacity(newOpacity))
watch(styleUrl, (newStyleUrl) => setMapLibreStyle(newStyleUrl))
function setMapLibreStyle(styleUrl) {
if (!layer?.maplibreMap) {
log.error('MapLibre instance is not attached to the layer')
return
}
// most of this methods will be edited while doing https://jira.swisstopo.ch/browse/BGDIINF_SB-2741
if (layerId.value === VECTOR_TILES_IMAGERY_STYLE_ID) {
// special case here, as the imagery is only over Switzerland (for now)
// we inject a fair-use WMTS that covers the globe under our aerial images
axios
.get(styleUrl)
.then((response) => {
const vectorStyle = response.data
// settings SwissImage to use the tiled WMS instead
// otherwise it covers the whole world with white tiles (when no data is present)
vectorStyle.sources.swissimage_wmts.tiles = [
'https://wms.geo.admin.ch/?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&LAYERS=ch.swisstopo.swissimage&LANG=en&WIDTH=256&HEIGHT=256&CRS=EPSG%3A3857&STYLES=&BBOX={bbox-epsg-3857}',
]
// setting up Sentinel2 WMTS to cover the globe outside of Switzerland
vectorStyle.sources['sentinel2_wmts'] = {
minzoom: 0,
maxzoom: 22,
tileSize: 256,
type: 'raster',
tiles: [
'https://tiles.maps.eox.at/wmts?layer=s2cloudless-2020_3857&style=default&tilematrixset=g&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fjpeg&TileMatrix={z}&TileCol={x}&TileRow={y}',
],
}
vectorStyle.layers.splice(1, 0, {
id: 'sentinel2',
source: 'sentinel2_wmts',
type: 'raster',
})
layer.maplibreMap.setStyle(vectorStyle)
})
.catch((err) => {
log.error('Error while fetching MapLibre style', styleUrl, err)
})
} else {
layer.maplibreMap.setStyle(styleUrl)
}
}
watch(styleUrl, (newStyleUrl) => layer.mapLibreMap?.setStyle(newStyleUrl))
</script>
<template>
Expand Down
4 changes: 2 additions & 2 deletions src/store/modules/position.store.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ const actions = {
return
}
if (Array.isArray(center)) {
if (state.projection.epsg != LV95.epsg || LV95.isInBounds(center[0], center[1])) {
if (state.projection.epsg !== LV95.epsg || LV95.isInBounds(center[0], center[1])) {
commit('setCenter', {
x: center[0],
y: center[1],
Expand All @@ -227,7 +227,7 @@ const actions = {
log.warn('center received is out of bounds, ignoring')
}
} else {
if (state.projection.epsg != LV95.epsg || LV95.isInBounds(center.x, center.y)) {
if (state.projection.epsg !== LV95.epsg || LV95.isInBounds(center.x, center.y)) {
const { x, y } = center
commit('setCenter', { x, y, dispatcher })
} else {
Expand Down
31 changes: 31 additions & 0 deletions src/store/plugins/load-layersconfig-on-lang-change.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import GeoAdminVectorLayer from '@/api/layers/GeoAdminVectorLayer.class.js'
import { loadLayersConfigFromBackend } from '@/api/layers/layers.api'
import { loadTopics, parseTopics } from '@/api/topics.api'
import { SET_LANG_MUTATION_KEY } from '@/store/modules/i18n.store'
Expand Down Expand Up @@ -50,6 +51,36 @@ const loadLayersAndTopicsConfigAndDispatchToStore = async (store, lang, topicId,
swissimage.idIn3d = swissimage3d.id
}

// adding all BG counterpart for VectorTiles
const pixelKarteFarbe = layersConfig.find(
(layer) => layer.id === 'ch.swisstopo.pixelkarte-farbe'
)
const pixelKarteGrau = layersConfig.find(
(layer) => layer.id === 'ch.swisstopo.pixelkarte-grau'
)

layersConfig.push(
new GeoAdminVectorLayer({
id: `${pixelKarteFarbe.id}_vt`,
vectorStyleId: 'ch.swisstopo.basemap.vt',
})
)
pixelKarteFarbe.idInVectorTile = `${pixelKarteFarbe.id}_vt`
layersConfig.push(
new GeoAdminVectorLayer({
id: `${pixelKarteGrau.id}_vt`,
vectorStyleId: 'ch.swisstopo.lightbasemap.vt',
})
)
pixelKarteGrau.idInVectorTile = `${pixelKarteGrau.id}_vt`
layersConfig.push(
new GeoAdminVectorLayer({
id: `${swissimage.id}_vt`,
vectorStyleId: 'ch.swisstopo.imagerybasemap.vt',
})
)
swissimage.idInVectorTile = `${swissimage.id}_vt`

store.dispatch('setLayerConfig', { config: layersConfig, dispatcher })
store.dispatch('setTopics', { topics, dispatcher })
log.debug(`layers config and topics dispatched`)
Expand Down
2 changes: 1 addition & 1 deletion src/utils/coordinates/StandardCoordinateSystem.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ export default class StandardCoordinateSystem extends CoordinateSystem {
}

getDefaultZoom() {
return STANDARD_ZOOM_LEVEL_1_25000_MAP
return 8
}
}
10 changes: 5 additions & 5 deletions src/utils/coordinates/WebMercatorCoordinateSystem.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ export default class WebMercatorCoordinateSystem extends StandardCoordinateSyste
'WebMercator',
// matrix comes from https://epsg.io/3857.proj4
'+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs',
// bounds are coming from https://epsg.io/3857
// bounds are coming from https://github.com/geoadmin/lib-gatilegrid/blob/58d6e574b69d32740a24edbc086d97897d4b41dc/gatilegrid/tilegrids.py#L122-L125
new CoordinateSystemBounds(
-20037508.34,
20037508.34,
-20048966.1,
20048966.1,
-20037508.342789244,
20037508.342789244,
-20037508.342789244,
20037508.342789244,
// center of LV95's extent transformed with epsg.io website
[917209.87, 5914737.43]
)
Expand Down

0 comments on commit 19e149e

Please sign in to comment.