Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ declare module 'vue' {
AreaMenu: typeof import('./src/components/AreaMenu.vue')['default']
LayerLegend: typeof import('./src/components/LayerLegend.vue')['default']
LayerList: typeof import('./src/components/LayerList.vue')['default']
MapboxHighlight: typeof import('./src/components/MapboxHighlight.vue')['default']
MapComponent: typeof import('./src/components/MapComponent.vue')['default']
MapLayer: typeof import('./src/components/MapLayer.vue')['default']
MapZoomControl: typeof import('./src/components/MapZoomControl.vue')['default']
NavigationDrawer: typeof import('./src/components/NavigationDrawer.vue')['default']
NumberInput: typeof import('./src/components/NumberInput.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
Expand Down
44 changes: 43 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@fontsource/roboto": "5.2.7",
"@mdi/font": "7.4.47",
"@studiometa/vue-mapbox-gl": "^2.7.2",
"@turf/bbox": "^7.3.4",
"axios": "^1.13.5",
"pinia": "^3.0.3",
"query-string": "^9.3.1",
Expand Down
7 changes: 7 additions & 0 deletions src/components/MapComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
:layer="layer"
@click="onFeatureClick"
/>
<MapZoomControl :feature="mapStore.activeRegion?.feature" />
<MapboxNavigationControl position="bottom-right" />
</mapbox-map>
</div>
Expand All @@ -23,6 +24,8 @@
import { MapboxMap, MapboxNavigationControl } from '@studiometa/vue-mapbox-gl'
import { MAP_CENTER, MAP_ZOOM, MAP_BASELAYERS, MAP_BASELAYER_DEFAULT } from '@/lib/constant'
import { useMapStore } from '@/stores/map'
import MapLayer from '@/components/MapLayer.vue'
import MapZoomControl from '@/components/MapZoomControl.vue'
import { computed, ref } from 'vue'
const mapStore = useMapStore()
const accessToken = import.meta.env.VITE_MAPBOX_TOKEN
Expand All @@ -36,6 +39,10 @@
}

function onFeatureClick (feature) {
if (feature == null) {
mapStore.clearActiveRegion()
return
}
if (feature?.layer?.id) {
mapStore.setActiveRegion(feature.layer.id, feature)
}
Expand Down
140 changes: 137 additions & 3 deletions src/components/MapLayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<script setup>
import { MapboxLayer, useMap } from '@studiometa/vue-mapbox-gl'
import { computed, unref } from 'vue'
import { computed, ref, unref, onMounted, onUnmounted, nextTick } from 'vue'
import { useMapStore } from '@/stores/map'

const props = defineProps({
Expand All @@ -26,12 +26,139 @@
const { map } = useMap()
const mapStore = useMapStore()

// Highlight & hover logic below is intended for vector WMTS tile sources
// built via `build-wmts-layer`, where features support Mapbox feature-state.
// It deliberately uses the feature's actual `source` / `sourceLayer` from
// Mapbox click events instead of using the layer id of the config, so it remains
// correct even if source IDs or layer is not passed correctly.

const isClickable = computed(() => mapStore.isLayerClickable(props.layer.id))
const layerId = computed(() => props.layer?.id)
const sourceLayer = computed(() => props.layer?.['source-layer'])

const selectedId = ref(null)
const selectedSource = ref(null)
const selectedSourceLayer = ref(null)

const hoveredId = ref(null)
const hoveredSource = ref(null)
const hoveredSourceLayer = ref(null)

function setHighlight(mapInstance, source, sourceLayerName, id, selected) {
if (!mapInstance || source == null || sourceLayerName == null || id == null) return
mapInstance.setFeatureState(
{ source, sourceLayer: sourceLayerName, id },
{ selected }
)
}

function setHover(mapInstance, source, sourceLayerName, id, hover) {
if (!mapInstance || source == null || sourceLayerName == null || id == null) return
mapInstance.setFeatureState(
{ source, sourceLayer: sourceLayerName, id },
{ hover }
)
}

function onLayerClicked(e) {
if (!isClickable.value) return

const feature = e.features?.[0]
if (!feature) return

const mapInstance = unref(map)
const source = feature.source
const sourceLayerName = feature.sourceLayer ?? sourceLayer.value

if (source != null && sourceLayerName != null) {
const clickedId = feature.id
if (clickedId == null) {
console.warn('No feature id found — check promoteId', feature.properties)
return
}

if (!mapInstance) return

if (selectedId.value !== null && selectedSource.value != null && selectedSourceLayer.value != null) {
setHighlight(mapInstance, selectedSource.value, selectedSourceLayer.value, selectedId.value, false)
}

if (selectedId.value === clickedId && selectedSource.value === source) {
selectedId.value = null
selectedSource.value = null
selectedSourceLayer.value = null
emit('click', null)
return
}

selectedId.value = clickedId
selectedSource.value = source
selectedSourceLayer.value = sourceLayerName
setHighlight(mapInstance, source, sourceLayerName, clickedId, true)
}

emit('click', feature)
}

function onMapClick(e) {
if (!isClickable.value || selectedId.value === null) return
if (selectedSource.value == null || selectedSourceLayer.value == null) return

const mapInstance = unref(map)
if (!mapInstance) return

const features = mapInstance.queryRenderedFeatures(e.point, {
layers: [layerId.value],
})
if (!features.length) {
setHighlight(mapInstance, selectedSource.value, selectedSourceLayer.value, selectedId.value, false)
selectedId.value = null
selectedSource.value = null
selectedSourceLayer.value = null
emit('click', null)
}
}

onMounted(() => {
const mapInstance = unref(map)
if (!mapInstance) return
if (isClickable.value) {
emit('click', e.features[0])
mapInstance.on('click', onMapClick)
nextTick(() => {
if (layerId.value && mapInstance.getLayer(layerId.value)) {
mapInstance.on('mousemove', layerId.value, onMousemove)
}
})
}
})

onUnmounted(() => {
const mapInstance = unref(map)
if (mapInstance) {
mapInstance.off('click', onMapClick)
if (layerId.value) mapInstance.off('mousemove', layerId.value, onMousemove)
}
})

function onMousemove(e) {
if (!isClickable.value) return

const mapInstance = unref(map)
const feature = e.features?.[0]
if (!mapInstance || !feature || feature.id == null) return

const source = feature.source
const sourceLayerName = feature.sourceLayer ?? sourceLayer.value
if (source == null || sourceLayerName == null) return

if (hoveredId.value !== null && hoveredSource.value != null && hoveredSourceLayer.value != null) {
setHover(mapInstance, hoveredSource.value, hoveredSourceLayer.value, hoveredId.value, false)
}

hoveredId.value = feature.id
hoveredSource.value = source
hoveredSourceLayer.value = sourceLayerName
setHover(mapInstance, source, sourceLayerName, feature.id, true)
}

function onMouseenter() {
Expand All @@ -41,6 +168,13 @@
}

function onMouseleave() {
unref(map).getCanvas().style.cursor = ''
const mapInstance = unref(map)
if (isClickable.value && hoveredId.value !== null && hoveredSource.value != null && hoveredSourceLayer.value != null) {
setHover(mapInstance, hoveredSource.value, hoveredSourceLayer.value, hoveredId.value, false)
hoveredId.value = null
hoveredSource.value = null
hoveredSourceLayer.value = null
}
if (mapInstance) mapInstance.getCanvas().style.cursor = ''
}
</script>
43 changes: 43 additions & 0 deletions src/components/MapZoomControl.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<div />
</template>

<script setup>
import { useMap } from '@studiometa/vue-mapbox-gl'
import { watch, unref } from 'vue'
import bbox from '@turf/bbox'

const props = defineProps({
feature: {
type: Object,
default: null,
},
padding: {
type: Number,
default: 100,
},
maxZoom: {
type: Number,
default: 12,
},
})

const { map } = useMap()

function zoomToFeature(feature) {
const mapInstance = unref(map)
if (!mapInstance || !feature?.geometry) return

const [west, south, east, north] = bbox(feature)
mapInstance.fitBounds(
[[west, south], [east, north]],
{ padding: props.padding, maxZoom: props.maxZoom },
)
}

watch(() => props.feature, (feature) => {
if (feature) {
zoomToFeature(feature)
}
})
</script>
3 changes: 0 additions & 3 deletions src/components/SubMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,6 @@
const context = { payload, stores }

const inputs = resolveInputs(props.wps.inputs, context)
console.log("inputs", inputs)
console.log("baseUrl", baseUrl)
console.log("identifier", identifier)
const result = await sendWpsRequest({
baseUrl,
identifier,
Expand Down
25 changes: 23 additions & 2 deletions src/data/base-layers-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,30 @@
"layer": "region:nuts_2021_level_3",
"url": "https://desirmed.openearth.eu/geoserver/gwc/service/wmts?",
"format": "application/vnd.mapbox-vector-tile",
"promoteId": "nuts_id",
"paint": {
"fill-color": "transparent",
"fill-opacity": 1
"fill-color": [
"case",
["boolean", ["feature-state", "selected"], false],
"#ffcc00",
[
"case",
["boolean", ["feature-state", "hover"], false],
"#ffeb3b",
"transparent"
]
],
"fill-opacity": [
"case",
["boolean", ["feature-state", "selected"], false],
1,
[
"case",
["boolean", ["feature-state", "hover"], false],
0.9,
0.7
]
]
},
"vectorType": "fill",
"minZoom": 0
Expand Down
2 changes: 2 additions & 0 deletions src/lib/build-wmts-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ function buildWmtsLayer ({
bbox = [],
format,
vectorType,
promoteId,
minZoom,
maxZoom,
}) {
Expand Down Expand Up @@ -40,6 +41,7 @@ function buildWmtsLayer ({
type: 'vector',
tiles: [ tile ],
...(bbox && Array.isArray(bbox) && bbox.length > 0 && { bounds: bbox }),
...(promoteId && { promoteId: { [layer.split(':')[1]]: promoteId } }),
},
'source-layer': layer.split(':')[1],
paint,
Expand Down
Loading