Search code examples
javascriptcomputational-geometrygoogle-earth-plugin

Compute a distance including elevation/terrain data for a KmlLinestring


The Google Earth Desktop Application shows the both the map length and ground length of a line.

In the Google Earth plugin I want to do a similar thing, that is I wish to determine the ground length of a tessellated KmlLineString taking the terrain into account.

Can I do this, and if so, how?


Solution

  • You can certainly get the length pretty easily if you use the earth-api-utility-library. Using that you can do.

    var length = (new geo.Path(linestring)).distance(); 
    

    Granted this method does not take the terrain into account - but there are a number of caveats you should be aware of before trying calculate distances using an elevation gradient.

    Firstly any differences between topographic and direct distance are minimal in most cases. Indeed many quality GPS receivers simply don't take any changes in elevation into account when calculating distances.

    Secondly ground altitude is one of the most unreliable pieces data. Using a gradient based on elevation to determine distance will often produce greater inaccuracy in distance measurements than using a simple 'as the crow flies' measure.

    Bearing that in mind, if you still wanted to do it then one way would be something like the following.

    1. Sample the line string at certain points (say every 10 meters).
    2. Get the ground altitude at each point.
    3. Convert each point to Cartesian coordinates
    4. Calculate the angular distances between each Cartesian point in sequence.

    You can improve your precision of this kind of method in two ways, either by increasing the sampling rate (say every meter) or by applying a smoothing procedure to the results.

    For a rougher version, you could just loop over the coordinates in the the KmlLinestring itself, rather than resampling at some set distance. You would use the latitude, longitude of the coordinate to get the ground altitude at each point. Then you would construct a Cartesian coordinate from this data (latitude, longitude, elevation => X,Y,Z) and work out the angular distance between it and the next point...and so on.

    something like the following idea should work - although it is written here and untested!

    var EARTH_RADIUS = 6378135; // approximate in meters 
    
    var degreestoRadians = function(degrees) {
      return degrees * Math.PI / 180;
    }
    
    var arcLength = function(point1 , point2) {   
      var length = Math.sqrt(Math.pow(point1.X-point2.X, 2) 
                   + Math.pow(point1.Y-point2.Y, 2) 
                   + Math.pow(point1.Z-point2.Z, 2));
      var angle = 2 * Math.asin(length/2/EARTH_RADIUS);
      return EARTH_RADIUS * angle;
    }
    
    var sphericalToCartesian = function(latitude, longitude, altitude) {
      var phi = degreestoRadians(latitude);
      var theta = degreestoRadians(longitude);
      var rho = EARTH_RADIUS + altitude;
      return {
        X: Math.cos(phi) * Math.cos(theta) * rho,
        Y: Math.cos(phi) * Math.sin(theta) * rho,
        Z: Math.sin(phi) * rho
      }
    }
    
    var topographicDistance = function(linestring) {
      var coordinates = linestring.getCoordinates(); //KmlCoordArray
      var last = null;
      var distance = 0;
      for(var i = 0; i < coordinates.length; i++) {
         var coord = coordinates.get(i); //KmlCoord
         var lat = coord.getLatitude();
         var lng = coord.getLongitude();
         var alt  = ge.getGlobe().getGroundAltitude(lat, lng);
         var latest = sphericalToCartesian(lat, lng, alt);
         if(last != null) {
           distance += arcLength(last, latest);
         } 
    
         last = latest;
      }
    
      return distance;
    }
    

    You would use it like so...

    var distance = topographicDistance(yourLinestring);