release: bump version to 0.46.2
This commit is contained in:
@@ -28,7 +28,7 @@ const _lpCameraToPoint = new THREE.Vector3();
|
||||
const LANDING_POINT_SPRITE_HEIGHT = 3;
|
||||
const LANDING_POINT_SPRITE_ASPECT = 1;
|
||||
const LANDING_POINT_SIZE_REFERENCE_FOV = 75;
|
||||
const LANDING_POINT_SIZE_SCALE_MIN = 0.36;
|
||||
const LANDING_POINT_SIZE_SCALE_MIN = 0.16;
|
||||
const LANDING_POINT_SIZE_SCALE_MAX = 3;
|
||||
const LANDING_POINT_ATLAS_CELL_SIZE = 128;
|
||||
let landingPointTexture = null;
|
||||
|
||||
@@ -232,7 +232,7 @@ export const VESSEL_CONFIG = {
|
||||
max: 2.4,
|
||||
},
|
||||
track: {
|
||||
altitudeOffset: 0.22,
|
||||
altitudeOffset: 0.2,
|
||||
color: 0x7dd3fc,
|
||||
opacity: 0.82,
|
||||
},
|
||||
|
||||
108
frontend/public/earth/js/controls.js
vendored
108
frontend/public/earth/js/controls.js
vendored
@@ -19,6 +19,7 @@ import {
|
||||
import {
|
||||
toggleTerrain,
|
||||
setDayNightEnabled,
|
||||
toggleClouds,
|
||||
toggleGridLines,
|
||||
getShowGridLines,
|
||||
} from "./earth.js";
|
||||
@@ -53,7 +54,7 @@ import {
|
||||
} from "./satellites.js";
|
||||
import { getShowCables } from "./cables.js";
|
||||
import { toggleBGP, getShowBGP, getBGPCount } from "./bgp.js";
|
||||
import { getShowCountryBoundaries } from "./country-boundaries.js";
|
||||
import { getShowCountryBoundaries, toggleCountryBoundaries } from "./country-boundaries.js";
|
||||
import {
|
||||
toggleComputeCenters,
|
||||
getShowComputeCenters,
|
||||
@@ -136,6 +137,7 @@ let focusViewAnimationToken = 0;
|
||||
let earthSettingsDefaults = null;
|
||||
let lastZoomStatusUpdateTime = 0;
|
||||
let earthSettingsState = null;
|
||||
let deferredLayerVisibilitySettings = null;
|
||||
let layerRegistry = new Map();
|
||||
let layerPanelInitialized = false;
|
||||
let layoutMode = "desktop";
|
||||
@@ -269,7 +271,7 @@ function closeTransientMobileOverlays({ except = null } = {}) {
|
||||
setMobileDrawerOpen("layer-toggles", false);
|
||||
}
|
||||
|
||||
if (except !== "media" && isTVPanelVisible()) {
|
||||
if (except !== "media" && except !== "search" && isTVPanelVisible()) {
|
||||
setTVPanelVisible(false);
|
||||
}
|
||||
}
|
||||
@@ -670,6 +672,14 @@ function shouldIncludeLayerInStartupLoad(definition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const persistedVisible = getPersistedLayerVisibilityOverride(definition.id);
|
||||
if (typeof persistedVisible === "boolean") {
|
||||
if (definition.startupMode === "preload" && definition.startupAlwaysLoad) {
|
||||
return true;
|
||||
}
|
||||
return persistedVisible;
|
||||
}
|
||||
|
||||
if (definition.startupMode === "preload") {
|
||||
return true;
|
||||
}
|
||||
@@ -677,6 +687,14 @@ function shouldIncludeLayerInStartupLoad(definition) {
|
||||
return Boolean(definition?.getVisible?.());
|
||||
}
|
||||
|
||||
function getPersistedLayerVisibilityOverride(layerId) {
|
||||
if (!layerId) return null;
|
||||
const layerVisibility =
|
||||
deferredLayerVisibilitySettings || earthSettingsState?.shared?.layerVisibility;
|
||||
const persistedVisible = layerVisibility?.[layerId];
|
||||
return typeof persistedVisible === "boolean" ? persistedVisible : null;
|
||||
}
|
||||
|
||||
function clampEarthZoomLevel(nextZoom) {
|
||||
const parsedZoom = Number.parseFloat(nextZoom);
|
||||
if (!Number.isFinite(parsedZoom)) {
|
||||
@@ -1127,7 +1145,7 @@ function setDefaultEarthZoom(nextZoom, { persist = true, applyToCurrentView = tr
|
||||
return defaultEarthZoom;
|
||||
}
|
||||
|
||||
async function applyEarthSettings(settings) {
|
||||
async function applyEarthSettings(settings, { applyLayers = true } = {}) {
|
||||
if (!settings) return;
|
||||
earthSettingsState = cloneEarthSettings(settings);
|
||||
|
||||
@@ -1161,12 +1179,31 @@ async function applyEarthSettings(settings) {
|
||||
applyToCurrentView: true,
|
||||
});
|
||||
|
||||
if (!applyLayers) {
|
||||
const layerVisibility = { ...(settings.shared.layerVisibility || {}) };
|
||||
applyImmediateLayerVisibilityHints(layerVisibility);
|
||||
deferredLayerVisibilitySettings = layerVisibility;
|
||||
return;
|
||||
}
|
||||
|
||||
deferredLayerVisibilitySettings = null;
|
||||
await applyLayerVisibilitySettings(settings.shared.layerVisibility, {
|
||||
persist: false,
|
||||
silent: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function applyDeferredLayerVisibilitySettings(options = {}) {
|
||||
const layerVisibility = deferredLayerVisibilitySettings;
|
||||
deferredLayerVisibilitySettings = null;
|
||||
if (!layerVisibility) return;
|
||||
await applyLayerVisibilitySettings(layerVisibility, {
|
||||
persist: false,
|
||||
silent: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
function resetEarthSettings() {
|
||||
const defaults = cloneEarthSettings(captureEarthSettingsDefaults());
|
||||
earthSettingsState = cloneEarthSettings(defaults);
|
||||
@@ -1307,8 +1344,15 @@ async function setCountryBoundariesLayerEnabled(button, enabled, { persist = tru
|
||||
}
|
||||
}
|
||||
|
||||
function setHighResTextureLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
|
||||
setHighResTextureEnabled(enabled, { suppressStatus: silent });
|
||||
async function setHighResTextureLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
|
||||
if (enabled) {
|
||||
setLayerButtonState(button, {
|
||||
active: false,
|
||||
loading: true,
|
||||
tooltip: "高清材质加载中...",
|
||||
});
|
||||
}
|
||||
await setHighResTextureEnabled(enabled, { suppressStatus: silent });
|
||||
setLayerButtonState(button, {
|
||||
active: enabled,
|
||||
loading: false,
|
||||
@@ -1319,8 +1363,15 @@ function setHighResTextureLayerEnabled(button, enabled, { persist = true, silent
|
||||
return enabled;
|
||||
}
|
||||
|
||||
function setAtmosphereCloudsLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
|
||||
setAtmosphereCloudsEnabled(enabled, { suppressStatus: silent });
|
||||
async function setAtmosphereCloudsLayerEnabled(button, enabled, { persist = true, silent = false } = {}) {
|
||||
if (enabled) {
|
||||
setLayerButtonState(button, {
|
||||
active: false,
|
||||
loading: true,
|
||||
tooltip: "大气云图加载中...",
|
||||
});
|
||||
}
|
||||
await setAtmosphereCloudsEnabled(enabled, { suppressStatus: silent });
|
||||
setLayerButtonState(button, {
|
||||
active: enabled,
|
||||
loading: false,
|
||||
@@ -1449,6 +1500,44 @@ async function applyLayerVisibilitySettings(layerVisibility = {}, options = {})
|
||||
}
|
||||
}
|
||||
|
||||
function applyImmediateLayerVisibilityHints(layerVisibility = {}) {
|
||||
if (typeof layerVisibility.gridLines === "boolean") {
|
||||
setGridLinesLayerEnabled(getLayerButton("gridLines"), layerVisibility.gridLines, {
|
||||
persist: false,
|
||||
silent: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof layerVisibility.countryBoundaries === "boolean") {
|
||||
toggleCountryBoundaries(layerVisibility.countryBoundaries, {
|
||||
showLandFill: true,
|
||||
});
|
||||
setLayerButtonState(getLayerButton("countryBoundaries"), {
|
||||
active: layerVisibility.countryBoundaries,
|
||||
loading: false,
|
||||
tooltip: layerVisibility.countryBoundaries ? "隐藏国界线" : "显示国界线",
|
||||
});
|
||||
}
|
||||
|
||||
if (layerVisibility.earthHighResTexture === false) {
|
||||
void setHighResTextureEnabled(false, { suppressStatus: true });
|
||||
setLayerButtonState(getLayerButton("earthHighResTexture"), {
|
||||
active: false,
|
||||
loading: false,
|
||||
tooltip: "显示高清材质",
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof layerVisibility.atmosphereClouds === "boolean") {
|
||||
toggleClouds(layerVisibility.atmosphereClouds);
|
||||
setLayerButtonState(getLayerButton("atmosphereClouds"), {
|
||||
active: layerVisibility.atmosphereClouds,
|
||||
loading: false,
|
||||
tooltip: layerVisibility.atmosphereClouds ? "隐藏大气云图" : "显示大气云图",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getBuiltinLayerDefinitions() {
|
||||
return [
|
||||
{
|
||||
@@ -1472,13 +1561,14 @@ function getBuiltinLayerDefinitions() {
|
||||
id: "countryBoundaries",
|
||||
buttonId: "toggle-country-boundaries",
|
||||
icon: "public",
|
||||
label: "国界",
|
||||
label: "国界线",
|
||||
meta: "Country Borders",
|
||||
keywords: "国界 国家 borders countries boundary",
|
||||
defaultActive: true,
|
||||
displayOrder: 90,
|
||||
startupPriority: 20,
|
||||
startupMode: "preload",
|
||||
startupAlwaysLoad: true,
|
||||
startupLabel: "海陆基座",
|
||||
startupMessage: "正在加载海陆基座...",
|
||||
getVisible: () => getShowCountryBoundaries(),
|
||||
@@ -2268,7 +2358,7 @@ function setupSettingsControls() {
|
||||
});
|
||||
|
||||
captureEarthSettingsDefaults();
|
||||
settingsApplyPromise = applyEarthSettings(loadEarthSettings());
|
||||
settingsApplyPromise = applyEarthSettings(loadEarthSettings(), { applyLayers: false });
|
||||
syncAllHudPanelToggles();
|
||||
syncRotationModeButtons();
|
||||
syncCruiseModuleControls();
|
||||
|
||||
@@ -25,8 +25,11 @@ let _earthTextureOverlayMaterial = null;
|
||||
let _earthShaders = [];
|
||||
let _dayNightEnabled = true;
|
||||
let _loadedTexture = null;
|
||||
let _textureLoadPromise = null;
|
||||
let _textureVisible = true;
|
||||
let _earthRimGlow = null;
|
||||
let _cloudTexture = null;
|
||||
let _cloudTextureLoadPromise = null;
|
||||
const _earthSunDirection = new THREE.Vector3(
|
||||
EARTH_MATERIAL_CONFIG.dayNight.sunDirection.x,
|
||||
EARTH_MATERIAL_CONFIG.dayNight.sunDirection.y,
|
||||
@@ -301,28 +304,16 @@ export function createClouds(scene, earthObj) {
|
||||
|
||||
clouds = new THREE.Mesh(geometry, material);
|
||||
clouds.name = "earth-atmosphere-clouds";
|
||||
clouds.visible = showClouds;
|
||||
clouds.visible = false;
|
||||
earthObj.add(clouds);
|
||||
|
||||
textureLoader.load(
|
||||
CLOUD_LAYER_CONFIG.textureUrl,
|
||||
function(texture) {
|
||||
material.map = texture;
|
||||
material.needsUpdate = true;
|
||||
},
|
||||
undefined,
|
||||
function(err) {
|
||||
console.log('云层纹理加载失败');
|
||||
}
|
||||
);
|
||||
|
||||
return clouds;
|
||||
}
|
||||
|
||||
export function toggleClouds(visible) {
|
||||
showClouds = Boolean(visible);
|
||||
if (clouds) {
|
||||
clouds.visible = showClouds;
|
||||
clouds.visible = showClouds && Boolean(clouds.material?.map);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,6 +321,39 @@ export function getShowClouds() {
|
||||
return showClouds;
|
||||
}
|
||||
|
||||
export function loadCloudTexture() {
|
||||
if (_cloudTexture) return Promise.resolve(_cloudTexture);
|
||||
if (_cloudTextureLoadPromise) return _cloudTextureLoadPromise;
|
||||
|
||||
_cloudTextureLoadPromise = new Promise((resolve, reject) => {
|
||||
if (!clouds?.material) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
textureLoader.load(
|
||||
CLOUD_LAYER_CONFIG.textureUrl,
|
||||
(texture) => {
|
||||
_cloudTexture = texture;
|
||||
clouds.material.map = texture;
|
||||
clouds.material.needsUpdate = true;
|
||||
clouds.visible = showClouds;
|
||||
resolve(texture);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
console.warn("云层纹理加载失败");
|
||||
reject(error);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
_cloudTextureLoadPromise.finally(() => {
|
||||
_cloudTextureLoadPromise = null;
|
||||
});
|
||||
return _cloudTextureLoadPromise;
|
||||
}
|
||||
|
||||
export function createTerrain(earthObj) {
|
||||
const geometry = new THREE.SphereGeometry(
|
||||
CONFIG.earthRadius + TERRAIN_CONFIG.baseRadiusOffset,
|
||||
@@ -478,6 +502,7 @@ export function getClouds() {
|
||||
|
||||
export function clearEarthTexture() {
|
||||
_loadedTexture = null;
|
||||
_textureLoadPromise = null;
|
||||
if (_earthTextureOverlayMaterial) {
|
||||
_earthTextureOverlayMaterial.map = null;
|
||||
_earthTextureOverlayMaterial.needsUpdate = true;
|
||||
@@ -522,7 +547,10 @@ export function setDayNightEnabled(enabled) {
|
||||
}
|
||||
|
||||
export function loadEarthTexture() {
|
||||
return new Promise((resolve) => {
|
||||
if (_loadedTexture) return Promise.resolve(_loadedTexture);
|
||||
if (_textureLoadPromise) return _textureLoadPromise;
|
||||
|
||||
_textureLoadPromise = new Promise((resolve) => {
|
||||
if (!_earthTextureOverlayMaterial) { resolve(); return; }
|
||||
|
||||
const urls = EARTH_MATERIAL_CONFIG.textureUrls;
|
||||
@@ -549,7 +577,7 @@ export function loadEarthTexture() {
|
||||
if (_earthRimGlow) {
|
||||
_earthRimGlow.visible = !_textureVisible;
|
||||
}
|
||||
resolve();
|
||||
resolve(texture);
|
||||
},
|
||||
null,
|
||||
() => tryLoad(index + 1),
|
||||
@@ -557,6 +585,11 @@ export function loadEarthTexture() {
|
||||
};
|
||||
tryLoad(0);
|
||||
});
|
||||
|
||||
_textureLoadPromise.finally(() => {
|
||||
_textureLoadPromise = null;
|
||||
});
|
||||
return _textureLoadPromise;
|
||||
}
|
||||
|
||||
export function setEarthTextureVisible(visible) {
|
||||
|
||||
@@ -23,6 +23,10 @@ import {
|
||||
loadVessels,
|
||||
toggleVessels,
|
||||
} from "./vessels.js";
|
||||
import {
|
||||
loadCloudTexture,
|
||||
loadEarthTexture,
|
||||
} from "./earth.js";
|
||||
import {
|
||||
getCountryBoundaryLegendItems,
|
||||
loadCountryBoundaries,
|
||||
@@ -83,6 +87,8 @@ export function registerLayerStartupTask(id, taskFactory) {
|
||||
function registerBuiltinLayerStartupTasks() {
|
||||
startupTaskRegistry.clear();
|
||||
registerCountryBoundaryStartupTask();
|
||||
registerEarthTextureStartupTask();
|
||||
registerCloudStartupTask();
|
||||
registerCableStartupTask();
|
||||
registerComputeCenterStartupTask();
|
||||
registerVesselStartupTask();
|
||||
@@ -210,6 +216,42 @@ function registerBGPStartupTask() {
|
||||
});
|
||||
}
|
||||
|
||||
function registerEarthTextureStartupTask() {
|
||||
registerLayerStartupTask("earthHighResTexture", (context) => async (layer) => {
|
||||
if (!context.isEarthTextureVisible()) return;
|
||||
|
||||
context.setLoadingMessage(
|
||||
resolveStartupMessage(layer, "load", "正在加载地球纹理..."),
|
||||
);
|
||||
await context.yieldFrame(12);
|
||||
try {
|
||||
await loadEarthTexture();
|
||||
} catch (error) {
|
||||
console.warn("地球纹理加载失败:", error);
|
||||
}
|
||||
if (context.isCancelled()) return;
|
||||
await context.yieldFrame(16);
|
||||
});
|
||||
}
|
||||
|
||||
function registerCloudStartupTask() {
|
||||
registerLayerStartupTask("atmosphereClouds", (context) => async (layer) => {
|
||||
if (!context.isCloudsEnabled()) return;
|
||||
|
||||
context.setLoadingMessage(
|
||||
resolveStartupMessage(layer, "load", "正在加载大气云图..."),
|
||||
);
|
||||
await context.yieldFrame(12);
|
||||
try {
|
||||
await loadCloudTexture();
|
||||
} catch (error) {
|
||||
context.reportError(layer?.startupLabel || layer?.label || "大气云图", error);
|
||||
}
|
||||
if (context.isCancelled()) return;
|
||||
await context.yieldFrame(16);
|
||||
});
|
||||
}
|
||||
|
||||
function registerCountryBoundaryStartupTask() {
|
||||
registerLayerStartupTask("countryBoundaries", (context) => async (layer) => {
|
||||
context.setLoadingMessage(
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
createGridLines,
|
||||
getEarth,
|
||||
getEarthSurfacePickTarget,
|
||||
loadCloudTexture,
|
||||
loadEarthTexture,
|
||||
clearEarthTexture,
|
||||
setEarthSunDirection,
|
||||
@@ -203,6 +204,7 @@ import {
|
||||
setDayNightEnabledExternal,
|
||||
setTerrainLayerInteractable,
|
||||
setDayNightInteractable,
|
||||
applyDeferredLayerVisibilitySettings,
|
||||
} from "./controls.js";
|
||||
import {
|
||||
createLayerStartupTaskMap,
|
||||
@@ -1470,23 +1472,7 @@ function formatBGPStatusFromSummary(summary) {
|
||||
return "当前无活跃事件";
|
||||
}
|
||||
|
||||
function applyEarthStatsSummary(summary) {
|
||||
if (!summary) return;
|
||||
updateEarthStats({
|
||||
cableCount: `${summary.cableCount}个`,
|
||||
landingPointCount: `${summary.landingPointCount}个`,
|
||||
satelliteCount: `${summary.satelliteCount} 颗`,
|
||||
computeCenterCount: `${summary.computeCenterCount} 个`,
|
||||
vesselCount: `${summary.vesselCount} 艘`,
|
||||
bgpAnomalyCount: `${summary.bgpEventCount} 起`,
|
||||
bgpCollectorCount: `${summary.bgpCollectorCount} 个`,
|
||||
bgpStatusSummary: formatBGPStatusFromSummary(summary),
|
||||
terrainOn: getShowTerrain(),
|
||||
textureQuality: "8K 卫星图",
|
||||
});
|
||||
}
|
||||
|
||||
async function loadEarthStatsSummary() {
|
||||
async function loadEarthStatsSummary({ shouldApply = () => true } = {}) {
|
||||
try {
|
||||
const response = await fetch(PATHS.earthSummaryApi);
|
||||
if (!response.ok) {
|
||||
@@ -1505,7 +1491,9 @@ async function loadEarthStatsSummary() {
|
||||
bgpAnomalyCount: toCount(stats.bgp_anomaly_count),
|
||||
bgpCollectorCount: toCount(stats.bgp_collector_count),
|
||||
};
|
||||
applyEarthStatsSummary(earthStatsSummary);
|
||||
if (shouldApply()) {
|
||||
updateStatsSummary();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("全球态势聚合统计加载失败:", error);
|
||||
}
|
||||
@@ -2594,24 +2582,14 @@ async function loadData() {
|
||||
await yieldFrame(18);
|
||||
if (loadToken !== currentLoadToken) { isDataLoading = false; return; }
|
||||
|
||||
setLoadingMessage("正在读取全球态势统计...");
|
||||
await loadEarthStatsSummary();
|
||||
if (loadToken !== currentLoadToken) { isDataLoading = false; return; }
|
||||
await yieldFrame(12);
|
||||
loadEarthStatsSummary({
|
||||
shouldApply: () => loadToken === currentLoadToken && !destroyed,
|
||||
}).catch((error) => {
|
||||
console.warn("后台刷新全球态势统计失败:", error);
|
||||
});
|
||||
|
||||
const errors = [];
|
||||
|
||||
// Step 1 — Earth texture
|
||||
setLoadingMessage("正在加载地球纹理...");
|
||||
await yieldFrame(12);
|
||||
try {
|
||||
await loadEarthTexture();
|
||||
} catch (err) {
|
||||
// texture failure is non-fatal
|
||||
}
|
||||
if (loadToken !== currentLoadToken) { isDataLoading = false; return; }
|
||||
await yieldFrame(16);
|
||||
|
||||
const startupLoaders = createLayerStartupTaskMap({
|
||||
scene,
|
||||
earth,
|
||||
@@ -2630,6 +2608,7 @@ async function loadData() {
|
||||
getShowCountryBoundaries,
|
||||
getShowBGP,
|
||||
isEarthTextureVisible: () => getEarthTextureVisible(),
|
||||
isCloudsEnabled: () => getShowClouds(),
|
||||
getInitialSatelliteLoadLimit,
|
||||
shouldHydrateFullSatelliteSet,
|
||||
scheduleSatellitePositionWarmup,
|
||||
@@ -2704,6 +2683,10 @@ async function loadData() {
|
||||
hideError();
|
||||
queueStatusMessage("数据已加载", "success");
|
||||
}
|
||||
|
||||
applyDeferredLayerVisibilitySettings().catch((error) => {
|
||||
console.warn("恢复 Earth 图层可见性失败:", error);
|
||||
});
|
||||
}
|
||||
|
||||
const POSITION_UPDATE_FORCE_DELTA = 250;
|
||||
@@ -2827,8 +2810,17 @@ export async function setCountryBoundariesEnabled(
|
||||
let _dayNightBeforeTextureOff = null;
|
||||
let _terrainBeforeTextureOff = null;
|
||||
|
||||
export function setHighResTextureEnabled(enabled, { suppressStatus = false } = {}) {
|
||||
export async function setHighResTextureEnabled(enabled, { suppressStatus = false } = {}) {
|
||||
setEarthTextureVisible(enabled);
|
||||
if (enabled) {
|
||||
try {
|
||||
await loadEarthTexture();
|
||||
setEarthTextureVisible(true);
|
||||
} catch (error) {
|
||||
console.warn("高清材质加载失败:", error);
|
||||
setEarthTextureVisible(false);
|
||||
}
|
||||
}
|
||||
setSurfaceTintEnabled(!enabled);
|
||||
setLandFillEnabled(true);
|
||||
setLandFillSuppressed(false);
|
||||
@@ -2863,8 +2855,17 @@ export function getHighResTextureEnabled() {
|
||||
return getEarthTextureVisible();
|
||||
}
|
||||
|
||||
export function setAtmosphereCloudsEnabled(enabled, { suppressStatus = false } = {}) {
|
||||
export async function setAtmosphereCloudsEnabled(enabled, { suppressStatus = false } = {}) {
|
||||
toggleClouds(enabled);
|
||||
if (enabled) {
|
||||
try {
|
||||
await loadCloudTexture();
|
||||
toggleClouds(true);
|
||||
} catch (error) {
|
||||
console.warn("大气云图加载失败:", error);
|
||||
toggleClouds(false);
|
||||
}
|
||||
}
|
||||
if (!suppressStatus) {
|
||||
showStatusMessage(enabled ? "大气云图已显示" : "大气云图已隐藏", "info");
|
||||
}
|
||||
|
||||
@@ -2139,7 +2139,7 @@ function showIridiumReservedStyle(position) {
|
||||
lockedIridiumFootprintMesh = createIridiumFootprintAdapter({
|
||||
earthObj: earthObjRef,
|
||||
earthRadiusWorld: CONFIG.earthRadius,
|
||||
renderOrder: 0,
|
||||
renderOrder: GROUND_FOOTPRINT_RENDER_ORDER,
|
||||
});
|
||||
updateIridiumReservedStyle(position);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const VESSEL_RENDER_ORDER = 4.4;
|
||||
const VESSEL_POINT_SIZE = 34;
|
||||
const VESSEL_ATLAS_CELL_SIZE = 128;
|
||||
const VESSEL_COURSE_BINS = 32;
|
||||
const VESSEL_TRACK_ENDPOINT_EPSILON = 0.001;
|
||||
|
||||
function normalizeVesselType(value, code) {
|
||||
const type = String(value || "").trim().toLowerCase();
|
||||
@@ -72,6 +73,26 @@ function getCourseBin(marker) {
|
||||
return Math.round((normalized / 360) * VESSEL_COURSE_BINS) % VESSEL_COURSE_BINS;
|
||||
}
|
||||
|
||||
function buildTrackPoint(lon, lat) {
|
||||
const latitude = Number(lat);
|
||||
const longitude = Number(lon);
|
||||
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return null;
|
||||
return latLonToVector3(
|
||||
latitude,
|
||||
longitude,
|
||||
CONFIG.earthRadius + VESSEL_CONFIG.track.altitudeOffset,
|
||||
);
|
||||
}
|
||||
|
||||
function appendCurrentMarkerTrackPoint(points, marker) {
|
||||
if (!(marker?.position instanceof THREE.Vector3)) return;
|
||||
const markerPosition = marker.position.clone();
|
||||
const lastPoint = points[points.length - 1];
|
||||
if (!lastPoint || lastPoint.distanceToSquared(markerPosition) > VESSEL_TRACK_ENDPOINT_EPSILON) {
|
||||
points.push(markerPosition);
|
||||
}
|
||||
}
|
||||
|
||||
const vesselIconLayer = createInteractableLayer({
|
||||
id: "vessels",
|
||||
objectType: "vessel",
|
||||
@@ -207,14 +228,15 @@ export async function showVesselTrack(marker, earth) {
|
||||
if (coordinates.length < 2) return null;
|
||||
|
||||
const points = coordinates
|
||||
.map(([lon, lat]) =>
|
||||
latLonToVector3(
|
||||
Number(lat),
|
||||
Number(lon),
|
||||
CONFIG.earthRadius + VESSEL_CONFIG.track.altitudeOffset,
|
||||
),
|
||||
)
|
||||
.filter((point) => Number.isFinite(point.x) && Number.isFinite(point.y) && Number.isFinite(point.z));
|
||||
.map(([lon, lat]) => buildTrackPoint(lon, lat))
|
||||
.filter(
|
||||
(point) =>
|
||||
point &&
|
||||
Number.isFinite(point.x) &&
|
||||
Number.isFinite(point.y) &&
|
||||
Number.isFinite(point.z),
|
||||
);
|
||||
appendCurrentMarkerTrackPoint(points, marker);
|
||||
if (points.length < 2) return null;
|
||||
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
|
||||
Reference in New Issue
Block a user