Leaflet - Calculate zoom level from radius in meters - leaflet

I'm trying to find a way to calculate zoom level given a radius in meters.
For example, I want to show 100km radius within point x,y, how do I calculate the proper zoom level for that point?

This will depend on:
the number of tiles of your map
the latitude of your view
The resolution (meters/pixel) of a web-Mercator map at zoom 0 (one tile for the whole map) is given by:
resolution = cos(latitude_rad) * earth circumference / tile width
with the latitude in radians, the earth circumference in meters (2 * pi * radius) and the map width given by:
map width = 256 as the tiles are 256 pixel wide.
At each zoom step, the width (in pixel) is doubled, so at zoom z:
map width = 256*2^zoom
so :
resolution=cos(latitude_rad) * 2*pi* 6371008 / (256*2^zoom)
but as you are using a certain number n of 256 pixel tiles:
widthmap = cos(latitude_rad) * 2*pi* 6371008*n / (2^zoom)
Now, to get the zoom from the distance:
with latitude in degrees, radius is your circle in meters, and n the number of tiles to display:
zoom = floor(log2 ((cos(latitude *pi/180) * 2*pi* 6371008*n)/radius))
(floor as you want an integer and to make sure your whole circle is on the map)
For example, at 45 degrees N/S, 100m and 1 tile will give you a zoom of 18.

Related

Rounding latitude and longitude to get within approximately 1 km

I would like to simplify a GPS coordinate to a "TOP LEFT" position of a defined virtual grid (for example 100 kilometers).
If my GPS coordinate is in a cell of the grid, then we use the GPS position of the "TOP LEFT" of the cell.
The new coordinate is not intended to be displayed on a map but just to be communicated and manipulated.
This imaginary grid would have an editable distance (e.g. 1 kilometer or 100 kilometers).
I had imagined calculating the distance between two known points:
latitude = 0 / longitude = 0 (Null Island)
has GPS coordinate (ex: lat 48.858284648396626 lng 2.294501066207886)
we calculate distances for lat/lng (i use leaflet distanceTo function )
distance_latitude: 5432.79 km (?) : distanceTo( [0,0],[48.858284648396626,0])
distance_longitude: 255.14 km (?) : distanceTo( [0,0],[0,2.294501066207886])
var distance=100; // in kilometers
distance_latitude= Math.floor(distance_latitude/ distance) * distance;
= 5400
distance_longitude= Math.floor(distance_longitude / distance) * distance;
= 200
but after that... how to transform these kilometers (from Null Island) to a new coordinate? (so the top left of the current cell where the poi is)
(grid not in scale!)
Sometimes it's better to wait for a good night's sleep!
answer: just move nullIsland the distance between the poi and... nullIsland... simply! So all the dots (red) take the TopLeft position -blue marker- of the imaginary grid (in my screenshot: a 5Km grid).

Get the exact satellite image for a given Lat/Long bbox rectangle?

