cesiumjs-anywidget 0.2.4__tar.gz → 0.4.0__tar.gz

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.
@@ -211,4 +211,4 @@ node_modules/
211
211
  package-lock.json
212
212
 
213
213
  # Generated/bundled files - source is in js/ subdirectory
214
- src/cesiumjs_anywidget/index.js
214
+ src/cesiumjs_anywidget/index.js
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cesiumjs-anywidget
3
- Version: 0.2.4
3
+ Version: 0.4.0
4
4
  Summary: A Jupyter widget for CesiumJS 3D globe visualization using anywidget
5
5
  Project-URL: Homepage, https://github.com/Alex-PLACET/cesiumjs_anywidget
6
6
  Project-URL: Repository, https://github.com/Alex-PLACET/cesiumjs_anywidget
@@ -0,0 +1,62 @@
1
+ import re
2
+
3
+ # Read the test file
4
+ with open('tests/test_methods.py', 'r') as f:
5
+ content = f.read()
6
+
7
+ # Fix the assertions
8
+ # Change: assert widget_instance.geojson_data == geojson
9
+ # To: assert widget_instance.geojson_data == [geojson]
10
+ content = re.sub(
11
+ r'assert widget_instance\.geojson_data == (sample_geojson|geojson|new_geojson)([^\[])',
12
+ r'assert widget_instance.geojson_data == [\1]\2',
13
+ content
14
+ )
15
+
16
+ # Fix: assert len(widget_instance.geojson_data["features"]) == 2
17
+ # To: assert len(widget_instance.geojson_data[0]["features"]) == 2
18
+ content = re.sub(
19
+ r'widget_instance\.geojson_data\["features"\]',
20
+ r'widget_instance.geojson_data[0]["features"]',
21
+ content
22
+ )
23
+
24
+ # Write back
25
+ with open('tests/test_methods.py', 'w') as f:
26
+ f.write(content)
27
+
28
+ print("Fixed test_methods.py")
29
+
30
+ # Also fix test_widget.py
31
+ with open('tests/test_widget.py', 'r') as f:
32
+ content = f.read()
33
+
34
+ # Change: assert widget_instance.geojson_data is None
35
+ # To: assert widget_instance.geojson_data == []
36
+ content = re.sub(
37
+ r'assert widget_instance\.geojson_data is None',
38
+ r'assert widget_instance.geojson_data == []',
39
+ content
40
+ )
41
+
42
+ with open('tests/test_widget.py', 'w') as f:
43
+ f.write(content)
44
+
45
+ print("Fixed test_widget.py")
46
+
47
+ # Fix test_integration.py
48
+ with open('tests/test_integration.py', 'r') as f:
49
+ content = f.read()
50
+
51
+ # Change: assert widget_instance.geojson_data == geojson
52
+ # To: assert widget_instance.geojson_data == [geojson]
53
+ content = re.sub(
54
+ r'assert widget_instance\.geojson_data == (empty_geojson|geojson)([^\[])',
55
+ r'assert widget_instance.geojson_data == [\1]\2',
56
+ content
57
+ )
58
+
59
+ with open('tests/test_integration.py', 'w') as f:
60
+ f.write(content)
61
+
62
+ print("Fixed test_integration.py")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cesiumjs-anywidget"
7
- version = "0.2.4"
7
+ version = "0.4.0"
8
8
  description = "A Jupyter widget for CesiumJS 3D globe visualization using anywidget"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -85,64 +85,318 @@ function setupViewerListeners(viewer, model, container, Cesium) {
85
85
  return;
86
86
  viewer.animation.container.style.visibility = model.get("show_animation") ? "visible" : "hidden";
87
87
  });
