I have 2 long, lat points of a rectangle(bottom left and top right) and I want to divide this rectangle into smaller ones based on a base area (long and lat) I already have. I already know that I can't deal with long and lat as distance measured with meters and kilometres but degrees on an approximation of Earth's surface shape.
The points taken is extracted by leaflet with a 4326 SRID and so are the original points. I need the centre of the "smaller squares" or the long and lat coordinates.
For example, this is my base rectangle 24.639567,46.782406 24.641452,46.785413 and for the rectangle, I want to divide 24.584749,46.612782 24.603323,46.653809.
First, let's turn your two points into a leaflet bounds object:
const bounds - L.latLngBounds(point1, point2)
Now let's pick a sample interval, meaning how many sub-rectangles across the width and height of your bounds. For example, a sampling size of 10 would give 100 sub-rectangles (10 x 10), though if your sub-rectangles don't need the same aspect-ratio as your main bounds, you could choose two separate sampling intervals (one for x and one for y)
const samplingInterval = 10 // easy to change
To properly interpolate through your main bounds, we'll grab the corners of it, as well as the width in longitude degrees, and height in latitude degrees, called dLat and dLng (for delta):
const sw = bounds.getSouthWest();
const nw = bounds.getNorthWest();
const ne = bounds.getNorthEast();
const dLat = ne.lat - sw.lat;
const dLng = ne.lng - nw.lng;
Now we can build an array of new bounds extrapolated from the original:
let subBounds = [];
for (let i = 0; i < samplingInterval - 1; i++){
for (let j = 1; j < samplingInterval; j++){
const corner1 = [
sw.lat + (dLat * i) / samplingInterval,
sw.lng + (dLng * j) / samplingInterval
];
const corner2 = [
sw.lat + (dLat * (i + 1)) / samplingInterval,
sw.lng + (dLng * (j + 1)) / samplingInterval
];
subBounds.push(L.latLngBounds(corner1, corner2));
}
}
Now to get the centers of these bounds, you can call .getCenter() on them:
const centerPoints = subBounds.map(bounds => bounds.getCenter());
Working codesandbox
Related
Athena only allows to calculate the distance of the buffer in decimal degrees but this value varies with respect to the latitude in the globe, tate to obtain a distance according to the following formula but it is not consistent in Mexico.
Athena function like this : ST_Buffer(geometry, double)
Athena geospatial functions
So, is posible obtain the corresponding distance in decimal degrees over a custom point in map , ex : get the decimal degree for point x, y like that distance in meters is 300 mts
Currently I use the following formula to approximate the decimal degrees but some buffers are quite horrible although it meets the minimum required
SELECT
ST_Buffer(ST_GeometryFromText( shape_wkt) ,
abs(5000.0 * 360.0 / (2.0 * pi() * cos( latitud )* 6400000.0) ) ) AS
dinamic_buffer_5000
5000 is buffer in meters
6400000.0 earth radius in meters
Some useffull questions :
gps-coordinates-in-degrees-to-calculate-distances
Calculate distance in meters using results in degrees
calculating-latitude-longitude-x-miles-from-point
A possible alternative is the following
To obtain the decimal degrees relative to a point one could:
Generate a second point at a distance d for this you would have to implement this formula, where the bearing does not matter
With this second point calculate the distance in Athena that will return the distance in decimal degrees, as input for the buffer function.
As an approximate is good alternative
Now how implement the second point ?....Here is the formula
I will try to convert to SQL code if can :
After a test I realize that even with the difference of distance it is not possible to obtain the buffer in an optimal way.
In this case the distance to the lower point was 300 meters, after obtaining the distance in decimal degrees with Athena an oblate shape is obtained, it changes the degree of inclination of the point by 90 degrees but it only generates a slightly larger shape.
Destination point given distance and bearing from start point
Source code (zory im edit for test my sql ):
destinationPoint(distance, bearing, radius=6371e3) {
// sinφ2 = sinφ1⋅cosδ + cosφ1⋅sinδ⋅cosθ
// tanΔλ = sinθ⋅sinδ⋅cosφ1 / cosδ−sinφ1⋅sinφ2
// see mathforum.org/library/drmath/view/52049.html for derivation
const dist_ang = distance / radius; // angular distance in radians
const angulo = Number(bearing).toRadians();
const rad_lat = this.lat.toRadians();
const rad_lon = this.lon.toRadians();
console.log("distance", distance);
console.log("radius", radius);
console.log("angular distance in radians", dist_ang);
console.log("bearing", Number(bearing));
console.log("bearing angulo ", angulo );
console.log("lat.toRadians", rad_lat);
console.log("lon.toRadians", rad_lon);
console.log("lon",this.lon);
console.log("lat",this.lat);
const sinφ2 = Math.sin(rad_lat) * Math.cos(dist_ang) + Math.cos(rad_lat) * Math.sin(dist_ang) * Math.cos(angulo);
const φ2 = Math.asin(sinφ2); //lat
console.log("φ2",φ2); //lat
console.log("sinφ2",sinφ2);
const y = Math.sin(angulo) * Math.sin(dist_ang) * Math.cos(rad_lat);
const x = Math.cos(dist_ang) - Math.sin(rad_lat) * sinφ2;
console.log("y",y);
console.log("x",x);
const λ2 = rad_lon + Math.atan2(y, x); //lon
console.log("λ2",λ2);
const lat = φ2.toDegrees();//lat
const lon = λ2.toDegrees();//lon
console.log("lon2",lon);
console.log("lat2",lat);
return new LatLonSpherical(lat, lon);
}
Given 2 coordinates (point 1 and 2 in red) in WGS84 I need to find the coordinates of the point perpendicular (point 3) to the line at a given distance.
I could manage to make the math to compute this perpendicular point, but when displayed on the map, the point seems to be at a wrong place, probably because of the projection.
What I want on a map:
And what I have instead on the map:
How can I take into account the projection so that the point on the map appears perpendicular to the line? The algorithm below to compute the point comes from here: https://math.stackexchange.com/questions/93424/calculate-rectangle-coordinates-from-line-and-height
public static Coords ComputePerpendicularPoint(Coords first, Coords last, double distance)
{
double slope = -(last.Lon.Value - first.Lon.Value) / (last.Lat.Value - first.Lat.Value);
// 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
distance = distance * 0.0000089 / 100; //0.0000089 => represents around 1m in wgs84. /100 because distance is in cm
double t = distance / Math.Sqrt(1 + (slope * slope));
Coords perp_coord = new Coords();
perp_coord.Lon = first.Lon + t;
perp_coord.Lat = first.Lat + (t * slope);
return perp_coord;
}
Thank you in advance!
I'm trying to use Mapbox Terrain RGB to get elevation for specific points in space. I used mercantile.tile to get the coordinates of the tile containing my point at zoom level 15, which for -43º, -22º (for simplicity sake) is 12454, 18527, then mercantile.xy to get the corresponding world coordinates: -4806237.7150042495, -2621281.2257876047.
Shouldn't the integer part of -4806237.7150042495 / 256 (tile size) equal the x coordinate of the tile containing the point, that is, 12454? If this calculation checked out I'd figure that I'm looking for the pixel column (x axis) corresponding to the decimal part of the result, like column 127(256 * 0,5) for 12454,5. However, the division results in -18774.366, (which is curiously close to the tile y coordinate, but it looks like a coincidence). What am I missing here?
As an alternative, I thought of using mercantile.bounds, assigning the first and last pixel columns to the westmost and eastmost longitudes, and finding my position with interpolation, but I wanted to check if I'm doing this the right/recommended way. I'm interested in point elevations, so everything said here goes for the Y axis as well.
Here's what I got so far:
def correct_altitude_mode(kml):
with open(kml, "r+") as f:
txt = f.read()
if re.search("(?<=<altitudeMode>)relative(?=<\/altitudeMode>)", txt):
lat = round(float(find_with_re("latitude", txt)), 5)
lng = round(float(find_with_re("longitude", txt)), 5)
alt = round(float(find_with_re("altitude", txt)), 5)
z = 15
tile = mercantile.tile(lng, lat, z)
westmost, southmost, eastmost, northmost = mercantile.bounds(tile)
pixel_column = np.interp(lng, [westmost, eastmost], [0,256])
pixel_row = np.interp(lat, [southmost, northmost], [256, 0])
response = requests.get(f"https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{tile.x}/{tile.y}.pngraw?access_token=pk.eyJ1IjoibWFydGltcGFzc29zIiwiYSI6ImNra3pmN2QxajBiYWUycW55N3E1dG1tcTEifQ.JFKSI85oP7M2gbeUTaUfQQ")
buffer = BytesIO(response.content)
tile_img = png.read_png_int(buffer)
_,R,G,B = (tile_img[int(pixel_row), int(pixel_column)])
print(tile_img[int(pixel_row), int(pixel_column)])
height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)
print(f"R:{R},G:{G},B:{B}\n{height}")
plt.hlines(pixel_row, 0.0, 256.0, colors="r")
plt.vlines(pixel_column, 0.0, 256.0, colors="r")
plt.imshow(tile_img)
I have a static image with only center point lat/long (for example https://api.mapbox.com/styles/v1/mapbox/light-v9/static/-78.4649,42.5128,5,0,0/300x200) and I want to put on this map some markers(lat.long) with the help of canvas.
But I need to calculate somehow the xy coordinates for those markers.
So I know the center of map(lat/long) and the lat/long marker coordinates. Is there any way to convert lat/long to xy knowing only zoom level and center?
Or if I know the xy of the center lat/long(it always be the same 150px * 100px) and zoom level, could I calculate the xy for other markers?
I have a lot of markers (>200, and they all are custom svg generated and so on) to place it on this map. I can't use mapbox mapbox static map because of the markers limitation and so on.
UPD: Based on the comments I updated the question.
How to calculate it for 256px square tiles?
Based on the OP comment I'm assuming that the requested image is square, for the sake of simplicity (TILE_SIZE could be decomposed in a TILE_SIZE_X and TILE_SIZE_Y component). I'm also assuming that the image is 256-pixels wide TILE_SIZE=256
I'm giving both the pixel coordinates relative to the center of the image (distanceInPixels function), and to the Lower Left Corner (imageCoordinates function). Changing to the Upper Left Corner in case that's necessary should be trivial (X will be equal and Y = TILE_SIZE -Y).
<!DOCTYPE html>
<html>
<body>
<p id="demo"></p>
<script>
var latLngMarker = {};
var latLngCenter = {};
// Image dimensions in pixels
var TILE_SIZE = 256;
var zoom = 5;
// Coordinates of the marker to be projected on the image
latLngMarker.lat = 41.850;
latLngMarker.lng = -87.650;
// Coordinates of the image center
latLngCenter.lat = 41.850;
latLngCenter.lng = -87.650;
// Coordinates projected on the cartographic plane (Mercator)
var centerProjected = project(latLngCenter);
var markerProjected = project(latLngMarker);
// The result should be X=Y=0, because I made Marker Lat Lng = Center Lat Lng
var distanceFromCenter = distanceInPixels(centerProjected, markerProjected);
alert("X: " + distanceFromCenter.x + " Y: " + distanceFromCenter.y);
// The result should be X=Y=256/2=128 for the same reason
var coords = imageCoordinates(centerProjected, markerProjected);
alert("X: " + coords.x + " Y: " + coords.y);
// The horizontal distance represented by one pixel for a given latitude and zoom level
function pixelResolution (latLng, zoom) {
var radius = 6378137.0 // semi-axis of WGS84 ellipsoid
var circumference = 2 * Math.PI * radius;
var distancePerImage = circumference * Math.cos(latLng.lat * Math.PI / 180.0) / Math.pow(2,zoom);
var distancePerPixel = distancePerImage / TILE_SIZE;
return distancePerPixel
}
// Web mercator projection.
function project(latLng) {
var siny = Math.sin(latLng.lat * Math.PI / 180);
siny = Math.min(Math.max(siny, -0.9999), 0.9999);
var xy = {};
xy.x = TILE_SIZE * (0.5 + latLng.lng / 360);
xy.y = TILE_SIZE * (0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI));
return xy
}
// Marker pixel coordinates relative to the image Center
function distanceInPixels(centerProjected, markerProjected) {
var delta = {};
var spacing = pixelResolution(latLngCenter, zoom);
delta.x = Math.round((centerProjected.x - markerProjected.x)/spacing);
delta.y = Math.round((centerProjected.y - markerProjected.y)/spacing);
return delta
}
// Marker pixel coordinates relative to the Lower Left Corner
function imageCoordinates(centerProjected, markerProjected) {
var pixelCoordinates = {};
var spacing = pixelResolution(latLngCenter, zoom);
var deltaPixels = distanceInPixels(centerProjected, markerProjected);
pixelCoordinates.x = TILE_SIZE / 2 - deltaPixels.x;
pixelCoordinates.y = TILE_SIZE / 2 - deltaPixels.y;
return pixelCoordinates
}
</script>
</body>
</html>
Note: I can confirm that the pixelResolution function only works with square image tiles with dimensions of powers of 2. The Math.pow(2,zoom); snippet gives the game away!
Web Mercator function based on:
https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/map-coordinates
Horizontal distance represented by one pixel from :
https://wiki.openstreetmap.org/wiki/Zoom_levels
See also:
https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Resolution_and_Scale
If you're going to linearly interpolate you'd need to know the lat/long & x/y for 2 points. It wouldn't be possible with only the center point unless you also have a conversion metric for pixels - ie. 50 pixels is .1 delta lat/long.
If you have the lat/long & x/y for two points you can create the ratio as y1 - y2 / lat1-lat2 or x1-x2/long1-long2 each of which should result in the same ratio
Then it'd be relatively easy, assume the ratio is 5 meaning 5px/l so you had a point that was (3,-4) away from that center point you'd simply multiple to find the pixel offset (15,-20) and add that to the center = (165, 80).
Since all of your images are zoomed the same amount you could manually calculate the ratio once and store it as a constant.
sudo/untested python:
def getRatio(latlongs=[(1,1),(0,0)], xys=[(5,5),(0,0)]:
return (xys[0][1]-xys[1][1]) / (latlongs[0][0] - latlongs[1][0])
centerLatLong = (5,5)
centerXY = (150, 100)
def getCoord(lat,long,ratio):
y = (lat-centerLatLong[0])*ratio + centerXY[1]
x = (long-centerLatLong[1])*ratio + centerXY[0]
return x, y
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);
}
};