Skip to content

Commit

Permalink
Merge pull request #1132 from geoadmin/develop
Browse files Browse the repository at this point in the history
New Release v1.48.0 - #minor
  • Loading branch information
pakb authored Nov 21, 2024
2 parents c0ed7cf + 86da74d commit 5c3b8c4
Show file tree
Hide file tree
Showing 198 changed files with 6,927 additions and 3,260 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = {
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'mocha/no-exclusive-tests': 'error', // Do not allow it.only() tests
eqeqeq: ['error', 'always'],
},
globals: {
VITE_ENVIRONMENT: true,
Expand Down
2,030 changes: 952 additions & 1,078 deletions package-lock.json

Large diffs are not rendered by default.

52 changes: 28 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,99 +44,103 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/vue-fontawesome": "^3.0.8",
"@geoblocks/cesium-compass": "^0.5.0",
"@geoblocks/mapfishprint": "^0.2.16",
"@geoblocks/cesium-compass": "^0.5.1",
"@geoblocks/mapfishprint": "^0.2.17",
"@geoblocks/ol-maplibre-layer": "^1.0.1",
"@ivanv/vue-collapse-transition": "^1.0.2",
"@mapbox/togeojson": "^0.16.2",
"@popperjs/core": "^2.11.8",
"@turf/area": "^7.1.0",
"@turf/bbox": "^7.1.0",
"@turf/boolean-contains": "^7.1.0",
"@turf/boolean-intersects": "^7.1.0",
"@turf/boolean-point-in-polygon": "^7.1.0",
"@turf/buffer": "^7.1.0",
"@turf/centroid": "^7.1.0",
"@turf/circle": "^7.1.0",
"@turf/distance": "^7.1.0",
"@turf/explode": "^7.1.0",
"@turf/helpers": "^7.1.0",
"@turf/nearest-point": "^7.1.0",
"@turf/point-to-line-distance": "^7.1.0",
"@turf/simplify": "^7.1.0",
"animate.css": "^4.1.1",
"axios": "^1.7.5",
"axios": "^1.7.7",
"bootstrap": "^5.3.3",
"cesium": "^1.120.0",
"cesium": "1.119.0",
"chart.js": "^4.4.4",
"chartjs-plugin-zoom": "^2.0.1",
"dompurify": "^3.1.6",
"dompurify": "^3.1.7",
"file-saver": "^2.0.5",
"form-data": "^4.0.0",
"geographiclib-geodesic": "^2.1.1",
"geotiff": "^2.1.3",
"hammerjs": "^2.0.8",
"jquery": "^3.7.1",
"jszip": "^3.10.1",
"liang-barsky": "^1.0.5",
"liang-barsky": "^1.0.12",
"lodash": "^4.17.21",
"maplibre-gl": "^4.6.0",
"ol": "^10.1.0",
"maplibre-gl": "^4.7.1",
"ol": "^10.2.1",
"pako": "^2.1.0",
"print-js": "^1.6.0",
"proj4": "^2.12.0",
"proj4": "^2.12.1",
"reproject": "^1.2.7",
"sortablejs": "^1.15.2",
"sortablejs": "^1.15.3",
"tippy.js": "^6.3.7",
"vue": "^3.4.38",
"vue-chartjs": "^5.3.1",
"vue-i18n": "^9.14.0",
"vue-router": "^4.4.3",
"vue-i18n": "^9.14.1",
"vue-router": "^4.4.5",
"vue3-social-sharing": "^1.1.1",
"vuex": "^4.1.0"
},
"devDependencies": {
"@4tw/cypress-drag-drop": "^2.2.5",
"@cypress/vite-dev-server": "^5.1.1",
"@cypress/vite-dev-server": "^5.2.0",
"@cypress/vue": "^6.0.1",
"@nuintun/qrcode": "^4.1.5",
"@prettier/plugin-xml": "^3.4.1",
"@rushstack/eslint-patch": "^1.10.4",
"@types/jsdom": "^21.1.7",
"@types/node": "^18.19.43",
"@types/node": "^18.19.54",
"@vitejs/plugin-basic-ssl": "^1.1.0",
"@vitejs/plugin-vue": "^5.1.2",
"@vitejs/plugin-vue": "^5.1.4",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
"axios-retry": "^4.5.0",
"chai": "^5.1.1",
"cypress": "^13.13.3",
"cypress": "^13.15.0",
"cypress-browser-permissions": "^1.1.0",
"cypress-real-events": "^1.13.0",
"cypress-recurse": "^1.35.3",
"cypress-vite": "^1.5.0",
"cypress-wait-until": "^3.0.2",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"eslint": "^8.57.1",
"eslint-plugin-cypress": "^3.5.0",
"eslint-plugin-markdownlint": "^0.6.0",
"eslint-plugin-mocha": "^10.5.0",
"eslint-plugin-prettier-vue": "^5.0.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-vue": "^9.27.0",
"eslint-plugin-vue": "^9.28.0",
"git-describe": "^4.1.1",
"googleapis": "^142.0.0",
"jsdom": "^25.0.0",
"jsdom": "^25.0.1",
"mime-types": "^2.1.35",
"mocha-junit-reporter": "^2.2.1",
"prettier": "^3.3.3",
"prettier-plugin-jsdoc": "^1.3.0",
"rimraf": "^5.0.10",
"sass": "1.77.6",
"start-server-and-test": "^2.0.5",
"typescript": "^5.5.4",
"vite": "^5.4.2",
"start-server-and-test": "^2.0.8",
"typescript": "^5.6.2",
"vite": "^5.4.8",
"vite-node": "^2.0.5",
"vite-plugin-static-copy": "^1.0.6",
"vitest": "^2.0.5",
"vue-tsc": "^2.0.29",
"vitest": "^2.1.1",
"vue-tsc": "^2.1.6",
"write-yaml-file": "^5.0.0",
"yargs": "^17.7.2"
},
Expand Down
33 changes: 4 additions & 29 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
import ErrorWindow from '@/utils/components/ErrorWindow.vue'
import WarningWindow from '@/utils/components/WarningWindow.vue'
import FeedbackPopup from '@/utils/components/FeedbackPopup.vue'
import debounce from '@/utils/debounce'
const withOutline = ref(false)
Expand All @@ -21,17 +20,8 @@ const i18n = useI18n()
const dispatcher = { dispatcher: 'App.vue' }
let debouncedOnResize
const error = computed(() => {
if (store.state.ui.errors.size > 0) {
return store.state.ui.errors.values().next().value
}
return null
})
const warning = computed(() => {
if (store.state.ui.warnings.size > 0) {
return store.state.ui.warnings.values().next().value
}
return null
const showFeedbackPopup = computed(() => {
return store.state.ui.errors.size + store.state.ui.warnings.size > 0
})
onMounted(() => {
Expand Down Expand Up @@ -65,22 +55,7 @@ function refreshPageTitle() {
@pointerdown="withOutline = false"
>
<router-view />
<ErrorWindow
v-if="error"
title="error"
@close="store.dispatch('removeError', { error, ...dispatcher })"
>
<div>
{{ i18n.t(error.msg, error.params) }}
</div>
</ErrorWindow>
<WarningWindow
v-if="warning"
title="warning"
@close="store.dispatch('removeWarning', { warning, ...dispatcher })"
>
<div>{{ i18n.t(warning.msg, warning.params) }}</div>
</WarningWindow>
<FeedbackPopup v-if="showFeedbackPopup"> </FeedbackPopup>
</div>
</template>

Expand Down
7 changes: 6 additions & 1 deletion src/api/__tests__/print.api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'chai'
import { describe, it } from 'vitest'

import { PrintLayout, PrintLayoutAttribute } from '@/api/print.api.js'
import { PRINT_DPI_COMPENSATION } from '@/config/print.config'
import { MIN_PRINT_SCALE_SIZE, PRINT_DPI_COMPENSATION } from '@/config/print.config'
import { adjustWidth } from '@/utils/styleUtils'

describe('Print API unit tests', () => {
Expand Down Expand Up @@ -79,6 +79,11 @@ describe('Print API unit tests', () => {
(100 * PRINT_DPI_COMPENSATION) / 254,
1 / 254
)
// we check that the minimum print scale size is correctly enforced
expect(adjustWidth(MIN_PRINT_SCALE_SIZE / 1000, 254)).to.be.closeTo(
MIN_PRINT_SCALE_SIZE,
MIN_PRINT_SCALE_SIZE / 2
)
})
})
})
23 changes: 23 additions & 0 deletions src/api/errorQueues.api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ErrorMessage from '@/utils/ErrorMessage.class'

export function getStandardErrorMessage(query, urlParamName) {
return new ErrorMessage('url_parameter_error', {
param: urlParamName,
value: query,
})
}

/**
* Return the standard feedback for most parameters given in the URL: if the query is validated, it
* can proceed and be set in the store.
*
* @param {any} query The value of the URL parameter given
* @param {Boolean} isValid Is the value valid or not
* @returns
*/
export function getStandardValidationResponse(query, isValid, urlParamName) {
return {
valid: isValid,
errors: isValid ? null : [getStandardErrorMessage(query, urlParamName)],
}
}
84 changes: 58 additions & 26 deletions src/api/features/features.api.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import axios from 'axios'
import { WMSGetFeatureInfo } from 'ol/format'
import GeoJSON from 'ol/format/GeoJSON'
import proj4 from 'proj4'

import LayerFeature from '@/api/features/LayerFeature.class'
import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class'
import ExternalLayer from '@/api/layers/ExternalLayer.class'
import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class'
import GeoAdminLayer from '@/api/layers/GeoAdminLayer.class'
Expand All @@ -14,15 +14,15 @@ import {
import { getApi3BaseUrl } from '@/config/baseUrl.config'
import { DEFAULT_FEATURE_COUNT_SINGLE_POINT } from '@/config/map.config'
import allCoordinateSystems, { LV95 } from '@/utils/coordinates/coordinateSystems'
import { projExtent } from '@/utils/coordinates/coordinateUtils'
import { createPixelExtentAround } from '@/utils/extentUtils'
import { createPixelExtentAround, projExtent } from '@/utils/extentUtils'
import { getGeoJsonFeatureCoordinates, reprojectGeoJsonData } from '@/utils/geoJsonUtils'
import log from '@/utils/logging'

const GET_FEATURE_INFO_FAKE_VIEWPORT_SIZE = 100

const APPLICATION_JSON_TYPE = 'application/json'
const APPLICATION_GML_3_TYPE = 'application/vnd.ogc.gml'
const APPLICATION_OGC_WMS_XML_TYPE = 'application/vnd.ogc.wms_xml'
const PLAIN_TEXT_TYPE = 'text/plain'

/**
Expand Down Expand Up @@ -237,6 +237,7 @@ async function identifyOnExternalLayer(config) {
}
// deciding on which projection we should land to ask the WMS server (the current map projection might not be supported)
let requestProjection = projection
let requestedCoordinate = coordinate
if (!requestProjection) {
throw new GetFeatureInfoError('Missing projection to build a getFeatureInfo request')
}
Expand All @@ -257,9 +258,13 @@ async function identifyOnExternalLayer(config) {
`No common projection found with external WMS provider, possible projection were ${layer.availableProjections.map((proj) => proj.epsg).join(', ')}`
)
}
if (requestProjection.epsg !== projection.epsg) {
// If we use different projection, we also need to project out initial coordinate
requestedCoordinate = proj4(projection.epsg, requestProjection.epsg, coordinate)
}
if (layer instanceof ExternalWMSLayer) {
return await identifyOnExternalWmsLayer({
coordinate,
coordinate: requestedCoordinate,
projection: requestProjection,
resolution,
layer,
Expand All @@ -268,34 +273,57 @@ async function identifyOnExternalLayer(config) {
tolerance,
outputProjection: projection,
})
} else if (layer instanceof ExternalGroupOfLayers) {
// firing one request per sub-layer
const allRequests = [
layer.layers.map((subLayer) =>
identifyOnExternalLayer({
...config,
layer: subLayer,
})
),
]
const allResponses = await Promise.allSettled(allRequests)
// logging any error
allResponses
.filter((response) => response.status !== 'fulfilled')
.forEach((failedResponse) => {
log.error('Error while identify an external sub-layer', failedResponse)
})
return allResponses
.filter((response) => response.status === 'fulfilled' && response.value)
.map((response) => response.value)
.flat()
} else {
throw new GetFeatureInfoError(
`Unsupported external layer type to build getFeatureInfo request: ${layer.type}`
)
}
}

// Parse OGC WMS XML response to GeoJSON
function parseOGCWMSFeatureInfoResponse(response) {
const parser = new DOMParser()
const xmlDoc = parser.parseFromString(response, 'text/xml')

// Check for parsing errors
const parserError = xmlDoc.getElementsByTagName('parsererror')
if (parserError.length > 0) {
console.error('Error parsing OGC WMS XML response')
return null
}

const features = []
const fieldElements = xmlDoc.getElementsByTagName('FIELDS')

for (let i = 0; i < fieldElements.length; i++) {
const fieldElement = fieldElements[i]
const properties = {}

// Extract attributes from the FIELDS element
for (let j = 0; j < fieldElement.attributes.length; j++) {
const attribute = fieldElement.attributes[j]
properties[attribute.name] = attribute.value
}

// Create a GeoJSON feature
const feature = {
type: 'Feature',
geometry: null, // Assuming geometry is not provided in the response
properties: properties,
}

features.push(feature)
}

// Create a GeoJSON FeatureCollection
const geojson = {
type: 'FeatureCollection',
features: features,
}

return geojson
}

/**
* Runs a getFeatureInfo request on the backend of an external WMS layer.
*
Expand Down Expand Up @@ -340,7 +368,6 @@ async function identifyOnExternalWmsLayer(config) {
coordinate,
projection,
resolution,
rounded: true,
})
if (!requestExtent) {
throw new GetFeatureInfoError('Unable to build required request extent')
Expand All @@ -352,6 +379,8 @@ async function identifyOnExternalWmsLayer(config) {
// if JSON isn't supported, we check if GML3 is supported
if (layer.getFeatureInfoCapability.formats?.includes(APPLICATION_GML_3_TYPE)) {
outputFormat = APPLICATION_GML_3_TYPE
} else if (layer.getFeatureInfoCapability.formats?.includes(APPLICATION_OGC_WMS_XML_TYPE)) {
outputFormat = APPLICATION_OGC_WMS_XML_TYPE
} else {
// if neither JSON nor GML3 are supported, we will ask for plain text
outputFormat = PLAIN_TEXT_TYPE
Expand Down Expand Up @@ -423,6 +452,9 @@ async function identifyOnExternalWmsLayer(config) {
// nothing to do other than extracting the data
features = getFeatureInfoResponse.data.features
break
case APPLICATION_OGC_WMS_XML_TYPE:
features = parseOGCWMSFeatureInfoResponse(getFeatureInfoResponse.data)?.features
break
case PLAIN_TEXT_TYPE:
// TODO : implement plain text parsing
log.error('Plain text parsing not yet implemented')
Expand Down
Loading

0 comments on commit 5c3b8c4

Please sign in to comment.