{
  "schema_version": 1,
  "scope": "Current code's data transformations, not an inventory of operational imports.",
  "operational_manifest_required": true,
  "database_licence": "https://opendatacommons.org/licenses/odbl/1-0/",
  "osm": {
    "original_data": "https://download.geofabrik.de/",
    "tag_rules": [
      [
        "inauspicious",
        -12.0,
        0.85,
        1.0,
        "amenity",
        [
          "crematorium",
          "grave_yard"
        ]
      ],
      [
        "inauspicious",
        -12.0,
        0.8,
        1.5,
        "landuse",
        [
          "cemetery"
        ]
      ],
      [
        "inauspicious",
        -8.0,
        0.7,
        1.0,
        "shop",
        [
          "funeral_directors"
        ]
      ],
      [
        "inauspicious",
        -10.0,
        0.8,
        1.0,
        "power",
        [
          "substation"
        ]
      ],
      [
        "inauspicious",
        -10.0,
        0.75,
        1.5,
        "power",
        [
          "plant"
        ]
      ],
      [
        "inauspicious",
        -10.0,
        0.85,
        1.0,
        "amenity",
        [
          "prison"
        ]
      ],
      [
        "inauspicious",
        -5.0,
        0.7,
        1.0,
        "amenity",
        [
          "fuel"
        ]
      ],
      [
        "civic",
        5.0,
        0.75,
        1.0,
        "amenity",
        [
          "school"
        ]
      ],
      [
        "civic",
        4.0,
        0.7,
        1.0,
        "amenity",
        [
          "townhall",
          "police"
        ]
      ],
      [
        "park",
        8.0,
        0.7,
        1.5,
        "leisure",
        [
          "park"
        ]
      ],
      [
        "water",
        6.0,
        0.65,
        2.0,
        "natural",
        [
          "water"
        ]
      ],
      [
        "water",
        6.0,
        0.65,
        2.0,
        "waterway",
        [
          "river",
          "stream",
          "canal"
        ]
      ],
      [
        "mountain",
        6.0,
        0.7,
        3.0,
        "natural",
        [
          "peak"
        ]
      ]
    ],
    "linear_keys": [
      "waterway"
    ],
    "python_methods": [
      "def wanted_tags() -> dict[str, set[str]]:\n    \"\"\"{tag_key: {허용 값…}} — 빈 set 은 '값 무관'.\"\"\"\n    from app.services.pungsu.osm_local import _TAG_RULES\n\n    out: dict[str, set[str]] = {}\n    for _kind, _sev, _conf, _rmult, key, values in _TAG_RULES:\n        out.setdefault(key, set()).update(values)\n    return out",
      "def pick_tag(tags: dict, wanted: dict[str, set[str]]) -> Optional[tuple[str, str]]:\n    \"\"\"이 요소가 적재 대상인지 — 첫 매치 태그 1쌍을 돌려준다.\n\n    `classify_tags` 와 **같은 우선순위**(_TAG_RULES 순서)를 따라야 한다.\n    흉시설이 자연지물보다 앞이라, 공동묘지 안 녹지가 park 로 적재되지 않는다.\"\"\"\n    from app.services.pungsu.osm_local import _TAG_RULES\n\n    for _kind, _sev, _conf, _rmult, key, values in _TAG_RULES:\n        v = tags.get(key)\n        if v is None:\n            continue\n        if not values or v in values:\n            return (key, str(v))\n    return None",
      "def pick_name(tags: dict) -> Optional[str]:\n    \"\"\"표시명 — name:ko 우선(한국어 UI), 없으면 name. 없으면 None.\"\"\"\n    for k in (\"name:ko\", \"name\"):\n        v = tags.get(k)\n        if v:\n            return str(v)[:80]\n    return None",
      "def centroid(points: list[tuple[float, float]]) -> Optional[tuple[float, float]]:\n    \"\"\"정점 평균 — way/area 를 대표점 1개로 축약. 빈 입력은 None.\"\"\"\n    if not points:\n        return None\n    return (\n        sum(p[0] for p in points) / len(points),\n        sum(p[1] for p in points) / len(points),\n    )",
      "def _make_handler(wanted: dict[str, set[str]], sink: list[dict]) -> Any:\n    \"\"\"추출을 훑어 대상 요소만 sink 에 담는 핸들러.\n\n    콜백 분담(중복 없이 전 지물 커버):\n      node  — 점 지물(산 정상·학교·주유소…)\n      way   — **선형만**(수로). 면인 way 는 area 가 담당하므로 건너뛴다.\n      area  — 면 지물(묘지·공원·호수) + **멀티폴리곤 relation**까지 통합 제공.\n              from_way() 로 원 요소 종류를, orig_id() 로 원 id 를 얻는다.\n    \"\"\"\n    import osmium\n\n    class _Handler(osmium.SimpleHandler):  # type: ignore[misc]\n        _progress_every = 0  # >0 이면 그 간격으로 진행 출력\n\n        def __init__(self) -> None:\n            super().__init__()\n            self._scanned = 0\n\n        def _tick(self) -> None:\n            self._scanned += 1\n            if self._progress_every and self._scanned % self._progress_every == 0:\n                print(\n                    f\"  … {self._scanned:,} elements 스캔, 대상 {len(sink):,}건\",\n                    flush=True,\n                )\n\n        def _emit(self, osm_type: str, osm_id: int, tags: dict, lat: float, lng: float) -> None:\n            hit = pick_tag(tags, wanted)\n            if hit is None:\n                return\n            sink.append(\n                {\n                    \"osm_type\": osm_type,\n                    \"osm_id\": int(osm_id),\n                    \"tag_key\": hit[0],\n                    \"tag_value\": hit[1][:40],\n                    \"name\": pick_name(tags),\n                    \"lat\": float(lat),\n                    \"lng\": float(lng),\n                }\n            )\n\n        def node(self, n: Any) -> None:\n            self._tick()\n            if n.location.valid():\n                self._emit(\n                    \"node\", n.id, {t.k: t.v for t in n.tags}, n.location.lat, n.location.lon\n                )\n\n        def way(self, w: Any) -> None:\n            self._tick()\n            tags = {t.k: t.v for t in w.tags}\n            if not (_LINEAR_KEYS & tags.keys()):\n                return  # 면 지물은 area 콜백이 처리 — 이중 적재 방지\n            try:\n                pts = [\n                    (nd.location.lat, nd.location.lon)\n                    for nd in w.nodes\n                    if nd.location.valid()\n                ]\n            except Exception:  # 좌표 미해결 way(추출 경계 절단 등)\n                return\n            c = centroid(pts)\n            if c is not None:\n                self._emit(\"way\", w.id, tags, c[0], c[1])\n\n        def area(self, a: Any) -> None:\n            self._tick()\n            pts: list[tuple[float, float]] = []\n            try:\n                for ring in a.outer_rings():\n                    for nd in ring:\n                        if nd.location.valid():\n                            pts.append((nd.location.lat, nd.location.lon))\n            except Exception:\n                return\n            c = centroid(pts)\n            if c is None:\n                return\n            self._emit(\n                \"way\" if a.from_way() else \"relation\",\n                a.orig_id(),\n                {t.k: t.v for t in a.tags},\n                c[0],\n                c[1],\n            )\n\n    return _Handler()"
    ],
    "retained_fields": [
      "osm_type",
      "osm_id",
      "tag_key",
      "tag_value",
      "name",
      "lat",
      "lng"
    ]
  },
  "overture": {
    "original_data": "s3://overturemaps-us-west-2/release",
    "theme": "buildings",
    "type": "building",
    "selection": "bbox.xmin and bbox.ymin within the operator's bounding box; height or num_floors present",
    "lat": "ST_Y(ST_Centroid(geometry))",
    "lng": "ST_X(ST_Centroid(geometry))",
    "height_m": "COALESCE(height, num_floors * 3.3)",
    "minimum_height_m": 12.0,
    "estimated": "height IS NULL",
    "retained_fields": [
      "id",
      "lat",
      "lng",
      "height_m",
      "num_floors",
      "estimated",
      "region"
    ],
    "upstream_credits": "https://docs.overturemaps.org/attribution/#buildings"
  }
}
