两种高效计算 两个经纬度之间距离的方法--解决Haversine 公式性能慢的问题

本文主要是介绍两种高效计算 两个经纬度之间距离的方法--解决Haversine 公式性能慢的问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言    

Haversine

原理

实现代码

优化后的距离计算

原理

代码

性能及精度对比


前言    

 最新开发的业务中,涉及到计算两个经纬度之间的距离。已知A点和B点的 经纬度,计算A点到B点之间的距离。最开始使用的是Haversine公式来进行计算,但上线后出现严重的性能问题,主要原因是业务数据量太大,每天PB级的数据量。

因此,需要研究一个更高效便捷的计算方法,下面将介绍Haversine公式和实现代码,以及新的计算公式&代码,并对他们的性能及精度进行分析。

Haversine

原理

Haversine 公式是一种用于计算球面上两个坐标点之间距离的数学公式,特别适用于地球上的球面距离计算。该公式基于球面三角学,利用球面上两点间的弧长来计算它们之间的距离。这个公式的形式如下:

总的来说,Haversine 公式通过计算球面上两点间的弧长,提供了一种相对精确的球面距离计算方法。这种方法在小球面(如地球)上非常常见,并在航海、导航等领域广泛应用。

实现代码

public class DistanceCalculator {// 地球半径,单位为千米private static final double EARTH_RADIUS = 6371.0;// 将角度转换为弧度private static double toRadians(double degree) {return degree * Math.PI / 180.0;}// 计算两个经纬度之间的距离,返回结果单位为千米public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {double dLat = toRadians(lat2 - lat1);double dLon = toRadians(lon2 - lon1);double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *Math.sin(dLon / 2) * Math.sin(dLon / 2);double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));return EARTH_RADIUS * c;}public static void main(String[] args) {// 示例坐标:纬度 Latitude 1: 40.748817, 经度 Longitude 1: -73.985428// 示例坐标:纬度 Latitude 2: 34.052235, 经度 Longitude 2: -118.243683double distance = calculateDistance(40.748817, -73.985428, 34.052235, -118.243683);System.out.println("Distance between the two coordinates: " + distance + " km");}
}

如果对计算性能不是特别严苛,可以优先使用Haversine 公式来进行计算。但是对于TB级或者PB量级的数据业务来说,这个公式由于计算过于复杂,存在比较大的性能问题。

通过火焰图分析,主要是Math.atan2() 函数的计算开销比较大。

优化后的距离计算

原理

使用弧度计算的简单直线距离计算公式:

由于地球的半径很大,在一个很小的区域内(公里级)可以近似成一个平面,这里直接采用勾股定理来计算直线距离。虽然精度可能会略有降低,但是由于计算公式很简单,性能会有极大提升。

代码

public class SimpleDistanceCalculator {// 将角度转换为弧度private static double toRadians(double degree) {return degree * Math.PI / 180.0;}// 计算两个经纬度之间的简易直线距离,返回结果单位为千米public static double calculateSimpleDistance(double lat1, double lon1, double lat2, double lon2) {double dLat = toRadians(lat2 - lat1);double dLon = toRadians(lon2 - lon1);// 使用简化的直线距离公式double distance = Math.sqrt(dLat * dLat + dLon * dLon) * 60 * 1.852;return distance;}public static void main(String[] args) {// 示例坐标:纬度 Latitude 1: 40.748817, 经度 Longitude 1: -73.985428// 示例坐标:纬度 Latitude 2: 34.052235, 经度 Longitude 2: -118.243683double simpleDistance = calculateSimpleDistance(40.748817, -73.985428, 34.052235, -118.243683);System.out.println("Simple distance between the two coordinates: " + simpleDistance + " km");}
}

性能及精度对比

前者计算1千万次使用的时间为:4462ms

优化后计算1千万次使用的时间为:244ms

优化后耗时为原来的 5.4%。

精度上,在短距离(例如 5 公里以内),Haversine 公式和等矩形投影(简化后的方法)之间的精度差异可能不会很大。在这个范围内,球面和平面的差异相对较小,因此简化的方法通常能够提供足够的精度。

然而,具体的精度差异会受到多个因素的影响,包括具体的坐标位置、所使用的地球半径,以及计算时是否考虑了地球的椭球形状等。因此,很难给出一个具体的数字来表示它们之间的精度差异。

