73 lines
3.4 KiB
Python
73 lines
3.4 KiB
Python
import json
|
||
from typing import Any
|
||
|
||
import httpx
|
||
|
||
from app.core.config import settings
|
||
from app.domain.schemas.place_search import PlaceSearchItem, ReverseGeocodingRequest
|
||
from app.domain.schemas.project_map_config import PlaceSearchConfig
|
||
from app.services.place_search import PlaceSearchError, PlaceSearchScopeError, validate_place_scope
|
||
|
||
|
||
async def reverse_geocode_tianditu(
|
||
request: ReverseGeocodingRequest,
|
||
*,
|
||
config: PlaceSearchConfig,
|
||
client: httpx.AsyncClient | None = None,
|
||
) -> PlaceSearchItem:
|
||
if not config.enabled:
|
||
raise PlaceSearchError(503, "当前项目暂未配置地名搜索")
|
||
if not settings.TIANDITU_TOKEN:
|
||
raise PlaceSearchError(503, "地名搜索服务未配置")
|
||
params = {
|
||
"postStr": json.dumps({"lon": request.longitude, "lat": request.latitude, "ver": 1}),
|
||
"type": "geocode",
|
||
"tk": settings.TIANDITU_TOKEN,
|
||
}
|
||
try:
|
||
if client is not None:
|
||
response = await client.get(settings.TIANDITU_GEOCODER_URL, params=params)
|
||
else:
|
||
async with httpx.AsyncClient(timeout=settings.TIANDITU_SEARCH_TIMEOUT_SECONDS) as managed:
|
||
response = await managed.get(settings.TIANDITU_GEOCODER_URL, params=params)
|
||
response.raise_for_status()
|
||
except httpx.TimeoutException:
|
||
raise PlaceSearchError(504, "地图取点查询超时,请重新取点") from None
|
||
except httpx.HTTPError:
|
||
raise PlaceSearchError(502, "天地图反编码服务暂不可用,请重新取点") from None
|
||
try:
|
||
return _parse_reverse_response(response.json(), request, config)
|
||
except PlaceSearchScopeError:
|
||
raise PlaceSearchError(422, f"取点位置不在当前项目的地名搜索范围内({config.region})") from None
|
||
except (ValueError, TypeError, KeyError, AttributeError):
|
||
raise PlaceSearchError(502, "天地图返回了无效的反编码结果,请重新取点") from None
|
||
|
||
|
||
def _parse_reverse_response(
|
||
payload: dict[str, Any], request: ReverseGeocodingRequest, config: PlaceSearchConfig,
|
||
) -> PlaceSearchItem:
|
||
if str(payload.get("status")) != "0":
|
||
raise PlaceSearchError(502, "天地图反编码请求失败,请重新取点")
|
||
result = payload.get("result")
|
||
if not result:
|
||
raise PlaceSearchError(404, "该位置未找到地名或地址,请重新取点")
|
||
address = result.get("formatted_address")
|
||
if not isinstance(address, str) or not address.strip():
|
||
raise PlaceSearchError(404, "该位置未找到地名或地址,请重新取点")
|
||
component = result.get("addressComponent") or {}
|
||
validate_place_scope(
|
||
config, county_code=str(component.get("county_code") or ""),
|
||
county=str(component.get("county") or ""),
|
||
province_code=str(component.get("province_code") or ""),
|
||
)
|
||
name = component.get("poi") or component.get("road") or address
|
||
if not isinstance(name, str) or not name.strip():
|
||
raise ValueError("Invalid place name")
|
||
# A nearby POI can have a different center. Always retain the clicked point.
|
||
return PlaceSearchItem(
|
||
id=f"map:{request.longitude:.8f},{request.latitude:.8f}",
|
||
name=name.strip(), address=address.strip(), longitude=request.longitude, latitude=request.latitude,
|
||
category="附近地名" if component.get("poi") else "地址",
|
||
district=str(component.get("county") or config.region),
|
||
)
|