feat(foundry): add OSM cache survey presets
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Geometric-error bands observed in the current Cesium OSM Buildings 96188
|
||||
* hierarchy. They are ordered from the global root towards maximum detail.
|
||||
* The provider may publish a newer hierarchy later, so these values are a
|
||||
* measured runtime profile rather than a permanent Cesium API contract.
|
||||
*/
|
||||
export const OSM_BUILDINGS_GEOMETRIC_ERROR_LEVELS = [
|
||||
77_067.33978,
|
||||
38_533.66989,
|
||||
19_266.834945,
|
||||
9_633.417472,
|
||||
4_816.708736,
|
||||
2_408.354368,
|
||||
1_204.177184,
|
||||
602.088592,
|
||||
301.044296,
|
||||
150.522148,
|
||||
75.261074,
|
||||
37.630537,
|
||||
18.815269,
|
||||
9.407634,
|
||||
4.703817,
|
||||
2.351909,
|
||||
] as const;
|
||||
|
||||
export const CAMERA_SURVEY_CROSS_TRACK_FRACTION = 0.69;
|
||||
|
||||
export type CameraSurveyPreset = {
|
||||
id: string;
|
||||
profile: number;
|
||||
label: string;
|
||||
heightAboveGroundMeters: number;
|
||||
pitchMetersPerTurn: number;
|
||||
speedMetersPerSecond: number;
|
||||
targetRadiusMeters: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ten operational scale profiles cover a regional survey from street to
|
||||
* macro scale. They deliberately do not claim a one-to-one provider LOD:
|
||||
* Cesium selects imagery, terrain and 3D Tiles through different SSE rules,
|
||||
* viewport geometry, pixel ratio, availability and tile bounding volumes.
|
||||
*
|
||||
* Height and turn pitch double together. With a 60 degree horizontal field
|
||||
* of view and a near-nadir camera, pitch = 0.8 * height keeps about 31%
|
||||
* cross-track overlap. Runtime additionally clamps pitch to the live viewport,
|
||||
* so a narrow inspector cannot open gaps between turns.
|
||||
*
|
||||
* Speed is a ceiling, not a promise: the renderer pauses whenever current
|
||||
* Imagery, Terrain or OSM Buildings requests are unsettled. A 25 km target
|
||||
* radius makes every profile a finite city-scale pass instead of silently
|
||||
* continuing to the 250 km geodesic safety boundary.
|
||||
*/
|
||||
export const CAMERA_SURVEY_PRESETS = [
|
||||
{ id: "osm-detail-1", profile: 1, label: "Профиль 1 · 80 м", heightAboveGroundMeters: 80, pitchMetersPerTurn: 64, speedMetersPerSecond: 256, targetRadiusMeters: 25_000 },
|
||||
{ id: "osm-detail-2", profile: 2, label: "Профиль 2 · 160 м", heightAboveGroundMeters: 160, pitchMetersPerTurn: 128, speedMetersPerSecond: 512, targetRadiusMeters: 25_000 },
|
||||
{ id: "osm-detail-3", profile: 3, label: "Профиль 3 · 320 м", heightAboveGroundMeters: 320, pitchMetersPerTurn: 256, speedMetersPerSecond: 1_024, targetRadiusMeters: 25_000 },
|
||||
{ id: "osm-detail-4", profile: 4, label: "Профиль 4 · 640 м", heightAboveGroundMeters: 640, pitchMetersPerTurn: 512, speedMetersPerSecond: 2_048, targetRadiusMeters: 25_000 },
|
||||
{ id: "osm-detail-5", profile: 5, label: "Профиль 5 · 1,28 км", heightAboveGroundMeters: 1_280, pitchMetersPerTurn: 1_024, speedMetersPerSecond: 4_096, targetRadiusMeters: 25_000 },
|
||||
{ id: "osm-detail-6", profile: 6, label: "Профиль 6 · 2,56 км", heightAboveGroundMeters: 2_560, pitchMetersPerTurn: 2_048, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
|
||||
{ id: "osm-detail-7", profile: 7, label: "Профиль 7 · 5,12 км", heightAboveGroundMeters: 5_120, pitchMetersPerTurn: 4_096, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
|
||||
{ id: "osm-detail-8", profile: 8, label: "Профиль 8 · 10,24 км", heightAboveGroundMeters: 10_240, pitchMetersPerTurn: 8_192, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
|
||||
{ id: "osm-detail-9", profile: 9, label: "Профиль 9 · 20,48 км", heightAboveGroundMeters: 20_480, pitchMetersPerTurn: 16_384, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
|
||||
{ id: "osm-detail-10", profile: 10, label: "Профиль 10 · 40,96 км", heightAboveGroundMeters: 40_960, pitchMetersPerTurn: 32_768, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
|
||||
] as const satisfies readonly CameraSurveyPreset[];
|
||||
|
||||
export type CameraSurveyPresetId = (typeof CAMERA_SURVEY_PRESETS)[number]["id"];
|
||||
export type CameraSurveySelection = CameraSurveyPresetId | "custom";
|
||||
|
||||
export const DEFAULT_CAMERA_SURVEY_PRESET = CAMERA_SURVEY_PRESETS[0];
|
||||
export const OSM_BUILDINGS_OBSERVED_BAND_COUNT = OSM_BUILDINGS_GEOMETRIC_ERROR_LEVELS.length;
|
||||
|
||||
export function findCameraSurveyPreset(id: CameraSurveyPresetId) {
|
||||
return CAMERA_SURVEY_PRESETS.find((preset) => preset.id === id);
|
||||
}
|
||||
|
||||
export function cameraSurveyCrossTrackWidth(heightAboveGroundMeters: number, horizontalFieldOfViewDegrees = 60) {
|
||||
return 2 * heightAboveGroundMeters * Math.tan(horizontalFieldOfViewDegrees * Math.PI / 360);
|
||||
}
|
||||
|
||||
export function cameraSurveyTurnOverlap(preset: CameraSurveyPreset, horizontalFieldOfViewDegrees = 60) {
|
||||
const width = cameraSurveyCrossTrackWidth(preset.heightAboveGroundMeters, horizontalFieldOfViewDegrees);
|
||||
return 1 - preset.pitchMetersPerTurn / width;
|
||||
}
|
||||
|
||||
export function cameraSurveyPitchForViewport(
|
||||
heightAboveGroundMeters: number,
|
||||
requestedPitchMeters: number,
|
||||
horizontalFieldOfViewRadians: number,
|
||||
) {
|
||||
const crossTrackWidthMeters = 2 * heightAboveGroundMeters * Math.tan(horizontalFieldOfViewRadians / 2);
|
||||
return Math.min(requestedPitchMeters, crossTrackWidthMeters * CAMERA_SURVEY_CROSS_TRACK_FRACTION);
|
||||
}
|
||||
|
||||
export function cameraSurveySpiralDistance(targetRadiusMeters: number, pitchMetersPerTurn: number) {
|
||||
const angle = (Math.PI * 2 * targetRadiusMeters) / pitchMetersPerTurn;
|
||||
const radialGrowth = pitchMetersPerTurn / (Math.PI * 2);
|
||||
return (radialGrowth / 2) * (
|
||||
angle * Math.sqrt(1 + angle * angle)
|
||||
+ Math.asinh(angle)
|
||||
);
|
||||
}
|
||||
|
||||
export function cameraSurveySampleDistances(
|
||||
startDistanceMeters: number,
|
||||
spacingMeters: number,
|
||||
targetDistanceMeters: number,
|
||||
maximumSamples: number,
|
||||
) {
|
||||
if (startDistanceMeters > targetDistanceMeters || maximumSamples <= 0) return [];
|
||||
const distances: number[] = [];
|
||||
for (let index = 0; index < maximumSamples; index += 1) {
|
||||
const distanceMeters = Math.min(startDistanceMeters + index * spacingMeters, targetDistanceMeters);
|
||||
distances.push(distanceMeters);
|
||||
if (distanceMeters >= targetDistanceMeters) break;
|
||||
}
|
||||
return distances;
|
||||
}
|
||||
|
||||
export function cameraSurveyTravelSeconds(preset: CameraSurveyPreset) {
|
||||
return cameraSurveySpiralDistance(preset.targetRadiusMeters, preset.pitchMetersPerTurn)
|
||||
/ preset.speedMetersPerSecond;
|
||||
}
|
||||
Reference in New Issue
Block a user