内容
# VEIL — 一个地理参考3D数字孪生引擎
https://github.com/user-attachments/assets/5f383d21-384b-4097-9977-d754a1557969
VEIL 将一片现实的地面转化为一个独立的、完全地理参考的 3D 数字孪生,您可以在浏览器中打开它,点击查看真实的 GPS 坐标,将您自己的地图层覆盖到地形上,并用自然语言提问。
无需数据库,无需云端,无需查看时的构建步骤:一个微小的零依赖 Node 静态服务器为 Three.js 查看器提供服务,查看器基于一个自包含的地理空间数据包。当您查看它时,不会从网络中获取任何内容。
```bash
npm start # -> http://127.0.0.1:4173
```
需要 Node ≥ 18(服务器使用内置的 `fetch`);数据管道脚本需要 Python 3 以及 GDAL(`osgeo`)、numpy、pyproj 和 Pillow。如果您不想自己组装该工具链,**[可以在容器中运行它](#使用Docker运行)** — GDAL、numpy、Node 和其他组件都已固定并预先构建。
指向您自己的 DEM 和图像(请参见下面的“构建您自己的孪生”)— 引擎是区域无关的。坐标系统、植被知识、地图层样式和任何源获取脚本都位于数据和可选的**区域包**中,从不硬编码在引擎中。
## 您将获得
- **3D 地形**,来自任何 DEM(LiDAR DTM、USGS 3DEP、国家测量网格),渲染为带有可选表面模式(航空/伪彩色/山影/高程)的照明网格。
- **航空影像**,覆盖在地形上,与网格对齐以适应地形,而不是浮动。
- **点击识别 + GPS 读取** — 点击任意位置即可固定真实世界的纬度/经度(与 proj4js 验证)、高程和该位置的所有地图特征。
- **地图集层** — 导入任何矢量或栅格地理空间文件,它将被投影到场景中,剪裁到地形足迹,自动样式化,并可点击。带入土壤、湿地、分区、栖息地、水文学、土地覆盖等。
- **植被** — 一个能力阶梯,根据可用的最佳信号(LiDAR 分割茎、冠层高度模型或 NDVI 冠层)导出树,渲染为低 LOD 3D 形式。在美国大陆的任何地方,捆绑的 **`us-national`** 包都会将它们标记为常绿/落叶和从 LANDFIRE 命名社区 — 无需区域设置(请参见 [docs/make-a-twin.md](docs/make-a-twin.md) 下的“植被”)。
- **“询问土地”聊天** — 一个查看器中的面板,与孪生体的只读查询工具(MCP 服务器)对话,范围限定在整个孪生体、您绘制的多边形或您选择的点。参见 [docs/mcp.md](docs/mcp.md)。
- **QField 调查伴侣** — 从活动孪生体生成 QField 项目,在现场收集小径/溪流中心线/照片点/观察结果,然后将压缩的项目文件夹上传回查看器。上传成为记录的 `survey_*` 实体,渲染为调查层,并可通过 MCP 查询。参见 [docs/survey.md](docs/survey.md)。
- **实时遥测** — 从查看器注册 Meshtastic/LoRA 网关,渲染实时跟踪器位置,重放记录的日子,并带有实时/速度控制和跟踪器 POV,然后将选定的样本追加到策划的孪生体存储中。参见 [docs/live-inputs.md](docs/live-inputs.md)。
## 使用Docker运行
静态服务器具有 **零 npm 依赖**,但数据管道需要 Python 以及 GDAL、numpy、pyproj 和 Pillow — 这些组件很难手动组装,因为 GDAL 的 Python(`osgeo`)绑定必须与系统 GDAL 匹配。容器为您完成了这项工作:一个镜像包含 Node 和整个 Python 管道,所有版本都已固定(GDAL 通过 `GDAL_VERSION`,pip 依赖项在 `requirements.txt` 中,Node 通过 `NODE_MAJOR`)。它基于官方 [OSGeo GDAL](https://github.com/OSGeo/gdal) 镜像,因此 GDAL + 绑定 + numpy 已经对齐。
```bash
docker compose up --build # 构建并在 http://127.0.0.1:4173 提供 ./data 服务
docker compose --profile live-hardware up veil-live --build
# 提供带有蓝牙/USB 串行访问的 Meshtastic 服务
# 相同的镜像运行每个管道步骤 — 孪生体持续到主机上的 ./twins:
docker compose run --rm veil npm run demo # 构建 Flatirons 演示孪生体
TWIN_DATA_DIR=/app/twins/demo/data docker compose up # 提供它
# 构建您自己的孪生体(请参见下面的“构建您自己的孪生体”以获取命令):
docker compose run --rm -e TWIN_DATA_DIR=/app/twins/mine/data \
veil npm run build-from-aoi -- --aoi /app/twins/mine/my_area.shp --name "My Place"
```
`./data` 和 `./twins` 被绑定安装,因此管道在容器内构建的任何内容都落在主机上并保持私有/忽略 Git。镜像包括 `/opt/veil-live` 中的实时 Meshtastic 桥接依赖项;`live-hardware` 配置文件还将主机 D-Bus 和 `/dev` 安装到具有提升的设备访问权限,以便发现蓝牙和 USB 串行网关。在仅需要查看器、数据管道、聊天或网络/TCP 遥测时,请使用普通的 `veil` 服务。PDAL 是可选的,如果未安装 `pdal` 命令,则不会跳过 LiDAR 派生的 DSM/DTM 生成,如 `requirements.txt` 中所述。对于 **“询问土地”** 聊天面板,请传递 `OPENAI_API_KEY`(环境变量或 `.env` 文件)— 参见下面的聊天部分。对于具有相同固定工具链的可编辑开发环境,请在 VS Code 或任何 devcontainer 感知编辑器中打开存储库。
更喜欢本地工具链?以下所有操作都适用于 `npm` 和 `python3`。
## 构建您自己的孪生体
**存储库仅附带代码**(以及一个 ~600 字节的演示 AOI)。您要么为您的区域获取国家数据,要么带来您自己的文件。一个存储库托管多个孪生体 — 将 `--data-dir` / `TWIN_DATA_DIR` 指向一个文件夹,您的其他孪生体将不受影响。完整的演练:[docs/make-a-twin.md](docs/make-a-twin.md)。
**从地图进行首次设置** — 启动初始化页面,可选地搜索美国街道地址以跳转地图到该位置,绘制一个 AOI,然后按 **设置区域并扫描层**:
```bash
npm run init
```
默认情况下,这会构建到 `./twins/init/data` 并打开 `http://127.0.0.1:4173/init.html`。在其他地方设置 `TWIN_DATA_DIR` 以构建其他位置:
```bash
TWIN_DATA_DIR=./twins/mine/data npm run init
```
初始化页面将绘制的多边形写入 `<data>/init/aoi.geojson`,然后运行与下面的 CLI 路径相同的国家 AOI 构建器。目前在此存储库中实现的国家层包括 3DEP 高程、3DEP LiDAR 点云图块(如果 USGS 有覆盖)、NAIP Plus 正射影像、LANDFIRE EVT 以及其他 LANDFIRE 森林/火灾生态层和 gSSURGO 土壤,加上 `us-national` 植被类型来自 LANDFIRE。LiDAR、额外的 LANDFIRE 层和 gSSURGO 都是尽力而为:如果某个源不可用,孪生体仍将从剩余的国家源构建完成。构建完成后,打开设置页面上显示的查看器链接。
**美国,从一个 AOI 获取所有内容** — 将其交给一个小的 AOI 多边形(shapefile、GeoJSON、GeoPackage ...),它会查询 3DEP 高程、3DEP LiDAR 点云图块(如果有)、NAIP Plus 正射影像、LANDFIRE 土地覆盖/植被、额外的 LANDFIRE 森林/火灾生态层和 gSSURGO 土壤,为该足迹构建一个完整、分类的孪生体:
```bash
npm run build-from-aoi -- --aoi my_area.shp --data-dir ./twins/mine/data --name "My Place"
TWIN_DATA_DIR=./twins/mine/data PORT=4174 npm start # -> http://127.0.0.1:4174
# 捆绑的演示正是如此,从一个提交的 shapefile:
npm run demo && npm run serve-demo
```
在 `npm run init` 期间,VEIL 还会针对绘制的 AOI 探测一个可选的国家层目录,然后在构建开始之前,列表中仅显示报告 AOI 交集的交互式源,解释每个层的作用,并将选定的层 ID 传递到构建中。这些选定的层在地形/地理参考存在后被获取,并注册为普通的 clickable 地图集层。目录包括 NWI、NHDPlus/WBD、FEMA NFHL、USFWS 关键栖息地、EPA 生态区、BLM 表面管理和放牧分配、USFS/NPS 边界/访问层、当前/历史 WFIGS 火灾周界、NCED 产权、国家水库大坝、USDA CDL、NRCS MLRA/LRR、PAD-US、当前美国干旱监测多边形、USDA 植物耐寒带、USFS 野火危险潜力等源的实时获取器。一些源使用直接存档下载而不是服务端的 AOI 分割:VEIL 检查广告的文件大小,需要 20 GB 的本地空闲磁盘空间,拒绝超过 1 GB 的源,下载源存档,在本地剪裁,然后丢弃原始存档。直接下载在设置菜单中标记为轻量级(小于 100 MB)、中(100 MB 至 1 GB)和重量级(1 GB 或更多)。像 PAD-US 这样的多用途源显示子选择及其自己的解释,因此用户可以选择他们需要的解释,而无需不必要的重复下载。菜单保留所选下载字节的实时总数和估计的剪裁输出大小,对于可以报告的源,包括 GAP 物种掩码。
目录还记录自助下载源,例如 NLCD、GAP 物种栖息地范围、TreeMap、PRISM/gridMET、POLARIS/SOLUS、MTBS/RAVG 等。这些不是一键 AOI 服务,但目录包括官方下载链接和注释,以便用户可以获取产品,将其放入 `manual_layers/`,并让 VEIL 在本地剪裁/注册。
CLI 等价项:
```bash
npm run --silent fetch-national-layers -- catalog
npm run --silent fetch-national-layers -- probe --aoi my_area.geojson
npm run --silent fetch-national-layers -- fetch --aoi my_area.geojson \
--data-dir ./twins/mine/data --layers nwi_wetlands,nhdplus_flowlines
```
显示完整的国家目录,包括下载/手动链接:
```bash
npm run --silent fetch-national-layers -- catalog
```
检查直接下载端点和本地磁盘空间:
```bash
npm run --silent fetch-national-layers -- check-downloads
```
**带来您自己的数据** — 任何地方,任何格式。将其放入并摄入:
```bash
# 来自任何 DEM 的地形 + 地理参考(CRS 是 DEM 的或 AOI 的 UTM 区)
npm run ingest-dem -- mydem.tif --aoi boundary.geojson --data-dir ./twins/mine/data
# 与地形足迹对齐的航空影像
npm run ingest-imagery -- myaerial.tif --data-dir ./twins/mine/data
# 任何矢量/栅格层,任何格式,任何 CRS
npm run add-layer -- soils.shp --id soils --label "Soils" --data-dir ./twins/mine/data
npm run add-layer -- wetlands.gpkg --id wetlands --layer NWI --data-dir ./twins/mine/data
npm run add-layer -- landcover.tif --id landcover --data-dir ./twins/mine/data
```
`add_layer` 接受 GDAL/OGR 可以读取的任何内容 — GeoJSON、Shapefile(`.shp`)、GeoPackage(`.gpkg`)、KML/KMZ、GPX、带有坐标的 CSV、File Geodatabase、GeoTIFF 等等。国家层(如 LANDFIRE)直接进入孪生体,并注册为 draped、可点击的地图集层。3DEP LPC LiDAR 由 `scripts/fetch_lidar.py` 单独处理,该脚本查询 TNMAccess 获取 LAZ 图块并导出 `terrain/dsm.tif` + `terrain/dtm.tif` 用于冠层高度植被检测;它不成为地图集覆盖层。额外的 LANDFIRE 森林/火灾层由 `scripts/fetch_forest_ecology.py` 处理,包括现有植被覆盖/高度、生物物理设置、演替类、植被状况/偏差、火灾行为燃料模型、冠层覆盖/高度、冠层基高、燃料干扰和火灾制度组。
### 可下载和手动层
许多公共产品可用,但不是小型 AOI 分割服务。对于那些,请使用放置目录的工作流:
1. 下载您需要的源产品。尽可能选择一个 AOI、州、年份、指标、物种或火灾事件而不是整个国家档案。
2. 将下载的文件或提取的数据集放入 `manual_layers/`。
3. 消化所有内容:
```bash
npm run ingest-manual-layers -- --data-dir ./twins/mine/data
```
4. 如果 GeoPackage、FileGDB 或 zip 有多个矢量层,请使用派生 ID 消化每个层:
```bash
npm run ingest-manual-layers -- --data-dir ./twins/mine/data --all-layers
```
支持的输入包括 GeoTIFF、GeoJSON、Shapefile、GeoPackage、KML/KMZ、GPX、带有坐标的 CSV、File Geodatabase 目录和许多 zip 存档。VEIL 剪裁每个层到孪生体地形足迹、投影、样式化,并在查看器中注册它们。
常见下载/手动源:
| 层 | 链接 | 注释 |
| --- | --- | --- |
| NLCD / 年度 NLCD | <https://www.mrlc.gov/data> | 选择产品和年份。 |
| PAD-US | <https://www.usgs.gov/programs/gap-analysis-project/science/pad-us-data-download> | 直接下载路径提供保护状态、公共访问权限、管理器/所有者和指定类型选项。 |
| GAP 物种栖息地 | <https://www.usgs.gov/programs/gap-analysis-project/science/species-data-download> | 启动物种是自动作为可选择的子下载;额外的物种可以从 ScienceBase 元数据添加。 |
| USFS TreeMap | <https://data.fs.usda.gov/geodata/rastergateway/treemap/index.php> | 选择年份和属性栅格。 |
| USFS LCMS | <https://data.fs.usda.gov/geodata/rastergateway/LCMS/index.php> | 提取了默认的土地覆盖/变化/损失/增益视图。 |
| USFS FIA DataMart | <https://apps.fs.usda.gov/fia/datamart/> | 表格/统计;不是直接的地图层。 |
| USFS Aerial Detection Survey | <https://www.fs.usda.gov/science-technology/data-tools-products/fhp-mapping-reporting/detection-surveys> | 下载区域/年份的空间数据。 |
| 野火危险潜力 | <https://research.fs.usda.gov/firelab/products/dataandtools/wildfire-hazard-potential> | 从 2023 分类图像服务中提取。 |
| MTBS 烧严重度 <https://www.mtbs.gov/direct-download> | 选择火灾/州/国家产品。 |
| RAVG 后火状况 <https://data.fs.usda.gov/geodata/rastergateway/LCMS/index.php> < 选定火/产品图层。 |
| RAVG 后火状况 | <https://data.fs.usda.gov/geodata/rastergateway/ravg/index.php> | 选择火灾和产品栅格。 |
| gNATSGO | <https://www.nrcs.usda.gov/resources/data-and-reports/gridded-national-soil-survey-geographic-database-gnatsgo> | 散装土壤源;基本构建已获取 SDA/gSSURGO。 |
| POLARIS 土壤 | <http://hydrology.cee.duke.edu/POLARIS/> | 选择属性、深度和统计数据栅格。 |
| SOLUS100 土壤 | <https://www.nrcs.usda.gov/resources/data-and-reports/soil-landscapes-of-the-united-states-solus> | 选择属性/深度栅格。 |
| NRCS MLRA/LRR | <https://www.nrcs.usda.gov/resources/data-and-reports/major-land-resource-area-mlra> | 从托管的特征服务中提取;也提供可下载的数据库。 |
| RAP 牧场 | <https://rangelands.app/> | 导出 AOI/年份/指标输出。 |
| USGS RCMAP | <https://www.mrlc.gov/data/type/rcmap-time-series-trends> | 从 MRLC WCS 自动提取所选 2025 组件视图。 |
| LANID 灌溉 | <https://zenodo.org/records/5548555> | 选择年度灌溉或频率栅格。 |
| MIrAD-US 灌溉 | <https://data.usgs.gov/datacatalog/data/USGS:5db08e84e4b0b0c58b56e04f> | 选择年份/分辨率栅格。 |
| OpenET | <https://etdata.org/api/> | API/日期/产品工作流;导出后导入。 |
| PRISM | <https://prism.oregonstate.edu/> | 选择变量和周期。 |
| gridMET | <https://www.climatologylab.org/gridmet.html> | 选择变量/年份或 AOI 子集。 |
| SNODAS | <https://nsidc.org/data/g02158/versions/1> | 将二进制平面产品转换为 GeoTIFF。 |
| 美国干旱监测 | <https://droughtmonitor.unl.edu/DmData/GISData.aspx> | 直接下载路径自动获取当前每周 shapefile。 |
| USGS 地质 / NGMDB | <https://ngmdb.usgs.gov/mapview/> | 选择地图/产品并下载提供的地理空间文件。 |
| EPA ATTAINS | <https://www.epa.gov/waterdata/get-data-access-public-attains-data> | API/表格/地理空间服务;适配器仍需。 |
| 水质门户 | <https://www.waterqualitydata.us/> | 下载站点/结果;丰富的观察适配器仍需。 |
| NOAA C-CAP | <https://coast.noaa.gov/digitalcoast/data/ccapregional.html> | 选择区域/高分辨率产品和年份。 |
| NOAA 基本鱼类栖息地 | <https://www.habitat.noaa.gov/application/efhinventory/> | 选择委员会/物种/生命周期 GIS 数据。 |
| USDA 植物耐寒带 | <https://prism.oregonstate.edu/phzm/> | 直接下载路径自动获取 2023 CONUS shapefile。 |
| NatureServe 生物多样性 | <https://www.natureserve.org/access-data> | 下载开放 GIS 层。 |
| TNC 弹性连接网络 | <https://crcs.tnc.org/pages/data-terrestrial-resilience> | 下载州/网络组件数据。 |
terrain 网格由 `ingest-dem` 编写,符合冻结接口 — 参见 [docs/grid-contract.md](docs/grid-contract.md)。它是每个 terrain 消费者依赖的唯一接口;不要在不通知的情况下更改它。
## QField 测量伴侣
VEIL 内置了一个用于 QField 的实地测量循环:应用程序从当前的 twin 中生成测量包,QField 记录针对该包的编辑,查看器将完成的包上传回 twin 存储。
为正在服务的 twin 构建一个包:
```bash
npm run build-survey-package -- --data-dir ./twins/mine/data --name "我的地点"
TWIN_DATA_DIR=./twins/mine/data npm start
```
然后打开查看器的 **测量伴侣** 面板并下载 `survey-package.zip`。解压或侧载 `survey/` 项目文件夹到 QField 并打开 `project.qgs`。生成的包包含:
- `survey.gpkg`,包含四个可编辑图层:`trails`、`stream_centerlines`、`photo_points` 和 `observations`。
- `project.qgs`,带有表单连接稳定 UUID、活动/退休/删除状态、捕获时间、GPS 精度、备注和相机附件。
- 如果 twin 有地理参考影像,则包含 `basemap.tif`。
实地测量后,将整个 QField 项目文件夹(包括其 `DCIM/` 照片)压缩,并从同一个 **测量伴侣** 面板上传。服务器将原始压缩包保存在 `<data>/surveys/incoming/` 下,在 `<data>/surveys/uploads.log.jsonl` 中记录,并运行 `scripts/ingest_survey.py --pending`,然后刷新查看器的测量图层。如果 Python 摄入不可用,则上传将被保留,`npm run export` 将在以后处理待处理上传。
测量功能存储为普通的 twin 存储实体,具有稳定的 ID,如 `survey_trails:<uuid>`。重新上传同一个项目是安全的:未更改的功能将被跳过,移动的功能保持身份,通过 `status` 字段显式退休,而不是根据缺少的功能推断。要在共享的 LAN 上控制上传,请创建一个 `.survey_token`;查看器提示输入令牌并将其作为 `X-Survey-Token` 发送。
详细信息:[docs/survey.md](docs/survey.md)。
## MCP 服务器和应用程序聊天
`scripts/mcp_server.py` 是 twin 存储上的 MCP 接口。它公开了查看器使用的相同信息 — 地形、实体、图层、测量图层、来源和区域摘要 — 作为结构化工具,供 LLM 代理使用。存储本身是只读的;唯一的写入工具是 `draw_polygon` / `draw_point` / `clear_drawings`,允许 LLM 在实时 3D 地图上放置带有标签的橙色形状(一个扁平的 `annotations.json` 查看器轮询 — 永远不会存储)。它从 `TWIN_DATA_DIR` 或 `./data` 读取活动的 twin。
一次性安装 Python 端:
```bash
pip install -r requirements.txt
TWIN_DATA_DIR=./twins/mine/data npm run rebuild-store # 仅当 twin.gpkg 丢失/过时时
TWIN_DATA_DIR=./twins/mine/data python3 scripts/twin_query.py describe_twin
```
您 **不** 需要单独为浏览器应用程序启动 MCP 服务器。打开 **询问土地**,Node 服务器将在第一个聊天请求时懒散地启动 `scripts/mcp_server.py`。提供 OpenAI 密钥:
```bash
# 自带密钥(推荐,尤其是对于共享/公共 twin):启动时没有密钥,然后在聊天面板中单击“密钥”并粘贴您的密钥。它仅存储在浏览器的 localStorage 中,并作为 X-OpenAI-Key 发送 - 永远不会在服务器或存储库中。
TWIN_DATA_DIR=./twins/mine/data npm start
# 服务器端密钥(单用户/本地便利):服务器为任何没有自己的请求使用此密钥。
OPENAI_API_KEY=sk-... TWIN_DATA_DIR=./twins/mine/data npm start
# 或将密钥放入 gitignored .openai_key 文件中
# 公共部署:禁止服务器密钥回退,以便每个请求都必须自带密钥。
OPENAI_REQUIRE_USER_KEY=1 TWIN_DATA_DIR=./twins/mine/data npm start
# 完全本地,无密钥,机器上没有任何内容离开:指向 Ollama 模型。`npm run start:local` 是快捷方式(CHAT_PROVIDER=ollama,OLLAMA_MODEL=gpt-oss:20b),并在集成 GPU Firefox 配置文件中打开查看器,以便 CUDA VRAM 保持空闲供模型使用;使用 `npm run start:local:server`(或 VEIL_OPEN_BROWSER=0)仅限服务器。
TWIN_DATA_DIR=./twins/mine/data npm run start:local
# 调整参数:OLLAMA_HOST(默认 http://127.0.0.1:11434),OLLAMA_NUM_CTX(默认 98304),OLLAMA_TEMPERATURE(默认 0),OLLAMA_MAX_TOOL_ROUNDS(默认 24)
```
在应用程序中,问题可以针对:
- **整个土地** — 默认范围。
- **绘制的区域** — 点击 **绘制区域**,放置 3+ 个地形点,然后询问关于“此区域”。
- **选择的点** — 点击 **选择点**,选择一个地形点,然后询问关于“此处”,并预加载附近的上下文。
聊天记录显示 MCP 工具模型调用的记录,因此答案可以针对存储数据和图层来源进行检查。
对于外部 MCP 客户端,直接注册 stdio 服务器:
```bash
claude mcp add veil-twin -- env TWIN_DATA_DIR=/ABS/PATH/TO/twins/mine/data \
python3 /ABS/PATH/TO/veil/scripts/mcp_server.py
```
有用的直接健全检查:
```bash
TWIN_DATA_DIR=./twins/mine/data python3 scripts/twin_query.py describe_twin
TWIN_DATA_DIR=./twins/mine/data python3 scripts/twin_query.py identify_at '{"point":{"x":50,"y":100}}'
npm test
```
核心 MCP 工具包括 `describe_twin`、`find_entities`、`get_entity`、`entity_history`、`identify_at`、`sample_raster`、`list_layers`、`layer_summary`、`summarize_region`、`aggregate_entities`、`canopy_change` 和 `list_survey_layers`,以及绘图三人组 `draw_polygon` / `draw_point` / `clear_drawings` — 答案可以指向具有标签的橙色形状的位置(内置聊天和外部 MCP 客户端 alike;聊天面板的 **清除绘图** 按钮删除它们)。测量上传自动显示为 `survey_*` 类型,用于 `find_entities`、`aggregate_entities` 和 `summarize_region`,由 `list_survey_layers` 编目,并现在包含在点的 `identify_at` 中(带有照片和状态)。完整的工具语义和示例:[docs/mcp.md](docs/mcp.md)。
## 架构:引擎 vs. 区域包
VEIL 是一个 **区域无关的引擎** (`scripts/`、`public/`、`server.js`) 加上一个可选的 **区域内容包** (`packs/<name>/`)。引擎中的任何内容都不命名 CRS、图层或物种。
- **坐标是数据。** `<data>/georef.json` 携带投影 CRS(EPSG + proj4 字符串)、地理 CRS 和场景原点。Python 通过 `scripts/twin_georef.py` 读取它;查看器通过 `public/viewer/georef.js` 中的 vendored **proj4js** 读取它。场景坐标是局部米(x = 东,y = 北)偏移到原点;存储保持相同的约定。
- **包** 通过 `scripts/twin_pack.py` 加载(由 `TWIN_PACK` 选择,否则 `<data>/pack.txt`,否则无)。包是一个文件夹,包含 `pack.json` 和可选的 `load(context)` 钩子模块:`vegetation.py`(物种/群落/类型知识)和 `layers.py`(图层样式、属性丰富、命名栅格渲染),以及其自己的源获取脚本。没有包,引擎会自动样式化每个图层并使用 `type:"unknown"` 发出树 — 它永远不会猜测本地植物群。
- **`packs/us-national`** 是区域无关的例外:它编码没有单个位置,只编码 CONUS 宽的 LANDFIRE EVT。任何美国 twin 都可以使用它(`TWIN_PACK=us-national`)来输入植被常绿/落叶并命名群落,在获取 twin 的 footprint 的 LANDFIRE 后(`packs/us-national/fetch_landfire.py --data-dir <data>`)。单个区域包添加了国家数据集没有的内容:精选的本地物种、您自己的图层样式和区域属性丰富。国家数据集(3DEP/NAIP/NLCD/LANDFIRE/GAP/gSSURGO)属于 `us-national` 样式的包,与引擎核心和任何区域都不同。
```
server.js 零依赖静态服务器(+ /api/chat, /api/* )
public/
index.html app.js chat.js UI, 启动, 点击识别, 聊天面板
viewer/ scene.js terrain.js vegetation.js overlays.js buildings3d.js
georef.js 场景本地米 <-> 经纬度(proj4js)
vendor/ three.min.js OrbitControls.js proj4.js
scripts/ 区域无关引擎
twin_georef.py twin_pack.py 读取 georef.json / 加载活动包
ingest_dem.py ingest_imagery.py genesis: DEM + 影像 -> twin
add_layer.py 导入任何地理空间文件为图层
analyze_vegetation.py veg_detect.py 能力门控植被
build_viewer_layers.py 通用图层本地化和样式化
twin_store.py migrate_to_store.py rebuild_store.py twin 存储
mcp_server.py twin_query.py 只读查询工具用于聊天/代理
packs/<name>/ 可选区域内容包
pack.json vegetation.py layers.py 知识 + 样式钩子
*.py 其自己的源获取脚本
data/ (或 --data-dir) 一个 twin 实例:georef, 地形,
影像, 植被, 图层, 存储
```
## twin 存储
记录系统是 **twin 存储** — `<data>/journal/` 中的追加写日志,以 GeoPackage 的形式物化为 `<data>/twin.gpkg`。日志位于 twin 的数据目录内(私有,gitignored 与其余部分一起);`npm run rebuild-store` 从中准确地重建 GeoPackage。查看器加载的扁平 JSON 是存储的 *导出*。原点和 CRS 位于存储的 `meta` 中;坐标是场景本地米。参见 `CLAUDE.md` 和 `docs/mcp.md` 了解存储模型和查询/代理层。
## 测试
```bash
npm test # 离线:提交的固定 twin
npm run test:demo # 真实数据:Flatirons demo twin
```
`npm test` 在 **提交的固定 twin** (`tests/fixtures/mini-twin/data`) 上运行 `scripts/twin_query_test.py` — 一个由 `scripts/build_test_fixture.py` 构建的合成、无网络 twin。它不需要互联网和 GDAL(只需要 Python + `pyproj`;用于 proj4js 交叉检查的 Node);因此,CI 运行完整的查询套件离线。每个期望值都源自正在测试的 twin,从不硬编码到某个位置。
`npm run test:demo` 在 **Flatirons demo twin** (`twins/demo/data`) 上运行相同的套件,首先从实时国家服务(如有)构建它(需要互联网 + GDAL 一次)— 真实数据检查。 将 `TWIN_DATA_DIR` 指向其他 twin 以运行套件。 使用 `npm run build-test-fixture` 在存储架构更改后重新生成固定文件,然后提交它。
## 隐私
twin 的 `data/` (和任何 `--data-dir`)保存真实坐标、区块/所有者属性、建筑物占地面积和特定地点的影像 — 它 **不** 被此存储库中的 git 跟踪(参见 `.gitignore`)。分享引擎和您的包;保留您的土地给您自己。如果您以前提交了一个 twin 并希望它从历史记录中删除(不仅是未来的提交),请使用 `git filter-repo` 重写历史记录 —
或者,更安全的是,从当前树开始一个全新的存储库,因为重写的历史记录仍然可以在克隆和分支中找到可达的对象。
## 安全姿态
服务器旨在用于 localhost 或受信任的 LAN/Tailscale 绑定,不适用于开放的互联网:
- **`POST /api/chat` 消耗 OpenAI 密钥。** 每个请求都使用调用者自己的密钥(如果查看器提供,则聊天面板的 **"密钥"** 按钮将其存储在浏览器的 `localStorage` 中,并将其作为 `X-OpenAI-Key` 头发送 — 它永远不会触及存储库或服务器的磁盘);否则,它会回退到服务器的 `OPENAI_API_KEY` / `.openai_key`。该回退是 **未经身份验证的支出**:任何可以访问端口的人都可以使用服务器的密钥调用 OpenAI。对于公共部署,请设置 **OPENAI_REQUIRE_USER_KEY=1**,以便服务器永远不会使用自己的密钥,并且每个请求都必须自带密钥。
- 测量上传可以通过令牌控制(存储库根目录中的 `.survey_token` 文件强制执行 `X-Survey-Token` 头);如果没有该文件,则该路由是开放的。
- 没有 TLS,没有速率限制,也没有身份验证的建筑物放置端点 — 按设计,用于单用户姿态。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器