Skip to content

BGDIINF_SB-3007: Send cesium camera changes to the store #429

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

Merged
merged 3 commits into from
Jul 14, 2023
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
48 changes: 36 additions & 12 deletions src/modules/map/components/cesium/CesiumMap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@ import {
Color,
Cartesian3,
RequestScheduler,
Math as CesiumMath,
} from 'cesium'
import { UIModes } from '@/store/modules/ui.store'
import { mapGetters, mapState } from 'vuex'
import { mapGetters, mapState, mapActions } from 'vuex'
import {
TERRAIN_URL,
CAMERA_MIN_ZOOM_DISTANCE,
CAMERA_MAX_ZOOM_DISTANCE,
CAMERA_MIN_PITCH,
CAMERA_MAX_PITCH,
} from './constants'
import { limitCameraCenter, limitCameraPitchRoll } from './utils/cameraUtils'
import { limitCameraCenter, limitCameraPitchRoll, calculateHeight } from './utils/cameraUtils'
import { IS_TESTING_WITH_CYPRESS, WMTS_BASE_URL, TILEGRID_EXTENT_EPSG_4326 } from '@/config'
import CesiumInternalLayer from './CesiumInternalLayer.vue'
import GeoAdminWMTSLayer from '@/api/layers/GeoAdminWMTSLayer.class'
Expand Down Expand Up @@ -77,8 +78,7 @@ export default {
...mapState({
zoom: (state) => state.position.zoom,
rotation: (state) => state.position.rotation,
center: (state) => state.position.center,
is3DActive: (state) => state.ui.showIn3d,
camera: (state) => state.position.camera,
uiMode: (state) => state.ui.mode,
previewYear: (state) => state.layers.previewYear,
}),
Expand Down Expand Up @@ -145,6 +145,8 @@ export default {
scene.pickTranslucentDepth = true
scene.backgroundColor = Color.TRANSPARENT

this.viewer.camera.moveEnd.addEventListener(this.onCameraMoveEnd)

const globe = scene.globe
globe.baseColor = Color.TRANSPARENT
globe.depthTestAgainstTerrain = true
Expand All @@ -167,20 +169,42 @@ export default {

this.flyToPosition()
},
unmounted() {
this.setCameraPosition(null)
this.viewer.destroy()
delete this.viewer
},
methods: {
...mapActions(['setCameraPosition']),
flyToPosition() {
const cameraHeight =
(this.resolution * this.viewer.canvas.clientWidth) /
(2 * Math.tan(this.viewer.camera.frustum.fov / 2))
const x = this.camera ? this.camera.x : this.centerEpsg4326[0]
const y = this.camera ? this.camera.y : this.centerEpsg4326[1]
const z = this.camera ? this.camera.z : calculateHeight(this.resolution, this.viewer.canvas.clientWidth)
const heading = this.camera ? CesiumMath.toRadians(this.camera.heading) : -this.rotation
const pitch = this.camera ? CesiumMath.toRadians(this.camera.pitch) : -CesiumMath.PI_OVER_TWO
const roll = this.camera ? CesiumMath.toRadians(this.camera.roll) : 0
this.viewer.camera.flyTo({
destination: Cartesian3.fromDegrees(
this.centerEpsg4326[0],
this.centerEpsg4326[1],
cameraHeight
),
destination: Cartesian3.fromDegrees(x, y, z),
orientation: {
heading,
pitch,
roll,
},
duration: 0,
})
},
onCameraMoveEnd() {
const camera = this.viewer.camera
const position = camera.positionCartographic
this.setCameraPosition({
x: CesiumMath.toDegrees(position.longitude).toFixed(6),
y: CesiumMath.toDegrees(position.latitude).toFixed(6),
z: position.height.toFixed(0),
heading: CesiumMath.toDegrees(camera.heading).toFixed(0),
pitch: CesiumMath.toDegrees(camera.pitch).toFixed(0),
roll: CesiumMath.toDegrees(camera.roll).toFixed(0),
})
},
},
}
</script>
Expand Down
20 changes: 20 additions & 0 deletions src/modules/map/components/cesium/utils/cameraUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {

/**
* Limits the camera pitch and roll.
*
* @param {number} minPitch
* @param {number} maxPitch
* @param {number} minRoll
Expand All @@ -34,6 +35,7 @@ export function limitCameraPitchRoll(minPitch, maxPitch, minRoll, maxRoll) {

/**
* Limits the camera center point to a rectangle.
*
* @param {Number[]} rectangle
* @returns
*/
Expand Down Expand Up @@ -95,3 +97,21 @@ export function limitCameraCenter(extent) {
}
}
}

/**
* @param {Number} resolution
* @param {Number} width Screen width in pixels
* @returns {number}
*/
export function calculateHeight(resolution, width) {
return (resolution * width) / (2 * Math.tan(Math.PI / 6))
}

