Skip to content

Commit

Permalink
Merge pull request #859 from geoadmin/develop
Browse files Browse the repository at this point in the history
New Release v1.19.0 - #minor
  • Loading branch information
ltshb authored May 23, 2024
2 parents 891f7d0 + 333dba9 commit 059814a
Show file tree
Hide file tree
Showing 28 changed files with 293 additions and 63 deletions.
18 changes: 17 additions & 1 deletion src/api/features/features.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import LayerFeature from '@/api/features/LayerFeature.class'
import ExternalLayer from '@/api/layers/ExternalLayer.class'
import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class'
import GeoAdminLayer from '@/api/layers/GeoAdminLayer.class'
import { YEAR_TO_DESCRIBE_ALL_OR_CURRENT_DATA } from '@/api/layers/LayerTimeConfigEntry.class'
import { API_BASE_URL, DEFAULT_FEATURE_COUNT_SINGLE_POINT } from '@/config'
import allCoordinateSystems, { LV95 } from '@/utils/coordinates/coordinateSystems'
import { projExtent } from '@/utils/coordinates/coordinateUtils'
Expand All @@ -26,6 +27,18 @@ const PLAIN_TEXT_TYPE = 'text/plain'
*/
const DEFAULT_FEATURE_IDENTIFICATION_TOLERANCE = 10

function getApi3TimeInstantParam(layer) {
// The api3 identify endpoint timeInstant parameter doesn't support the "all" and "current"
// timestamp therefore we need to set it to null in this case.
if (
layer.timeConfig?.currentYear &&
layer.timeConfig.currentYear !== YEAR_TO_DESCRIBE_ALL_OR_CURRENT_DATA
) {
return layer.timeConfig.currentYear
}
return null
}

/**
* Error when building or requesting an external layer's getFeatureInfo endpoint
*
Expand Down Expand Up @@ -138,7 +151,7 @@ export async function identifyOnGeomAdminLayer({
limit: featureCount,
tolerance: DEFAULT_FEATURE_IDENTIFICATION_TOLERANCE,
returnGeometry: true,
timeInstant: layer.timeConfig?.currentYear ?? null,
timeInstant: getApi3TimeInstantParam(layer),
lang: lang,
offset,
},
Expand Down Expand Up @@ -340,6 +353,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
4 changes: 4 additions & 0 deletions src/api/feedback.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { createShortLink } from '@/api/shortlink.api'
import { API_SERVICES_BASE_URL, APP_VERSION } from '@/config'
import log from '@/utils/logging'

/** Maximum size allowed by the backend, can be used to do validation up front */
export const ATTACHMENT_MAX_SIZE = 10 * 1024 * 1024
export const KML_MAX_SIZE = 2 * 1024 * 1024

/**
* @param {String} subject Mandatory
* @param {String} text Mandatory
Expand Down
9 changes: 0 additions & 9 deletions src/api/layers/ExternalLayer.class.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import AbstractLayer, { LayerAttribution } from '@/api/layers/AbstractLayer.class'
import { InvalidLayerDataError } from '@/api/layers/InvalidLayerData.error'
import { WGS84 } from '@/utils/coordinates/coordinateSystems'
import log from '@/utils/logging'

/**
* Information required to create a GetFeatureInfo request to this external WM(T)S server. This
Expand Down Expand Up @@ -148,13 +146,6 @@ export default class ExternalLayer extends AbstractLayer {
this.extent = extent
this.legends = legends
this.availableProjections = [...availableProjections]
if (this.availableProjections.length === 0) {
log.error(
'No supported projection found within external layer config, falling back to WGS84',
externalLayerData
)
this.availableProjections.push(WGS84)
}
this.getFeatureInfoCapability = getFeatureInfoCapability
this.currentYear = currentYear
if (currentYear && this.timeConfig) {
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
1 change: 1 addition & 0 deletions src/api/layers/LayerTimeConfigEntry.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { isTimestampYYYYMMDD } from '@/utils/numberUtils'
* @type {number}
*/
export const YEAR_TO_DESCRIBE_ALL_OR_CURRENT_DATA = 9999

/**
* Timestamp to describe "all data" for time enabled WMS layer
*
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)
}
}
3 changes: 2 additions & 1 deletion src/api/print.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,13 +366,14 @@ async function transformOlMapToPrintParams(olMap, config) {
},
})
}

const now = i18n.global.d(new Date(), 'datetime', i18n.global.locale)
const spec = {
attributes: {
map: encodedMap,
copyright: attributionsOneLine,
url: shortLink,
qrimage: qrCodeUrl,
printDate: now,
},
format: 'pdf',
layout: layout.name,
Expand Down
4 changes: 2 additions & 2 deletions src/modules/drawing/components/DrawingHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ function onClose() {
<TextTruncate class="drawing-header-title px-2" data-cy="drawing-header-title">
{{ i18n.t(drawingTitle) }}
</TextTruncate>
<diV>
<div>
<!-- This empty div is needed to keep the title in the middle of the header it uses
the d-flex justify-content-between functionality -->
</diV>
</div>
</div>
</template>

Expand Down
27 changes: 26 additions & 1 deletion src/modules/i18n/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,43 @@ import rm from './locales/rm.json'

export const languages = { de, fr, it, en, rm }

const locales = Object.entries(languages).reduce((obj, entry) => {
const key = langToLocal(entry[0])
obj[key] = entry[1]
return obj
}, {})

export function langToLocal(lang) {
return ['de', 'fr', 'it', 'rm'].includes(lang) ? `${lang}-CH` : lang
}

// detecting navigator's locale as the default language
// (if it is a language served by this app)
let matchedLanguage = null
if (navigator.languages) {
// we keep the first match we found
matchedLanguage = navigator.languages.find((lang) => lang in languages)
matchedLanguage = navigator.languages.find((lang) => Object.keys(locales).includes(lang))
}

const datetimeFormats = Object.keys(locales).reduce((obj, key) => {
obj[key] = {
date: { year: 'numeric', month: 'numeric', day: 'numeric' },
datetime: {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
},
}
return obj
}, {})

const i18n = createI18n({
locale: matchedLanguage || 'en', // default locale
messages: languages,
legacy: false,
datetimeFormats,
})

export default i18n
Loading

0 comments on commit 059814a

Please sign in to comment.