当前位置: 首页 > 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.jsqmd.com/news/980271/

相关文章:

  • 跨境多店铺管理混乱,先排查浏览器环境边界
  • 人文综合素养类赛事解析,文科生的竞赛新赛道
  • 使用Perfetto网页直接抓取trace 注意事项
  • 餐饮扫码点餐系统源码:支持外卖+自取、多店独立运营,Java后端+Vue3前端
  • PostgreSQL 技术日报 (6月8日)|索引预取迭代,AI 安全功能上新
  • 从Mathtype到BibTeX:让你的IEEE LaTeX写作效率翻倍的几个隐藏技巧
  • pac4j-jwt 身份验证绕过漏洞分析
  • 上市公司空气流通系数(2000-2025)
  • 别再死记硬背了!用TensorFlow 2.x手把手复现Google的WideDeep推荐模型
  • ASP.NET MVC多租户仓储系统源码:支持多企业隔离库存+采购销售财务全流程管理
  • 企业微信外部群机器人接入 AI:一套能落地的工程方案
  • 2026肇庆市黄金回收铂金回收白银回收彩金回收机构实力:项链+戒指+手镯+吊坠专业鉴定上门服务及联系方式推荐 - 亦辰小黄鸭
  • C语言介绍——通用的计算机编程语言
  • Gemini 3.5逻辑推理与精准度实测:算法题与知识问答场景下的能力边界
  • Bending Spoons 上市声明或揭秘“收购、裁员、然后呢?”策略真相
  • 归环夏奈角色介绍 归环夏奈玩法解析
  • Qt连接仪器踩坑记:VISA库配置、SCPI指令调试与NI-MAX使用全攻略
  • 云尖信息亮相英特尔至强6+发布会暨数据中心创新日,以全栈能力构筑Agentic AI时代新算力底座
  • BLE、Zigbee 超市货架电子价签(ESL)应用方案
  • 从DH1到3DH5:一文读懂蓝牙射频测试中那些让人头疼的数据包与调制方式
  • 告别均匀采样!用PER优先经验回放,让你的DQN在Atari游戏上快人一步
  • 科视 Christie 激光投影助力沉浸式水秀呈现南宋诗人陆游文化之旅
  • 定制换热板片该怎么选才靠谱
  • 华为USG6000防火墙升级避坑实录:从V1R1C30到V500R005C20的完整操作指南
  • 用C语言实战:最小公倍数在嵌入式编程和单片机开发中的一个具体应用案例
  • PHP并发处理与协程入门
  • 成本降87.5%:模具冲头助力3C企业年省28万 - 速递信息
  • Python小说章节自动采集入库工具:含MySQL连接池、去重建表与配置化部署
  • vue3实现的纯前端护肤品商城网站
  • 无人机管理系统|完整源码交付,支持私有化部署与定制开发