calculate latitude and longitude using python [duplicate] - python

Given an existing point in lat/long, distance in (in KM) and bearing (in degrees converted to radians), I would like to calculate the new lat/long. This site crops up over and over again, but I just can't get the formula to work for me.
The formulas as taken the above link are:
lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ))
lon2 = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))
The above formula is for MSExcel where-
asin = arc sin()
d = distance (in any unit)
R = Radius of the earth (in the same unit as above)
and hence d/r = is the angular distance (in radians)
atan2(a,b) = arc tan(b/a)
θ is the bearing (in radians, clockwise from north);
Here's the code I've got in Python.
import math
R = 6378.1 #Radius of the Earth
brng = 1.57 #Bearing is 90 degrees converted to radians.
d = 15 #Distance in km
#lat2 52.20444 - the lat result I'm hoping for
#lon2 0.36056 - the long result I'm hoping for.
lat1 = 52.20472 * (math.pi * 180) #Current lat point converted to radians
lon1 = 0.14056 * (math.pi * 180) #Current long point converted to radians
lat2 = math.asin( math.sin(lat1)*math.cos(d/R) +
math.cos(lat1)*math.sin(d/R)*math.cos(brng))
lon2 = lon1 + math.atan2(math.sin(brng)*math.sin(d/R)*math.cos(lat1),
math.cos(d/R)-math.sin(lat1)*math.sin(lat2))
print(lat2)
print(lon2)
I get
lat2 = 0.472492248844
lon2 = 79.4821662373

Needed to convert answers from radians back to degrees. Working code below:
from math import asin, atan2, cos, degrees, radians, sin
def get_point_at_distance(lat1, lon1, d, bearing, R=6371):
"""
lat: initial latitude, in degrees
lon: initial longitude, in degrees
d: target distance from initial
bearing: (true) heading in degrees
R: optional radius of sphere, defaults to mean radius of earth
Returns new lat/lon coordinate {d}km from initial, in degrees
"""
lat1 = radians(lat1)
lon1 = radians(lon1)
a = radians(bearing)
lat2 = asin(sin(lat1) * cos(d/R) + cos(lat1) * sin(d/R) * cos(a))
lon2 = lon1 + atan2(
sin(a) * sin(d/R) * cos(lat1),
cos(d/R) - sin(lat1) * sin(lat2)
)
return (degrees(lat2), degrees(lon2),)
lat = 52.20472
lon = 0.14056
distance = 15
bearing = 90
lat2, lon2 = get_point_at_distance(lat, lon, distance, bearing)
# lat2 52.20444 - the lat result I'm hoping for
# lon2 0.36056 - the long result I'm hoping for.
print(lat2, lon2)
# prints "52.20451523755824 0.36067845713550956"

The geopy library supports this:
import geopy
from geopy.distance import VincentyDistance
# given: lat1, lon1, b = bearing in degrees, d = distance in kilometers
origin = geopy.Point(lat1, lon1)
destination = VincentyDistance(kilometers=d).destination(origin, b)
lat2, lon2 = destination.latitude, destination.longitude
Found via https://stackoverflow.com/a/4531227/37610

This question is known as the direct problem in the study of geodesy.
This is indeed a very popular question and one that is a constant cause of confusion. The reason is that most people are looking for a simple and straight-forward answer. But there is none, because most people asking this question are not supplying enough information, simply because they are not aware that:
Earth is not a perfect sphere, since it is flattened/compressed by it poles
Because of (1) earth does not have a constant Radius, R. See here.
Earth is not perfectly smooth (variations in altitude) etc.
Due to tectonic plate movement, a geographic point's lat/lon position may change by several millimeters (at least), every year.
Therefore there are many different assumptions used in the various geometric models that apply differently, depending on your needed accuracy. So to answer the question you need to consider to what accuracy you would like to have your result.
Some examples:
I'm just looking for an approximate location to the nearest few kilometers for small ( < 100 km) distances of in latitudes between 0-70 deg N|S. (Earth is ~flat model.)
I want an answer that is good anywhere on the globe, but only accurate to about a few meters
I want a super accurate positioning that is valid down to atomic scales of nanometers [nm].
I want answers that is very fast and easy to calculate and not computationally intensive.
So you can have many choices in which algorithm to use. In addition each programming language has it's own implementation or "package" multiplied by number of models and the model developers specific needs. For all practical purposes here, it pays off to ignore any other language apart javascript, since it very closely resemble pseudo-code by its nature. Thus it can be easily converted to any other language, with minimal changes.
Then the main models are:
Euclidian/Flat earth model: good for very short distances under ~10 km
Spherical model: good for large longitudinal distances, but with small latitudinal difference. Popular model:
Haversine: meter accuracy on [km] scales, very simple code.
Ellipsoidal models: Most accurate at any lat/lon and distance, but is still a numerical approximation that depend on what accuracy you need. Some popular models are:
Lambert: ~10 meter precision over 1000's of km.
Paul D.Thomas: Andoyer-Lambert approximation
Vincenty: millimeter precision and computational efficiency
Kerney: nanometer precision
References:
https://en.wikipedia.org/wiki/Reference_ellipsoid
https://en.wikipedia.org/wiki/Haversine_formula
https://en.wikipedia.org/wiki/Earth_ellipsoid
https://en.wikipedia.org/wiki/Geodesics_on_an_ellipsoid
https://en.wikipedia.org/wiki/Vincenty%27s_formulae
https://geographiclib.sourceforge.io/scripts/geod-calc.html

