source: subversion/applications/routing/pyroute/tiles.py @ 5730

Last change on this file since 5730 was 5730, checked in by ojw, 12 years ago

more zoom levels
+ disable the "threaded downloads" (causing problems when the program
exits and the thread can't be killed)

File size: 5.3 KB
Line 
1#!/usr/bin/python
2#-----------------------------------------------------------------------------
3# Tile image handler (download, cache, and display tiles)
4#
5# Usage:
6#   (library code for pyroute GUI, not for direct use)
7#-----------------------------------------------------------------------------
8# Copyright 2007, Oliver White
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program.  If not, see <http://www.gnu.org/licenses/>.
22#-----------------------------------------------------------------------------
23from base import pyrouteModule
24from tilenames import *
25import urllib
26import os
27import cairo
28from threading import Thread
29
30def downloadTile(x,y,z,layer,filename):
31    url = tileURL(x,y,z)
32    urllib.urlretrieve(url, filename)
33
34class tileLoader(Thread):
35  """Downloads images in a separate thread"""
36  def __init__(self, x,y,z,layer,filename):
37    """Download a tile image"""
38    self.x = x
39    self.y = y
40    self.z = z
41    self.layer = layer
42    self.finished = 0
43    self.filename = filename
44    Thread.__init__(self)
45   
46  def run(self):
47    downloadTile( \
48      self.x,
49      self.y,
50      self.z,
51      self.layer,
52      self.filename)
53     
54    self.finished = 1
55
56class tileHandler(pyrouteModule):
57  """Loads and displays map tiles"""
58  def __init__(self, modules):
59    pyrouteModule.__init__(self, modules)
60    self.images = {}
61    self.threads = {}
62    self.downloadInThread = False
63   
64  def __del__(self):
65    print "Shutting-down tiles"
66    for name,thread in self.threads.items():
67      pass
68  def imageName(self,x,y,z,layer):
69    """Get a unique name for a tile image
70    (suitable for use as part of filenames, dictionary keys, etc)"""
71    return("%s_%d_%d_%d" % (layer,z,x,y))
72 
73  def loadImage(self,x,y,z, layer):
74    """Check that an image is loaded, and try to load it if not"""
75   
76    # First: is the image already in memory?
77    name = self.imageName(x,y,z,layer)
78    if name in self.images.keys():
79      return
80   
81    # Second, is it already in the process of being downloaded?
82    if name in self.threads.keys():
83      if(not self.threads[name].finished):
84        return
85   
86    # Third, is it in the disk cache?  (including ones recently-downloaded)
87    filename = "cache/%s.png" % name
88    if os.path.exists(filename):
89      self.images[name]  = cairo.ImageSurface.create_from_png(filename)
90      return
91   
92    # Image not found anywhere - resort to downloading it
93    print "Downloading %s" % (name)
94    if(self.downloadInThread):
95      self.threads[name] = tileLoader(x,y,z,layer,filename)
96      self.threads[name].start()
97    else:
98      downloadTile(x,y,z,layer,filename)
99   
100  def drawImage(self,cr, tile, bbox):
101    """Draw a tile image"""
102    name = self.imageName(tile[0],tile[1],tile[2], tile[3])
103    # If it's not in memory, then stop here
104    if not name in self.images.keys():
105      return
106    # Move the cairo projection onto the area where we want to draw the image
107    cr.save()
108    cr.translate(bbox[0],bbox[1])
109    cr.scale((bbox[2] - bbox[0]) / 256.0, (bbox[3] - bbox[1]) / 256.0)
110   
111    # Display the image
112    cr.set_source_surface(self.images[name],0,0)
113    cr.paint()
114   
115    # Return the cairo projection to what it was
116    cr.restore()
117   
118  def zoomFromScale(self,scale):
119    """Given a 'scale' (such as it is) from the projection module,
120    decide what zoom-level of map tiles to display"""
121   
122    # TODO: This isn't really logical or optimised at all, it's just
123    # a basic guess that needs to be checked at some point
124    if(scale > 5):
125      return(5)
126    if(scale > 0.55):
127      return(7)
128    if(scale > 0.046):
129      return(10)
130    if(scale > 0.0085):
131      return(13)
132    if(scale > 0.0026):
133      return(15)
134    return(17)
135   
136  def tileZoom(self):
137    """Decide (based on global data) what zoom-level of map tiles to display"""
138    return(self.zoomFromScale(self.m['projection'].scale))
139 
140  def draw(self, cr):
141    """Draw all the map tiles that are in view"""
142    proj = self.m['projection']
143    layer = "tah"
144   
145    # Pick a zoom level
146    z = self.tileZoom()
147   
148    # Find out what tiles are in view at that zoom level
149    view_x1,view_y1 = latlon2xy(proj.N, proj.W, z)
150    view_x2,view_y2 = latlon2xy(proj.S, proj.E, z)
151   
152    # Loop through all tiles
153    for x in range(int(floor(view_x1)), int(ceil(view_x2))):
154      for y in range(int(floor(view_y1)), int(ceil(view_y2))):
155       
156        # Find the edges of the tile as lat/long
157        S,W,N,E = tileEdges(x,y,z) 
158       
159        # Convert those edges to screen coordinates
160        x1,y1 = proj.ll2xy(N,W)
161        x2,y2 = proj.ll2xy(S,E)
162       
163        # Check that the image is available in the memory cache
164        # and start downloading it if it's not
165        self.loadImage(x,y,z,layer)
166       
167        # Draw the image
168        self.drawImage(cr,(x,y,z,layer),(x1,y1,x2,y2))
Note: See TracBrowser for help on using the repository browser.