Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

enter image description here.

enter image description here

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

like image 499
user824624 Avatar asked Nov 04 '17 00:11

user824624


People also ask

How do you get LatLng?

On your computer, open Google Maps. Right-click the place or area on the map. This will open a pop-up window. You can find your latitude and longitude in decimal format at the top.

Is Google Maps static or dynamic?

The Google Maps Platform static web APIs let you embed a Google Maps image on your web page without requiring JavaScript or any dynamic page loading. The APIs create an image based on URL parameters sent through a standard HTTP request and allow you to display the result on your web page.

Is Google static map API free?

The Maps Static API uses a pay-as-you-go pricing model. Requests for the Maps Static API are billed under the SKU for Static Maps. Along with the overall Google Terms of Use, there are usage limits specific to the Maps Static API.

What is LatLng in Google map?

A LatLng is a point in geographical coordinates: latitude and longitude. Latitude ranges between -90 and 90 degrees, inclusive.


2 Answers

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.

like image 166
shukshin.ivan Avatar answered Oct 16 '22 17:10

shukshin.ivan


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 
like image 3
xomena Avatar answered Oct 16 '22 17:10

xomena