88
+ model.on("change:atmosphere_settings", () => {
89
+ if (!viewer || !viewer.scene || !viewer.scene.atmosphere)
90
+ return;
91
+ const settings = model.get("atmosphere_settings");
92
+ if (!settings || Object.keys(settings).length === 0)
93
+ return;
94
+ const atmosphere = viewer.scene.atmosphere;
95
+ if (settings.brightnessShift !== void 0) {
96
+ atmosphere.brightnessShift = settings.brightnessShift;
97
+ }
98
+ if (settings.hueShift !== void 0) {
99
+ atmosphere.hueShift = settings.hueShift;
100
+ }
101
+ if (settings.saturationShift !== void 0) {
102
+ atmosphere.saturationShift = settings.saturationShift;
103
+ }
104
+ if (settings.lightIntensity !== void 0) {
105
+ atmosphere.lightIntensity = settings.lightIntensity;
106
+ }
107
+ if (settings.rayleighCoefficient !== void 0 && Array.isArray(settings.rayleighCoefficient) && settings.rayleighCoefficient.length === 3) {
108
+ atmosphere.rayleighCoefficient = new Cesium.Cartesian3(
109
+ settings.rayleighCoefficient[0],
110
+ settings.rayleighCoefficient[1],
111
+ settings.rayleighCoefficient[2]
112
+ );
113
+ }
114
+ if (settings.rayleighScaleHeight !== void 0) {
115
+ atmosphere.rayleighScaleHeight = settings.rayleighScaleHeight;
116
+ }
117
+ if (settings.mieCoefficient !== void 0 && Array.isArray(settings.mieCoefficient) && settings.mieCoefficient.length === 3) {
118
+ atmosphere.mieCoefficient = new Cesium.Cartesian3(
119
+ settings.mieCoefficient[0],
120
+ settings.mieCoefficient[1],
121
+ settings.mieCoefficient[2]
122
+ );
123
+ }
124
+ if (settings.mieScaleHeight !== void 0) {
125
+ atmosphere.mieScaleHeight = settings.mieScaleHeight;
126
+ }
127
+ if (settings.mieAnisotropy !== void 0) {
128
+ atmosphere.mieAnisotropy = settings.mieAnisotropy;
129
+ }
130
+ });
131
+ model.on("change:sky_atmosphere_settings", () => {
132
+ if (!viewer || !viewer.scene || !viewer.scene.skyAtmosphere)
133
+ return;
134
+ const settings = model.get("sky_atmosphere_settings");
135
+ if (!settings || Object.keys(settings).length === 0)
136
+ return;
137
+ const skyAtmosphere = viewer.scene.skyAtmosphere;
138
+ if (settings.show !== void 0) {
139
+ skyAtmosphere.show = settings.show;
140
+ }
141
+ if (settings.brightnessShift !== void 0) {
142
+ skyAtmosphere.brightnessShift = settings.brightnessShift;
143
+ }
144
+ if (settings.hueShift !== void 0) {
145
+ skyAtmosphere.hueShift = settings.hueShift;
146
+ }
147
+ if (settings.saturationShift !== void 0) {
148
+ skyAtmosphere.saturationShift = settings.saturationShift;
149
+ }
150
+ if (settings.atmosphereLightIntensity !== void 0) {
151
+ skyAtmosphere.atmosphereLightIntensity = settings.atmosphereLightIntensity;
152
+ }
153
+ if (settings.atmosphereRayleighCoefficient !== void 0 && Array.isArray(settings.atmosphereRayleighCoefficient) && settings.atmosphereRayleighCoefficient.length === 3) {
154
+ skyAtmosphere.atmosphereRayleighCoefficient = new Cesium.Cartesian3(
155
+ settings.atmosphereRayleighCoefficient[0],
156
+ settings.atmosphereRayleighCoefficient[1],
157
+ settings.atmosphereRayleighCoefficient[2]
158
+ );
159
+ }
160
+ if (settings.atmosphereRayleighScaleHeight !== void 0) {
161
+ skyAtmosphere.atmosphereRayleighScaleHeight = settings.atmosphereRayleighScaleHeight;
162
+ }
163
+ if (settings.atmosphereMieCoefficient !== void 0 && Array.isArray(settings.atmosphereMieCoefficient) && settings.atmosphereMieCoefficient.length === 3) {
164
+ skyAtmosphere.atmosphereMieCoefficient = new Cesium.Cartesian3(
165
+ settings.atmosphereMieCoefficient[0],
166
+ settings.atmosphereMieCoefficient[1],
167
+ settings.atmosphereMieCoefficient[2]
168
+ );
169
+ }
170
+ if (settings.atmosphereMieScaleHeight !== void 0) {
171
+ skyAtmosphere.atmosphereMieScaleHeight = settings.atmosphereMieScaleHeight;
172
+ }
173
+ if (settings.atmosphereMieAnisotropy !== void 0) {
174
+ skyAtmosphere.atmosphereMieAnisotropy = settings.atmosphereMieAnisotropy;
175
+ }
176
+ if (settings.perFragmentAtmosphere !== void 0) {
177
+ skyAtmosphere.perFragmentAtmosphere = settings.perFragmentAtmosphere;
178
+ }
179
+ });
180
+ model.on("change:skybox_settings", () => {
181
+ if (!viewer || !viewer.scene || !viewer.scene.skyBox)
182
+ return;
183
+ const settings = model.get("skybox_settings");
184
+ if (!settings || Object.keys(settings).length === 0)
185
+ return;
186
+ const skyBox = viewer.scene.skyBox;
187
+ if (settings.show !== void 0) {
188
+ skyBox.show = settings.show;
189
+ }
190
+ if (settings.sources !== void 0 && settings.sources !== null) {
191
+ const sources = settings.sources;
192
+ if (sources.positiveX && sources.negativeX && sources.positiveY && sources.negativeY && sources.positiveZ && sources.negativeZ) {
193
+ viewer.scene.skyBox = new Cesium.SkyBox({
194
+ sources: {
195
+ positiveX: sources.positiveX,
196
+ negativeX: sources.negativeX,
197
+ positiveY: sources.positiveY,
198
+ negativeY: sources.negativeY,
199
+ positiveZ: sources.positiveZ,
200
+ negativeZ: sources.negativeZ
201
+ }
202
+ });
203
+ if (settings.show !== void 0) {
204
+ viewer.scene.skyBox.show = settings.show;
205
+ }
206
+ }
207
+ }
208
+ });
209
+ function getCameraState() {
210
+ const cartographic = viewer.camera.positionCartographic;
211
+ return {
212
+ latitude: Cesium.Math.toDegrees(cartographic.latitude),
213
+ longitude: Cesium.Math.toDegrees(cartographic.longitude),
214
+ altitude: cartographic.height,
215
+ heading: Cesium.Math.toDegrees(viewer.camera.heading),
216
+ pitch: Cesium.Math.toDegrees(viewer.camera.pitch),
217
+ roll: Cesium.Math.toDegrees(viewer.camera.roll)
218
+ };
219
+ }
220
+ function getClockState() {
221
+ if (!viewer.clock)
222
+ return null;
223
+ return {
224
+ current_time: Cesium.JulianDate.toIso8601(viewer.clock.currentTime),
225
+ multiplier: viewer.clock.multiplier,
226
+ is_animating: viewer.clock.shouldAnimate
227
+ };
228
+ }
229
+ function sendInteractionEvent(type, additionalData = {}) {
230
+ const event = {
231
+ type,
232
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
233
+ camera: getCameraState(),
234
+ clock: getClockState(),
235
+ ...additionalData
236
+ };
237
+ console.log("[CesiumWidget] Interaction event:", type, event);
238
+ model.set("interaction_event", event);
239
+ model.save_changes();
240
+ }
241
+ const camera = viewer.camera;
242
+ camera.moveEnd.addEventListener(() => {
243
+ sendInteractionEvent("camera_move");
244
+ });
245
+ const scene = viewer.scene;
246
+ const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
247
+ handler.setInputAction((click) => {
248
+ const pickedData = {};
249
+ const ray = viewer.camera.getPickRay(click.position);
250
+ const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
251
+ if (cartesian) {
252
+ const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
253
+ pickedData.picked_position = {
254
+ latitude: Cesium.Math.toDegrees(cartographic.latitude),
255
+ longitude: Cesium.Math.toDegrees(cartographic.longitude),
256
+ altitude: cartographic.height
257
+ };
258
+ }
259
+ const pickedObject = viewer.scene.pick(click.position);
260
+ if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
261
+ const entity = pickedObject.id;
262
+ pickedData.picked_entity = {
263
+ id: entity.id,
264
+ name: entity.name || null
265
+ };
266
+ if (entity.properties) {
267
+ const props = {};
268
+ const propertyNames = entity.properties.propertyNames;
269
+ if (propertyNames && propertyNames.length > 0) {
270
+ propertyNames.forEach((name) => {
271
+ try {
272
+ props[name] = entity.properties[name].getValue(viewer.clock.currentTime);
273
+ } catch (e) {
274
+ }
275
+ });
276
+ if (Object.keys(props).length > 0) {
277
+ pickedData.picked_entity.properties = props;
278
+ }
279
+ }
280
+ }
281
+ }
282
+ sendInteractionEvent("left_click", pickedData);
283
+ }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
284
+ handler.setInputAction((click) => {
285
+ const pickedData = {};
286
+ const ray = viewer.camera.getPickRay(click.position);
287
+ const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
288
+ if (cartesian) {
289
+ const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
290
+ pickedData.picked_position = {
291
+ latitude: Cesium.Math.toDegrees(cartographic.latitude),
292
+ longitude: Cesium.Math.toDegrees(cartographic.longitude),
293
+ altitude: cartographic.height
294
+ };
295
+ }
296
+ sendInteractionEvent("right_click", pickedData);
297
+ }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
298
+ if (viewer.timeline) {
299
+ let timelineScrubbing = false;
300
+ let scrubTimeout = null;
301
+ viewer.clock.onTick.addEventListener(() => {
302
+ if (viewer.timeline) {
303
+ if (scrubTimeout) {
304
+ clearTimeout(scrubTimeout);
305
+ }
306
+ scrubTimeout = setTimeout(() => {
307
+ if (timelineScrubbing) {
308
+ timelineScrubbing = false;
309
+ sendInteractionEvent("timeline_scrub");
310
+ }
311
+ }, 500);
312
+ timelineScrubbing = true;
313
+ }
314
+ });
315
+ }
88
316
  }
