title: Geographic coordinates converter
body: <p>I wanted a class that can convert one system to another.</p>
  
  <p>I've found a source code in python and tried to port it into C#.</p>
  
  <p>This is the python source. <a href="http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/" rel="nofollow">From here</a></p>
  
  <pre><code>import math
  
  class GlobalMercator(object):
  
      def __init__(self, tileSize=256):
          "Initialize the TMS Global Mercator pyramid"
          self.tileSize = tileSize
          self.initialResolution = 2 * math.pi * 6378137 / self.tileSize
          # 156543.03392804062 for tileSize 256 pixels
          self.originShift = 2 * math.pi * 6378137 / 2.0
          # 20037508.342789244
  
      def LatLonToMeters(self, lat, lon ):
          "Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913"
  
          mx = lon * self.originShift / 180.0
          my = math.log( math.tan((90 + lat) * math.pi / 360.0 )) / (math.pi / 180.0)
  
          my = my * self.originShift / 180.0
          return mx, my
  
      def MetersToLatLon(self, mx, my ):
          "Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum"
  
          lon = (mx / self.originShift) * 180.0
          lat = (my / self.originShift) * 180.0
  
          lat = 180 / math.pi * (2 * math.atan( math.exp( lat * math.pi / 180.0)) - math.pi / 2.0)
          return lat, lon
  
      def PixelsToMeters(self, px, py, zoom):
          "Converts pixel coordinates in given zoom level of pyramid to EPSG:900913"
  
          res = self.Resolution( zoom )
          mx = px * res - self.originShift
          my = py * res - self.originShift
          return mx, my
  
      def MetersToPixels(self, mx, my, zoom):
          "Converts EPSG:900913 to pyramid pixel coordinates in given zoom level"
  
          res = self.Resolution( zoom )
          px = (mx + self.originShift) / res
          py = (my + self.originShift) / res
          return px, py
  
      def PixelsToTile(self, px, py):
          "Returns a tile covering region in given pixel coordinates"
  
          tx = int( math.ceil( px / float(self.tileSize) ) - 1 )
          ty = int( math.ceil( py / float(self.tileSize) ) - 1 )
          return tx, ty
  
      def PixelsToRaster(self, px, py, zoom):
          "Move the origin of pixel coordinates to top-left corner"
  
          mapSize = self.tileSize &lt;&lt; zoom
          return px, mapSize - py
  
      def MetersToTile(self, mx, my, zoom):
          "Returns tile for given mercator coordinates"
  
          px, py = self.MetersToPixels( mx, my, zoom)
          return self.PixelsToTile( px, py)
  
      def TileBounds(self, tx, ty, zoom):
          "Returns bounds of the given tile in EPSG:900913 coordinates"
  
          minx, miny = self.PixelsToMeters( tx*self.tileSize, ty*self.tileSize, zoom )
          maxx, maxy = self.PixelsToMeters( (tx+1)*self.tileSize, (ty+1)*self.tileSize, zoom )
          return ( minx, miny, maxx, maxy )
  
      def TileLatLonBounds(self, tx, ty, zoom ):
          "Returns bounds of the given tile in latutude/longitude using WGS84 datum"
  
          bounds = self.TileBounds( tx, ty, zoom)
          minLat, minLon = self.MetersToLatLon(bounds[0], bounds[1])
          maxLat, maxLon = self.MetersToLatLon(bounds[2], bounds[3])
  
          return ( minLat, minLon, maxLat, maxLon )
  
      def Resolution(self, zoom ):
          "Resolution (meters/pixel) for given zoom level (measured at Equator)"
  
          # return (2 * math.pi * 6378137) / (self.tileSize * 2**zoom)
          return self.initialResolution / (2**zoom)
  
      def ZoomForPixelSize(self, pixelSize ):
          "Maximal scaledown zoom of the pyramid closest to the pixelSize."
  
          for i in range(30):
              if pixelSize &gt; self.Resolution(i):
                  return i-1 if i!=0 else 0 # We don't want to scale up
  
      def GoogleTile(self, tx, ty, zoom):
          "Converts TMS tile coordinates to Google Tile coordinates"
  
          # coordinate origin is moved from bottom-left to top-left corner of the extent
          return tx, (2**zoom - 1) - ty
  
      def QuadTree(self, tx, ty, zoom ):
          "Converts TMS tile coordinates to Microsoft QuadTree"
  
          quadKey = ""
          ty = (2**zoom - 1) - ty
          for i in range(zoom, 0, -1):
              digit = 0
              mask = 1 &lt;&lt; (i-1)
              if (tx &amp; mask) != 0:
                  digit += 1
              if (ty &amp; mask) != 0:
                  digit += 2
              quadKey += str(digit)
  
          return quadKey
  </code></pre>
  
  <p>Here is my C# port.</p>
  
  <pre><code>public class GlobalMercator {
      private Int32 TileSize;
      private Double InitialResolution;
      private Double OriginShift;
      private const Int32 EarthRadius = 6378137;
      public GlobalMercator() {
          TileSize = 256;
          InitialResolution = 2 * Math.PI * EarthRadius / TileSize;
          OriginShift = 2 * Math.PI * EarthRadius / 2;
      }
      public DPoint LatLonToMeters(Double lat, Double lon) {
          var p = new DPoint();
          p.X = lon * OriginShift / 180;
          p.Y = Math.Log(Math.Tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
          p.Y = p.Y * OriginShift / 180;
          return p;
      }
      public GeoPoint MetersToLatLon(DPoint m) {
          var ll = new GeoPoint();
          ll.Longitude = (m.X / OriginShift) * 180;
          ll.Latitude = (m.Y / OriginShift) * 180;
          ll.Latitude = 180 / Math.PI * (2 * Math.Atan(Math.Exp(ll.Latitude * Math.PI / 180)) - Math.PI / 2);
          return ll;
      }
      public DPoint PixelsToMeters(DPoint p, Int32 zoom) {
          var res = Resolution(zoom);
          var met = new DPoint();
          met.X = p.X * res - OriginShift;
          met.Y = p.Y * res - OriginShift;
          return met;
      }
      public DPoint MetersToPixels(DPoint m, Int32 zoom) {
          var res = Resolution(zoom);
          var pix = new DPoint();
          pix.X = (m.X + OriginShift) / res;
          pix.Y = (m.Y + OriginShift) / res;
          return pix;
      }
      public Point PixelsToTile(DPoint p) {
          var t = new Point();
          t.X = (Int32)Math.Ceiling(p.X / (Double)TileSize) - 1;
          t.Y = (Int32)Math.Ceiling(p.Y / (Double)TileSize) - 1;
          return t;
      }
      public Point PixelsToRaster(Point p, Int32 zoom) {
          var mapSize = TileSize &lt;&lt; zoom;
          return new Point(p.X, mapSize - p.Y);
      }
      public Point MetersToTile(Point m, Int32 zoom) {
          var p = MetersToPixels(m, zoom);
          return PixelsToTile(p);
      }
  
      public Pair&lt;DPoint&gt; TileBounds(Point t, Int32 zoom) {
          var min = PixelsToMeters(new DPoint(t.X * TileSize, t.Y * TileSize), zoom);
          var max = PixelsToMeters(new DPoint((t.X + 1) * TileSize, (t.Y + 1) * TileSize), zoom);
          return new Pair&lt;DPoint&gt;(min, max);
      }
      public Pair&lt;GeoPoint&gt; TileLatLonBounds(Point t, Int32 zoom) {
          var bound = TileBounds(t, zoom);
          var min = MetersToLatLon(bound.Min);
          var max = MetersToLatLon(bound.Max);
          return new Pair&lt;GeoPoint&gt;(min, max);
      }
      public Double Resolution(Int32 zoom) {
          return InitialResolution / (2 ^ zoom);
      }
      public Double ZoomForPixelSize(Double pixelSize) {
          for (var i = 0; i &lt; 30; i++)
              if (pixelSize &gt; Resolution(i))
                  return i != 0 ? i - 1 : 0;
          throw new InvalidOperationException();
      }
      public Point ToGoogleTile(Point t, Int32 zoom) {
          return new Point(t.X, ((Int32)Math.Pow(2, zoom) - 1) - t.Y);
      }
      public Point ToTmsTile(Point t, Int32 zoom) {
          return new Point(t.X, ((Int32)Math.Pow(2, zoom) - 1) - t.Y);
      }
  }
  public struct Point {
      public Point(Int32 x, Int32 y)
          : this() {
          X = x;
          Y = y;
      }
      public Int32 X { get; set; }
      public Int32 Y { get; set; }
  }
  public struct DPoint {
      public DPoint(Double x, Double y)
          : this() {
          this.X = x;
          this.Y = y;
      }
      public Double X { get; set; }
      public Double Y { get; set; }
      public static implicit operator DPoint(Point p) {
          return new DPoint(p.X, p.Y);
      }
  }
  public class GeoPoint {
      public Double Latitude  { get; set; }
      public Double Longitude { get; set; }
  }
  public class Pair&lt;T&gt; {
      public Pair() {}
      public Pair(T min, T max) {
          Min = min;
          Max = max;
      }
      public T Min { get; set; }
      public T Max { get; set; }
  }
  </code></pre>
  
  <p>I have two questions.</p>
  
  <ol>
  <li><p>Did I port the code correctly? (I intentionally omitted one method as I don't use it and added one my own)</p></li>
  <li><p>Here I have coordinates</p></li>
  </ol>
  
  <pre>
  WGS84 datum (longitude/latitude):
  -123.75 36.59788913307022
  -118.125 40.97989806962013
  
  Spherical Mercator (meters):
  -13775786.985667605 4383204.9499851465
  -13149614.849955441 5009377.085697312
  
  Pixels
  2560 6144 2816 6400
  
  Google
  x:10, y:24, z:6
  
  TMS
  x:10, y:39, z:6
  
  QuadTree
  023010</pre>
  
  <p>How should I chain the methods so that I get from Google's tile coordinates (10, 24, 6) the  spherical mercator meters?</p>
  
  <p><b>Update</b></p>
  
  <p>Finding answer for my second question is more important for me.</p>