cesiumjs-anywidget 0.2.4__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.
@@ -0,0 +1,1415 @@
1
+ // Generated bundle - DO NOT EDIT DIRECTLY. Edit files in src/cesiumjs_anywidget/js/ instead.
2
+
3
+
4
+ // src/cesiumjs_anywidget/js/viewer-init.js
5
+ async function loadCesiumJS() {
6
+ if (window.Cesium) {
7
+ return window.Cesium;
8
+ }
9
+ const script = document.createElement("script");
10
+ script.src = "https://cesium.com/downloads/cesiumjs/releases/1.135/Build/Cesium/Cesium.js";
11
+ await new Promise((resolve, reject) => {
12
+ script.onload = resolve;
13
+ script.onerror = reject;
14
+ document.head.appendChild(script);
15
+ });
16
+ return window.Cesium;
17
+ }
18
+ function createLoadingIndicator(container, hasToken) {
19
+ const loadingDiv = document.createElement("div");
20
+ loadingDiv.textContent = "Loading CesiumJS...";
21
+ loadingDiv.style.cssText = "position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 18px; color: #fff; background: rgba(0,0,0,0.7); padding: 20px; border-radius: 5px;";
22
+ if (!hasToken) {
23
+ loadingDiv.innerHTML = `
24
+ <div style="text-align: center;">
25
+ <div>Loading CesiumJS...</div>
26
+ <div style="font-size: 12px; margin-top: 10px; color: #ffa500;">
27
+ \u26A0\uFE0F No Cesium Ion token set<br>
28
+ Some features may not work
29
+ </div>
30
+ </div>
31
+ `;
32
+ }
33
+ container.appendChild(loadingDiv);
34
+ return loadingDiv;
35
+ }
36
+ function createViewer(container, model, Cesium) {
37
+ const viewerOptions = {
38
+ timeline: model.get("show_timeline"),
39
+ animation: model.get("show_animation"),
40
+ baseLayerPicker: true,
41
+ geocoder: true,
42
+ homeButton: true,
43
+ sceneModePicker: true,
44
+ navigationHelpButton: true,
45
+ fullscreenButton: true,
46
+ scene3DOnly: false,
47
+ shadows: false,
48
+ shouldAnimate: false
49
+ };
50
+ if (model.get("enable_terrain")) {
51
+ viewerOptions.terrain = Cesium.Terrain.fromWorldTerrain();
52
+ }
53
+ const viewer = new Cesium.Viewer(container, viewerOptions);
54
+ viewer.scene.globe.enableLighting = model.get("enable_lighting");
55
+ return viewer;
56
+ }
57
+ function setupViewerListeners(viewer, model, container, Cesium) {
58
+ model.on("change:enable_terrain", () => {
59
+ if (!viewer)
60
+ return;
61
+ if (model.get("enable_terrain")) {
62
+ viewer.scene.setTerrain(Cesium.Terrain.fromWorldTerrain());
63
+ } else {
64
+ viewer.scene.setTerrain(void 0);
65
+ }
66
+ });
67
+ model.on("change:enable_lighting", () => {
68
+ if (!viewer)
69
+ return;
70
+ viewer.scene.globe.enableLighting = model.get("enable_lighting");
71
+ });
72
+ model.on("change:height", () => {
73
+ if (!viewer)
74
+ return;
75
+ container.style.height = model.get("height");
76
+ viewer.resize();
77
+ });
78
+ model.on("change:show_timeline", () => {
79
+ if (!viewer || !viewer.timeline)
80
+ return;
81
+ viewer.timeline.container.style.visibility = model.get("show_timeline") ? "visible" : "hidden";
82
+ });
83
+ model.on("change:show_animation", () => {
84
+ if (!viewer || !viewer.animation)
85
+ return;
86
+ viewer.animation.container.style.visibility = model.get("show_animation") ? "visible" : "hidden";
87
+ });
88
+ }
89
+ function setupGeoJSONLoader(viewer, model, Cesium) {
90
+ let geojsonDataSource = null;
91
+ model.on("change:geojson_data", async () => {
92
+ if (!viewer)
93
+ return;
94
+ const geojsonData = model.get("geojson_data");
95
+ if (geojsonDataSource) {
96
+ viewer.dataSources.remove(geojsonDataSource);
97
+ geojsonDataSource = null;
98
+ }
99
+ if (geojsonData) {
100
+ try {
101
+ geojsonDataSource = await Cesium.GeoJsonDataSource.load(geojsonData, {
102
+ stroke: Cesium.Color.HOTPINK,
103
+ fill: Cesium.Color.PINK.withAlpha(0.5),
104
+ strokeWidth: 3
105
+ });
106
+ viewer.dataSources.add(geojsonDataSource);
107
+ viewer.flyTo(geojsonDataSource);
108
+ } catch (error) {
109
+ console.error("Error loading GeoJSON:", error);
110
+ }
111
+ }
112
+ });
113
+ return {
114
+ destroy: () => {
115
+ if (geojsonDataSource && viewer) {
116
+ viewer.dataSources.remove(geojsonDataSource);
117
+ }
118
+ }
119
+ };
120
+ }
121
+ function setupCZMLLoader(viewer, model, Cesium) {
122
+ let czmlDataSource = null;
123
+ model.on("change:czml_data", async () => {
124
+ if (!viewer)
125
+ return;
126
+ const czmlData = model.get("czml_data");
127
+ if (czmlDataSource) {
128
+ viewer.dataSources.remove(czmlDataSource);
129
+ czmlDataSource = null;
130
+ }
131
+ if (czmlData && Array.isArray(czmlData) && czmlData.length > 0) {
132
+ try {
133
+ czmlDataSource = await Cesium.CzmlDataSource.load(czmlData);
134
+ viewer.dataSources.add(czmlDataSource);
135
+ viewer.flyTo(czmlDataSource);
136
+ } catch (error) {
137
+ console.error("Error loading CZML:", error);
138
+ }
139
+ }
140
+ });
141
+ return {
142
+ destroy: () => {
143
+ if (czmlDataSource && viewer) {
144
+ viewer.dataSources.remove(czmlDataSource);
145
+ }
146
+ }
147
+ };
148
+ }
149
+
150
+ // src/cesiumjs_anywidget/js/camera-sync.js
151
+ function initializeCameraSync(viewer, model) {
152
+ const Cesium = window.Cesium;
153
+ let cameraUpdateTimeout;
154
+ function updateCameraFromModel() {
155
+ if (!viewer)
156
+ return;
157
+ const lat = model.get("latitude");
158
+ const lon = model.get("longitude");
159
+ const alt = model.get("altitude");
160
+ const heading = Cesium.Math.toRadians(model.get("heading"));
161
+ const pitch = Cesium.Math.toRadians(model.get("pitch"));
162
+ const roll = Cesium.Math.toRadians(model.get("roll"));
163
+ viewer.camera.setView({
164
+ destination: Cesium.Cartesian3.fromDegrees(lon, lat, alt),
165
+ orientation: { heading, pitch, roll }
166
+ });
167
+ }
168
+ function updateModelFromCamera() {
169
+ if (!viewer)
170
+ return;
171
+ const position = viewer.camera.positionCartographic;
172
+ const heading = viewer.camera.heading;
173
+ const pitch = viewer.camera.pitch;
174
+ const roll = viewer.camera.roll;
175
+ model.set("latitude", Cesium.Math.toDegrees(position.latitude));
176
+ model.set("longitude", Cesium.Math.toDegrees(position.longitude));
177
+ model.set("altitude", position.height);
178
+ model.set("heading", Cesium.Math.toDegrees(heading));
179
+ model.set("pitch", Cesium.Math.toDegrees(pitch));
180
+ model.set("roll", Cesium.Math.toDegrees(roll));
181
+ model.save_changes();
182
+ }
183
+ function handleCameraChanged() {
184
+ clearTimeout(cameraUpdateTimeout);
185
+ cameraUpdateTimeout = setTimeout(() => {
186
+ updateModelFromCamera();
187
+ }, 500);
188
+ }
189
+ updateCameraFromModel();
190
+ viewer.camera.changed.addEventListener(handleCameraChanged);
191
+ model.on("change:latitude", updateCameraFromModel);
192
+ model.on("change:longitude", updateCameraFromModel);
193
+ model.on("change:altitude", updateCameraFromModel);
194
+ model.on("change:heading", updateCameraFromModel);
195
+ model.on("change:pitch", updateCameraFromModel);
196
+ model.on("change:roll", updateCameraFromModel);
197
+ return {
198
+ updateCameraFromModel,
199
+ updateModelFromCamera,
200
+ destroy: () => {
201
+ clearTimeout(cameraUpdateTimeout);
202
+ viewer.camera.changed.removeEventListener(handleCameraChanged);
203
+ }
204
+ };
205
+ }
206
+
207
+ // src/cesiumjs_anywidget/js/measurement-tools.js
208
+ function initializeMeasurementTools(viewer, model, container) {
209
+ const Cesium = window.Cesium;
210
+ let measurementHandler = null;
211
+ let editHandler = null;
212
+ let measurementState = {
213
+ mode: null,
214
+ points: [],
215
+ entities: [],
216
+ labels: [],
217
+ polylines: [],
218
+ polyline: null,
219
+ tempPolyline: null
220
+ };
221
+ let editState = {
222
+ enabled: false,
223
+ selectedPoint: null,
224
+ selectedEntity: null,
225
+ dragging: false,
226
+ measurementIndex: null,
227
+ pointIndex: null
228
+ };
229
+ let completedMeasurements = [];
230
+ const toolbarDiv = document.createElement("div");
231
+ toolbarDiv.style.cssText = `
232
+ position: absolute;
233
+ top: 10px;
234
+ left: 10px;
235
+ background: rgba(42, 42, 42, 0.9);
236
+ padding: 10px;
237
+ border-radius: 5px;
238
+ z-index: 1000;
239
+ display: flex;
240
+ flex-direction: column;
241
+ gap: 5px;
242
+ `;
243
+ container.appendChild(toolbarDiv);
244
+ function createMeasurementButton(text, mode) {
245
+ const btn = document.createElement("button");
246
+ btn.textContent = text;
247
+ btn.style.cssText = `
248
+ padding: 8px 12px;
249
+ background: #3498db;
250
+ color: white;
251
+ border: none;
252
+ border-radius: 3px;
253
+ cursor: pointer;
254
+ font-size: 12px;
255
+ transition: background 0.2s;
256
+ `;
257
+ btn.onmouseover = () => {
258
+ btn.style.background = "#2980b9";
259
+ };
260
+ btn.onmouseout = () => {
261
+ btn.style.background = measurementState.mode === mode ? "#e74c3c" : "#3498db";
262
+ };
263
+ btn.onclick = () => {
264
+ if (measurementState.mode === mode) {
265
+ model.set("measurement_mode", "");
266
+ model.save_changes();
267
+ } else {
268
+ model.set("measurement_mode", mode);
269
+ model.save_changes();
270
+ }
271
+ };
272
+ return btn;
273
+ }
274
+ const distanceBtn = createMeasurementButton("\u{1F4CF} Distance", "distance");
275
+ const multiDistanceBtn = createMeasurementButton("\u{1F4D0} Multi Distance", "multi-distance");
276
+ const heightBtn = createMeasurementButton("\u{1F4CA} Height", "height");
277
+ const areaBtn = createMeasurementButton("\u2B1B Area", "area");
278
+ const clearBtn = document.createElement("button");
279
+ clearBtn.textContent = "\u{1F5D1}\uFE0F Clear";
280
+ clearBtn.style.cssText = `
281
+ padding: 8px 12px;
282
+ background: #e74c3c;
283
+ color: white;
284
+ border: none;
285
+ border-radius: 3px;
286
+ cursor: pointer;
287
+ font-size: 12px;
288
+ transition: background 0.2s;
289
+ `;
290
+ clearBtn.onmouseover = () => {
291
+ clearBtn.style.background = "#c0392b";
292
+ };
293
+ clearBtn.onmouseout = () => {
294
+ clearBtn.style.background = "#e74c3c";
295
+ };
296
+ clearBtn.onclick = () => {
297
+ clearAllMeasurements();
298
+ model.set("measurement_mode", "");
299
+ model.set("measurement_results", []);
300
+ model.save_changes();
301
+ };
302
+ toolbarDiv.appendChild(distanceBtn);
303
+ toolbarDiv.appendChild(multiDistanceBtn);
304
+ toolbarDiv.appendChild(heightBtn);
305
+ toolbarDiv.appendChild(areaBtn);
306
+ toolbarDiv.appendChild(clearBtn);
307
+ const editBtn = document.createElement("button");
308
+ editBtn.textContent = "\u270F\uFE0F Edit Points";
309
+ editBtn.style.cssText = `
310
+ padding: 8px 12px;
311
+ background: #9b59b6;
312
+ color: white;
313
+ border: none;
314
+ border-radius: 3px;
315
+ cursor: pointer;
316
+ font-size: 12px;
317
+ transition: background 0.2s;
318
+ `;
319
+ editBtn.onmouseover = () => {
320
+ editBtn.style.background = "#8e44ad";
321
+ };
322
+ editBtn.onmouseout = () => {
323
+ editBtn.style.background = editState.enabled ? "#e74c3c" : "#9b59b6";
324
+ };
325
+ editBtn.onclick = () => {
326
+ editState.enabled = !editState.enabled;
327
+ editBtn.style.background = editState.enabled ? "#e74c3c" : "#9b59b6";
328
+ if (editState.enabled) {
329
+ enableEditMode();
330
+ } else {
331
+ disableEditMode();
332
+ }
333
+ };
334
+ toolbarDiv.appendChild(editBtn);
335
+ const editorPanel = document.createElement("div");
336
+ editorPanel.style.cssText = `
337
+ position: absolute;
338
+ top: 10px;
339
+ right: 10px;
340
+ background: rgba(42, 42, 42, 0.95);
341
+ padding: 15px;
342
+ border-radius: 5px;
343
+ z-index: 1000;
344
+ display: none;
345
+ color: white;
346
+ font-family: sans-serif;
347
+ font-size: 12px;
348
+ min-width: 250px;
349
+ `;
350
+ container.appendChild(editorPanel);
351
+ const measurementsListPanel = document.createElement("div");
352
+ measurementsListPanel.style.cssText = `
353
+ position: absolute;
354
+ bottom: 10px;
355
+ right: 10px;
356
+ background: rgba(42, 42, 42, 0.95);
357
+ padding: 15px;
358
+ border-radius: 5px;
359
+ z-index: 1000;
360
+ color: white;
361
+ font-family: sans-serif;
362
+ font-size: 12px;
363
+ max-width: 350px;
364
+ max-height: 400px;
365
+ overflow-y: auto;
366
+ `;
367
+ measurementsListPanel.innerHTML = `
368
+ <div style="font-weight: bold; border-bottom: 1px solid #555; padding-bottom: 8px; margin-bottom: 10px;">
369
+ Measurements
370
+ </div>
371
+ <div id="measurements-list-content"></div>
372
+ `;
373
+ container.appendChild(measurementsListPanel);
374
+ function getPosition(screenPosition) {
375
+ const pickedObject = viewer.scene.pick(screenPosition);
376
+ if (viewer.scene.pickPositionSupported && Cesium.defined(pickedObject)) {
377
+ const cartesian = viewer.scene.pickPosition(screenPosition);
378
+ if (Cesium.defined(cartesian)) {
379
+ return cartesian;
380
+ }
381
+ }
382
+ const ray = viewer.camera.getPickRay(screenPosition);
383
+ return viewer.scene.globe.pick(ray, viewer.scene);
384
+ }
385
+ function addMarker(position, color = Cesium.Color.RED) {
386
+ const marker = viewer.entities.add({
387
+ position,
388
+ point: {
389
+ pixelSize: 10,
390
+ color,
391
+ outlineColor: Cesium.Color.WHITE,
392
+ outlineWidth: 2,
393
+ disableDepthTestDistance: Number.POSITIVE_INFINITY
394
+ }
395
+ });
396
+ measurementState.entities.push(marker);
397
+ return marker;
398
+ }
399
+ function addLabel(position, text) {
400
+ const label = viewer.entities.add({
401
+ position,
402
+ label: {
403
+ text,
404
+ font: "14px sans-serif",
405
+ fillColor: Cesium.Color.WHITE,
406
+ style: Cesium.LabelStyle.FILL_AND_OUTLINE,
407
+ outlineWidth: 2,
408
+ outlineColor: Cesium.Color.BLACK,
409
+ pixelOffset: new Cesium.Cartesian2(0, -20),
410
+ showBackground: true,
411
+ backgroundColor: Cesium.Color.fromAlpha(Cesium.Color.BLACK, 0.7),
412
+ disableDepthTestDistance: Number.POSITIVE_INFINITY
413
+ }
414
+ });
415
+ measurementState.labels.push(label);
416
+ return label;
417
+ }
418
+ function calculateDistance(point1, point2) {
419
+ return Cesium.Cartesian3.distance(point1, point2);
420
+ }
421
+ function getMidpoint(point1, point2) {
422
+ return Cesium.Cartesian3.lerp(point1, point2, 0.5, new Cesium.Cartesian3());
423
+ }
424
+ function cartesianToLatLonAlt(cartesian) {
425
+ const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
426
+ return {
427
+ lat: Cesium.Math.toDegrees(cartographic.latitude),
428
+ lon: Cesium.Math.toDegrees(cartographic.longitude),
429
+ alt: cartographic.height
430
+ };
431
+ }
432
+ function clearAllMeasurements() {
433
+ measurementState.entities.forEach((e) => viewer.entities.remove(e));
434
+ measurementState.labels.forEach((l) => viewer.entities.remove(l));
435
+ measurementState.polylines.forEach((p) => viewer.entities.remove(p));
436
+ if (measurementState.polyline) {
437
+ viewer.entities.remove(measurementState.polyline);
438
+ }
439
+ if (measurementState.tempPolyline) {
440
+ viewer.entities.remove(measurementState.tempPolyline);
441
+ }
442
+ measurementState.points = [];
443
+ measurementState.entities = [];
444
+ measurementState.labels = [];
445
+ measurementState.polylines = [];
446
+ measurementState.polyline = null;
447
+ measurementState.tempPolyline = null;
448
+ }
449
+ function clearInProgressMeasurement() {
450
+ if (measurementState.tempPolyline) {
451
+ viewer.entities.remove(measurementState.tempPolyline);
452
+ measurementState.tempPolyline = null;
453
+ }
454
+ if ((measurementState.mode === "multi-distance" || measurementState.mode === "area") && measurementState.polyline) {
455
+ viewer.entities.remove(measurementState.polyline);
456
+ measurementState.polyline = null;
457
+ measurementState.polylines = measurementState.polylines.filter((p) => p !== measurementState.polyline);
458
+ }
459
+ measurementState.points = [];
460
+ measurementState.tempPoint = null;
461
+ }
462
+ function enableEditMode() {
463
+ if (measurementState.mode) {
464
+ model.set("measurement_mode", "");
465
+ model.save_changes();
466
+ }
467
+ measurementState.entities.forEach((entity) => {
468
+ if (entity.point) {
469
+ entity.point.pixelSize = 12;
470
+ entity.point.outlineWidth = 3;
471
+ }
472
+ });
473
+ editHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
474
+ editHandler.setInputAction((click) => {
475
+ const pickedObject = viewer.scene.pick(click.position);
476
+ if (Cesium.defined(pickedObject) && pickedObject.id && pickedObject.id.point) {
477
+ selectPoint(pickedObject.id, click.position);
478
+ } else {
479
+ deselectPoint();
480
+ }
481
+ }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
482
+ editHandler.setInputAction((movement) => {
483
+ if (editState.dragging && editState.selectedEntity) {
484
+ const position = getPosition(movement.endPosition);
485
+ if (position) {
486
+ updatePointPosition(position);
487
+ }
488
+ }
489
+ }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
490
+ editHandler.setInputAction(() => {
491
+ if (editState.selectedEntity) {
492
+ editState.dragging = true;
493
+ viewer.scene.screenSpaceCameraController.enableRotate = false;
494
+ }
495
+ }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
496
+ editHandler.setInputAction(() => {
497
+ if (editState.dragging) {
498
+ editState.dragging = false;
499
+ viewer.scene.screenSpaceCameraController.enableRotate = true;
500
+ finalizeMeasurementUpdate();
501
+ }
502
+ }, Cesium.ScreenSpaceEventType.LEFT_UP);
503
+ }
504
+ function disableEditMode() {
505
+ if (editHandler) {
506
+ editHandler.destroy();
507
+ editHandler = null;
508
+ }
509
+ deselectPoint();
510
+ measurementState.entities.forEach((entity) => {
511
+ if (entity.point) {
512
+ entity.point.pixelSize = 10;
513
+ entity.point.outlineWidth = 2;
514
+ }
515
+ });
516
+ viewer.scene.screenSpaceCameraController.enableRotate = true;
517
+ }
518
+ function selectPoint(entity, screenPosition) {
519
+ const results = model.get("measurement_results") || [];
520
+ let measurementIndex = -1;
521
+ let pointIndex = -1;
522
+ for (let i = 0; i < measurementState.entities.length; i++) {
523
+ if (measurementState.entities[i] === entity) {
524
+ let entityCount = 0;
525
+ for (let m = 0; m < results.length; m++) {
526
+ const measurement = results[m];
527
+ const numPoints = measurement.points.length;
528
+ if (i < entityCount + numPoints) {
529
+ measurementIndex = m;
530
+ pointIndex = i - entityCount;
531
+ break;
532
+ }
533
+ entityCount += numPoints;
534
+ }
535
+ break;
536
+ }
537
+ }
538
+ if (measurementIndex === -1)
539
+ return;
540
+ editState.selectedEntity = entity;
541
+ editState.measurementIndex = measurementIndex;
542
+ editState.pointIndex = pointIndex;
543
+ editState.selectedPoint = entity.position.getValue(Cesium.JulianDate.now());
544
+ entity.point.pixelSize = 15;
545
+ entity.point.outlineWidth = 4;
546
+ entity.point.outlineColor = Cesium.Color.YELLOW;
547
+ showCoordinateEditor(results[measurementIndex], pointIndex);
548
+ }
549
+ function deselectPoint() {
550
+ if (editState.selectedEntity && editState.selectedEntity.point) {
551
+ editState.selectedEntity.point.pixelSize = 12;
552
+ editState.selectedEntity.point.outlineWidth = 3;
553
+ editState.selectedEntity.point.outlineColor = Cesium.Color.WHITE;
554
+ }
555
+ editState.selectedEntity = null;
556
+ editState.selectedPoint = null;
557
+ editState.measurementIndex = null;
558
+ editState.pointIndex = null;
559
+ editState.dragging = false;
560
+ editorPanel.style.display = "none";
561
+ }
562
+ function showCoordinateEditor(measurement, pointIndex) {
563
+ const point = measurement.points[pointIndex];
564
+ editorPanel.innerHTML = `
565
+ <div style="margin-bottom: 10px; font-weight: bold; border-bottom: 1px solid #555; padding-bottom: 5px;">
566
+ Edit Point ${pointIndex + 1} (${measurement.type})
567
+ </div>
568
+ <div style="margin-bottom: 8px;">
569
+ <label style="display: block; margin-bottom: 3px;">Longitude (\xB0):</label>
570
+ <input type="number" id="edit-lon" value="${point.lon.toFixed(6)}" step="0.000001"
571
+ style="width: 100%; padding: 5px; border-radius: 3px; border: 1px solid #555; background: #2c2c2c; color: white;">
572
+ </div>
573
+ <div style="margin-bottom: 8px;">
574
+ <label style="display: block; margin-bottom: 3px;">Latitude (\xB0):</label>
575
+ <input type="number" id="edit-lat" value="${point.lat.toFixed(6)}" step="0.000001"
576
+ style="width: 100%; padding: 5px; border-radius: 3px; border: 1px solid #555; background: #2c2c2c; color: white;">
577
+ </div>
578
+ <div style="margin-bottom: 10px;">
579
+ <label style="display: block; margin-bottom: 3px;">Altitude (m):</label>
580
+ <input type="number" id="edit-alt" value="${point.alt.toFixed(2)}" step="1"
581
+ style="width: 100%; padding: 5px; border-radius: 3px; border: 1px solid #555; background: #2c2c2c; color: white;">
582
+ </div>
583
+ <button id="apply-coords" style="width: 100%; padding: 8px; background: #27ae60; color: white; border: none; border-radius: 3px; cursor: pointer; margin-bottom: 5px;">
584
+ Apply
585
+ </button>
586
+ <button id="close-editor" style="width: 100%; padding: 8px; background: #95a5a6; color: white; border: none; border-radius: 3px; cursor: pointer;">
587
+ Close
588
+ </button>
589
+ `;
590
+ editorPanel.style.display = "block";
591
+ document.getElementById("apply-coords").onclick = () => {
592
+ const lon = parseFloat(document.getElementById("edit-lon").value);
593
+ const lat = parseFloat(document.getElementById("edit-lat").value);
594
+ const alt = parseFloat(document.getElementById("edit-alt").value);
595
+ const newPosition = Cesium.Cartesian3.fromDegrees(lon, lat, alt);
596
+ updatePointPosition(newPosition);
597
+ finalizeMeasurementUpdate();
598
+ };
599
+ document.getElementById("close-editor").onclick = () => {
600
+ deselectPoint();
601
+ };
602
+ ["edit-lon", "edit-lat", "edit-alt"].forEach((id) => {
603
+ document.getElementById(id).onkeypress = (e) => {
604
+ if (e.key === "Enter") {
605
+ document.getElementById("apply-coords").click();
606
+ }
607
+ };
608
+ });
609
+ }
610
+ function updatePointPosition(newPosition) {
611
+ if (!editState.selectedEntity)
612
+ return;
613
+ editState.selectedEntity.position = newPosition;
614
+ editState.selectedPoint = newPosition;
615
+ updateMeasurementVisuals();
616
+ }
617
+ function updateMeasurementVisuals() {
618
+ const results = model.get("measurement_results") || [];
619
+ if (editState.measurementIndex === null)
620
+ return;
621
+ const measurement = results[editState.measurementIndex];
622
+ let entityStartIndex = 0;
623
+ for (let i = 0; i < editState.measurementIndex; i++) {
624
+ entityStartIndex += results[i].points.length;
625
+ }
626
+ const positions = [];
627
+ for (let i = 0; i < measurement.points.length; i++) {
628
+ const entity = measurementState.entities[entityStartIndex + i];
629
+ if (entity && entity.position) {
630
+ positions.push(entity.position.getValue(Cesium.JulianDate.now()));
631
+ }
632
+ }
633
+ const polylineStartIndex = editState.measurementIndex;
634
+ if (measurementState.polylines[polylineStartIndex]) {
635
+ const oldEntity = measurementState.polylines[polylineStartIndex];
636
+ if (measurement.type === "area" && oldEntity.polygon) {
637
+ viewer.entities.remove(oldEntity);
638
+ const newPolygon = viewer.entities.add({
639
+ polygon: {
640
+ hierarchy: new Cesium.PolygonHierarchy(positions),
641
+ material: Cesium.Color.ORANGE.withAlpha(0.3),
642
+ outline: true,
643
+ outlineColor: Cesium.Color.ORANGE,
644
+ outlineWidth: 2
645
+ }
646
+ });
647
+ measurementState.polylines[polylineStartIndex] = newPolygon;
648
+ } else if (oldEntity.polyline) {
649
+ if (measurement.type === "height") {
650
+ const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
651
+ const carto1 = Cesium.Cartographic.fromCartesian(positions[1]);
652
+ oldEntity.polyline.positions = [
653
+ positions[0],
654
+ Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, carto0.height),
655
+ positions[1]
656
+ ];
657
+ } else {
658
+ oldEntity.polyline.positions = positions;
659
+ }
660
+ }
661
+ }
662
+ updateMeasurementLabels(measurement.type, positions);
663
+ }
664
+ function updateMeasurementLabels(type, positions) {
665
+ const labelStartIndex = editState.measurementIndex;
666
+ if (type === "distance") {
667
+ const distance = Cesium.Cartesian3.distance(positions[0], positions[1]);
668
+ const midpoint = Cesium.Cartesian3.midpoint(positions[0], positions[1], new Cesium.Cartesian3());
669
+ const distanceText = distance >= 1e3 ? `${(distance / 1e3).toFixed(2)} km` : `${distance.toFixed(2)} m`;
670
+ if (measurementState.labels[labelStartIndex]) {
671
+ measurementState.labels[labelStartIndex].position = midpoint;
672
+ measurementState.labels[labelStartIndex].label.text = distanceText;
673
+ }
674
+ } else if (type === "height") {
675
+ const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
676
+ const carto1 = Cesium.Cartographic.fromCartesian(positions[1]);
677
+ const verticalDistance = Math.abs(carto1.height - carto0.height);
678
+ const midHeight = (carto0.height + carto1.height) / 2;
679
+ const labelPos = Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, midHeight);
680
+ const heightText = verticalDistance >= 1e3 ? `${(verticalDistance / 1e3).toFixed(2)} km` : `${verticalDistance.toFixed(2)} m`;
681
+ if (measurementState.labels[labelStartIndex]) {
682
+ measurementState.labels[labelStartIndex].position = labelPos;
683
+ measurementState.labels[labelStartIndex].label.text = heightText;
684
+ }
685
+ }
686
+ }
687
+ function finalizeMeasurementUpdate() {
688
+ if (editState.measurementIndex === null || editState.pointIndex === null)
689
+ return;
690
+ const results = model.get("measurement_results") || [];
691
+ const measurement = results[editState.measurementIndex];
692
+ const cartographic = Cesium.Cartographic.fromCartesian(editState.selectedPoint);
693
+ measurement.points[editState.pointIndex] = {
694
+ lat: Cesium.Math.toDegrees(cartographic.latitude),
695
+ lon: Cesium.Math.toDegrees(cartographic.longitude),
696
+ alt: cartographic.height
697
+ };
698
+ let entityStartIndex = 0;
699
+ for (let i = 0; i < editState.measurementIndex; i++) {
700
+ entityStartIndex += results[i].points.length;
701
+ }
702
+ const positions = [];
703
+ for (let i = 0; i < measurement.points.length; i++) {
704
+ const entity = measurementState.entities[entityStartIndex + i];
705
+ if (entity && entity.position) {
706
+ positions.push(entity.position.getValue(Cesium.JulianDate.now()));
707
+ }
708
+ }
709
+ if (measurement.type === "distance") {
710
+ measurement.value = Cesium.Cartesian3.distance(positions[0], positions[1]);
711
+ } else if (measurement.type === "height") {
712
+ const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
713
+ const carto1 = Cesium.Cartographic.fromCartesian(positions[1]);
714
+ measurement.value = Math.abs(carto1.height - carto0.height);
715
+ } else if (measurement.type === "multi-distance") {
716
+ let totalDistance = 0;
717
+ for (let i = 0; i < positions.length - 1; i++) {
718
+ totalDistance += Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
719
+ }
720
+ measurement.value = totalDistance;
721
+ } else if (measurement.type === "area") {
722
+ const polygonHierarchy = new Cesium.PolygonHierarchy(positions);
723
+ const geometry = Cesium.PolygonGeometry.createGeometry(
724
+ new Cesium.PolygonGeometry({
725
+ polygonHierarchy,
726
+ perPositionHeight: false,
727
+ arcType: Cesium.ArcType.GEODESIC
728
+ })
729
+ );
730
+ let area = 0;
731
+ if (geometry) {
732
+ const positionsArray = geometry.attributes.position.values;
733
+ const indices = geometry.indices;
734
+ for (let i = 0; i < indices.length; i += 3) {
735
+ const i0 = indices[i] * 3;
736
+ const i1 = indices[i + 1] * 3;
737
+ const i2 = indices[i + 2] * 3;
738
+ const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
739
+ const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
740
+ const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
741
+ const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
742
+ const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
743
+ const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
744
+ const triangleArea = Cesium.Cartesian3.magnitude(crossProduct) / 2;
745
+ area += triangleArea;
746
+ }
747
+ }
748
+ measurement.value = area;
749
+ }
750
+ const newResults = [...results];
751
+ model.set("measurement_results", newResults);
752
+ model.save_changes();
753
+ updateMeasurementsList();
754
+ if (editorPanel.style.display !== "none") {
755
+ showCoordinateEditor(measurement, editState.pointIndex);
756
+ }
757
+ }
758
+ function updateMeasurementsList() {
759
+ const results = model.get("measurement_results") || [];
760
+ const listContent = document.getElementById("measurements-list-content");
761
+ if (results.length === 0) {
762
+ listContent.innerHTML = '<div style="color: #888; font-style: italic;">No measurements yet</div>';
763
+ return;
764
+ }
765
+ listContent.innerHTML = "";
766
+ results.forEach((measurement, index) => {
767
+ const measurementDiv = document.createElement("div");
768
+ measurementDiv.style.cssText = `
769
+ background: rgba(255, 255, 255, 0.05);
770
+ padding: 10px;
771
+ margin-bottom: 8px;
772
+ border-radius: 3px;
773
+ cursor: pointer;
774
+ transition: background 0.2s;
775
+ border-left: 3px solid ${getMeasurementColor(measurement.type)};
776
+ `;
777
+ measurementDiv.onmouseover = () => {
778
+ measurementDiv.style.background = "rgba(255, 255, 255, 0.15)";
779
+ };
780
+ measurementDiv.onmouseout = () => {
781
+ measurementDiv.style.background = "rgba(255, 255, 255, 0.05)";
782
+ };
783
+ const name = measurement.name || `${getMeasurementTypeLabel(measurement.type)} ${index + 1}`;
784
+ const nameDiv = document.createElement("div");
785
+ nameDiv.style.cssText = `
786
+ font-weight: bold;
787
+ margin-bottom: 5px;
788
+ display: flex;
789
+ justify-content: space-between;
790
+ align-items: center;
791
+ `;
792
+ nameDiv.innerHTML = `
793
+ <span style="flex: 1;">${name}</span>
794
+ <button id="rename-${index}" style="padding: 2px 6px; background: #3498db; color: white; border: none; border-radius: 2px; cursor: pointer; font-size: 10px;">\u270E</button>
795
+ `;
796
+ measurementDiv.appendChild(nameDiv);
797
+ const valueDiv = document.createElement("div");
798
+ valueDiv.style.cssText = "color: #aaa; font-size: 11px; margin-bottom: 3px;";
799
+ valueDiv.textContent = formatMeasurementValue(measurement);
800
+ measurementDiv.appendChild(valueDiv);
801
+ const pointsDiv = document.createElement("div");
802
+ pointsDiv.style.cssText = "color: #888; font-size: 10px;";
803
+ pointsDiv.textContent = `${measurement.points.length} point${measurement.points.length > 1 ? "s" : ""}`;
804
+ measurementDiv.appendChild(pointsDiv);
805
+ measurementDiv.onclick = (e) => {
806
+ if (!e.target.id.startsWith("rename-")) {
807
+ focusOnMeasurement(index);
808
+ }
809
+ };
810
+ listContent.appendChild(measurementDiv);
811
+ document.getElementById(`rename-${index}`).onclick = (e) => {
812
+ e.stopPropagation();
813
+ renameMeasurement(index, name);
814
+ };
815
+ });
816
+ }
817
+ function getMeasurementColor(type) {
818
+ const colors = {
819
+ "distance": "#e74c3c",
820
+ "multi-distance": "#3498db",
821
+ "height": "#2ecc71",
822
+ "area": "#e67e22"
823
+ };
824
+ return colors[type] || "#95a5a6";
825
+ }
826
+ function getMeasurementTypeLabel(type) {
827
+ const labels = {
828
+ "distance": "Distance",
829
+ "multi-distance": "Multi-Distance",
830
+ "height": "Height",
831
+ "area": "Area"
832
+ };
833
+ return labels[type] || type;
834
+ }
835
+ function formatMeasurementValue(measurement) {
836
+ const value = measurement.value;
837
+ const type = measurement.type;
838
+ if (type === "area") {
839
+ return value >= 1e6 ? `${(value / 1e6).toFixed(2)} km\xB2` : `${value.toFixed(2)} m\xB2`;
840
+ } else {
841
+ return value >= 1e3 ? `${(value / 1e3).toFixed(2)} km` : `${value.toFixed(2)} m`;
842
+ }
843
+ }
844
+ function renameMeasurement(index, currentName) {
845
+ const newName = prompt("Enter new name for measurement:", currentName);
846
+ if (newName && newName.trim()) {
847
+ const results = model.get("measurement_results") || [];
848
+ const newResults = [...results];
849
+ newResults[index] = { ...newResults[index], name: newName.trim() };
850
+ model.set("measurement_results", newResults);
851
+ model.save_changes();
852
+ updateMeasurementsList();
853
+ }
854
+ }
855
+ function focusOnMeasurement(index) {
856
+ const results = model.get("measurement_results") || [];
857
+ if (index < 0 || index >= results.length)
858
+ return;
859
+ const measurement = results[index];
860
+ if (!measurement.points || measurement.points.length === 0)
861
+ return;
862
+ const positions = measurement.points.map(
863
+ (p) => Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.alt || 0)
864
+ );
865
+ const boundingSphere = Cesium.BoundingSphere.fromPoints(positions);
866
+ viewer.camera.flyToBoundingSphere(boundingSphere, {
867
+ duration: 1.5,
868
+ offset: new Cesium.HeadingPitchRange(
869
+ 0,
870
+ Cesium.Math.toRadians(-45),
871
+ boundingSphere.radius * 3
872
+ )
873
+ });
874
+ }
875
+ function handleDistanceClick(click) {
876
+ const position = getPosition(click.position);
877
+ if (!position)
878
+ return;
879
+ if (measurementState.points.length === 0) {
880
+ measurementState.points.push(position);
881
+ addMarker(position);
882
+ measurementState.tempPolyline = viewer.entities.add({
883
+ polyline: {
884
+ positions: new Cesium.CallbackProperty(() => {
885
+ if (measurementState.points.length === 1 && measurementState.tempPoint) {
886
+ return [measurementState.points[0], measurementState.tempPoint];
887
+ }
888
+ return measurementState.points;
889
+ }, false),
890
+ width: 3,
891
+ material: Cesium.Color.YELLOW,
892
+ depthFailMaterial: Cesium.Color.YELLOW
893
+ }
894
+ });
895
+ } else if (measurementState.points.length === 1) {
896
+ measurementState.points.push(position);
897
+ addMarker(position);
898
+ const distance = calculateDistance(measurementState.points[0], measurementState.points[1]);
899
+ const midpoint = getMidpoint(measurementState.points[0], measurementState.points[1]);
900
+ addLabel(midpoint, `${distance.toFixed(2)} m`);
901
+ if (measurementState.tempPolyline) {
902
+ viewer.entities.remove(measurementState.tempPolyline);
903
+ measurementState.tempPolyline = null;
904
+ }
905
+ measurementState.polyline = viewer.entities.add({
906
+ polyline: {
907
+ positions: measurementState.points,
908
+ width: 3,
909
+ material: Cesium.Color.RED,
910
+ depthFailMaterial: Cesium.Color.RED
911
+ }
912
+ });
913
+ measurementState.polylines.push(measurementState.polyline);
914
+ const results = model.get("measurement_results") || [];
915
+ const newResults = [...results, {
916
+ type: "distance",
917
+ value: distance,
918
+ points: measurementState.points.map(cartesianToLatLonAlt),
919
+ name: `Distance ${results.filter((r) => r.type === "distance").length + 1}`
920
+ }];
921
+ model.set("measurement_results", newResults);
922
+ model.save_changes();
923
+ measurementState.points = [];
924
+ }
925
+ }
926
+ function handleMultiDistanceClick(click) {
927
+ const position = getPosition(click.position);
928
+ if (!position)
929
+ return;
930
+ measurementState.points.push(position);
931
+ addMarker(position, Cesium.Color.BLUE);
932
+ if (measurementState.points.length === 1) {
933
+ measurementState.polyline = viewer.entities.add({
934
+ polyline: {
935
+ positions: new Cesium.CallbackProperty(() => measurementState.points, false),
936
+ width: 3,
937
+ material: Cesium.Color.BLUE,
938
+ depthFailMaterial: Cesium.Color.BLUE
939
+ }
940
+ });
941
+ measurementState.polylines.push(measurementState.polyline);
942
+ } else {
943
+ const p1 = measurementState.points[measurementState.points.length - 2];
944
+ const p2 = measurementState.points[measurementState.points.length - 1];
945
+ const distance = calculateDistance(p1, p2);
946
+ const midpoint = getMidpoint(p1, p2);
947
+ addLabel(midpoint, `${distance.toFixed(2)} m`);
948
+ let totalDistance = 0;
949
+ for (let i = 0; i < measurementState.points.length - 1; i++) {
950
+ totalDistance += calculateDistance(
951
+ measurementState.points[i],
952
+ measurementState.points[i + 1]
953
+ );
954
+ }
955
+ const results = model.get("measurement_results") || [];
956
+ const lastResult = results[results.length - 1];
957
+ let newResults;
958
+ if (lastResult && lastResult.type === "multi-distance" && lastResult.isActive) {
959
+ newResults = [...results];
960
+ newResults[newResults.length - 1] = {
961
+ ...lastResult,
962
+ value: totalDistance,
963
+ points: measurementState.points.map(cartesianToLatLonAlt)
964
+ };
965
+ } else {
966
+ const multiDistanceCount = results.filter((r) => r.type === "multi-distance").length + 1;
967
+ newResults = [...results, {
968
+ type: "multi-distance",
969
+ value: totalDistance,
970
+ points: measurementState.points.map(cartesianToLatLonAlt),
971
+ isActive: true,
972
+ name: `Multi-Distance ${multiDistanceCount}`
973
+ }];
974
+ }
975
+ model.set("measurement_results", newResults);
976
+ model.save_changes();
977
+ }
978
+ }
979
+ function handleHeightClick(click) {
980
+ const pickedPosition = getPosition(click.position);
981
+ if (!pickedPosition)
982
+ return;
983
+ const cartographic = Cesium.Cartographic.fromCartesian(pickedPosition);
984
+ const terrainHeight = viewer.scene.globe.getHeight(cartographic) || 0;
985
+ const pickedHeight = cartographic.height;
986
+ const height = pickedHeight - terrainHeight;
987
+ const groundPosition = Cesium.Cartesian3.fromRadians(
988
+ cartographic.longitude,
989
+ cartographic.latitude,
990
+ terrainHeight
991
+ );
992
+ addMarker(groundPosition, Cesium.Color.GREEN);
993
+ addMarker(pickedPosition, Cesium.Color.GREEN);
994
+ const heightLine = viewer.entities.add({
995
+ polyline: {
996
+ positions: [groundPosition, pickedPosition],
997
+ width: 3,
998
+ material: Cesium.Color.GREEN,
999
+ depthFailMaterial: Cesium.Color.GREEN
1000
+ }
1001
+ });
1002
+ measurementState.polylines.push(heightLine);
1003
+ const midpoint = getMidpoint(groundPosition, pickedPosition);
1004
+ addLabel(midpoint, `${height.toFixed(2)} m`);
1005
+ const results = model.get("measurement_results") || [];
1006
+ const newResults = [...results, {
1007
+ type: "height",
1008
+ value: height,
1009
+ points: [cartesianToLatLonAlt(groundPosition), cartesianToLatLonAlt(pickedPosition)],
1010
+ name: `Height ${results.filter((r) => r.type === "height").length + 1}`
1011
+ }];
1012
+ model.set("measurement_results", newResults);
1013
+ model.save_changes();
1014
+ }
1015
+ function handleAreaClick(click) {
1016
+ const position = getPosition(click.position);
1017
+ if (!position)
1018
+ return;
1019
+ measurementState.points.push(position);
1020
+ addMarker(position, Cesium.Color.ORANGE);
1021
+ if (measurementState.points.length === 1) {
1022
+ measurementState.polyline = viewer.entities.add({
1023
+ polygon: {
1024
+ hierarchy: new Cesium.CallbackProperty(() => {
1025
+ return new Cesium.PolygonHierarchy(measurementState.points);
1026
+ }, false),
1027
+ material: Cesium.Color.ORANGE.withAlpha(0.3),
1028
+ outline: true,
1029
+ outlineColor: Cesium.Color.ORANGE,
1030
+ outlineWidth: 2
1031
+ }
1032
+ });
1033
+ measurementState.polylines.push(measurementState.polyline);
1034
+ }
1035
+ if (measurementState.points.length >= 3) {
1036
+ const positions = measurementState.points;
1037
+ const polygonHierarchy = new Cesium.PolygonHierarchy(positions);
1038
+ const geometry = Cesium.PolygonGeometry.createGeometry(
1039
+ new Cesium.PolygonGeometry({
1040
+ polygonHierarchy,
1041
+ perPositionHeight: false,
1042
+ arcType: Cesium.ArcType.GEODESIC
1043
+ })
1044
+ );
1045
+ let area = 0;
1046
+ if (geometry) {
1047
+ const positionsArray = geometry.attributes.position.values;
1048
+ const indices = geometry.indices;
1049
+ for (let i = 0; i < indices.length; i += 3) {
1050
+ const i0 = indices[i] * 3;
1051
+ const i1 = indices[i + 1] * 3;
1052
+ const i2 = indices[i + 2] * 3;
1053
+ const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
1054
+ const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
1055
+ const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
1056
+ const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
1057
+ const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
1058
+ const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
1059
+ const triangleArea = Cesium.Cartesian3.magnitude(crossProduct) / 2;
1060
+ area += triangleArea;
1061
+ }
1062
+ }
1063
+ let centroidLon = 0, centroidLat = 0;
1064
+ positions.forEach((pos) => {
1065
+ const carto = Cesium.Cartographic.fromCartesian(pos);
1066
+ centroidLon += carto.longitude;
1067
+ centroidLat += carto.latitude;
1068
+ });
1069
+ centroidLon /= positions.length;
1070
+ centroidLat /= positions.length;
1071
+ const areaText = area >= 1e6 ? `${(area / 1e6).toFixed(2)} km\xB2` : `${area.toFixed(2)} m\xB2`;
1072
+ const oldLabel = measurementState.labels.find((l) => l.label && l.label.text._value.includes("m\xB2") || l.label.text._value.includes("km\xB2"));
1073
+ if (oldLabel) {
1074
+ viewer.entities.remove(oldLabel);
1075
+ measurementState.labels = measurementState.labels.filter((l) => l !== oldLabel);
1076
+ }
1077
+ const centroidCarto = new Cesium.Cartographic(centroidLon, centroidLat);
1078
+ const promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]);
1079
+ promise.then(() => {
1080
+ const centroid = Cesium.Cartographic.toCartesian(centroidCarto);
1081
+ addLabel(centroid, areaText);
1082
+ });
1083
+ const results = model.get("measurement_results") || [];
1084
+ const lastResult = results[results.length - 1];
1085
+ let newResults;
1086
+ if (lastResult && lastResult.type === "area" && lastResult.isActive) {
1087
+ newResults = [...results];
1088
+ newResults[newResults.length - 1] = {
1089
+ ...lastResult,
1090
+ value: area,
1091
+ points: measurementState.points.map(cartesianToLatLonAlt)
1092
+ };
1093
+ } else {
1094
+ const areaCount = results.filter((r) => r.type === "area").length + 1;
1095
+ newResults = [...results, {
1096
+ type: "area",
1097
+ value: area,
1098
+ points: measurementState.points.map(cartesianToLatLonAlt),
1099
+ isActive: true,
1100
+ name: `Area ${areaCount}`
1101
+ }];
1102
+ }
1103
+ model.set("measurement_results", newResults);
1104
+ model.save_changes();
1105
+ }
1106
+ }
1107
+ function handleMouseMove(movement) {
1108
+ if (measurementState.mode === "distance" && measurementState.points.length === 1) {
1109
+ const position = getPosition(movement.endPosition);
1110
+ if (position) {
1111
+ measurementState.tempPoint = position;
1112
+ }
1113
+ }
1114
+ }
1115
+ function enableMeasurementMode(mode) {
1116
+ if (measurementHandler) {
1117
+ measurementHandler.destroy();
1118
+ measurementHandler = null;
1119
+ }
1120
+ clearInProgressMeasurement();
1121
+ measurementState.mode = mode;
1122
+ distanceBtn.style.background = mode === "distance" ? "#e74c3c" : "#3498db";
1123
+ multiDistanceBtn.style.background = mode === "multi-distance" ? "#e74c3c" : "#3498db";
1124
+ heightBtn.style.background = mode === "height" ? "#e74c3c" : "#3498db";
1125
+ areaBtn.style.background = mode === "area" ? "#e74c3c" : "#3498db";
1126
+ if (!mode)
1127
+ return;
1128
+ measurementHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
1129
+ if (mode === "distance") {
1130
+ measurementHandler.setInputAction(handleDistanceClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
1131
+ measurementHandler.setInputAction(handleMouseMove, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
1132
+ } else if (mode === "multi-distance") {
1133
+ measurementHandler.setInputAction(handleMultiDistanceClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
1134
+ measurementHandler.setInputAction(() => {
1135
+ if (measurementState.points.length > 0) {
1136
+ const results = model.get("measurement_results") || [];
1137
+ const lastResult = results[results.length - 1];
1138
+ if (lastResult && lastResult.isActive) {
1139
+ const newResults = [...results];
1140
+ const { isActive, ...finalResult } = lastResult;
1141
+ newResults[newResults.length - 1] = finalResult;
1142
+ model.set("measurement_results", newResults);
1143
+ model.save_changes();
1144
+ }
1145
+ measurementState.points = [];
1146
+ }
1147
+ }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
1148
+ } else if (mode === "height") {
1149
+ measurementHandler.setInputAction(handleHeightClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
1150
+ } else if (mode === "area") {
1151
+ measurementHandler.setInputAction(handleAreaClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
1152
+ measurementHandler.setInputAction(() => {
1153
+ if (measurementState.points.length >= 3) {
1154
+ const results = model.get("measurement_results") || [];
1155
+ const lastResult = results[results.length - 1];
1156
+ if (lastResult && lastResult.isActive) {
1157
+ const newResults = [...results];
1158
+ const { isActive, ...finalResult } = lastResult;
1159
+ newResults[newResults.length - 1] = finalResult;
1160
+ model.set("measurement_results", newResults);
1161
+ model.save_changes();
1162
+ }
1163
+ measurementState.points = [];
1164
+ }
1165
+ }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
1166
+ }
1167
+ }
1168
+ function loadAndDisplayMeasurements(measurements) {
1169
+ if (!Array.isArray(measurements))
1170
+ return;
1171
+ measurements.forEach((measurement) => {
1172
+ const { type, points } = measurement;
1173
+ if (!type || !Array.isArray(points) || points.length < 2)
1174
+ return;
1175
+ const positions = points.map((point) => {
1176
+ const [lon, lat, alt] = point;
1177
+ return Cesium.Cartesian3.fromDegrees(lon, lat, alt || 0);
1178
+ });
1179
+ if (type === "distance" && positions.length === 2) {
1180
+ displayDistance(positions);
1181
+ } else if (type === "multi-distance" && positions.length >= 2) {
1182
+ displayMultiDistance(positions);
1183
+ } else if (type === "height" && positions.length === 2) {
1184
+ displayHeight(positions);
1185
+ } else if (type === "area" && positions.length >= 3) {
1186
+ displayArea(positions);
1187
+ }
1188
+ });
1189
+ }
1190
+ function displayDistance(positions) {
1191
+ positions.forEach((pos) => addMarker(pos, Cesium.Color.RED));
1192
+ const line = viewer.entities.add({
1193
+ polyline: {
1194
+ positions,
1195
+ width: 3,
1196
+ material: Cesium.Color.RED
1197
+ }
1198
+ });
1199
+ measurementState.polylines.push(line);
1200
+ const distance = Cesium.Cartesian3.distance(positions[0], positions[1]);
1201
+ const midpoint = Cesium.Cartesian3.midpoint(positions[0], positions[1], new Cesium.Cartesian3());
1202
+ const distanceText = distance >= 1e3 ? `${(distance / 1e3).toFixed(2)} km` : `${distance.toFixed(2)} m`;
1203
+ addLabel(midpoint, distanceText);
1204
+ }
1205
+ function displayMultiDistance(positions) {
1206
+ positions.forEach((pos) => addMarker(pos, Cesium.Color.BLUE));
1207
+ const line = viewer.entities.add({
1208
+ polyline: {
1209
+ positions,
1210
+ width: 3,
1211
+ material: Cesium.Color.BLUE
1212
+ }
1213
+ });
1214
+ measurementState.polylines.push(line);
1215
+ let totalDistance = 0;
1216
+ for (let i = 0; i < positions.length - 1; i++) {
1217
+ const segmentDistance = Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
1218
+ totalDistance += segmentDistance;
1219
+ const midpoint = Cesium.Cartesian3.midpoint(positions[i], positions[i + 1], new Cesium.Cartesian3());
1220
+ const segmentText = segmentDistance >= 1e3 ? `${(segmentDistance / 1e3).toFixed(2)} km` : `${segmentDistance.toFixed(2)} m`;
1221
+ addLabel(midpoint, segmentText);
1222
+ }
1223
+ const lastPos = positions[positions.length - 1];
1224
+ const totalText = totalDistance >= 1e3 ? `Total: ${(totalDistance / 1e3).toFixed(2)} km` : `Total: ${totalDistance.toFixed(2)} m`;
1225
+ addLabel(lastPos, totalText);
1226
+ }
1227
+ function displayHeight(positions) {
1228
+ positions.forEach((pos) => addMarker(pos, Cesium.Color.GREEN));
1229
+ const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
1230
+ const carto1 = Cesium.Cartographic.fromCartesian(positions[1]);
1231
+ const verticalDistance = Math.abs(carto1.height - carto0.height);
1232
+ const line = viewer.entities.add({
1233
+ polyline: {
1234
+ positions: [
1235
+ positions[0],
1236
+ Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, carto0.height),
1237
+ positions[1]
1238
+ ],
1239
+ width: 3,
1240
+ material: Cesium.Color.GREEN
1241
+ }
1242
+ });
1243
+ measurementState.polylines.push(line);
1244
+ const midHeight = (carto0.height + carto1.height) / 2;
1245
+ const labelPos = Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, midHeight);
1246
+ const heightText = verticalDistance >= 1e3 ? `${(verticalDistance / 1e3).toFixed(2)} km` : `${verticalDistance.toFixed(2)} m`;
1247
+ addLabel(labelPos, heightText);
1248
+ }
1249
+ function displayArea(positions) {
1250
+ positions.forEach((pos) => addMarker(pos, Cesium.Color.ORANGE));
1251
+ const polygon = viewer.entities.add({
1252
+ polygon: {
1253
+ hierarchy: new Cesium.PolygonHierarchy(positions),
1254
+ material: Cesium.Color.ORANGE.withAlpha(0.3),
1255
+ outline: true,
1256
+ outlineColor: Cesium.Color.ORANGE,
1257
+ outlineWidth: 2
1258
+ }
1259
+ });
1260
+ measurementState.polylines.push(polygon);
1261
+ const polygonHierarchy = new Cesium.PolygonHierarchy(positions);
1262
+ const geometry = Cesium.PolygonGeometry.createGeometry(
1263
+ new Cesium.PolygonGeometry({
1264
+ polygonHierarchy,
1265
+ perPositionHeight: false,
1266
+ arcType: Cesium.ArcType.GEODESIC
1267
+ })
1268
+ );
1269
+ let area = 0;
1270
+ if (geometry) {
1271
+ const positionsArray = geometry.attributes.position.values;
1272
+ const indices = geometry.indices;
1273
+ for (let i = 0; i < indices.length; i += 3) {
1274
+ const i0 = indices[i] * 3;
1275
+ const i1 = indices[i + 1] * 3;
1276
+ const i2 = indices[i + 2] * 3;
1277
+ const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
1278
+ const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
1279
+ const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
1280
+ const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
1281
+ const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
1282
+ const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
1283
+ const triangleArea = Cesium.Cartesian3.magnitude(crossProduct) / 2;
1284
+ area += triangleArea;
1285
+ }
1286
+ }
1287
+ let centroidLon = 0, centroidLat = 0;
1288
+ positions.forEach((pos) => {
1289
+ const carto = Cesium.Cartographic.fromCartesian(pos);
1290
+ centroidLon += carto.longitude;
1291
+ centroidLat += carto.latitude;
1292
+ });
1293
+ centroidLon /= positions.length;
1294
+ centroidLat /= positions.length;
1295
+ const areaText = area >= 1e6 ? `${(area / 1e6).toFixed(2)} km\xB2` : `${area.toFixed(2)} m\xB2`;
1296
+ const centroidCarto = new Cesium.Cartographic(centroidLon, centroidLat);
1297
+ const promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]);
1298
+ promise.then(() => {
1299
+ const centroid = Cesium.Cartographic.toCartesian(centroidCarto);
1300
+ addLabel(centroid, areaText);
1301
+ });
1302
+ }
1303
+ model.on("change:measurement_mode", () => {
1304
+ const mode = model.get("measurement_mode");
1305
+ enableMeasurementMode(mode);
1306
+ });
1307
+ model.on("change:measurement_results", () => {
1308
+ const results = model.get("measurement_results") || [];
1309
+ if (results.length === 0) {
1310
+ clearAllMeasurements();
1311
+ }
1312
+ updateMeasurementsList();
1313
+ });
1314
+ model.on("change:load_measurements_trigger", () => {
1315
+ const triggerData = model.get("load_measurements_trigger");
1316
+ if (triggerData && triggerData.measurements) {
1317
+ loadAndDisplayMeasurements(triggerData.measurements);
1318
+ updateMeasurementsList();
1319
+ }
1320
+ });
1321
+ model.on("change:focus_measurement_trigger", () => {
1322
+ const triggerData = model.get("focus_measurement_trigger");
1323
+ if (triggerData && typeof triggerData.index === "number") {
1324
+ focusOnMeasurement(triggerData.index);
1325
+ }
1326
+ });
1327
+ model.on("change:show_measurement_tools", () => {
1328
+ const show = model.get("show_measurement_tools");
1329
+ toolbarDiv.style.display = show ? "flex" : "none";
1330
+ editorPanel.style.display = show ? editorPanel.style.display : "none";
1331
+ if (!show && editState.enabled) {
1332
+ editState.enabled = false;
1333
+ disableEditMode();
1334
+ }
1335
+ });
1336
+ model.on("change:show_measurements_list", () => {
1337
+ const show = model.get("show_measurements_list");
1338
+ measurementsListPanel.style.display = show ? "block" : "none";
1339
+ });
1340
+ toolbarDiv.style.display = model.get("show_measurement_tools") ? "flex" : "none";
1341
+ measurementsListPanel.style.display = model.get("show_measurements_list") ? "block" : "none";
1342
+ updateMeasurementsList();
1343
+ return {
1344
+ enableMeasurementMode,
1345
+ clearAllMeasurements,
1346
+ destroy: () => {
1347
+ if (measurementHandler) {
1348
+ measurementHandler.destroy();
1349
+ }
1350
+ clearAllMeasurements();
1351
+ if (toolbarDiv.parentNode) {
1352
+ toolbarDiv.remove();
1353
+ }
1354
+ }
1355
+ };
1356
+ }
1357
+
1358
+ // src/cesiumjs_anywidget/js/index.js
1359
+ window.CESIUM_BASE_URL = "https://cesium.com/downloads/cesiumjs/releases/1.135/Build/Cesium/";
1360
+ async function render({ model, el }) {
1361
+ const Cesium = await loadCesiumJS();
1362
+ const container = document.createElement("div");
1363
+ container.style.width = "100%";
1364
+ container.style.height = model.get("height");
1365
+ container.style.position = "relative";
1366
+ el.appendChild(container);
1367
+ const ionToken = model.get("ion_access_token");
1368
+ if (ionToken) {
1369
+ Cesium.Ion.defaultAccessToken = ionToken;
1370
+ }
1371
+ const loadingDiv = createLoadingIndicator(container, !!ionToken);
1372
+ let viewer = null;
1373
+ let cameraSync = null;
1374
+ let measurementTools = null;
1375
+ let geoJsonLoader = null;
1376
+ let czmlLoader = null;
1377
+ (async () => {
1378
+ try {
1379
+ viewer = createViewer(container, model, Cesium);
1380
+ if (loadingDiv.parentNode) {
1381
+ loadingDiv.remove();
1382
+ }
1383
+ cameraSync = initializeCameraSync(viewer, model);
1384
+ measurementTools = initializeMeasurementTools(viewer, model, container);
1385
+ setupViewerListeners(viewer, model, container, Cesium);
1386
+ geoJsonLoader = setupGeoJSONLoader(viewer, model, Cesium);
1387
+ czmlLoader = setupCZMLLoader(viewer, model, Cesium);
1388
+ } catch (error) {
1389
+ console.error("Error initializing CesiumJS viewer:", error);
1390
+ loadingDiv.textContent = `Error: ${error.message}`;
1391
+ loadingDiv.style.background = "rgba(255,0,0,0.8)";
1392
+ }
1393
+ })();
1394
+ return () => {
1395
+ if (cameraSync) {
1396
+ cameraSync.destroy();
1397
+ }
1398
+ if (measurementTools) {
1399
+ measurementTools.destroy();
1400
+ }
1401
+ if (geoJsonLoader) {
1402
+ geoJsonLoader.destroy();
1403
+ }
1404
+ if (czmlLoader) {
1405
+ czmlLoader.destroy();
1406
+ }
1407
+ if (viewer) {
1408
+ viewer.destroy();
1409
+ }
1410
+ };
1411
+ }
1412
+ var js_default = { render };
1413
+ export {
1414
+ js_default as default
1415
+ };