在 web 地图开发过程中可能需要根据某一个区域生成一些随机点用来做测试。
之前在某个网站有这个功能是借助百度地图相关 api 来实现的,那我们不借助这些地图厂商的 api 可以实现吗?答案是可以的。
在线体验地址: leaflet-gaode.netlify.app/
仓库地址: github.com/leafletjsEx...
要实现什么?
实现的内容如下图:选择辖区、输入生成点位数量后点击"生成点位" 会移动到辖区中心点并在辖区范围内生成一定数量的随机点(因为需要过滤掉不在辖区内的的点位实际加载的点位数量会小于输入框的数量)。
分解下任务
- 获取辖区数据即省市区三级联动数据
- 获取区域的中心点、区域边界框
- 判断点位是否在指定区域内
- 生成随机点
- 加载到地图
获取辖区数据即省市区三级联动数据
这个数据是从 datav 这个网站获取的。
获取的数据是一维数据手动处理成了省市区三级联动数据点击获取。
获取区域的中心点、边界框
上边通过 datav 获取的数据中已经包含了区域边界框、中心点。
你可能会好奇如果没有怎么计算呢?
使用 turfjs 执行 pnpm i @turf/turf 安装依赖
Turf.js 是一个用于地理空间分析和处理的 JavaScript 库。它提供了一系列功能强大且易于使用的工具,用于处理地理空间数据、执行地理空间分析以及生成地理空间几何对象等操作。
根据范围获取边界框
bbox方法接收一个 geojson 类型的参数
js
import {bbox as turfBbox} from '@turf/turf'
const getBbox = async () => {
// 获取 datav 区域边界 geojson
const data = await fetch('https://geo.datav.aliyun.com/areas_v3/bound/371500.json').then(res => res.json())
// 输出四个顶点坐标数组 [115.271447, 35.779921,116.549533,37.032217]
console.log(turfBbox(data));
}
线上 github、netlify 等部署后无法访问 datav geojson 数据(403)使用 aircode写接口代理一层。
js
// @see https://docs.aircode.io/guide/functions/
const aircode = require('aircode');
module.exports = async function (params, context) {
const areaCode = params.areaCode
if (!areaCode) {
return {
msg: 'areaCode 不能为空'
}
}
const url = `https://geo.datav.aliyun.com/areas_v3/bound/${areaCode}.json`
return await fetch(url).then(res => res.json())
};
// 访问如下接口传递 区域 code 获取数据
const areaData = await fetch(`https://36dvjmmx39.us.aircode.run/index?areaCode=${code}`).then(res => res.json())
非 geojson 类型的数据使用 polygon 方法转换为 gepjson 在进行上边的操作
js
import { polygon , bbox} from '@turf/turf'
// 创建一个表示区域范围的多边形
const polygon = polygon([[
[0, 0],
[0, 10],
[10, 10],
[10, 0],
[0, 0]
]]);
// 计算多边形的边界框
const bbox = bbox(polygon);
console.log(bbox); // 返回一个表示边界框的数组 [minX, minY, maxX, maxY]
生成随机点
随机点是根据边界框来计算生成逻辑比较简单
js
const generateRandomPointInBoundingBox = (bbox) => {
const [minX, minY, maxX, maxY] = bbox;
const randomX = Math.random() * (maxX - minX) + minX;
const randomY = Math.random() * (maxY - minY) + minY;
// 坐标点
return [randomX, randomY];
}
判断点位是否在指定区域内
这个需要使用 turfjs 的 booleanPointInPolygon 方法用于判断点是否在多边形内部。接收一个 geojson<Polygon | MultiPolygon> 类型的数据。
区域边界数据从 datav 来获取https://geo.datav.aliyun.com/areas_v3/bound/371500.json
将链接中的 371500
替换成对应的辖区 code 就可以获取到对应的辖区数据(上边获取的省市区数据里包含区域 code)。
js
// todo 这里重命名 只是为了防止名称重复
import {
point as turfPoint,
multiPolygon as turfMultiPolygon,
booleanPointInPolygon
} from '@turf/turf'
// 判断点是否在 geoJson 内
const isPointInsideFeatureCollection = (featureCollection, pointCoordinates) => {
// 将点坐标转换为 Turf.js 对象
const point = turfPoint(pointCoordinates);
// 遍历 FeatureCollection 中的每个 Feature
for (const feature of featureCollection.features) {
const multiPolygon = turfMultiPolygon(feature.geometry.coordinates);
const isPointInsidePolygon = booleanPointInPolygon(point, multiPolygon);
if (isPointInsidePolygon) {
return true; // 如果点在任何一个 Feature 内,返回 true
}
}
return false; // 如果点不在任何一个 Feature 内,返回 false
}
生成随机点
这个逻辑也比较简单根据输入的生成数量执行 for 循环生成随机点然后判断是否在区域范围内,在的话就添加到数组。
js
const pointArr = ref([]);
// num 由参数传入
for (let i = 0; i < num; i++) {
// 生成随机点
const point = generateRandomPointInBoundingBox(areaInfo.bbox);
// 判断点是否在区域范围内
const isInside = isPointInsideFeatureCollection(areaData, point);
// 添加到数组中
if (isInside) {
pointArr.value.push(point)
}
}
将点位添加到地图
作者使用的是 leafletjs 所以就以此为例,创建地图的过程就省略了。
js
const pointArr = ref([]);
const markerList = ref([]);
const clearAllMarker = () => {
markerList.value.map((item) => {
item.remove();
return item;
});
markerList.value = [];
};
// 获取 marker icon
const getMarkerIcon = () => {
return L.icon({
iconUrl: point_icon,
iconSize: [40, 40],
});
};
const markersLayerGroup = ref();
const addMarkerToMap = () => {
clearAllMarker()
// 图曾存在先清除
if (markersLayerGroup.value) {
mapObj.value.removeLayer(markersLayerGroup.value);
}
// 创建图层组 聚合图层 leaflet.markercluster 插件
markersLayerGroup.value = L.markerClusterGroup({
chunkedLoading: true,
showCoverageOnHover: false,
});
// 向图层添加数据
pointArr.value.map((item) => {
markersLayerGroup.value.addLayer(
L.marker(item.reverse(), {icon: getMarkerIcon()}),
);
});
// 将图层组加载到地图
mapObj.value.addLayer(markersLayerGroup.value);
};
最终效果
总结
本文介绍了如何根据辖区范围生成随机点及 turfjs
的简单使用。
完整示例请查看 git 仓库: github.com/leafletjsEx...