~gepatino/+junk/mapper

« back to all changes in this revision

Viewing changes to mapper/providers.py

  • Committer: Gabriel Patiño
  • Date: 2013-06-14 12:50:29 UTC
  • Revision ID: gepatino@gmail.com-20130614125029-530i095xxlcsyxom
Initial commmit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from math import *
 
2
import Image
 
3
import ImageDraw
 
4
import os.path
 
5
import StringIO
 
6
import urllib2
 
7
 
 
8
import cache
 
9
 
 
10
 
 
11
def numTiles(z):
 
12
    return pow(2, z) 
 
13
 
 
14
 
 
15
def sec(x):
 
16
    return 1 / cos(x)
 
17
 
 
18
 
 
19
def latlon2relativeXY(lat, lon):
 
20
    x = (lon + 180.0) / 360.0
 
21
    y = (1 - log(tan(radians(lat)) + sec(radians(lat))) / pi) / 2
 
22
    return (x, y)
 
23
 
 
24
 
 
25
def latlon2xy(lat, lon, z):
 
26
    n = numTiles(z) 
 
27
    x, y = latlon2relativeXY(lat, lon)
 
28
    return (n * x, n * y)
 
29
 
 
30
 
 
31
def tileXY(lat, lon, z):
 
32
    x, y = latlon2xy(lat, lon, z)
 
33
    return (int(x), int(y))
 
34
 
 
35
 
 
36
def xy2latlon(x, y, z):
 
37
    n = numTiles(z)
 
38
    relY = y / n
 
39
    lat = mercatorToLat(pi * (1 - 2 * relY))
 
40
    lon = -180.0 + 360.0 * x / n
 
41
    return (lat, lon)
 
42
  
 
43
 
 
44
def latEdges(y, z):
 
45
    n = numTiles(z)
 
46
    unit = 1 / n
 
47
    relY1 = y * unit
 
48
    relY2 = relY1 + unit
 
49
    lat1 = mercatorToLat(pi * (1 - 2 * relY1))
 
50
    lat2 = mercatorToLat(pi * (1 - 2 * relY2))
 
51
    return (lat1, lat2)
 
52
 
 
53
 
 
54
def lonEdges(x, z):
 
55
    n = numTiles(z)
 
56
    unit = 360 / n
 
57
    lon1 = -180 + x * unit
 
58
    lon2 = lon1 + unit
 
59
    return (lon1, lon2)
 
60
  
 
61
 
 
62
def tileEdges(x, y, z):
 
63
    north, south = latEdges(y, z)
 
64
    west, east = lonEdges(x, z)
 
65
    return (south, west, north, east) 
 
66
 
 
67
 
 
68
def mercatorToLat(mercatorY):
 
69
    return degrees(atan(sinh(mercatorY)))
 
70
 
 
71
 
 
72
class TileProvider(object):
 
73
    def __init__(self):
 
74
        self.name = 'default'
 
75
        self.tile_size = 256
 
76
        self.max_zoom = 18
 
77
        self.url = '%d/%d/%d'
 
78
 
 
79
    def get_tile_url(self, x, y, z):
 
80
        return self.url % (z, x, y)
 
81
 
 
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)
 
85
 
 
86
        (x1, y1) = tileXY(crop_n, crop_w, zoom)
 
87
        (x2, y2) = tileXY(crop_s, crop_e, zoom)
 
88
 
 
89
        map_width = ((x2 - x1) + 1) * self.tile_size
 
90
        map_height = ((y2 - y1) + 1) * self.tile_size
 
91
 
 
92
        (_, w_limit, n_limit, _) = tileEdges(x1, y1, zoom)
 
93
        (s_limit, _, _, e_limit) = tileEdges(x2, y2, zoom)
 
94
 
 
95
        lat_degs = n_limit - s_limit
 
96
        lon_degs = e_limit - w_limit
 
97
 
 
98
        lat_deg_size = map_height / lat_degs
 
99
        lon_deg_size = map_width / lon_degs
 
100
 
 
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))
 
105
 
 
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)
 
112
                data = c[url]
 
113
                if data is None:
 
114
                    print 'Fetching %s ' % url,
 
115
                    res = urllib2.urlopen(url)
 
116
                    data = res.read()
 
117
                    c[url] = data
 
118
                    print 'DONE'
 