May be a bit late for answering, but after testing the other answers, it appears they don't work correctly. Here is a PHP code we use for our system. Working in all directions.
PHP code:
lat1 = latitude of start point in degrees
long1 = longitude of start point in degrees
d = distance in KM
angle = bearing in degrees
function get_gps_distance($lat1,$long1,$d,$angle)
{
# Earth Radious in KM
$R = 6378.14;
# Degree to Radian
$latitude1 = $lat1 * (M_PI/180);
$longitude1 = $long1 * (M_PI/180);
$brng = $angle * (M_PI/180);
$latitude2 = asin(sin($latitude1)*cos($d/$R) + cos($latitude1)*sin($d/$R)*cos($brng));
$longitude2 = $longitude1 + atan2(sin($brng)*sin($d/$R)*cos($latitude1),cos($d/$R)-sin($latitude1)*sin($latitude2));
# back to degrees
$latitude2 = $latitude2 * (180/M_PI);
$longitude2 = $longitude2 * (180/M_PI);
# 6 decimal for Leaflet and other system compatibility
$lat2 = round ($latitude2,6);
$long2 = round ($longitude2,6);
// Push in array and get back
$tab[0] = $lat2;
$tab[1] = $long2;
return $tab;
}

I ported answer by Brad to vanilla JS answer, with no Bing maps dependency
https://jsfiddle.net/kodisha/8a3hcjtd/
// ----------------------------------------
// Calculate new Lat/Lng from original points
// on a distance and bearing (angle)
// ----------------------------------------
let llFromDistance = function(latitude, longitude, distance, bearing) {
// taken from: https://stackoverflow.com/a/46410871/13549
// distance in KM, bearing in degrees
const R = 6378.1; // Radius of the Earth
const brng = bearing * Math.PI / 180; // Convert bearing to radian
let lat = latitude * Math.PI / 180; // Current coords to radians
let lon = longitude * Math.PI / 180;
// Do the math magic
lat = Math.asin(Math.sin(lat) * Math.cos(distance / R) + Math.cos(lat) * Math.sin(distance / R) * Math.cos(brng));
lon += Math.atan2(Math.sin(brng) * Math.sin(distance / R) * Math.cos(lat), Math.cos(distance / R) - Math.sin(lat) * Math.sin(lat));
// Coords back to degrees and return
return [(lat * 180 / Math.PI), (lon * 180 / Math.PI)];
}
let pointsOnMapCircle = function(latitude, longitude, distance, numPoints) {
const points = [];
for (let i = 0; i <= numPoints - 1; i++) {
const bearing = Math.round((360 / numPoints) * i);
console.log(bearing, i);
const newPoints = llFromDistance(latitude, longitude, distance, bearing);
points.push(newPoints);
}
return points;
}
const points = pointsOnMapCircle(41.890242042122836, 12.492358982563019, 0.2, 8);
let geoJSON = {
"type": "FeatureCollection",
"features": []
};
points.forEach((p) => {
geoJSON.features.push({
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
p[1],
p[0]
]
}
});
});
document.getElementById('res').innerHTML = JSON.stringify(geoJSON, true, 2);
In addition, I added geoJSON export, so you can simply paste resulting geoJSON to: http://geojson.io/#map=17/41.89017/12.49171 to see the results instantly.
Result:

Quick way using geopy
from geopy import distance
#distance.distance(unit=15).destination((lat,lon),bering)
#Exemples
distance.distance(nautical=15).destination((-24,-42),90)
distance.distance(miles=15).destination((-24,-42),90)
distance.distance(kilometers=15).destination((-24,-42),90)

lon1 and lat1 in degrees
brng = bearing in radians
d = distance in km
R = radius of the Earth in km
lat2 = math.degrees((d/R) * math.cos(brng)) + lat1
long2 = math.degrees((d/(R*math.sin(math.radians(lat2)))) * math.sin(brng)) + long1
I implemented your algorithm and mine in PHP and benchmarked it. This version ran in about 50% of the time. The results generated were identical, so it seems to be mathematically equivalent.
I didn't test the python code above so there might be syntax errors.

I ported the Python to Javascript. This returns a Bing Maps Location object, you can change to whatever you like.
getLocationXDistanceFromLocation: function(latitude, longitude, distance, bearing) {
// distance in KM, bearing in degrees
var R = 6378.1, // Radius of the Earth
brng = Math.radians(bearing) // Convert bearing to radian
lat = Math.radians(latitude), // Current coords to radians
lon = Math.radians(longitude);
// Do the math magic
lat = Math.asin(Math.sin(lat) * Math.cos(distance / R) + Math.cos(lat) * Math.sin(distance / R) * Math.cos(brng));
lon += Math.atan2(Math.sin(brng) * Math.sin(distance / R) * Math.cos(lat), Math.cos(distance/R)-Math.sin(lat)*Math.sin(lat));
// Coords back to degrees and return
return new Microsoft.Maps.Location(Math.degrees(lat), Math.degrees(lon));
},

