Skip to content

Commit 13335fe

Browse files
authored
Merge pull request #891 from geoadmin/develop
New Release v1.22.0 - #minor
2 parents ded6e47 + 2f1ab9f commit 13335fe

26 files changed

+513
-376
lines changed

src/api/lv03Reframe.api.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import axios from 'axios'
2+
import proj4 from 'proj4'
3+
4+
import { LV03, LV95 } from '@/utils/coordinates/coordinateSystems'
5+
import log from '@/utils/logging'
6+
7+
const REFRAME_BASE_URL = 'https://geodesy.geo.admin.ch/reframe/'
8+
9+
/**
10+
* Re-frames LV95 coordinate taking all LV03 -> LV95 deformation into account (they are not stable,
11+
* so using "simple" proj4 matrices isn't enough to get a very accurate result)
12+
*
13+
* @param {[Number, Number]} lv95coordinate LV95 coordinate that we want expressed in LV03
14+
* @returns {Promise<[Number, Number]>} Input LV95 coordinate re-framed by the backend service into
15+
* LV03 coordinate
16+
* @see https://www.swisstopo.admin.ch/en/rest-api-geoservices-reframe-web
17+
* @see https://github.com/geoadmin/mf-geoadmin3/blob/master/src/components/ReframeService.js
18+
*/
19+
export default function reframe(lv95coordinate) {
20+
return new Promise((resolve, reject) => {
21+
if (!Array.isArray(lv95coordinate) || lv95coordinate.length !== 2) {
22+
reject(new Error('lv95coordinate must be an array with length of 2'))
23+
}
24+
axios({
25+
method: 'GET',
26+
url: `${REFRAME_BASE_URL}lv95tolv03`,
27+
params: {
28+
easting: lv95coordinate[0],
29+
northing: lv95coordinate[1],
30+
},
31+
})
32+
.then((response) => {
33+
if (response.data?.coordinates) {
34+
resolve(response.data.coordinates)
35+
} else {
36+
log.error(
37+
'Error while re-framing coordinate',
38+
lv95coordinate,
39+
'fallback to proj4'
40+
)
41+
resolve(proj4(LV95.epsg, LV03.epsg, lv95coordinate))
42+
}
43+
})
44+
.catch((error) => {
45+
log.error('Error while re-framing coordinate', lv95coordinate, error)
46+
reject(error)
47+
})
48+
})
49+
}

