@@ -5,33 +5,38 @@ import {customElement, property, state} from 'lit/decorators.js';
5
5
import draggable from './draggable' ;
6
6
import i18next from 'i18next' ;
7
7
import type { Interactable } from '@interactjs/types' ;
8
- import { Event , Scene , Viewer } from 'cesium' ;
9
8
import {
9
+ Event ,
10
+ Scene ,
11
+ Viewer ,
10
12
Cartesian2 ,
13
+ Cartesian3 ,
11
14
KeyboardEventModifier ,
12
15
Math as CesiumMath ,
13
16
Matrix4 ,
14
17
ScreenSpaceEventHandler ,
15
- ScreenSpaceEventType
18
+ ScreenSpaceEventType ,
19
+ Ellipsoid
16
20
} from 'cesium' ;
17
- import { formatCartographicAs2DLv95 , radToDeg } from '../projection' ;
21
+ import { formatCartographicAs2DLv95 , lv95ToDegrees , radToDeg } from '../projection' ;
18
22
import { styleMap } from 'lit/directives/style-map.js' ;
19
23
import { classMap } from 'lit/directives/class-map.js' ;
20
24
import './ngm-cam-coordinates' ;
21
25
import NavToolsStore from '../store/navTools' ;
22
26
import { dragArea } from './helperElements' ;
23
27
import './ngm-minimap' ;
24
- import { unsafeHTML } from 'lit/directives/unsafe-html.js ' ;
28
+ import { CoordinateWithCrs } from './ngm-cam-coordinates ' ;
25
29
26
30
export type LockType = '' | 'elevation' | 'angle' | 'pitch' | 'move' ;
27
-
31
+ const ABSOLUTE_ELEVATION_MIN = 30000 ;
32
+ const ABSOLUTE_ELEVATION_MAX = 700000 ;
28
33
/*
29
34
* Convert cartographic height (between -30'000m and +300'000) to input value (between 0 and 1)
30
35
* The input value between 0 to 0.5 is mapped to the height between -30'000m and 0m
31
36
* The input between 0.5 and 1 is mapped to the height between 0m and +300'000m
32
37
*/
33
38
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 ) ;
35
40
return m * height + 0.5 ;
36
41
}
37
42
@@ -42,9 +47,9 @@ export function heightToValue(height: number): number {
42
47
*/
43
48
export function valueToHeight ( value : number ) : number {
44
49
if ( value < 0.5 ) {
45
- return ( 30000 / 0.5 ) * value - 30000 ;
50
+ return ( ABSOLUTE_ELEVATION_MIN / 0.5 ) * value - ABSOLUTE_ELEVATION_MIN ;
46
51
} else {
47
- return ( 300000 / 0.5 ) * value - 300000 ;
52
+ return ( ABSOLUTE_ELEVATION_MAX / 0.5 ) * value - ABSOLUTE_ELEVATION_MAX ;
48
53
}
49
54
}
50
55
@@ -69,49 +74,70 @@ export class NgmCamConfiguration extends LitElementI18n {
69
74
@state ( )
70
75
accessor lockType : LockType = '' ;
71
76
// 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' , {
73
78
maximumFractionDigits : 1
74
79
} ) ;
80
+ private timeout : null | NodeJS . Timeout = null ;
75
81
private handler : ScreenSpaceEventHandler | undefined ;
76
82
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 ( ) ;
79
85
private removeOnTick : Event . RemoveCallback | undefined ;
80
- private configurations = [
86
+ private readonly configurations = [
81
87
{
82
- label : ( ) => i18next . t ( 'camera_position_height_label' ) ,
88
+ label : ( ) => html ` ${ i18next . t ( 'camera_position_height_label' ) } < br > ${ i18next . t ( 'camera_position_height_unit' ) } ` ,
83
89
iconClass : ( ) => classMap ( { 'ngm-cam-h-icon' : true , 'ngm-active-icon' : this . lockType === 'elevation' } ) ,
84
90
minValue : 0 ,
85
91
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 ,
87
96
style : ( ) => this . getSliderStyle ( heightToValue ( this . elevation ) , 0 , 1 ) ,
88
97
getValue : ( ) => heightToValue ( this . elevation ) ,
98
+ getInputValue : ( ) => this . elevation . toFixed ( 1 ) ,
89
99
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
+ } ,
91
117
lock : ( ) => this . toggleLock ( 'elevation' )
92
118
} ,
93
119
{
94
- label : ( ) => i18next . t ( 'camera_position_angle_label' ) ,
120
+ label : ( ) => html ` ${ i18next . t ( 'camera_position_angle_label' ) } < br > (°)` ,
95
121
iconClass : ( ) => classMap ( { 'ngm-cam-d-icon' : true , 'ngm-active-icon' : this . lockType === 'angle' } ) ,
96
122
minValue : 0 ,
97
123
maxValue : 359 ,
98
124
step : 1 ,
99
125
style : ( ) => this . getSliderStyle ( this . heading , 0 , 359 , true ) ,
100
126
getValue : ( ) => this . heading ,
101
127
getValueLabel : ( ) => `${ this . integerFormat . format ( this . heading ) } °` ,
102
- onChange : ( evt ) => this . updateAngle ( Number ( evt . target . value ) ) ,
128
+ onSliderChange : ( evt ) => this . updateAngle ( Number ( evt . target . value ) ) ,
103
129
lock : ( ) => this . toggleLock ( 'angle' )
104
130
} ,
105
131
{
106
- label : ( ) => i18next . t ( 'camera_position_pitch_label' ) ,
132
+ label : ( ) => html ` ${ i18next . t ( 'camera_position_pitch_label' ) } < br > (°)` ,
107
133
iconClass : ( ) => classMap ( { 'ngm-cam-t-icon' : true , 'ngm-active-icon' : this . lockType === 'pitch' } ) ,
108
134
minValue : - 90 ,
109
135
maxValue : 90 ,
110
136
step : 1 ,
111
137
style : ( ) => this . getSliderStyle ( this . pitch , - 90 , 90 ) ,
112
138
getValue : ( ) => this . pitch ,
113
139
getValueLabel : ( ) => `${ this . integerFormat . format ( this . pitch ) } °` ,
114
- onChange : ( evt ) => this . updatePitch ( Number ( evt . target . value ) ) ,
140
+ onSliderChange : ( evt ) => this . updatePitch ( Number ( evt . target . value ) ) ,
115
141
lock : ( ) => this . toggleLock ( 'pitch' )
116
142
} ,
117
143
] ;
@@ -130,10 +156,11 @@ export class NgmCamConfiguration extends LitElementI18n {
130
156
super . disconnectedCallback ( ) ;
131
157
}
132
158
159
+
133
160
updated ( changedProperties : PropertyValues ) {
134
161
if ( this . viewer && ! this . unlistenPostRender ) {
135
162
this . scene = this . viewer . scene ;
136
- this . handler = new ScreenSpaceEventHandler ( this . viewer ! . canvas ) ;
163
+ this . handler = new ScreenSpaceEventHandler ( this . viewer . canvas ) ;
137
164
this . unlistenPostRender = this . scene . postRender . addEventListener ( ( ) => this . updateFromCamera ( ) ) ;
138
165
this . updateFromCamera ( ) ;
139
166
}
@@ -145,7 +172,7 @@ export class NgmCamConfiguration extends LitElementI18n {
145
172
updateFromCamera ( ) {
146
173
const camera = this . scene ! . camera ;
147
174
const pc = camera . positionCartographic ;
148
- const altitude = this . scene ! . globe . getHeight ( pc ) || 0 ;
175
+ const altitude = this . scene ! . globe . getHeight ( pc ) ?? 0 ;
149
176
this . elevation = pc . height - altitude ;
150
177
this . pitch = Math . round ( CesiumMath . toDegrees ( camera . pitch ) ) ;
151
178
let heading = Math . round ( CesiumMath . toDegrees ( camera . heading ) ) ;
@@ -160,7 +187,7 @@ export class NgmCamConfiguration extends LitElementI18n {
160
187
}
161
188
162
189
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 ;
164
191
NavToolsStore . setCameraHeight ( value + altitude ) ;
165
192
}
166
193
@@ -184,6 +211,12 @@ export class NgmCamConfiguration extends LitElementI18n {
184
211
} ) ;
185
212
}
186
213
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
+
187
220
getSliderStyle ( value : number , minValue : number , maxValue : number , oneDirection = false ) {
188
221
if ( oneDirection ) {
189
222
return {
@@ -292,21 +325,31 @@ export class NgmCamConfiguration extends LitElementI18n {
292
325
< div class =${ c . iconClass ( ) } title =${ i18next . t ( 'cam_lock' ) } @click=${ c . lock } > </ div >
293
326
< div class ="ngm-cam-conf-slider ">
294
327
< 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
+ />
297
338
</ div >
298
339
< input type ="range " class ="ngm-slider " style =${ styleMap ( c . style ( ) ) }
299
340
min =${ c . minValue }
300
341
max=${ c . maxValue }
301
342
step=${ c . step }
302
343
.value=${ c . getValue ( ) }
303
- @input=${ c . onChange } />
344
+ @input=${ c . onSliderChange }
345
+ @keydown="${ ( e ) => { if ( e . key === 'ArrowLeft' || e . key === 'ArrowRight' ) e . stopPropagation ( ) ; } } "
346
+ />
304
347
</ div >
305
348
</ div > ` ) }
306
349
< div >
307
350
< div class ="ngm-cam-icon ${ classMap ( { 'ngm-active-icon' : this . lockType === 'move' } ) } "
308
351
@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 >
310
353
</ div >
311
354
</ div >
312
355
${ dragArea }
0 commit comments