Thanks to #kodisha, here is a Swift version, but with improved and more precise calculation for Earth radius:
extension CLLocationCoordinate2D {
func earthRadius() -> CLLocationDistance {
let earthRadiusInMetersAtSeaLevel = 6378137.0
let earthRadiusInMetersAtPole = 6356752.314
let r1 = earthRadiusInMetersAtSeaLevel
let r2 = earthRadiusInMetersAtPole
let beta = latitude
let earthRadiuseAtGivenLatitude = (
( pow(pow(r1, 2) * cos(beta), 2) + pow(pow(r2, 2) * sin(beta), 2) ) /
( pow(r1 * cos(beta), 2) + pow(r2 * sin(beta), 2) )
)
.squareRoot()
return earthRadiuseAtGivenLatitude
}
func locationByAdding(
distance: CLLocationDistance,
bearing: CLLocationDegrees
) -> CLLocationCoordinate2D {
let latitude = self.latitude
let longitude = self.longitude
let earthRadiusInMeters = self.earthRadius()
let brng = bearing.degreesToRadians
var lat = latitude.degreesToRadians
var lon = longitude.degreesToRadians
lat = asin(
sin(lat) * cos(distance / earthRadiusInMeters) +
cos(lat) * sin(distance / earthRadiusInMeters) * cos(brng)
)
lon += atan2(
sin(brng) * sin(distance / earthRadiusInMeters) * cos(lat),
cos(distance / earthRadiusInMeters) - sin(lat) * sin(lat)
)
let newCoordinate = CLLocationCoordinate2D(
latitude: lat.radiansToDegrees,
longitude: lon.radiansToDegrees
)
return newCoordinate
}
}
extension FloatingPoint {
var degreesToRadians: Self { self * .pi / 180 }
var radiansToDegrees: Self { self * 180 / .pi }
}

Also late but for those who might find this, you will get more accurate results using the geographiclib library. Check out the geodesic problem descriptions and the JavaScript examples for an easy introduction to how to use to answer the subject question as well as many others. Implementations in a variety of languages including Python. Far better than coding your own if you care about accuracy; better than VincentyDistance in the earlier "use a library" recommendation. As the documentation says: "The emphasis is on returning accurate results with errors close to round-off (about 5–15 nanometers)."

Just interchange the values in the atan2(y,x) function. Not atan2(x,y)!

I ported the answer from #David M to java if anyone wanted this... I do get a slight different result of 52.20462299620793, 0.360433887489931
double R = 6378.1; //Radius of the Earth
double brng = 1.57; //Bearing is 90 degrees converted to radians.
double d = 15; //Distance in km
double lat2 = 52.20444; // - the lat result I'm hoping for
double lon2 = 0.36056; // - the long result I'm hoping for.
double lat1 = Math.toRadians(52.20472); //Current lat point converted to radians
double lon1 = Math.toRadians(0.14056); //Current long point converted to radians
lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng));
lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
lat2 = Math.toDegrees(lat2);
lon2 = Math.toDegrees(lon2);
System.out.println(lat2 + ", " + lon2);

Here is a PHP version based on Ed Williams Aviation Formulary. Modulus is handled a little different in PHP. This works for me.
function get_new_waypoint ( $lat, $lon, $radial, $magvar, $range )
{
// $range in nm.
// $radial is heading to or bearing from
// $magvar for local area.
$range = $range * pi() /(180*60);
$radial = $radial - $magvar ;
if ( $radial < 1 )
{
$radial = 360 + $radial - $magvar;
}
$radial = deg2rad($radial);
$tmp_lat = deg2rad($lat);
$tmp_lon = deg2rad($lon);
$new_lat = asin(sin($tmp_lat)* cos($range) + cos($tmp_lat) * sin($range) * cos($radial));
$new_lat = rad2deg($new_lat);
$new_lon = $tmp_lon - asin(sin($radial) * sin($range)/cos($new_lat))+ pi() % 2 * pi() - pi();
$new_lon = rad2deg($new_lon);
return $new_lat." ".$new_lon;
}

For whoever is interested in a Java solution here is my code:
I noticed that the initial solution needs some tweaks in order to return a proper longitude value, especially when the point is at one of the poles.
Also a round operation is sometimes required as the results on 0 latitude / longitude seem to slightly shift away from 0. For small distances, rounding will help in this regard.
private static final double EARTH_RADIUS = 6371; // average earth radius
/**
* Returns the coordinates of the point situated at the distance specified, in
* the direction specified. Note that the value is an approximation, not an
* exact result.
*
* #param startPointLatitude
* #param startPointLongitude
* #param distanceInKm
* #param bearing: 0 means moving north, 90 moving east, 180 moving
* south, 270 moving west. Max value 360 min value 0;
* #return new point location
*/
public static LocationDTO getPointAt(double startPointLatitude, double startPointLongitude, double distanceInKm,
double bearing) {
if (Math.abs(startPointLatitude) > 90) {
throw new BadRequestException(ExceptionMessages.INVALID_LATITUDE);
} else if (Math.abs(startPointLatitude) == 90) {
startPointLatitude = 89.99999 * Math.signum(startPointLatitude); // we have to do this conversion else the formula doesnt return the correct longitude value
}
if (Math.abs(startPointLongitude) > 180) {
throw new BadRequestException(ExceptionMessages.INVALID_LONGITUDE);
}
double angularDistance = distanceInKm / EARTH_RADIUS;
bearing = deg2rad(bearing);
startPointLatitude = deg2rad(startPointLatitude);
startPointLongitude = deg2rad(startPointLongitude);
double latitude = Math.asin(Math.sin(startPointLatitude) * Math.cos(angularDistance)
+ Math.cos(startPointLatitude) * Math.sin(angularDistance) * Math.cos(bearing));
double longitude = startPointLongitude
+ Math.atan2(Math.sin(bearing) * Math.sin(angularDistance) * Math.cos(startPointLatitude),
Math.cos(angularDistance) - Math.sin(startPointLatitude) * Math.sin(latitude));
longitude = (rad2deg(longitude) + 540) % 360 - 180; // normalize longitude to be in -180 +180 interval
LocationDTO result = new LocationDTO();
result.setLatitude(roundValue(rad2deg(latitude)));
result.setLongitude(roundValue(longitude));
return result;
}
private static double roundValue(double value) {
DecimalFormat df = new DecimalFormat("#.#####");
df.setRoundingMode(RoundingMode.CEILING);
return Double.valueOf(df.format(value));
}
// This function converts decimal degrees to radians
private static double deg2rad(double deg) {
return (deg * Math.PI / 180.0);
}
// This function converts radians to decimal degrees
private static double rad2deg(double rad) {
return (rad * 180.0 / Math.PI);
}

