Converting Pixels to LatLng Coordinates from google static image - python

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

Related

calculate latitude and longitude using python [duplicate]

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);
}

GIS / GEOTiff / GDAL / Python How to get coordinates from pixel

I'm working on the project to detect the object from GEOTiff files and return coordinates of the objects and those output will use for drone to fly to those coordinate
I use tensorflow with YOLO v2(image detector framework) and OpenCV to detect the objects that I need in GEOTiff
import cv2
from darkflow.net.build import TFNet
import math
import gdal
# initial stage for YOLO v2
options = {
'model': 'cfg/yolo.cfg',
'load': 'bin/yolov2.weights',
'threshold': 0.4,
}
tfnet = TFNet(options)
# OpenCV read Image
img = cv2.imread('final.tif', cv2.IMREAD_COLOR)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#Predict the image
result = tfnet.return_predict(img)
#Calculate the center and radius of each objects
i = 0
while i < len(result):
tl = (result[i]['topleft']['x'], result[i]['topleft']['y'])
br = (result[i]['bottomright']['x'], result[i]['bottomright']['y'])
point = (int((result[i]['topleft']['x']+result[i]['bottomright']['x'])/2), int((result[i]['topleft']['y']+result[i]['bottomright']['y'])/2))
radius = int(math.hypot(result[i]['topleft']['x'] - point[0], result[i]['topleft']['y'] - point[1]))
label = result[i]['label']
result[i]['pointx'] = point[0]
result[i]['pointy'] = point[1]
result[i]['radius'] = radius
i += 1
print(result)
So the results come out like set of JSON
[{'label': 'person', 'confidence': 0.6090355, 'topleft': {'x': 3711, 'y': 1310}, 'bottomright': {'x': 3981, 'y': 1719}, 'pointx': 3846, 'pointy': 1514, 'radius': 244}]
as you can see the location of the object is return in pixel (x,y)
and I want to use these x,y to convert to coordinate in lat,lng
so I try to use GDAL (the library use for read the GEO infomation that contain inside the image)
so here's the GEO infomation of the image by using gdalinfo in terminal
Driver: GTiff/GeoTIFF
Files: final.tif
Size is 8916, 6888
Coordinate System is:
PROJCS["WGS 84 / UTM zone 47N",
GEOGCS["WGS 84",
DATUM["WGS_1984",
SPHEROID["WGS 84",6378137,298.257223563,
AUTHORITY["EPSG","7030"]],
AUTHORITY["EPSG","6326"]],
PRIMEM["Greenwich",0,
AUTHORITY["EPSG","8901"]],
UNIT["degree",0.0174532925199433,
AUTHORITY["EPSG","9122"]],
AUTHORITY["EPSG","4326"]],
PROJECTION["Transverse_Mercator"],
PARAMETER["latitude_of_origin",0],
PARAMETER["central_meridian",99],
PARAMETER["scale_factor",0.9996],
PARAMETER["false_easting",500000],
PARAMETER["false_northing",0],
UNIT["metre",1,
AUTHORITY["EPSG","9001"]],
AXIS["Easting",EAST],
AXIS["Northing",NORTH],
AUTHORITY["EPSG","32647"]]
Origin = (667759.259870000067167,1546341.352920000208542)
Pixel Size = (0.032920000000000,-0.032920000000000)
Metadata:
AREA_OR_POINT=Area
TIFFTAG_SOFTWARE=pix4dmapper
Image Structure Metadata:
COMPRESSION=LZW
INTERLEAVE=PIXEL
Corner Coordinates:
Upper Left ( 667759.260, 1546341.353) (100d33'11.42"E, 13d58'57.03"N)
Lower Left ( 667759.260, 1546114.600) (100d33'11.37"E, 13d58'49.65"N)
Upper Right ( 668052.775, 1546341.353) (100d33'21.20"E, 13d58'56.97"N)
Lower Right ( 668052.775, 1546114.600) (100d33'21.15"E, 13d58'49.59"N)
Center ( 667906.017, 1546227.976) (100d33'16.29"E, 13d58'53.31"N)
Band 1 Block=8916x1 Type=Byte, ColorInterp=Red
NoData Value=-10000
Band 2 Block=8916x1 Type=Byte, ColorInterp=Green
NoData Value=-10000
Band 3 Block=8916x1 Type=Byte, ColorInterp=Blue
NoData Value=-10000
Band 4 Block=8916x1 Type=Byte, ColorInterp=Alpha
NoData Value=-10000
Any one?
You need to transform pixel coordinates to geographic space using the GeoTransform matrix that is associated to your raster files. Using GDAL you could do something like the following:
# open the dataset and get the geo transform matrix
ds = gdal.Open('final.tif')
xoffset, px_w, rot1, yoffset, px_h, rot2 = ds.GetGeoTransform()
# supposing x and y are your pixel coordinate this
# is how to get the coordinate in space.
posX = px_w * x + rot1 * y + xoffset
posY = rot2 * x + px_h * y + yoffset
# shift to the center of the pixel
posX += px_w / 2.0
posY += px_h / 2.0
Of course the position you get will be relative to the same coordinate reference system that is used for your raster dataset. So if you need to transform it to lat/long you will have to do further elaborations:
# get CRS from dataset
crs = osr.SpatialReference()
crs.ImportFromWkt(ds.GetProjectionRef())
# create lat/long crs with WGS84 datum
crsGeo = osr.SpatialReference()
crsGeo.ImportFromEPSG(4326) # 4326 is the EPSG id of lat/long crs
t = osr.CoordinateTransformation(crs, crsGeo)
(lat, long, z) = t.TransformPoint(posX, posY)
Sorry I'm not really fluent in python, so probably you will have to adapt this code. Checkout the documentation of GeoTransform here for the C++ API to learn more about the matrix elements.
Without the excellent and clear Python code posted by Gabriella, I don't know if I would have ever figured out how to do this in C. The documentation and examples for gdal are amazingly sparse.
Here's a C version of Gabriella's code:
const char fn[] = "/path/to/geo/file.tif";
GDALDatasetH hDataset;
GDALAllRegister(); // Register all GDAL formats
hDataset = GDALOpen( fn, GA_ReadOnly ); // Open our geo file (GeoTIFF or other supported format)
if (hDataset == NULL)
{
printf("Failed to open dataset\n");
return;
}
// These are the input points to be transformed, in pixel coordinates of the source raster file
double x = 20;
double y = 20;
double adfGeoTransform[6];
GDALGetGeoTransform( hDataset, adfGeoTransform );
// Put the returned transform values into named vars for readability
double xoffset = adfGeoTransform[0];
double px_w = adfGeoTransform[1];
double rot1 = adfGeoTransform[2];
double yoffset = adfGeoTransform[3];
double rot2 = adfGeoTransform[4];
double px_h = adfGeoTransform[5];
// Apply transform to x,y. Put into posX,posY
double posX = px_w * x + rot1 * y + xoffset;
double posY = rot2 * x + px_h * y + yoffset;
// Transform to center of pixel
posX += px_w / 2.0;
posY += px_h / 2.0;
OGRErr err = 0;
// sr0 is the "from" spatial reference, pulled out of our file
OGRSpatialReferenceH sr0 = OSRNewSpatialReference(GDALGetProjectionRef(hDataset));
// sr1 is the "to" spatial reference, initialized as EPSG 4326 (lat/lon)
OGRSpatialReferenceH sr1 = OSRNewSpatialReference(NULL);
err = OSRImportFromEPSG(sr1, 4326);
double xtrans = posX;
double ytrans = posY;
double ztrans = 0;
int pabSuccess = 0;
// Make our transformation object
OGRCoordinateTransformationH trans = OCTNewCoordinateTransformation(sr0, sr1);
// Transform our point posX,posY, put it into xTrans,yTrans
OCTTransformEx(trans, 1, &xtrans, &ytrans, &ztrans, &pabSuccess);
GDALClose(hDataset);
printf("map coordinates (%f, %f)\n", xtrans, ytrans);

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.

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]
}
}
}

projecting lat/lng to pixel x,y on google maps

In python, I want to convert between lat/lng (in epsg 4326) to x, y in google maps pixels (assuming the closest zoom level).
pyproj lets me convert between projection spaces with code like:
>>> p1 = pyproj.Proj(init='epsg:4326')
>>> p2 = pyproj.Proj(init='epsg:3857')
>>> pyproj.transform(p1, p2, 10, 10)
>>> pyproj.transform(p1, p2, 10, 10)
(1113194.9079327343, 1118889.9748579597)
What I'm not sure about, is the proper projection to use to get the x, y pixels.
There's a PHP implementation of the transformation on this page:
http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps
define('OFFSET', 268435456);
define('RADIUS', 85445659.4471); /* $offset / pi() */
function lonToX($lon) {
return round(OFFSET + RADIUS * $lon * pi() / 180);
}
function latToY($lat) {
return round(OFFSET - RADIUS *
log((1 + sin($lat * pi() / 180)) /
(1 - sin($lat * pi() / 180))) / 2);
}
Rather than coding the transform manually in python, I would rather use pyproj, since it will be more flexible, and easier to reverse the transform.
These are the values that implementation gives on the sample point (10, 10):
>>> lon_to_x(10)
283348536.88889694
>>> lat_to_y(10)
253446080.30134577

Categories