根据步行速度在 2 个 GPS 位置之间进行插值

2024-04-14

Problem:


给定两个位置:

L1 = (latitude1, longitude1, timestamp1), L2 = (latitude2, longitude2, timestamp2),

以及可配置但恒定的移动速度:

v = 1.39 米每秒(例如)。

How can we interpolate between these two locations to estimate a users location as he travels from L1 to L2?


我一直在寻找这个问题的解决方案,到目前为止我发现,对于小距离(远离极点)可以使用线性插值。于是,我抬头一看线性插值 https://en.wikipedia.org/wiki/Linear_interpolation在维基百科上找到了这个:

// Imprecise method which does not guarantee v = v1 when t = 1,
// due to floating-point arithmetic error.
float lerp(float v0, float v1, float t) {
    return v0 + t*(v1-v0);
}

So I am thinking of using this lerp function to interpolate latitude and longitude between L1 and L2. That was the easy part. How do I calculate t? I'm guessing I have to calculate some time deltas, but how do I factor in the movement speed?


Edit:

我正在测试各种收集 GPS 位置的方法。为此,我在整个步行过程中记录路径点位置。我需要使用移动速度在这些路径点之间进行插值,以估计我沿步行的位置。然后我可以将我的结果与估计进行比较,看看它们的表现如何。

Example:


仔细看看计算纬度/经度点之间的距离、方位角等 http://www.movable-type.co.uk/scripts/latlong.html

它包含几个可能对您有帮助的公式和 JavaScript 示例。我知道它不是 Java,但它应该足够简单,可以移植代码。特别给出了公式的详细描述。

EDIT:

虽然对于较短的距离使用线性插值似乎可以,但实际上可能会很糟糕,尤其是当您接近极点时。从您在汉堡的示例来看,这已经在几百米范围内产生了明显的影响。看这个答案 https://stackoverflow.com/a/1739066/801652以获得一个很好的解释。

问题:经度 1 度之间的距离根据纬度的不同而变化很大。

这是因为地球是NOT平坦,但是是一个球体——实际上是一个椭球体。因此二维地图上的直线是NOT地球上的一条直线 - 反之亦然。

为了解决这个问题,可以使用以下方法:

  1. 获取从起始坐标(L1)到终止坐标(L2)的方位角
  2. 给定计算出的方位角和指定距离,从起始坐标 (L1) 沿大圆路径计算新坐标
  3. 重复此过程,但使用新计算的坐标作为起始坐标

我们可以创建一些简单的函数来实现我们的目的:

double radius = 6371; // earth's mean radius in km

// Helper function to convert degrees to radians
double DegToRad(double deg) {
    return (deg * Math.PI / 180);
}

// Helper function to convert radians to degrees
double RadToDeg(double rad) {
    return (rad * 180 / Math.PI);
}

// Calculate the (initial) bearing between two points, in degrees
double CalculateBearing(Location startPoint, Location endPoint) {
    double lat1 = DegToRad(startPoint.latitude);
    double lat2 = DegToRad(endPoint.latitude);
    double deltaLon = DegToRad(endPoint.longitude - startPoint.longitude);

    double y = Math.sin(deltaLon) * Math.cos(lat2);
    double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(deltaLon);
    double bearing = Math.atan2(y, x);

    // since atan2 returns a value between -180 and +180, we need to convert it to 0 - 360 degrees
    return (RadToDeg(bearing) + 360) % 360;
}

// Calculate the destination point from given point having travelled the given distance (in km), on the given initial bearing (bearing may vary before destination is reached)
Location CalculateDestinationLocation(Location point, double bearing, double distance) {

    distance = distance / radius; // convert to angular distance in radians
    bearing = DegToRad(bearing); // convert bearing in degrees to radians

    double lat1 = DegToRad(point.latitude);
    double lon1 = DegToRad(point.longitude);

    double lat2 = Math.asin(Math.sin(lat1) * Math.cos(distance) + Math.cos(lat1) * Math.sin(distance) * Math.cos(bearing));
    double lon2 = lon1 + Math.atan2(Math.sin(bearing) * Math.sin(distance) * Math.cos(lat1), Math.cos(distance) - Math.sin(lat1) * Math.sin(lat2));
    lon2 = (lon2 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalize to -180 - + 180 degrees

    return new Location(RadToDeg(lat2), RadToDeg(lon2));
}

// Calculate the distance between two points in km
double CalculateDistanceBetweenLocations(Location startPoint, Location endPoint) {

    double lat1 = DegToRad(startPoint.latitude);
    double lon1 = DegToRad(startPoint.longitude);

    double lat2 = DegToRad(endPoint.latitude);
    double lon2 = DegToRad(endPoint.longitude);

    double deltaLat = lat2 - lat1;
    double deltaLon = lon2 - lon1;

    double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

    return (radius * c);
}

这里使用的平均地球半径为 6371 公里。看维基百科 https://en.wikipedia.org/wiki/Earth_radius有关此数字及其准确性的解释。

现在可以在给定行驶距离(以公里为单位)的情况下计算两点之间的新中间位置:

double bearing = CalculateBearing(startLocation, endLocation);

Location intermediaryLocation = CalculateDestinationLocation(startLocation, bearing, distanceTravelled);

假设速度为每秒 v(例如 1.39)米,现在可以使用简单的 for 循环来获取相隔 1 秒的点:

List<Location> locations = new ArrayList<Location>();

// assuming duration in full seconds
for (int i = 0; i < duration; i++){
    double bearing = CalculateBearing(startLocation, endLocation);
    double distanceInKm = v / 1000;
    Location intermediaryLocation = CalculateDestinationLocation(startLocation, bearing, distanceInKm);

    // add intermediary location to list
    locations.add(intermediaryLocation);

    // set intermediary location as new starting location
    startLocation = intermediaryLocation;
}

作为额外的好处,您甚至可以确定任意两点之间行驶所需的时间:

double distanceBetweenPoints = CalculateDistanceBetweenLocations(startPoint, endPoint) * 1000; // multiply by 1000 to get meters instead of km

double timeRequired = distanceBetweenPoints / v;

与仅使用坐标增量的简单线性插值相比,这将在任何距离上产生更高的精度。虽然这种方法并不完美,但一般误差在0.3%以下,这是完全可以接受的。如果您需要更好的解决方案,您可能需要研究 Vincenty 公式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

根据步行速度在 2 个 GPS 位置之间进行插值 的相关文章

随机推荐