refactor: simplify earth bgp fallback and geography helpers
This commit is contained in:
1
TODO.md
1
TODO.md
@@ -14,3 +14,4 @@
|
||||
- [ ] 接入 `OpenGeoFeed` 作为 prefix geography 的高质量覆盖/override 数据源
|
||||
- [ ] 把 RIR delegated / `inetnum` / `inet6num` whois 设计成 prefix geography 的 fallback,而不是主来源
|
||||
- [x] 在 activity layer 之后继续补 `route leak` 和 `path instability / flap` detector
|
||||
- [ ] 对 [frontend/public/earth/js/bgp.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/bgp.js) 做按职责拆分的小重构,拆成 data / markers / overlays / animation,降低后续维护复杂度
|
||||
|
||||
@@ -19,7 +19,6 @@ from app.models.bgp_anomaly import BGPAnomaly
|
||||
from app.models.bgp_incident import BGPIncident
|
||||
from app.models.collected_data import CollectedData
|
||||
from app.services.bgp_collectors import build_bgp_collector_coverage
|
||||
from app.services.bgp_enrichment import _lookup_prefix_geography
|
||||
from app.services.cable_graph import build_graph_from_data, CableGraph, haversine_distance
|
||||
from app.services.collectors.bgp_common import RIPE_RIS_COLLECTOR_COORDS
|
||||
|
||||
@@ -437,44 +436,9 @@ async def build_anomaly_geography_hints(
|
||||
) -> Dict[str, Dict[str, Any]]:
|
||||
hints: Dict[str, Dict[str, Any]] = {}
|
||||
for record in records:
|
||||
evidence = record.evidence or {}
|
||||
key = str(record.entity_key or record.id)
|
||||
|
||||
prefix_geo_regions = []
|
||||
prefix_regions = []
|
||||
asn_regions = []
|
||||
evidence_prefix_geography = evidence.get("prefix_geography") or {}
|
||||
prefix_geo_regions.extend(
|
||||
_normalize_geo_regions(evidence_prefix_geography.get("regions") or [])
|
||||
)
|
||||
|
||||
prefix_scope = evidence.get("prefix_scope") or {}
|
||||
prefix_regions.extend(_normalize_geo_regions(prefix_scope.get("regions") or []))
|
||||
|
||||
for profile_key in ("origin_asn_profile", "new_origin_asn_profile"):
|
||||
profile = evidence.get(profile_key) or {}
|
||||
latitude = profile.get("latitude")
|
||||
longitude = profile.get("longitude")
|
||||
if isinstance(latitude, (int, float)) and isinstance(longitude, (int, float)):
|
||||
asn_regions.append(
|
||||
{
|
||||
"country": profile.get("country"),
|
||||
"city": profile.get("city"),
|
||||
"latitude": float(latitude),
|
||||
"longitude": float(longitude),
|
||||
}
|
||||
)
|
||||
|
||||
prefix_geo_regions = _normalize_geo_regions(prefix_geo_regions)
|
||||
prefix_regions = _normalize_geo_regions(prefix_regions)
|
||||
asn_regions = _normalize_geo_regions(asn_regions)
|
||||
|
||||
if prefix_geo_regions:
|
||||
hints[key] = {"regions": prefix_geo_regions, "geography_mode": "prefix_geography"}
|
||||
elif prefix_regions:
|
||||
hints[key] = {"regions": prefix_regions, "geography_mode": "prefix_scope"}
|
||||
elif asn_regions:
|
||||
hints[key] = {"regions": asn_regions, "geography_mode": "asn_region"}
|
||||
hint = _extract_evidence_geography_hint(record.evidence or {})
|
||||
if hint:
|
||||
hints[str(record.entity_key or record.id)] = hint
|
||||
|
||||
return hints
|
||||
|
||||
@@ -597,6 +561,46 @@ def _normalize_geo_regions(regions: List[Dict[str, Any]]) -> List[Dict[str, Any]
|
||||
return normalized
|
||||
|
||||
|
||||
def _extract_evidence_geography_hint(evidence: Dict[str, Any]) -> Dict[str, Any] | None:
|
||||
prefix_geo_regions = []
|
||||
prefix_regions = []
|
||||
asn_regions = []
|
||||
|
||||
evidence_prefix_geography = evidence.get("prefix_geography") or {}
|
||||
prefix_geo_regions.extend(
|
||||
_normalize_geo_regions(evidence_prefix_geography.get("regions") or [])
|
||||
)
|
||||
|
||||
prefix_scope = evidence.get("prefix_scope") or {}
|
||||
prefix_regions.extend(_normalize_geo_regions(prefix_scope.get("regions") or []))
|
||||
|
||||
for profile_key in ("origin_asn_profile", "new_origin_asn_profile"):
|
||||
profile = evidence.get(profile_key) or {}
|
||||
latitude = profile.get("latitude")
|
||||
longitude = profile.get("longitude")
|
||||
if isinstance(latitude, (int, float)) and isinstance(longitude, (int, float)):
|
||||
asn_regions.append(
|
||||
{
|
||||
"country": profile.get("country"),
|
||||
"city": profile.get("city"),
|
||||
"latitude": float(latitude),
|
||||
"longitude": float(longitude),
|
||||
}
|
||||
)
|
||||
|
||||
prefix_geo_regions = _normalize_geo_regions(prefix_geo_regions)
|
||||
prefix_regions = _normalize_geo_regions(prefix_regions)
|
||||
asn_regions = _normalize_geo_regions(asn_regions)
|
||||
|
||||
if prefix_geo_regions:
|
||||
return {"regions": prefix_geo_regions, "geography_mode": "prefix_geography"}
|
||||
if prefix_regions:
|
||||
return {"regions": prefix_regions, "geography_mode": "prefix_scope"}
|
||||
if asn_regions:
|
||||
return {"regions": asn_regions, "geography_mode": "asn_region"}
|
||||
return None
|
||||
|
||||
|
||||
async def build_incident_geography_hints(
|
||||
db: AsyncSession,
|
||||
records: List[BGPIncident],
|
||||
@@ -624,53 +628,33 @@ async def build_incident_geography_hints(
|
||||
|
||||
hints: Dict[str, Dict[str, Any]] = {}
|
||||
for record in records:
|
||||
prefix_geo_regions: list[dict[str, Any]] = []
|
||||
prefix_regions: list[dict[str, Any]] = []
|
||||
asn_regions: list[dict[str, Any]] = []
|
||||
merged_hint: Dict[str, Any] | None = None
|
||||
priority = {"prefix_geography": 3, "prefix_scope": 2, "asn_region": 1}
|
||||
|
||||
for ref in record.evidence_refs or []:
|
||||
anomaly = anomaly_by_key.get(str(ref))
|
||||
if anomaly is None:
|
||||
continue
|
||||
evidence = anomaly.evidence or {}
|
||||
if not prefix_geo_regions:
|
||||
prefix_geography = evidence.get("prefix_geography") or {}
|
||||
prefix_geo_regions.extend(_normalize_geo_regions(prefix_geography.get("regions") or []))
|
||||
prefix_scope = evidence.get("prefix_scope") or {}
|
||||
prefix_regions.extend(_normalize_geo_regions(prefix_scope.get("regions") or []))
|
||||
hint = _extract_evidence_geography_hint(anomaly.evidence or {})
|
||||
if hint is None:
|
||||
continue
|
||||
if merged_hint is None:
|
||||
merged_hint = {
|
||||
"regions": list(hint["regions"]),
|
||||
"geography_mode": hint["geography_mode"],
|
||||
}
|
||||
continue
|
||||
if priority[hint["geography_mode"]] > priority[merged_hint["geography_mode"]]:
|
||||
merged_hint = {
|
||||
"regions": list(hint["regions"]),
|
||||
"geography_mode": hint["geography_mode"],
|
||||
}
|
||||
elif priority[hint["geography_mode"]] == priority[merged_hint["geography_mode"]]:
|
||||
merged_hint["regions"].extend(hint["regions"])
|
||||
|
||||
for key in ("origin_asn_profile", "new_origin_asn_profile"):
|
||||
profile = evidence.get(key) or {}
|
||||
latitude = profile.get("latitude")
|
||||
longitude = profile.get("longitude")
|
||||
if isinstance(latitude, (int, float)) and isinstance(longitude, (int, float)):
|
||||
asn_regions.append(
|
||||
{
|
||||
"country": profile.get("country"),
|
||||
"city": profile.get("city"),
|
||||
"latitude": float(latitude),
|
||||
"longitude": float(longitude),
|
||||
}
|
||||
)
|
||||
|
||||
prefix_geo_regions = _normalize_geo_regions(prefix_geo_regions)
|
||||
prefix_regions = _normalize_geo_regions(prefix_regions)
|
||||
asn_regions = _normalize_geo_regions(asn_regions)
|
||||
if prefix_geo_regions:
|
||||
hints[record.incident_key] = {
|
||||
"regions": prefix_geo_regions,
|
||||
"geography_mode": "prefix_geography",
|
||||
}
|
||||
elif prefix_regions:
|
||||
hints[record.incident_key] = {
|
||||
"regions": prefix_regions,
|
||||
"geography_mode": "prefix_scope",
|
||||
}
|
||||
elif asn_regions:
|
||||
hints[record.incident_key] = {
|
||||
"regions": asn_regions,
|
||||
"geography_mode": "asn_region",
|
||||
}
|
||||
if merged_hint:
|
||||
merged_hint["regions"] = _normalize_geo_regions(merged_hint["regions"])
|
||||
hints[record.incident_key] = merged_hint
|
||||
|
||||
return hints
|
||||
|
||||
|
||||
@@ -7,6 +7,31 @@ This project follows the repository versioning rule:
|
||||
- `feature` -> `+0.1.0`
|
||||
- `bugfix` -> `+0.0.1`
|
||||
|
||||
## 0.22.11
|
||||
|
||||
Released: 2026-04-02
|
||||
|
||||
### Highlights
|
||||
|
||||
- Cleaned up the most obvious BGP/Earth cleanup leftovers from the recent stabilization work without changing the current user-facing interaction model.
|
||||
|
||||
### Improved
|
||||
|
||||
- Improved [bgp.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/bgp.js) by factoring the BGP Earth loading flow into smaller helpers, separating timed GeoJSON fetch fallback from `incident vs anomaly` render-mode selection.
|
||||
- Improved [bgp.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/bgp.js) by extracting shared feature parsing helpers so anomaly and incident marker preparation no longer duplicate coordinate, severity, and timestamp parsing logic.
|
||||
- Improved [visualization.py](/home/ray/dev/linkong/planet/backend/app/api/v1/visualization.py) by consolidating repeated BGP evidence geography parsing into a shared helper used by both anomaly and incident geography-hint builders.
|
||||
- Improved planning hygiene in [TODO.md](/home/ray/dev/linkong/planet/TODO.md) by recording the deferred `bgp.js` responsibility split as an explicit follow-up task instead of leaving the idea only in conversation context.
|
||||
|
||||
### Refined
|
||||
|
||||
- Refined [constants.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/constants.js) by removing stale Earth BGP configuration entries that were left behind after the floating event hub design was removed.
|
||||
- Refined [bgp.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/bgp.js) by removing no-longer-used arc helpers that only served the deleted off-surface hub/link route.
|
||||
- Refined [visualization.py](/home/ray/dev/linkong/planet/backend/app/api/v1/visualization.py) by dropping an unused `_lookup_prefix_geography` import after the earlier rollback from expensive live prefix-geography visualization lookups.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the codebase drift where Earth BGP cleanup patches had left behind dead constants, duplicate parsing branches, and fallback-loading glue that was harder to reason about than the now-stable runtime required.
|
||||
|
||||
## 0.22.10
|
||||
|
||||
Released: 2026-04-02
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planet-frontend",
|
||||
"version": "0.22.10",
|
||||
"version": "0.22.11",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "planet"
|
||||
version = "0.22.10"
|
||||
version = "0.22.11"
|
||||
description = "智能星球计划 - 态势感知系统"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
|
||||
Reference in New Issue
Block a user