For a visualization I need an optical satellite image for a specific rectangular AOI, that is defined by two lat/long coordinates. I tried Mapbox Static Images API, which takes a lat/long bounding box and a resolution in width/height pixel for the output. The problem is that it looks like to me that if ratio of the lat/long box is not the same as the w/h pixels, it will add padding to the lat/long bounding box to fill the w/h of the pixel image.
And this would prevent me from combining the optical image with the other data, because I would not know which image pixel would (roughly) correspond to which lat/long coordinate.
I see three "solutions", but I don't know how to achive any of them.
"Make" Mapbox return the images with out padding.
Compute the ratio for the correct w/h pixel ratio using the lat/long coordinate, so there would be no padding. Maybe with https://en.wikipedia.org/wiki/Equirectangular_projection like discussed here: https://stackoverflow.com/a/16271669/380038?
Find a way to determine the lat/long coordinates of the optical satellite image so I can cut off the possible padding.
I checked How can I extract a satellite image from google maps given a Lat Long Rectangle?, but I would prefer to use my existing paid Mapbox account and I got the impression that I still wouldn't get the exact optical image or the exact corner coordinates of the optical image.
Mapbox Static Images API serves maps
You have optical image from other source
You want to overlay these data
Right?
Note the Red and Green pins: the waypoints are at opposite corners on Mapbox.
After Equirectangular correction Mapbox matches Openstreetmaps (little wonder), but Google coordinates are quite close too.
curl -g "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/[17.55490,47.10434,17.55718,47.10543]/600x419?access_token=YOUR_TOKEN_HERE" --output example-walk-600x419-nopad.png
What is your scale? 1 km - 100 km?
What is your source of optical image?
What is the required accuracy?
Just to mention, optical images have their own sources of distortions.
In practice:
You must have the extent of your non optical satellite data (let's preserve the mist around...) I'll call it ((x1, y1), (x2, y2)) We are coders, not cartographers - right!?
If you feed your extent to https://docs.mapbox.com/playground/static/ as
min longitude = x1, min lattitude = y1, max longitude = x2, max lattitude = y2
Select "Bounding box" entry! Do you see mapbox around your data!? Don't mind the exact dimensions, just check if mapbox is related to your data! May be you have to swap some values to get to the right corner of the globe.
If you have the right ((x1, y1), (x2, y2)) coordinates, do the equirectangular transformation to get the right pixel size.
You've called it Solution #2.
Let's say the with of your non optical satellite data is Wd, the height is Hd.
The mapbox image will fit your data, if you ask for Wm widht, and Hm height of mapbox data where
Wm = Wd
Hm = Wd * (y2 - y1) * cos(x1) / (x2 - x1)
Now you can pull the mapbox by
curl -g "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/[<x1>,<y1>,<x2>,<y2>]/<Wm>x<Hm>?access_token=<YOUR_TOKEN>" --output overlay.png
If (Hd == Hm)
then {you are lucky :) the two images just fit each other}
else { the two images are for the same area, but you have to scale the height of one of the images to make match }
Well... almost. You have not revealed what size of area you want to cover. The equation above is just an approximation which works up to the size of a smaller country (~100 km or so). For continent scale you probably have to apply more accurate formulas.
In my opinion, your #2 idea is the way to go. You do have the LLng bbox, so all that remains is calculate its "real" size in pixels.
Let us say that you want (or can allow, or can afford) a resolution of 50m per pixel, and the area is small enough not to have distortions (i.e., a rectangle of say 1 arcsecond of latitude and 1 arcsecond of longitude has top and bottom sides of the same length, with an error less than your chosen resolution). These are, I believe, very loose requisites and easy to fulfill.
Then, you just need to calculate the distance between the (Lat1, Lon1) and (Lat1, Lon2) points, and betwen (Lat1, Lon1) and (Lat2, Lon1). Divide that distance in meters by 50, and you'll get the exact number of pixels:
Lon1 Lon2
Lat1 +---------------+
| |
| |
Lat2 +---------------+
And you have a formula for that - the haversine formula.
If you need a higher precision, you could recourse to the Vincenty oblate spheroid (here a Javascript library). On the MT site (first link) there is a live calculator that you can use to plug data from your calls, and verify whether the approach is indeed working. I.e. you plug in your bounding box, get the distance in meters, divide and get the pixel size of the image (if the image is good, chances are that you can go with the simpler haversine. If it isn't, then there has to be some further quirk in the maps API - its projection, perhaps - that doesn't return the expected bounding box. But it seems unlikely).
I've had this exact problem when using a satellite image on an apple watch. I overlay some markers and a path. I convert everything from coordinates to pixels. Below is my code to determine the exact bbox result
var maxHoleLat = 52.5738902
var maxHoleLon = 4.9577606
var minHoleLat = 52.563994
var minHoleLon = 4.922364
var mapMaxLat = 0.0
var mapMaxLon = 0.0
var mapMinLat = 0.0
var mapMinLon = 0.0
let token = "your token"
var resX = 1000.0
var resY = 1000.0
let screenX = 184.0
let screenY = 224.0 // 448/2 = 224 - navbarHeight
let navbarHeight = 0.0
var latDist = 111000.0
var lonDist = 111000.0
var dx = 0.0
var dy = 0.0
func latLonDist(){
//calgary.rasc.ca/latlong.htm
let latRad = maxHoleLat * .pi / 180
//distance between 1 degree of longitude at given latitude
self.lonDist = 111412.88 * cos(latRad) - 0.09350*cos(3 * latRad) + 0.00012 * cos(5 * latRad)
print("lonDist = \(self.lonDist)")
//distance between 1 degree of latitude at a given longitude
self.latDist = 111132.95 - 0.55982 * cos(2 * latRad) + 0.00117 * cos(4 * latRad)
print("latDist = \(self.latDist)")
}
func getMapUrl(){
self.dx = (maxHoleLon - minHoleLon) * lonDist
self.dy = (maxHoleLat - minHoleLat) * latDist
//the map is square, but the hole not
//check if the hole has less x than y
if dx < dy {
mapMaxLat = maxHoleLat
mapMinLat = minHoleLat
let midLon = (maxHoleLon + minHoleLon ) / 2
mapMaxLon = midLon + dy / 2 / lonDist
mapMinLon = midLon - dy / 2 / lonDist
} else {
mapMaxLon = maxHoleLon
mapMinLon = minHoleLon
let midLat = (maxHoleLat + minHoleLat ) / 2
mapMaxLat = midLat + dx / 2 / latDist
mapMinLat = midLat - dx / 2 / latDist
}
self.imageUrl = URL(string:"https://api.mapbox.com/styles/v1/mapbox/satellite-v9/static/[\(mapMinLon),\(mapMinLat),\(mapMaxLon),\(mapMaxLat)]/1000x1000?logo=false&access_token=\(token)")
print("\(imageUrl)")
}

