release: bump version to 0.41.0

This commit is contained in:
linkong
2026-04-27 13:58:29 +08:00
parent f9c1334365
commit 3ea99a9529
25 changed files with 1652 additions and 171 deletions

View File

@@ -33,11 +33,17 @@ import {
createEarth,
createClouds,
createTerrain,
getShowClouds,
createGridLines,
getEarth,
getEarthSurfacePickTarget,
loadEarthTexture,
clearEarthTexture,
setEarthSunDirection,
setEarthTextureVisible,
getEarthTextureVisible,
toggleClouds,
toggleTerrain,
} from "./earth.js";
import { registerTerrainMesh, clearTerrainData, sampleElevationAt } from "./terrain.js";
import {
@@ -50,6 +56,19 @@ import {
setCelestialFollow,
setCelestialDayNightEnabled,
} from "./celestial.js";
import {
clearCountryBoundaryData,
clearCountryBoundaryHover,
createCountryBoundaryLayer,
ensureCountryBoundariesReady,
getCountryBoundaryLegendItems,
getShowCountryBoundaries,
setLandFillEnabled,
setLandFillSuppressed,
setSurfaceTintEnabled,
toggleCountryBoundaries,
updateCountryBoundaryHover,
} from "./country-boundaries.js";
import {
loadGeoJSONFromPath,
loadLandingPoints,
@@ -162,6 +181,10 @@ import {
getZoomLevel,
setZoomLevel,
teardownControls,
getDayNightEnabled,
setDayNightEnabledExternal,
setTerrainLayerInteractable,
setDayNightInteractable,
} from "./controls.js";
import {
createLayerStartupTaskMap,
@@ -227,7 +250,7 @@ let destroyed = false;
let isDataLoading = false;
let currentLoadToken = 0;
let cablesEnabled = true;
let satellitesEnabled = true;
let satellitesEnabled = false;
let cableToggleToken = 0;
let satelliteToggleToken = 0;
let satelliteHydrationToken = 0;
@@ -254,6 +277,8 @@ const scratchBGPDirection = new THREE.Vector3();
const scratchBGPWorldPosition = new THREE.Vector3();
const scratchComputeCenterDirection = new THREE.Vector3();
const scratchComputeCenterWorldPosition = new THREE.Vector3();
const scratchSatelliteWorldPosition = new THREE.Vector3();
const scratchSatelliteScreenPosition = new THREE.Vector3();
const scratchViewCenterWorld = new THREE.Vector3();
const cleanupFns = [];
@@ -472,6 +497,7 @@ function resetTransientComputeCenterStates() {
function clearTransientHoverState() {
resetTransientBGPStates();
resetTransientComputeCenterStates();
clearCountryBoundaryHover();
hoveredBGP = null;
hoveredComputeCenter = null;
@@ -648,6 +674,13 @@ function getComputeCenterBriefHtml(marker) {
return `<strong>${name}</strong><br>${type}${location ? ` · ${location}` : ""}${precision}`;
}
function getCountryBoundaryBriefHtml(country) {
const name = country?.nameZh || country?.name || "未知国家";
const code = country?.isoA3 || country?.isoA2 || "-";
const continent = country?.continent || "-";
return `<strong>${name}</strong><br>ISO: ${code}<br>大洲: ${continent}`;
}
function showBGPInfo(marker, coords) {
setLegendMode("bgp");
const impactedRegions =
@@ -1216,11 +1249,11 @@ function getBGPStatusText(bgpResult) {
function updateComputeCenterHud(computeCenterResult) {
const computeBtn = document.getElementById("toggle-compute-centers");
if (computeBtn) {
computeBtn.classList.add("active");
const tooltip = computeBtn.querySelector(".earth-toolbar-tooltip");
if (tooltip) {
tooltip.textContent = "隐藏算力中心";
}
setLayerButtonState(computeBtn, {
active: getShowComputeCenters(),
loading: false,
tooltip: getShowComputeCenters() ? "隐藏算力中心" : "显示算力中心",
});
}
setEarthStatValue("compute-center-count", `${computeCenterResult.totalCount}`);
@@ -1229,11 +1262,11 @@ function updateComputeCenterHud(computeCenterResult) {
function updateBGPHud(bgpResult) {
const bgpBtn = document.getElementById("toggle-bgp");
if (bgpBtn) {
bgpBtn.classList.add("active");
const tooltip = bgpBtn.querySelector(".earth-toolbar-tooltip");
if (tooltip) {
tooltip.textContent = ACTIVE_BGP_TOOLTIP_TEXT;
}
setLayerButtonState(bgpBtn, {
active: getShowBGP(),
loading: false,
tooltip: getShowBGP() ? ACTIVE_BGP_TOOLTIP_TEXT : "显示BGP观测",
});
}
setEarthStatValue("bgp-anomaly-count", `${bgpResult.totalCount}`);
@@ -1780,6 +1813,58 @@ function updatePointerFromEvent(event) {
interactionRaycaster.setFromCamera(interactionMouse, camera);
}
function getSatellitePointerIntersections(event) {
if (!renderer || !camera || !getShowSatellites()) return [];
const satPoints = getSatellitePoints();
const satPositions = getSatellitePositions();
if (!satPoints?.visible || !Array.isArray(satPositions) || satPositions.length === 0) {
return [];
}
const rect = renderer.domElement.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
const shaderSize = satPoints.material?.uniforms?.size?.value;
const dotSizePx = Number.isFinite(shaderSize)
? shaderSize / dpr
: SATELLITE_CONFIG.dotBaseSize;
const pickRadiusPx = Math.max(12, Math.min(30, dotSizePx * 4));
const pickRadiusSq = pickRadiusPx * pickRadiusPx;
const hits = [];
satPoints.updateMatrixWorld(true);
camera.updateMatrixWorld(true);
const satelliteCount = Math.min(getSatelliteData().length, satPositions.length);
for (let index = 0; index < satelliteCount; index++) {
const position = satPositions[index]?.current;
if (!position || !isSatelliteFrontFacing(index, camera)) continue;
scratchSatelliteWorldPosition.copy(position).applyMatrix4(satPoints.matrixWorld);
scratchSatelliteScreenPosition.copy(scratchSatelliteWorldPosition).project(camera);
if (
scratchSatelliteScreenPosition.z < -1 ||
scratchSatelliteScreenPosition.z > 1
) {
continue;
}
const screenX =
rect.left + (scratchSatelliteScreenPosition.x * 0.5 + 0.5) * rect.width;
const screenY =
rect.top + (-scratchSatelliteScreenPosition.y * 0.5 + 0.5) * rect.height;
const dx = screenX - event.clientX;
const dy = screenY - event.clientY;
const distanceSq = dx * dx + dy * dy;
if (distanceSq <= pickRadiusSq) {
hits.push({ index, distanceSq });
}
}
hits.sort((a, b) => a.distanceSq - b.distanceSq);
return hits;
}
function buildLoadErrorMessage(errors) {
if (errors.length === 0) return "";
return errors
@@ -1922,6 +2007,7 @@ function disableSatellites() {
satellitesEnabled = false;
satelliteToggleToken += 1;
satelliteHydrationToken += 1;
toggleSatellites(false);
resetSatelliteState();
updateSatelliteToggleUi(false, 0);
setLegendItems("satellites", getSatelliteLegendItems());
@@ -2002,6 +2088,7 @@ export function init() {
initLegend();
setLegendItems("cables", getCableLegendItems());
setLegendItems("satellites", getSatelliteLegendItems());
setLegendItems("countryBoundaries", getCountryBoundaryLegendItems());
setLegendItems("computeCenters", getComputeCenterLegendItems());
setLegendItems("bgp", getBGPLegendItems());
const earthObj = createEarth(scene);
@@ -2023,15 +2110,28 @@ export function init() {
});
setCelestialDayNightEnabled(true);
createGridLines(scene, earthObj);
createCountryBoundaryLayer(earthObj);
createSatellites(scene, earthObj);
setupControls(camera, renderer, scene, earthObj);
setupEventListeners();
clock.start();
loadData();
animate();
registerGlobalApi();
setupControls(camera, renderer, scene, earthObj)
.catch((error) => {
console.error("初始化 Earth 控制项失败:", error);
void reportEarthClientLog({
level: "error",
category: "init",
module: "controls",
message: `初始化 Earth 控制项失败: ${error?.message || String(error)}`,
detail: error,
});
})
.finally(() => {
if (destroyed) return;
setupEventListeners();
clock.start();
loadData();
animate();
registerGlobalApi();
});
}
function registerGlobalApi() {
@@ -2162,6 +2262,7 @@ async function loadData() {
clearCableData(earth);
clearComputeCenterData(earth);
clearSatelliteData();
clearCountryBoundaryHover();
setLoadingMessage("正在初始化...");
setLoading(true);
@@ -2194,7 +2295,9 @@ async function loadData() {
updateComputeCenterHud,
updateBGPHud,
getShowComputeCenters,
getShowCountryBoundaries,
getShowBGP,
isEarthTextureVisible: () => getEarthTextureVisible(),
getInitialSatelliteLoadLimit,
shouldHydrateFullSatelliteSet,
scheduleSatellitePositionWarmup,
@@ -2247,6 +2350,7 @@ async function loadData() {
updateSatelliteToggleUi(satellitesEnabled);
setLegendItems("cables", getCableLegendItems());
setLegendItems("satellites", getSatelliteLegendItems());
setLegendItems("countryBoundaries", getCountryBoundaryLegendItems());
setLegendItems("computeCenters", getComputeCenterLegendItems());
setLegendItems("bgp", getBGPLegendItems());
refreshLegend();
@@ -2338,6 +2442,102 @@ export async function setCablesEnabled(
}
}
export async function setCountryBoundariesEnabled(
enabled,
{ suppressStatus = false } = {},
) {
if (!enabled) {
toggleCountryBoundaries(false);
clearCountryBoundaryHover();
if (!suppressStatus) {
showStatusMessage("国界已隐藏", "info");
}
return 0;
}
try {
const countryCount = await ensureCountryBoundariesReady();
const textureOn = getEarthTextureVisible();
toggleCountryBoundaries(true, {
showTint: !textureOn,
showLandFill: true,
suppressLandFill: false,
});
setLegendItems("countryBoundaries", getCountryBoundaryLegendItems());
refreshLegend();
if (!suppressStatus) {
showStatusMessage("国界已显示", "info");
}
return countryCount;
} catch (error) {
toggleCountryBoundaries(false);
clearCountryBoundaryHover();
const message = `国界加载失败: ${error?.message || String(error)}`;
void reportEarthClientLog({
level: "error",
category: "layer-toggle",
module: "country-boundaries",
message,
detail: error,
});
if (!suppressStatus) {
showStatusMessage(message, "error");
}
throw error;
}
}
let _dayNightBeforeTextureOff = null;
let _terrainBeforeTextureOff = null;
export function setHighResTextureEnabled(enabled, { suppressStatus = false } = {}) {
setEarthTextureVisible(enabled);
setSurfaceTintEnabled(!enabled);
setLandFillEnabled(true);
setLandFillSuppressed(false);
setTerrainLayerInteractable(enabled);
setDayNightInteractable(enabled);
if (!enabled) {
if (_terrainBeforeTextureOff === null) {
_terrainBeforeTextureOff = getShowTerrain();
}
toggleTerrain(false);
if (_dayNightBeforeTextureOff === null) {
_dayNightBeforeTextureOff = getDayNightEnabled();
}
setDayNightEnabledExternal(false, { persist: false });
} else {
if (_terrainBeforeTextureOff !== null) {
toggleTerrain(_terrainBeforeTextureOff);
_terrainBeforeTextureOff = null;
}
if (_dayNightBeforeTextureOff !== null) {
setDayNightEnabledExternal(_dayNightBeforeTextureOff, { persist: false });
_dayNightBeforeTextureOff = null;
}
}
if (!suppressStatus) {
showStatusMessage(enabled ? "高清材质已启用" : "高清材质已隐藏", "info");
}
return enabled;
}
export function getHighResTextureEnabled() {
return getEarthTextureVisible();
}
export function setAtmosphereCloudsEnabled(enabled, { suppressStatus = false } = {}) {
toggleClouds(enabled);
if (!suppressStatus) {
showStatusMessage(enabled ? "大气云图已显示" : "大气云图已隐藏", "info");
}
return enabled;
}
export function getAtmosphereCloudsEnabled() {
return getShowClouds();
}
export async function setSatellitesEnabled(
enabled,
{ suppressStatus = false, suppressLoadingUi = false } = {},
@@ -2561,6 +2761,7 @@ function onMouseMove(event) {
inertialVelocity.y = rotationDeltaY;
inertialVelocity.x = rotationDeltaX;
previousMousePosition = { x: event.clientX, y: event.clientY };
clearCountryBoundaryHover();
hideTooltip();
return;
}
@@ -2590,18 +2791,11 @@ function onMouseMove(event) {
let hoveredSat = null;
let hoveredSatIndexFromIntersect = null;
if (getShowSatellites()) {
const satPoints = getSatellitePoints();
if (satPoints) {
const satIntersects = interactionRaycaster.intersectObject(satPoints);
if (satIntersects.length > 0) {
const satIndex = satIntersects[0].index;
if (isSatelliteFrontFacing(satIndex, camera)) {
hoveredSatIndexFromIntersect = satIndex;
hoveredSat = selectSatellite(satIndex);
}
}
}
const satIntersects = getSatellitePointerIntersections(event);
if (satIntersects.length > 0) {
const satIndex = satIntersects[0].index;
hoveredSatIndexFromIntersect = satIndex;
hoveredSat = selectSatellite(satIndex);
}
const hoveredBGPMarker = getPrimaryBGPHoverTarget(
@@ -2706,7 +2900,7 @@ function onMouseMove(event) {
event.clientX,
event.clientY,
camera,
earth,
getEarthSurfacePickTarget() || earth,
document.body,
interactionRaycaster,
interactionMouse,
@@ -2714,6 +2908,18 @@ function onMouseMove(event) {
if (earthPoint) {
const coords = vector3ToLatLon(earthPoint);
updateCoordinatesDisplay(coords.lat, coords.lon, coords.alt);
const hoveredCountry = getShowCountryBoundaries()
? updateCountryBoundaryHover(coords)
: null;
if (hoveredCountry) {
showTooltip(
event.clientX + TOOLTIP_CURSOR_OFFSET,
event.clientY + TOOLTIP_CURSOR_OFFSET,
getCountryBoundaryBriefHtml(hoveredCountry),
);
return;
}
clearCountryBoundaryHover();
const elevMeters = sampleElevationAt(coords.lat, coords.lon);
const elevText = elevMeters !== null
? elevMeters >= 1000
@@ -2726,8 +2932,11 @@ function onMouseMove(event) {
`纬度: ${coords.lat}°<br>经度: ${coords.lon}°<br>海拔: ${elevText}`,
);
} else {
clearCountryBoundaryHover();
hideTooltip();
}
} else {
clearCountryBoundaryHover();
}
}
@@ -2864,6 +3073,7 @@ function onPointerUp(event) {
}
function onMouseLeave() {
clearCountryBoundaryHover();
hideTooltip();
}
@@ -2898,9 +3108,7 @@ function onClick(event) {
getFrontFacingComputeCenterMarkers(getComputeCenterMarkers()),
)
: [];
const satIntersects = getShowSatellites()
? interactionRaycaster.intersectObject(getSatellitePoints())
: [];
const satIntersects = getSatellitePointerIntersections(event);
const clickedBGPMarker = getShowBGP()
? getPrimaryBGPClickTarget(event, bgpAnomalyIntersects, bgpCollectorIntersects)
@@ -2996,9 +3204,7 @@ function onClick(event) {
const clickX = event.clientX;
const clickY = event.clientY;
const frontFacingSats = satIntersects.filter((sat) =>
isSatelliteFrontFacing(sat.index, camera),
);
const frontFacingSats = satIntersects;
if (frontFacingSats.length === 0) return;
let selectedIndex = frontFacingSats[0].index;
@@ -3191,6 +3397,7 @@ export function destroy() {
clearCableData(getEarth());
clearBGPData(getEarth());
clearComputeCenterData(getEarth());
clearCountryBoundaryData();
resetSatelliteState();
clearUiState();
disposeCelestialLayer();