Interpolate between 2 GPS locations based on walking speed

前端 未结 5 1603
忘了有多久
忘了有多久 2021-02-15 10:39

Problem:


Given two locations:

L1 = (latitude1, longitude1, timestamp1), L2

5条回答
  •  北荒
    北荒 (楼主)
    2021-02-15 11:17

    Take a close look at Calculate distance, bearing and more between Latitude/Longitude points

    It contains several formulas and JavaScript examples that might help you. I know that it's NOT Java, but it should be simple enough to port the code over. Especially given the detailed description of the formula.

    EDIT:

    While it seems OK to use linear interpolation for shorter distances, it can in fact be quite off, especially as you get closer to the poles. Seeing from the example that you are in Hamburg, this will already have an effect that's noticable over a few hundred meters. See this answer for a good explanation.

    The Problem: The distance between 1 degree in longitude varies greatly depending on your latitude.

    This is because the earth is NOT flat, but a sphere - actually an ellipsoid. Therefore a straight line on a two dimensional map is NOT a straight line on the globe - and vice versa.

    To get around this problem one can use the following approach:

    1. Get the bearing from the start coordinate (L1) to the end coordinate (L2)
    2. Calculate a new coordinate from the start coordinate (L1) along a great circle path, given the calculated bearing and a specified distance
    3. Repeat this process, but using the newly calculated coordinate as the starting coordinate

    We can create a few simple functions that will do the trick for us:

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

    This uses a mean earth radius of 6371 km. See Wikipedia for an explanation of this number and its accuracy.

    One can now calculate a new intermediary location between the two points, given a distance travelled (in km):

    double bearing = CalculateBearing(startLocation, endLocation);
    
    Location intermediaryLocation = CalculateDestinationLocation(startLocation, bearing, distanceTravelled);
    

    Assuming a speed of v (e.g. 1.39) meters per second, one can now use a simple for loop to get points 1 second apart:

    List locations = new ArrayList();
    
    // 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;
    }
    

    As an added bonus, you can even determin the time required to travel between any two points:

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

    This will result in greater accuracy over any distance than a simple linear interpolation using just the delta of the coordinates. Although this approach is not perfect, it will have an error of generally 0.3% or less, which is quite acceptable. If you need a better solution, you might want to look into the Vincenty formula.

提交回复
热议问题