从一组坐标中寻找线性回归方位角并没有给出预期的结果
Finding linear regression bearing from set of coordinates isn't giving desired result
我有一系列坐标,我想为其找到线性回归,这样我就可以找到直线的方位角。我在这组坐标上使用 Swift 算法俱乐部的 Linear Regression algorithm:
51.48163827836369, -0.019464668521006683
51.481654860705667, -0.019452641350085287
51.481674140657908, -0.01943882290242982
51.481690344713748, -0.019423713183982727
51.481705506128442, -0.019419045258473489
51.481722553489625, -0.01940979681751287
51.48173752576799, -0.019412687104136239
51.48174912673462, -0.019409150632213823
51.4817646359283, -0.019389300889997685
51.481779676567427, -0.019388957697628939
51.481792568262044, -0.019402453532393338
51.481804168699682, -0.019415663863242116
51.481822746271966, -0.019423725406568337
51.481838162880258, -0.019428618620622728
51.481855587496689, -0.01942372804705883
51.481867836051975, -0.019430554178484272
51.481883136496599, -0.019432972610502475
51.481899505688553, -0.019425501321734373
51.481914919015246, -0.019424832166464512
51.481932613348015, -0.019457392982985766
51.481949346615657, -0.019472056279255412
51.481968002664601, -0.019458232757642691
51.481986902059589, -0.019446792660346546
51.482003086257393, -0.019433403642779012
使用 darrinward.com 将其添加到地图中,我得到了这条路径,大致向北移动:
因此,为了获得方位,我采用第一个坐标的经度回归(以获得纬度),:
let regression = linearRegression(longitudes, latitudes)
let firstRegressionCoordinate = CLLocationCoordinate2D(latitude: regression(firstCoordinate.longitude), longitude:firstCoordinate.longitude)
let lastRegressionCoordinate = CLLocationCoordinate2D(latitude: regression(lastCoordinate.longitude), longitude: lastCoordinate.longitude)
return firstRegressionCoordinate.bearing(to: lastRegressionCoordinate)
我的方位角功能正常,并给出了 156.20969
º 的方位角。考虑到实际采用的路径,我期望的是接近 0º 或更接近 360º 的数字。
我觉得问题不在于线性回归算法或方位角函数,而在于我要求的坐标以确定方位角。我问错了吗?还有其他我应该做的不同的事情吗?
完整的代码,可以在 playground 中 运行:
import CoreLocation
public extension FloatingPoint {
public var degreesToRadians: Self { return self * .pi / 180 }
public var radiansToDegrees: Self { return self * 180 / .pi }
}
extension CLLocationCoordinate2D {
///Returns the initial bearing of travel to another coordinate
func bearing(to: CLLocationCoordinate2D) -> CLLocationDegrees {
let fromLatRadians = latitude.degreesToRadians
let fromLongRadians = longitude.degreesToRadians
let toLatRadians = to.latitude.degreesToRadians
let toLongRadians = to.longitude.degreesToRadians
let y = sin(toLongRadians - fromLongRadians) * cos(toLatRadians)
let x = cos(fromLatRadians) * sin(toLatRadians) - sin(fromLatRadians) * cos(toLatRadians) * cos(toLongRadians - fromLongRadians)
var bearing = atan2(y, x).radiansToDegrees
bearing = (bearing + 360.0).truncatingRemainder(dividingBy: 360.0)
return bearing
}
}
extension Array where Element == CLLocationCoordinate2D {
func linearRegressionBearing() -> Double {
var longitudes = [CLLocationDegrees]()
var latitudes = [CLLocationDegrees]()
for coordinate in self {
longitudes.append(coordinate.longitude)
latitudes.append(coordinate.latitude)
}
let regression = linearRegression(longitudes, latitudes)
let firstCoordinate = CLLocationCoordinate2D(latitude: regression(self.first!.longitude), longitude: self.first!.longitude)
let lastCoordinate = CLLocationCoordinate2D(latitude: regression(self.last!.longitude), longitude: self.last!.longitude)
return firstCoordinate.bearing(to: lastCoordinate)
}
}
// A closed form solution
func average(_ input: [Double]) -> Double {
return input.reduce(0, +) / Double(input.count)
}
func multiply(_ a: [Double], _ b: [Double]) -> [Double] {
return zip(a, b).map(*)
}
func linearRegression(_ xs: [Double], _ ys: [Double]) -> (Double) -> Double {
let sum1 = average(multiply(xs, ys)) - average(xs) * average(ys)
let sum2 = average(multiply(xs, xs)) - pow(average(xs), 2)
let slope = sum1 / sum2
let intercept = average(ys) - slope * average(xs)
return { x in intercept + slope * x }
}
let coordinates = [
CLLocationCoordinate2D(latitude: 51.48163827836369, longitude: -0.019464668521006683),
CLLocationCoordinate2D(latitude: 51.481654860705667, longitude: -0.019452641350085287),
CLLocationCoordinate2D(latitude: 51.481674140657908, longitude: -0.01943882290242982),
CLLocationCoordinate2D(latitude: 51.481690344713748, longitude: -0.019423713183982727),
CLLocationCoordinate2D(latitude: 51.481705506128442, longitude: -0.019419045258473489),
CLLocationCoordinate2D(latitude: 51.481722553489625, longitude: -0.01940979681751287),
CLLocationCoordinate2D(latitude: 51.48173752576799, longitude: -0.019412687104136239),
CLLocationCoordinate2D(latitude: 51.48174912673462, longitude: -0.019409150632213823),
CLLocationCoordinate2D(latitude: 51.4817646359283, longitude: -0.019389300889997685),
CLLocationCoordinate2D(latitude: 51.481779676567427, longitude: -0.019388957697628939),
CLLocationCoordinate2D(latitude: 51.481792568262044, longitude: -0.019402453532393338),
CLLocationCoordinate2D(latitude: 51.481804168699682, longitude: -0.019415663863242116),
CLLocationCoordinate2D(latitude: 51.481822746271966, longitude: -0.019423725406568337),
CLLocationCoordinate2D(latitude: 51.481838162880258, longitude: -0.019428618620622728),
CLLocationCoordinate2D(latitude: 51.481855587496689, longitude: -0.01942372804705883),
CLLocationCoordinate2D(latitude: 51.481867836051975, longitude: -0.019430554178484272),
CLLocationCoordinate2D(latitude: 51.481883136496599, longitude: -0.019432972610502475),
CLLocationCoordinate2D(latitude: 51.481899505688553, longitude: -0.019425501321734373),
CLLocationCoordinate2D(latitude: 51.481914919015246, longitude: -0.019424832166464512),
CLLocationCoordinate2D(latitude: 51.481932613348015, longitude: -0.019457392982985766),
CLLocationCoordinate2D(latitude: 51.481949346615657, longitude: -0.019472056279255412),
CLLocationCoordinate2D(latitude: 51.481968002664601, longitude: -0.019458232757642691),
CLLocationCoordinate2D(latitude: 51.481986902059589, longitude: -0.019446792660346546),
CLLocationCoordinate2D(latitude: 51.482003086257393, longitude: -0.019433403642779012)
]
coordinates.linearRegressionBearing()
我确认您的轴承功能正常工作。但是,您获得的纬度回归值为 51.45437262420372 - 这是回归线与经度 = 0.0 相交的位置。我认为您需要同时使用回归线的交点和斜率来计算拟合的经纬度值(在您的地图区域内),然后使用这些值找到方位角。
后期补充:
这里的一个重要问题是经度坐标与距离的关系不是线性的——经度覆盖的距离随纬度而变化。我认为最简单的解决方案就是将您的 lat,lon 值数组转换为 lat,normalisedLon 值,其中 normalisedLon = lon * cosine(lat).
执行此操作时,从第一个坐标到最后一个坐标的路径角度 = 3.055 度(在北方以东,这似乎是正确的)。
完成此操作后,回归线的斜率实际上将代表您正在寻找的方向,即 heading = atan(slope)。
但是,您的回归代码似乎对我不起作用。它也应该给出非常接近 3 度的结果,但距离很远。
我有一系列坐标,我想为其找到线性回归,这样我就可以找到直线的方位角。我在这组坐标上使用 Swift 算法俱乐部的 Linear Regression algorithm:
51.48163827836369, -0.019464668521006683
51.481654860705667, -0.019452641350085287
51.481674140657908, -0.01943882290242982
51.481690344713748, -0.019423713183982727
51.481705506128442, -0.019419045258473489
51.481722553489625, -0.01940979681751287
51.48173752576799, -0.019412687104136239
51.48174912673462, -0.019409150632213823
51.4817646359283, -0.019389300889997685
51.481779676567427, -0.019388957697628939
51.481792568262044, -0.019402453532393338
51.481804168699682, -0.019415663863242116
51.481822746271966, -0.019423725406568337
51.481838162880258, -0.019428618620622728
51.481855587496689, -0.01942372804705883
51.481867836051975, -0.019430554178484272
51.481883136496599, -0.019432972610502475
51.481899505688553, -0.019425501321734373
51.481914919015246, -0.019424832166464512
51.481932613348015, -0.019457392982985766
51.481949346615657, -0.019472056279255412
51.481968002664601, -0.019458232757642691
51.481986902059589, -0.019446792660346546
51.482003086257393, -0.019433403642779012
使用 darrinward.com 将其添加到地图中,我得到了这条路径,大致向北移动:
因此,为了获得方位,我采用第一个坐标的经度回归(以获得纬度),:
let regression = linearRegression(longitudes, latitudes)
let firstRegressionCoordinate = CLLocationCoordinate2D(latitude: regression(firstCoordinate.longitude), longitude:firstCoordinate.longitude)
let lastRegressionCoordinate = CLLocationCoordinate2D(latitude: regression(lastCoordinate.longitude), longitude: lastCoordinate.longitude)
return firstRegressionCoordinate.bearing(to: lastRegressionCoordinate)
我的方位角功能正常,并给出了 156.20969
º 的方位角。考虑到实际采用的路径,我期望的是接近 0º 或更接近 360º 的数字。
我觉得问题不在于线性回归算法或方位角函数,而在于我要求的坐标以确定方位角。我问错了吗?还有其他我应该做的不同的事情吗?
完整的代码,可以在 playground 中 运行:
import CoreLocation
public extension FloatingPoint {
public var degreesToRadians: Self { return self * .pi / 180 }
public var radiansToDegrees: Self { return self * 180 / .pi }
}
extension CLLocationCoordinate2D {
///Returns the initial bearing of travel to another coordinate
func bearing(to: CLLocationCoordinate2D) -> CLLocationDegrees {
let fromLatRadians = latitude.degreesToRadians
let fromLongRadians = longitude.degreesToRadians
let toLatRadians = to.latitude.degreesToRadians
let toLongRadians = to.longitude.degreesToRadians
let y = sin(toLongRadians - fromLongRadians) * cos(toLatRadians)
let x = cos(fromLatRadians) * sin(toLatRadians) - sin(fromLatRadians) * cos(toLatRadians) * cos(toLongRadians - fromLongRadians)
var bearing = atan2(y, x).radiansToDegrees
bearing = (bearing + 360.0).truncatingRemainder(dividingBy: 360.0)
return bearing
}
}
extension Array where Element == CLLocationCoordinate2D {
func linearRegressionBearing() -> Double {
var longitudes = [CLLocationDegrees]()
var latitudes = [CLLocationDegrees]()
for coordinate in self {
longitudes.append(coordinate.longitude)
latitudes.append(coordinate.latitude)
}
let regression = linearRegression(longitudes, latitudes)
let firstCoordinate = CLLocationCoordinate2D(latitude: regression(self.first!.longitude), longitude: self.first!.longitude)
let lastCoordinate = CLLocationCoordinate2D(latitude: regression(self.last!.longitude), longitude: self.last!.longitude)
return firstCoordinate.bearing(to: lastCoordinate)
}
}
// A closed form solution
func average(_ input: [Double]) -> Double {
return input.reduce(0, +) / Double(input.count)
}
func multiply(_ a: [Double], _ b: [Double]) -> [Double] {
return zip(a, b).map(*)
}
func linearRegression(_ xs: [Double], _ ys: [Double]) -> (Double) -> Double {
let sum1 = average(multiply(xs, ys)) - average(xs) * average(ys)
let sum2 = average(multiply(xs, xs)) - pow(average(xs), 2)
let slope = sum1 / sum2
let intercept = average(ys) - slope * average(xs)
return { x in intercept + slope * x }
}
let coordinates = [
CLLocationCoordinate2D(latitude: 51.48163827836369, longitude: -0.019464668521006683),
CLLocationCoordinate2D(latitude: 51.481654860705667, longitude: -0.019452641350085287),
CLLocationCoordinate2D(latitude: 51.481674140657908, longitude: -0.01943882290242982),
CLLocationCoordinate2D(latitude: 51.481690344713748, longitude: -0.019423713183982727),
CLLocationCoordinate2D(latitude: 51.481705506128442, longitude: -0.019419045258473489),
CLLocationCoordinate2D(latitude: 51.481722553489625, longitude: -0.01940979681751287),
CLLocationCoordinate2D(latitude: 51.48173752576799, longitude: -0.019412687104136239),
CLLocationCoordinate2D(latitude: 51.48174912673462, longitude: -0.019409150632213823),
CLLocationCoordinate2D(latitude: 51.4817646359283, longitude: -0.019389300889997685),
CLLocationCoordinate2D(latitude: 51.481779676567427, longitude: -0.019388957697628939),
CLLocationCoordinate2D(latitude: 51.481792568262044, longitude: -0.019402453532393338),
CLLocationCoordinate2D(latitude: 51.481804168699682, longitude: -0.019415663863242116),
CLLocationCoordinate2D(latitude: 51.481822746271966, longitude: -0.019423725406568337),
CLLocationCoordinate2D(latitude: 51.481838162880258, longitude: -0.019428618620622728),
CLLocationCoordinate2D(latitude: 51.481855587496689, longitude: -0.01942372804705883),
CLLocationCoordinate2D(latitude: 51.481867836051975, longitude: -0.019430554178484272),
CLLocationCoordinate2D(latitude: 51.481883136496599, longitude: -0.019432972610502475),
CLLocationCoordinate2D(latitude: 51.481899505688553, longitude: -0.019425501321734373),
CLLocationCoordinate2D(latitude: 51.481914919015246, longitude: -0.019424832166464512),
CLLocationCoordinate2D(latitude: 51.481932613348015, longitude: -0.019457392982985766),
CLLocationCoordinate2D(latitude: 51.481949346615657, longitude: -0.019472056279255412),
CLLocationCoordinate2D(latitude: 51.481968002664601, longitude: -0.019458232757642691),
CLLocationCoordinate2D(latitude: 51.481986902059589, longitude: -0.019446792660346546),
CLLocationCoordinate2D(latitude: 51.482003086257393, longitude: -0.019433403642779012)
]
coordinates.linearRegressionBearing()
我确认您的轴承功能正常工作。但是,您获得的纬度回归值为 51.45437262420372 - 这是回归线与经度 = 0.0 相交的位置。我认为您需要同时使用回归线的交点和斜率来计算拟合的经纬度值(在您的地图区域内),然后使用这些值找到方位角。
后期补充: 这里的一个重要问题是经度坐标与距离的关系不是线性的——经度覆盖的距离随纬度而变化。我认为最简单的解决方案就是将您的 lat,lon 值数组转换为 lat,normalisedLon 值,其中 normalisedLon = lon * cosine(lat).
执行此操作时,从第一个坐标到最后一个坐标的路径角度 = 3.055 度(在北方以东,这似乎是正确的)。
完成此操作后,回归线的斜率实际上将代表您正在寻找的方向,即 heading = atan(slope)。
但是,您的回归代码似乎对我不起作用。它也应该给出非常接近 3 度的结果,但距离很远。