当前位置: 首页 > news >正文

HarmonyOS6 map.calculateDistance vs Haversine:两种距离计算方案对比

文章目录

    • 前言
    • 一、为什么经纬度不能直接算距离?
      • 1.1 平面距离的错误示范
      • 1.2 经纬度与实际距离的关系
    • 二、Haversine 公式:球面距离的标准算法
      • 2.1 公式推导(简化版)
      • 2.2 TypeScript 实现
    • 三、本项目实现:map.calculateDistance
      • 3.1 源码分析
      • 3.2 map.calculateDistance vs 自实现 Haversine 对比
    • 四、距离相关的实用工具函数
      • 4.1 排序、过滤、格式化
    • 五、Haversine 精度说明
    • 总结

前言

本项目用CalculateUtil.getDistance()计算用户与加油站的距离,内部调用了map.calculateDistance()。你有没有想过:为什么不直接用Math.sqrt((lat2-lat1)² + (lng2-lng1)²)来算?

答案是:地球是球体,不是平面。在球面上,经纬度的度数并不直接等于距离,必须用专门的球面距离公式——Haversine 公式。本篇从数学原理到代码实现,带你彻底搞懂地理距离计算。

一、为什么经纬度不能直接算距离?

1.1 平面距离的错误示范

// ❌ 错误:经纬度差不是实际距离(单位也不对)functionwrongDistance(lat1:number,lng1:number,lat2:number,lng2:number):number{constdlat=lat2-lat1;constdlng=lng2-lng1;returnMath.sqrt(dlat*dlat+dlng*dlng);// 这只是"度数差",不是公里}wrongDistance(39.9,116.4,40.0,116.5);// 结果约 0.141,但实际距离约 15km!// 为什么错?// 问题1:纬度1度 ≈ 111km(在地球任意位置都差不多)// 问题2:经度1度的实际距离随纬度变化!// 赤道(0°):经度1度 ≈ 111km// 北京(40°N):经度1度 ≈ 85km// 北极(90°N):经度1度 ≈ 0km// 地球不是平面,而是球体,需要球面几何

1.2 经纬度与实际距离的关系

纬度(°N)纬度1°≈km经度1°≈km
0(赤道)110.6111.3
30(上海)110.996.4
40(北京)111.085.4
60111.255.8
90(北极)111.70

二、Haversine 公式:球面距离的标准算法

2.1 公式推导(简化版)

Haversine 公式基于球面三角学,计算球面上两点之间的大圆距离(最短路径):

设: φ1, φ2 = 两点纬度(弧度) λ1, λ2 = 两点经度(弧度) R = 地球半径(6371km) Δφ = φ2 - φ1(纬度差) Δλ = λ2 - λ1(经度差) a = sin²(Δφ/2) + cos(φ1) × cos(φ2) × sin²(Δλ/2) c = 2 × atan2(√a, √(1-a)) d = R × c (单位:km)

2.2 TypeScript 实现

