Skip to content

Commit

Permalink
Feature: update camera settings
Browse files Browse the repository at this point in the history
  • Loading branch information
TIL-EBP committed Nov 26, 2024
1 parent 6fef90b commit 3b4f039
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 61 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Added

### Changed
- Values for "Height", "Angle", "Pitch", and coordinates are now input fields. Users can adjust values using arrow keys.
- The range for height has been increased to 700'000m.

- Config is loaded from the frontend at runtime now.

Expand Down
4 changes: 3 additions & 1 deletion ui/locales/app.de.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"cam_lock_info_pitch": "Im Moment kann im 3D Viewer nur der Neigungswinkel verändert werden.",
"camera_position_angle_label": "Azimut",
"camera_position_coordinates_label": "Koordinaten",
"camera_position_coordinates_input_error": "Die eingegebenen Koordinaten sind ungültig.",
"camera_position_coordinates_system_label": "Koordinatensystem",
"camera_position_height_label": "Höhe<br>(m. ü. OKT)",
"camera_position_height_label": "Höhe",
"camera_position_height_unit": "(m. ü. OKT)",
"camera_position_pitch_label": "Neigungswinkel",
"cancel": "Abbrechen",
"contact_mailto_text": "Kontakt",
Expand Down
6 changes: 4 additions & 2 deletions ui/locales/app.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
"cam_lock_info_elevation": "In the 3D viewer currently you can only change the height.",
"cam_lock_info_move": "In the 3D viewer currently you can only change the coordinates.",
"cam_lock_info_pitch": "In the 3D viewer currently you can only change the pitch.",
"camera_position_angle_label": "Angle (N±)",
"camera_position_angle_label": "Angle(N±)",
"camera_position_coordinates_label": "Coordinates",
"camera_position_coordinates_input_error": "The entered coordinates are invalid.",
"camera_position_coordinates_system_label": "Coordinate system",
"camera_position_height_label": "Height<br>(m AGL)",
"camera_position_height_label": "Height",
"camera_position_height_unit": "(m AGL)",
"camera_position_pitch_label": "Pitch",
"cancel": "Cancel",
"contact_mailto_text": "Contact",
Expand Down
4 changes: 3 additions & 1 deletion ui/locales/app.fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"cam_lock_info_pitch": "Dans le viewer 3D, seul l'angle peut être modifié actuellement.",
"camera_position_angle_label": "Angle",
"camera_position_coordinates_label": "Coordonnées",
"camera_position_coordinates_input_error": "Les coordonnées saisies sont invalides.",
"camera_position_coordinates_system_label": "Système de coordonnées",
"camera_position_height_label": "Altitude<br>(m AGL)",
"camera_position_height_label": "Altitude",
"camera_position_height_unit": "(m AGL)",
"camera_position_pitch_label": "Inclinaison",
"cancel": "Annuler",
"contact_mailto_text": "Contact",
Expand Down
4 changes: 3 additions & 1 deletion ui/locales/app.it.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"cam_lock_info_pitch": "Nel visualizzatore 3D si può solo cambiare l'angolo attualmente",
"camera_position_angle_label": "Angolo",
"camera_position_coordinates_label": "Coordinate",
"camera_position_coordinates_input_error": "Le coordinate inserite non sono valide.",
"camera_position_coordinates_system_label": "Sistema di coordinate",
"camera_position_height_label": "Altitudine<br>(m AGL)",
"camera_position_height_label": "Altitudine",
"camera_position_height_unit": "(m AGL)",
"camera_position_pitch_label": "Inclinazione",
"cancel": "Annullare",
"contact_mailto_text": "Contatto",
Expand Down
6 changes: 4 additions & 2 deletions ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import {Cartesian3, Color, ColorBlendMode, Math as CMath, Rectangle, ShadowMode,

export {LayerType, DEFAULT_LAYER_OPACITY} from './layertree';

export const SWITZERLAND_BOUNDS = [5.140242, 45.398181, 11.47757, 48.230651];

export const SWITZERLAND_RECTANGLE = Rectangle.fromDegrees(...SWITZERLAND_BOUNDS);
export const SWITZERLAND_BOUNDS_WGS84 = [4.54249, 44.61921, 12.67250, 48.45365];
export const SWITZERLAND_BOUNDS_LV95 = [2370000, 945000, 2987000, 1380000];

export const SWITZERLAND_RECTANGLE = Rectangle.fromDegrees(...SWITZERLAND_BOUNDS_WGS84);

export const MINIMAP_EXTENT = [5.910642046, 45.191912227, 10.554524194, 48.04750923];

Expand Down
94 changes: 68 additions & 26 deletions ui/src/elements/ngm-cam-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,38 @@ import {customElement, property, state} from 'lit/decorators.js';
import draggable from './draggable';
import i18next from 'i18next';
import type {Interactable} from '@interactjs/types';
import {Event, Scene, Viewer} from 'cesium';
import {
Event,
Scene,
Viewer,
Cartesian2,
Cartesian3,
KeyboardEventModifier,
Math as CesiumMath,
Matrix4,
ScreenSpaceEventHandler,
ScreenSpaceEventType
ScreenSpaceEventType,
Ellipsoid
} from 'cesium';
import {formatCartographicAs2DLv95, radToDeg} from '../projection';
import {formatCartographicAs2DLv95, lv95ToDegrees, radToDeg} from '../projection';
import {styleMap} from 'lit/directives/style-map.js';
import {classMap} from 'lit/directives/class-map.js';
import './ngm-cam-coordinates';
import NavToolsStore from '../store/navTools';
import {dragArea} from './helperElements';
import './ngm-minimap';
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
import {CoordinateWithCrs} from './ngm-cam-coordinates';

export type LockType = '' | 'elevation' | 'angle' | 'pitch' | 'move';

export const ABSOLUTE_ELEVATION_MIN = 30000;
export const ABSOLUTE_ELEVATION_MAX = 700000;
/*
* Convert cartographic height (between -30'000m and +300'000) to input value (between 0 and 1)
* The input value between 0 to 0.5 is mapped to the height between -30'000m and 0m
* The input between 0.5 and 1 is mapped to the height between 0m and +300'000m
*/
export function heightToValue(height: number): number {
const m = 0.5 / (height < 0 ? 30000 : 300000);
const m = 0.5 / (height < 0 ? ABSOLUTE_ELEVATION_MIN : ABSOLUTE_ELEVATION_MAX);
return m * height + 0.5;
}

Expand All @@ -42,9 +47,9 @@ export function heightToValue(height: number): number {
*/
export function valueToHeight(value: number): number {
if (value < 0.5) {
return (30000 / 0.5) * value - 30000;
return (ABSOLUTE_ELEVATION_MIN / 0.5) * value - ABSOLUTE_ELEVATION_MIN;
} else {
return (300000 / 0.5) * value - 300000;
return (ABSOLUTE_ELEVATION_MAX / 0.5) * value - ABSOLUTE_ELEVATION_MAX;
}
}

Expand All @@ -69,49 +74,70 @@ export class NgmCamConfiguration extends LitElementI18n {
@state()
accessor lockType: LockType = '';
// always use the 'de-CH' locale to always have the simple tick as thousands separator
private integerFormat = new Intl.NumberFormat('de-CH', {
private readonly integerFormat = new Intl.NumberFormat('de-CH', {
maximumFractionDigits: 1
});
private timeout: null | NodeJS.Timeout = null;
private handler: ScreenSpaceEventHandler | undefined;
private lockMove = false;
private lockMoveStartPosition: Cartesian2 = new Cartesian2();
private lockMovePosition: Cartesian2 = new Cartesian2();
private readonly lockMoveStartPosition: Cartesian2 = new Cartesian2();
private readonly lockMovePosition: Cartesian2 = new Cartesian2();
private removeOnTick: Event.RemoveCallback | undefined;
private configurations = [
private readonly configurations = [
{
label: () => i18next.t('camera_position_height_label'),
label: () => html`${i18next.t('camera_position_height_label')}<br>${i18next.t('camera_position_height_unit')}`,
iconClass: () => classMap({'ngm-cam-h-icon': true, 'ngm-active-icon': this.lockType === 'elevation'}),
minValue: 0,
maxValue: 1,
step: 'any',
step: 0.1 / (ABSOLUTE_ELEVATION_MAX + ABSOLUTE_ELEVATION_MIN),
minInputValue: -ABSOLUTE_ELEVATION_MIN,
maxInputValue: ABSOLUTE_ELEVATION_MAX,
inputStep: 0.1,
style: () => this.getSliderStyle(heightToValue(this.elevation), 0, 1),
getValue: () => heightToValue(this.elevation),
getInputValue: () => this.elevation.toFixed(1),
getValueLabel: () => `${this.integerFormat.format(this.elevation)} m`,
onChange: evt => this.updateHeight(valueToHeight(Number(evt.target.value))),
onSliderChange: (evt) => this.updateHeight(valueToHeight(Number(evt.target.value))),
onInputChange: (evt) => {
if (this.timeout) {
clearTimeout(this.timeout);
}
let value = Number(evt.target.value);
if (value < -ABSOLUTE_ELEVATION_MIN) {
value = -ABSOLUTE_ELEVATION_MIN;
}
if (value > ABSOLUTE_ELEVATION_MAX) {
value = ABSOLUTE_ELEVATION_MAX;
}
this.timeout = setTimeout(() => {
this.updateHeight(value);
this.timeout = null;
}, 300);
},
lock: () => this.toggleLock('elevation')
},
{
label: () => i18next.t('camera_position_angle_label'),
label: () => html`${i18next.t('camera_position_angle_label')}<br>(°)`,
iconClass: () => classMap({'ngm-cam-d-icon': true, 'ngm-active-icon': this.lockType === 'angle'}),
minValue: 0,
maxValue: 359,
step: 1,
style: () => this.getSliderStyle(this.heading, 0, 359, true),
getValue: () => this.heading,
getValueLabel: () => `${this.integerFormat.format(this.heading)}°`,
onChange: (evt) => this.updateAngle(Number(evt.target.value)),
onSliderChange: (evt) => this.updateAngle(Number(evt.target.value)),
lock: () => this.toggleLock('angle')
},
{
label: () => i18next.t('camera_position_pitch_label'),
label: () => html`${i18next.t('camera_position_pitch_label')}<br>(°)`,
iconClass: () => classMap({'ngm-cam-t-icon': true, 'ngm-active-icon': this.lockType === 'pitch'}),
minValue: -90,
maxValue: 90,
step: 1,
style: () => this.getSliderStyle(this.pitch, -90, 90),
getValue: () => this.pitch,
getValueLabel: () => `${this.integerFormat.format(this.pitch)}°`,
onChange: (evt) => this.updatePitch(Number(evt.target.value)),
onSliderChange: (evt) => this.updatePitch(Number(evt.target.value)),
lock: () => this.toggleLock('pitch')
},
];
Expand All @@ -130,10 +156,11 @@ export class NgmCamConfiguration extends LitElementI18n {
super.disconnectedCallback();
}


updated(changedProperties: PropertyValues) {
if (this.viewer && !this.unlistenPostRender) {
this.scene = this.viewer.scene;
this.handler = new ScreenSpaceEventHandler(this.viewer!.canvas);
this.handler = new ScreenSpaceEventHandler(this.viewer.canvas);
this.unlistenPostRender = this.scene.postRender.addEventListener(() => this.updateFromCamera());
this.updateFromCamera();
}
Expand All @@ -145,7 +172,7 @@ export class NgmCamConfiguration extends LitElementI18n {
updateFromCamera() {
const camera = this.scene!.camera;
const pc = camera.positionCartographic;
const altitude = this.scene!.globe.getHeight(pc) || 0;
const altitude = this.scene!.globe.getHeight(pc) ?? 0;
this.elevation = pc.height - altitude;
this.pitch = Math.round(CesiumMath.toDegrees(camera.pitch));
let heading = Math.round(CesiumMath.toDegrees(camera.heading));
Expand All @@ -160,7 +187,7 @@ export class NgmCamConfiguration extends LitElementI18n {
}

updateHeight(value: number) {
const altitude = this.scene!.globe.getHeight(this.scene!.camera.positionCartographic) || 0;
const altitude = this.scene!.globe.getHeight(this.scene!.camera.positionCartographic) ?? 0;
NavToolsStore.setCameraHeight(value + altitude);
}

Expand All @@ -184,6 +211,12 @@ export class NgmCamConfiguration extends LitElementI18n {
});
}

updateCoordinates(event: CustomEvent<CoordinateWithCrs>) {
const detail = event.detail;
const coordinates = detail.crs === 'lv95' ? lv95ToDegrees([detail.long, detail.lat]) : [detail.long, detail.lat];
this.scene?.camera.setView({destination: Cartesian3.fromDegrees(coordinates[0], coordinates[1], Ellipsoid.WGS84.cartesianToCartographic(this.scene.camera.position).height)});
}

getSliderStyle(value: number, minValue: number, maxValue: number, oneDirection = false) {
if (oneDirection) {
return {
Expand Down Expand Up @@ -292,21 +325,30 @@ export class NgmCamConfiguration extends LitElementI18n {
<div class=${c.iconClass()} title=${i18next.t('cam_lock')} @click=${c.lock}></div>
<div class="ngm-cam-conf-slider">
<div>
<label>${unsafeHTML(c.label())}</label>
<label>${c.getValueLabel()}</label>
<label>${c.label()}</label>
<input type="number" class="ngm-cam-conf-number-input"
min="${c.minInputValue ?? c.minValue}"
max="${c.maxInputValue ?? c.maxValue}"
step="${c.inputStep ?? c.step}"
.value=${c.getInputValue ? c.getInputValue() : c.getValue()}
@input=${c.onInputChange ? c.onInputChange : c.onSliderChange}
/>
</div>
<input type="range" class="ngm-slider" style=${styleMap(c.style())}
min=${c.minValue}
max=${c.maxValue}
step=${c.step}
.value=${c.getValue()}
@input=${c.onChange}/>
@input=${c.onSliderChange}
@keydown="${(e) => (e.key === 'ArrowLeft' || e.key === 'ArrowRight') && e.stopPropagation()}"
/>
</div>
</div>`)}
<div>
<div class="ngm-cam-icon ${classMap({'ngm-active-icon': this.lockType === 'move'})}"
@click=${() => this.toggleLock('move')}></div>
<ngm-cam-coordinates .coordinates=${this.coordinates}></ngm-cam-coordinates>
<ngm-cam-coordinates @coordinates-changed="${this.updateCoordinates}" .coordinates=${this.coordinates}></ngm-cam-coordinates>
</div>
</div>
${dragArea}
Expand Down
Loading

0 comments on commit 3b4f039

Please sign in to comment.