Skip to content

Commit 6646eac

Browse files
committed
Feature: update camera settings
1 parent 6fef90b commit 6646eac

File tree

9 files changed

+169
-36
lines changed

9 files changed

+169
-36
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
### Added
66

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

911
- Config is loaded from the frontend at runtime now.
1012

ui/locales/app.de.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
"cam_lock_info_pitch": "Im Moment kann im 3D Viewer nur der Neigungswinkel verändert werden.",
1010
"camera_position_angle_label": "Azimut",
1111
"camera_position_coordinates_label": "Koordinaten",
12+
"camera_position_coordinates_input_error": "Ungültige Koordinaten!",
1213
"camera_position_coordinates_system_label": "Koordinatensystem",
13-
"camera_position_height_label": "Höhe<br>(m. ü. OKT)",
14+
"camera_position_height_label": "Höhe",
15+
"camera_position_height_unit": "(m. ü. OKT)",
1416
"camera_position_pitch_label": "Neigungswinkel",
1517
"cancel": "Abbrechen",
1618
"contact_mailto_text": "Kontakt",

ui/locales/app.en.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
"cam_lock_info_elevation": "In the 3D viewer currently you can only change the height.",
88
"cam_lock_info_move": "In the 3D viewer currently you can only change the coordinates.",
99
"cam_lock_info_pitch": "In the 3D viewer currently you can only change the pitch.",
10-
"camera_position_angle_label": "Angle (N±)",
10+
"camera_position_angle_label": "Angle(N±)",
1111
"camera_position_coordinates_label": "Coordinates",
12+
"camera_position_coordinates_input_error": "Invalid coordinates!",
1213
"camera_position_coordinates_system_label": "Coordinate system",
13-
"camera_position_height_label": "Height<br>(m AGL)",
14+
"camera_position_height_label": "Height",
15+
"camera_position_height_unit": "(m AGL)",
1416
"camera_position_pitch_label": "Pitch",
1517
"cancel": "Cancel",
1618
"contact_mailto_text": "Contact",

ui/locales/app.fr.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
"cam_lock_info_pitch": "Dans le viewer 3D, seul l'angle peut être modifié actuellement.",
1010
"camera_position_angle_label": "Angle",
1111
"camera_position_coordinates_label": "Coordonnées",
12+
"camera_position_coordinates_input_error": "Coordonnées incorrectes!",
1213
"camera_position_coordinates_system_label": "Système de coordonnées",
13-
"camera_position_height_label": "Altitude<br>(m AGL)",
14+
"camera_position_height_label": "Altitude",
15+
"camera_position_height_unit": "(m AGL)",
1416
"camera_position_pitch_label": "Inclinaison",
1517
"cancel": "Annuler",
1618
"contact_mailto_text": "Contact",

ui/locales/app.it.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
"cam_lock_info_pitch": "Nel visualizzatore 3D si può solo cambiare l'angolo attualmente",
1010
"camera_position_angle_label": "Angolo",
1111
"camera_position_coordinates_label": "Coordinate",
12+
"camera_position_coordinates_input_error": "Coordinate non valide!",
1213
"camera_position_coordinates_system_label": "Sistema di coordinate",
13-
"camera_position_height_label": "Altitudine<br>(m AGL)",
14+
"camera_position_height_label": "Altitudine",
15+
"camera_position_height_unit": "(m AGL)",
1416
"camera_position_pitch_label": "Inclinazione",
1517
"cancel": "Annullare",
1618
"contact_mailto_text": "Contatto",

