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

PB-583 : use backend service to get LV03 <> LV95 conversion right #887

Merged
merged 1 commit into from
Jun 10, 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
49 changes: 49 additions & 0 deletions src/api/lv03Reframe.api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import axios from 'axios'
import proj4 from 'proj4'

import { LV03, LV95 } from '@/utils/coordinates/coordinateSystems'
import log from '@/utils/logging'

const REFRAME_BASE_URL = 'https://geodesy.geo.admin.ch/reframe/'

/**
* Re-frames LV95 coordinate taking all LV03 -> LV95 deformation into account (they are not stable,
* so using "simple" proj4 matrices isn't enough to get a very accurate result)
*
* @param {[Number, Number]} lv95coordinate LV95 coordinate that we want expressed in LV03
* @returns {Promise<[Number, Number]>} Input LV95 coordinate re-framed by the backend service into
* LV03 coordinate
* @see https://www.swisstopo.admin.ch/en/rest-api-geoservices-reframe-web
* @see https://github.com/geoadmin/mf-geoadmin3/blob/master/src/components/ReframeService.js
*/
export default function reframe(lv95coordinate) {
return new Promise((resolve, reject) => {
if (!Array.isArray(lv95coordinate) || lv95coordinate.length !== 2) {
reject(new Error('lv95coordinate must be an array with length of 2'))
}
axios({
method: 'GET',
url: `${REFRAME_BASE_URL}lv95tolv03`,
params: {
easting: lv95coordinate[0],
northing: lv95coordinate[1],
},
})
.then((response) => {
if (response.data?.coordinates) {
resolve(response.data.coordinates)
} else {
log.error(
'Error while re-framing coordinate',
lv95coordinate,
'fallback to proj4'
)
resolve(proj4(LV95.epsg, LV03.epsg, lv95coordinate))
}
})
.catch((error) => {
log.error('Error while re-framing coordinate', lv95coordinate, error)
reject(error)
})
Comment on lines +45 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't use the fallback as well here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because if wrong coordinate are given as input, we will end up here (among other cases). And I didn't want to run a proj4 call on malformed coordinate, as it would require another try...catch wrapping

})
}
20 changes: 18 additions & 2 deletions src/modules/map/components/LocationPopupPosition.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { computed, onMounted, ref, toRefs, watch } from 'vue'
import { useI18n } from 'vue-i18n'

import { requestHeight } from '@/api/height.api'
import reframe from '@/api/lv03Reframe.api'
import { registerWhat3WordsLocation } from '@/api/what3words.api'
import CoordinateCopySlot from '@/utils/components/CoordinateCopySlot.vue'
import {
Expand All @@ -15,7 +16,7 @@ import {
UTMFormat,
WGS84Format,
} from '@/utils/coordinates/coordinateFormat'
import { WGS84 } from '@/utils/coordinates/coordinateSystems'
import { LV03, LV95, WGS84 } from '@/utils/coordinates/coordinateSystems'
import log from '@/utils/logging'

const props = defineProps({
Expand All @@ -38,6 +39,7 @@ const props = defineProps({
})
const { coordinate, clickInfo, projection, currentLang } = toRefs(props)

const lv03Coordinate = ref(null)
const what3Words = ref(null)
const height = ref(null)

Expand Down Expand Up @@ -69,13 +71,15 @@ const heightInMeter = computed(() => {

onMounted(() => {
if (clickInfo.value) {
updateLV03Coordinate()
updateWhat3Word()
updateHeight()
}
})

watch(clickInfo, (newClickInfo) => {
if (newClickInfo) {
updateLV03Coordinate()
updateWhat3Word()
updateHeight()
}
Expand All @@ -84,6 +88,16 @@ watch(currentLang, () => {
updateWhat3Word()
})

async function updateLV03Coordinate() {
try {
const lv95coordinate = proj4(projection.value.epsg, LV95.epsg, coordinate.value)
lv03Coordinate.value = await reframe(lv95coordinate)
} catch (error) {
log.error('Failed to retrieve LV03 coordinate', error)
lv03Coordinate.value = null
}
}

async function updateWhat3Word() {
try {
what3Words.value = await registerWhat3WordsLocation(
Expand Down Expand Up @@ -119,9 +133,11 @@ async function updateHeight() {
</a>
</CoordinateCopySlot>
<CoordinateCopySlot
v-if="lv03Coordinate"
identifier="location-popup-lv03"
:value="coordinate"
:value="lv03Coordinate"
:coordinate-format="LV03Format"
:coordinate-projection="LV03"
>
<a :href="i18n.t('contextpopup_lv03_url')" target="_blank">
{{ LV03Format.label }}
Expand Down
10 changes: 8 additions & 2 deletions src/utils/components/CoordinateCopySlot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'

import { CoordinateFormat } from '@/utils/coordinates/coordinateFormat'
import CoordinateSystem from '@/utils/coordinates/CoordinateSystem.class'
import log from '@/utils/logging'

const props = defineProps({
Expand All @@ -28,16 +29,21 @@ const props = defineProps({
type: CoordinateFormat,
default: null,
},
coordinateProjection: {
type: CoordinateSystem,
default: null,
},
})
const { identifier, value, extraValue, resetDelay, coordinateFormat } = toRefs(props)
const { identifier, value, extraValue, resetDelay, coordinateFormat, coordinateProjection } =
toRefs(props)

const copyButton = ref(null)
const copied = ref(false)

const i18n = useI18n()

const store = useStore()
const projection = computed(() => store.state.position.projection)
const projection = computed(() => coordinateProjection.value ?? store.state.position.projection)
const lang = computed(() => store.state.i18n.lang)

const buttonIcon = computed(() => {
Expand Down
6 changes: 5 additions & 1 deletion tests/cypress/tests-e2e/mouseposition.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ describe('Test mouse position and interactions', () => {
})
it('shows the LocationPopUp when rightclick occurs on the map', () => {
let shortUrl = 'https://s.geo.admin.ch/0000000'

const fakeLV03Coordinate = [1234.56, 7890.12]
cy.intercept('**/lv95tolv03**', { coordinates: fakeLV03Coordinate }).as('reframe')
cy.intercept(/^http[s]?:\/\/(sys-s\.\w+\.bgdi\.ch|s\.geo\.admin\.ch)\//, {
body: { shorturl: shortUrl, success: true },
}).as('shortlink')
Expand Down Expand Up @@ -163,10 +166,11 @@ describe('Test mouse position and interactions', () => {
.then(checkXY(...centerLV95))
cy.log('it shows coordinates, correctly re-projected into LV95, in the popup')

cy.wait('@reframe')
cy.get('[data-cy="location-popup-lv03"]')
.invoke('text')
.then(parseLV)
.then(checkXY(...centerLV03))
.then(checkXY(...fakeLV03Coordinate))
cy.log('it shows coordinates, correctly re-projected into LV03, in the popup')

cy.get('[data-cy="location-popup-wgs84"]').contains(
Expand Down
Loading