Skip to content

关于地图的相关需求

这次做地图相关的需求,涉及到几个方法的计算,觉得很有价值,列出来防止下次用到

场景一:

多个经纬度围成的网格,用户点击网格,将点击坐标投影到距离最近的网格线上

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;
}

后续有什么再继续补充吧,今天就到这里啦~~