forked from kartverket/kivyMaps
-
Notifications
You must be signed in to change notification settings - Fork 4
/
TileServer.py
362 lines (296 loc) · 11.5 KB
/
TileServer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
import kivy
kivy.require('1.0.7')
from kivy.loader import Loader
from kivy.cache import Cache
from kivy.logger import Logger
from kivy.factory import Factory
from kivy.core.image import Image
from os.path import join, dirname, exists, isdir, isfile, sep
from os import makedirs, mkdir, _exit
from collections import deque
from threading import Condition, Thread, Event
#from httplib import HTTPConnection
from urllib2 import urlopen
from random import randint
from projections import *
### static configuration - TODO: parametrize ####################################
# number of threads to use
TILESERVER_POOLSIZE = 10
TILESERVER_MAXPIPELINE = 2
#################################################################################
### init cache - TODO: parametrize ##############################################
Cache.register('tileserver.tiles', limit=10, timeout=10) #1000/10000
Cache.register('tileserver.tilesalpha', limit=10, timeout=10)
#################################################################################
class TileServer(object):
'''Base implementation for a tile provider.
Check GoogleTileServer and YahooTileServer if you intend to use more
'''
provider_name = 'unknown'
providers = dict()
@staticmethod
def register(cls):
TileServer.providers[cls.provider_name] = cls
def __init__(self, poolsize=TILESERVER_POOLSIZE):
self.cache_path = join(dirname(__file__), 'cache', self.provider_name)
if not isdir(self.cache_path):
makedirs(self.cache_path)
black = Loader.image(join('documents','black.png'))
#Loader._loading_image = black
self.q_in = deque()
self.q_out = deque()
self.q_count = 0
self.c_in = Condition()
self.workers = []
self.poolsize = poolsize
self.uniqid = 1
self.want_close = False
self.available_maptype = dict(roadmap='Roadmap')
self.hcsvnt = Loader.image(join('documents','hcsvnt.png'))
def start(self):
'''Start all the workers
'''
for i in xrange(self.poolsize):
self.create_worker()
def create_worker(self):
'''Create a new worker, and append to the list of current workers
'''
thread = Thread(target=self._worker_run,
args=(self.c_in, self.q_in, self.q_out))
thread.daemon = True
thread.start()
self.workers.append(thread)
def stop(self, wait=False):
'''Stop all workers
'''
self.want_close = True
if wait:
for x in self.workers:
x.join()
def post_download(self, filename):
'''Callback called after the download append. You can use it for
doing some image processing, like cropping
.. warning::
This function is called inside a worker Thread.
'''
pass
def to_filename(self, nx, ny, zoom, maptype, format):
fid = self.to_id(nx, ny, zoom, maptype, format)
hash = fid[0:2]
return join(self.cache_path, hash, fid)
def to_id(self, nx, ny, zoom, maptype, format):
return '%d_%d_%d_%s.%s' % (nx, ny, zoom, maptype, format)
def exist(self, nx, ny, zoom, maptype, format='png'):
filename = self.to_filename(nx, ny, zoom, maptype, format)
img = Cache.get('tileserver.tiles', filename)
return bool(img)
def get(self, nx, ny, zoom, maptype, format='png'):
'''Get a tile
'''
filename = self.to_filename(nx, ny, zoom, maptype, format)
img = Cache.get('tileserver.tiles', filename)
# check if the tile is already being loaded
if img is False:
return None
# check if the tile exist in the cache
if img is not None:
return img
# no tile, ask to workers to download
Cache.append('tileserver.tiles', filename, False)
self.q_count += 1
self.q_in.append((nx, ny, zoom, maptype, format))
self.c_in.acquire()
self.c_in.notify()
self.c_in.release()
return None
def update(self):
'''Must be called to get pull image from the workers queue
'''
pop = self.q_out.pop
while True:
try:
filename, image = pop()
self.q_count -= 1
except:
return
Cache.append('tileserver.tiles', filename, image)
def _worker_run(self, c_in, q_in, q_out):
'''Internal. Main function for every worker
'''
do = self._worker_run_once
while not self.want_close:
try:
do(c_in, q_in, q_out)
except:
Logger.exception('TileServerWorker: Unknown exception, stop the worker')
return
def _worker_run_once(self, c_in, q_in, q_out):
'''Internal. Load one image, process, and push.
'''
# get one tile to process
try:
nx, ny, zoom, maptype, format = q_in.pop()
except:
c_in.acquire()
c_in.wait()
c_in.release()
return
# check if the tile already have been downloaded
filename = self.to_filename(nx, ny, zoom, maptype, format)
loaded = True
if not isfile(filename):
loaded = False
# calculate the good tile index
tz = pow(2, zoom)
lx, ly = unit_to_latlon(2.0 * (nx + 0.5) / tz - 1, 1 - 2.0 * (ny + 0.5) / tz)
lx, ly = map(fix180, (lx, ly))
# get url for this specific tile
url = self.geturl(
nx=nx, ny=ny,
lx=lx, ly=ly,
tilew=256, tileh=256,
zoom=zoom,
format=format,
maptype=maptype
)
for i in xrange(1,3):
try:
conn = urlopen("http://%s%s" % (self.provider_host, url))
except Exception,ex:
print "ERROR IN %s/%s \n%s" % (self.provider_host, url, str(ex))
continue
try:
data = conn.read()
conn.close()
except Exception, e:
print 'Exception %s' % (url)
Logger.error('TileServer: "%s": %s' % (str(e), filename))
Logger.error('TileServer: "%s": URL=%s' % (str(e),url))
continue
# discard error messages
if data[:5] == "<?xml":
msg = ""
try:
msg = data[data.index("<ServiceException>")+18 : data.index("</ServiceException")]
except:
pass
Logger.error('Tileserver: Received error fetching %s: %s' % (url, msg))
continue
# write data on disk
try:
directory = sep.join(filename.split(sep)[:-1])
if not isdir(directory):
try:
mkdir(directory)
except:
pass # that was probably just a concurrency error - if dir is missing, all threads report it
with open(filename, 'wb') as fd:
fd.write(data)
except:
Logger.exception('Tileserver: Unable to write %s' % filename)
continue
# post processing
self.post_download(filename)
loaded = True
break
if not loaded:
return
# load image
try:
image = Loader.image(filename)
except Exception,e:
Logger.error('TileServer|HCSVNT "%s": file=%s' % (str(e), filename))
image = self.hcsvnt
image.id = 'img%d' % self.uniqid
self.uniqid += 1
# push image on the queue
q_out.appendleft((filename, image))
class GoogleTileServer(TileServer):
'''Google tile server.
.. warning::
This tile server will not work, cause of limitation of Google.
It's just for testing purpose, don't use it !
'''
provider_name = 'google'
provider_host = 'maps.google.com'
available_maptype = dict(roadmap='Roadmap')
def geturl(self, **infos):
infos['tileh'] += GMAPS_CROPSIZE * 2 # for cropping
return '/maps/api/staticmap?center=' + \
"%(lx)f,%(ly)f&zoom=%(zoom)d&size=%(tilew)dx%(tileh)d" \
"&sensor=false&maptype=%(maptype)s&format=%(format)s" % \
infos
def post_download(self, filename):
# Reread the file with pygame to crop it
import pygame
img = pygame.image.load(filename)
img = img.subsurface((0, GMAPS_CROPSIZE, 256, 256))
pygame.image.save(img, filename)
class YahooTileServer(TileServer):
'''Yahoo tile server implementation
'''
provider_name = 'yahoo'
provider_host = 'us.maps2.yimg.com'
available_maptype = dict(roadmap='Roadmap')
def geturl(self, **infos):
def toYahoo(col, row, zoom):
x = col
y = int(pow(2, zoom - 1) - row - 1)
z = 18 - zoom
return x, y, z
coordinates = 'x=%d&y=%d&z=%d' % toYahoo(infos['nx'], infos['ny'], infos['zoom'])
return '/us.png.maps.yimg.com/png?v=%s&t=m&%s' % \
('3.52', coordinates)
class BlueMarbleTileServer(TileServer):
'''Blue Marble tile server implementation
'''
provider_name = 'bluemarble'
provider_host = 's3.amazonaws.com'
available_maptype = dict(roadmap='Satellite')
def geturl(self, **infos):
return '/com.modestmaps.bluemarble/%d-r%d-c%d.jpg' % (
infos['zoom'], infos['ny'], infos['nx']
)
class BingTileServer(TileServer):
'''Bing tile server implementation. Support road and satellite
'''
provider_name = 'bing'
available_maptype = dict(roadmap='Roadmap', satellite='Satellite')
def geturl(self, **infos):
octalStrings = ('000', '001', '010', '011', '100', '101', '110', '111')
microsoftToCorners = {'00': '0', '01': '1', '10': '2', '11': '3'}
def toBinaryString(i):
return ''.join([octalStrings[int(c)] for c in oct(i)]).lstrip('0')
def toMicrosoft(col, row, zoom):
x = col
y = row
y, x = toBinaryString(y).rjust(zoom, '0'), toBinaryString(x).rjust(zoom, '0')
string = ''.join([microsoftToCorners[y[c]+x[c]] for c in range(zoom)])
return string
if infos['maptype'] in ('satellite', 'aerial'):
mapprefix = 'h'
else:
mapprefix = 'r'
return '/tiles/%s%s.png?g=90&shading=hill' % \
(mapprefix, toMicrosoft(infos['nx'], infos['ny'], infos['zoom']))
@property
def provider_host(self):
return 'r%d.ortho.tiles.virtualearth.net' % randint(0, 3)
class OpenStreetMapTileServer(TileServer):
'''OSM tile server implementation
'''
provider_name = 'openstreetmap'
provider_host = 'tile.openstreetmap.org'
available_maptype = dict(roadmap='Roadmap')
def geturl(self, **infos):
row, col, zoom = infos['nx'], infos['ny'], infos['zoom']
return '/%d/%d/%d.png' % (zoom, row, col)
#
# Registers
#
TileServer.register(BlueMarbleTileServer)
TileServer.register(BingTileServer)
TileServer.register(YahooTileServer)
TileServer.register(OpenStreetMapTileServer)
#TileServer.register(GoogleTileServer) # disfunct
Factory.register('TileServer', TileServer)