cesiumjs-anywidget 0.5.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.
@@ -1,28 +1,58 @@
1
1
  // Generated bundle - DO NOT EDIT DIRECTLY. Edit files in src/cesiumjs_anywidget/js/ instead.
2
2
 
3
3
 
4
+ // src/cesiumjs_anywidget/js/logger.js
5
+ var debugEnabled = false;
6
+ function setDebugMode(enabled) {
7
+ debugEnabled = enabled;
8
+ if (enabled) {
9
+ console.log("[CesiumWidget] Debug mode enabled");
10
+ }
11
+ }
12
+ function log(prefix, ...args) {
13
+ if (debugEnabled) {
14
+ console.log(`[CesiumWidget:${prefix}]`, ...args);
15
+ }
16
+ }
17
+ function warn(prefix, ...args) {
18
+ console.warn(`[CesiumWidget:${prefix}]`, ...args);
19
+ }
20
+ function error(prefix, ...args) {
21
+ console.error(`[CesiumWidget:${prefix}]`, ...args);
22
+ }
23
+
4
24
  // src/cesiumjs_anywidget/js/viewer-init.js
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
+ };
5
35
  async function loadCesiumJS() {
6
- console.log("[CesiumWidget:ViewerInit] Loading CesiumJS...");
36
+ log(PREFIX, "Loading CesiumJS...");
7
37
  if (window.Cesium) {
8
- console.log("[CesiumWidget:ViewerInit] CesiumJS already loaded, reusing existing instance");
38
+ log(PREFIX, "CesiumJS already loaded, reusing existing instance");
9
39
  return window.Cesium;
10
40
  }
11
41
  const script = document.createElement("script");
12
- script.src = "https://cesium.com/downloads/cesiumjs/releases/1.135/Build/Cesium/Cesium.js";
13
- console.log("[CesiumWidget:ViewerInit] Loading CesiumJS from CDN...");
42
+ script.src = `https://cesium.com/downloads/cesiumjs/releases/${CONSTANTS.CESIUM_CDN_VERSION}/Build/Cesium/Cesium.js`;
43
+ log(PREFIX, "Loading CesiumJS from CDN...");
14
44
  await new Promise((resolve, reject) => {
15
45
  script.onload = () => {
16
- console.log("[CesiumWidget:ViewerInit] CesiumJS script loaded successfully");
46
+ log(PREFIX, "CesiumJS script loaded successfully");
17
47
  resolve();
18
48
  };
19
- script.onerror = (error) => {
20
- console.error("[CesiumWidget:ViewerInit] Failed to load CesiumJS script:", error);
21
- reject(error);
49
+ script.onerror = (err) => {
50
+ error(PREFIX, "Failed to load CesiumJS script:", err);
51
+ reject(err);
22
52
  };
23
53
  document.head.appendChild(script);
24
54
  });
25
- console.log("[CesiumWidget:ViewerInit] CesiumJS initialized");
55
+ log(PREFIX, "CesiumJS initialized");
26
56
  return window.Cesium;
27
57
  }
28
58
  function createLoadingIndicator(container, hasToken) {
@@ -44,7 +74,7 @@ function createLoadingIndicator(container, hasToken) {
44
74
  return loadingDiv;
45
75
  }
46
76
  function createViewer(container, model, Cesium) {
47
- console.log("[CesiumWidget:ViewerInit] Creating viewer with options...");
77
+ log(PREFIX, "Creating viewer with options...");
48
78
  const viewerOptions = {
49
79
  timeline: model.get("show_timeline"),
50
80
  animation: model.get("show_animation"),
@@ -58,28 +88,36 @@ function createViewer(container, model, Cesium) {
58
88
  shadows: false,
59
89
  shouldAnimate: false
60
90
  };
61
- console.log("[CesiumWidget:ViewerInit] Viewer options:", viewerOptions);
62
- if (model.get("enable_terrain")) {
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
+ }
96
+ log(PREFIX, "Viewer options:", viewerOptions);
97
+ if (model.get("enable_terrain") && showGlobe !== false) {
63
98
  viewerOptions.terrain = Cesium.Terrain.fromWorldTerrain();
64
- console.log("[CesiumWidget:ViewerInit] Terrain enabled");
99
+ log(PREFIX, "Terrain enabled");
65
100
  }
66
101
  const viewer = new Cesium.Viewer(container, viewerOptions);
67
- viewer.scene.globe.enableLighting = model.get("enable_lighting");
68
- console.log("[CesiumWidget:ViewerInit] Viewer created, lighting:", model.get("enable_lighting"));
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
+ }
69
108
  return viewer;
70
109
  }
71
110
  function setupViewerListeners(viewer, model, container, Cesium) {
72
- console.log("[CesiumWidget:ViewerInit] Setting up viewer listeners");
111
+ log(PREFIX, "Setting up viewer listeners");
73
112
  let isDestroyed = false;
74
113
  let scrubTimeout = null;
75
114
  model.on("change:enable_terrain", () => {
76
115
  if (isDestroyed) {
77
- console.log("[CesiumWidget:ViewerInit] Skipping enable_terrain change - destroyed");
116
+ log(PREFIX, "Skipping enable_terrain change - destroyed");
78
117
  return;
79
118
  }
80
- if (!viewer)
81
- return;
82
- console.log("[CesiumWidget:ViewerInit] Terrain setting changed:", model.get("enable_terrain"));
119
+ if (!viewer) return;
120
+ log(PREFIX, "Terrain setting changed:", model.get("enable_terrain"));
83
121
  if (model.get("enable_terrain")) {
84
122
  viewer.scene.setTerrain(Cesium.Terrain.fromWorldTerrain());
85
123
  } else {
@@ -87,47 +125,36 @@ function setupViewerListeners(viewer, model, container, Cesium) {
87
125
  }
88
126
  });
89
127
  model.on("change:enable_lighting", () => {
90
- if (isDestroyed)
91
- return;
92
- if (!viewer)
93
- return;
94
- console.log("[CesiumWidget:ViewerInit] Lighting setting changed:", model.get("enable_lighting"));
128
+ if (isDestroyed) return;
129
+ if (!viewer) return;
130
+ log(PREFIX, "Lighting setting changed:", model.get("enable_lighting"));
95
131
  viewer.scene.globe.enableLighting = model.get("enable_lighting");
96
132
  });
97
133
  model.on("change:height", () => {
98
- if (isDestroyed)
99
- return;
100
- if (!viewer)
101
- return;
102
- console.log("[CesiumWidget:ViewerInit] Height changed:", model.get("height"));
134
+ if (isDestroyed) return;
135
+ if (!viewer) return;
136
+ log(PREFIX, "Height changed:", model.get("height"));
103
137
  container.style.height = model.get("height");
104
138
  viewer.resize();
105
139
  });
106
140
  model.on("change:show_timeline", () => {
107
- if (isDestroyed)
108
- return;
109
- if (!viewer || !viewer.timeline)
110
- return;
111
- console.log("[CesiumWidget:ViewerInit] Timeline visibility changed:", model.get("show_timeline"));
141
+ if (isDestroyed) return;
142
+ if (!viewer || !viewer.timeline) return;
143
+ log(PREFIX, "Timeline visibility changed:", model.get("show_timeline"));
112
144
  viewer.timeline.container.style.visibility = model.get("show_timeline") ? "visible" : "hidden";
113
145
  });
114
146
  model.on("change:show_animation", () => {
115
- if (isDestroyed)
116
- return;
117
- if (!viewer || !viewer.animation)
118
- return;
119
- console.log("[CesiumWidget:ViewerInit] Animation visibility changed:", model.get("show_animation"));
147
+ if (isDestroyed) return;
148
+ if (!viewer || !viewer.animation) return;
149
+ log(PREFIX, "Animation visibility changed:", model.get("show_animation"));
120
150
  viewer.animation.container.style.visibility = model.get("show_animation") ? "visible" : "hidden";
121
151
  });
122
152
  model.on("change:atmosphere_settings", () => {
123
- if (isDestroyed)
124
- return;
125
- if (!viewer || !viewer.scene || !viewer.scene.atmosphere)
126
- return;
153
+ if (isDestroyed) return;
154
+ if (!viewer || !viewer.scene || !viewer.scene.atmosphere) return;
127
155
  const settings = model.get("atmosphere_settings");
128
- if (!settings || Object.keys(settings).length === 0)
129
- return;
130
- console.log("[CesiumWidget:ViewerInit] Atmosphere settings changed:", settings);
156
+ if (!settings || Object.keys(settings).length === 0) return;
157
+ log(PREFIX, "Atmosphere settings changed:", settings);
131
158
  const atmosphere = viewer.scene.atmosphere;
132
159
  if (settings.brightnessShift !== void 0) {
133
160
  atmosphere.brightnessShift = settings.brightnessShift;
@@ -166,14 +193,11 @@ function setupViewerListeners(viewer, model, container, Cesium) {
166
193
  }
167
194
  });
168
195
  model.on("change:sky_atmosphere_settings", () => {
169
- if (isDestroyed)
170
- return;
171
- if (!viewer || !viewer.scene || !viewer.scene.skyAtmosphere)
172
- return;
196
+ if (isDestroyed) return;
197
+ if (!viewer || !viewer.scene || !viewer.scene.skyAtmosphere) return;
173
198
  const settings = model.get("sky_atmosphere_settings");
174
- if (!settings || Object.keys(settings).length === 0)
175
- return;
176
- console.log("[CesiumWidget:ViewerInit] Sky atmosphere settings changed:", settings);
199
+ if (!settings || Object.keys(settings).length === 0) return;
200
+ log(PREFIX, "Sky atmosphere settings changed:", settings);
177
201
  const skyAtmosphere = viewer.scene.skyAtmosphere;
178
202
  if (settings.show !== void 0) {
179
203
  skyAtmosphere.show = settings.show;
@@ -218,14 +242,11 @@ function setupViewerListeners(viewer, model, container, Cesium) {
218
242
  }
219
243
  });
220
244
  model.on("change:skybox_settings", () => {
221
- if (isDestroyed)
222
- return;
223
- if (!viewer || !viewer.scene || !viewer.scene.skyBox)
224
- return;
245
+ if (isDestroyed) return;
246
+ if (!viewer || !viewer.scene || !viewer.scene.skyBox) return;
225
247
  const settings = model.get("skybox_settings");
226
- if (!settings || Object.keys(settings).length === 0)
227
- return;
228
- console.log("[CesiumWidget:ViewerInit] SkyBox settings changed:", settings);
248
+ if (!settings || Object.keys(settings).length === 0) return;
249
+ log(PREFIX, "SkyBox settings changed:", settings);
229
250
  const skyBox = viewer.scene.skyBox;
230
251
  if (settings.show !== void 0) {
231
252
  skyBox.show = settings.show;
@@ -249,9 +270,187 @@ function setupViewerListeners(viewer, model, container, Cesium) {
249
270
  }
250
271
  }
251
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
+ });
252
451
  function getCameraState() {
253
452
  if (!viewer || !viewer.camera || !viewer.camera.positionCartographic) {
254
- console.warn("[CesiumWidget:ViewerInit] Cannot get camera state - viewer or camera not available");
453
+ warn(PREFIX, "Cannot get camera state - viewer or camera not available");
255
454
  return null;
256
455
  }
257
456
  try {
@@ -264,37 +463,91 @@ function setupViewerListeners(viewer, model, container, Cesium) {
264
463
  pitch: Cesium.Math.toDegrees(viewer.camera.pitch),
265
464
  roll: Cesium.Math.toDegrees(viewer.camera.roll)
266
465
  };
267
- } catch (error) {
268
- console.warn("[CesiumWidget:ViewerInit] Error getting camera state:", error);
466
+ } catch (error2) {
467
+ warn(PREFIX, "Error getting camera state:", error2);
269
468
  return null;
270
469
  }
271
470
  }
272
471
  function getClockState() {
273
- if (!viewer || !viewer.clock)
274
- return null;
472
+ if (!viewer || !viewer.clock) return null;
275
473
  try {
276
474
  return {
277
475
  current_time: Cesium.JulianDate.toIso8601(viewer.clock.currentTime),
278
476
  multiplier: viewer.clock.multiplier,
279
477
  is_animating: viewer.clock.shouldAnimate
280
478
  };
281
- } catch (error) {
282
- console.warn("[CesiumWidget:ViewerInit] Error getting clock state:", error);
479
+ } catch (error2) {
480
+ warn(PREFIX, "Error getting clock state:", error2);
481
+ return null;
482
+ }
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);
283
536
  return null;
284
537
  }
285
538
  }
286
539
  function sendInteractionEvent(type, additionalData = {}) {
287
540
  if (isDestroyed) {
288
- console.log("[CesiumWidget:ViewerInit] Skipping interaction event - destroyed:", type);
541
+ log(PREFIX, "Skipping interaction event - destroyed:", type);
289
542
  return;
290
543
  }
291
544
  if (!viewer) {
292
- console.warn("[CesiumWidget:ViewerInit] Cannot send interaction event - viewer not available");
545
+ warn(PREFIX, "Cannot send interaction event - viewer not available");
293
546
  return;
294
547
  }
295
548
  const cameraState = getCameraState();
296
549
  if (!cameraState) {
297
- console.warn("[CesiumWidget:ViewerInit] Skipping interaction event - camera state not available");
550
+ warn(PREFIX, "Skipping interaction event - camera state not available");
298
551
  return;
299
552
  }
300
553
  const event = {
@@ -304,94 +557,43 @@ function setupViewerListeners(viewer, model, container, Cesium) {
304
557
  clock: getClockState(),
305
558
  ...additionalData
306
559
  };
307
- console.log("[CesiumWidget:ViewerInit] Interaction event:", type, event);
560
+ log(PREFIX, "Interaction event:", type, event);
308
561
  model.set("interaction_event", event);
309
562
  model.save_changes();
310
563
  }
311
564
  const camera = viewer.camera;
312
565
  camera.moveEnd.addEventListener(() => {
313
- if (isDestroyed || !viewer)
314
- return;
566
+ if (isDestroyed || !viewer) return;
315
567
  sendInteractionEvent("camera_move");
316
568
  });
317
569
  const scene = viewer.scene;
318
570
  const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
319
571
  handler.setInputAction((click) => {
320
- if (isDestroyed || !viewer || !viewer.scene || !viewer.camera)
321
- return;
572
+ if (isDestroyed || !viewer || !viewer.scene || !viewer.camera) return;
322
573
  const pickedData = {};
323
- try {
324
- const ray = viewer.camera.getPickRay(click.position);
325
- if (ray && viewer.scene.globe) {
326
- const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
327
- if (cartesian) {
328
- const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
329
- pickedData.picked_position = {
330
- latitude: Cesium.Math.toDegrees(cartographic.latitude),
331
- longitude: Cesium.Math.toDegrees(cartographic.longitude),
332
- altitude: cartographic.height
333
- };
334
- }
335
- }
336
- } catch (error) {
337
- console.warn("[CesiumWidget:ViewerInit] Error picking position:", error);
574
+ const pickedPosition = getPickedPosition(click);
575
+ if (pickedPosition) {
576
+ pickedData.picked_position = pickedPosition;
338
577
  }
339
- try {
340
- const pickedObject = viewer.scene.pick(click.position);
341
- if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
342
- const entity = pickedObject.id;
343
- pickedData.picked_entity = {
344
- id: entity.id,
345
- name: entity.name || null
346
- };
347
- if (entity.properties) {
348
- const props = {};
349
- const propertyNames = entity.properties.propertyNames;
350
- if (propertyNames && propertyNames.length > 0) {
351
- propertyNames.forEach((name) => {
352
- try {
353
- props[name] = entity.properties[name].getValue(viewer.clock.currentTime);
354
- } catch (e) {
355
- }
356
- });
357
- if (Object.keys(props).length > 0) {
358
- pickedData.picked_entity.properties = props;
359
- }
360
- }
361
- }
362
- }
363
- } catch (error) {
364
- console.warn("[CesiumWidget:ViewerInit] Error picking entity:", error);
578
+ const pickedEntity = getPickedEntity(click);
579
+ if (pickedEntity) {
580
+ pickedData.picked_entity = pickedEntity;
365
581
  }
366
582
  sendInteractionEvent("left_click", pickedData);
367
583
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
368
584
  handler.setInputAction((click) => {
369
- if (isDestroyed || !viewer || !viewer.scene || !viewer.camera)
370
- return;
585
+ if (isDestroyed || !viewer || !viewer.scene || !viewer.camera) return;
371
586
  const pickedData = {};
372
- try {
373
- const ray = viewer.camera.getPickRay(click.position);
374
- if (ray && viewer.scene.globe) {
375
- const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
376
- if (cartesian) {
377
- const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
378
- pickedData.picked_position = {
379
- latitude: Cesium.Math.toDegrees(cartographic.latitude),
380
- longitude: Cesium.Math.toDegrees(cartographic.longitude),
381
- altitude: cartographic.height
382
- };
383
- }
384
- }
385
- } catch (error) {
386
- console.warn("[CesiumWidget:ViewerInit] Error picking position:", error);
587
+ const pickedPosition = getPickedPosition(click);
588
+ if (pickedPosition) {
589
+ pickedData.picked_position = pickedPosition;
387
590
  }
388
591
  sendInteractionEvent("right_click", pickedData);
389
592
  }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
390
593
  if (viewer.timeline) {
391
594
  let timelineScrubbing = false;
392
595
  viewer.clock.onTick.addEventListener(() => {
393
- if (isDestroyed)
394
- return;
596
+ if (isDestroyed) return;
395
597
  if (viewer.timeline) {
396
598
  if (scrubTimeout) {
397
599
  clearTimeout(scrubTimeout);
@@ -402,28 +604,42 @@ function setupViewerListeners(viewer, model, container, Cesium) {
402
604
  timelineScrubbing = false;
403
605
  sendInteractionEvent("timeline_scrub");
404
606
  }
405
- }, 500);
607
+ }, CONSTANTS.TIMELINE_SCRUB_DEBOUNCE_MS);
406
608
  timelineScrubbing = true;
407
609
  }
408
610
  });
409
611
  }
410
- console.log("[CesiumWidget:ViewerInit] Viewer listeners setup complete");
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
+ };
411
627
  }
412
628
  function setupGeoJSONLoader(viewer, model, Cesium) {
413
- console.log("[CesiumWidget:ViewerInit] Setting up GeoJSON loader");
629
+ log(PREFIX, "Setting up GeoJSON loader");
414
630
  let geojsonDataSources = [];
415
631
  let isDestroyed = false;
416
632
  async function loadGeoJSONData(flyToData = true) {
417
633
  if (isDestroyed) {
418
- console.log("[CesiumWidget:ViewerInit] Skipping geojson_data load - destroyed");
634
+ log(PREFIX, "Skipping geojson_data load - destroyed");
419
635
  return;
420
636
  }
421
637
  if (!viewer || !viewer.dataSources) {
422
- console.warn("[CesiumWidget:ViewerInit] Cannot load GeoJSON - viewer or dataSources not available");
638
+ warn(PREFIX, "Cannot load GeoJSON - viewer or dataSources not available");
423
639
  return;
424
640
  }
425
641
  const geojsonDataArray = model.get("geojson_data");
426
- console.log("[CesiumWidget:ViewerInit] Loading GeoJSON data, count:", geojsonDataArray?.length || 0);
642
+ log(PREFIX, "Loading GeoJSON data, count:", geojsonDataArray?.length || 0);
427
643
  geojsonDataSources.forEach((dataSource) => {
428
644
  if (viewer && viewer.dataSources) {
429
645
  viewer.dataSources.remove(dataSource);
@@ -433,23 +649,23 @@ function setupGeoJSONLoader(viewer, model, Cesium) {
433
649
  if (geojsonDataArray && Array.isArray(geojsonDataArray)) {
434
650
  for (const geojsonData of geojsonDataArray) {
435
651
  try {
436
- console.log("[CesiumWidget:ViewerInit] Loading GeoJSON dataset...");
652
+ log(PREFIX, "Loading GeoJSON dataset...");
437
653
  const dataSource = await Cesium.GeoJsonDataSource.load(geojsonData, {
438
654
  stroke: Cesium.Color.HOTPINK,
439
- fill: Cesium.Color.PINK.withAlpha(0.5),
440
- strokeWidth: 3
655
+ fill: Cesium.Color.PINK.withAlpha(CONSTANTS.GEOJSON_FILL_ALPHA),
656
+ strokeWidth: CONSTANTS.GEOJSON_STROKE_WIDTH
441
657
  });
442
- if (viewer && viewer.dataSources) {
658
+ if (!isDestroyed && viewer && viewer.dataSources) {
443
659
  viewer.dataSources.add(dataSource);
444
660
  geojsonDataSources.push(dataSource);
445
- console.log("[CesiumWidget:ViewerInit] GeoJSON dataset loaded successfully");
661
+ log(PREFIX, "GeoJSON dataset loaded successfully");
446
662
  }
447
- } catch (error) {
448
- console.error("[CesiumWidget:ViewerInit] Error loading GeoJSON:", error);
663
+ } catch (error2) {
664
+ error2(PREFIX, "Error loading GeoJSON:", error2);
449
665
  }
450
666
  }
451
667
  if (flyToData && geojsonDataSources.length > 0 && viewer && viewer.flyTo) {
452
- console.log("[CesiumWidget:ViewerInit] Flying to GeoJSON data");
668
+ log(PREFIX, "Flying to GeoJSON data");
453
669
  viewer.flyTo(geojsonDataSources[0]);
454
670
  }
455
671
  }
@@ -457,12 +673,12 @@ function setupGeoJSONLoader(viewer, model, Cesium) {
457
673
  model.on("change:geojson_data", () => loadGeoJSONData(true));
458
674
  const initialData = model.get("geojson_data");
459
675
  if (initialData && Array.isArray(initialData) && initialData.length > 0) {
460
- console.log("[CesiumWidget:ViewerInit] Loading initial GeoJSON data...");
676
+ log(PREFIX, "Loading initial GeoJSON data...");
461
677
  loadGeoJSONData(true);
462
678
  }
463
679
  return {
464
680
  destroy: () => {
465
- console.log("[CesiumWidget:ViewerInit] Destroying GeoJSON loader");
681
+ log(PREFIX, "Destroying GeoJSON loader");
466
682
  isDestroyed = true;
467
683
  geojsonDataSources.forEach((dataSource) => {
468
684
  if (viewer) {
@@ -474,20 +690,21 @@ function setupGeoJSONLoader(viewer, model, Cesium) {
474
690
  };
475
691
  }
476
692
  function setupCZMLLoader(viewer, model, Cesium) {
477
- console.log("[CesiumWidget:ViewerInit] Setting up CZML loader");
693
+ log(PREFIX, "Setting up CZML loader");
478
694
  let czmlDataSources = [];
479
695
  let isDestroyed = false;
696
+ viewer._czmlDataSources = czmlDataSources;
480
697
  async function loadCZMLData(flyToData = true) {
481
698
  if (isDestroyed) {
482
- console.log("[CesiumWidget:ViewerInit] Skipping czml_data load - destroyed");
699
+ log(PREFIX, "Skipping czml_data load - destroyed");
483
700
  return;
484
701
  }
485
702
  if (!viewer || !viewer.dataSources) {
486
- console.warn("[CesiumWidget:ViewerInit] Cannot load CZML - viewer or dataSources not available");
703
+ warn(PREFIX, "Cannot load CZML - viewer or dataSources not available");
487
704
  return;
488
705
  }
489
706
  const czmlDataArray = model.get("czml_data");
490
- console.log("[CesiumWidget:ViewerInit] Loading CZML data, count:", czmlDataArray?.length || 0);
707
+ log(PREFIX, "Loading CZML data, count:", czmlDataArray?.length || 0);
491
708
  czmlDataSources.forEach((dataSource) => {
492
709
  if (viewer && viewer.dataSources) {
493
710
  viewer.dataSources.remove(dataSource);
@@ -498,22 +715,22 @@ function setupCZMLLoader(viewer, model, Cesium) {
498
715
  for (const czmlData of czmlDataArray) {
499
716
  if (Array.isArray(czmlData) && czmlData.length > 0) {
500
717
  try {
501
- console.log("[CesiumWidget:ViewerInit] Loading CZML document with", czmlData.length, "packets...");
718
+ log(PREFIX, "Loading CZML document with", czmlData.length, "packets...");
502
719
  const dataSource = await Cesium.CzmlDataSource.load(czmlData);
503
- if (viewer && viewer.dataSources) {
720
+ if (!isDestroyed && viewer && viewer.dataSources) {
504
721
  viewer.dataSources.add(dataSource);
505
722
  czmlDataSources.push(dataSource);
506
- console.log("[CesiumWidget:ViewerInit] CZML document loaded successfully, entities:", dataSource.entities.values.length);
723
+ log(PREFIX, "CZML document loaded successfully, entities:", dataSource.entities.values.length);
507
724
  }
508
- } catch (error) {
509
- console.error("[CesiumWidget:ViewerInit] Error loading CZML:", error);
725
+ } catch (error2) {
726
+ error2(PREFIX, "Error loading CZML:", error2);
510
727
  }
511
728
  } else {
512
- console.warn("[CesiumWidget:ViewerInit] Skipping invalid CZML data (not an array or empty):", czmlData);
729
+ warn(PREFIX, "Skipping invalid CZML data (not an array or empty):", czmlData);
513
730
  }
514
731
  }
515
732
  if (flyToData && czmlDataSources.length > 0 && viewer && viewer.flyTo) {
516
- console.log("[CesiumWidget:ViewerInit] Flying to CZML data");
733
+ log(PREFIX, "Flying to CZML data");
517
734
  viewer.flyTo(czmlDataSources[0]);
518
735
  }
519
736
  }
@@ -521,12 +738,12 @@ function setupCZMLLoader(viewer, model, Cesium) {
521
738
  model.on("change:czml_data", () => loadCZMLData(true));
522
739
  const initialData = model.get("czml_data");
523
740
  if (initialData && Array.isArray(initialData) && initialData.length > 0) {
524
- console.log("[CesiumWidget:ViewerInit] Loading initial CZML data...");
741
+ log(PREFIX, "Loading initial CZML data...");
525
742
  loadCZMLData(true);
526
743
  }
527
744
  return {
528
745
  destroy: () => {
529
- console.log("[CesiumWidget:ViewerInit] Destroying CZML loader");
746
+ log(PREFIX, "Destroying CZML loader");
530
747
  isDestroyed = true;
531
748
  czmlDataSources.forEach((dataSource) => {
532
749
  if (viewer) {
@@ -537,48 +754,171 @@ function setupCZMLLoader(viewer, model, Cesium) {
537
754
  }
538
755
  };
539
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
+ }
540
810
 
541
811
  // src/cesiumjs_anywidget/js/camera-sync.js
812
+ var PREFIX2 = "CameraSync";
542
813
  function initializeCameraSync(viewer, model) {
543
814
  const Cesium = window.Cesium;
544
815
  let cameraUpdateTimeout = null;
816
+ let modelUpdateTimeout = null;
545
817
  let isDestroyed = false;
546
- console.log("[CesiumWidget:CameraSync] Initializing camera synchronization");
547
- function updateCameraFromModel() {
818
+ let syncEnabled = model.get("camera_sync_enabled") || false;
819
+ log(PREFIX2, "Initializing camera synchronization, sync enabled:", syncEnabled);
820
+ if (!Cesium) {
821
+ error(PREFIX2, "Cesium global not available");
822
+ throw new Error("Cesium is not loaded");
823
+ }
824
+ const handleSyncEnabledChange = () => {
825
+ syncEnabled = model.get("camera_sync_enabled");
826
+ log(PREFIX2, "Camera sync enabled changed:", syncEnabled);
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() {
548
860
  if (isDestroyed) {
549
- console.log("[CesiumWidget:CameraSync] Skipping updateCameraFromModel - module destroyed");
861
+ log(PREFIX2, "Skipping updateCameraFromModel - module destroyed");
550
862
  return;
551
863
  }
552
864
  if (!viewer) {
553
- console.warn("[CesiumWidget:CameraSync] updateCameraFromModel called but viewer is null");
865
+ warn(PREFIX2, "updateCameraFromModel called but viewer is null");
554
866
  return;
555
867
  }
556
868
  const lat = model.get("latitude");
557
869
  const lon = model.get("longitude");
558
870
  const alt = model.get("altitude");
559
- const heading = Cesium.Math.toRadians(model.get("heading"));
560
- const pitch = Cesium.Math.toRadians(model.get("pitch"));
561
- const roll = Cesium.Math.toRadians(model.get("roll"));
562
- console.log("[CesiumWidget:CameraSync] Updating camera from model:", { lat, lon, alt });
563
- viewer.camera.setView({
564
- destination: Cesium.Cartesian3.fromDegrees(lon, lat, alt),
565
- orientation: { heading, pitch, roll }
566
- });
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
+ }
880
+ log(PREFIX2, "Updating camera from model:", { lat, lon, alt });
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);
567
903
  }
568
904
  function updateModelFromCamera() {
569
905
  if (isDestroyed) {
570
- console.log("[CesiumWidget:CameraSync] Skipping updateModelFromCamera - module destroyed");
906
+ log(PREFIX2, "Skipping updateModelFromCamera - module destroyed");
907
+ return;
908
+ }
909
+ if (!syncEnabled) {
910
+ log(PREFIX2, "Skipping updateModelFromCamera - sync disabled");
571
911
  return;
572
912
  }
573
913
  if (!viewer) {
574
- console.warn("[CesiumWidget:CameraSync] updateModelFromCamera called but viewer is null");
914
+ warn(PREFIX2, "updateModelFromCamera called but viewer is null");
575
915
  return;
576
916
  }
577
917
  const position = viewer.camera.positionCartographic;
578
918
  const heading = viewer.camera.heading;
579
919
  const pitch = viewer.camera.pitch;
580
920
  const roll = viewer.camera.roll;
581
- console.log("[CesiumWidget:CameraSync] Updating model from camera:", {
921
+ log(PREFIX2, "Updating model from camera:", {
582
922
  lat: Cesium.Math.toDegrees(position.latitude),
583
923
  lon: Cesium.Math.toDegrees(position.longitude),
584
924
  alt: position.height
@@ -593,19 +933,22 @@ function initializeCameraSync(viewer, model) {
593
933
  }
594
934
  function handleCameraChanged() {
595
935
  if (isDestroyed) {
596
- console.log("[CesiumWidget:CameraSync] Skipping handleCameraChanged - module destroyed");
936
+ log(PREFIX2, "Skipping handleCameraChanged - module destroyed");
937
+ return;
938
+ }
939
+ if (!syncEnabled) {
597
940
  return;
598
941
  }
599
942
  if (cameraUpdateTimeout) {
600
943
  clearTimeout(cameraUpdateTimeout);
601
944
  }
602
945
  cameraUpdateTimeout = setTimeout(() => {
603
- if (!isDestroyed) {
946
+ if (!isDestroyed && syncEnabled) {
604
947
  updateModelFromCamera();
605
948
  }
606
949
  }, 500);
607
950
  }
608
- updateCameraFromModel();
951
+ updateCameraFromModelImmediate();
609
952
  viewer.camera.changed.addEventListener(handleCameraChanged);
610
953
  model.on("change:latitude", updateCameraFromModel);
611
954
  model.on("change:longitude", updateCameraFromModel);
@@ -613,19 +956,22 @@ function initializeCameraSync(viewer, model) {
613
956
  model.on("change:heading", updateCameraFromModel);
614
957
  model.on("change:pitch", updateCameraFromModel);
615
958
  model.on("change:roll", updateCameraFromModel);
616
- model.on("change:camera_command", () => {
959
+ const handleCameraCommand = () => {
617
960
  if (isDestroyed) {
618
- console.log("[CesiumWidget:CameraSync] Skipping camera_command - module destroyed");
961
+ log(PREFIX2, "Skipping camera_command - module destroyed");
619
962
  return;
620
963
  }
621
964
  const command = model.get("camera_command");
622
- if (!command || !command.command || !command.timestamp)
623
- return;
965
+ if (!command || !command.command || !command.timestamp) return;
624
966
  const cmd = command.command;
625
- console.log("[CesiumWidget:CameraSync] Executing camera command:", cmd, command);
967
+ log(PREFIX2, "Executing camera command:", cmd, command);
626
968
  try {
627
969
  switch (cmd) {
628
970
  case "flyTo":
971
+ if (!validateCameraParameters(command.latitude, command.longitude, command.altitude)) {
972
+ error(PREFIX2, "Invalid parameters for flyTo command");
973
+ return;
974
+ }
629
975
  viewer.camera.flyTo({
630
976
  destination: Cesium.Cartesian3.fromDegrees(
631
977
  command.longitude,
@@ -641,6 +987,10 @@ function initializeCameraSync(viewer, model) {
641
987
  });
642
988
  break;
643
989
  case "setView":
990
+ if (!validateCameraParameters(command.latitude, command.longitude, command.altitude)) {
991
+ error(PREFIX2, "Invalid parameters for setView command");
992
+ return;
993
+ }
644
994
  viewer.camera.setView({
645
995
  destination: Cesium.Cartesian3.fromDegrees(
646
996
  command.longitude,
@@ -653,8 +1003,35 @@ function initializeCameraSync(viewer, model) {
653
1003
  roll: Cesium.Math.toRadians(command.roll || 0)
654
1004
  }
655
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
+ }
656
1029
  break;
657
1030
  case "lookAt":
1031
+ if (!validateCameraParameters(command.targetLatitude, command.targetLongitude, command.targetAltitude || 0)) {
1032
+ error(PREFIX2, "Invalid parameters for lookAt command");
1033
+ return;
1034
+ }
658
1035
  const target = Cesium.Cartesian3.fromDegrees(
659
1036
  command.targetLongitude,
660
1037
  command.targetLatitude,
@@ -705,31 +1082,252 @@ function initializeCameraSync(viewer, model) {
705
1082
  viewer.camera.zoomOut(command.distance || 100);
706
1083
  break;
707
1084
  default:
708
- console.warn(`Unknown camera command: ${cmd}`);
1085
+ warn(PREFIX2, `Unknown camera command: ${cmd}`);
709
1086
  }
710
- } catch (error) {
711
- console.error(`Error executing camera command ${cmd}:`, error);
1087
+ } catch (err) {
1088
+ error(PREFIX2, `Error executing camera command ${cmd}:`, err);
712
1089
  }
713
- });
1090
+ };
1091
+ model.on("change:camera_command", handleCameraCommand);
714
1092
  return {
715
1093
  updateCameraFromModel,
1094
+ updateCameraFromModelImmediate,
716
1095
  updateModelFromCamera,
717
1096
  destroy: () => {
718
- console.log("[CesiumWidget:CameraSync] Destroying camera sync module");
1097
+ log(PREFIX2, "Destroying camera sync module");
719
1098
  isDestroyed = true;
720
1099
  if (cameraUpdateTimeout) {
721
1100
  clearTimeout(cameraUpdateTimeout);
722
1101
  cameraUpdateTimeout = null;
723
1102
  }
724
- viewer.camera.changed.removeEventListener(handleCameraChanged);
725
- console.log("[CesiumWidget:CameraSync] Camera sync module destroyed");
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);
1118
+ log(PREFIX2, "Camera sync module destroyed");
726
1119
  }
727
1120
  };
728
1121
  }
729
1122
 
730
1123
  // src/cesiumjs_anywidget/js/measurement-tools.js
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
+ }
731
1329
  function initializeMeasurementTools(viewer, model, container) {
732
- console.log("[CesiumWidget:MeasurementTools] Initializing measurement tools");
1330
+ log(PREFIX3, "Initializing measurement tools");
733
1331
  const Cesium = window.Cesium;
734
1332
  let measurementHandler = null;
735
1333
  let editHandler = null;
@@ -749,121 +1347,106 @@ function initializeMeasurementTools(viewer, model, container) {
749
1347
  selectedEntity: null,
750
1348
  dragging: false,
751
1349
  measurementIndex: null,
752
- pointIndex: null
1350
+ pointIndex: null,
1351
+ addPointMode: false
753
1352
  };
754
- let completedMeasurements = [];
1353
+ const cleanupFunctions = [];
755
1354
  const toolbarDiv = document.createElement("div");
756
1355
  toolbarDiv.style.cssText = `
757
1356
  position: absolute;
758
1357
  top: 10px;
759
1358
  left: 10px;
760
1359
  background: rgba(42, 42, 42, 0.9);
761
- padding: 10px;
1360
+ padding: 0;
762
1361
  border-radius: 5px;
763
1362
  z-index: 1000;
764
1363
  display: flex;
765
1364
  flex-direction: column;
766
- gap: 5px;
1365
+ gap: 0;
767
1366
  `;
768
1367
  container.appendChild(toolbarDiv);
769
- function createMeasurementButton(text, mode) {
770
- const btn = document.createElement("button");
771
- btn.textContent = text;
772
- btn.style.cssText = `
773
- padding: 8px 12px;
774
- background: #3498db;
775
- color: white;
776
- border: none;
777
- border-radius: 3px;
778
- cursor: pointer;
779
- font-size: 12px;
780
- transition: background 0.2s;
781
- `;
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;
782
1392
  btn.onmouseover = () => {
783
- btn.style.background = "#2980b9";
1393
+ btn.style.background = hoverColor;
784
1394
  };
785
1395
  btn.onmouseout = () => {
786
- btn.style.background = measurementState.mode === mode ? "#e74c3c" : "#3498db";
1396
+ btn.style.background = getActiveState() ? btn.dataset.activeColor : baseColor;
787
1397
  };
1398
+ }
1399
+ function createMeasurementButton(text, mode) {
1400
+ const btn = createStyledButton(text, "#3498db");
1401
+ setupButtonHover(btn, "#2980b9", () => measurementState.mode === mode);
788
1402
  btn.onclick = () => {
789
- if (measurementState.mode === mode) {
790
- model.set("measurement_mode", "");
791
- model.save_changes();
792
- } else {
793
- model.set("measurement_mode", mode);
794
- model.save_changes();
795
- }
1403
+ model.set("measurement_mode", measurementState.mode === mode ? "" : mode);
1404
+ model.save_changes();
796
1405
  };
797
1406
  return btn;
798
1407
  }
799
- const distanceBtn = createMeasurementButton("\u{1F4CF} Distance", "distance");
800
- const multiDistanceBtn = createMeasurementButton("\u{1F4D0} Multi Distance", "multi-distance");
801
- const heightBtn = createMeasurementButton("\u{1F4CA} Height", "height");
802
- const areaBtn = createMeasurementButton("\u2B1B Area", "area");
803
- const clearBtn = document.createElement("button");
804
- clearBtn.textContent = "\u{1F5D1}\uFE0F Clear";
805
- clearBtn.style.cssText = `
806
- padding: 8px 12px;
807
- background: #e74c3c;
808
- color: white;
809
- border: none;
810
- border-radius: 3px;
811
- cursor: pointer;
812
- font-size: 12px;
813
- transition: background 0.2s;
814
- `;
815
- clearBtn.onmouseover = () => {
816
- clearBtn.style.background = "#c0392b";
817
- };
818
- clearBtn.onmouseout = () => {
819
- 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")
820
1413
  };
1414
+ const clearBtn = createStyledButton("\u{1F5D1}\uFE0F Clear", "#e74c3c");
1415
+ setupButtonHover(clearBtn, "#c0392b");
821
1416
  clearBtn.onclick = () => {
822
1417
  clearAllMeasurements();
823
1418
  model.set("measurement_mode", "");
824
1419
  model.set("measurement_results", []);
825
1420
  model.save_changes();
826
1421
  };
827
- toolbarDiv.appendChild(distanceBtn);
828
- toolbarDiv.appendChild(multiDistanceBtn);
829
- toolbarDiv.appendChild(heightBtn);
830
- toolbarDiv.appendChild(areaBtn);
831
- toolbarDiv.appendChild(clearBtn);
832
- const editBtn = document.createElement("button");
833
- editBtn.textContent = "\u270F\uFE0F Edit Points";
834
- editBtn.style.cssText = `
835
- padding: 8px 12px;
836
- background: #9b59b6;
837
- color: white;
838
- border: none;
839
- border-radius: 3px;
840
- cursor: pointer;
841
- font-size: 12px;
842
- transition: background 0.2s;
843
- `;
844
- editBtn.onmouseover = () => {
845
- editBtn.style.background = "#8e44ad";
846
- };
847
- editBtn.onmouseout = () => {
848
- editBtn.style.background = editState.enabled ? "#e74c3c" : "#9b59b6";
849
- };
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);
850
1426
  editBtn.onclick = () => {
851
1427
  editState.enabled = !editState.enabled;
852
1428
  editBtn.style.background = editState.enabled ? "#e74c3c" : "#9b59b6";
853
- if (editState.enabled) {
854
- enableEditMode();
855
- } else {
856
- disableEditMode();
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();
857
1440
  }
858
1441
  };
859
- toolbarDiv.appendChild(editBtn);
1442
+ toolbarContent.appendChild(addPointBtn);
860
1443
  const editorPanel = document.createElement("div");
861
1444
  editorPanel.style.cssText = `
862
1445
  position: absolute;
863
1446
  top: 10px;
864
1447
  right: 10px;
865
1448
  background: rgba(42, 42, 42, 0.95);
866
- padding: 15px;
1449
+ padding: 0;
867
1450
  border-radius: 5px;
868
1451
  z-index: 1000;
869
1452
  display: none;
@@ -872,49 +1455,108 @@ function initializeMeasurementTools(viewer, model, container) {
872
1455
  font-size: 12px;
873
1456
  min-width: 250px;
874
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);
875
1473
  container.appendChild(editorPanel);
1474
+ cleanupFunctions.push(makeDraggable(editorPanel, editorHeader));
876
1475
  const measurementsListPanel = document.createElement("div");
877
1476
  measurementsListPanel.style.cssText = `
878
1477
  position: absolute;
879
1478
  bottom: 10px;
880
1479
  right: 10px;
881
1480
  background: rgba(42, 42, 42, 0.95);
882
- padding: 15px;
1481
+ padding: 0;
883
1482
  border-radius: 5px;
884
1483
  z-index: 1000;
885
1484
  color: white;
886
1485
  font-family: sans-serif;
887
1486
  font-size: 12px;
888
- max-width: 350px;
889
- max-height: 400px;
890
- overflow-y: auto;
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;
1497
+ `;
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;
891
1509
  `;
892
- measurementsListPanel.innerHTML = `
893
- <div style="font-weight: bold; border-bottom: 1px solid #555; padding-bottom: 8px; margin-bottom: 10px;">
894
- Measurements
895
- </div>
896
- <div id="measurements-list-content"></div>
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;
897
1526
  `;
1527
+ measurementsContent.innerHTML = `<div id="measurements-list-content" style="width: 100%; box-sizing: border-box;"></div>`;
1528
+ measurementsListPanel.appendChild(measurementsContent);
898
1529
  container.appendChild(measurementsListPanel);
1530
+ cleanupFunctions.push(makeDraggable(measurementsListPanel, measurementsHeader));
1531
+ cleanupFunctions.push(makeResizable(measurementsListPanel));
899
1532
  function getPosition(screenPosition) {
900
- const pickedObject = viewer.scene.pick(screenPosition);
901
- if (viewer.scene.pickPositionSupported && Cesium.defined(pickedObject)) {
902
- const cartesian = viewer.scene.pickPosition(screenPosition);
903
- if (Cesium.defined(cartesian)) {
904
- return cartesian;
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
+ }
1540
+ }
1541
+ const ray = viewer.camera.getPickRay(screenPosition);
1542
+ if (!ray) {
1543
+ warn(PREFIX3, "Unable to get pick ray from camera");
1544
+ return null;
905
1545
  }
1546
+ return viewer.scene.globe.pick(ray, viewer.scene);
1547
+ } catch (err) {
1548
+ error(PREFIX3, "Error getting position:", err);
1549
+ return null;
906
1550
  }
907
- const ray = viewer.camera.getPickRay(screenPosition);
908
- return viewer.scene.globe.pick(ray, viewer.scene);
909
1551
  }
910
1552
  function addMarker(position, color = Cesium.Color.RED) {
911
1553
  const marker = viewer.entities.add({
912
1554
  position,
913
1555
  point: {
914
- pixelSize: 10,
1556
+ pixelSize: CONSTANTS2.MARKER_SIZE,
915
1557
  color,
916
1558
  outlineColor: Cesium.Color.WHITE,
917
- outlineWidth: 2,
1559
+ outlineWidth: CONSTANTS2.MARKER_OUTLINE_WIDTH,
918
1560
  disableDepthTestDistance: Number.POSITIVE_INFINITY
919
1561
  }
920
1562
  });
@@ -931,7 +1573,7 @@ function initializeMeasurementTools(viewer, model, container) {
931
1573
  style: Cesium.LabelStyle.FILL_AND_OUTLINE,
932
1574
  outlineWidth: 2,
933
1575
  outlineColor: Cesium.Color.BLACK,
934
- pixelOffset: new Cesium.Cartesian2(0, -20),
1576
+ pixelOffset: new Cesium.Cartesian2(0, CONSTANTS2.LABEL_PIXEL_OFFSET_Y),
935
1577
  showBackground: true,
936
1578
  backgroundColor: Cesium.Color.fromAlpha(Cesium.Color.BLACK, 0.7),
937
1579
  disableDepthTestDistance: Number.POSITIVE_INFINITY
@@ -954,8 +1596,29 @@ function initializeMeasurementTools(viewer, model, container) {
954
1596
  alt: cartographic.height
955
1597
  };
956
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
+ }
957
1620
  function clearAllMeasurements() {
958
- console.log("[CesiumWidget:MeasurementTools] Clearing all measurements");
1621
+ log(PREFIX3, "Clearing all measurements");
959
1622
  measurementState.entities.forEach((e) => viewer.entities.remove(e));
960
1623
  measurementState.labels.forEach((l) => viewer.entities.remove(l));
961
1624
  measurementState.polylines.forEach((p) => viewer.entities.remove(p));
@@ -990,19 +1653,24 @@ function initializeMeasurementTools(viewer, model, container) {
990
1653
  model.set("measurement_mode", "");
991
1654
  model.save_changes();
992
1655
  }
1656
+ addPointBtn.style.display = "block";
993
1657
  measurementState.entities.forEach((entity) => {
994
1658
  if (entity.point) {
995
- entity.point.pixelSize = 12;
996
- entity.point.outlineWidth = 3;
1659
+ entity.point.pixelSize = CONSTANTS2.MARKER_SIZE_HOVER;
1660
+ entity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH_HOVER;
997
1661
  }
998
1662
  });
999
1663
  editHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
1000
1664
  editHandler.setInputAction((click) => {
1001
- const pickedObject = viewer.scene.pick(click.position);
1002
- if (Cesium.defined(pickedObject) && pickedObject.id && pickedObject.id.point) {
1003
- selectPoint(pickedObject.id, click.position);
1665
+ if (editState.addPointMode) {
1666
+ handleAddPointClick(click);
1004
1667
  } else {
1005
- deselectPoint();
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
+ }
1006
1674
  }
1007
1675
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
1008
1676
  editHandler.setInputAction((movement) => {
@@ -1014,7 +1682,7 @@ function initializeMeasurementTools(viewer, model, container) {
1014
1682
  }
1015
1683
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
1016
1684
  editHandler.setInputAction(() => {
1017
- if (editState.selectedEntity) {
1685
+ if (editState.selectedEntity && !editState.addPointMode) {
1018
1686
  editState.dragging = true;
1019
1687
  viewer.scene.screenSpaceCameraController.enableRotate = false;
1020
1688
  }
@@ -1032,11 +1700,14 @@ function initializeMeasurementTools(viewer, model, container) {
1032
1700
  editHandler.destroy();
1033
1701
  editHandler = null;
1034
1702
  }
1703
+ addPointBtn.style.display = "none";
1704
+ editState.addPointMode = false;
1705
+ addPointBtn.style.background = "#16a085";
1035
1706
  deselectPoint();
1036
1707
  measurementState.entities.forEach((entity) => {
1037
1708
  if (entity.point) {
1038
- entity.point.pixelSize = 10;
1039
- entity.point.outlineWidth = 2;
1709
+ entity.point.pixelSize = CONSTANTS2.MARKER_SIZE;
1710
+ entity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH;
1040
1711
  }
1041
1712
  });
1042
1713
  viewer.scene.screenSpaceCameraController.enableRotate = true;
@@ -1061,21 +1732,20 @@ function initializeMeasurementTools(viewer, model, container) {
1061
1732
  break;
1062
1733
  }
1063
1734
  }
1064
- if (measurementIndex === -1)
1065
- return;
1735
+ if (measurementIndex === -1) return;
1066
1736
  editState.selectedEntity = entity;
1067
1737
  editState.measurementIndex = measurementIndex;
1068
1738
  editState.pointIndex = pointIndex;
1069
1739
  editState.selectedPoint = entity.position.getValue(Cesium.JulianDate.now());
1070
- entity.point.pixelSize = 15;
1071
- entity.point.outlineWidth = 4;
1740
+ entity.point.pixelSize = CONSTANTS2.MARKER_SIZE_SELECTED;
1741
+ entity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH_SELECTED;
1072
1742
  entity.point.outlineColor = Cesium.Color.YELLOW;
1073
1743
  showCoordinateEditor(results[measurementIndex], pointIndex);
1074
1744
  }
1075
1745
  function deselectPoint() {
1076
1746
  if (editState.selectedEntity && editState.selectedEntity.point) {
1077
- editState.selectedEntity.point.pixelSize = 12;
1078
- editState.selectedEntity.point.outlineWidth = 3;
1747
+ editState.selectedEntity.point.pixelSize = CONSTANTS2.MARKER_SIZE_HOVER;
1748
+ editState.selectedEntity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH_HOVER;
1079
1749
  editState.selectedEntity.point.outlineColor = Cesium.Color.WHITE;
1080
1750
  }
1081
1751
  editState.selectedEntity = null;
@@ -1087,10 +1757,8 @@ function initializeMeasurementTools(viewer, model, container) {
1087
1757
  }
1088
1758
  function showCoordinateEditor(measurement, pointIndex) {
1089
1759
  const point = measurement.points[pointIndex];
1090
- editorPanel.innerHTML = `
1091
- <div style="margin-bottom: 10px; font-weight: bold; border-bottom: 1px solid #555; padding-bottom: 5px;">
1092
- Edit Point ${pointIndex + 1} (${measurement.type})
1093
- </div>
1760
+ editorHeader.textContent = `\u22EE\u22EE Edit Point ${pointIndex + 1} (${measurement.type})`;
1761
+ editorContent.innerHTML = `
1094
1762
  <div style="margin-bottom: 8px;">
1095
1763
  <label style="display: block; margin-bottom: 3px;">Longitude (\xB0):</label>
1096
1764
  <input type="number" id="edit-lon" value="${point.lon.toFixed(6)}" step="0.000001"
@@ -1120,12 +1788,12 @@ function initializeMeasurementTools(viewer, model, container) {
1120
1788
  const editLatInput = document.getElementById("edit-lat");
1121
1789
  const editAltInput = document.getElementById("edit-alt");
1122
1790
  if (!applyBtn || !closeBtn || !editLonInput || !editLatInput || !editAltInput) {
1123
- console.warn("[CesiumWidget] Editor panel input elements not found in DOM");
1791
+ warn(PREFIX3, "Editor panel input elements not found in DOM");
1124
1792
  }
1125
1793
  if (applyBtn) {
1126
1794
  applyBtn.onclick = () => {
1127
1795
  if (!editLonInput || !editLatInput || !editAltInput) {
1128
- console.warn("[CesiumWidget] Editor input fields not available");
1796
+ warn(PREFIX3, "Editor input fields not available");
1129
1797
  return;
1130
1798
  }
1131
1799
  const lon = parseFloat(editLonInput.value);
@@ -1153,16 +1821,14 @@ function initializeMeasurementTools(viewer, model, container) {
1153
1821
  });
1154
1822
  }
1155
1823
  function updatePointPosition(newPosition) {
1156
- if (!editState.selectedEntity)
1157
- return;
1824
+ if (!editState.selectedEntity) return;
1158
1825
  editState.selectedEntity.position = newPosition;
1159
1826
  editState.selectedPoint = newPosition;
1160
1827
  updateMeasurementVisuals();
1161
1828
  }
1162
1829
  function updateMeasurementVisuals() {
1163
1830
  const results = model.get("measurement_results") || [];
1164
- if (editState.measurementIndex === null)
1165
- return;
1831
+ if (editState.measurementIndex === null) return;
1166
1832
  const measurement = results[editState.measurementIndex];
1167
1833
  let entityStartIndex = 0;
1168
1834
  for (let i = 0; i < editState.measurementIndex; i++) {
@@ -1183,10 +1849,10 @@ function initializeMeasurementTools(viewer, model, container) {
1183
1849
  const newPolygon = viewer.entities.add({
1184
1850
  polygon: {
1185
1851
  hierarchy: new Cesium.PolygonHierarchy(positions),
1186
- material: Cesium.Color.ORANGE.withAlpha(0.3),
1852
+ material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
1187
1853
  outline: true,
1188
1854
  outlineColor: Cesium.Color.ORANGE,
1189
- outlineWidth: 2
1855
+ outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
1190
1856
  }
1191
1857
  });
1192
1858
  measurementState.polylines[polylineStartIndex] = newPolygon;
@@ -1211,10 +1877,9 @@ function initializeMeasurementTools(viewer, model, container) {
1211
1877
  if (type === "distance") {
1212
1878
  const distance = Cesium.Cartesian3.distance(positions[0], positions[1]);
1213
1879
  const midpoint = Cesium.Cartesian3.midpoint(positions[0], positions[1], new Cesium.Cartesian3());
1214
- const distanceText = distance >= 1e3 ? `${(distance / 1e3).toFixed(2)} km` : `${distance.toFixed(2)} m`;
1215
1880
  if (measurementState.labels[labelStartIndex]) {
1216
1881
  measurementState.labels[labelStartIndex].position = midpoint;
1217
- measurementState.labels[labelStartIndex].label.text = distanceText;
1882
+ measurementState.labels[labelStartIndex].label.text = formatValue(distance);
1218
1883
  }
1219
1884
  } else if (type === "height") {
1220
1885
  const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
@@ -1222,16 +1887,14 @@ function initializeMeasurementTools(viewer, model, container) {
1222
1887
  const verticalDistance = Math.abs(carto1.height - carto0.height);
1223
1888
  const midHeight = (carto0.height + carto1.height) / 2;
1224
1889
  const labelPos = Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, midHeight);
1225
- const heightText = verticalDistance >= 1e3 ? `${(verticalDistance / 1e3).toFixed(2)} km` : `${verticalDistance.toFixed(2)} m`;
1226
1890
  if (measurementState.labels[labelStartIndex]) {
1227
1891
  measurementState.labels[labelStartIndex].position = labelPos;
1228
- measurementState.labels[labelStartIndex].label.text = heightText;
1892
+ measurementState.labels[labelStartIndex].label.text = formatValue(verticalDistance);
1229
1893
  }
1230
1894
  }
1231
1895
  }
1232
1896
  function finalizeMeasurementUpdate() {
1233
- if (editState.measurementIndex === null || editState.pointIndex === null)
1234
- return;
1897
+ if (editState.measurementIndex === null || editState.pointIndex === null) return;
1235
1898
  const results = model.get("measurement_results") || [];
1236
1899
  const measurement = results[editState.measurementIndex];
1237
1900
  const cartographic = Cesium.Cartographic.fromCartesian(editState.selectedPoint);
@@ -1264,33 +1927,7 @@ function initializeMeasurementTools(viewer, model, container) {
1264
1927
  }
1265
1928
  measurement.value = totalDistance;
1266
1929
  } else if (measurement.type === "area") {
1267
- const polygonHierarchy = new Cesium.PolygonHierarchy(positions);
1268
- const geometry = Cesium.PolygonGeometry.createGeometry(
1269
- new Cesium.PolygonGeometry({
1270
- polygonHierarchy,
1271
- perPositionHeight: false,
1272
- arcType: Cesium.ArcType.GEODESIC
1273
- })
1274
- );
1275
- let area = 0;
1276
- if (geometry) {
1277
- const positionsArray = geometry.attributes.position.values;
1278
- const indices = geometry.indices;
1279
- for (let i = 0; i < indices.length; i += 3) {
1280
- const i0 = indices[i] * 3;
1281
- const i1 = indices[i + 1] * 3;
1282
- const i2 = indices[i + 2] * 3;
1283
- const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
1284
- const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
1285
- const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
1286
- const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
1287
- const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
1288
- const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
1289
- const triangleArea = Cesium.Cartesian3.magnitude(crossProduct) / 2;
1290
- area += triangleArea;
1291
- }
1292
- }
1293
- measurement.value = area;
1930
+ measurement.value = calculateGeodesicArea(positions);
1294
1931
  }
1295
1932
  const newResults = [...results];
1296
1933
  model.set("measurement_results", newResults);
@@ -1300,12 +1937,175 @@ function initializeMeasurementTools(viewer, model, container) {
1300
1937
  showCoordinateEditor(measurement, editState.pointIndex);
1301
1938
  }
1302
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
+ }
1303
2103
  function updateMeasurementsList() {
1304
2104
  const results = model.get("measurement_results") || [];
1305
- console.log("[CesiumWidget:MeasurementTools] Updating measurements list, count:", results.length);
2105
+ log(PREFIX3, "Updating measurements list, count:", results.length);
1306
2106
  const listContent = document.getElementById("measurements-list-content");
1307
2107
  if (!listContent) {
1308
- console.warn("[CesiumWidget] Measurements list content element not found in DOM");
2108
+ warn(PREFIX3, "Measurements list content element not found in DOM");
1309
2109
  return;
1310
2110
  }
1311
2111
  if (results.length === 0) {
@@ -1323,6 +2123,11 @@ function initializeMeasurementTools(viewer, model, container) {
1323
2123
  cursor: pointer;
1324
2124
  transition: background 0.2s;
1325
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;
1326
2131
  `;
1327
2132
  measurementDiv.onmouseover = () => {
1328
2133
  measurementDiv.style.background = "rgba(255, 255, 255, 0.15)";
@@ -1338,15 +2143,16 @@ function initializeMeasurementTools(viewer, model, container) {
1338
2143
  display: flex;
1339
2144
  justify-content: space-between;
1340
2145
  align-items: center;
2146
+ gap: 8px;
1341
2147
  `;
1342
2148
  nameDiv.innerHTML = `
1343
- <span style="flex: 1;">${name}</span>
1344
- <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>
1345
2151
  `;
1346
2152
  measurementDiv.appendChild(nameDiv);
1347
2153
  const valueDiv = document.createElement("div");
1348
2154
  valueDiv.style.cssText = "color: #aaa; font-size: 11px; margin-bottom: 3px;";
1349
- valueDiv.textContent = formatMeasurementValue(measurement);
2155
+ valueDiv.textContent = formatValue(measurement.value, measurement.type === "area");
1350
2156
  measurementDiv.appendChild(valueDiv);
1351
2157
  const pointsDiv = document.createElement("div");
1352
2158
  pointsDiv.style.cssText = "color: #888; font-size: 10px;";
@@ -1365,36 +2171,15 @@ function initializeMeasurementTools(viewer, model, container) {
1365
2171
  renameMeasurement(index, name);
1366
2172
  };
1367
2173
  } else {
1368
- console.warn(`[CesiumWidget] Rename button not found for measurement ${index}`);
2174
+ warn(PREFIX3, `Rename button not found for measurement ${index}`);
1369
2175
  }
1370
2176
  });
1371
2177
  }
1372
2178
  function getMeasurementColor(type) {
1373
- const colors = {
1374
- "distance": "#e74c3c",
1375
- "multi-distance": "#3498db",
1376
- "height": "#2ecc71",
1377
- "area": "#e67e22"
1378
- };
1379
- return colors[type] || "#95a5a6";
2179
+ return getColors()[type]?.button || "#95a5a6";
1380
2180
  }
1381
2181
  function getMeasurementTypeLabel(type) {
1382
- const labels = {
1383
- "distance": "Distance",
1384
- "multi-distance": "Multi-Distance",
1385
- "height": "Height",
1386
- "area": "Area"
1387
- };
1388
- return labels[type] || type;
1389
- }
1390
- function formatMeasurementValue(measurement) {
1391
- const value = measurement.value;
1392
- const type = measurement.type;
1393
- if (type === "area") {
1394
- return value >= 1e6 ? `${(value / 1e6).toFixed(2)} km\xB2` : `${value.toFixed(2)} m\xB2`;
1395
- } else {
1396
- return value >= 1e3 ? `${(value / 1e3).toFixed(2)} km` : `${value.toFixed(2)} m`;
1397
- }
2182
+ return TYPE_LABELS[type] || type;
1398
2183
  }
1399
2184
  function renameMeasurement(index, currentName) {
1400
2185
  const newName = prompt("Enter new name for measurement:", currentName);
@@ -1409,11 +2194,9 @@ function initializeMeasurementTools(viewer, model, container) {
1409
2194
  }
1410
2195
  function focusOnMeasurement(index) {
1411
2196
  const results = model.get("measurement_results") || [];
1412
- if (index < 0 || index >= results.length)
1413
- return;
2197
+ if (index < 0 || index >= results.length) return;
1414
2198
  const measurement = results[index];
1415
- if (!measurement.points || measurement.points.length === 0)
1416
- return;
2199
+ if (!measurement.points || measurement.points.length === 0) return;
1417
2200
  const positions = measurement.points.map(
1418
2201
  (p) => Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.alt || 0)
1419
2202
  );
@@ -1429,8 +2212,7 @@ function initializeMeasurementTools(viewer, model, container) {
1429
2212
  }
1430
2213
  function handleDistanceClick(click) {
1431
2214
  const position = getPosition(click.position);
1432
- if (!position)
1433
- return;
2215
+ if (!position) return;
1434
2216
  if (measurementState.points.length === 0) {
1435
2217
  measurementState.points.push(position);
1436
2218
  addMarker(position);
@@ -1442,7 +2224,7 @@ function initializeMeasurementTools(viewer, model, container) {
1442
2224
  }
1443
2225
  return measurementState.points;
1444
2226
  }, false),
1445
- width: 3,
2227
+ width: CONSTANTS2.POLYLINE_WIDTH,
1446
2228
  material: Cesium.Color.YELLOW,
1447
2229
  depthFailMaterial: Cesium.Color.YELLOW
1448
2230
  }
@@ -1452,7 +2234,7 @@ function initializeMeasurementTools(viewer, model, container) {
1452
2234
  addMarker(position);
1453
2235
  const distance = calculateDistance(measurementState.points[0], measurementState.points[1]);
1454
2236
  const midpoint = getMidpoint(measurementState.points[0], measurementState.points[1]);
1455
- addLabel(midpoint, `${distance.toFixed(2)} m`);
2237
+ addLabel(midpoint, formatValue(distance));
1456
2238
  if (measurementState.tempPolyline) {
1457
2239
  viewer.entities.remove(measurementState.tempPolyline);
1458
2240
  measurementState.tempPolyline = null;
@@ -1460,7 +2242,7 @@ function initializeMeasurementTools(viewer, model, container) {
1460
2242
  measurementState.polyline = viewer.entities.add({
1461
2243
  polyline: {
1462
2244
  positions: measurementState.points,
1463
- width: 3,
2245
+ width: CONSTANTS2.POLYLINE_WIDTH,
1464
2246
  material: Cesium.Color.RED,
1465
2247
  depthFailMaterial: Cesium.Color.RED
1466
2248
  }
@@ -1480,15 +2262,14 @@ function initializeMeasurementTools(viewer, model, container) {
1480
2262
  }
1481
2263
  function handleMultiDistanceClick(click) {
1482
2264
  const position = getPosition(click.position);
1483
- if (!position)
1484
- return;
2265
+ if (!position) return;
1485
2266
  measurementState.points.push(position);
1486
2267
  addMarker(position, Cesium.Color.BLUE);
1487
2268
  if (measurementState.points.length === 1) {
1488
2269
  measurementState.polyline = viewer.entities.add({
1489
2270
  polyline: {
1490
2271
  positions: new Cesium.CallbackProperty(() => measurementState.points, false),
1491
- width: 3,
2272
+ width: CONSTANTS2.POLYLINE_WIDTH,
1492
2273
  material: Cesium.Color.BLUE,
1493
2274
  depthFailMaterial: Cesium.Color.BLUE
1494
2275
  }
@@ -1498,43 +2279,17 @@ function initializeMeasurementTools(viewer, model, container) {
1498
2279
  const p1 = measurementState.points[measurementState.points.length - 2];
1499
2280
  const p2 = measurementState.points[measurementState.points.length - 1];
1500
2281
  const distance = calculateDistance(p1, p2);
1501
- const midpoint = getMidpoint(p1, p2);
1502
- addLabel(midpoint, `${distance.toFixed(2)} m`);
2282
+ addLabel(getMidpoint(p1, p2), formatValue(distance));
1503
2283
  let totalDistance = 0;
1504
2284
  for (let i = 0; i < measurementState.points.length - 1; i++) {
1505
- totalDistance += calculateDistance(
1506
- measurementState.points[i],
1507
- measurementState.points[i + 1]
1508
- );
2285
+ totalDistance += calculateDistance(measurementState.points[i], measurementState.points[i + 1]);
1509
2286
  }
1510
- const results = model.get("measurement_results") || [];
1511
- const lastResult = results[results.length - 1];
1512
- let newResults;
1513
- if (lastResult && lastResult.type === "multi-distance" && lastResult.isActive) {
1514
- newResults = [...results];
1515
- newResults[newResults.length - 1] = {
1516
- ...lastResult,
1517
- value: totalDistance,
1518
- points: measurementState.points.map(cartesianToLatLonAlt)
1519
- };
1520
- } else {
1521
- const multiDistanceCount = results.filter((r) => r.type === "multi-distance").length + 1;
1522
- newResults = [...results, {
1523
- type: "multi-distance",
1524
- value: totalDistance,
1525
- points: measurementState.points.map(cartesianToLatLonAlt),
1526
- isActive: true,
1527
- name: `Multi-Distance ${multiDistanceCount}`
1528
- }];
1529
- }
1530
- model.set("measurement_results", newResults);
1531
- model.save_changes();
2287
+ updateOrCreateMeasurement("multi-distance", totalDistance, measurementState.points.map(cartesianToLatLonAlt));
1532
2288
  }
1533
2289
  }
1534
2290
  function handleHeightClick(click) {
1535
2291
  const pickedPosition = getPosition(click.position);
1536
- if (!pickedPosition)
1537
- return;
2292
+ if (!pickedPosition) return;
1538
2293
  const cartographic = Cesium.Cartographic.fromCartesian(pickedPosition);
1539
2294
  const terrainHeight = viewer.scene.globe.getHeight(cartographic) || 0;
1540
2295
  const pickedHeight = cartographic.height;
@@ -1549,7 +2304,7 @@ function initializeMeasurementTools(viewer, model, container) {
1549
2304
  const heightLine = viewer.entities.add({
1550
2305
  polyline: {
1551
2306
  positions: [groundPosition, pickedPosition],
1552
- width: 3,
2307
+ width: CONSTANTS2.POLYLINE_WIDTH,
1553
2308
  material: Cesium.Color.GREEN,
1554
2309
  depthFailMaterial: Cesium.Color.GREEN
1555
2310
  }
@@ -1569,8 +2324,7 @@ function initializeMeasurementTools(viewer, model, container) {
1569
2324
  }
1570
2325
  function handleAreaClick(click) {
1571
2326
  const position = getPosition(click.position);
1572
- if (!position)
1573
- return;
2327
+ if (!position) return;
1574
2328
  measurementState.points.push(position);
1575
2329
  addMarker(position, Cesium.Color.ORANGE);
1576
2330
  if (measurementState.points.length === 1) {
@@ -1579,84 +2333,27 @@ function initializeMeasurementTools(viewer, model, container) {
1579
2333
  hierarchy: new Cesium.CallbackProperty(() => {
1580
2334
  return new Cesium.PolygonHierarchy(measurementState.points);
1581
2335
  }, false),
1582
- material: Cesium.Color.ORANGE.withAlpha(0.3),
2336
+ material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
1583
2337
  outline: true,
1584
2338
  outlineColor: Cesium.Color.ORANGE,
1585
- outlineWidth: 2
2339
+ outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
1586
2340
  }
1587
2341
  });
1588
2342
  measurementState.polylines.push(measurementState.polyline);
1589
2343
  }
1590
2344
  if (measurementState.points.length >= 3) {
1591
2345
  const positions = measurementState.points;
1592
- const polygonHierarchy = new Cesium.PolygonHierarchy(positions);
1593
- const geometry = Cesium.PolygonGeometry.createGeometry(
1594
- new Cesium.PolygonGeometry({
1595
- polygonHierarchy,
1596
- perPositionHeight: false,
1597
- arcType: Cesium.ArcType.GEODESIC
1598
- })
1599
- );
1600
- let area = 0;
1601
- if (geometry) {
1602
- const positionsArray = geometry.attributes.position.values;
1603
- const indices = geometry.indices;
1604
- for (let i = 0; i < indices.length; i += 3) {
1605
- const i0 = indices[i] * 3;
1606
- const i1 = indices[i + 1] * 3;
1607
- const i2 = indices[i + 2] * 3;
1608
- const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
1609
- const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
1610
- const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
1611
- const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
1612
- const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
1613
- const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
1614
- const triangleArea = Cesium.Cartesian3.magnitude(crossProduct) / 2;
1615
- area += triangleArea;
1616
- }
1617
- }
1618
- let centroidLon = 0, centroidLat = 0;
1619
- positions.forEach((pos) => {
1620
- const carto = Cesium.Cartographic.fromCartesian(pos);
1621
- centroidLon += carto.longitude;
1622
- centroidLat += carto.latitude;
1623
- });
1624
- centroidLon /= positions.length;
1625
- centroidLat /= positions.length;
1626
- const areaText = area >= 1e6 ? `${(area / 1e6).toFixed(2)} km\xB2` : `${area.toFixed(2)} m\xB2`;
2346
+ const area = calculateGeodesicArea(positions);
1627
2347
  const oldLabel = measurementState.labels.find((l) => l.label && l.label.text._value.includes("m\xB2") || l.label.text._value.includes("km\xB2"));
1628
2348
  if (oldLabel) {
1629
2349
  viewer.entities.remove(oldLabel);
1630
2350
  measurementState.labels = measurementState.labels.filter((l) => l !== oldLabel);
1631
2351
  }
1632
- const centroidCarto = new Cesium.Cartographic(centroidLon, centroidLat);
1633
- const promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]);
1634
- promise.then(() => {
1635
- const centroid = Cesium.Cartographic.toCartesian(centroidCarto);
1636
- addLabel(centroid, areaText);
2352
+ const centroidCarto = calculateCentroid(positions);
2353
+ Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]).then(() => {
2354
+ addLabel(Cesium.Cartographic.toCartesian(centroidCarto), formatValue(area, true));
1637
2355
  });
1638
- const results = model.get("measurement_results") || [];
1639
- const lastResult = results[results.length - 1];
1640
- let newResults;
1641
- if (lastResult && lastResult.type === "area" && lastResult.isActive) {
1642
- newResults = [...results];
1643
- newResults[newResults.length - 1] = {
1644
- ...lastResult,
1645
- value: area,
1646
- points: measurementState.points.map(cartesianToLatLonAlt)
1647
- };
1648
- } else {
1649
- const areaCount = results.filter((r) => r.type === "area").length + 1;
1650
- newResults = [...results, {
1651
- type: "area",
1652
- value: area,
1653
- points: measurementState.points.map(cartesianToLatLonAlt),
1654
- isActive: true,
1655
- name: `Area ${areaCount}`
1656
- }];
1657
- }
1658
- model.set("measurement_results", newResults);
1659
- model.save_changes();
2356
+ updateOrCreateMeasurement("area", area, measurementState.points.map(cartesianToLatLonAlt));
1660
2357
  }
1661
2358
  }
1662
2359
  function handleMouseMove(movement) {
@@ -1668,19 +2365,17 @@ function initializeMeasurementTools(viewer, model, container) {
1668
2365
  }
1669
2366
  }
1670
2367
  function enableMeasurementMode(mode) {
1671
- console.log("[CesiumWidget:MeasurementTools] Enabling measurement mode:", mode);
2368
+ log(PREFIX3, "Enabling measurement mode:", mode);
1672
2369
  if (measurementHandler) {
1673
2370
  measurementHandler.destroy();
1674
2371
  measurementHandler = null;
1675
2372
  }
1676
2373
  clearInProgressMeasurement();
1677
2374
  measurementState.mode = mode;
1678
- distanceBtn.style.background = mode === "distance" ? "#e74c3c" : "#3498db";
1679
- multiDistanceBtn.style.background = mode === "multi-distance" ? "#e74c3c" : "#3498db";
1680
- heightBtn.style.background = mode === "height" ? "#e74c3c" : "#3498db";
1681
- areaBtn.style.background = mode === "area" ? "#e74c3c" : "#3498db";
1682
- if (!mode)
1683
- return;
2375
+ Object.entries(modeButtons).forEach(([btnMode, btn]) => {
2376
+ btn.style.background = mode === btnMode ? "#e74c3c" : "#3498db";
2377
+ });
2378
+ if (!mode) return;
1684
2379
  measurementHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
1685
2380
  if (mode === "distance") {
1686
2381
  measurementHandler.setInputAction(handleDistanceClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
@@ -1722,12 +2417,10 @@ function initializeMeasurementTools(viewer, model, container) {
1722
2417
  }
1723
2418
  }
1724
2419
  function loadAndDisplayMeasurements(measurements) {
1725
- if (!Array.isArray(measurements))
1726
- return;
2420
+ if (!Array.isArray(measurements)) return;
1727
2421
  measurements.forEach((measurement) => {
1728
2422
  const { type, points } = measurement;
1729
- if (!type || !Array.isArray(points) || points.length < 2)
1730
- return;
2423
+ if (!type || !Array.isArray(points) || points.length < 2) return;
1731
2424
  const positions = points.map((point) => {
1732
2425
  const [lon, lat, alt] = point;
1733
2426
  return Cesium.Cartesian3.fromDegrees(lon, lat, alt || 0);
@@ -1748,22 +2441,21 @@ function initializeMeasurementTools(viewer, model, container) {
1748
2441
  const line = viewer.entities.add({
1749
2442
  polyline: {
1750
2443
  positions,
1751
- width: 3,
2444
+ width: CONSTANTS2.POLYLINE_WIDTH,
1752
2445
  material: Cesium.Color.RED
1753
2446
  }
1754
2447
  });
1755
2448
  measurementState.polylines.push(line);
1756
2449
  const distance = Cesium.Cartesian3.distance(positions[0], positions[1]);
1757
2450
  const midpoint = Cesium.Cartesian3.midpoint(positions[0], positions[1], new Cesium.Cartesian3());
1758
- const distanceText = distance >= 1e3 ? `${(distance / 1e3).toFixed(2)} km` : `${distance.toFixed(2)} m`;
1759
- addLabel(midpoint, distanceText);
2451
+ addLabel(midpoint, formatValue(distance));
1760
2452
  }
1761
2453
  function displayMultiDistance(positions) {
1762
2454
  positions.forEach((pos) => addMarker(pos, Cesium.Color.BLUE));
1763
2455
  const line = viewer.entities.add({
1764
2456
  polyline: {
1765
2457
  positions,
1766
- width: 3,
2458
+ width: CONSTANTS2.POLYLINE_WIDTH,
1767
2459
  material: Cesium.Color.BLUE
1768
2460
  }
1769
2461
  });
@@ -1773,12 +2465,9 @@ function initializeMeasurementTools(viewer, model, container) {
1773
2465
  const segmentDistance = Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
1774
2466
  totalDistance += segmentDistance;
1775
2467
  const midpoint = Cesium.Cartesian3.midpoint(positions[i], positions[i + 1], new Cesium.Cartesian3());
1776
- const segmentText = segmentDistance >= 1e3 ? `${(segmentDistance / 1e3).toFixed(2)} km` : `${segmentDistance.toFixed(2)} m`;
1777
- addLabel(midpoint, segmentText);
2468
+ addLabel(midpoint, formatValue(segmentDistance));
1778
2469
  }
1779
- const lastPos = positions[positions.length - 1];
1780
- const totalText = totalDistance >= 1e3 ? `Total: ${(totalDistance / 1e3).toFixed(2)} km` : `Total: ${totalDistance.toFixed(2)} m`;
1781
- addLabel(lastPos, totalText);
2470
+ addLabel(positions[positions.length - 1], `Total: ${formatValue(totalDistance)}`);
1782
2471
  }
1783
2472
  function displayHeight(positions) {
1784
2473
  positions.forEach((pos) => addMarker(pos, Cesium.Color.GREEN));
@@ -1792,115 +2481,75 @@ function initializeMeasurementTools(viewer, model, container) {
1792
2481
  Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, carto0.height),
1793
2482
  positions[1]
1794
2483
  ],
1795
- width: 3,
2484
+ width: CONSTANTS2.POLYLINE_WIDTH,
1796
2485
  material: Cesium.Color.GREEN
1797
2486
  }
1798
2487
  });
1799
2488
  measurementState.polylines.push(line);
1800
2489
  const midHeight = (carto0.height + carto1.height) / 2;
1801
2490
  const labelPos = Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, midHeight);
1802
- const heightText = verticalDistance >= 1e3 ? `${(verticalDistance / 1e3).toFixed(2)} km` : `${verticalDistance.toFixed(2)} m`;
1803
- addLabel(labelPos, heightText);
2491
+ addLabel(labelPos, formatValue(verticalDistance));
1804
2492
  }
1805
2493
  function displayArea(positions) {
1806
2494
  positions.forEach((pos) => addMarker(pos, Cesium.Color.ORANGE));
1807
2495
  const polygon = viewer.entities.add({
1808
2496
  polygon: {
1809
2497
  hierarchy: new Cesium.PolygonHierarchy(positions),
1810
- material: Cesium.Color.ORANGE.withAlpha(0.3),
2498
+ material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
1811
2499
  outline: true,
1812
2500
  outlineColor: Cesium.Color.ORANGE,
1813
- outlineWidth: 2
2501
+ outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
1814
2502
  }
1815
2503
  });
1816
2504
  measurementState.polylines.push(polygon);
1817
- const polygonHierarchy = new Cesium.PolygonHierarchy(positions);
1818
- const geometry = Cesium.PolygonGeometry.createGeometry(
1819
- new Cesium.PolygonGeometry({
1820
- polygonHierarchy,
1821
- perPositionHeight: false,
1822
- arcType: Cesium.ArcType.GEODESIC
1823
- })
1824
- );
1825
- let area = 0;
1826
- if (geometry) {
1827
- const positionsArray = geometry.attributes.position.values;
1828
- const indices = geometry.indices;
1829
- for (let i = 0; i < indices.length; i += 3) {
1830
- const i0 = indices[i] * 3;
1831
- const i1 = indices[i + 1] * 3;
1832
- const i2 = indices[i + 2] * 3;
1833
- const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
1834
- const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
1835
- const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
1836
- const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
1837
- const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
1838
- const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
1839
- const triangleArea = Cesium.Cartesian3.magnitude(crossProduct) / 2;
1840
- area += triangleArea;
1841
- }
1842
- }
1843
- let centroidLon = 0, centroidLat = 0;
1844
- positions.forEach((pos) => {
1845
- const carto = Cesium.Cartographic.fromCartesian(pos);
1846
- centroidLon += carto.longitude;
1847
- centroidLat += carto.latitude;
1848
- });
1849
- centroidLon /= positions.length;
1850
- centroidLat /= positions.length;
1851
- const areaText = area >= 1e6 ? `${(area / 1e6).toFixed(2)} km\xB2` : `${area.toFixed(2)} m\xB2`;
1852
- const centroidCarto = new Cesium.Cartographic(centroidLon, centroidLat);
1853
- const promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]);
1854
- promise.then(() => {
1855
- const centroid = Cesium.Cartographic.toCartesian(centroidCarto);
1856
- 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));
1857
2509
  });
1858
2510
  }
1859
2511
  model.on("change:measurement_mode", () => {
1860
2512
  if (isDestroyed) {
1861
- console.log("[CesiumWidget:MeasurementTools] Skipping measurement_mode change - destroyed");
2513
+ log(PREFIX3, "Skipping measurement_mode change - destroyed");
1862
2514
  return;
1863
2515
  }
1864
2516
  const mode = model.get("measurement_mode");
1865
- console.log("[CesiumWidget:MeasurementTools] Measurement mode changed:", mode);
2517
+ log(PREFIX3, "Measurement mode changed:", mode);
1866
2518
  enableMeasurementMode(mode);
1867
2519
  });
1868
2520
  model.on("change:measurement_results", () => {
1869
2521
  if (isDestroyed) {
1870
- console.log("[CesiumWidget:MeasurementTools] Skipping measurement_results change - destroyed");
2522
+ log(PREFIX3, "Skipping measurement_results change - destroyed");
1871
2523
  return;
1872
2524
  }
1873
2525
  const results = model.get("measurement_results") || [];
1874
- console.log("[CesiumWidget:MeasurementTools] Measurement results changed, count:", results.length);
2526
+ log(PREFIX3, "Measurement results changed, count:", results.length);
1875
2527
  if (results.length === 0) {
1876
2528
  clearAllMeasurements();
1877
2529
  }
1878
2530
  updateMeasurementsList();
1879
2531
  });
1880
2532
  model.on("change:load_measurements_trigger", () => {
1881
- if (isDestroyed)
1882
- return;
2533
+ if (isDestroyed) return;
1883
2534
  const triggerData = model.get("load_measurements_trigger");
1884
- console.log("[CesiumWidget:MeasurementTools] Load measurements trigger:", triggerData);
2535
+ log(PREFIX3, "Load measurements trigger:", triggerData);
1885
2536
  if (triggerData && triggerData.measurements) {
1886
2537
  loadAndDisplayMeasurements(triggerData.measurements);
1887
2538
  updateMeasurementsList();
1888
2539
  }
1889
2540
  });
1890
2541
  model.on("change:focus_measurement_trigger", () => {
1891
- if (isDestroyed)
1892
- return;
2542
+ if (isDestroyed) return;
1893
2543
  const triggerData = model.get("focus_measurement_trigger");
1894
- console.log("[CesiumWidget:MeasurementTools] Focus measurement trigger:", triggerData);
2544
+ log(PREFIX3, "Focus measurement trigger:", triggerData);
1895
2545
  if (triggerData && typeof triggerData.index === "number") {
1896
2546
  focusOnMeasurement(triggerData.index);
1897
2547
  }
1898
2548
  });
1899
2549
  model.on("change:show_measurement_tools", () => {
1900
- if (isDestroyed)
1901
- return;
2550
+ if (isDestroyed) return;
1902
2551
  const show = model.get("show_measurement_tools");
1903
- console.log("[CesiumWidget:MeasurementTools] Show measurement tools:", show);
2552
+ log(PREFIX3, "Show measurement tools:", show);
1904
2553
  toolbarDiv.style.display = show ? "flex" : "none";
1905
2554
  editorPanel.style.display = show ? editorPanel.style.display : "none";
1906
2555
  if (!show && editState.enabled) {
@@ -1909,10 +2558,9 @@ function initializeMeasurementTools(viewer, model, container) {
1909
2558
  }
1910
2559
  });
1911
2560
  model.on("change:show_measurements_list", () => {
1912
- if (isDestroyed)
1913
- return;
2561
+ if (isDestroyed) return;
1914
2562
  const show = model.get("show_measurements_list");
1915
- console.log("[CesiumWidget:MeasurementTools] Show measurements list:", show);
2563
+ log(PREFIX3, "Show measurements list:", show);
1916
2564
  measurementsListPanel.style.display = show ? "block" : "none";
1917
2565
  });
1918
2566
  toolbarDiv.style.display = model.get("show_measurement_tools") ? "flex" : "none";
@@ -1922,102 +2570,418 @@ function initializeMeasurementTools(viewer, model, container) {
1922
2570
  enableMeasurementMode,
1923
2571
  clearAllMeasurements,
1924
2572
  destroy: () => {
1925
- console.log("[CesiumWidget:MeasurementTools] Destroying measurement tools");
2573
+ log(PREFIX3, "Destroying measurement tools");
1926
2574
  isDestroyed = true;
1927
2575
  if (measurementHandler) {
1928
2576
  measurementHandler.destroy();
2577
+ measurementHandler = null;
1929
2578
  }
2579
+ if (editHandler) {
2580
+ editHandler.destroy();
2581
+ editHandler = null;
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;
1930
2591
  clearAllMeasurements();
1931
2592
  if (toolbarDiv.parentNode) {
1932
2593
  toolbarDiv.remove();
1933
2594
  }
1934
- console.log("[CesiumWidget:MeasurementTools] Measurement tools destroyed");
2595
+ if (editorPanel.parentNode) {
2596
+ editorPanel.remove();
2597
+ }
2598
+ if (measurementsListPanel.parentNode) {
2599
+ measurementsListPanel.remove();
2600
+ }
2601
+ log(PREFIX3, "Measurement tools destroyed");
2602
+ }
2603
+ };
2604
+ }
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");
1935
2867
  }
1936
2868
  };
1937
2869
  }
1938
2870
 
1939
2871
  // src/cesiumjs_anywidget/js/index.js
1940
- window.CESIUM_BASE_URL = "https://cesium.com/downloads/cesiumjs/releases/1.135/Build/Cesium/";
2872
+ window.CESIUM_BASE_URL = "https://cesium.com/downloads/cesiumjs/releases/1.137/Build/Cesium/";
1941
2873
  async function render({ model, el }) {
1942
- console.log("[CesiumWidget] Starting render");
1943
- console.log("[CesiumWidget] Loading CesiumJS...");
2874
+ setDebugMode(model.get("debug_mode") || false);
2875
+ model.on("change:debug_mode", () => {
2876
+ setDebugMode(model.get("debug_mode"));
2877
+ });
2878
+ log("Main", "Starting render");
2879
+ log("Main", "Loading CesiumJS...");
1944
2880
  const Cesium = await loadCesiumJS();
1945
- console.log("[CesiumWidget] CesiumJS loaded successfully");
2881
+ log("Main", "CesiumJS loaded successfully");
1946
2882
  const container = document.createElement("div");
1947
2883
  container.style.width = "100%";
1948
2884
  container.style.height = model.get("height");
1949
2885
  container.style.position = "relative";
1950
2886
  el.appendChild(container);
1951
- console.log("[CesiumWidget] Container created with height:", model.get("height"));
2887
+ log("Main", "Container created with height:", model.get("height"));
1952
2888
  const ionToken = model.get("ion_access_token");
1953
2889
  if (ionToken) {
1954
2890
  Cesium.Ion.defaultAccessToken = ionToken;
1955
- console.log("[CesiumWidget] Ion access token set");
2891
+ log("Main", "Ion access token set");
1956
2892
  } else {
1957
- console.warn("[CesiumWidget] No Ion access token provided");
2893
+ warn("Main", "No Ion access token provided");
1958
2894
  }
1959
2895
  const loadingDiv = createLoadingIndicator(container, !!ionToken);
1960
2896
  let viewer = null;
1961
2897
  let cameraSync = null;
1962
2898
  let measurementTools = null;
2899
+ let pointPicking = null;
2900
+ let photoProjection = null;
1963
2901
  let geoJsonLoader = null;
1964
2902
  let czmlLoader = null;
2903
+ let photorealisticTiles = null;
1965
2904
  (async () => {
1966
2905
  try {
1967
- console.log("[CesiumWidget] Creating Cesium Viewer...");
2906
+ log("Main", "Creating Cesium Viewer...");
1968
2907
  viewer = createViewer(container, model, Cesium);
1969
- console.log("[CesiumWidget] Cesium Viewer created successfully");
2908
+ log("Main", "Cesium Viewer created successfully");
1970
2909
  if (loadingDiv.parentNode) {
1971
2910
  loadingDiv.remove();
1972
2911
  }
1973
- console.log("[CesiumWidget] Initializing camera synchronization...");
2912
+ log("Main", "Initializing camera synchronization...");
1974
2913
  cameraSync = initializeCameraSync(viewer, model);
1975
- console.log("[CesiumWidget] Camera synchronization initialized");
1976
- console.log("[CesiumWidget] Initializing measurement tools...");
2914
+ log("Main", "Camera synchronization initialized");
2915
+ log("Main", "Initializing measurement tools...");
1977
2916
  measurementTools = initializeMeasurementTools(viewer, model, container);
1978
- console.log("[CesiumWidget] Measurement tools initialized");
1979
- console.log("[CesiumWidget] Setting up viewer listeners...");
2917
+ log("Main", "Measurement tools initialized");
2918
+ log("Main", "Initializing point picking...");
2919
+ pointPicking = initializePointPicking(viewer, model, container);
2920
+ log("Main", "Point picking initialized");
2921
+ log("Main", "Setting up viewer listeners...");
1980
2922
  setupViewerListeners(viewer, model, container, Cesium);
1981
- console.log("[CesiumWidget] Viewer listeners set up");
1982
- console.log("[CesiumWidget] Setting up GeoJSON loader...");
2923
+ log("Main", "Viewer listeners set up");
2924
+ log("Main", "Setting up GeoJSON loader...");
1983
2925
  geoJsonLoader = setupGeoJSONLoader(viewer, model, Cesium);
1984
- console.log("[CesiumWidget] GeoJSON loader set up");
1985
- console.log("[CesiumWidget] Setting up CZML loader...");
2926
+ log("Main", "GeoJSON loader set up");
2927
+ log("Main", "Setting up CZML loader...");
1986
2928
  czmlLoader = setupCZMLLoader(viewer, model, Cesium);
1987
- console.log("[CesiumWidget] CZML loader set up");
1988
- console.log("[CesiumWidget] Initialization complete");
1989
- } catch (error) {
1990
- console.error("[CesiumWidget] Error initializing CesiumJS viewer:", error);
1991
- loadingDiv.textContent = `Error: ${error.message}`;
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
+ });
2940
+ log("Main", "Initialization complete");
2941
+ } catch (err) {
2942
+ error("Main", "Error initializing CesiumJS viewer:", err);
2943
+ loadingDiv.textContent = `Error: ${err.message}`;
1992
2944
  loadingDiv.style.background = "rgba(255,0,0,0.8)";
1993
2945
  }
1994
2946
  })();
1995
2947
  return () => {
1996
- console.log("[CesiumWidget] Starting cleanup...");
2948
+ log("Main", "Starting cleanup...");
1997
2949
  if (cameraSync) {
1998
- console.log("[CesiumWidget] Destroying camera sync...");
2950
+ log("Main", "Destroying camera sync...");
1999
2951
  cameraSync.destroy();
2000
2952
  }
2001
2953
  if (measurementTools) {
2002
- console.log("[CesiumWidget] Destroying measurement tools...");
2954
+ log("Main", "Destroying measurement tools...");
2003
2955
  measurementTools.destroy();
2004
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
+ }
2005
2965
  if (geoJsonLoader) {
2006
- console.log("[CesiumWidget] Destroying GeoJSON loader...");
2966
+ log("Main", "Destroying GeoJSON loader...");
2007
2967
  geoJsonLoader.destroy();
2008
2968
  }
2009
2969
  if (czmlLoader) {
2010
- console.log("[CesiumWidget] Destroying CZML loader...");
2970
+ log("Main", "Destroying CZML loader...");
2011
2971
  czmlLoader.destroy();
2012
2972
  }
2973
+ if (photorealisticTiles) {
2974
+ log("Main", "Destroying photorealistic tiles...");
2975
+ photorealisticTiles.destroy();
2976
+ }
2013
2977
  if (viewer) {
2014
- console.log("[CesiumWidget] Destroying viewer...");
2978
+ log("Main", "Destroying viewer...");
2015
2979
  viewer.destroy();
2016
2980
  }
2017
- console.log("[CesiumWidget] Cleanup complete");
2981
+ log("Main", "Cleanup complete");
2018
2982
  };
2019
2983
  }
2020
- var js_default = { render };
2984
+ var index_default = { render };
2021
2985
  export {
2022
- js_default as default
2986
+ index_default as default
2023
2987
  };