release: bump version to 0.41.0
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user