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.
- cesiumjs_anywidget/__init__.py +23 -2
- cesiumjs_anywidget/exif_utils.py +281 -0
- cesiumjs_anywidget/geoid.py +299 -0
- cesiumjs_anywidget/index.js +1569 -605
- cesiumjs_anywidget/logger.py +73 -0
- cesiumjs_anywidget/styles.css +24 -1
- cesiumjs_anywidget/widget.py +616 -42
- {cesiumjs_anywidget-0.5.0.dist-info → cesiumjs_anywidget-0.7.0.dist-info}/METADATA +45 -6
- cesiumjs_anywidget-0.7.0.dist-info/RECORD +11 -0
- cesiumjs_anywidget-0.5.0.dist-info/RECORD +0 -8
- {cesiumjs_anywidget-0.5.0.dist-info → cesiumjs_anywidget-0.7.0.dist-info}/WHEEL +0 -0
- {cesiumjs_anywidget-0.5.0.dist-info → cesiumjs_anywidget-0.7.0.dist-info}/licenses/LICENSE +0 -0
cesiumjs_anywidget/index.js
CHANGED
|
@@ -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
|
-
|
|
36
|
+
log(PREFIX, "Loading CesiumJS...");
|
|
7
37
|
if (window.Cesium) {
|
|
8
|
-
|
|
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 =
|
|
13
|
-
|
|
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
|
-
|
|
46
|
+
log(PREFIX, "CesiumJS script loaded successfully");
|
|
17
47
|
resolve();
|
|
18
48
|
};
|
|
19
|
-
script.onerror = (
|
|
20
|
-
|
|
21
|
-
reject(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
if (
|
|
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
|
-
|
|
99
|
+
log(PREFIX, "Terrain enabled");
|
|
65
100
|
}
|
|
66
101
|
const viewer = new Cesium.Viewer(container, viewerOptions);
|
|
67
|
-
viewer.scene.globe
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
log(PREFIX, "Skipping enable_terrain change - destroyed");
|
|
78
117
|
return;
|
|
79
118
|
}
|
|
80
|
-
if (!viewer)
|
|
81
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
268
|
-
|
|
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 (
|
|
282
|
-
|
|
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
|
-
|
|
541
|
+
log(PREFIX, "Skipping interaction event - destroyed:", type);
|
|
289
542
|
return;
|
|
290
543
|
}
|
|
291
544
|
if (!viewer) {
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
},
|
|
607
|
+
}, CONSTANTS.TIMELINE_SCRUB_DEBOUNCE_MS);
|
|
406
608
|
timelineScrubbing = true;
|
|
407
609
|
}
|
|
408
610
|
});
|
|
409
611
|
}
|
|
410
|
-
|
|
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
|
-
|
|
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
|
-
|
|
634
|
+
log(PREFIX, "Skipping geojson_data load - destroyed");
|
|
419
635
|
return;
|
|
420
636
|
}
|
|
421
637
|
if (!viewer || !viewer.dataSources) {
|
|
422
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
440
|
-
strokeWidth:
|
|
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
|
-
|
|
661
|
+
log(PREFIX, "GeoJSON dataset loaded successfully");
|
|
446
662
|
}
|
|
447
|
-
} catch (
|
|
448
|
-
|
|
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
|
-
|
|
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
|
-
|
|
676
|
+
log(PREFIX, "Loading initial GeoJSON data...");
|
|
461
677
|
loadGeoJSONData(true);
|
|
462
678
|
}
|
|
463
679
|
return {
|
|
464
680
|
destroy: () => {
|
|
465
|
-
|
|
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
|
-
|
|
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
|
-
|
|
699
|
+
log(PREFIX, "Skipping czml_data load - destroyed");
|
|
483
700
|
return;
|
|
484
701
|
}
|
|
485
702
|
if (!viewer || !viewer.dataSources) {
|
|
486
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
723
|
+
log(PREFIX, "CZML document loaded successfully, entities:", dataSource.entities.values.length);
|
|
507
724
|
}
|
|
508
|
-
} catch (
|
|
509
|
-
|
|
725
|
+
} catch (error2) {
|
|
726
|
+
error2(PREFIX, "Error loading CZML:", error2);
|
|
510
727
|
}
|
|
511
728
|
} else {
|
|
512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
741
|
+
log(PREFIX, "Loading initial CZML data...");
|
|
525
742
|
loadCZMLData(true);
|
|
526
743
|
}
|
|
527
744
|
return {
|
|
528
745
|
destroy: () => {
|
|
529
|
-
|
|
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
|
-
|
|
547
|
-
|
|
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
|
-
|
|
861
|
+
log(PREFIX2, "Skipping updateCameraFromModel - module destroyed");
|
|
550
862
|
return;
|
|
551
863
|
}
|
|
552
864
|
if (!viewer) {
|
|
553
|
-
|
|
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 =
|
|
560
|
-
const pitch =
|
|
561
|
-
const roll =
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
959
|
+
const handleCameraCommand = () => {
|
|
617
960
|
if (isDestroyed) {
|
|
618
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1085
|
+
warn(PREFIX2, `Unknown camera command: ${cmd}`);
|
|
709
1086
|
}
|
|
710
|
-
} catch (
|
|
711
|
-
|
|
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
|
-
|
|
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
|
-
|
|
725
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1360
|
+
padding: 0;
|
|
762
1361
|
border-radius: 5px;
|
|
763
1362
|
z-index: 1000;
|
|
764
1363
|
display: flex;
|
|
765
1364
|
flex-direction: column;
|
|
766
|
-
gap:
|
|
1365
|
+
gap: 0;
|
|
767
1366
|
`;
|
|
768
1367
|
container.appendChild(toolbarDiv);
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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 =
|
|
1393
|
+
btn.style.background = hoverColor;
|
|
784
1394
|
};
|
|
785
1395
|
btn.onmouseout = () => {
|
|
786
|
-
btn.style.background =
|
|
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
|
-
|
|
790
|
-
|
|
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
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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:
|
|
1556
|
+
pixelSize: CONSTANTS2.MARKER_SIZE,
|
|
915
1557
|
color,
|
|
916
1558
|
outlineColor: Cesium.Color.WHITE,
|
|
917
|
-
outlineWidth:
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
996
|
-
entity.point.outlineWidth =
|
|
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
|
-
|
|
1002
|
-
|
|
1003
|
-
selectPoint(pickedObject.id, click.position);
|
|
1665
|
+
if (editState.addPointMode) {
|
|
1666
|
+
handleAddPointClick(click);
|
|
1004
1667
|
} else {
|
|
1005
|
-
|
|
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 =
|
|
1039
|
-
entity.point.outlineWidth =
|
|
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 =
|
|
1071
|
-
entity.point.outlineWidth =
|
|
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 =
|
|
1078
|
-
editState.selectedEntity.point.outlineWidth =
|
|
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
|
-
|
|
1091
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1852
|
+
material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
|
|
1187
1853
|
outline: true,
|
|
1188
1854
|
outlineColor: Cesium.Color.ORANGE,
|
|
1189
|
-
outlineWidth:
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
2105
|
+
log(PREFIX3, "Updating measurements list, count:", results.length);
|
|
1306
2106
|
const listContent = document.getElementById("measurements-list-content");
|
|
1307
2107
|
if (!listContent) {
|
|
1308
|
-
|
|
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 =
|
|
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
|
-
|
|
2174
|
+
warn(PREFIX3, `Rename button not found for measurement ${index}`);
|
|
1369
2175
|
}
|
|
1370
2176
|
});
|
|
1371
2177
|
}
|
|
1372
2178
|
function getMeasurementColor(type) {
|
|
1373
|
-
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
2336
|
+
material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
|
|
1583
2337
|
outline: true,
|
|
1584
2338
|
outlineColor: Cesium.Color.ORANGE,
|
|
1585
|
-
outlineWidth:
|
|
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
|
|
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 =
|
|
1633
|
-
|
|
1634
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
1777
|
-
addLabel(midpoint, segmentText);
|
|
2468
|
+
addLabel(midpoint, formatValue(segmentDistance));
|
|
1778
2469
|
}
|
|
1779
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
2498
|
+
material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
|
|
1811
2499
|
outline: true,
|
|
1812
2500
|
outlineColor: Cesium.Color.ORANGE,
|
|
1813
|
-
outlineWidth:
|
|
2501
|
+
outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
|
|
1814
2502
|
}
|
|
1815
2503
|
});
|
|
1816
2504
|
measurementState.polylines.push(polygon);
|
|
1817
|
-
const
|
|
1818
|
-
const
|
|
1819
|
-
|
|
1820
|
-
|
|
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
|
-
|
|
2513
|
+
log(PREFIX3, "Skipping measurement_mode change - destroyed");
|
|
1862
2514
|
return;
|
|
1863
2515
|
}
|
|
1864
2516
|
const mode = model.get("measurement_mode");
|
|
1865
|
-
|
|
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
|
-
|
|
2522
|
+
log(PREFIX3, "Skipping measurement_results change - destroyed");
|
|
1871
2523
|
return;
|
|
1872
2524
|
}
|
|
1873
2525
|
const results = model.get("measurement_results") || [];
|
|
1874
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2872
|
+
window.CESIUM_BASE_URL = "https://cesium.com/downloads/cesiumjs/releases/1.137/Build/Cesium/";
|
|
1941
2873
|
async function render({ model, el }) {
|
|
1942
|
-
|
|
1943
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2891
|
+
log("Main", "Ion access token set");
|
|
1956
2892
|
} else {
|
|
1957
|
-
|
|
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
|
-
|
|
2906
|
+
log("Main", "Creating Cesium Viewer...");
|
|
1968
2907
|
viewer = createViewer(container, model, Cesium);
|
|
1969
|
-
|
|
2908
|
+
log("Main", "Cesium Viewer created successfully");
|
|
1970
2909
|
if (loadingDiv.parentNode) {
|
|
1971
2910
|
loadingDiv.remove();
|
|
1972
2911
|
}
|
|
1973
|
-
|
|
2912
|
+
log("Main", "Initializing camera synchronization...");
|
|
1974
2913
|
cameraSync = initializeCameraSync(viewer, model);
|
|
1975
|
-
|
|
1976
|
-
|
|
2914
|
+
log("Main", "Camera synchronization initialized");
|
|
2915
|
+
log("Main", "Initializing measurement tools...");
|
|
1977
2916
|
measurementTools = initializeMeasurementTools(viewer, model, container);
|
|
1978
|
-
|
|
1979
|
-
|
|
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
|
-
|
|
1982
|
-
|
|
2923
|
+
log("Main", "Viewer listeners set up");
|
|
2924
|
+
log("Main", "Setting up GeoJSON loader...");
|
|
1983
2925
|
geoJsonLoader = setupGeoJSONLoader(viewer, model, Cesium);
|
|
1984
|
-
|
|
1985
|
-
|
|
2926
|
+
log("Main", "GeoJSON loader set up");
|
|
2927
|
+
log("Main", "Setting up CZML loader...");
|
|
1986
2928
|
czmlLoader = setupCZMLLoader(viewer, model, Cesium);
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
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
|
-
|
|
2948
|
+
log("Main", "Starting cleanup...");
|
|
1997
2949
|
if (cameraSync) {
|
|
1998
|
-
|
|
2950
|
+
log("Main", "Destroying camera sync...");
|
|
1999
2951
|
cameraSync.destroy();
|
|
2000
2952
|
}
|
|
2001
2953
|
if (measurementTools) {
|
|
2002
|
-
|
|
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
|
-
|
|
2966
|
+
log("Main", "Destroying GeoJSON loader...");
|
|
2007
2967
|
geoJsonLoader.destroy();
|
|
2008
2968
|
}
|
|
2009
2969
|
if (czmlLoader) {
|
|
2010
|
-
|
|
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
|
-
|
|
2978
|
+
log("Main", "Destroying viewer...");
|
|
2015
2979
|
viewer.destroy();
|
|
2016
2980
|
}
|
|
2017
|
-
|
|
2981
|
+
log("Main", "Cleanup complete");
|
|
2018
2982
|
};
|
|
2019
2983
|
}
|
|
2020
|
-
var
|
|
2984
|
+
var index_default = { render };
|
|
2021
2985
|
export {
|
|
2022
|
-
|
|
2986
|
+
index_default as default
|
|
2023
2987
|
};
|