Skip to content

Commit

Permalink
Merge pull request #851 from geoadmin/bug-PB-496-wms-time-external
Browse files Browse the repository at this point in the history
PB-496: Added External WMS time config support
  • Loading branch information
ltshb authored May 23, 2024
2 parents 3449200 + a76f9a8 commit 2cd1a38
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/api/features/features.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ async function identifyOnExternalWmsLayer(config) {
// there might exist more implementation of WMS, but I stopped there looking for more
// (please add more if you think one of our customer/external layer providers uses another flavor of WMS)
}
if (layer.timeConfig?.currentYear) {
params.TIME = layer.timeConfig.currentYear
}
// WMS 1.3.0 uses i,j to describe pixel coordinate where we want feature info
if (params.VERSION === '1.3.0') {
params.I = GET_FEATURE_INFO_FAKE_VIEWPORT_SIZE / 2
Expand Down
36 changes: 36 additions & 0 deletions src/api/layers/ExternalWMSLayer.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@ import ExternalLayer from '@/api/layers/ExternalLayer.class'
import { InvalidLayerDataError } from '@/api/layers/InvalidLayerData.error'
import LayerTypes from '@/api/layers/LayerTypes.enum'

/**
* A WMS Layer dimension
*
* See WMS OGC Spec
*
* @class
*/
export class WMSDimension {
/**
* @param {String} id Dimension identifier
* @param {String} dft Dimension default value
* @param {[String]} values All dimension values
* @param {Boolean} [optionals.current] Boolean flag if the dimension support current (see WMS
* OGC spec)
*/
constructor(id, dft, values, optionals = {}) {
const { current = false } = optionals
this.id = id
this.default = dft
this.values = values
this.current = current
}
}

/**
* Metadata for an external WMS layer.
*
Expand Down Expand Up @@ -45,6 +69,12 @@ export default class ExternalWMSLayer extends ExternalLayer {
* @param {ExternalLayerGetFeatureInfoCapability | null} [externalWmsData.getFeatureInfoCapability=null]
* Configuration describing how to request this layer's server to get feature information.
* Default is `null`
* @param {[WMSDimension]} [externalWmsData.dimensions=[]] WMS Dimensions. Default is `[]`
* @param {LayerTimeConfig | null} [externalWmsData.timeConfig=null] Time series config (if
* available). Default is `null`
* @param {Number} [externalWmsData.currentYear=null] Current year of the time series config to
* use. This parameter is needed as it is set in the URL while the timeConfig parameter is not
* yet available and parse later on from the GetCapabilities. Default is `null`
* @throws InvalidLayerDataError if no `externalWmsData` is given or if it is invalid
*/
constructor(externalWmsData) {
Expand All @@ -67,6 +97,9 @@ export default class ExternalWMSLayer extends ExternalLayer {
availableProjections = [],
hasTooltip = false,
getFeatureInfoCapability = null,
dimensions = [],
timeConfig = null,
currentYear = null,
} = externalWmsData
super({
name,
Expand All @@ -83,8 +116,11 @@ export default class ExternalWMSLayer extends ExternalLayer {
availableProjections,
hasTooltip,
getFeatureInfoCapability,
timeConfig,
currentYear,
})
this.wmsVersion = wmsVersion
this.format = format
this.dimensions = dimensions
}
}
2 changes: 1 addition & 1 deletion src/api/layers/ExternalWMTSLayer.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default class ExternalWMTSLayer extends ExternalLayer {
* identifiers. Default is `[]`
* @param {[WMTSDimension]} [externalWmtsData.dimensions=[]] WMTS tile dimensions. Default is
* `[]`
* @param {LayerTimeConfig | null} [externalLayerData.timeConfig=null] Time series config (if
* @param {LayerTimeConfig | null} [externalWmtsData.timeConfig=null] Time series config (if
* available). Default is `null`
* @param {Number} [externalWmtsData.currentYear=null] Current year of the time series config to
* use. This parameter is needed as it is set in the URL while the timeConfig parameter is not
Expand Down
86 changes: 84 additions & 2 deletions src/api/layers/WMSCapabilitiesParser.class.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { range } from 'lodash'
import { WMSCapabilities } from 'ol/format'
import proj4 from 'proj4'

import { LayerAttribution } from '@/api/layers/AbstractLayer.class'
import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class'
import { LayerLegend } from '@/api/layers/ExternalLayer.class'
import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class'
import ExternalWMSLayer, { WMSDimension } from '@/api/layers/ExternalWMSLayer.class'
import { CapabilitiesError } from '@/api/layers/layers-external.api'
import LayerTimeConfig from '@/api/layers/LayerTimeConfig.class'
import LayerTimeConfigEntry from '@/api/layers/LayerTimeConfigEntry.class'
import { WMS_SUPPORTED_VERSIONS } from '@/config'
import allCoordinateSystems, { WGS84 } from '@/utils/coordinates/coordinateSystems'
import log from '@/utils/logging'
Expand Down Expand Up @@ -148,19 +151,29 @@ export default class WMSCapabilitiesParser {
* @param {CoordinateSystem} projection Projection currently used by the application
* @param {number} opacity
* @param {boolean} visible
* @param {Number | null} [currentYear=null] Current year to select for the time config. Only
* needed when a time config is present a year is pre-selected in the url parameter. Default
* is `null`
* @param {boolean} ignoreError Don't throw exception in case of error, but return a default
* value or null
* @returns {[ExternalWMSLayer | ExternalGroupOfLayers]} List of
* ExternalWMSLayer|ExternalGroupOfLayers objects
*/
getAllExternalLayerObjects(projection, opacity = 1, visible = true, ignoreError = true) {
getAllExternalLayerObjects(
projection,
opacity = 1,
visible = true,
currentYear = null,
ignoreError = true
) {
return this.Capability.Layer.Layer.map((layer) =>
this._getExternalLayerObject(
layer,
[this.Capability.Layer],
projection,
opacity,
visible,
currentYear,
ignoreError
)
).filter((layer) => !!layer)
Expand All @@ -186,6 +199,7 @@ export default class WMSCapabilitiesParser {
legends,
queryable,
availableProjections,
dimensions,
} = this._getLayerAttributes(layer, parents, projection, ignoreError)

if (!layerId) {
Expand Down Expand Up @@ -239,6 +253,8 @@ export default class WMSCapabilitiesParser {
hasTooltip: queryable,
getFeatureInfoCapability: this.getFeatureInfoCapability(ignoreError),
currentYear,
dimensions: dimensions,
timeConfig: this._getTimeConfig(layerId, dimensions),
})
}

Expand Down Expand Up @@ -306,6 +322,7 @@ export default class WMSCapabilitiesParser {
legends: this._getLayerLegends(layerId, layer),
queryable: layer.queryable,
availableProjections,
dimensions: this._getDimensions(layerId, layer),
}
}

Expand Down Expand Up @@ -406,4 +423,69 @@ export default class WMSCapabilitiesParser {
)
.flat()
}

_parseDimesionValues(layerId, rawValues) {
const parseYear = (value) => {
const date = new Date(value)
if (!isNaN(date)) {
return date.getFullYear()
}
return null
}
return rawValues
.split(',')
.map((v) => {
if (v.includes('/')) {
const [min, max, res] = v.split('/')
const minYear = parseYear(min)
const maxYear = parseYear(max)
if (minYear === null || maxYear === null) {
log.warn(
`Unsupported dimension min/max value "${min}"/"${max}" for layer ${layerId}`
)
return null
}
let step = 1
const periodMatch = /P(\d+)Y/.exec(res)
if (periodMatch) {
step = periodMatch[1]
} else {
log.warn(
`Unsupported dimension resolution "${res}" for layer ${layerId}, fallback to 1 year period`
)
}
return range(minYear, maxYear, step)
}
return v
})
.flat()
.filter((v) => !!v)
.map((v) => `${v}`)
}

_getDimensions(layerId, layer) {
return (
layer.Dimension?.map(
(d) =>
new WMSDimension(
d.name,
d.default,
this._parseDimesionValues(layerId, d.values ?? ''),
{
current: d.current ?? false,
}
)
) ?? []
)
}

_getTimeConfig(layerId, dimensions) {
const timeDimension = dimensions.find((d) => d.id.toLowerCase() === 'time')
if (!timeDimension) {
return null
}
const timeEntries =
timeDimension.values?.map((value) => new LayerTimeConfigEntry(value)) ?? []
return new LayerTimeConfig(timeDimension.default ?? null, timeEntries)
}
}

0 comments on commit 2cd1a38

Please sign in to comment.