fix: polish earth legend and info panel interactions

This commit is contained in:
linkong
2026-03-27 16:01:12 +08:00
parent b448a1e560
commit 62f2d9f403
14 changed files with 466 additions and 30 deletions

View File

@@ -30,6 +30,7 @@ import {
handleCableClick,
clearCableSelection,
getCableLines,
getCableLegendItems,
getCableState,
setCableState,
clearAllCableStates,
@@ -44,6 +45,9 @@ import {
updateSatellitePositions,
toggleSatellites,
getShowSatellites,
getSatelliteLegendItems,
setSelectedSatelliteLegend,
clearSelectedSatelliteLegend,
getSatelliteCount,
selectSatellite,
getSatellitePoints,
@@ -60,6 +64,18 @@ import {
resetSatelliteState,
clearSatelliteData,
} from "./satellites.js";
import {
loadBGPAnomalies,
getBGPMarkers,
getBGPLegendItems,
getBGPCount,
getShowBGP,
clearBGPSelection,
setBGPMarkerState,
updateBGPVisualState,
clearBGPData,
toggleBGP,
} from "./bgp.js";
import {
setupControls,
getAutoRotate,
@@ -74,7 +90,12 @@ import {
hideInfoCard,
setInfoCardNoBorder,
} from "./info-card.js";
import { initLegend, setLegendMode } from "./legend.js";
import {
initLegend,
setLegendMode,
refreshLegend,
setLegendItems,
} from "./legend.js";
export let scene;
export let camera;
@@ -86,6 +107,7 @@ let previousMousePosition = { x: 0, y: 0 };
let targetRotation = { x: 0, y: 0 };
let inertialVelocity = { x: 0, y: 0 };
let hoveredCable = null;
let hoveredBGP = null;
let hoveredSatellite = null;
let hoveredSatelliteIndex = null;
let lockedSatellite = null;
@@ -110,6 +132,7 @@ const interactionMouse = new THREE.Vector2();
const scratchCameraToEarth = new THREE.Vector3();
const scratchCableCenter = new THREE.Vector3();
const scratchCableDirection = new THREE.Vector3();
const scratchBGPDirection = new THREE.Vector3();
const cleanupFns = [];
const DRAG_ROTATION_FACTOR = 0.005;
@@ -169,6 +192,7 @@ function disposeSceneObject(object) {
function clearRuntimeSelection() {
hoveredCable = null;
hoveredBGP = null;
hoveredSatellite = null;
hoveredSatelliteIndex = null;
lockedObject = null;
@@ -176,14 +200,17 @@ function clearRuntimeSelection() {
lockedSatellite = null;
lockedSatelliteIndex = null;
setLockedSatelliteIndex(null);
clearSelectedSatelliteLegend();
}
export function clearLockedObject() {
hidePredictedOrbit();
clearAllCableStates();
clearCableSelection();
clearBGPSelection();
setSatelliteRingState(null, "none", null);
clearRuntimeSelection();
setLegendItems("satellites", getSatelliteLegendItems());
}
function isSameCable(cable1, cable2) {
@@ -213,6 +240,8 @@ function showSatelliteInfo(props) {
const perigee = (6371 * (1 - ecc)).toFixed(0);
const apogee = (6371 * (1 + ecc)).toFixed(0);
setSelectedSatelliteLegend(props);
setLegendItems("satellites", getSatelliteLegendItems());
setLegendMode("satellites");
showInfoCard("satellite", {
name: props?.name || "-",
@@ -224,6 +253,24 @@ function showSatelliteInfo(props) {
});
}
function showBGPInfo(marker) {
setLegendMode("bgp");
showInfoCard("bgp", {
anomaly_type: marker.userData.anomaly_type,
severity: marker.userData.rawSeverity || marker.userData.severity,
status: marker.userData.status,
prefix: marker.userData.prefix,
origin_asn: marker.userData.origin_asn,
new_origin_asn: marker.userData.new_origin_asn,
confidence: marker.userData.confidence,
collector: marker.userData.collector,
country: marker.userData.country,
city: marker.userData.city,
created_at: marker.userData.created_at,
summary: marker.userData.summary,
});
}
function applyCableVisualState() {
const allCables = getCableLines();
const pulse = (Math.sin(Date.now() * CABLE_CONFIG.pulseSpeed) + 1) * 0.5;
@@ -248,7 +295,8 @@ function applyCableVisualState() {
default:
if (
(lockedObjectType === "cable" && lockedObject) ||
(lockedObjectType === "satellite" && lockedSatellite)
(lockedObjectType === "satellite" && lockedSatellite) ||
(lockedObjectType === "bgp" && lockedObject)
) {
cable.material.opacity = CABLE_CONFIG.otherOpacity;
const origColor = cable.userData.originalColor;
@@ -289,6 +337,7 @@ function updateStatsSummary() {
cableCount: getCableLines().length,
landingPointCount:
document.getElementById("landing-point-count")?.textContent || 0,
bgpAnomalyCount: `${getBGPCount()}`,
terrainOn: getShowTerrain(),
textureQuality: "8K 卫星图",
});
@@ -337,6 +386,9 @@ export function init() {
addLights();
initInfoCard();
initLegend();
setLegendItems("cables", getCableLegendItems());
setLegendItems("satellites", getSatelliteLegendItems());
setLegendItems("bgp", getBGPLegendItems());
const earthObj = createEarth(scene);
targetRotation = {
x: earthObj.rotation.x,
@@ -400,8 +452,8 @@ async function loadData(showWhiteSphere = false) {
setLoadingMessage(
showWhiteSphere ? "正在刷新全球态势数据..." : "正在初始化全球态势数据...",
showWhiteSphere
? "重新同步卫星、海底光缆登陆点数据"
: "同步卫星、海底光缆登陆点数据",
? "重新同步卫星、海底光缆登陆点与BGP异常数据"
: "同步卫星、海底光缆登陆点与BGP异常数据",
);
setLoading(true);
clearLockedObject();
@@ -434,6 +486,22 @@ async function loadData(showWhiteSphere = false) {
}
return satelliteCount;
})(),
(async () => {
clearBGPData(earth);
const bgpResult = await loadBGPAnomalies(scene, earth);
toggleBGP(true);
const bgpBtn = document.getElementById("toggle-bgp");
if (bgpBtn) {
bgpBtn.classList.add("active");
const tooltip = bgpBtn.querySelector(".tooltip");
if (tooltip) tooltip.textContent = "隐藏BGP观测";
}
const bgpCountEl = document.getElementById("bgp-anomaly-count");
if (bgpCountEl) {
bgpCountEl.textContent = `${bgpResult.totalCount}`;
}
return bgpResult;
})(),
]);
if (loadToken !== currentLoadToken) {
@@ -451,6 +519,9 @@ async function loadData(showWhiteSphere = false) {
if (results[2].status === "rejected") {
errors.push({ label: "卫星", reason: results[2].reason });
}
if (results[3].status === "rejected") {
errors.push({ label: "BGP异常", reason: results[3].reason });
}
if (errors.length > 0) {
const errorMessage = buildLoadErrorMessage(errors);
@@ -462,6 +533,10 @@ async function loadData(showWhiteSphere = false) {
}
updateStatsSummary();
setLegendItems("cables", getCableLegendItems());
setLegendItems("satellites", getSatelliteLegendItems());
setLegendItems("bgp", getBGPLegendItems());
refreshLegend();
setLoading(false);
isDataLoading = false;
@@ -524,6 +599,18 @@ function getFrontFacingCables(cableLines) {
});
}
function getFrontFacingBGPMarkers(markers) {
const earth = getEarth();
if (!earth) return markers;
scratchCameraToEarth.subVectors(camera.position, earth.position).normalize();
return markers.filter((marker) => {
scratchBGPDirection.copy(marker.position).normalize();
return scratchCameraToEarth.dot(scratchBGPDirection) > 0;
});
}
function onMouseMove(event) {
const earth = getEarth();
if (!earth) return;
@@ -551,6 +638,10 @@ function onMouseMove(event) {
const frontCables = getFrontFacingCables(getCableLines());
const cableIntersects = interactionRaycaster.intersectObjects(frontCables);
const frontFacingBGPMarkers = getFrontFacingBGPMarkers(getBGPMarkers());
const bgpIntersects = getShowBGP()
? interactionRaycaster.intersectObjects(frontFacingBGPMarkers)
: [];
let hoveredSat = null;
let hoveredSatIndexFromIntersect = null;
@@ -568,6 +659,16 @@ function onMouseMove(event) {
}
}
if (
hoveredBGP &&
(!bgpIntersects.length || bgpIntersects[0]?.object !== hoveredBGP)
) {
if (hoveredBGP !== lockedObject) {
setBGPMarkerState(hoveredBGP, "normal");
}
hoveredBGP = null;
}
if (
hoveredCable &&
(!cableIntersects.length ||
@@ -589,7 +690,16 @@ function onMouseMove(event) {
hoveredSatelliteIndex = null;
}
if (cableIntersects.length > 0 && getShowCables()) {
if (bgpIntersects.length > 0 && getShowBGP()) {
const marker = bgpIntersects[0].object;
hoveredBGP = marker;
if (marker !== lockedObject) {
setBGPMarkerState(marker, "hover");
}
showBGPInfo(marker);
setInfoCardNoBorder(true);
hideTooltip();
} else if (cableIntersects.length > 0 && getShowCables()) {
const cable = cableIntersects[0].object;
hoveredCable = cable;
if (!isSameCable(cable, lockedObject)) {
@@ -613,6 +723,8 @@ function onMouseMove(event) {
}
showSatelliteInfo(hoveredSat.properties);
setInfoCardNoBorder(true);
} else if (lockedObjectType === "bgp" && lockedObject) {
showBGPInfo(lockedObject);
} else if (lockedObjectType === "cable" && lockedObject) {
showCableInfo(lockedObject);
} else if (lockedObjectType === "satellite" && lockedSatellite) {
@@ -686,10 +798,31 @@ function onClick(event) {
const cableIntersects = interactionRaycaster.intersectObjects(
getFrontFacingCables(getCableLines()),
);
const frontFacingBGPMarkers = getFrontFacingBGPMarkers(getBGPMarkers());
const bgpIntersects = getShowBGP()
? interactionRaycaster.intersectObjects(frontFacingBGPMarkers)
: [];
const satIntersects = getShowSatellites()
? interactionRaycaster.intersectObject(getSatellitePoints())
: [];
if (bgpIntersects.length > 0 && getShowBGP()) {
clearLockedObject();
const clickedMarker = bgpIntersects[0].object;
setBGPMarkerState(clickedMarker, "locked");
lockedObject = clickedMarker;
lockedObjectType = "bgp";
setAutoRotate(false);
showBGPInfo(clickedMarker);
showStatusMessage(
`已选择BGP异常: ${clickedMarker.userData.collector}`,
"info",
);
return;
}
if (cableIntersects.length > 0 && getShowCables()) {
clearLockedObject();
@@ -816,10 +949,15 @@ function animate() {
}
applyCableVisualState();
updateBGPVisualState(lockedObjectType, lockedObject);
if (lockedObjectType === "cable" && lockedObject) {
applyLandingPointVisualState(lockedObject.userData.name, false);
} else if (lockedObjectType === "satellite" && lockedSatellite) {
} else if (
lockedObjectType === "satellite" && lockedSatellite
) {
applyLandingPointVisualState(null, true);
} else if (lockedObjectType === "bgp" && lockedObject) {
applyLandingPointVisualState(null, true);
} else {
resetLandingPointVisualState();
@@ -864,6 +1002,7 @@ export function destroy() {
clearLockedObject();
clearCableData(getEarth());
clearBGPData(getEarth());
resetSatelliteState();
clearUiState();