import { requestWorkbenchJson } from "./request-json"; import { z } from "zod"; import type { WorkbenchAccess } from "./request-context"; import { type PlaceSearchConfiguration } from "./project-client"; export const MAX_PLACE_SEARCH_RESULTS = 10; const placeSchema = z.object({ id: z.string().min(1), name: z.string().trim().min(1), address: z.string(), longitude: z.number().finite().min(-180).max(180), latitude: z.number().finite().min(-90).max(90), category: z.string().nullable(), district: z.string().trim().min(1) }); const resultsSchema = z.object({ items: z.array(placeSchema).max(MAX_PLACE_SEARCH_RESULTS), total: z.number().int().min(0).max(MAX_PLACE_SEARCH_RESULTS), truncated: z.boolean().default(false), region: z.string().trim().min(1) }); export type PlaceSearchItem = z.infer; export type PlaceSearchResults = z.infer; export async function searchPlaces( keyword: string, projectId: string | null, getAccessToken?: WorkbenchAccess, signal?: AbortSignal, configuration?: PlaceSearchConfiguration | null ): Promise { const query = keyword.trim(); if (!query) throw new Error("请输入需要搜索的地址或地名"); if (query.length > 120) throw new Error("查询关键字不能超过120个字符"); const payload = await requestPlaceData("place-search-requests", { keyword: query }, projectId, getAccessToken, signal); const parsed = resultsSchema.safeParse(payload); if (!parsed.success || new Set(parsed.data.items.map((item) => item.id)).size !== parsed.data.items.length || parsed.data.total !== parsed.data.items.length || (configuration && (parsed.data.region !== configuration.region || (!configuration.admin_code.endsWith("00") && parsed.data.items.some((item) => item.district !== configuration.region))))) { throw new Error("未获得有效的地名搜索结果,请稍后重试"); } return parsed.data; } export async function reverseGeocodePoint( longitude: number, latitude: number, projectId: string | null, getAccessToken?: WorkbenchAccess, signal?: AbortSignal, configuration?: PlaceSearchConfiguration | null ): Promise { const coordinates = z.object({ longitude: z.number().finite().min(-180).max(180), latitude: z.number().finite().min(-90).max(90) }).safeParse({ longitude, latitude }); if (!coordinates.success) throw new Error("地图取点坐标无效"); const payload = await requestPlaceData("reverse-geocoding-requests", coordinates.data, projectId, getAccessToken, signal); const parsed = placeSchema.safeParse(payload); if (!parsed.success || parsed.data.longitude !== longitude || parsed.data.latitude !== latitude || (configuration && !configuration.admin_code.endsWith("00") && parsed.data.district !== configuration.region)) { throw new Error("未获得有效的地图取点结果,请重新取点"); } return parsed.data; } async function requestPlaceData( path: string, body: Record, projectId: string | null, getAccessToken?: WorkbenchAccess, signal?: AbortSignal ): Promise { return requestWorkbenchJson(`/api/v1/${path}`, "地名搜索", { access: getAccessToken, projectId, timeoutMs: 20_000, init: { method: "POST", body: JSON.stringify(body), signal } }); }