开发背景
接上文我求的两经纬度点之间的方位角,我的需求里还提到了要计算距离,当然这个距离也是为后面的需求做铺垫的,因此需要求两个经纬度电之间的距离。
不要妄想用勾股定理求出来,实际上距离的计算还是稍微复杂些。这里使用的是Haversine公式,用于在给定两个地理坐标点的情况下计算它们之间的球面距离,我直接将这个公式的数学计算实现为一个方法,然后再代码中调用。
生产环境使用(球面短距离计算)
Haversine公式的数学理论基于球面三角学和三角恒等式的推导,通过近似计算大圆航线距离,适用于小距离的球面距离计算。这基本符合我的需求,因为我的计算都是基本是短距离计算的,基本不会跨省,实际效果也不错,如果你是超远距离计算,比如跨国,跨洲了,可以先试试,然后再考虑使用。
来源 https://wikipredia.net/zh/Haversine_formula#Formulation
说完理论部分,我就要开始上代码了,基本上你配置完spark环境,直接把我的代码扔上去就能看到输出结果,因为我是做了很多遍验证的。
spark代码
这个你可以转成python,反正算法基本都一样,不过换了一种写法
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._
/**
* 增加了多对多的方位角计算以及计算对应距离模型计算方法
* 代码实现了计算多个点位对多个点位的方位角计算以及对应的距离计算,基本算是final版本
* 基本实现了角度计算和距离计算
* 在实际生产中,会出现噪音点,以及点位null值等,还是提前清洗一下数据为好
* 2024年8月6日写,几个月前就搞好了,一直没空发博客。。。今天又闲下来了,干就完了,有问题及时联系
* @email matrix70@163.com
* @author lixh
*/
object Angle_MoreToMore_Distance {
/**
* @author lixh
* @param lon1
* @param lat1
* @param lon2
* @param lat2
* @return
*/
// 计算两个经纬度坐标之间的方位角
def calculateAzimuth(lon1: Double, lat1: Double, lon2: Double, lat2: Double): Double = {
val dx = lon2 - lon1
val dy = lat2 - lat1
val azimuth = math.atan2(dx, dy) * 180 / math.Pi
(azimuth + 360) % 360
}
//距离算法 Haversine @author lixh
def haversineDistance(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double = {
val toRadians = Math.toRadians(_: Double)
val dLat = toRadians(lat2 - lat1)
val dLon = toRadians(lon2 - lon1)
val 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)
val c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
val EARTH_RADIUS_KM = 6371.0
val distance = EARTH_RADIUS_KM * c
distance
}
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName("Azimuth Calculation") // 设置应用程序名称
.master("local[*]") // 运行模式,这里使用本地模式
.getOrCreate()
import spark.implicits._
// DF A 包含地点信息和经纬度信息 @author https://blog.csdn.net/qq_52128187?type=blog val A = Seq((101, "北京", 39.9042, 116.4074),
(102, "广州", 23.16, 113.23)
).toDF("id1", "name1", "latitudeA", "longitudeA")
// DF C 包含地点信息和经纬度信息
val C = Seq(
(101, "吉林", 43.8171, 125.3235),
(101, "黑龙江", 45.8023, 126.5350),
(102, "江苏", 32.0603, 118.7969),
(102, "浙江", 30.2875, 120.1536),
(101,"新疆", 43.77, 87.68),
(102, "台湾省", 25.05, 121.50)
).toDF("id2", "name2", "latitudeC", "longitudeC")
val calculateAzimuthUDF = udf(calculateAzimuth _)
// 执行内连接操作,计算方位角并添加到新列 "azimuth"
val azimuthDF = A.join(C, A("id1") === C("id2"))
.withColumn("azimuth", calculateAzimuthUDF($"longitudeA", $"latitudeA", $"longitudeC", $"latitudeC"))
// 计算得到的方位角数据
azimuthDF.show(false)
val haversineDistanceUDF = udf((lat1: Double, lon1: Double, lat2: Double, lon2: Double) => haversineDistance(lat1, lon1, lat2, lon2))
//距离字段添加,@author lixh
val resultDf = azimuthDF.withColumn("distance", haversineDistanceUDF($"latitudeA", $"longitudeA", $"latitudeC", $"longitudeC"))
resultDf.show()
spark.stop()
}
}
代码输出结果
到这里基本就完成了,你可以对输出结果进行小数点位限制,一个round函数就解决。
参考资料:
https://wikipredia.net/zh/Haversine_formula