Map distance to zoom in Google Static Maps

I am using Google Static Maps to display maps in my AppleTV app. What I need is to somehow map a distance of e.g. 1km to the zoom parameter of the Static Maps API.
In other words I have an imageView in which I wish to load the map image and if I know that the height of my imageView is 400px, and I wish for this map to show a real Earth surface of 1000m North to South, how would I tell the API to return me the map with this exact zoom?
I found a very similar question here, however no suitable answer is provided.
As stated at Google Maps Documentation:
Because the basic Mercator Google Maps tile is 256 x 256 pixels.
Also note that every zoom level, the map has 2 n tiles.
Meaning that at zoomLevel 2,the pixels in any direction of a map are = 256 * 2² = 1024px.
Taking into account that the earth has a perimeter of ~40,000 kilometers, in zoom 0, every pixel ~= 40,000 km/256 = 156.25 km
At zoom 9, pixels are 131072: 1px = 40,000 km / 131072 = 0.305 km ... and so on.
If we want 400px = 1km, we have to choose the closest approximation possible, so: 1px = 1km/400 = 0.0025km
I tried zoom = 15 and obtained 1px = 0.00478 and zoom = 16 that gave me 1px = 0.00238km
Meaning that you should use zoom = 16, and you will have 0.955km every 400px in the Equator line and only for x coordinates.
As you go north or south in latitude, perimeter is everytime smaller, thus changing the distance. And of course it also changes the correlation in the y axis as the projection of a sphere is tricky.
If you want to calculate with a function the exact distance, you should use the one provided by Google at their documentation:
// Describe the Gall-Peters projection used by these tiles.
gallPetersMapType.projection = {
fromLatLngToPoint: function(latLng) {
var latRadians = latLng.lat() * Math.PI / 180;
return new google.maps.Point(
GALL_PETERS_RANGE_X * (0.5 + latLng.lng() / 360),
GALL_PETERS_RANGE_Y * (0.5 - 0.5 * Math.sin(latRadians)));
},
fromPointToLatLng: function(point, noWrap) {
var x = point.x / GALL_PETERS_RANGE_X;
var y = Math.max(0, Math.min(1, point.y / GALL_PETERS_RANGE_Y));
return new google.maps.LatLng(
Math.asin(1 - 2 * y) * 180 / Math.PI,
-180 + 360 * x,
noWrap);
}
};

