8b9c908237
Co-authored-by: Cursor <cursoragent@cursor.com>
163 lines
5.4 KiB
JavaScript
163 lines
5.4 KiB
JavaScript
/** 地点搜索 v5/place/text 使用的「Web 服务」类型 Key */
|
|
export const AMAP_WEB_SERVICE_KEY = 'b43016a0d1f5b0c32211560392059708';
|
|
|
|
/**
|
|
* 高德地点关键词搜索(Web 服务 v5)。
|
|
* @see https://restapi.amap.com/v5/place/text
|
|
* @param {string} keyword 地点关键字
|
|
* @param {{ key?: string, pageSize?: number, pageNum?: number, region?: string, cityLimit?: boolean }} options
|
|
* @returns {Promise<Array>}
|
|
*/
|
|
export function searchAmapPlaceText(keyword, options = {}) {
|
|
const key = options.key || AMAP_WEB_SERVICE_KEY;
|
|
const pageSize = options.pageSize || 20;
|
|
const pageNum = options.pageNum || 1;
|
|
if (!key) {
|
|
return Promise.reject(new Error('高德地图 Key 未配置'));
|
|
}
|
|
const trimmed = String(keyword || '').trim();
|
|
if (!trimmed) {
|
|
return Promise.reject(new Error('请输入地址关键词'));
|
|
}
|
|
|
|
const params = new URLSearchParams({
|
|
key,
|
|
keywords: trimmed,
|
|
page_size: String(pageSize),
|
|
page_num: String(pageNum),
|
|
show_fields: 'photos',
|
|
});
|
|
if (options.region) {
|
|
params.set('region', options.region);
|
|
}
|
|
if (options.cityLimit != null) {
|
|
params.set('city_limit', options.cityLimit ? 'true' : 'false');
|
|
}
|
|
|
|
return fetch(`https://restapi.amap.com/v5/place/text?${params.toString()}`)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('高德地点搜索请求失败');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (String(data?.status) === '1') {
|
|
return Array.isArray(data.pois) ? data.pois : [];
|
|
}
|
|
const error = new Error(data?.info || '高德地点搜索失败');
|
|
error.code = data?.infocode;
|
|
throw error;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 将高德 Geocoder / place/text 结果规范化为地图选址浮层列表数据。
|
|
* @param {Array|Object} geocodesOrResult geocodes/pois 数组,或含 geocodes/pois/location 的结果对象
|
|
* @param {string} keyword 搜索关键词兜底文案
|
|
* @returns {Array<{id: string|number, name: string, address: string, location: any, photo: string}>}
|
|
*/
|
|
export function normalizeMapSearchResults(geocodesOrResult, keyword = '') {
|
|
const fallback = String(keyword || '').trim();
|
|
let list = [];
|
|
if (Array.isArray(geocodesOrResult)) {
|
|
list = geocodesOrResult;
|
|
} else if (geocodesOrResult && typeof geocodesOrResult === 'object') {
|
|
if (Array.isArray(geocodesOrResult.pois)) {
|
|
list = geocodesOrResult.pois;
|
|
} else if (Array.isArray(geocodesOrResult.geocodes)) {
|
|
list = geocodesOrResult.geocodes;
|
|
} else if (geocodesOrResult.location) {
|
|
list = [geocodesOrResult];
|
|
}
|
|
}
|
|
|
|
return list
|
|
.map((item, index) => {
|
|
if (!item) return null;
|
|
if (isPlacePoi(item)) {
|
|
const address = buildPoiAddress(item) || fallback || '-';
|
|
return {
|
|
id: item.id || index,
|
|
name: String(item.name || '').trim() || address,
|
|
address,
|
|
location: parseMapLocation(item.location),
|
|
photo: pickPoiPhoto(item),
|
|
};
|
|
}
|
|
|
|
const buildingName = pickNamedField(item?.building);
|
|
const neighborhoodName = pickNamedField(item?.neighborhood);
|
|
const address = item?.formattedAddress || item?.address || fallback || '-';
|
|
return {
|
|
id: item?.id || index,
|
|
name: buildingName || neighborhoodName || address,
|
|
address,
|
|
location: parseMapLocation(item?.location),
|
|
photo: item?.photo || item?.image || '',
|
|
};
|
|
})
|
|
.filter(item => item && item.location);
|
|
}
|
|
|
|
export function parseMapLocation(location) {
|
|
if (!location) return null;
|
|
if (typeof location.getLng === 'function' && typeof location.getLat === 'function') {
|
|
return location;
|
|
}
|
|
if (typeof location === 'string') {
|
|
const parts = location.split(',');
|
|
const lng = Number(parts[0]);
|
|
const lat = Number(parts[1]);
|
|
if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null;
|
|
return { lng, lat };
|
|
}
|
|
if (Number.isFinite(Number(location.lng)) && Number.isFinite(Number(location.lat))) {
|
|
return { lng: Number(location.lng), lat: Number(location.lat) };
|
|
}
|
|
if (
|
|
Number.isFinite(Number(location.longitude)) &&
|
|
Number.isFinite(Number(location.latitude))
|
|
) {
|
|
return { lng: Number(location.longitude), lat: Number(location.latitude) };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function isPlacePoi(item) {
|
|
return Boolean(item && item.name && item.location && (item.id || item.typecode || item.adname));
|
|
}
|
|
|
|
function buildPoiAddress(item) {
|
|
const detail = String(item.address || '').trim();
|
|
const region = [item.pname, item.cityname, item.adname]
|
|
.map(value => String(value || '').trim())
|
|
.filter(Boolean);
|
|
// 直辖市等场景省市名可能重复
|
|
const uniqueRegion = region.filter((value, index) => region.indexOf(value) === index);
|
|
const prefix = uniqueRegion.join('');
|
|
if (prefix && detail) {
|
|
if (detail.startsWith(prefix)) return detail;
|
|
return `${prefix}${detail}`;
|
|
}
|
|
return detail || prefix || String(item.name || '').trim();
|
|
}
|
|
|
|
function pickPoiPhoto(item) {
|
|
const photos = item?.photos;
|
|
if (Array.isArray(photos) && photos.length) {
|
|
return photos[0]?.url || '';
|
|
}
|
|
if (photos && typeof photos === 'object') {
|
|
return photos.url || '';
|
|
}
|
|
return item?.photo || item?.image || '';
|
|
}
|
|
|
|
function pickNamedField(value) {
|
|
if (!value) return '';
|
|
if (typeof value === 'string') return value.trim();
|
|
if (typeof value === 'object' && value.name) return String(value.name).trim();
|
|
return '';
|
|
}
|