Compute a distance including elevation/terrain data for a KmlLinestring - javascript

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?

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.
Sample the line string at certain points (say every 10 meters).
Get the ground altitude at each point.
Convert each point to Cartesian coordinates
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);

Related

Calculate distance between two Coordinates, with short distances considering altitude variation

I'm working on a GPS application for agriculture purposes. I need a way to calculate with precision the distance, and difference (X, Y) between two coordinates, using latitude, longitude, and altitude (altitude is given in meters).
The points will be close to each other, something about 10m or less, the distance will be used in other calculations like area, and the difference (X, Y) will be used to render a 2D map.
My first thought was to use the right triangle to calculate the 2D (X, Y), and again to calculate the 3D distance.
Here a Script (in Javascript):
function xyDifference(lat1, lng1, lat2, lng2){
var xdif = (lng1 - lng2)/0.00000898; //value to convert decimal degrees difference to meters
var ydif = (lat1 - lat2)/0.00000898;
return [xdif,ydif];
}
function distanceBetween2D(lat1,lng1,lat2,lng2) {
var b = Math.abs(lat1 - lat2);
var c = Math.abs(lng1 - lng2);
var a = Math.sqrt(Math.pow(b,2) + Math.pow(c,2));
var dst = a / 0.00000898;
return dst;
}
function distanceBetween3D(lat1,lng1,alt1,lat2,lng2,alt2){
var dst = distanceBetween2D(lat1,lng1,lat2,lng2);
a = Math.abs(alt1 - alt2);
dst = Math.sqrt(Math.pow(dst,2) + Math.pow(a,2));
return dst;
}
console.log(distanceBetween(-24.09234566666,-52.5289494999999,588,-24.09231633333,-52.5288795,589));
//It prints 8.51m
//Using Haversine formula with these values it gives something about 7.82m
I want to know if this is a good way, or if there is a better way. I have seen the Haversine formula but I don't know how to use it with the altitudes difference, and how to get an (X,Y) difference to render the map.
Suggest using a geodesy library, in particular https://www.movable-type.co.uk/scripts/geodesy-library.html. Note that this libray offers a number of geodesy models to chose from ( eg, spherical vs elliptical ). In your particular case, I have selected two different modules to exemplify their use...
"latlon-ellipsoidal-vincenty.js" which offers functions for calculating the distance between Lat / Lon, but does not support the calculation with height ( altitude ).
"latlon-ellipsoidal.js" which offers functions to convert Lat / Lon to cartesian earth-centred earth-fixed (ECEF) vectors, which factors in altitude.
<script type="module">
import LatLonE from 'https://cdn.jsdelivr.net/npm/geodesy#2/latlon-ellipsoidal-vincenty.js';
import LatLon, { Cartesian, Vector3d, Dms } from 'https://cdn.jsdelivr.net/npm/geodesy#2/latlon-ellipsoidal.js';
let latlon0 = new LatLonE( -24.09234566666, -52.5289494999999 );
let latlon1 = new LatLonE( -24.09231633333, -52.5288795 );
let latlonDistance = latlon0.distanceTo( latlon1 );
console.log( `Lat/Lon distance from ${latlon0.toString()} to ${latlon1.toString()} is ${latlonDistance}m` );
let cart0 = new LatLon( -24.09234566666, -52.5289494999999, 588 ).toCartesian();
let cart1 = new LatLon( -24.09231633333, -52.5288795, 589 ).toCartesian();
let cartDistance = Math.sqrt( ( cart1.x - cart0.x ) ** 2 + ( cart1.y - cart0.y ) ** 2 + ( cart1.z - cart0.z ) ** 2 );
console.log( `Cartesian distance from ${cart0.toString()} to ${cart1.toString()} is ${cartDistance}m` );
</script>
Note that the Lat / Lon calculation results in the 7.824m that you observed in the Haversine formula. ( This is in line with #Bergi's comment too, in that the Haversine formula does not appear to take into account altitude. )
The conversion to cartesian coordinates and calculation of distance results in a value a touch bigger, specifically 7.88836m, as there is an elevation difference of 1m between the coordinates. A straight up calculation of the hypotenuse of right a triangle with sides of 7.824m and 1m results in a length of 7.88765m, very close to the result of the library. Bearing in mind that 1) the higher the altitude, the further apart the same Lat / Lon becomes and 2) the ellipsoidal shape of the earth affects the calculations too, then the result of the library is within reason...

