1 | """This is a port of Google's GMercatorProjection.fromLatLngToPixel.
|
---|
2 |
|
---|
3 | Doco on the original:
|
---|
4 |
|
---|
5 | http://code.google.com/apis/maps/documentation/reference.html#GMercatorProjection
|
---|
6 |
|
---|
7 |
|
---|
8 | Here's how I ported it:
|
---|
9 |
|
---|
10 | http://blag.whit537.org/2007/07/how-to-hack-on-google-maps.html
|
---|
11 |
|
---|
12 |
|
---|
13 | The goofy variable names below are an artifact of Google's javascript
|
---|
14 | obfuscation.
|
---|
15 |
|
---|
16 | """
|
---|
17 | import math
|
---|
18 |
|
---|
19 |
|
---|
20 | # Constants
|
---|
21 | # =========
|
---|
22 | # My knowledge of what these mean is undefined.
|
---|
23 |
|
---|
24 | CBK = [128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 4294967296, 8589934592, 17179869184, 34359738368, 68719476736, 137438953472]
|
---|
25 | CEK = [0.7111111111111111, 1.4222222222222223, 2.8444444444444446, 5.688888888888889, 11.377777777777778, 22.755555555555556, 45.51111111111111, 91.02222222222223, 182.04444444444445, 364.0888888888889, 728.1777777777778, 1456.3555555555556, 2912.711111111111, 5825.422222222222, 11650.844444444445, 23301.68888888889, 46603.37777777778, 93206.75555555556, 186413.51111111112, 372827.02222222224, 745654.0444444445, 1491308.088888889, 2982616.177777778, 5965232.355555556, 11930464.711111112, 23860929.422222223, 47721858.844444446, 95443717.68888889, 190887435.37777779, 381774870.75555557, 763549741.5111111]
|
---|
26 | CFK = [40.74366543152521, 81.48733086305042, 162.97466172610083, 325.94932345220167, 651.8986469044033, 1303.7972938088067, 2607.5945876176133, 5215.189175235227, 10430.378350470453, 20860.756700940907, 41721.51340188181, 83443.02680376363, 166886.05360752725, 333772.1072150545, 667544.214430109, 1335088.428860218, 2670176.857720436, 5340353.715440872, 10680707.430881744, 21361414.86176349, 42722829.72352698, 85445659.44705395, 170891318.8941079, 341782637.7882158, 683565275.5764316, 1367130551.1528633, 2734261102.3057265, 5468522204.611453, 10937044409.222906, 21874088818.445812, 43748177636.891624]
|
---|
27 |
|
---|
28 |
|
---|
29 | def ll2px(lat, lng, zoom):
|
---|
30 | """Given two floats and an int, return a 2-tuple of ints.
|
---|
31 |
|
---|
32 | Note that the pixel coordinates are tied to the entire map, not to the map
|
---|
33 | section currently in view.
|
---|
34 |
|
---|
35 | """
|
---|
36 | assert isinstance(lat, (float, int, long)), \
|
---|
37 | ValueError("lat must be a float")
|
---|
38 | lat = float(lat)
|
---|
39 | assert isinstance(lng, (float, int, long)), \
|
---|
40 | ValueError("lng must be a float")
|
---|
41 | lng = float(lng)
|
---|
42 | assert isinstance(zoom, int), TypeError("zoom must be an int from 0 to 30")
|
---|
43 | assert 0 <= zoom <= 30, ValueError("zoom must be an int from 0 to 30")
|
---|
44 |
|
---|
45 | cbk = CBK[zoom]
|
---|
46 |
|
---|
47 | x = int(round(cbk + (lng * CEK[zoom])))
|
---|
48 |
|
---|
49 | foo = math.sin(lat * math.pi / 180)
|
---|
50 | if foo < -0.9999:
|
---|
51 | foo = -0.9999
|
---|
52 | elif foo > 0.9999:
|
---|
53 | foo = 0.9999
|
---|
54 |
|
---|
55 | y = int(round(cbk + (0.5 * math.log((1+foo)/(1-foo)) * (-CFK[zoom]))))
|
---|
56 |
|
---|
57 | return (x, y)
|
---|
58 |
|
---|
59 |
|
---|
60 |
|
---|
61 | def px2ll(x, y, zoom):
|
---|
62 | """Given three ints, return a 2-tuple of floats.
|
---|
63 |
|
---|
64 | Note that the pixel coordinates are tied to the entire map, not to the map
|
---|
65 | section currently in view.
|
---|
66 |
|
---|
67 | """
|
---|
68 | assert isinstance(x, (int, long)), \
|
---|
69 | ValueError("px must be a 2-tuple of ints")
|
---|
70 | assert isinstance(y, (int, long)), \
|
---|
71 | ValueError("px must be a 2-tuple of ints")
|
---|
72 | assert isinstance(zoom, int), TypeError("zoom must be an int from 0 to 30")
|
---|
73 | assert 0 <= zoom <= 30, ValueError("zoom must be an int from 0 to 30")
|
---|
74 |
|
---|
75 | foo = CBK[zoom]
|
---|
76 | lng = (x - foo) / CEK[zoom]
|
---|
77 | bar = (y - foo) / -CFK[zoom]
|
---|
78 | blam = 2 * math.atan(math.exp(bar)) - math.pi / 2
|
---|
79 | lat = blam / (math.pi / 180)
|
---|
80 |
|
---|
81 | return (lat, lng)
|
---|
82 |
|
---|
83 |
|
---|
84 | if __name__ == '__main__':
|
---|
85 |
|
---|
86 | # Tests
|
---|
87 | # =====
|
---|
88 | # The un-round numbers were gotten by calling Google's js function.
|
---|
89 |
|
---|
90 | data = [ (3, 39.81447, -98.565388, 463, 777)
|
---|
91 | , (3, 40.609538, -80.224528, 568, 771)
|
---|
92 |
|
---|
93 | , (0, -90, 180, 256, 330)
|
---|
94 | , (0, -90, -180, 0, 330)
|
---|
95 | , (0, 90, 180, 256, -74)
|
---|
96 | , (0, 90, -180, 0, -74)
|
---|
97 |
|
---|
98 | , (1, -90, 180, 512, 660)
|
---|
99 | , (1, -90, -180, 0, 660)
|
---|
100 | , (1, 90, 180, 512, -148)
|
---|
101 | , (1, 90, -180, 0, -148)
|
---|
102 |
|
---|
103 | , (2, -90, 180, 1024, 1319)
|
---|
104 | , (2, -90, -180, 0, 1319)
|
---|
105 | , (2, 90, 180, 1024, -295)
|
---|
106 | , (2, 90, -180, 0, -295)
|
---|
107 |
|
---|
108 | ]
|
---|
109 |
|
---|
110 | def close(floats, floats2):
|
---|
111 | """Compare two sets of floats.
|
---|
112 | """
|
---|
113 | lat_actual = abs(floats[0] - floats2[0])
|
---|
114 | lng_actual = abs(floats[1] - floats2[1])
|
---|
115 | assert lat_actual < 1, (floats[0], floats2[0])
|
---|
116 | assert lng_actual < 1, (floats[1], floats2[1])
|
---|
117 | return True
|
---|
118 |
|
---|
119 | for zoom, lat, lng, x, y in data:
|
---|
120 | assert ll2px(lat, lng, zoom) == (x, y), (lat, lng)
|
---|
121 | assert close(px2ll(x, y, zoom), (lat, lng)), (x, y)
|
---|