根据步行速度插入2个GPS位置

时间:2015-09-28 18:10:55

标签: java android algorithm gps interpolation

问题:

给出两个位置:

L 1 =(纬度 1 ,经度 1 ,时间戳 1 L 2 =(纬度 2 ,经度 2 ,timestamp 2

和可配置但不变的移动速度:

v =每秒1.39米(例如)。

当我从 L 1 旅行到 L 2 时,我们如何在这两个位置之间进行插值以估计用户位置/强>

我一直在寻找这个问题的解决方案,到目前为止我发现,对于小距离(远离极点),可以使用线性插值。所以,我在维基百科上查了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);
}

所以我想用这个lerp函数来插入 L 1 L 2 。这很容易。如何计算 t ?我猜测我必须计算一些时间增量,但我如何计算运动速度?

编辑:

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

示例:

map

5 个答案:

答案 0 :(得分:10)

仔细查看Calculate distance, bearing and more between Latitude/Longitude points

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

修改

虽然似乎可以使用线性插值来缩短距离,但事实上它可能非常偏离,特别是当你靠近极点时。从你在汉堡的例子来看,这已经具有了几百米的显着效果。有关详细说明,请参阅this answer

问题:经度1度之间的距离因纬度而异。

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

要解决此问题,可以使用以下方法:

  1. 从起始坐标(L1)到结束坐标(L2)
  2. 获取方位
  3. 在给定计算的方位和指定距离的情况下,沿大圆路径从起始坐标(L1)计算新坐标
  4. 重复此过程,但使用新计算的坐标作为起始坐标
  5. 我们可以创建一些简单的函数来为我们提供技巧:

    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.logintude);
    
        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 - 1));
    
        return (radius * c);
    }
    

    这使用的平均地球半径为6371 km。有关此编号及其准确性的说明,请参阅Wikipedia

    现在可以计算两个点之间的新中间位置,给定距离(以km为单位):

    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公式。

答案 1 :(得分:1)

  

我猜我必须计算一些时间增量,但我该怎么做   移动速度因素?

在线性插值中,在您的情况下,使用从开始时间t1到结束时间t2运行的迭代变量t,使用预定义步骤在两个时间点之间进行迭代。 假设步长= 1秒,这对您的应用程序非常有用。

long t1 = location1.getTimeStamp(); // in milliseconds;
long t2 = location2.getTimeStamp();
double deltaLat = location2.latitude - location1.latitude;
doule deltaLon =  location2.longitude- location1.longtude;
// remove this line if you don't have measured speed:
double deltaSpeed =  location2.speed - location1.speed;

long step = 1 * 1000; // 1 second in millis 
for (long t = t1; t1 < t2; t+= step) {

   // t0_1 shall run from 0.0 to (nearly) 1.0 in that loop
  double t0_1 = (t - t1) / (t2 - t1);
  double latInter = lat1 + deltaLat  * t0_1;
  double lonInter = lon1 + deltaLon  * t0_1;
  // remove the line below if you dont have speed
  double speedInter = speed1 + deltaSpeed  * t0_1;
  Location interPolLocation = new Location(latInter, lonInter, speedInter);
  // add interPolLocation to list or plot.
}

答案 2 :(得分:1)

还有一些其他插值策略比线性插值更好,包括运动插值,它将锚点的初始和最终速度作为输入。例如,请参阅最近的一篇论文中的这种比较(Long JA(2015)运动数据的运动插值.Ind J Geogr Inf Sci 8816:1-15.doi:10.1080 / 13658816.2015.1081909):

From Long JA (2015) Kinematic interpolation of movement data. Int J Geogr Inf Sci 8816:1–15. doi: 10.1080/13658816.2015.1081909

Kinematic Interpolation有RPython个实现。编写Java版本应该很容易。

答案 3 :(得分:1)

如果您首先将纬度/长度转换为n向量(https://en.wikipedia.org/wiki/N-vector),那么这些计算实际上非常简单。转换后你可以使用标准插值,你也可以避免长距离,极点或日期线的任何问题。

如果您在维基百科页面上查看“外部链接”,则会有一个页面(http://www.navlab.net/nvector/),其中有十个问题已解决,该页面上的问题6(插值位置)应与您的问题相同。正如您所看到的,该解决方案对于任何距离都是精确的,并且也适用于任何地球位置,如极点。

答案 4 :(得分:0)

是的。线性插值。

L1 = (1, 2, 3)
L2 = (4, 5, 6)
desired_number_of_interpolation_points = 9
interpolation_points = []

lat_step = (L2[0] - L1[0]) / (desired_number_of_interpolation_points + 1)
lon_step = (L2[1] - L1[1]) / (desired_number_of_interpolation_points + 1)
time_step = (L2[2] - L1[2]) / (desired_number_of_interpolation_points + 1) 

for i in range(1, desired_number_of_interpolation_points + 1)
    interpolation_points.append((lat_step * i, lon_step * i, time_step * i))