Mysql polygon between multiple coordinates

I have 4 points on the map(latitude, longitude), there can be a more.
And I need to build a MySQL POLYGON with 10 Kilometers wide between these points.
As you see from the picture - I have blue line, and I need a polygon (green outline).
My first thought was to create two separate points for each given point.
For example:
Given 1st point(Bremen)
calculate 5km right and add a point,
calculate 5km left and add a point.
Logic is simple.
BUT the problem is - I don't know what to calculate(right,top,bottom,left), i need some kind of angle, but I am stuck here.
I just need an algorithm, no need for a full code example.
function in javascript i tried:
var meters = 10000 / 2;
var my_lat = 52.51978;
var my_long = 13.388211;
// number of km per degree = ~111km (111.32 in google maps, but range varies
/* between 110.567km at the equator and 111.699km at the poles) */
// 1km in degree = 1 / 111.32km = 0.0089
// 1m in degree = 0.0089 / 1000 = 0.0000089
var coef = meters * 0.0000089;
var new_lat = my_lat + coef;
// pi / 180 = 0.018
var new_long = my_long - coef / Math.cos(my_lat * 0.018);

Parametric equation to place a leaflet marker on the circumference of a circle is not precise?

I am working on an application where I have the center of a circle and the radius and I am plotting the circle with the help of Leaflet.
I placed a marker on the north most end of the circumference and made it draggable.
var circle = L.circle(coords, radius).addTo(map);
convertRadiusToLatitude = parseInt(response.radius)/111111;
var coordsOnRadius = [parseFloat(response.lat) + convertRadiusToLatitude, parseFloat(response.long)];
var markerOnRadius = L.marker(coordsOnRadius, {draggable: true}).addTo(map);
Now, this adds the marker to the circumference and now I wanted it to be draggable only on the circumference itself for which I used the parametric equation.
Parametric equation
x = Xc + R * cos(theta)
y = Yc + R * sin(theta)
Code for dragging
markerOnRadius.on('drag', function(e){
bearing = marker.getLatLng().bearingTo(markerOnRadius.getLatLng());
var markerOnRadiusX = parseFloat(response.lat) + ((0.000009 * parseFloat(response.radius)) * Math.cos( toRad(bearing) ));
var markerOnRadiusY = parseFloat(response.long) + ((0.000009 * parseFloat(response.radius)) * Math.sin( toRad(bearing) ));
markerOnRadius.setLatLng([markerOnRadiusX, markerOnRadiusY]);
});
The bearingTo method:
L.LatLng.prototype.bearingTo = function(other) {
var d2r = L.LatLng.DEG_TO_RAD;
var r2d = L.LatLng.RAD_TO_DEG;
var lat1 = this.lat * d2r;
var lat2 = other.lat * d2r;
var dLon = (other.lng-this.lng) * d2r;
var y = Math.sin(dLon) * Math.cos(lat2);
var x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
var brng = Math.atan2(y, x);
brng = parseInt( brng * r2d );
brng = (brng + 360) % 360;
return brng;
};
Issue
When I start dragging the marker, this code is working fine and brings it back to the circumference at the bearing at which the marker is dragged to. But there is one problem, the coords on the circumference are slightly off and in terms of longitude. When the bearing is 0 (north), the coords are perfect, but when it is 90 (east), the longitude is slightly less that it should for the marker to be at the circumference.
Again at 180 (south), coords are perfect, but at 270 (west), the longitude calculated is slightly less and the marker tends towards the radius again.
So basically if you visualize the marker being dragged, it starts perfectly on the north end and starts coming inside the circle slightly increasing with the bearing till it reacher 90 and then starts going towards the circumference again till 180 when it is perfect again.
It forms more like a ellipse if you get the gist of it.
Could anyone tell me why is longitude coming a little off and why the marker moves in an elliptical path. Has it something to do with the world coordinates and window coordinates. Or are my equations slightly off somewhere?
It does look like a projection issue. In your dragging code you are basically doing
lat = a + r cos(baring)
long = b + r sin(baring)
giving a circle in the Lat-Long coordinates. This would work fine if you were at the equator with Mercator projection. You will get more distortion as you move further towards the polls.
Assume you are using the defaults for Leaflet reference doc You have the EPSG3857 Web Mercator coordinates.
If you want to ensure you have a exact circle it will be better to work using screen coordinates. You can get these using methods on the ICRS objects. First get the coordinate system L.CRS.EPSG3857 and use the latLngToPoint and pointToLatLng methods.
var crs = L.CRS.EPSG3857;
var zoom = ...; // how you calculate your zoom factor
markerOnRadius.on('drag', function(e){
var markerLL = marker.getLatLng()
var morLL = markerOnRadius.getLatLng();
var markerP = crs.latLngToPoint(markerLL,zoom);
var morP = crs.latLngToPoint(morLL,zoom);
// get the distance between the two points
var dist = markerP.distanceTo(morP);
// Get the vector from center to point
var A = morP.subtract(markerP);
// scale so its of the desired length
var B = A. multiplyBy( factor / dist);
// Add on the center
var C = markerP.add(B);
// Convert back to LatLong
var D = crs.pointToLatLng(C,zoom);
markerOnRadius.setLatLong(D);
});

