344 lines
14 KiB
JavaScript
344 lines
14 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
import {
|
|
DEFAULT_GRID_LOD_PROFILES,
|
|
gridLodProfile,
|
|
gridShouldBeVisible,
|
|
resolveGridMode,
|
|
selectGridLod,
|
|
snapGridCenter,
|
|
} from "../apps/catalog/src/mapGridPolicy.mjs";
|
|
|
|
const settings = {
|
|
gridVisible: true,
|
|
gridLodEnabled: true,
|
|
grid3dEnabled: true,
|
|
gridGraticuleEnabled: true,
|
|
gridAutoDisableHeightKm: 10_000,
|
|
gridCenterMode: "fixed",
|
|
gridCenterLatitude: 55.7558,
|
|
gridCenterLongitude: 37.6173,
|
|
gridLodProfiles: DEFAULT_GRID_LOD_PROFILES,
|
|
};
|
|
|
|
test("canonical defaults preserve the exact effective MMAP/MOSCOWMAP five-LOD donor profile", () => {
|
|
const donorProfiles = DEFAULT_GRID_LOD_PROFILES.map(({
|
|
majorLinesEnabled: _majorLinesEnabled,
|
|
majorLabelsEnabled: _majorLabelsEnabled,
|
|
majorLineWidthMultiplier: _majorLineWidthMultiplier,
|
|
volumeEnabled: _volumeEnabled,
|
|
volumeMinimumHeightMeters: _volumeMinimumHeightMeters,
|
|
volumeMaximumHeightMeters: _volumeMaximumHeightMeters,
|
|
volumeBandHeightMeters: _volumeBandHeightMeters,
|
|
selectionFillColor: _selectionFillColor,
|
|
selectionFillOpacityPercent: _selectionFillOpacityPercent,
|
|
selectionOutlineColor: _selectionOutlineColor,
|
|
selectionOutlineWidthPx: _selectionOutlineWidthPx,
|
|
selectionOutlineOpacityPercent: _selectionOutlineOpacityPercent,
|
|
...profile
|
|
}) => profile);
|
|
assert.deepEqual(donorProfiles, [
|
|
{
|
|
maxHeightKm: 10,
|
|
stepKm: 1,
|
|
mode: "3d",
|
|
heightMeters: 300,
|
|
max3dViewAngleDegrees: 30,
|
|
tileSizeKm: 10,
|
|
radiusKm: 50,
|
|
lineDiameterMeters: 4,
|
|
lineColor: "#f5f5f5",
|
|
lineOpacity: 12,
|
|
dotsEnabled: true,
|
|
dotsDiameterMeters: 7,
|
|
dotsColor: "#ffffff",
|
|
dotsOpacity: 58,
|
|
crossesEnabled: false,
|
|
crossesLengthMeters: 60,
|
|
crossesWidthMeters: 10,
|
|
crossesColor: "#9c9c9c",
|
|
crossesOpacity: 46,
|
|
graticuleStepDegrees: 0.25,
|
|
graticuleLineWidthPx: 1,
|
|
graticuleColor: "#f5f5f5",
|
|
graticuleOpacity: 12,
|
|
},
|
|
{
|
|
maxHeightKm: 50,
|
|
stepKm: 5,
|
|
mode: "3d",
|
|
heightMeters: 500,
|
|
max3dViewAngleDegrees: 30,
|
|
tileSizeKm: 10,
|
|
radiusKm: 1_000,
|
|
lineDiameterMeters: 10,
|
|
lineColor: "#f8fbfc",
|
|
lineOpacity: 12,
|
|
dotsEnabled: true,
|
|
dotsDiameterMeters: 80,
|
|
dotsColor: "#ffffff",
|
|
dotsOpacity: 22,
|
|
crossesEnabled: false,
|
|
crossesLengthMeters: 60,
|
|
crossesWidthMeters: 10,
|
|
crossesColor: "#9c9c9c",
|
|
crossesOpacity: 46,
|
|
graticuleStepDegrees: 0.5,
|
|
graticuleLineWidthPx: 1,
|
|
graticuleColor: "#f8fbfc",
|
|
graticuleOpacity: 12,
|
|
},
|
|
{
|
|
maxHeightKm: 200,
|
|
stepKm: 25,
|
|
mode: "3d",
|
|
heightMeters: 500,
|
|
max3dViewAngleDegrees: 30,
|
|
tileSizeKm: 25,
|
|
radiusKm: 1_000,
|
|
lineDiameterMeters: 7,
|
|
lineColor: "#fafafa",
|
|
lineOpacity: 12,
|
|
dotsEnabled: true,
|
|
dotsDiameterMeters: 10,
|
|
dotsColor: "#ffffff",
|
|
dotsOpacity: 58,
|
|
crossesEnabled: false,
|
|
crossesLengthMeters: 60,
|
|
crossesWidthMeters: 10,
|
|
crossesColor: "#9c9c9c",
|
|
crossesOpacity: 46,
|
|
graticuleStepDegrees: 1,
|
|
graticuleLineWidthPx: 1,
|
|
graticuleColor: "#fafafa",
|
|
graticuleOpacity: 12,
|
|
},
|
|
{
|
|
maxHeightKm: 800,
|
|
stepKm: 50,
|
|
mode: "graticule",
|
|
heightMeters: 500,
|
|
max3dViewAngleDegrees: 30,
|
|
tileSizeKm: 10,
|
|
radiusKm: 1_000,
|
|
lineDiameterMeters: 10,
|
|
lineColor: "#fcfdfd",
|
|
lineOpacity: 12,
|
|
dotsEnabled: true,
|
|
dotsDiameterMeters: 10,
|
|
dotsColor: "#fcfdfd",
|
|
dotsOpacity: 58,
|
|
crossesEnabled: false,
|
|
crossesLengthMeters: 60,
|
|
crossesWidthMeters: 10,
|
|
crossesColor: "#9c9c9c",
|
|
crossesOpacity: 46,
|
|
graticuleStepDegrees: 2,
|
|
graticuleLineWidthPx: 1,
|
|
graticuleColor: "#fcfdfd",
|
|
graticuleOpacity: 12,
|
|
},
|
|
{
|
|
maxHeightKm: 3_000,
|
|
stepKm: 50,
|
|
mode: "graticule",
|
|
heightMeters: 500,
|
|
max3dViewAngleDegrees: 30,
|
|
tileSizeKm: 10,
|
|
radiusKm: 100,
|
|
lineDiameterMeters: 7,
|
|
lineColor: "#9c9c9c",
|
|
lineOpacity: 12,
|
|
dotsEnabled: true,
|
|
dotsDiameterMeters: 10,
|
|
dotsColor: "#9c9c9c",
|
|
dotsOpacity: 58,
|
|
crossesEnabled: false,
|
|
crossesLengthMeters: 60,
|
|
crossesWidthMeters: 10,
|
|
crossesColor: "#9c9c9c",
|
|
crossesOpacity: 46,
|
|
graticuleStepDegrees: 2,
|
|
graticuleLineWidthPx: 1,
|
|
graticuleColor: "#9c9c9c",
|
|
graticuleOpacity: 8,
|
|
},
|
|
]);
|
|
assert.deepEqual(DEFAULT_GRID_LOD_PROFILES.map((profile) => ({
|
|
majorLinesEnabled: profile.majorLinesEnabled,
|
|
majorLabelsEnabled: profile.majorLabelsEnabled,
|
|
majorLineWidthMultiplier: profile.majorLineWidthMultiplier,
|
|
volumeEnabled: profile.volumeEnabled,
|
|
volumeMinimumHeightMeters: profile.volumeMinimumHeightMeters,
|
|
volumeMaximumHeightMeters: profile.volumeMaximumHeightMeters,
|
|
volumeBandHeightMeters: profile.volumeBandHeightMeters,
|
|
selectionFillColor: profile.selectionFillColor,
|
|
selectionFillOpacityPercent: profile.selectionFillOpacityPercent,
|
|
selectionOutlineColor: profile.selectionOutlineColor,
|
|
selectionOutlineWidthPx: profile.selectionOutlineWidthPx,
|
|
selectionOutlineOpacityPercent: profile.selectionOutlineOpacityPercent,
|
|
})), [
|
|
{ majorLinesEnabled: true, majorLabelsEnabled: true, majorLineWidthMultiplier: 2.5, volumeEnabled: true, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 300, volumeBandHeightMeters: 300, selectionFillColor: "#35cfff", selectionFillOpacityPercent: 10, selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 6.25, selectionOutlineOpacityPercent: 95 },
|
|
{ majorLinesEnabled: true, majorLabelsEnabled: true, majorLineWidthMultiplier: 2.5, volumeEnabled: true, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 500, volumeBandHeightMeters: 500, selectionFillColor: "#35cfff", selectionFillOpacityPercent: 10, selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 6.25, selectionOutlineOpacityPercent: 95 },
|
|
{ majorLinesEnabled: true, majorLabelsEnabled: false, majorLineWidthMultiplier: 2, volumeEnabled: false, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 500, volumeBandHeightMeters: 500, selectionFillColor: "#35cfff", selectionFillOpacityPercent: 16, selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 5, selectionOutlineOpacityPercent: 95 },
|
|
{ majorLinesEnabled: true, majorLabelsEnabled: false, majorLineWidthMultiplier: 2, volumeEnabled: false, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 500, volumeBandHeightMeters: 500, selectionFillColor: "#35cfff", selectionFillOpacityPercent: 12, selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 3, selectionOutlineOpacityPercent: 95 },
|
|
{ majorLinesEnabled: true, majorLabelsEnabled: false, majorLineWidthMultiplier: 2, volumeEnabled: false, volumeMinimumHeightMeters: 0, volumeMaximumHeightMeters: 500, volumeBandHeightMeters: 500, selectionFillColor: "#35cfff", selectionFillOpacityPercent: 12, selectionOutlineColor: "#35cfff", selectionOutlineWidthPx: 3, selectionOutlineOpacityPercent: 95 },
|
|
]);
|
|
});
|
|
|
|
test("LOD selection keeps three spatial bands, two graticule bands and the independent 10,000 km cutoff", () => {
|
|
assert.deepEqual(
|
|
[0, 10, 10.001, 50, 50.001, 200, 200.001, 800, 800.001, 3_000, 9_999].map((height) => {
|
|
const band = selectGridLod(settings, height);
|
|
return [band.id, band.stepKm, band.mode];
|
|
}),
|
|
[
|
|
["lod-1", 1, "3d"],
|
|
["lod-1", 1, "3d"],
|
|
["lod-2", 5, "3d"],
|
|
["lod-2", 5, "3d"],
|
|
["lod-3", 25, "3d"],
|
|
["lod-3", 25, "3d"],
|
|
["lod-4", 50, "graticule"],
|
|
["lod-4", 50, "graticule"],
|
|
["lod-5", 50, "graticule"],
|
|
["lod-5", 50, "graticule"],
|
|
["lod-5", 50, "graticule"],
|
|
],
|
|
);
|
|
assert.equal(gridShouldBeVisible(settings, 10_000), true);
|
|
assert.equal(gridShouldBeVisible(settings, 10_000.001), false);
|
|
});
|
|
|
|
test("every LOD owns independent metric and angular spacing and visual fields", () => {
|
|
const profiles = DEFAULT_GRID_LOD_PROFILES.map((profile, index) => ({
|
|
...profile,
|
|
stepKm: 11 + index,
|
|
lineDiameterMeters: 21 + index,
|
|
graticuleStepDegrees: 0.125 * (index + 1),
|
|
graticuleLineWidthPx: 1 + (index % 3),
|
|
graticuleColor: `#00000${index}`,
|
|
graticuleOpacity: 30 + index,
|
|
selectionFillColor: `#12345${index}`,
|
|
selectionFillOpacityPercent: 40 + index,
|
|
selectionOutlineColor: `#abcde${index}`,
|
|
selectionOutlineWidthPx: 4 + index,
|
|
selectionOutlineOpacityPercent: 70 + index,
|
|
}));
|
|
const profile = gridLodProfile({ ...settings, gridLodProfiles: profiles }, 3);
|
|
assert.equal(profile.stepKm, 14);
|
|
assert.equal(profile.lineDiameterMeters, 24);
|
|
assert.equal(profile.graticuleStepDegrees, 0.5);
|
|
assert.equal(profile.graticuleLineWidthPx, 1);
|
|
assert.equal(profile.graticuleColor, "#000003");
|
|
assert.equal(profile.graticuleOpacity, 33);
|
|
assert.equal(profile.selectionFillColor, "#123453");
|
|
assert.equal(profile.selectionFillOpacityPercent, 43);
|
|
assert.equal(profile.selectionOutlineColor, "#abcde3");
|
|
assert.equal(profile.selectionOutlineWidthPx, 7);
|
|
assert.equal(profile.selectionOutlineOpacityPercent, 73);
|
|
});
|
|
|
|
test("selection visuals clamp to their public UI contract and a 1,000 m band is accepted client-side", () => {
|
|
const profile = gridLodProfile({
|
|
...settings,
|
|
gridLodProfiles: DEFAULT_GRID_LOD_PROFILES.map((item, index) => index === 0 ? {
|
|
...item,
|
|
volumeMaximumHeightMeters: 1_000,
|
|
volumeBandHeightMeters: 1_000,
|
|
selectionFillOpacityPercent: 110,
|
|
selectionOutlineWidthPx: 30,
|
|
selectionOutlineOpacityPercent: -5,
|
|
} : item),
|
|
}, 0);
|
|
assert.equal(profile.volumeBandHeightMeters, 1_000);
|
|
assert.equal(profile.selectionFillOpacityPercent, 100);
|
|
assert.equal(profile.selectionOutlineWidthPx, 12);
|
|
assert.equal(profile.selectionOutlineOpacityPercent, 0);
|
|
});
|
|
|
|
test("legacy flat visuals migrate while angular graticule spacing stays independent", () => {
|
|
const profile = gridLodProfile({
|
|
...settings,
|
|
gridLodProfiles: undefined,
|
|
gridRadiusKm: 1_234,
|
|
gridHeightMeters: 640,
|
|
gridLineDiameterMeters: 17,
|
|
gridColor: "#abcdef",
|
|
gridOpacity: 37,
|
|
gridDotsEnabled: false,
|
|
gridLod2StepKm: 7,
|
|
}, 1);
|
|
assert.equal(profile.maxHeightKm, 50);
|
|
assert.equal(profile.stepKm, 7);
|
|
assert.equal(profile.radiusKm, 1_234);
|
|
assert.equal(profile.heightMeters, 640);
|
|
assert.equal(profile.lineDiameterMeters, 17);
|
|
assert.equal(profile.lineColor, "#abcdef");
|
|
assert.equal(profile.lineOpacity, 37);
|
|
assert.equal(profile.dotsEnabled, false);
|
|
assert.equal(profile.graticuleStepDegrees, 0.5);
|
|
assert.equal(profile.graticuleLineWidthPx, 1);
|
|
assert.equal(profile.graticuleColor, "#abcdef");
|
|
assert.equal(profile.graticuleOpacity, 37);
|
|
});
|
|
|
|
test("LOD hysteresis holds the previous band for eight percent on either side of a threshold", () => {
|
|
assert.equal(selectGridLod(settings, 10.8, 0).id, "lod-1");
|
|
assert.equal(selectGridLod(settings, 10.8001, 0).id, "lod-2");
|
|
assert.equal(selectGridLod(settings, 9.2001, 1).id, "lod-2");
|
|
assert.equal(selectGridLod(settings, 9.1999, 1).id, "lod-1");
|
|
assert.equal(selectGridLod(settings, 864, 3).id, "lod-4");
|
|
assert.equal(selectGridLod(settings, 864.001, 3).id, "lod-5");
|
|
});
|
|
|
|
test("mode fallback preserves a usable grid when one renderer is disabled", () => {
|
|
assert.equal(resolveGridMode(settings, "3d"), "3d");
|
|
assert.equal(resolveGridMode(settings, "graticule"), "graticule");
|
|
assert.equal(resolveGridMode({ ...settings, grid3dEnabled: false }, "3d"), "graticule");
|
|
assert.equal(resolveGridMode({ ...settings, gridGraticuleEnabled: false }, "graticule"), "3d");
|
|
assert.equal(resolveGridMode({ ...settings, grid3dEnabled: false, gridGraticuleEnabled: false }, "3d"), "hidden");
|
|
});
|
|
|
|
test("grid center is fixed and camera-independent, including for legacy camera-mode payloads", () => {
|
|
const first = snapGridCenter({ latitude: 55.7558, longitude: 37.6173 }, settings);
|
|
const second = snapGridCenter({ latitude: -22, longitude: -140 }, settings);
|
|
assert.deepEqual(first, { latitude: 55.7558, longitude: 37.6173 });
|
|
assert.deepEqual(second, first);
|
|
assert.deepEqual(snapGridCenter({ latitude: 0, longitude: 0 }, {
|
|
...settings,
|
|
gridCenterMode: "camera",
|
|
gridCenterLatitude: 12.5,
|
|
gridCenterLongitude: 190,
|
|
}), { latitude: 12.5, longitude: -170 });
|
|
});
|
|
|
|
test("renderer uses fixed ENU sectors, angular graticules and a non-blank double-buffer swap", async () => {
|
|
const source = await readFile(new URL("../apps/catalog/src/CesiumMapRenderer.tsx", import.meta.url), "utf8");
|
|
assert.match(source, /class GridLayerController/);
|
|
assert.match(source, /dataSourceDisplay\.ready/);
|
|
assert.match(source, /fixedGridOrigin\(presentation\)/);
|
|
assert.match(source, /Transforms\.eastNorthUpToFixedFrame\(anchor\)/);
|
|
assert.match(source, /localSectorAt\(/);
|
|
assert.match(source, /gridController\?\.pick\(worldPosition\)/);
|
|
assert.match(source, /lod\.stepKm \* 1_000/);
|
|
assert.match(source, /lod\.graticuleStepDegrees/);
|
|
assert.match(source, /lod\.graticuleLineWidthPx/);
|
|
assert.match(source, /arcType: ArcType\.RHUMB/);
|
|
assert.match(source, /clampToGround: false/);
|
|
assert.match(source, /majorLineWidthMultiplier/);
|
|
assert.match(source, /isLocalMajorLineIndex/);
|
|
assert.match(source, /isGraticuleMajorLineValue/);
|
|
assert.match(source, /new LabelCollection\(\)/);
|
|
assert.match(source, /const maximumLabels = 48/);
|
|
assert.match(source, /materializeLocalSelection/);
|
|
assert.match(source, /localVolumeAt\(/);
|
|
assert.match(source, /focusGridSector/);
|
|
assert.match(source, /selectedGridSector/);
|
|
assert.match(source, /mountResources\(resources\)[\s\S]*?const previous = this\.current;[\s\S]*?removeResources\(previous\.resources\)/);
|
|
assert.doesNotMatch(source, /function rebuildElevatedGrid[\s\S]*?entities\.removeAll\(\)/);
|
|
assert.doesNotMatch(source, /Math\.min\(40,[\s\S]*?safeRadiusKm \/ safeStepKm/);
|
|
assert.doesNotMatch(source, /snapGridCenter\(/);
|
|
});
|