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

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