ui/src/constants.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {Cartesian3, Color, ColorBlendMode, Math as CMath, Rectangle, ShadowMode,
22

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

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

7-
export const SWITZERLAND_RECTANGLE = Rectangle.fromDegrees(...SWITZERLAND_BOUNDS);
6+
export const SWITZERLAND_BOUNDS_WGS84 = [4.54249, 44.61921, 12.67250, 48.45365];
7+
export const SWITZERLAND_BOUNDS_LV95 = [2370000, 945000, 2987000, 1380000];
8+
9+
export const SWITZERLAND_RECTANGLE = Rectangle.fromDegrees(...SWITZERLAND_BOUNDS_WGS84);
810

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

ui/src/elements/ngm-cam-configuration.ts

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,38 @@ import {customElement, property, state} from 'lit/decorators.js';
55
import draggable from './draggable';
66
import i18next from 'i18next';
77
import type {Interactable} from '@interactjs/types';
8-
import {Event, Scene, Viewer} from 'cesium';
98
import {
9+
Event,
10+
Scene,
11+
Viewer,
1012
Cartesian2,
13+
Cartesian3,
1114
KeyboardEventModifier,
1215
Math as CesiumMath,
1316
Matrix4,
1417
ScreenSpaceEventHandler,
15-
ScreenSpaceEventType
18+
ScreenSpaceEventType,
19+
Ellipsoid
1620
} from 'cesium';
17-
import {formatCartographicAs2DLv95, radToDeg} from '../projection';
21+
import {formatCartographicAs2DLv95, lv95ToDegrees, radToDeg} from '../projection';
1822
import {styleMap} from 'lit/directives/style-map.js';
1923
import {classMap} from 'lit/directives/class-map.js';
2024
import './ngm-cam-coordinates';
2125
import NavToolsStore from '../store/navTools';
2226
import {dragArea} from './helperElements';
2327
import './ngm-minimap';
24-
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
28+
import {CoordinateWithCrs} from './ngm-cam-coordinates';
2529

2630
export type LockType = '' | 'elevation' | 'angle' | 'pitch' | 'move';
27-
31+
const ABSOLUTE_ELEVATION_MIN = 30000;
32+
const ABSOLUTE_ELEVATION_MAX = 700000;
2833
/*
2934
* Convert cartographic height (between -30'000m and +300'000) to input value (between 0 and 1)
3035
* The input value between 0 to 0.5 is mapped to the height between -30'000m and 0m
3136
* The input between 0.5 and 1 is mapped to the height between 0m and +300'000m
3237
*/
3338
export function heightToValue(height: number): number {
34-
const m = 0.5 / (height < 0 ? 30000 : 300000);
39+
const m = 0.5 / (height < 0 ? ABSOLUTE_ELEVATION_MIN : ABSOLUTE_ELEVATION_MAX);
3540
return m * height + 0.5;
3641
}
3742

@@ -42,9 +47,9 @@ export function heightToValue(height: number): number {
4247
*/
4348
export function valueToHeight(value: number): number {
4449
if (value < 0.5) {
45-
return (30000 / 0.5) * value - 30000;
50+
return (ABSOLUTE_ELEVATION_MIN / 0.5) * value - ABSOLUTE_ELEVATION_MIN;
4651
} else {
47-
return (300000 / 0.5) * value - 300000;
52+
return (ABSOLUTE_ELEVATION_MAX / 0.5) * value - ABSOLUTE_ELEVATION_MAX;
4853
}
4954
}
5055

@@ -69,49 +74,70 @@ export class NgmCamConfiguration extends LitElementI18n {
6974
@state()
7075
accessor lockType: LockType = '';
7176
// always use the 'de-CH' locale to always have the simple tick as thousands separator
72-
private integerFormat = new Intl.NumberFormat('de-CH', {
77+
private readonly integerFormat = new Intl.NumberFormat('de-CH', {
7378
maximumFractionDigits: 1
7479
});
80+
private timeout: null | NodeJS.Timeout = null;
7581
private handler: ScreenSpaceEventHandler | undefined;
7682
private lockMove = false;
77-
private lockMoveStartPosition: Cartesian2 = new Cartesian2();
78-
private lockMovePosition: Cartesian2 = new Cartesian2();
83+
private readonly lockMoveStartPosition: Cartesian2 = new Cartesian2();
84+
private readonly lockMovePosition: Cartesian2 = new Cartesian2();
7985
private removeOnTick: Event.RemoveCallback | undefined;
80-
private configurations = [
86+
private readonly configurations = [
8187
{
82-
label: () => i18next.t('camera_position_height_label'),
88+
label: () => html`${i18next.t('camera_position_height_label')}<br>${i18next.t('camera_position_height_unit')}`,
8389
iconClass: () => classMap({'ngm-cam-h-icon': true, 'ngm-active-icon': this.lockType === 'elevation'}),
8490
minValue: 0,
8591
maxValue: 1,
86-
step: 'any',
92+
step: 0.1 / (ABSOLUTE_ELEVATION_MAX + ABSOLUTE_ELEVATION_MIN),
93+
minInputValue: -ABSOLUTE_ELEVATION_MIN,
94+
maxInputValue: ABSOLUTE_ELEVATION_MAX,
95+
inputStep: 0.1,
8796
style: () => this.getSliderStyle(heightToValue(this.elevation), 0, 1),
8897
getValue: () => heightToValue(this.elevation),
98+
getInputValue: () => this.elevation.toFixed(1),
8999
getValueLabel: () => `${this.integerFormat.format(this.elevation)} m`,
90-
onChange: evt => this.updateHeight(valueToHeight(Number(evt.target.value))),
100+
onSliderChange: (evt) => this.updateHeight(valueToHeight(Number(evt.target.value))),
101+
onInputChange: (evt) => {
102+
if (this.timeout) {
103+
clearTimeout(this.timeout);
104+
}
105+
let value = Number(evt.target.value);
106+
if (value < -ABSOLUTE_ELEVATION_MIN) {
107+
value = -ABSOLUTE_ELEVATION_MIN;
108+
}
109+
if (value > ABSOLUTE_ELEVATION_MAX) {
110+
value = ABSOLUTE_ELEVATION_MAX;
111+
}
112+
this.timeout = setTimeout(() => {
113+
this.updateHeight(value);
114+
this.timeout = null;
115+
}, 300);
116+
},
91117
lock: () => this.toggleLock('elevation')
92118
},
93119
{
94-
label: () => i18next.t('camera_position_angle_label'),
120+
label: () => html`${i18next.t('camera_position_angle_label')}<br>(°)`,
95121
iconClass: () => classMap({'ngm-cam-d-icon': true, 'ngm-active-icon': this.lockType === 'angle'}),
96122
minValue: 0,
97123
maxValue: 359,
98124
step: 1,
99125
style: () => this.getSliderStyle(this.heading, 0, 359, true),
100126
getValue: () => this.heading,
101127
getValueLabel: () => `${this.integerFormat.format(this.heading)}°`,
102-
onChange: (evt) => this.updateAngle(Number(evt.target.value)),
128+
onSliderChange: (evt) => this.updateAngle(Number(evt.target.value)),
103129
lock: () => this.toggleLock('angle')
104130
},
105131
{
106-
label: () => i18next.t('camera_position_pitch_label'),
132+
label: () => html`${i18next.t('camera_position_pitch_label')}<br>(°)`,
107133
iconClass: () => classMap({'ngm-cam-t-icon': true, 'ngm-active-icon': this.lockType === 'pitch'}),
108134
minValue: -90,
109135
maxValue: 90,
110136
step: 1,
111137
style: () => this.getSliderStyle(this.pitch, -90, 90),
112138
getValue: () => this.pitch,
113139
getValueLabel: () => `${this.integerFormat.format(this.pitch)}°`,
114-
onChange: (evt) => this.updatePitch(Number(evt.target.value)),
140+
onSliderChange: (evt) => this.updatePitch(Number(evt.target.value)),
115141
lock: () => this.toggleLock('pitch')
116142
},
117143
];
@@ -130,10 +156,11 @@ export class NgmCamConfiguration extends LitElementI18n {
130156
super.disconnectedCallback();
131157
}
132158

159+
133160
updated(changedProperties: PropertyValues) {
134161
if (this.viewer && !this.unlistenPostRender) {
135162
this.scene = this.viewer.scene;
136-
this.handler = new ScreenSpaceEventHandler(this.viewer!.canvas);
163+
this.handler = new ScreenSpaceEventHandler(this.viewer.canvas);
137164
this.unlistenPostRender = this.scene.postRender.addEventListener(() => this.updateFromCamera());
138165
this.updateFromCamera();
139166
}
@@ -145,7 +172,7 @@ export class NgmCamConfiguration extends LitElementI18n {
145172
updateFromCamera() {
146173
const camera = this.scene!.camera;
147174
const pc = camera.positionCartographic;
148-
const altitude = this.scene!.globe.getHeight(pc) || 0;
175+
const altitude = this.scene!.globe.getHeight(pc) ?? 0;
149176
this.elevation = pc.height - altitude;
150177
this.pitch = Math.round(CesiumMath.toDegrees(camera.pitch));
151178
let heading = Math.round(CesiumMath.toDegrees(camera.heading));
@@ -160,7 +187,7 @@ export class NgmCamConfiguration extends LitElementI18n {
160187
}
161188

162189
updateHeight(value: number) {
163-
const altitude = this.scene!.globe.getHeight(this.scene!.camera.positionCartographic) || 0;
190+
const altitude = this.scene!.globe.getHeight(this.scene!.camera.positionCartographic) ?? 0;
164191
NavToolsStore.setCameraHeight(value + altitude);
165192
}
166193

@@ -184,6 +211,12 @@ export class NgmCamConfiguration extends LitElementI18n {
184211
});
185212
}
186213

214+
updateCoordinates(event: CustomEvent<CoordinateWithCrs>) {
215+
const detail = event.detail;
216+
const coordinates = detail.crs === 'lv95' ? lv95ToDegrees([detail.long, detail.lat]) : [detail.long, detail.lat];
217+
this.scene?.camera.setView({destination: Cartesian3.fromDegrees(coordinates[0], coordinates[1], Ellipsoid.WGS84.cartesianToCartographic(this.scene.camera.position).height)});
218+
}
219+
187220
getSliderStyle(value: number, minValue: number, maxValue: number, oneDirection = false) {
188221
if (oneDirection) {
189222
return {
@@ -292,21 +325,31 @@ export class NgmCamConfiguration extends LitElementI18n {
292325
<div class=${c.iconClass()} title=${i18next.t('cam_lock')} @click=${c.lock}></div>
293326
<div class="ngm-cam-conf-slider">
294327
<div>
295-
<label>${unsafeHTML(c.label())}</label>
296-
<label>${c.getValueLabel()}</label>
328+
<label>${c.label()}</label>
329+
<input style="border: none;padding: 0; width: 50%; background-color: unset; justify-items: end"
330+
type="number"
331+
min="${c.minInputValue ?? c.minValue}"
332+
max="${c.maxInputValue ?? c.maxValue}"
333+
step="${c.inputStep ?? c.step}"
334+
.value=${c.getInputValue ? c.getInputValue() : c.getValue()}
335+
@input=${c.onInputChange ? c.onInputChange : c.onSliderChange}
336+
337+
/>
297338
</div>
298339
<input type="range" class="ngm-slider" style=${styleMap(c.style())}
299340
min=${c.minValue}
300341
max=${c.maxValue}
301342
step=${c.step}
302343
.value=${c.getValue()}
303-
@input=${c.onChange}/>
344+
@input=${c.onSliderChange}
345+
@keydown="${(e) => { if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') e.stopPropagation(); }}"
346+
/>
304347
</div>
305348
</div>`)}
306349
<div>
307350
<div class="ngm-cam-icon ${classMap({'ngm-active-icon': this.lockType === 'move'})}"
308351
@click=${() => this.toggleLock('move')}></div>
309-
<ngm-cam-coordinates .coordinates=${this.coordinates}></ngm-cam-coordinates>
352+
<ngm-cam-coordinates @coordinates-changed="${this.updateCoordinates}" .coordinates=${this.coordinates}></ngm-cam-coordinates>
310353
</div>
311354
</div>
312355
${dragArea}

0 commit comments

Comments
 (0)