// cables.js - Cable loading and rendering module import * as THREE from 'three'; import { CONFIG, CABLE_COLORS, PATHS } from './constants.js'; import { latLonToVector3 } from './utils.js'; import { updateCableDetails, updateEarthStats, showStatusMessage } from './ui.js'; export let cableLines = []; export let landingPoints = []; export let lockedCable = null; let cableIdMap = new Map(); function getCableColor(properties) { if (properties.color) { if (typeof properties.color === 'string' && properties.color.startsWith('#')) { return parseInt(properties.color.substring(1), 16); } else if (typeof properties.color === 'number') { return properties.color; } } const cableName = properties.Name || properties.cableName || properties.shortname || ''; if (cableName.includes('Americas II')) { return CABLE_COLORS['Americas II']; } else if (cableName.includes('AU Aleutian A')) { return CABLE_COLORS['AU Aleutian A']; } else if (cableName.includes('AU Aleutian B')) { return CABLE_COLORS['AU Aleutian B']; } return CABLE_COLORS.default; } function createCableLine(points, color, properties, earthObj) { if (points.length < 2) return null; const lineGeometry = new THREE.BufferGeometry().setFromPoints(points); const lineMaterial = new THREE.LineBasicMaterial({ color: color, linewidth: 1, transparent: true, opacity: 1.0, depthTest: true, depthWrite: true }); const cableLine = new THREE.Line(lineGeometry, lineMaterial); const cableId = properties.cable_id || properties.id || properties.Name || Math.random().toString(36); cableLine.userData = { type: 'cable', cableId: cableId, name: properties.Name || properties.cableName || 'Unknown', owner: properties.owner || properties.owners || '-', status: properties.status || '-', length: properties.length || '-', coords: '-', rfs: properties.rfs || '-', originalColor: color }; cableLine.renderOrder = 1; if (!cableIdMap.has(cableId)) { cableIdMap.set(cableId, []); } cableIdMap.get(cableId).push(cableLine); return cableLine; } function calculateGreatCirclePoints(lat1, lon1, lat2, lon2, radius, segments = 50) { const points = []; const phi1 = lat1 * Math.PI / 180; const lambda1 = lon1 * Math.PI / 180; const phi2 = lat2 * Math.PI / 180; const lambda2 = lon2 * Math.PI / 180; const dLambda = Math.min(Math.abs(lambda2 - lambda1), 2 * Math.PI - Math.abs(lambda2 - lambda1)); const cosDelta = Math.sin(phi1) * Math.sin(phi2) + Math.cos(phi1) * Math.cos(phi2) * Math.cos(dLambda); let delta = Math.acos(Math.max(-1, Math.min(1, cosDelta))); if (delta < 0.01) { const p1 = latLonToVector3(lat1, lon1, radius); const p2 = latLonToVector3(lat2, lon2, radius); return [p1, p2]; } for (let i = 0; i <= segments; i++) { const t = i / segments; const sinDelta = Math.sin(delta); const A = Math.sin((1 - t) * delta) / sinDelta; const B = Math.sin(t * delta) / sinDelta; const x1 = Math.cos(phi1) * Math.cos(lambda1); const y1 = Math.cos(phi1) * Math.sin(lambda1); const z1 = Math.sin(phi1); const x2 = Math.cos(phi2) * Math.cos(lambda2); const y2 = Math.cos(phi2) * Math.sin(lambda2); const z2 = Math.sin(phi2); let x = A * x1 + B * x2; let y = A * y1 + B * y2; let z = A * z1 + B * z2; const norm = Math.sqrt(x*x + y*y + z*z); x = x / norm * radius; y = y / norm * radius; z = z / norm * radius; const lat = Math.asin(z / radius) * 180 / Math.PI; let lon = Math.atan2(y, x) * 180 / Math.PI; if (lon > 180) lon -= 360; if (lon < -180) lon += 360; const point = latLonToVector3(lat, lon, radius); points.push(point); } return points; } export async function loadGeoJSONFromPath(scene, earthObj) { try { console.log('正在加载电缆数据...'); showStatusMessage('正在加载电缆数据...', 'warning'); const response = await fetch(PATHS.geoJSON); if (!response.ok) { throw new Error(`HTTP错误: ${response.status}`); } const data = await response.json(); cableLines.forEach(line => earthObj.remove(line)); cableLines = []; if (!data.features || !Array.isArray(data.features)) { throw new Error('无效的GeoJSON格式'); } const cableCount = data.features.length; document.getElementById('cable-count').textContent = cableCount + '个'; const inServiceCount = data.features.filter( feature => feature.properties && feature.properties.status === 'In Service' ).length; const statusEl = document.getElementById('cable-status-summary'); if (statusEl) { statusEl.textContent = `${inServiceCount}/${cableCount} 运行中`; } for (const feature of data.features) { const geometry = feature.geometry; const properties = feature.properties || {}; if (!geometry || !geometry.coordinates) continue; const color = getCableColor(properties); console.log('电缆:', properties.Name, '颜色:', color); if (geometry.type === 'MultiLineString') { for (const lineCoords of geometry.coordinates) { if (!lineCoords || lineCoords.length < 2) continue; const points = []; for (let i = 0; i < lineCoords.length - 1; i++) { const lon1 = lineCoords[i][0]; const lat1 = lineCoords[i][1]; const lon2 = lineCoords[i + 1][0]; const lat2 = lineCoords[i + 1][1]; const segment = calculateGreatCirclePoints(lat1, lon1, lat2, lon2, 100.2, 50); if (i === 0) { points.push(...segment); } else { points.push(...segment.slice(1)); } } if (points.length >= 2) { const line = createCableLine(points, color, properties, earthObj); if (line) { cableLines.push(line); earthObj.add(line); console.log('添加线缆成功'); } } } } else if (geometry.type === 'LineString') { const allCoords = geometry.coordinates; const points = []; for (let i = 0; i < allCoords.length - 1; i++) { const lon1 = allCoords[i][0]; const lat1 = allCoords[i][1]; const lon2 = allCoords[i + 1][0]; const lat2 = allCoords[i + 1][1]; const segment = calculateGreatCirclePoints(lat1, lon1, lat2, lon2, 100.2, 50); if (i === 0) { points.push(...segment); } else { points.push(...segment.slice(1)); } } if (points.length >= 2) { const line = createCableLine(points, color, properties, earthObj); if (line) { cableLines.push(line); earthObj.add(line); } } } } updateEarthStats({ cableCount: cableLines.length, landingPointCount: landingPoints.length, terrainOn: false, textureQuality: '8K 卫星图' }); showStatusMessage(`成功加载 ${cableLines.length} 条电缆`, 'success'); document.getElementById('loading').style.display = 'none'; } catch (error) { console.error('加载电缆数据失败:', error); showStatusMessage('加载电缆数据失败: ' + error.message, 'error'); } } export async function loadLandingPoints(scene, earthObj) { try { console.log('正在加载登陆点数据...'); const response = await fetch('./landing-point-geo.geojson'); if (!response.ok) { console.error('HTTP错误:', response.status); return; } const data = await response.json(); if (!data.features || !Array.isArray(data.features)) { console.error('无效的GeoJSON格式'); return; } landingPoints = []; let validCount = 0; const sphereGeometry = new THREE.SphereGeometry(0.4, 16, 16); const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xffaa00, emissive: 0x442200, emissiveIntensity: 0.5 }); for (const feature of data.features) { if (!feature.geometry || !feature.geometry.coordinates) continue; const [lon, lat] = feature.geometry.coordinates; const properties = feature.properties || {}; if (typeof lon !== 'number' || typeof lat !== 'number' || isNaN(lon) || isNaN(lat) || Math.abs(lat) > 90 || Math.abs(lon) > 180) { continue; } const position = latLonToVector3(lat, lon, 100.1); if (isNaN(position.x) || isNaN(position.y) || isNaN(position.z)) { continue; } const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial.clone()); sphere.position.copy(position); sphere.userData = { type: 'landingPoint', name: properties.name || '未知登陆站', cableName: properties.cable_system || '未知系统', country: properties.country || '未知国家', status: properties.status || 'Unknown' }; earthObj.add(sphere); landingPoints.push(sphere); validCount++; } console.log(`成功创建 ${validCount} 个登陆点标记`); showStatusMessage(`成功加载 ${validCount} 个登陆点`, 'success'); const lpCountEl = document.getElementById('landing-point-count'); if (lpCountEl) { lpCountEl.textContent = validCount + '个'; } } catch (error) { console.error('加载登陆点数据失败:', error); } } export function handleCableClick(cable) { lockedCable = cable; const data = cable.userData; // console.log(data) updateCableDetails({ name: data.name, owner: data.owner, status: data.status, length: data.length, coords: data.coords, rfs: data.rfs }); showStatusMessage(`已锁定: ${data.name}`, 'info'); } export function clearCableSelection() { lockedCable = null; updateCableDetails({ name: '点击电缆查看详情', owner: '-', status: '-', length: '-', coords: '-', rfs: '-' }); } export function getCableLines() { return cableLines; } export function getCablesById(cableId) { return cableIdMap.get(cableId) || []; } export function getLandingPoints() { return landingPoints; }