/** * Haversine 公式计算两点球面距离 * @param lat1 起点纬度(度) * @param lng1 起点经度(度) * @param lat2 终点纬度(度) * @param lng2 终点经度(度) * @returns 距离(公里) */functionhaversineDistance(lat1:number,lng1:number,lat2:number,lng2:number):number{constR=6371;// 地球平均半径(km)// 将度转换为弧度consttoRad=(deg:number):number=>deg*Math.PI/180;constφ1=toRad(lat1);constφ2=toRad(lat2);constΔφ=toRad(lat2-lat1);constΔλ=toRad(lng2-lng1);// Haversine 公式核心consta=Math.sin(Δφ/2)*Math.sin(Δφ/2)+Math.cos(φ1)*Math.cos(φ2)*Math.sin(Δλ/2)*Math.sin(Δλ/2);constc=2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));returnR*c;// 返回距离(km)}// 测试:北京天安门 → 上海外滩constdistance=haversineDistance(39.9087,116.3975,31.2365,121.4897);console.log(`北京到上海直线距离:${distance.toFixed(0)}km`);// 约 1066km(实际约1067km)// 测试:同一地点距离为0console.log(haversineDistance(39.9,116.4,39.9,116.4));// 0// 测试:非常近的距离(1km以内)constnearDist=haversineDistance(39.9042,116.4074,39.9092,116.4124);console.log(`附近距离:${(nearDist*1000).toFixed(0)}m`);// 约 671m

三、本项目实现:map.calculateDistance

3.1 源码分析

// CalculateUtil.etsimport{map,mapCommon}from'@kit.MapKit';exportclassCalculateUtil{publicstaticgetDistance(lat1:number,long1:number,lat2:number,long2:number):string{letlan1:mapCommon.LatLng={latitude:lat1,longitude:long1};letlan2:mapCommon.LatLng={latitude:lat2,longitude:long2};// MapKit 提供的距离计算函数(内部也使用球面距离算法)// 返回值单位:米letdistance:number=map.calculateDistance(lan1,lan2)/1000;// 转换为公里returndistance.toFixed(1);// 保留一位小数}}

3.2 map.calculateDistance vs 自实现 Haversine 对比

// 对比两种方法的结果constuserLat=39.9042,userLng=116.4074;// 天安门conststationLat=40.0046,stationLng=116.4823;// 望京// 方案1:map.calculateDistance(项目实际使用)import{map,mapCommon}from'@kit.MapKit';constp1:mapCommon.LatLng={latitude:userLat,longitude:userLng};constp2:mapCommon.LatLng={latitude:stationLat,longitude:stationLng};constsdkDist=map.calculateDistance(p1,p2)/1000;console.log(`SDK 计算:${sdkDist.toFixed(2)}km`);// 方案2:自实现 HaversineconstmyDist=haversineDistance(userLat,userLng,stationLat,stationLng);console.log(`Haversine:${myDist.toFixed(2)}km`);// 两者结果几乎一致(差异 < 0.1%)
方案依赖精度适用场景
map.calculateDistanceMapKit SDK高(考虑地球扁率)HarmonyOS 项目(推荐)
Haversine 自实现无依赖高(±0.5%)通用场景、离线计算
平面勾股定理极低(误差大)❌ 不要用

四、距离相关的实用工具函数

4.1 排序、过滤、格式化

interfacePointData{id:string;name:string;latitude:number;longitude:number;distance?:number;// 可选,动态计算}classGeoUtils{// 计算距离(返回km)staticdistance(lat1:number,lng1:number,lat2:number,lng2:number):number{constR=6371;consttoRad=(d:number)=>d*Math.PI/180;constdLat=toRad(lat2-lat1);constdLng=toRad(lng2-lng1);consta=Math.sin(dLat/2)**2+Math.cos(toRad(lat1))*Math.cos(toRad(lat2))*Math.sin(dLng/2)**2;returnR*2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));}// 格式化距离显示staticformatDistance(km:number):string{if(km<0.1)return`${(km*1000).toFixed(0)}m`;if(km<1)return`${(km*1000).toFixed(0)}m`;if(km<10)return`${km.toFixed(1)}km`;return`${km.toFixed(0)}km`;}// 给点列表添加距离并排序staticsortByDistance<TextendsPointData>(points:T[],userLat:number,userLng:number):(T&{distance:number;distanceStr:string})[]{returnpoints.map(p=>({...p,distance:GeoUtils.distance(userLat,userLng,p.latitude,p.longitude),distanceStr:GeoUtils.formatDistance(GeoUtils.distance(userLat,userLng,p.latitude,p.longitude))})).sort((a,b)=>a.distance-b.distance);}// 过滤指定范围内的点staticfilterByRadius<TextendsPointData>(points:T[],userLat:number,userLng:number,radiusKm:number):T[]{returnpoints.filter(p=>GeoUtils.distance(userLat,userLng,p.latitude,p.longitude)<=radiusKm);}// 判断两点是否在同一区域(快速判断,用于粗筛)staticisNearby(lat1:number,lng1:number,lat2:number,lng2:number,radiusDeg:number=0.1):boolean{returnMath.abs(lat2-lat1)<=radiusDeg&&Math.abs(lng2-lng1)<=radiusDeg;}}// 使用示例@Entry@Componentstruct GeoUtilsDemoPage{@StateuserLat:number=39.9042;@StateuserLng:number=116.4074;@StatesortedStations:Array<PointData&{distance:number;distanceStr:string}>=[];privaterawStations:PointData[]=[{id:'1',name:'望京石化',latitude:40.0046,longitude:116.4823},{id:'2',name:'朝阳石油',latitude:39.9219,longitude:116.4386},{id:'3',name:'国贸壳牌',latitude:39.9108,longitude:116.4551},{id:'4',name:'通州加油站',latitude:39.8947,longitude:116.6561},// 远处];aboutToAppear():void{// 筛选5km内 + 按距离排序constnearby=GeoUtils.filterByRadius(this.rawStations,this.userLat,this.userLng,5);this.sortedStations=GeoUtils.sortByDistance(nearby,this.userLat,this.userLng);}build(){Column({space:12}){Text(`我的位置:${this.userLat.toFixed(4)},${this.userLng.toFixed(4)}`).fontSize(13).fontColor('#999999')Text('5km内加油站(按距离排序)').fontSize(16).fontWeight(FontWeight.Bold)ForEach(this.sortedStations,(station:PointData&{distanceStr:string})=>{Row({space:12}){Text('⛽').fontSize(20)Text(station.name).fontSize(15).layoutWeight(1)Text(station.distanceStr).fontSize(14).fontColor('#1A6FF5').fontWeight(FontWeight.Bold)}.padding(16).width('100%').backgroundColor('#FFFFFF').borderRadius(12)},(s:PointData)=>s.id)}.padding(20).width('100%').height('100%').backgroundColor('#F5F7FA')}}

五、Haversine 精度说明

// Haversine 的精度误差来源:// 1. 地球不是完美球体,而是椭球体(赤道半径 6378km,极地半径 6357km)// 2. Haversine 使用平均半径 6371km,在赤道误差约 0.3%,在极地误差约 0.5%// 3. 对于城市级(<500km)的距离计算,误差可以忽略不计// 高精度需求(如导航)推荐:Vincenty 公式(考虑地球扁率)// 一般应用(如找附近加油站):Haversine 完全够用// 快速估算(不需要精确,只是排序用):functionroughDistance(lat1:number,lng1:number,lat2:number,lng2:number):number{// 在 40°N 左右的中国大多数城市constLAT_TO_KM=111.0;// 纬度1度 ≈ 111kmconstLNG_TO_KM=85.0;// 经度1度 ≈ 85km(40°N 附近)constdLat=(lat2-lat1)*LAT_TO_KM;constdLng=(lng2-lng1)*LNG_TO_KM;returnMath.sqrt(dLat*dLat+dLng*dLng);// 这里的平面计算是局部近似,精度约5%}

总结

地理距离计算必须用球面几何,因为地球不是平面。Haversine公式是最广泛使用的球面距离算法:将经纬度差转换为弧度,通过三角函数计算球面角度,再乘以地球半径得到实际距离。本项目使用map.calculateDistance()是最佳选择(MapKit 内置,精度更高);如果需要离线或跨平台计算,Haversine 手写实现也能满足城市级距离计算的精度要求。

http://www.gsyq.cn/news/1491998.html

相关文章:

  • 使用Perfetto网页直接抓取trace 注意事项
  • pac4j-jwt 身份验证绕过漏洞分析
  • ASP.NET MVC多租户仓储系统源码:支持多企业隔离库存+采购销售财务全流程管理
  • 归环夏奈角色介绍 归环夏奈玩法解析
  • Qt连接仪器踩坑记:VISA库配置、SCPI指令调试与NI-MAX使用全攻略
  • BLE、Zigbee 超市货架电子价签(ESL)应用方案
  • 定制换热板片该怎么选才靠谱
  • 科视 Christie 激光投影助力沉浸式水秀呈现南宋诗人陆游文化之旅
  • vue3实现的纯前端护肤品商城网站
  • 手把手教你用Simulink搭建永磁直驱风机并网模型(附单位功率因数控制与弱磁控制仿真)
  • 小程序毕设选题推荐:基于python的档案室档案宝微信小程序基于python的档案室档案宝微信小程序【附源码、mysql、文档、调试+代码讲解+全bao等】
  • XUnity Auto Translator:高效配置智能翻译插件的深度解析与实战指南
  • 医院HIS药房模块实战避坑系列》之三:公立/私立医院药品调价模式对比:账务处理与行业演进
  • 告别Softmax:YOLOv3的多标签分类与Binary Cross-Entropy Loss实战调优指南
  • NCMconverter终极指南:3步解锁网易云音乐加密格式,免费实现ncm到mp3/flac批量转换
  • 别再买错卡了!Arduino+RC522复制门禁卡全指南:从M1 S50卡到UID卡避坑详解
  • 从零到一:拆解一个开源QScada项目(HmiFuncDesigner),搞懂工业组态软件的核心模块设计
  • NLP工程实践指南:从2020年技术快照看RAG与零样本落地
  • MASA模组汉化包:终极中文解决方案,让7大Minecraft工具模组无障碍使用
  • 从Echo到Epoll:我的第一个C++并发服务器踩坑实录(ET模式详解)
  • 别再死记硬背语法了!用OpenModelica 1.8.1从物理系统建模实战中掌握Modelica核心
  • AI写论文的绝佳帮手!4款AI论文写作工具让期刊论文写作更轻松
  • UiPath自动化包:WI5工作项客户信息哈希值本地计算与ACME系统集成
  • 锐捷AC虚拟化(VAC)配置避坑指南:高职比赛实验中的同型号同版本要求详解
  • 英雄联盟智能助手League Akari:3步实现游戏自动化与数据洞察的终极指南
  • Sqribble电子书自动化排版系统深度解析
  • AWS Glue + Athena:无服务器数据湖分析闭环实战指南
  • 三菱FX PLC控制东芝4轴机械手完整工程包:带注释程序+信捷HMI+电气图+仿真软件
  • 从家庭Wi-Fi到企业网络:手把手教你规划不同规模的局域网架构
  • 小程序毕业设计-基于Springboot+微信小程序的个性化漫画阅读推荐智能推荐、在线阅读、收藏评论系统的设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)