89
317
  function setupGeoJSONLoader(viewer, model, Cesium) {
90
- let geojsonDataSource = null;
318
+ let geojsonDataSources = [];
91
319
  model.on("change:geojson_data", async () => {
92
- if (!viewer)
320
+ if (!viewer || !viewer.dataSources)
93
321
  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);
322
+ const geojsonDataArray = model.get("geojson_data");
323
+ geojsonDataSources.forEach((dataSource) => {
324
+ if (viewer && viewer.dataSources) {
325
+ viewer.dataSources.remove(dataSource);
326
+ }
327
+ });
328
+ geojsonDataSources = [];
329
+ if (geojsonDataArray && Array.isArray(geojsonDataArray)) {
330
+ for (const geojsonData of geojsonDataArray) {
331
+ try {
332
+ const dataSource = await Cesium.GeoJsonDataSource.load(geojsonData, {
333
+ stroke: Cesium.Color.HOTPINK,
334
+ fill: Cesium.Color.PINK.withAlpha(0.5),
335
+ strokeWidth: 3
336
+ });
337
+ if (viewer && viewer.dataSources) {
338
+ viewer.dataSources.add(dataSource);
339
+ geojsonDataSources.push(dataSource);
340
+ }
341
+ } catch (error) {
342
+ console.error("Error loading GeoJSON:", error);
343
+ }
344
+ }
345
+ if (geojsonDataSources.length > 0 && viewer && viewer.flyTo) {
346
+ viewer.flyTo(geojsonDataSources[0]);
110
347
  }
111
348
  }
112
349
  });
