Skip to content

Commit 448ffe7

Browse files
Merge pull request #211 from Geoportail-Luxembourg/open-in-vcs-3d
Open 3D map (VCS)
2 parents fd859a6 + 36235cb commit 448ffe7

File tree

9 files changed

+149
-23
lines changed

9 files changed

+149
-23
lines changed

.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,6 @@ VITE_USERINFO_URL="/getuserinfo"
7777
VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu"
7878
VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password"
7979
VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user"
80+
81+
# VCS
82+
VITE_LUX_VCS_URL="https://3d.geoportail.lu/"

.env.development

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ VITE_USERINFO_URL="https://migration.geoportail.lu/getuserinfo"
7878
VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu"
7979
VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password"
8080
VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user"
81+
82+
# VCS
83+
VITE_LUX_VCS_URL="http://localhost:8008/"

.env.e2e

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ VITE_USERINFO_URL="https://migration.geoportail.lu/getuserinfo"
7878
VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu"
7979
VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password"
8080
VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user"
81+
82+
# VCS
83+
VITE_LUX_VCS_URL="https://3d.geoportail.lu/"

.env.staging

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ VITE_USERINFO_URL="/getuserinfo"
7878
VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu"
7979
VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password"
8080
VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user"
81+
82+
# VCS
83+
VITE_LUX_VCS_URL="https://3d.geoportail.lu/"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
describe('Link to 3D Viewer', () => {
2+
beforeEach(() => {
3+
cy.visit('/')
4+
})
5+
6+
describe('When user arrives on the page', () => {
7+
beforeEach(() => {
8+
cy.get('[data-cy="catalogButton"]').click()
9+
cy.get('[data-cy="parentLayerLabel-242"]').find('button').first().click()
10+
cy.get('[data-cy="parentLayerLabel-309"]').click()
11+
cy.get('[data-cy="layerLabel-269"]').click()
12+
cy.get('[data-cy="layerLabel-349"]').click()
13+
cy.get('[data-cy="layerLabel-329"]').click()
14+
})
15+
16+
it('The 3D button is an external link that opens the 3D viewer in a predefined tab named "lux3d", automatically loading the current coordinates (x, y, z) and selected layers for seamless continuation of the session', () => {
17+
cy.get('[data-cy="3dViewerLink"] > a')
18+
.should(
19+
'have.attr',
20+
'href',
21+
'https://3d.geoportail.lu/?state=%5B%5B%5B6.000000496232584%2C49.69999815293053%2C350000%5D%2C%5B6.000000496232584%2C49.69999815293053%2C350000%5D%2C300%2C0%2C-90%2C0%5D%2C%22cesium%22%2C%5B%22catalogConfig%22%2C%22LuxConfig%22%5D%2C%5B%5B%22communes_labels%22%2C1%2C0%5D%2C%5B%22country%22%2C1%2C0%5D%2C%5B%22cantons%22%2C1%2C0%5D%5D%2C%5B%5D%2C0%5D'
22+
)
23+
.and('have.attr', 'target', 'lux3d')
24+
})
25+
})
26+
})

src/assets/ol.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
@apply absolute rounded p-1;
1818
}
1919