Leaflet calculating meters per pixel at zoom level

I am trying to determine a way to calculate the number of meters represented by 1 pixel at a given zoom level and geo centerpoint in Leaflet. Could anyone direct me to the math involved or if there is a way to do this out of the box in leaflet? I am not finding much out there.
You can use the containerPointToLatLng conversion function of L.Map to get the latLngcoordinates for a given pixelcoordinate. If you take one of the first pixel, and one of the next, you can use the distanceTo utility method of L.LatLng to calculate the distance in meters between them. See the following code (assuming map is an instance of L.Map):
var centerLatLng = map.getCenter(); // get map center
var pointC = map.latLngToContainerPoint(centerLatLng); // convert to containerpoint (pixels)
var pointX = [pointC.x + 1, pointC.y]; // add one pixel to x
var pointY = [pointC.x, pointC.y + 1]; // add one pixel to y
// convert containerpoints to latlng's
var latLngC = map.containerPointToLatLng(pointC);
var latLngX = map.containerPointToLatLng(pointX);
var latLngY = map.containerPointToLatLng(pointY);
var distanceX = latLngC.distanceTo(latLngX); // calculate distance between c and x (latitude)
var distanceY = latLngC.distanceTo(latLngY); // calculate distance between c and y (longitude)
That should work, thanks to Jarek Piórkowski for pointing my mistake before the edit.
You can use this to work out the metres per pixel:
metresPerPixel = 40075016.686 * Math.abs(Math.cos(map.getCenter().lat * Math.PI/180)) / Math.pow(2, map.getZoom()+8);
Take a look at openstreetmap.org page on zoom levels. It gives this formula for calculating the meters per pixel:
The distance represented by one pixel (S) is given by
S=C*cos(y)/2^(z+8) where...
C is the (equatorial) circumference of the Earth
z is the zoom level
y is the latitude of where you're interested in the scale.
Correct me if I am wrong, IMHO, the number of meters per pixel = map height in meters / map height in pixels
function metresPerPixel() {
const southEastPoint = map.getBounds().getSouthEast();
const northEastPoint = map.getBounds().getNorthEast();
const mapHeightInMetres = southEastPoint.distanceTo(northEastPoint);
const mapHeightInPixels = map.getSize().y;
return mapHeightInMetres / mapHeightInPixels;
}

How to get shortest distance between two shapes on Google Maps, using JavaScript?