113
350
  return {
114
351
  destroy: () => {
115
- if (geojsonDataSource && viewer) {
116
- viewer.dataSources.remove(geojsonDataSource);
117
- }
352
+ geojsonDataSources.forEach((dataSource) => {
353
+ if (viewer) {
354
+ viewer.dataSources.remove(dataSource);
355
+ }
356
+ });
357
+ geojsonDataSources = [];
118
358
  }
119
359
  };
120
360
  }
121
361
  function setupCZMLLoader(viewer, model, Cesium) {
122
- let czmlDataSource = null;
362
+ let czmlDataSources = [];
123
363
  model.on("change:czml_data", async () => {
124
- if (!viewer)
364
+ if (!viewer || !viewer.dataSources)
125
365
  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);
366
+ const czmlDataArray = model.get("czml_data");
367
+ czmlDataSources.forEach((dataSource) => {
368
+ if (viewer && viewer.dataSources) {
369
+ viewer.dataSources.remove(dataSource);
370
+ }
371
+ });
372
+ czmlDataSources = [];
373
+ if (czmlDataArray && Array.isArray(czmlDataArray)) {
374
+ for (const czmlData of czmlDataArray) {
375
+ if (Array.isArray(czmlData) && czmlData.length > 0) {
376
+ try {
377
+ const dataSource = await Cesium.CzmlDataSource.load(czmlData);
378
+ if (viewer && viewer.dataSources) {
379
+ viewer.dataSources.add(dataSource);
380
+ czmlDataSources.push(dataSource);
381
+ }
382
+ } catch (error) {
383
+ console.error("Error loading CZML:", error);
384
+ }
385
+ }
386
+ }
387
+ if (czmlDataSources.length > 0 && viewer && viewer.flyTo) {
388
+ viewer.flyTo(czmlDataSources[0]);
138
389
  }
139
390
  }
140
391
  });
