cesiumjs-anywidget 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cesiumjs_anywidget/__init__.py +23 -2
- cesiumjs_anywidget/exif_utils.py +281 -0
- cesiumjs_anywidget/geoid.py +299 -0
- cesiumjs_anywidget/index.js +1415 -490
- cesiumjs_anywidget/logger.py +73 -0
- cesiumjs_anywidget/styles.css +24 -1
- cesiumjs_anywidget/widget.py +552 -42
- {cesiumjs_anywidget-0.6.0.dist-info → cesiumjs_anywidget-0.7.0.dist-info}/METADATA +45 -6
- cesiumjs_anywidget-0.7.0.dist-info/RECORD +11 -0
- cesiumjs_anywidget-0.6.0.dist-info/RECORD +0 -8
- {cesiumjs_anywidget-0.6.0.dist-info → cesiumjs_anywidget-0.7.0.dist-info}/WHEEL +0 -0
- {cesiumjs_anywidget-0.6.0.dist-info → cesiumjs_anywidget-0.7.0.dist-info}/licenses/LICENSE +0 -0
cesiumjs_anywidget/index.js
CHANGED
|
@@ -23,6 +23,15 @@ function error(prefix, ...args) {
|
|
|
23
23
|
|
|
24
24
|
// src/cesiumjs_anywidget/js/viewer-init.js
|
|
25
25
|
var PREFIX = "ViewerInit";
|
|
26
|
+
var CONSTANTS = {
|
|
27
|
+
// CesiumJS CDN
|
|
28
|
+
CESIUM_CDN_VERSION: "1.137",
|
|
29
|
+
// Interaction Timing
|
|
30
|
+
TIMELINE_SCRUB_DEBOUNCE_MS: 500,
|
|
31
|
+
// GeoJSON Defaults
|
|
32
|
+
GEOJSON_STROKE_WIDTH: 3,
|
|
33
|
+
GEOJSON_FILL_ALPHA: 0.5
|
|
34
|
+
};
|
|
26
35
|
async function loadCesiumJS() {
|
|
27
36
|
log(PREFIX, "Loading CesiumJS...");
|
|
28
37
|
if (window.Cesium) {
|
|
@@ -30,7 +39,7 @@ async function loadCesiumJS() {
|
|
|
30
39
|
return window.Cesium;
|
|
31
40
|
}
|
|
32
41
|
const script = document.createElement("script");
|
|
33
|
-
script.src =
|
|
42
|
+
script.src = `https://cesium.com/downloads/cesiumjs/releases/${CONSTANTS.CESIUM_CDN_VERSION}/Build/Cesium/Cesium.js`;
|
|
34
43
|
log(PREFIX, "Loading CesiumJS from CDN...");
|
|
35
44
|
await new Promise((resolve, reject) => {
|
|
36
45
|
script.onload = () => {
|
|
@@ -79,14 +88,23 @@ function createViewer(container, model, Cesium) {
|
|
|
79
88
|
shadows: false,
|
|
80
89
|
shouldAnimate: false
|
|
81
90
|
};
|
|
91
|
+
const showGlobe = model.get("show_globe");
|
|
92
|
+
if (showGlobe === false) {
|
|
93
|
+
viewerOptions.globe = false;
|
|
94
|
+
log(PREFIX, "Globe disabled (typically for photorealistic tiles)");
|
|
95
|
+
}
|
|
82
96
|
log(PREFIX, "Viewer options:", viewerOptions);
|
|
83
|
-
if (model.get("enable_terrain")) {
|
|
97
|
+
if (model.get("enable_terrain") && showGlobe !== false) {
|
|
84
98
|
viewerOptions.terrain = Cesium.Terrain.fromWorldTerrain();
|
|
85
99
|
log(PREFIX, "Terrain enabled");
|
|
86
100
|
}
|
|
87
101
|
const viewer = new Cesium.Viewer(container, viewerOptions);
|
|
88
|
-
viewer.scene.globe
|
|
89
|
-
|
|
102
|
+
if (viewer.scene.globe) {
|
|
103
|
+
viewer.scene.globe.enableLighting = model.get("enable_lighting");
|
|
104
|
+
log(PREFIX, "Viewer created, lighting:", model.get("enable_lighting"));
|
|
105
|
+
} else {
|
|
106
|
+
log(PREFIX, "Viewer created without globe");
|
|
107
|
+
}
|
|
90
108
|
return viewer;
|
|
91
109
|
}
|
|
92
110
|
function setupViewerListeners(viewer, model, container, Cesium) {
|
|
@@ -98,8 +116,7 @@ function setupViewerListeners(viewer, model, container, Cesium) {
|
|
|
98
116
|
log(PREFIX, "Skipping enable_terrain change - destroyed");
|
|
99
117
|
return;
|
|
100
118
|
}
|
|
101
|
-
if (!viewer)
|
|
102
|
-
return;
|
|
119
|
+
if (!viewer) return;
|
|
103
120
|
log(PREFIX, "Terrain setting changed:", model.get("enable_terrain"));
|
|
104
121
|
if (model.get("enable_terrain")) {
|
|
105
122
|
viewer.scene.setTerrain(Cesium.Terrain.fromWorldTerrain());
|
|
@@ -108,46 +125,35 @@ function setupViewerListeners(viewer, model, container, Cesium) {
|
|
|
108
125
|
}
|
|
109
126
|
});
|
|
110
127
|
model.on("change:enable_lighting", () => {
|
|
111
|
-
if (isDestroyed)
|
|
112
|
-
|
|
113
|
-
if (!viewer)
|
|
114
|
-
return;
|
|
128
|
+
if (isDestroyed) return;
|
|
129
|
+
if (!viewer) return;
|
|
115
130
|
log(PREFIX, "Lighting setting changed:", model.get("enable_lighting"));
|
|
116
131
|
viewer.scene.globe.enableLighting = model.get("enable_lighting");
|
|
117
132
|
});
|
|
118
133
|
model.on("change:height", () => {
|
|
119
|
-
if (isDestroyed)
|
|
120
|
-
|
|
121
|
-
if (!viewer)
|
|
122
|
-
return;
|
|
134
|
+
if (isDestroyed) return;
|
|
135
|
+
if (!viewer) return;
|
|
123
136
|
log(PREFIX, "Height changed:", model.get("height"));
|
|
124
137
|
container.style.height = model.get("height");
|
|
125
138
|
viewer.resize();
|
|
126
139
|
});
|
|
127
140
|
model.on("change:show_timeline", () => {
|
|
128
|
-
if (isDestroyed)
|
|
129
|
-
|
|
130
|
-
if (!viewer || !viewer.timeline)
|
|
131
|
-
return;
|
|
141
|
+
if (isDestroyed) return;
|
|
142
|
+
if (!viewer || !viewer.timeline) return;
|
|
132
143
|
log(PREFIX, "Timeline visibility changed:", model.get("show_timeline"));
|
|
133
144
|
viewer.timeline.container.style.visibility = model.get("show_timeline") ? "visible" : "hidden";
|
|
134
145
|
});
|
|
135
146
|
model.on("change:show_animation", () => {
|
|
136
|
-
if (isDestroyed)
|
|
137
|
-
|
|
138
|
-
if (!viewer || !viewer.animation)
|
|
139
|
-
return;
|
|
147
|
+
if (isDestroyed) return;
|
|
148
|
+
if (!viewer || !viewer.animation) return;
|
|
140
149
|
log(PREFIX, "Animation visibility changed:", model.get("show_animation"));
|
|
141
150
|
viewer.animation.container.style.visibility = model.get("show_animation") ? "visible" : "hidden";
|
|
142
151
|
});
|
|
143
152
|
model.on("change:atmosphere_settings", () => {
|
|
144
|
-
if (isDestroyed)
|
|
145
|
-
|
|
146
|
-
if (!viewer || !viewer.scene || !viewer.scene.atmosphere)
|
|
147
|
-
return;
|
|
153
|
+
if (isDestroyed) return;
|
|
154
|
+
if (!viewer || !viewer.scene || !viewer.scene.atmosphere) return;
|
|
148
155
|
const settings = model.get("atmosphere_settings");
|
|
149
|
-
if (!settings || Object.keys(settings).length === 0)
|
|
150
|
-
return;
|
|
156
|
+
if (!settings || Object.keys(settings).length === 0) return;
|
|
151
157
|
log(PREFIX, "Atmosphere settings changed:", settings);
|
|
152
158
|
const atmosphere = viewer.scene.atmosphere;
|
|
153
159
|
if (settings.brightnessShift !== void 0) {
|
|
@@ -187,13 +193,10 @@ function setupViewerListeners(viewer, model, container, Cesium) {
|
|
|
187
193
|
}
|
|
188
194
|
});
|
|
189
195
|
model.on("change:sky_atmosphere_settings", () => {
|
|
190
|
-
if (isDestroyed)
|
|
191
|
-
|
|
192
|
-
if (!viewer || !viewer.scene || !viewer.scene.skyAtmosphere)
|
|
193
|
-
return;
|
|
196
|
+
if (isDestroyed) return;
|
|
197
|
+
if (!viewer || !viewer.scene || !viewer.scene.skyAtmosphere) return;
|
|
194
198
|
const settings = model.get("sky_atmosphere_settings");
|
|
195
|
-
if (!settings || Object.keys(settings).length === 0)
|
|
196
|
-
return;
|
|
199
|
+
if (!settings || Object.keys(settings).length === 0) return;
|
|
197
200
|
log(PREFIX, "Sky atmosphere settings changed:", settings);
|
|
198
201
|
const skyAtmosphere = viewer.scene.skyAtmosphere;
|
|
199
202
|
if (settings.show !== void 0) {
|
|
@@ -239,13 +242,10 @@ function setupViewerListeners(viewer, model, container, Cesium) {
|
|
|
239
242
|
}
|
|
240
243
|
});
|
|
241
244
|
model.on("change:skybox_settings", () => {
|
|
242
|
-
if (isDestroyed)
|
|
243
|
-
|
|
244
|
-
if (!viewer || !viewer.scene || !viewer.scene.skyBox)
|
|
245
|
-
return;
|
|
245
|
+
if (isDestroyed) return;
|
|
246
|
+
if (!viewer || !viewer.scene || !viewer.scene.skyBox) return;
|
|
246
247
|
const settings = model.get("skybox_settings");
|
|
247
|
-
if (!settings || Object.keys(settings).length === 0)
|
|
248
|
-
return;
|
|
248
|
+
if (!settings || Object.keys(settings).length === 0) return;
|
|
249
249
|
log(PREFIX, "SkyBox settings changed:", settings);
|
|
250
250
|
const skyBox = viewer.scene.skyBox;
|
|
251
251
|
if (settings.show !== void 0) {
|
|
@@ -270,6 +270,184 @@ function setupViewerListeners(viewer, model, container, Cesium) {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
});
|
|
273
|
+
const initialTime = model.get("current_time");
|
|
274
|
+
if (initialTime && viewer.clock) {
|
|
275
|
+
try {
|
|
276
|
+
const julianDate = Cesium.JulianDate.fromIso8601(initialTime);
|
|
277
|
+
viewer.clock.currentTime = julianDate;
|
|
278
|
+
viewer.clock.startTime = julianDate.clone();
|
|
279
|
+
viewer.clock.stopTime = Cesium.JulianDate.addDays(julianDate.clone(), 1, new Cesium.JulianDate());
|
|
280
|
+
log(PREFIX, "Clock initialized with time:", initialTime);
|
|
281
|
+
} catch (err) {
|
|
282
|
+
warn(PREFIX, "Failed to parse initial time:", initialTime, err);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const initialMultiplier = model.get("clock_multiplier");
|
|
286
|
+
if (initialMultiplier !== void 0 && viewer.clock) {
|
|
287
|
+
viewer.clock.multiplier = initialMultiplier;
|
|
288
|
+
log(PREFIX, "Clock multiplier initialized:", initialMultiplier);
|
|
289
|
+
}
|
|
290
|
+
const initialAnimate = model.get("should_animate");
|
|
291
|
+
if (initialAnimate !== void 0 && viewer.clock) {
|
|
292
|
+
viewer.clock.shouldAnimate = initialAnimate;
|
|
293
|
+
log(PREFIX, "Clock animation initialized:", initialAnimate);
|
|
294
|
+
}
|
|
295
|
+
model.on("change:current_time", () => {
|
|
296
|
+
if (isDestroyed) return;
|
|
297
|
+
if (!viewer || !viewer.clock) return;
|
|
298
|
+
const timeStr = model.get("current_time");
|
|
299
|
+
if (!timeStr) return;
|
|
300
|
+
try {
|
|
301
|
+
const julianDate = Cesium.JulianDate.fromIso8601(timeStr);
|
|
302
|
+
viewer.clock.currentTime = julianDate;
|
|
303
|
+
log(PREFIX, "Clock time updated:", timeStr);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
warn(PREFIX, "Failed to parse time:", timeStr, err);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
model.on("change:clock_multiplier", () => {
|
|
309
|
+
if (isDestroyed) return;
|
|
310
|
+
if (!viewer || !viewer.clock) return;
|
|
311
|
+
const multiplier = model.get("clock_multiplier");
|
|
312
|
+
if (multiplier !== void 0) {
|
|
313
|
+
viewer.clock.multiplier = multiplier;
|
|
314
|
+
log(PREFIX, "Clock multiplier updated:", multiplier);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
model.on("change:should_animate", () => {
|
|
318
|
+
if (isDestroyed) return;
|
|
319
|
+
if (!viewer || !viewer.clock) return;
|
|
320
|
+
const shouldAnimate = model.get("should_animate");
|
|
321
|
+
if (shouldAnimate !== void 0) {
|
|
322
|
+
viewer.clock.shouldAnimate = shouldAnimate;
|
|
323
|
+
log(PREFIX, "Clock animation updated:", shouldAnimate);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
model.on("change:clock_command", () => {
|
|
327
|
+
if (isDestroyed) return;
|
|
328
|
+
if (!viewer || !viewer.clock) return;
|
|
329
|
+
const command = model.get("clock_command");
|
|
330
|
+
if (!command || !command.command) return;
|
|
331
|
+
log(PREFIX, "Clock command received:", command.command);
|
|
332
|
+
switch (command.command) {
|
|
333
|
+
case "setTime":
|
|
334
|
+
if (command.time) {
|
|
335
|
+
try {
|
|
336
|
+
const julianDate = Cesium.JulianDate.fromIso8601(command.time);
|
|
337
|
+
viewer.clock.currentTime = julianDate;
|
|
338
|
+
log(PREFIX, "Clock time set via command:", command.time);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
warn(PREFIX, "Failed to parse time in command:", command.time, err);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
case "play":
|
|
345
|
+
viewer.clock.shouldAnimate = true;
|
|
346
|
+
log(PREFIX, "Clock animation started");
|
|
347
|
+
break;
|
|
348
|
+
case "pause":
|
|
349
|
+
viewer.clock.shouldAnimate = false;
|
|
350
|
+
log(PREFIX, "Clock animation paused");
|
|
351
|
+
break;
|
|
352
|
+
case "setMultiplier":
|
|
353
|
+
if (command.multiplier !== void 0) {
|
|
354
|
+
viewer.clock.multiplier = command.multiplier;
|
|
355
|
+
log(PREFIX, "Clock multiplier set via command:", command.multiplier);
|
|
356
|
+
}
|
|
357
|
+
break;
|
|
358
|
+
case "setRange":
|
|
359
|
+
if (command.startTime && command.stopTime) {
|
|
360
|
+
try {
|
|
361
|
+
const startDate = Cesium.JulianDate.fromIso8601(command.startTime);
|
|
362
|
+
const stopDate = Cesium.JulianDate.fromIso8601(command.stopTime);
|
|
363
|
+
viewer.clock.startTime = startDate;
|
|
364
|
+
viewer.clock.stopTime = stopDate;
|
|
365
|
+
log(PREFIX, "Clock range set via command:", command.startTime, "to", command.stopTime);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
warn(PREFIX, "Failed to parse time range in command:", err);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
break;
|
|
371
|
+
default:
|
|
372
|
+
warn(PREFIX, "Unknown clock command:", command.command);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
model.on("change:czml_entity_update", () => {
|
|
376
|
+
if (isDestroyed) return;
|
|
377
|
+
if (!viewer || !viewer.dataSources) return;
|
|
378
|
+
const update = model.get("czml_entity_update");
|
|
379
|
+
if (!update || !update.entity_id || !update.properties) return;
|
|
380
|
+
log(PREFIX, "CZML entity update:", update);
|
|
381
|
+
let entity = null;
|
|
382
|
+
for (let i = 0; i < viewer.dataSources.length; i++) {
|
|
383
|
+
const dataSource = viewer.dataSources.get(i);
|
|
384
|
+
if (dataSource instanceof Cesium.CzmlDataSource) {
|
|
385
|
+
entity = dataSource.entities.getById(update.entity_id);
|
|
386
|
+
if (entity) break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (!entity) {
|
|
390
|
+
warn(PREFIX, "Entity not found:", update.entity_id);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const props = update.properties;
|
|
394
|
+
if (props.orientation && !props.position) {
|
|
395
|
+
const heading = Cesium.Math.toRadians(props.orientation.heading ?? 0);
|
|
396
|
+
const pitch = Cesium.Math.toRadians(props.orientation.pitch ?? 0);
|
|
397
|
+
const roll = Cesium.Math.toRadians(props.orientation.roll ?? 0);
|
|
398
|
+
const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
|
|
399
|
+
if (entity.position) {
|
|
400
|
+
const position = entity.position.getValue(viewer.clock.currentTime);
|
|
401
|
+
if (position) {
|
|
402
|
+
const orientation = Cesium.Transforms.headingPitchRollQuaternion(
|
|
403
|
+
position,
|
|
404
|
+
hpr
|
|
405
|
+
);
|
|
406
|
+
entity.orientation = new Cesium.ConstantProperty(orientation);
|
|
407
|
+
log(PREFIX, "Updated entity orientation:", props.orientation);
|
|
408
|
+
} else {
|
|
409
|
+
warn(PREFIX, "Cannot update orientation - no valid position");
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
warn(PREFIX, "Cannot update orientation - entity has no position");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (props.position) {
|
|
416
|
+
const lat = Cesium.Math.toRadians(props.position.latitude);
|
|
417
|
+
const lon = Cesium.Math.toRadians(props.position.longitude);
|
|
418
|
+
const alt = props.position.altitude ?? 0;
|
|
419
|
+
const position = Cesium.Cartesian3.fromRadians(lon, lat, alt);
|
|
420
|
+
entity.position = new Cesium.ConstantPositionProperty(position);
|
|
421
|
+
log(PREFIX, "Updated entity position:", props.position);
|
|
422
|
+
if (props.orientation && entity.orientation) {
|
|
423
|
+
const heading = Cesium.Math.toRadians(props.orientation.heading ?? 0);
|
|
424
|
+
const pitch = Cesium.Math.toRadians(props.orientation.pitch ?? 0);
|
|
425
|
+
const roll = Cesium.Math.toRadians(props.orientation.roll ?? 0);
|
|
426
|
+
const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
|
|
427
|
+
const orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
|
|
428
|
+
entity.orientation = new Cesium.ConstantProperty(orientation);
|
|
429
|
+
log(PREFIX, "Updated entity orientation after position change:", props.orientation);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
for (const [key, value] of Object.entries(props)) {
|
|
433
|
+
if (key !== "orientation" && key !== "position" && entity[key] !== void 0) {
|
|
434
|
+
try {
|
|
435
|
+
if (typeof value === "object" && value !== null) {
|
|
436
|
+
for (const [subKey, subValue] of Object.entries(value)) {
|
|
437
|
+
if (entity[key][subKey] !== void 0) {
|
|
438
|
+
entity[key][subKey] = new Cesium.ConstantProperty(subValue);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
entity[key] = new Cesium.ConstantProperty(value);
|
|
443
|
+
}
|
|
444
|
+
log(PREFIX, `Updated entity ${key}:`, value);
|
|
445
|
+
} catch (err) {
|
|
446
|
+
warn(PREFIX, `Failed to update property ${key}:`, err);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
});
|
|
273
451
|
function getCameraState() {
|
|
274
452
|
if (!viewer || !viewer.camera || !viewer.camera.positionCartographic) {
|
|
275
453
|
warn(PREFIX, "Cannot get camera state - viewer or camera not available");
|
|
@@ -291,8 +469,7 @@ function setupViewerListeners(viewer, model, container, Cesium) {
|
|
|
291
469
|
}
|
|
292
470
|
}
|
|
293
471
|
function getClockState() {
|
|
294
|
-
if (!viewer || !viewer.clock)
|
|
295
|
-
return null;
|
|
472
|
+
if (!viewer || !viewer.clock) return null;
|
|
296
473
|
try {
|
|
297
474
|
return {
|
|
298
475
|
current_time: Cesium.JulianDate.toIso8601(viewer.clock.currentTime),
|
|
@@ -304,6 +481,61 @@ function setupViewerListeners(viewer, model, container, Cesium) {
|
|
|
304
481
|
return null;
|
|
305
482
|
}
|
|
306
483
|
}
|
|
484
|
+
function getPickedPosition(click) {
|
|
485
|
+
if (!viewer || !viewer.camera || !viewer.scene || !viewer.scene.globe) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
const ray = viewer.camera.getPickRay(click.position);
|
|
490
|
+
if (!ray) return null;
|
|
491
|
+
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
|
|
492
|
+
if (!cartesian) return null;
|
|
493
|
+
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
|
|
494
|
+
return {
|
|
495
|
+
latitude: Cesium.Math.toDegrees(cartographic.latitude),
|
|
496
|
+
longitude: Cesium.Math.toDegrees(cartographic.longitude),
|
|
497
|
+
altitude: cartographic.height
|
|
498
|
+
};
|
|
499
|
+
} catch (error2) {
|
|
500
|
+
warn(PREFIX, "Error picking position:", error2);
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function getPickedEntity(click) {
|
|
505
|
+
if (!viewer || !viewer.scene || !viewer.clock) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
const pickedObject = viewer.scene.pick(click.position);
|
|
510
|
+
if (!Cesium.defined(pickedObject) || !Cesium.defined(pickedObject.id)) {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
const entity = pickedObject.id;
|
|
514
|
+
const entityData = {
|
|
515
|
+
id: entity.id,
|
|
516
|
+
name: entity.name || null
|
|
517
|
+
};
|
|
518
|
+
if (entity.properties) {
|
|
519
|
+
const props = {};
|
|
520
|
+
const propertyNames = entity.properties.propertyNames;
|
|
521
|
+
if (propertyNames && propertyNames.length > 0) {
|
|
522
|
+
propertyNames.forEach((name) => {
|
|
523
|
+
try {
|
|
524
|
+
props[name] = entity.properties[name].getValue(viewer.clock.currentTime);
|
|
525
|
+
} catch (e) {
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
if (Object.keys(props).length > 0) {
|
|
529
|
+
entityData.properties = props;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return entityData;
|
|
534
|
+
} catch (error2) {
|
|
535
|
+
warn(PREFIX, "Error picking entity:", error2);
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
307
539
|
function sendInteractionEvent(type, additionalData = {}) {
|
|
308
540
|
if (isDestroyed) {
|
|
309
541
|
log(PREFIX, "Skipping interaction event - destroyed:", type);
|
|
@@ -331,88 +563,37 @@ function setupViewerListeners(viewer, model, container, Cesium) {
|
|
|
331
563
|
}
|
|
332
564
|
const camera = viewer.camera;
|
|
333
565
|
camera.moveEnd.addEventListener(() => {
|
|
334
|
-
if (isDestroyed || !viewer)
|
|
335
|
-
return;
|
|
566
|
+
if (isDestroyed || !viewer) return;
|
|
336
567
|
sendInteractionEvent("camera_move");
|
|
337
568
|
});
|
|
338
569
|
const scene = viewer.scene;
|
|
339
570
|
const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
|
|
340
571
|
handler.setInputAction((click) => {
|
|
341
|
-
if (isDestroyed || !viewer || !viewer.scene || !viewer.camera)
|
|
342
|
-
return;
|
|
572
|
+
if (isDestroyed || !viewer || !viewer.scene || !viewer.camera) return;
|
|
343
573
|
const pickedData = {};
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
|
|
348
|
-
if (cartesian) {
|
|
349
|
-
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
|
|
350
|
-
pickedData.picked_position = {
|
|
351
|
-
latitude: Cesium.Math.toDegrees(cartographic.latitude),
|
|
352
|
-
longitude: Cesium.Math.toDegrees(cartographic.longitude),
|
|
353
|
-
altitude: cartographic.height
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
} catch (error2) {
|
|
358
|
-
warn(PREFIX, "Error picking position:", error2);
|
|
574
|
+
const pickedPosition = getPickedPosition(click);
|
|
575
|
+
if (pickedPosition) {
|
|
576
|
+
pickedData.picked_position = pickedPosition;
|
|
359
577
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const entity = pickedObject.id;
|
|
364
|
-
pickedData.picked_entity = {
|
|
365
|
-
id: entity.id,
|
|
366
|
-
name: entity.name || null
|
|
367
|
-
};
|
|
368
|
-
if (entity.properties) {
|
|
369
|
-
const props = {};
|
|
370
|
-
const propertyNames = entity.properties.propertyNames;
|
|
371
|
-
if (propertyNames && propertyNames.length > 0) {
|
|
372
|
-
propertyNames.forEach((name) => {
|
|
373
|
-
try {
|
|
374
|
-
props[name] = entity.properties[name].getValue(viewer.clock.currentTime);
|
|
375
|
-
} catch (e) {
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
if (Object.keys(props).length > 0) {
|
|
379
|
-
pickedData.picked_entity.properties = props;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
} catch (error2) {
|
|
385
|
-
warn(PREFIX, "Error picking entity:", error2);
|
|
578
|
+
const pickedEntity = getPickedEntity(click);
|
|
579
|
+
if (pickedEntity) {
|
|
580
|
+
pickedData.picked_entity = pickedEntity;
|
|
386
581
|
}
|
|
387
582
|
sendInteractionEvent("left_click", pickedData);
|
|
388
583
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
389
584
|
handler.setInputAction((click) => {
|
|
390
|
-
if (isDestroyed || !viewer || !viewer.scene || !viewer.camera)
|
|
391
|
-
return;
|
|
585
|
+
if (isDestroyed || !viewer || !viewer.scene || !viewer.camera) return;
|
|
392
586
|
const pickedData = {};
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
|
|
397
|
-
if (cartesian) {
|
|
398
|
-
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
|
|
399
|
-
pickedData.picked_position = {
|
|
400
|
-
latitude: Cesium.Math.toDegrees(cartographic.latitude),
|
|
401
|
-
longitude: Cesium.Math.toDegrees(cartographic.longitude),
|
|
402
|
-
altitude: cartographic.height
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
} catch (error2) {
|
|
407
|
-
warn(PREFIX, "Error picking position:", error2);
|
|
587
|
+
const pickedPosition = getPickedPosition(click);
|
|
588
|
+
if (pickedPosition) {
|
|
589
|
+
pickedData.picked_position = pickedPosition;
|
|
408
590
|
}
|
|
409
591
|
sendInteractionEvent("right_click", pickedData);
|
|
410
592
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
|
|
411
593
|
if (viewer.timeline) {
|
|
412
594
|
let timelineScrubbing = false;
|
|
413
595
|
viewer.clock.onTick.addEventListener(() => {
|
|
414
|
-
if (isDestroyed)
|
|
415
|
-
return;
|
|
596
|
+
if (isDestroyed) return;
|
|
416
597
|
if (viewer.timeline) {
|
|
417
598
|
if (scrubTimeout) {
|
|
418
599
|
clearTimeout(scrubTimeout);
|
|
@@ -423,12 +604,26 @@ function setupViewerListeners(viewer, model, container, Cesium) {
|
|
|
423
604
|
timelineScrubbing = false;
|
|
424
605
|
sendInteractionEvent("timeline_scrub");
|
|
425
606
|
}
|
|
426
|
-
},
|
|
607
|
+
}, CONSTANTS.TIMELINE_SCRUB_DEBOUNCE_MS);
|
|
427
608
|
timelineScrubbing = true;
|
|
428
609
|
}
|
|
429
610
|
});
|
|
430
611
|
}
|
|
431
612
|
log(PREFIX, "Viewer listeners setup complete");
|
|
613
|
+
return {
|
|
614
|
+
destroy: () => {
|
|
615
|
+
log(PREFIX, "Destroying viewer listeners");
|
|
616
|
+
isDestroyed = true;
|
|
617
|
+
if (scrubTimeout) {
|
|
618
|
+
clearTimeout(scrubTimeout);
|
|
619
|
+
scrubTimeout = null;
|
|
620
|
+
}
|
|
621
|
+
if (handler) {
|
|
622
|
+
handler.destroy();
|
|
623
|
+
}
|
|
624
|
+
log(PREFIX, "Viewer listeners destroyed");
|
|
625
|
+
}
|
|
626
|
+
};
|
|
432
627
|
}
|
|
433
628
|
function setupGeoJSONLoader(viewer, model, Cesium) {
|
|
434
629
|
log(PREFIX, "Setting up GeoJSON loader");
|
|
@@ -457,10 +652,10 @@ function setupGeoJSONLoader(viewer, model, Cesium) {
|
|
|
457
652
|
log(PREFIX, "Loading GeoJSON dataset...");
|
|
458
653
|
const dataSource = await Cesium.GeoJsonDataSource.load(geojsonData, {
|
|
459
654
|
stroke: Cesium.Color.HOTPINK,
|
|
460
|
-
fill: Cesium.Color.PINK.withAlpha(
|
|
461
|
-
strokeWidth:
|
|
655
|
+
fill: Cesium.Color.PINK.withAlpha(CONSTANTS.GEOJSON_FILL_ALPHA),
|
|
656
|
+
strokeWidth: CONSTANTS.GEOJSON_STROKE_WIDTH
|
|
462
657
|
});
|
|
463
|
-
if (viewer && viewer.dataSources) {
|
|
658
|
+
if (!isDestroyed && viewer && viewer.dataSources) {
|
|
464
659
|
viewer.dataSources.add(dataSource);
|
|
465
660
|
geojsonDataSources.push(dataSource);
|
|
466
661
|
log(PREFIX, "GeoJSON dataset loaded successfully");
|
|
@@ -498,6 +693,7 @@ function setupCZMLLoader(viewer, model, Cesium) {
|
|
|
498
693
|
log(PREFIX, "Setting up CZML loader");
|
|
499
694
|
let czmlDataSources = [];
|
|
500
695
|
let isDestroyed = false;
|
|
696
|
+
viewer._czmlDataSources = czmlDataSources;
|
|
501
697
|
async function loadCZMLData(flyToData = true) {
|
|
502
698
|
if (isDestroyed) {
|
|
503
699
|
log(PREFIX, "Skipping czml_data load - destroyed");
|
|
@@ -521,7 +717,7 @@ function setupCZMLLoader(viewer, model, Cesium) {
|
|
|
521
717
|
try {
|
|
522
718
|
log(PREFIX, "Loading CZML document with", czmlData.length, "packets...");
|
|
523
719
|
const dataSource = await Cesium.CzmlDataSource.load(czmlData);
|
|
524
|
-
if (viewer && viewer.dataSources) {
|
|
720
|
+
if (!isDestroyed && viewer && viewer.dataSources) {
|
|
525
721
|
viewer.dataSources.add(dataSource);
|
|
526
722
|
czmlDataSources.push(dataSource);
|
|
527
723
|
log(PREFIX, "CZML document loaded successfully, entities:", dataSource.entities.values.length);
|
|
@@ -558,20 +754,109 @@ function setupCZMLLoader(viewer, model, Cesium) {
|
|
|
558
754
|
}
|
|
559
755
|
};
|
|
560
756
|
}
|
|
757
|
+
function setupPhotorealisticTiles(viewer, model, Cesium) {
|
|
758
|
+
log(PREFIX, "Setting up Photorealistic 3D Tiles loader");
|
|
759
|
+
let photorealisticTileset = null;
|
|
760
|
+
let isDestroyed = false;
|
|
761
|
+
async function updatePhotorealisticTiles() {
|
|
762
|
+
if (isDestroyed) {
|
|
763
|
+
log(PREFIX, "Skipping photorealistic tiles update - destroyed");
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (!viewer || !viewer.scene || !viewer.scene.primitives) {
|
|
767
|
+
warn(PREFIX, "Cannot update photorealistic tiles - viewer not available");
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const enabled = model.get("enable_photorealistic_tiles");
|
|
771
|
+
log(PREFIX, "Photorealistic tiles enabled:", enabled);
|
|
772
|
+
if (photorealisticTileset) {
|
|
773
|
+
log(PREFIX, "Removing existing photorealistic tileset");
|
|
774
|
+
viewer.scene.primitives.remove(photorealisticTileset);
|
|
775
|
+
photorealisticTileset = null;
|
|
776
|
+
}
|
|
777
|
+
if (enabled) {
|
|
778
|
+
try {
|
|
779
|
+
log(PREFIX, "Creating Google Photorealistic 3D Tileset...");
|
|
780
|
+
photorealisticTileset = await Cesium.createGooglePhotorealistic3DTileset();
|
|
781
|
+
if (viewer && viewer.scene && viewer.scene.primitives && !isDestroyed) {
|
|
782
|
+
viewer.scene.primitives.add(photorealisticTileset);
|
|
783
|
+
log(PREFIX, "Google Photorealistic 3D Tileset loaded successfully");
|
|
784
|
+
} else {
|
|
785
|
+
log(PREFIX, "Viewer destroyed during tileset load, skipping add");
|
|
786
|
+
photorealisticTileset = null;
|
|
787
|
+
}
|
|
788
|
+
} catch (err) {
|
|
789
|
+
error(PREFIX, "Failed to load Google Photorealistic 3D Tileset:", err);
|
|
790
|
+
photorealisticTileset = null;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
model.on("change:enable_photorealistic_tiles", () => updatePhotorealisticTiles());
|
|
795
|
+
if (model.get("enable_photorealistic_tiles")) {
|
|
796
|
+
log(PREFIX, "Loading initial photorealistic tiles...");
|
|
797
|
+
updatePhotorealisticTiles();
|
|
798
|
+
}
|
|
799
|
+
return {
|
|
800
|
+
destroy: () => {
|
|
801
|
+
log(PREFIX, "Destroying photorealistic tiles loader");
|
|
802
|
+
isDestroyed = true;
|
|
803
|
+
if (photorealisticTileset && viewer && viewer.scene && viewer.scene.primitives) {
|
|
804
|
+
viewer.scene.primitives.remove(photorealisticTileset);
|
|
805
|
+
}
|
|
806
|
+
photorealisticTileset = null;
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
}
|
|
561
810
|
|
|
562
811
|
// src/cesiumjs_anywidget/js/camera-sync.js
|
|
563
812
|
var PREFIX2 = "CameraSync";
|
|
564
813
|
function initializeCameraSync(viewer, model) {
|
|
565
814
|
const Cesium = window.Cesium;
|
|
566
815
|
let cameraUpdateTimeout = null;
|
|
816
|
+
let modelUpdateTimeout = null;
|
|
567
817
|
let isDestroyed = false;
|
|
568
818
|
let syncEnabled = model.get("camera_sync_enabled") || false;
|
|
569
819
|
log(PREFIX2, "Initializing camera synchronization, sync enabled:", syncEnabled);
|
|
570
|
-
|
|
820
|
+
if (!Cesium) {
|
|
821
|
+
error(PREFIX2, "Cesium global not available");
|
|
822
|
+
throw new Error("Cesium is not loaded");
|
|
823
|
+
}
|
|
824
|
+
const handleSyncEnabledChange = () => {
|
|
571
825
|
syncEnabled = model.get("camera_sync_enabled");
|
|
572
826
|
log(PREFIX2, "Camera sync enabled changed:", syncEnabled);
|
|
573
|
-
}
|
|
574
|
-
|
|
827
|
+
};
|
|
828
|
+
model.on("change:camera_sync_enabled", handleSyncEnabledChange);
|
|
829
|
+
function validateCameraParameters(lat, lon, alt) {
|
|
830
|
+
if (typeof lat !== "number" || isNaN(lat) || lat < -90 || lat > 90) {
|
|
831
|
+
warn(PREFIX2, "Invalid latitude:", lat);
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
if (typeof lon !== "number" || isNaN(lon)) {
|
|
835
|
+
warn(PREFIX2, "Invalid longitude:", lon);
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
if (typeof alt !== "number" || isNaN(alt)) {
|
|
839
|
+
warn(PREFIX2, "Invalid altitude:", alt);
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
function validateOrientationAngles(heading, pitch, roll) {
|
|
845
|
+
if (typeof heading !== "number" || isNaN(heading)) {
|
|
846
|
+
warn(PREFIX2, "Invalid heading:", heading);
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
if (typeof pitch !== "number" || isNaN(pitch)) {
|
|
850
|
+
warn(PREFIX2, "Invalid pitch:", pitch);
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
853
|
+
if (typeof roll !== "number" || isNaN(roll)) {
|
|
854
|
+
warn(PREFIX2, "Invalid roll:", roll);
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
function updateCameraFromModelImmediate() {
|
|
575
860
|
if (isDestroyed) {
|
|
576
861
|
log(PREFIX2, "Skipping updateCameraFromModel - module destroyed");
|
|
577
862
|
return;
|
|
@@ -583,14 +868,38 @@ function initializeCameraSync(viewer, model) {
|
|
|
583
868
|
const lat = model.get("latitude");
|
|
584
869
|
const lon = model.get("longitude");
|
|
585
870
|
const alt = model.get("altitude");
|
|
586
|
-
const heading =
|
|
587
|
-
const pitch =
|
|
588
|
-
const roll =
|
|
871
|
+
const heading = model.get("heading");
|
|
872
|
+
const pitch = model.get("pitch");
|
|
873
|
+
const roll = model.get("roll");
|
|
874
|
+
if (!validateCameraParameters(lat, lon, alt)) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (!validateOrientationAngles(heading, pitch, roll)) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
589
880
|
log(PREFIX2, "Updating camera from model:", { lat, lon, alt });
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
881
|
+
try {
|
|
882
|
+
viewer.camera.setView({
|
|
883
|
+
destination: Cesium.Cartesian3.fromDegrees(lon, lat, alt),
|
|
884
|
+
orientation: {
|
|
885
|
+
heading: Cesium.Math.toRadians(heading),
|
|
886
|
+
pitch: Cesium.Math.toRadians(pitch),
|
|
887
|
+
roll: Cesium.Math.toRadians(roll)
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
} catch (err) {
|
|
891
|
+
error(PREFIX2, "Error updating camera from model:", err);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function updateCameraFromModel() {
|
|
895
|
+
if (modelUpdateTimeout) {
|
|
896
|
+
clearTimeout(modelUpdateTimeout);
|
|
897
|
+
}
|
|
898
|
+
modelUpdateTimeout = setTimeout(() => {
|
|
899
|
+
if (!isDestroyed) {
|
|
900
|
+
updateCameraFromModelImmediate();
|
|
901
|
+
}
|
|
902
|
+
}, 50);
|
|
594
903
|
}
|
|
595
904
|
function updateModelFromCamera() {
|
|
596
905
|
if (isDestroyed) {
|
|
@@ -639,7 +948,7 @@ function initializeCameraSync(viewer, model) {
|
|
|
639
948
|
}
|
|
640
949
|
}, 500);
|
|
641
950
|
}
|
|
642
|
-
|
|
951
|
+
updateCameraFromModelImmediate();
|
|
643
952
|
viewer.camera.changed.addEventListener(handleCameraChanged);
|
|
644
953
|
model.on("change:latitude", updateCameraFromModel);
|
|
645
954
|
model.on("change:longitude", updateCameraFromModel);
|
|
@@ -647,19 +956,22 @@ function initializeCameraSync(viewer, model) {
|
|
|
647
956
|
model.on("change:heading", updateCameraFromModel);
|
|
648
957
|
model.on("change:pitch", updateCameraFromModel);
|
|
649
958
|
model.on("change:roll", updateCameraFromModel);
|
|
650
|
-
|
|
959
|
+
const handleCameraCommand = () => {
|
|
651
960
|
if (isDestroyed) {
|
|
652
961
|
log(PREFIX2, "Skipping camera_command - module destroyed");
|
|
653
962
|
return;
|
|
654
963
|
}
|
|
655
964
|
const command = model.get("camera_command");
|
|
656
|
-
if (!command || !command.command || !command.timestamp)
|
|
657
|
-
return;
|
|
965
|
+
if (!command || !command.command || !command.timestamp) return;
|
|
658
966
|
const cmd = command.command;
|
|
659
967
|
log(PREFIX2, "Executing camera command:", cmd, command);
|
|
660
968
|
try {
|
|
661
969
|
switch (cmd) {
|
|
662
970
|
case "flyTo":
|
|
971
|
+
if (!validateCameraParameters(command.latitude, command.longitude, command.altitude)) {
|
|
972
|
+
error(PREFIX2, "Invalid parameters for flyTo command");
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
663
975
|
viewer.camera.flyTo({
|
|
664
976
|
destination: Cesium.Cartesian3.fromDegrees(
|
|
665
977
|
command.longitude,
|
|
@@ -675,6 +987,10 @@ function initializeCameraSync(viewer, model) {
|
|
|
675
987
|
});
|
|
676
988
|
break;
|
|
677
989
|
case "setView":
|
|
990
|
+
if (!validateCameraParameters(command.latitude, command.longitude, command.altitude)) {
|
|
991
|
+
error(PREFIX2, "Invalid parameters for setView command");
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
678
994
|
viewer.camera.setView({
|
|
679
995
|
destination: Cesium.Cartesian3.fromDegrees(
|
|
680
996
|
command.longitude,
|
|
@@ -687,8 +1003,35 @@ function initializeCameraSync(viewer, model) {
|
|
|
687
1003
|
roll: Cesium.Math.toRadians(command.roll || 0)
|
|
688
1004
|
}
|
|
689
1005
|
});
|
|
1006
|
+
if (command.fov !== void 0 || command.aspectRatio !== void 0 || command.near !== void 0 || command.far !== void 0) {
|
|
1007
|
+
const frustum = viewer.camera.frustum;
|
|
1008
|
+
if (frustum instanceof Cesium.PerspectiveFrustum) {
|
|
1009
|
+
if (command.fov !== void 0) {
|
|
1010
|
+
frustum.fov = Cesium.Math.toRadians(command.fov);
|
|
1011
|
+
}
|
|
1012
|
+
if (command.aspectRatio !== void 0) {
|
|
1013
|
+
frustum.aspectRatio = command.aspectRatio;
|
|
1014
|
+
}
|
|
1015
|
+
if (command.near !== void 0) {
|
|
1016
|
+
frustum.near = command.near;
|
|
1017
|
+
}
|
|
1018
|
+
if (command.far !== void 0) {
|
|
1019
|
+
frustum.far = command.far;
|
|
1020
|
+
}
|
|
1021
|
+
log(PREFIX2, "Applied frustum parameters:", {
|
|
1022
|
+
fov: command.fov,
|
|
1023
|
+
aspectRatio: command.aspectRatio,
|
|
1024
|
+
near: command.near,
|
|
1025
|
+
far: command.far
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
690
1029
|
break;
|
|
691
1030
|
case "lookAt":
|
|
1031
|
+
if (!validateCameraParameters(command.targetLatitude, command.targetLongitude, command.targetAltitude || 0)) {
|
|
1032
|
+
error(PREFIX2, "Invalid parameters for lookAt command");
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
692
1035
|
const target = Cesium.Cartesian3.fromDegrees(
|
|
693
1036
|
command.targetLongitude,
|
|
694
1037
|
command.targetLatitude,
|
|
@@ -744,9 +1087,11 @@ function initializeCameraSync(viewer, model) {
|
|
|
744
1087
|
} catch (err) {
|
|
745
1088
|
error(PREFIX2, `Error executing camera command ${cmd}:`, err);
|
|
746
1089
|
}
|
|
747
|
-
}
|
|
1090
|
+
};
|
|
1091
|
+
model.on("change:camera_command", handleCameraCommand);
|
|
748
1092
|
return {
|
|
749
1093
|
updateCameraFromModel,
|
|
1094
|
+
updateCameraFromModelImmediate,
|
|
750
1095
|
updateModelFromCamera,
|
|
751
1096
|
destroy: () => {
|
|
752
1097
|
log(PREFIX2, "Destroying camera sync module");
|
|
@@ -755,7 +1100,21 @@ function initializeCameraSync(viewer, model) {
|
|
|
755
1100
|
clearTimeout(cameraUpdateTimeout);
|
|
756
1101
|
cameraUpdateTimeout = null;
|
|
757
1102
|
}
|
|
758
|
-
|
|
1103
|
+
if (modelUpdateTimeout) {
|
|
1104
|
+
clearTimeout(modelUpdateTimeout);
|
|
1105
|
+
modelUpdateTimeout = null;
|
|
1106
|
+
}
|
|
1107
|
+
if (viewer && viewer.camera) {
|
|
1108
|
+
viewer.camera.changed.removeEventListener(handleCameraChanged);
|
|
1109
|
+
}
|
|
1110
|
+
model.off("change:camera_sync_enabled", handleSyncEnabledChange);
|
|
1111
|
+
model.off("change:latitude", updateCameraFromModel);
|
|
1112
|
+
model.off("change:longitude", updateCameraFromModel);
|
|
1113
|
+
model.off("change:altitude", updateCameraFromModel);
|
|
1114
|
+
model.off("change:heading", updateCameraFromModel);
|
|
1115
|
+
model.off("change:pitch", updateCameraFromModel);
|
|
1116
|
+
model.off("change:roll", updateCameraFromModel);
|
|
1117
|
+
model.off("change:camera_command", handleCameraCommand);
|
|
759
1118
|
log(PREFIX2, "Camera sync module destroyed");
|
|
760
1119
|
}
|
|
761
1120
|
};
|
|
@@ -763,6 +1122,210 @@ function initializeCameraSync(viewer, model) {
|
|
|
763
1122
|
|
|
764
1123
|
// src/cesiumjs_anywidget/js/measurement-tools.js
|
|
765
1124
|
var PREFIX3 = "Measurements";
|
|
1125
|
+
var CONSTANTS2 = {
|
|
1126
|
+
// UI Dimensions
|
|
1127
|
+
MARKER_SIZE: 10,
|
|
1128
|
+
MARKER_SIZE_HOVER: 12,
|
|
1129
|
+
MARKER_SIZE_SELECTED: 15,
|
|
1130
|
+
MARKER_OUTLINE_WIDTH: 2,
|
|
1131
|
+
MARKER_OUTLINE_WIDTH_HOVER: 3,
|
|
1132
|
+
MARKER_OUTLINE_WIDTH_SELECTED: 4,
|
|
1133
|
+
// Panel Dimensions
|
|
1134
|
+
PANEL_MIN_WIDTH: 250,
|
|
1135
|
+
PANEL_MIN_HEIGHT: 200,
|
|
1136
|
+
PANEL_MAX_WIDTH: 600,
|
|
1137
|
+
PANEL_MAX_HEIGHT: 800,
|
|
1138
|
+
// Measurement Thresholds
|
|
1139
|
+
MAX_ADD_POINT_DISTANCE_METERS: 50,
|
|
1140
|
+
AREA_UNIT_THRESHOLD: 1e6,
|
|
1141
|
+
// m² to km² conversion
|
|
1142
|
+
DISTANCE_UNIT_THRESHOLD: 1e3,
|
|
1143
|
+
// m to km conversion
|
|
1144
|
+
// Visual
|
|
1145
|
+
POLYLINE_WIDTH: 3,
|
|
1146
|
+
LABEL_PIXEL_OFFSET_Y: -20,
|
|
1147
|
+
POLYGON_ALPHA: 0.3,
|
|
1148
|
+
POLYGON_OUTLINE_WIDTH: 2
|
|
1149
|
+
};
|
|
1150
|
+
function getColors() {
|
|
1151
|
+
const Cesium = window.Cesium;
|
|
1152
|
+
return {
|
|
1153
|
+
distance: { main: Cesium.Color.RED, button: "#e74c3c" },
|
|
1154
|
+
"multi-distance": { main: Cesium.Color.BLUE, button: "#3498db" },
|
|
1155
|
+
height: { main: Cesium.Color.GREEN, button: "#2ecc71" },
|
|
1156
|
+
area: { main: Cesium.Color.ORANGE, button: "#e67e22" }
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
var TYPE_LABELS = {
|
|
1160
|
+
distance: "Distance",
|
|
1161
|
+
"multi-distance": "Multi-Distance",
|
|
1162
|
+
height: "Height",
|
|
1163
|
+
area: "Area"
|
|
1164
|
+
};
|
|
1165
|
+
function calculateGeodesicArea(positions) {
|
|
1166
|
+
const Cesium = window.Cesium;
|
|
1167
|
+
const polygonHierarchy = new Cesium.PolygonHierarchy(positions);
|
|
1168
|
+
const geometry = Cesium.PolygonGeometry.createGeometry(
|
|
1169
|
+
new Cesium.PolygonGeometry({
|
|
1170
|
+
polygonHierarchy,
|
|
1171
|
+
perPositionHeight: false,
|
|
1172
|
+
arcType: Cesium.ArcType.GEODESIC
|
|
1173
|
+
})
|
|
1174
|
+
);
|
|
1175
|
+
if (!geometry) return 0;
|
|
1176
|
+
const positionsArray = geometry.attributes.position.values;
|
|
1177
|
+
const indices = geometry.indices;
|
|
1178
|
+
let area = 0;
|
|
1179
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
1180
|
+
const i0 = indices[i] * 3;
|
|
1181
|
+
const i1 = indices[i + 1] * 3;
|
|
1182
|
+
const i2 = indices[i + 2] * 3;
|
|
1183
|
+
const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
|
|
1184
|
+
const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
|
|
1185
|
+
const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
|
|
1186
|
+
const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
|
|
1187
|
+
const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
|
|
1188
|
+
const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
|
|
1189
|
+
area += Cesium.Cartesian3.magnitude(crossProduct) / 2;
|
|
1190
|
+
}
|
|
1191
|
+
return area;
|
|
1192
|
+
}
|
|
1193
|
+
function calculateCentroid(positions) {
|
|
1194
|
+
const Cesium = window.Cesium;
|
|
1195
|
+
let centroidLon = 0, centroidLat = 0;
|
|
1196
|
+
positions.forEach((pos) => {
|
|
1197
|
+
const carto = Cesium.Cartographic.fromCartesian(pos);
|
|
1198
|
+
centroidLon += carto.longitude;
|
|
1199
|
+
centroidLat += carto.latitude;
|
|
1200
|
+
});
|
|
1201
|
+
return new Cesium.Cartographic(centroidLon / positions.length, centroidLat / positions.length);
|
|
1202
|
+
}
|
|
1203
|
+
function formatValue(value, isArea = false) {
|
|
1204
|
+
if (isArea) {
|
|
1205
|
+
return value >= CONSTANTS2.AREA_UNIT_THRESHOLD ? `${(value / CONSTANTS2.AREA_UNIT_THRESHOLD).toFixed(2)} km\xB2` : `${value.toFixed(2)} m\xB2`;
|
|
1206
|
+
}
|
|
1207
|
+
return value >= CONSTANTS2.DISTANCE_UNIT_THRESHOLD ? `${(value / CONSTANTS2.DISTANCE_UNIT_THRESHOLD).toFixed(2)} km` : `${value.toFixed(2)} m`;
|
|
1208
|
+
}
|
|
1209
|
+
function createStyledButton(text, baseColor, activeColor = "#e74c3c") {
|
|
1210
|
+
const btn = document.createElement("button");
|
|
1211
|
+
btn.textContent = text;
|
|
1212
|
+
btn.dataset.baseColor = baseColor;
|
|
1213
|
+
btn.dataset.activeColor = activeColor;
|
|
1214
|
+
btn.style.cssText = `
|
|
1215
|
+
padding: 8px 12px;
|
|
1216
|
+
background: ${baseColor};
|
|
1217
|
+
color: white;
|
|
1218
|
+
border: none;
|
|
1219
|
+
border-radius: 3px;
|
|
1220
|
+
cursor: pointer;
|
|
1221
|
+
font-size: 12px;
|
|
1222
|
+
transition: background 0.2s;
|
|
1223
|
+
`;
|
|
1224
|
+
return btn;
|
|
1225
|
+
}
|
|
1226
|
+
function makeDraggable(panel, handle) {
|
|
1227
|
+
let isDragging = false;
|
|
1228
|
+
let startX, startY, initialLeft, initialTop;
|
|
1229
|
+
handle.style.cursor = "move";
|
|
1230
|
+
const handleMouseDown = (e) => {
|
|
1231
|
+
if (e.button !== 0 || e.target.tagName === "BUTTON" || e.target.tagName === "INPUT") return;
|
|
1232
|
+
isDragging = true;
|
|
1233
|
+
startX = e.clientX;
|
|
1234
|
+
startY = e.clientY;
|
|
1235
|
+
const rect = panel.getBoundingClientRect();
|
|
1236
|
+
const parentRect = panel.parentElement.getBoundingClientRect();
|
|
1237
|
+
initialLeft = rect.left - parentRect.left;
|
|
1238
|
+
initialTop = rect.top - parentRect.top;
|
|
1239
|
+
panel.style.right = "auto";
|
|
1240
|
+
panel.style.bottom = "auto";
|
|
1241
|
+
panel.style.left = initialLeft + "px";
|
|
1242
|
+
panel.style.top = initialTop + "px";
|
|
1243
|
+
e.preventDefault();
|
|
1244
|
+
};
|
|
1245
|
+
const handleMouseMove = (e) => {
|
|
1246
|
+
if (!isDragging) return;
|
|
1247
|
+
const deltaX = e.clientX - startX;
|
|
1248
|
+
const deltaY = e.clientY - startY;
|
|
1249
|
+
const parentRect = panel.parentElement.getBoundingClientRect();
|
|
1250
|
+
const panelRect = panel.getBoundingClientRect();
|
|
1251
|
+
let newLeft = initialLeft + deltaX;
|
|
1252
|
+
let newTop = initialTop + deltaY;
|
|
1253
|
+
newLeft = Math.max(0, Math.min(newLeft, parentRect.width - panelRect.width));
|
|
1254
|
+
newTop = Math.max(0, Math.min(newTop, parentRect.height - panelRect.height));
|
|
1255
|
+
panel.style.left = newLeft + "px";
|
|
1256
|
+
panel.style.top = newTop + "px";
|
|
1257
|
+
};
|
|
1258
|
+
const handleMouseUp = () => {
|
|
1259
|
+
isDragging = false;
|
|
1260
|
+
};
|
|
1261
|
+
handle.addEventListener("mousedown", handleMouseDown);
|
|
1262
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
1263
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
1264
|
+
return () => {
|
|
1265
|
+
handle.removeEventListener("mousedown", handleMouseDown);
|
|
1266
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
1267
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
function makeResizable(panel, minWidth = CONSTANTS2.PANEL_MIN_WIDTH, minHeight = CONSTANTS2.PANEL_MIN_HEIGHT, maxWidth = CONSTANTS2.PANEL_MAX_WIDTH, maxHeight = CONSTANTS2.PANEL_MAX_HEIGHT) {
|
|
1271
|
+
const resizeHandle = document.createElement("div");
|
|
1272
|
+
resizeHandle.style.cssText = `
|
|
1273
|
+
position: absolute;
|
|
1274
|
+
bottom: 2px;
|
|
1275
|
+
right: 2px;
|
|
1276
|
+
width: 20px;
|
|
1277
|
+
height: 20px;
|
|
1278
|
+
cursor: nwse-resize;
|
|
1279
|
+
z-index: 1002;
|
|
1280
|
+
`;
|
|
1281
|
+
resizeHandle.innerHTML = `
|
|
1282
|
+
<svg width="20" height="20" viewBox="0 0 20 20" style="pointer-events: none;">
|
|
1283
|
+
<path d="M 20 0 L 20 20 L 0 20" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="1"/>
|
|
1284
|
+
<path d="M 14 20 L 20 14" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
|
1285
|
+
<path d="M 8 20 L 20 8" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
|
1286
|
+
</svg>
|
|
1287
|
+
`;
|
|
1288
|
+
panel.appendChild(resizeHandle);
|
|
1289
|
+
let isResizing = false;
|
|
1290
|
+
let startX, startY, startWidth, startHeight;
|
|
1291
|
+
const handleMouseDown = (e) => {
|
|
1292
|
+
isResizing = true;
|
|
1293
|
+
startX = e.clientX;
|
|
1294
|
+
startY = e.clientY;
|
|
1295
|
+
const rect = panel.getBoundingClientRect();
|
|
1296
|
+
startWidth = rect.width;
|
|
1297
|
+
startHeight = rect.height;
|
|
1298
|
+
e.preventDefault();
|
|
1299
|
+
e.stopPropagation();
|
|
1300
|
+
};
|
|
1301
|
+
const handleMouseMove = (e) => {
|
|
1302
|
+
if (!isResizing) return;
|
|
1303
|
+
const deltaX = e.clientX - startX;
|
|
1304
|
+
const deltaY = e.clientY - startY;
|
|
1305
|
+
let newWidth = startWidth + deltaX;
|
|
1306
|
+
let newHeight = startHeight + deltaY;
|
|
1307
|
+
newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth));
|
|
1308
|
+
newHeight = Math.max(minHeight, Math.min(newHeight, maxHeight));
|
|
1309
|
+
panel.style.width = newWidth + "px";
|
|
1310
|
+
panel.style.height = newHeight + "px";
|
|
1311
|
+
panel.style.maxWidth = "none";
|
|
1312
|
+
panel.style.maxHeight = "none";
|
|
1313
|
+
};
|
|
1314
|
+
const handleMouseUp = () => {
|
|
1315
|
+
isResizing = false;
|
|
1316
|
+
};
|
|
1317
|
+
resizeHandle.addEventListener("mousedown", handleMouseDown);
|
|
1318
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
1319
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
1320
|
+
return () => {
|
|
1321
|
+
resizeHandle.removeEventListener("mousedown", handleMouseDown);
|
|
1322
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
1323
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
1324
|
+
if (resizeHandle.parentNode) {
|
|
1325
|
+
resizeHandle.remove();
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
766
1329
|
function initializeMeasurementTools(viewer, model, container) {
|
|
767
1330
|
log(PREFIX3, "Initializing measurement tools");
|
|
768
1331
|
const Cesium = window.Cesium;
|
|
@@ -784,121 +1347,106 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
784
1347
|
selectedEntity: null,
|
|
785
1348
|
dragging: false,
|
|
786
1349
|
measurementIndex: null,
|
|
787
|
-
pointIndex: null
|
|
1350
|
+
pointIndex: null,
|
|
1351
|
+
addPointMode: false
|
|
788
1352
|
};
|
|
789
|
-
|
|
1353
|
+
const cleanupFunctions = [];
|
|
790
1354
|
const toolbarDiv = document.createElement("div");
|
|
791
1355
|
toolbarDiv.style.cssText = `
|
|
792
1356
|
position: absolute;
|
|
793
1357
|
top: 10px;
|
|
794
1358
|
left: 10px;
|
|
795
1359
|
background: rgba(42, 42, 42, 0.9);
|
|
796
|
-
padding:
|
|
1360
|
+
padding: 0;
|
|
797
1361
|
border-radius: 5px;
|
|
798
1362
|
z-index: 1000;
|
|
799
1363
|
display: flex;
|
|
800
1364
|
flex-direction: column;
|
|
801
|
-
gap:
|
|
1365
|
+
gap: 0;
|
|
802
1366
|
`;
|
|
803
1367
|
container.appendChild(toolbarDiv);
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1368
|
+
const toolbarHeader = document.createElement("div");
|
|
1369
|
+
toolbarHeader.style.cssText = `
|
|
1370
|
+
padding: 8px 10px;
|
|
1371
|
+
background: rgba(60, 60, 60, 0.95);
|
|
1372
|
+
border-radius: 5px 5px 0 0;
|
|
1373
|
+
font-family: sans-serif;
|
|
1374
|
+
font-size: 11px;
|
|
1375
|
+
color: #aaa;
|
|
1376
|
+
border-bottom: 1px solid #555;
|
|
1377
|
+
user-select: none;
|
|
1378
|
+
`;
|
|
1379
|
+
toolbarHeader.textContent = "\u22EE\u22EE Measurement Tools";
|
|
1380
|
+
toolbarDiv.appendChild(toolbarHeader);
|
|
1381
|
+
const toolbarContent = document.createElement("div");
|
|
1382
|
+
toolbarContent.style.cssText = `
|
|
1383
|
+
padding: 10px;
|
|
1384
|
+
display: flex;
|
|
1385
|
+
flex-direction: column;
|
|
1386
|
+
gap: 5px;
|
|
1387
|
+
`;
|
|
1388
|
+
toolbarDiv.appendChild(toolbarContent);
|
|
1389
|
+
cleanupFunctions.push(makeDraggable(toolbarDiv, toolbarHeader));
|
|
1390
|
+
function setupButtonHover(btn, hoverColor, getActiveState = () => false) {
|
|
1391
|
+
const baseColor = btn.dataset.baseColor;
|
|
817
1392
|
btn.onmouseover = () => {
|
|
818
|
-
btn.style.background =
|
|
1393
|
+
btn.style.background = hoverColor;
|
|
819
1394
|
};
|
|
820
1395
|
btn.onmouseout = () => {
|
|
821
|
-
btn.style.background =
|
|
1396
|
+
btn.style.background = getActiveState() ? btn.dataset.activeColor : baseColor;
|
|
822
1397
|
};
|
|
1398
|
+
}
|
|
1399
|
+
function createMeasurementButton(text, mode) {
|
|
1400
|
+
const btn = createStyledButton(text, "#3498db");
|
|
1401
|
+
setupButtonHover(btn, "#2980b9", () => measurementState.mode === mode);
|
|
823
1402
|
btn.onclick = () => {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
model.save_changes();
|
|
827
|
-
} else {
|
|
828
|
-
model.set("measurement_mode", mode);
|
|
829
|
-
model.save_changes();
|
|
830
|
-
}
|
|
1403
|
+
model.set("measurement_mode", measurementState.mode === mode ? "" : mode);
|
|
1404
|
+
model.save_changes();
|
|
831
1405
|
};
|
|
832
1406
|
return btn;
|
|
833
1407
|
}
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
clearBtn.textContent = "\u{1F5D1}\uFE0F Clear";
|
|
840
|
-
clearBtn.style.cssText = `
|
|
841
|
-
padding: 8px 12px;
|
|
842
|
-
background: #e74c3c;
|
|
843
|
-
color: white;
|
|
844
|
-
border: none;
|
|
845
|
-
border-radius: 3px;
|
|
846
|
-
cursor: pointer;
|
|
847
|
-
font-size: 12px;
|
|
848
|
-
transition: background 0.2s;
|
|
849
|
-
`;
|
|
850
|
-
clearBtn.onmouseover = () => {
|
|
851
|
-
clearBtn.style.background = "#c0392b";
|
|
852
|
-
};
|
|
853
|
-
clearBtn.onmouseout = () => {
|
|
854
|
-
clearBtn.style.background = "#e74c3c";
|
|
1408
|
+
const modeButtons = {
|
|
1409
|
+
distance: createMeasurementButton("\u{1F4CF} Distance", "distance"),
|
|
1410
|
+
"multi-distance": createMeasurementButton("\u{1F4D0} Multi Distance", "multi-distance"),
|
|
1411
|
+
height: createMeasurementButton("\u{1F4CA} Height", "height"),
|
|
1412
|
+
area: createMeasurementButton("\u2B1B Area", "area")
|
|
855
1413
|
};
|
|
1414
|
+
const clearBtn = createStyledButton("\u{1F5D1}\uFE0F Clear", "#e74c3c");
|
|
1415
|
+
setupButtonHover(clearBtn, "#c0392b");
|
|
856
1416
|
clearBtn.onclick = () => {
|
|
857
1417
|
clearAllMeasurements();
|
|
858
1418
|
model.set("measurement_mode", "");
|
|
859
1419
|
model.set("measurement_results", []);
|
|
860
1420
|
model.save_changes();
|
|
861
1421
|
};
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
toolbarDiv.appendChild(clearBtn);
|
|
867
|
-
const editBtn = document.createElement("button");
|
|
868
|
-
editBtn.textContent = "\u270F\uFE0F Edit Points";
|
|
869
|
-
editBtn.style.cssText = `
|
|
870
|
-
padding: 8px 12px;
|
|
871
|
-
background: #9b59b6;
|
|
872
|
-
color: white;
|
|
873
|
-
border: none;
|
|
874
|
-
border-radius: 3px;
|
|
875
|
-
cursor: pointer;
|
|
876
|
-
font-size: 12px;
|
|
877
|
-
transition: background 0.2s;
|
|
878
|
-
`;
|
|
879
|
-
editBtn.onmouseover = () => {
|
|
880
|
-
editBtn.style.background = "#8e44ad";
|
|
881
|
-
};
|
|
882
|
-
editBtn.onmouseout = () => {
|
|
883
|
-
editBtn.style.background = editState.enabled ? "#e74c3c" : "#9b59b6";
|
|
884
|
-
};
|
|
1422
|
+
Object.values(modeButtons).forEach((btn) => toolbarContent.appendChild(btn));
|
|
1423
|
+
toolbarContent.appendChild(clearBtn);
|
|
1424
|
+
const editBtn = createStyledButton("\u270F\uFE0F Edit Points", "#9b59b6");
|
|
1425
|
+
setupButtonHover(editBtn, "#8e44ad", () => editState.enabled);
|
|
885
1426
|
editBtn.onclick = () => {
|
|
886
1427
|
editState.enabled = !editState.enabled;
|
|
887
1428
|
editBtn.style.background = editState.enabled ? "#e74c3c" : "#9b59b6";
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1429
|
+
editState.enabled ? enableEditMode() : disableEditMode();
|
|
1430
|
+
};
|
|
1431
|
+
toolbarContent.appendChild(editBtn);
|
|
1432
|
+
const addPointBtn = createStyledButton("\u2795 Add Point", "#16a085");
|
|
1433
|
+
addPointBtn.style.display = "none";
|
|
1434
|
+
setupButtonHover(addPointBtn, "#138d75", () => editState.addPointMode);
|
|
1435
|
+
addPointBtn.onclick = () => {
|
|
1436
|
+
editState.addPointMode = !editState.addPointMode;
|
|
1437
|
+
addPointBtn.style.background = editState.addPointMode ? "#e74c3c" : "#16a085";
|
|
1438
|
+
if (editState.addPointMode) {
|
|
1439
|
+
deselectPoint();
|
|
892
1440
|
}
|
|
893
1441
|
};
|
|
894
|
-
|
|
1442
|
+
toolbarContent.appendChild(addPointBtn);
|
|
895
1443
|
const editorPanel = document.createElement("div");
|
|
896
1444
|
editorPanel.style.cssText = `
|
|
897
1445
|
position: absolute;
|
|
898
1446
|
top: 10px;
|
|
899
1447
|
right: 10px;
|
|
900
1448
|
background: rgba(42, 42, 42, 0.95);
|
|
901
|
-
padding:
|
|
1449
|
+
padding: 0;
|
|
902
1450
|
border-radius: 5px;
|
|
903
1451
|
z-index: 1000;
|
|
904
1452
|
display: none;
|
|
@@ -907,49 +1455,108 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
907
1455
|
font-size: 12px;
|
|
908
1456
|
min-width: 250px;
|
|
909
1457
|
`;
|
|
1458
|
+
const editorHeader = document.createElement("div");
|
|
1459
|
+
editorHeader.style.cssText = `
|
|
1460
|
+
padding: 10px 15px;
|
|
1461
|
+
background: rgba(60, 60, 60, 0.95);
|
|
1462
|
+
border-radius: 5px 5px 0 0;
|
|
1463
|
+
font-weight: bold;
|
|
1464
|
+
border-bottom: 1px solid #555;
|
|
1465
|
+
user-select: none;
|
|
1466
|
+
cursor: move;
|
|
1467
|
+
`;
|
|
1468
|
+
editorHeader.textContent = "\u22EE\u22EE Edit Point";
|
|
1469
|
+
editorPanel.appendChild(editorHeader);
|
|
1470
|
+
const editorContent = document.createElement("div");
|
|
1471
|
+
editorContent.style.cssText = `padding: 15px;`;
|
|
1472
|
+
editorPanel.appendChild(editorContent);
|
|
910
1473
|
container.appendChild(editorPanel);
|
|
1474
|
+
cleanupFunctions.push(makeDraggable(editorPanel, editorHeader));
|
|
911
1475
|
const measurementsListPanel = document.createElement("div");
|
|
912
1476
|
measurementsListPanel.style.cssText = `
|
|
913
1477
|
position: absolute;
|
|
914
1478
|
bottom: 10px;
|
|
915
1479
|
right: 10px;
|
|
916
1480
|
background: rgba(42, 42, 42, 0.95);
|
|
917
|
-
padding:
|
|
1481
|
+
padding: 0;
|
|
918
1482
|
border-radius: 5px;
|
|
919
1483
|
z-index: 1000;
|
|
920
1484
|
color: white;
|
|
921
1485
|
font-family: sans-serif;
|
|
922
1486
|
font-size: 12px;
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1487
|
+
width: 350px;
|
|
1488
|
+
height: 400px;
|
|
1489
|
+
min-width: 250px;
|
|
1490
|
+
min-height: 200px;
|
|
1491
|
+
max-width: 600px;
|
|
1492
|
+
max-height: 800px;
|
|
1493
|
+
display: flex;
|
|
1494
|
+
flex-direction: column;
|
|
1495
|
+
box-sizing: border-box;
|
|
1496
|
+
overflow: hidden;
|
|
926
1497
|
`;
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1498
|
+
const measurementsHeader = document.createElement("div");
|
|
1499
|
+
measurementsHeader.style.cssText = `
|
|
1500
|
+
padding: 10px 15px;
|
|
1501
|
+
background: rgba(60, 60, 60, 0.95);
|
|
1502
|
+
border-radius: 5px 5px 0 0;
|
|
1503
|
+
font-weight: bold;
|
|
1504
|
+
border-bottom: 1px solid #555;
|
|
1505
|
+
user-select: none;
|
|
1506
|
+
cursor: move;
|
|
1507
|
+
flex-shrink: 0;
|
|
1508
|
+
box-sizing: border-box;
|
|
932
1509
|
`;
|
|
1510
|
+
measurementsHeader.textContent = "\u22EE\u22EE Measurements";
|
|
1511
|
+
measurementsListPanel.appendChild(measurementsHeader);
|
|
1512
|
+
const measurementsContent = document.createElement("div");
|
|
1513
|
+
measurementsContent.className = "measurement-panel-scrollable";
|
|
1514
|
+
measurementsContent.style.cssText = `
|
|
1515
|
+
padding: 10px 15px 25px 15px;
|
|
1516
|
+
overflow-y: scroll;
|
|
1517
|
+
overflow-x: hidden;
|
|
1518
|
+
flex: 1 1 auto;
|
|
1519
|
+
min-height: 0;
|
|
1520
|
+
max-height: 100%;
|
|
1521
|
+
word-wrap: break-word;
|
|
1522
|
+
overflow-wrap: break-word;
|
|
1523
|
+
box-sizing: border-box;
|
|
1524
|
+
position: relative;
|
|
1525
|
+
z-index: 1;
|
|
1526
|
+
`;
|
|
1527
|
+
measurementsContent.innerHTML = `<div id="measurements-list-content" style="width: 100%; box-sizing: border-box;"></div>`;
|
|
1528
|
+
measurementsListPanel.appendChild(measurementsContent);
|
|
933
1529
|
container.appendChild(measurementsListPanel);
|
|
1530
|
+
cleanupFunctions.push(makeDraggable(measurementsListPanel, measurementsHeader));
|
|
1531
|
+
cleanupFunctions.push(makeResizable(measurementsListPanel));
|
|
934
1532
|
function getPosition(screenPosition) {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1533
|
+
try {
|
|
1534
|
+
const pickedObject = viewer.scene.pick(screenPosition);
|
|
1535
|
+
if (viewer.scene.pickPositionSupported && Cesium.defined(pickedObject)) {
|
|
1536
|
+
const cartesian = viewer.scene.pickPosition(screenPosition);
|
|
1537
|
+
if (Cesium.defined(cartesian)) {
|
|
1538
|
+
return cartesian;
|
|
1539
|
+
}
|
|
940
1540
|
}
|
|
1541
|
+
const ray = viewer.camera.getPickRay(screenPosition);
|
|
1542
|
+
if (!ray) {
|
|
1543
|
+
warn(PREFIX3, "Unable to get pick ray from camera");
|
|
1544
|
+
return null;
|
|
1545
|
+
}
|
|
1546
|
+
return viewer.scene.globe.pick(ray, viewer.scene);
|
|
1547
|
+
} catch (err) {
|
|
1548
|
+
error(PREFIX3, "Error getting position:", err);
|
|
1549
|
+
return null;
|
|
941
1550
|
}
|
|
942
|
-
const ray = viewer.camera.getPickRay(screenPosition);
|
|
943
|
-
return viewer.scene.globe.pick(ray, viewer.scene);
|
|
944
1551
|
}
|
|
945
1552
|
function addMarker(position, color = Cesium.Color.RED) {
|
|
946
1553
|
const marker = viewer.entities.add({
|
|
947
1554
|
position,
|
|
948
1555
|
point: {
|
|
949
|
-
pixelSize:
|
|
1556
|
+
pixelSize: CONSTANTS2.MARKER_SIZE,
|
|
950
1557
|
color,
|
|
951
1558
|
outlineColor: Cesium.Color.WHITE,
|
|
952
|
-
outlineWidth:
|
|
1559
|
+
outlineWidth: CONSTANTS2.MARKER_OUTLINE_WIDTH,
|
|
953
1560
|
disableDepthTestDistance: Number.POSITIVE_INFINITY
|
|
954
1561
|
}
|
|
955
1562
|
});
|
|
@@ -966,7 +1573,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
966
1573
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|
967
1574
|
outlineWidth: 2,
|
|
968
1575
|
outlineColor: Cesium.Color.BLACK,
|
|
969
|
-
pixelOffset: new Cesium.Cartesian2(0,
|
|
1576
|
+
pixelOffset: new Cesium.Cartesian2(0, CONSTANTS2.LABEL_PIXEL_OFFSET_Y),
|
|
970
1577
|
showBackground: true,
|
|
971
1578
|
backgroundColor: Cesium.Color.fromAlpha(Cesium.Color.BLACK, 0.7),
|
|
972
1579
|
disableDepthTestDistance: Number.POSITIVE_INFINITY
|
|
@@ -989,6 +1596,27 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
989
1596
|
alt: cartographic.height
|
|
990
1597
|
};
|
|
991
1598
|
}
|
|
1599
|
+
function updateOrCreateMeasurement(type, value, points) {
|
|
1600
|
+
const results = model.get("measurement_results") || [];
|
|
1601
|
+
const lastResult = results[results.length - 1];
|
|
1602
|
+
const isActiveType = lastResult && lastResult.type === type && lastResult.isActive;
|
|
1603
|
+
let newResults;
|
|
1604
|
+
if (isActiveType) {
|
|
1605
|
+
newResults = [...results];
|
|
1606
|
+
newResults[newResults.length - 1] = { ...lastResult, value, points };
|
|
1607
|
+
} else {
|
|
1608
|
+
const count = results.filter((r) => r.type === type).length + 1;
|
|
1609
|
+
newResults = [...results, {
|
|
1610
|
+
type,
|
|
1611
|
+
value,
|
|
1612
|
+
points,
|
|
1613
|
+
isActive: type === "multi-distance" || type === "area",
|
|
1614
|
+
name: `${TYPE_LABELS[type]} ${count}`
|
|
1615
|
+
}];
|
|
1616
|
+
}
|
|
1617
|
+
model.set("measurement_results", newResults);
|
|
1618
|
+
model.save_changes();
|
|
1619
|
+
}
|
|
992
1620
|
function clearAllMeasurements() {
|
|
993
1621
|
log(PREFIX3, "Clearing all measurements");
|
|
994
1622
|
measurementState.entities.forEach((e) => viewer.entities.remove(e));
|
|
@@ -1025,19 +1653,24 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1025
1653
|
model.set("measurement_mode", "");
|
|
1026
1654
|
model.save_changes();
|
|
1027
1655
|
}
|
|
1656
|
+
addPointBtn.style.display = "block";
|
|
1028
1657
|
measurementState.entities.forEach((entity) => {
|
|
1029
1658
|
if (entity.point) {
|
|
1030
|
-
entity.point.pixelSize =
|
|
1031
|
-
entity.point.outlineWidth =
|
|
1659
|
+
entity.point.pixelSize = CONSTANTS2.MARKER_SIZE_HOVER;
|
|
1660
|
+
entity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH_HOVER;
|
|
1032
1661
|
}
|
|
1033
1662
|
});
|
|
1034
1663
|
editHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
|
1035
1664
|
editHandler.setInputAction((click) => {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
selectPoint(pickedObject.id, click.position);
|
|
1665
|
+
if (editState.addPointMode) {
|
|
1666
|
+
handleAddPointClick(click);
|
|
1039
1667
|
} else {
|
|
1040
|
-
|
|
1668
|
+
const pickedObject = viewer.scene.pick(click.position);
|
|
1669
|
+
if (Cesium.defined(pickedObject) && pickedObject.id && pickedObject.id.point) {
|
|
1670
|
+
selectPoint(pickedObject.id, click.position);
|
|
1671
|
+
} else {
|
|
1672
|
+
deselectPoint();
|
|
1673
|
+
}
|
|
1041
1674
|
}
|
|
1042
1675
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
1043
1676
|
editHandler.setInputAction((movement) => {
|
|
@@ -1049,7 +1682,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1049
1682
|
}
|
|
1050
1683
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
|
1051
1684
|
editHandler.setInputAction(() => {
|
|
1052
|
-
if (editState.selectedEntity) {
|
|
1685
|
+
if (editState.selectedEntity && !editState.addPointMode) {
|
|
1053
1686
|
editState.dragging = true;
|
|
1054
1687
|
viewer.scene.screenSpaceCameraController.enableRotate = false;
|
|
1055
1688
|
}
|
|
@@ -1067,11 +1700,14 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1067
1700
|
editHandler.destroy();
|
|
1068
1701
|
editHandler = null;
|
|
1069
1702
|
}
|
|
1703
|
+
addPointBtn.style.display = "none";
|
|
1704
|
+
editState.addPointMode = false;
|
|
1705
|
+
addPointBtn.style.background = "#16a085";
|
|
1070
1706
|
deselectPoint();
|
|
1071
1707
|
measurementState.entities.forEach((entity) => {
|
|
1072
1708
|
if (entity.point) {
|
|
1073
|
-
entity.point.pixelSize =
|
|
1074
|
-
entity.point.outlineWidth =
|
|
1709
|
+
entity.point.pixelSize = CONSTANTS2.MARKER_SIZE;
|
|
1710
|
+
entity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH;
|
|
1075
1711
|
}
|
|
1076
1712
|
});
|
|
1077
1713
|
viewer.scene.screenSpaceCameraController.enableRotate = true;
|
|
@@ -1096,21 +1732,20 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1096
1732
|
break;
|
|
1097
1733
|
}
|
|
1098
1734
|
}
|
|
1099
|
-
if (measurementIndex === -1)
|
|
1100
|
-
return;
|
|
1735
|
+
if (measurementIndex === -1) return;
|
|
1101
1736
|
editState.selectedEntity = entity;
|
|
1102
1737
|
editState.measurementIndex = measurementIndex;
|
|
1103
1738
|
editState.pointIndex = pointIndex;
|
|
1104
1739
|
editState.selectedPoint = entity.position.getValue(Cesium.JulianDate.now());
|
|
1105
|
-
entity.point.pixelSize =
|
|
1106
|
-
entity.point.outlineWidth =
|
|
1740
|
+
entity.point.pixelSize = CONSTANTS2.MARKER_SIZE_SELECTED;
|
|
1741
|
+
entity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH_SELECTED;
|
|
1107
1742
|
entity.point.outlineColor = Cesium.Color.YELLOW;
|
|
1108
1743
|
showCoordinateEditor(results[measurementIndex], pointIndex);
|
|
1109
1744
|
}
|
|
1110
1745
|
function deselectPoint() {
|
|
1111
1746
|
if (editState.selectedEntity && editState.selectedEntity.point) {
|
|
1112
|
-
editState.selectedEntity.point.pixelSize =
|
|
1113
|
-
editState.selectedEntity.point.outlineWidth =
|
|
1747
|
+
editState.selectedEntity.point.pixelSize = CONSTANTS2.MARKER_SIZE_HOVER;
|
|
1748
|
+
editState.selectedEntity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH_HOVER;
|
|
1114
1749
|
editState.selectedEntity.point.outlineColor = Cesium.Color.WHITE;
|
|
1115
1750
|
}
|
|
1116
1751
|
editState.selectedEntity = null;
|
|
@@ -1122,10 +1757,8 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1122
1757
|
}
|
|
1123
1758
|
function showCoordinateEditor(measurement, pointIndex) {
|
|
1124
1759
|
const point = measurement.points[pointIndex];
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
Edit Point ${pointIndex + 1} (${measurement.type})
|
|
1128
|
-
</div>
|
|
1760
|
+
editorHeader.textContent = `\u22EE\u22EE Edit Point ${pointIndex + 1} (${measurement.type})`;
|
|
1761
|
+
editorContent.innerHTML = `
|
|
1129
1762
|
<div style="margin-bottom: 8px;">
|
|
1130
1763
|
<label style="display: block; margin-bottom: 3px;">Longitude (\xB0):</label>
|
|
1131
1764
|
<input type="number" id="edit-lon" value="${point.lon.toFixed(6)}" step="0.000001"
|
|
@@ -1188,16 +1821,14 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1188
1821
|
});
|
|
1189
1822
|
}
|
|
1190
1823
|
function updatePointPosition(newPosition) {
|
|
1191
|
-
if (!editState.selectedEntity)
|
|
1192
|
-
return;
|
|
1824
|
+
if (!editState.selectedEntity) return;
|
|
1193
1825
|
editState.selectedEntity.position = newPosition;
|
|
1194
1826
|
editState.selectedPoint = newPosition;
|
|
1195
1827
|
updateMeasurementVisuals();
|
|
1196
1828
|
}
|
|
1197
1829
|
function updateMeasurementVisuals() {
|
|
1198
1830
|
const results = model.get("measurement_results") || [];
|
|
1199
|
-
if (editState.measurementIndex === null)
|
|
1200
|
-
return;
|
|
1831
|
+
if (editState.measurementIndex === null) return;
|
|
1201
1832
|
const measurement = results[editState.measurementIndex];
|
|
1202
1833
|
let entityStartIndex = 0;
|
|
1203
1834
|
for (let i = 0; i < editState.measurementIndex; i++) {
|
|
@@ -1218,10 +1849,10 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1218
1849
|
const newPolygon = viewer.entities.add({
|
|
1219
1850
|
polygon: {
|
|
1220
1851
|
hierarchy: new Cesium.PolygonHierarchy(positions),
|
|
1221
|
-
material: Cesium.Color.ORANGE.withAlpha(
|
|
1852
|
+
material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
|
|
1222
1853
|
outline: true,
|
|
1223
1854
|
outlineColor: Cesium.Color.ORANGE,
|
|
1224
|
-
outlineWidth:
|
|
1855
|
+
outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
|
|
1225
1856
|
}
|
|
1226
1857
|
});
|
|
1227
1858
|
measurementState.polylines[polylineStartIndex] = newPolygon;
|
|
@@ -1246,10 +1877,9 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1246
1877
|
if (type === "distance") {
|
|
1247
1878
|
const distance = Cesium.Cartesian3.distance(positions[0], positions[1]);
|
|
1248
1879
|
const midpoint = Cesium.Cartesian3.midpoint(positions[0], positions[1], new Cesium.Cartesian3());
|
|
1249
|
-
const distanceText = distance >= 1e3 ? `${(distance / 1e3).toFixed(2)} km` : `${distance.toFixed(2)} m`;
|
|
1250
1880
|
if (measurementState.labels[labelStartIndex]) {
|
|
1251
1881
|
measurementState.labels[labelStartIndex].position = midpoint;
|
|
1252
|
-
measurementState.labels[labelStartIndex].label.text =
|
|
1882
|
+
measurementState.labels[labelStartIndex].label.text = formatValue(distance);
|
|
1253
1883
|
}
|
|
1254
1884
|
} else if (type === "height") {
|
|
1255
1885
|
const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
|
|
@@ -1257,16 +1887,14 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1257
1887
|
const verticalDistance = Math.abs(carto1.height - carto0.height);
|
|
1258
1888
|
const midHeight = (carto0.height + carto1.height) / 2;
|
|
1259
1889
|
const labelPos = Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, midHeight);
|
|
1260
|
-
const heightText = verticalDistance >= 1e3 ? `${(verticalDistance / 1e3).toFixed(2)} km` : `${verticalDistance.toFixed(2)} m`;
|
|
1261
1890
|
if (measurementState.labels[labelStartIndex]) {
|
|
1262
1891
|
measurementState.labels[labelStartIndex].position = labelPos;
|
|
1263
|
-
measurementState.labels[labelStartIndex].label.text =
|
|
1892
|
+
measurementState.labels[labelStartIndex].label.text = formatValue(verticalDistance);
|
|
1264
1893
|
}
|
|
1265
1894
|
}
|
|
1266
1895
|
}
|
|
1267
1896
|
function finalizeMeasurementUpdate() {
|
|
1268
|
-
if (editState.measurementIndex === null || editState.pointIndex === null)
|
|
1269
|
-
return;
|
|
1897
|
+
if (editState.measurementIndex === null || editState.pointIndex === null) return;
|
|
1270
1898
|
const results = model.get("measurement_results") || [];
|
|
1271
1899
|
const measurement = results[editState.measurementIndex];
|
|
1272
1900
|
const cartographic = Cesium.Cartographic.fromCartesian(editState.selectedPoint);
|
|
@@ -1299,33 +1927,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1299
1927
|
}
|
|
1300
1928
|
measurement.value = totalDistance;
|
|
1301
1929
|
} else if (measurement.type === "area") {
|
|
1302
|
-
|
|
1303
|
-
const geometry = Cesium.PolygonGeometry.createGeometry(
|
|
1304
|
-
new Cesium.PolygonGeometry({
|
|
1305
|
-
polygonHierarchy,
|
|
1306
|
-
perPositionHeight: false,
|
|
1307
|
-
arcType: Cesium.ArcType.GEODESIC
|
|
1308
|
-
})
|
|
1309
|
-
);
|
|
1310
|
-
let area = 0;
|
|
1311
|
-
if (geometry) {
|
|
1312
|
-
const positionsArray = geometry.attributes.position.values;
|
|
1313
|
-
const indices = geometry.indices;
|
|
1314
|
-
for (let i = 0; i < indices.length; i += 3) {
|
|
1315
|
-
const i0 = indices[i] * 3;
|
|
1316
|
-
const i1 = indices[i + 1] * 3;
|
|
1317
|
-
const i2 = indices[i + 2] * 3;
|
|
1318
|
-
const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
|
|
1319
|
-
const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
|
|
1320
|
-
const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
|
|
1321
|
-
const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
|
|
1322
|
-
const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
|
|
1323
|
-
const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
|
|
1324
|
-
const triangleArea = Cesium.Cartesian3.magnitude(crossProduct) / 2;
|
|
1325
|
-
area += triangleArea;
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
measurement.value = area;
|
|
1930
|
+
measurement.value = calculateGeodesicArea(positions);
|
|
1329
1931
|
}
|
|
1330
1932
|
const newResults = [...results];
|
|
1331
1933
|
model.set("measurement_results", newResults);
|
|
@@ -1335,6 +1937,169 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1335
1937
|
showCoordinateEditor(measurement, editState.pointIndex);
|
|
1336
1938
|
}
|
|
1337
1939
|
}
|
|
1940
|
+
function handleAddPointClick(click) {
|
|
1941
|
+
const position = getPosition(click.position);
|
|
1942
|
+
if (!position) {
|
|
1943
|
+
log(PREFIX3, "No valid position for add point");
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
const results = model.get("measurement_results") || [];
|
|
1947
|
+
let nearestMeasurement = null;
|
|
1948
|
+
let nearestSegment = null;
|
|
1949
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
1950
|
+
let insertIndex = -1;
|
|
1951
|
+
for (let measIdx = 0; measIdx < results.length; measIdx++) {
|
|
1952
|
+
const measurement = results[measIdx];
|
|
1953
|
+
if (measurement.type !== "multi-distance" && measurement.type !== "area") {
|
|
1954
|
+
continue;
|
|
1955
|
+
}
|
|
1956
|
+
let entityStartIndex = 0;
|
|
1957
|
+
for (let i = 0; i < measIdx; i++) {
|
|
1958
|
+
entityStartIndex += results[i].points.length;
|
|
1959
|
+
}
|
|
1960
|
+
const positions = [];
|
|
1961
|
+
for (let i = 0; i < measurement.points.length; i++) {
|
|
1962
|
+
const entity = measurementState.entities[entityStartIndex + i];
|
|
1963
|
+
if (entity && entity.position) {
|
|
1964
|
+
positions.push(entity.position.getValue(Cesium.JulianDate.now()));
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
for (let i = 0; i < positions.length - 1; i++) {
|
|
1968
|
+
const p1 = positions[i];
|
|
1969
|
+
const p2 = positions[i + 1];
|
|
1970
|
+
const closestPoint = closestPointOnSegment(position, p1, p2);
|
|
1971
|
+
const dist = Cesium.Cartesian3.distance(position, closestPoint);
|
|
1972
|
+
if (dist < minDistance) {
|
|
1973
|
+
minDistance = dist;
|
|
1974
|
+
nearestMeasurement = measIdx;
|
|
1975
|
+
nearestSegment = { start: p1, end: p2 };
|
|
1976
|
+
insertIndex = i + 1;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
if (measurement.type === "area" && positions.length > 2) {
|
|
1980
|
+
const p1 = positions[positions.length - 1];
|
|
1981
|
+
const p2 = positions[0];
|
|
1982
|
+
const closestPoint = closestPointOnSegment(position, p1, p2);
|
|
1983
|
+
const dist = Cesium.Cartesian3.distance(position, closestPoint);
|
|
1984
|
+
if (dist < minDistance) {
|
|
1985
|
+
minDistance = dist;
|
|
1986
|
+
nearestMeasurement = measIdx;
|
|
1987
|
+
nearestSegment = { start: p1, end: p2 };
|
|
1988
|
+
insertIndex = positions.length;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
if (nearestMeasurement !== null && minDistance < CONSTANTS2.MAX_ADD_POINT_DISTANCE_METERS) {
|
|
1993
|
+
addPointToMeasurement(nearestMeasurement, insertIndex, position);
|
|
1994
|
+
} else {
|
|
1995
|
+
log(PREFIX3, "No nearby line segment found to add point (min distance:", minDistance.toFixed(2), "m)");
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
function closestPointOnSegment(point, segStart, segEnd) {
|
|
1999
|
+
const segmentVector = Cesium.Cartesian3.subtract(segEnd, segStart, new Cesium.Cartesian3());
|
|
2000
|
+
const pointVector = Cesium.Cartesian3.subtract(point, segStart, new Cesium.Cartesian3());
|
|
2001
|
+
const segmentLength = Cesium.Cartesian3.magnitude(segmentVector);
|
|
2002
|
+
if (segmentLength === 0) return segStart;
|
|
2003
|
+
const normalizedSegment = Cesium.Cartesian3.normalize(segmentVector, new Cesium.Cartesian3());
|
|
2004
|
+
const projection = Cesium.Cartesian3.dot(pointVector, normalizedSegment);
|
|
2005
|
+
const t = Math.max(0, Math.min(segmentLength, projection));
|
|
2006
|
+
const closestPoint = Cesium.Cartesian3.add(
|
|
2007
|
+
segStart,
|
|
2008
|
+
Cesium.Cartesian3.multiplyByScalar(normalizedSegment, t, new Cesium.Cartesian3()),
|
|
2009
|
+
new Cesium.Cartesian3()
|
|
2010
|
+
);
|
|
2011
|
+
return closestPoint;
|
|
2012
|
+
}
|
|
2013
|
+
function addPointToMeasurement(measurementIndex, insertIndex, position) {
|
|
2014
|
+
const results = model.get("measurement_results") || [];
|
|
2015
|
+
const measurement = results[measurementIndex];
|
|
2016
|
+
const cartographic = Cesium.Cartographic.fromCartesian(position);
|
|
2017
|
+
const newPoint = {
|
|
2018
|
+
lat: Cesium.Math.toDegrees(cartographic.latitude),
|
|
2019
|
+
lon: Cesium.Math.toDegrees(cartographic.longitude),
|
|
2020
|
+
alt: cartographic.height
|
|
2021
|
+
};
|
|
2022
|
+
measurement.points.splice(insertIndex, 0, newPoint);
|
|
2023
|
+
const colors = getColors();
|
|
2024
|
+
const color = colors[measurement.type]?.main || Cesium.Color.WHITE;
|
|
2025
|
+
const marker = addMarker(position, color);
|
|
2026
|
+
let entityInsertIndex = 0;
|
|
2027
|
+
for (let i = 0; i < measurementIndex; i++) {
|
|
2028
|
+
entityInsertIndex += results[i].points.length;
|
|
2029
|
+
}
|
|
2030
|
+
entityInsertIndex += insertIndex;
|
|
2031
|
+
measurementState.entities.splice(entityInsertIndex, 0, marker);
|
|
2032
|
+
const positions = [];
|
|
2033
|
+
let entityStartIndex = 0;
|
|
2034
|
+
for (let i = 0; i < measurementIndex; i++) {
|
|
2035
|
+
entityStartIndex += results[i].points.length;
|
|
2036
|
+
}
|
|
2037
|
+
for (let i = 0; i < measurement.points.length; i++) {
|
|
2038
|
+
const entity = measurementState.entities[entityStartIndex + i];
|
|
2039
|
+
if (entity && entity.position) {
|
|
2040
|
+
positions.push(entity.position.getValue(Cesium.JulianDate.now()));
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
if (measurement.type === "multi-distance") {
|
|
2044
|
+
let totalDistance = 0;
|
|
2045
|
+
for (let i = 0; i < positions.length - 1; i++) {
|
|
2046
|
+
totalDistance += Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
|
|
2047
|
+
}
|
|
2048
|
+
measurement.value = totalDistance;
|
|
2049
|
+
} else if (measurement.type === "area") {
|
|
2050
|
+
measurement.value = calculateGeodesicArea(positions);
|
|
2051
|
+
}
|
|
2052
|
+
updateMeasurementVisualsForIndex(measurementIndex);
|
|
2053
|
+
const newResults = [...results];
|
|
2054
|
+
model.set("measurement_results", newResults);
|
|
2055
|
+
model.save_changes();
|
|
2056
|
+
updateMeasurementsList();
|
|
2057
|
+
log(PREFIX3, `Added point to ${measurement.type} measurement at index ${insertIndex}`);
|
|
2058
|
+
}
|
|
2059
|
+
function updateMeasurementVisualsForIndex(measurementIndex) {
|
|
2060
|
+
const results = model.get("measurement_results") || [];
|
|
2061
|
+
const measurement = results[measurementIndex];
|
|
2062
|
+
let entityStartIndex = 0;
|
|
2063
|
+
for (let i = 0; i < measurementIndex; i++) {
|
|
2064
|
+
entityStartIndex += results[i].points.length;
|
|
2065
|
+
}
|
|
2066
|
+
const positions = [];
|
|
2067
|
+
for (let i = 0; i < measurement.points.length; i++) {
|
|
2068
|
+
const entity = measurementState.entities[entityStartIndex + i];
|
|
2069
|
+
if (entity && entity.position) {
|
|
2070
|
+
positions.push(entity.position.getValue(Cesium.JulianDate.now()));
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
const colors = getColors();
|
|
2074
|
+
const color = colors[measurement.type]?.main || Cesium.Color.WHITE;
|
|
2075
|
+
if (measurementState.polylines[measurementIndex]) {
|
|
2076
|
+
viewer.entities.remove(measurementState.polylines[measurementIndex]);
|
|
2077
|
+
}
|
|
2078
|
+
if (measurement.type === "multi-distance") {
|
|
2079
|
+
const polyline = viewer.entities.add({
|
|
2080
|
+
polyline: {
|
|
2081
|
+
positions,
|
|
2082
|
+
width: CONSTANTS2.POLYLINE_WIDTH,
|
|
2083
|
+
material: color,
|
|
2084
|
+
clampToGround: false
|
|
2085
|
+
}
|
|
2086
|
+
});
|
|
2087
|
+
measurementState.polylines[measurementIndex] = polyline;
|
|
2088
|
+
const labelStartIndex = measurementIndex * 20;
|
|
2089
|
+
} else if (measurement.type === "area") {
|
|
2090
|
+
const polygon = viewer.entities.add({
|
|
2091
|
+
polygon: {
|
|
2092
|
+
hierarchy: new Cesium.PolygonHierarchy(positions),
|
|
2093
|
+
material: Cesium.Color.fromAlpha(color, CONSTANTS2.POLYGON_ALPHA),
|
|
2094
|
+
outline: true,
|
|
2095
|
+
outlineColor: color,
|
|
2096
|
+
outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH,
|
|
2097
|
+
perPositionHeight: true
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
measurementState.polylines[measurementIndex] = polygon;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
1338
2103
|
function updateMeasurementsList() {
|
|
1339
2104
|
const results = model.get("measurement_results") || [];
|
|
1340
2105
|
log(PREFIX3, "Updating measurements list, count:", results.length);
|
|
@@ -1358,6 +2123,11 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1358
2123
|
cursor: pointer;
|
|
1359
2124
|
transition: background 0.2s;
|
|
1360
2125
|
border-left: 3px solid ${getMeasurementColor(measurement.type)};
|
|
2126
|
+
box-sizing: border-box;
|
|
2127
|
+
width: 100%;
|
|
2128
|
+
word-wrap: break-word;
|
|
2129
|
+
overflow-wrap: break-word;
|
|
2130
|
+
overflow: hidden;
|
|
1361
2131
|
`;
|
|
1362
2132
|
measurementDiv.onmouseover = () => {
|
|
1363
2133
|
measurementDiv.style.background = "rgba(255, 255, 255, 0.15)";
|
|
@@ -1373,15 +2143,16 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1373
2143
|
display: flex;
|
|
1374
2144
|
justify-content: space-between;
|
|
1375
2145
|
align-items: center;
|
|
2146
|
+
gap: 8px;
|
|
1376
2147
|
`;
|
|
1377
2148
|
nameDiv.innerHTML = `
|
|
1378
|
-
<span style="flex: 1;">${name}</span>
|
|
1379
|
-
<button id="rename-${index}" style="padding: 2px 6px; background: #3498db; color: white; border: none; border-radius: 2px; cursor: pointer; font-size: 10px;">\u270E</button>
|
|
2149
|
+
<span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;">${name}</span>
|
|
2150
|
+
<button id="rename-${index}" style="padding: 2px 6px; background: #3498db; color: white; border: none; border-radius: 2px; cursor: pointer; font-size: 10px; flex-shrink: 0;">\u270E</button>
|
|
1380
2151
|
`;
|
|
1381
2152
|
measurementDiv.appendChild(nameDiv);
|
|
1382
2153
|
const valueDiv = document.createElement("div");
|
|
1383
2154
|
valueDiv.style.cssText = "color: #aaa; font-size: 11px; margin-bottom: 3px;";
|
|
1384
|
-
valueDiv.textContent =
|
|
2155
|
+
valueDiv.textContent = formatValue(measurement.value, measurement.type === "area");
|
|
1385
2156
|
measurementDiv.appendChild(valueDiv);
|
|
1386
2157
|
const pointsDiv = document.createElement("div");
|
|
1387
2158
|
pointsDiv.style.cssText = "color: #888; font-size: 10px;";
|
|
@@ -1405,31 +2176,10 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1405
2176
|
});
|
|
1406
2177
|
}
|
|
1407
2178
|
function getMeasurementColor(type) {
|
|
1408
|
-
|
|
1409
|
-
"distance": "#e74c3c",
|
|
1410
|
-
"multi-distance": "#3498db",
|
|
1411
|
-
"height": "#2ecc71",
|
|
1412
|
-
"area": "#e67e22"
|
|
1413
|
-
};
|
|
1414
|
-
return colors[type] || "#95a5a6";
|
|
2179
|
+
return getColors()[type]?.button || "#95a5a6";
|
|
1415
2180
|
}
|
|
1416
2181
|
function getMeasurementTypeLabel(type) {
|
|
1417
|
-
|
|
1418
|
-
"distance": "Distance",
|
|
1419
|
-
"multi-distance": "Multi-Distance",
|
|
1420
|
-
"height": "Height",
|
|
1421
|
-
"area": "Area"
|
|
1422
|
-
};
|
|
1423
|
-
return labels[type] || type;
|
|
1424
|
-
}
|
|
1425
|
-
function formatMeasurementValue(measurement) {
|
|
1426
|
-
const value = measurement.value;
|
|
1427
|
-
const type = measurement.type;
|
|
1428
|
-
if (type === "area") {
|
|
1429
|
-
return value >= 1e6 ? `${(value / 1e6).toFixed(2)} km\xB2` : `${value.toFixed(2)} m\xB2`;
|
|
1430
|
-
} else {
|
|
1431
|
-
return value >= 1e3 ? `${(value / 1e3).toFixed(2)} km` : `${value.toFixed(2)} m`;
|
|
1432
|
-
}
|
|
2182
|
+
return TYPE_LABELS[type] || type;
|
|
1433
2183
|
}
|
|
1434
2184
|
function renameMeasurement(index, currentName) {
|
|
1435
2185
|
const newName = prompt("Enter new name for measurement:", currentName);
|
|
@@ -1444,11 +2194,9 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1444
2194
|
}
|
|
1445
2195
|
function focusOnMeasurement(index) {
|
|
1446
2196
|
const results = model.get("measurement_results") || [];
|
|
1447
|
-
if (index < 0 || index >= results.length)
|
|
1448
|
-
return;
|
|
2197
|
+
if (index < 0 || index >= results.length) return;
|
|
1449
2198
|
const measurement = results[index];
|
|
1450
|
-
if (!measurement.points || measurement.points.length === 0)
|
|
1451
|
-
return;
|
|
2199
|
+
if (!measurement.points || measurement.points.length === 0) return;
|
|
1452
2200
|
const positions = measurement.points.map(
|
|
1453
2201
|
(p) => Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.alt || 0)
|
|
1454
2202
|
);
|
|
@@ -1464,8 +2212,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1464
2212
|
}
|
|
1465
2213
|
function handleDistanceClick(click) {
|
|
1466
2214
|
const position = getPosition(click.position);
|
|
1467
|
-
if (!position)
|
|
1468
|
-
return;
|
|
2215
|
+
if (!position) return;
|
|
1469
2216
|
if (measurementState.points.length === 0) {
|
|
1470
2217
|
measurementState.points.push(position);
|
|
1471
2218
|
addMarker(position);
|
|
@@ -1477,7 +2224,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1477
2224
|
}
|
|
1478
2225
|
return measurementState.points;
|
|
1479
2226
|
}, false),
|
|
1480
|
-
width:
|
|
2227
|
+
width: CONSTANTS2.POLYLINE_WIDTH,
|
|
1481
2228
|
material: Cesium.Color.YELLOW,
|
|
1482
2229
|
depthFailMaterial: Cesium.Color.YELLOW
|
|
1483
2230
|
}
|
|
@@ -1487,7 +2234,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1487
2234
|
addMarker(position);
|
|
1488
2235
|
const distance = calculateDistance(measurementState.points[0], measurementState.points[1]);
|
|
1489
2236
|
const midpoint = getMidpoint(measurementState.points[0], measurementState.points[1]);
|
|
1490
|
-
addLabel(midpoint,
|
|
2237
|
+
addLabel(midpoint, formatValue(distance));
|
|
1491
2238
|
if (measurementState.tempPolyline) {
|
|
1492
2239
|
viewer.entities.remove(measurementState.tempPolyline);
|
|
1493
2240
|
measurementState.tempPolyline = null;
|
|
@@ -1495,7 +2242,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1495
2242
|
measurementState.polyline = viewer.entities.add({
|
|
1496
2243
|
polyline: {
|
|
1497
2244
|
positions: measurementState.points,
|
|
1498
|
-
width:
|
|
2245
|
+
width: CONSTANTS2.POLYLINE_WIDTH,
|
|
1499
2246
|
material: Cesium.Color.RED,
|
|
1500
2247
|
depthFailMaterial: Cesium.Color.RED
|
|
1501
2248
|
}
|
|
@@ -1515,15 +2262,14 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1515
2262
|
}
|
|
1516
2263
|
function handleMultiDistanceClick(click) {
|
|
1517
2264
|
const position = getPosition(click.position);
|
|
1518
|
-
if (!position)
|
|
1519
|
-
return;
|
|
2265
|
+
if (!position) return;
|
|
1520
2266
|
measurementState.points.push(position);
|
|
1521
2267
|
addMarker(position, Cesium.Color.BLUE);
|
|
1522
2268
|
if (measurementState.points.length === 1) {
|
|
1523
2269
|
measurementState.polyline = viewer.entities.add({
|
|
1524
2270
|
polyline: {
|
|
1525
2271
|
positions: new Cesium.CallbackProperty(() => measurementState.points, false),
|
|
1526
|
-
width:
|
|
2272
|
+
width: CONSTANTS2.POLYLINE_WIDTH,
|
|
1527
2273
|
material: Cesium.Color.BLUE,
|
|
1528
2274
|
depthFailMaterial: Cesium.Color.BLUE
|
|
1529
2275
|
}
|
|
@@ -1533,43 +2279,17 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1533
2279
|
const p1 = measurementState.points[measurementState.points.length - 2];
|
|
1534
2280
|
const p2 = measurementState.points[measurementState.points.length - 1];
|
|
1535
2281
|
const distance = calculateDistance(p1, p2);
|
|
1536
|
-
|
|
1537
|
-
addLabel(midpoint, `${distance.toFixed(2)} m`);
|
|
2282
|
+
addLabel(getMidpoint(p1, p2), formatValue(distance));
|
|
1538
2283
|
let totalDistance = 0;
|
|
1539
2284
|
for (let i = 0; i < measurementState.points.length - 1; i++) {
|
|
1540
|
-
totalDistance += calculateDistance(
|
|
1541
|
-
measurementState.points[i],
|
|
1542
|
-
measurementState.points[i + 1]
|
|
1543
|
-
);
|
|
1544
|
-
}
|
|
1545
|
-
const results = model.get("measurement_results") || [];
|
|
1546
|
-
const lastResult = results[results.length - 1];
|
|
1547
|
-
let newResults;
|
|
1548
|
-
if (lastResult && lastResult.type === "multi-distance" && lastResult.isActive) {
|
|
1549
|
-
newResults = [...results];
|
|
1550
|
-
newResults[newResults.length - 1] = {
|
|
1551
|
-
...lastResult,
|
|
1552
|
-
value: totalDistance,
|
|
1553
|
-
points: measurementState.points.map(cartesianToLatLonAlt)
|
|
1554
|
-
};
|
|
1555
|
-
} else {
|
|
1556
|
-
const multiDistanceCount = results.filter((r) => r.type === "multi-distance").length + 1;
|
|
1557
|
-
newResults = [...results, {
|
|
1558
|
-
type: "multi-distance",
|
|
1559
|
-
value: totalDistance,
|
|
1560
|
-
points: measurementState.points.map(cartesianToLatLonAlt),
|
|
1561
|
-
isActive: true,
|
|
1562
|
-
name: `Multi-Distance ${multiDistanceCount}`
|
|
1563
|
-
}];
|
|
2285
|
+
totalDistance += calculateDistance(measurementState.points[i], measurementState.points[i + 1]);
|
|
1564
2286
|
}
|
|
1565
|
-
|
|
1566
|
-
model.save_changes();
|
|
2287
|
+
updateOrCreateMeasurement("multi-distance", totalDistance, measurementState.points.map(cartesianToLatLonAlt));
|
|
1567
2288
|
}
|
|
1568
2289
|
}
|
|
1569
2290
|
function handleHeightClick(click) {
|
|
1570
2291
|
const pickedPosition = getPosition(click.position);
|
|
1571
|
-
if (!pickedPosition)
|
|
1572
|
-
return;
|
|
2292
|
+
if (!pickedPosition) return;
|
|
1573
2293
|
const cartographic = Cesium.Cartographic.fromCartesian(pickedPosition);
|
|
1574
2294
|
const terrainHeight = viewer.scene.globe.getHeight(cartographic) || 0;
|
|
1575
2295
|
const pickedHeight = cartographic.height;
|
|
@@ -1584,7 +2304,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1584
2304
|
const heightLine = viewer.entities.add({
|
|
1585
2305
|
polyline: {
|
|
1586
2306
|
positions: [groundPosition, pickedPosition],
|
|
1587
|
-
width:
|
|
2307
|
+
width: CONSTANTS2.POLYLINE_WIDTH,
|
|
1588
2308
|
material: Cesium.Color.GREEN,
|
|
1589
2309
|
depthFailMaterial: Cesium.Color.GREEN
|
|
1590
2310
|
}
|
|
@@ -1604,8 +2324,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1604
2324
|
}
|
|
1605
2325
|
function handleAreaClick(click) {
|
|
1606
2326
|
const position = getPosition(click.position);
|
|
1607
|
-
if (!position)
|
|
1608
|
-
return;
|
|
2327
|
+
if (!position) return;
|
|
1609
2328
|
measurementState.points.push(position);
|
|
1610
2329
|
addMarker(position, Cesium.Color.ORANGE);
|
|
1611
2330
|
if (measurementState.points.length === 1) {
|
|
@@ -1614,84 +2333,27 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1614
2333
|
hierarchy: new Cesium.CallbackProperty(() => {
|
|
1615
2334
|
return new Cesium.PolygonHierarchy(measurementState.points);
|
|
1616
2335
|
}, false),
|
|
1617
|
-
material: Cesium.Color.ORANGE.withAlpha(
|
|
2336
|
+
material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
|
|
1618
2337
|
outline: true,
|
|
1619
2338
|
outlineColor: Cesium.Color.ORANGE,
|
|
1620
|
-
outlineWidth:
|
|
2339
|
+
outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
|
|
1621
2340
|
}
|
|
1622
2341
|
});
|
|
1623
2342
|
measurementState.polylines.push(measurementState.polyline);
|
|
1624
2343
|
}
|
|
1625
2344
|
if (measurementState.points.length >= 3) {
|
|
1626
2345
|
const positions = measurementState.points;
|
|
1627
|
-
const
|
|
1628
|
-
const geometry = Cesium.PolygonGeometry.createGeometry(
|
|
1629
|
-
new Cesium.PolygonGeometry({
|
|
1630
|
-
polygonHierarchy,
|
|
1631
|
-
perPositionHeight: false,
|
|
1632
|
-
arcType: Cesium.ArcType.GEODESIC
|
|
1633
|
-
})
|
|
1634
|
-
);
|
|
1635
|
-
let area = 0;
|
|
1636
|
-
if (geometry) {
|
|
1637
|
-
const positionsArray = geometry.attributes.position.values;
|
|
1638
|
-
const indices = geometry.indices;
|
|
1639
|
-
for (let i = 0; i < indices.length; i += 3) {
|
|
1640
|
-
const i0 = indices[i] * 3;
|
|
1641
|
-
const i1 = indices[i + 1] * 3;
|
|
1642
|
-
const i2 = indices[i + 2] * 3;
|
|
1643
|
-
const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
|
|
1644
|
-
const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
|
|
1645
|
-
const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
|
|
1646
|
-
const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
|
|
1647
|
-
const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
|
|
1648
|
-
const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
|
|
1649
|
-
const triangleArea = Cesium.Cartesian3.magnitude(crossProduct) / 2;
|
|
1650
|
-
area += triangleArea;
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
let centroidLon = 0, centroidLat = 0;
|
|
1654
|
-
positions.forEach((pos) => {
|
|
1655
|
-
const carto = Cesium.Cartographic.fromCartesian(pos);
|
|
1656
|
-
centroidLon += carto.longitude;
|
|
1657
|
-
centroidLat += carto.latitude;
|
|
1658
|
-
});
|
|
1659
|
-
centroidLon /= positions.length;
|
|
1660
|
-
centroidLat /= positions.length;
|
|
1661
|
-
const areaText = area >= 1e6 ? `${(area / 1e6).toFixed(2)} km\xB2` : `${area.toFixed(2)} m\xB2`;
|
|
2346
|
+
const area = calculateGeodesicArea(positions);
|
|
1662
2347
|
const oldLabel = measurementState.labels.find((l) => l.label && l.label.text._value.includes("m\xB2") || l.label.text._value.includes("km\xB2"));
|
|
1663
2348
|
if (oldLabel) {
|
|
1664
2349
|
viewer.entities.remove(oldLabel);
|
|
1665
2350
|
measurementState.labels = measurementState.labels.filter((l) => l !== oldLabel);
|
|
1666
2351
|
}
|
|
1667
|
-
const centroidCarto =
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
const centroid = Cesium.Cartographic.toCartesian(centroidCarto);
|
|
1671
|
-
addLabel(centroid, areaText);
|
|
2352
|
+
const centroidCarto = calculateCentroid(positions);
|
|
2353
|
+
Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]).then(() => {
|
|
2354
|
+
addLabel(Cesium.Cartographic.toCartesian(centroidCarto), formatValue(area, true));
|
|
1672
2355
|
});
|
|
1673
|
-
|
|
1674
|
-
const lastResult = results[results.length - 1];
|
|
1675
|
-
let newResults;
|
|
1676
|
-
if (lastResult && lastResult.type === "area" && lastResult.isActive) {
|
|
1677
|
-
newResults = [...results];
|
|
1678
|
-
newResults[newResults.length - 1] = {
|
|
1679
|
-
...lastResult,
|
|
1680
|
-
value: area,
|
|
1681
|
-
points: measurementState.points.map(cartesianToLatLonAlt)
|
|
1682
|
-
};
|
|
1683
|
-
} else {
|
|
1684
|
-
const areaCount = results.filter((r) => r.type === "area").length + 1;
|
|
1685
|
-
newResults = [...results, {
|
|
1686
|
-
type: "area",
|
|
1687
|
-
value: area,
|
|
1688
|
-
points: measurementState.points.map(cartesianToLatLonAlt),
|
|
1689
|
-
isActive: true,
|
|
1690
|
-
name: `Area ${areaCount}`
|
|
1691
|
-
}];
|
|
1692
|
-
}
|
|
1693
|
-
model.set("measurement_results", newResults);
|
|
1694
|
-
model.save_changes();
|
|
2356
|
+
updateOrCreateMeasurement("area", area, measurementState.points.map(cartesianToLatLonAlt));
|
|
1695
2357
|
}
|
|
1696
2358
|
}
|
|
1697
2359
|
function handleMouseMove(movement) {
|
|
@@ -1710,12 +2372,10 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1710
2372
|
}
|
|
1711
2373
|
clearInProgressMeasurement();
|
|
1712
2374
|
measurementState.mode = mode;
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
if (!mode)
|
|
1718
|
-
return;
|
|
2375
|
+
Object.entries(modeButtons).forEach(([btnMode, btn]) => {
|
|
2376
|
+
btn.style.background = mode === btnMode ? "#e74c3c" : "#3498db";
|
|
2377
|
+
});
|
|
2378
|
+
if (!mode) return;
|
|
1719
2379
|
measurementHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
|
1720
2380
|
if (mode === "distance") {
|
|
1721
2381
|
measurementHandler.setInputAction(handleDistanceClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
@@ -1757,12 +2417,10 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1757
2417
|
}
|
|
1758
2418
|
}
|
|
1759
2419
|
function loadAndDisplayMeasurements(measurements) {
|
|
1760
|
-
if (!Array.isArray(measurements))
|
|
1761
|
-
return;
|
|
2420
|
+
if (!Array.isArray(measurements)) return;
|
|
1762
2421
|
measurements.forEach((measurement) => {
|
|
1763
2422
|
const { type, points } = measurement;
|
|
1764
|
-
if (!type || !Array.isArray(points) || points.length < 2)
|
|
1765
|
-
return;
|
|
2423
|
+
if (!type || !Array.isArray(points) || points.length < 2) return;
|
|
1766
2424
|
const positions = points.map((point) => {
|
|
1767
2425
|
const [lon, lat, alt] = point;
|
|
1768
2426
|
return Cesium.Cartesian3.fromDegrees(lon, lat, alt || 0);
|
|
@@ -1783,22 +2441,21 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1783
2441
|
const line = viewer.entities.add({
|
|
1784
2442
|
polyline: {
|
|
1785
2443
|
positions,
|
|
1786
|
-
width:
|
|
2444
|
+
width: CONSTANTS2.POLYLINE_WIDTH,
|
|
1787
2445
|
material: Cesium.Color.RED
|
|
1788
2446
|
}
|
|
1789
2447
|
});
|
|
1790
2448
|
measurementState.polylines.push(line);
|
|
1791
2449
|
const distance = Cesium.Cartesian3.distance(positions[0], positions[1]);
|
|
1792
2450
|
const midpoint = Cesium.Cartesian3.midpoint(positions[0], positions[1], new Cesium.Cartesian3());
|
|
1793
|
-
|
|
1794
|
-
addLabel(midpoint, distanceText);
|
|
2451
|
+
addLabel(midpoint, formatValue(distance));
|
|
1795
2452
|
}
|
|
1796
2453
|
function displayMultiDistance(positions) {
|
|
1797
2454
|
positions.forEach((pos) => addMarker(pos, Cesium.Color.BLUE));
|
|
1798
2455
|
const line = viewer.entities.add({
|
|
1799
2456
|
polyline: {
|
|
1800
2457
|
positions,
|
|
1801
|
-
width:
|
|
2458
|
+
width: CONSTANTS2.POLYLINE_WIDTH,
|
|
1802
2459
|
material: Cesium.Color.BLUE
|
|
1803
2460
|
}
|
|
1804
2461
|
});
|
|
@@ -1808,12 +2465,9 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1808
2465
|
const segmentDistance = Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
|
|
1809
2466
|
totalDistance += segmentDistance;
|
|
1810
2467
|
const midpoint = Cesium.Cartesian3.midpoint(positions[i], positions[i + 1], new Cesium.Cartesian3());
|
|
1811
|
-
|
|
1812
|
-
addLabel(midpoint, segmentText);
|
|
2468
|
+
addLabel(midpoint, formatValue(segmentDistance));
|
|
1813
2469
|
}
|
|
1814
|
-
|
|
1815
|
-
const totalText = totalDistance >= 1e3 ? `Total: ${(totalDistance / 1e3).toFixed(2)} km` : `Total: ${totalDistance.toFixed(2)} m`;
|
|
1816
|
-
addLabel(lastPos, totalText);
|
|
2470
|
+
addLabel(positions[positions.length - 1], `Total: ${formatValue(totalDistance)}`);
|
|
1817
2471
|
}
|
|
1818
2472
|
function displayHeight(positions) {
|
|
1819
2473
|
positions.forEach((pos) => addMarker(pos, Cesium.Color.GREEN));
|
|
@@ -1827,68 +2481,31 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1827
2481
|
Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, carto0.height),
|
|
1828
2482
|
positions[1]
|
|
1829
2483
|
],
|
|
1830
|
-
width:
|
|
2484
|
+
width: CONSTANTS2.POLYLINE_WIDTH,
|
|
1831
2485
|
material: Cesium.Color.GREEN
|
|
1832
2486
|
}
|
|
1833
2487
|
});
|
|
1834
2488
|
measurementState.polylines.push(line);
|
|
1835
2489
|
const midHeight = (carto0.height + carto1.height) / 2;
|
|
1836
2490
|
const labelPos = Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, midHeight);
|
|
1837
|
-
|
|
1838
|
-
addLabel(labelPos, heightText);
|
|
2491
|
+
addLabel(labelPos, formatValue(verticalDistance));
|
|
1839
2492
|
}
|
|
1840
2493
|
function displayArea(positions) {
|
|
1841
2494
|
positions.forEach((pos) => addMarker(pos, Cesium.Color.ORANGE));
|
|
1842
2495
|
const polygon = viewer.entities.add({
|
|
1843
2496
|
polygon: {
|
|
1844
2497
|
hierarchy: new Cesium.PolygonHierarchy(positions),
|
|
1845
|
-
material: Cesium.Color.ORANGE.withAlpha(
|
|
2498
|
+
material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
|
|
1846
2499
|
outline: true,
|
|
1847
2500
|
outlineColor: Cesium.Color.ORANGE,
|
|
1848
|
-
outlineWidth:
|
|
2501
|
+
outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
|
|
1849
2502
|
}
|
|
1850
2503
|
});
|
|
1851
2504
|
measurementState.polylines.push(polygon);
|
|
1852
|
-
const
|
|
1853
|
-
const
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
perPositionHeight: false,
|
|
1857
|
-
arcType: Cesium.ArcType.GEODESIC
|
|
1858
|
-
})
|
|
1859
|
-
);
|
|
1860
|
-
let area = 0;
|
|
1861
|
-
if (geometry) {
|
|
1862
|
-
const positionsArray = geometry.attributes.position.values;
|
|
1863
|
-
const indices = geometry.indices;
|
|
1864
|
-
for (let i = 0; i < indices.length; i += 3) {
|
|
1865
|
-
const i0 = indices[i] * 3;
|
|
1866
|
-
const i1 = indices[i + 1] * 3;
|
|
1867
|
-
const i2 = indices[i + 2] * 3;
|
|
1868
|
-
const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
|
|
1869
|
-
const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
|
|
1870
|
-
const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
|
|
1871
|
-
const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
|
|
1872
|
-
const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
|
|
1873
|
-
const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
|
|
1874
|
-
const triangleArea = Cesium.Cartesian3.magnitude(crossProduct) / 2;
|
|
1875
|
-
area += triangleArea;
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
let centroidLon = 0, centroidLat = 0;
|
|
1879
|
-
positions.forEach((pos) => {
|
|
1880
|
-
const carto = Cesium.Cartographic.fromCartesian(pos);
|
|
1881
|
-
centroidLon += carto.longitude;
|
|
1882
|
-
centroidLat += carto.latitude;
|
|
1883
|
-
});
|
|
1884
|
-
centroidLon /= positions.length;
|
|
1885
|
-
centroidLat /= positions.length;
|
|
1886
|
-
const areaText = area >= 1e6 ? `${(area / 1e6).toFixed(2)} km\xB2` : `${area.toFixed(2)} m\xB2`;
|
|
1887
|
-
const centroidCarto = new Cesium.Cartographic(centroidLon, centroidLat);
|
|
1888
|
-
const promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]);
|
|
1889
|
-
promise.then(() => {
|
|
1890
|
-
const centroid = Cesium.Cartographic.toCartesian(centroidCarto);
|
|
1891
|
-
addLabel(centroid, areaText);
|
|
2505
|
+
const area = calculateGeodesicArea(positions);
|
|
2506
|
+
const centroidCarto = calculateCentroid(positions);
|
|
2507
|
+
Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]).then(() => {
|
|
2508
|
+
addLabel(Cesium.Cartographic.toCartesian(centroidCarto), formatValue(area, true));
|
|
1892
2509
|
});
|
|
1893
2510
|
}
|
|
1894
2511
|
model.on("change:measurement_mode", () => {
|
|
@@ -1913,8 +2530,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1913
2530
|
updateMeasurementsList();
|
|
1914
2531
|
});
|
|
1915
2532
|
model.on("change:load_measurements_trigger", () => {
|
|
1916
|
-
if (isDestroyed)
|
|
1917
|
-
return;
|
|
2533
|
+
if (isDestroyed) return;
|
|
1918
2534
|
const triggerData = model.get("load_measurements_trigger");
|
|
1919
2535
|
log(PREFIX3, "Load measurements trigger:", triggerData);
|
|
1920
2536
|
if (triggerData && triggerData.measurements) {
|
|
@@ -1923,8 +2539,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1923
2539
|
}
|
|
1924
2540
|
});
|
|
1925
2541
|
model.on("change:focus_measurement_trigger", () => {
|
|
1926
|
-
if (isDestroyed)
|
|
1927
|
-
return;
|
|
2542
|
+
if (isDestroyed) return;
|
|
1928
2543
|
const triggerData = model.get("focus_measurement_trigger");
|
|
1929
2544
|
log(PREFIX3, "Focus measurement trigger:", triggerData);
|
|
1930
2545
|
if (triggerData && typeof triggerData.index === "number") {
|
|
@@ -1932,8 +2547,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1932
2547
|
}
|
|
1933
2548
|
});
|
|
1934
2549
|
model.on("change:show_measurement_tools", () => {
|
|
1935
|
-
if (isDestroyed)
|
|
1936
|
-
return;
|
|
2550
|
+
if (isDestroyed) return;
|
|
1937
2551
|
const show = model.get("show_measurement_tools");
|
|
1938
2552
|
log(PREFIX3, "Show measurement tools:", show);
|
|
1939
2553
|
toolbarDiv.style.display = show ? "flex" : "none";
|
|
@@ -1944,8 +2558,7 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1944
2558
|
}
|
|
1945
2559
|
});
|
|
1946
2560
|
model.on("change:show_measurements_list", () => {
|
|
1947
|
-
if (isDestroyed)
|
|
1948
|
-
return;
|
|
2561
|
+
if (isDestroyed) return;
|
|
1949
2562
|
const show = model.get("show_measurements_list");
|
|
1950
2563
|
log(PREFIX3, "Show measurements list:", show);
|
|
1951
2564
|
measurementsListPanel.style.display = show ? "block" : "none";
|
|
@@ -1961,18 +2574,302 @@ function initializeMeasurementTools(viewer, model, container) {
|
|
|
1961
2574
|
isDestroyed = true;
|
|
1962
2575
|
if (measurementHandler) {
|
|
1963
2576
|
measurementHandler.destroy();
|
|
2577
|
+
measurementHandler = null;
|
|
2578
|
+
}
|
|
2579
|
+
if (editHandler) {
|
|
2580
|
+
editHandler.destroy();
|
|
2581
|
+
editHandler = null;
|
|
1964
2582
|
}
|
|
2583
|
+
cleanupFunctions.forEach((cleanup) => {
|
|
2584
|
+
try {
|
|
2585
|
+
cleanup();
|
|
2586
|
+
} catch (err) {
|
|
2587
|
+
warn(PREFIX3, "Error during cleanup:", err);
|
|
2588
|
+
}
|
|
2589
|
+
});
|
|
2590
|
+
cleanupFunctions.length = 0;
|
|
1965
2591
|
clearAllMeasurements();
|
|
1966
2592
|
if (toolbarDiv.parentNode) {
|
|
1967
2593
|
toolbarDiv.remove();
|
|
1968
2594
|
}
|
|
2595
|
+
if (editorPanel.parentNode) {
|
|
2596
|
+
editorPanel.remove();
|
|
2597
|
+
}
|
|
2598
|
+
if (measurementsListPanel.parentNode) {
|
|
2599
|
+
measurementsListPanel.remove();
|
|
2600
|
+
}
|
|
1969
2601
|
log(PREFIX3, "Measurement tools destroyed");
|
|
1970
2602
|
}
|
|
1971
2603
|
};
|
|
1972
2604
|
}
|
|
1973
2605
|
|
|
2606
|
+
// src/cesiumjs_anywidget/js/point-picking.js
|
|
2607
|
+
var PREFIX4 = "PointPicking";
|
|
2608
|
+
function initializePointPicking(viewer, model, container) {
|
|
2609
|
+
const Cesium = window.Cesium;
|
|
2610
|
+
let handler = null;
|
|
2611
|
+
let pickedPointEntities = [];
|
|
2612
|
+
let isPickingMode = false;
|
|
2613
|
+
let pickingPanel = null;
|
|
2614
|
+
let statusText = null;
|
|
2615
|
+
function createPickingPanel() {
|
|
2616
|
+
pickingPanel = document.createElement("div");
|
|
2617
|
+
pickingPanel.id = "point-picking-panel";
|
|
2618
|
+
pickingPanel.style.cssText = `
|
|
2619
|
+
position: absolute;
|
|
2620
|
+
top: 10px;
|
|
2621
|
+
left: 50%;
|
|
2622
|
+
transform: translateX(-50%);
|
|
2623
|
+
background: rgba(0, 0, 0, 0.8);
|
|
2624
|
+
color: white;
|
|
2625
|
+
padding: 10px 20px;
|
|
2626
|
+
border-radius: 5px;
|
|
2627
|
+
font-family: sans-serif;
|
|
2628
|
+
font-size: 14px;
|
|
2629
|
+
z-index: 1000;
|
|
2630
|
+
display: none;
|
|
2631
|
+
pointer-events: none;
|
|
2632
|
+
text-align: center;
|
|
2633
|
+
`;
|
|
2634
|
+
statusText = document.createElement("span");
|
|
2635
|
+
statusText.textContent = "\u{1F3AF} Point Picking Mode - Click on the scene to pick a point";
|
|
2636
|
+
pickingPanel.appendChild(statusText);
|
|
2637
|
+
container.appendChild(pickingPanel);
|
|
2638
|
+
}
|
|
2639
|
+
function setPickingPanelVisible(visible) {
|
|
2640
|
+
if (pickingPanel) {
|
|
2641
|
+
pickingPanel.style.display = visible ? "block" : "none";
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
function updateStatus(text) {
|
|
2645
|
+
if (statusText) {
|
|
2646
|
+
statusText.textContent = text;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
function getPositionFromScreen(screenPosition) {
|
|
2650
|
+
const pickedObject = viewer.scene.pick(screenPosition);
|
|
2651
|
+
if (viewer.scene.pickPositionSupported && Cesium.defined(pickedObject)) {
|
|
2652
|
+
const cartesian = viewer.scene.pickPosition(screenPosition);
|
|
2653
|
+
if (Cesium.defined(cartesian)) {
|
|
2654
|
+
return cartesian;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
const ray = viewer.camera.getPickRay(screenPosition);
|
|
2658
|
+
if (ray) {
|
|
2659
|
+
return viewer.scene.globe.pick(ray, viewer.scene);
|
|
2660
|
+
}
|
|
2661
|
+
return null;
|
|
2662
|
+
}
|
|
2663
|
+
function cartesianToLatLonAlt(cartesian) {
|
|
2664
|
+
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
|
|
2665
|
+
return {
|
|
2666
|
+
latitude: Cesium.Math.toDegrees(cartographic.latitude),
|
|
2667
|
+
longitude: Cesium.Math.toDegrees(cartographic.longitude),
|
|
2668
|
+
altitude_wgs84: cartographic.height
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
async function getAltitudeMSL(latitude, longitude, altitude_wgs84) {
|
|
2672
|
+
try {
|
|
2673
|
+
const positions = [Cesium.Cartographic.fromDegrees(longitude, latitude)];
|
|
2674
|
+
const terrainProvider = viewer.terrainProvider;
|
|
2675
|
+
if (terrainProvider && terrainProvider.availability) {
|
|
2676
|
+
const sampledPositions = await Cesium.sampleTerrainMostDetailed(terrainProvider, positions);
|
|
2677
|
+
const terrainHeight = sampledPositions[0].height || 0;
|
|
2678
|
+
return altitude_wgs84;
|
|
2679
|
+
}
|
|
2680
|
+
} catch (e) {
|
|
2681
|
+
warn(PREFIX4, "Could not sample terrain for MSL conversion:", e);
|
|
2682
|
+
}
|
|
2683
|
+
return altitude_wgs84;
|
|
2684
|
+
}
|
|
2685
|
+
function addPointMarker(position, config) {
|
|
2686
|
+
const color = config.color || [255, 0, 0, 255];
|
|
2687
|
+
const label = config.label || config.point_id || "";
|
|
2688
|
+
const pointId = config.point_id || `point_${Date.now()}`;
|
|
2689
|
+
const cesiumColor = new Cesium.Color(
|
|
2690
|
+
color[0] / 255,
|
|
2691
|
+
color[1] / 255,
|
|
2692
|
+
color[2] / 255,
|
|
2693
|
+
color[3] / 255
|
|
2694
|
+
);
|
|
2695
|
+
const entity = viewer.entities.add({
|
|
2696
|
+
id: `picked_point_${pointId}`,
|
|
2697
|
+
position,
|
|
2698
|
+
point: {
|
|
2699
|
+
pixelSize: 12,
|
|
2700
|
+
color: cesiumColor,
|
|
2701
|
+
outlineColor: Cesium.Color.WHITE,
|
|
2702
|
+
outlineWidth: 2,
|
|
2703
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY
|
|
2704
|
+
},
|
|
2705
|
+
label: {
|
|
2706
|
+
text: label,
|
|
2707
|
+
font: "12pt sans-serif",
|
|
2708
|
+
fillColor: cesiumColor,
|
|
2709
|
+
outlineColor: Cesium.Color.BLACK,
|
|
2710
|
+
outlineWidth: 2,
|
|
2711
|
+
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|
2712
|
+
pixelOffset: new Cesium.Cartesian2(15, 0),
|
|
2713
|
+
horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
|
|
2714
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY
|
|
2715
|
+
}
|
|
2716
|
+
});
|
|
2717
|
+
pickedPointEntities.push(entity);
|
|
2718
|
+
return entity;
|
|
2719
|
+
}
|
|
2720
|
+
function clearPointMarkers() {
|
|
2721
|
+
for (const entity of pickedPointEntities) {
|
|
2722
|
+
viewer.entities.remove(entity);
|
|
2723
|
+
}
|
|
2724
|
+
pickedPointEntities = [];
|
|
2725
|
+
log(PREFIX4, "Cleared all point markers");
|
|
2726
|
+
}
|
|
2727
|
+
function removePointMarker(pointId) {
|
|
2728
|
+
const entityId = `picked_point_${pointId}`;
|
|
2729
|
+
const entity = viewer.entities.getById(entityId);
|
|
2730
|
+
if (entity) {
|
|
2731
|
+
viewer.entities.remove(entity);
|
|
2732
|
+
pickedPointEntities = pickedPointEntities.filter((e) => e.id !== entityId);
|
|
2733
|
+
log(PREFIX4, `Removed point marker: ${pointId}`);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
async function handleClick(click) {
|
|
2737
|
+
if (!isPickingMode) return;
|
|
2738
|
+
const position = getPositionFromScreen(click.position);
|
|
2739
|
+
if (!position) {
|
|
2740
|
+
warn(PREFIX4, "Could not determine click position");
|
|
2741
|
+
updateStatus("\u26A0\uFE0F Could not pick point at this location. Try clicking on visible terrain or 3D tiles.");
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
const coords = cartesianToLatLonAlt(position);
|
|
2745
|
+
const altitude_msl = await getAltitudeMSL(coords.latitude, coords.longitude, coords.altitude_wgs84);
|
|
2746
|
+
const config = model.get("point_picking_config") || {};
|
|
2747
|
+
const currentPoints = model.get("picked_points") || [];
|
|
2748
|
+
const pointNumber = currentPoints.length + 1;
|
|
2749
|
+
const labelPrefix = config.label_prefix || "GCP";
|
|
2750
|
+
const pointIdPrefix = config.point_id_prefix || labelPrefix;
|
|
2751
|
+
const pointId = `${pointIdPrefix}_${pointNumber}`;
|
|
2752
|
+
const label = `${labelPrefix}_${pointNumber}`;
|
|
2753
|
+
addPointMarker(position, {
|
|
2754
|
+
...config,
|
|
2755
|
+
point_id: pointId,
|
|
2756
|
+
label
|
|
2757
|
+
});
|
|
2758
|
+
const pointData = {
|
|
2759
|
+
id: pointId,
|
|
2760
|
+
latitude: coords.latitude,
|
|
2761
|
+
longitude: coords.longitude,
|
|
2762
|
+
altitude_wgs84: coords.altitude_wgs84,
|
|
2763
|
+
altitude_msl,
|
|
2764
|
+
color: config.color || [255, 0, 0, 255],
|
|
2765
|
+
label,
|
|
2766
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2767
|
+
};
|
|
2768
|
+
log(PREFIX4, "Picked point:", pointData);
|
|
2769
|
+
model.set("picked_points", [...currentPoints, pointData]);
|
|
2770
|
+
model.save_changes();
|
|
2771
|
+
model.set("point_picking_event", pointData);
|
|
2772
|
+
model.save_changes();
|
|
2773
|
+
updateStatus(`\u2705 Picked ${label} at (${coords.latitude.toFixed(6)}\xB0, ${coords.longitude.toFixed(6)}\xB0, ${altitude_msl.toFixed(1)}m) - Click for next point`);
|
|
2774
|
+
const continuous = config.continuous !== false;
|
|
2775
|
+
if (!continuous) {
|
|
2776
|
+
isPickingMode = false;
|
|
2777
|
+
model.set("point_picking_mode", false);
|
|
2778
|
+
model.save_changes();
|
|
2779
|
+
setPickingPanelVisible(false);
|
|
2780
|
+
log(PREFIX4, "Single-point mode: picking stopped after one point");
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
function startPickingMode() {
|
|
2784
|
+
if (handler) {
|
|
2785
|
+
handler.destroy();
|
|
2786
|
+
}
|
|
2787
|
+
handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
|
2788
|
+
handler.setInputAction(handleClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
2789
|
+
isPickingMode = true;
|
|
2790
|
+
setPickingPanelVisible(true);
|
|
2791
|
+
const config = model.get("point_picking_config") || {};
|
|
2792
|
+
const currentPoints = model.get("picked_points") || [];
|
|
2793
|
+
const nextNumber = currentPoints.length + 1;
|
|
2794
|
+
const labelPrefix = config.label_prefix || "GCP";
|
|
2795
|
+
const continuous = config.continuous !== false;
|
|
2796
|
+
if (continuous) {
|
|
2797
|
+
updateStatus(`\u{1F3AF} Point Picking Mode (continuous) - Click to pick ${labelPrefix}_${nextNumber}`);
|
|
2798
|
+
} else {
|
|
2799
|
+
updateStatus(`\u{1F3AF} Point Picking Mode (single) - Click to pick ${labelPrefix}_${nextNumber}`);
|
|
2800
|
+
}
|
|
2801
|
+
log(PREFIX4, "Started point picking mode", { continuous, labelPrefix });
|
|
2802
|
+
}
|
|
2803
|
+
function stopPickingMode() {
|
|
2804
|
+
if (handler) {
|
|
2805
|
+
handler.destroy();
|
|
2806
|
+
handler = null;
|
|
2807
|
+
}
|
|
2808
|
+
isPickingMode = false;
|
|
2809
|
+
setPickingPanelVisible(false);
|
|
2810
|
+
log(PREFIX4, "Stopped point picking mode");
|
|
2811
|
+
}
|
|
2812
|
+
createPickingPanel();
|
|
2813
|
+
model.on("change:point_picking_mode", () => {
|
|
2814
|
+
const enabled = model.get("point_picking_mode");
|
|
2815
|
+
if (enabled) {
|
|
2816
|
+
startPickingMode();
|
|
2817
|
+
} else {
|
|
2818
|
+
stopPickingMode();
|
|
2819
|
+
}
|
|
2820
|
+
});
|
|
2821
|
+
model.on("change:point_picking_config", () => {
|
|
2822
|
+
if (isPickingMode) {
|
|
2823
|
+
const config = model.get("point_picking_config") || {};
|
|
2824
|
+
const currentPoints = model.get("picked_points") || [];
|
|
2825
|
+
const nextNumber = currentPoints.length + 1;
|
|
2826
|
+
const labelPrefix = config.label_prefix || "GCP";
|
|
2827
|
+
updateStatus(`\u{1F3AF} Ready to pick ${labelPrefix}_${nextNumber} - Click on scene`);
|
|
2828
|
+
}
|
|
2829
|
+
});
|
|
2830
|
+
model.on("change:picked_points", () => {
|
|
2831
|
+
const points = model.get("picked_points") || [];
|
|
2832
|
+
if (points.length === 0 && pickedPointEntities.length > 0) {
|
|
2833
|
+
clearPointMarkers();
|
|
2834
|
+
}
|
|
2835
|
+
const currentIds = new Set(pickedPointEntities.map((e) => e.id.replace("picked_point_", "")));
|
|
2836
|
+
const newIds = new Set(points.map((p) => p.id));
|
|
2837
|
+
for (const id of currentIds) {
|
|
2838
|
+
if (!newIds.has(id)) {
|
|
2839
|
+
removePointMarker(id);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
});
|
|
2843
|
+
if (model.get("point_picking_mode")) {
|
|
2844
|
+
startPickingMode();
|
|
2845
|
+
}
|
|
2846
|
+
const existingPoints = model.get("picked_points") || [];
|
|
2847
|
+
for (const point of existingPoints) {
|
|
2848
|
+
const cartesian = Cesium.Cartesian3.fromDegrees(
|
|
2849
|
+
point.longitude,
|
|
2850
|
+
point.latitude,
|
|
2851
|
+
point.altitude_wgs84
|
|
2852
|
+
);
|
|
2853
|
+
addPointMarker(cartesian, {
|
|
2854
|
+
point_id: point.id,
|
|
2855
|
+
color: point.color,
|
|
2856
|
+
label: point.label
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2859
|
+
return {
|
|
2860
|
+
destroy: () => {
|
|
2861
|
+
stopPickingMode();
|
|
2862
|
+
clearPointMarkers();
|
|
2863
|
+
if (pickingPanel && pickingPanel.parentNode) {
|
|
2864
|
+
pickingPanel.remove();
|
|
2865
|
+
}
|
|
2866
|
+
log(PREFIX4, "Point picking module destroyed");
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
|
|
1974
2871
|
// src/cesiumjs_anywidget/js/index.js
|
|
1975
|
-
window.CESIUM_BASE_URL = "https://cesium.com/downloads/cesiumjs/releases/1.
|
|
2872
|
+
window.CESIUM_BASE_URL = "https://cesium.com/downloads/cesiumjs/releases/1.137/Build/Cesium/";
|
|
1976
2873
|
async function render({ model, el }) {
|
|
1977
2874
|
setDebugMode(model.get("debug_mode") || false);
|
|
1978
2875
|
model.on("change:debug_mode", () => {
|
|
@@ -1999,8 +2896,11 @@ async function render({ model, el }) {
|
|
|
1999
2896
|
let viewer = null;
|
|
2000
2897
|
let cameraSync = null;
|
|
2001
2898
|
let measurementTools = null;
|
|
2899
|
+
let pointPicking = null;
|
|
2900
|
+
let photoProjection = null;
|
|
2002
2901
|
let geoJsonLoader = null;
|
|
2003
2902
|
let czmlLoader = null;
|
|
2903
|
+
let photorealisticTiles = null;
|
|
2004
2904
|
(async () => {
|
|
2005
2905
|
try {
|
|
2006
2906
|
log("Main", "Creating Cesium Viewer...");
|
|
@@ -2015,6 +2915,9 @@ async function render({ model, el }) {
|
|
|
2015
2915
|
log("Main", "Initializing measurement tools...");
|
|
2016
2916
|
measurementTools = initializeMeasurementTools(viewer, model, container);
|
|
2017
2917
|
log("Main", "Measurement tools initialized");
|
|
2918
|
+
log("Main", "Initializing point picking...");
|
|
2919
|
+
pointPicking = initializePointPicking(viewer, model, container);
|
|
2920
|
+
log("Main", "Point picking initialized");
|
|
2018
2921
|
log("Main", "Setting up viewer listeners...");
|
|
2019
2922
|
setupViewerListeners(viewer, model, container, Cesium);
|
|
2020
2923
|
log("Main", "Viewer listeners set up");
|
|
@@ -2024,6 +2927,16 @@ async function render({ model, el }) {
|
|
|
2024
2927
|
log("Main", "Setting up CZML loader...");
|
|
2025
2928
|
czmlLoader = setupCZMLLoader(viewer, model, Cesium);
|
|
2026
2929
|
log("Main", "CZML loader set up");
|
|
2930
|
+
log("Main", "Setting up Photorealistic 3D Tiles...");
|
|
2931
|
+
photorealisticTiles = setupPhotorealisticTiles(viewer, model, Cesium);
|
|
2932
|
+
log("Main", "Photorealistic 3D Tiles set up");
|
|
2933
|
+
model.on("change:camera_command", () => {
|
|
2934
|
+
const command = model.get("camera_command");
|
|
2935
|
+
if (command && command.command === "projectPhoto" && photoProjection) {
|
|
2936
|
+
log("Main", "Handling projectPhoto command");
|
|
2937
|
+
photoProjection.projectPhoto(command);
|
|
2938
|
+
}
|
|
2939
|
+
});
|
|
2027
2940
|
log("Main", "Initialization complete");
|
|
2028
2941
|
} catch (err) {
|
|
2029
2942
|
error("Main", "Error initializing CesiumJS viewer:", err);
|
|
@@ -2041,6 +2954,14 @@ async function render({ model, el }) {
|
|
|
2041
2954
|
log("Main", "Destroying measurement tools...");
|
|
2042
2955
|
measurementTools.destroy();
|
|
2043
2956
|
}
|
|
2957
|
+
if (photoProjection) {
|
|
2958
|
+
log("Main", "Destroying photo projection...");
|
|
2959
|
+
photoProjection.destroy();
|
|
2960
|
+
}
|
|
2961
|
+
if (pointPicking) {
|
|
2962
|
+
log("Main", "Destroying point picking...");
|
|
2963
|
+
pointPicking.destroy();
|
|
2964
|
+
}
|
|
2044
2965
|
if (geoJsonLoader) {
|
|
2045
2966
|
log("Main", "Destroying GeoJSON loader...");
|
|
2046
2967
|
geoJsonLoader.destroy();
|
|
@@ -2049,6 +2970,10 @@ async function render({ model, el }) {
|
|
|
2049
2970
|
log("Main", "Destroying CZML loader...");
|
|
2050
2971
|
czmlLoader.destroy();
|
|
2051
2972
|
}
|
|
2973
|
+
if (photorealisticTiles) {
|
|
2974
|
+
log("Main", "Destroying photorealistic tiles...");
|
|
2975
|
+
photorealisticTiles.destroy();
|
|
2976
|
+
}
|
|
2052
2977
|
if (viewer) {
|
|
2053
2978
|
log("Main", "Destroying viewer...");
|
|
2054
2979
|
viewer.destroy();
|
|
@@ -2056,7 +2981,7 @@ async function render({ model, el }) {
|
|
|
2056
2981
|
log("Main", "Cleanup complete");
|
|
2057
2982
|
};
|
|
2058
2983
|
}
|
|
2059
|
-
var
|
|
2984
|
+
var index_default = { render };
|
|
2060
2985
|
export {
|
|
2061
|
-
|
|
2986
|
+
index_default as default
|
|
2062
2987
|
};
|