refactor: simplify earth bgp fallback and geography helpers

This commit is contained in:
linkong
2026-04-02 16:26:31 +08:00
parent e5fec8ba3d
commit f01d24240f
9 changed files with 191 additions and 190 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "planet-frontend",
"version": "0.22.10",
"version": "0.22.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "planet-frontend",
"version": "0.22.10",
"version": "0.22.11",
"dependencies": {
"@ant-design/icons": "^5.2.6",
"antd": "^5.12.5",

View File

@@ -1,6 +1,6 @@
{
"name": "planet-frontend",
"version": "0.22.10",
"version": "0.22.11",
"private": true,
"dependencies": {
"@ant-design/icons": "^5.2.6",

View File

@@ -636,26 +636,17 @@ function spreadCollectorPositions(markers) {
}
function buildAnomalyFeatureData(feature) {
const coordinates = feature?.geometry?.coordinates || [];
const [longitude, latitude] = coordinates;
if (
typeof latitude !== "number" ||
typeof longitude !== "number" ||
Number.isNaN(latitude) ||
Number.isNaN(longitude)
) {
return null;
}
const point = extractFeaturePoint(feature);
if (!point) return null;
const { latitude, longitude } = point;
const properties = feature?.properties || {};
const severity = normalizeSeverity(properties.severity);
const createdAt = properties.created_at || null;
const meta = extractFeatureMeta(properties, properties.created_at || null);
return {
latitude,
longitude,
rawSeverity: properties.severity || severity,
severity,
rawSeverity: meta.rawSeverity,
severity: meta.severity,
collector: properties.collector || "-",
city: properties.city || "-",
country: properties.country || "-",
@@ -673,8 +664,8 @@ function buildAnomalyFeatureData(feature) {
: [],
confidence: properties.confidence ?? "-",
summary: properties.summary || "-",
created_at: formatLocalDateTime(createdAt),
created_at_raw: createdAt,
created_at: meta.createdAt,
created_at_raw: meta.createdAtRaw,
id:
properties.id ||
`${properties.collector || "unknown"}-${latitude}-${longitude}`,
@@ -682,20 +673,12 @@ function buildAnomalyFeatureData(feature) {
}
function buildIncidentFeatureData(feature) {
const coordinates = feature?.geometry?.coordinates || [];
const [longitude, latitude] = coordinates;
if (
typeof latitude !== "number" ||
typeof longitude !== "number" ||
Number.isNaN(latitude) ||
Number.isNaN(longitude)
) {
return null;
}
const point = extractFeaturePoint(feature);
if (!point) return null;
const { latitude, longitude } = point;
const properties = feature?.properties || {};
const severity = normalizeSeverity(properties.severity);
const startedAt = properties.started_at || properties.created_at || null;
const meta = extractFeatureMeta(properties, startedAt);
const affectedPrefixes = Array.isArray(properties.affected_prefixes)
? properties.affected_prefixes
: [];
@@ -714,8 +697,8 @@ function buildIncidentFeatureData(feature) {
return {
latitude,
longitude,
rawSeverity: properties.severity || severity,
severity,
rawSeverity: meta.rawSeverity,
severity: meta.severity,
collector: affectedCollectors[0] || primaryRegion.collector || "-",
city: primaryRegion.city || "-",
country: primaryRegion.country || "-",
@@ -741,8 +724,8 @@ function buildIncidentFeatureData(feature) {
: [],
confidence: properties.confidence ?? "-",
summary: properties.summary || properties.title || "-",
created_at: formatLocalDateTime(startedAt),
created_at_raw: startedAt,
created_at: meta.createdAt,
created_at_raw: meta.createdAtRaw,
route_change:
affectedAsns.length > 1
? affectedAsns.slice(0, 2).map((asn) => `AS${asn}`).join(" -> ")
@@ -759,6 +742,30 @@ function buildIncidentFeatureData(feature) {
};
}
function extractFeaturePoint(feature) {
const coordinates = feature?.geometry?.coordinates || [];
const [longitude, latitude] = coordinates;
if (
typeof latitude !== "number" ||
typeof longitude !== "number" ||
Number.isNaN(latitude) ||
Number.isNaN(longitude)
) {
return null;
}
return { latitude, longitude };
}
function extractFeatureMeta(properties, createdAtRaw) {
const severity = normalizeSeverity(properties.severity);
return {
rawSeverity: properties.severity || severity,
severity,
createdAt: formatLocalDateTime(createdAtRaw),
createdAtRaw: createdAtRaw,
};
}
function clearMarkerArray(markers) {
while (markers.length > 0) {
const marker = markers.pop();
@@ -798,32 +805,6 @@ function createOverlaySprite({ color, opacity, scale }) {
return sprite;
}
function createArcLine(start, end, color, opacity = 0.82) {
const points = createArcPoints(start, end);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({
color,
transparent: true,
opacity,
depthWrite: false,
blending: THREE.AdditiveBlending,
});
return new THREE.Line(geometry, material);
}
function createArcPoints(start, end, heightOffset = BGP_CONFIG.eventHubAltitudeOffset * 0.8, segments = 32) {
const midpoint = start
.clone()
.add(end)
.multiplyScalar(0.5)
.normalize()
.multiplyScalar(CONFIG.earthRadius + heightOffset);
const curve = new THREE.QuadraticBezierCurve3(start, midpoint, end);
return curve.getPoints(segments);
}
function projectLatLon(lat, lon, bearingDeg, distanceDeg) {
const latRad = (lat * Math.PI) / 180;
const lonRad = (lon * Math.PI) / 180;
@@ -1237,6 +1218,43 @@ function applyCollectorCounts() {
});
}
async function fetchGeoJSONWithTimeout(url, timeoutMs, warningMessage, fallbackPayload) {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.warn(warningMessage, error);
return fallbackPayload;
}
}
function selectBGPEventFeatures(incidentPayload, anomalyPayload) {
const incidentFeatures = Array.isArray(incidentPayload?.features)
? incidentPayload.features
: [];
if (incidentFeatures.length > 0) {
return {
features: incidentFeatures,
totalIncidentCount: incidentPayload?.count ?? incidentFeatures.length,
totalAnomalyCount: anomalyPayload?.count ?? 0,
mode: "incident",
};
}
const anomalyFeatures = Array.isArray(anomalyPayload?.features)
? anomalyPayload.features
: [];
return {
features: anomalyFeatures,
totalIncidentCount: 0,
totalAnomalyCount: anomalyPayload?.count ?? anomalyFeatures.length,
mode: "anomaly",
};
}
export async function loadBGPAnomalies(scene, earth) {
clearBGPData(earth);
@@ -1245,47 +1263,27 @@ export async function loadBGPAnomalies(scene, earth) {
throw new Error(`BGP collectors HTTP ${collectorsResponse.status}`);
}
let anomaliesPayload = { type: "FeatureCollection", features: [], count: 0 };
try {
const anomaliesResponse = await fetch(
`${PATHS.bgpApi}?limit=${BGP_CONFIG.defaultFetchLimit}`,
{ signal: AbortSignal.timeout(5000) },
);
if (!anomaliesResponse.ok) {
throw new Error(`BGP anomalies HTTP ${anomaliesResponse.status}`);
}
anomaliesPayload = await anomaliesResponse.json();
} catch (error) {
console.warn("BGP anomalies unavailable, falling back to collectors only:", error);
}
let incidentsPayload = { type: "FeatureCollection", features: [], count: 0 };
try {
const incidentsResponse = await fetch(
`${PATHS.bgpIncidentsApi}?limit=${BGP_CONFIG.defaultFetchLimit}`,
{ signal: AbortSignal.timeout(5000) },
);
if (!incidentsResponse.ok) {
throw new Error(`BGP incidents HTTP ${incidentsResponse.status}`);
}
incidentsPayload = await incidentsResponse.json();
} catch (error) {
console.warn("BGP incidents unavailable, falling back to anomalies:", error);
}
const emptyPayload = { type: "FeatureCollection", features: [], count: 0 };
const anomaliesPayload = await fetchGeoJSONWithTimeout(
`${PATHS.bgpApi}?limit=${BGP_CONFIG.defaultFetchLimit}`,
5000,
"BGP anomalies unavailable, falling back to collectors only:",
emptyPayload,
);
const incidentsPayload = await fetchGeoJSONWithTimeout(
`${PATHS.bgpIncidentsApi}?limit=${BGP_CONFIG.defaultFetchLimit}`,
5000,
"BGP incidents unavailable, falling back to anomalies:",
emptyPayload,
);
const collectorsPayload = await collectorsResponse.json();
const collectorFeatures = Array.isArray(collectorsPayload?.features)
? collectorsPayload.features
: [];
const incidentFeatures = Array.isArray(incidentsPayload?.features)
? incidentsPayload.features
: [];
const anomalyFeatures = Array.isArray(anomaliesPayload?.features)
? anomaliesPayload.features
: [];
totalAnomalyCount = anomaliesPayload?.count ?? anomalyFeatures.length;
totalIncidentCount = incidentsPayload?.count ?? incidentFeatures.length;
const selectedEventData = selectBGPEventFeatures(incidentsPayload, anomaliesPayload);
totalAnomalyCount = selectedEventData.totalAnomalyCount;
totalIncidentCount = selectedEventData.totalIncidentCount;
activeEventCountByCollector.clear();
spreadCollectorPositions(
@@ -1294,11 +1292,10 @@ export async function loadBGPAnomalies(scene, earth) {
.filter(Boolean),
).forEach(createCollectorMarker);
const incidentMarkers = dedupeIncidents(incidentFeatures);
if (incidentMarkers.length > 0) {
incidentMarkers.forEach(createAnomalyMarker);
if (selectedEventData.mode === "incident") {
dedupeIncidents(selectedEventData.features).forEach(createAnomalyMarker);
} else {
dedupeAnomalies(anomalyFeatures).forEach(createAnomalyMarker);
dedupeAnomalies(selectedEventData.features).forEach(createAnomalyMarker);
}
applyCollectorCounts();

View File

@@ -29,8 +29,6 @@ export const PATHS = {
bgpApi: '/api/v1/visualization/geo/bgp-anomalies',
bgpIncidentsApi: '/api/v1/visualization/geo/bgp-incidents',
bgpCollectorsApi: '/api/v1/visualization/geo/bgp-collectors',
geoJSON: './geo.json',
landingPointsStatic: './landing-point-geo.geojson',
};
// Cable colors mapping
@@ -83,8 +81,6 @@ export const BGP_CONFIG = {
dimmedScale: 0.92,
pulseSpeed: 0.0045,
collectorPulseSpeed: 0.0024,
eventHubAltitudeOffset: 7.2,
eventHubScale: 4.8,
regionScale: 11.5,
normalPulseAmplitude: 0.08,
lockedPulseAmplitude: 0.28,
@@ -135,8 +131,6 @@ export const BGP_CONFIG = {
lockedNeutralColor: 0xd8e4ed,
dimmedColor: 0x7d8ca3,
},
eventHubColor: 0x8af5ff,
linkColor: 0x54d2ff,
regionColor: 0x2dd4bf,
eventRingScaleA: 2.5,
eventRingScaleB: 3.4,