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;
}
Related
I'm looking for a way to find multiple coordinates between 2 points in Javascript (I don't need distance or mid point).
Say I have these 2 points:
Point A (left) lat: 39.091868 long: -9.263187
Point B (right) lat:
39.089815 long: -9.261857
I want to generate for instance 20 coordinates between points A and B.
I tried using this piece of Javacript code (Found on an old thread):
Point = function(x, y) {
this.x = x;
this.y = y;
}
var pointA = new Point(39.091868, -9.263187);
var pointB = new Point(39.089815, -9.261857);
var numberOfPoints = 20;
var points = new Array();
for (var i = 0; i < numberOfPoints; i++) {
points.push(new Point((Math.abs(pointA.x - pointB.x) / 10) * i + pointB.y, (Math.abs(pointA.y - pointB.y) / numberOfPoints) * i + pointB.y));
}
console.log(points);
When I run the code above, it prints out 20 values of the points array between point A and B. However, it only seems to store the correct longitude values of the points. (x and y both show longitude values).
For example, the first item of the points array is:
Point x: -9.261857 y: -9.261857
How should I adjust the calculation inside the for loop so that each point between point A and B also stores a correct latitude value? Or does anyone know another way to calculate a fixed number of coordinates between 2 points in Javascript?
Any kind of help is appreciated
Because the earth is not flat, the calculation can be good for small distance.
Just calculate the point.
1 Degree is approximal 110 km. so you may do just Pitagoras calculation for small distance and multiply by 110 km, and do iterative loop.
For accuracy you may use open-layers library (most of it is free), but you should provide map with projection. Make things a bit complicated.
One of the function is find the coordinate of a specific point, give distance and azimuth.
Need to find the the azimuth first of two points.
Use O/L: openlayers sphere getlength & bearing.
O/L distance
Open layers
I am building a map in d3 and basing it off of this codepen by Andy Barefoot: https://codepen.io/nb123456/pen/zLdqvM?editors=0010. I want to modify the initiateZoom() function so that if I set the lat/lon coordinates for a box surrounding say Ohio, the map will initialize its panning to be over Ohio.
function initiateZoom() {
minZoom = Math.max($("#map-holder").width() / w, $("#map-holder").height() / h);
maxZoom = 20 * minZoom;
zoom
.scaleExtent([minZoom, maxZoom])
.translateExtent([[0, 0], [w, h]])
;
midX = ($("#map-holder").width() - minZoom * w) / 2;
midY = ($("#map-holder").height() - minZoom * h) / 2;//These are the original values
var swlat = 32;
var swlon = -82;
var nelat = 42;
var nelon = -72;
var projectCoordinates = projection([(swlat+nelat)/2, (swlon+nelon)/2]);
/*This did not work
var midX = minZoom*(w-(swlat+nelat)/2) - ($("#map-holder").width()-(swlat+nelat)/2);
var midY = minZoom*(h-(swlon+nelon)/2) - ($("#map-holder").height()-(swlon+nelon)/2);*/
/*Neither did this
var midX = minZoom*(w-projectCoordinates[0])-($("#map-holder").width()-projectCoordinates[0]);
var midY = minZoom*(h-projectCoordinates[1])-($("#map-holder").height()-projectCoordinates[1]);*/
svg.call(zoom.transform, d3.zoomIdentity.translate(midX, midY).scale(minZoom));
}
The idea behind the original approach was to:
1: Get the current pixel display of the map
2: Get the new pixel distance from the map corner to the map point after zoom has been applied
3: The pixel distance of the center of the container to the top of the container
4: subtract the values from 2 and 3
The original post was trying to translate the map so that it would initialize the zoom and pan over the center of the map. I tried to modify this approach first by directly substituting the lat/lon values into the above equations. I also tried first transforming the lat/lon values using the projection and then substituting those values in, with little success. What do I need to do in order to get my desired result?
Setting a translateExtent could be a bad idea because it depends on the zoom scale.
The following replacement works.
function initiateZoom() {
// Define a "minzoom" whereby the "Countries" is as small possible without leaving white space at top/bottom or sides
minZoom = Math.max($("#map-holder").width() / w, $("#map-holder").height() / h);
// set max zoom to a suitable factor of this value
maxZoom = 20 * minZoom;
// set extent of zoom to chosen values
// set translate extent so that panning can't cause map to move out of viewport
zoom
.scaleExtent([minZoom, maxZoom])
.translateExtent([[0, 0], [w, h]])
;
var swlat = 32;
var swlon = -82;
var nelat = 42;
var nelon = -72;
var nwXY = projection([swlon, nelat]);
var seXY = projection([nelon, swlat]);
var zoomScale = Math.min($("#map-holder").width()/(seXY[0]-nwXY[0]), $("#map-holder").height()/(seXY[1]-nwXY[1]))
var projectCoordinates = projection([(swlon+nelon)/2, (swlat+nelat)/2]);
svg.call(zoom.transform, d3.zoomIdentity.translate($("#map-holder").width()*0.5-zoomScale*projectCoordinates[0], $("#map-holder").height()*0.5-zoomScale*projectCoordinates[1]).scale(zoomScale));
}
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);
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);
});
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);