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