说明
上次已经讲了怎么简单的接入高德地图到项目中(JS 如何接入高德地图?),这里我将结合真实的业务场景来接入高德地图,并且会封装一个通用hook useAMap(),有类似业务场景的小伙伴可以拿去复用。
业务场景
在一些表单中需要为一些地点选择地址信息 包括省市区详细地址,经纬度等。
在这里把选择位置分为三个模块,选择省市区详情地址一个模块,地图一个模块,经纬度一个模块。
这里省市区和经纬度都是可以手动输入的,地图模块会有一个可以拖动的标记,可以在拖动选择位置。
场景:是当三个模块任意一个变化时,另外两个模块也要跟着变化到对应位置;
例如:
1.选择北京-北京辖区—东城区 天安门,那么地图自动更新定位到天安门,经纬度自动设置天安门经纬度。
2.地图拖动标记到 上海外滩 那么省市区详细地址自动更新到上海外滩的位置,经纬度也会变化到外滩。
3.随便输入一个有效经纬度,地址和地图也随之变化到对应位置
问题分析
首先这三个模块单独看是很好实现的,复杂点在如何实现联动。联动可以拆解下面几个问题:
- 如何通过一个地址获取经纬度
- 如何通过地址设置地图位置
- 如何通过经纬度获取到地址
- 地图图标拖动的时候如何得到经纬度和地址
这些问题其实都可以通过高德地图的插件实现。这里注意的是地图只能通过经纬度设置位置,如何地址设置地图位置,需要先将地址转化为经纬度。
hook 设计
既然以上几个问题都可以通过高德插件实现,那我们的hook肯定是要把这些功能封装进去的,除此之外hook还要包含地图的初始化等功能。
以下就是hook的大概功能
interface IUseAMapProp {
// 地图容器id
eleId: string;
// 地图标记拖动变化回调
onChang: () => void;
}
interface IUseAMapRes {
//通过经纬度获取地址信息
getAreaByLngLat: (lng: number, lat: number) => Promise<any>;
//通过地址获取经纬度
getLngLatByArea: (area: string) => Promise<any>;
//通过经纬度设置地图位置
setMapLocation: (lng: number, lat: number) => void;
}
export const useAMap = (props: IUseAMapProp): IUseAMapRes => {
const { eleId, onChang } = props;
const getAreaByLngLat = (lng: number, lat: number) => {};
const getLngLatByArea = (area: string) => {};
const setMapLocation = (lng: number, lat: number) => {};
return {
getAreaByLngLat,
getLngLatByArea,
setMapLocation
};
};
hook实现
里面的注释写的很详细
代码片段
这里省去了省市区select框的选择
code
import { useEffect } from 'react';
import AMapLoader from '@amap/amap-jsapi-loader';
import { Message } from '@alifd/next';
interface IUseAMapProp {
// 地图容器id
eleId: string;
// 地图标记拖动变化回调
onChang: (val: {
areaList: any[]; // 区域列表
address: string; // 详细地址
lng: number; // 经度
lat: number; // 纬度
}) => void;
}
interface IUseAMapRes {
//通过经纬度获取地址信息
getAreaByLngLat: ({
lng,
lat,
}: {
lng: number;
lat: number;
}) => Promise<{ areaList: any[]; address: string }>;
//通过地址获取经纬度
getLngLatByArea: (area: string) => Promise<{ lng: number; lat: number }>;
//通过经纬度设置地图位置
setMapLocation: ({ lng, lat }: { lng: number; lat: number }) => void;
}
let AmapInstance: any;
let geocoderInstance: any;
let poiMarker: any;
export const useAMap = (props: IUseAMapProp): IUseAMapRes => {
const { eleId, onChang } = props;
// 通过经纬度获取地址信息
const getAreaByLngLat = ({
lng,
lat,
}: {
lng: number;
lat: number;
}): Promise<{ areaList: any[]; address: string }> => {
return new Promise((resolve, reject) => {
geocoderInstance.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.regeocode) {
const { addressComponent, formattedAddress } = result.regeocode;
const {
adcode,
province: provinceName,
city: cityName,
district: districtName,
towncode,
township: townName,
} = addressComponent;
const _areaList = [
{
areaCode: Number(`${adcode.substring(0, 2)}0000`),
areaName: provinceName,
},
cityName && {
areaCode: Number(`${adcode.substring(0, 4)}00`),
areaName: cityName,
},
districtName && {
areaCode: Number(adcode),
areaName: districtName,
},
towncode && {
areaCode: Number(towncode.substring(0, 9)),
areaName: townName,
},
].filter(Boolean);
resolve({
areaList: _areaList,
address: formattedAddress,
});
} else {
Message.error('经纬度查询地址失败');
reject('获取地址失败');
}
});
});
};
// 通过地址获取经纬度
const getLngLatByArea = (
area: string,
): Promise<{ lng: number; lat: number }> => {
return new Promise((resolve, reject) => {
geocoderInstance.getLocation(area, (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const [item] = result.geocodes || [];
resolve({
lng: item.location.lng,
lat: item.location.lat,
});
} else {
Message.error(`未查询到地址 ${area}`);
reject('获取经纬度失败');
}
});
});
};
// 通过经纬度设置地图位置和标记位置
const setMapLocation = ({ lng, lat }: { lng: number; lat: number }) => {
// 设置marker位置
poiMarker.setPosition([lng, lat]);
// 设置地图中心点
AmapInstance.setZoomAndCenter(14, [lng, lat]);
};
// 地图标记拖动事件
const handleMarkerDragEvent = async (res) => {
const { lng, lat } = res.lnglat;
// 设置地图中心点
AmapInstance.setZoomAndCenter(14, [lng, lat]);
// 获取地址
const { areaList, address } = await getAreaByLngLat({ lng, lat });
// 触发回调
onChang({ areaList, address, lng, lat });
};
// 加载高德地图脚本
const loadAMapScript = async () => {
// @ts-ignore
window._AMapSecurityConfig = {
securityJsCode: 'a054b7f5ad770dc2c3ab8d9736abd7be',
};
const AMap = await AMapLoader.load({
key: '9894b4d2641a4180e60e07419f65cab8',
version: '2.0',
plugins: [
'AMap.Geocoder', // 地址编码与逆地址编码( 通过经纬度获取地址,通过地址获取经纬度的插件)
],
});
//天安门经纬度
const initLngLat = [116.397455, 39.909187];
// 创建地图实例
AmapInstance = new AMap.Map(eleId, {
center: initLngLat,
zoom: 14, // 初始地图缩放比例
});
// 创建地理编码实例
geocoderInstance = new AMap.Geocoder({});
//设置marker(地图标记)
poiMarker = new AMap.Marker({
position: initLngLat, //初始标记在天安门
draggable: true, // 标记可拖动
});
// 监听marker拖动事件
poiMarker?.on('dragend', handleMarkerDragEvent);
// 添加marker
AmapInstance.add(poiMarker);
};
useEffect(() => {
//进入页面加载地图脚本
loadAMapScript();
return () => {
// 离开页面销毁地图
AmapInstance.destroy();
poiMarker = null;
geocoderInstance = null;
};
}, []);
return {
getAreaByLngLat,
getLngLatByArea,
setMapLocation,
};
};