ФГИС ТП Минэк: Функциональные зоны (846615) federal Генплан — bbox tiling для ЕКБ #106

Closed
opened 2026-05-12 12:10:53 +00:00 by lekss361 · 2 comments
lekss361 commented 2026-05-12 12:10:53 +00:00 (Migrated from github.com)

Эшелон: Foundation / Gate G1 supplement

Контекст

После теста theme=99 для ЕКБ (см. #101 wontfix verdict + NSPD_API_Map_May12 Update part 7) выяснилось: из 4531 слоёв theme=99 для ЕКБ работает ровно 1846615 «Функциональные зоны» от ФГИС ТП Минэкономразвития.

Это federal Генплан на уровне функциональных зон. 18 hits на 4 ключевых точках ЕКБ (Центр / Уралмаш / Эльмаш / Химмаш) = 100% покрытие.

Поля response

{
  "id": 12345,
  "geometry": { "type": "Polygon", "coordinates": [...], "crs": "EPSG:3857" },
  "properties": {
    "area": 24500,
    "obshnz": "Жилая",
    "obshoz": "Многоквартирная застройка",
    "status": "Утверждена",
    ...
  }
}

Категории obshnz:

  • Жилая
  • Производственная
  • Общественно-деловая (ОД)
  • Рекреационная
  • Сельскохозяйственная
  • Специальная / военная
  • Транспортная / инженерная инфраструктура

Что сделать

1. Backend client (~1 день)

Расширить backend/app/services/scrapers/nspd_client.py:

async def get_functional_zones_in_bbox(self, bbox_3857: tuple) -> list[FuncZone]:
    """WMS GetFeatureInfo для 846615 в bbox. Bbox tiling если bbox > 5km."""
    url = f"{self.BASE}/aeggis/v4/846615/wms"
    params = {
        "SERVICE": "WMS", "VERSION": "1.3.0", "REQUEST": "GetFeatureInfo",
        "QUERY_LAYERS": "846615", "LAYERS": "846615", "STYLES": "",
        "INFO_FORMAT": "application/json", "FEATURE_COUNT": "100",
        "CRS": "EPSG:3857", "BBOX": ",".join(map(str, bbox_3857)),
        "WIDTH": "512", "HEIGHT": "512", "I": "256", "J": "256",
    }
    r = await self._client.get(url, params=params)
    r.raise_for_status()
    return [FuncZone.from_feature(f) for f in r.json().get("features", [])]

2. Schema migration data/sql/86_nspd_functional_zones.sql

BEGIN;

CREATE TABLE IF NOT EXISTS nspd_functional_zones (
  id BIGSERIAL PRIMARY KEY,
  source_layer_id INT NOT NULL,         -- 846615 (federal) или 846381 (Свердл-РИАСУРТ)
  source_origin TEXT NOT NULL,           -- 'fgis_tp_minec' | 'riasurt_sverdl'
  obshnz TEXT,                           -- 'Жилая' | 'Производственная' | etc
  obshoz TEXT,                           -- subcategory
  area NUMERIC,
  status TEXT,
  raw_props JSONB,
  geom GEOMETRY(MULTIPOLYGON, 3857) NOT NULL,
  geom_msk66 GEOMETRY(MULTIPOLYGON, 100000) GENERATED ALWAYS AS (ST_Transform(geom, 100000)) STORED,
  fetched_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS gist_func_zones_geom_3857 ON nspd_functional_zones USING GIST(geom);
CREATE INDEX IF NOT EXISTS gist_func_zones_geom_msk66 ON nspd_functional_zones USING GIST(geom_msk66);
CREATE INDEX IF NOT EXISTS func_zones_obshnz_idx ON nspd_functional_zones(obshnz);
CREATE INDEX IF NOT EXISTS func_zones_source_idx ON nspd_functional_zones(source_origin);

COMMIT;

3. Celery task tasks.nspd_func_zones_sync

@celery.task(rate_limit="1/s")
def harvest_func_zones_bbox(bbox: tuple):
    zones = nspd.get_functional_zones_in_bbox(bbox)
    upsert_functional_zones(zones, source_layer_id=846615, source_origin="fgis_tp_minec")

Beat schedule: раз в квартал full Свердл bbox tiling (3-5 минут).

4. analyze_parcel extension

# В gate.functional_zone
func_zone = await get_func_zone_at(cad_geom)
if func_zone:
    response["gate"]["functional_zone"] = {
        "obshnz": func_zone.obshnz,           # 'Жилая'
        "obshoz": func_zone.obshoz,           # 'Многоквартирная застройка'
        "compatible_with_mkd": func_zone.obshnz in {"Жилая"},
        "source": "fgis_tp_minec_846615",
    }

Acceptance

  • nspd_functional_zones имеет >100 записей для bbox Свердл (после первого harvest)
  • obshnz distinct values содержит как минимум: «Жилая», «Производственная», «Общественно-деловая», «Рекреационная», «Сельскохозяйственная»
  • /parcels/{cad}/analyze для 5 test cad ЕКБ возвращает gate.functional_zone.obshnz непустой
  • Cross-check с тер.зонами (875838 из PKK через #94 search-by-quarter) — для 5 test cad сравнить ВРИ vs func_zone obshnz

Effort

~1 день

Owner

backend-engineer (client + analyze integration) + database-expert (schema)

Dependencies

  • #94 (NSPD client) — этот issue extension к нему
  • МСК-66 SRID 100000 — уже готов в data/sql/85_engineering_networks_191fz.sql + app/core/crs.py
  • Closes: replaces part of #101 (closed as wontfix)
  • Cross-ref: #28 G1 ПЗЗ — функ.зоны Минэк это federal supplement к локальным тер.зонам ПЗЗ
  • See: NSPD_API_Map_May12 Update part 7 — почему именно 846615 единственный работающий federal слой для ЕКБ
**Эшелон:** Foundation / Gate G1 supplement ## Контекст После теста theme=99 для ЕКБ (см. #101 wontfix verdict + [[NSPD_API_Map_May12]] Update part 7) выяснилось: **из 4531 слоёв theme=99 для ЕКБ работает ровно 1** — **846615 «Функциональные зоны»** от ФГИС ТП Минэкономразвития. Это **federal Генплан** на уровне функциональных зон. 18 hits на 4 ключевых точках ЕКБ (Центр / Уралмаш / Эльмаш / Химмаш) = 100% покрытие. ## Поля response ```json { "id": 12345, "geometry": { "type": "Polygon", "coordinates": [...], "crs": "EPSG:3857" }, "properties": { "area": 24500, "obshnz": "Жилая", "obshoz": "Многоквартирная застройка", "status": "Утверждена", ... } } ``` Категории `obshnz`: - Жилая - Производственная - Общественно-деловая (ОД) - Рекреационная - Сельскохозяйственная - Специальная / военная - Транспортная / инженерная инфраструктура ## Что сделать ### 1. Backend client (~1 день) Расширить `backend/app/services/scrapers/nspd_client.py`: ```python async def get_functional_zones_in_bbox(self, bbox_3857: tuple) -> list[FuncZone]: """WMS GetFeatureInfo для 846615 в bbox. Bbox tiling если bbox > 5km.""" url = f"{self.BASE}/aeggis/v4/846615/wms" params = { "SERVICE": "WMS", "VERSION": "1.3.0", "REQUEST": "GetFeatureInfo", "QUERY_LAYERS": "846615", "LAYERS": "846615", "STYLES": "", "INFO_FORMAT": "application/json", "FEATURE_COUNT": "100", "CRS": "EPSG:3857", "BBOX": ",".join(map(str, bbox_3857)), "WIDTH": "512", "HEIGHT": "512", "I": "256", "J": "256", } r = await self._client.get(url, params=params) r.raise_for_status() return [FuncZone.from_feature(f) for f in r.json().get("features", [])] ``` ### 2. Schema migration `data/sql/86_nspd_functional_zones.sql` ```sql BEGIN; CREATE TABLE IF NOT EXISTS nspd_functional_zones ( id BIGSERIAL PRIMARY KEY, source_layer_id INT NOT NULL, -- 846615 (federal) или 846381 (Свердл-РИАСУРТ) source_origin TEXT NOT NULL, -- 'fgis_tp_minec' | 'riasurt_sverdl' obshnz TEXT, -- 'Жилая' | 'Производственная' | etc obshoz TEXT, -- subcategory area NUMERIC, status TEXT, raw_props JSONB, geom GEOMETRY(MULTIPOLYGON, 3857) NOT NULL, geom_msk66 GEOMETRY(MULTIPOLYGON, 100000) GENERATED ALWAYS AS (ST_Transform(geom, 100000)) STORED, fetched_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS gist_func_zones_geom_3857 ON nspd_functional_zones USING GIST(geom); CREATE INDEX IF NOT EXISTS gist_func_zones_geom_msk66 ON nspd_functional_zones USING GIST(geom_msk66); CREATE INDEX IF NOT EXISTS func_zones_obshnz_idx ON nspd_functional_zones(obshnz); CREATE INDEX IF NOT EXISTS func_zones_source_idx ON nspd_functional_zones(source_origin); COMMIT; ``` ### 3. Celery task `tasks.nspd_func_zones_sync` ```python @celery.task(rate_limit="1/s") def harvest_func_zones_bbox(bbox: tuple): zones = nspd.get_functional_zones_in_bbox(bbox) upsert_functional_zones(zones, source_layer_id=846615, source_origin="fgis_tp_minec") ``` Beat schedule: раз в квартал full Свердл bbox tiling (3-5 минут). ### 4. analyze_parcel extension ```python # В gate.functional_zone func_zone = await get_func_zone_at(cad_geom) if func_zone: response["gate"]["functional_zone"] = { "obshnz": func_zone.obshnz, # 'Жилая' "obshoz": func_zone.obshoz, # 'Многоквартирная застройка' "compatible_with_mkd": func_zone.obshnz in {"Жилая"}, "source": "fgis_tp_minec_846615", } ``` ## Acceptance - [ ] `nspd_functional_zones` имеет >100 записей для bbox Свердл (после первого harvest) - [ ] `obshnz` distinct values содержит как минимум: «Жилая», «Производственная», «Общественно-деловая», «Рекреационная», «Сельскохозяйственная» - [ ] `/parcels/{cad}/analyze` для 5 test cad ЕКБ возвращает `gate.functional_zone.obshnz` непустой - [ ] Cross-check с тер.зонами (875838 из PKK через #94 search-by-quarter) — для 5 test cad сравнить ВРИ vs func_zone obshnz ## Effort ~1 день ## Owner `backend-engineer` (client + analyze integration) + `database-expert` (schema) ## Dependencies - #94 (NSPD client) — этот issue extension к нему - МСК-66 SRID 100000 — уже готов в `data/sql/85_engineering_networks_191fz.sql` + `app/core/crs.py` ## Links - Closes: replaces part of #101 (closed as wontfix) - Cross-ref: #28 G1 ПЗЗ — функ.зоны Минэк это **federal supplement** к локальным тер.зонам ПЗЗ - See: [[NSPD_API_Map_May12]] Update part 7 — почему именно 846615 единственный работающий federal слой для ЕКБ
Owner

Status update — частично реализовано. NSPD client с risk + opportunity layers готов через #94 PR1 (TIER 3 risk zones, PR #217) и #94 PR2 (TIER 4 opportunity + red lines, PR #220).

Конкретно слой 846615 «Функциональные зоны» ещё НЕ интегрирован отдельной таблицей nspd_functional_zones. Текущие данные хранятся в общем nspd_quarter_dumps + denorm tables (см. #94 PR3 — denorm nspd_parcels / nspd_buildings).

Acceptance остаётся актуальной — обособленная схема nspd_functional_zones + bbox tiling beat job ещё нужны если хотим полное Свердл покрытие. Issue корректен, остаётся в backlog.

Status update — частично реализовано. NSPD client с risk + opportunity layers готов через **#94 PR1** (TIER 3 risk zones, PR #217) и **#94 PR2** (TIER 4 opportunity + red lines, PR #220). Конкретно слой **846615 «Функциональные зоны»** ещё НЕ интегрирован отдельной таблицей `nspd_functional_zones`. Текущие данные хранятся в общем `nspd_quarter_dumps` + denorm tables (см. #94 PR3 — denorm nspd_parcels / nspd_buildings). Acceptance остаётся актуальной — обособленная схема `nspd_functional_zones` + bbox tiling beat job ещё нужны если хотим полное Свердл покрытие. Issue корректен, остаётся в backlog.
Collaborator

Superseded (ground-truth 2026-06-13). Цель (функциональные зоны федерального Генплана в analyze) достигнута ИНЫМ источником: ekb_genplan_functional_zone (6436 строк, commit #139) + functional_zone_lookup.py через EKB-геопортал WFS, а не ФГИС ТП bbox-tiling слоя 846615. Исходный подход (NSPD WMS 846615 tiling) не реализован, но функция покрыта. Закрываю как superseded; если потребуется именно федеральный ФГИС-ТП слой сверх ЕКБ-геопортала — отдельная задача.

**Superseded (ground-truth 2026-06-13).** Цель (функциональные зоны федерального Генплана в analyze) достигнута ИНЫМ источником: `ekb_genplan_functional_zone` (6436 строк, commit #139) + `functional_zone_lookup.py` через EKB-геопортал WFS, а не ФГИС ТП bbox-tiling слоя 846615. Исходный подход (NSPD WMS 846615 tiling) не реализован, но функция покрыта. Закрываю как superseded; если потребуется именно федеральный ФГИС-ТП слой сверх ЕКБ-геопортала — отдельная задача.
Sign in to join this conversation.
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#106
No description provided.