Skip to content

Commit

Permalink
Merge pull request #1146 from geoadmin/feat-pb-995-cesium-wmts-compos…
Browse files Browse the repository at this point in the history
…able

PB-995 : Cesium WMTS as composable
  • Loading branch information
pakb authored Nov 29, 2024
2 parents 04c928e + 314cbca commit a9eb9a0
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 180 deletions.
13 changes: 10 additions & 3 deletions src/api/layers/ExternalWMTSLayer.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ export default class ExternalWMTSLayer extends ExternalLayer {
* Default is `REST`
* @param {String | null} [externalWmtsData.urlTemplate=''] WMTS Get Tile url template for REST
* encoding. Default is `''`
* @param {String} [externalWmtsData.style='default'] WMTS layer style. Default is `'default'`
* @param {String} [externalWmtsData.style=null] WMTS layer style. If no style is given here,
* and no style is found in the options, the 'default' style will be used. Default is `null`
* @param {[TileMatrixSet]} [externalWmtsData.tileMatrixSets=[]] WMTS tile matrix sets
* identifiers. Default is `[]`
* @param {[WMTSDimension]} [externalWmtsData.dimensions=[]] WMTS tile dimensions. Default is
Expand Down Expand Up @@ -123,7 +124,7 @@ export default class ExternalWMTSLayer extends ExternalLayer {
options = null,
getTileEncoding = WMTSEncodingTypes.REST,
urlTemplate = '',
style = 'default',
style = null,
tileMatrixSets = [],
dimensions = [],
timeConfig = null,
Expand All @@ -149,7 +150,13 @@ export default class ExternalWMTSLayer extends ExternalLayer {
this.options = options
this.getTileEncoding = getTileEncoding
this.urlTemplate = urlTemplate
this.style = style
if (style) {
this.style = style
} else if (options?.style) {
this.style = options.style
} else {
this.style = 'default'
}
this.tileMatrixSets = tileMatrixSets
this.dimensions = dimensions
}
Expand Down
3 changes: 1 addition & 2 deletions src/modules/map/components/cesium/CesiumInternalLayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
<CesiumWMTSLayer
v-if="layerConfig.type === LayerTypes.WMTS"
:wmts-layer-config="layerConfig"
:projection="projection"
:parent-layer-opacity="parentLayerOpacity"
:z-index="zIndex"
:is-time-slider-active="isTimeSliderActive"
/>
<CesiumWMSLayer
v-if="layerConfig.type === LayerTypes.WMS"
:wms-layer-config="layerConfig"
:parent-layer-opacity="parentLayerOpacity"
:projection="projection"
:z-index="zIndex"
:is-time-slider-active="isTimeSliderActive"
Expand Down
11 changes: 8 additions & 3 deletions src/modules/map/components/cesium/CesiumVisibleLayers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ const backgroundLayersFor3D = computed(() => store.getters.backgroundLayersFor3D
const visibleImageryLayers = computed(() =>
visibleLayers.value.filter(isImageryLayer).map((imageryLayer) => {
if (imageryLayer.idIn3d) {
return (
layersConfig.value.find((layer) => layer.id === imageryLayer.idIn3d) ?? imageryLayer
)
// in order to have the correct opacity, we need to clone the 3D config and give it the 2D opacity
// (we can't just modify the 3D config without cloning it, as it comes directly from the store)
let configIn3d = layersConfig.value.find((layer) => layer.id === imageryLayer.idIn3d)
if (configIn3d) {
configIn3d = configIn3d.clone()
configIn3d.opacity = imageryLayer.opacity
return configIn3d
}
}
return imageryLayer
})
Expand Down
260 changes: 108 additions & 152 deletions src/modules/map/components/cesium/CesiumWMTSLayer.vue
Original file line number Diff line number Diff line change
@@ -1,169 +1,125 @@
<template>
<slot />
</template>

<script>
import {
ImageryLayer,
Rectangle,
UrlTemplateImageryProvider,
WebMapTileServiceImageryProvider,
} from 'cesium'
import { isEqual } from 'lodash'
import { mapActions } from 'vuex'
<script setup>
import { Rectangle, UrlTemplateImageryProvider, WebMapTileServiceImageryProvider } from 'cesium'
import { computed, inject, onBeforeUnmount, toRef, toRefs, watch } from 'vue'
import { useStore } from 'vuex'
import ExternalWMTSLayer, { WMTSEncodingTypes } from '@/api/layers/ExternalWMTSLayer.class'
import GeoAdminWMTSLayer from '@/api/layers/GeoAdminWMTSLayer.class'
import { DEFAULT_PROJECTION } from '@/config/map.config'
import CoordinateSystem from '@/utils/coordinates/CoordinateSystem.class'
import useAddImageryLayer from '@/modules/map/components/cesium/utils/useAddImageryLayer.composable'
import { WGS84 } from '@/utils/coordinates/coordinateSystems'
import ErrorMessage from '@/utils/ErrorMessage.class'
import { getTimestampFromConfig, getWmtsXyzUrl } from '@/utils/layerUtils'
import { getWmtsXyzUrl } from '@/utils/layerUtils'
import log from '@/utils/logging'
import addImageryLayerMixins from './utils/addImageryLayer-mixins'
const dispatcher = { dispatcher: 'CesiumWMTSLayer.vue' }
const MAXIMUM_LEVEL_OF_DETAILS = 18
const unsupportedProjectionError = new ErrorMessage('3d_unsupported_projection')
const threeDError = new ErrorMessage('3d_unsupported_projection')
export default {
mixins: [addImageryLayerMixins],
props: {
wmtsLayerConfig: {
type: [GeoAdminWMTSLayer, ExternalWMTSLayer],
required: true,
},
projection: {
type: CoordinateSystem,
required: true,
},
zIndex: {
type: Number,
default: -1,
},
isTimeSliderActive: {
type: Boolean,
default: false,
},
parentLayerOpacity: {
type: Number,
default: null,
},
const props = defineProps({
wmtsLayerConfig: {
type: [GeoAdminWMTSLayer, ExternalWMTSLayer],
required: true,
},
computed: {
layerId() {
return this.wmtsLayerConfig.id
},
opacity() {
return this.parentLayerOpacity ?? this.wmtsLayerConfig.opacity ?? 1.0
},
url() {
return getWmtsXyzUrl(this.wmtsLayerConfig, this.projection, {
addTimestamp: true,
})
},
tileMatrixSet() {
const set =
this.wmtsLayerConfig.tileMatrixSets.find(
(set) => set.projection.epsg === this.projection.epsg
) ?? null
if (!set) {
log.error(
`External layer ${this.wmtsLayerConfig.id} does not support ${this.projection.epsg}`
)
this.addLayerError({
layerId: this.wmtsLayerConfig.id,
isExternal: this.wmtsLayerConfig.isExternal,
baseUrl: this.wmtsLayerConfig.baseUrl,
error: threeDError,
...dispatcher,
})
}
return set
},
tileMatrixSetId() {
return this.tileMatrixSet?.id ?? ''
},
dimensions() {
const dimensions = {}
this.wmtsLayerConfig.dimensions?.reduce((acc, dimension) => {
if (dimension.current) {
acc[dimension.id] = 'current'
} else {
acc[dimension.id] = dimension.values[0]
}
}, dimensions)
if (this.wmtsLayerConfig.hasMultipleTimestamps) {
// if we have a time config use it as dimension
const timestamp = getTimestampFromConfig(this.wmtsLayerConfig)
// overwrite any Time, TIME or time dimension
const timeDimension = Object.entries(dimensions).find(
(e) => e[0].toLowerCase() === 'time'
)
if (timeDimension) {
dimensions[timeDimension[0]] = timestamp
}
}
return dimensions
},
},
watch: {
dimensions(newDimension, oldDimension) {
if (!isEqual(newDimension, oldDimension)) {
log.debug(`layer dimension have been updated`, oldDimension, newDimension)
this.updateLayer()
}
},
},
unmounted() {
if (this.wmtsLayerConfig.containErrorMessage(threeDError)) {
this.removeLayerError({
layerId: this.wmtsLayerConfig.id,
isExternal: this.wmtsLayerConfig.isExternal,
baseUrl: this.wmtsLayerConfig.baseUrl,
error: threeDError,
...dispatcher,
})
}
zIndex: {
type: Number,
default: -1,
},
methods: {
...mapActions(['addLayerError', 'removeLayerError']),
createImagery(url) {
const options = {
alpha: this.opacity,
}
if (this.wmtsLayerConfig instanceof ExternalWMTSLayer && this.tileMatrixSetId) {
return new ImageryLayer(
new WebMapTileServiceImageryProvider({
url:
this.wmtsLayerConfig.getTileEncoding === WMTSEncodingTypes.KVP
? this.wmtsLayerConfig.baseUrl
: this.wmtsLayerConfig.urlTemplate,
layer: this.wmtsLayerConfig.id,
style: this.wmtsLayerConfig.style,
tileMatrixSetID: this.tileMatrixSetId,
dimensions: this.dimensions,
}),
options
)
} else if (this.wmtsLayerConfig instanceof GeoAdminWMTSLayer) {
return new ImageryLayer(
new UrlTemplateImageryProvider({
rectangle: Rectangle.fromDegrees(
...DEFAULT_PROJECTION.getBoundsAs(WGS84).flatten
),
maximumLevel: MAXIMUM_LEVEL_OF_DETAILS,
url: url,
}),
options
)
}
return null
},
parentLayerOpacity: {
type: Number,
default: null,
},
})
const { wmtsLayerConfig, zIndex, parentLayerOpacity } = toRefs(props)
const getViewer = inject('getViewer')
const store = useStore()
const projection = computed(() => store.state.position.projection)
const opacity = computed(() => parentLayerOpacity.value ?? wmtsLayerConfig.value.opacity ?? 1.0)
const currentYear = computed(() => wmtsLayerConfig.value.timeConfig?.currentYear)
const url = computed(() =>
getWmtsXyzUrl(wmtsLayerConfig.value, projection.value, {
addTimestamp: true,
})
)
const tileMatrixSet = computed(() => {
if (!wmtsLayerConfig.value.tileMatrixSets) {
return null
}
if (
!wmtsLayerConfig.value.tileMatrixSets.some(
(set) => set.projection.epsg === projection.value.epsg
)
) {
log.error(
`External layer ${wmtsLayerConfig.value.id} does not support ${projection.value.epsg}`
)
store.dispatch('addLayerError', {
layerId: wmtsLayerConfig.value.id,
isExternal: wmtsLayerConfig.value.isExternal,
baseUrl: wmtsLayerConfig.value.baseUrl,
error: unsupportedProjectionError,
...dispatcher,
})
}
return wmtsLayerConfig.value.tileMatrixSets
})
const tileMatrixSetId = computed(() => tileMatrixSet.value?.id ?? projection.value.epsg)
const tileMatrixLabels = computed(() => wmtsLayerConfig.value?.options?.tileGrid?.getMatrixIds())
watch(currentYear, () => {
refreshLayer()
})
onBeforeUnmount(() => {
if (wmtsLayerConfig.value.containErrorMessage(unsupportedProjectionError)) {
store.dispatch('removeLayerError', {
layerId: wmtsLayerConfig.value.id,
isExternal: wmtsLayerConfig.value.isExternal,
baseUrl: wmtsLayerConfig.value.baseUrl,
error: unsupportedProjectionError,
...dispatcher,
})
}
})
function createProvider() {
let provider
if (wmtsLayerConfig.value instanceof ExternalWMTSLayer && tileMatrixSetId.value) {
provider = new WebMapTileServiceImageryProvider({
url:
wmtsLayerConfig.value.getTileEncoding === WMTSEncodingTypes.KVP
? wmtsLayerConfig.value.baseUrl
: wmtsLayerConfig.value.urlTemplate,
layer: wmtsLayerConfig.value.id,
style: wmtsLayerConfig.value.style,
tileMatrixSetID: tileMatrixSetId.value,
tileMatrixLabels: tileMatrixLabels.value,
})
} else if (wmtsLayerConfig.value instanceof GeoAdminWMTSLayer) {
provider = new UrlTemplateImageryProvider({
rectangle: Rectangle.fromDegrees(...DEFAULT_PROJECTION.getBoundsAs(WGS84).flatten),
maximumLevel: MAXIMUM_LEVEL_OF_DETAILS,
url: url.value,
})
} else {
log.error('Unknown WMTS layer type', wmtsLayerConfig.value, 'could not create 3D layer')
}
return provider
}
const { refreshLayer } = useAddImageryLayer(
getViewer(),
createProvider,
toRef(zIndex),
toRef(opacity)
)
</script>
<template>
<slot />
</template>
Loading

0 comments on commit a9eb9a0

Please sign in to comment.