20-
.ol-control button {
20+
.ol-control button,
21+
.ol-control a[type='button'] {
2122
@apply w-10 h-10 text-xl text-center font-normal indent-0 block ol-btn m-[-1px] p-0 border-[1px] border-[color:var(--color-border-default)];
2223
font-family: 'geoportail-icons-wc';
2324
}
Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
11
<script setup lang="ts">
2-
import { ref, onMounted } from 'vue'
2+
import { ref, onMounted, computed } from 'vue'
3+
import { storeToRefs } from 'pinia'
34
import { useTranslation } from 'i18next-vue'
45
import { CLASS_CONTROL, CLASS_UNSELECTABLE } from 'ol/css'
56
import Control from 'ol/control/Control'
67
import { Options } from 'ol/control/Control'
8+
import { toLonLat } from 'ol/proj'
79
810
import useControl from '@/composables/control/control.composable'
911
import { useMapStore } from '@/stores/map.store'
1012
13+
const LUX_VCS_URL = import.meta.env.VITE_LUX_VCS_URL
14+
const LUX_VCS_COORDINATES = [6.13, 49.61]
15+
const LUX_VCS_MODULES = ['catalogConfig', 'LuxConfig']
16+
const zoomToCesiumAltitude = {
17+
9: 350000,
18+
10: 180000,
19+
11: 100000,
20+
12: 40000,
21+
13: 25000,
22+
14: 9000,
23+
15: 6000,
24+
16: 3500,
25+
17: 1900,
26+
18: 900,
27+
19: 600,
28+
}
29+
1130
const mapStore = useMapStore()
1231
const { t } = useTranslation()
1332
const props = withDefaults(
@@ -18,33 +37,77 @@ const props = withDefaults(
1837
}>(),
1938
{
2039
className: 'map-3d-button',
21-
label: '\ue057',
22-
tipLabel: '3d',
40+
label: '3D',
41+
tipLabel: '3D',
2342
}
2443
)
44+
const { x, y, zoom, layers } = storeToRefs(mapStore)
2545
const controlElement = ref(null)
46+
const linkTo3dMap = computed(() => {
47+
// Example pattern for VCS state
48+
// [[[6.131935,49.611622,5000],[6.131935,49.611622,50],300,0,-90,0],"cesium",["moduleName"],[["addresses",1,0],["communes",1,0]],[],0]
49+
50+
const [lon, lat] = getLonLatFromXY(x.value, y.value)
51+
const altitude = getAltFromZoom(zoom.value ?? 12)
52+
const selectedLayers = layers.value.map(l => JSON.stringify([l.name, 1, 0]))
53+
const state = `[[[${[lon, lat, altitude].join(',')}],[${[
54+
lon,
55+
lat,
56+
altitude,
57+
].join(',')}],300,0,-90,0],"cesium",["${LUX_VCS_MODULES.join(
58+
'","'
59+
)}"],[${selectedLayers.join(',')}],[],0]`
60+
61+
return `${LUX_VCS_URL}?state=${encodeURIComponent(state)}`
62+
})
2663
2764
onMounted(() =>
2865
useControl(Control, {
2966
...props,
30-
...{ target: controlElement },
67+
target: controlElement,
3168
} as unknown as Options)
3269
)
3370
34-
const toggle3d = () => {
35-
mapStore.setIs3dActive(!mapStore.is3dActive)
71+
function getLonLatFromXY(
72+
x: number | undefined | null,
73+
y: number | undefined | null
74+
) {
75+
return x && y ? toLonLat([x, y]) : LUX_VCS_COORDINATES
76+
}
77+
78+
function getAltFromZoom(zoom: number) {
79+
const minZoom = 9
80+
const maxZoom = 19
81+
82+
const clampedZoom = Math.max(
83+
minZoom,
84+
Math.min(maxZoom, Math.round(zoom))
85+
) as keyof typeof zoomToCesiumAltitude
86+
87+
return zoomToCesiumAltitude[clampedZoom]
3688
}
3789
</script>
3890

3991
<template>
4092
<div
93+
data-cy="3dViewerLink"
4194
ref="controlElement"
42-
:class="`${props.className} ${CLASS_UNSELECTABLE} ${CLASS_CONTROL} ${
43-
mapStore.is3dActive ? 'active' : ''
44-
}`"
95+
:class="`${props.className} ${CLASS_UNSELECTABLE} ${CLASS_CONTROL}`"
4596
>
46-
<button :title="t(props.tipLabel)" @click="toggle3d">
47-
{{ props.label }}
48-
</button>
97+
<a
98+
type="button"
99+
target="lux3d"
100+
:href="linkTo3dMap"
101+
:title="t(props.tipLabel)"
102+
>3D</a
103+
>
49104
</div>
50105
</template>
106+
107+
<style lang="css" scoped>
108+
.ol-control a[type='button'] {
109+
font-family: 'DINNextLTPro-Condensed', Arial, sans-serif;
110+
font-size: 1.5em;
111+
line-height: 2.5rem;
112+
}
113+
</style>

src/services/state-persistor/state-persistor-map.service.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getTransform, ProjectionLike, transform } from 'ol/proj'
33
import { Coordinate } from 'ol/coordinate'
44
import ObjectEventType from 'ol/ObjectEventType'
55

6+
import { useMapStore } from '@/stores/map.store'
67
import useMap, {
78
PROJECTION_WEBMERCATOR,
89
PROJECTION_LUX,
@@ -22,6 +23,7 @@ import {
2223
V2_ZOOM_TO_V3_ZOOM_,
2324
} from './state-persistor-map.mapper'
2425
import { debounce, stringToNumber } from '@/services/utils'
26+
import { storeToRefs } from 'pinia'
2527

2628
class StatePersistorMapService implements StatePersistorService {
2729
bootstrap(): void {
@@ -30,18 +32,25 @@ class StatePersistorMapService implements StatePersistorService {
3032
}
3133

3234
persistZoom() {
35+
const mapStore = useMapStore()
36+
const { zoom } = storeToRefs(mapStore)
3337
const view = useMap().getOlMap().getView()
34-
const fnStorageSetValueZoom = () => {
35-
const zoom = view.getZoom()
36-
storageHelper.setValue(SP_KEY_ZOOM, zoom ? Math.ceil(zoom) : null)
38+
39+
const fnPersistValueZoom = () => {
40+
const z = view.getZoom()
41+
const pz = z ? Math.ceil(z) : null
42+
43+
storageHelper.setValue(SP_KEY_ZOOM, pz)
44+
45+
zoom.value = pz
3746
}
3847

39-
fnStorageSetValueZoom()
48+
fnPersistValueZoom()
4049

4150
olEvents.listen(
4251
view,
4352
'change:resolution',
44-
debounce(fnStorageSetValueZoom, 300)
53+
debounce(fnPersistValueZoom, 300)
4554
)
4655
}
4756

@@ -62,19 +71,28 @@ class StatePersistorMapService implements StatePersistorService {
6271
}
6372

6473
persistXY() {
74+
const mapStore = useMapStore()
75+
const { x, y } = storeToRefs(mapStore)
6576
const view = useMap().getOlMap().getView()
66-
const fnStorageSetValueXY = () => {
77+
78+
const fnPersistValueXY = () => {
6779
const center = view.getCenter()
68-
storageHelper.setValue(SP_KEY_X, center ? Math.round(center[0]) : null)
69-
storageHelper.setValue(SP_KEY_Y, center ? Math.round(center[1]) : null)
80+
const px = center ? Math.round(center[0]) : null
81+
const py = center ? Math.round(center[1]) : null
82+
83+
storageHelper.setValue(SP_KEY_X, px)
84+
storageHelper.setValue(SP_KEY_Y, py)
85+
86+
x.value = px
87+
y.value = py
7088
}
7189

72-
fnStorageSetValueXY()
90+
fnPersistValueXY()
7391

7492
olEvents.listen(
7593
view,
7694
ObjectEventType.PROPERTYCHANGE,
77-
debounce(fnStorageSetValueXY, 300)
95+
debounce(fnPersistValueXY, 300)
7896
)
7997
}
8098

src/stores/map.store.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ export const useMapStore = defineStore('map', () => {
1515
const bgLayer: Ref<Layer | undefined | null> = ref(undefined) // undefined => at start app | null => blank bgLayer
1616
const minZoom: Ref<number | undefined> = ref(undefined)
1717
const maxZoom: Ref<number | undefined> = ref(undefined)
18+
const x = ref<number | undefined | null>(undefined)
19+
const y = ref<number | undefined | null>(undefined)
20+
const zoom = ref<number | undefined | null>(undefined)
1821

1922
function setBgLayer(layer: Layer | null) {
2023
bgLayer.value = layer
@@ -112,6 +115,9 @@ export const useMapStore = defineStore('map', () => {
112115
bgLayer,
113116
minZoom,
114117
maxZoom,
118+
x,
119+
y,
120+
zoom,
115121
addLayers,
116122
add3dLayers,
117123
removeLayers,

0 commit comments

Comments
 (0)