Related

How to calculate size in m2 of each lat/long grid square

I have estimates for data in units m2 at gridsquare resolution. I need to calculate the number of m2 in each latitude / longitude grid cell?
Cell sizes are much smaller near the poles than at the equator so this is important.
I would like a netcdf file or array of the number of square meters in each grid square.
In case anyone would like a netcdf of the number of square meters in each lat long grid cell.
This is probably not the cleanest solution, but will create a netcdf (earth_m2.nc) of m2 in each grid using xarray.
The gridsize() function is adapted from another stack overflow question.
We can then make a dummy array and create a earth field of m2s using the longitude distances at each location.
"""
This will create a global grid of the approximate size of each grid square.
"""
import numpy as np
import xarray as xr
import matplotlib.pyplot as plt
def gridsize(lat1):
#https://en.wikipedia.org/wiki/Haversine_formula
#https://stackoverflow.com/questions/639695/how-to-convert-latitude-or-longitude-to-meters/11172685#11172685
lon1=200
import math
lat2=lat1
lon2=lon1+1
R = 6378.137 # // Radius of earth in km
dLat = lat2 * np.pi / 180 - lat1 * np.pi / 180
dLon = lon2 * np.pi / 180 - lon1 * np.pi / 180
a = np.sin(dLat/2) * np.sin(dLat/2) + np.cos(lat1 * np.pi / 180) * np.cos(lat2 * np.pi / 180) * np.sin(dLon/2) * np.sin(dLon/2)
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1-a))
d = R * c
return d * 1000 #; // meters
boxlo,boxla=np.array(np.meshgrid(np.arange(-179.5,179.5,1),np.arange(-89.5,89.5,1)))
sizes=np.ones(boxlo.shape)
grid=gridsize(boxla)
grid_nc=xr.DataArray(grid,coords={'lat':boxla[:,1],'lon':boxlo[1,:]},dims=['lat','lon'])
lat_size=110567 #in m
grid_nc['m2']=grid_nc*lat_size
grid_nc=grid_nc['m2']
grid_nc.to_netcdf('earth_m2.nc')
plt.pcolormesh(boxlo[1,:],boxla[:,1],grid_nc)
plt.colorbar()
plt.show()
By spherical trigonometry, the surface area of a triangle on a sphere of radius R with 1 vertex at the North pole (latitude: π/2) and 2 vertices at the same latitude -π/2 < x < π/2 separated (longitudinally) by d radians is
S(x, d) = (cos⁻¹((cos(b) - cos²(a))/sin²(a)) + 2cos⁻¹((cos(a) - cos(a)cos(b))/(sin(a)sin(b))) - π)R² where a = R(π/2 - x) and b = Rd
So, the surface area of a grid rectangle on a sphere of radius R between lines of longitude separated by d radians and latitudes x₁ > x₂ is
S(x₂, d) - S(x₁, d)
One option is to transform your cells into a coordinate reference system (CRS) that has units in, say, meters rather than degrees. Then the area calculation is simple.
I assume your coordinates are in WGS84.
For the target CRS there are choices especially if you know the locality of the points, but a common collection of global CRSs like this are Universal Transverse Mercator (UTM), or near the poles Universal Polar Stereographic
For example, for UTM, assuming a list of points of the form [lon, lat] where the last point is equal to the first:
import pyproj
from shapely.geometry import Polygon
from shapely.ops import transform
def utm_epsg(lon: float, lat: float) -> int:
"""
Return the UTM EPSG code for the given lon-lat.
"""
offset = int(round((183 + lon) / 6.0))
return 32600 + offset if lat > 0 else 32700 + offset
for lat in range(-79, 83):
for lon in range(-179, 179):
polygon = Polygon([
[lon, lat],
[lon+1, lat],
[lon+1, lat+1],
[lon, lat+1],
[lon, lat],
])
src_crs = pyproj.CRS.from_epsg(4326)
tgt_crs = pyproj.CRS.from_epsg(utm_epsg(polygon.centroid.x, polygon.centroid.y))
project = pyproj.Transformer.from_crs(src_crs, tgt_crs, always_xy=True).transform
utm_polygon = transform(project, polygon)
# aggregate into some result. Here just printed to stdout.
print(polygon.centroid, utm_polygon.area)
It's worth noting that UTM isn't defined south of 80°S and north of 84°N.

Converting Pixels to LatLng Coordinates from google static image

