2

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
  • For reference, I was using the Lat Lon co-ordinates for brisbane, -27.4679, 153.0278, zoom level 17. The maps are clearly shifted to far across and lose information between the starting point and new co-ords. – NathanielJPerkins Dec 22 '14 at 08:12
  • As an aside, a lon, lat of any precision does not uniquely specify a point on the Earth's surface (it gets within about 1km). You need the datum that the lon, lat was measured in to uniquely identify a point. In some cases, the datum is a sphere or ellipsoid, but for modern data, it's actually a grid showing the irregularities in "sea level". However, google discards the datum information in its "projection". The differences you're seeing are due to google using an ellipsoidal approximation instead of a spherical approximation as you did. The "correct" approximation is an irregular datum. – Joe Kington Dec 22 '14 at 15:35
  • Are the approximations google used available somewhere? My project doesn't particularly need to be 100% accurate, I'm just a bit of a perfectionist. So I'd need to redefine my PixDist function to have like a north-south pole circumference as well as one running perpendicular to that? – NathanielJPerkins Dec 23 '14 at 11:41

2 Answers2

3

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

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Yes, what I made is (kind of) a naive look at the earth, as a perfect ellipsoid. It seems to give me results I am happy with, but if great precision is needed, I can understand that the calculations need extra formulas – Emmanuel Delay Dec 22 '14 at 15:17
  • Interestingly enough, the google maps "projection" uses WGS84 lon, lat coordinates, but _doesn't_ use the datum information in the Mercator projection (it assumes an ellipsoid, rather than the full, irregular datum). At any rate, you're quite correct in general (projections are like cryptography: best left to a standard library), but it is possible to recreate google's Mercator projection with just the ellipsoidal parameters. You don't need the gridded datum information to do it, and you'll actually get the "wrong" answer if you do it correctly and use the datum information. – Joe Kington Dec 22 '14 at 15:25
2

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]
    }
  }
}
Emmanuel Delay
  • 3,619
  • 1
  • 11
  • 17