119
 
 
120
                datastream = StringIO.StringIO(data)
 
121
                im = Image.open(datastream)
 
122
 
 
123
                xpos = self.tile_size * (i - x1)
 
124
                ypos = self.tile_size * (j - y1)
 
125
                map_img.paste(im, (xpos, ypos))
 
126
 
 
127
        map_img = map_img.crop((crop_left, crop_top, crop_right, crop_bottom))
 
128
        map_img = map_img.resize(size, Image.ANTIALIAS)
 
129
 
 
130
        return (map_img, (crop_n, crop_s, crop_e, crop_w))
 
131
 
 
132
    def best_zoom(self, north, west, south, east, map_size):
 
133
        (relx1, rely1) = latlon2relativeXY(north, west)
 
134
        (relx2, rely2) = latlon2relativeXY(south, east)
 
135
 
 
136
        rel_width = relx2 - relx1
 
137
        rel_height = rely2 - rely1
 
138
 
 
139
        z = self.max_zoom
 
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]:
 
145
                break
 
146
        return z
 
147
 
 
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)
 
151
        finish = False
 
152
        while not finish:
 
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)
 
158
 
 
159
            if aspect_ratio >= 1:
 
160
                # landscape, keep height
 
161
                new_height = map_height
 
162
                new_width = map_height * aspect_ratio
 
163
            else:
 
164
                # portrait, keep width
 
165
                new_width = map_width
 
166
                new_height = new_width / aspect_ratio
 
167
 
 
168
            new_w_delta = new_width - map_width
 
169
            new_h_delta = new_height - map_height
 
170
 
 
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)
 
175
 
 
176
            (new_n, new_w) = xy2latlon(new_x1, new_y1, zoom)
 
177
            (new_s, new_e) = xy2latlon(new_x2, new_y2, zoom)
 
178
 
 
179
            new_zoom = self.best_zoom(new_n, new_w, new_s, new_e, size)
 
180
 
 
181
            if new_zoom == zoom:
 
182
                finish = True
 
183
            else:
 
184
                (n, w, s, e) = (new_n, new_w, new_s, new_e)
 
185
 
 
186
        return (new_n, new_w, new_s, new_e, new_zoom)
 
187
 
 
188
 
 
189
 
 
190
class OpenStreetMapProvider(TileProvider):
 
191
    def __init__(self):
 
192
        super(OpenStreetMapProvider, self).__init__()
 
193
        self.name = 'openstreetmap'
 
194
        self.url = 'http://tile.openstreetmap.org/%d/%d/%d.png'
 
195
 
 
196
 
 
197
class OpenCycleMapProvider(TileProvider):
 
198
    def __init__(self):
 
199
        super(OpenCycleMapProvider, self).__init__()
 
200
        self.name = 'opencyclemap'
 
201
        self.url = 'http://tile.opencyclemap.org/cycle/%d/%d/%d.png'
 
202
 
 
203
 
 
204
class TransportProvider(TileProvider):
 
205
    def __init__(self):
 
206
        super(TransportProvider, self).__init__()
 
207
        self.name = 'transport'
 
208
        self.url = 'http://tile2.opencyclemap.org/transport/%d/%d/%d.png'
 
209
 
 
210
 
 
211
class LandscapeProvider(TileProvider):
 
212
    def __init__(self):
 
213
        super(LandscapeProvider, self).__init__()
 
214
        self.name = 'landscape'
 
215
        self.url = 'http://tile3.opencyclemap.org/landscape/%d/%d/%d.png'
 
216
 
 
217
 
 
218
class OutdoorsProvider(TileProvider):
 
219
    def __init__(self):
 
220
        super(OutdoorsProvider, self).__init__()
 
221
        self.name = 'outdoors'
 
222
        self.url = 'http://tile.thunderforest.com/outdoors/%d/%d/%d.png'
 
223
 
 
224
 
 
225
PROVIDERS = {
 
226
    'openstreetmap': OpenStreetMapProvider,
 
227
    'opencyclemap': OpenCycleMapProvider,
 
228
    'transport': TransportProvider,
 
229
    'landscape': LandscapeProvider,
 
230
    'outdoors': OutdoorsProvider,
 
231
    }
 
232
 
 
233
 
 
234
def get_provider(pname):
 
235
    return PROVIDERS[pname]()
 
236