release: bump version to 0.34.0

This commit is contained in:
linkong
2026-04-22 12:49:37 +08:00
parent 0082cf3fbd
commit 5b623a6385
15 changed files with 1016 additions and 33 deletions

View File

@@ -72,6 +72,7 @@ import {
toggleSatellites,
getShowSatellites,
getSatelliteLegendItems,
getSatelliteData,
setSelectedSatelliteLegend,
clearSelectedSatelliteLegend,
getSatelliteCount,
@@ -162,6 +163,7 @@ import {
import { mountBrand } from "./brand.js";
import { initTVPanel } from "./tv.js";
import { initNewsPanel, updateNewsViewFocus } from "./news.js";
import { initSearchPanel } from "./search.js";
export let scene;
export let camera;
@@ -609,6 +611,391 @@ function getBGPCollectorBriefHtml(marker) {
return `<strong>${name}</strong><br>${count} 条事件`;
}
function getSearchCardCoords() {
return {
x: Math.round(window.innerWidth * SEARCH_CARD_X_RATIO),
y: Math.round(window.innerHeight * SEARCH_CARD_Y_RATIO),
absolute: true,
};
}
function normalizeSearchString(...parts) {
return parts
.flat()
.filter((part) => part !== undefined && part !== null && part !== false)
.map((part) => String(part).trim())
.filter(Boolean)
.join(" ")
.toLowerCase();
}
function computeSearchScore(query, ...parts) {
const text = normalizeSearchString(...parts);
if (!text) return -1;
const normalizedQuery = query.trim().toLowerCase();
if (!normalizedQuery) return -1;
if (text === normalizedQuery) return 240;
if (text.startsWith(normalizedQuery)) return 180;
if (text.includes(normalizedQuery)) return 120;
const tokens = normalizedQuery.split(/\s+/).filter(Boolean);
if (tokens.length === 0) return -1;
let score = 0;
for (const token of tokens) {
if (text.startsWith(token)) {
score += 60;
continue;
}
if (text.includes(token)) {
score += 36;
continue;
}
return -1;
}
return score;
}
function getCableFocusCoords(cable) {
if (!cable?.userData?.localCenter) return null;
return vector3ToLatLon(cable.userData.localCenter);
}
function getLandingPointFocusCoords(point) {
if (!point?.position) return null;
return vector3ToLatLon(point.position);
}
function getSatelliteFocusCoords(index) {
const positions = getSatellitePositions();
const vector = positions?.[index]?.current;
if (!vector) return null;
return vector3ToLatLon(vector);
}
function getBGPFocusCoords(marker) {
const lat = marker?.userData?.displayLatitude ?? marker?.userData?.latitude;
const lon = marker?.userData?.displayLongitude ?? marker?.userData?.longitude;
if (typeof lat !== "number" || typeof lon !== "number") return null;
return { lat, lon };
}
async function focusSearchTarget(coords, zoom = Math.max(getZoomLevel(), 1.12)) {
if (!coords || !camera) return;
await focusEarthView(camera, {
lat: coords.lat,
lon: coords.lon,
zoom,
duration: 950,
suppressStatus: true,
});
}
function showLandingPointInfo(point, coords) {
const cableNames = Array.isArray(point?.userData?.cableNames)
? point.userData.cableNames
: [];
setLegendMode("cables");
showInfoCard(
"landing_point",
{
name: point?.userData?.name || "-",
country: point?.userData?.country || "-",
status: point?.userData?.status || "-",
cable_count: cableNames.length,
cables: cableNames.length > 0 ? cableNames.join(" / ") : "-",
},
coords,
);
}
async function focusSearchCable(cable) {
await setCablesEnabled(true, {
suppressStatus: true,
suppressLoadingUi: true,
});
interruptCruisePresentation({ resetLoop: true });
clearLockedObject();
setAutoRotate(false);
const coords = getCableFocusCoords(cable);
if (coords) {
await focusSearchTarget(coords, Math.max(getZoomLevel(), 1.14));
}
const cableId = cable?.userData?.cableId;
if (cableId !== undefined) {
setCableState(cableId, CABLE_STATE.LOCKED);
}
lockedObject = cable;
lockedObjectType = "cable";
handleCableClick(cable);
showCableInfo(cable, getSearchCardCoords());
}
async function focusSearchLandingPoint(point) {
await setCablesEnabled(true, {
suppressStatus: true,
suppressLoadingUi: true,
});
interruptCruisePresentation({ resetLoop: true });
clearLockedObject();
setAutoRotate(false);
const coords = getLandingPointFocusCoords(point);
if (coords) {
await focusSearchTarget(coords, Math.max(getZoomLevel(), 1.22));
}
const relatedCableNames = Array.isArray(point?.userData?.cableNames)
? point.userData.cableNames
: [];
clearAllCableStates();
getCableLines().forEach((cable) => {
if (relatedCableNames.includes(cable.userData?.name)) {
setCableState(cable.userData.cableId, CABLE_STATE.LOCKED);
}
});
applyLandingPointVisualState(relatedCableNames, relatedCableNames.length === 0, camera);
showLandingPointInfo(point, getSearchCardCoords());
showStatusMessage(`已定位登陆点:${point.userData?.name || "未知登陆点"}`, "info");
}
async function focusSearchSatellite(index) {
await setSatellitesEnabled(true, {
suppressStatus: true,
suppressLoadingUi: true,
});
interruptCruisePresentation({ resetLoop: true });
clearLockedObject();
setAutoRotate(false);
const sat = selectSatellite(index);
if (!sat?.properties) return;
const coords = getSatelliteFocusCoords(index);
if (coords) {
await focusSearchTarget(coords, Math.max(getZoomLevel(), 1.18));
}
lockedObject = sat;
lockedObjectType = "satellite";
lockedSatellite = sat;
lockedSatelliteIndex = index;
setLockedSatelliteIndex(index);
showPredictedOrbit(sat);
const satPositions = getSatellitePositions();
if (satPositions?.[index]) {
setSatelliteRingState(index, "locked", satPositions[index].current);
}
showSatelliteInfo(sat.properties, getSearchCardCoords());
showStatusMessage(`已定位卫星:${sat.properties.name || sat.properties.norad_cat_id || "未知卫星"}`, "info");
}
async function focusSearchBGPMarker(marker) {
if (!getShowBGP()) {
toggleBGP(true);
}
interruptCruisePresentation({ resetLoop: true });
clearLockedObject();
setAutoRotate(false);
const coords = getBGPFocusCoords(marker);
if (coords) {
await focusSearchTarget(coords, Math.max(getZoomLevel(), 1.2));
}
const earth = getEarth();
if (marker?.userData?.type === "bgp") {
setBGPMarkerState(marker, "locked");
lockedObject = marker;
lockedObjectType = "bgp";
showBGPEventOverlay(marker, earth);
applyBGPEventSatelliteHighlights(marker);
showBGPInfo(marker, getSearchCardCoords());
showStatusMessage(`已定位 BGP 事件:${marker.userData?.collector || "未知观测站"}`, "info");
return;
}
if (marker?.userData?.type === "bgp_collector") {
setBGPMarkerState(marker, "locked");
lockedObject = marker;
lockedObjectType = "bgp_collector";
showBGPCollectorCoverageOverlay(marker, earth);
showBGPCollectorInfo(marker, getSearchCardCoords());
showStatusMessage(`已定位观测站:${marker.userData?.collector || "未知观测站"}`, "info");
}
}
function resolveEarthSearchResults(query) {
const results = [];
const normalizedQuery = query.trim().toLowerCase();
if (!normalizedQuery) return results;
getCableLines().forEach((cable) => {
const score = computeSearchScore(
normalizedQuery,
cable.userData?.name,
cable.userData?.owner,
cable.userData?.status,
cable.userData?.length,
"海缆 电缆 cable",
);
if (score < 0) return;
results.push({
id: `cable:${cable.userData?.cableId || cable.uuid}`,
kind: "cable",
icon: "cable",
typeLabel: "海缆",
title: cable.userData?.name || "未知海缆",
subtitle: [cable.userData?.owner, cable.userData?.status].filter(Boolean).join(" · ") || "海底光缆系统",
score,
entity: cable,
});
});
getLandingPoints().forEach((point, index) => {
const score = computeSearchScore(
normalizedQuery,
point.userData?.name,
point.userData?.country,
point.userData?.status,
point.userData?.cableNames,
"登陆点 landing point",
);
if (score < 0) return;
results.push({
id: `landing:${point.uuid || index}`,
kind: "landing_point",
icon: "location_on",
typeLabel: "登陆点",
title: point.userData?.name || "未知登陆点",
subtitle:
[point.userData?.country, Array.isArray(point.userData?.cableNames) ? `${point.userData.cableNames.length} 条海缆` : ""]
.filter(Boolean)
.join(" · ") || "海缆登陆点",
score,
entity: point,
});
});
getSatelliteData().forEach((satellite, index) => {
const props = satellite?.properties;
const score = computeSearchScore(
normalizedQuery,
props?.name,
props?.norad_cat_id,
props?.inclination,
"卫星 satellite norad",
);
if (score < 0) return;
results.push({
id: `sat:${props?.norad_cat_id || index}`,
kind: "satellite",
icon: "satellite_alt",
typeLabel: "卫星",
title: props?.name || `NORAD ${props?.norad_cat_id || index}`,
subtitle: props?.norad_cat_id ? `NORAD ${props.norad_cat_id}` : "在轨卫星",
score,
entity: { index },
});
});
getBGPAnomalyMarkers().forEach((marker) => {
const score = computeSearchScore(
normalizedQuery,
marker.userData?.collector,
marker.userData?.prefix,
marker.userData?.city,
marker.userData?.country,
marker.userData?.anomaly_type,
marker.userData?.incident_type,
marker.userData?.origin_asn,
marker.userData?.new_origin_asn,
"bgp 事件 anomaly prefix asn",
);
if (score < 0) return;
results.push({
id: `bgp:${marker.userData?.id || marker.uuid}`,
kind: "bgp",
icon: "hub",
typeLabel: "BGP事件",
title:
formatBGPAnomalyTypeLabel(
marker.userData?.incident_type || marker.userData?.anomaly_type,
) || "BGP 事件",
subtitle:
[
marker.userData?.collector,
marker.userData?.prefix,
formatBGPLocation(marker.userData?.city, marker.userData?.country),
]
.filter(Boolean)
.join(" · ") || "BGP 异常事件",
score,
entity: marker,
});
});
getBGPCollectorMarkers().forEach((marker) => {
const score = computeSearchScore(
normalizedQuery,
marker.userData?.collector,
marker.userData?.city,
marker.userData?.country,
marker.userData?.status,
"bgp collector 观测站",
);
if (score < 0) return;
results.push({
id: `collector:${marker.userData?.collector || marker.uuid}`,
kind: "bgp_collector",
icon: "travel_explore",
typeLabel: "观测站",
title: marker.userData?.collector || "未知观测站",
subtitle:
[
formatBGPLocation(marker.userData?.city, marker.userData?.country),
formatBGPCollectorStatus(marker.userData?.status || "online"),
]
.filter(Boolean)
.join(" · ") || "BGP 观测站",
score,
entity: marker,
});
});
return results
.sort((left, right) => {
if (right.score !== left.score) return right.score - left.score;
return left.title.localeCompare(right.title, "zh-CN");
})
.slice(0, SEARCH_RESULT_LIMIT);
}
async function handleSearchSelection(result) {
if (!result) return;
if (result.kind === "cable") {
await focusSearchCable(result.entity);
return;
}
if (result.kind === "landing_point") {
await focusSearchLandingPoint(result.entity);
return;
}
if (result.kind === "satellite") {
await focusSearchSatellite(result.entity.index);
return;
}
if (result.kind === "bgp" || result.kind === "bgp_collector") {
await focusSearchBGPMarker(result.entity);
}
}
function getBGPStatusText(bgpResult) {
if (bgpResult.totalCount > 0) {
return `${bgpResult.totalCount} 起活跃事件`;
@@ -1188,6 +1575,10 @@ export function init() {
mountBrand(brandRoot, HUD_CONFIG.brandLanguage);
initTVPanel();
initNewsPanel();
initSearchPanel({
resolveResults: resolveEarthSearchResults,
onSelectResult: handleSearchSelection,
});
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(
@@ -1474,6 +1865,9 @@ async function loadData() {
}
const POSITION_UPDATE_FORCE_DELTA = 250;
const SEARCH_RESULT_LIMIT = 28;
const SEARCH_CARD_X_RATIO = 0.68;
const SEARCH_CARD_Y_RATIO = 0.18;
export async function reloadData() {
await loadData();