From 2e37831ad32955d47e988622d68ade05195e8f73 Mon Sep 17 00:00:00 2001 From: Bazottie Date: Fri, 6 Dec 2024 17:11:34 +0100 Subject: [PATCH 1/3] Adds minimal split logic for loading vector tiles from a WMTS --- package-lock.json | 2 +- src/App.vue | 11 +- src/components/MapComponents/MapLayerWmts.js | 157 ++++++++++ src/lib/get-capabilities.js | 68 +++- src/lib/wmts-layer.js | 98 ++++++ src/store/modules/map/index.js | 307 ++++++++++++------- src/views/Layers.vue | 69 +++-- 7 files changed, 560 insertions(+), 152 deletions(-) create mode 100644 src/components/MapComponents/MapLayerWmts.js create mode 100644 src/lib/wmts-layer.js diff --git a/package-lock.json b/package-lock.json index 6e78c867..834c4588 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,7 +74,6 @@ "stylelint": "^16.6.1", "stylelint-config-property-sort-order-smacss": "^10.0.0", "stylelint-config-standard": "^36.0.1", - "ts-node": "^10.9.2", "vue-cli-plugin-vuetify": "^2.4.3", "vue-template-compiler": "^2.6.11", "vuetify-loader": "^1.9.2" @@ -5470,6 +5469,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/@vue/composition-api/-/composition-api-1.7.2.tgz", "integrity": "sha512-M8jm9J/laYrYT02665HkZ5l2fWTK4dcVg3BsDHm/pfz+MjDYwX+9FUaZyGwEyXEDonQYRCo0H7aLgdklcIELjw==", + "license": "MIT", "peerDependencies": { "vue": ">= 2.5 < 2.7" } diff --git a/src/App.vue b/src/App.vue index be7098b7..71af09b6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -92,6 +92,13 @@ :opacity="layer.opacity" /> + @@ -117,6 +124,7 @@ import { mapActions, mapGetters } from 'vuex' import AppShell from './components/AppShell/AppShell' import MapLayer from './components/MapComponents/MapLayer.js' + import MapLayerWmts from './components/MapComponents/MapLayerWmts.js' import MapZoom from './components/MapComponents/MapZoom.js' import MapLayerInfo from './components/MapComponents/MapLayerInfo' import MapboxDrawControl from '~/components/MapboxDrawControl/MapboxDrawControl' @@ -147,6 +155,7 @@ components: { AppShell, MapLayer, + MapLayerWmts, MapZoom, MapLayerInfo, MapboxDrawControl, @@ -189,7 +198,7 @@ computed: { ...mapGetters('app', [ 'viewerName', 'appNavigationOpen', 'appNavigationWidth', 'viewerUserAgreement', 'viewerPrivacyStatement', 'acknowledgments' ]), - ...mapGetters('map', [ 'drawnFeatures', 'drawMode', 'wmsLayerIds', 'wmsLayers', 'filteredLayerId', 'mapCenter', 'mapZoom', 'zoomExtent', 'selectedLayerForSelection', 'activeFlattenedLayers', 'wmsApiLayer', 'multipleSelection' ]), + ...mapGetters('map', [ 'drawnFeatures', 'drawMode', 'wmsLayerIds', 'wmsLayers','wmtsLayers', 'wmtsSources', 'filteredLayerId', 'mapCenter', 'mapZoom', 'zoomExtent', 'selectedLayerForSelection', 'activeFlattenedLayers', 'wmsApiLayer', 'multipleSelection' ]), ...mapGetters('data', [ 'timeExtent', 'flattenedLayers', 'displayLayers' ]), formattedTimeExtent() { return this.formatTimeExtent(this.timeExtent) diff --git a/src/components/MapComponents/MapLayerWmts.js b/src/components/MapComponents/MapLayerWmts.js new file mode 100644 index 00000000..efd33009 --- /dev/null +++ b/src/components/MapComponents/MapLayerWmts.js @@ -0,0 +1,157 @@ +import { isNil } from "~/lib/helpers.js"; + +export default { + name: "v-mapbox-layer", + + inject: [ "getMap" ], + + render: () => null, + + props: { + options: { + type: Object, + default: () => ({}), + }, + + // Allows to place a layer before another + before: { + type: String, + default: undefined, + }, + + clickable: { + type: Boolean, + default: false, + }, + + opacity: { + type: Number, + required: false, + validator: (val) => val >= 0 && val <= 1, + }, + }, + + data: () => ({ + isInitialized: false, + }), + + methods: { + deferredMountedTo() { + if (!this.isInitialized) { + this.renderLayer(); + this.isInitialized = true; + } + }, + + renderLayer() { + this.removeLayer(); + this.addLayer(); + }, + + addLayer() { + const map = this.getMap(); + if (this.before && map.getLayer(this.before)) { + map.addLayer(this.options.layer, this.before) + } else if (map.getLayer('gl-draw-polygon-fill-inactive.cold')) { + map.addLayer(this.options.layer, 'gl-draw-polygon-fill-inactive.cold') + } else { + map.addLayer(this.options.layer) + } + + if (this.clickable) { + const layerId = this.options.layer.id; + map.on("click", layerId, this.clickFn); + map.on("mouseenter", layerId, this.mouseEnterFn); + map.on("mouseleave", layerId, this.mouseLeaveFn); + } + + + if (!isNil(this.opacity)) { + this.setOpacity(); + } + }, + + removeSource() { + const map = this.getMap(); + if (map) { + const sourceKey = this.options.source.key; + const source = map.getSource(sourceKey); + if (source) { + map.removeSource(sourceKey); + } + } + }, + + removeLayer() { + const map = this.getMap(); + if (map) { + const layerId = this.options.layer.id; + const layer = map.getLayer(layerId); + if (layer) { + map.removeLayer(layerId); + if (this.clickable) { + map.off("click", layerId, this.clickFn); + map.off("mouseenter", layerId, this.mouseEnterFn); + map.off("mouseleave", layerId, this.mouseLeaveFn); + } + } + } + }, + + clickFn(e) { + this.$emit("click", e); + }, + + mouseEnterFn() { + const map = this.getMap(); + map.getCanvas().style.cursor = "pointer"; + }, + + mouseLeaveFn() { + const map = this.getMap(); + map.getCanvas().style.cursor = ""; + }, + + setOpacity() { + const map = this.getMap(); + const { id, type } = this.options.layer; + map.setPaintProperty(id, `${ type }-opacity`, this.opacity); + }, + }, + + mounted() { + const map = this.getMap(); + // We can immediately initialize if we have the map ready + + if (map) { + map.addSource(this.options.source.key, this.options.source); + this.renderLayer(); + this.isInitialized = true; + } + }, + + destroyed() { + this.removeLayer(); + this.removeSource(); + }, + + watch: { + options: { + deep: true, + handler() { + this.renderLayer(); + }, + }, + + opacity() { + this.setOpacity(); + }, + before(onTopLayer) { + const map = this.getMap(); + map.moveLayer(this.options.layer.id, onTopLayer); + if (!onTopLayer) { + map.moveLayer(this.options.layer.id, "gl-draw-polygon-fill-inactive.cold"); + } + }, + }, +}; diff --git a/src/lib/get-capabilities.js b/src/lib/get-capabilities.js index f897f822..6a3c0b6a 100644 --- a/src/lib/get-capabilities.js +++ b/src/lib/get-capabilities.js @@ -84,6 +84,15 @@ export async function getWmsCapabilities(service) { } +export async function getWmtsCapabilities(service) { + const servicePath = `${ service.origin }${ service.pathname }`; + const { data } = await axios( + `${ servicePath }?service=WMTS&request=GetCapabilities` + ); + return new DOMParser().parseFromString(data, "text/xml"); +} + + export function getSupportedOutputFormats(type, capabilities) { //wfs @@ -137,19 +146,19 @@ export function isRasterLayer(type, capabilities, layer) { return keywords.includes('GeoTIFF') } -export function getLayerProperties(capabilities, layer) { /** * function that reads the wms capabilities response of the workpspace * 1. find the given layer - * 2. extracts: - * -wmsVersion - * -bbox of layer - * -keywords (that contain the service type) - * -service type of layer (wfs or wcs) - * -time extent of layer - * - * * */ - + * 2. extracts: + * - wmsVersion + * - bbox of layer + * - keywords (that contain the service type) + * - service type of layer (wfs or wcs) + * - time extent of layer + * @param {Object} capabilities - XML response of capabilities + * @param {string} layer - layer name + */ +export function getLayerProperties(capabilities, layer) { const wmsVersion = pipe( () => capabilities.querySelector('WMS_Capabilities'), el => el.getAttribute('version'), @@ -201,4 +210,43 @@ export function getLayerProperties(capabilities, layer) { return { serviceType, timeExtent, wmsVersion, bbox } } +/** + * function that reads the wms capabilities response of the workpspace + * 1. find the given layer + * 2. extracts: + * - wmsVersion + * - bbox of layer + * - keywords (that contain the service type) + * - service type of layer (wfs or wcs) + * - time extent of layer + * @param {Object} capabilities - XML response of capabilities + * @param {('WMS'|'WMTS')} type - type of service + * @param {Object} layerObject - layer name + */ +export function getWmtsLayerProperties(capabilities, layerObject) { + const version = pipe( + () => capabilities.querySelector(`Capabilities`), + (el) => el.getAttribute("version") + )(); + const workspaceLayer = layerObject.layer.split(":") + const layer = workspaceLayer.pop(); + const workspace = workspaceLayer.pop(); + const keywords = pipe( + () => [ + capabilities.querySelector("KeywordList") || + capabilities.querySelector("Keywords"), + ], + getTags("Keyword"), + map(getTagContent) + )(); + + const serviceType = [ "features", "wfs", "FEATURES", "WFS" ].some((val) => + keywords.includes(val) + ) + ? "wfs" + : [ "WCS", "GeoTIFF", "wcs" ].some((val) => keywords.includes(val)) + ? "wcs" + : null; + return { version, keywords, serviceType, workspace, layer }; +} diff --git a/src/lib/wmts-layer.js b/src/lib/wmts-layer.js new file mode 100644 index 00000000..06dcb71c --- /dev/null +++ b/src/lib/wmts-layer.js @@ -0,0 +1,98 @@ +import { stringify } from "query-string"; + +const defaultUrl = process.env.VUE_APP_GEOSERVER_BASE_URL; + +export function buildWmtsLayer ({ + url: rawUrl = defaultUrl, + id, + layer, + workspace, + styles = "", + paint = { + 'fill-color': '#0080ff', + 'fill-opacity': 0.5 + }, + time, + filter, + version, +}) { + const url = new URL(rawUrl); + const searchParamEntries = url.searchParams.entries(); + const searchParamsObject = Object.fromEntries(searchParamEntries); + + const tile = buildWmtsGeoserverUrl({ + request: "GetTile", + version, + layer: workspace ? `${ workspace }:${ layer }` : layer, + style: styles, + url: url.origin + url.pathname, + transparent: true, + encode: false, + ...(time && { time: time }), + ...(filter && { cql_filter: filter }), + ...searchParamsObject, + }); + + return { + source: { + id, + key: layer, + type: "vector", + tiles: [ tile ], + minZoom: 6, + maxZoom: 20, + }, + layer: { + id, + type: "fill", + source: layer, + "source-layer": layer, + paint, + }, + }; +} + + +export function buildWmtsGeoserverUrl({ + url, + service="WMTS", + request, + version = "1.0.0.", + layer, + style="", + tilematrix = "EPSG:900913:{z}", + tilematrixset = "EPSG:900913", + format = "application/vnd.mapbox-vector-tile", + tilecol = "{x}", + tilerow = "{y}", + encode = true, + ...rest +}) { + if (!service || !request) { + return undefined; + } + + const config = { + request, + service, + version, + layer, + style, + tilematrix, + tilematrixset, + format, + tilecol, + tilerow, + ...rest, + }; + const entries = Object.entries(config); + const upperCasedEntries = entries.map(([ key, value ]) => [ + key.toUpperCase(), + value, + ]); + const upperCasedConfig = Object.fromEntries(upperCasedEntries); + + const params = stringify(upperCasedConfig, { encode, sort: false }); + + return `${ url }?${ params }`; +} diff --git a/src/store/modules/map/index.js b/src/store/modules/map/index.js index dd1fe42a..4d994e79 100644 --- a/src/store/modules/map/index.js +++ b/src/store/modules/map/index.js @@ -1,8 +1,12 @@ -import { difference, update } from 'ramda' -import buildWmsLayer from '~/lib/build-wms-layer' -import addFilterAttributesToLayer from '~/lib/add-filter-attributes-to-layer' -import { getWmsCapabilities, getLayerProperties } from '~/lib/get-capabilities' -import { NETHERLANDS_MAP_CENTER, NETHERLANDS_MAP_ZOOM } from '~/lib/constants' +import { difference, update } from "ramda"; +import buildWmsLayer from "~/lib/build-wms-layer"; +import addFilterAttributesToLayer from "~/lib/add-filter-attributes-to-layer"; +import { + getLayerProperties, + getWmsCapabilities, getWmtsCapabilities, getWmtsLayerProperties, +} from "~/lib/get-capabilities"; +import { NETHERLANDS_MAP_CENTER, NETHERLANDS_MAP_ZOOM } from "~/lib/constants"; +import { buildWmtsLayer, loadWmtsLayer } from "~/lib/wmts-layer"; export default { namespaced: true, @@ -14,6 +18,8 @@ export default { drawnFeatures: [], filteredLayerId: null, // id of active layer to filter wmsLayers: [], + wmtsSources: [], + wmtsLayers: [], wmsApiLayer: null, selectedLayerForSelection: null, mapCenter: NETHERLANDS_MAP_CENTER, @@ -23,159 +29,212 @@ export default { }), getters: { - mapLoaded: state => state.mapLoaded, - activeFlattenedLayers: state => (state.mapLoaded && state.activeFlattenedLayers) || [], - activeFlattenedLayerIds: state => (state.activeFlattenedLayers || []).map(({ id }) => id), - activeFlattenedLayersIdsWithTimeOption (state, getters) { + mapLoaded: (state) => state.mapLoaded, + activeFlattenedLayers: (state) => + (state.mapLoaded && state.activeFlattenedLayers) || [], + activeFlattenedLayerIds: (state) => + (state.activeFlattenedLayers || []).map(({ id }) => id), + activeFlattenedLayersIdsWithTimeOption(state, getters) { if (!getters.activeFlattenedLayers) { - return [] + return []; } - return getters.activeFlattenedLayers.filter(layer => layer?.timeFilter).map(({ id })=>id) + return getters.activeFlattenedLayers + .filter((layer) => layer?.timeFilter) + .map(({ id }) => id); }, wmsLayers(state) { if (!state.mapLoaded) { - return [] + return []; } - return state.wmsLayers - } , + return state.wmsLayers; + }, + wmtsSources(state) { + if (!state.mapLoaded) { + return []; + } + return state.wmtsSources; + }, + wmtsLayers(state) { + if (!state.mapLoaded) { + return []; + } + return state.wmtsLayers; + }, wmsApiLayer(state) { if (!state.mapLoaded) { - return [] + return []; + } + return state.wmsApiLayer; + }, + wmtsApiLayer(state) { + if (!state.mapLoaded) { + return []; } - return state.wmsApiLayer - }, - wmsLayerIds: state => (state.activeFlattenedLayers || []).map(({ id }) => id), - drawMode: state => state.drawMode, - drawnFeatures: state => state.drawnFeatures, - filteredLayerId: state => state.filteredLayerId, - selectedLayerForSelection: state => state.selectedLayerForSelection, - mapCenter: state => state.mapCenter, - mapZoom: state => state.mapZoom, - zoomExtent: state => state.zoomExtent, - multipleSelection: state => state.multipleSelection, + return state.wmtsApiLayer; + }, + wmsLayerIds: (state) => + (state.activeFlattenedLayers || []).map(({ id }) => id), + drawMode: (state) => state.drawMode, + drawnFeatures: (state) => state.drawnFeatures, + filteredLayerId: (state) => state.filteredLayerId, + selectedLayerForSelection: (state) => state.selectedLayerForSelection, + mapCenter: (state) => state.mapCenter, + mapZoom: (state) => state.mapZoom, + zoomExtent: (state) => state.zoomExtent, + multipleSelection: (state) => state.multipleSelection, }, mutations: { SET_MAP_LOADED(state) { - state.mapLoaded = true + state.mapLoaded = true; }, SET_RASTER_LAYERS(state, { layers }) { - state.activeFlattenedLayers = layers + state.activeFlattenedLayers = layers; }, ADD_ACTIVE_FLATTENED_LAYER(state, layer) { - state.activeFlattenedLayers = [ layer, ...state.activeFlattenedLayers ] + state.activeFlattenedLayers = [ layer, ...state.activeFlattenedLayers ]; }, MOVE_ACTIVE_FLATTENED_LAYER(state, { fromIndex, toIndex }) { - var element = state.activeFlattenedLayers[fromIndex] - state.activeFlattenedLayers.splice(fromIndex, 1) - state.activeFlattenedLayers.splice(toIndex, 0, element) + var element = state.activeFlattenedLayers[fromIndex]; + state.activeFlattenedLayers.splice(fromIndex, 1); + state.activeFlattenedLayers.splice(toIndex, 0, element); }, MOVE_WMS_LAYER(state, { fromIndex, toIndex }) { - var element = state.wmsLayers[fromIndex] - state.wmsLayers.splice(fromIndex, 1) - state.wmsLayers.splice(toIndex, 0, element) + var element = state.wmsLayers[fromIndex]; + state.wmsLayers.splice(fromIndex, 1); + state.wmsLayers.splice(toIndex, 0, element); }, REMOVE_ACTIVE_FLATTENED_LAYER(state, { layer }) { - state.activeFlattenedLayers = state.activeFlattenedLayers.filter(activeLayer => activeLayer.id !== layer.id) + state.activeFlattenedLayers = state.activeFlattenedLayers.filter( + (activeLayer) => activeLayer.id !== layer.id + ); }, ADD_WMS_LAYER(state, layer) { - state.wmsLayers = [ layer, ...state.wmsLayers ] + state.wmsLayers = [ layer, ...state.wmsLayers ]; }, REMOVE_WMS_LAYER(state, layerId) { - state.wmsLayers = state.wmsLayers.filter(wmsLayer => wmsLayer.id !== layerId) + state.wmsLayers = state.wmsLayers.filter( + (wmsLayer) => wmsLayer.id !== layerId + ); }, - ADD_WMS_API_LAYER(state, layer) { - state.wmsApiLayer = layer + state.wmsApiLayer = layer; }, REMOVE_WMS_API_LAYER(state) { - state.wmsApiLayer = null + state.wmsApiLayer = null; + }, + + ADD_WMTS_LAYER(state, layer) { + state.wmtsLayers = [ layer, ...state.wmtsLayers ]; + }, + ADD_WMTS_SOURCE(state, layer) { + state.wmtsSources = [ layer, ...state.wmtsSources ]; + }, + REMOVE_WMTS_LAYER(state, layerId) { + state.wmtsLayers = state.wmtsLayers.filter( + (wmtsLayer) => wmtsLayer.layer.id !== layerId + ); + }, + + ADD_WMTS_API_LAYER(state, layer) { + state.wmtsApiLayer = layer; + }, + REMOVE_WMTS_API_LAYER(state) { + state.wmtsApiLayer = null; }, RESET_ACTIVE_FLATTENED_LAYERS(state) { - state.activeFlattenedLayers = [] + state.activeFlattenedLayers = []; }, RESET_WMS_LAYERS(state) { - state.wmsLayers = [] + state.wmsLayers = []; + }, + RESET_WMTS_LAYERS(state) { + state.wmtsLayers = []; }, SET_DRAW_MODE(state, { mode }) { - state.drawMode = mode + state.drawMode = mode; }, ADD_DRAWN_FEATURE(state, feature) { if (state.multipleSelection) { - if (!state.drawnFeatures.find(f => f.id === feature.id)) { - state.drawnFeatures = [ - ...state.drawnFeatures, - feature, - ] + if (!state.drawnFeatures.find((f) => f.id === feature.id)) { + state.drawnFeatures = [ ...state.drawnFeatures, feature ]; } } else { - state.drawnFeatures = [ feature ] + state.drawnFeatures = [ feature ]; } - }, REMOVE_DRAWN_FEATURE(state, feature) { if (state.multipleSelection) { - state.drawnFeatures = state.drawnFeatures.filter(f => f.id !== feature.id) + state.drawnFeatures = state.drawnFeatures.filter( + (f) => f.id !== feature.id + ); } else { - state.drawnFeatures = [] + state.drawnFeatures = []; } - }, SET_DRAWN_FEATURES(state, feature) { - state.drawnFeatures = Object.freeze(feature) + state.drawnFeatures = Object.freeze(feature); }, UPDATE_WMS_LAYER_OPACITY(state, { id, opacity }) { - const layerToUpdate = state.wmsLayers.find(layer => layer.id === id) - const index = state.wmsLayers.findIndex(layer => layer.id === id) + const layerToUpdate = state.wmsLayers.find((layer) => layer.id === id); + const index = state.wmsLayers.findIndex((layer) => layer.id === id); if (!layerToUpdate) { - return + return; } - layerToUpdate.opacity = opacity - state.wmsLayers = update(index, layerToUpdate, state.wmsLayers) + layerToUpdate.opacity = opacity; + state.wmsLayers = update(index, layerToUpdate, state.wmsLayers); }, SET_FILTERED_LAYER_ID(state, id) { - state.filteredLayerId = id + state.filteredLayerId = id; }, REMOVE_FILTERED_LAYER_ID(state) { - state.filteredLayerId = null + state.filteredLayerId = null; }, SET_SELECTED_LAYER_FOR_SELECTION(state, layer) { - state.selectedLayerForSelection = layer + state.selectedLayerForSelection = layer; }, SET_MAP_CENTER(state, coords) { - state.mapCenter = coords + state.mapCenter = coords; }, SET_MAP_ZOOM(state, zoom) { - state.mapZoom = zoom + state.mapZoom = zoom; }, UPDATE_ZOOM_EXTENT(state, bbox) { - state.zoomExtent = bbox + state.zoomExtent = bbox; }, SET_MULTIPLE_SELECTION(state, boolean) { - state.multipleSelection = boolean + state.multipleSelection = boolean; }, }, actions: { setMapLoaded({ commit }) { - commit('SET_MAP_LOADED') + commit("SET_MAP_LOADED"); }, - loadLayerOnMap({ commit, state }, { layers }) { - - const layersToAdd = difference(layers, state.activeFlattenedLayers) + async loadLayerOnMap({ commit, state }, { layers }) { + const layersToAdd = difference(layers, state.activeFlattenedLayers); - layersToAdd.forEach((layer) => { - getWmsCapabilities(layer.url) - .then(capabilities => getLayerProperties(capabilities, layer.layer)) - .then(({ serviceType, timeExtent, wmsVersion, bbox }) => { - commit('ADD_ACTIVE_FLATTENED_LAYER', { ...layer, ...{ serviceType: serviceType }, ... { timeExtent: timeExtent }, ... { version: wmsVersion }, ... { bbox: bbox } } ) - commit('ADD_WMS_LAYER', buildWmsLayer({ ...layer, ...{ serviceType: serviceType }, ... { timeExtent: timeExtent }, ... { version: wmsVersion }, ... { bbox: bbox } })) - }, - ) - }) + await Promise.all( + layersToAdd.map(async (layer) => { + const url = new URL(layer.url); + const serviceType = url.pathname.split("/").pop().toUpperCase(); + if (serviceType === "WMTS") { + const capabilities = await getWmtsCapabilities(url); + const properties = getWmtsLayerProperties(capabilities, layer); + commit("ADD_ACTIVE_FLATTENED_LAYER", { ...layer, ...properties }); + commit("ADD_WMTS_LAYER", buildWmtsLayer({ ...layer, ...properties })); + } + if (serviceType === "WMS") { + const capabilities = await getWmsCapabilities(url) + const properties = getLayerProperties(capabilities, layer.layer); + commit("ADD_ACTIVE_FLATTENED_LAYER", { ...layer, ...properties }); + commit("ADD_WMS_LAYER", buildWmsLayer({ ...layer, ...properties })); + } + }) + ); }, reloadLayerOnMap({ commit, state, rootState }) { @@ -183,92 +242,108 @@ export default { then in the filter tab the user can load on the map the layer with a new time or a new cql filter */ - const { filteredLayerId, activeFlattenedLayers } = state - commit('REMOVE_WMS_LAYER', filteredLayerId) - const { selectedTimestamp, cqlFilter } = rootState.data - const layer = addFilterAttributesToLayer(activeFlattenedLayers, filteredLayerId, selectedTimestamp, cqlFilter ) + const { filteredLayerId, activeFlattenedLayers } = state; + commit("REMOVE_WMS_LAYER", filteredLayerId); + const { selectedTimestamp, cqlFilter } = rootState.data; + const layer = addFilterAttributesToLayer( + activeFlattenedLayers, + filteredLayerId, + selectedTimestamp, + cqlFilter + ); - commit('ADD_WMS_LAYER', buildWmsLayer(layer)) + commit("ADD_WMS_LAYER", buildWmsLayer(layer)); }, removeLayerFromMap({ commit }, { layers }) { - layers.forEach(layer => { - commit('REMOVE_ACTIVE_FLATTENED_LAYER', { layer }) - commit('REMOVE_WMS_LAYER', layer.id) - }) + layers.forEach((layer) => { + commit("REMOVE_ACTIVE_FLATTENED_LAYER", { layer }); + commit("REMOVE_WMS_LAYER", layer.id); + commit("REMOVE_WMTS_LAYER", layer.id); + }); }, loadApiLayerOnMap({ commit }, layer) { getWmsCapabilities(layer.url) - .then(capabilities => getLayerProperties(capabilities, layer.layer)) - .then(({ serviceType, timeExtent, wmsVersion, bbox }) => { - commit('ADD_WMS_API_LAYER', buildWmsLayer({ ...layer, ...{ serviceType: serviceType }, ... { timeExtent: timeExtent }, ... { version: wmsVersion }, ... { bbox: bbox } })) - }, - ) + .then((capabilities) => getLayerProperties(capabilities, layer.layer)) + .then(({ serviceType, timeExtent, wmsVersion, bbox }) => { + commit( + "ADD_WMS_API_LAYER", + buildWmsLayer({ + ...layer, + ...{ serviceType: serviceType }, + ...{ timeExtent: timeExtent }, + ...{ version: wmsVersion }, + ...{ bbox: bbox }, + }) + ); + }); }, removeApiLayerFromMap({ commit }) { - commit('REMOVE_WMS_API_LAYER') + commit("REMOVE_WMS_API_LAYER"); }, removeFilteredLayerId({ commit }) { - commit('REMOVE_FILTERED_LAYER_ID') + commit("REMOVE_FILTERED_LAYER_ID"); }, resetLayers({ commit }) { - commit('RESET_ACTIVE_FLATTENED_LAYERS') - commit('RESET_WMS_LAYERS') - commit('REMOVE_FILTERED_LAYER_ID') + commit("RESET_ACTIVE_FLATTENED_LAYERS"); + commit("RESET_WMS_LAYERS"); + commit("REMOVE_FILTERED_LAYER_ID"); }, moveRasterLayer({ commit }, { fromIndex, toIndex }) { - commit('MOVE_ACTIVE_FLATTENED_LAYER', { fromIndex, toIndex }) - commit('MOVE_WMS_LAYER', { fromIndex, toIndex }) + commit("MOVE_ACTIVE_FLATTENED_LAYER", { fromIndex, toIndex }); + commit("MOVE_WMS_LAYER", { fromIndex, toIndex }); }, setDrawMode({ commit, state }, { mode }) { - const modeToCommit = state.drawMode === mode ? null : mode - commit('SET_DRAW_MODE', { mode: modeToCommit }) + const modeToCommit = state.drawMode === mode ? null : mode; + commit("SET_DRAW_MODE", { mode: modeToCommit }); }, addDrawnFeature({ commit }, feature) { - commit('ADD_DRAWN_FEATURE', feature) + commit("ADD_DRAWN_FEATURE", feature); }, removeDrawnFeature({ commit }, feature) { - commit('REMOVE_DRAWN_FEATURE', feature) + commit("REMOVE_DRAWN_FEATURE", feature); }, setDrawnFeatures({ commit }, features) { - commit('SET_DRAWN_FEATURES', features) + commit("SET_DRAWN_FEATURES", features); }, clearDrawnFeatures({ commit }) { - commit('SET_DRAWN_FEATURES', []) + commit("SET_DRAWN_FEATURES", []); }, updateWmsLayerOpacity({ commit }, { id, opacity }) { - commit('UPDATE_WMS_LAYER_OPACITY', { id, opacity }) + commit("UPDATE_WMS_LAYER_OPACITY", { id, opacity }); }, setFilteredLayerId({ commit }, id) { - commit('SET_FILTERED_LAYER_ID', id) + commit("SET_FILTERED_LAYER_ID", id); }, setSelectedLayerForSelection({ commit }, layer) { - commit('SET_SELECTED_LAYER_FOR_SELECTION', layer) + commit("SET_SELECTED_LAYER_FOR_SELECTION", layer); }, setMapCenter({ commit }, coords) { - commit('SET_MAP_CENTER', coords) + commit("SET_MAP_CENTER", coords); }, setMapZoom({ commit }, zoom) { - commit('SET_MAP_ZOOM', zoom) + commit("SET_MAP_ZOOM", zoom); }, updateZoomExtent({ commit, state }, id) { //updates zoom extent based on the bbox of the layer. //input is the id of the layer //activeFlattenedLayers - const layerToZoom = state.activeFlattenedLayers.find(layer => layer.id === id) - commit('UPDATE_ZOOM_EXTENT', layerToZoom.bbox) + const layerToZoom = state.activeFlattenedLayers.find( + (layer) => layer.id === id + ); + commit("UPDATE_ZOOM_EXTENT", layerToZoom.bbox); }, setMultipleSelection({ commit }, boolean) { - commit('SET_MULTIPLE_SELECTION', boolean) + commit("SET_MULTIPLE_SELECTION", boolean); }, }, -} +}; diff --git a/src/views/Layers.vue b/src/views/Layers.vue index 68e149fb..56fad7a8 100644 --- a/src/views/Layers.vue +++ b/src/views/Layers.vue @@ -8,64 +8,85 @@ From 9c47b1817876760b86d2b3cacf061bbcfaf858b4 Mon Sep 17 00:00:00 2001 From: Bazottie Date: Fri, 13 Dec 2024 16:53:14 +0100 Subject: [PATCH 2/3] Reset some formatting changes --- .../LayerInfoDialog/LayerInfoDialog.vue | 10 +- src/store/modules/map/index.js | 214 +++++++++--------- src/views/Layers.vue | 64 +++--- 3 files changed, 134 insertions(+), 154 deletions(-) diff --git a/src/components/LayerInfoDialog/LayerInfoDialog.vue b/src/components/LayerInfoDialog/LayerInfoDialog.vue index f47cc3da..22ce0591 100644 --- a/src/components/LayerInfoDialog/LayerInfoDialog.vue +++ b/src/components/LayerInfoDialog/LayerInfoDialog.vue @@ -257,12 +257,10 @@ this.errorMessage = '' this.isLoading = true - const data = await this.multipleFetch([ - `/api/record-register?record=${this.layerId}&viewer=${this.viewerName}`, - `/api/record-register?record=${this.viewerLayerId}&viewer=${this.viewerName}`, + this.recordUrl = await this.multipleFetch([ + `/api/record-register?record=${ this.layerId }&viewer=${ this.viewerName }`, + `/api/record-register?record=${ this.viewerLayerId }&viewer=${ this.viewerName }`, ]) - - this.recordUrl = data } catch (e) { if (!e.response.data.error) { this.errorMessage = this.$t('recordUrlFechingError') @@ -306,7 +304,7 @@ return response.data; } catch (error) { - console.error(`Failed to fetch from URL ${i + 1}:`, error.message); + console.error(`Failed to fetch from URL ${ i + 1 }:`, error.message); } } diff --git a/src/store/modules/map/index.js b/src/store/modules/map/index.js index 4d994e79..5ad552d1 100644 --- a/src/store/modules/map/index.js +++ b/src/store/modules/map/index.js @@ -1,12 +1,12 @@ -import { difference, update } from "ramda"; -import buildWmsLayer from "~/lib/build-wms-layer"; -import addFilterAttributesToLayer from "~/lib/add-filter-attributes-to-layer"; +import { difference, update } from 'ramda' +import buildWmsLayer from '~/lib/build-wms-layer' +import addFilterAttributesToLayer from '~/lib/add-filter-attributes-to-layer' import { getLayerProperties, getWmsCapabilities, getWmtsCapabilities, getWmtsLayerProperties, -} from "~/lib/get-capabilities"; -import { NETHERLANDS_MAP_CENTER, NETHERLANDS_MAP_ZOOM } from "~/lib/constants"; -import { buildWmtsLayer, loadWmtsLayer } from "~/lib/wmts-layer"; +} from '~/lib/get-capabilities' +import { NETHERLANDS_MAP_CENTER, NETHERLANDS_MAP_ZOOM } from '~/lib/constants' +import { buildWmtsLayer } from '~/lib/wmts-layer' export default { namespaced: true, @@ -36,41 +36,35 @@ export default { (state.activeFlattenedLayers || []).map(({ id }) => id), activeFlattenedLayersIdsWithTimeOption(state, getters) { if (!getters.activeFlattenedLayers) { - return []; + return [] } return getters.activeFlattenedLayers .filter((layer) => layer?.timeFilter) - .map(({ id }) => id); + .map(({ id }) => id) }, wmsLayers(state) { if (!state.mapLoaded) { - return []; + return [] } - return state.wmsLayers; + return state.wmsLayers }, wmtsSources(state) { if (!state.mapLoaded) { - return []; + return [] } - return state.wmtsSources; + return state.wmtsSources }, wmtsLayers(state) { if (!state.mapLoaded) { - return []; + return [] } - return state.wmtsLayers; + return state.wmtsLayers }, wmsApiLayer(state) { if (!state.mapLoaded) { - return []; + return [] } - return state.wmsApiLayer; - }, - wmtsApiLayer(state) { - if (!state.mapLoaded) { - return []; - } - return state.wmtsApiLayer; + return state.wmsApiLayer }, wmsLayerIds: (state) => (state.activeFlattenedLayers || []).map(({ id }) => id), @@ -86,155 +80,155 @@ export default { mutations: { SET_MAP_LOADED(state) { - state.mapLoaded = true; + state.mapLoaded = true }, SET_RASTER_LAYERS(state, { layers }) { - state.activeFlattenedLayers = layers; + state.activeFlattenedLayers = layers }, ADD_ACTIVE_FLATTENED_LAYER(state, layer) { - state.activeFlattenedLayers = [ layer, ...state.activeFlattenedLayers ]; + state.activeFlattenedLayers = [ layer, ...state.activeFlattenedLayers ] }, MOVE_ACTIVE_FLATTENED_LAYER(state, { fromIndex, toIndex }) { - var element = state.activeFlattenedLayers[fromIndex]; - state.activeFlattenedLayers.splice(fromIndex, 1); - state.activeFlattenedLayers.splice(toIndex, 0, element); + var element = state.activeFlattenedLayers[fromIndex] + state.activeFlattenedLayers.splice(fromIndex, 1) + state.activeFlattenedLayers.splice(toIndex, 0, element) }, MOVE_WMS_LAYER(state, { fromIndex, toIndex }) { - var element = state.wmsLayers[fromIndex]; - state.wmsLayers.splice(fromIndex, 1); - state.wmsLayers.splice(toIndex, 0, element); + var element = state.wmsLayers[fromIndex] + state.wmsLayers.splice(fromIndex, 1) + state.wmsLayers.splice(toIndex, 0, element) }, REMOVE_ACTIVE_FLATTENED_LAYER(state, { layer }) { state.activeFlattenedLayers = state.activeFlattenedLayers.filter( (activeLayer) => activeLayer.id !== layer.id - ); + ) }, ADD_WMS_LAYER(state, layer) { - state.wmsLayers = [ layer, ...state.wmsLayers ]; + state.wmsLayers = [ layer, ...state.wmsLayers ] }, REMOVE_WMS_LAYER(state, layerId) { state.wmsLayers = state.wmsLayers.filter( (wmsLayer) => wmsLayer.id !== layerId - ); + ) }, ADD_WMS_API_LAYER(state, layer) { - state.wmsApiLayer = layer; + state.wmsApiLayer = layer }, REMOVE_WMS_API_LAYER(state) { - state.wmsApiLayer = null; + state.wmsApiLayer = null }, ADD_WMTS_LAYER(state, layer) { - state.wmtsLayers = [ layer, ...state.wmtsLayers ]; + state.wmtsLayers = [ layer, ...state.wmtsLayers ] }, ADD_WMTS_SOURCE(state, layer) { - state.wmtsSources = [ layer, ...state.wmtsSources ]; + state.wmtsSources = [ layer, ...state.wmtsSources ] }, REMOVE_WMTS_LAYER(state, layerId) { state.wmtsLayers = state.wmtsLayers.filter( (wmtsLayer) => wmtsLayer.layer.id !== layerId - ); + ) }, ADD_WMTS_API_LAYER(state, layer) { - state.wmtsApiLayer = layer; + state.wmtsApiLayer = layer }, REMOVE_WMTS_API_LAYER(state) { - state.wmtsApiLayer = null; + state.wmtsApiLayer = null }, RESET_ACTIVE_FLATTENED_LAYERS(state) { - state.activeFlattenedLayers = []; + state.activeFlattenedLayers = [] }, RESET_WMS_LAYERS(state) { - state.wmsLayers = []; + state.wmsLayers = [] }, RESET_WMTS_LAYERS(state) { - state.wmtsLayers = []; + state.wmtsLayers = [] }, SET_DRAW_MODE(state, { mode }) { - state.drawMode = mode; + state.drawMode = mode }, ADD_DRAWN_FEATURE(state, feature) { if (state.multipleSelection) { if (!state.drawnFeatures.find((f) => f.id === feature.id)) { - state.drawnFeatures = [ ...state.drawnFeatures, feature ]; + state.drawnFeatures = [ ...state.drawnFeatures, feature ] } } else { - state.drawnFeatures = [ feature ]; + state.drawnFeatures = [ feature ] } }, REMOVE_DRAWN_FEATURE(state, feature) { if (state.multipleSelection) { state.drawnFeatures = state.drawnFeatures.filter( (f) => f.id !== feature.id - ); + ) } else { - state.drawnFeatures = []; + state.drawnFeatures = [] } }, SET_DRAWN_FEATURES(state, feature) { - state.drawnFeatures = Object.freeze(feature); + state.drawnFeatures = Object.freeze(feature) }, UPDATE_WMS_LAYER_OPACITY(state, { id, opacity }) { - const layerToUpdate = state.wmsLayers.find((layer) => layer.id === id); - const index = state.wmsLayers.findIndex((layer) => layer.id === id); + const layerToUpdate = state.wmsLayers.find((layer) => layer.id === id) + const index = state.wmsLayers.findIndex((layer) => layer.id === id) if (!layerToUpdate) { - return; + return } - layerToUpdate.opacity = opacity; - state.wmsLayers = update(index, layerToUpdate, state.wmsLayers); + layerToUpdate.opacity = opacity + state.wmsLayers = update(index, layerToUpdate, state.wmsLayers) }, SET_FILTERED_LAYER_ID(state, id) { - state.filteredLayerId = id; + state.filteredLayerId = id }, REMOVE_FILTERED_LAYER_ID(state) { - state.filteredLayerId = null; + state.filteredLayerId = null }, SET_SELECTED_LAYER_FOR_SELECTION(state, layer) { - state.selectedLayerForSelection = layer; + state.selectedLayerForSelection = layer }, SET_MAP_CENTER(state, coords) { - state.mapCenter = coords; + state.mapCenter = coords }, SET_MAP_ZOOM(state, zoom) { - state.mapZoom = zoom; + state.mapZoom = zoom }, UPDATE_ZOOM_EXTENT(state, bbox) { - state.zoomExtent = bbox; + state.zoomExtent = bbox }, SET_MULTIPLE_SELECTION(state, boolean) { - state.multipleSelection = boolean; + state.multipleSelection = boolean }, }, actions: { setMapLoaded({ commit }) { - commit("SET_MAP_LOADED"); + commit('SET_MAP_LOADED') }, async loadLayerOnMap({ commit, state }, { layers }) { - const layersToAdd = difference(layers, state.activeFlattenedLayers); + const layersToAdd = difference(layers, state.activeFlattenedLayers) await Promise.all( layersToAdd.map(async (layer) => { - const url = new URL(layer.url); - const serviceType = url.pathname.split("/").pop().toUpperCase(); - if (serviceType === "WMTS") { - const capabilities = await getWmtsCapabilities(url); - const properties = getWmtsLayerProperties(capabilities, layer); - commit("ADD_ACTIVE_FLATTENED_LAYER", { ...layer, ...properties }); - commit("ADD_WMTS_LAYER", buildWmtsLayer({ ...layer, ...properties })); + const url = new URL(layer.url) + const serviceType = url.pathname.split('/').pop().toUpperCase() + if (serviceType === 'WMTS') { + const capabilities = await getWmtsCapabilities(url) + const properties = getWmtsLayerProperties(capabilities, layer) + commit('ADD_ACTIVE_FLATTENED_LAYER', { ...layer, ...properties }) + commit('ADD_WMTS_LAYER', buildWmtsLayer({ ...layer, ...properties })) } - if (serviceType === "WMS") { + if (serviceType === 'WMS') { const capabilities = await getWmsCapabilities(url) - const properties = getLayerProperties(capabilities, layer.layer); - commit("ADD_ACTIVE_FLATTENED_LAYER", { ...layer, ...properties }); - commit("ADD_WMS_LAYER", buildWmsLayer({ ...layer, ...properties })); + const properties = getLayerProperties(capabilities, layer.layer) + commit('ADD_ACTIVE_FLATTENED_LAYER', { ...layer, ...properties }) + commit('ADD_WMS_LAYER', buildWmsLayer({ ...layer, ...properties })) } }) - ); + ) }, reloadLayerOnMap({ commit, state, rootState }) { @@ -242,24 +236,24 @@ export default { then in the filter tab the user can load on the map the layer with a new time or a new cql filter */ - const { filteredLayerId, activeFlattenedLayers } = state; - commit("REMOVE_WMS_LAYER", filteredLayerId); - const { selectedTimestamp, cqlFilter } = rootState.data; + const { filteredLayerId, activeFlattenedLayers } = state + commit('REMOVE_WMS_LAYER', filteredLayerId) + const { selectedTimestamp, cqlFilter } = rootState.data const layer = addFilterAttributesToLayer( activeFlattenedLayers, filteredLayerId, selectedTimestamp, cqlFilter - ); + ) - commit("ADD_WMS_LAYER", buildWmsLayer(layer)); + commit('ADD_WMS_LAYER', buildWmsLayer(layer)) }, removeLayerFromMap({ commit }, { layers }) { layers.forEach((layer) => { - commit("REMOVE_ACTIVE_FLATTENED_LAYER", { layer }); - commit("REMOVE_WMS_LAYER", layer.id); - commit("REMOVE_WMTS_LAYER", layer.id); - }); + commit('REMOVE_ACTIVE_FLATTENED_LAYER', { layer }) + commit('REMOVE_WMS_LAYER', layer.id) + commit('REMOVE_WMTS_LAYER', layer.id) + }) }, loadApiLayerOnMap({ commit }, layer) { @@ -267,7 +261,7 @@ export default { .then((capabilities) => getLayerProperties(capabilities, layer.layer)) .then(({ serviceType, timeExtent, wmsVersion, bbox }) => { commit( - "ADD_WMS_API_LAYER", + 'ADD_WMS_API_LAYER', buildWmsLayer({ ...layer, ...{ serviceType: serviceType }, @@ -275,63 +269,63 @@ export default { ...{ version: wmsVersion }, ...{ bbox: bbox }, }) - ); - }); + ) + }) }, removeApiLayerFromMap({ commit }) { - commit("REMOVE_WMS_API_LAYER"); + commit('REMOVE_WMS_API_LAYER') }, removeFilteredLayerId({ commit }) { - commit("REMOVE_FILTERED_LAYER_ID"); + commit('REMOVE_FILTERED_LAYER_ID') }, resetLayers({ commit }) { - commit("RESET_ACTIVE_FLATTENED_LAYERS"); - commit("RESET_WMS_LAYERS"); - commit("REMOVE_FILTERED_LAYER_ID"); + commit('RESET_ACTIVE_FLATTENED_LAYERS') + commit('RESET_WMS_LAYERS') + commit('REMOVE_FILTERED_LAYER_ID') }, moveRasterLayer({ commit }, { fromIndex, toIndex }) { - commit("MOVE_ACTIVE_FLATTENED_LAYER", { fromIndex, toIndex }); - commit("MOVE_WMS_LAYER", { fromIndex, toIndex }); + commit('MOVE_ACTIVE_FLATTENED_LAYER', { fromIndex, toIndex }) + commit('MOVE_WMS_LAYER', { fromIndex, toIndex }) }, setDrawMode({ commit, state }, { mode }) { - const modeToCommit = state.drawMode === mode ? null : mode; - commit("SET_DRAW_MODE", { mode: modeToCommit }); + const modeToCommit = state.drawMode === mode ? null : mode + commit('SET_DRAW_MODE', { mode: modeToCommit }) }, addDrawnFeature({ commit }, feature) { - commit("ADD_DRAWN_FEATURE", feature); + commit('ADD_DRAWN_FEATURE', feature) }, removeDrawnFeature({ commit }, feature) { - commit("REMOVE_DRAWN_FEATURE", feature); + commit('REMOVE_DRAWN_FEATURE', feature) }, setDrawnFeatures({ commit }, features) { - commit("SET_DRAWN_FEATURES", features); + commit('SET_DRAWN_FEATURES', features) }, clearDrawnFeatures({ commit }) { - commit("SET_DRAWN_FEATURES", []); + commit('SET_DRAWN_FEATURES', []) }, updateWmsLayerOpacity({ commit }, { id, opacity }) { - commit("UPDATE_WMS_LAYER_OPACITY", { id, opacity }); + commit('UPDATE_WMS_LAYER_OPACITY', { id, opacity }) }, setFilteredLayerId({ commit }, id) { - commit("SET_FILTERED_LAYER_ID", id); + commit('SET_FILTERED_LAYER_ID', id) }, setSelectedLayerForSelection({ commit }, layer) { - commit("SET_SELECTED_LAYER_FOR_SELECTION", layer); + commit('SET_SELECTED_LAYER_FOR_SELECTION', layer) }, setMapCenter({ commit }, coords) { - commit("SET_MAP_CENTER", coords); + commit('SET_MAP_CENTER', coords) }, setMapZoom({ commit }, zoom) { - commit("SET_MAP_ZOOM", zoom); + commit('SET_MAP_ZOOM', zoom) }, updateZoomExtent({ commit, state }, id) { //updates zoom extent based on the bbox of the layer. @@ -339,11 +333,11 @@ export default { //activeFlattenedLayers const layerToZoom = state.activeFlattenedLayers.find( (layer) => layer.id === id - ); - commit("UPDATE_ZOOM_EXTENT", layerToZoom.bbox); + ) + commit('UPDATE_ZOOM_EXTENT', layerToZoom.bbox) }, setMultipleSelection({ commit }, boolean) { - commit("SET_MULTIPLE_SELECTION", boolean); + commit('SET_MULTIPLE_SELECTION', boolean) }, }, -}; +} diff --git a/src/views/Layers.vue b/src/views/Layers.vue index 56fad7a8..8ad16878 100644 --- a/src/views/Layers.vue +++ b/src/views/Layers.vue @@ -8,85 +8,73 @@ From 8e63df36f470ba52fb666e0a1954f6c44322e073 Mon Sep 17 00:00:00 2001 From: sjoerdbeentjes <11621275+sjoerdbeentjes@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:34:42 +0100 Subject: [PATCH 3/3] add support for more variants of wmts layers --- src/components/LayerChooser/LayerChooser.vue | 3 +- src/lib/get-capabilities.js | 13 ++++- src/lib/wmts-layer.js | 58 +++++++++++++------- src/store/modules/map/index.js | 3 +- 4 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/components/LayerChooser/LayerChooser.vue b/src/components/LayerChooser/LayerChooser.vue index 363cd4ea..61a9d88d 100644 --- a/src/components/LayerChooser/LayerChooser.vue +++ b/src/components/LayerChooser/LayerChooser.vue @@ -87,12 +87,13 @@