|
|
|
@@ -22,6 +22,17 @@ import {
|
|
|
|
|
} from "./runtime.js";
|
|
|
|
|
|
|
|
|
|
type CesiumNamespace = typeof CesiumModule;
|
|
|
|
|
type RendererRuntime = {
|
|
|
|
|
Cesium: CesiumNamespace;
|
|
|
|
|
viewer: CesiumModule.Viewer;
|
|
|
|
|
ellipsoidTerrain: CesiumModule.EllipsoidTerrainProvider;
|
|
|
|
|
worldTerrain: CesiumModule.CesiumTerrainProvider | null;
|
|
|
|
|
imageryLayer: CesiumModule.ImageryLayer | null;
|
|
|
|
|
buildings: CesiumModule.Cesium3DTileset | null;
|
|
|
|
|
grid: CesiumModule.CustomDataSource;
|
|
|
|
|
rebuildGrid: () => void;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function CesiumMapRenderer({
|
|
|
|
|
runtimeConfigUrl,
|
|
|
|
|
camera,
|
|
|
|
@@ -36,9 +47,23 @@ export function CesiumMapRenderer({
|
|
|
|
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
const stateCallbackRef = useRef(onRuntimeStateChange);
|
|
|
|
|
const cameraCallbackRef = useRef(onCameraChange);
|
|
|
|
|
const runtimeRef = useRef<RendererRuntime | null>(null);
|
|
|
|
|
const settingsRef = useRef(settings);
|
|
|
|
|
const layersRef = useRef(layers);
|
|
|
|
|
|
|
|
|
|
stateCallbackRef.current = onRuntimeStateChange;
|
|
|
|
|
cameraCallbackRef.current = onCameraChange;
|
|
|
|
|
settingsRef.current = settings;
|
|
|
|
|
layersRef.current = layers;
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const runtime = runtimeRef.current;
|
|
|
|
|
if (!runtime) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
applyPresentation(runtime, settings, layers);
|
|
|
|
|
runtime.rebuildGrid();
|
|
|
|
|
}, [settings, layers]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const container = containerRef.current;
|
|
|
|
@@ -48,6 +73,8 @@ export function CesiumMapRenderer({
|
|
|
|
|
const abortController = new AbortController();
|
|
|
|
|
let disposed = false;
|
|
|
|
|
let viewer: CesiumModule.Viewer | null = null;
|
|
|
|
|
let removeCameraListener: (() => void) | undefined;
|
|
|
|
|
let removeRenderListener: (() => void) | undefined;
|
|
|
|
|
let runtimeState = initialMapRuntimeState();
|
|
|
|
|
|
|
|
|
|
const publish = (next: MapRuntimeState) => {
|
|
|
|
@@ -68,10 +95,39 @@ export function CesiumMapRenderer({
|
|
|
|
|
if (disposed) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
viewer = createViewer(Cesium, container, settings);
|
|
|
|
|
const creditContainer = configuration.sandbox?.hide_credit_overlay
|
|
|
|
|
? createSandboxCreditContainer(container)
|
|
|
|
|
: undefined;
|
|
|
|
|
viewer = createViewer(Cesium, container, creditContainer);
|
|
|
|
|
const ellipsoidTerrain = new Cesium.EllipsoidTerrainProvider();
|
|
|
|
|
const grid = new Cesium.CustomDataSource("nodedc-map-grid");
|
|
|
|
|
void viewer.dataSources.add(grid);
|
|
|
|
|
const runtime: RendererRuntime = {
|
|
|
|
|
Cesium,
|
|
|
|
|
viewer,
|
|
|
|
|
ellipsoidTerrain,
|
|
|
|
|
worldTerrain: null,
|
|
|
|
|
imageryLayer: null,
|
|
|
|
|
buildings: null,
|
|
|
|
|
grid,
|
|
|
|
|
rebuildGrid: () => undefined,
|
|
|
|
|
};
|
|
|
|
|
runtime.rebuildGrid = () => {
|
|
|
|
|
rebuildElevatedGrid(runtime, settingsRef.current, layersRef.current.grid);
|
|
|
|
|
};
|
|
|
|
|
runtimeRef.current = runtime;
|
|
|
|
|
viewer.terrainProvider = ellipsoidTerrain;
|
|
|
|
|
viewer.scene.globe.depthTestAgainstTerrain = true;
|
|
|
|
|
applyPresentation(runtime, settingsRef.current, layersRef.current);
|
|
|
|
|
applyCamera(Cesium, viewer, camera);
|
|
|
|
|
const removeCameraListener = bindCamera(Cesium, viewer, cameraCallbackRef);
|
|
|
|
|
const removeRenderListener = bindRenderRecovery(viewer, publish);
|
|
|
|
|
runtime.rebuildGrid();
|
|
|
|
|
removeCameraListener = bindCamera(
|
|
|
|
|
Cesium,
|
|
|
|
|
viewer,
|
|
|
|
|
cameraCallbackRef,
|
|
|
|
|
runtime.rebuildGrid,
|
|
|
|
|
);
|
|
|
|
|
removeRenderListener = bindRenderRecovery(viewer, publish);
|
|
|
|
|
|
|
|
|
|
const providerJobs = (["imagery", "terrain", "buildings"] as const).map(
|
|
|
|
|
async (providerId) => {
|
|
|
|
@@ -94,15 +150,15 @@ export function CesiumMapRenderer({
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await attachProvider(
|
|
|
|
|
Cesium,
|
|
|
|
|
viewer,
|
|
|
|
|
runtime,
|
|
|
|
|
configuration.gateway.cache_proxy_prefix,
|
|
|
|
|
endpoint,
|
|
|
|
|
providerId,
|
|
|
|
|
cacheIntent,
|
|
|
|
|
false,
|
|
|
|
|
settings,
|
|
|
|
|
);
|
|
|
|
|
addAttributions(Cesium, viewer, endpoint);
|
|
|
|
|
applyPresentation(runtime, settingsRef.current, layersRef.current);
|
|
|
|
|
publish(
|
|
|
|
|
withProviderState(runtimeState, providerId, {
|
|
|
|
|
phase: "ready",
|
|
|
|
@@ -123,29 +179,9 @@ export function CesiumMapRenderer({
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
await Promise.allSettled(providerJobs);
|
|
|
|
|
if (!disposed && viewer && layers.grid) {
|
|
|
|
|
viewer.imageryLayers.addImageryProvider(
|
|
|
|
|
new Cesium.GridImageryProvider({
|
|
|
|
|
cells: 16,
|
|
|
|
|
color: tokenColor(
|
|
|
|
|
Cesium,
|
|
|
|
|
container,
|
|
|
|
|
"--nodedc-text-secondary",
|
|
|
|
|
"rgba(247, 248, 244, 0.72)",
|
|
|
|
|
),
|
|
|
|
|
glowColor: tokenColor(
|
|
|
|
|
Cesium,
|
|
|
|
|
container,
|
|
|
|
|
"--nodedc-text-muted",
|
|
|
|
|
"rgba(247, 248, 244, 0.48)",
|
|
|
|
|
),
|
|
|
|
|
backgroundColor: Cesium.Color.TRANSPARENT,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (disposed) {
|
|
|
|
|
removeCameraListener();
|
|
|
|
|
removeRenderListener();
|
|
|
|
|
removeCameraListener?.();
|
|
|
|
|
removeRenderListener?.();
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (!abortController.signal.aborted) {
|
|
|
|
@@ -166,6 +202,9 @@ export function CesiumMapRenderer({
|
|
|
|
|
return () => {
|
|
|
|
|
disposed = true;
|
|
|
|
|
abortController.abort();
|
|
|
|
|
removeCameraListener?.();
|
|
|
|
|
removeRenderListener?.();
|
|
|
|
|
runtimeRef.current = null;
|
|
|
|
|
viewer?.destroy();
|
|
|
|
|
viewer = null;
|
|
|
|
|
container.replaceChildren();
|
|
|
|
@@ -176,15 +215,8 @@ export function CesiumMapRenderer({
|
|
|
|
|
layers.imagery,
|
|
|
|
|
layers.terrain,
|
|
|
|
|
layers.buildings,
|
|
|
|
|
layers.grid,
|
|
|
|
|
cacheIntent.enabled,
|
|
|
|
|
cacheIntent.no_overwrite,
|
|
|
|
|
settings.atmosphere_enabled,
|
|
|
|
|
settings.lighting_enabled,
|
|
|
|
|
settings.monochrome_enabled,
|
|
|
|
|
settings.terrain_exaggeration,
|
|
|
|
|
settings.buildings_maximum_screen_space_error,
|
|
|
|
|
settings.camera_animation_enabled,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return <div ref={containerRef} className={className} data-nodedc-map-renderer="cesium" />;
|
|
|
|
@@ -193,7 +225,7 @@ export function CesiumMapRenderer({
|
|
|
|
|
function createViewer(
|
|
|
|
|
Cesium: CesiumNamespace,
|
|
|
|
|
container: HTMLElement,
|
|
|
|
|
settings: CesiumMapRendererProps["settings"],
|
|
|
|
|
creditContainer?: HTMLElement,
|
|
|
|
|
): CesiumModule.Viewer {
|
|
|
|
|
const viewer = new Cesium.Viewer(container, {
|
|
|
|
|
animation: false,
|
|
|
|
@@ -210,16 +242,22 @@ function createViewer(
|
|
|
|
|
terrainProvider: new Cesium.EllipsoidTerrainProvider(),
|
|
|
|
|
requestRenderMode: true,
|
|
|
|
|
maximumRenderTimeChange: Number.POSITIVE_INFINITY,
|
|
|
|
|
showRenderLoopErrors: false,
|
|
|
|
|
creditContainer,
|
|
|
|
|
});
|
|
|
|
|
viewer.scene.globe.enableLighting = settings.lighting_enabled;
|
|
|
|
|
if (viewer.scene.skyAtmosphere) {
|
|
|
|
|
viewer.scene.skyAtmosphere.show = settings.atmosphere_enabled;
|
|
|
|
|
}
|
|
|
|
|
viewer.scene.verticalExaggeration = settings.terrain_exaggeration;
|
|
|
|
|
viewer.scene.rethrowRenderErrors = false;
|
|
|
|
|
return viewer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createSandboxCreditContainer(container: HTMLElement): HTMLDivElement {
|
|
|
|
|
const credits = document.createElement("div");
|
|
|
|
|
credits.hidden = true;
|
|
|
|
|
credits.setAttribute("aria-hidden", "true");
|
|
|
|
|
credits.dataset.nodedcSandboxCredits = "suppressed";
|
|
|
|
|
container.appendChild(credits);
|
|
|
|
|
return credits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fetchProviderEndpoint(
|
|
|
|
|
configuration: Awaited<ReturnType<typeof fetchMapRuntimeConfiguration>>,
|
|
|
|
|
providerId: MapProviderId,
|
|
|
|
@@ -234,15 +272,14 @@ async function fetchProviderEndpoint(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function attachProvider(
|
|
|
|
|
Cesium: CesiumNamespace,
|
|
|
|
|
viewer: CesiumModule.Viewer,
|
|
|
|
|
runtime: RendererRuntime,
|
|
|
|
|
cacheProxyPrefix: string,
|
|
|
|
|
endpoint: ProviderEndpoint,
|
|
|
|
|
providerId: MapProviderId,
|
|
|
|
|
cacheIntent: CesiumMapRendererProps["cacheIntent"],
|
|
|
|
|
refresh: boolean,
|
|
|
|
|
settings: CesiumMapRendererProps["settings"],
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const { Cesium, viewer } = runtime;
|
|
|
|
|
const publicUrl =
|
|
|
|
|
providerId === "imagery" ? endpoint.options?.url : endpoint.url;
|
|
|
|
|
if (!publicUrl) {
|
|
|
|
@@ -261,28 +298,39 @@ async function attachProvider(
|
|
|
|
|
mapStyle: bingMapStyle(Cesium, endpoint.options?.mapStyle),
|
|
|
|
|
tileProtocol: "https",
|
|
|
|
|
});
|
|
|
|
|
const layer = viewer.imageryLayers.addImageryProvider(provider);
|
|
|
|
|
if (settings.monochrome_enabled) {
|
|
|
|
|
layer.saturation = 0;
|
|
|
|
|
}
|
|
|
|
|
runtime.imageryLayer = viewer.imageryLayers.addImageryProvider(provider);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (providerId === "terrain") {
|
|
|
|
|
viewer.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(resource);
|
|
|
|
|
runtime.worldTerrain = await Cesium.CesiumTerrainProvider.fromUrl(resource);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const tileset = await Cesium.Cesium3DTileset.fromUrl(resource, {
|
|
|
|
|
maximumScreenSpaceError: settings.buildings_maximum_screen_space_error,
|
|
|
|
|
});
|
|
|
|
|
viewer.scene.primitives.add(tileset);
|
|
|
|
|
runtime.buildings = await Cesium.Cesium3DTileset.fromUrl(resource);
|
|
|
|
|
viewer.scene.primitives.add(runtime.buildings);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addAttributions(
|
|
|
|
|
Cesium: CesiumNamespace,
|
|
|
|
|
viewer: CesiumModule.Viewer,
|
|
|
|
|
endpoint: ProviderEndpoint,
|
|
|
|
|
): void {
|
|
|
|
|
for (const attribution of endpoint.attributions) {
|
|
|
|
|
if (attribution.html) {
|
|
|
|
|
viewer.creditDisplay.addStaticCredit(
|
|
|
|
|
new Cesium.Credit(attribution.html, attribution.collapsible),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function bindCamera(
|
|
|
|
|
Cesium: CesiumNamespace,
|
|
|
|
|
viewer: CesiumModule.Viewer,
|
|
|
|
|
callbackRef: { current: ((camera: MapCamera) => void) | undefined },
|
|
|
|
|
onMoveEnd: () => void,
|
|
|
|
|
): () => void {
|
|
|
|
|
const listener = () => {
|
|
|
|
|
onMoveEnd();
|
|
|
|
|
const position = viewer.camera.positionCartographic;
|
|
|
|
|
callbackRef.current?.({
|
|
|
|
|
longitude: Cesium.Math.toDegrees(position.longitude),
|
|
|
|
@@ -355,16 +403,229 @@ function bingMapStyle(
|
|
|
|
|
return Cesium.BingMapsStyle.AERIAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyPresentation(
|
|
|
|
|
runtime: RendererRuntime,
|
|
|
|
|
settings: CesiumMapRendererProps["settings"],
|
|
|
|
|
layers: CesiumMapRendererProps["layers"],
|
|
|
|
|
): void {
|
|
|
|
|
const { Cesium, viewer, imageryLayer, buildings } = runtime;
|
|
|
|
|
if (imageryLayer) {
|
|
|
|
|
imageryLayer.show = layers.imagery && !settings.monochrome_enabled;
|
|
|
|
|
imageryLayer.brightness = settings.imagery_brightness / 100;
|
|
|
|
|
imageryLayer.contrast = settings.imagery_contrast / 100;
|
|
|
|
|
imageryLayer.saturation = settings.imagery_saturation / 100;
|
|
|
|
|
imageryLayer.gamma = settings.imagery_gamma / 100;
|
|
|
|
|
imageryLayer.hue = Cesium.Math.toRadians(settings.imagery_hue);
|
|
|
|
|
imageryLayer.alpha = settings.imagery_alpha / 100;
|
|
|
|
|
}
|
|
|
|
|
if (buildings) {
|
|
|
|
|
buildings.show = layers.buildings;
|
|
|
|
|
buildings.maximumScreenSpaceError =
|
|
|
|
|
settings.buildings_maximum_screen_space_error;
|
|
|
|
|
buildings.style = new Cesium.Cesium3DTileStyle({
|
|
|
|
|
color: `color('${settings.buildings_color}', ${settings.buildings_opacity})`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
viewer.terrainProvider =
|
|
|
|
|
layers.terrain && runtime.worldTerrain
|
|
|
|
|
? runtime.worldTerrain
|
|
|
|
|
: runtime.ellipsoidTerrain;
|
|
|
|
|
viewer.scene.globe.show = true;
|
|
|
|
|
viewer.scene.globe.baseColor = cssColor(
|
|
|
|
|
Cesium,
|
|
|
|
|
settings.monochrome_enabled
|
|
|
|
|
? settings.monochrome_color
|
|
|
|
|
: settings.globe_color,
|
|
|
|
|
"#15151b",
|
|
|
|
|
);
|
|
|
|
|
viewer.scene.globe.enableLighting = settings.sun_enabled;
|
|
|
|
|
viewer.scene.verticalExaggeration = clamp(
|
|
|
|
|
settings.terrain_exaggeration,
|
|
|
|
|
0.25,
|
|
|
|
|
3,
|
|
|
|
|
);
|
|
|
|
|
viewer.scene.backgroundColor = cssColor(
|
|
|
|
|
Cesium,
|
|
|
|
|
settings.background_color,
|
|
|
|
|
"#08090d",
|
|
|
|
|
);
|
|
|
|
|
viewer.scene.fog.enabled = settings.fog_enabled;
|
|
|
|
|
viewer.scene.fog.density = clamp(settings.fog_density / 10_000, 0, 0.01);
|
|
|
|
|
const atmosphere = viewer.scene.skyAtmosphere;
|
|
|
|
|
if (atmosphere) {
|
|
|
|
|
atmosphere.show = settings.atmosphere_enabled;
|
|
|
|
|
atmosphere.hueShift = clamp(settings.atmosphere_hue / 100, -1, 1);
|
|
|
|
|
atmosphere.saturationShift = clamp(
|
|
|
|
|
settings.atmosphere_saturation / 100,
|
|
|
|
|
-1,
|
|
|
|
|
1,
|
|
|
|
|
);
|
|
|
|
|
atmosphere.brightnessShift = clamp(
|
|
|
|
|
settings.atmosphere_brightness / 100,
|
|
|
|
|
-1,
|
|
|
|
|
1,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
viewer.shadows = settings.shadows_enabled;
|
|
|
|
|
const sunDate = Cesium.JulianDate.toDate(Cesium.JulianDate.now());
|
|
|
|
|
sunDate.setUTCHours(
|
|
|
|
|
clamp(Math.round(settings.sun_hour), 0, 24),
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
);
|
|
|
|
|
viewer.clock.currentTime = Cesium.JulianDate.fromDate(sunDate);
|
|
|
|
|
viewer.clock.shouldAnimate = false;
|
|
|
|
|
const sun = new Cesium.SunLight();
|
|
|
|
|
(sun as CesiumModule.SunLight & { intensity?: number }).intensity = clamp(
|
|
|
|
|
settings.sun_intensity / 100,
|
|
|
|
|
0,
|
|
|
|
|
2,
|
|
|
|
|
);
|
|
|
|
|
viewer.scene.light = sun;
|
|
|
|
|
viewer.scene.requestRender();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rebuildElevatedGrid(
|
|
|
|
|
runtime: RendererRuntime,
|
|
|
|
|
settings: CesiumMapRendererProps["settings"],
|
|
|
|
|
visible: boolean,
|
|
|
|
|
): void {
|
|
|
|
|
const { Cesium, viewer, grid } = runtime;
|
|
|
|
|
const entities = grid.entities;
|
|
|
|
|
entities.removeAll();
|
|
|
|
|
if (!visible) {
|
|
|
|
|
viewer.scene.requestRender();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cameraHeightKm =
|
|
|
|
|
Math.max(0, Number(viewer.camera.positionCartographic?.height || 0)) / 1000;
|
|
|
|
|
const stepKm =
|
|
|
|
|
!settings.grid_lod_enabled ||
|
|
|
|
|
cameraHeightKm <= settings.grid_lod_1_max_height_km
|
|
|
|
|
? settings.grid_lod_1_step_km
|
|
|
|
|
: cameraHeightKm <= settings.grid_lod_2_max_height_km
|
|
|
|
|
? settings.grid_lod_2_step_km
|
|
|
|
|
: settings.grid_lod_3_step_km;
|
|
|
|
|
const safeStepKm = clamp(stepKm, 0.25, 100);
|
|
|
|
|
const safeRadiusKm = clamp(settings.grid_radius_km, safeStepKm, 150);
|
|
|
|
|
const stepsPerSide = Math.min(
|
|
|
|
|
32,
|
|
|
|
|
Math.max(1, Math.floor(safeRadiusKm / safeStepKm)),
|
|
|
|
|
);
|
|
|
|
|
const cameraPosition = viewer.camera.positionCartographic;
|
|
|
|
|
const latitude = cameraPosition
|
|
|
|
|
? Cesium.Math.toDegrees(cameraPosition.latitude)
|
|
|
|
|
: 55.751244;
|
|
|
|
|
const longitude = cameraPosition
|
|
|
|
|
? Cesium.Math.toDegrees(cameraPosition.longitude)
|
|
|
|
|
: 37.618423;
|
|
|
|
|
const metersPerLatitudeDegree = 110_574;
|
|
|
|
|
const metersPerLongitudeDegree = Math.max(
|
|
|
|
|
1,
|
|
|
|
|
111_320 * Math.cos(Cesium.Math.toRadians(latitude)),
|
|
|
|
|
);
|
|
|
|
|
const stepMeters = safeStepKm * 1000;
|
|
|
|
|
const radiusMeters = stepsPerSide * stepMeters;
|
|
|
|
|
const deltaLatitude = stepMeters / metersPerLatitudeDegree;
|
|
|
|
|
const deltaLongitude = stepMeters / metersPerLongitudeDegree;
|
|
|
|
|
const radiusLatitude = radiusMeters / metersPerLatitudeDegree;
|
|
|
|
|
const radiusLongitude = radiusMeters / metersPerLongitudeDegree;
|
|
|
|
|
const lineColor = cssColor(Cesium, settings.grid_color, "#f5f5f5").withAlpha(
|
|
|
|
|
clamp(settings.grid_opacity / 100, 0, 1),
|
|
|
|
|
);
|
|
|
|
|
const dotColor = cssColor(Cesium, settings.grid_dots_color, "#ffffff").withAlpha(
|
|
|
|
|
clamp(settings.grid_dots_opacity / 100, 0, 1),
|
|
|
|
|
);
|
|
|
|
|
const elevation = Math.max(0, settings.grid_height_meters);
|
|
|
|
|
|
|
|
|
|
for (let index = -stepsPerSide; index <= stepsPerSide; index += 1) {
|
|
|
|
|
const nextLatitude = latitude + index * deltaLatitude;
|
|
|
|
|
const nextLongitude = longitude + index * deltaLongitude;
|
|
|
|
|
entities.add({
|
|
|
|
|
polyline: {
|
|
|
|
|
positions: [
|
|
|
|
|
Cesium.Cartesian3.fromDegrees(
|
|
|
|
|
longitude - radiusLongitude,
|
|
|
|
|
nextLatitude,
|
|
|
|
|
elevation,
|
|
|
|
|
),
|
|
|
|
|
Cesium.Cartesian3.fromDegrees(
|
|
|
|
|
longitude + radiusLongitude,
|
|
|
|
|
nextLatitude,
|
|
|
|
|
elevation,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
width: clamp(settings.grid_line_width, 1, 8),
|
|
|
|
|
material: lineColor,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
entities.add({
|
|
|
|
|
polyline: {
|
|
|
|
|
positions: [
|
|
|
|
|
Cesium.Cartesian3.fromDegrees(
|
|
|
|
|
nextLongitude,
|
|
|
|
|
latitude - radiusLatitude,
|
|
|
|
|
elevation,
|
|
|
|
|
),
|
|
|
|
|
Cesium.Cartesian3.fromDegrees(
|
|
|
|
|
nextLongitude,
|
|
|
|
|
latitude + radiusLatitude,
|
|
|
|
|
elevation,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
width: clamp(settings.grid_line_width, 1, 8),
|
|
|
|
|
material: lineColor,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (settings.grid_dots_enabled) {
|
|
|
|
|
const dotStride = Math.max(
|
|
|
|
|
1,
|
|
|
|
|
Math.ceil((stepsPerSide * 2 + 1) / 25),
|
|
|
|
|
);
|
|
|
|
|
for (let row = -stepsPerSide; row <= stepsPerSide; row += dotStride) {
|
|
|
|
|
for (
|
|
|
|
|
let column = -stepsPerSide;
|
|
|
|
|
column <= stepsPerSide;
|
|
|
|
|
column += dotStride
|
|
|
|
|
) {
|
|
|
|
|
entities.add({
|
|
|
|
|
position: Cesium.Cartesian3.fromDegrees(
|
|
|
|
|
longitude + column * deltaLongitude,
|
|
|
|
|
latitude + row * deltaLatitude,
|
|
|
|
|
elevation,
|
|
|
|
|
),
|
|
|
|
|
point: {
|
|
|
|
|
pixelSize: clamp(settings.grid_dots_size, 2, 28),
|
|
|
|
|
color: dotColor,
|
|
|
|
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
viewer.scene.requestRender();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function safeErrorCode(error: unknown): string {
|
|
|
|
|
return error instanceof MapRuntimeError ? error.code : "map_provider_unavailable";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function tokenColor(
|
|
|
|
|
function cssColor(
|
|
|
|
|
Cesium: CesiumNamespace,
|
|
|
|
|
container: HTMLElement,
|
|
|
|
|
token: string,
|
|
|
|
|
value: string,
|
|
|
|
|
fallback: string,
|
|
|
|
|
): CesiumModule.Color {
|
|
|
|
|
const value = getComputedStyle(container).getPropertyValue(token).trim();
|
|
|
|
|
return Cesium.Color.fromCssColorString(value || fallback) ?? Cesium.Color.WHITE;
|
|
|
|
|
return (
|
|
|
|
|
Cesium.Color.fromCssColorString(value) ??
|
|
|
|
|
Cesium.Color.fromCssColorString(fallback) ??
|
|
|
|
|
Cesium.Color.WHITE
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clamp(value: number, minimum: number, maximum: number): number {
|
|
|
|
|
return Math.min(maximum, Math.max(minimum, value));
|
|
|
|
|
}
|
|
|
|
|