release: bump version to 0.40.0
This commit is contained in:
@@ -184,6 +184,12 @@ def convert_satellite_to_geojson(records: List[CollectedData]) -> Dict[str, Any]
|
|||||||
mean_motion=metadata.get("mean_motion"),
|
mean_motion=metadata.get("mean_motion"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
constellation_group = _normalize_satellite_constellation_group(
|
||||||
|
metadata.get("constellation_group"),
|
||||||
|
record.name,
|
||||||
|
)
|
||||||
|
footprint_policy = _get_satellite_footprint_policy(constellation_group)
|
||||||
|
|
||||||
features.append(
|
features.append(
|
||||||
{
|
{
|
||||||
"type": "Feature",
|
"type": "Feature",
|
||||||
@@ -193,6 +199,8 @@ def convert_satellite_to_geojson(records: List[CollectedData]) -> Dict[str, Any]
|
|||||||
"id": record.id,
|
"id": record.id,
|
||||||
"norad_cat_id": norad_id,
|
"norad_cat_id": norad_id,
|
||||||
"name": record.name,
|
"name": record.name,
|
||||||
|
"constellation_group": constellation_group,
|
||||||
|
"footprint_policy": footprint_policy,
|
||||||
"international_designator": metadata.get("international_designator"),
|
"international_designator": metadata.get("international_designator"),
|
||||||
"epoch": metadata.get("epoch"),
|
"epoch": metadata.get("epoch"),
|
||||||
"inclination": metadata.get("inclination"),
|
"inclination": metadata.get("inclination"),
|
||||||
@@ -213,6 +221,31 @@ def convert_satellite_to_geojson(records: List[CollectedData]) -> Dict[str, Any]
|
|||||||
return {"type": "FeatureCollection", "features": features}
|
return {"type": "FeatureCollection", "features": features}
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_satellite_constellation_group(
|
||||||
|
raw_group: Any,
|
||||||
|
name: Optional[str],
|
||||||
|
) -> Optional[str]:
|
||||||
|
normalized_group = str(raw_group or "").strip().lower()
|
||||||
|
if normalized_group:
|
||||||
|
return normalized_group
|
||||||
|
|
||||||
|
normalized_name = str(name or "").strip().upper()
|
||||||
|
if normalized_name.startswith("STARLINK"):
|
||||||
|
return "starlink"
|
||||||
|
if normalized_name.startswith("IRIDIUM"):
|
||||||
|
return "iridium-next"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_satellite_footprint_policy(constellation_group: Optional[str]) -> str:
|
||||||
|
if constellation_group == "starlink":
|
||||||
|
return "starlink_ground_footprint"
|
||||||
|
if constellation_group == "iridium-next":
|
||||||
|
return "iridium_coverage_ring"
|
||||||
|
return "none"
|
||||||
|
|
||||||
|
|
||||||
def _current_collected_data_stmt(source: str):
|
def _current_collected_data_stmt(source: str):
|
||||||
return (
|
return (
|
||||||
select(CollectedData)
|
select(CollectedData)
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ class CelesTrakTLECollector(BaseCollector):
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if isinstance(data, list):
|
if isinstance(data, list):
|
||||||
|
for item in data:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
item["_celestrak_group"] = group
|
||||||
all_satellites.extend(data)
|
all_satellites.extend(data)
|
||||||
print(f"CelesTrak: Fetched {len(data)} satellites from group '{group}'")
|
print(f"CelesTrak: Fetched {len(data)} satellites from group '{group}'")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -78,6 +81,7 @@ class CelesTrakTLECollector(BaseCollector):
|
|||||||
"name": item.get("OBJECT_NAME", "Unknown"),
|
"name": item.get("OBJECT_NAME", "Unknown"),
|
||||||
"reference_date": item.get("EPOCH", ""),
|
"reference_date": item.get("EPOCH", ""),
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
"constellation_group": item.get("_celestrak_group"),
|
||||||
"norad_cat_id": item.get("NORAD_CAT_ID"),
|
"norad_cat_id": item.get("NORAD_CAT_ID"),
|
||||||
"international_designator": item.get("OBJECT_ID"),
|
"international_designator": item.get("OBJECT_ID"),
|
||||||
"epoch": item.get("EPOCH"),
|
"epoch": item.get("EPOCH"),
|
||||||
|
|||||||
@@ -10,6 +10,25 @@ This project follows the repository versioning rule:
|
|||||||
|
|
||||||
## [0.39.0] — 2026-04-24
|
## [0.39.0] — 2026-04-24
|
||||||
|
|
||||||
|
## [0.40.0] — 2026-04-24
|
||||||
|
|
||||||
|
### ✨ Highlights
|
||||||
|
- Earth 卫星 footprint 正式按星座能力分层:Starlink 保留专用地表覆盖,Iridium 改为独立外圈覆盖表达,其它非 Starlink 星座不再误用同一套 footprint
|
||||||
|
- Earth 卫星详情卡补齐覆盖能力与当前显示说明,用户现在可以直接看见每颗卫星为什么显示 footprint、为何回退为自身发光
|
||||||
|
|
||||||
|
### 🔧 Improvements
|
||||||
|
- 后端可视化接口新增并透传 `constellation_group` 与 `footprint_policy`,前端据此执行 capability-gated footprint renderer
|
||||||
|
- 新增 Iridium 独立 coverage ring adapter,并继续保留 Starlink 专用 footprint 调校与昼夜可读性增强
|
||||||
|
- 新增 Earth 卫星 footprint 策略技术文档,明确 GNSS、generic LEO、GEO 与 Iridium 的显示边界
|
||||||
|
|
||||||
|
### 🐛 Fixes
|
||||||
|
- 修复前后端对 Iridium footprint policy 命名不一致,导致策略分发语义含混的问题
|
||||||
|
- 清理 Starlink footprint 渲染中的未使用常量与过时命名,减少后续继续调校时的歧义
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.39.0] — 2026-04-24
|
||||||
|
|
||||||
### ✨ Highlights
|
### ✨ Highlights
|
||||||
- 后端正式落下统一结构化日志地基:请求上下文、事件名、脱敏与持久化链路开始收口为可扩展的企业级日志体系
|
- 后端正式落下统一结构化日志地基:请求上下文、事件名、脱敏与持久化链路开始收口为可扩展的企业级日志体系
|
||||||
- 系统日志页重构为真正的日志工作台:顶部筛选更紧凑,终端日志区成为主视觉,移动端 Earth 新闻/态势细节交互继续补稳
|
- 系统日志页重构为真正的日志工作台:顶部筛选更紧凑,终端日志区成为主视觉,移动端 Earth 新闻/态势细节交互继续补稳
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
- 前端上下文
|
- 前端上下文
|
||||||
- Earth 前端结构
|
- Earth 前端结构
|
||||||
|
- Earth 卫星 footprint 策略
|
||||||
- 后端运行控制
|
- 后端运行控制
|
||||||
- collector 现状
|
- collector 现状
|
||||||
- 采集格式约定
|
- 采集格式约定
|
||||||
|
|||||||
198
docs/technical/earth-satellite-footprint-policy.md
Normal file
198
docs/technical/earth-satellite-footprint-policy.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# Earth Satellite Footprint Policy
|
||||||
|
|
||||||
|
本文件记录 Earth 卫星图层当前关于 `footprint` 的产品边界、资料依据和已落地实现,目标是避免把 Starlink 这套专用地表覆盖模型误用到其它星座上。
|
||||||
|
|
||||||
|
相关上下文:
|
||||||
|
|
||||||
|
- [earth-frontend-context.md](/home/ray/dev/linkong/planet/docs/technical/earth-frontend-context.md)
|
||||||
|
- [backend-collectors.md](/home/ray/dev/linkong/planet/docs/technical/backend-collectors.md)
|
||||||
|
- [backend/app/services/collectors/celestrak.py](/home/ray/dev/linkong/planet/backend/app/services/collectors/celestrak.py)
|
||||||
|
- [frontend/public/earth/js/satellites.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/satellites.js)
|
||||||
|
|
||||||
|
## 当前目标
|
||||||
|
|
||||||
|
- 明确哪些非 Starlink 卫星不该显示贴地 footprint
|
||||||
|
- 明确哪些星座未来可以有独立 footprint,但不能复用 Starlink bowtie / GSO-gap 模型
|
||||||
|
- 把这条策略沉淀成可执行实现边界,而不是继续散落在视觉参数里
|
||||||
|
|
||||||
|
## 本地实际类别
|
||||||
|
|
||||||
|
当前 CelesTrak 卫星分组在 [backend/app/services/collectors/celestrak.py](/home/ray/dev/linkong/planet/backend/app/services/collectors/celestrak.py) 中包括:
|
||||||
|
|
||||||
|
- `starlink`
|
||||||
|
- `gps-ops`
|
||||||
|
- `galileo`
|
||||||
|
- `glonass`
|
||||||
|
- `beidou`
|
||||||
|
- `leo`
|
||||||
|
- `geo`
|
||||||
|
- `iridium-next`
|
||||||
|
|
||||||
|
其中非 Starlink 类别是:
|
||||||
|
|
||||||
|
- `gps-ops`
|
||||||
|
- `galileo`
|
||||||
|
- `glonass`
|
||||||
|
- `beidou`
|
||||||
|
- `leo`
|
||||||
|
- `geo`
|
||||||
|
- `iridium-next`
|
||||||
|
|
||||||
|
## 资料结论
|
||||||
|
|
||||||
|
### 1. GNSS / RNSS: `gps-ops`, `galileo`, `glonass`, `beidou`
|
||||||
|
|
||||||
|
默认不要画局部地表 footprint。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 公开资料强调的是 `Earth-pointing`、`Earth coverage`、`continuous global coverage`
|
||||||
|
- 这类系统的公开语义是全球导航 / 授时覆盖,不是 Starlink 那种面向终端业务的局部 spot footprint
|
||||||
|
|
||||||
|
更合适的表示:
|
||||||
|
|
||||||
|
- 默认只显示卫星本体和轨道
|
||||||
|
- 如果后续要强调“服务可达性”,只能做很弱的 global coverage 语义,不应画贴地局部光斑
|
||||||
|
|
||||||
|
资料:
|
||||||
|
|
||||||
|
- [GPS III EC Antenna Patterns](https://www.navcen.uscg.gov/sites/default/files/pdf/gps/GPS_ZIP/GPS_III_EC_Antenna_Patterns_SVN_74_75_76_77_78.pdf)
|
||||||
|
- [ESA Galileo satellites](https://www.esa.int/Applications/Satellite_navigation/Galileo/Galileo_satellites)
|
||||||
|
- [Navipedia Galileo General Introduction](https://gssc.esa.int/navipedia/index.php/Galileo_General_Introduction)
|
||||||
|
- [BeiDou official overview](https://www.beidou.gov.cn/xt/gfxz/201812/P020190117356387956569.pdf)
|
||||||
|
- [GPS.gov GNSS overview](https://www.gps.gov/systems/gnss/)
|
||||||
|
|
||||||
|
### 2. `iridium-next`
|
||||||
|
|
||||||
|
可以有 footprint,但不能复用 Starlink 的单一 bowtie footprint。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- Iridium NEXT 公开资料强调的是固定多 spot beam 体系
|
||||||
|
- 公开示例里常见的是 `48 fixed spot beams in 4 tiers`
|
||||||
|
- 这和 Starlink 当前这套“单星、单主 footprint、带 GSO 缺口”的业务可视化不是同一个问题
|
||||||
|
|
||||||
|
更合适的表示:
|
||||||
|
|
||||||
|
- 默认:仍然不画 Starlink 式地表 footprint
|
||||||
|
- 后续如果要做:单独接入 Iridium 多波束适配层
|
||||||
|
- 在视觉上更接近多束 cluster / 蜂窝 / 分层束,而不是单个 bowtie 光斑
|
||||||
|
|
||||||
|
资料:
|
||||||
|
|
||||||
|
- [Iridium Satellite Spot Beam Coverage on the US](https://www.mathworks.com/help/phased/ug/iridium-satellite-spot-beam-coverage-on-the-us-1.html)
|
||||||
|
|
||||||
|
### 3. `geo`
|
||||||
|
|
||||||
|
默认不要画统一 footprint。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- GEO 通信星公开上可能是 global beam、zone beam、spot beam、steerable spot beam
|
||||||
|
- 没有 operator / payload / beam contour 元数据时,统一画一个 footprint 很容易错
|
||||||
|
|
||||||
|
更合适的表示:
|
||||||
|
|
||||||
|
- 默认只显示 GEO belt 和卫星驻点语义
|
||||||
|
- 只有拿到 beam contour / operator metadata 时才允许画 footprint
|
||||||
|
|
||||||
|
资料:
|
||||||
|
|
||||||
|
- [ITU Handbook on Satellite](https://www.itu.int/dms_pub/itu-r/opb/hdb/R-HDB-42-2002-PDF-E.pdf)
|
||||||
|
|
||||||
|
### 4. `leo`(generic)
|
||||||
|
|
||||||
|
默认不要画 footprint。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- `leo` 组过于混杂,可能同时包含通信、遥感、试验、观测等不同任务
|
||||||
|
- 没有 mission / payload / antenna pattern 元数据时,无法判断是否存在可视化意义上的服务覆盖面
|
||||||
|
|
||||||
|
更合适的表示:
|
||||||
|
|
||||||
|
- 默认只显示卫星和轨道
|
||||||
|
- 后续如果按 operator / mission subtype 细分,再决定是否引入独立 coverage mode
|
||||||
|
|
||||||
|
## 产品策略
|
||||||
|
|
||||||
|
当前统一策略如下:
|
||||||
|
|
||||||
|
- `Starlink`
|
||||||
|
- 保留当前专用 `ground_footprint` 逻辑
|
||||||
|
- `Iridium NEXT`
|
||||||
|
- 预留独立适配层
|
||||||
|
- 当前不复用 Starlink footprint
|
||||||
|
- `GPS / Galileo / GLONASS / BeiDou`
|
||||||
|
- 不显示贴地 footprint
|
||||||
|
- `GEO`
|
||||||
|
- 无 beam metadata 不显示 footprint
|
||||||
|
- `generic LEO`
|
||||||
|
- 无 mission metadata 不显示 footprint
|
||||||
|
|
||||||
|
## 已落地实现
|
||||||
|
|
||||||
|
本次实现只做最小可执行版本,不改现有 Starlink 视觉参数:
|
||||||
|
|
||||||
|
1. 后端把星座分组和 footprint 策略提示透给前端
|
||||||
|
|
||||||
|
- CelesTrak collector 会把 `GROUP` 记入 `metadata.constellation_group`
|
||||||
|
- Visualization API 会输出:
|
||||||
|
- `properties.constellation_group`
|
||||||
|
- `properties.footprint_policy`
|
||||||
|
|
||||||
|
当前策略值:
|
||||||
|
|
||||||
|
- `starlink_ground_footprint`
|
||||||
|
- `iridium_coverage_ring`
|
||||||
|
- `none`
|
||||||
|
|
||||||
|
对应代码:
|
||||||
|
|
||||||
|
- [backend/app/services/collectors/celestrak.py](/home/ray/dev/linkong/planet/backend/app/services/collectors/celestrak.py)
|
||||||
|
- [backend/app/api/v1/visualization.py](/home/ray/dev/linkong/planet/backend/app/api/v1/visualization.py)
|
||||||
|
|
||||||
|
2. 前端把 footprint 变成 capability-gated renderer
|
||||||
|
|
||||||
|
- `ground_footprint` 只有在 `footprint_policy === starlink_ground_footprint` 时才真正启用
|
||||||
|
- `iridium-next` 不再回退成占位分支,而是走独立的 Iridium coverage ring adapter
|
||||||
|
- 其它非 Starlink 即使用户全局选择了 `ground_footprint`,也会自动回退到 `self_glow`
|
||||||
|
|
||||||
|
对应代码:
|
||||||
|
|
||||||
|
- [frontend/public/earth/js/satellites.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/satellites.js)
|
||||||
|
- [frontend/public/earth/js/iridium-footprint-adapter.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/iridium-footprint-adapter.js)
|
||||||
|
|
||||||
|
3. 卫星信息卡显示 capability,而不是只显示轨道参数
|
||||||
|
|
||||||
|
- 卫星详情现在会明确显示:
|
||||||
|
- `星座/分组`
|
||||||
|
- `覆盖能力`
|
||||||
|
- `当前显示`
|
||||||
|
- `覆盖模型`
|
||||||
|
- 这样用户能直接看到:
|
||||||
|
- 当前卫星是否支持 footprint
|
||||||
|
- 当前显示是不是因为 capability gating 被回退
|
||||||
|
- Iridium 和 Starlink 使用的不是同一种模型
|
||||||
|
|
||||||
|
对应代码:
|
||||||
|
|
||||||
|
- [frontend/public/earth/js/main.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/main.js)
|
||||||
|
- [frontend/public/earth/js/info-card.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/info-card.js)
|
||||||
|
|
||||||
|
## 当前实现边界
|
||||||
|
|
||||||
|
这条边界需要继续保持:
|
||||||
|
|
||||||
|
- `Starlink` 的 footprint 参数和 shader 逻辑只服务于 Starlink
|
||||||
|
- 非 Starlink 的能力判断属于“策略层 / 适配层”
|
||||||
|
- 不要把不同星座的覆盖模型再混写进同一套参数里
|
||||||
|
- `iridium-next` 已经切成独立 adapter,应继续沿这条边界演进,而不是给现有 Starlink bowtie 增加更多 if/else
|
||||||
|
|
||||||
|
## 后续建议
|
||||||
|
|
||||||
|
如果继续往前做,推荐顺序是:
|
||||||
|
|
||||||
|
1. 为 `iridium-next` 新建独立 footprint adapter
|
||||||
|
2. 在 UI 上补一个只读提示,让用户知道当前卫星是否支持 footprint
|
||||||
|
3. 如果未来拿到 GEO beam contour / operator metadata,再为 GEO 开 operator-specific footprint
|
||||||
@@ -16,12 +16,13 @@
|
|||||||
## Current Version
|
## Current Version
|
||||||
|
|
||||||
- `main` 当前主线历史推导到:`0.16.5`
|
- `main` 当前主线历史推导到:`0.16.5`
|
||||||
- `dev` 当前开发分支历史推导到:`0.39.0`
|
- `dev` 当前开发分支历史推导到:`0.40.0`
|
||||||
|
|
||||||
## Timeline
|
## Timeline
|
||||||
|
|
||||||
| Version | Type | Branch | Commit | Summary |
|
| Version | Type | Branch | Commit | Summary |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `0.40.0` | feature | `dev` | `pending` | Earth 卫星 footprint 按星座能力分层,Iridium 独立 coverage ring 落地,卫星详情卡补齐覆盖能力与当前显示说明 |
|
||||||
| `0.39.0` | feature | `dev` | `pending` | 后端统一结构化日志地基落地,系统日志页重构为紧凑日志工作台,并修复 Earth 移动端态势抽屉与新闻详情同步问题 |
|
| `0.39.0` | feature | `dev` | `pending` | 后端统一结构化日志地基落地,系统日志页重构为紧凑日志工作台,并修复 Earth 移动端态势抽屉与新闻详情同步问题 |
|
||||||
| `0.38.0` | feature | `dev` | `pending` | Earth 新闻接入通用巡航与专用卡片链路,系统日志页升级为结构化时间/级别过滤与真正字符串检索 |
|
| `0.38.0` | feature | `dev` | `pending` | Earth 新闻接入通用巡航与专用卡片链路,系统日志页升级为结构化时间/级别过滤与真正字符串检索 |
|
||||||
| `0.37.2` | bugfix | `dev` | `pending` | Earth 图层系统新增经纬线开关,并将经纬线接入统一 layer registry、移动端抽屉与设置持久化流 |
|
| `0.37.2` | bugfix | `dev` | `pending` | Earth 图层系统新增经纬线开关,并将经纬线接入统一 layer registry、移动端抽屉与设置持久化流 |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "planet-frontend",
|
"name": "planet-frontend",
|
||||||
"version": "0.39.0",
|
"version": "0.40.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "bun@1",
|
"packageManager": "bun@1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -687,6 +687,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="earth-mobile-settings-group">
|
||||||
|
<div class="earth-mobile-settings-title">卫星</div>
|
||||||
|
<div class="earth-mobile-settings-card earth-mobile-settings-card--stacked">
|
||||||
|
<div class="earth-mobile-settings-copy">
|
||||||
|
<span class="earth-mobile-settings-label">卫星显示风格</span>
|
||||||
|
<span class="earth-mobile-settings-subtitle">可选自身发光或真实地表覆盖两种选中表现</span>
|
||||||
|
</div>
|
||||||
|
<div class="earth-mobile-settings-segmented" role="group" aria-label="移动端选择卫星显示风格">
|
||||||
|
<button type="button" class="earth-mobile-settings-pill is-active" data-satellite-display-style="self_glow" aria-pressed="true">自身发光</button>
|
||||||
|
<button type="button" class="earth-mobile-settings-pill" data-satellite-display-style="ground_footprint" aria-pressed="false">真实地表覆盖</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="earth-mobile-settings-group">
|
<div class="earth-mobile-settings-group">
|
||||||
<div class="earth-mobile-settings-title">视图</div>
|
<div class="earth-mobile-settings-title">视图</div>
|
||||||
<label class="earth-mobile-settings-card">
|
<label class="earth-mobile-settings-card">
|
||||||
@@ -875,6 +888,30 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="earth-settings-item earth-settings-item--stacked">
|
||||||
|
<div class="earth-settings-copy">
|
||||||
|
<span class="earth-settings-item-title">卫星显示风格</span>
|
||||||
|
<span class="earth-settings-item-subtitle">选择卫星锁定态使用自身发光,还是强调真实地表覆盖范围。</span>
|
||||||
|
</div>
|
||||||
|
<div class="earth-settings-segmented" role="group" aria-label="选择卫星显示风格">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="earth-settings-segmented-btn is-active"
|
||||||
|
data-satellite-display-style="self_glow"
|
||||||
|
aria-pressed="true"
|
||||||
|
>
|
||||||
|
自身发光
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="earth-settings-segmented-btn"
|
||||||
|
data-satellite-display-style="ground_footprint"
|
||||||
|
aria-pressed="false"
|
||||||
|
>
|
||||||
|
真实地表覆盖
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="earth-settings-section">
|
<section class="earth-settings-section">
|
||||||
|
|||||||
@@ -25,6 +25,14 @@ export const CRUISE_MODULES = {
|
|||||||
|
|
||||||
export const DEFAULT_CRUISE_MODULES = [CRUISE_MODULES.BGP];
|
export const DEFAULT_CRUISE_MODULES = [CRUISE_MODULES.BGP];
|
||||||
|
|
||||||
|
export const SATELLITE_DISPLAY_STYLES = {
|
||||||
|
SELF_GLOW: "self_glow",
|
||||||
|
GROUND_FOOTPRINT: "ground_footprint",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_SATELLITE_DISPLAY_STYLE =
|
||||||
|
SATELLITE_DISPLAY_STYLES.SELF_GLOW;
|
||||||
|
|
||||||
export const CRUISE_CONFIG = {
|
export const CRUISE_CONFIG = {
|
||||||
dwellMs: 7_000,
|
dwellMs: 7_000,
|
||||||
focusDurationMs: 1_400,
|
focusDurationMs: 1_400,
|
||||||
|
|||||||
79
frontend/public/earth/js/controls.js
vendored
79
frontend/public/earth/js/controls.js
vendored
@@ -4,9 +4,11 @@ import * as THREE from "three";
|
|||||||
import {
|
import {
|
||||||
CONFIG,
|
CONFIG,
|
||||||
CRUISE_MODULES,
|
CRUISE_MODULES,
|
||||||
|
DEFAULT_SATELLITE_DISPLAY_STYLE,
|
||||||
DEFAULT_CRUISE_MODULES,
|
DEFAULT_CRUISE_MODULES,
|
||||||
EARTH_CONFIG,
|
EARTH_CONFIG,
|
||||||
ROTATION_MODE,
|
ROTATION_MODE,
|
||||||
|
SATELLITE_DISPLAY_STYLES,
|
||||||
} from "./constants.js";
|
} from "./constants.js";
|
||||||
import { setEarthStatValue, updateZoomDisplay, showStatusMessage } from "./ui.js";
|
import { setEarthStatValue, updateZoomDisplay, showStatusMessage } from "./ui.js";
|
||||||
import {
|
import {
|
||||||
@@ -34,6 +36,8 @@ import {
|
|||||||
toggleTrails,
|
toggleTrails,
|
||||||
getShowTrails,
|
getShowTrails,
|
||||||
getSatelliteCount,
|
getSatelliteCount,
|
||||||
|
getSatelliteDisplayStyle,
|
||||||
|
setSatelliteDisplayStyle as applySatelliteDisplayStyle,
|
||||||
} from "./satellites.js";
|
} from "./satellites.js";
|
||||||
import { getShowCables } from "./cables.js";
|
import { getShowCables } from "./cables.js";
|
||||||
import { toggleBGP, getShowBGP, getBGPCount } from "./bgp.js";
|
import { toggleBGP, getShowBGP, getBGPCount } from "./bgp.js";
|
||||||
@@ -116,6 +120,9 @@ let mobileDrawerOpen = false;
|
|||||||
let mobileDrawerCard = "layers";
|
let mobileDrawerCard = "layers";
|
||||||
let mobileDrawerHintTimer = null;
|
let mobileDrawerHintTimer = null;
|
||||||
const ALLOWED_CRUISE_MODULES = new Set(Object.values(CRUISE_MODULES));
|
const ALLOWED_CRUISE_MODULES = new Set(Object.values(CRUISE_MODULES));
|
||||||
|
const ALLOWED_SATELLITE_DISPLAY_STYLES = new Set(
|
||||||
|
Object.values(SATELLITE_DISPLAY_STYLES),
|
||||||
|
);
|
||||||
|
|
||||||
function detectLayoutMode() {
|
function detectLayoutMode() {
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth;
|
||||||
@@ -641,6 +648,7 @@ function getCurrentSharedSettingsSnapshot() {
|
|||||||
return {
|
return {
|
||||||
rotationMode,
|
rotationMode,
|
||||||
cruiseModules: getCruiseModules(),
|
cruiseModules: getCruiseModules(),
|
||||||
|
satelliteDisplayStyle: getSatelliteDisplayStyle(),
|
||||||
layerVisibility: Object.fromEntries(
|
layerVisibility: Object.fromEntries(
|
||||||
getPersistedLayers().map((layer) => [layer.id, Boolean(layer.getVisible?.())]),
|
getPersistedLayers().map((layer) => [layer.id, Boolean(layer.getVisible?.())]),
|
||||||
),
|
),
|
||||||
@@ -676,6 +684,8 @@ function cloneEarthSettings(settings) {
|
|||||||
shared: {
|
shared: {
|
||||||
rotationMode: settings.shared.rotationMode,
|
rotationMode: settings.shared.rotationMode,
|
||||||
cruiseModules: [...(settings.shared.cruiseModules || DEFAULT_CRUISE_MODULES)],
|
cruiseModules: [...(settings.shared.cruiseModules || DEFAULT_CRUISE_MODULES)],
|
||||||
|
satelliteDisplayStyle:
|
||||||
|
settings.shared.satelliteDisplayStyle || DEFAULT_SATELLITE_DISPLAY_STYLE,
|
||||||
terrainOpacity: settings.shared.terrainOpacity,
|
terrainOpacity: settings.shared.terrainOpacity,
|
||||||
dayNightEnabled: settings.shared.dayNightEnabled,
|
dayNightEnabled: settings.shared.dayNightEnabled,
|
||||||
defaultEarthZoom: settings.shared.defaultEarthZoom,
|
defaultEarthZoom: settings.shared.defaultEarthZoom,
|
||||||
@@ -755,6 +765,11 @@ function normalizeEarthSettings(rawSettings, defaults) {
|
|||||||
requestedCruiseModules.filter((moduleId) => ALLOWED_CRUISE_MODULES.has(moduleId)),
|
requestedCruiseModules.filter((moduleId) => ALLOWED_CRUISE_MODULES.has(moduleId)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const nextSatelliteDisplayStyle = ALLOWED_SATELLITE_DISPLAY_STYLES.has(
|
||||||
|
sharedSettings?.satelliteDisplayStyle,
|
||||||
|
)
|
||||||
|
? sharedSettings.satelliteDisplayStyle
|
||||||
|
: defaults.shared.satelliteDisplayStyle;
|
||||||
const nextTerrainOpacity = Number.parseFloat(sharedSettings?.terrainOpacity);
|
const nextTerrainOpacity = Number.parseFloat(sharedSettings?.terrainOpacity);
|
||||||
const nextDayNightEnabled = typeof sharedSettings?.dayNightEnabled === "boolean"
|
const nextDayNightEnabled = typeof sharedSettings?.dayNightEnabled === "boolean"
|
||||||
? sharedSettings.dayNightEnabled
|
? sharedSettings.dayNightEnabled
|
||||||
@@ -770,6 +785,7 @@ function normalizeEarthSettings(rawSettings, defaults) {
|
|||||||
cruiseModules: nextCruiseModules.length > 0
|
cruiseModules: nextCruiseModules.length > 0
|
||||||
? nextCruiseModules
|
? nextCruiseModules
|
||||||
: [...DEFAULT_CRUISE_MODULES],
|
: [...DEFAULT_CRUISE_MODULES],
|
||||||
|
satelliteDisplayStyle: nextSatelliteDisplayStyle,
|
||||||
layerVisibility: normalizedLayerVisibility,
|
layerVisibility: normalizedLayerVisibility,
|
||||||
terrainOpacity: Number.isFinite(nextTerrainOpacity)
|
terrainOpacity: Number.isFinite(nextTerrainOpacity)
|
||||||
? nextTerrainOpacity
|
? nextTerrainOpacity
|
||||||
@@ -883,6 +899,17 @@ function syncCruiseModuleControls() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncSatelliteDisplayStyleControls() {
|
||||||
|
const activeStyle = getSatelliteDisplayStyle();
|
||||||
|
document.querySelectorAll("[data-satellite-display-style]").forEach((button) => {
|
||||||
|
if (!(button instanceof HTMLButtonElement)) return;
|
||||||
|
const styleId = button.dataset.satelliteDisplayStyle || "";
|
||||||
|
const active = styleId === activeStyle;
|
||||||
|
button.classList.toggle("is-active", active);
|
||||||
|
button.setAttribute("aria-pressed", active ? "true" : "false");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function getCruiseModules() {
|
export function getCruiseModules() {
|
||||||
const configuredModules = earthSettingsState?.shared?.cruiseModules;
|
const configuredModules = earthSettingsState?.shared?.cruiseModules;
|
||||||
return normalizeCruiseModules(configuredModules);
|
return normalizeCruiseModules(configuredModules);
|
||||||
@@ -925,6 +952,42 @@ export function setCruiseModules(nextModules, { persist = true, suppressStatus =
|
|||||||
return normalizedModules;
|
return normalizedModules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setSatelliteDisplayStyle(
|
||||||
|
nextStyle,
|
||||||
|
{ persist = true, suppressStatus = false } = {},
|
||||||
|
) {
|
||||||
|
const normalizedStyle = ALLOWED_SATELLITE_DISPLAY_STYLES.has(nextStyle)
|
||||||
|
? nextStyle
|
||||||
|
: DEFAULT_SATELLITE_DISPLAY_STYLE;
|
||||||
|
const previousStyle = getSatelliteDisplayStyle();
|
||||||
|
|
||||||
|
if (normalizedStyle === previousStyle) {
|
||||||
|
syncSatelliteDisplayStyleControls();
|
||||||
|
return normalizedStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
earthSettingsState = cloneEarthSettings(
|
||||||
|
earthSettingsState || cloneEarthSettings(captureEarthSettingsDefaults()),
|
||||||
|
);
|
||||||
|
earthSettingsState.shared.satelliteDisplayStyle = normalizedStyle;
|
||||||
|
applySatelliteDisplayStyle(normalizedStyle);
|
||||||
|
syncSatelliteDisplayStyleControls();
|
||||||
|
|
||||||
|
if (persist) {
|
||||||
|
persistEarthSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!suppressStatus) {
|
||||||
|
const nextLabel =
|
||||||
|
normalizedStyle === SATELLITE_DISPLAY_STYLES.GROUND_FOOTPRINT
|
||||||
|
? "真实地表覆盖"
|
||||||
|
: "自身发光";
|
||||||
|
showStatusMessage(`卫星显示风格已切换为:${nextLabel}`, "info");
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedStyle;
|
||||||
|
}
|
||||||
|
|
||||||
function syncDefaultEarthZoomUi(nextZoom) {
|
function syncDefaultEarthZoomUi(nextZoom) {
|
||||||
const sliders = document.querySelectorAll("#default-earth-size-slider, [data-default-earth-size-slider]");
|
const sliders = document.querySelectorAll("#default-earth-size-slider, [data-default-earth-size-slider]");
|
||||||
const values = document.querySelectorAll("#default-earth-size-value, [data-default-earth-size-value]");
|
const values = document.querySelectorAll("#default-earth-size-value, [data-default-earth-size-value]");
|
||||||
@@ -988,6 +1051,10 @@ async function applyEarthSettings(settings) {
|
|||||||
|
|
||||||
setRotationMode(settings.shared.rotationMode, { persist: false, suppressStatus: true });
|
setRotationMode(settings.shared.rotationMode, { persist: false, suppressStatus: true });
|
||||||
setCruiseModules(settings.shared.cruiseModules, { persist: false, suppressStatus: true });
|
setCruiseModules(settings.shared.cruiseModules, { persist: false, suppressStatus: true });
|
||||||
|
setSatelliteDisplayStyle(settings.shared.satelliteDisplayStyle, {
|
||||||
|
persist: false,
|
||||||
|
suppressStatus: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof settings.shared.dayNightEnabled === "boolean") {
|
if (typeof settings.shared.dayNightEnabled === "boolean") {
|
||||||
applyDayNightEnabled(settings.shared.dayNightEnabled, { persist: false });
|
applyDayNightEnabled(settings.shared.dayNightEnabled, { persist: false });
|
||||||
@@ -1797,6 +1864,7 @@ function setupSettingsControls() {
|
|||||||
const defaultEarthSizeSliders = document.querySelectorAll("#default-earth-size-slider, [data-default-earth-size-slider]");
|
const defaultEarthSizeSliders = document.querySelectorAll("#default-earth-size-slider, [data-default-earth-size-slider]");
|
||||||
const rotationModeButtons = document.querySelectorAll("[data-rotation-mode]");
|
const rotationModeButtons = document.querySelectorAll("[data-rotation-mode]");
|
||||||
const cruiseModuleButtons = document.querySelectorAll("[data-cruise-module-toggle]");
|
const cruiseModuleButtons = document.querySelectorAll("[data-cruise-module-toggle]");
|
||||||
|
const satelliteDisplayStyleButtons = document.querySelectorAll("[data-satellite-display-style]");
|
||||||
const syncTerrainOpacityUi = (nextOpacity) => {
|
const syncTerrainOpacityUi = (nextOpacity) => {
|
||||||
const safeOpacity = Math.round(nextOpacity * 100);
|
const safeOpacity = Math.round(nextOpacity * 100);
|
||||||
terrainOpacitySliders.forEach((slider) => {
|
terrainOpacitySliders.forEach((slider) => {
|
||||||
@@ -1869,6 +1937,16 @@ function setupSettingsControls() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
satelliteDisplayStyleButtons.forEach((button) => {
|
||||||
|
bindListener(button, "click", (event) => {
|
||||||
|
const target = event.currentTarget;
|
||||||
|
if (!(target instanceof HTMLButtonElement)) return;
|
||||||
|
const nextStyle = target.dataset.satelliteDisplayStyle;
|
||||||
|
if (!nextStyle) return;
|
||||||
|
setSatelliteDisplayStyle(nextStyle);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
document.querySelectorAll("#toggle-daynight, [data-daynight-toggle]").forEach((dayNightToggle) => {
|
document.querySelectorAll("#toggle-daynight, [data-daynight-toggle]").forEach((dayNightToggle) => {
|
||||||
if (!(dayNightToggle instanceof HTMLInputElement)) return;
|
if (!(dayNightToggle instanceof HTMLInputElement)) return;
|
||||||
bindListener(dayNightToggle, "change", () => {
|
bindListener(dayNightToggle, "change", () => {
|
||||||
@@ -1886,6 +1964,7 @@ function setupSettingsControls() {
|
|||||||
syncAllHudPanelToggles();
|
syncAllHudPanelToggles();
|
||||||
syncRotationModeButtons();
|
syncRotationModeButtons();
|
||||||
syncCruiseModuleControls();
|
syncCruiseModuleControls();
|
||||||
|
syncSatelliteDisplayStyleControls();
|
||||||
syncDayNightToggle(dayNightEnabled);
|
syncDayNightToggle(dayNightEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -437,6 +437,10 @@ const CARD_CONFIG = {
|
|||||||
fields: [
|
fields: [
|
||||||
{ key: 'name', label: '名称' },
|
{ key: 'name', label: '名称' },
|
||||||
{ key: 'norad_id', label: 'NORAD ID' },
|
{ key: 'norad_id', label: 'NORAD ID' },
|
||||||
|
{ key: 'constellation', label: '星座/分组' },
|
||||||
|
{ key: 'footprint_capability', label: '覆盖能力' },
|
||||||
|
{ key: 'current_display', label: '当前显示' },
|
||||||
|
{ key: 'footprint_model', label: '覆盖模型' },
|
||||||
{ key: 'inclination', label: '倾角', unit: '°' },
|
{ key: 'inclination', label: '倾角', unit: '°' },
|
||||||
{ key: 'period', label: '周期', unit: '分钟' },
|
{ key: 'period', label: '周期', unit: '分钟' },
|
||||||
{ key: 'perigee', label: '近地点', unit: 'km' },
|
{ key: 'perigee', label: '近地点', unit: 'km' },
|
||||||
|
|||||||
167
frontend/public/earth/js/iridium-footprint-adapter.js
Normal file
167
frontend/public/earth/js/iridium-footprint-adapter.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
const EARTH_RADIUS_KM = 6378.137;
|
||||||
|
const SURFACE_SCALE = 1.003;
|
||||||
|
const SURFACE_OFFSET = 0.72;
|
||||||
|
const CLUSTER_DIAMETER_KM_APPROX = 4500;
|
||||||
|
const CLUSTER_RADIUS_KM_BASE = CLUSTER_DIAMETER_KM_APPROX / 2;
|
||||||
|
const SURFACE_AXIS = new THREE.Vector3(0, 0, 1);
|
||||||
|
|
||||||
|
function disposeMaterial(material) {
|
||||||
|
if (!material) return;
|
||||||
|
if (Array.isArray(material)) {
|
||||||
|
material.forEach(disposeMaterial);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
material.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
function disposeObjectTree(object) {
|
||||||
|
if (!object) return;
|
||||||
|
object.traverse((child) => {
|
||||||
|
if (child.geometry) {
|
||||||
|
child.geometry.dispose();
|
||||||
|
}
|
||||||
|
if (child.material) {
|
||||||
|
disposeMaterial(child.material);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIridiumClusterMaterial() {
|
||||||
|
return new THREE.ShaderMaterial({
|
||||||
|
transparent: true,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
depthTest: true,
|
||||||
|
depthWrite: false,
|
||||||
|
polygonOffset: true,
|
||||||
|
polygonOffsetFactor: -3,
|
||||||
|
polygonOffsetUnits: -3,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
uniforms: {
|
||||||
|
uColor: { value: new THREE.Color(0x5faeff) },
|
||||||
|
uOpacity: { value: 0.24 },
|
||||||
|
},
|
||||||
|
vertexShader: `
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fragmentShader: `
|
||||||
|
uniform vec3 uColor;
|
||||||
|
uniform float uOpacity;
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 p = vUv * 2.0 - 1.0;
|
||||||
|
float ellipseMetric = p.x * p.x * 0.82 + p.y * p.y * 1.06;
|
||||||
|
float alpha = exp(-ellipseMetric * 1.05) * (1.0 - smoothstep(0.86, 1.24, ellipseMetric));
|
||||||
|
alpha *= uOpacity;
|
||||||
|
if (alpha <= 0.001) discard;
|
||||||
|
gl_FragColor = vec4(uColor, alpha);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function projectOffsetToSurface(
|
||||||
|
centerNormal,
|
||||||
|
alongTrack,
|
||||||
|
crossTrack,
|
||||||
|
alongKm,
|
||||||
|
crossKm,
|
||||||
|
earthRadiusWorld,
|
||||||
|
) {
|
||||||
|
const worldUnitsPerKm = earthRadiusWorld / EARTH_RADIUS_KM;
|
||||||
|
const surfaceRadius = earthRadiusWorld * SURFACE_SCALE + SURFACE_OFFSET;
|
||||||
|
return centerNormal
|
||||||
|
.clone()
|
||||||
|
.multiplyScalar(earthRadiusWorld)
|
||||||
|
.addScaledVector(alongTrack, alongKm * worldUnitsPerKm)
|
||||||
|
.addScaledVector(crossTrack, crossKm * worldUnitsPerKm)
|
||||||
|
.normalize()
|
||||||
|
.multiplyScalar(surfaceRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeClusterRadiusKm(altitudeKm) {
|
||||||
|
const altitudeScale = THREE.MathUtils.clamp(
|
||||||
|
(Number(altitudeKm) || 780) / 780,
|
||||||
|
0.88,
|
||||||
|
1.18,
|
||||||
|
);
|
||||||
|
return CLUSTER_RADIUS_KM_BASE * altitudeScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createIridiumFootprintAdapter({
|
||||||
|
earthObj,
|
||||||
|
earthRadiusWorld,
|
||||||
|
renderOrder,
|
||||||
|
}) {
|
||||||
|
if (!earthObj) return null;
|
||||||
|
|
||||||
|
const group = new THREE.Group();
|
||||||
|
group.name = "iridium-footprint-overlay";
|
||||||
|
group.renderOrder = renderOrder;
|
||||||
|
group.userData = {
|
||||||
|
earthRadiusWorld,
|
||||||
|
clusterGlow: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const clusterGlow = new THREE.Mesh(
|
||||||
|
new THREE.CircleGeometry(1, 72),
|
||||||
|
createIridiumClusterMaterial(),
|
||||||
|
);
|
||||||
|
clusterGlow.name = "iridium-cluster-glow";
|
||||||
|
clusterGlow.renderOrder = renderOrder - 1;
|
||||||
|
group.add(clusterGlow);
|
||||||
|
group.userData.clusterGlow = clusterGlow;
|
||||||
|
|
||||||
|
earthObj.add(group);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateIridiumFootprintAdapter(
|
||||||
|
group,
|
||||||
|
{ position, alongTrack, crossTrack, altitudeKm },
|
||||||
|
) {
|
||||||
|
if (!group || !position || !alongTrack || !crossTrack) return;
|
||||||
|
|
||||||
|
const earthRadiusWorld =
|
||||||
|
group.userData?.earthRadiusWorld || EARTH_RADIUS_KM;
|
||||||
|
const centerNormal = position.clone().normalize();
|
||||||
|
const clusterRadiusKm = computeClusterRadiusKm(altitudeKm);
|
||||||
|
const clusterGlow = group.userData?.clusterGlow || null;
|
||||||
|
const worldUnitsPerKm = earthRadiusWorld / EARTH_RADIUS_KM;
|
||||||
|
|
||||||
|
if (clusterGlow) {
|
||||||
|
const clusterCenter = projectOffsetToSurface(
|
||||||
|
centerNormal,
|
||||||
|
alongTrack,
|
||||||
|
crossTrack,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
earthRadiusWorld,
|
||||||
|
);
|
||||||
|
const clusterNormal = clusterCenter.clone().normalize();
|
||||||
|
clusterGlow.position.copy(clusterCenter);
|
||||||
|
clusterGlow.quaternion.setFromUnitVectors(SURFACE_AXIS, clusterNormal);
|
||||||
|
clusterGlow.scale.set(
|
||||||
|
clusterRadiusKm * worldUnitsPerKm * 1.18,
|
||||||
|
clusterRadiusKm * worldUnitsPerKm * 0.96,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function disposeIridiumFootprintAdapter(group, earthObj) {
|
||||||
|
if (!group) return;
|
||||||
|
if (earthObj) {
|
||||||
|
earthObj.remove(group);
|
||||||
|
} else if (group.parent) {
|
||||||
|
group.parent.remove(group);
|
||||||
|
}
|
||||||
|
disposeObjectTree(group);
|
||||||
|
}
|
||||||
@@ -80,6 +80,7 @@ import {
|
|||||||
getSatelliteCount,
|
getSatelliteCount,
|
||||||
selectSatellite,
|
selectSatellite,
|
||||||
getSatellitePoints,
|
getSatellitePoints,
|
||||||
|
getSatellitePresentationInfo,
|
||||||
setSatelliteRingState,
|
setSatelliteRingState,
|
||||||
updateLockedRingPosition,
|
updateLockedRingPosition,
|
||||||
updateHoverRingPosition,
|
updateHoverRingPosition,
|
||||||
@@ -93,6 +94,7 @@ import {
|
|||||||
updateBreathingPhase,
|
updateBreathingPhase,
|
||||||
isSatelliteFrontFacing,
|
isSatelliteFrontFacing,
|
||||||
setSatelliteCamera,
|
setSatelliteCamera,
|
||||||
|
setSatelliteSunDirection,
|
||||||
setLockedSatelliteIndex,
|
setLockedSatelliteIndex,
|
||||||
resetSatelliteState,
|
resetSatelliteState,
|
||||||
clearSatelliteData,
|
clearSatelliteData,
|
||||||
@@ -576,6 +578,14 @@ function showSatelliteInfo(props, coords) {
|
|||||||
const ecc = props?.eccentricity || 0;
|
const ecc = props?.eccentricity || 0;
|
||||||
const perigee = (6371 * (1 - ecc)).toFixed(0);
|
const perigee = (6371 * (1 - ecc)).toFixed(0);
|
||||||
const apogee = (6371 * (1 + ecc)).toFixed(0);
|
const apogee = (6371 * (1 + ecc)).toFixed(0);
|
||||||
|
const presentation = getSatellitePresentationInfo(props);
|
||||||
|
|
||||||
|
let footprintModel = "不适用";
|
||||||
|
if (presentation.footprintPolicy === "starlink_ground_footprint") {
|
||||||
|
footprintModel = "Starlink 单星地表覆盖";
|
||||||
|
} else if (presentation.footprintPolicy === "iridium_coverage_ring") {
|
||||||
|
footprintModel = "Iridium 外圈半透明覆盖";
|
||||||
|
}
|
||||||
|
|
||||||
setSelectedSatelliteLegend(props);
|
setSelectedSatelliteLegend(props);
|
||||||
setLegendItems("satellites", getSatelliteLegendItems());
|
setLegendItems("satellites", getSatelliteLegendItems());
|
||||||
@@ -583,6 +593,10 @@ function showSatelliteInfo(props, coords) {
|
|||||||
showInfoCard("satellite", {
|
showInfoCard("satellite", {
|
||||||
name: props?.name || "-",
|
name: props?.name || "-",
|
||||||
norad_id: props?.norad_cat_id,
|
norad_id: props?.norad_cat_id,
|
||||||
|
constellation: presentation.constellationLabel,
|
||||||
|
footprint_capability: presentation.footprintCapabilityLabel,
|
||||||
|
current_display: presentation.presentationModeLabel,
|
||||||
|
footprint_model: footprintModel,
|
||||||
inclination: props?.inclination ? props.inclination.toFixed(2) : "-",
|
inclination: props?.inclination ? props.inclination.toFixed(2) : "-",
|
||||||
period,
|
period,
|
||||||
perigee,
|
perigee,
|
||||||
@@ -1050,7 +1064,9 @@ function resolveEarthSearchResults(query) {
|
|||||||
icon: "satellite_alt",
|
icon: "satellite_alt",
|
||||||
typeLabel: "卫星",
|
typeLabel: "卫星",
|
||||||
title: props?.name || `NORAD ${props?.norad_cat_id || index}`,
|
title: props?.name || `NORAD ${props?.norad_cat_id || index}`,
|
||||||
subtitle: props?.norad_cat_id ? `NORAD ${props.norad_cat_id}` : "在轨卫星",
|
subtitle: props?.norad_cat_id
|
||||||
|
? `NORAD ${props.norad_cat_id} · ${getSatellitePresentationInfo(props).constellationLabel}`
|
||||||
|
: `${getSatellitePresentationInfo(props).constellationLabel} · 在轨卫星`,
|
||||||
score,
|
score,
|
||||||
entity: { index },
|
entity: { index },
|
||||||
});
|
});
|
||||||
@@ -3115,7 +3131,9 @@ function animate() {
|
|||||||
updateBreathingPhase(deltaTime);
|
updateBreathingPhase(deltaTime);
|
||||||
updateRelatedSatelliteHighlights();
|
updateRelatedSatelliteHighlights();
|
||||||
updateCelestialLayer(new Date(), camera);
|
updateCelestialLayer(new Date(), camera);
|
||||||
setEarthSunDirection(getSunDirection());
|
const currentSunDirection = getSunDirection();
|
||||||
|
setEarthSunDirection(currentSunDirection);
|
||||||
|
setSatelliteSunDirection(currentSunDirection);
|
||||||
updateNewsViewFocus(getCurrentViewCenterCoords());
|
updateNewsViewFocus(getCurrentViewCenterCoords());
|
||||||
const satPositions = getSatellitePositions();
|
const satPositions = getSatellitePositions();
|
||||||
if (
|
if (
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "planet"
|
name = "planet"
|
||||||
version = "0.39.0"
|
version = "0.40.0"
|
||||||
description = "智能星球计划 - 态势感知系统"
|
description = "智能星球计划 - 态势感知系统"
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
Reference in New Issue
Block a user