Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Release v1.19.0 - #minor #859

Merged
merged 15 commits into from
May 23, 2024
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
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
Loading