141
392
  return {
142
393
  destroy: () => {
143
- if (czmlDataSource && viewer) {
144
- viewer.dataSources.remove(czmlDataSource);
145
- }
394
+ czmlDataSources.forEach((dataSource) => {
395
+ if (viewer) {
396
+ viewer.dataSources.remove(dataSource);
397
+ }
398
+ });
399
+ czmlDataSources = [];
146
400
  }
147
401
  };
148
402
  }
@@ -194,6 +448,99 @@ function initializeCameraSync(viewer, model) {
194
448
  model.on("change:heading", updateCameraFromModel);
195
449
  model.on("change:pitch", updateCameraFromModel);
196
450
  model.on("change:roll", updateCameraFromModel);
451
+ model.on("change:camera_command", () => {
452
+ const command = model.get("camera_command");
453
+ if (!command || !command.command || !command.timestamp)
454
+ return;
455
+ const cmd = command.command;
456
+ try {
457
+ switch (cmd) {
458
+ case "flyTo":
459
+ viewer.camera.flyTo({
460
+ destination: Cesium.Cartesian3.fromDegrees(
461
+ command.longitude,
462
+ command.latitude,
463
+ command.altitude
464
+ ),
465
+ orientation: {
466
+ heading: Cesium.Math.toRadians(command.heading || 0),
467
+ pitch: Cesium.Math.toRadians(command.pitch || -15),
468
+ roll: Cesium.Math.toRadians(command.roll || 0)
469
+ },
470
+ duration: command.duration || 3
471
+ });
472
+ break;
473
+ case "setView":
474
+ viewer.camera.setView({
475
+ destination: Cesium.Cartesian3.fromDegrees(
476
+ command.longitude,
477
+ command.latitude,
478
+ command.altitude
479
+ ),
480
+ orientation: {
481
+ heading: Cesium.Math.toRadians(command.heading || 0),
482
+ pitch: Cesium.Math.toRadians(command.pitch || -15),
483
+ roll: Cesium.Math.toRadians(command.roll || 0)
484
+ }
485
+ });
486
+ break;
487
+ case "lookAt":
488
+ const target = Cesium.Cartesian3.fromDegrees(
489
+ command.targetLongitude,
490
+ command.targetLatitude,
491
+ command.targetAltitude || 0
492
+ );
493
+ const offset = new Cesium.HeadingPitchRange(
494
+ Cesium.Math.toRadians(command.offsetHeading || 0),
495
+ Cesium.Math.toRadians(command.offsetPitch || -45),
496
+ command.offsetRange || 1e3
497
+ );
498
+ viewer.camera.lookAt(target, offset);
499
+ viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
500
+ break;
501
+ case "moveForward":
502
+ viewer.camera.moveForward(command.distance || 100);
503
+ break;
504
+ case "moveBackward":
505
+ viewer.camera.moveBackward(command.distance || 100);
506
+ break;
507
+ case "moveUp":
508
+ viewer.camera.moveUp(command.distance || 100);
509
+ break;
510
+ case "moveDown":
511
+ viewer.camera.moveDown(command.distance || 100);
512
+ break;
513
+ case "moveLeft":
514
+ viewer.camera.moveLeft(command.distance || 100);
515
+ break;
516
+ case "moveRight":
517
+ viewer.camera.moveRight(command.distance || 100);
518
+ break;
519
+ case "rotateLeft":
520
+ viewer.camera.rotateLeft(Cesium.Math.toRadians(command.angle || 15));
521
+ break;
522
+ case "rotateRight":
523
+ viewer.camera.rotateRight(Cesium.Math.toRadians(command.angle || 15));
524
+ break;
525
+ case "rotateUp":
526
+ viewer.camera.rotateUp(Cesium.Math.toRadians(command.angle || 15));
527
+ break;
528
+ case "rotateDown":
529
+ viewer.camera.rotateDown(Cesium.Math.toRadians(command.angle || 15));
530
+ break;
531
+ case "zoomIn":
532
+ viewer.camera.zoomIn(command.distance || 100);
533
+ break;
534
+ case "zoomOut":
535
+ viewer.camera.zoomOut(command.distance || 100);
536
+ break;
537
+ default:
538
+ console.warn(`Unknown camera command: ${cmd}`);
539
+ }
540
+ } catch (error) {
541
+ console.error(`Error executing camera command ${cmd}:`, error);
542
+ }
543
+ });
197
544
  return {
198
545
  updateCameraFromModel,
199
546
  updateModelFromCamera,
@@ -588,23 +935,42 @@ function initializeMeasurementTools(viewer, model, container) {
588
935
  </button>
589
936
  `;
590
937
  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();
938
+ const applyBtn = document.getElementById("apply-coords");
939
+ const closeBtn = document.getElementById("close-editor");
940
+ const editLonInput = document.getElementById("edit-lon");
941
+ const editLatInput = document.getElementById("edit-lat");
942
+ const editAltInput = document.getElementById("edit-alt");
943
+ if (!applyBtn || !closeBtn || !editLonInput || !editLatInput || !editAltInput) {
944
+ console.warn("[CesiumWidget] Editor panel input elements not found in DOM");
945
+ }
946
+ if (applyBtn) {
947
+ applyBtn.onclick = () => {
948
+ if (!editLonInput || !editLatInput || !editAltInput) {
949
+ console.warn("[CesiumWidget] Editor input fields not available");
950
+ return;
606
951
  }
952
+ const lon = parseFloat(editLonInput.value);
953
+ const lat = parseFloat(editLatInput.value);
954
+ const alt = parseFloat(editAltInput.value);
955
+ const newPosition = Cesium.Cartesian3.fromDegrees(lon, lat, alt);
956
+ updatePointPosition(newPosition);
957
+ finalizeMeasurementUpdate();
607
958
  };
959
+ }
960
+ if (closeBtn) {
961
+ closeBtn.onclick = () => {
962
+ deselectPoint();
963
+ };
964
+ }
965
+ ["edit-lon", "edit-lat", "edit-alt"].forEach((id) => {
966
+ const element = document.getElementById(id);
967
+ if (element) {
968
+ element.onkeypress = (e) => {
969
+ if (e.key === "Enter" && applyBtn) {
970
+ applyBtn.click();
971
+ }
972
+ };
973
+ }
608
974
  });
609
975
  }
610
976
  function updatePointPosition(newPosition) {
@@ -758,6 +1124,10 @@ function initializeMeasurementTools(viewer, model, container) {
758
1124
  function updateMeasurementsList() {
759
1125
  const results = model.get("measurement_results") || [];
760
1126
  const listContent = document.getElementById("measurements-list-content");
1127
+ if (!listContent) {
1128
+ console.warn("[CesiumWidget] Measurements list content element not found in DOM");
1129
+ return;
1130
+ }
761
1131
  if (results.length === 0) {
762
1132
  listContent.innerHTML = '<div style="color: #888; font-style: italic;">No measurements yet</div>';
763
1133
  return;
@@ -808,10 +1178,15 @@ function initializeMeasurementTools(viewer, model, container) {
808
1178
  }
809
1179
  };
810
1180
  listContent.appendChild(measurementDiv);
811
- document.getElementById(`rename-${index}`).onclick = (e) => {
812
- e.stopPropagation();
813
- renameMeasurement(index, name);
814
- };
1181
+ const renameBtn = document.getElementById(`rename-${index}`);
1182
+ if (renameBtn) {
1183
+ renameBtn.onclick = (e) => {
1184
+ e.stopPropagation();
1185
+ renameMeasurement(index, name);
1186
+ };
1187
+ } else {
1188
+ console.warn(`[CesiumWidget] Rename button not found for measurement ${index}`);
1189
+ }
815
1190
  });
816
1191
  }
817
1192
  function getMeasurementColor(type) {