124 lines
6.0 KiB
TypeScript
124 lines
6.0 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|