react接入高德地图,并转换GeoHash值
react接入高德地图,并转换GeoHash值
可视化中经常会遇到接入高德地图的需求,比如这个例子ryan-miao.gitee.io/geohash_too… 需求方要求苦逼的前端工作者完成一个这种需求
需求
1、点击选中一个区域,再点击就取消选中。
2、点击一个地方的时候吧周围8个方向的框也勾选上。
3、实现框选,框范围内的部分自动填充。就像这样
实现需求1
1、第一步
首先我们要引入一些包 「ngeohash」、「@amap/amap-jsapi-loader」这两个包是核心,还有一个辅助包,这个是做一些数据处理的「lodash」
好。引入之后我们首先要创建一个地图实例,要先去高德地图控制台自己申请:传送门=>「lbs.amap.com/ 」 记得要选【Web端】
**
重点强调一下:目前demo里面用到了绘制多边形的方法则在plugins里面添加AMap.PolygonEditor实例,如果想要使用别的方法(绘制矢量图形)则需要新增加【"AMap.MouseTool"】
**
// 创建一个变量,存储实例
let mapObj: any = {}; // 创建完地图之后返回的实例
let mapALl: any = {}; // 地图实例
let polyEditorObj: any = {}; // 编辑多边形的实例
let polygonObj: any; // 初始化多边形的实例
AMapLoader.load({
key: '', // 申请好的Web端开发者Key,首次调用 load 时必填
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ["AMap.PolygonEditor"]
}).then((AMap: any) => {
const map = new AMap.Map('container', { // 创建地图实例
zoom: 10, //。缩放比例
center: [116.403322, 39.920255], 选择加载完地图定位到的中心点,这里选的是北京
showIndoorMap: false,
viewMode: '2D',
});
// 绑定鼠标左键click事件
// 注意只有用on绑定的事件才能用off解绑。这个踩了坑 要记得
map.on('click', onMapClickOne);
mapObj = map;
mapALl = AMap;
// 如果是编辑状态执行,用作于编辑的时候回显
editMap(demo);
}).catch((e: any) => {
console.log(e);
});
2、第二步:创建点击过后填充内容,并展示GeoHash
ngeohash中文文档:www.npmdoc.org/ngeohashzho…
// 每次点击增加一个标注
const onMapClickOne = (e: any) => {
const { lnglat } = e;
// 将一对纬度和经度值编码为 geohash。encode将经纬度转换为geoH
// 第三个参数是可选的,你可以指定这个hash字符串的长度,这也会影响geohash的精度。
const geohashs = geohash.encode(lnglat.getLat(), lnglat.getLng(), 6);
createa([geohashs]);
};
3、第三步,创建矩形和文本实例并将其填入map地图中
问题:创建实例的时候可能会重复创建,造成不必要的性能浪费。 解决思路:通过getAllOverlays获取地图中所有的覆盖物,包括矩形、文字。因为每一个矩形对应的geoHash是唯一的,所以我们就通过geoHash去过滤。
const createa = (list: any) => {
const lists = mapObj.getAllOverlays().map((item: any) => (item._opts.text));
// 过滤掉重复的值, 如果两个方块中的geoHash相同,则就不渲染。
const compactList = list.map((item: string) => {
return lists.includes(item) ? '' : item;
})
compact(compactList).forEach((item: any, index: number) => {
const bounds = boundsPosition(item, mapALl);
// 创建矩形
const mpms = createRectangles(mapALl, bounds, item, index);
// 创建文本标注
const text = createText(mpms, mapALl, item);
mpms.on('click', deleteClick); // 绑定删除实例的方法
mapObj.add(text);
mapObj.add(mpms);
})
}
4、第四步,创建矩形、文本,转换为经纬度
转换为经纬度
// 将geoHash转换成矩形的两个对角坐标,,以确定矩形的位置
/**
*
* @param item geoHash值
* @param AMap Amap为地图实例
* @returns
*/
export const boundsPosition = (item: string, AMap: any) => {
const [southWest, northEast] = chunk(reverse(geohash.decode_bbox(item)), 2);
const bounds = new AMap.Bounds(southWest, northEast);
return bounds
}
创建矩形
/**
* @param AMap Amap为地图实例
* @param bounds 矩形两个对角的坐标值
* @param text 原对象中没有,text为新增加的属性,增加text为方便过滤重复的geoHash值 text里面为geoHash值
* @param i 原对象中没有,createId为新增加的属性 createId为索引值
* @returns
*/
export const createRectangles = (AMap: any, bounds: any, text: string, i: number) => {
return new AMap.Rectangle({
bounds: bounds,
strokeColor: 'red',
strokeWeight: 1,
strokeOpacity: 0.5,
strokeDasharray: [30, 10],
strokeStyle: 'dashed',
fillColor: 'blue',
fillOpacity: 0.5,
cursor: 'pointer',
zIndex: 50,
text: text,
createId: i,
})
}
创建文本
/**
*
* @param mpms 创建好的矩形实例
* @param AMap 地图实例
* @param item 要展示的文本
* @returns
*/
export const createText = (mpms: any, AMap: any, item: string) => {
// 获取矩形中心经纬度
const { lng, lat } = mpms.getCenter();
return new AMap.Text({
text: item,
anchor: 'center', // 设置文本标记锚点
cursor: 'pointer',
angle: 0,
style: {
'background-color': 'white',
'border-width': 0,
'text-align': 'center',
'font-size': '5px',
'color': 'blue'
},
position: [lng, lat]
});
}
结果
这样就可以实现点击一个区域这个区域高亮这样
查缺补漏
1、点击选中、点击取消
但是目前是没有办法点击同一块区域取消的,所以要在优化一下,仔细看会发现我们在创建矩形实例的时候顺便进行了一个click绑定,这样绑定就是为了点击同一块矩形区域将这快区域删除的方法
...
mpms.on('click', deleteClick); // 绑定删除实例的方法
...
我们在创建每一个矩形的时候都会给这个矩形进行做一个事件的绑定,我们在删除矩形实例的同时还要将文本实例一同删除。
const deleteClick = (e: any, text: any) => {
// 找到文本对应的实例
const lists = mapObj.getAllOverlays().filter((item: any) => (item.type === 'AMap.Text')).filter((i: any) => (i._opts.text === e.target._opts.text));
mapObj.remove(e.target);
// 删除文本实例
mapObj.remove(lists[0]);
}
2、单个和多个之间切换
好。我们目前已经完成了单个点击选中取消,选中附近范围的8个方向功能,那我们如何进行单个和多个切换呢。
const neightdiv = () => {
mapObj?.off('click', onMapClickOne); // 解绑创建一个框
mapObj?.on('click', onMapClickEight); // 绑定创建9个框
}
const onediv = () => {
mapObj?.off('click', onMapClickEight); // 解绑创建九个框
mapObj?.on('click', onMapClickOne); // 绑定创建一个框
}
实现需求2
点击一个区域周围的8个方向全都选中怎么弄呢
通过neighbors方法获取周围8个方向的geoHash
// 每次点击增加当前点,和其周围8个邻居的标注
const onMapClickEight = (e: any) => {
const { lnglat } = e;
// 将一对纬度和经度值编码为 geohash。
// 第三个参数是可选的,你可以指定这个hash字符串的长度,这也会影响geohash的精度。
const geohashs = geohash.encode(lnglat.getLat(), lnglat.getLng(), 6);
//查找[n, ne, e, se, s, sw, w, nw] geohash 字符串的所有 8 个 geohash 邻居
const eight = geohash.neighbors(geohashs);
createa([...eight, geohashs])
};
最后是这样:
实现需求3
上面点击选中、点击取消、点击一个之后周围8个区域同时选中这两个需求我们已经实现了,就剩圈地的需求了。
好。接下来我们继续完善,我选择的是点击按钮自动创建一个多边形,通过编辑这个多边形进行圈地。
1、创建多边形
// 创建初始的多边形
const createpoly = () => {
// 每一次创建多边形之前先清除原来的多边形,不然会产生冲突
polygonObj && mapObj.remove(polygonObj);
// 选定一个多边形的顶点
const path = [
[116.403322, 39.920255],
[116.410703, 39.897555],
[116.402292, 39.892353],
]
// 创建一个多边形 createPolygon为创建多边形的方法
const polygon = createPolygon(mapALl, path);
// 向地图中添加一个多边形,只有通过add方法创建的覆盖物才能通过remove方法删除
mapObj.add([polygon])
// 创建编辑状态下的多边形
const polyEditor = new mapALl.PolygonEditor(mapObj, polygon);
polyEditor.open();
polygonObj = polygon;
polyEditorObj = polyEditor;
}
创建多边形的实例方法
/**
*
* @param AMap 地图实例
* @param path 多边形各个顶点的坐标
* @returns
*/
export const createPolygon = (AMap: any, path: Array<Array<number>>) => {
return new AMap.Polygon({
path: path,
strokeColor: "#FF33FF",
strokeWeight: 6,
strokeOpacity: 0.2,
fillOpacity: 0.4,
fillColor: '#1791fc',
zIndex: 50,
bubble: true,
})
}
2、多边形编辑结束
我们圈地结束之后需要将geoHash值填充进去。
// 用户对多边形的编辑结束,创建多边形的范围的标注
const createpolyCLose = () => {
polyEditorObj.on('end', function (event: any) {
const coordinates = event.target._opts.path;
let minlat = coordinates[0][1]; // 表示最小纬度,即该区域的南侧边界;
let maxlat = coordinates[0][1]; // 表示最大纬度,即该区域的北侧边界;
let minlon = coordinates[0][0]; // 表示最大经度,即该区域的东侧边界。
let maxlon = coordinates[0][0]; // 表示最小经度,即该区域的西侧边界;
for (let i = 1; i < coordinates.length; i++) {
if (coordinates[i][1] < minlat) {
minlat = coordinates[i][1];
}
if (coordinates[i][1] > maxlat) {
maxlat = coordinates[i][1];
}
if (coordinates[i][0] < minlon) {
minlon = coordinates[i][0];
}
if (coordinates[i][0] > maxlon) {
maxlon = coordinates[i][0];
}
}
const gephash = geohash.bboxes(minlat, minlon, maxlat, maxlon, 6);
createa(gephash);
});
polyEditorObj.close();
// 只有通过add方法创建的覆盖物才能通过remove方法删除
// 编辑完成移除多边形
polygonObj && mapObj.remove(polygonObj);
}
进阶
1、回显历史数据
// 编辑状态下将数据添加入地图,直接调用createa方法创建矩形
const list=['wx4ehb', 'wx4ej0', 'wx4ej2', 'wx4ej8', 'wx4ejb', 'wx4dvz', 'wx4dvy', 'wx4dvw', 'wx4dvq', 'wx4dvn', 'wx4duy', 'wx4duz', 'wx4dvx', 'wx4dvp', 'wx4dvr'];
const editMap = (list: Array<string>) => {
createa(list)
}
完整代码
import React, { useEffect } from "react";
import { Button } from 'antd';
import { compact } from 'lodash';
import "../global.css";
import geohash from 'ngeohash';
import {
boundsPosition,
createRectangles,
createText,
createPolygon
} from './utils';
import AMapLoader from '@amap/amap-jsapi-loader';
const demo=['wx4ehb', 'wx4ej0', 'wx4ej2', 'wx4ej8', 'wx4ejb', 'wx4dvz', 'wx4dvy', 'wx4dvw', 'wx4dvq', 'wx4dvn', 'wx4duy', 'wx4duz', 'wx4dvx', 'wx4dvp', 'wx4dvr']
const MapDemo = () => {
let mapObj: any = {}; // 创建完地图之后返回的实例
let mapALl: any = {}; // 地图实例
let polyEditorObj: any = {}; // 编辑多边形的实例
let polygonObj: any; // 初始化多边形的实例
const createa = (list: any) => {
const lists = mapObj.getAllOverlays().map((item: any) => (item._opts.text));
// 过滤掉重复的值, 如果两个方块中的geoHash相同,则就不渲染。
const compactList = list.map((item: string) => {
return lists.includes(item) ? '' : item;
})
compact(compactList).forEach((item: any, index: number) => {
const bounds = boundsPosition(item, mapALl);
// 创建矩形
const mpms = createRectangles(mapALl, bounds, item, index);
// 创建文本标注
const text = createText(mpms, mapALl, item);
mpms.on('click', deleteClick);
mapObj.add(text);
mapObj.add(mpms);
})
}
// 每次点击增加当前点,和其周围8个邻居的标注
const onMapClickEight = (e: any) => {
const { lnglat } = e;
//将一对纬度和经度值编码为 geohash。第三个参数是可选的,你可以指定这个hash字符串的长度,这也会影响geohash的精度。
const geohashs = geohash.encode(lnglat.getLat(), lnglat.getLng(), 6);
//查找[n, ne, e, se, s, sw, w, nw]geohash 字符串的所有 8 个 geohash 邻居
const eight = geohash.neighbors(geohashs);
createa([...eight, geohashs])
};
// 每次点击增加一个标注
const onMapClickOne = (e: any) => {
const { lnglat } = e;
//将一对纬度和经度值编码为 geohash。第三个参数是可选的,你可以指定这个hash字符串的长度,这也会影响geohash的精度。
const geohashs = geohash.encode(lnglat.getLat(), lnglat.getLng(), 6);
createa([geohashs]);
};
const deleteClick = (e: any, text: any) => {
// 找到文本对应的实例
const lists = mapObj.getAllOverlays().filter((item: any) => (item.type === 'AMap.Text')).filter((i: any) => (i._opts.text === e.target._opts.text));
mapObj.remove(lists[0]);
mapObj.remove(e.target);
}
// 创建初始的多边形
const createpoly = () => {
polygonObj && mapObj.remove(polygonObj);
const path = [
[116.403322, 39.920255],
[116.410703, 39.897555],
[116.402292, 39.892353],
]
// 创建一个多边形
const polygon = createPolygon(mapALl, path);
// 向地图中添加一个多边形,只有通过add方法创建的覆盖物才能通过remove方法删除
mapObj.add([polygon])
// 创建编辑状态下的多边形
const polyEditor = new mapALl.PolygonEditor(mapObj, polygon);
polyEditor.open();
polygonObj = polygon;
polyEditorObj = polyEditor;
}
const editepoly = () => {
polyEditorObj.open();
}
// 用户对多边形的编辑结束,创建多边形的范围的标注
const createpolyCLose = () => {
polyEditorObj.on('end', function (event: any) {
const coordinates = event.target._opts.path;
let minlat = coordinates[0][1]; // 表示最小纬度,即该区域的南侧边界;
let maxlat = coordinates[0][1]; // 表示最大纬度,即该区域的北侧边界;
let minlon = coordinates[0][0]; // 表示最大经度,即该区域的东侧边界。
let maxlon = coordinates[0][0]; // 表示最小经度,即该区域的西侧边界;
for (let i = 1; i < coordinates.length; i++) {
if (coordinates[i][1] < minlat) {
minlat = coordinates[i][1];
}
if (coordinates[i][1] > maxlat) {
maxlat = coordinates[i][1];
}
if (coordinates[i][0] < minlon) {
minlon = coordinates[i][0];
}
if (coordinates[i][0] > maxlon) {
maxlon = coordinates[i][0];
}
}
const gephash = geohash.bboxes(minlat, minlon, maxlat, maxlon, 6);
createa(gephash);
});
polyEditorObj.close();
// 只有通过add方法创建的覆盖物才能通过remove方法删除
polygonObj && mapObj.remove(polygonObj);
}
// 编辑状态下将数据添加入地图
const editMap = (list: Array<string>) => {
// 添加矩形
let rectanglesList = [];
for (let i = 0; i < list.length; i++) {
const bounds = boundsPosition(list[i], mapALl);
rectanglesList.push(createRectangles(mapALl, bounds, list[i], i))
}
// 向每一个矩形中添加文本
let textList = [];
for (let i = 0; i < rectanglesList.length; i++) {
textList.push(createText(rectanglesList?.[i], mapALl, rectanglesList?.[i]?._opts.text))
}
mapObj.add(rectanglesList); // 将大量的矩形数据添加进map
mapObj.add(textList); // 将大量的文本数据添加进map
}
useEffect(() => {
AMapLoader.load({
key: 'a7bc2439738da0216ca8843354167003', // 申请好的Web端开发者Key,首次调用 load 时必填
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ["AMap.PolygonEditor"]
}).then((AMap: any) => {
const map = new AMap.Map('container', { // 创建地图实例
zoom: 10,
center: [116.403322, 39.920255],
showIndoorMap: false,
viewMode: '2D',
});
map.on('click', onMapClickOne); //绑定鼠标左键click事件
mapObj = map;
mapALl = AMap;
// 如果是编辑状态执行
editMap(demo);
}).catch((e: any) => {
console.log(e);
});
}, []);
const neightdiv = () => {
mapObj?.off('click', onMapClickOne); // 解绑创建一个框
mapObj?.on('click', onMapClickEight); // 绑定创建9个框
}
const onediv = () => {
mapObj?.off('click', onMapClickEight); // 解绑创建九个框
mapObj?.on('click', onMapClickOne); // 绑定创建一个框
}
const save = () => {
const AMapList = mapObj.getAllOverlays().filter((item: any) => (item.type === 'AMap.Overlay')).map((it: any) => (it._opts.text))
console.log(AMapList, 'AMapList');
}
return (
<div className="map-container">
<Button onClick={createpoly}>创建折线</Button>
<Button onClick={neightdiv}>周围9个区域</Button>
<Button onClick={onediv}>1个区域</Button>
<Button onClick={editepoly}>编辑折线</Button>
<Button onClick={createpolyCLose}>折现编辑完成</Button>
<Button onClick={save}>完成</Button>
<div id="container" style={{ width: "1500px", height: "1500px" }}/>
</div>
);
};
export default MapDemo;
转载自:https://juejin.cn/post/7233620479469502521