15
15
# You should have received a copy of the GNU General Public License
16
16
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
# Author: Daniel Fett advancedcaching@fragcom.de
18
# Author: Daniel Fett agtl@danielfett.de
19
# Jabber: fett.daniel@jaber.ccc.de
20
# Bugtracker and GIT Repository: http://github.com/webhamster/advancedcaching
32
# socket.setdefaulttimeout(10)
33
# makes problem on slow (gprs) connections
35
class TileLoader(threading.Thread):
37
semaphore = threading.Semaphore(40)
38
lock = thread.allocate_lock() #download-lock
42
def __init__(self, tile, zoom, gui, base_dir, num=0):
43
threading.Thread.__init__(self)
48
self.base_dir = base_dir
51
self.local_filename = os.path.join(self.base_dir, str(self.zoom), str(self.tile[0]), "%d.png" % self.tile[1])
52
self.remote_filename = "http://128.40.168.104/mapnik/%d/%d/%d.png" % (self.zoom, self.tile[0], self.tile[1])
53
self.my_noimage = None
54
#self.remote_filename = "http://andy.sandbox.cloudmade.com/tiles/cycle/%d/%d/%d.png" % (self.zoom, self.tile[0], self.tile[1])
59
if not os.path.isfile(self.local_filename):
60
print "Datei existiert nicht: '%s' " % self.local_filename
61
path_1 = "%s%d" % (self.base_dir, self.zoom)
62
path_2 = "%s/%d" % (path_1, self.tile[0])
64
if not os.path.exists(path_1):
66
if not os.path.exists(path_2):
23
from __future__ import with_statement
26
logger = logging.getLogger('openstreetmap')
28
from os import path, mkdir, extsep, remove
29
from threading import Semaphore
30
from urllib import urlretrieve
31
from socket import setdefaulttimeout
34
CONCURRENT_THREADS = 10
36
def get_tile_loader(prefix, remote_url, max_zoom = 18, reverse_zoom = False, file_type = 'png', size = 256):
39
semaphore = Semaphore(CONCURRENT_THREADS)
40
noimage_cantload = None
41
noimage_loading = None
47
REMOTE_URL = remote_url
50
TPL_LOCAL_PATH = path.join("%s", PREFIX, "%d", "%d")
51
TPL_LOCAL_FILENAME = path.join("%s", "%%d%s%s" % (extsep, FILE_TYPE))
53
def __init__(self, id_string, tile, zoom, undersample = False, x = 0, y = 0, callback_draw = None, callback_load = None):
54
self.id_string = id_string
55
self.undersample = undersample
57
#self.download_tile = self.gui.ts.check_bounds(*tile)
58
self.download_tile = tile
60
self.download_zoom = zoom
61
self.display_zoom = zoom
63
self.download_zoom = zoom - 1
64
self.display_zoom = zoom
65
self.download_tile = (int(self.download_tile[0]/2), int(self.download_tile[1]/2))
67
self.callback_draw = callback_draw
68
self.callback_load = callback_load
70
self.my_noimage = None
76
self.local_path = self.TPL_LOCAL_PATH % (self.base_dir, self.download_zoom, self.download_tile[0])
77
self.local_filename = self.TPL_LOCAL_FILENAME % (self.local_path, self.download_tile[1])
78
self.remote_filename = self.REMOTE_URL % {'zoom': self.download_zoom, 'x' : self.download_tile[0], 'y' : self.download_tile[1]}
86
def create_recursive(dpath):
88
if not path.exists(dpath):
89
head, tail = path.split(dpath)
90
TileLoader.create_recursive(head)
94
# let others fail here.
100
if not path.isfile(self.local_filename):
101
self.create_recursive(self.local_path)
102
self.draw(self.get_no_image(self.noimage_loading))
103
answer = self.__download(self.remote_filename, self.local_filename)
105
# now the file hopefully exists
109
#gobject.idle_add(lambda: self.draw(self.pbuf))
110
elif answer == False:
111
#gobject.idle_add(lambda: self.draw(self.get_no_image(self.noimage_cantload)))
112
self.draw(self.get_no_image(self.noimage_cantload))
114
# Do nothing here, as the thread was told to stop
119
#gobject.idle_add(lambda: self.draw(self.pbuf))
123
def get_no_image(self, default):
124
return (default, None)
126
if self.my_noimage != None:
127
return self.my_noimage
128
size, tile = self.TILE_SIZE, self.tile
129
# we have no image available. so what do now?
130
# first, check if we've the "supertile" available (zoomed out)
131
supertile_zoom = self.download_zoom - 1
132
supertile_x = int(tile[0]/2)
133
supertile_y = int(tile[1]/2)
134
supertile_path = self.TPL_LOCAL_PATH % (self.base_dir, supertile_zoom, supertile_x)
135
supertile_name = self.TPL_LOCAL_FILENAME % (supertile_path, supertile_y)
136
#supertile_name = path.join(TileLoader.base_dir, self.PREFIX, str(supertile_zoom), str(supertile_x), "%d%s%s" % (supertile_y, extsep, self.FILE_TYPE))
137
if not self.undersample and path.exists(supertile_name):
138
off_x = (tile[0]/2.0 - supertile_x) * size
139
off_y = (tile[1]/2.0 - supertile_y) * size
140
#pbuf = gtk.gdk.pixbuf_new_from_file(supertile_name)
141
#dest = gtk.gdk.Pixbuf(pbuf.get_colorspace(), pbuf.get_has_alpha(), pbuf.get_bits_per_sample(), size, size)
142
#pbuf.scale(dest, 0, 0, 256, 256, -off_x*2, -off_y*2, 2, 2, gtk.gdk.INTERP_BILINEAR)
143
self.pbuf = (surface, (off_x, off_y))
144
self.my_noimage = surface
147
self.my_noimage = default
151
def load(self, tryno=0):
152
# load the pixbuf to memory
156
size, tile = self.TILE_SIZE, self.tile
158
# don't load the tile directly, but load the supertile instead
159
supertile_x = int(tile[0]/2)
160
supertile_y = int(tile[1]/2)
161
off_x = (tile[0]/2.0 - supertile_x) * size
162
off_y = (tile[1]/2.0 - supertile_y) * size
163
surface = self.callback_load(self.local_filename)
165
self.pbuf = (surface, (off_x, off_y))
167
surface = self.callback_load(self.local_filename)
168
self.pbuf = (surface, None)
172
return self.recover()
174
logger.exception("Exception while loading map tile: %s" % e)
175
self.pbuf = (self.noimage_cantload, None)
180
remove(self.local_filename)
70
# this may fail due to threading issues.
71
# too lazy to do proper locking here
72
# so just forget about the error
75
gobject.idle_add(lambda: self.draw(self.get_no_image()))
76
answer = self.download(self.remote_filename, self.local_filename)
77
# now the file hopefully exists
80
gobject.idle_add(lambda: self.draw(self.pbuf))
82
print "loading default"
83
gobject.idle_add(lambda: self.draw(self.get_no_image()))
88
self.__log("prep draw")
90
def get_no_image(self):
91
if self.my_noimage != None:
92
return self.my_noimage
93
size = self.gui.ts.tile_size()
94
# we have no image available. so what do now?
95
# first, check if we've the "supertile" available (zoomed out)
96
supertile_zoom = self.zoom - 1
97
supertile_x = int(self.tile[0]/2)
98
supertile_y = int(self.tile[1]/2)
99
supertile_name = os.path.join(self.base_dir, str(supertile_zoom), str(supertile_x), "%d.png" % supertile_y)
100
if os.path.exists(supertile_name):
101
print "Loading supertile for %d %d, which is %d %d" % (self.tile[0], self.tile[1], supertile_x, supertile_y)
102
# great! now find the right spot.
103
# the supertile is 'size' px wide and high.
104
off_x = (self.tile[0]/2.0 - supertile_x) * size
105
off_y = (self.tile[1]/2.0 - supertile_y) * size
107
pbuf = gtk.gdk.pixbuf_new_from_file(supertile_name)
108
#pbuf.scale(pbuf, 0, 0, size/2, size/2, off_x, off_y, 2, 2, gtk.gdk.INTERP_BILINEAR)
109
dest = gtk.gdk.Pixbuf(pbuf.get_colorspace(), pbuf.get_has_alpha(), pbuf.get_bits_per_sample(), size, size)
110
pbuf.scale(dest, 0, 0, 256, 256, -off_x*2, -off_y*2, 2, 2, gtk.gdk.INTERP_BILINEAR)
111
self.my_noimage = dest
114
self.my_noimage = TileLoader.noimage
115
return TileLoader.noimage
117
def load(self, tryno=0):
118
# load the pixbuf to memory
120
self.pbuf = gtk.gdk.pixbuf_new_from_file(self.local_filename)
121
if self.pbuf.get_width() < self.gui.ts.tile_size() or self.pbuf.get_height() < self.gui.ts.tile_size():
122
raise Exception("Image too small, probably corrupted file")
124
except Exception as e:
126
return self.recover()
129
self.pbuf = TileLoader.noimage
183
self.__download(self.remote_filename, self.local_filename)
186
def draw(self, pbuf):
188
return self.callback_draw(self.id_string, pbuf[0], self.x, self.y, pbuf[1])
192
def __download(self, remote, local):
193
if path.exists(local):
134
os.remove(self.local_filename)
137
self.download(self.remote_filename, self.local_filename)
140
def draw(self, pbuf):
142
#gc.set_function(gtk.gdk.COPY)
143
#gc.set_rgb_fg_color(self.COLOR_BG)
144
# to draw "night mode": INVERT
146
size = self.gui.ts.tile_size()
147
x = self.gui.map_center_x
148
y = self.gui.map_center_y
149
xi = int(self.gui.map_center_x)
150
yi = int(self.gui.map_center_y)
151
span_x = int(math.ceil(float(self.gui.map_width) / (size * 2.0)))
152
span_y = int(math.ceil(float(self.gui.map_height) / (size * 2.0)))
153
if self.tile[0] in xrange(xi - span_x, xi + span_x + 1, 1) and self.tile[1] in xrange(yi - span_y, yi + span_y + 1, 1) and self.zoom == self.gui.ts.zoom:
155
offset_x = int(self.gui.map_width / 2 - (x - int(x)) * size)
156
offset_y = int(self.gui.map_height / 2 -(y - int(y)) * size)
157
dx = (self.tile[0] - xi) * size + offset_x
158
dy = (self.tile[1] - yi) * size + offset_y
163
self.gui.pixmap.draw_pixbuf(gc, pbuf, 0, 0, dx, dy, size, size)
165
self.gui.pixmap.draw_pixbuf(gc, TileLoader.noimage, 0, 0, dx, dy, size, size)
167
self.gui.drawing_area.queue_draw_area(max(self.gui.draw_root_x + self.gui.draw_at_x + dx, 0), max(self.gui.draw_root_y + self.gui.draw_at_y + dy, 0), size, size)
173
def download(self, remote, local):
174
print "downloading", remote
176
self.__log("dl-start")
178
TileLoader.lock.acquire()
180
if remote in TileLoader.downloading:
181
print 'lädt schon: %s' % remote
183
if os.path.exists(local):
184
print 'ex schon: %s' % remote
186
TileLoader.downloading.append(remote)
188
TileLoader.lock.release()
190
TileLoader.semaphore.acquire()
193
if not self.zoom == self.gui.ts.zoom:
195
info = urllib.urlretrieve(remote, local)
197
if "text/html" in info[1]['Content-Type']:
198
print "File not found: %s" % remote
201
except Exception as e:
202
print "Download Error", e
206
TileLoader.semaphore.release()
207
TileLoader.downloading.remove(remote)
211
def __log(self, string):
213
# a = "%d " % self.num
214
# for i in xrange(self.num):
227
def set_zoom(self, zoom):
228
if zoom < 1 or zoom > self.max_zoom:
236
def deg2tilenum(self, lat_deg, lon_deg):
237
#lat_rad = lat_deg * math.pi / 180.0
238
lat_rad = math.radians(lat_deg)
240
xtile = int((lon_deg + 180) / 360 * n)
241
ytile = int((1.0 - math.log(math.tan(lat_rad) + (1.0 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
244
def deg2num(self, coord):
245
lat_rad = math.radians(coord.lat)
246
#lat_rad = (coord.lat * math.pi) / 180.0
248
xtile = (coord.lon + 180) / 360 * n
249
ytile = (1.0 - math.log(math.tan(lat_rad) + (1.0 / math.cos(lat_rad))) / math.pi) / 2.0 * n
252
def num2deg(self, xtile, ytile):
254
lon_deg = xtile / n * 360.0 - 180.0
255
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
256
lat_deg = lat_rad * 180.0 / math.pi
257
return geo.Coordinate(lat_deg, lon_deg)
198
with TileLoader.semaphore:
202
info = urlretrieve(remote, local)
204
if "text/html" in info[1]['Content-Type']:
208
print "Download Error", e
211
def download_tile_only(self):
212
if not path.isfile(self.local_filename):
213
self.create_recursive(self.local_path)
214
return self.__download(self.remote_filename, self.local_filename)