feat: add project-scoped operations
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
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),
|
||||
)
|
||||
Reference in New Issue
Block a user