src/modules/map/components/LocationPopupPosition.vue

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { computed, onMounted, ref, toRefs, watch } from 'vue'
66
import { useI18n } from 'vue-i18n'
77
88
import { requestHeight } from '@/api/height.api'
9+
import reframe from '@/api/lv03Reframe.api'
910
import { registerWhat3WordsLocation } from '@/api/what3words.api'
1011
import CoordinateCopySlot from '@/utils/components/CoordinateCopySlot.vue'
1112
import {
@@ -15,7 +16,7 @@ import {
1516
UTMFormat,
1617
WGS84Format,
1718
} from '@/utils/coordinates/coordinateFormat'
18-
import { WGS84 } from '@/utils/coordinates/coordinateSystems'
19+
import { LV03, LV95, WGS84 } from '@/utils/coordinates/coordinateSystems'
1920
import log from '@/utils/logging'
2021
2122
const props = defineProps({
@@ -38,6 +39,7 @@ const props = defineProps({
3839
})
3940
const { coordinate, clickInfo, projection, currentLang } = toRefs(props)
4041
42+
const lv03Coordinate = ref(null)
4143
const what3Words = ref(null)
4244
const height = ref(null)
4345
@@ -69,13 +71,15 @@ const heightInMeter = computed(() => {
6971
7072
onMounted(() => {
7173
if (clickInfo.value) {
74+
updateLV03Coordinate()
7275
updateWhat3Word()
7376
updateHeight()
7477
}
7578
})
7679
7780
watch(clickInfo, (newClickInfo) => {
7881
if (newClickInfo) {
82+
updateLV03Coordinate()
7983
updateWhat3Word()
8084
updateHeight()
8185
}
@@ -84,6 +88,16 @@ watch(currentLang, () => {
8488
updateWhat3Word()
8589
})
8690
91+
async function updateLV03Coordinate() {
92+
try {
93+
const lv95coordinate = proj4(projection.value.epsg, LV95.epsg, coordinate.value)
94+
lv03Coordinate.value = await reframe(lv95coordinate)
95+
} catch (error) {
96+
log.error('Failed to retrieve LV03 coordinate', error)
97+
lv03Coordinate.value = null
98+
}
99+
}
100+
87101
async function updateWhat3Word() {
88102
try {
89103
what3Words.value = await registerWhat3WordsLocation(
@@ -119,9 +133,11 @@ async function updateHeight() {
119133
</a>
120134
</CoordinateCopySlot>
121135
<CoordinateCopySlot
136+
v-if="lv03Coordinate"
122137
identifier="location-popup-lv03"
123-
:value="coordinate"
138+
:value="lv03Coordinate"
124139
:coordinate-format="LV03Format"
140+
:coordinate-projection="LV03"
125141
>
126142
<a :href="i18n.t('contextpopup_lv03_url')" target="_blank">
127143
{{ LV03Format.label }}

src/modules/map/components/MapPopover.vue

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
cssDrawingMobileToolbarHeight,
1818
cssFooterHeight,
1919
cssHeaderHeight,
20+
cssTimeSliderBarHeight,
21+
cssTimeSliderDropdownHeight,
2022
} from '@/scss/exports'
2123
import { useMovableElement } from '@/utils/composables/useMovableElement.composable'
2224
import { useTippyTooltip } from '@/utils/composables/useTippyTooltip'
@@ -63,8 +65,10 @@ const store = useStore()
6365
// can adapt the limits for the floating tooltip.
6466
const isCurrentlyDrawing = computed(() => store.state.drawing.drawingOverlay.show)
6567
const hasDevSiteWarning = computed(() => store.getters.hasDevSiteWarning)
68+
const isTimeSliderActive = computed(() => store.state.ui.isTimeSliderActive)
6669
const currentHeaderHeight = computed(() => store.state.ui.headerHeight)
6770
const isPhoneMode = computed(() => store.getters.isPhoneMode)
71+
const isDesktopMode = computed(() => store.getters.isTraditionalDesktopSize)
6872
6973
const cssPositionOnScreen = computed(() => {
7074
if (mode.value === MapPopoverMode.FEATURE_TOOLTIP) {
@@ -88,6 +92,9 @@ const popoverLimits = computed(() => {
8892
} else if (hasDevSiteWarning.value) {
8993
top += cssDevDisclaimerHeight
9094
}
95+
if (isTimeSliderActive.value) {
96+
top += isDesktopMode.value ? cssTimeSliderBarHeight : cssTimeSliderDropdownHeight
97+
}
9198
return {
9299
top,
93100
bottom: isPhoneMode.value ? 0 : cssFooterHeight,
@@ -125,6 +132,12 @@ function printContent() {
125132
floating: mode === MapPopoverMode.FLOATING,
126133
'feature-anchored': mode === MapPopoverMode.FEATURE_TOOLTIP,
127134
'with-dev-disclaimer': hasDevSiteWarning,
135+
'with-time-slider-bar': isTimeSliderActive && isDesktopMode,
136+
'with-dev-disclaimer-and-time-slider-bar':
137+
hasDevSiteWarning && isTimeSliderActive && isDesktopMode,
138+
'with-time-slider-dropdown': isTimeSliderActive && !isDesktopMode,
139+
'with-dev-disclaimer-and-time-slider-dropdown':
140+
hasDevSiteWarning && isTimeSliderActive && !isDesktopMode,
128141
'phone-mode': isPhoneMode,
129142
'is-drawing': isCurrentlyDrawing,
130143
}"
@@ -194,6 +207,26 @@ function printContent() {
194207
&.with-dev-disclaimer {
195208
top: calc($header-height + $dev-disclaimer-height + $screen-padding-for-ui-elements);
196209
}
210+
&.with-time-slider-bar {
211+
top: calc($header-height + $time-slider-bar-height + $screen-padding-for-ui-elements);
212+
}
213+
&.with-time-slider-dropdown {
214+
top: calc(
215+
$header-height + $time-slider-dropdown-height + $screen-padding-for-ui-elements
216+
);
217+
}
218+
&.with-dev-disclaimer-and-time-slider-bar {
219+
top: calc(
220+
$header-height + $time-slider-bar-height + $dev-disclaimer-height +
221+
$screen-padding-for-ui-elements
222+
);
223+
}
224+
&.with-dev-disclaimer-and-time-slider-dropdown {
225+
top: calc(
226+
$header-height + $time-slider-dropdown-height + $dev-disclaimer-height +
227+
$screen-padding-for-ui-elements
228+
);
229+
}
197230
&.phone-mode.is-drawing {
198231
top: calc($drawing-tools-height-mobile + $screen-padding-for-ui-elements);
199232
}
@@ -253,6 +286,28 @@ function printContent() {
253286
2 * $header-height + $dev-disclaimer-height + $screen-padding-for-ui-elements
254287
);
255288
}
289+
&.floating.with-time-slider-bar {
290+
top: calc(
291+
2 * $header-height + $time-slider-bar-height + $screen-padding-for-ui-elements
292+
);
293+
}
294+
&.floating.with-time-slider-dropdown {
295+
top: calc(
296+
2 * $header-height + $time-slider-dropdown-height + $screen-padding-for-ui-elements
297+
);
298+
}
299+
&.floating.with-dev-disclaimer-and-time-slider-bar {
300+
top: calc(
301+
2 * $header-height + $time-slider-bar-height + $dev-disclaimer-height +
302+
$screen-padding-for-ui-elements
303+
);
304+
}
305+
&.floating.with-dev-disclaimer-and-time-slider-dropdown {
306+
top: calc(
307+
2 * $header-height + $time-slider-dropdown-height + $dev-disclaimer-height +
308+
$screen-padding-for-ui-elements
309+
);
310+
}
256311
}
257312
}
258313
</style>
Lines changed: 86 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,93 @@
1-
<template>
2-
<slot />
3-
</template>
1+
<script setup>
2+
import { ArcType, Color, HeightReference, KmlDataSource, LabelStyle, VerticalOrigin } from 'cesium'
3+
import { computed, inject, onBeforeUnmount, onMounted, toRefs, watch } from 'vue'
44
5-
<script>
6-
import VectorSource from 'ol/source/Vector'
7-
import { mapActions, mapState } from 'vuex'
5+
import KMLLayer from '@/api/layers/KMLLayer.class.js'
86
9-
import KMLLayer from '@/api/layers/KMLLayer.class'
10-
import { parseKml } from '@/utils/kmlUtils'
7+
const props = defineProps({
8+
kmlLayerConfig: {
9+
type: KMLLayer,
10+
required: true,
11+
},
12+
})
1113
12-
import addPrimitiveFromOLLayerMixins from './utils/addPrimitiveFromOLLayer.mixins'
14+
const { kmlLayerConfig } = toRefs(props)
1315
14-
const dispatcher = { dispatcher: 'CesiumKMLLayer.vue' }
16+
const kmlData = computed(() => kmlLayerConfig.value.kmlData)
1517
16-
/** Renders a KML file to the Cesium viewer */
17-
export default {
18-
mixins: [addPrimitiveFromOLLayerMixins],
19-
props: {
20-
kmlLayerConfig: {
21-
type: KMLLayer,
22-
required: true,
23-
},
24-
},
25-
computed: {
26-
...mapState({
27-
availableIconSets: (state) => state.drawing.iconSets,
28-
projection: (state) => state.position.projection,
29-
}),
30-
opacity() {
31-
return this.kmlLayerConfig.opacity
32-
},
33-
kmlData() {
34-
return this.kmlLayerConfig.kmlData
35-
},
36-
iconsArePresent() {
37-
return this.availableIconSets.length > 0
38-
},
39-
},
40-
watch: {
41-
iconsArePresent() {
42-
this.loadDataInOLLayer().then(this.addPrimitive)
43-
},
44-
kmlData() {
45-
this.loadDataInOLLayer().then(this.addPrimitive)
46-
},
47-
},
48-
mounted() {
49-
if (!this.iconsArePresent) {
50-
this.loadAvailableIconSets(dispatcher)
51-
}
52-
},
53-
methods: {
54-
...mapActions(['loadAvailableIconSets']),
55-
loadDataInOLLayer() {
56-
return new Promise((resolve, reject) => {
57-
if (!this.kmlData) {
58-
reject(new Error('no KML data loaded yet, could not create source'))
59-
}
60-
if (!this.iconsArePresent) {
61-
reject(new Error('no icons loaded yet, could not create source'))
62-
}
63-
this.olLayer.setSource(
64-
new VectorSource({
65-
wrapX: true,
66-
projection: this.projection.epsg,
67-
features: parseKml(
68-
this.kmlLayerConfig,
69-
this.projection,
70-
this.availableIconSets
71-
),
72-
})
73-
)
74-
resolve()
75-
})
76-
},
77-
},
18+
const getViewer = inject('getViewer', () => {}, true)
19+
20+
onMounted(addKmlLayer)
21+
onBeforeUnmount(removeKmlLayer)
22+
23+
watch(kmlData, addKmlLayer)
24+
25+
let kmlDataSource = null
26+
27+
function removeKmlLayer() {
28+
if (kmlDataSource) {
29+
const viewer = getViewer()
30+
viewer.dataSources.remove(kmlDataSource)
31+
viewer.scene.requestRender()
32+
}
33+
}
34+
35+
function addKmlLayer() {
36+
removeKmlLayer()
37+
if (kmlData.value) {
38+
new KmlDataSource.load(new Blob([kmlData.value]), { clampToGround: false }).then(
39+
(dataSource) => {
40+
kmlDataSource = dataSource
41+
// adding some visual improvements to KML feature, depending on their type
42+
kmlDataSource.entities.values.forEach((entity) => {
43+
let geometry
44+
let alphaToApply = 0.2
45+
let clampToGround = false
46+
if (entity.ellipse) {
47+
geometry = entity.ellipse
48+
}
49+
if (entity.polygon) {
50+
geometry = entity.polygon
51+
}
52+
if (entity.polyline) {
53+
geometry = entity.polyline
54+
alphaToApply = 0.5
55+
clampToGround = true
56+
}
57+
if (entity.billboard) {
58+
entity.billboard.heightReference = HeightReference.CLAMP_TO_GROUND
59+
entity.billboard.verticalOrigin = VerticalOrigin.BOTTOM
60+
}
61+
if (entity.label) {
62+
const { label } = entity
63+
label.disableDepthTestDistance = Number.POSITIVE_INFINITY
64+
label.heightReference = HeightReference.CLAMP_TO_GROUND
65+
label.verticalOrigin = VerticalOrigin.CENTER
66+
label.outlineColor = Color.BLACK
67+
label.outlineWidth = 2
68+
label.style = LabelStyle.FILL_AND_OUTLINE
69+
}
70+
if (geometry) {
71+
if (clampToGround) {
72+
geometry.arcType = ArcType.GEODESIC
73+
geometry.clampToGround = true
74+
}
75+
if (geometry.material?.color) {
76+
geometry.material.color = geometry.material.color
77+
.getValue()
78+
.withAlpha(alphaToApply)
79+
}
80+
}
81+
})
82+
const viewer = getViewer()
83+
viewer.dataSources.add(kmlDataSource)
84+
viewer.scene.requestRender()
85+
}
86+
)
87+
}
7888
}
7989
</script>
90+
91+
<template>
92+
<slot />
93+
</template>

0 commit comments

Comments
 (0)