feat(earth): add cable-landing point relation via city_id
Backend: - Fix arcgis_landing collector to extract city_id - Fix arcgis_relation collector to extract city_id - Fix convert_landing_point_to_geojson to use city_id mapping Frontend: - Update landing point cableNames to use array - Add applyLandingPointVisualState for cable lock highlight - Dim all landing points when satellite is locked
This commit is contained in:
@@ -96,8 +96,7 @@ def convert_cable_to_geojson(records: List[CollectedData]) -> Dict[str, Any]:
|
||||
return {"type": "FeatureCollection", "features": features}
|
||||
|
||||
|
||||
def convert_landing_point_to_geojson(records: List[CollectedData]) -> Dict[str, Any]:
|
||||
"""Convert landing point records to GeoJSON FeatureCollection"""
|
||||
def convert_landing_point_to_geojson(records: List[CollectedData], city_to_cable_ids_map: Dict[int, List[int]] = None, cable_id_to_name_map: Dict[int, str] = None) -> Dict[str, Any]:
|
||||
features = []
|
||||
|
||||
for record in records:
|
||||
@@ -111,19 +110,31 @@ def convert_landing_point_to_geojson(records: List[CollectedData]) -> Dict[str,
|
||||
continue
|
||||
|
||||
metadata = record.extra_data or {}
|
||||
city_id = metadata.get("city_id")
|
||||
|
||||
props = {
|
||||
"id": record.id,
|
||||
"source_id": record.source_id,
|
||||
"name": record.name,
|
||||
"country": record.country,
|
||||
"city": record.city,
|
||||
"is_tbd": metadata.get("is_tbd", False),
|
||||
}
|
||||
|
||||
cable_names = []
|
||||
if city_to_cable_ids_map and city_id in city_to_cable_ids_map:
|
||||
for cable_id in city_to_cable_ids_map[city_id]:
|
||||
if cable_id_to_name_map and cable_id in cable_id_to_name_map:
|
||||
cable_names.append(cable_id_to_name_map[cable_id])
|
||||
|
||||
if cable_names:
|
||||
props["cable_names"] = cable_names
|
||||
|
||||
features.append(
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "Point", "coordinates": [lon, lat]},
|
||||
"properties": {
|
||||
"id": record.id,
|
||||
"source_id": record.source_id,
|
||||
"name": record.name,
|
||||
"country": record.country,
|
||||
"city": record.city,
|
||||
"is_tbd": metadata.get("is_tbd", False),
|
||||
},
|
||||
"properties": props,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -264,11 +275,37 @@ async def get_cables_geojson(db: AsyncSession = Depends(get_db)):
|
||||
|
||||
@router.get("/geo/landing-points")
|
||||
async def get_landing_points_geojson(db: AsyncSession = Depends(get_db)):
|
||||
"""获取登陆点 GeoJSON 数据 (Point)"""
|
||||
try:
|
||||
stmt = select(CollectedData).where(CollectedData.source == "arcgis_landing_points")
|
||||
result = await db.execute(stmt)
|
||||
records = result.scalars().all()
|
||||
landing_stmt = select(CollectedData).where(CollectedData.source == "arcgis_landing_points")
|
||||
landing_result = await db.execute(landing_stmt)
|
||||
records = landing_result.scalars().all()
|
||||
|
||||
relation_stmt = select(CollectedData).where(CollectedData.source == "arcgis_cable_landing_relation")
|
||||
relation_result = await db.execute(relation_stmt)
|
||||
relation_records = relation_result.scalars().all()
|
||||
|
||||
cable_stmt = select(CollectedData).where(CollectedData.source == "arcgis_cables")
|
||||
cable_result = await db.execute(cable_stmt)
|
||||
cable_records = cable_result.scalars().all()
|
||||
|
||||
city_to_cable_ids_map = {}
|
||||
for rel in relation_records:
|
||||
if rel.extra_data:
|
||||
city_id = rel.extra_data.get("city_id")
|
||||
cable_id = rel.extra_data.get("cable_id")
|
||||
if city_id is not None and cable_id is not None:
|
||||
if city_id not in city_to_cable_ids_map:
|
||||
city_to_cable_ids_map[city_id] = []
|
||||
if cable_id not in city_to_cable_ids_map[city_id]:
|
||||
city_to_cable_ids_map[city_id].append(cable_id)
|
||||
|
||||
cable_id_to_name_map = {}
|
||||
for cable in cable_records:
|
||||
if cable.extra_data:
|
||||
cable_id = cable.extra_data.get("cable_id")
|
||||
cable_name = cable.name
|
||||
if cable_id and cable_name:
|
||||
cable_id_to_name_map[cable_id] = cable_name
|
||||
|
||||
if not records:
|
||||
raise HTTPException(
|
||||
@@ -276,7 +313,7 @@ async def get_landing_points_geojson(db: AsyncSession = Depends(get_db)):
|
||||
detail="No landing point data found. Please run the arcgis_landing_points collector first.",
|
||||
)
|
||||
|
||||
return convert_landing_point_to_geojson(records)
|
||||
return convert_landing_point_to_geojson(records, city_to_cable_ids_map, cable_id_to_name_map)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
@@ -285,7 +322,6 @@ async def get_landing_points_geojson(db: AsyncSession = Depends(get_db)):
|
||||
|
||||
@router.get("/geo/all")
|
||||
async def get_all_geojson(db: AsyncSession = Depends(get_db)):
|
||||
"""获取所有可视化数据 (电缆 + 登陆点)"""
|
||||
cables_stmt = select(CollectedData).where(CollectedData.source == "arcgis_cables")
|
||||
cables_result = await db.execute(cables_stmt)
|
||||
cables_records = cables_result.scalars().all()
|
||||
@@ -294,13 +330,36 @@ async def get_all_geojson(db: AsyncSession = Depends(get_db)):
|
||||
points_result = await db.execute(points_stmt)
|
||||
points_records = points_result.scalars().all()
|
||||
|
||||
relation_stmt = select(CollectedData).where(CollectedData.source == "arcgis_cable_landing_relation")
|
||||
relation_result = await db.execute(relation_stmt)
|
||||
relation_records = relation_result.scalars().all()
|
||||
|
||||
city_to_cable_ids_map = {}
|
||||
for rel in relation_records:
|
||||
if rel.extra_data:
|
||||
city_id = rel.extra_data.get("city_id")
|
||||
cable_id = rel.extra_data.get("cable_id")
|
||||
if city_id is not None and cable_id is not None:
|
||||
if city_id not in city_to_cable_ids_map:
|
||||
city_to_cable_ids_map[city_id] = []
|
||||
if cable_id not in city_to_cable_ids_map[city_id]:
|
||||
city_to_cable_ids_map[city_id].append(cable_id)
|
||||
|
||||
cable_id_to_name_map = {}
|
||||
for cable in cables_records:
|
||||
if cable.extra_data:
|
||||
cable_id = cable.extra_data.get("cable_id")
|
||||
cable_name = cable.name
|
||||
if cable_id and cable_name:
|
||||
cable_id_to_name_map[cable_id] = cable_name
|
||||
|
||||
cables = (
|
||||
convert_cable_to_geojson(cables_records)
|
||||
if cables_records
|
||||
else {"type": "FeatureCollection", "features": []}
|
||||
)
|
||||
points = (
|
||||
convert_landing_point_to_geojson(points_records)
|
||||
convert_landing_point_to_geojson(points_records, city_to_cable_ids_map, cable_id_to_name_map)
|
||||
if points_records
|
||||
else {"type": "FeatureCollection", "features": []}
|
||||
)
|
||||
|
||||
@@ -59,6 +59,7 @@ class ArcGISLandingPointCollector(BaseCollector):
|
||||
"unit": "",
|
||||
"metadata": {
|
||||
"objectid": props.get("OBJECTID"),
|
||||
"city_id": props.get("city_id"),
|
||||
"cable_id": props.get("cable_id"),
|
||||
"cable_name": props.get("cable_name"),
|
||||
"facility": props.get("facility"),
|
||||
|
||||
@@ -50,6 +50,7 @@ class ArcGISCableLandingRelationCollector(BaseCollector):
|
||||
"unit": "",
|
||||
"metadata": {
|
||||
"objectid": props.get("OBJECTID"),
|
||||
"city_id": props.get("city_id"),
|
||||
"cable_id": props.get("cable_id"),
|
||||
"cable_name": props.get("cable_name"),
|
||||
"landing_point_id": props.get("landing_point_id"),
|
||||
|
||||
@@ -286,7 +286,7 @@ export async function loadLandingPoints(scene, earthObj) {
|
||||
sphere.userData = {
|
||||
type: 'landingPoint',
|
||||
name: properties.name || '未知登陆站',
|
||||
cableName: properties.cable_system || '未知系统',
|
||||
cableNames: properties.cable_names || [],
|
||||
country: properties.country || '未知国家',
|
||||
status: properties.status || 'Unknown'
|
||||
};
|
||||
@@ -362,3 +362,47 @@ export function getCableStateInfo() {
|
||||
});
|
||||
return states;
|
||||
}
|
||||
|
||||
export function getLandingPointsByCableName(cableName) {
|
||||
return landingPoints.filter(lp => lp.userData.cableNames?.includes(cableName));
|
||||
}
|
||||
|
||||
export function getAllLandingPoints() {
|
||||
return landingPoints;
|
||||
}
|
||||
|
||||
export function applyLandingPointVisualState(lockedCableName, dimAll = false) {
|
||||
const pulse = (Math.sin(Date.now() * 0.003) + 1) * 0.5;
|
||||
const brightness = 0.3;
|
||||
|
||||
landingPoints.forEach(lp => {
|
||||
const isRelated = !dimAll && lp.userData.cableNames?.includes(lockedCableName);
|
||||
|
||||
if (isRelated) {
|
||||
lp.material.color.setHex(0xffaa00);
|
||||
lp.material.emissive.setHex(0x442200);
|
||||
lp.material.emissiveIntensity = 0.5 + pulse * 0.5;
|
||||
lp.material.opacity = 0.8 + pulse * 0.2;
|
||||
lp.scale.setScalar(1.2 + pulse * 0.3);
|
||||
} else {
|
||||
const r = 255 * brightness;
|
||||
const g = 170 * brightness;
|
||||
const b = 0 * brightness;
|
||||
lp.material.color.setRGB(r / 255, g / 255, b / 255);
|
||||
lp.material.emissive.setHex(0x000000);
|
||||
lp.material.emissiveIntensity = 0;
|
||||
lp.material.opacity = 0.3;
|
||||
lp.scale.setScalar(1.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function resetLandingPointVisualState() {
|
||||
landingPoints.forEach(lp => {
|
||||
lp.material.color.setHex(0xffaa00);
|
||||
lp.material.emissive.setHex(0x442200);
|
||||
lp.material.emissiveIntensity = 0.5;
|
||||
lp.material.opacity = 1.0;
|
||||
lp.scale.setScalar(1.0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
hideTooltip
|
||||
} from './ui.js';
|
||||
import { createEarth, createClouds, createTerrain, createStars, createGridLines, toggleTerrain, getEarth } from './earth.js';
|
||||
import { loadGeoJSONFromPath, loadLandingPoints, handleCableClick, clearCableSelection, getCableLines, getCablesById, lockedCable as cableLocked, getCableState, setCableState, clearAllCableStates } from './cables.js';
|
||||
import { loadGeoJSONFromPath, loadLandingPoints, handleCableClick, clearCableSelection, getCableLines, getCablesById, lockedCable as cableLocked, getCableState, setCableState, clearAllCableStates, applyLandingPointVisualState, resetLandingPointVisualState, getAllLandingPoints } from './cables.js';
|
||||
import { createSatellites, loadSatellites, updateSatellitePositions, toggleSatellites, toggleTrails, getShowSatellites, selectSatellite, getSatelliteData, getSatellitePoints, setSatelliteRingState, updateLockedRingPosition, updateHoverRingPosition, getSatellitePositions } from './satellites.js';
|
||||
import { setupControls, getAutoRotate, getShowTerrain, zoomLevel, setAutoRotate, toggleAutoRotate, resetView } from './controls.js';
|
||||
import { initInfoCard, showInfoCard, hideInfoCard, getCurrentType, setInfoCardNoBorder } from './info-card.js';
|
||||
@@ -475,6 +475,14 @@ function animate() {
|
||||
|
||||
applyCableVisualState();
|
||||
|
||||
if (lockedObjectType === 'cable' && lockedObject) {
|
||||
applyLandingPointVisualState(lockedObject.userData.name, false);
|
||||
} else if (lockedObjectType === 'satellite' && lockedSatellite) {
|
||||
applyLandingPointVisualState(null, true);
|
||||
} else {
|
||||
resetLandingPointVisualState();
|
||||
}
|
||||
|
||||
updateSatellitePositions(16);
|
||||
|
||||
const satPositions = getSatellitePositions();
|
||||
|
||||
Reference in New Issue
Block a user