feat(foundry): add OSM cache survey presets
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { DefaultProxy, Resource } from "cesium";
|
||||
|
||||
const gatewayProxyBase = "/api/map-gateway/api/map/cache?url=";
|
||||
|
||||
function decodedGatewayTarget(resource) {
|
||||
const browserUrl = new URL(resource.url, "https://foundry.nodedc.test");
|
||||
assert.equal(browserUrl.pathname, "/api/map-gateway/api/map/cache");
|
||||
const target = browserUrl.searchParams.get("url");
|
||||
assert.ok(target);
|
||||
return new URL(target);
|
||||
}
|
||||
|
||||
test("OSM Buildings external JSON and b3dm inherit the Gateway cache route", () => {
|
||||
const root = new Resource({
|
||||
url: "https://assets.ion.cesium.com/asset_depot/96188/OpenStreetMap/tileset.json?v=19&nodedc_client_revision=2",
|
||||
proxy: new DefaultProxy(gatewayProxyBase),
|
||||
});
|
||||
const externalTileset = root.getDerivedResource({ url: "0-0-0.json" });
|
||||
const buildingTile = externalTileset.getDerivedResource({ url: "14/31780/12975.b3dm" });
|
||||
|
||||
const externalTarget = decodedGatewayTarget(externalTileset);
|
||||
const buildingTarget = decodedGatewayTarget(buildingTile);
|
||||
assert.equal(externalTarget.pathname, "/asset_depot/96188/OpenStreetMap/0-0-0.json");
|
||||
assert.equal(buildingTarget.pathname, "/asset_depot/96188/OpenStreetMap/14/31780/12975.b3dm");
|
||||
for (const target of [externalTarget, buildingTarget]) {
|
||||
assert.equal(target.searchParams.get("v"), "19");
|
||||
assert.equal(target.searchParams.get("nodedc_client_revision"), "2");
|
||||
assert.equal(target.searchParams.has("access_token"), false);
|
||||
}
|
||||
});
|
||||
|
||||
test("append-only and passthrough cache intent is inherited by Buildings children", () => {
|
||||
for (const cacheQuery of [
|
||||
"nodedc_client_revision=2",
|
||||
"nodedc_client_revision=2&nodedc_cache_mode=passthrough",
|
||||
"nodedc_client_revision=2&nodedc_cache_refresh=1",
|
||||
]) {
|
||||
const root = new Resource({
|
||||
url: `https://assets.ion.cesium.com/96188/tileset.json?${cacheQuery}`,
|
||||
proxy: new DefaultProxy(gatewayProxyBase),
|
||||
});
|
||||
const target = decodedGatewayTarget(root.getDerivedResource({ url: "root.b3dm" }));
|
||||
for (const [name, value] of new URLSearchParams(cacheQuery)) {
|
||||
assert.equal(target.searchParams.get(name), value);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import ts from "typescript";
|
||||
|
||||
const source = await readFile(new URL("../apps/catalog/src/mapCameraPresets.ts", import.meta.url), "utf8");
|
||||
const { outputText } = ts.transpileModule(source, {
|
||||
compilerOptions: {
|
||||
module: ts.ModuleKind.ES2022,
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
},
|
||||
fileName: "mapCameraPresets.ts",
|
||||
});
|
||||
const presetsModule = await import(`data:text/javascript;base64,${Buffer.from(outputText).toString("base64")}`);
|
||||
const {
|
||||
CAMERA_SURVEY_PRESETS,
|
||||
OSM_BUILDINGS_OBSERVED_BAND_COUNT,
|
||||
OSM_BUILDINGS_GEOMETRIC_ERROR_LEVELS,
|
||||
cameraSurveySpiralDistance,
|
||||
cameraSurveyPitchForViewport,
|
||||
cameraSurveySampleDistances,
|
||||
cameraSurveyTravelSeconds,
|
||||
cameraSurveyTurnOverlap,
|
||||
} = presetsModule;
|
||||
|
||||
test("OSM Buildings measured hierarchy has 16 halving geometric-error bands", () => {
|
||||
assert.equal(OSM_BUILDINGS_GEOMETRIC_ERROR_LEVELS.length, 16);
|
||||
for (let index = 1; index < OSM_BUILDINGS_GEOMETRIC_ERROR_LEVELS.length; index += 1) {
|
||||
assert.ok(Math.abs(OSM_BUILDINGS_GEOMETRIC_ERROR_LEVELS[index - 1] / 2 - OSM_BUILDINGS_GEOMETRIC_ERROR_LEVELS[index]) < 0.000_001);
|
||||
}
|
||||
});
|
||||
|
||||
test("camera survey exposes ten regional coverage profiles", () => {
|
||||
assert.equal(CAMERA_SURVEY_PRESETS.length, 10);
|
||||
assert.equal(OSM_BUILDINGS_OBSERVED_BAND_COUNT, 16);
|
||||
assert.equal(CAMERA_SURVEY_PRESETS[0].heightAboveGroundMeters, 80);
|
||||
assert.equal(CAMERA_SURVEY_PRESETS.at(-1).heightAboveGroundMeters, 40_960);
|
||||
assert.equal(new Set(CAMERA_SURVEY_PRESETS.map((preset) => preset.id)).size, CAMERA_SURVEY_PRESETS.length);
|
||||
});
|
||||
|
||||
test("height and pitch preserve overlap while speed ceilings remain monotonic", () => {
|
||||
const expectedOverlap = cameraSurveyTurnOverlap(CAMERA_SURVEY_PRESETS[0]);
|
||||
assert.ok(expectedOverlap > 0.3 && expectedOverlap < 0.32);
|
||||
|
||||
for (const [index, preset] of CAMERA_SURVEY_PRESETS.entries()) {
|
||||
assert.equal(preset.pitchMetersPerTurn, preset.heightAboveGroundMeters * 0.8);
|
||||
assert.equal(preset.targetRadiusMeters, 25_000);
|
||||
assert.ok(Math.abs(cameraSurveyTurnOverlap(preset) - expectedOverlap) < 1e-12);
|
||||
if (index === 0) continue;
|
||||
assert.equal(preset.heightAboveGroundMeters, CAMERA_SURVEY_PRESETS[index - 1].heightAboveGroundMeters * 2);
|
||||
assert.ok(preset.speedMetersPerSecond >= CAMERA_SURVEY_PRESETS[index - 1].speedMetersPerSecond);
|
||||
assert.ok(cameraSurveyTravelSeconds(preset) < cameraSurveyTravelSeconds(CAMERA_SURVEY_PRESETS[index - 1]));
|
||||
}
|
||||
});
|
||||
|
||||
test("every profile is a finite city-scale pass with an honest no-wait ETA", () => {
|
||||
const first = CAMERA_SURVEY_PRESETS[0];
|
||||
const distance = cameraSurveySpiralDistance(first.targetRadiusMeters, first.pitchMetersPerTurn);
|
||||
assert.ok(distance > 30_000_000 && distance < 31_000_000);
|
||||
assert.ok(cameraSurveyTravelSeconds(first) > 32 * 3_600 && cameraSurveyTravelSeconds(first) < 34 * 3_600);
|
||||
assert.ok(Number.isFinite(cameraSurveyTravelSeconds(CAMERA_SURVEY_PRESETS.at(-1))));
|
||||
});
|
||||
|
||||
test("narrow viewports reduce turn pitch instead of opening coverage gaps", () => {
|
||||
const preset = CAMERA_SURVEY_PRESETS[0];
|
||||
const wideFov = Math.PI / 3;
|
||||
const widePitch = cameraSurveyPitchForViewport(preset.heightAboveGroundMeters, preset.pitchMetersPerTurn, wideFov);
|
||||
const wideWidth = 2 * preset.heightAboveGroundMeters * Math.tan(wideFov / 2);
|
||||
const narrowFov = Math.PI / 6;
|
||||
const narrowPitch = cameraSurveyPitchForViewport(preset.heightAboveGroundMeters, preset.pitchMetersPerTurn, narrowFov);
|
||||
const narrowWidth = 2 * preset.heightAboveGroundMeters * Math.tan(narrowFov / 2);
|
||||
assert.ok(widePitch <= preset.pitchMetersPerTurn);
|
||||
assert.ok(Math.abs(1 - widePitch / wideWidth - 0.31) < 1e-12);
|
||||
assert.ok(narrowPitch < preset.pitchMetersPerTurn);
|
||||
assert.ok(Math.abs(1 - narrowPitch / narrowWidth - 0.31) < 1e-12);
|
||||
});
|
||||
|
||||
test("terrain prefetch is clipped to the selected survey boundary", () => {
|
||||
const distances = cameraSurveySampleDistances(0, 166.7, 1_000, 120);
|
||||
assert.equal(distances[0], 0);
|
||||
assert.equal(distances.at(-1), 1_000);
|
||||
assert.ok(distances.length < 120);
|
||||
assert.ok(distances.every((distance) => distance <= 1_000));
|
||||
assert.deepEqual(cameraSurveySampleDistances(1_001, 10, 1_000, 120), []);
|
||||
});
|
||||
|
||||
test("all operational preset values remain inside renderer safety limits", () => {
|
||||
for (const preset of CAMERA_SURVEY_PRESETS) {
|
||||
assert.ok(preset.heightAboveGroundMeters >= 10 && preset.heightAboveGroundMeters <= 100_000);
|
||||
assert.ok(preset.speedMetersPerSecond >= 1 && preset.speedMetersPerSecond <= 5_000);
|
||||
assert.ok(preset.pitchMetersPerTurn >= 20 && preset.pitchMetersPerTurn <= 100_000);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user