Spaces:
Sleeping
Sleeping
| """VWorld Data API 2.0 ν΄λΌμ΄μΈνΈ β geomFilter=BOX() λ°©μ.""" | |
| import re | |
| import httpx | |
| from typing import Optional | |
| from api.config import VWORLD_API_KEY, VWORLD_DATA_URL, VWORLD_DOMAIN | |
| _JIMOK: dict[str, str] = { | |
| "μ ": "μ ", "λ΅": "λ΅", "κ³Ό": "κ³Όμμ", "λͺ©": "λͺ©μ₯μ©μ§", | |
| "μ": "μμΌ", "κ΄": "κ΄μ²μ§", "μΌ": "μΌμ ", "λ": "λμ§", | |
| "곡": "곡μ₯μ©μ§", "ν": "νκ΅μ©μ§", "μ£Ό": "μ£Όμ°¨μ₯", | |
| "μ ": "μ μμ§", "μ’ ": "μ’ κ΅μ©μ§", "μ¬": "μ¬μ μ§", | |
| "λ¬": "λ¬μ§", "μ‘": "μ‘μ’ μ§", "λ": "λλ‘", | |
| "μ² ": "μ² λμ©μ§", "μ ": "μ λ°©", "ν": "νμ²", | |
| "ꡬ": "ꡬ거", "μ": "μμ΄μ₯", "μ": "μλμ©μ§", | |
| "곡μ": "곡μ", "체": "체μ‘μ©μ§", | |
| } | |
| def _parse_jimok(jibun: str) -> tuple[str | None, str | None]: | |
| """'161λ' β ('λ', 'λμ§') ννλ‘ μ§λͺ©μ½λ/μ΄λ¦ νμ±.""" | |
| m = re.search(r'([κ°-ν£]+)$', jibun or '') | |
| if not m: | |
| return None, None | |
| code = m.group(1) | |
| return code, _JIMOK.get(code, code) | |
| _DELTA = 0.001 # ~111m bbox half-width | |
| def _box_filter(lat: float, lon: float, delta: float = _DELTA) -> str: | |
| return f"BOX({lon-delta},{lat-delta},{lon+delta},{lat+delta})" | |
| def _get(data: str, geom_filter: str, attr_filter: str = "", size: int = 5) -> list[dict]: | |
| if not VWORLD_API_KEY: | |
| return [] | |
| params = { | |
| "service": "data", | |
| "request": "GetFeature", | |
| "data": data, | |
| "key": VWORLD_API_KEY, | |
| "domain": VWORLD_DOMAIN, | |
| "format": "json", | |
| "size": str(size), | |
| "page": "1", | |
| "geometry": "true", | |
| "attribute": "true", | |
| "crs": "EPSG:4326", | |
| "geomFilter": geom_filter, | |
| } | |
| if attr_filter: | |
| params["attrFilter"] = attr_filter | |
| try: | |
| r = httpx.get(VWORLD_DATA_URL, params=params, timeout=10) | |
| r.raise_for_status() | |
| body = r.json() | |
| fc = ( | |
| body.get("response", {}) | |
| .get("result", {}) | |
| .get("featureCollection", {}) | |
| ) | |
| return fc.get("features", []) | |
| except (httpx.TimeoutException, httpx.RequestError, httpx.HTTPStatusError, ValueError, KeyError): | |
| return [] | |
| def fetch_parcel_at(lat: float, lon: float) -> Optional[dict]: | |
| """μ’νμμ κ°μ₯ κ°κΉμ΄ νμ§ λ°ν (LP_PA_CBND_BUBUN).""" | |
| features = _get("LP_PA_CBND_BUBUN", _box_filter(lat, lon)) | |
| if not features: | |
| return None | |
| props = features[0].get("properties", {}) | |
| jimok_code, jimok_name = _parse_jimok(props.get("jibun", "")) | |
| return { | |
| "pnu": props.get("pnu"), | |
| "jimok": jimok_code, | |
| "jimok_name": jimok_name, | |
| "area_m2": None, # LP_PA_CBND_BUBUN λ μ΄μ΄μ λ©΄μ νλ μμ | |
| "address": props.get("addr"), | |
| } | |
| def fetch_regulatory_zones(lat: float, lon: float) -> dict: | |
| """κ·μ ꡬμ 10μ’ ν¬ν¨ μ¬λΆ λ°ν. True = κ·μ μ μ©.""" | |
| bbox = _box_filter(lat, lon) | |
| def hits(layer_id: str) -> bool: | |
| return len(_get(layer_id, bbox, size=1)) > 0 | |
| return { | |
| "agri_promotion": hits("LT_C_AGRIXUE101"), | |
| "agri_unfavorable": hits("LT_C_AGRIXUE102"), | |
| "natural_conservation": hits("LT_C_UQ114"), | |
| "wetland_protection": hits("LT_C_UM901"), | |
| "forest_protection": hits("LT_C_UF151"), | |
| "greenbelt": hits("LT_C_UD801"), | |
| "water_source": hits("LT_C_UM710"), | |
| "wildlife_protection": hits("LT_C_UM221"), | |
| "steep_slope_hazard": hits("LT_C_UP401"), | |
| "disaster_risk": hits("LT_C_UP201"), | |
| } | |