8 files modified
38 files added
| | |
| | | dist/ |
| | | build/ |
| | | *.egg-info/ |
| | | *.pt |
| | | Ultralytics/ |
| | | |
| | | shared/data/raw/* |
| | | shared/data/interim/* |
| | |
| | | shared/outputs/* |
| | | !**/.gitkeep |
| | | !shared/**/README.md |
| | | |
| New file |
| | |
| | | # GeoAI Workbench Instructions |
| | | |
| | | ## Scope |
| | | |
| | | These instructions apply to the entire repository at `E:\AllWorkProject\geoai-workbench`. |
| | | Read `PROJECT_CONTEXT.md` before planning or changing a capability. |
| | | |
| | | ## Project Definition |
| | | |
| | | - In this repository, GeoAI means workflows based on `opengeos/geoai` (`geoai-py`), not every spatial-intelligence feature. |
| | | - Keep the capability boundary explicit: |
| | | - A: directly provided or documented by `geoai-py`. |
| | | - B: built with `geoai-py` plus geospatial or AI ecosystem libraries. |
| | | - C: a product/service capability that consumes GeoAI outputs. |
| | | - Verify current APIs against the installed package, source, or official documentation. Do not label B or C work as a built-in GeoAI function. |
| | | - Treat the user as a beginner who supplies goals, sample data, and visual feedback. Explain decisions in clear Chinese and handle implementation, environment work, debugging, and verification end to end. |
| | | |
| | | ## Repository Conventions |
| | | |
| | | - Keep one capability in `capabilities/<number>-<name>/`. |
| | | - Every capability must have a `README.md` and `requirements.txt`; add code, tests, configs, and examples only when needed. |
| | | - Share compatible dependencies through `requirements/base.txt`. Use `.venvs/<capability>` for isolated environments when needed. |
| | | - Use Python 3.12 for GeoAI and deep-learning capabilities. Do not install project dependencies into system Python. |
| | | - Store reusable code in `src/geoai_common/` only after it is genuinely shared. |
| | | - Keep raw data, processed data, weights, caches, and generated outputs out of Git and under the existing `shared/` layout. |
| | | |
| | | ## Capability Workflow |
| | | |
| | | 1. Read the root documentation, `PROJECT_CONTEXT.md`, and the target capability README. |
| | | 2. Classify the capability as A, B, or C and identify the exact role of `geoai-py`. |
| | | 3. Define a runnable Demo with real input, inspectable output, structured metadata, and measurable acceptance criteria. |
| | | 4. Inspect hardware and existing environments before installing dependencies. Default to CPU because no NVIDIA CUDA is available. |
| | | 5. Validate one normal and one difficult representative sample before running a full directory. |
| | | 6. Visually inspect generated images, maps, documents, or spreadsheets. |
| | | 7. Record model, package, dataset, and weight licenses independently before suggesting commercial use. |
| | | 8. Update the capability README and `PROJECT_CONTEXT.md` after verified changes. |
| | | |
| | | ## Engineering Rules |
| | | |
| | | - Prefer existing project patterns and `geoai-py` APIs over new abstractions. |
| | | - Preserve geospatial coordinates only when the input has a valid CRS and transform. Ordinary JPEG results remain pixel coordinates. |
| | | - Report false positives, misses, runtime, and unsupported classes honestly. Do not hide limitations by lowering thresholds. |
| | | - Do not batch-run large datasets until a representative validation indicates that the approach is suitable. |
| | | - Do not delete or overwrite source imagery, model weights, results, or unrelated user changes. |
| | | - Do not commit unless the user explicitly asks. |
| New file |
| | |
| | | # GeoAI Workbench Current Context |
| | | |
| | | Last updated: 2026-08-14 |
| | | |
| | | This file is the current project snapshot for new Codex tasks. Keep it concise and replace stale facts instead of appending a conversation diary. |
| | | |
| | | ## Project Identity |
| | | |
| | | - Repository: `E:\AllWorkProject\geoai-workbench` |
| | | - Technical foundation: [opengeos/geoai](https://github.com/opengeos/geoai), PyPI package `geoai-py`, import name `geoai`. |
| | | - Purpose: build independently runnable GeoAI capability Demos for drone and remote-sensing workflows, then evaluate integration with existing products. |
| | | - Product repositories in scope for later integration: |
| | | - `E:\AllWorkProject\drone_project\ztzf-drone-web\applications\command-center-dashboard` |
| | | - `E:\AllWorkProject\drone_project\ztzf-drone-web\applications\drone-web-manage` |
| | | - Capability classification: A = direct `geoai-py`; B = GeoAI plus ecosystem libraries; C = product/service capability consuming GeoAI results. |
| | | |
| | | ## Collaboration and Constraints |
| | | |
| | | - The user is a beginner and can invest about 7-8 hours per day in running Demos and giving visual feedback. |
| | | - Codex is expected to handle environment setup, code, model selection, debugging, verification, and iteration. |
| | | - The machine runs Windows and PowerShell with 64 GB RAM and an AMD RX 590 GME 8 GB GPU. There is no NVIDIA CUDA, so current Demos use CPU inference. |
| | | - Use Python 3.12 for capability environments. The system also has Python 3.13, which must not replace or contaminate project environments. |
| | | - `geoai-py` is MIT-licensed, but every dependency, dataset, and model weight needs a separate commercial-license check. |
| | | - Current JPEG samples have no usable georeferencing. Their detections use pixel coordinates; accurate GeoJSON requires a georeferenced GeoTIFF. |
| | | |
| | | ## Environment Snapshot |
| | | |
| | | Dedicated object-detection environment: |
| | | |
| | | `E:\AllWorkProject\geoai-workbench\.venvs\01-object-detection` |
| | | |
| | | | Component | Version | |
| | | | --- | --- | |
| | | | Python | 3.12.10 | |
| | | | geoai-py | 0.42.0 | |
| | | | torch | 2.13.0+cpu | |
| | | | torchvision | 0.28.0+cpu | |
| | | | rasterio | 1.5.1 | |
| | | | geopandas | 1.1.4 | |
| | | | ultralytics | 8.4.118 | |
| | | |
| | | `pip check` passes in this environment. |
| | | |
| | | Dedicated trajectory-analysis environment: |
| | | |
| | | `E:\AllWorkProject\geoai-workbench\.venvs\15-trajectory-analysis` |
| | | |
| | | | Component | Version | |
| | | | --- | --- | |
| | | | Python | 3.12.10 | |
| | | | pandas | 2.3.3 | |
| | | | geopandas | 1.1.4 | |
| | | | shapely | 2.1.2 | |
| | | | pyproj | 3.7.2 | |
| | | | scikit-learn | 1.9.0 | |
| | | | matplotlib | 3.11.1 | |
| | | |
| | | `geoai-py` is intentionally not installed in this environment because version 0.42.0 has no trajectory, tracking, or behavior-recognition API. The capability consumes timestamped track results that may originate from an upstream GeoAI detection/export workflow. `pip check` passes. |
| | | |
| | | ## Local Experiment Console |
| | | |
| | | - Location: `apps/workbench-console/` |
| | | - Frontend: Vue 3 + Vite + Ant Design Vue + Pinia. Trajectory maps use Cesium `1.126.0` with a local grid base layer. |
| | | - Build: `Set-Location .\apps\workbench-console; npm install; npm run build` |
| | | - Start command: `py -3.12 .\scripts\serve_workbench_console.py` |
| | | - URL: `http://127.0.0.1:6173` (built console); Vite development is `http://127.0.0.1:6174/apps/workbench-console/`. Only `6xxx` ports are accepted. |
| | | - Scope: independent, read-only local UI for this workbench. It has no code, API, account, data-upload, or task-execution link to the two drone-product repositories. |
| | | - First release: overview plus live result pages for `01-object-detection` and `15-trajectory-analysis`; other capability pages show their registered status only. |
| | | - File exposure: the local server permits only console assets, `shared/outputs`, and source images required for the object-detection comparison. It does not expose the rest of the repository. |
| | | |
| | | ## Capability Status |
| | | |
| | | | Capability | Status | Current conclusion | |
| | | | --- | --- | --- | |
| | | | `00-change-detection` | Existing product capability confirmed; local Demo not implemented | The current system's orthophoto change detection already uses GeoAI. | |
| | | | `01-object-detection` | In progress; runnable people and vehicle experiments | Tiled YOLO helps people; GeoAI NWPU substantially improves top-down vehicles; tree detection is not implemented. | |
| | | | `15-trajectory-analysis` | Runnable CPU Demo verified | C capability consuming timestamped tracks: spatial metrics, DBSCAN and explicit behavior rules; not a built-in `geoai-py` function. | |
| | | | `02` through `14`, `16` through `18` | Directory and initial README only | No verified local Demo yet. Start each one through `$geoai-capability-builder`. | |
| | | |
| | | ## Object Detection Snapshot |
| | | |
| | | Target classes: people, vehicles, and trees. |
| | | |
| | | Inputs: |
| | | |
| | | - Directory: `shared/data/raw/01-object-detection` |
| | | - 20 files total: 19 ordinary JPEG images and one internal DJI MPO with a `.jpeg` extension. |
| | | - Most images are 3840 x 2160. The MPO is skipped safely. |
| | | |
| | | Environment and scripts: |
| | | |
| | | - General tiled detector: `capabilities/01-object-detection/run_detection.py` |
| | | - GeoAI aerial vehicle detector: `capabilities/01-object-detection/run_geoai_vehicle_detection.py` |
| | | - General model: Ultralytics `yolo11n.pt`, CPU, 1024-pixel tiles, 20% overlap, confidence 0.20. |
| | | - Aerial vehicle model: `giswqs/nwpu-vhr10-maskrcnn:best_model.pth`, called through `geoai-py`, 512-pixel windows, 128-pixel overlap, confidence 0.30. |
| | | |
| | | Measured results: |
| | | |
| | | - Whole-image YOLO baseline across 19 JPEGs: 13 detections, including 3 people and 10 cars; this misses many small targets. |
| | | - `DJI_20260713102047_0001_V_19.jpeg`: tiled YOLO found five person candidates, including duplicates and a false positive; the visible red vehicle was still missed by both YOLO and NWPU. |
| | | - `DJI_20260810092727_0001_V_10.jpeg`: general YOLO found only 2-3 cars. GeoAI NWPU produced 33 raw vehicle detections and 32 after containment deduplication, with about 90 seconds CPU inference. |
| | | - NWPU is much better for top-down aerial vehicles but still misses vehicles and can produce partial-box duplicates or other aerial-class false positives. |
| | | |
| | | Current technical decisions: |
| | | |
| | | - Do not batch-run the final pipeline across all images yet. |
| | | - Use separate model branches: tiled YOLO for people, aerial-specific detection for vehicles, and a future tree-crown detection or segmentation model for trees. |
| | | - Do not claim that the current COCO or NWPU model detects trees. |
| | | - Establish a fixed manually annotated validation set before tuning or training. |
| | | - Product use of Ultralytics and NWPU weights remains blocked on license review and accuracy evaluation. |
| | | |
| | | ## Trajectory Analysis Snapshot |
| | | |
| | | Inputs and artifacts: |
| | | |
| | | - Generator: `capabilities/15-trajectory-analysis/generate_demo_inputs.py` |
| | | - Analyzer: `capabilities/15-trajectory-analysis/run_trajectory_analysis.py` |
| | | - Input contract: WGS84 timestamped CSV observations plus GeoJSON reference routes and zones, connected by a `*.case.json` manifest. |
| | | - Generated representative inputs: `shared/data/raw/15-trajectory-analysis` |
| | | - Inspectable outputs: `shared/outputs/15-trajectory-analysis` |
| | | |
| | | Measured validation: |
| | | |
| | | - The normal synthetic case has 2 tracks and 0 events. |
| | | - The difficult synthetic case has 3 tracks, drops 1 duplicate timestamp, and finds exactly one stop, one route deviation, one restricted-zone event, and one gathering event. |
| | | - Directory processing handled 5 tracks and 78 cleaned observations in about 2.8 seconds on CPU. |
| | | - Missing required CSV columns fail cleanly with exit code 2. Both 1400 x 980 PNG results were visually inspected. |
| | | |
| | | Current decisions and limitations: |
| | | |
| | | - Keep the boundary explicit: the current Demo is C because it does not call `geoai-py`; using geospatial ecosystem libraries alone does not make it B. `geoai-py` is only a possible upstream source. |
| | | - This is rule-based behavior detection, not learned video action recognition, and it assumes track IDs already exist. |
| | | - Synthetic data verifies logic only. Do not claim real-world accuracy or batch-run operational data yet. |
| | | - Next decision: obtain one normal and one difficult anonymized real track set, manually label events, then measure false positives, misses, GPS sensitivity, and per-entity thresholds. |
| | | |
| | | ## Starting a New Capability |
| | | |
| | | Open a new Codex task with the repository as the working directory and use: |
| | | |
| | | ```text |
| | | 使用 $geoai-capability-builder,创建下一个能力:<能力名称>。 |
| | | 先读取 AGENTS.md 和 PROJECT_CONTEXT.md,再准备环境、Demo 输入输出和验收方式。 |
| | | ``` |
| | | |
| | | After completing verified work, update this snapshot and the capability README. Change `AGENTS.md` only when a long-lived rule changes. |
| | |
| | | # GeoAI Workbench |
| | | |
| | | GeoAI 多能力实验与产品验证工作区。每个能力都有独立目录、依赖声明和 |
| | | 输入/输出约定;通用依赖、共享数据、模型权重和运行结果由根目录统一管理。 |
| | | 本工作区以 [opengeos/geoai](https://github.com/opengeos/geoai) 的 `geoai-py` |
| | | 为 GeoAI 工作流基础,围绕无人机和遥感数据做实验与产品验证。这里的“GeoAI |
| | | 能力”分为三层:`geoai-py` 直接提供的功能、基于它的底层地理/AI生态组合出的 |
| | | 能力、以及需要由现有产品后端和业务规则实现的产品能力。后两类不能表述为 |
| | | `geoai-py` 内置功能。 |
| | | |
| | | ## 目录 |
| | | |
| | | ```text |
| | | geoai-workbench/ |
| | | |-- apps/ # 本地实验控制台等独立界面 |
| | | |-- capabilities/ # 一个能力一个目录 |
| | | |-- requirements/ # 跨能力共享的 Python 依赖 |
| | | |-- scripts/ # 环境初始化和检查脚本 |
| | |
| | | |
| | | ## 环境策略 |
| | | |
| | | - `geoai-py` 的包名是 `geoai-py`,导入名通常是 `geoai`,不是一个叫 `geoai` |
| | | 的操作系统或独立运行时。它要求 Python 3.12 或更高版本;本工作区固定使用 |
| | | Python 3.12 以兼容 PyTorch 和地理库。 |
| | | - 推荐 Python 3.11 或 3.12。当前电脑的 Python 3.13 可运行部分能力,但地理和 |
| | | 深度学习包的兼容性可能不完整。 |
| | | - 根目录 `.venv` 用于轻量能力和公共开发工具。 |
| | |
| | | powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\check-environment.ps1 |
| | | ``` |
| | | |
| | | 优先从 `06-spatial-reasoning`、`01-object-detection`、 |
| | | ## 本地实验控制台 |
| | | |
| | | 控制台是本工作区内部的只读展示界面,独立于现有无人机产品。它读取 |
| | | `shared/outputs/` 中已有的实验结果,方便直接查看标注影像、轨迹图、事件和运行 |
| | | 元数据;不会上传数据、调用外部接口或启动能力脚本。 |
| | | |
| | | ```powershell |
| | | py -3.12 .\scripts\serve_workbench_console.py |
| | | ``` |
| | | |
| | | 浏览器访问 <http://127.0.0.1:6173>。默认端口为 `6173`,仅监听本机;其他可用的 |
| | | `6xxx` 端口可以通过 `--port` 指定。界面说明见 |
| | | `apps/workbench-console/README.md`。 |
| | | |
| | | 优先从 `01-object-detection`、`02-semantic-mapping`、 |
| | | `07-risk-rule-engine` 和 `12-quality-control-refly` 开始。 |
| | | |
| | | ## 能力边界 |
| | | |
| | | - 直接能力:目标检测、语义/实例分割、影像分类、变化检测、遥感数据下载与 |
| | | 预处理、地理结果导出与可视化等,具体以 `geoai-py` 当前版本文档和源码为准。 |
| | | - 组合能力:空间测量、质量检测、灾害统计等,需要 `geoai-py` 加 Rasterio、 |
| | | GeoPandas、Shapely、PyTorch 或其他专用库。 |
| | | - 产品能力:空间规则、风险评分、航线规划、集群调度、工单闭环、GeoLLM、 |
| | | 知识图谱和模型治理,不是 `geoai-py` 单独提供的功能,需要产品服务实现。 |
| | | |
| | | 完整映射见 `capabilities/README.md` 和规划工作簿中的“能力映射”工作表。 |
| | | |
| | | ## 新对话继续工作 |
| | | |
| | | 新建 Codex 对话时,把工作目录选择为本项目根目录,然后使用个人 Skill |
| | | `$geoai-capability-builder`。它会先读取 `AGENTS.md` 和 `PROJECT_CONTEXT.md`, |
| | | 无需复制此前的长对话。可直接参考根目录的 `新对话启动说明.txt`。 |
| New file |
| | |
| | | # GeoAI Workbench 本地实验控制台 |
| | | |
| | | 这是一个完全独立于无人机产品的本地只读控制台。它只展示本工作区已有能力的 |
| | | 输入说明、运行元数据和结果工件,不上传数据、不调用外部 API,也不提供删除、 |
| | | 覆盖或启动算法的操作。 |
| | | |
| | | ## 启动 |
| | | |
| | | 首次使用先安装和构建前端: |
| | | |
| | | ```powershell |
| | | Set-Location .\apps\workbench-console |
| | | npm install |
| | | npm run build |
| | | Set-Location ..\.. |
| | | ``` |
| | | |
| | | 再从仓库根目录运行: |
| | | |
| | | ```powershell |
| | | py -3.12 .\scripts\serve_workbench_console.py |
| | | ``` |
| | | |
| | | 然后在浏览器打开 <http://127.0.0.1:6173>。服务默认只监听本机回环地址;若端口 |
| | | 已占用,可以显式传入另一个 `6xxx` 端口: |
| | | |
| | | ```powershell |
| | | py -3.12 .\scripts\serve_workbench_console.py --port 6174 |
| | | ``` |
| | | |
| | | 开发 Vue 页面时,保持结果服务在 `6173`,并在另一个 PowerShell 中运行: |
| | | |
| | | ```powershell |
| | | Set-Location .\apps\workbench-console |
| | | npm run dev |
| | | ``` |
| | | |
| | | Vite 开发服务固定使用 `http://127.0.0.1:6174/apps/workbench-console/`,会通过本机代理读取 `6173` 的结果工件。 |
| | | |
| | | ## 目录职责 |
| | | |
| | | - `apps/workbench-console/src/`:Vue 3 组件、路由、Pinia 状态与能力适配层。 |
| | | - `apps/workbench-console/dist/`:Vite 构建产物(不提交 Git)。 |
| | | - `scripts/serve_workbench_console.py`:只读本地文件服务;仅暴露构建后的控制台、已有输出和目标检测原图,不暴露仓库其余文件。 |
| | | - `shared/outputs/`:能力原始输出;控制台不复制、不修改这些文件。 |
| | | |
| | | 首版已接入 `01-object-detection` 和 `15-trajectory-analysis`。其他能力保留目录和 |
| | | 边界状态,待其首个 Demo 产出可检查工件后再接入。 |
| New file |
| | |
| | | <!doctype html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8" /> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| | | <meta name="color-scheme" content="light" /> |
| | | <title>GeoAI Workbench | 本地实验控制台</title> |
| | | </head> |
| | | <body> |
| | | <div id="app"></div> |
| | | <script type="module" src="/src/main.ts"></script> |
| | | </body> |
| | | </html> |
| New file |
| | |
| | | { |
| | | "name": "geoai-workbench-console", |
| | | "version": "0.1.0", |
| | | "lockfileVersion": 3, |
| | | "requires": true, |
| | | "packages": { |
| | | "": { |
| | | "name": "geoai-workbench-console", |
| | | "version": "0.1.0", |
| | | "dependencies": { |
| | | "@ant-design/icons-vue": "^7.0.1", |
| | | "ant-design-vue": "^4.2.6", |
| | | "cesium": "1.126.0", |
| | | "pinia": "^3.0.1", |
| | | "vue": "^3.5.13", |
| | | "vue-router": "^4.5.0" |
| | | }, |
| | | "devDependencies": { |
| | | "@vitejs/plugin-vue": "^5.2.1", |
| | | "typescript": "^5.7.2", |
| | | "vite": "^6.0.5", |
| | | "vite-plugin-static-copy": "^2.3.0", |
| | | "vue-tsc": "^2.2.0" |
| | | } |
| | | }, |
| | | "node_modules/@ant-design/colors": { |
| | | "version": "6.0.0", |
| | | "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz", |
| | | "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", |
| | | "dependencies": { |
| | | "@ctrl/tinycolor": "^3.4.0" |
| | | } |
| | | }, |
| | | "node_modules/@ant-design/icons-svg": { |
| | | "version": "4.5.0", |
| | | "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.5.0.tgz", |
| | | "integrity": "sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==" |
| | | }, |
| | | "node_modules/@ant-design/icons-vue": { |
| | | "version": "7.0.1", |
| | | "resolved": "https://registry.npmmirror.com/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz", |
| | | "integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==", |
| | | "dependencies": { |
| | | "@ant-design/colors": "^6.0.0", |
| | | "@ant-design/icons-svg": "^4.2.1" |
| | | }, |
| | | "peerDependencies": { |
| | | "vue": ">=3.0.3" |
| | | } |
| | | }, |
| | | "node_modules/@babel/helper-string-parser": { |
| | | "version": "7.29.7", |
| | | "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", |
| | | "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", |
| | | "engines": { |
| | | "node": ">=6.9.0" |
| | | } |
| | | }, |
| | | "node_modules/@babel/helper-validator-identifier": { |
| | | "version": "7.29.7", |
| | | "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", |
| | | "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", |
| | | "engines": { |
| | | "node": ">=6.9.0" |
| | | } |
| | | }, |
| | | "node_modules/@babel/parser": { |
| | | "version": "7.29.8", |
| | | "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.8.tgz", |
| | | "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", |
| | | "dependencies": { |
| | | "@babel/types": "^7.29.8" |
| | | }, |
| | | "bin": { |
| | | "parser": "bin/babel-parser.js" |
| | | }, |
| | | "engines": { |
| | | "node": ">=6.0.0" |
| | | } |
| | | }, |
| | | "node_modules/@babel/runtime": { |
| | | "version": "7.29.7", |
| | | "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz", |
| | | "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", |
| | | "engines": { |
| | | "node": ">=6.9.0" |
| | | } |
| | | }, |
| | | "node_modules/@babel/types": { |
| | | "version": "7.29.8", |
| | | "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", |
| | | "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", |
| | | "dependencies": { |
| | | "@babel/helper-string-parser": "^7.29.7", |
| | | "@babel/helper-validator-identifier": "^7.29.7" |
| | | }, |
| | | "engines": { |
| | | "node": ">=6.9.0" |
| | | } |
| | | }, |
| | | "node_modules/@cesium/engine": { |
| | | "version": "14.0.0", |
| | | "resolved": "https://registry.npmmirror.com/@cesium/engine/-/engine-14.0.0.tgz", |
| | | "integrity": "sha512-nmW0uQCyg4CRqi3a8o30gU9S9bFm3TLh2fRO74iv+6a8FFArvZ1xF7IqG2tyRjSXkyH4c5vpavNRT3R51R8NyQ==", |
| | | "dependencies": { |
| | | "@tweenjs/tween.js": "^25.0.0", |
| | | "@zip.js/zip.js": "^2.7.34", |
| | | "autolinker": "^4.0.0", |
| | | "bitmap-sdf": "^1.0.3", |
| | | "dompurify": "^3.0.2", |
| | | "draco3d": "^1.5.1", |
| | | "earcut": "^3.0.0", |
| | | "grapheme-splitter": "^1.0.4", |
| | | "jsep": "^1.3.8", |
| | | "kdbush": "^4.0.1", |
| | | "ktx-parse": "^0.7.0", |
| | | "lerc": "^2.0.0", |
| | | "mersenne-twister": "^1.1.0", |
| | | "meshoptimizer": "^0.22.0", |
| | | "pako": "^2.0.4", |
| | | "protobufjs": "^7.1.0", |
| | | "rbush": "3.0.1", |
| | | "topojson-client": "^3.1.0", |
| | | "urijs": "^1.19.7" |
| | | }, |
| | | "engines": { |
| | | "node": ">=14.0.0" |
| | | } |
| | | }, |
| | | "node_modules/@cesium/widgets": { |
| | | "version": "10.2.0", |
| | | "resolved": "https://registry.npmmirror.com/@cesium/widgets/-/widgets-10.2.0.tgz", |
| | | "integrity": "sha512-f0Wrp3MG02P2KSAopVESHWOhF+2eK9cQR9prEYGWuPM3iF6YktKrZqXzNnsOxCw1KPup0aSXsCrFl2asT5jF9g==", |
| | | "dependencies": { |
| | | "@cesium/engine": "^14.0.0", |
| | | "nosleep.js": "^0.12.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=14.0.0" |
| | | } |
| | | }, |
| | | "node_modules/@ctrl/tinycolor": { |
| | | "version": "3.6.1", |
| | | "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", |
| | | "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", |
| | | "engines": { |
| | | "node": ">=10" |
| | | } |
| | | }, |
| | | "node_modules/@emotion/hash": { |
| | | "version": "0.9.2", |
| | | "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz", |
| | | "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" |
| | | }, |
| | | "node_modules/@emotion/unitless": { |
| | | "version": "0.8.1", |
| | | "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz", |
| | | "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" |
| | | }, |
| | | "node_modules/@esbuild/aix-ppc64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", |
| | | "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", |
| | | "cpu": [ |
| | | "ppc64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "aix" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/android-arm": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", |
| | | "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", |
| | | "cpu": [ |
| | | "arm" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "android" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/android-arm64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", |
| | | "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "android" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/android-x64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", |
| | | "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "android" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/darwin-arm64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", |
| | | "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "darwin" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/darwin-x64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", |
| | | "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "darwin" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/freebsd-arm64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", |
| | | "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "freebsd" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/freebsd-x64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", |
| | | "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "freebsd" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/linux-arm": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", |
| | | "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", |
| | | "cpu": [ |
| | | "arm" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/linux-arm64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", |
| | | "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/linux-ia32": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", |
| | | "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", |
| | | "cpu": [ |
| | | "ia32" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/linux-loong64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", |
| | | "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", |
| | | "cpu": [ |
| | | "loong64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/linux-mips64el": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", |
| | | "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", |
| | | "cpu": [ |
| | | "mips64el" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/linux-ppc64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", |
| | | "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", |
| | | "cpu": [ |
| | | "ppc64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/linux-riscv64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", |
| | | "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", |
| | | "cpu": [ |
| | | "riscv64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/linux-s390x": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", |
| | | "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", |
| | | "cpu": [ |
| | | "s390x" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/linux-x64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", |
| | | "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/netbsd-arm64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", |
| | | "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "netbsd" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/netbsd-x64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", |
| | | "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "netbsd" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/openbsd-arm64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", |
| | | "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "openbsd" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/openbsd-x64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", |
| | | "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "openbsd" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/openharmony-arm64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", |
| | | "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "openharmony" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/sunos-x64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", |
| | | "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "sunos" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/win32-arm64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", |
| | | "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "win32" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/win32-ia32": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", |
| | | "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", |
| | | "cpu": [ |
| | | "ia32" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "win32" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@esbuild/win32-x64": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", |
| | | "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "win32" |
| | | ], |
| | | "engines": { |
| | | "node": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/@jridgewell/sourcemap-codec": { |
| | | "version": "1.5.5", |
| | | "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", |
| | | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" |
| | | }, |
| | | "node_modules/@napi-rs/lzma-linux-x64-gnu": { |
| | | "version": "1.5.1", |
| | | "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", |
| | | "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ], |
| | | "engines": { |
| | | "node": "^22.20 || ^24.12 || >=25" |
| | | } |
| | | }, |
| | | "node_modules/@nodelib/fs.scandir": { |
| | | "version": "2.1.5", |
| | | "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", |
| | | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "@nodelib/fs.stat": "2.0.5", |
| | | "run-parallel": "^1.1.9" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 8" |
| | | } |
| | | }, |
| | | "node_modules/@nodelib/fs.stat": { |
| | | "version": "2.0.5", |
| | | "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", |
| | | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">= 8" |
| | | } |
| | | }, |
| | | "node_modules/@nodelib/fs.walk": { |
| | | "version": "1.2.8", |
| | | "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", |
| | | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "@nodelib/fs.scandir": "2.1.5", |
| | | "fastq": "^1.6.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 8" |
| | | } |
| | | }, |
| | | "node_modules/@protobufjs/aspromise": { |
| | | "version": "1.1.2", |
| | | "resolved": "https://registry.npmmirror.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", |
| | | "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" |
| | | }, |
| | | "node_modules/@protobufjs/base64": { |
| | | "version": "1.1.2", |
| | | "resolved": "https://registry.npmmirror.com/@protobufjs/base64/-/base64-1.1.2.tgz", |
| | | "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" |
| | | }, |
| | | "node_modules/@protobufjs/codegen": { |
| | | "version": "2.0.5", |
| | | "resolved": "https://registry.npmmirror.com/@protobufjs/codegen/-/codegen-2.0.5.tgz", |
| | | "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==" |
| | | }, |
| | | "node_modules/@protobufjs/eventemitter": { |
| | | "version": "1.1.1", |
| | | "resolved": "https://registry.npmmirror.com/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", |
| | | "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==" |
| | | }, |
| | | "node_modules/@protobufjs/fetch": { |
| | | "version": "1.1.1", |
| | | "resolved": "https://registry.npmmirror.com/@protobufjs/fetch/-/fetch-1.1.1.tgz", |
| | | "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", |
| | | "dependencies": { |
| | | "@protobufjs/aspromise": "^1.1.1" |
| | | } |
| | | }, |
| | | "node_modules/@protobufjs/float": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmmirror.com/@protobufjs/float/-/float-1.0.2.tgz", |
| | | "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" |
| | | }, |
| | | "node_modules/@protobufjs/path": { |
| | | "version": "1.1.2", |
| | | "resolved": "https://registry.npmmirror.com/@protobufjs/path/-/path-1.1.2.tgz", |
| | | "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" |
| | | }, |
| | | "node_modules/@protobufjs/pool": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmmirror.com/@protobufjs/pool/-/pool-1.1.0.tgz", |
| | | "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" |
| | | }, |
| | | "node_modules/@protobufjs/utf8": { |
| | | "version": "1.1.2", |
| | | "resolved": "https://registry.npmmirror.com/@protobufjs/utf8/-/utf8-1.1.2.tgz", |
| | | "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==" |
| | | }, |
| | | "node_modules/@rollup/rollup-android-arm-eabi": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", |
| | | "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", |
| | | "cpu": [ |
| | | "arm" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "android" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-android-arm64": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", |
| | | "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "android" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-darwin-arm64": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", |
| | | "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "darwin" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-darwin-x64": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", |
| | | "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "darwin" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-freebsd-arm64": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", |
| | | "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "freebsd" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-freebsd-x64": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", |
| | | "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "freebsd" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", |
| | | "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", |
| | | "cpu": [ |
| | | "arm" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-arm-musleabihf": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", |
| | | "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", |
| | | "cpu": [ |
| | | "arm" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-arm64-gnu": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", |
| | | "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-arm64-musl": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", |
| | | "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-loong64-gnu": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", |
| | | "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", |
| | | "cpu": [ |
| | | "loong64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-loong64-musl": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", |
| | | "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", |
| | | "cpu": [ |
| | | "loong64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-ppc64-gnu": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", |
| | | "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", |
| | | "cpu": [ |
| | | "ppc64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-ppc64-musl": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", |
| | | "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", |
| | | "cpu": [ |
| | | "ppc64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-riscv64-gnu": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", |
| | | "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", |
| | | "cpu": [ |
| | | "riscv64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-riscv64-musl": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", |
| | | "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", |
| | | "cpu": [ |
| | | "riscv64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-s390x-gnu": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", |
| | | "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", |
| | | "cpu": [ |
| | | "s390x" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-x64-gnu": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", |
| | | "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-linux-x64-musl": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", |
| | | "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "linux" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-openbsd-x64": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", |
| | | "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "openbsd" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-openharmony-arm64": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", |
| | | "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "openharmony" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-win32-arm64-msvc": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", |
| | | "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", |
| | | "cpu": [ |
| | | "arm64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "win32" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-win32-ia32-msvc": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", |
| | | "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", |
| | | "cpu": [ |
| | | "ia32" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "win32" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-win32-x64-gnu": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", |
| | | "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "win32" |
| | | ] |
| | | }, |
| | | "node_modules/@rollup/rollup-win32-x64-msvc": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", |
| | | "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", |
| | | "cpu": [ |
| | | "x64" |
| | | ], |
| | | "dev": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "win32" |
| | | ] |
| | | }, |
| | | "node_modules/@simonwep/pickr": { |
| | | "version": "1.8.2", |
| | | "resolved": "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz", |
| | | "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", |
| | | "dependencies": { |
| | | "core-js": "^3.15.1", |
| | | "nanopop": "^2.1.0" |
| | | } |
| | | }, |
| | | "node_modules/@tweenjs/tween.js": { |
| | | "version": "25.0.0", |
| | | "resolved": "https://registry.npmmirror.com/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", |
| | | "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==" |
| | | }, |
| | | "node_modules/@types/estree": { |
| | | "version": "1.0.9", |
| | | "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", |
| | | "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/@types/node": { |
| | | "version": "26.2.0", |
| | | "resolved": "https://registry.npmmirror.com/@types/node/-/node-26.2.0.tgz", |
| | | "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", |
| | | "dependencies": { |
| | | "undici-types": "~8.3.0" |
| | | } |
| | | }, |
| | | "node_modules/@types/trusted-types": { |
| | | "version": "2.0.7", |
| | | "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", |
| | | "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", |
| | | "optional": true |
| | | }, |
| | | "node_modules/@vitejs/plugin-vue": { |
| | | "version": "5.2.4", |
| | | "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", |
| | | "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": "^18.0.0 || >=20.0.0" |
| | | }, |
| | | "peerDependencies": { |
| | | "vite": "^5.0.0 || ^6.0.0", |
| | | "vue": "^3.2.25" |
| | | } |
| | | }, |
| | | "node_modules/@volar/language-core": { |
| | | "version": "2.4.15", |
| | | "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.15.tgz", |
| | | "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "@volar/source-map": "2.4.15" |
| | | } |
| | | }, |
| | | "node_modules/@volar/source-map": { |
| | | "version": "2.4.15", |
| | | "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.15.tgz", |
| | | "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/@volar/typescript": { |
| | | "version": "2.4.15", |
| | | "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.15.tgz", |
| | | "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "@volar/language-core": "2.4.15", |
| | | "path-browserify": "^1.0.1", |
| | | "vscode-uri": "^3.0.8" |
| | | } |
| | | }, |
| | | "node_modules/@vue/compiler-core": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.41.tgz", |
| | | "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", |
| | | "dependencies": { |
| | | "@babel/parser": "^7.29.8", |
| | | "@vue/shared": "3.5.41", |
| | | "entities": "^7.0.1", |
| | | "estree-walker": "^2.0.2", |
| | | "source-map-js": "^1.2.1" |
| | | } |
| | | }, |
| | | "node_modules/@vue/compiler-dom": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", |
| | | "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", |
| | | "dependencies": { |
| | | "@vue/compiler-core": "3.5.41", |
| | | "@vue/shared": "3.5.41" |
| | | } |
| | | }, |
| | | "node_modules/@vue/compiler-sfc": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", |
| | | "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", |
| | | "dependencies": { |
| | | "@babel/parser": "^7.29.8", |
| | | "@vue/compiler-core": "3.5.41", |
| | | "@vue/compiler-dom": "3.5.41", |
| | | "@vue/compiler-ssr": "3.5.41", |
| | | "@vue/shared": "3.5.41", |
| | | "estree-walker": "^2.0.2", |
| | | "magic-string": "^0.30.21", |
| | | "postcss": "^8.5.19", |
| | | "source-map-js": "^1.2.1" |
| | | } |
| | | }, |
| | | "node_modules/@vue/compiler-ssr": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", |
| | | "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", |
| | | "dependencies": { |
| | | "@vue/compiler-dom": "3.5.41", |
| | | "@vue/shared": "3.5.41" |
| | | } |
| | | }, |
| | | "node_modules/@vue/compiler-vue2": { |
| | | "version": "2.7.16", |
| | | "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", |
| | | "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "de-indent": "^1.0.2", |
| | | "he": "^1.2.0" |
| | | } |
| | | }, |
| | | "node_modules/@vue/devtools-api": { |
| | | "version": "7.7.10", |
| | | "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.10.tgz", |
| | | "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", |
| | | "dependencies": { |
| | | "@vue/devtools-kit": "^7.7.10" |
| | | } |
| | | }, |
| | | "node_modules/@vue/devtools-kit": { |
| | | "version": "7.7.10", |
| | | "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", |
| | | "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", |
| | | "dependencies": { |
| | | "@vue/devtools-shared": "^7.7.10", |
| | | "birpc": "^2.3.0", |
| | | "hookable": "^5.5.3", |
| | | "mitt": "^3.0.1", |
| | | "perfect-debounce": "^1.0.0", |
| | | "speakingurl": "^14.0.1", |
| | | "superjson": "^2.2.2" |
| | | } |
| | | }, |
| | | "node_modules/@vue/devtools-shared": { |
| | | "version": "7.7.10", |
| | | "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", |
| | | "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", |
| | | "dependencies": { |
| | | "rfdc": "^1.4.1" |
| | | } |
| | | }, |
| | | "node_modules/@vue/language-core": { |
| | | "version": "2.2.12", |
| | | "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.12.tgz", |
| | | "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "@volar/language-core": "2.4.15", |
| | | "@vue/compiler-dom": "^3.5.0", |
| | | "@vue/compiler-vue2": "^2.7.16", |
| | | "@vue/shared": "^3.5.0", |
| | | "alien-signals": "^1.0.3", |
| | | "minimatch": "^9.0.3", |
| | | "muggle-string": "^0.4.1", |
| | | "path-browserify": "^1.0.1" |
| | | }, |
| | | "peerDependencies": { |
| | | "typescript": "*" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "typescript": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/@vue/reactivity": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.41.tgz", |
| | | "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", |
| | | "dependencies": { |
| | | "@vue/shared": "3.5.41" |
| | | } |
| | | }, |
| | | "node_modules/@vue/runtime-core": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.41.tgz", |
| | | "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", |
| | | "dependencies": { |
| | | "@vue/reactivity": "3.5.41", |
| | | "@vue/shared": "3.5.41" |
| | | } |
| | | }, |
| | | "node_modules/@vue/runtime-dom": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", |
| | | "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", |
| | | "dependencies": { |
| | | "@vue/reactivity": "3.5.41", |
| | | "@vue/runtime-core": "3.5.41", |
| | | "@vue/shared": "3.5.41", |
| | | "csstype": "^3.2.3" |
| | | } |
| | | }, |
| | | "node_modules/@vue/server-renderer": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.41.tgz", |
| | | "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", |
| | | "dependencies": { |
| | | "@vue/compiler-ssr": "3.5.41", |
| | | "@vue/runtime-dom": "3.5.41", |
| | | "@vue/shared": "3.5.41" |
| | | } |
| | | }, |
| | | "node_modules/@vue/shared": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.41.tgz", |
| | | "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==" |
| | | }, |
| | | "node_modules/@zip.js/zip.js": { |
| | | "version": "2.8.49", |
| | | "resolved": "https://registry.npmmirror.com/@zip.js/zip.js/-/zip.js-2.8.49.tgz", |
| | | "integrity": "sha512-TY3fKR/IQqPJqrOQAohvW6kv7Qd1aehzU7M7hbqhNNKxVP8uKSMO+aUlBwtdWzTCd62E0YOB+HHW9N0hMihrWw==", |
| | | "engines": { |
| | | "bun": ">=0.7.0", |
| | | "deno": ">=1.0.0", |
| | | "node": ">=18.0.0" |
| | | } |
| | | }, |
| | | "node_modules/alien-signals": { |
| | | "version": "1.0.13", |
| | | "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-1.0.13.tgz", |
| | | "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/ant-design-vue": { |
| | | "version": "4.2.6", |
| | | "resolved": "https://registry.npmmirror.com/ant-design-vue/-/ant-design-vue-4.2.6.tgz", |
| | | "integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==", |
| | | "dependencies": { |
| | | "@ant-design/colors": "^6.0.0", |
| | | "@ant-design/icons-vue": "^7.0.0", |
| | | "@babel/runtime": "^7.10.5", |
| | | "@ctrl/tinycolor": "^3.5.0", |
| | | "@emotion/hash": "^0.9.0", |
| | | "@emotion/unitless": "^0.8.0", |
| | | "@simonwep/pickr": "~1.8.0", |
| | | "array-tree-filter": "^2.1.0", |
| | | "async-validator": "^4.0.0", |
| | | "csstype": "^3.1.1", |
| | | "dayjs": "^1.10.5", |
| | | "dom-align": "^1.12.1", |
| | | "dom-scroll-into-view": "^2.0.0", |
| | | "lodash": "^4.17.21", |
| | | "lodash-es": "^4.17.15", |
| | | "resize-observer-polyfill": "^1.5.1", |
| | | "scroll-into-view-if-needed": "^2.2.25", |
| | | "shallow-equal": "^1.0.0", |
| | | "stylis": "^4.1.3", |
| | | "throttle-debounce": "^5.0.0", |
| | | "vue-types": "^3.0.0", |
| | | "warning": "^4.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=12.22.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/ant-design-vue" |
| | | }, |
| | | "peerDependencies": { |
| | | "vue": ">=3.2.0" |
| | | } |
| | | }, |
| | | "node_modules/anymatch": { |
| | | "version": "3.1.3", |
| | | "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", |
| | | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "normalize-path": "^3.0.0", |
| | | "picomatch": "^2.0.4" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 8" |
| | | } |
| | | }, |
| | | "node_modules/anymatch/node_modules/picomatch": { |
| | | "version": "2.3.2", |
| | | "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", |
| | | "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=8.6" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/jonschlinkert" |
| | | } |
| | | }, |
| | | "node_modules/array-tree-filter": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz", |
| | | "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==" |
| | | }, |
| | | "node_modules/async-validator": { |
| | | "version": "4.2.5", |
| | | "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", |
| | | "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" |
| | | }, |
| | | "node_modules/autolinker": { |
| | | "version": "4.1.5", |
| | | "resolved": "https://registry.npmmirror.com/autolinker/-/autolinker-4.1.5.tgz", |
| | | "integrity": "sha512-vEfYZPmvVOIuE567XBVCsx8SBgOYtjB2+S1iAaJ+HgH+DNjAcrHem2hmAeC9yaNGWayicv4yR+9UaJlkF3pvtw==", |
| | | "dependencies": { |
| | | "tslib": "^2.8.1" |
| | | }, |
| | | "engines": { |
| | | "pnpm": ">=10.10.0" |
| | | } |
| | | }, |
| | | "node_modules/balanced-match": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", |
| | | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/binary-extensions": { |
| | | "version": "2.3.0", |
| | | "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", |
| | | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=8" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/birpc": { |
| | | "version": "2.9.0", |
| | | "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", |
| | | "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/antfu" |
| | | } |
| | | }, |
| | | "node_modules/bitmap-sdf": { |
| | | "version": "1.0.4", |
| | | "resolved": "https://registry.npmmirror.com/bitmap-sdf/-/bitmap-sdf-1.0.4.tgz", |
| | | "integrity": "sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg==" |
| | | }, |
| | | "node_modules/brace-expansion": { |
| | | "version": "2.1.4", |
| | | "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.4.tgz", |
| | | "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "balanced-match": "^1.0.0" |
| | | } |
| | | }, |
| | | "node_modules/braces": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", |
| | | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "fill-range": "^7.1.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/cesium": { |
| | | "version": "1.126.0", |
| | | "resolved": "https://registry.npmmirror.com/cesium/-/cesium-1.126.0.tgz", |
| | | "integrity": "sha512-29Cy6eq9NzTbQUaOXQEnVJyYoHCeizrBTbbB76OClI5+FNq9saUUx+iQXg/J6r8vX+KWt6oD/lVpZ0QMcvAgog==", |
| | | "workspaces": [ |
| | | "packages/engine", |
| | | "packages/widgets" |
| | | ], |
| | | "dependencies": { |
| | | "@cesium/engine": "^14.0.0", |
| | | "@cesium/widgets": "^10.2.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=18.18.0" |
| | | } |
| | | }, |
| | | "node_modules/chokidar": { |
| | | "version": "3.6.0", |
| | | "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", |
| | | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "anymatch": "~3.1.2", |
| | | "braces": "~3.0.2", |
| | | "glob-parent": "~5.1.2", |
| | | "is-binary-path": "~2.1.0", |
| | | "is-glob": "~4.0.1", |
| | | "normalize-path": "~3.0.0", |
| | | "readdirp": "~3.6.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 8.10.0" |
| | | }, |
| | | "funding": { |
| | | "url": "https://paulmillr.com/funding/" |
| | | }, |
| | | "optionalDependencies": { |
| | | "fsevents": "~2.3.2" |
| | | } |
| | | }, |
| | | "node_modules/commander": { |
| | | "version": "2.20.3", |
| | | "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", |
| | | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" |
| | | }, |
| | | "node_modules/compute-scroll-into-view": { |
| | | "version": "1.0.20", |
| | | "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", |
| | | "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" |
| | | }, |
| | | "node_modules/copy-anything": { |
| | | "version": "4.0.5", |
| | | "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", |
| | | "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", |
| | | "dependencies": { |
| | | "is-what": "^5.2.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=18" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/mesqueeb" |
| | | } |
| | | }, |
| | | "node_modules/core-js": { |
| | | "version": "3.50.0", |
| | | "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.50.0.tgz", |
| | | "integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==", |
| | | "hasInstallScript": true, |
| | | "engines": { |
| | | "node": "*" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/core-js" |
| | | } |
| | | }, |
| | | "node_modules/csstype": { |
| | | "version": "3.2.3", |
| | | "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", |
| | | "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" |
| | | }, |
| | | "node_modules/dayjs": { |
| | | "version": "1.11.21", |
| | | "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.21.tgz", |
| | | "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==" |
| | | }, |
| | | "node_modules/de-indent": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", |
| | | "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/dom-align": { |
| | | "version": "1.12.4", |
| | | "resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz", |
| | | "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==" |
| | | }, |
| | | "node_modules/dom-scroll-into-view": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz", |
| | | "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==" |
| | | }, |
| | | "node_modules/dompurify": { |
| | | "version": "3.4.13", |
| | | "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.4.13.tgz", |
| | | "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", |
| | | "optionalDependencies": { |
| | | "@types/trusted-types": "^2.0.7" |
| | | } |
| | | }, |
| | | "node_modules/draco3d": { |
| | | "version": "1.5.7", |
| | | "resolved": "https://registry.npmmirror.com/draco3d/-/draco3d-1.5.7.tgz", |
| | | "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==" |
| | | }, |
| | | "node_modules/earcut": { |
| | | "version": "3.2.3", |
| | | "resolved": "https://registry.npmmirror.com/earcut/-/earcut-3.2.3.tgz", |
| | | "integrity": "sha512-vnS4AVwp1KHAF13i1vp1/2D5evWy3k5u/iW/B81QVsUZtV8cv2tU0b2VNFlqvh4kYwrFMDdjPCfAmfyJW9y14Q==" |
| | | }, |
| | | "node_modules/entities": { |
| | | "version": "7.0.1", |
| | | "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", |
| | | "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", |
| | | "engines": { |
| | | "node": ">=0.12" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/fb55/entities?sponsor=1" |
| | | } |
| | | }, |
| | | "node_modules/esbuild": { |
| | | "version": "0.25.12", |
| | | "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", |
| | | "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", |
| | | "dev": true, |
| | | "hasInstallScript": true, |
| | | "bin": { |
| | | "esbuild": "bin/esbuild" |
| | | }, |
| | | "engines": { |
| | | "node": ">=18" |
| | | }, |
| | | "optionalDependencies": { |
| | | "@esbuild/aix-ppc64": "0.25.12", |
| | | "@esbuild/android-arm": "0.25.12", |
| | | "@esbuild/android-arm64": "0.25.12", |
| | | "@esbuild/android-x64": "0.25.12", |
| | | "@esbuild/darwin-arm64": "0.25.12", |
| | | "@esbuild/darwin-x64": "0.25.12", |
| | | "@esbuild/freebsd-arm64": "0.25.12", |
| | | "@esbuild/freebsd-x64": "0.25.12", |
| | | "@esbuild/linux-arm": "0.25.12", |
| | | "@esbuild/linux-arm64": "0.25.12", |
| | | "@esbuild/linux-ia32": "0.25.12", |
| | | "@esbuild/linux-loong64": "0.25.12", |
| | | "@esbuild/linux-mips64el": "0.25.12", |
| | | "@esbuild/linux-ppc64": "0.25.12", |
| | | "@esbuild/linux-riscv64": "0.25.12", |
| | | "@esbuild/linux-s390x": "0.25.12", |
| | | "@esbuild/linux-x64": "0.25.12", |
| | | "@esbuild/netbsd-arm64": "0.25.12", |
| | | "@esbuild/netbsd-x64": "0.25.12", |
| | | "@esbuild/openbsd-arm64": "0.25.12", |
| | | "@esbuild/openbsd-x64": "0.25.12", |
| | | "@esbuild/openharmony-arm64": "0.25.12", |
| | | "@esbuild/sunos-x64": "0.25.12", |
| | | "@esbuild/win32-arm64": "0.25.12", |
| | | "@esbuild/win32-ia32": "0.25.12", |
| | | "@esbuild/win32-x64": "0.25.12" |
| | | } |
| | | }, |
| | | "node_modules/estree-walker": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", |
| | | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" |
| | | }, |
| | | "node_modules/fast-glob": { |
| | | "version": "3.3.3", |
| | | "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", |
| | | "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "@nodelib/fs.stat": "^2.0.2", |
| | | "@nodelib/fs.walk": "^1.2.3", |
| | | "glob-parent": "^5.1.2", |
| | | "merge2": "^1.3.0", |
| | | "micromatch": "^4.0.8" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8.6.0" |
| | | } |
| | | }, |
| | | "node_modules/fastq": { |
| | | "version": "1.20.1", |
| | | "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", |
| | | "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "reusify": "^1.0.4" |
| | | } |
| | | }, |
| | | "node_modules/fdir": { |
| | | "version": "6.5.0", |
| | | "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", |
| | | "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=12.0.0" |
| | | }, |
| | | "peerDependencies": { |
| | | "picomatch": "^3 || ^4" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "picomatch": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/fill-range": { |
| | | "version": "7.1.1", |
| | | "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", |
| | | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "to-regex-range": "^5.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/fs-extra": { |
| | | "version": "11.4.0", |
| | | "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.4.0.tgz", |
| | | "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "graceful-fs": "^4.2.0", |
| | | "jsonfile": "^6.0.1", |
| | | "universalify": "^2.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=14.14" |
| | | } |
| | | }, |
| | | "node_modules/fsevents": { |
| | | "version": "2.3.3", |
| | | "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", |
| | | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", |
| | | "dev": true, |
| | | "hasInstallScript": true, |
| | | "optional": true, |
| | | "os": [ |
| | | "darwin" |
| | | ], |
| | | "engines": { |
| | | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" |
| | | } |
| | | }, |
| | | "node_modules/glob-parent": { |
| | | "version": "5.1.2", |
| | | "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", |
| | | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "is-glob": "^4.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 6" |
| | | } |
| | | }, |
| | | "node_modules/graceful-fs": { |
| | | "version": "4.2.11", |
| | | "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", |
| | | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/grapheme-splitter": { |
| | | "version": "1.0.4", |
| | | "resolved": "https://registry.npmmirror.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", |
| | | "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" |
| | | }, |
| | | "node_modules/he": { |
| | | "version": "1.2.0", |
| | | "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", |
| | | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", |
| | | "dev": true, |
| | | "bin": { |
| | | "he": "bin/he" |
| | | } |
| | | }, |
| | | "node_modules/hookable": { |
| | | "version": "5.5.3", |
| | | "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", |
| | | "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==" |
| | | }, |
| | | "node_modules/is-binary-path": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", |
| | | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "binary-extensions": "^2.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/is-extglob": { |
| | | "version": "2.1.1", |
| | | "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", |
| | | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/is-glob": { |
| | | "version": "4.0.3", |
| | | "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", |
| | | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "is-extglob": "^2.1.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/is-number": { |
| | | "version": "7.0.0", |
| | | "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", |
| | | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=0.12.0" |
| | | } |
| | | }, |
| | | "node_modules/is-plain-object": { |
| | | "version": "3.0.1", |
| | | "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-3.0.1.tgz", |
| | | "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", |
| | | "engines": { |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/is-what": { |
| | | "version": "5.5.0", |
| | | "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", |
| | | "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", |
| | | "engines": { |
| | | "node": ">=18" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/mesqueeb" |
| | | } |
| | | }, |
| | | "node_modules/js-tokens": { |
| | | "version": "4.0.0", |
| | | "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", |
| | | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" |
| | | }, |
| | | "node_modules/jsep": { |
| | | "version": "1.4.0", |
| | | "resolved": "https://registry.npmmirror.com/jsep/-/jsep-1.4.0.tgz", |
| | | "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", |
| | | "engines": { |
| | | "node": ">= 10.16.0" |
| | | } |
| | | }, |
| | | "node_modules/jsonfile": { |
| | | "version": "6.2.1", |
| | | "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.1.tgz", |
| | | "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "universalify": "^2.0.0" |
| | | }, |
| | | "optionalDependencies": { |
| | | "graceful-fs": "^4.1.6" |
| | | } |
| | | }, |
| | | "node_modules/kdbush": { |
| | | "version": "4.1.0", |
| | | "resolved": "https://registry.npmmirror.com/kdbush/-/kdbush-4.1.0.tgz", |
| | | "integrity": "sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==" |
| | | }, |
| | | "node_modules/ktx-parse": { |
| | | "version": "0.7.1", |
| | | "resolved": "https://registry.npmmirror.com/ktx-parse/-/ktx-parse-0.7.1.tgz", |
| | | "integrity": "sha512-FeA3g56ksdFNwjXJJsc1CCc7co+AJYDp6ipIp878zZ2bU8kWROatLYf39TQEd4/XRSUvBXovQ8gaVKWPXsCLEQ==" |
| | | }, |
| | | "node_modules/lerc": { |
| | | "version": "2.0.0", |
| | | "resolved": "https://registry.npmmirror.com/lerc/-/lerc-2.0.0.tgz", |
| | | "integrity": "sha512-7qo1Mq8ZNmaR4USHHm615nEW2lPeeWJ3bTyoqFbd35DLx0LUH7C6ptt5FDCTAlbIzs3+WKrk5SkJvw8AFDE2hg==" |
| | | }, |
| | | "node_modules/lodash": { |
| | | "version": "4.18.1", |
| | | "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", |
| | | "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" |
| | | }, |
| | | "node_modules/lodash-es": { |
| | | "version": "4.18.1", |
| | | "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz", |
| | | "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==" |
| | | }, |
| | | "node_modules/long": { |
| | | "version": "5.3.2", |
| | | "resolved": "https://registry.npmmirror.com/long/-/long-5.3.2.tgz", |
| | | "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" |
| | | }, |
| | | "node_modules/loose-envify": { |
| | | "version": "1.4.0", |
| | | "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", |
| | | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", |
| | | "dependencies": { |
| | | "js-tokens": "^3.0.0 || ^4.0.0" |
| | | }, |
| | | "bin": { |
| | | "loose-envify": "cli.js" |
| | | } |
| | | }, |
| | | "node_modules/magic-string": { |
| | | "version": "0.30.21", |
| | | "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", |
| | | "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", |
| | | "dependencies": { |
| | | "@jridgewell/sourcemap-codec": "^1.5.5" |
| | | } |
| | | }, |
| | | "node_modules/merge2": { |
| | | "version": "1.4.1", |
| | | "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", |
| | | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">= 8" |
| | | } |
| | | }, |
| | | "node_modules/mersenne-twister": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmmirror.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz", |
| | | "integrity": "sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA==" |
| | | }, |
| | | "node_modules/meshoptimizer": { |
| | | "version": "0.22.0", |
| | | "resolved": "https://registry.npmmirror.com/meshoptimizer/-/meshoptimizer-0.22.0.tgz", |
| | | "integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==" |
| | | }, |
| | | "node_modules/micromatch": { |
| | | "version": "4.0.8", |
| | | "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", |
| | | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "braces": "^3.0.3", |
| | | "picomatch": "^2.3.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8.6" |
| | | } |
| | | }, |
| | | "node_modules/micromatch/node_modules/picomatch": { |
| | | "version": "2.3.2", |
| | | "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", |
| | | "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=8.6" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/jonschlinkert" |
| | | } |
| | | }, |
| | | "node_modules/minimatch": { |
| | | "version": "9.0.9", |
| | | "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", |
| | | "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "brace-expansion": "^2.0.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">=16 || 14 >=14.17" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/isaacs" |
| | | } |
| | | }, |
| | | "node_modules/mitt": { |
| | | "version": "3.0.1", |
| | | "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", |
| | | "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" |
| | | }, |
| | | "node_modules/muggle-string": { |
| | | "version": "0.4.1", |
| | | "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", |
| | | "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/nanoid": { |
| | | "version": "3.3.18", |
| | | "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.18.tgz", |
| | | "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", |
| | | "funding": [ |
| | | { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/ai" |
| | | } |
| | | ], |
| | | "bin": { |
| | | "nanoid": "bin/nanoid.cjs" |
| | | }, |
| | | "engines": { |
| | | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" |
| | | } |
| | | }, |
| | | "node_modules/nanopop": { |
| | | "version": "2.4.2", |
| | | "resolved": "https://registry.npmmirror.com/nanopop/-/nanopop-2.4.2.tgz", |
| | | "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==" |
| | | }, |
| | | "node_modules/normalize-path": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", |
| | | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/nosleep.js": { |
| | | "version": "0.12.0", |
| | | "resolved": "https://registry.npmmirror.com/nosleep.js/-/nosleep.js-0.12.0.tgz", |
| | | "integrity": "sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA==" |
| | | }, |
| | | "node_modules/p-map": { |
| | | "version": "7.0.6", |
| | | "resolved": "https://registry.npmmirror.com/p-map/-/p-map-7.0.6.tgz", |
| | | "integrity": "sha512-I4Prw6ivkd6p8PiYR1tXASOAOBzIJwu0TB7fqaX0c/8c3QAehNYmX57EijyGGGBt3c/BIowGwV03RVBtXvHEVg==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=18" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/pako": { |
| | | "version": "2.2.0", |
| | | "resolved": "https://registry.npmmirror.com/pako/-/pako-2.2.0.tgz", |
| | | "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==", |
| | | "funding": [ |
| | | { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/puzrin" |
| | | }, |
| | | { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/nodeca" |
| | | } |
| | | ] |
| | | }, |
| | | "node_modules/path-browserify": { |
| | | "version": "1.0.1", |
| | | "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", |
| | | "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/perfect-debounce": { |
| | | "version": "1.0.0", |
| | | "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", |
| | | "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==" |
| | | }, |
| | | "node_modules/picocolors": { |
| | | "version": "1.1.1", |
| | | "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", |
| | | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" |
| | | }, |
| | | "node_modules/picomatch": { |
| | | "version": "4.0.5", |
| | | "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", |
| | | "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=12" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/jonschlinkert" |
| | | } |
| | | }, |
| | | "node_modules/pinia": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", |
| | | "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", |
| | | "dependencies": { |
| | | "@vue/devtools-api": "^7.7.7" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/posva" |
| | | }, |
| | | "peerDependencies": { |
| | | "typescript": ">=4.5.0", |
| | | "vue": "^3.5.11" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "typescript": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/postcss": { |
| | | "version": "8.5.26", |
| | | "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.26.tgz", |
| | | "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", |
| | | "funding": [ |
| | | { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/postcss/" |
| | | }, |
| | | { |
| | | "type": "tidelift", |
| | | "url": "https://tidelift.com/funding/github/npm/postcss" |
| | | }, |
| | | { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/ai" |
| | | } |
| | | ], |
| | | "dependencies": { |
| | | "nanoid": "^3.3.17", |
| | | "picocolors": "^1.1.1", |
| | | "source-map-js": "^1.2.1" |
| | | }, |
| | | "engines": { |
| | | "node": "^10 || ^12 || >=14" |
| | | } |
| | | }, |
| | | "node_modules/protobufjs": { |
| | | "version": "7.6.5", |
| | | "resolved": "https://registry.npmmirror.com/protobufjs/-/protobufjs-7.6.5.tgz", |
| | | "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", |
| | | "hasInstallScript": true, |
| | | "dependencies": { |
| | | "@protobufjs/aspromise": "^1.1.2", |
| | | "@protobufjs/base64": "^1.1.2", |
| | | "@protobufjs/codegen": "^2.0.5", |
| | | "@protobufjs/eventemitter": "^1.1.1", |
| | | "@protobufjs/fetch": "^1.1.1", |
| | | "@protobufjs/float": "^1.0.2", |
| | | "@protobufjs/path": "^1.1.2", |
| | | "@protobufjs/pool": "^1.1.0", |
| | | "@protobufjs/utf8": "^1.1.1", |
| | | "@types/node": ">=13.7.0", |
| | | "long": "^5.3.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">=12.0.0" |
| | | } |
| | | }, |
| | | "node_modules/queue-microtask": { |
| | | "version": "1.2.3", |
| | | "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", |
| | | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", |
| | | "dev": true, |
| | | "funding": [ |
| | | { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/feross" |
| | | }, |
| | | { |
| | | "type": "patreon", |
| | | "url": "https://www.patreon.com/feross" |
| | | }, |
| | | { |
| | | "type": "consulting", |
| | | "url": "https://feross.org/support" |
| | | } |
| | | ] |
| | | }, |
| | | "node_modules/quickselect": { |
| | | "version": "2.0.0", |
| | | "resolved": "https://registry.npmmirror.com/quickselect/-/quickselect-2.0.0.tgz", |
| | | "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" |
| | | }, |
| | | "node_modules/rbush": { |
| | | "version": "3.0.1", |
| | | "resolved": "https://registry.npmmirror.com/rbush/-/rbush-3.0.1.tgz", |
| | | "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", |
| | | "dependencies": { |
| | | "quickselect": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/readdirp": { |
| | | "version": "3.6.0", |
| | | "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", |
| | | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "picomatch": "^2.2.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8.10.0" |
| | | } |
| | | }, |
| | | "node_modules/readdirp/node_modules/picomatch": { |
| | | "version": "2.3.2", |
| | | "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", |
| | | "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=8.6" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/jonschlinkert" |
| | | } |
| | | }, |
| | | "node_modules/resize-observer-polyfill": { |
| | | "version": "1.5.1", |
| | | "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", |
| | | "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" |
| | | }, |
| | | "node_modules/reusify": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", |
| | | "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", |
| | | "dev": true, |
| | | "engines": { |
| | | "iojs": ">=1.0.0", |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/rfdc": { |
| | | "version": "1.4.1", |
| | | "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", |
| | | "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" |
| | | }, |
| | | "node_modules/rollup": { |
| | | "version": "4.62.4", |
| | | "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.4.tgz", |
| | | "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "@types/estree": "1.0.9" |
| | | }, |
| | | "bin": { |
| | | "rollup": "dist/bin/rollup" |
| | | }, |
| | | "engines": { |
| | | "node": ">=18.0.0", |
| | | "npm": ">=8.0.0" |
| | | }, |
| | | "optionalDependencies": { |
| | | "@napi-rs/lzma-linux-x64-gnu": "1.5.1", |
| | | "@rollup/rollup-android-arm-eabi": "4.62.4", |
| | | "@rollup/rollup-android-arm64": "4.62.4", |
| | | "@rollup/rollup-darwin-arm64": "4.62.4", |
| | | "@rollup/rollup-darwin-x64": "4.62.4", |
| | | "@rollup/rollup-freebsd-arm64": "4.62.4", |
| | | "@rollup/rollup-freebsd-x64": "4.62.4", |
| | | "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", |
| | | "@rollup/rollup-linux-arm-musleabihf": "4.62.4", |
| | | "@rollup/rollup-linux-arm64-gnu": "4.62.4", |
| | | "@rollup/rollup-linux-arm64-musl": "4.62.4", |
| | | "@rollup/rollup-linux-loong64-gnu": "4.62.4", |
| | | "@rollup/rollup-linux-loong64-musl": "4.62.4", |
| | | "@rollup/rollup-linux-ppc64-gnu": "4.62.4", |
| | | "@rollup/rollup-linux-ppc64-musl": "4.62.4", |
| | | "@rollup/rollup-linux-riscv64-gnu": "4.62.4", |
| | | "@rollup/rollup-linux-riscv64-musl": "4.62.4", |
| | | "@rollup/rollup-linux-s390x-gnu": "4.62.4", |
| | | "@rollup/rollup-linux-x64-gnu": "4.62.4", |
| | | "@rollup/rollup-linux-x64-musl": "4.62.4", |
| | | "@rollup/rollup-openbsd-x64": "4.62.4", |
| | | "@rollup/rollup-openharmony-arm64": "4.62.4", |
| | | "@rollup/rollup-win32-arm64-msvc": "4.62.4", |
| | | "@rollup/rollup-win32-ia32-msvc": "4.62.4", |
| | | "@rollup/rollup-win32-x64-gnu": "4.62.4", |
| | | "@rollup/rollup-win32-x64-msvc": "4.62.4", |
| | | "fsevents": "~2.3.2" |
| | | } |
| | | }, |
| | | "node_modules/run-parallel": { |
| | | "version": "1.2.0", |
| | | "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", |
| | | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", |
| | | "dev": true, |
| | | "funding": [ |
| | | { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/feross" |
| | | }, |
| | | { |
| | | "type": "patreon", |
| | | "url": "https://www.patreon.com/feross" |
| | | }, |
| | | { |
| | | "type": "consulting", |
| | | "url": "https://feross.org/support" |
| | | } |
| | | ], |
| | | "dependencies": { |
| | | "queue-microtask": "^1.2.2" |
| | | } |
| | | }, |
| | | "node_modules/scroll-into-view-if-needed": { |
| | | "version": "2.2.31", |
| | | "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", |
| | | "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", |
| | | "dependencies": { |
| | | "compute-scroll-into-view": "^1.0.20" |
| | | } |
| | | }, |
| | | "node_modules/shallow-equal": { |
| | | "version": "1.2.1", |
| | | "resolved": "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz", |
| | | "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" |
| | | }, |
| | | "node_modules/source-map-js": { |
| | | "version": "1.2.1", |
| | | "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", |
| | | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", |
| | | "engines": { |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/speakingurl": { |
| | | "version": "14.0.1", |
| | | "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", |
| | | "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", |
| | | "engines": { |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/stylis": { |
| | | "version": "4.4.0", |
| | | "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.4.0.tgz", |
| | | "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==" |
| | | }, |
| | | "node_modules/superjson": { |
| | | "version": "2.2.6", |
| | | "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", |
| | | "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", |
| | | "dependencies": { |
| | | "copy-anything": "^4" |
| | | }, |
| | | "engines": { |
| | | "node": ">=16" |
| | | } |
| | | }, |
| | | "node_modules/throttle-debounce": { |
| | | "version": "5.0.2", |
| | | "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", |
| | | "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", |
| | | "engines": { |
| | | "node": ">=12.22" |
| | | } |
| | | }, |
| | | "node_modules/tinyglobby": { |
| | | "version": "0.2.17", |
| | | "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", |
| | | "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "fdir": "^6.5.0", |
| | | "picomatch": "^4.0.4" |
| | | }, |
| | | "engines": { |
| | | "node": ">=12.0.0" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/SuperchupuDev" |
| | | } |
| | | }, |
| | | "node_modules/to-regex-range": { |
| | | "version": "5.0.1", |
| | | "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", |
| | | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "is-number": "^7.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8.0" |
| | | } |
| | | }, |
| | | "node_modules/topojson-client": { |
| | | "version": "3.1.0", |
| | | "resolved": "https://registry.npmmirror.com/topojson-client/-/topojson-client-3.1.0.tgz", |
| | | "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", |
| | | "dependencies": { |
| | | "commander": "2" |
| | | }, |
| | | "bin": { |
| | | "topo2geo": "bin/topo2geo", |
| | | "topomerge": "bin/topomerge", |
| | | "topoquantize": "bin/topoquantize" |
| | | } |
| | | }, |
| | | "node_modules/tslib": { |
| | | "version": "2.8.1", |
| | | "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", |
| | | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" |
| | | }, |
| | | "node_modules/typescript": { |
| | | "version": "5.9.3", |
| | | "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", |
| | | "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", |
| | | "devOptional": true, |
| | | "bin": { |
| | | "tsc": "bin/tsc", |
| | | "tsserver": "bin/tsserver" |
| | | }, |
| | | "engines": { |
| | | "node": ">=14.17" |
| | | } |
| | | }, |
| | | "node_modules/undici-types": { |
| | | "version": "8.3.0", |
| | | "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz", |
| | | "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==" |
| | | }, |
| | | "node_modules/universalify": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", |
| | | "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">= 10.0.0" |
| | | } |
| | | }, |
| | | "node_modules/urijs": { |
| | | "version": "1.19.11", |
| | | "resolved": "https://registry.npmmirror.com/urijs/-/urijs-1.19.11.tgz", |
| | | "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" |
| | | }, |
| | | "node_modules/vite": { |
| | | "version": "6.4.3", |
| | | "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.3.tgz", |
| | | "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "esbuild": "^0.25.0", |
| | | "fdir": "^6.4.4", |
| | | "picomatch": "^4.0.2", |
| | | "postcss": "^8.5.3", |
| | | "rollup": "^4.34.9", |
| | | "tinyglobby": "^0.2.13" |
| | | }, |
| | | "bin": { |
| | | "vite": "bin/vite.js" |
| | | }, |
| | | "engines": { |
| | | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/vitejs/vite?sponsor=1" |
| | | }, |
| | | "optionalDependencies": { |
| | | "fsevents": "~2.3.3" |
| | | }, |
| | | "peerDependencies": { |
| | | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", |
| | | "jiti": ">=1.21.0", |
| | | "less": "*", |
| | | "lightningcss": "^1.21.0", |
| | | "sass": "*", |
| | | "sass-embedded": "*", |
| | | "stylus": "*", |
| | | "sugarss": "*", |
| | | "terser": "^5.16.0", |
| | | "tsx": "^4.8.1", |
| | | "yaml": "^2.4.2" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "@types/node": { |
| | | "optional": true |
| | | }, |
| | | "jiti": { |
| | | "optional": true |
| | | }, |
| | | "less": { |
| | | "optional": true |
| | | }, |
| | | "lightningcss": { |
| | | "optional": true |
| | | }, |
| | | "sass": { |
| | | "optional": true |
| | | }, |
| | | "sass-embedded": { |
| | | "optional": true |
| | | }, |
| | | "stylus": { |
| | | "optional": true |
| | | }, |
| | | "sugarss": { |
| | | "optional": true |
| | | }, |
| | | "terser": { |
| | | "optional": true |
| | | }, |
| | | "tsx": { |
| | | "optional": true |
| | | }, |
| | | "yaml": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/vite-plugin-static-copy": { |
| | | "version": "2.3.2", |
| | | "resolved": "https://registry.npmmirror.com/vite-plugin-static-copy/-/vite-plugin-static-copy-2.3.2.tgz", |
| | | "integrity": "sha512-iwrrf+JupY4b9stBttRWzGHzZbeMjAHBhkrn67MNACXJVjEMRpCI10Q3AkxdBkl45IHaTfw/CNVevzQhP7yTwg==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "chokidar": "^3.5.3", |
| | | "fast-glob": "^3.2.11", |
| | | "fs-extra": "^11.1.0", |
| | | "p-map": "^7.0.3", |
| | | "picocolors": "^1.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": "^18.0.0 || >=20.0.0" |
| | | }, |
| | | "peerDependencies": { |
| | | "vite": "^5.0.0 || ^6.0.0" |
| | | } |
| | | }, |
| | | "node_modules/vscode-uri": { |
| | | "version": "3.1.0", |
| | | "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", |
| | | "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/vue": { |
| | | "version": "3.5.41", |
| | | "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.41.tgz", |
| | | "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", |
| | | "dependencies": { |
| | | "@vue/compiler-dom": "3.5.41", |
| | | "@vue/compiler-sfc": "3.5.41", |
| | | "@vue/runtime-dom": "3.5.41", |
| | | "@vue/server-renderer": "3.5.41", |
| | | "@vue/shared": "3.5.41" |
| | | }, |
| | | "peerDependencies": { |
| | | "typescript": "*" |
| | | }, |
| | | "peerDependenciesMeta": { |
| | | "typescript": { |
| | | "optional": true |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/vue-router": { |
| | | "version": "4.6.4", |
| | | "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", |
| | | "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", |
| | | "dependencies": { |
| | | "@vue/devtools-api": "^6.6.4" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/posva" |
| | | }, |
| | | "peerDependencies": { |
| | | "vue": "^3.5.0" |
| | | } |
| | | }, |
| | | "node_modules/vue-router/node_modules/@vue/devtools-api": { |
| | | "version": "6.6.4", |
| | | "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", |
| | | "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" |
| | | }, |
| | | "node_modules/vue-tsc": { |
| | | "version": "2.2.12", |
| | | "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.2.12.tgz", |
| | | "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "@volar/typescript": "2.4.15", |
| | | "@vue/language-core": "2.2.12" |
| | | }, |
| | | "bin": { |
| | | "vue-tsc": "bin/vue-tsc.js" |
| | | }, |
| | | "peerDependencies": { |
| | | "typescript": ">=5.0.0" |
| | | } |
| | | }, |
| | | "node_modules/vue-types": { |
| | | "version": "3.0.2", |
| | | "resolved": "https://registry.npmmirror.com/vue-types/-/vue-types-3.0.2.tgz", |
| | | "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==", |
| | | "dependencies": { |
| | | "is-plain-object": "3.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=10.15.0" |
| | | }, |
| | | "peerDependencies": { |
| | | "vue": "^3.0.0" |
| | | } |
| | | }, |
| | | "node_modules/warning": { |
| | | "version": "4.0.3", |
| | | "resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz", |
| | | "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", |
| | | "dependencies": { |
| | | "loose-envify": "^1.0.0" |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | { |
| | | "name": "geoai-workbench-console", |
| | | "private": true, |
| | | "version": "0.1.0", |
| | | "type": "module", |
| | | "scripts": { |
| | | "dev": "vite", |
| | | "build": "vue-tsc -b && vite build", |
| | | "preview": "vite preview" |
| | | }, |
| | | "dependencies": { |
| | | "@ant-design/icons-vue": "^7.0.1", |
| | | "ant-design-vue": "^4.2.6", |
| | | "cesium": "1.126.0", |
| | | "pinia": "^3.0.1", |
| | | "vue": "^3.5.13", |
| | | "vue-router": "^4.5.0" |
| | | }, |
| | | "devDependencies": { |
| | | "@vitejs/plugin-vue": "^5.2.1", |
| | | "typescript": "^5.7.2", |
| | | "vite": "^6.0.5", |
| | | "vite-plugin-static-copy": "^2.3.0", |
| | | "vue-tsc": "^2.2.0" |
| | | } |
| | | } |
| New file |
| | |
| | | <script setup lang="ts"> |
| | | import { computed } from "vue"; |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | import { |
| | | AppstoreOutlined, |
| | | CheckCircleFilled, |
| | | CompassOutlined, |
| | | ExperimentOutlined |
| | | } from "@ant-design/icons-vue"; |
| | | |
| | | import { capabilities, verifiedCapabilities } from "@/data/capabilities"; |
| | | |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const activeKey = computed(() => route.name === "overview" ? "overview" : String(route.params.id)); |
| | | const plannedCapabilities = computed(() => capabilities.filter((item) => item.status !== "verified")); |
| | | |
| | | function navigate(key: string) { |
| | | if (key === "overview") router.push({ name: "overview" }); |
| | | else router.push({ name: "capability", params: { id: key } }); |
| | | } |
| | | |
| | | function handleMenuClick(info: { key: string | number }) { |
| | | navigate(String(info.key)); |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <a-layout class="application-frame"> |
| | | <a-layout-sider class="application-sider" :width="252" breakpoint="lg" collapsed-width="0"> |
| | | <div class="brand" @click="navigate('overview')"> |
| | | <span class="brand-mark">GW</span> |
| | | <span><strong>GeoAI Workbench</strong><small>本地实验控制台</small></span> |
| | | </div> |
| | | |
| | | <a-menu class="navigation-menu" theme="dark" mode="inline" :selected-keys="[activeKey]" @click="handleMenuClick"> |
| | | <a-menu-item key="overview"><AppstoreOutlined /><span>实验总览</span></a-menu-item> |
| | | <a-menu-divider /> |
| | | <a-menu-item-group key="verified" title="已验证能力"> |
| | | <a-menu-item v-for="capability in verifiedCapabilities" :key="capability.id"> |
| | | <ExperimentOutlined /><span>{{ capability.title }}</span> |
| | | </a-menu-item> |
| | | </a-menu-item-group> |
| | | <a-menu-divider /> |
| | | <a-menu-item-group key="planned" title="能力目录"> |
| | | <a-menu-item v-for="capability in plannedCapabilities" :key="capability.id"> |
| | | <CompassOutlined /><span>{{ capability.title }}</span> |
| | | </a-menu-item> |
| | | </a-menu-item-group> |
| | | </a-menu> |
| | | |
| | | <div class="sider-status"> |
| | | <CheckCircleFilled /> |
| | | <div><strong>仅本机读取</strong><small>无产品连接或外部服务</small></div> |
| | | </div> |
| | | </a-layout-sider> |
| | | |
| | | <a-layout class="content-layout"><a-layout-content><router-view /></a-layout-content></a-layout> |
| | | </a-layout> |
| | | </template> |
| New file |
| | |
| | | export interface Detection { |
| | | class_id: number; |
| | | class_name: string; |
| | | confidence: number; |
| | | bbox_xyxy: number[]; |
| | | } |
| | | |
| | | export interface DetectionImage { |
| | | file: string; |
| | | annotated_file: string; |
| | | width: number; |
| | | height: number; |
| | | detections: Detection[]; |
| | | } |
| | | |
| | | export interface DetectionRun { |
| | | detection_count: number; |
| | | processed_images: number; |
| | | elapsed_seconds: number; |
| | | device: string; |
| | | model: string; |
| | | confidence: number; |
| | | input_dir: string; |
| | | notes: string[]; |
| | | } |
| | | |
| | | export interface TrajectoryEvent { |
| | | event_id: string; |
| | | event_type: string; |
| | | track_ids: string[]; |
| | | start_time: string; |
| | | end_time: string; |
| | | duration_seconds: number; |
| | | } |
| | | |
| | | export interface TrajectoryRun { |
| | | case_count: number; |
| | | track_count: number; |
| | | event_count: number; |
| | | dropped_duplicate_observations: number; |
| | | elapsed_seconds: number; |
| | | device: string; |
| | | input: string; |
| | | thresholds_by_case: Record<string, Record<string, number>>; |
| | | } |
| | | |
| | | export interface TrajectorySummary { |
| | | track_id: string; |
| | | entity_type: string; |
| | | point_count: string; |
| | | distance_m: string; |
| | | average_speed_mps: string; |
| | | behavior_labels: string; |
| | | } |
| | | |
| | | export const artifactUrl = (path: string) => `/${path.replace(/\\/g, "/").split("/").map(encodeURIComponent).join("/")}`; |
| | | |
| | | async function getJson<T>(path: string): Promise<T> { |
| | | const response = await fetch(artifactUrl(path), { cache: "no-store" }); |
| | | if (!response.ok) throw new Error(`无法读取 ${path} (${response.status})`); |
| | | return response.json() as Promise<T>; |
| | | } |
| | | |
| | | async function getText(path: string): Promise<string> { |
| | | const response = await fetch(artifactUrl(path), { cache: "no-store" }); |
| | | if (!response.ok) throw new Error(`无法读取 ${path} (${response.status})`); |
| | | return response.text(); |
| | | } |
| | | |
| | | function parseCsv(text: string): TrajectorySummary[] { |
| | | const lines = text.trim().split(/\r?\n/); |
| | | const headers = lines.shift()?.replace(/^\uFEFF/, "").split(",") ?? []; |
| | | return lines.filter(Boolean).map((line) => { |
| | | const values = line.split(","); |
| | | return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""])) as unknown as TrajectorySummary; |
| | | }); |
| | | } |
| | | |
| | | export async function loadDetectionArtifacts() { |
| | | const [run, payload] = await Promise.all([ |
| | | getJson<DetectionRun>("shared/outputs/01-object-detection/run_metadata.json"), |
| | | getJson<{ images: DetectionImage[] }>("shared/outputs/01-object-detection/detections.json") |
| | | ]); |
| | | return { run, images: payload.images }; |
| | | } |
| | | |
| | | export async function loadTrajectoryArtifacts() { |
| | | const [run, difficultEvents, difficultSummary, normalSummary] = await Promise.all([ |
| | | getJson<TrajectoryRun>("shared/outputs/15-trajectory-analysis/run_metadata.json"), |
| | | getJson<{ events: TrajectoryEvent[] }>("shared/outputs/15-trajectory-analysis/difficult/events.json"), |
| | | getText("shared/outputs/15-trajectory-analysis/difficult/trajectory_summary.csv"), |
| | | getText("shared/outputs/15-trajectory-analysis/normal/trajectory_summary.csv") |
| | | ]); |
| | | return { |
| | | run, |
| | | cases: { |
| | | difficult: { events: difficultEvents.events, summary: parseCsv(difficultSummary) }, |
| | | normal: { events: [], summary: parseCsv(normalSummary) } |
| | | } |
| | | }; |
| | | } |
| New file |
| | |
| | | <script setup lang="ts"> |
| | | import { LoadingOutlined } from "@ant-design/icons-vue"; |
| | | |
| | | defineProps<{ loading: boolean; error: string | null }>(); |
| | | </script> |
| | | |
| | | <template> |
| | | <a-alert v-if="error" type="error" show-icon :message="error" description="请确认只读结果服务已在 127.0.0.1:6173 运行,且该能力已有输出文件。" /> |
| | | <div v-else-if="loading" class="loading-block"><LoadingOutlined spin /><span>正在读取本地实验工件...</span></div> |
| | | </template> |
| New file |
| | |
| | | <script setup lang="ts"> |
| | | import { computed, onMounted, ref } from "vue"; |
| | | import { FileImageOutlined, InfoCircleOutlined } from "@ant-design/icons-vue"; |
| | | |
| | | import { artifactUrl } from "@/api/artifacts"; |
| | | import ArtifactState from "@/components/ArtifactState.vue"; |
| | | import { useArtifactStore } from "@/stores/artifacts"; |
| | | |
| | | const store = useArtifactStore(); |
| | | const mode = ref<"annotated" | "original">("annotated"); |
| | | const selectedName = ref(""); |
| | | const selectedImage = computed(() => store.detectionImages.find((item) => item.file === selectedName.value) ?? store.detectionImages[0]); |
| | | const previewSource = computed(() => { |
| | | if (!selectedImage.value) return ""; |
| | | const path = mode.value === "annotated" |
| | | ? `shared/outputs/01-object-detection/${selectedImage.value.annotated_file}` |
| | | : `shared/data/raw/01-object-detection/${selectedImage.value.file}`; |
| | | return artifactUrl(path); |
| | | }); |
| | | |
| | | onMounted(async () => { |
| | | await store.loadDetection(); |
| | | const mostDetections = [...store.detectionImages].sort((left, right) => right.detections.length - left.detections.length)[0]; |
| | | selectedName.value = mostDetections?.file ?? ""; |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <ArtifactState :loading="store.loading" :error="store.error" /> |
| | | <template v-if="store.detectionRun && selectedImage"> |
| | | <a-row :gutter="[18, 18]"> |
| | | <a-col :xs="24" :xl="17"><section class="surface-section"><div class="section-toolbar"><a-select v-model:value="selectedName" :options="store.detectionImages.map((item) => ({ value: item.file, label: `${item.file} · ${item.detections.length} 个候选` }))" /><a-segmented v-model:value="mode" :options="[{ label: '标注结果', value: 'annotated' }, { label: '原始影像', value: 'original' }]" /></div><a-image class="detection-image" :src="previewSource" :alt="`${selectedImage.file} 预览`" /><p class="image-caption">{{ selectedImage.width }} x {{ selectedImage.height }} 像素 · {{ selectedImage.detections.length }} 个候选</p></section></a-col> |
| | | <a-col :xs="24" :xl="7"><section class="surface-section"><h2>本次运行</h2><a-descriptions size="small" :column="1"><a-descriptions-item label="处理影像">{{ store.detectionRun.processed_images }} 张</a-descriptions-item><a-descriptions-item label="检测候选">{{ store.detectionRun.detection_count }} 个</a-descriptions-item><a-descriptions-item label="耗时">{{ store.detectionRun.elapsed_seconds }} 秒</a-descriptions-item><a-descriptions-item label="设备">{{ store.detectionRun.device }}</a-descriptions-item><a-descriptions-item label="模型">{{ store.detectionRun.model }}</a-descriptions-item><a-descriptions-item label="阈值">{{ store.detectionRun.confidence }}</a-descriptions-item></a-descriptions><a-divider /><a-space direction="vertical"><a-button type="link" :href="artifactUrl('shared/outputs/01-object-detection/detections.json')" target="_blank"><FileImageOutlined />检测 JSON</a-button><a-button type="link" :href="artifactUrl('shared/outputs/01-object-detection/run_metadata.json')" target="_blank"><InfoCircleOutlined />运行元数据</a-button></a-space></section></a-col> |
| | | </a-row> |
| | | <section class="surface-section"><div class="section-heading"><div><h2>当前影像检测明细</h2><p>边界框是 JPEG 像素坐标,不含可用地理坐标。</p></div></div><a-table :data-source="selectedImage.detections" :pagination="false" row-key="bbox_xyxy" size="small" :scroll="{ x: 680 }"><a-table-column title="类别" data-index="class_name" key="class_name" /><a-table-column title="置信度" key="confidence" align="right"><template #default="{ record }">{{ (record.confidence * 100).toFixed(1) }}%</template></a-table-column><a-table-column title="像素框 x1, y1, x2, y2" key="bbox"><template #default="{ record }">{{ record.bbox_xyxy.map((value: number) => value.toFixed(1)).join(', ') }}</template></a-table-column><template #emptyText>该影像没有达到当前阈值的候选目标。</template></a-table></section> |
| | | </template> |
| | | </template> |
| New file |
| | |
| | | <script setup lang="ts"> |
| | | defineProps<{ eyebrow: string; title: string; description: string }>(); |
| | | </script> |
| | | |
| | | <template> |
| | | <header class="page-header"> |
| | | <div><p class="eyebrow">{{ eyebrow }}</p><h1>{{ title }}</h1><p class="page-description">{{ description }}</p></div> |
| | | <a-tag class="local-tag" color="green">本机只读</a-tag> |
| | | </header> |
| | | </template> |
| New file |
| | |
| | | <script setup lang="ts"> |
| | | import { computed, defineAsyncComponent, onMounted, ref } from "vue"; |
| | | import { DownloadOutlined } from "@ant-design/icons-vue"; |
| | | |
| | | import { artifactUrl } from "@/api/artifacts"; |
| | | import ArtifactState from "@/components/ArtifactState.vue"; |
| | | import { useArtifactStore } from "@/stores/artifacts"; |
| | | |
| | | const TrajectoryMap = defineAsyncComponent(() => import("@/components/TrajectoryMap.vue")); |
| | | |
| | | const store = useArtifactStore(); |
| | | const caseId = ref<"difficult" | "normal">("difficult"); |
| | | const currentCase = computed(() => store.trajectoryCases[caseId.value] ?? { events: [], summary: [] }); |
| | | const eventNames: Record<string, string> = { stop: "停留", route_deviation: "偏航", restricted_zone: "进入禁入区", gathering: "聚集" }; |
| | | const caseNote = computed(() => caseId.value === "difficult" ? "含停留、偏航、禁入区、聚集与重复观测。" : "连续移动且贴合参考路线,预期无事件。" ); |
| | | const difficultRules = computed(() => store.trajectoryRun?.thresholds_by_case.difficult ?? {}); |
| | | |
| | | onMounted(() => store.loadTrajectory()); |
| | | </script> |
| | | |
| | | <template> |
| | | <ArtifactState :loading="store.loading" :error="store.error" /> |
| | | <template v-if="store.trajectoryRun"> |
| | | <a-row :gutter="[18, 18]"><a-col :xs="24" :xl="17"><section class="surface-section"><div class="section-toolbar"><a-segmented v-model:value="caseId" :options="[{ label: '困难样本', value: 'difficult' }, { label: '正常样本', value: 'normal' }]" /><span class="toolbar-note">{{ caseNote }}</span></div><TrajectoryMap :case-id="caseId" /></section></a-col><a-col :xs="24" :xl="7"><section class="surface-section"><h2>本次运行</h2><a-descriptions size="small" :column="1"><a-descriptions-item label="案例">{{ store.trajectoryRun.case_count }} 个</a-descriptions-item><a-descriptions-item label="轨迹">{{ store.trajectoryRun.track_count }} 条</a-descriptions-item><a-descriptions-item label="事件">{{ store.trajectoryRun.event_count }} 个</a-descriptions-item><a-descriptions-item label="重复清洗">{{ store.trajectoryRun.dropped_duplicate_observations }} 条</a-descriptions-item><a-descriptions-item label="耗时">{{ store.trajectoryRun.elapsed_seconds }} 秒</a-descriptions-item><a-descriptions-item label="设备">{{ store.trajectoryRun.device }}</a-descriptions-item></a-descriptions><a-divider /><h3>默认规则</h3><a-descriptions size="small" :column="1"><a-descriptions-item label="停留速度">{{ difficultRules.stop_speed_mps }} m/s</a-descriptions-item><a-descriptions-item label="偏航距离">{{ difficultRules.route_deviation_m }} m</a-descriptions-item><a-descriptions-item label="聚集距离">{{ difficultRules.gathering_radius_m }} m</a-descriptions-item></a-descriptions></section></a-col></a-row> |
| | | <a-row :gutter="[18, 18]" class="result-row"><a-col :xs="24" :xl="10"><section class="surface-section"><div class="section-heading"><div><h2>事件记录</h2><p>{{ currentCase.events.length }} 个规则事件</p></div></div><a-empty v-if="!currentCase.events.length" description="正常样本未触发规则事件" /><a-timeline v-else><a-timeline-item v-for="event in currentCase.events" :key="event.event_id" :color="event.event_type === 'gathering' ? 'green' : event.event_type === 'restricted_zone' ? 'gold' : event.event_type === 'route_deviation' ? 'purple' : 'red'"><strong>{{ eventNames[event.event_type] || event.event_type }}</strong><p>{{ event.track_ids.join('、') }}</p><small>{{ new Date(event.start_time).toLocaleString('zh-CN', { hour12: false }) }} · {{ event.duration_seconds }} 秒</small></a-timeline-item></a-timeline></section></a-col><a-col :xs="24" :xl="14"><section class="surface-section"><div class="section-heading"><div><h2>轨迹汇总</h2><p>聚类 ID 仅用于探索相似轨迹,不代表确认异常。</p></div></div><a-table :data-source="currentCase.summary" :pagination="false" row-key="track_id" size="small" :scroll="{ x: 760 }"><a-table-column title="轨迹" data-index="track_id" key="track_id" /><a-table-column title="对象" data-index="entity_type" key="entity_type" /><a-table-column title="点数" data-index="point_count" key="point_count" align="right" /><a-table-column title="距离" key="distance" align="right"><template #default="{ record }">{{ Number(record.distance_m).toFixed(1) }} m</template></a-table-column><a-table-column title="均速" key="speed" align="right"><template #default="{ record }">{{ Number(record.average_speed_mps).toFixed(2) }} m/s</template></a-table-column><a-table-column title="行为标签" data-index="behavior_labels" key="behavior_labels" /></a-table></section></a-col></a-row> |
| | | <section class="surface-section result-files"><a-space wrap><a-button v-for="file in ['events.json', 'events.geojson', 'trajectories.geojson', 'run_metadata.json']" :key="file" :href="artifactUrl(`shared/outputs/15-trajectory-analysis/${caseId}/${file}`)" target="_blank"><DownloadOutlined />{{ file }}</a-button></a-space></section> |
| | | </template> |
| | | </template> |
| New file |
| | |
| | | <script setup lang="ts"> |
| | | import { onBeforeUnmount, onMounted, ref, watch } from "vue"; |
| | | import { |
| | | Color, |
| | | ColorMaterialProperty, |
| | | ConstantProperty, |
| | | GeoJsonDataSource, |
| | | GridImageryProvider, |
| | | HeightReference, |
| | | PointGraphics, |
| | | Viewer |
| | | } from "cesium"; |
| | | |
| | | import { artifactUrl } from "@/api/artifacts"; |
| | | |
| | | const props = defineProps<{ caseId: "difficult" | "normal" }>(); |
| | | const host = ref<HTMLElement>(); |
| | | const mapError = ref<string | null>(null); |
| | | let viewer: Viewer | null = null; |
| | | let trajectorySource: GeoJsonDataSource | null = null; |
| | | let eventSource: GeoJsonDataSource | null = null; |
| | | |
| | | function eventColor(type: string) { |
| | | if (type === "route_deviation") return Color.fromCssColorString("#9a5da8"); |
| | | if (type === "restricted_zone") return Color.fromCssColorString("#bc861a"); |
| | | if (type === "gathering") return Color.fromCssColorString("#176b50"); |
| | | return Color.fromCssColorString("#c6533f"); |
| | | } |
| | | |
| | | async function drawCase() { |
| | | if (!viewer) return; |
| | | try { |
| | | mapError.value = null; |
| | | if (trajectorySource) viewer.dataSources.remove(trajectorySource, true); |
| | | if (eventSource) viewer.dataSources.remove(eventSource, true); |
| | | const root = `shared/outputs/15-trajectory-analysis/${props.caseId}`; |
| | | trajectorySource = await GeoJsonDataSource.load(artifactUrl(`${root}/trajectories.geojson`), { clampToGround: false }); |
| | | trajectorySource.entities.values.forEach((entity) => { |
| | | if (entity.polyline) { |
| | | entity.polyline.width = new ConstantProperty(4); |
| | | entity.polyline.material = new ColorMaterialProperty(Color.fromCssColorString("#176b50")); |
| | | } |
| | | }); |
| | | eventSource = await GeoJsonDataSource.load(artifactUrl(`${root}/events.geojson`), { clampToGround: false }); |
| | | eventSource.entities.values.forEach((entity) => { |
| | | const type = String(entity.properties?.event_type?.getValue() ?? "stop"); |
| | | entity.point = new PointGraphics({ |
| | | pixelSize: new ConstantProperty(12), |
| | | color: new ConstantProperty(eventColor(type)), |
| | | outlineColor: new ConstantProperty(Color.WHITE), |
| | | outlineWidth: new ConstantProperty(2), |
| | | heightReference: new ConstantProperty(HeightReference.NONE) |
| | | }); |
| | | }); |
| | | await viewer.dataSources.add(trajectorySource); |
| | | await viewer.dataSources.add(eventSource); |
| | | await viewer.flyTo(trajectorySource, { duration: 0 }); |
| | | } catch (error) { |
| | | mapError.value = error instanceof Error ? error.message : "Cesium 地图加载失败"; |
| | | } |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | if (!host.value) return; |
| | | viewer = new Viewer(host.value, { |
| | | animation: false, |
| | | baseLayerPicker: false, |
| | | fullscreenButton: false, |
| | | geocoder: false, |
| | | homeButton: false, |
| | | infoBox: false, |
| | | navigationHelpButton: false, |
| | | sceneModePicker: false, |
| | | selectionIndicator: false, |
| | | timeline: false |
| | | }); |
| | | viewer.imageryLayers.removeAll(); |
| | | viewer.imageryLayers.addImageryProvider(new GridImageryProvider({ cells: 8, glowWidth: 0 })); |
| | | await drawCase(); |
| | | }); |
| | | |
| | | watch(() => props.caseId, drawCase); |
| | | onBeforeUnmount(() => viewer?.destroy()); |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="trajectory-map"><div ref="host" class="cesium-host"></div><a-alert v-if="mapError" class="map-error" type="warning" show-icon :message="mapError" /></div> |
| | | </template> |
| New file |
| | |
| | | export type CapabilityStatus = "verified" | "existing" | "planned"; |
| | | |
| | | export interface CapabilityRecord { |
| | | id: string; |
| | | title: string; |
| | | level: "A" | "B" | "C"; |
| | | status: CapabilityStatus; |
| | | note: string; |
| | | } |
| | | |
| | | export const capabilities: CapabilityRecord[] = [ |
| | | { id: "00-change-detection", title: "变化检测", level: "A", status: "existing", note: "现有产品能力已确认;本地 Demo 尚未实现。" }, |
| | | { id: "01-object-detection", title: "地物目标检测", level: "A", status: "verified", note: "人员与车辆实验可运行,含标注影像和结构化检测结果。" }, |
| | | { id: "02-semantic-mapping", title: "语义制图", level: "A", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "03-attribute-classification", title: "属性分类", level: "A", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "04-spatial-measurement", title: "空间测量", level: "B", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "05-3d-pointcloud", title: "三维点云", level: "B", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "06-spatial-reasoning", title: "空间推理", level: "C", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "07-risk-rule-engine", title: "风险规则引擎", level: "C", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "08-spatiotemporal-forecasting", title: "时空预测", level: "B", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "09-anomaly-detection", title: "异常检测", level: "B", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "10-smart-route-planning", title: "智能航线规划", level: "C", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "11-fleet-scheduling", title: "集群调度", level: "C", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "12-quality-control-refly", title: "质量检查与补飞", level: "B", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "13-asset-health-diagnosis", title: "资产健康诊断", level: "C", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "14-disaster-response", title: "灾害响应", level: "B", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "15-trajectory-analysis", title: "轨迹分析与行为识别", level: "C", status: "verified", note: "规则事件与轨迹聚类 CPU Demo 已验证。" }, |
| | | { id: "16-geollm-assistant", title: "GeoLLM 助手", level: "C", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "17-spatiotemporal-knowledge-graph", title: "时空知识图谱", level: "C", status: "planned", note: "等待首个本地 Demo。" }, |
| | | { id: "18-reporting-model-governance", title: "报告与模型治理", level: "C", status: "planned", note: "等待首个本地 Demo。" } |
| | | ]; |
| | | |
| | | export const verifiedCapabilities = capabilities.filter((item) => item.status === "verified"); |
| | | export const capabilityById = (id: string) => capabilities.find((item) => item.id === id); |
| New file |
| | |
| | | import { createApp } from "vue"; |
| | | import Antd from "ant-design-vue"; |
| | | import "ant-design-vue/dist/reset.css"; |
| | | import "cesium/Build/Cesium/Widgets/widgets.css"; |
| | | import "./styles.css"; |
| | | |
| | | import App from "./App.vue"; |
| | | import { router } from "./router"; |
| | | import { pinia } from "./stores"; |
| | | |
| | | createApp(App).use(pinia).use(router).use(Antd).mount("#app"); |
| New file |
| | |
| | | import { createRouter, createWebHashHistory } from "vue-router"; |
| | | |
| | | import CapabilityView from "@/views/CapabilityView.vue"; |
| | | import OverviewView from "@/views/OverviewView.vue"; |
| | | |
| | | export const router = createRouter({ |
| | | history: createWebHashHistory(), |
| | | routes: [ |
| | | { path: "/", name: "overview", component: OverviewView }, |
| | | { path: "/capability/:id", name: "capability", component: CapabilityView }, |
| | | { path: "/:pathMatch(.*)*", redirect: "/" } |
| | | ] |
| | | }); |
| New file |
| | |
| | | import { defineStore } from "pinia"; |
| | | |
| | | import { |
| | | loadDetectionArtifacts, |
| | | loadTrajectoryArtifacts, |
| | | type DetectionImage, |
| | | type DetectionRun, |
| | | type TrajectoryRun, |
| | | type TrajectorySummary, |
| | | type TrajectoryEvent |
| | | } from "@/api/artifacts"; |
| | | |
| | | interface ArtifactState { |
| | | detectionRun: DetectionRun | null; |
| | | detectionImages: DetectionImage[]; |
| | | trajectoryRun: TrajectoryRun | null; |
| | | trajectoryCases: Record<string, { events: TrajectoryEvent[]; summary: TrajectorySummary[] }>; |
| | | loading: boolean; |
| | | error: string | null; |
| | | } |
| | | |
| | | export const useArtifactStore = defineStore("artifacts", { |
| | | state: (): ArtifactState => ({ |
| | | detectionRun: null, |
| | | detectionImages: [], |
| | | trajectoryRun: null, |
| | | trajectoryCases: {}, |
| | | loading: false, |
| | | error: null |
| | | }), |
| | | actions: { |
| | | async loadDetection() { |
| | | if (this.detectionRun) return; |
| | | this.loading = true; |
| | | this.error = null; |
| | | try { |
| | | const result = await loadDetectionArtifacts(); |
| | | this.detectionRun = result.run; |
| | | this.detectionImages = result.images; |
| | | } catch (error) { |
| | | this.error = error instanceof Error ? error.message : "目标检测结果读取失败"; |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | }, |
| | | async loadTrajectory() { |
| | | if (this.trajectoryRun) return; |
| | | this.loading = true; |
| | | this.error = null; |
| | | try { |
| | | const result = await loadTrajectoryArtifacts(); |
| | | this.trajectoryRun = result.run; |
| | | this.trajectoryCases = result.cases; |
| | | } catch (error) { |
| | | this.error = error instanceof Error ? error.message : "轨迹分析结果读取失败"; |
| | | } finally { |
| | | this.loading = false; |
| | | } |
| | | } |
| | | } |
| | | }); |
| New file |
| | |
| | | import { createPinia } from "pinia"; |
| | | |
| | | export const pinia = createPinia(); |
| New file |
| | |
| | | :root { color: #1b2620; background: #f3f5f1; font-family: "Microsoft YaHei UI", "Segoe UI", Arial, sans-serif; font-synthesis: none; } |
| | | * { box-sizing: border-box; } |
| | | body { margin: 0; min-width: 320px; background: #f3f5f1; } |
| | | .application-frame { min-height: 100vh; } |
| | | .application-sider { position: sticky !important; top: 0; height: 100vh; background: #1e2923 !important; border-right: 1px solid #152019; } |
| | | .brand { display: flex; align-items: center; gap: 10px; height: 82px; padding: 18px 20px; color: #f4f9f5; cursor: pointer; } |
| | | .brand-mark { display: grid; width: 34px; height: 34px; place-items: center; background: #dcf0e4; color: #195d43; font-size: 12px; font-weight: 800; } |
| | | .brand strong, .brand small { display: block; }.brand strong { font-size: 14px; letter-spacing: 0; }.brand small { color: #aac0b0; font-size: 11px; } |
| | | .navigation-menu { height: calc(100vh - 150px); overflow-y: auto; background: #1e2923 !important; border-inline-end: 0 !important; }.navigation-menu .ant-menu-item-group-title { color: #9eb2a4 !important; font-size: 11px; font-weight: 700; letter-spacing: 0; }.navigation-menu .ant-menu-item { margin-inline: 0 !important; width: 100% !important; border-radius: 0 !important; }.navigation-menu .ant-menu-item-selected { background: #31443a !important; }.navigation-menu .ant-menu-item::after { border-inline-end-color: #79c99e !important; } |
| | | .sider-status { position: absolute; right: 18px; bottom: 18px; left: 18px; display: flex; gap: 9px; padding-top: 14px; color: #dce8e0; border-top: 1px solid #42534a; }.sider-status .anticon { margin-top: 4px; color: #79c99e; }.sider-status strong, .sider-status small { display: block; }.sider-status strong { font-size: 12px; }.sider-status small { color: #a9bcb0; font-size: 11px; } |
| | | .content-layout { background: #f3f5f1 !important; }.view-container { width: min(1460px, 100%); margin: 0 auto; padding: 34px 42px 58px; }.page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; margin-bottom: 28px; }.eyebrow { margin: 0 0 6px; color: #176b50; font-size: 12px; font-weight: 700; letter-spacing: 0; }.page-header h1 { margin: 0 0 8px; color: #19231e; font-size: 29px; line-height: 1.2; letter-spacing: 0; }.page-description { max-width: 800px; margin: 0; color: #66716b; }.local-tag { margin: 2px 0 0 !important; border-radius: 3px !important; } |
| | | .loading-block { display: flex; gap: 9px; align-items: center; justify-content: center; min-height: 150px; color: #66716b; }.metric-row { margin-bottom: 30px; }.metric-row .ant-statistic { min-height: 106px; padding: 17px; background: #ffffff; border: 1px solid #d9e0da; box-shadow: 0 9px 25px rgba(20, 43, 30, 0.06); }.metric-row .ant-statistic-content { color: #1c392b; font-size: 29px; }.view-section { margin-top: 32px; }.section-heading { display: flex; justify-content: space-between; gap: 16px; margin-bottom: 14px; }.section-heading h2, .surface-section h2 { margin: 0 0 4px; color: #202b25; font-size: 18px; letter-spacing: 0; }.section-heading p, .surface-section p { margin: 0; color: #66716b; font-size: 12px; } |
| | | .capability-card { overflow: hidden; border-radius: 4px !important; border-color: #d9e0da !important; box-shadow: 0 9px 25px rgba(20, 43, 30, 0.07) !important; }.card-media { display: block; width: 100%; height: 260px; object-fit: cover; background: #e0e6e1; }.card-copy { padding: 18px; }.card-topline { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }.card-topline h3 { margin: 0 0 6px; color: #202b25; font-size: 16px; letter-spacing: 0; }.card-topline p, .planned-card p { color: #66716b; }.card-copy .ant-space { display: flex; min-height: 40px; align-content: flex-start; margin-top: 14px; }.card-copy .ant-btn { margin-top: 8px; }.planned-card { height: 100%; border-radius: 4px !important; border-color: #d9e0da !important; }.planned-card p { min-height: 38px; margin: 7px 0 8px; font-size: 12px; } |
| | | .surface-section { margin-bottom: 18px; padding: 20px; background: #ffffff; border: 1px solid #d9e0da; box-shadow: 0 9px 25px rgba(20, 43, 30, 0.06); }.section-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 13px; }.section-toolbar .ant-select { width: min(65%, 540px); }.detection-image { display: block; width: 100%; min-height: 390px; max-height: 660px; overflow: hidden; background: #232e28; text-align: center; }.detection-image .ant-image-img { width: 100%; max-height: 660px; object-fit: contain; }.image-caption { padding-top: 9px; }.surface-section .ant-descriptions { font-size: 12px; }.surface-section .ant-descriptions-item-label { color: #66716b; }.surface-section h3 { margin: 0 0 8px; font-size: 14px; }.result-row { margin-top: 0; }.result-files { margin-bottom: 0; }.toolbar-note { color: #66716b; font-size: 12px; text-align: right; } |
| | | .trajectory-map { position: relative; height: 510px; overflow: hidden; background: #1b2620; }.cesium-host { width: 100%; height: 100%; }.map-error { position: absolute; right: 14px; bottom: 14px; left: 14px; }.cesium-viewer-bottom { display: none !important; }.cesium-viewer-toolbar { top: 10px; right: 10px; }.ant-empty { margin: 70px 0; }.ant-empty-description p { color: #66716b; } |
| | | @media (max-width: 991px) { .application-sider { position: static !important; height: auto; }.navigation-menu { height: auto; max-height: 290px; }.sider-status { display: none; }.view-container { padding: 25px 22px 42px; }.detection-image { min-height: 270px; }.trajectory-map { height: 440px; } } |
| | | @media (max-width: 640px) { .view-container { padding: 20px 14px 34px; }.page-header, .section-toolbar { align-items: stretch; flex-direction: column; }.page-header h1 { font-size: 24px; }.local-tag { align-self: flex-start; }.section-toolbar .ant-select { width: 100%; }.toolbar-note { text-align: left; }.surface-section { padding: 14px; }.trajectory-map { height: 380px; }.card-media { height: 205px; } } |
| New file |
| | |
| | | <script setup lang="ts"> |
| | | import { computed } from "vue"; |
| | | import { useRoute } from "vue-router"; |
| | | |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import ObjectDetectionPanel from "@/components/ObjectDetectionPanel.vue"; |
| | | import TrajectoryAnalysisPanel from "@/components/TrajectoryAnalysisPanel.vue"; |
| | | import { capabilityById } from "@/data/capabilities"; |
| | | |
| | | const route = useRoute(); |
| | | const capability = computed(() => capabilityById(String(route.params.id))); |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="view-container" v-if="capability"> |
| | | <PageHeader :eyebrow="`CAPABILITY ${capability.id} / ${capability.level}`" :title="capability.title" :description="capability.note" /> |
| | | <ObjectDetectionPanel v-if="capability.id === '01-object-detection'" /> |
| | | <TrajectoryAnalysisPanel v-else-if="capability.id === '15-trajectory-analysis'" /> |
| | | <a-empty v-else description="等待第一个可验证 Demo"><template #description><h2>尚无本地结果工件</h2><p>该能力已登记在目录中,但尚未产生可视化结果、结构化输出或运行元数据。</p></template></a-empty> |
| | | </div> |
| | | <div v-else class="view-container"><a-result status="404" title="未找到能力页面" sub-title="请从左侧目录选择已登记的能力。" /></div> |
| | | </template> |
| New file |
| | |
| | | <script setup lang="ts"> |
| | | import { computed, onMounted } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { EyeOutlined, ExperimentOutlined } from "@ant-design/icons-vue"; |
| | | |
| | | import ArtifactState from "@/components/ArtifactState.vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { capabilities, verifiedCapabilities } from "@/data/capabilities"; |
| | | import { artifactUrl } from "@/api/artifacts"; |
| | | import { useArtifactStore } from "@/stores/artifacts"; |
| | | |
| | | const store = useArtifactStore(); |
| | | const router = useRouter(); |
| | | const planned = computed(() => capabilities.filter((item) => item.status === "planned")); |
| | | |
| | | onMounted(async () => Promise.all([store.loadDetection(), store.loadTrajectory()])); |
| | | function openCapability(id: string) { router.push({ name: "capability", params: { id } }); } |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="view-container"><PageHeader eyebrow="WORKBENCH / OVERVIEW" title="实验结果一眼可见" description="独立于无人机产品的本地控制台。它只读取当前工作区已有结果,不上传数据,也不会触发算法运行。" /><ArtifactState :loading="store.loading" :error="store.error" /> |
| | | <a-row :gutter="[14, 14]" class="metric-row"><a-col :xs="12" :lg="6"><a-statistic title="能力目录" :value="capabilities.length" /></a-col><a-col :xs="12" :lg="6"><a-statistic title="已验证 Demo" :value="verifiedCapabilities.length" /></a-col><a-col :xs="12" :lg="6"><a-statistic title="最近检测结果" :value="store.detectionRun?.detection_count ?? 0" suffix="个" /></a-col><a-col :xs="12" :lg="6"><a-statistic title="最近轨迹事件" :value="store.trajectoryRun?.event_count ?? 0" suffix="个" /></a-col></a-row> |
| | | <section class="view-section"><div class="section-heading"><div><h2>已验证能力</h2><p>从实验结果进入,不需要再查找输出目录。</p></div></div><a-row :gutter="[18, 18]"><a-col :xs="24" :xl="12"><a-card class="capability-card" :body-style="{ padding: '0' }"><img class="card-media" :src="artifactUrl('shared/outputs/01-object-detection/annotated/DJI_20260810092727_0001_V_10.jpeg')" alt="目标检测标注图" /><div class="card-copy"><div class="card-topline"><div><h3>地物目标检测</h3><p>原图与标注图切换、检测框明细与运行参数。</p></div><a-tag color="green">已验证</a-tag></div><a-space wrap><a-tag color="gold">A:直接能力</a-tag><a-tag>{{ store.detectionRun?.processed_images ?? 0 }} 张影像</a-tag><a-tag>{{ store.detectionRun?.detection_count ?? 0 }} 个候选</a-tag></a-space><a-button type="primary" @click="openCapability('01-object-detection')"><EyeOutlined />查看实验结果</a-button></div></a-card></a-col><a-col :xs="24" :xl="12"><a-card class="capability-card" :body-style="{ padding: '0' }"><img class="card-media" :src="artifactUrl('shared/outputs/15-trajectory-analysis/difficult/analysis.png')" alt="轨迹分析图" /><div class="card-copy"><div class="card-topline"><div><h3>轨迹分析与行为识别</h3><p>Cesium 轨迹地图、规则事件和轨迹汇总。</p></div><a-tag color="green">已验证</a-tag></div><a-space wrap><a-tag color="gold">C:产品能力</a-tag><a-tag>{{ store.trajectoryRun?.track_count ?? 0 }} 条轨迹</a-tag><a-tag>{{ store.trajectoryRun?.event_count ?? 0 }} 个事件</a-tag></a-space><a-button type="primary" @click="openCapability('15-trajectory-analysis')"><EyeOutlined />查看实验结果</a-button></div></a-card></a-col></a-row></section> |
| | | <section class="view-section"><div class="section-heading"><div><h2>待实现能力</h2><p>首个 Demo 产生可检查工件后,可以按相同方式接入控制台。</p></div></div><a-row :gutter="[14, 14]"><a-col v-for="capability in planned.slice(0, 8)" :key="capability.id" :xs="24" :md="12" :xl="6"><a-card size="small" class="planned-card"><div class="card-topline"><h3>{{ capability.title }}</h3><a-tag>{{ capability.level }}</a-tag></div><p>{{ capability.note }}</p><a-button type="link" size="small" @click="openCapability(capability.id)"><ExperimentOutlined />查看状态</a-button></a-card></a-col></a-row></section> |
| | | </div> |
| | | </template> |
| New file |
| | |
| | | { |
| | | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], |
| | | "exclude": ["src/**/__tests__/*"], |
| | | "compilerOptions": { |
| | | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
| | | "target": "ES2021", |
| | | "useDefineForClassFields": true, |
| | | "module": "ESNext", |
| | | "lib": ["ES2021", "DOM", "DOM.Iterable"], |
| | | "moduleResolution": "Bundler", |
| | | "allowImportingTsExtensions": true, |
| | | "verbatimModuleSyntax": true, |
| | | "moduleDetection": "force", |
| | | "noEmit": true, |
| | | "jsx": "preserve", |
| | | "strict": true, |
| | | "skipLibCheck": true, |
| | | "baseUrl": ".", |
| | | "paths": { "@/*": ["./src/*"] } |
| | | } |
| | | } |
| New file |
| | |
| | | { |
| | | "files": [], |
| | | "references": [ |
| | | { "path": "./tsconfig.app.json" }, |
| | | { "path": "./tsconfig.node.json" } |
| | | ] |
| | | } |
| New file |
| | |
| | | { |
| | | "compilerOptions": { |
| | | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
| | | "target": "ES2022", |
| | | "module": "ESNext", |
| | | "moduleResolution": "Bundler", |
| | | "noEmit": true, |
| | | "strict": true, |
| | | "skipLibCheck": true |
| | | }, |
| | | "include": ["vite.config.ts"] |
| | | } |
| New file |
| | |
| | | import { fileURLToPath, URL } from "node:url"; |
| | | |
| | | import { defineConfig } from "vite"; |
| | | import vue from "@vitejs/plugin-vue"; |
| | | import { viteStaticCopy } from "vite-plugin-static-copy"; |
| | | |
| | | const consoleBase = "/apps/workbench-console/"; |
| | | |
| | | export default defineConfig({ |
| | | base: consoleBase, |
| | | define: { |
| | | CESIUM_BASE_URL: JSON.stringify(`${consoleBase}cesium`) |
| | | }, |
| | | plugins: [ |
| | | vue(), |
| | | viteStaticCopy({ |
| | | targets: [ |
| | | { src: "node_modules/cesium/Build/Cesium/Workers", dest: "cesium" }, |
| | | { src: "node_modules/cesium/Build/Cesium/ThirdParty", dest: "cesium" }, |
| | | { src: "node_modules/cesium/Build/Cesium/Assets", dest: "cesium" }, |
| | | { src: "node_modules/cesium/Build/Cesium/Widgets", dest: "cesium" } |
| | | ] |
| | | }) |
| | | ], |
| | | resolve: { |
| | | alias: { |
| | | "@": fileURLToPath(new URL("./src", import.meta.url)), |
| | | "@zip.js/zip.js/lib/zip-no-worker.js": fileURLToPath(new URL("./node_modules/@zip.js/zip.js/index.js", import.meta.url)) |
| | | } |
| | | }, |
| | | server: { |
| | | host: "127.0.0.1", |
| | | port: 6174, |
| | | strictPort: true, |
| | | proxy: { |
| | | "/shared": "http://127.0.0.1:6173" |
| | | } |
| | | } |
| | | }); |
| | |
| | | # Object Detection |
| | | # 地物目标检测 |
| | | |
| | | - 输入:无人机照片或视频帧。 |
| | | - 输出:目标框、类别、置信度和位置。 |
| | | - 首个 Demo:10–30 张图片的预训练模型推理与 JSON 导出。 |
| | | ## 目标 |
| | | |
| | | 先做一个可在本机 CPU 运行的预训练模型 Demo:输入无人机照片,输出带目标框、类别和置信度的标注图片,以及结构化 JSON。第二阶段再支持正射 GeoTIFF 切片,并把像素坐标转换为经纬度后输出 GeoJSON,便于接入现有 Cesium 地图和算法管理模块。 |
| | | |
| | | 本能力使用 [opengeos/geoai](https://github.com/opengeos/geoai) 发布的 `geoai-py` 作为 GeoAI 工作流层。它负责把地理影像处理、AI 推理、地理结果和可视化串起来;PyTorch、Rasterio、GeoPandas 等仍是它和本 Demo 的底层运行依赖。 |
| | | |
| | | ## 当前范围 |
| | | |
| | | 首版优先识别人员、车辆。`geoai-py` 自带的 NWPU-VHR10 预训练模型面向航空影像,并提供统一的 `vehicle` 类,但不包含人员和树木;因此当前采用“切片 YOLO 检人员 + GeoAI NWPU 检车辆”的分支方案。树木通常需要专门的树冠/单木数据集和模型微调。 |
| | | |
| | | ## 需要你准备的内容 |
| | | |
| | | 1. **目标清单**:请先确定最关心的 3–5 类目标,例如人员、车辆、挖掘机、船只、建筑物。 |
| | | 2. **样例图片**:准备 10–30 张无人机 JPG/PNG,尽量包含不同高度、角度、天气和目标大小。不要使用涉密或未经授权的照片。 |
| | | 3. **正射样例(第二阶段)**:准备 1–2 份 GeoTIFF,并记录坐标系、分辨率和拍摄时间。 |
| | | 4. **输出接入约定**:确认结果需要保存为 JSON/GeoJSON,还是还要生成 Cesium 可加载的 GeoJSON/KML。 |
| | | 5. **验收样例**:从图片中挑选 5 张作为固定验收集,不参与后续调参。 |
| | | |
| | | ## 目录约定 |
| | | |
| | | ```text |
| | | 01-object-detection/ |
| | | |-- README.md |
| | | |-- requirements.txt |
| | | |-- src/ # 推理和结果转换代码 |
| | | |-- scripts/ # 一键运行脚本 |
| | | |-- tests/ # 单元测试 |
| | | shared/ |
| | | |-- data/raw/01-object-detection/ # 原始图片,只存本地 |
| | | |-- data/processed/01-object-detection/ # 切片或预处理结果 |
| | | |-- models/01-object-detection/ # 模型权重,不提交 Git |
| | | `-- outputs/01-object-detection/ # 标注图、JSON、GeoJSON |
| | | ``` |
| | | |
| | | ## 环境与硬件 |
| | | |
| | | - 使用 Python 3.12 专用环境:`.venvs/01-object-detection`。 |
| | | - 当前电脑是 AMD RX 590 GME 8GB,不具备 NVIDIA CUDA;首版使用 CPU 推理。 |
| | | - 64GB 内存足够运行 10–30 张图片的 Demo。大尺寸正射影像需要切片,不能一次性全部载入内存。 |
| | | - 模型权重和 Python 包必须记录版本与许可证,产品使用前检查是否允许商用。 |
| | | - `geoai-py` 项目本身为 MIT 许可证;它依赖的模型权重、数据集和第三方库仍需分别核查许可证。`ultralytics` 等可选底层组件的许可证不能由 GeoAI 的 MIT 许可证自动覆盖。 |
| | | |
| | | ## 预期输入输出 |
| | | |
| | | 输入:JPG/PNG 图片,或第二阶段的 GeoTIFF。 |
| | | |
| | | 输出: |
| | | |
| | | - `annotated/`:画出检测框和标签的图片; |
| | | - `detections.json`:图片名、类别、置信度、像素框坐标; |
| | | - `detections.geojson`:正射影像场景下的地理框或中心点; |
| | | - `run_metadata.json`:模型版本、阈值、运行时间、设备(CPU/DirectML)。 |
| | | |
| | | ## 首版验收标准 |
| | | |
| | | - 能通过命令行处理单张图片和一个图片目录; |
| | | - 输出图片能看到检测框、类别和置信度; |
| | | - JSON 字段固定且可被前端读取; |
| | | - 对固定 5 张验收图片,结果可重复生成; |
| | | - 无 GPU 时能正常运行,并对超大图片给出清晰错误提示或自动缩放; |
| | | - 运行日志包含模型、置信度阈值、输入数量、输出路径和耗时。 |
| | | |
| | | ## 预计开发顺序 |
| | | |
| | | 1. 创建专用环境并安装 PyTorch、torchvision、Ultralytics; |
| | | 2. 下载并登记一个允许研究/商用核查的预训练权重; |
| | | 3. 完成单图推理; |
| | | 4. 完成目录批处理、标注图和 JSON 导出; |
| | | 5. 用你的无人机图片验证并调整阈值; |
| | | 6. 增加 GeoTIFF 切片和 GeoJSON 坐标转换; |
| | | 7. 最后再做 FastAPI 接口和现有前端接入。 |
| | | |
| | | ## 当前 Demo 运行命令 |
| | | |
| | | 在项目根目录执行人员/通用车辆切片基线: |
| | | |
| | | ```powershell |
| | | .\.venvs\01-object-detection\Scripts\python.exe .\capabilities\01-object-detection\run_detection.py |
| | | ``` |
| | | |
| | | 默认使用 CPU、`yolo11n.pt`、1024 像素切片、20% 重叠和置信度阈值 0.20。 |
| | | 结果写入 `shared/outputs/01-object-detection/`。 |
| | | |
| | | 执行 GeoAI NWPU-VHR10 航拍车辆检测: |
| | | |
| | | ```powershell |
| | | .\.venvs\01-object-detection\Scripts\python.exe .\capabilities\01-object-detection\run_geoai_vehicle_detection.py |
| | | ``` |
| | | |
| | | 该脚本默认使用 CPU、512 像素滑窗、128 像素重叠和置信度阈值 0.30,结果写入 |
| | | `shared/outputs/01-object-detection/geoai-vehicles/`。首次运行会自动下载约 98 MB |
| | | 的模型权重。 |
| | | |
| | | ## 当前验证结论 |
| | | |
| | | - `DJI_20260810092727_0001_V_10.jpeg`:通用 YOLO 检出 2–3 辆车;GeoAI NWPU 检出 33 个车辆候选,CPU 推理约 90 秒,明显改善俯视小车辆漏检。 |
| | | - `DJI_20260713102047_0001_V_19.jpeg`:切片 YOLO 检出多个人员,但两种模型均未检出右上角红色汽车;该近景车顶外观仍需要更匹配的航拍数据或本项目样本微调。 |
| | | - NWPU 会产生少量其他航拍类别误检,正式 Demo 只保留 `vehicle`;脚本还会过滤被高置信度整车框大部分包含的重复局部框。 |
| | | - 树木不属于当前两个模型的有效类别,必须单独建设树冠检测/分割分支。 |
| | |
| | | -r ../../requirements/base.txt |
| | | # GeoAI workflow layer: geospatial image preparation, detection helpers, |
| | | # georeferenced output and visualization utilities. |
| | | geoai-py>=0.42,<0.43 |
| | | torch>=2.4,<3 |
| | | torchvision>=0.19,<1 |
| | | ultralytics>=8.3,<9 |
| | | |
| | | rasterio>=1.4,<2 |
| | | geopandas>=1,<2 |
| New file |
| | |
| | | """Run the first GeoAI object-detection baseline on drone images. |
| | | |
| | | This baseline uses the opengeos/geoai environment plus a general-purpose |
| | | Ultralytics model. It intentionally keeps georeferenced GeoTIFF processing for |
| | | the next stage, because the current JPEGs do not carry usable GPS metadata. |
| | | """ |
| | | |
| | | from __future__ import annotations |
| | | |
| | | import argparse |
| | | import json |
| | | import os |
| | | import sys |
| | | import tempfile |
| | | import time |
| | | from datetime import UTC, datetime |
| | | from pathlib import Path |
| | | from typing import Any |
| | | |
| | | from PIL import Image |
| | | |
| | | |
| | | IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png"} |
| | | COCO_TARGET_CLASS_IDS = [0, 1, 2, 3, 5, 7] |
| | | |
| | | |
| | | def parse_args() -> argparse.Namespace: |
| | | root = Path(__file__).resolve().parents[2] |
| | | default_input = root / "shared" / "data" / "raw" / "01-object-detection" |
| | | default_output = root / "shared" / "outputs" / "01-object-detection" |
| | | parser = argparse.ArgumentParser(description="Detect objects in drone images.") |
| | | parser.add_argument("--input", type=Path, default=default_input) |
| | | parser.add_argument("--output", type=Path, default=default_output) |
| | | parser.add_argument("--model", default="yolo11n.pt", help="Ultralytics model name or path.") |
| | | parser.add_argument("--confidence", type=float, default=0.20) |
| | | parser.add_argument("--image-size", type=int, default=1024, help="Model input size for each tile.") |
| | | parser.add_argument("--tile-size", type=int, default=1024, help="Pixel size of the sliding detection window.") |
| | | parser.add_argument("--tile-overlap", type=float, default=0.20, help="Overlap ratio between adjacent windows.") |
| | | return parser.parse_args() |
| | | |
| | | |
| | | def load_image_paths(input_dir: Path) -> tuple[list[Path], list[dict[str, str]]]: |
| | | paths: list[Path] = [] |
| | | skipped: list[dict[str, str]] = [] |
| | | candidates = [input_dir] if input_dir.is_file() else sorted(input_dir.iterdir()) |
| | | for path in candidates: |
| | | if not path.is_file() or path.suffix.lower() not in IMAGE_SUFFIXES: |
| | | continue |
| | | try: |
| | | with Image.open(path) as image: |
| | | image.verify() |
| | | if image.format == "MPO": |
| | | skipped.append({"file": path.name, "reason": "MPO is not supported in baseline"}) |
| | | continue |
| | | except Exception as exc: # pragma: no cover - depends on source files |
| | | skipped.append({"file": path.name, "reason": f"unreadable: {exc}"}) |
| | | continue |
| | | paths.append(path) |
| | | return paths, skipped |
| | | |
| | | |
| | | def tile_starts(length: int, tile_size: int, overlap: float) -> list[int]: |
| | | if not 0 <= overlap < 1: |
| | | raise ValueError("tile-overlap must be between 0 and 1") |
| | | if length <= tile_size: |
| | | return [0] |
| | | stride = max(1, int(tile_size * (1 - overlap))) |
| | | starts = list(range(0, length - tile_size + 1, stride)) |
| | | last = length - tile_size |
| | | if starts[-1] != last: |
| | | starts.append(last) |
| | | return starts |
| | | |
| | | |
| | | def intersection_over_union(box_a: list[float], box_b: list[float]) -> float: |
| | | left = max(box_a[0], box_b[0]) |
| | | top = max(box_a[1], box_b[1]) |
| | | right = min(box_a[2], box_b[2]) |
| | | bottom = min(box_a[3], box_b[3]) |
| | | intersection = max(0.0, right - left) * max(0.0, bottom - top) |
| | | area_a = max(0.0, box_a[2] - box_a[0]) * max(0.0, box_a[3] - box_a[1]) |
| | | area_b = max(0.0, box_b[2] - box_b[0]) * max(0.0, box_b[3] - box_b[1]) |
| | | union = area_a + area_b - intersection |
| | | return intersection / union if union else 0.0 |
| | | |
| | | |
| | | def intersection_over_smaller(box_a: list[float], box_b: list[float]) -> float: |
| | | left = max(box_a[0], box_b[0]) |
| | | top = max(box_a[1], box_b[1]) |
| | | right = min(box_a[2], box_b[2]) |
| | | bottom = min(box_a[3], box_b[3]) |
| | | intersection = max(0.0, right - left) * max(0.0, bottom - top) |
| | | area_a = max(0.0, box_a[2] - box_a[0]) * max(0.0, box_a[3] - box_a[1]) |
| | | area_b = max(0.0, box_b[2] - box_b[0]) * max(0.0, box_b[3] - box_b[1]) |
| | | smaller_area = min(area_a, area_b) |
| | | return intersection / smaller_area if smaller_area else 0.0 |
| | | |
| | | |
| | | def class_aware_nms( |
| | | detections: list[dict[str, Any]], |
| | | iou_threshold: float = 0.50, |
| | | containment_threshold: float = 0.80, |
| | | ) -> list[dict[str, Any]]: |
| | | kept: list[dict[str, Any]] = [] |
| | | for candidate in sorted(detections, key=lambda item: item["confidence"], reverse=True): |
| | | if all( |
| | | candidate["class_id"] != existing["class_id"] |
| | | or ( |
| | | intersection_over_union(candidate["bbox_xyxy"], existing["bbox_xyxy"]) < iou_threshold |
| | | and intersection_over_smaller(candidate["bbox_xyxy"], existing["bbox_xyxy"]) |
| | | < containment_threshold |
| | | ) |
| | | for existing in kept |
| | | ): |
| | | kept.append(candidate) |
| | | return kept |
| | | |
| | | |
| | | def predict_tiled(model: Any, image_path: Path, args: argparse.Namespace, np: Any) -> tuple[list[dict[str, Any]], int, int]: |
| | | with Image.open(image_path) as source: |
| | | image = source.convert("RGB") |
| | | width, height = image.size |
| | | detections: list[dict[str, Any]] = [] |
| | | names: dict[int, str] = {} |
| | | for y0 in tile_starts(height, args.tile_size, args.tile_overlap): |
| | | for x0 in tile_starts(width, args.tile_size, args.tile_overlap): |
| | | tile = image.crop((x0, y0, min(x0 + args.tile_size, width), min(y0 + args.tile_size, height))) |
| | | result = model.predict( |
| | | source=np.asarray(tile), |
| | | device="cpu", |
| | | imgsz=args.image_size, |
| | | conf=args.confidence, |
| | | classes=COCO_TARGET_CLASS_IDS, |
| | | max_det=100, |
| | | verbose=False, |
| | | )[0] |
| | | names = result.names |
| | | if result.boxes is None: |
| | | continue |
| | | for box in result.boxes: |
| | | class_id = int(box.cls.item()) |
| | | local_box = [float(value) for value in box.xyxy[0].tolist()] |
| | | detections.append( |
| | | { |
| | | "class_id": class_id, |
| | | "class_name": names[class_id], |
| | | "confidence": round(float(box.conf.item()), 4), |
| | | "bbox_xyxy": [ |
| | | round(local_box[0] + x0, 2), |
| | | round(local_box[1] + y0, 2), |
| | | round(local_box[2] + x0, 2), |
| | | round(local_box[3] + y0, 2), |
| | | ], |
| | | } |
| | | ) |
| | | return class_aware_nms(detections), width, height |
| | | |
| | | |
| | | def draw_detections(image_path: Path, detections: list[dict[str, Any]], output_path: Path) -> None: |
| | | import cv2 |
| | | import numpy as np |
| | | |
| | | canvas = cv2.cvtColor(np.asarray(Image.open(image_path).convert("RGB")), cv2.COLOR_RGB2BGR) |
| | | for detection in detections: |
| | | x1, y1, x2, y2 = [int(round(value)) for value in detection["bbox_xyxy"]] |
| | | label = f"{detection['class_name']} {detection['confidence']:.2f}" |
| | | cv2.rectangle(canvas, (x1, y1), (x2, y2), (255, 80, 0), 4) |
| | | cv2.putText(canvas, label, (x1, max(30, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 80, 0), 2) |
| | | cv2.imwrite(str(output_path), canvas) |
| | | |
| | | |
| | | def main() -> int: |
| | | args = parse_args() |
| | | input_dir = args.input.resolve() |
| | | output_dir = args.output.resolve() |
| | | annotated_dir = output_dir / "annotated" |
| | | annotated_dir.mkdir(parents=True, exist_ok=True) |
| | | cache_root = Path(tempfile.gettempdir()) / "geoai-object-detection" |
| | | os.environ.setdefault("YOLO_CONFIG_DIR", str(cache_root / "ultralytics")) |
| | | os.environ.setdefault("MPLCONFIGDIR", str(cache_root / "matplotlib")) |
| | | if not input_dir.exists(): |
| | | print(f"Input directory does not exist: {input_dir}", file=sys.stderr) |
| | | return 2 |
| | | |
| | | image_paths, skipped = load_image_paths(input_dir) |
| | | if not image_paths: |
| | | print("No supported images found.", file=sys.stderr) |
| | | return 2 |
| | | |
| | | # Imports happen after argument validation so the CLI can explain path |
| | | # mistakes without requiring the heavyweight ML stack. |
| | | import geoai |
| | | import numpy as np |
| | | import torch |
| | | import ultralytics |
| | | from ultralytics import YOLO |
| | | |
| | | started = time.perf_counter() |
| | | model = YOLO(args.model) |
| | | detections: list[dict[str, Any]] = [] |
| | | for image_path in image_paths: |
| | | image_detections, width, height = predict_tiled(model, image_path, args, np) |
| | | annotated_path = annotated_dir / image_path.name |
| | | draw_detections(image_path, image_detections, annotated_path) |
| | | detections.append( |
| | | { |
| | | "file": image_path.name, |
| | | "annotated_file": str(annotated_path.relative_to(output_dir)), |
| | | "width": width, |
| | | "height": height, |
| | | "detections": image_detections, |
| | | } |
| | | ) |
| | | |
| | | elapsed = round(time.perf_counter() - started, 3) |
| | | (output_dir / "detections.json").write_text( |
| | | json.dumps({"images": detections}, ensure_ascii=False, indent=2), encoding="utf-8" |
| | | ) |
| | | metadata = { |
| | | "created_at": datetime.now(UTC).isoformat(), |
| | | "geoai_package": getattr(geoai, "__version__", "unknown"), |
| | | "ultralytics": ultralytics.__version__, |
| | | "torch": torch.__version__, |
| | | "device": "cpu", |
| | | "cuda_available": bool(torch.cuda.is_available()), |
| | | "model": args.model, |
| | | "confidence": args.confidence, |
| | | "image_size": args.image_size, |
| | | "tile_size": args.tile_size, |
| | | "tile_overlap": args.tile_overlap, |
| | | "merge_iou_threshold": 0.50, |
| | | "merge_containment_threshold": 0.80, |
| | | "target_class_ids": COCO_TARGET_CLASS_IDS, |
| | | "target_class_note": "COCO person, bicycle, car, motorcycle, bus and truck; tree is not a COCO class.", |
| | | "input_dir": str(input_dir), |
| | | "processed_images": len(image_paths), |
| | | "skipped_images": skipped, |
| | | "detection_count": sum(len(item["detections"]) for item in detections), |
| | | "elapsed_seconds": elapsed, |
| | | "notes": [ |
| | | "This is a baseline for people and common vehicle classes.", |
| | | "The default COCO model does not provide a tree class.", |
| | | "JPEG inputs have no usable GPS metadata; GeoJSON is deferred to GeoTIFF stage.", |
| | | ], |
| | | } |
| | | (output_dir / "run_metadata.json").write_text( |
| | | json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8" |
| | | ) |
| | | print(f"Processed {len(image_paths)} images; detections={metadata['detection_count']}; elapsed={elapsed}s") |
| | | print(f"Annotated images: {annotated_dir}") |
| | | print(f"JSON: {output_dir / 'detections.json'}") |
| | | return 0 |
| | | |
| | | |
| | | if __name__ == "__main__": |
| | | raise SystemExit(main()) |
| New file |
| | |
| | | """Detect aerial vehicles with the pretrained model bundled by geoai-py.""" |
| | | |
| | | from __future__ import annotations |
| | | |
| | | import argparse |
| | | import json |
| | | import os |
| | | import sys |
| | | import tempfile |
| | | import time |
| | | from collections import Counter |
| | | from datetime import UTC, datetime |
| | | from pathlib import Path |
| | | from typing import Any |
| | | |
| | | from PIL import Image |
| | | |
| | | |
| | | IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png", ".tif", ".tiff"} |
| | | VEHICLE_CLASS_ID = 10 |
| | | CONTAINMENT_THRESHOLD = 0.80 |
| | | |
| | | |
| | | def parse_args() -> argparse.Namespace: |
| | | root = Path(__file__).resolve().parents[2] |
| | | parser = argparse.ArgumentParser(description="Detect vehicles in aerial images with geoai-py.") |
| | | parser.add_argument( |
| | | "--input", |
| | | type=Path, |
| | | default=root / "shared" / "data" / "raw" / "01-object-detection", |
| | | ) |
| | | parser.add_argument( |
| | | "--output", |
| | | type=Path, |
| | | default=root / "shared" / "outputs" / "01-object-detection" / "geoai-vehicles", |
| | | ) |
| | | parser.add_argument("--confidence", type=float, default=0.30) |
| | | parser.add_argument("--window-size", type=int, default=512) |
| | | parser.add_argument("--overlap", type=int, default=128) |
| | | parser.add_argument("--nms-threshold", type=float, default=0.30) |
| | | parser.add_argument("--batch-size", type=int, default=4) |
| | | return parser.parse_args() |
| | | |
| | | |
| | | def load_image_paths(input_path: Path) -> tuple[list[Path], list[dict[str, str]]]: |
| | | candidates = [input_path] if input_path.is_file() else sorted(input_path.iterdir()) |
| | | paths: list[Path] = [] |
| | | skipped: list[dict[str, str]] = [] |
| | | for path in candidates: |
| | | if not path.is_file() or path.suffix.lower() not in IMAGE_SUFFIXES: |
| | | continue |
| | | try: |
| | | with Image.open(path) as image: |
| | | image.verify() |
| | | if image.format == "MPO": |
| | | skipped.append({"file": path.name, "reason": "MPO is not supported"}) |
| | | continue |
| | | except Exception as exc: |
| | | skipped.append({"file": path.name, "reason": f"unreadable: {exc}"}) |
| | | continue |
| | | paths.append(path) |
| | | return paths, skipped |
| | | |
| | | |
| | | def intersection_over_smaller(box_a: list[float], box_b: list[float]) -> float: |
| | | left = max(box_a[0], box_b[0]) |
| | | top = max(box_a[1], box_b[1]) |
| | | right = min(box_a[2], box_b[2]) |
| | | bottom = min(box_a[3], box_b[3]) |
| | | intersection = max(0.0, right - left) * max(0.0, bottom - top) |
| | | area_a = max(0.0, box_a[2] - box_a[0]) * max(0.0, box_a[3] - box_a[1]) |
| | | area_b = max(0.0, box_b[2] - box_b[0]) * max(0.0, box_b[3] - box_b[1]) |
| | | smaller_area = min(area_a, area_b) |
| | | return intersection / smaller_area if smaller_area else 0.0 |
| | | |
| | | |
| | | def serializable_vehicle_detections(detections: list[dict[str, Any]]) -> list[dict[str, Any]]: |
| | | vehicles: list[dict[str, Any]] = [] |
| | | for detection in sorted(detections, key=lambda item: float(item["score"]), reverse=True): |
| | | if int(detection["label"]) != VEHICLE_CLASS_ID: |
| | | continue |
| | | box = [round(float(value), 2) for value in detection["box"]] |
| | | if any( |
| | | intersection_over_smaller(box, existing["bbox_xyxy"]) >= CONTAINMENT_THRESHOLD |
| | | for existing in vehicles |
| | | ): |
| | | continue |
| | | vehicles.append( |
| | | { |
| | | "class_id": VEHICLE_CLASS_ID, |
| | | "class_name": "vehicle", |
| | | "confidence": round(float(detection["score"]), 4), |
| | | "bbox_xyxy": box, |
| | | } |
| | | ) |
| | | return vehicles |
| | | |
| | | |
| | | def draw_detections(image_path: Path, detections: list[dict[str, Any]], output_path: Path) -> None: |
| | | import cv2 |
| | | import numpy as np |
| | | |
| | | canvas = cv2.cvtColor(np.asarray(Image.open(image_path).convert("RGB")), cv2.COLOR_RGB2BGR) |
| | | for detection in detections: |
| | | x1, y1, x2, y2 = [int(round(value)) for value in detection["bbox_xyxy"]] |
| | | label = f"vehicle {detection['confidence']:.2f}" |
| | | cv2.rectangle(canvas, (x1, y1), (x2, y2), (0, 140, 255), 4) |
| | | cv2.putText( |
| | | canvas, |
| | | label, |
| | | (x1, max(30, y1 - 8)), |
| | | cv2.FONT_HERSHEY_SIMPLEX, |
| | | 1.0, |
| | | (0, 140, 255), |
| | | 2, |
| | | ) |
| | | cv2.imwrite(str(output_path), canvas) |
| | | |
| | | |
| | | def main() -> int: |
| | | args = parse_args() |
| | | input_path = args.input.resolve() |
| | | output_dir = args.output.resolve() |
| | | annotated_dir = output_dir / "annotated" |
| | | raster_dir = output_dir / "rasters" |
| | | annotated_dir.mkdir(parents=True, exist_ok=True) |
| | | raster_dir.mkdir(parents=True, exist_ok=True) |
| | | |
| | | cache_root = Path(tempfile.gettempdir()) / "geoai-object-detection" |
| | | os.environ.setdefault("MPLCONFIGDIR", str(cache_root / "matplotlib")) |
| | | |
| | | if not input_path.exists(): |
| | | print(f"Input does not exist: {input_path}", file=sys.stderr) |
| | | return 2 |
| | | |
| | | image_paths, skipped = load_image_paths(input_path) |
| | | if not image_paths: |
| | | print("No supported images found.", file=sys.stderr) |
| | | return 2 |
| | | |
| | | import geoai |
| | | import torch |
| | | from geoai.object_detect import NWPU_VHR10_CLASSES, multiclass_detection |
| | | |
| | | started = time.perf_counter() |
| | | results: list[dict[str, Any]] = [] |
| | | for image_path in image_paths: |
| | | raster_path = raster_dir / f"{image_path.stem}_instances.tif" |
| | | _, inference_seconds, raw_detections = multiclass_detection( |
| | | input_path=str(image_path), |
| | | output_path=str(raster_path), |
| | | window_size=args.window_size, |
| | | overlap=args.overlap, |
| | | confidence_threshold=args.confidence, |
| | | nms_threshold=args.nms_threshold, |
| | | batch_size=args.batch_size, |
| | | device=torch.device("cpu"), |
| | | ) |
| | | vehicles = serializable_vehicle_detections(raw_detections) |
| | | annotated_path = annotated_dir / f"{image_path.stem}.jpg" |
| | | draw_detections(image_path, vehicles, annotated_path) |
| | | with Image.open(image_path) as image: |
| | | width, height = image.size |
| | | raw_counts = Counter(NWPU_VHR10_CLASSES[int(item["label"])] for item in raw_detections) |
| | | results.append( |
| | | { |
| | | "file": image_path.name, |
| | | "width": width, |
| | | "height": height, |
| | | "annotated_file": str(annotated_path.relative_to(output_dir)), |
| | | "instance_raster": str(raster_path.relative_to(output_dir)), |
| | | "inference_seconds": round(float(inference_seconds), 3), |
| | | "raw_class_counts": dict(raw_counts), |
| | | "detections": vehicles, |
| | | } |
| | | ) |
| | | del raw_detections |
| | | |
| | | elapsed = round(time.perf_counter() - started, 3) |
| | | (output_dir / "detections.json").write_text( |
| | | json.dumps({"images": results}, ensure_ascii=False, indent=2), encoding="utf-8" |
| | | ) |
| | | metadata = { |
| | | "created_at": datetime.now(UTC).isoformat(), |
| | | "geoai_package": getattr(geoai, "__version__", "unknown"), |
| | | "device": "cpu", |
| | | "model": "giswqs/nwpu-vhr10-maskrcnn:best_model.pth", |
| | | "model_architecture": "Mask R-CNN ResNet-50 FPN", |
| | | "source_classes": NWPU_VHR10_CLASSES, |
| | | "kept_classes": ["vehicle"], |
| | | "confidence": args.confidence, |
| | | "window_size": args.window_size, |
| | | "overlap": args.overlap, |
| | | "nms_threshold": args.nms_threshold, |
| | | "containment_threshold": CONTAINMENT_THRESHOLD, |
| | | "batch_size": args.batch_size, |
| | | "processed_images": len(results), |
| | | "skipped_images": skipped, |
| | | "vehicle_count": sum(len(item["detections"]) for item in results), |
| | | "elapsed_seconds": elapsed, |
| | | "notes": [ |
| | | "The NWPU-VHR10 vehicle class combines vehicle subtypes.", |
| | | "The model does not contain person or tree classes.", |
| | | "JPEG detections use pixel coordinates, not geographic coordinates.", |
| | | ], |
| | | } |
| | | (output_dir / "run_metadata.json").write_text( |
| | | json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8" |
| | | ) |
| | | print( |
| | | f"Processed {len(results)} images; vehicles={metadata['vehicle_count']}; " |
| | | f"elapsed={elapsed}s" |
| | | ) |
| | | print(f"Annotated images: {annotated_dir}") |
| | | print(f"JSON: {output_dir / 'detections.json'}") |
| | | return 0 |
| | | |
| | | |
| | | if __name__ == "__main__": |
| | | raise SystemExit(main()) |
| | |
| | | # Trajectory Analysis |
| | | # 轨迹分析与行为识别 |
| | | |
| | | - 输入:带时间戳的无人机、车辆、人员或船舶轨迹。 |
| | | - 输出:偏航、越界、聚集、停留和轨迹类别。 |
| | | - 首个 Demo:规则检测加轨迹聚类。 |
| | | 状态:首个 CPU Demo 已验证(2026-08-14)。 |
| | | |
| | | ## 能力边界 |
| | | |
| | | 当前 Demo 归为 **C(产品业务能力)**,不是 `geoai-py` 内置功能: |
| | | |
| | | - 产品实现用 GeoPandas、Shapely、PyProj 和 scikit-learn 完成轨迹度量、空间关系计算、DBSCAN 分组和规则判断。仅使用这些生态库、但没有实际调用 `geoai-py`,不能标为 B。 |
| | | - `geoai-py` 的准确角色是上游来源之一:其目标检测和地理结果导出可为跟踪系统提供观测,但 `geoai-py 0.42.0` 没有轨迹、目标跟踪或行为识别 API。本 Demo 运行时因此不安装或调用 `geoai-py`。 |
| | | |
| | | 未来若把 `geoai-py` 检测/导出、独立多目标跟踪器和本规则服务连成完整流水线,流水线可同时包含 A、B、C;当前已实现并验证的部分仍是 C。 |
| | | |
| | | 首版所称“行为识别”是可解释的时空规则,不是视频动作识别模型,也不负责把逐帧检测框关联成轨迹 ID。 |
| | | |
| | | ## Demo 契约 |
| | | |
| | | 输入是一个 `*.case.json` 清单,引用三类真实业务可导出的文件: |
| | | |
| | | 1. 轨迹点 CSV:固定字段 `track_id,entity_type,timestamp,longitude,latitude`。 |
| | | 2. 参考路线 GeoJSON:每条 `LineString` 带 `track_id`。 |
| | | 3. 区域 GeoJSON:每个面带 `zone_id` 和 `zone_type`;首版识别 `restricted`。 |
| | | |
| | | 首版只接受带有效 CRS 的 WGS84 经纬度(`EPSG:4326`)。每条轨迹至少需要两个不同时间戳;同一轨迹的重复时间戳保留最后一条并在元数据中计数。 |
| | | |
| | | 仓库没有用户实测轨迹,因此 [generate_demo_inputs.py](./generate_demo_inputs.py) 会生成两组小型、可审计的合成代表样本: |
| | | |
| | | - `normal`:2 条连续移动且贴合参考路线的轨迹,预期无事件。 |
| | | - `difficult`:3 条乱序轨迹,含 1 条重复观测,以及可人工核对的停留、偏航、禁入区和双人聚集。 |
| | | |
| | | 生成数据位于 `shared/data/raw/15-trajectory-analysis/`,不会提交 Git。真实验证仍需用户提供同结构的脱敏轨迹。 |
| | | |
| | | ## 环境与运行 |
| | | |
| | | ```powershell |
| | | # Python 3.12 隔离环境 |
| | | powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\setup.ps1 -Capability 15-trajectory-analysis |
| | | |
| | | $py = '.\.venvs\15-trajectory-analysis\Scripts\python.exe' |
| | | |
| | | # 第一次生成验收输入 |
| | | & $py .\capabilities\15-trajectory-analysis\generate_demo_inputs.py |
| | | |
| | | # 分析目录内的 normal.case.json 和 difficult.case.json |
| | | & $py .\capabilities\15-trajectory-analysis\run_trajectory_analysis.py |
| | | |
| | | # 只分析一个用户清单;输出仍放在 <output>/<case_id>/ |
| | | & $py .\capabilities\15-trajectory-analysis\run_trajectory_analysis.py ` |
| | | --input .\path\to\my.case.json ` |
| | | --output .\shared\outputs\15-trajectory-analysis\my-run |
| | | ``` |
| | | |
| | | 脚本默认拒绝覆盖已有输入和结果。只有明确要替换此前运行时才传 `--overwrite`。 |
| | | |
| | | ## 规则与输出 |
| | | |
| | | 默认规则: |
| | | |
| | | | 事件 | 条件 | |
| | | | --- | --- | |
| | | | 停留 `stop` | 速度不高于 0.8 m/s,持续至少 60 秒 | |
| | | | 偏航 `route_deviation` | 到参考路线距离大于 25 m,持续至少 20 秒 | |
| | | | 禁入区 `restricted_zone` | 轨迹点落入 `restricted` 面 | |
| | | | 聚集 `gathering` | 至少 2 条轨迹在 20 m 内,持续至少 30 秒 | |
| | | |
| | | 清单可用 `thresholds` 对象覆盖上述阈值。相邻观测超过 60 秒时事件连续段会断开,避免把长时间数据缺失误算为持续行为。 |
| | | |
| | | 每个案例固定输出: |
| | | |
| | | | 文件 | 内容 | |
| | | | --- | --- | |
| | | | `trajectory_summary.csv` | 每条轨迹的时间、距离、速度、偏航比例、停留/禁入时长、聚类 ID 和行为标签 | |
| | | | `events.json` | 固定事件字段、参与轨迹、起止时间、位置和规则细节 | |
| | | | `trajectories.geojson` | WGS84 轨迹线及汇总属性 | |
| | | | `events.geojson` | WGS84 事件点及事件属性 | |
| | | | `analysis.png` | 轨迹、参考路线、区域和不同形状事件标记 | |
| | | | `run_metadata.json` | Python/依赖版本、规则/模型、阈值、CPU、输入数、耗时、清洗数和限制 | |
| | | |
| | | DBSCAN 只对轨迹汇总特征做探索性分组;`cluster_id=-1` 表示没有足够相似轨迹形成簇,不代表异常已被确认。 |
| | | |
| | | ## 已测验收结果 |
| | | |
| | | 运行环境:Python 3.12.10、CPU;`pip check` 通过。目录级运行处理 2 个案例、5 条轨迹和 78 条去重后观测,总耗时约 2.8 秒。 |
| | | |
| | | | 验收项 | 预期 | 实测 | |
| | | | --- | --- | --- | |
| | | | 正常样本 | 2 条轨迹,0 个事件 | 通过:2 条轨迹,0 个事件 | |
| | | | 困难样本清洗 | 乱序可排序,删除 1 条重复时间观测 | 通过:删除 1 条 | |
| | | | 困难样本行为 | 四类事件各至少 1 个 | 通过:停留、偏航、禁入区、聚集各 1 个 | |
| | | | 失败处理 | 缺少必填 CSV 字段时退出码为 2,并指出字段 | 通过 | |
| | | | 输出完整性 | 每个案例生成 6 个非空文件 | 通过 | |
| | | | 视觉检查 | 正常/困难 PNG 非空,轨迹、区域和事件可区分 | 通过;两张图均为 1400 x 980 | |
| | | |
| | | 自动验收: |
| | | |
| | | ```powershell |
| | | $py = '.\.venvs\15-trajectory-analysis\Scripts\python.exe' |
| | | & $py -m unittest discover -s .\capabilities\15-trajectory-analysis\tests -v |
| | | ``` |
| | | |
| | | ## 许可证 |
| | | |
| | | | 项目 | 当前记录 | |
| | | | --- | --- | |
| | | | `geoai-py 0.42.0` | MIT;仅为可选上游,本 Demo 环境未安装 | |
| | | | pandas / GeoPandas / Shapely / scikit-learn | BSD 3-Clause 系列许可证 | |
| | | | PyProj | MIT;同时需遵守其随附 PROJ 数据/组件许可 | |
| | | | Matplotlib | Matplotlib License;随附字体等资产有各自许可证 | |
| | | | Demo 数据 | 由本仓库脚本生成,无第三方数据集或模型权重 | |
| | | | 预训练模型/权重 | 无 | |
| | | |
| | | 以上是开发阶段记录,不等于已完成产品商用法务审查。 |
| | | |
| | | ## 已知限制与下一步 |
| | | |
| | | - 合成样本只能验证程序逻辑,不能证明真实场景准确率;目前没有可报告的真实误报率和漏报率。 |
| | | - 聚集要求时间戳对齐,GPS 漂移、采样频率和轨迹断点会直接影响结果。 |
| | | - 偏航依赖可信参考路线;禁入区依赖有效区域数据;不同人员、车辆、船舶和无人机需要分别标定阈值。 |
| | | - 下一步应先选 1 个正常、1 个困难的脱敏实测轨迹,人工标注事件,再评估误报、漏报和阈值,而不是直接跑大目录。 |
| | | - 若输入来自无人机视频,还需在此能力之前引入独立的多目标跟踪器,并验证 ID 切换问题。 |
| New file |
| | |
| | | """Generate small, auditable trajectory-analysis demo inputs.""" |
| | | |
| | | from __future__ import annotations |
| | | |
| | | import argparse |
| | | import csv |
| | | import json |
| | | import math |
| | | import sys |
| | | from datetime import UTC, datetime, timedelta |
| | | from pathlib import Path |
| | | from typing import Any |
| | | |
| | | |
| | | def parse_args() -> argparse.Namespace: |
| | | root = Path(__file__).resolve().parents[2] |
| | | parser = argparse.ArgumentParser(description="Generate trajectory demo inputs.") |
| | | parser.add_argument( |
| | | "--output", |
| | | type=Path, |
| | | default=root / "shared" / "data" / "raw" / "15-trajectory-analysis", |
| | | ) |
| | | parser.add_argument("--overwrite", action="store_true") |
| | | return parser.parse_args() |
| | | |
| | | |
| | | def feature_collection(features: list[dict[str, Any]]) -> dict[str, Any]: |
| | | return {"type": "FeatureCollection", "features": features} |
| | | |
| | | |
| | | def line_feature(track_id: str, coordinates: list[list[float]]) -> dict[str, Any]: |
| | | return { |
| | | "type": "Feature", |
| | | "properties": {"track_id": track_id}, |
| | | "geometry": {"type": "LineString", "coordinates": coordinates}, |
| | | } |
| | | |
| | | |
| | | def write_json(path: Path, payload: dict[str, Any]) -> None: |
| | | path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") |
| | | |
| | | |
| | | def write_case( |
| | | output_dir: Path, |
| | | case_id: str, |
| | | description: str, |
| | | rows: list[dict[str, str]], |
| | | routes: list[dict[str, Any]], |
| | | zones: list[dict[str, Any]], |
| | | ) -> None: |
| | | csv_name = f"{case_id}_observations.csv" |
| | | routes_name = f"{case_id}_routes.geojson" |
| | | zones_name = f"{case_id}_zones.geojson" |
| | | with (output_dir / csv_name).open("w", newline="", encoding="utf-8") as stream: |
| | | writer = csv.DictWriter( |
| | | stream, |
| | | fieldnames=["track_id", "entity_type", "timestamp", "longitude", "latitude"], |
| | | ) |
| | | writer.writeheader() |
| | | writer.writerows(rows) |
| | | write_json(output_dir / routes_name, feature_collection(routes)) |
| | | write_json(output_dir / zones_name, feature_collection(zones)) |
| | | write_json( |
| | | output_dir / f"{case_id}.case.json", |
| | | { |
| | | "case_id": case_id, |
| | | "description": description, |
| | | "crs": "EPSG:4326", |
| | | "observations": csv_name, |
| | | "reference_routes": routes_name, |
| | | "zones": zones_name, |
| | | }, |
| | | ) |
| | | |
| | | |
| | | def observation( |
| | | track_id: str, |
| | | entity_type: str, |
| | | timestamp: datetime, |
| | | longitude: float, |
| | | latitude: float, |
| | | ) -> dict[str, str]: |
| | | return { |
| | | "track_id": track_id, |
| | | "entity_type": entity_type, |
| | | "timestamp": timestamp.isoformat().replace("+00:00", "Z"), |
| | | "longitude": f"{longitude:.7f}", |
| | | "latitude": f"{latitude:.7f}", |
| | | } |
| | | |
| | | |
| | | def build_normal_case() -> tuple[list[dict[str, str]], list[dict[str, Any]]]: |
| | | started = datetime(2026, 8, 14, 1, 0, tzinfo=UTC) |
| | | rows: list[dict[str, str]] = [] |
| | | routes: list[dict[str, Any]] = [] |
| | | for track_id, entity_type, base_lat in ( |
| | | ("drone-normal-01", "drone", 31.2000), |
| | | ("vehicle-normal-01", "vehicle", 31.1995), |
| | | ): |
| | | coordinates: list[list[float]] = [] |
| | | for index in range(16): |
| | | lon = 121.4700 + index * 0.00012 |
| | | lat = base_lat + math.sin(index / 2) * 0.000006 |
| | | coordinates.append([lon, base_lat]) |
| | | rows.append(observation(track_id, entity_type, started + timedelta(seconds=10 * index), lon, lat)) |
| | | routes.append(line_feature(track_id, coordinates)) |
| | | return rows, routes |
| | | |
| | | |
| | | def build_difficult_case() -> tuple[list[dict[str, str]], list[dict[str, Any]]]: |
| | | started = datetime(2026, 8, 14, 2, 0, tzinfo=UTC) |
| | | rows: list[dict[str, str]] = [] |
| | | routes: list[dict[str, Any]] = [] |
| | | |
| | | car_route = [[121.4700 + index * 0.00010, 31.2000] for index in range(20)] |
| | | routes.append(line_feature("vehicle-difficult-01", car_route)) |
| | | for index in range(20): |
| | | if index <= 5: |
| | | lon, lat = 121.4700 + index * 0.00010, 31.2000 |
| | | elif index <= 13: |
| | | lon, lat = 121.4705, 31.2000 |
| | | else: |
| | | lon, lat = 121.4705 + (index - 13) * 0.00010, 31.20055 |
| | | rows.append( |
| | | observation( |
| | | "vehicle-difficult-01", "vehicle", started + timedelta(seconds=10 * index), lon, lat |
| | | ) |
| | | ) |
| | | # One duplicate is intentional: the analyzer must deduplicate it and report the cleanup. |
| | | rows.append(observation("vehicle-difficult-01", "vehicle", started + timedelta(seconds=100), 121.4705, 31.2000)) |
| | | |
| | | for track_id, base_lat, offset in ( |
| | | ("person-group-01", 31.19935, -0.000035), |
| | | ("person-group-02", 31.19965, 0.000035), |
| | | ): |
| | | coordinates: list[list[float]] = [] |
| | | for index in range(13): |
| | | lon = 121.4700 + index * 0.00011 |
| | | if 4 <= index <= 9: |
| | | lat = 31.19950 + offset |
| | | else: |
| | | lat = base_lat |
| | | coordinates.append([lon, lat]) |
| | | rows.append(observation(track_id, "person", started + timedelta(seconds=10 * index), lon, lat)) |
| | | routes.append(line_feature(track_id, coordinates)) |
| | | |
| | | # Deliberately reverse the rows so sorting is exercised on the difficult case. |
| | | rows.reverse() |
| | | return rows, routes |
| | | |
| | | |
| | | def main() -> int: |
| | | args = parse_args() |
| | | output_dir = args.output.resolve() |
| | | expected = output_dir / "normal.case.json" |
| | | if expected.exists() and not args.overwrite: |
| | | print(f"Demo inputs already exist: {output_dir}. Use --overwrite to replace them.", file=sys.stderr) |
| | | return 2 |
| | | output_dir.mkdir(parents=True, exist_ok=True) |
| | | zone = { |
| | | "type": "Feature", |
| | | "properties": {"zone_id": "restricted-01", "zone_type": "restricted"}, |
| | | "geometry": { |
| | | "type": "Polygon", |
| | | "coordinates": [[ |
| | | [121.47035, 31.20030], |
| | | [121.47125, 31.20030], |
| | | [121.47125, 31.20080], |
| | | [121.47035, 31.20080], |
| | | [121.47035, 31.20030], |
| | | ]], |
| | | }, |
| | | } |
| | | normal_rows, normal_routes = build_normal_case() |
| | | write_case( |
| | | output_dir, |
| | | "normal", |
| | | "Two continuously moving tracks following their routes without rule events.", |
| | | normal_rows, |
| | | normal_routes, |
| | | [zone], |
| | | ) |
| | | difficult_rows, difficult_routes = build_difficult_case() |
| | | write_case( |
| | | output_dir, |
| | | "difficult", |
| | | "Unsorted observations with a duplicate, a stop, route deviation, restricted-zone entry and gathering.", |
| | | difficult_rows, |
| | | difficult_routes, |
| | | [zone], |
| | | ) |
| | | print(f"Generated normal and difficult demo inputs in {output_dir}") |
| | | return 0 |
| | | |
| | | |
| | | if __name__ == "__main__": |
| | | raise SystemExit(main()) |
| | |
| | | -r ../../requirements/base.txt |
| | | geopandas>=1,<2 |
| | | scikit-learn>=1.5,<2 |
| | | movingpandas>=0.19,<1 |
| | | |
| | | matplotlib>=3.9,<4 |
| New file |
| | |
| | | """Analyze timestamped WGS84 trajectories with clustering and explicit rules.""" |
| | | |
| | | from __future__ import annotations |
| | | |
| | | import argparse |
| | | import json |
| | | import platform |
| | | import sys |
| | | import time |
| | | from collections import defaultdict |
| | | from datetime import UTC, datetime |
| | | from importlib.metadata import PackageNotFoundError, version |
| | | from pathlib import Path |
| | | from typing import Any, Iterable |
| | | |
| | | import geopandas as gpd |
| | | import matplotlib |
| | | import numpy as np |
| | | import pandas as pd |
| | | from pyproj import CRS |
| | | from shapely.geometry import LineString, Point, mapping |
| | | from sklearn.cluster import DBSCAN |
| | | from sklearn.preprocessing import StandardScaler |
| | | |
| | | matplotlib.use("Agg") |
| | | import matplotlib.pyplot as plt |
| | | from matplotlib.lines import Line2D |
| | | |
| | | |
| | | DEFAULT_THRESHOLDS = { |
| | | "stop_speed_mps": 0.8, |
| | | "stop_duration_seconds": 60.0, |
| | | "route_deviation_m": 25.0, |
| | | "route_deviation_duration_seconds": 20.0, |
| | | "gathering_radius_m": 20.0, |
| | | "gathering_duration_seconds": 30.0, |
| | | "max_observation_gap_seconds": 60.0, |
| | | } |
| | | REQUIRED_COLUMNS = {"track_id", "entity_type", "timestamp", "longitude", "latitude"} |
| | | EVENT_COLORS = { |
| | | "stop": "#d55e00", |
| | | "route_deviation": "#cc79a7", |
| | | "restricted_zone": "#e69f00", |
| | | "gathering": "#009e73", |
| | | } |
| | | EVENT_MARKERS = { |
| | | "stop": "s", |
| | | "route_deviation": "^", |
| | | "restricted_zone": "D", |
| | | "gathering": "P", |
| | | } |
| | | EVENT_SIZES = { |
| | | "stop": 62, |
| | | "route_deviation": 42, |
| | | "restricted_zone": 92, |
| | | "gathering": 68, |
| | | } |
| | | |
| | | |
| | | def parse_args() -> argparse.Namespace: |
| | | root = Path(__file__).resolve().parents[2] |
| | | parser = argparse.ArgumentParser(description="Analyze timestamped WGS84 trajectories.") |
| | | parser.add_argument( |
| | | "--input", |
| | | type=Path, |
| | | default=root / "shared" / "data" / "raw" / "15-trajectory-analysis", |
| | | help="A .case.json manifest or a directory containing manifests.", |
| | | ) |
| | | parser.add_argument( |
| | | "--output", |
| | | type=Path, |
| | | default=root / "shared" / "outputs" / "15-trajectory-analysis", |
| | | ) |
| | | parser.add_argument("--overwrite", action="store_true", help="Replace existing case outputs.") |
| | | return parser.parse_args() |
| | | |
| | | |
| | | def package_version(name: str) -> str: |
| | | try: |
| | | return version(name) |
| | | except PackageNotFoundError: |
| | | return "not-installed" |
| | | |
| | | |
| | | def utc_text(value: pd.Timestamp | datetime) -> str: |
| | | return value.to_pydatetime().astimezone(UTC).isoformat() if isinstance(value, pd.Timestamp) else value.astimezone(UTC).isoformat() |
| | | |
| | | |
| | | def load_json(path: Path) -> dict[str, Any]: |
| | | with path.open(encoding="utf-8") as stream: |
| | | payload = json.load(stream) |
| | | if not isinstance(payload, dict): |
| | | raise ValueError(f"JSON root must be an object: {path}") |
| | | return payload |
| | | |
| | | |
| | | def resolve_input_path(manifest_path: Path, value: str) -> Path: |
| | | path = Path(value) |
| | | return path.resolve() if path.is_absolute() else (manifest_path.parent / path).resolve() |
| | | |
| | | |
| | | def choose_metric_crs(longitude: float, latitude: float) -> CRS: |
| | | zone = int((longitude + 180) // 6) + 1 |
| | | epsg = (32600 if latitude >= 0 else 32700) + zone |
| | | return CRS.from_epsg(epsg) |
| | | |
| | | |
| | | def load_case(manifest_path: Path) -> tuple[dict[str, Any], pd.DataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame, int, CRS]: |
| | | manifest = load_json(manifest_path) |
| | | for field in ("case_id", "crs", "observations", "reference_routes", "zones"): |
| | | if not manifest.get(field): |
| | | raise ValueError(f"Manifest is missing '{field}': {manifest_path}") |
| | | if manifest["crs"] != "EPSG:4326": |
| | | raise ValueError("Demo v1 accepts only EPSG:4326 longitude/latitude observations.") |
| | | observations_path = resolve_input_path(manifest_path, manifest["observations"]) |
| | | routes_path = resolve_input_path(manifest_path, manifest["reference_routes"]) |
| | | zones_path = resolve_input_path(manifest_path, manifest["zones"]) |
| | | frame = pd.read_csv(observations_path) |
| | | missing = REQUIRED_COLUMNS - set(frame.columns) |
| | | if missing: |
| | | raise ValueError(f"Observation CSV is missing columns: {', '.join(sorted(missing))}") |
| | | raw_count = len(frame) |
| | | if raw_count == 0: |
| | | raise ValueError("Observation CSV is empty.") |
| | | frame["track_id"] = frame["track_id"].astype(str).str.strip() |
| | | frame["entity_type"] = frame["entity_type"].astype(str).str.strip() |
| | | if (frame["track_id"] == "").any() or (frame["entity_type"] == "").any(): |
| | | raise ValueError("track_id and entity_type cannot be empty.") |
| | | frame["timestamp"] = pd.to_datetime(frame["timestamp"], utc=True, errors="raise") |
| | | frame["longitude"] = pd.to_numeric(frame["longitude"], errors="raise") |
| | | frame["latitude"] = pd.to_numeric(frame["latitude"], errors="raise") |
| | | valid = frame["longitude"].between(-180, 180) & frame["latitude"].between(-90, 90) |
| | | if not valid.all(): |
| | | raise ValueError("longitude/latitude contains values outside WGS84 bounds.") |
| | | frame = ( |
| | | frame.sort_values(["track_id", "timestamp"]) |
| | | .drop_duplicates(["track_id", "timestamp"], keep="last") |
| | | .reset_index(drop=True) |
| | | ) |
| | | duplicate_count = raw_count - len(frame) |
| | | if frame.groupby("track_id").size().min() < 2: |
| | | raise ValueError("Every track must contain at least two distinct timestamps.") |
| | | routes = gpd.read_file(routes_path) |
| | | zones = gpd.read_file(zones_path) |
| | | if "track_id" not in routes.columns: |
| | | raise ValueError("Reference route GeoJSON must contain a track_id property.") |
| | | if "zone_id" not in zones.columns or "zone_type" not in zones.columns: |
| | | raise ValueError("Zone GeoJSON must contain zone_id and zone_type properties.") |
| | | if routes.crs is None or zones.crs is None: |
| | | raise ValueError("Route and zone GeoJSON files must declare a CRS.") |
| | | expected = set(frame["track_id"]) |
| | | missing_routes = expected - set(routes["track_id"].astype(str)) |
| | | if missing_routes: |
| | | raise ValueError(f"Missing reference routes for: {', '.join(sorted(missing_routes))}") |
| | | metric_crs = choose_metric_crs(float(frame["longitude"].mean()), float(frame["latitude"].mean())) |
| | | return manifest, frame, routes.to_crs(metric_crs), zones.to_crs(metric_crs), duplicate_count, metric_crs |
| | | |
| | | |
| | | def true_runs(flags: list[bool], times: list[pd.Timestamp], max_gap: float) -> Iterable[tuple[int, int]]: |
| | | start: int | None = None |
| | | for index, flag in enumerate(flags): |
| | | separated = index > 0 and (times[index] - times[index - 1]).total_seconds() > max_gap |
| | | if flag and (start is None or separated): |
| | | if start is not None: |
| | | yield start, index - 1 |
| | | start = index |
| | | elif not flag and start is not None: |
| | | yield start, index - 1 |
| | | start = None |
| | | if start is not None: |
| | | yield start, len(flags) - 1 |
| | | |
| | | |
| | | def make_event( |
| | | case_id: str, |
| | | event_type: str, |
| | | track_ids: list[str], |
| | | started: pd.Timestamp, |
| | | ended: pd.Timestamp, |
| | | point: Point, |
| | | details: dict[str, Any], |
| | | ) -> dict[str, Any]: |
| | | duration = max(0.0, (ended - started).total_seconds()) |
| | | return { |
| | | "event_id": "", |
| | | "case_id": case_id, |
| | | "event_type": event_type, |
| | | "track_ids": track_ids, |
| | | "start_time": utc_text(started), |
| | | "end_time": utc_text(ended), |
| | | "duration_seconds": round(duration, 3), |
| | | "longitude": round(point.x, 7), |
| | | "latitude": round(point.y, 7), |
| | | "details": details, |
| | | } |
| | | |
| | | |
| | | def analyze_tracks( |
| | | case_id: str, |
| | | frame: pd.DataFrame, |
| | | routes_metric: gpd.GeoDataFrame, |
| | | zones_metric: gpd.GeoDataFrame, |
| | | metric_crs: CRS, |
| | | thresholds: dict[str, float], |
| | | ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, gpd.GeoDataFrame]]: |
| | | points = gpd.GeoDataFrame( |
| | | frame.copy(), |
| | | geometry=gpd.points_from_xy(frame["longitude"], frame["latitude"]), |
| | | crs="EPSG:4326", |
| | | ) |
| | | points_metric = points.to_crs(metric_crs) |
| | | route_map = {str(row.track_id): row.geometry for row in routes_metric.itertuples()} |
| | | restricted = zones_metric[zones_metric["zone_type"].astype(str) == "restricted"] |
| | | events: list[dict[str, Any]] = [] |
| | | summaries: list[dict[str, Any]] = [] |
| | | track_frames: dict[str, gpd.GeoDataFrame] = {} |
| | | |
| | | for track_id, group_indexes in points_metric.groupby("track_id", sort=True).groups.items(): |
| | | track = points_metric.loc[group_indexes].sort_values("timestamp").copy().reset_index(drop=True) |
| | | track_wgs84 = track.to_crs("EPSG:4326") |
| | | track_frames[str(track_id)] = track_wgs84 |
| | | times = list(track["timestamp"]) |
| | | step_dt = track["timestamp"].diff().dt.total_seconds().fillna(0.0).to_numpy() |
| | | step_distance = np.zeros(len(track), dtype=float) |
| | | for index in range(1, len(track)): |
| | | step_distance[index] = track.geometry.iloc[index - 1].distance(track.geometry.iloc[index]) |
| | | step_speed = np.divide(step_distance, step_dt, out=np.zeros_like(step_distance), where=step_dt > 0) |
| | | max_gap = thresholds["max_observation_gap_seconds"] |
| | | |
| | | slow_intervals = [ |
| | | index > 0 and step_dt[index] <= max_gap and step_speed[index] <= thresholds["stop_speed_mps"] |
| | | for index in range(len(track)) |
| | | ] |
| | | stop_seconds = 0.0 |
| | | for start, end in true_runs(slow_intervals, times, max_gap): |
| | | event_start = max(0, start - 1) |
| | | duration = (times[end] - times[event_start]).total_seconds() |
| | | if duration >= thresholds["stop_duration_seconds"]: |
| | | stop_seconds += duration |
| | | point = track_wgs84.geometry.iloc[event_start : end + 1].union_all().centroid |
| | | events.append( |
| | | make_event( |
| | | case_id, |
| | | "stop", |
| | | [str(track_id)], |
| | | times[event_start], |
| | | times[end], |
| | | point, |
| | | {"max_speed_mps": round(float(step_speed[start : end + 1].max()), 3)}, |
| | | ) |
| | | ) |
| | | |
| | | route = route_map[str(track_id)] |
| | | route_distances = np.array([geometry.distance(route) for geometry in track.geometry], dtype=float) |
| | | deviated = list(route_distances > thresholds["route_deviation_m"]) |
| | | deviation_seconds = 0.0 |
| | | for start, end in true_runs(deviated, times, max_gap): |
| | | duration = (times[end] - times[start]).total_seconds() |
| | | if duration >= thresholds["route_deviation_duration_seconds"]: |
| | | deviation_seconds += duration |
| | | events.append( |
| | | make_event( |
| | | case_id, |
| | | "route_deviation", |
| | | [str(track_id)], |
| | | times[start], |
| | | times[end], |
| | | track_wgs84.geometry.iloc[end], |
| | | {"max_distance_m": round(float(route_distances[start : end + 1].max()), 3)}, |
| | | ) |
| | | ) |
| | | |
| | | restricted_seconds = 0.0 |
| | | inside_by_zone: dict[str, list[bool]] = {} |
| | | for zone in restricted.itertuples(): |
| | | flags = [bool(zone.geometry.covers(geometry)) for geometry in track.geometry] |
| | | inside_by_zone[str(zone.zone_id)] = flags |
| | | for start, end in true_runs(flags, times, max_gap): |
| | | duration = (times[end] - times[start]).total_seconds() |
| | | restricted_seconds += duration |
| | | events.append( |
| | | make_event( |
| | | case_id, |
| | | "restricted_zone", |
| | | [str(track_id)], |
| | | times[start], |
| | | times[end], |
| | | track_wgs84.geometry.iloc[end], |
| | | {"zone_id": str(zone.zone_id)}, |
| | | ) |
| | | ) |
| | | |
| | | duration_seconds = (times[-1] - times[0]).total_seconds() |
| | | summaries.append( |
| | | { |
| | | "case_id": case_id, |
| | | "track_id": str(track_id), |
| | | "entity_type": str(track["entity_type"].iloc[0]), |
| | | "point_count": len(track), |
| | | "start_time": utc_text(times[0]), |
| | | "end_time": utc_text(times[-1]), |
| | | "duration_seconds": round(duration_seconds, 3), |
| | | "distance_m": round(float(step_distance.sum()), 3), |
| | | "average_speed_mps": round(float(step_distance.sum() / duration_seconds), 3), |
| | | "max_speed_mps": round(float(step_speed.max()), 3), |
| | | "route_deviation_ratio": round(float(np.mean(deviated)), 4), |
| | | "restricted_zone_seconds": round(restricted_seconds, 3), |
| | | "stop_seconds": round(stop_seconds, 3), |
| | | "cluster_id": -1, |
| | | "behavior_labels": "", |
| | | } |
| | | ) |
| | | |
| | | gathering_events = detect_gathering(case_id, points_metric, thresholds) |
| | | events.extend(gathering_events) |
| | | labels_by_track: dict[str, set[str]] = defaultdict(set) |
| | | for event in events: |
| | | for track_id in event["track_ids"]: |
| | | labels_by_track[track_id].add(event["event_type"]) |
| | | |
| | | if len(summaries) >= 2: |
| | | features = np.array( |
| | | [ |
| | | [ |
| | | item["distance_m"], |
| | | item["duration_seconds"], |
| | | item["average_speed_mps"], |
| | | item["route_deviation_ratio"], |
| | | item["stop_seconds"] / max(item["duration_seconds"], 1), |
| | | ] |
| | | for item in summaries |
| | | ] |
| | | ) |
| | | labels = DBSCAN(eps=1.35, min_samples=2).fit_predict(StandardScaler().fit_transform(features)) |
| | | for item, label in zip(summaries, labels, strict=True): |
| | | item["cluster_id"] = int(label) |
| | | for item in summaries: |
| | | item["behavior_labels"] = "|".join(sorted(labels_by_track[item["track_id"]])) or "normal" |
| | | |
| | | events.sort(key=lambda item: (item["start_time"], item["event_type"], item["track_ids"])) |
| | | for index, event in enumerate(events, start=1): |
| | | event["event_id"] = f"{case_id}-event-{index:03d}" |
| | | return summaries, events, track_frames |
| | | |
| | | |
| | | def detect_gathering( |
| | | case_id: str, points_metric: gpd.GeoDataFrame, thresholds: dict[str, float] |
| | | ) -> list[dict[str, Any]]: |
| | | occurrences: dict[tuple[str, ...], list[tuple[pd.Timestamp, Point]]] = defaultdict(list) |
| | | radius = thresholds["gathering_radius_m"] |
| | | for timestamp, group in points_metric.groupby("timestamp"): |
| | | rows = list(group.itertuples()) |
| | | adjacency: dict[str, set[str]] = {str(row.track_id): set() for row in rows} |
| | | geometries = {str(row.track_id): row.geometry for row in rows} |
| | | for left_index, left in enumerate(rows): |
| | | for right in rows[left_index + 1 :]: |
| | | if left.geometry.distance(right.geometry) <= radius: |
| | | adjacency[str(left.track_id)].add(str(right.track_id)) |
| | | adjacency[str(right.track_id)].add(str(left.track_id)) |
| | | remaining = set(adjacency) |
| | | while remaining: |
| | | seed = remaining.pop() |
| | | component = {seed} |
| | | queue = [seed] |
| | | while queue: |
| | | current = queue.pop() |
| | | neighbors = adjacency[current] & remaining |
| | | remaining -= neighbors |
| | | component |= neighbors |
| | | queue.extend(neighbors) |
| | | if len(component) >= 2: |
| | | members = tuple(sorted(component)) |
| | | centroid = gpd.GeoSeries([geometries[item] for item in members], crs=points_metric.crs).union_all().centroid |
| | | occurrences[members].append((timestamp, centroid)) |
| | | |
| | | events: list[dict[str, Any]] = [] |
| | | max_gap = thresholds["max_observation_gap_seconds"] |
| | | for members, values in occurrences.items(): |
| | | values.sort(key=lambda item: item[0]) |
| | | groups: list[list[tuple[pd.Timestamp, Point]]] = [[values[0]]] |
| | | for value in values[1:]: |
| | | if (value[0] - groups[-1][-1][0]).total_seconds() <= max_gap: |
| | | groups[-1].append(value) |
| | | else: |
| | | groups.append([value]) |
| | | for run in groups: |
| | | duration = (run[-1][0] - run[0][0]).total_seconds() |
| | | if duration < thresholds["gathering_duration_seconds"]: |
| | | continue |
| | | wgs84_point = gpd.GeoSeries([run[-1][1]], crs=points_metric.crs).to_crs("EPSG:4326").iloc[0] |
| | | events.append( |
| | | make_event( |
| | | case_id, |
| | | "gathering", |
| | | list(members), |
| | | run[0][0], |
| | | run[-1][0], |
| | | wgs84_point, |
| | | {"member_count": len(members), "radius_m": radius}, |
| | | ) |
| | | ) |
| | | return events |
| | | |
| | | |
| | | def write_outputs( |
| | | output_dir: Path, |
| | | manifest: dict[str, Any], |
| | | summaries: list[dict[str, Any]], |
| | | events: list[dict[str, Any]], |
| | | tracks: dict[str, gpd.GeoDataFrame], |
| | | routes_wgs84: gpd.GeoDataFrame, |
| | | zones_wgs84: gpd.GeoDataFrame, |
| | | metadata: dict[str, Any], |
| | | ) -> None: |
| | | output_dir.mkdir(parents=True, exist_ok=True) |
| | | pd.DataFrame(summaries).to_csv(output_dir / "trajectory_summary.csv", index=False, encoding="utf-8-sig") |
| | | (output_dir / "events.json").write_text( |
| | | json.dumps({"case_id": manifest["case_id"], "events": events}, ensure_ascii=False, indent=2), |
| | | encoding="utf-8", |
| | | ) |
| | | trajectory_features = [] |
| | | summary_map = {item["track_id"]: item for item in summaries} |
| | | for track_id, track in tracks.items(): |
| | | properties = dict(summary_map[track_id]) |
| | | trajectory_features.append( |
| | | {"type": "Feature", "properties": properties, "geometry": mapping(LineString(track.geometry.tolist()))} |
| | | ) |
| | | write_geojson(output_dir / "trajectories.geojson", trajectory_features) |
| | | event_features = [ |
| | | { |
| | | "type": "Feature", |
| | | "properties": {key: value for key, value in event.items() if key not in {"longitude", "latitude"}}, |
| | | "geometry": {"type": "Point", "coordinates": [event["longitude"], event["latitude"]]}, |
| | | } |
| | | for event in events |
| | | ] |
| | | write_geojson(output_dir / "events.geojson", event_features) |
| | | render_map(output_dir / "analysis.png", tracks, routes_wgs84, zones_wgs84, events, manifest["case_id"]) |
| | | (output_dir / "run_metadata.json").write_text( |
| | | json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8" |
| | | ) |
| | | |
| | | |
| | | def write_geojson(path: Path, features: list[dict[str, Any]]) -> None: |
| | | path.write_text( |
| | | json.dumps( |
| | | {"type": "FeatureCollection", "name": path.stem, "crs": {"type": "name", "properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"}}, "features": features}, |
| | | ensure_ascii=False, |
| | | indent=2, |
| | | ), |
| | | encoding="utf-8", |
| | | ) |
| | | |
| | | |
| | | def render_map( |
| | | path: Path, |
| | | tracks: dict[str, gpd.GeoDataFrame], |
| | | routes: gpd.GeoDataFrame, |
| | | zones: gpd.GeoDataFrame, |
| | | events: list[dict[str, Any]], |
| | | case_id: str, |
| | | ) -> None: |
| | | figure, axis = plt.subplots(figsize=(10, 7), dpi=140) |
| | | zones.plot(ax=axis, facecolor="#f0e442", edgecolor="#8c6d1f", alpha=0.28, linewidth=1.5) |
| | | routes.plot(ax=axis, color="#666666", linestyle="--", linewidth=1.2) |
| | | palette = ["#0072b2", "#009e73", "#d55e00", "#cc79a7", "#56b4e9"] |
| | | for index, (track_id, track) in enumerate(sorted(tracks.items())): |
| | | color = palette[index % len(palette)] |
| | | axis.plot(track.geometry.x, track.geometry.y, color=color, linewidth=2.2, marker="o", markersize=2.8, label=track_id) |
| | | axis.annotate(track_id, (track.geometry.x.iloc[-1], track.geometry.y.iloc[-1]), fontsize=7, color=color) |
| | | for event in events: |
| | | event_type = event["event_type"] |
| | | axis.scatter( |
| | | [event["longitude"]], |
| | | [event["latitude"]], |
| | | marker=EVENT_MARKERS[event_type], |
| | | s=EVENT_SIZES[event_type], |
| | | color=EVENT_COLORS[event_type], |
| | | edgecolor="white", |
| | | linewidth=0.8, |
| | | zorder=5, |
| | | ) |
| | | handles, labels = axis.get_legend_handles_labels() |
| | | present_event_types = {event["event_type"] for event in events} |
| | | handles.extend( |
| | | Line2D( |
| | | [0], |
| | | [0], |
| | | marker=EVENT_MARKERS[name], |
| | | color="none", |
| | | markerfacecolor=color, |
| | | markeredgecolor="white", |
| | | markersize=8, |
| | | label=name, |
| | | ) |
| | | for name, color in EVENT_COLORS.items() |
| | | if name in present_event_types |
| | | ) |
| | | labels.extend(name for name in EVENT_COLORS if name in present_event_types) |
| | | axis.legend(handles, labels, loc="best", fontsize=7, framealpha=0.9) |
| | | axis.set_title(f"Trajectory analysis: {case_id}") |
| | | axis.set_xlabel("Longitude (WGS84)") |
| | | axis.set_ylabel("Latitude (WGS84)") |
| | | axis.grid(alpha=0.18) |
| | | axis.ticklabel_format(useOffset=False) |
| | | figure.tight_layout() |
| | | figure.savefig(path) |
| | | plt.close(figure) |
| | | |
| | | |
| | | def process_manifest(manifest_path: Path, output_dir: Path) -> dict[str, Any]: |
| | | started = time.perf_counter() |
| | | manifest, frame, routes_metric, zones_metric, duplicates, metric_crs = load_case(manifest_path) |
| | | thresholds = dict(DEFAULT_THRESHOLDS) |
| | | for key, value in manifest.get("thresholds", {}).items(): |
| | | if key not in thresholds: |
| | | raise ValueError(f"Unsupported threshold: {key}") |
| | | thresholds[key] = float(value) |
| | | if thresholds[key] <= 0: |
| | | raise ValueError(f"Threshold must be greater than zero: {key}") |
| | | summaries, events, tracks = analyze_tracks( |
| | | str(manifest["case_id"]), frame, routes_metric, zones_metric, metric_crs, thresholds |
| | | ) |
| | | elapsed = round(time.perf_counter() - started, 3) |
| | | metadata = { |
| | | "created_at": datetime.now(UTC).isoformat(), |
| | | "case_id": manifest["case_id"], |
| | | "input_manifest": str(manifest_path.resolve()), |
| | | "input_count": int(len(frame)), |
| | | "track_count": len(summaries), |
| | | "dropped_duplicate_observations": duplicates, |
| | | "event_count": len(events), |
| | | "event_counts": {name: sum(event["event_type"] == name for event in events) for name in EVENT_COLORS}, |
| | | "elapsed_seconds": elapsed, |
| | | "device": "cpu", |
| | | "python": platform.python_version(), |
| | | "packages": { |
| | | "geoai-py": package_version("geoai-py"), |
| | | "pandas": package_version("pandas"), |
| | | "geopandas": package_version("geopandas"), |
| | | "shapely": package_version("shapely"), |
| | | "pyproj": package_version("pyproj"), |
| | | "scikit-learn": package_version("scikit-learn"), |
| | | "matplotlib": package_version("matplotlib"), |
| | | }, |
| | | "model": { |
| | | "behavior_recognition": "deterministic-threshold-rules-v1", |
| | | "trajectory_grouping": "DBSCAN on standardized summary features", |
| | | "pretrained_weights": None, |
| | | }, |
| | | "thresholds": thresholds, |
| | | "output_crs": "EPSG:4326", |
| | | "metric_crs": metric_crs.to_string(), |
| | | "limitations": [ |
| | | "This is rule-based behavior detection, not learned action recognition.", |
| | | "Track identities must already be present; this demo does not associate detections across video frames.", |
| | | "Gathering requires aligned timestamps and is sensitive to sampling gaps and GPS error.", |
| | | "Thresholds are illustrative and require domain validation before operational use.", |
| | | ], |
| | | } |
| | | write_outputs( |
| | | output_dir, |
| | | manifest, |
| | | summaries, |
| | | events, |
| | | tracks, |
| | | routes_metric.to_crs("EPSG:4326"), |
| | | zones_metric.to_crs("EPSG:4326"), |
| | | metadata, |
| | | ) |
| | | return metadata |
| | | |
| | | |
| | | def main() -> int: |
| | | args = parse_args() |
| | | input_path = args.input.resolve() |
| | | output_root = args.output.resolve() |
| | | if not input_path.exists(): |
| | | print(f"Input does not exist: {input_path}", file=sys.stderr) |
| | | return 2 |
| | | manifests = [input_path] if input_path.is_file() else sorted(input_path.glob("*.case.json")) |
| | | if not manifests: |
| | | print(f"No .case.json manifests found: {input_path}", file=sys.stderr) |
| | | return 2 |
| | | try: |
| | | cases = [(path, str(load_json(path).get("case_id", ""))) for path in manifests] |
| | | if any(not case_id for _, case_id in cases): |
| | | raise ValueError("Every manifest must define a non-empty case_id.") |
| | | destinations = [output_root / case_id for _, case_id in cases] |
| | | existing = [path for path in destinations if path.exists()] |
| | | if existing and not args.overwrite: |
| | | raise ValueError(f"Output already exists: {existing[0]}. Use --overwrite to replace it.") |
| | | results = [process_manifest(path, destination) for (path, _), destination in zip(cases, destinations, strict=True)] |
| | | aggregate = { |
| | | "created_at": datetime.now(UTC).isoformat(), |
| | | "input": str(input_path), |
| | | "input_count": sum(item["input_count"] for item in results), |
| | | "case_count": len(results), |
| | | "track_count": sum(item["track_count"] for item in results), |
| | | "dropped_duplicate_observations": sum( |
| | | item["dropped_duplicate_observations"] for item in results |
| | | ), |
| | | "event_count": sum(item["event_count"] for item in results), |
| | | "event_counts": { |
| | | name: sum(item["event_counts"][name] for item in results) for name in EVENT_COLORS |
| | | }, |
| | | "elapsed_seconds": round(sum(item["elapsed_seconds"] for item in results), 3), |
| | | "device": "cpu", |
| | | "python": platform.python_version(), |
| | | "packages": results[0]["packages"], |
| | | "model": results[0]["model"], |
| | | "thresholds_by_case": { |
| | | item["case_id"]: item["thresholds"] for item in results |
| | | }, |
| | | "limitations": results[0]["limitations"], |
| | | "cases": [ |
| | | {"case_id": item["case_id"], "output": item["case_id"]} for item in results |
| | | ], |
| | | } |
| | | output_root.mkdir(parents=True, exist_ok=True) |
| | | (output_root / "run_metadata.json").write_text( |
| | | json.dumps(aggregate, ensure_ascii=False, indent=2), encoding="utf-8" |
| | | ) |
| | | except (OSError, ValueError, json.JSONDecodeError, pd.errors.ParserError) as exc: |
| | | print(f"Analysis failed: {exc}", file=sys.stderr) |
| | | return 2 |
| | | print( |
| | | f"Processed {len(results)} case(s), {aggregate['track_count']} tracks and " |
| | | f"{aggregate['event_count']} events in {aggregate['elapsed_seconds']}s" |
| | | ) |
| | | print(f"Outputs: {output_root}") |
| | | return 0 |
| | | |
| | | |
| | | if __name__ == "__main__": |
| | | raise SystemExit(main()) |
| New file |
| | |
| | | from __future__ import annotations |
| | | |
| | | import json |
| | | import subprocess |
| | | import sys |
| | | import tempfile |
| | | import unittest |
| | | from pathlib import Path |
| | | |
| | | |
| | | CAPABILITY_DIR = Path(__file__).resolve().parents[1] |
| | | GENERATOR = CAPABILITY_DIR / "generate_demo_inputs.py" |
| | | ANALYZER = CAPABILITY_DIR / "run_trajectory_analysis.py" |
| | | |
| | | |
| | | class TrajectoryDemoTests(unittest.TestCase): |
| | | def run_command(self, *arguments: str) -> subprocess.CompletedProcess[str]: |
| | | return subprocess.run( |
| | | [sys.executable, *arguments], |
| | | text=True, |
| | | capture_output=True, |
| | | encoding="utf-8", |
| | | check=False, |
| | | ) |
| | | |
| | | def test_normal_and_difficult_cases(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temporary: |
| | | root = Path(temporary) |
| | | inputs = root / "inputs" |
| | | outputs = root / "outputs" |
| | | generated = self.run_command(str(GENERATOR), "--output", str(inputs)) |
| | | self.assertEqual(generated.returncode, 0, generated.stderr) |
| | | analyzed = self.run_command(str(ANALYZER), "--input", str(inputs), "--output", str(outputs)) |
| | | self.assertEqual(analyzed.returncode, 0, analyzed.stderr) |
| | | |
| | | normal = json.loads((outputs / "normal" / "run_metadata.json").read_text(encoding="utf-8")) |
| | | difficult = json.loads((outputs / "difficult" / "run_metadata.json").read_text(encoding="utf-8")) |
| | | self.assertEqual(normal["track_count"], 2) |
| | | self.assertEqual(normal["event_count"], 0) |
| | | self.assertEqual(difficult["dropped_duplicate_observations"], 1) |
| | | for event_type in ("stop", "route_deviation", "restricted_zone", "gathering"): |
| | | self.assertGreaterEqual(difficult["event_counts"][event_type], 1) |
| | | for case_id in ("normal", "difficult"): |
| | | for filename in ( |
| | | "trajectory_summary.csv", |
| | | "events.json", |
| | | "trajectories.geojson", |
| | | "events.geojson", |
| | | "analysis.png", |
| | | "run_metadata.json", |
| | | ): |
| | | self.assertGreater((outputs / case_id / filename).stat().st_size, 0) |
| | | |
| | | def test_missing_required_column_fails_cleanly(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temporary: |
| | | root = Path(temporary) |
| | | inputs = root / "inputs" |
| | | self.assertEqual(self.run_command(str(GENERATOR), "--output", str(inputs)).returncode, 0) |
| | | csv_path = inputs / "normal_observations.csv" |
| | | csv_path.write_text("track_id,timestamp,longitude,latitude\na,2026-01-01T00:00:00Z,1,1\n", encoding="utf-8") |
| | | result = self.run_command( |
| | | str(ANALYZER), |
| | | "--input", |
| | | str(inputs / "normal.case.json"), |
| | | "--output", |
| | | str(root / "outputs"), |
| | | ) |
| | | self.assertEqual(result.returncode, 2) |
| | | self.assertIn("entity_type", result.stderr) |
| | | |
| | | |
| | | if __name__ == "__main__": |
| | | unittest.main() |
| | |
| | | # Capabilities |
| | | |
| | | 目录编号表示建议研究顺序,不代表运行时依赖关系。 |
| | | 目录编号表示建议研究顺序,不代表运行时依赖关系,也不代表每个目录都是 |
| | | `opengeos/geoai` 内置能力。 |
| | | |
| | | ## 基于 opengeos/geoai 的能力分层 |
| | | |
| | | | 层级 | 含义 | 当前工作区示例 | |
| | | | --- | --- | --- | |
| | | | A:直接能力 | `geoai-py` 已有模块或文档直接覆盖 | 目标检测、分割、分类、变化检测、影像处理/导出 | |
| | | | B:生态组合 | 以 `geoai-py` 为核心,再组合地理计算或业务库 | 空间测量、成果质检、灾害统计、部分三维分析 | |
| | | | C:产品业务能力 | 主要由现有产品后端、规则和数据服务实现 | 风险规则、航线规划、集群调度、GeoLLM、知识图谱、模型治理 | |
| | | |
| | | 判断一个方向是否属于 GeoAI 项目,要看它是否处理地理空间数据并调用 AI 或 |
| | | 空间分析;判断它是否是 `geoai-py` 的能力,则必须以该仓库当前源码和文档为准。 |
| | | |
| | | 官方依据: |
| | | |
| | | - https://github.com/opengeos/geoai |
| | | - https://pypi.org/project/geoai-py/ |
| | | |
| | | 每个能力目录至少包含: |
| | | |
| | |
| | | - `requirements.txt`: 公共依赖之外的专属依赖。 |
| | | |
| | | 开始实现时再增加 `src/`、`tests/`、`examples/` 和 `configs/`,避免初期产生大量空目录。 |
| | | |
| | |
| | | Write-Host "Python installations:" |
| | | py -0p |
| | | |
| | | $PythonCandidates = @( |
| | | (Join-Path $env:LOCALAPPDATA "Programs/Python/Python312/python.exe"), |
| | | (Join-Path $env:LOCALAPPDATA "Programs/Python/Python311/python.exe"), |
| | | (Join-Path $env:ProgramFiles "Python312/python.exe"), |
| | | (Join-Path $env:ProgramFiles "Python311/python.exe") |
| | | ) |
| | | $SupportedPython = $PythonCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 |
| | | |
| | | Write-Host "" |
| | | Write-Host "Node:" |
| | | node --version |
| | |
| | | } |
| | | |
| | | Write-Host "" |
| | | if ((py -0p | Select-String "3.11|3.12")) { |
| | | if ($SupportedPython) { |
| | | Write-Host "Detected: $(& $SupportedPython --version) at $SupportedPython" |
| | | Write-Host "Python version: ready" -ForegroundColor Green |
| | | } else { |
| | | Write-Warning "Install Python 3.11 or 3.12 before running setup.ps1." |
| New file |
| | |
| | | """Serve the local GeoAI Workbench console from the repository root.""" |
| | | |
| | | from __future__ import annotations |
| | | |
| | | import argparse |
| | | import os |
| | | from pathlib import PurePosixPath |
| | | from http import HTTPStatus |
| | | from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer |
| | | from pathlib import Path |
| | | from urllib.parse import unquote, urlsplit |
| | | |
| | | |
| | | DEFAULT_HOST = "127.0.0.1" |
| | | DEFAULT_PORT = 6173 |
| | | ALLOWED_PATH_PREFIXES = ( |
| | | "apps/workbench-console", |
| | | "shared/outputs", |
| | | "shared/data/raw/01-object-detection", |
| | | ) |
| | | |
| | | |
| | | class WorkbenchConsoleHandler(SimpleHTTPRequestHandler): |
| | | """Read-only static handler rooted at the workbench repository.""" |
| | | |
| | | def do_GET(self) -> None: # noqa: N802 - inherited standard-library method name |
| | | if urlsplit(self.path).path == "/": |
| | | self.send_response(HTTPStatus.FOUND) |
| | | self.send_header("Location", "/apps/workbench-console/") |
| | | self.end_headers() |
| | | return |
| | | super().do_GET() |
| | | |
| | | def translate_path(self, path: str) -> str: |
| | | """Expose only the static UI and artifacts required by the local console.""" |
| | | decoded_path = unquote(urlsplit(path).path).lstrip("/") |
| | | requested = PurePosixPath(decoded_path) |
| | | is_allowed = any( |
| | | decoded_path == prefix or decoded_path.startswith(f"{prefix}/") |
| | | for prefix in ALLOWED_PATH_PREFIXES |
| | | ) |
| | | if ".." in requested.parts or not is_allowed: |
| | | return os.fspath(Path(self.directory) / ".console-forbidden") |
| | | if decoded_path == "apps/workbench-console" or decoded_path.startswith("apps/workbench-console/"): |
| | | console_relative = requested.parts[2:] |
| | | return os.fspath(Path(self.directory) / "apps" / "workbench-console" / "dist" / Path(*console_relative)) |
| | | return os.fspath(Path(self.directory).joinpath(*requested.parts)) |
| | | |
| | | def end_headers(self) -> None: |
| | | self.send_header("Cache-Control", "no-store") |
| | | self.send_header("X-Content-Type-Options", "nosniff") |
| | | super().end_headers() |
| | | |
| | | |
| | | def parse_args() -> argparse.Namespace: |
| | | root = Path(__file__).resolve().parents[1] |
| | | parser = argparse.ArgumentParser(description="Serve the local GeoAI Workbench console.") |
| | | parser.add_argument("--host", default=DEFAULT_HOST, help="Bind address. Defaults to loopback only.") |
| | | parser.add_argument("--port", type=int, default=DEFAULT_PORT, help="TCP port in the 6000-6999 range.") |
| | | parser.add_argument("--root", type=Path, default=root, help="Workbench repository root to serve.") |
| | | return parser.parse_args() |
| | | |
| | | |
| | | def main() -> int: |
| | | args = parse_args() |
| | | if not 6000 <= args.port <= 6999: |
| | | raise SystemExit("Port must be in the 6000-6999 range.") |
| | | if args.host not in {"127.0.0.1", "localhost", "::1"}: |
| | | raise SystemExit("This console is local-only. Use 127.0.0.1, localhost, or ::1.") |
| | | root = args.root.resolve() |
| | | app_dir = root / "apps" / "workbench-console" |
| | | if not (root / "shared").is_dir() or not (app_dir / "dist" / "index.html").is_file(): |
| | | raise SystemExit(f"Not a GeoAI Workbench root: {root}") |
| | | |
| | | handler = lambda *handler_args, **handler_kwargs: WorkbenchConsoleHandler( # noqa: E731 |
| | | *handler_args, directory=os.fspath(root), **handler_kwargs |
| | | ) |
| | | server = ThreadingHTTPServer((args.host, args.port), handler) |
| | | print(f"GeoAI Workbench console: http://{args.host}:{args.port}") |
| | | print(f"Serving built console and read-only artifacts from: {root}") |
| | | try: |
| | | server.serve_forever() |
| | | except KeyboardInterrupt: |
| | | print("\nConsole stopped.") |
| | | finally: |
| | | server.server_close() |
| | | return 0 |
| | | |
| | | |
| | | if __name__ == "__main__": |
| | | raise SystemExit(main()) |
| New file |
| | |
| | | from __future__ import annotations |
| | | |
| | | import importlib.util |
| | | import unittest |
| | | from pathlib import Path |
| | | |
| | | |
| | | ROOT = Path(__file__).resolve().parents[1] |
| | | SCRIPT = ROOT / "scripts" / "serve_workbench_console.py" |
| | | SPEC = importlib.util.spec_from_file_location("serve_workbench_console", SCRIPT) |
| | | assert SPEC and SPEC.loader |
| | | MODULE = importlib.util.module_from_spec(SPEC) |
| | | SPEC.loader.exec_module(MODULE) |
| | | |
| | | |
| | | class WorkbenchConsoleHandlerTests(unittest.TestCase): |
| | | def make_handler(self) -> MODULE.WorkbenchConsoleHandler: |
| | | handler = object.__new__(MODULE.WorkbenchConsoleHandler) |
| | | handler.directory = str(ROOT) |
| | | return handler |
| | | |
| | | def test_allows_only_console_artifacts_and_detection_originals(self) -> None: |
| | | handler = self.make_handler() |
| | | self.assertEqual( |
| | | Path(handler.translate_path("/apps/workbench-console/index.html")), |
| | | ROOT / "apps" / "workbench-console" / "dist" / "index.html", |
| | | ) |
| | | self.assertEqual( |
| | | Path(handler.translate_path("/shared/outputs/15-trajectory-analysis/run_metadata.json")), |
| | | ROOT / "shared" / "outputs" / "15-trajectory-analysis" / "run_metadata.json", |
| | | ) |
| | | self.assertEqual( |
| | | Path(handler.translate_path("/shared/data/raw/01-object-detection/sample.jpeg")), |
| | | ROOT / "shared" / "data" / "raw" / "01-object-detection" / "sample.jpeg", |
| | | ) |
| | | |
| | | def test_blocks_repository_files_and_encoded_traversal(self) -> None: |
| | | handler = self.make_handler() |
| | | forbidden = ROOT / ".console-forbidden" |
| | | self.assertEqual(Path(handler.translate_path("/.git/HEAD")), forbidden) |
| | | self.assertEqual(Path(handler.translate_path("/%2e%2e/.env")), forbidden) |
| | | self.assertEqual(Path(handler.translate_path("/PROJECT_CONTEXT.md")), forbidden) |
| | | |
| | | |
| | | if __name__ == "__main__": |
| | | unittest.main() |
| New file |
| | |
| | | GeoAI Workbench 新对话启动说明 |
| | | ============================== |
| | | |
| | | 1. 新建 Codex 对话。 |
| | | 2. 把工作目录选择为: |
| | | |
| | | E:\AllWorkProject\geoai-workbench |
| | | |
| | | 3. 发送下面这句话,把“能力名称”替换成实际目标: |
| | | |
| | | 使用 $geoai-capability-builder,创建下一个能力:<能力名称>。 |
| | | 先读取 AGENTS.md 和 PROJECT_CONTEXT.md,再准备环境、Demo 输入输出和验收方式。 |
| | | |
| | | 示例: |
| | | |
| | | 使用 $geoai-capability-builder,创建下一个能力:语义制图。 |
| | | 先读取 AGENTS.md 和 PROJECT_CONTEXT.md,再准备环境、Demo 输入输出和验收方式。 |
| | | |
| | | 说明: |
| | | |
| | | - 不需要复制旧对话。 |
| | | - AGENTS.md 保存长期项目规则。 |
| | | - PROJECT_CONTEXT.md 保存当前进度、环境和实验结论。 |
| | | - 每完成一个能力,Codex 应更新 PROJECT_CONTEXT.md。 |
| New file |
| | |
| | | # GeoAI 能力边界与来源说明 |
| | | |
| | | ## 统一口径 |
| | | |
| | | 本项目所说的 GeoAI,默认指以 `opengeos/geoai` 开源项目(PyPI 包名 |
| | | `geoai-py`)为基础的地理空间人工智能工作流,而不是泛指所有空间智能、 |
| | | 运筹优化或大语言模型功能。 |
| | | |
| | | ## 三层能力 |
| | | |
| | | 1. **直接能力**:`geoai-py` 已经提供相应模块、接口或示例,例如遥感影像 |
| | | 目标检测、分割、分类、变化检测、影像切片、地理结果导出和可视化。 |
| | | 2. **生态组合能力**:`geoai-py` 提供数据和模型工作流,但还要组合 |
| | | Rasterio、GeoPandas、Shapely、PyTorch、OpenCV 或其他库,例如空间测量、 |
| | | 成果质量检查和灾害统计。 |
| | | 3. **产品业务能力**:不是 `geoai-py` 内置功能,而是利用其结果接入现有 |
| | | 产品后实现,例如风险规则、事件工单、航线规划、集群调度、GeoLLM、知识 |
| | | 图谱和模型治理。 |
| | | |
| | | ## 许可证边界 |
| | | |
| | | `geoai-py` 项目本身采用 MIT 许可证。其依赖库、预训练模型、模型权重和数据集 |
| | | 仍需逐项核查许可证;上层库的 MIT 许可证不会自动改变下游组件的授权要求。 |
| | | |
| | | ## 规划文件说明 |
| | | |
| | | 此前生成的规划文件保留作为历史版本。最新规划以文件名中带有 |
| | | `基于opengeos-geoai修订版` 的工作簿为准,避免把产品能力误称为 |
| | | `geoai-py` 内置能力。 |