I am loading a image from google static Map API, the loaded satellite image is a place with hundreds of meters wide and length.
https://maps.googleapis.com/maps/api/staticmap?center=53.4055429,-2.9976502&zoom=16&size=400x400&maptype=satellite&key=YOUR_API_KEY
Additionally, the image resolution shows to be 10 meters, as shown below
.
My question is
as I have known the centered geolocation (53.4055429,-2.9976502) and resolution of this static image, how would I be able to extend it to calculate the geolocation of left up or right bottom in the image, and finally calculate each pixel of the image
What kind of solution is it
Looks like you need not a javascript solution but for python to use it not in browser but on a server. I've created a python example, but it is the math that I am going to stand on, math is all you need to calculate coordinates. Let me do it with js as well to make snippet work in browser. You can see, that python and js give the same results.
Jump to the answer
If you just need formulae for degrees per pixel, here you are. They are simple enough and you don't need any external libraries but just a python's math. The explanation can be found further.
#!/usr/bin/python
import math
w = 400
h = 400
zoom = 16
lat = 53.4055429
lng = -2.9976502
def getPointLatLng(x, y):
parallelMultiplier = math.cos(lat * math.pi / 180)
degreesPerPixelX = 360 / math.pow(2, zoom + 8)
degreesPerPixelY = 360 / math.pow(2, zoom + 8) * parallelMultiplier
pointLat = lat - degreesPerPixelY * ( y - h / 2)
pointLng = lng + degreesPerPixelX * ( x - w / 2)
return (pointLat, pointLng)
print 'NE: ', getPointLatLng(w, 0)
print 'SW: ', getPointLatLng(0, h)
print 'NW: ', getPointLatLng(0, 0)
print 'SE: ', getPointLatLng(w, h)
The output of the script is
$ python getcoords.py
NE: (53.40810128625675, -2.9933586655761717)
SW: (53.40298451374325, -3.001941734423828)
NW: (53.40810128625675, -3.001941734423828)
SE: (53.40298451374325, -2.9933586655761717)
What we have to start with
We have some parameters needed in url https://maps.googleapis.com/maps/api/staticmap?center=53.4055429,-2.9976502&zoom=16&size=400x400&maptype=satellite&key=YOUR_API_KEY – coordinates, zoom, size in pixels.
Let's introduce some initial variables:
var config = {
lat: 53.4055429,
lng: -2.9976502,
zoom: 16,
size: {
x: 400,
y: 400,
}
};
The math of the Earth of 512 pixels
The math is as follows. Zoom 1 stands for full view of the Earth equator 360° when using image size 512 (see the docs for size and zoom). See the example at zoom 1. It is a very important point. The scale (degrees per pixel) doesn't depend on the image size. When one changes image size, one sees the same scale: compare 1 and 2 – the second image is a cropped version of the bigger one. The maximum image size for googleapis is 640.
Every zoom-in increases resolution twice. Therefore the width of your image in terms of longitude is
lngDegrees = 360 / 2**(zoom - 1); // full image width in degrees, ** for power
Then use linear function to find coordinates for any point of the image. It should be mentioned, that linearity works well only for high zoomed images, you can't use it for low zooms like 5 or less. Low zooms have slightly more complex math.
lngDegreesPerPixel = lngDegrees / 512 = 360 / 2**(zoom - 1) / 2**9 = 360 / 2**(zoom + 8);
lngX = config.lng + lngDegreesPerPixel * ( point.x - config.size.x / 2);
Latitude degrees are different
Latitude degree and longitude degree on the equator are of the same size, but if we go north or south, longitude degree become smaller since rings of parallels on the Earth have smaller radii - r = R * cos(lat) < R and therefore image height in degrees becomes smaller (see P.S.).
latDegrees = 360 / 2**(zoom - 1) * cos(lat); // full image height in degrees, ** for power
And respectively
latDegreesPerPixel = latDegrees / 512 = 360 / 2**(zoom - 1) * cos(lat) / 2**9 = 360 / 2**(zoom + 8) * cos(lat);
latY = config.lat - latDegreesPerPixel * ( point.y - config.size.y / 2)
The sign after config.lat differs from the sign for lngX since earth longitude direction coincide with image x direction, but latitude direction is opposed to y direction on the image.
So we can make a simple function now to find a pixel's coordinates using its x and y coordinates on the picture.
var config = {
lat: 53.4055429,
lng: -2.9976502,
zoom: 16,
size: {
x: 400,
y: 400,
}
};
function getCoordinates(x, y) {
var degreesPerPixelX = 360 / Math.pow(2, config.zoom + 8);
var degreesPerPixelY = 360 / Math.pow(2, config.zoom + 8) * Math.cos(config.lat * Math.PI / 180);
return {
lat: config.lat - degreesPerPixelY * ( y - config.size.y / 2),
lng: config.lng + degreesPerPixelX * ( x - config.size.x / 2),
};
}
console.log('SW', getCoordinates(0, config.size.y));
console.log('NE', getCoordinates(config.size.x, 0));
console.log('SE', getCoordinates(config.size.x, config.size.y));
console.log('NW', getCoordinates(0, 0));
console.log('Something at 300,128', getCoordinates(300, 128));
P.S. You can probably ask me, why I place cos(lat) multiplier to latitude, not as a divider to longitude formula. I found, that google chooses to have constant longitude scale per pixel on different latitudes, so, cos goes to latitude as a multiplier.
I believe you can calculate a bounding box using Maps JavaScript API.
You have a center position and know that distance from the center to the NorthEast and SouthWest is 200 pixels, because the size in your example is 400x400.
Have a look at the following code that calculates NE and SW points
var map;
function initMap() {
var latLng = new google.maps.LatLng(53.4055429,-2.9976502);
map = new google.maps.Map(document.getElementById('map'), {
center: latLng,
zoom: 16,
mapTypeId: google.maps.MapTypeId.SATELLITE
});
var marker = new google.maps.Marker({
position: latLng,
map: map
});
google.maps.event.addListener(map, "idle", function() {
//Verical and horizontal distance from center in pixels
var h = 200;
var w = 200;
var centerPixel = map.getProjection().fromLatLngToPoint(latLng);
var pixelSize = Math.pow(2, -map.getZoom());
var nePoint = new google.maps.Point(centerPixel.x + w*pixelSize, centerPixel.y - h*pixelSize);
var swPoint = new google.maps.Point(centerPixel.x - w*pixelSize, centerPixel.y + h*pixelSize);
var ne = map.getProjection().fromPointToLatLng(nePoint);
var sw = map.getProjection().fromPointToLatLng(swPoint);
var neMarker = new google.maps.Marker({
position: ne,
map: map,
title: "NE: " + ne.toString()
});
var swMarker = new google.maps.Marker({
position: sw,
map: map,
title: "SW: " + sw.toString()
});
var polygon = new google.maps.Polygon({
paths: [ne, new google.maps.LatLng(ne.lat(),sw.lng()), sw, new google.maps.LatLng(sw.lat(),ne.lng())],
map: map,
strokeColor: "green"
});
console.log("NE: " + ne.toString());
console.log("SW: " + sw.toString());
});
}
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDztlrk_3CnzGHo7CFvLFqE_2bUKEq1JEU&libraries=geometry&callback=initMap"
async defer></script>
I hope this helps!
UPDATE
In order to solve this in python you should understand the Map and Tile Coordinates principles used by Google Maps JavaScript API and implement projection logic similar to Google Maps API in python.
Fortunately, somebody has already did this task and you can find the project that implements methods similar to map.getProjection().fromLatLngToPoint() and map.getProjection().fromPointToLatLng() from my example in python. Have a look at this project in github:
https://github.com/hrldcpr/mercator.py
So, you can download mercator.py and use it in your project. My JavaScript API example converts into the following python code
#!/usr/bin/python
from mercator import *
w = 200
h = 200
zoom = 16
lat = 53.4055429
lng = -2.9976502
centerPixel = get_lat_lng_tile(lat, lng, zoom)
pixelSize = pow(2, -zoom)
nePoint = (centerPixel[0] + w*pixelSize, centerPixel[1] - h*pixelSize)
swPoint = (centerPixel[0] - w*pixelSize, centerPixel[1] + h*pixelSize)
ne = get_tile_lat_lng(zoom, nePoint[0], nePoint[1]);
sw = get_tile_lat_lng(zoom, swPoint[0], swPoint[1]);
print 'NorthEast: ', ne
print 'SouthWest: ', sw

