mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
- Imported metadata stays inert: descriptions, native labels, properties and KML extended data are dropped from every entity; names are kept as short plain text. GeoJSON loads with a describe that builds no HTML, and simplestyle (including marker-symbol) is dropped, so no icon asset is fetched for a file and GeoJSON draws in its row's color. - Bound geometry before loading: features and vertices are counted in the parsed GeoJSON, the KML document and while reading GPX, and a file over 20,000 features or 250,000 vertices is refused before Cesium builds it. - Imports run one batch at a time, so concurrent picks or drops cannot pass the 12-file cap. - Fly-to framing walks long tracks by index instead of spreading them into a call. - Fly-to is an immediate navigation through the application shell; the fly after an import is a deferred navigation stamped when the batch starts and re-asserted just before flying. - Dated imports never change the shared clock: imported sources expose no clock, entities are available for all time, and a KML gx:Track is drawn as its line. - GPX is read into GeoJSON with one line per track segment, without times. - KML/KMZ rows show a hatched swatch (they keep their own styles); keyboard focus is kept on the same control after hide/show and moves to the next row after remove. - Unit regressions for each, and the rendered QA now imports hostile metadata, dated KML/GPX and a two-segment track, and fails on any request for a file's contents or icon assets, a clock change, or surviving metadata. Co-Authored-By: Claude Opus 5.5 <[email protected]>
91 lines
3.1 KiB
JavaScript
91 lines
3.1 KiB
JavaScript
/**
|
|
* Fly to everything in a data source (an imported file, a live feed) without
|
|
* trusting Cesium's own framing of ground-clamped points: before the
|
|
* destination's terrain has streamed in, a clamped billboard reports a
|
|
* bounding sphere kilometers below the surface, and `viewer.flyTo` follows it
|
|
* underground. Instead the footprint is framed from the features' longitudes
|
|
* and latitudes, and the camera ground guard lifts the eye once the real
|
|
* surface can be measured.
|
|
*/
|
|
import * as Cesium from 'cesium';
|
|
import { guardCameraAboveGround } from '../cameraGroundGuard.js';
|
|
|
|
const MAX_POINTS = 5000;
|
|
|
|
/**
|
|
* Longitude/latitude pairs of every entity's position, line and polygon,
|
|
* thinned to at most `MAX_POINTS`. Long tracks are walked by index, never
|
|
* spread into a call, so a file with hundreds of thousands of vertices cannot
|
|
* overflow the stack; only the kept points are converted.
|
|
*/
|
|
export function dataSourcePoints(dataSource, time = Cesium.JulianDate.now()) {
|
|
const parts = [];
|
|
let total = 0;
|
|
for (const entity of dataSource?.entities?.values ?? []) {
|
|
const at = entity.position?.getValue?.(time);
|
|
if (at) {
|
|
parts.push([at]);
|
|
total += 1;
|
|
}
|
|
const line = entity.polyline?.positions?.getValue?.(time);
|
|
if (Array.isArray(line) && line.length) {
|
|
parts.push(line);
|
|
total += line.length;
|
|
}
|
|
const ring = entity.polygon?.hierarchy?.getValue?.(time)?.positions;
|
|
if (Array.isArray(ring) && ring.length) {
|
|
parts.push(ring);
|
|
total += ring.length;
|
|
}
|
|
}
|
|
const stride = Math.max(1, Math.ceil(total / MAX_POINTS));
|
|
const points = [];
|
|
let index = 0;
|
|
for (const part of parts) {
|
|
// Keep every stride-th vertex across all parts, plus each part's ends so
|
|
// a short line is never skipped entirely.
|
|
for (let i = 0; i < part.length; i++, index++) {
|
|
if (index % stride !== 0 && i !== 0 && i !== part.length - 1) continue;
|
|
const carto = Cesium.Cartographic.fromCartesian(part[i]);
|
|
if (!carto) continue;
|
|
points.push([
|
|
Cesium.Math.toDegrees(carto.longitude),
|
|
Cesium.Math.toDegrees(carto.latitude),
|
|
]);
|
|
}
|
|
}
|
|
return points;
|
|
}
|
|
|
|
/**
|
|
* @param {Cesium.Viewer} viewer
|
|
* @param {Cesium.DataSource} dataSource
|
|
* @param {{duration?: number}} [options]
|
|
* @returns {boolean} Whether there was anything to fly to.
|
|
*/
|
|
export function flyToDataSource(viewer, dataSource, { duration = 1.5 } = {}) {
|
|
const points = dataSourcePoints(dataSource);
|
|
if (!points.length) {
|
|
viewer.flyTo?.(dataSource, { duration });
|
|
return false;
|
|
}
|
|
const sphere = Cesium.BoundingSphere.fromPoints(
|
|
points.map(([lon, lat]) => Cesium.Cartesian3.fromDegrees(lon, lat, 0)),
|
|
);
|
|
const center = Cesium.Cartographic.fromCartesian(sphere.center);
|
|
viewer.camera?.flyToBoundingSphere?.(sphere, {
|
|
offset: new Cesium.HeadingPitchRange(
|
|
viewer.camera.heading || 0,
|
|
Cesium.Math.toRadians(-45),
|
|
Math.max(800, sphere.radius * 2.8),
|
|
),
|
|
duration,
|
|
complete: () =>
|
|
guardCameraAboveGround(viewer, {
|
|
lat: Cesium.Math.toDegrees(center.latitude),
|
|
lon: Cesium.Math.toDegrees(center.longitude),
|
|
}),
|
|
});
|
|
return true;
|
|
}
|