"""회사 주소 지도 — VWorld 주소검색·배경지도 타일 (2026-09-06 사용자 지시). 이미 쓰던 VWorld 키를 그대로 쓴다. 키가 화면으로 새지 않게 타일도 서버가 받아 넘긴다. """ from __future__ import annotations import asyncio import json import urllib.parse import urllib.request from typing import Any from B04_PreProcess.B04_PreProcess_Engine_VWorld import VWORLD_API_KEY _GEOCODE_URL = "https://api.vworld.kr/req/address" _TILE_URL = "http://api.vworld.kr/req/wmts/1.0.0/{key}/Base/{z}/{y}/{x}.png" def _fetch(url: str, timeout: int = 5) -> bytes: request = urllib.request.Request(url, headers={"User-Agent": "Aislo/1.0"}) with urllib.request.urlopen(request, timeout=timeout) as response: return response.read() def _geocode_sync(address: str) -> dict[str, Any] | None: """도로명으로 먼저 찾고, 없으면 지번으로 다시 찾는다.""" for address_type in ("ROAD", "PARCEL"): query = urllib.parse.urlencode( { "service": "address", "request": "getcoord", "version": "2.0", "crs": "epsg:4326", "address": address, "refine": "true", "simple": "false", "format": "json", "type": address_type, "key": VWORLD_API_KEY, } ) try: body = json.loads(_fetch(f"{_GEOCODE_URL}?{query}").decode("utf-8")) except Exception: continue point = (body.get("response") or {}).get("result", {}).get("point") if point: return {"lon": float(point["x"]), "lat": float(point["y"])} return None async def geocode_address(address: str) -> dict[str, Any] | None: return await asyncio.to_thread(_geocode_sync, address.strip()) async def fetch_base_tile(z: int, x: int, y: int) -> bytes: url = _TILE_URL.format(key=VWORLD_API_KEY, z=z, y=y, x=x) return await asyncio.to_thread(_fetch, url)