Using Python S2/S2sphere library - find all s2 cells of a particular level with in a circle(lat, long and radius is given)

What S2Region and how should I use to get all s2 cells at certain parent level(let say 9) covered by the circle drawn from given lat, long and radius. Below is an example which use python s2 library for getting all cells under a rectangle.
region_rect = S2LatLngRect(
S2LatLng.FromDegrees(-51.264871, -30.241701),
S2LatLng.FromDegrees(-51.04618, -30.000003))
coverer = S2RegionCoverer()
coverer.set_min_level(8)
coverer.set_max_level(15)
coverer.set_max_cells(500)
covering = coverer.GetCovering(region_rect)
source of example http://blog.christianperone.com/2015/08/googles-s2-geometry-on-the-sphere-cells-and-hilbert-curve/
I am looking for something like
region_circle = S2latLangCircle(lat,lang,radius)
I find answer of this question for google s2 library implemented in c++ Using google s2 library - find all s2 cells of a certain level within the circle, given lat/lng and radius in miles/km but I need this in python.
Thanks
With the help of link, I worked out for python solution.
I am using python s2sphere library.
earthCircumferenceMeters = 1000 * 40075.017
def earthMetersToRadians(meters):
return (2 * math.pi) * (float(meters) /
const.earthCircumferenceMeters)
def getCoveringRect(lat, lng, radius, parent_level):
radius_radians = earthMetersToRadians(radius)
latlng = LatLng.from_degrees(float(lat),
float(lng)).normalized().to_point()
region = Cap.from_axis_height(latlng,
(radius_radians*radius_radians)/2)
coverer = RegionCoverer()
coverer.min_level = int(parent_level)
coverer.max_level = int(parent_level)
coverer.max_cells = const.MAX_S2_CELLS
covering = coverer.get_covering(region)
s2_rect = []
for cell_id in covering:
new_cell = Cell(cell_id)
vertices = []
for i in range(4):
vertex = new_cell.get_vertex(i)
latlng = LatLng.from_point(vertex)
vertices.append((math.degrees(latlng.lat().radians),
math.degrees(latlng.lng().radians)))
s2_rect.append(vertices)
return s2_rect
getCoveringRect method returns all s2 cells(Rectangle boundary) at given parent level which is covered by circle drawn from given lat, long as center and given radius
Here is a Go example how to get covering cells
import (
"math"
"sort"
"strconv"
"github.com/golang/geo/s2"
)
const (
earthRadiusInMeter = 1000 * 6371.393 // earth radius is 6371km
)
// S=4πR²,s2 regards surface is 4π,that is R=1
func getS2EarthSurfaceArea(radius float64) float64 {
area := math.Pi * radius * radius / (earthRadiusInMeter * earthRadiusInMeter)
return area
}
func GetCellIDs(lng, lat, radius float64) []string {
point := s2.PointFromLatLng(s2.LatLngFromDegrees(lat, lng))
area := getS2EarthSurfaceArea(radius)
_cap := s2.CapFromCenterArea(point, area)
_cover := s2.RegionCoverer{
MinLevel: 13,
MaxLevel: 13,
LevelMod: 1,
MaxCells: 16,
}
cellUnion := _cover.Covering(_cap)
stringCellIDs := make([]string, 0, len(cellUnion))
for _, c := range cellUnion {
stringCellIDs = append(stringCellIDs, c.ToToken())
}
return stringCellIDs
}
I'm not sure if the formula used by Guarav is right.
First, function earthMetersToRadians does not return radians, it just computes (2*pi*r) / (2*pi*R) = r/R where R denotes the earth' radius.
From that, it computes height = (r/R)^2/2, and I'm not sure where this formula comes from.
From the formulae of a spherical cap, we have height = 1 - cos(theta) where theta = arcsin(r/R) in our case.
Together have height = 1 - cos(arcsin(r/R)) which can be computed as height = 1 - sqrt(1 - (r/R)^2).
Note, however, that both formulas are very close, so in practical cases they are pretty much the same, especially if you run an S2Coverer on your cap afterwards.