为了获取更准确的精度比较,最好的方法是使用实际的测试数据,并比较两种方法得到的结果。你可以选择一些已知距离的坐标点,分别使用 Haversine 公式和等矩形投影计算它们之间的距离,然后比较计算结果。

本人的一个实际路测,在5公里范围内,大约存在5%到15% 之间的差异。

这篇关于两种高效计算 两个经纬度之间距离的方法--解决Haversine 公式性能慢的问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/571640

相关文章

Python字符串处理方法超全攻略

《Python字符串处理方法超全攻略》字符串可以看作多个字符的按照先后顺序组合,相当于就是序列结构,意味着可以对它进行遍历、切片,:本文主要介绍Python字符串处理方法的相关资料,文中通过代码介... 目录一、基础知识:字符串的“不可变”特性与创建方式二、常用操作:80%场景的“万能工具箱”三、格式化方法

springboot+redis实现订单过期(超时取消)功能的方法详解

《springboot+redis实现订单过期(超时取消)功能的方法详解》在SpringBoot中使用Redis实现订单过期(超时取消)功能,有多种成熟方案,本文为大家整理了几个详细方法,文中的示例代... 目录一、Redis键过期回调方案(推荐)1. 配置Redis监听器2. 监听键过期事件3. Redi

基于SpringBoot实现分布式锁的三种方法

《基于SpringBoot实现分布式锁的三种方法》这篇文章主要为大家详细介绍了基于SpringBoot实现分布式锁的三种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、基于Redis原生命令实现分布式锁1. 基础版Redis分布式锁2. 可重入锁实现二、使用Redisso

JAVA Calendar设置上个月时,日期不存在或错误提示问题及解决

《JAVACalendar设置上个月时,日期不存在或错误提示问题及解决》在使用Java的Calendar类设置上个月的日期时,如果遇到不存在的日期(如4月31日),默认会自动调整到下个月的相应日期(... 目录Java Calendar设置上个月时,日期不存在或错误提示java进行日期计算时如果出现不存在的

Mybatis对MySQL if 函数的不支持问题解读

《Mybatis对MySQLif函数的不支持问题解读》接手项目后,为了实现多租户功能,引入了Mybatis-plus,发现之前运行正常的SQL语句报错,原因是Mybatis不支持MySQL的if函... 目录MyBATis对mysql if 函数的不支持问题描述经过查询网上搜索资料找到原因解决方案总结Myb

自定义注解SpringBoot防重复提交AOP方法详解

《自定义注解SpringBoot防重复提交AOP方法详解》该文章描述了一个防止重复提交的流程,通过HttpServletRequest对象获取请求信息,生成唯一标识,使用Redis分布式锁判断请求是否... 目录防重复提交流程引入依赖properties配置自定义注解切面Redis工具类controller

Nginx错误拦截转发 error_page的问题解决

《Nginx错误拦截转发error_page的问题解决》Nginx通过配置错误页面和请求处理机制,可以在请求失败时展示自定义错误页面,提升用户体验,下面就来介绍一下Nginx错误拦截转发error_... 目录1. 准备自定义错误页面2. 配置 Nginx 错误页面基础配置示例:3. 关键配置说明4. 生效

Java调用DeepSeek API的8个高频坑与解决方法

《Java调用DeepSeekAPI的8个高频坑与解决方法》现在大模型开发特别火,DeepSeek因为中文理解好、反应快、还便宜,不少Java开发者都用它,本文整理了最常踩的8个坑,希望对... 目录引言一、坑 1:Token 过期未处理,鉴权异常引发服务中断问题本质典型错误代码解决方案:实现 Token

Nginx 访问控制的多种方法

《Nginx访问控制的多种方法》本文系统介绍了Nginx实现Web访问控制的多种方法,包括IP黑白名单、路径/方法/参数控制、HTTP基本认证、防盗链机制、客户端证书校验、限速限流、地理位置控制等基... 目录一、IP 白名单与黑名单1. 允许/拒绝指定IP2. 全局黑名单二、基于路径、方法、参数的访问控制

springboot3.x使用@NacosValue无法获取配置信息的解决过程

《springboot3.x使用@NacosValue无法获取配置信息的解决过程》在SpringBoot3.x中升级Nacos依赖后,使用@NacosValue无法动态获取配置,通过引入SpringC... 目录一、python问题描述二、解决方案总结一、问题描述springboot从2android.x