Appearance
关于地图的相关需求
这次做地图相关的需求,涉及到几个方法的计算,觉得很有价值,列出来防止下次用到
场景一:
多个经纬度围成的网格,用户点击网格,将点击坐标投影到距离最近的网格线上
js
// 计算点击坐标距离最近的网格线的投影坐标
function calculateDistanceToSegment(point, segment) {
const A = { x: segment[0].longitude, y: segment[0].latitude };
const B = { x: segment[1].longitude, y: segment[1].latitude };
const P = { x: point.longitude, y: point.latitude };
const AP = { x: P.x - A.x, y: P.y - A.y };
const AB = { x: B.x - A.x, y: B.y - A.y };
const ab2 = AB.x * AB.x + AB.y * AB.y;
const ap_ab = AP.x * AB.x + AP.y * AB.y;
let t = ap_ab / ab2;
if (t < 0) t = 0;
else if (t > 1) t = 1;
const closest = { x: A.x + t * AB.x, y: A.y + t * AB.y };
return {
distance: Math.sqrt((P.x - closest.x) * (P.x - closest.x) + (P.y - closest.y) * (P.y - closest.y)),
closestPoint: closest
};
}
function findClosestGridProjection(clickPoint,gridPoints) {
let minDistance = Infinity;
let closestSegment = null;
let closestProjection = null;
const newgridPoints = gridPoints.map(point => ({
latitude: parseFloat(point.latitude),
longitude: parseFloat(point.longitude)
}));
for (let i = 0; i < newgridPoints.length; i++) {
const j = (i + 1) % newgridPoints.length;
const segment = [newgridPoints[i], newgridPoints[j]];
const { distance, closestPoint } = calculateDistanceToSegment(clickPoint, segment);
if (distance < minDistance) {
minDistance = distance;
closestSegment = segment;
closestProjection = closestPoint;
}
}
return {
segment: closestSegment,
distance: minDistance,
projection: closestProjection
};
}
场景二:
计算多个位置在这条线上的均分的经纬度,这个需求说实话真的很奇怪,他要求建档的商户不按照实际位置在网格线上排布,而是要按照建档的顺序,同时等分排布,例如我有5个商户,那我5个商户要在网格线等距排开,我们知道地图上显示商户的位置,是要根据经纬度的,这也就要我们前端去动态的计算商户的经纬度
js
// 计算多个位置在这条线上的均分的经纬度
function calculateUniformPointsAlongLine(startPoint, endPoint, numPoints) {
// 计算首尾两点之间的经纬度差
const deltaLat = endPoint.latitude - startPoint.latitude;
const deltaLon = endPoint.longitude - startPoint.longitude;
// 初始化一个数组来存储均匀分布的点
const uniformPoints = [];
// 遍历每个位置,计算并添加到数组中
for (let i = 0; i < numPoints; i++) {
// 计算每个位置的经度和纬度
const lat = startPoint.latitude + (deltaLat / (numPoints - 1) * i);
const lon = startPoint.longitude + (deltaLon / (numPoints - 1) * i);
// 将计算出的经纬度添加到数组中
uniformPoints.push({ latitude: lat, longitude: lon });
}
// 返回均匀分布的点数组
return uniformPoints;
}
场景三:
判断用户点击的经纬度是否在两个经纬度构成的线上
js
// 判断点击的经纬度是否在两个经纬度构成的线上,这里增加了误差值deviationThreshold,毕竟用户在手机上操作不可能精准的点到线上
function isClickOnLine(clickPoint, startPoint, endPoint, deviationThreshold=5) {
// 将经纬度转换为弧度
const toRadians = angle => angle * (Math.PI / 180);
const { latitude: lat1, longitude: lon1 } = startPoint;
const { latitude: lat2, longitude: lon2 } = endPoint;
const { latitude: lat3, longitude: lon3 } = clickPoint;
// 地球半径(近似值)
const R = 6371e3; // 米
const lat1Rad = toRadians(lat1);
const lon1Rad = toRadians(lon1);
const lat2Rad = toRadians(lat2);
const lon2Rad = toRadians(lon2);
const lat3Rad = toRadians(lat3);
const lon3Rad = toRadians(lon3);
// 计算线段的长度
const dLon = lon2Rad - lon1Rad;
const dLat = lat2Rad - lat1Rad;
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const lineLength = R * c;
// 计算点到线段所在直线的距离
const numerator = (lat3Rad - lat1Rad) * (lat2Rad - lat1Rad) + (lon3Rad - lon1Rad) * (lon2Rad - lon1Rad);
const denominator = (lat2Rad - lat1Rad) * (lat2Rad - lat1Rad) + (lon2Rad - lon1Rad) * (lon2Rad - lon1Rad);
const u = numerator / denominator;
const pointLat = lat1Rad + u * (lat2Rad - lat1Rad);
const pointLon = lon1Rad + u * (lon2Rad - lon1Rad);
// 判断投影点是否在线段上
const onSegment = u >= 0 && u <= 1;
// 计算投影点到点击点的距离
const distance = R * Math.acos(Math.sin(lat3Rad) * Math.sin(pointLat) +
Math.cos(lat3Rad) * Math.cos(pointLat) * Math.cos(lon3Rad - pointLon));
// 返回距离和投影点坐标
return distance <= deviationThreshold;
}
场景四:
计算两个经纬度直线距离,我们知道经纬度构成的线其实并不是想象当中的二维直线,而是三维的,因此需要加上地球的曲率计算
js
// 计算两个经纬度直线距离
function haversine(coord1, coord2) {
const R = 6371; // 地球的平均半径,单位千米
const lat1 = toRadians(coord1.latitude);
const lat2 = toRadians(coord2.latitude);
const deltaLat = toRadians(coord2.latitude - coord1.latitude);
const deltaLon = toRadians(coord2.longitude - coord1.longitude);
const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c;
return distance.toFixed(2)*1000;
}
function toRadians(degrees) {
return degrees * (Math.PI / 180);
}
场景五:
判断点击处是否在网格内,这是一个常见的需求了,小程序上,官方并没有提供相应的api,因此也是要靠自己计算
js
//判断点击处是否在网格内
const isInGrid = (point,polygons) => {
var resList=[];
for(let i=0;i<polygons.length;i++){
var tempPolygons=polygons[i].points;
var nCross = 0;
var n = tempPolygons.length
//遍历多边形每一条线
for(let j =0 ;j< n;j++){
var p1=tempPolygons[j] //定义第一个点的坐标
var p2=tempPolygons[(j+1)%n] //定义下一个点的坐标,%n的作用在于让最后一个点的坐标与第一个点坐标重合
//如果两点之间与y轴平行,则进行下一轮循环
if(Number(p1.longitude) == Number(p2.longitude))
continue;
//如果两点之间的x值都大于选取点的x值 ,则进行下一轮循环
if(Number(point[0]) < Math.min(Number(p1.longitude),Number(p2.longitude)))
continue;
//如果两点之间的x值都小于或等于选取点的x值 ,则进行下一轮循环
if(Number(point[0]) >=Math.max(Number(p1.longitude),Number(p2.longitude)))
continue;
//如果x1<x<x2 ,且已知x,则通过两点间直线公式, 算出交点y
var y = (Number(point[0]) - Number(p1.longitude)) *(Number(p2.latitude)-Number(p1.latitude)) / (Number(p2.longitude)-Number(p1.longitude)) + (Number(p1.latitude))
//交点y值大于选点y值 则计数
if (y > Number(point[1]))
nCross ++;
}
if(nCross%2 === 1){
// console.log(tempPolygons,"在范围内");
// return i;
resList.push({
index:i,
name:polygons[i].name,
uid:polygons[i].uid,
});
}
}
return resList;
}
后续有什么再继续补充吧,今天就到这里啦~~