19
def latlon2relativeXY(lat, lon):
20
x = (lon + 180.0) / 360.0
21
y = (1 - log(tan(radians(lat)) + sec(radians(lat))) / pi) / 2
25
def latlon2xy(lat, lon, z):
27
x, y = latlon2relativeXY(lat, lon)
31
def tileXY(lat, lon, z):
32
x, y = latlon2xy(lat, lon, z)
33
return (int(x), int(y))
36
def xy2latlon(x, y, z):
39
lat = mercatorToLat(pi * (1 - 2 * relY))
40
lon = -180.0 + 360.0 * x / n
49
lat1 = mercatorToLat(pi * (1 - 2 * relY1))
50
lat2 = mercatorToLat(pi * (1 - 2 * relY2))
57
lon1 = -180 + x * unit
62
def tileEdges(x, y, z):
63
north, south = latEdges(y, z)
64
west, east = lonEdges(x, z)
65
return (south, west, north, east)
68
def mercatorToLat(mercatorY):
69
return degrees(atan(sinh(mercatorY)))
72
class TileProvider(object):
79
def get_tile_url(self, x, y, z):
80
return self.url % (z, x, y)
82
def compose_map(self, limits, size):
83
north, south, east, west = limits
84
(crop_n, crop_w, crop_s, crop_e, zoom) = self.surrounding_area(north, west, south, east, size)
86
(x1, y1) = tileXY(crop_n, crop_w, zoom)
87
(x2, y2) = tileXY(crop_s, crop_e, zoom)
89
map_width = ((x2 - x1) + 1) * self.tile_size
90
map_height = ((y2 - y1) + 1) * self.tile_size
92
(_, w_limit, n_limit, _) = tileEdges(x1, y1, zoom)
93
(s_limit, _, _, e_limit) = tileEdges(x2, y2, zoom)
95
lat_degs = n_limit - s_limit
96
lon_degs = e_limit - w_limit
98
lat_deg_size = map_height / lat_degs
99
lon_deg_size = map_width / lon_degs
101
crop_top = int(ceil((n_limit - crop_n) * lat_deg_size))
102
crop_bottom = int(floor((n_limit - crop_s) * lat_deg_size))
103
crop_left = int(floor((crop_w - w_limit) * lon_deg_size))
104
crop_right = int(ceil((crop_e - w_limit) * lon_deg_size))
106
path = os.path.join('tiles', self.name)
107
c = cache.DiskCache(path)
108
map_img = Image.new('RGBA', (map_width, map_height))
109
for i in range(x1, x2 + 1):
110
for j in range(y1, y2 + 1):
111
url = self.get_tile_url(i, j, zoom)
114
print 'Fetching %s ' % url,
115
res = urllib2.urlopen(url)
120
datastream = StringIO.StringIO(data)
121
im = Image.open(datastream)
123
xpos = self.tile_size * (i - x1)
124
ypos = self.tile_size * (j - y1)
125
map_img.paste(im, (xpos, ypos))
127
map_img = map_img.crop((crop_left, crop_top, crop_right, crop_bottom))
128
map_img = map_img.resize(size, Image.ANTIALIAS)
130
return (map_img, (crop_n, crop_s, crop_e, crop_w))
132
def best_zoom(self, north, west, south, east, map_size):
133
(relx1, rely1) = latlon2relativeXY(north, west)
134
(relx2, rely2) = latlon2relativeXY(south, east)
136
rel_width = relx2 - relx1
137
rel_height = rely2 - rely1
140
for z in range(self.max_zoom + 1):
141
zoom_pixels = self.tile_size * numTiles(z)
142
zoom_width = zoom_pixels * rel_width
143
zoom_height = zoom_pixels * rel_height
144
if zoom_width >= map_size[0] and zoom_height >= map_size[1]:
148
def surrounding_area(self, north, west, south, east, size):
149
aspect_ratio = float(size[0]) / float(size[1])
150
(n, w, s, e) = (north, west, south, east)
153
zoom = self.best_zoom(n, w, s, e, size)
154
(x1, y1) = latlon2xy(n, w, zoom)
155
(x2, y2) = latlon2xy(s, e, zoom)
156
map_width = (x2 - x1)
157
map_height = (y2 - y1)
159
if aspect_ratio >= 1:
160
# landscape, keep height
161
new_height = map_height
162
new_width = map_height * aspect_ratio
164
# portrait, keep width
165
new_width = map_width
166
new_height = new_width / aspect_ratio
168
new_w_delta = new_width - map_width
169
new_h_delta = new_height - map_height
171
new_x1 = x1 - (new_w_delta / 2)
172
new_x2 = x2 + (new_w_delta / 2)
173
new_y1 = y1 - (new_h_delta / 2)
174
new_y2 = y2 + (new_h_delta / 2)
176
(new_n, new_w) = xy2latlon(new_x1, new_y1, zoom)
177
(new_s, new_e) = xy2latlon(new_x2, new_y2, zoom)
179
new_zoom = self.best_zoom(new_n, new_w, new_s, new_e, size)
184
(n, w, s, e) = (new_n, new_w, new_s, new_e)
186
return (new_n, new_w, new_s, new_e, new_zoom)
190
class OpenStreetMapProvider(TileProvider):
192
super(OpenStreetMapProvider, self).__init__()
193
self.name = 'openstreetmap'
194
self.url = 'http://tile.openstreetmap.org/%d/%d/%d.png'
197
class OpenCycleMapProvider(TileProvider):
199
super(OpenCycleMapProvider, self).__init__()
200
self.name = 'opencyclemap'
201
self.url = 'http://tile.opencyclemap.org/cycle/%d/%d/%d.png'
204
class TransportProvider(TileProvider):
206
super(TransportProvider, self).__init__()
207
self.name = 'transport'
208
self.url = 'http://tile2.opencyclemap.org/transport/%d/%d/%d.png'
211
class LandscapeProvider(TileProvider):
213
super(LandscapeProvider, self).__init__()
214
self.name = 'landscape'
215
self.url = 'http://tile3.opencyclemap.org/landscape/%d/%d/%d.png'
218
class OutdoorsProvider(TileProvider):
220
super(OutdoorsProvider, self).__init__()
221
self.name = 'outdoors'
222
self.url = 'http://tile.thunderforest.com/outdoors/%d/%d/%d.png'
226
'openstreetmap': OpenStreetMapProvider,
227
'opencyclemap': OpenCycleMapProvider,
228
'transport': TransportProvider,
229
'landscape': LandscapeProvider,
230
'outdoors': OutdoorsProvider,
234
def get_provider(pname):
235
return PROVIDERS[pname]()