I am using the Google Maps API in my application (ASP.NET with MVC).
I have an array of coordinates (each consisting of latitude and longitude), let us call it "origin" (this can be a polygon, polyline or marker) and another array of coordinates, let us call that "destination" (can be a polygon, polyline or marker either).
I want to calculate the shortest distance between "origin" and "destination". How can I do that?
Well, looking from it at a mathematical standpoint:
your problem is to find the shortest distance between a point in space and a vektor line or plane.
So if you have your coords in arrays like [a1,a2,a3] and [b1,b2,b3] the distance between this 2 points in 3 dimensional space is like the Pythagorean theorem with three elements:
sqrt[(a1-b1)²+(a2-b2)²+(a3-b3)²]=d(a,b).
I know this does not take the curvature of the earth into account but for "short" distances this is is not important.
If you understand some math the wikipedia article might help you as well. http://en.wikipedia.org/wiki/Euclidean_distance#Three_dimensions
Edit 12.08.14:
To take the curvature of the earth into account, you can do a simple calculation:
(1) You already know the distance of the earth
(2) you know the approx. radius of the earth
With your start point(A) and your destination(B) known, you now build a triangle with the center of the earth(C). You do this to now calculate the angle at (C) (sin/cos/tan). With that angle you can now get the length of the earth (including the curvature).
([boundary of earth]/360°)*[angle at (C)] = the dinstance from (A) to (B) over the curvature of the earth.
I recommend that you use the Spherical Law of Cosines to calculate the distance between the points. If you have an array of latitude and longitude for the origins, and an array of latitude and longitude coordinates for destinations, then you can do something like this:
var origins = [{lat: "35.5", lon: "-80.0"}, ...]; // Array of origin coordinates
var destinations = [{lat: "34.5", lon: "-80.0"}, ...]; // Array of destination coordinates
var shortestDistance = null;
var shortestPair = [];
for (i = 0; i < origins.length; i++) {
for (j = 0; j < destinations.length; j++) {
var lat1 = origins[i].lat.toRadians();
var lat2 = destinations[j].lat.toRadians();
var lon = (destinations[j].lon - origins[i].lon).toRadians();
var R = 6371; // gives distance in kilometers
var calcDistance = Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon)) * R;
if (shortestDistance === null || calcDistance < shortestDistance) {
shortestPair[0] = origins[i]; // Store the origin coordinates
shortestPair[1] = destinations[j]; // Store the destination coordinates
shortestDistance = calcDistance; // Update the shortest distance
}
}
}
/* After this runs, you'll have the array indexes for the origin and
destination with the shortest distance as well as the actual distance (kilometers)
in the array shortestPair and the variable shortestDistance respectively.
For miles, divide shortestDistance by 1.609344
For nautical miles, divide shortestDistance by 1.852
*/
This seems to be a simpler approach than attempting to use the Maps API for the distance calculations. The above formula was sourced from http://www.movable-type.co.uk/scripts/latlong.html. You could also use the haversine formula if you need the calculations to be more accurate; it's detailed on the page that I linked.
One solution would be to take one of the options found here and calculate the distance from each point in the origin to each point in the destination. The smallest distance would be the distance between your two shapes.
Code might look like this (untested):
var minDistance = Number.POSITIVE_INFINITY;
for (var i=0; i<origin.length; i++){
for (var j=0; j<destination.length; j++){
var dist = google.maps.geometry.spherical.computeDistanceBetween(origin[i], destination[j]);
if (dist < minDistance)
minDistance = dist;
}
}
This could likely be optimized if performance is an issue. For more information on that, I would look at this question and its answers which deal with the same issue, although from a purely mathematical perspective.
function moveAlongPath(points, distance, index) {
index = index || 0;
if (index < points.length && typeof points[index +1] !="undefined") {
var polyline = new google.maps.Polyline({
path: [points[index], points[index + 1]],
geodesic: true,
strokeColor: '#FF0000',
strokeOpacity: 1.0,
strokeWeight: 2
});
var distanceToNextPoint = polyline.Distance();
if (distance <= distanceToNextPoint) {
return polyline_des(points[index],points[index + 1], distance);
}
else {
return moveAlongPath(points,
distance - distanceToNextPoint,
index + 1);
}
}
else {
return null;
}
}

Categories

Resources