/**
* @param {Number} height
* @param {Number} width Screen width in pixels
* @returns {number}
*/
export function calculateResolution(height, width) {
return (2 * Math.tan(Math.PI / 6) * height) / width
}
124 changes: 33 additions & 91 deletions src/router/storeSync/CameraParamConfig.class.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,58 @@
import AbstractParamConfig from '@/router/storeSync/abstractParamConfig.class'

function readValueFromObjectOrReturnNull(object, paramName, type) {
if (object && paramName in object) {
return type(object[paramName])
}
return null
}

/**
* @param query
* Reads the camera position from the single URL param. Returns null if the camera position is not
* defined or not complete
*
* @param urlParamValue
* @returns {CameraPosition | null}
*/
function parseCameraFromQuery(query) {
const camera = readValueFromObjectOrReturnNull(query, 'camera', String)
if (camera) {
let cameraValues = camera.split(',')
// the split must have 6 components (x, y, z, pitch, yaw and roll)
export function readCameraFromUrlParam(urlParamValue) {
if (urlParamValue) {
let cameraValues = urlParamValue.split(',')
// the split must have 6 components (x, y, z, pitch, heading and roll)
if (cameraValues.length === 6) {
// parsing to number all values (default to 0 if the value is empty)
cameraValues = cameraValues.map((value) => (value === '' ? 0 : Number(value)))
const [x, y, z, pitch, yaw, roll] = cameraValues
const [x, y, z, pitch, heading, roll] = cameraValues
return {
x,
y,
z,
pitch,
yaw,
heading,
roll,
}
}
}
return null
}

function dispatchCameraFromUrlIntoStore(store, urlParamValue) {
const promisesForAllDispatch = []
const camera = readCameraFromUrlParam(urlParamValue)
if (camera) {
promisesForAllDispatch.push(store.dispatch('setCameraPosition', camera))
}
return Promise.all(promisesForAllDispatch)
}

function generateCameraUrlParamFromStoreValues(store) {
if (store.state.ui.showIn3d && store.state.position.camera !== null) {
const { x, y, z, pitch, heading, roll } = store.state.position.camera
const valuesAsString = [x, y, z, pitch, heading, roll].map((value) =>
value === 0 ? '' : `${value}`
)
return valuesAsString.join(',')
}
return null
}

/**
* Definition of a set of URL params to store the position camera for the 3D viewer
*
* It will generate a unique camera URL param that will be a concat of all relevant camera values
* (x,y,z,yaw,pitch,roll), this param will only be added to the URL when 3D is active
* (x,y,z,heading,pitch,roll), this param will only be added to the URL when 3D is active
*
* This param parsing is based on the value of the 3D flag in the store, and not the one in the URL.
*/
Expand All @@ -46,83 +61,10 @@ export default class CameraParamConfig extends AbstractParamConfig {
super(
'camera',
'setCameraPosition',
() => {},
() => {},
dispatchCameraFromUrlIntoStore,
generateCameraUrlParamFromStoreValues,
false,
String
)
}

/**
* Reads the camera position from the single URL param
*
* @param query
* @returns {CameraPosition | null}
* @override
*/
readValueFromQuery(query) {
return parseCameraFromQuery(query)
}

/**
* Adds the camera URL param if 3D is active, or removes the camera URL param when not active
*
* @param query
* @param store
* @override
*/
populateQueryWithStoreValue(query, store) {
if (store.state.ui.showIn3d) {
const { x, y, z, pitch, yaw, roll } = store.state.position.camera
const valuesAsString = [x, y, z, pitch, yaw, roll].map((value) =>
value === 0 ? '' : `${value}`
)
query['camera'] = valuesAsString.join(',')
} else {
delete query['camera']
}
}

/**
* Dispatches to the store the camera position from the URL, if 3D is active
*
* @param {Vuex.Store} store
* @param query
* @returns {Promise<Awaited[]>}
* @override
*/
populateStoreWithQueryValue(store, query) {
const promisesSetValuesInStore = []
if (store.state.ui.showIn3d) {
const cameraInQuery = parseCameraFromQuery(query)
if (cameraInQuery) {
promisesSetValuesInStore.push(store.dispatch('setCameraPosition', cameraInQuery))
}
}
return Promise.all(promisesSetValuesInStore)
}

/**
* Checks if the camera in the URL is different from the one in the store, this check happens
* only when 3D is active
*
* @param query
* @param store
* @returns {boolean}
* @override
*/
valuesAreDifferentBetweenQueryAndStore(query, store) {
if (store.state.ui.showIn3d) {
const queryCamera = parseCameraFromQuery(query)
if (!queryCamera) {
return true
}
const camera = store.state.position.camera
let isEqual = true
Object.entries(camera).forEach(([key, value]) => {
isEqual &= value === queryCamera[key]
})
return !isEqual
}
}
}
Loading