leaflet pixel size depending on zoom level

I have to display 5x5 degrees pie chart on a leaflet map. I can display pie chart using the great leaflet-dvf library, but I have to provide the radius in pixel, and it is static so far.
I would like to have it dynamic so that at any zoom level, the pie chart fills the 5x5 square (aka radius = 5x5 degree length).
How can I know the length in pixel of the side of a 5x5 degrees square, depending on the zoom level?
Thanks
This will work out metres per pixel
metresPerPixel = 40075016.686 * Math.abs(Math.cos(map.getCenter().lat / 180 * Math.PI)) / Math.pow(2, map.getZoom()+8);
I used the following page that provides the pixel per meter on a leaflet map depending on the zoom level:
http://wiki.openstreetmap.org/wiki/Zoom_levels
Then I computed the length of a 5x5 square at the equator: 556000 meters.
Then I store the length ratio for zoom level = 0:
$scope.lengthRatio = 556000 / 156412 // in meter / pixel
Finally, I get the radius of a pie chart depending on the zoom level ($scope.mapZoom):
var radius = $scope.lengthRatio * Math.pow(2,$scope.mapZoom)) / 2
The /2 is because I want the radius and not the diameter
Simple!

Draw circle using latitude and longitude

I want to plot a latitude and longitude using matlab. Using that latitude and longitude as center of the circle, I want to plot a circle of radius 5 Nm.
r = 5/60;
nseg = 100;
x = 25.01;
y = 55.01;
theta = 0 : (2 * pi / nseg) : (2 * pi);
pline_x = r * cos(theta) + x;
pline_y = r * sin(theta) + y;
hold all
geoshow(pline_x, pline_y)
geoshow(x, y)
The circle does not look of what I expected.
Drawing a circle on earth is more complex that it looks like.
Drawing a line or a poly line is simple, because the vertices are defined.
Not so on circle.
a circle is defined by all points having the same distance from center (in meters! not in degrees!!!)
Unfortuantley lat and lon coordinates have not the same scale.
(The distance between two degrees of latidtude is always approx. 111.3 km, while for longitude this is only true at the equator. At the poles the distance between two longitudes approach zero. In Europe the factor is about 0.6. (cos(48deg))
There are two solution, the first is more universal, usefull for nearly all problems.
convert spherical coordinate (of circle center) to cartesian plane with unit = 1m, using a transformation (e.g equidistant transformation, also called equirectangular transf., this transformation works with the cos(centerLat) compensation factor)
calculate points (e.g circle points) in x,y plane using school mathematics.
transform all (x,y) points back to spherical (lat, lon) coordinates, using the inverse transformation of point 1.
Other solution
1. write a function which draws an ellipse in defined rectangle (all cartesian x,y)
2. define bounding of the circle to draw:
2a: calculate north-south diameter of circle/ in degrees: this a bit tricky: the distance is define in meters, you need a transformation to get the latitudeSpan: one degrees of lat is approx 111.3 km (eart circumence / 360.0): With this meters_per_degree value calc the N-S disatcne in degrees.
2b: calculate E-W span in degrees: now more tricky: calculate like 2a, but now divide by cos(centerLatitude) to compensate that E-W distances need more degrees when moving north to have the same meters.
Now draw ellipseInRectangle using N-S and E_W span for heigh and width.
But a circle on a sphere looks on the projected monitor display (or paper) only like a circle in the center of the projection. This shows:
Tissot's Error Ellipse