how to find user location within 500 meters from given lat and long in python

I want to find a user location within 500 meters from given lat and long in Python.
Given lat & long = 19.114315,72.911174
And I want to check whether new lat and long is in the range of 500 meters from given lat and long..
new lat and long = 19.112398,72.912743
I am using this formula in python..
math.acos(math.sin(19.114315) * math.sin(19.112398) + math.cos(19.114315) * math.cos(19.112398) * math.cos(72.912743 - (72.911174))) * 6371 <= 0.500
But its not giving me expected results.. Am I missing something?
please help..
You can use the Haversine formula to get the great-circle distance (along a sphere) between two points. There's some problems about treating the earth like a sphere for great distances, but for 500 meters, you're probably fine (assuming that you're not trying to drop medical packages on a boat or something).
from math import radians, sin, cos, asin, sqrt
def haversine(lat1, long1, lat2, long2, EARTH_RADIUS_KM=6372.8):
# get distance between the points
phi_Lat = radians(lat2 - lat1)
phi_Long = radians(long2 - long1)
lat1 = radians(lat1)
lat2 = radians(lat2)
a = sin(phi_Lat/2)**2 + \
cos(lat1) * cos(lat2) * \
sin(phi_Long/2)**2
c = 2 * asin(sqrt(a))
return EARTH_RADIUS_KM * c
if the distance between the two points is less than your threshold, it's within the range:
points_1 = (19.114315,72.911174)
points_2 = (19.112398,72.912743)
threshold_km = 0.5
distance_km = haversine(points_1[0], points_1[1], points_2[0], points_2[1])
if distance_km < threshold_km:
print('within range')
else:
print('outside range')
hint: write re-usable code, and cleanup your math.
it seems you have an error, right near the end of that formula..
math.cos(longRad - (longRad)) == math.cos(0) == 1
im not good with geo stuff, so I won't correct it..
import math
def inRangeRad(latRad, longRad):
sinLatSqrd = math.sin(latRad) * math.sin(latRad)
cosLatSqrd = math.cos(latRad) * math.cos(latRad)
inner = sinLatSqrd + cosLatSqrd * math.cos(longRad - (longRad))
return math.acos( inner ) * 6371 <= 0.500
def inRangeDeg(latDeg, longDeg):
latRad = 0.0174532925 * latDeg
longRad = 0.0174532925 * longDeg
return inRangeRad(latRad, longRad)
print "test"
print "19.114315, 72.911174"
print inRangeDeg(19.114315, 72.911174)
Be very careful with this! You cannot just use cos and sin for distances using GPS coordinates, because the distance will be incorrect!
https://en.wikipedia.org/wiki/Geodesy#Geodetic_problems
In the case of plane geometry (valid for small areas on the Earth's
surface) the solutions to both problems reduce to simple trigonometry.
On the sphere, the solution is significantly more complex, e.g., in
the inverse problem the azimuths will differ between the two end
points of the connecting great circle, arc, i.e. the geodesic.
Look into GeoPy for these calculations, you really do not want to implement that yourself.

Mercator Projection slightly off

I'm building a project requiring large amounts of google maps images. I defined these functions to be used in another function that will automatically collect images. The latitude changes nicely, but I've noticed the longitude is slightly off. Is that an artifact of the approximate Mercator projection method? I was under the impression that the conversion I've used was pretty accurate except on approaching the poles.
import math
import os
import DLMaps
#Finds the distance covered in a Static Maps image pixel
def PixDist(zoom,scale=2):
earthCirc = 40075.0 #in Km's
base = 256 #size of google maps at zoom = 0
return earthCirc/(base*scale*(2**zoom))
#Finds the Km distance to the next google static maps image based on size of images,
# and distance per pixel
def DistNextImage(distpp, scale=2, size=640):
return distpp*scale*size
#returns a new Lat, Lon co-ordinate given a starting point, km distance change and
# a NESW direction, values 1-4 being used to represent corresponding direction.
def NewLatLon(lat,lon, dist, direction):
if direction==1:
dist = dist/110.54 #approximate change in latitude mercator projection
lat = lat + dist #heading north
elif direction == 2:
dist = dist/(110.32 * math.cos(math.pi*lat/180.0)) #approx change in lon
lon = lon + dist
elif direction==3:
dist = dist/110.54 #approximate change in latitude mercator projection
lat = lat - dist #heading south
elif direction ==4:
dist = dist/(110.32 * math.cos(math.pi*lat/180.0)) #approx change in lon
lon = lon - dist
return lat, lon
The earth is not a true ellipsoid, there are a high number of coordinate systems, and passing from one system to another one is far from simple. You could have a look to pyproj a Python interface to the well known proj.4 library to convert from Lat-Lon (I assume WGS84 ...) to almost any other coordinate including of course Mercator. You could try to roll your own, but there are so many caveats such as different origin meridians, slight differences in reference ellipsoid, that you have little hope to have correct and accurate results.
But you have some reference material on WGS84 on wikipedia
I made an object that does similar calculations. Maybe it might give you some inspiration.
Basically I treat the earth as an ellipsoid. earthCirc along the equator is not the same as earthCirc through the poles.
I try to make conversions between distances in meter <-> angles of lat & lng.
See if my object is more accurate than yours (mine surely has bugs, if you use some extreme values)
/**
* #file: Javascript object to help calculate with latitude, longitude, together with distances (in meter) and angles.
* The initial goal was to calculate the end point (in lat and latitude & longitude) of a line perpendicular to another line.
*
* Planet Earth is approximately an ellipsoid. The circumference along the equator is
* somewhat greater than the equator through both poles.
* this javascript object makes calculations that are useful for Google Maps.
* This will allow to use pythagoras for coordinates, as if earth is a flat rectangle.
* The precision of the results decreases when the distances increase; and near the poles.
* Any calculation where the latitude goes beyond the poles ( > 90 or < -90 ) will probably return complete nonsence.
*
* #author: Emmanuel Delay, emmanueldelay#gmail.com
* copyleft 2014. Feel free to use, copy, share, improve
* Please send me the code, if you make improvements.
*
* Examples:
<script>
function log(message) {
document.getElementById('log').innerHTML += message + '<br>';
}
window.onload = function() {
var dLatLng = Earth.xy2LatLng(5000000, 5000000, 0.0);
latLng = [dLatLng.lat, dLatLng.lng ];
log(
'Start from 0,0 ; move 5000km to the north, 5000km to the east: ' +
latLng[0] +','+ latLng[1]
);
var eifel = {lat: 48.8582186, lng: 2.2946114};
var dLatLng = Earth.xy2LatLng(1000, 2000, eifel.lat);
latLng = [dLatLng.lat, dLatLng.lng ];
var dest = [eifel.lat + latLng[0], eifel.lng + latLng[1] ];
log(
'Move 1km to the north, 2km to the east of the Eifel Tower: ' +
dest[0] +','+ dest[1]
);
var dLatLng = Earth.setHeading(eifel.lat, eifel.lng, 10000, 30);
latLng = [dLatLng.lat, dLatLng.lng ];
log(
'Move 10km from the Eifel Tower, heading 30° (North = 0; east = 90°; ...): ' +
latLng[0] +','+ latLng[1]
);
}
</script>
<div id="log"></div>
* note:
* - all distances are in meter. all angles are in degree
* - the d in dLat and dLng stands for delta, being a change in coordinates
* - x is along the longitude, y is along latitude
*/
Earth = {
// #see http://www.space.com/17638-how-big-is-earth.html for the data
// along the equator
circumference_equator: 40075000,
// throught both poles.
// Note: this is basically the original definition of the meter; they were 2km off on a distance from pole to equator ( http://en.wikipedia.org/wiki/History_of_the_metre )
circumference_poles: 40008000,
// given a change in latitude, how many meters did you move?
lat2Y: function(dLat) {
return this.circumference_poles / 360 * dLat;
},
// given a change in longitude and a given latitude, how many meters did you move?
lng2X: function(dLng, lat) {
return Math.cos( this.deg2rad(lat) ) * (this.circumference_poles / 360 * dLng);
},
// given a distance you move due North (or South), what's the new coordinates?
// returns a change in latitude
y2Lat: function(y) {
return y * 360 / this.circumference_poles;
},
// given a distance you move due East (or West) and a given latitude, what's the new coordinates?
// returns a change in longitude
x2Lng: function(x, lat) {
return x * 360 / ( Math.cos( this.deg2rad(lat) ) * this.circumference_poles);
},
// (360°) degrees to radials
deg2rad: function(deg) {
return deg * Math.PI / 180;
},
// returns a change in position
xy2LatLng: function(y, x, lat) {
return {
lat: this.y2Lat(y),
lng: this.x2Lng(x, lat)
};
},
// #param heading: North = 0; east = 90°; ...
// returns a change in position
setHeading: function(lat, lng, dist, heading) {
var latDestination = lat + this.y2Lat(dist * Math.cos(this.deg2rad(heading)));
var lngDestination = lng + this.x2Lng(dist * Math.sin(this.deg2rad(heading)), lat);
return {
lat: latDestination,
lng: lngDestination
};
},
// returns the absolute position
moveByXY: function(lat, lng, x, y) {
var dLatLng = Earth.xy2LatLng(x, y, lat);
latLng = [dLatLng.lat, dLatLng.lng ];
return {
lat: lat + latLng[0],
lng: lng + latLng[1]
}
}
}

Categories