Системные требования: интеграция карт (SR-MAP)¶
Дата: 2026-09-18. Ветка: fix4/maps-sys-req (от release/m1).
Основание: вердикт исследования 01-research.md — self-host OSM vector (Planetiler → PMTiles) + Copernicus GLO-30 terrain-RGB; запасной путь — Яндекс Tiles API только на интернет-стенде https://aerozveno.ff. Бизнес-требования — docs/task5/60-maps/02-business-requirements.md (ветка fix4/maps-biz-req, worktree w3-maps-biz-req, BR-MAP-01…20); трассировка ниже использует те же ID и смыслы, что в том документе.
Связанные артефакты: docs/task5/92-code/03-map-and-3d.md (../92-code/03-map-and-3d.md) (MAP-xx, MAP-R-13), docs/task5/50-stack/04-frontend-3d.md (../50-stack/04-frontend-3d.md) (UTM, terrain-RGB, interleaved), simulate/docs/10-world/01-geodata-sources.md. Координация симулятора с картами: пакет Б-7 — simulate/docs/98-fixes/real-geodata.md (ветка fix4/sim-geodata, worktree w3-sim-geodata; в release/m1 / этой ветке нет); схема манифеста — ../../simulate/src/geoscan_sim/world/manifest.py.
1. Резюме решения¶
| Решение | Обоснование (кратко) |
|---|---|
Подложка — стиль MapLibre в существующем MapCanvas, не новый виджет |
layers.ts не знает про тайлы; исследование §1 |
| PMTiles по HTTP Range с того же origin, что SPA | R-DOC-3, SIM-BIZ-09, MAP-R-13; отдельный тайл-сервер (Martin, TileServer GL) не нужен для PMTiles |
DEM — terrain-RGB (encoding: mapbox) в отдельном terrain.pmtiles |
MAP-08, 92-code/03-map-and-3d.md §4.2 |
| Отображение EPSG:3857 (Web Mercator); хранение сценария EPSG:4326; геометрическая математика — UTM зоны сценария | 50-stack/04-frontend-3d.md §«Крутить-вертеть полигоны», 40-formats/03-our-mission-schema.md |
Инфра стенда продукта сегодня — n1, ns aerozveno, статика + /tiles/ через nginx/MinIO |
k3s/aerozveno/45-deployment-frontend.yaml, docs/task5/95-deploy/frontend-on-aerozveno.md |
Опциональный хост tiles.ff — выделенная раздача больших архивов (ЦФО/РФ), не замена same-origin на первой фазе |
см. SR-MAP-INF-04 |
2. Контекст развёртывания¶
flowchart TB
subgraph browser["Браузер оператора"]
shell["apps/shell"]
planner["apps/planner MapCanvas + deck.gl"]
shell --> planner
end
subgraph n1["n1 / ns aerozveno"]
ing["Ingress aerozveno.ff"]
fe["deploy/geoscan-frontend nginx :8080"]
minio["MinIO bucket aerozveno-frontend"]
gw["deploy/geoscan gateway"]
ing --> fe
ing --> gw
minio -->|"mc mirror"| fe
end
subgraph build["Разовая сборка на n1"]
pt["Planetiler → basemap.pmtiles"]
dem["GLO-30 → rio-rgbify → terrain.pmtiles"]
pt --> minio
dem --> minio
end
browser -->|"HTTPS GET /, /remotes/*, /tiles/*.pmtiles Range"| ing
browser -->|"/api/*"| gw
subgraph sim["Симулятор air-gap"]
simweb["simulate/web nginx"]
vol["Volume / public/tiles"]
vol --> simweb
end
3. Требования по слоям¶
3.1. Frontend (src/frontend)¶
Точка врезки подложки — src/frontend/apps/planner/src/map/MapCanvas.tsx. Слои сценария — src/frontend/apps/planner/src/map/layers.ts (без изменений контракта с тайлами). Рельеф для профилей — src/frontend/packages/domain/src/terrain.ts (переход на TerrainSource, MAP-07).
| ID | Требование | Трассировка BR | Файлы / артефакты |
|---|---|---|---|
| SR-MAP-FE-01 | Режим подложки задаётся runtime, не хардкодом в бандле: mapBasemap: 'pmtiles' \| 'openfreemap' \| 'yandex-tiles'. На изолированном контуре допустим только pmtiles. |
BR-MAP-10, BR-MAP-11, BR-MAP-16 | src/frontend/packages/state/src/runtime-config.ts, src/frontend/apps/shell/public/config.json.example, src/frontend/apps/shell/src/runtime-config.ts |
| SR-MAP-FE-02 | Для pmtiles: до new maplibregl.Map регистрируется протокол pmtiles (import { Protocol } from 'pmtiles', maplibregl.addProtocol('pmtiles', …)). Зависимость pmtiles добавляется в src/frontend/apps/planner/package.json. |
BR-MAP-10 | src/frontend/apps/planner/src/map/MapCanvas.tsx, src/frontend/apps/planner/package.json |
| SR-MAP-FE-03 | Стиль BASEMAP_STYLE (замена OFFLINE_STYLE) содержит источники osm (vector, url: 'pmtiles:///tiles/basemap.pmtiles') и terrain (raster-dem, url: 'pmtiles:///tiles/terrain.pmtiles', tileSize: 256, encoding: 'mapbox'), блок terrain: { source: 'terrain', exaggeration: 1 }. URL в стиле — только pmtiles:// или относительные /… (MAP-R-13). |
BR-MAP-01, BR-MAP-08 | src/frontend/apps/planner/src/map/MapCanvas.tsx, эталон в 01-research.md §6.1 |
| SR-MAP-FE-04 | Фаза 0 (интернет-стенд): mapBasemap: 'openfreemap' подставляет style: 'https://tiles.openfreemap.org/styles/dark' (или liberty). Тест MAP-R-13 для этого режима — исключение с отдельным барьерным тестом MAP-R-13-OFM. |
BR-MAP-11 | src/frontend/apps/planner/src/map/MapCanvas.tsx или src/frontend/apps/planner/src/map/basemap-styles.ts (новый) |
| SR-MAP-FE-05 | attributionControl: true; текст OSM и Copernicus берётся из source.attribution стиля. Дополнительный React-комponent MapAttribution дублирует обязательные строки при кастомном UI (режим Яндекс — логотип-ссылка). |
BR-MAP-15 | src/frontend/apps/planner/src/map/MapCanvas.tsx, новый src/frontend/apps/planner/src/map/MapAttribution.tsx |
| SR-MAP-FE-06 | При наличии raster-dem в стиле: MapboxOverlay с interleaved: true, overlay подключается в map.once('load', …) до отрисовки deck-слоёв с рельефом. |
BR-MAP-08, BR-MAP-18 | src/frontend/apps/planner/src/map/MapCanvas.tsx, 92-code/03-map-and-3d.md §2 |
| SR-MAP-FE-07 | Переключатель спутника (optional): второй raster/vector source satellite из pmtiles:///tiles/satellite.pmtiles или слой в том же архиве; UI — флаг в LayerFlags (src/frontend/packages/domain/src/store.ts) / переключатель в панели карты, не отдельный dвижок. |
BR-MAP-04 | src/frontend/packages/domain/src/store.ts, src/frontend/apps/planner/src/map/layers.ts не обязан меняться |
| SR-MAP-FE-08 | Офлайн-fallback: если fetch/pmtiles HEAD/Range на /tiles/basemap.pmtiles завершился ошибкой или таймаутом > 8 с — карта переключается на встроенный OFFLINE_STYLE (фон #0D1117), показывается banner «Подложка недоступна, сценарий и рельеф (если загружен) работают». deck.gl-слои остаются. |
BR-MAP-10 | src/frontend/apps/planner/src/map/MapCanvas.tsx, src/frontend/apps/planner/src/map/BasemapErrorBanner.tsx (новый) |
| SR-MAP-FE-09 | Ошибки сети: при mapBasemap: 'yandex-tiles' неуспех тайла (HTTP ≥ 400, сеть) — полупрозрачная сетка + сообщение; ключ API не логировать. |
BR-MAP-12 | src/frontend/apps/planner/src/map/MapCanvas.tsx, стиль с raster source |
| SR-MAP-FE-10 | Glyphs/symbol: по умолчанию стиль без symbol-слоёв OSM (подписи сценария — TextLayer deck.gl, как сейчас). Опционально: /tiles/glyphs/{fontstack}/{range}.pbf и /tiles/sprite в статике. |
BR-MAP-01 | src/frontend/apps/planner/src/map/MapCanvas.tsx, каталог в MinIO /tiles/glyphs/ |
| SR-MAP-FE-11 | DemTerrain.preload(bbox) вызывается при загрузке сценария (bbox области + 1 км буфер); до preload — skeleton на профиле высот. SyntheticTerrain сохраняется для mock/tests. |
BR-MAP-08 | src/frontend/packages/domain/src/terrain.ts, хук загрузки сценария в planner |
| SR-MAP-FE-12 | Сборка: base remotes не меняется; тайлы не в git. Для локальной разработки planner может монтировать public/tiles/ (опционально src/frontend/apps/planner/public/tiles/ + vite publicDir). Конфиги vite — только если понадобится proxy /tiles на локальный каталог. |
BR-MAP-10, BR-MAP-20 | src/frontend/apps/planner/vite.config.ts, src/frontend/apps/shell/vite.config.ts, src/frontend/apps/viewer3d/vite.config.ts, src/frontend/apps/validator/vite.config.ts |
| SR-MAP-FE-13 | Барьерный тест MAP-R-13 (и исключение OFM) в src/frontend/apps/planner/src/map/ — обход JSON стиля, отсутствие подстроки http в URL источников для режима pmtiles. |
BR-MAP-10, BR-MAP-13 | новый src/frontend/apps/planner/src/map/basemap-style.spec.ts |
Запрещено (наследие исследования): подмена MapLibre на ymaps3 / 2GIS MapGL; tile.openstreetmap.org в прод-стиле; спутник Яндекс JS API в MapLibre.
3.2. Backend / gateway (src/backend/gateway)¶
| ID | Требование | Обоснование |
|---|---|---|
| SR-MAP-BE-01 | По умолчанию gateway не меняется: тайлы и стиль отдаёт nginx фронтенда (или будущий tiles.ff), не NestJS. PMTiles не требует прокси для tailnet-стенда. |
01-research.md §6.1; отсутствие маршрутов tiles в src/backend/gateway/src/main.ts |
| SR-MAP-BE-02 | Опционально (только yandex-tiles на aerozveno): маршрут GET /api/tiles/yandex/{z}/{x}/{y}.png проксирует Tiles API с ключом из creds-store project/geoscan / YANDEX_TILES_API_KEY, query projection=web_mercator, lang=ru_RU, l=map. Ключ не попадает в config.json клиента. |
BR-MAP-12, BR-MAP-16; исследование §6.2 |
| SR-MAP-BE-03 | Прокси Яндекс: без дискового кеша > 30 суток; заголовки Cache-Control не противоречат п. 5.1.5 соглашения Яндекс Maps API. |
BR-MAP-12, L-MAP-03 |
| SR-MAP-BE-04 | Gateway не отдаёт GeoJSON зон с карты — зоны остаются в теле сценария API (airspace в JSON сценария). |
src/frontend/apps/planner/src/map/layers.ts, 40-formats/03-our-mission-schema.md |
| SR-MAP-BE-05 | Высоты для метрик качества плана по-прежнему считает ядро/валидатор; gateway не становится WMS/DEM-сервисом. | 50-stack/04-frontend-3d.md §«Чего на фронтенде быть не должно» |
Вывод: критический путь не блокируется бэкендом; SR-MAP-BE-02 — отдельный инкремент для демо «как Яндекс» без PMTiles.
3.3. Инфра (k3s/, сборка данных)¶
| ID | Требование | Детали |
|---|---|---|
| SR-MAP-INF-01 | Артефакты карт хранятся в MinIO бакет aerozveno-frontend, префикс tiles/, раздаются nginx как https://aerozveno.ff/tiles/…. Не emptyDir как единственная копия. |
k3s/aerozveno/45-deployment-frontend.yaml |
| SR-MAP-INF-02 | Nginx: для location /tiles/ — try_files $uri =404, add_header Accept-Ranges bytes, gzip off для .pmtiles, кеш Cache-Control: public, max-age=86400 (обновление — новый hash-имя файла или rollout restart после mirror). Правка k3s/aerozveno/45-deployment-frontend.yaml (ConfigMap geoscan-frontend-nginx). |
PMTiles Range |
| SR-MAP-INF-03 | Отдельный образ тайл-сервера не разворачивается для PMTiles. Planetiler/rio-rgbify — Job/CronJob вне hot path или ручной запуск на n1; скрипт scripts/build-map-tiles.sh (создать). |
01-research.md §6.1 |
| SR-MAP-INF-04 | Хост tiles.ff (предложение): второй Ingress на n1 (тот же nginx/MinIO или read-only mirror). DNS правится в CoreDNS на n2 (cm/ff-coredns, ns ff-dns): A-запись tiles.ff = тот же tailnet-IP, что у aerozveno.ff (на момент ревью dig +short aerozveno.ff → 100.66.249.6, n1 / nubble01; не IP n2 100.119.7.25 / geoscan.ff). Назначение — разгрузить основной бандл и отдавать ЦФО/РФ extract (>1 ГБ) без смешения с SPA. Фронт на первой фазе может оставаться на /tiles/ same-origin; tiles.ff — для geoscan.ff и CI mirror. |
BR-MAP-20; бюджет диска n1 |
| SR-MAP-INF-05 | Объём (бюджет): демо bbox s04 ~14×14 км: basemap.pmtiles < 100 МБ, terrain.pmtiles < 20 МБ; ЦФО ~0,5–2 ГБ + 5–8 ГБ temp при сборке; РФ ~3–8 ГБ PMTiles при необходимости (n1 ~800 ГБ свободно — вне репо, допущение задания). |
01-research.md §2.2 |
| SR-MAP-INF-06 | Node placement: сборка и MinIO остаются на n1 (aerozveno). n2 (geoscan.ff) на M1 без фронта — при переносе продукта тайлы реplicate/mc mirror на n2, не пересборка planet. |
docs/task5/95-deploy/frontend-on-aerozveno.md |
| SR-MAP-INF-07 | Мониторинг (docs/05-monitoring.md): (1) HTTP probe (blackbox или эквивалент в Prometheus) на https://aerozveno.ff/tiles/basemap.pmtiles — после SR-MAP-INF-02 ожидается 200/206 и Content-Type не text/html; пока файла нет — 404, но не SPA-fallback index.html; (2) алерт AerozvenoTilesProbeFailed: probe_success{instance=~".*aerozveno.ff/tiles/basemap.pmtiles.*"} == 0 sustained 10m или (до появления probe) AerozvenoFrontendDown: up{job="aerozveno",component="frontend"} == 0 — по образцу k3s/monitoring/aerozveno.rules.yml; (3) панель на k3s/monitoring/grafana/dashboards/aerozveno.json — sum(rate(nginx_http_requests_total{job="aerozveno"}[5m])) (stub_status без лейблов uri/status). Не использовать PromQL с несуществующими лейблами stub_status. |
k3s/monitoring/prometheus-aerozveno-scrape.yaml, k3s/monitoring/aerozveno.rules.yml |
| SR-MAP-INF-08 | Опциональный satellite.pmtiles (Sentinel-2 bbox) — тот же каталог tiles/, лимит демо < 200 МБ. |
BR-MAP-04 |
Образ (если когда-либо понадобится dedicated tiles pod): nginx:alpine + volume mount read-only; не MapTiler On-Prem. Для SR-MAP-INF-04 достаточно второго Service+Ingress на существующий nginx или MinIO console bypass.
3.4. Симулятор (simulate/)¶
| ID | Требование | Детали |
|---|---|---|
| SR-MAP-SIM-01 | docker compose up без интернета: UI карты использует те же PMTiles, что планировщик (basemap.pmtiles, terrain.pmtiles), mapBasemap жёстко pmtiles. |
BR-MAP-10, SIM-BIZ-09, simulate/docs/90-final/02-requirements-registry.md |
| SR-MAP-SIM-02 | Файлы лежат в simulate/web/public/tiles/ (или volume, монтируемый в образ web при сборке stand); стиль — расширение simulate/web/public/style/offline.json или simulate/web/public/style/pmtiles.json с pmtiles:// / /tiles/…. |
simulate/web/src/components/map/RunMap2D.tsx, simulate/web/src/test/offline.test.ts |
| SR-MAP-SIM-03 | simulate/web/src/components/map/RunMap2D.tsx: регистрация протокола pmtiles, attributionControl: true, тест simulate/web/src/test/offline.test.ts обновлён: нет http в стиле, наличие sources terrain. |
SR-MAP-FE-02, BR-MAP-10, MAP-R-13 |
| SR-MAP-SIM-04 | World loader (контракт Б-7): физика — GeoTIFF float32 EPSG:4326 (спецификация: demo_glo30_30m.tif, bbox WGS84 [37.4, 55.6, 37.9, 55.9] — см. simulate/fixtures/mirror/manifest.json и real-geodata.md, fix4/sim-geodata), ENU-кэш demo_glo30_enu_30m.npz, мир demo-glo30, source_id=copernicus_glo30. terrain.pmtiles / basemap.pmtiles — тот же клип (единый world_snapshot_id). В ветке release/m1 mirror status: blocked — файлы datasets появятся с merge Б-7. Следующий шаг пайплайна: float32 COG в UTM — simulate/docs/10-world/01-geodata-sources.md §2.3. |
BR-MAP-14 |
| SR-MAP-SIM-05 | world_manifest.json — схема ../../simulate/src/geoscan_sim/world/manifest.py: layers (8 имён: terrain…quality), у каждого слоя source_id, release, crs, vertical_datum, resolution_m, sha256. layers.terrain.sha256 — только SHA256 GeoTIFF физики (datasets[] с source_id=copernicus_glo30, путь fixtures/mirror/datasets/demo_glo30_30m.tif); runtime Б-7 выставляет его из этой записи, не из PMTiles. layers.basemap.sha256 — целевой SHA256 basemap.pmtiles (source_id=osm_odbl); в Б-7 слой synthetic (пустой sha). terrain.pmtiles — отдельная запись в fixtures/mirror/manifest.json → datasets[] (тот же bbox/клип, свой path, отдельный source_id, например terrain_rgb_pmtiles / производный rgbify), sha в datasets[].sha256, без перезаписи layers.terrain. Top-level поля dem_source / basemap_source не вводятся. |
BR-MAP-14, SIM-GEO-17 |
| SR-MAP-SIM-06 | Compose не тянет внешние URL при старте web-контейнера; проверка — расширить offline.test.ts + e2e «нет DNS lookup на openfreemap/yandex» в CI simulate. |
SIM-BIZ-09 |
4. Контракты данных¶
4.1. URL и протоколы¶
| Контракт | Значение | Примечание |
|---|---|---|
| Basemap vector | pmtiles:///tiles/basemap.pmtiles |
MapLibre + protocol handler |
| DEM | pmtiles:///tiles/terrain.pmtiles, type: raster-dem, encoding: mapbox |
Декод: h = −10000 + (R·65536 + G·256 + B) · 0,1 |
| Satellite (opt.) | pmtiles:///tiles/satellite.pmtiles |
Raster или RGB в MVT-схеме |
| Glyphs (opt.) | /tiles/glyphs/{fontstack}/{range}.pbf |
Protomaps/OpenMapTiles layout |
| Sprite (opt.) | /tiles/sprite + @2x.json/png |
|
| OpenFreeMap phase 0 | https://tiles.openfreemap.org/styles/dark |
только openfreemap |
| Yandex fallback | /api/tiles/yandex/{z}/{x}/{y}.png или Tiles API с referrer |
web_mercator обязателен |
4.2. Проекции и CRS¶
| Слой | CRS | Правило |
|---|---|---|
| MapLibre view, deck.gl overlay, тайлы OSM/DEM | EPSG:3857 (display) | Яндекс Tiles — только projection=web_mercator |
| Сценарий, KML/GeoJSON export, API | EPSG:4326 (WGS84, RFC 7946 порядок [lon, lat]) |
40-formats/03-our-mission-schema.md |
| Редактирование, буферы, азимут галсов | UTM (зона по центроиду сценария, поле в схеме) | 50-stack/04-frontend-3d.md:121-128 |
| DEM симулятора (Б-7 сейчас) | EPSG:4326 GeoTIFF float32 + ENU-кэш; runtime sampling в LOCAL_ENU | simulate/fixtures/mirror/manifest.json (bbox), спецификация файлов — real-geodata.md (fix4/sim-geodata) |
| DEM симулятора / backend sampling (целевой пайплайн) | UTM зона сценария, vertical EGM2008 (GLO-30) | 01-geodata-sources.md §2.3 — следующий шаг после Б-7, не текущий файл |
Инвариант: координаты в deck.gl layers — WGS84; MapLibre проецирует в 3857; geo.ts/makeProjector — единственное место локальной метрической геометрии (MAP-R-06).
4.3. DEM terrain-RGB¶
| Поле | Тип | Описание |
|---|---|---|
encoding |
'mapbox' |
MapLibre raster-dem |
tileSize |
256 |
|
maxzoom |
≤ 14 для bbox 14 км |
~z12 достаточно (92-code/03-map-and-3d.md) |
| nodata | не кодировать в RGB | клип по bbox до rgbify |
4.4. GeoJSON зон (сценарий, не тайловый слой)¶
Схема — фрагмент airspace в 40-formats/03-our-mission-schema.md:
{
"allowed": { "type": "Polygon", "coordinates": [ [ [lon, lat], ... ] ] },
"no_fly": [
{
"geometry": { "type": "Polygon", "coordinates": [ [ [lon, lat], ... ] ] },
"altitude_min_m": 0,
"altitude_max_m": 500,
"zone_kind": "no_fly",
"valid_from": null,
"valid_to": null
}
]
}
На карте: allowed — штрих layers.ts id allowed; NFZ — nfz, nfz-buffer с safety_buffer_m из objective. Отдельной tile-схемы для зон нет.
erDiagram
SCENARIO ||--|| AIRSPACE : contains
SCENARIO ||--o{ TASK : tasks
AIRSPACE ||--|| ALLOWED_POLYGON : allowed
AIRSPACE ||--o{ NFZ : no_fly
BASEMAP_PMTILES ||--o{ VECTOR_TILE : encodes
TERRAIN_PMTILES ||--o{ DEM_TILE : terrain_rgb
MAP_STYLE ||--|| BASEMAP_PMTILES : source_osm
MAP_STYLE ||--|| TERRAIN_PMTILES : source_terrain
WORLD_MANIFEST ||--|| GLO30_GEOTIFF : layers_terrain_sha256
WORLD_MANIFEST ||--|| BASEMAP_PMTILES : layers_basemap_sha256
WORLD_MANIFEST ||--o{ DATASET_MIRROR : datasets
GLO30_GEOTIFF ||--|| TERRAIN_PMTILES : same_clip_rgbify
GLO30_GEOTIFF ||--o| COG_UTM_TARGET : pipeline_step_2_3
SCENARIO {
string schema_version
json airspace
json tasks
}
ALLOWED_POLYGON {
string type "Polygon"
json coordinates "EPSG:4326"
}
NFZ {
json geometry
number altitude_min_m
number altitude_max_m
string zone_kind
}
BASEMAP_PMTILES {
string url "/tiles/basemap.pmtiles"
string attribution "OSM ODbL"
}
TERRAIN_PMTILES {
string url "/tiles/terrain.pmtiles"
string encoding "mapbox"
}
MAP_STYLE {
int version "8"
string glyphs "optional /tiles/glyphs/..."
}
WORLD_MANIFEST {
string source_id "copernicus_glo30 demo-glo30"
json layers "terrain basemap quality..."
string world_snapshot_id
}
GLO30_GEOTIFF {
string spec "real-geodata.md B-7"
string crs "EPSG:4326"
json bbox "manifest.json 37.4-55.9"
string path "datasets/demo_glo30_30m.tif"
}
DATASET_MIRROR {
string source_id "copernicus_glo30 terrain_rgb_pmtiles..."
string path "datasets/ or tiles/"
string sha256 "per-file hash"
}
COG_UTM_TARGET {
string crs "EPSG:326xx"
string status "planned 01-geodata-sources 2.3"
}
5. Офлайн-режим¶
5.1. Что входит в пакет (демо-регион s04)¶
| Компонент | Включать | Оценка размера |
|---|---|---|
| MapLibre style JSON (без внешних URL) | да | < 50 КБ |
basemap.pmtiles (OSM vector, bbox ~14×14 км) |
да | 30–80 МБ |
terrain.pmtiles (GLO-30 terrain-RGB, тот же bbox) |
да | 5–20 МБ |
satellite.pmtiles |
опционально | до 200 МБ |
| glyphs + sprite | нет в минимальном пакете | 0 (или +20 МБ полный) |
| JS бандл planner/shell | уже в SPA / simulate web | без изменения логики |
Итого минимальный offline-пакет: ~50–100 МБ; с опциональным спутником — до ~300 МБ.
5.2. Переключение на фронте¶
flowchart TD
start[load config.json] --> mode{mapBasemap}
mode -->|pmtiles| reg[register pmtiles protocol]
reg --> fetch[HEAD /tiles/basemap.pmtiles]
fetch -->|200| full[BASEMAP_STYLE + terrain]
fetch -->|fail| fb[OFFLINE_STYLE + banner]
mode -->|openfreemap| ofm[external style URL]
mode -->|yandex-tiles| ya[raster + own DEM pmtiles]
full --> dem[DemTerrain.preload scenario bbox]
dem --> ready[interleaved overlay on load]
fb --> deck[deck.gl layers only]
- Shell читает
/config.json(src/frontend/apps/shell/src/runtime-config.ts). - Planner строит стиль по
mapBasemap. - При
pmtiles— проверка доступности архива; при сбое — SR-MAP-FE-08. DemTerrain.preload— параллельно отрисовке карты; ошибка DEM — banner, профиль наSyntheticTerrainзапрещён на prod-стенде (только dev/mock).
5.3. Изолированный стенд симулятора¶
- Те же файлы в
simulate/web/public/tiles/, стиль указывает/tiles/…. docker composeweb-сервис (когда появится в stand-docker) монтирует volume read-only; нет переменных с API-ключами карт.- Согласование с Б-7 (
real-geodata.md, fix4/sim-geodata):layers.terrain.sha256= sha GeoTIFFdemo_glo30_30m.tif(copernicus_glo30);layers.basemap.sha256= shabasemap.pmtiles(целевое);terrain.pmtilesиbasemap.pmtilesв mirror — отдельные строкиdatasets[]со своимиpath/sha256(PMTiles не подменяютlayers.terrain). GeoTIFF GLO-30 — тот же клип, что feed дляrio-rgbify→terrain.pmtiles.
6. Последовательность загрузки (runtime)¶
sequenceDiagram
participant U as Пользователь
participant S as shell config.json
participant M as MapCanvas
participant N as nginx /tiles
participant P as pmtiles protocol
participant D as DemTerrain
participant G as deck.gl layers.ts
U->>S: открыть /plan
S->>M: mapBasemap pmtiles
M->>M: addProtocol pmtiles
M->>N: GET basemap.pmtiles Range bytes=0-16383
N-->>P: 206 partial
P-->>M: vector tiles
M->>N: GET terrain.pmtiles Range
N-->>M: DEM tiles
M->>M: map load interleaved true
M->>G: MapboxOverlay layers
par preload
M->>D: preload scenario bbox
D->>N: Range DEM tiles
end
G-->>U: NFZ swaths routes on terrain
7. План миграции (независимые инкременты)¶
Оценка — агенто-заходы (1 заход ≈ ревьюируемый PR).
| Шаг | Содержание | Заходов | Зависимости | Риски |
|---|---|---|---|---|
| M0 | mapBasemap в runtime + фаза 0 OpenFreeMap на aerozveno only + attributionControl: true |
1 | — | MAP-R-13 красный до исключения OFM; жюри видит внешний CDN |
| M1 | scripts/build-map-tiles.sh, выкладка demo bbox в MinIO, nginx /tiles/ + Range |
2 | доступ n1, Geofabrik | сборка Planetiler OOM — уменьшить bbox; mirror без Range ломает PMTiles |
| M2 | MAP-07 TerrainSource + DemTerrain.preload |
2 | M1 terrain file | рассинхрон bbox COG/sim vs rgbify |
| M3 | MAP-08 стиль pmtiles, protocol, interleaved, SR-MAP-FE-08 fallback | 2 | M2 | забыть load before overlay — рельеф плоский |
| M4 | Simulate: public/tiles + стиль + offline tests | 1 | M1 artifacts | большой git LFS — не коммитить, только CI artifact |
| M5 | MAP-09 contours, optional satellite layer | 1 | M3 | perf на слабом GPU |
| M6 | SR-MAP-BE-02 Yandex proxy (optional) | 1 | creds-store ключ | 30 RPS лимит; блокировка без интернета |
| M7 | SR-MAP-INF-04 tiles.ff + мониторинг алертов |
1 | M1 | DNS ff-coredns забыли — 404 на geoscan.ff |
| M8 | ЦФО extract вместо demo bbox | 1 | M7 disk | 8 ГБ temp заполнил диск n1 |
Текущее состояние (baseline): OFFLINE_STYLE без sources (src/frontend/apps/planner/src/map/MapCanvas.tsx:20-24), attributionControl: false, interleaved: false, синтетика src/frontend/packages/domain/src/terrain.ts, simulate simulate/web/public/style/offline.json пустой.
8. Критерии приёмки (действие → доказательство)¶
| ID | Критерий приёмки |
|---|---|
| SR-MAP-FE-01 | На aerozveno в DevTools → Network видно GET /config.json; смена mapBasemap без пересборки бандла меняет источник карты после F5. |
| SR-MAP-FE-02 | В консоли нет ошибки «Unknown protocol pmtiles»; cd src/frontend && npm test проходит (регистрация протокола покрыта unit/integration в planner map). |
| SR-MAP-FE-03 | cd src/frontend && npx vitest run apps/planner/src/map/basemap-style.spec.ts — MAP-R-13 green для pmtiles. |
| SR-MAP-FE-04 | При mapBasemap: openfreemap карта грузит liberty/dark; тест MAP-R-13-OFM помечен @openfreemap-only. |
| SR-MAP-FE-05 | В UI виден блок атрибуции © OpenStreetMap и текст Copernicus; скриншот для отчёта. |
| SR-MAP-FE-06 | На DEM: incline map pitch 60°, swath дraped; борт за хребтом перекрыт рельефом (checklist MAP-08). |
| SR-MAP-FE-07 | Toggle «Спутник» меняет видимость слоя; без сети спутник доступен только если satellite.pmtiles локален. |
| SR-MAP-FE-08 | curl -I https://aerozveno.ff/tiles/basemap.pmtiles → 404 или offline nginx: UI показывает banner, deck-слои NFZ видны. |
| SR-MAP-FE-09 | Симуляция 503 от proxy Yandex: сетка ошибки, приложение не падает. |
| SR-MAP-FE-10 | Стиль без glyphs URL — карта открывается; подписи сценария от TextLayer. |
| SR-MAP-FE-11 | s01-simple: профиль высот в drawer не плоский линия 112…288 синтетики — значения совпадают с DemTerrain ±0,1 м на 3 контрольных точках. |
| SR-MAP-FE-12 | git grep pmtiles не находит .pmtiles в tracked files. |
| SR-MAP-FE-13 | CI job frontend: MAP-R-13 green. |
| SR-MAP-BE-01 | curl https://aerozveno.ff/tiles/basemap.pmtiles -H 'Range: bytes=0-100' -I — 206 от nginx, не от gateway. |
| SR-MAP-BE-02 | curl -o /dev/null -w '%{http_code}' 'https://aerozveno.ff/api/tiles/yandex/10/618/320.png' → 200 при настроенном ключе; в ответе нет заголовка с apikey. |
| SR-MAP-BE-03 | Code review: нет disk cache backend для Yandex tiles. |
| SR-MAP-BE-04 | GET /api/scenarios/{id} — зоны только в JSON, отдельного /zones.mvt нет. |
| SR-MAP-BE-05 | grep gateway — нет elevationAt, DEM routes. |
| SR-MAP-INF-01 | mc ls minio/aerozveno-frontend/tiles/ показывает pmtiles после сборки. |
| SR-MAP-INF-02 | Response headers: Accept-Ranges: bytes, partial content 206. |
| SR-MAP-INF-03 | scripts/build-map-tiles.sh --bbox s04 exit 0 на n1; образ tileserver не в k3s/aerozveno/kustomization.yaml. |
| SR-MAP-INF-04 | После A-записи tiles.ff → IP aerozveno.ff: curl --cacert ff-ca/ff-ca.crt -I https://tiles.ff/tiles/basemap.pmtiles → 200/206 (или 404 без text/html до выкладки файла). |
| SR-MAP-INF-05 | du -sh каталога tiles в пределах бюджета шага M1/M8. |
| SR-MAP-INF-06 | kubectl --context n1 get pods -n aerozveno -o wide — frontend на n1. |
| SR-MAP-INF-07 | В Prometheus загружен probe (или правило AerozvenoFrontendDown); kubectl --context n1 -n monitoring get prometheusrule содержит AerozvenoTilesProbeFailed или эквивалент; probe_success==1 для URL PMTiles после M1+INF-02. |
| SR-MAP-INF-08 | Toggle спутника + файл satellite.pmtiles present. |
| SR-MAP-SIM-01 | На машине без default route: docker compose up + открыть web UI — рельеф виден, Network tab без внешних hosts карт. |
| SR-MAP-SIM-02 | ls simulate/web/public/tiles/*.pmtiles после make target. |
| SR-MAP-SIM-03 | cd simulate/web && npm test -- offline.test.ts — green. |
| SR-MAP-SIM-04 | Для мира demo-glo30: sha256sum simulate/fixtures/mirror/datasets/demo_glo30_30m.tif совпадает с layers.terrain.sha256 в world_manifest.json (или mirror manifest); sha256sum simulate/web/public/tiles/terrain.pmtiles — с sha256 записи datasets[], у которой path указывает на этот PMTiles (отдельный source_id, не copernicus_glo30). |
| SR-MAP-SIM-05 | Два прогона sim с одним manifest — одинаковые layers.terrain.source_id и sha256 в отчёте. |
| SR-MAP-SIM-06 | CI simulate: test «no external map hosts». |
9. Трассировка BR-MAP → SR-MAP¶
Источник ID и формулировок: docs/task5/60-maps/02-business-requirements.md (ветка fix4/maps-biz-req).
| BR-MAP | Смысл (кратко) | SR-MAP |
|---|---|---|
| 01 | Ориентация на местности, топосъёмка | FE-03, FE-10, INF-01 |
| 02 | Навигация (масштаб, панорама) | (поведение MapLibre по умолчанию; MAP-R-16 — без отдельного SR) |
| 03 | Поиск / flyTo по координатам | (Could в BR; отдельный SR не в scope карт M1 — UI planner) |
| 04 | Спутник / схема | FE-07, INF-08 |
| 05 | Область съёмки на подложке | (deck.gl + MAP-05; подложка — FE-03) |
| 06 | ВПП на карте | (MAP-05; подложка — FE-03) |
| 07 | БПЗ и ВП поверх подложки | (layers.ts; подложка — FE-03) |
| 08 | Терренный рельеф, AGL | FE-03, FE-06, FE-11 |
| 09 | Изолинии | M5 / MAP-09 (SR через FE-06 после MAP-08) |
| 10 | Офлайн-демо | FE-01, FE-08, FE-12, FE-13, SIM-01…06, INF-03 |
| 11 | OpenFreeMap фаза 0 | FE-04 |
| 12 | Яндекс Tiles fallback | FE-09, BE-02, BE-03 |
| 13 | KML / WGS-84 без сдвига | FE-13, MAP-R-06 (src/frontend/packages/domain/src/geo.ts) |
| 14 | Тот же рельеф/bbox для sim | SIM-04, SIM-05, INF-01 |
| 15 | Атрибуция | FE-05, SIM-03 |
| 16 | Секреты карт | FE-01, BE-02 |
| 17 | План на карте после расчёта | (MAP-02/03 + FE-03; deck.gl) |
| 18 | Производительность | FE-06 |
| 19 | Ограничения точности DEM | (документ limitations; DemTerrain badge — вне SR-MAP) |
| 20 | Сборка и поставка тайлов | INF-03, INF-04, INF-05, FE-12, M1, M8 |
10. Открытые точки¶
- BR-MAP-02/03/05…07/17 — в основном закрываются существующими MAP-xx и deck.gl; при появлении отдельных SR в planner их добавить с трассировкой из
02-business-requirements.md. - HTTP probe PMTiles — модуль blackbox в кластере n1 может потребовать отдельного scrape config (сейчас только stub_status nginx).
- quantized-mesh для Cesium (MAP-11) — второй артеfact из того же GLO-30, в scope SR-MAP только упоминание; детали DEV-02.
- Свободное место n1 не автоматизировано — перед M8 ручной
df -h.
Документ согласован с 02-business-requirements.md (fix4/maps-biz-req) и Б-7 real-geodata.md (fix4/sim-geodata).