1 | #!/usr/bin/python |
---|
2 | from math import pi,cos,sin,log,exp,atan |
---|
3 | from subprocess import call |
---|
4 | import sys, os |
---|
5 | from Queue import Queue |
---|
6 | import mapnik |
---|
7 | import threading |
---|
8 | |
---|
9 | DEG_TO_RAD = pi/180 |
---|
10 | RAD_TO_DEG = 180/pi |
---|
11 | |
---|
12 | # Default number of rendering threads to spawn, should be roughly equal to number of CPU cores available |
---|
13 | NUM_THREADS = 4 |
---|
14 | |
---|
15 | |
---|
16 | def minmax (a,b,c): |
---|
17 | a = max(a,b) |
---|
18 | a = min(a,c) |
---|
19 | return a |
---|
20 | |
---|
21 | class GoogleProjection: |
---|
22 | def __init__(self,levels=18): |
---|
23 | self.Bc = [] |
---|
24 | self.Cc = [] |
---|
25 | self.zc = [] |
---|
26 | self.Ac = [] |
---|
27 | c = 256 |
---|
28 | for d in range(0,levels): |
---|
29 | e = c/2; |
---|
30 | self.Bc.append(c/360.0) |
---|
31 | self.Cc.append(c/(2 * pi)) |
---|
32 | self.zc.append((e,e)) |
---|
33 | self.Ac.append(c) |
---|
34 | c *= 2 |
---|
35 | |
---|
36 | def fromLLtoPixel(self,ll,zoom): |
---|
37 | d = self.zc[zoom] |
---|
38 | e = round(d[0] + ll[0] * self.Bc[zoom]) |
---|
39 | f = minmax(sin(DEG_TO_RAD * ll[1]),-0.9999,0.9999) |
---|
40 | g = round(d[1] + 0.5*log((1+f)/(1-f))*-self.Cc[zoom]) |
---|
41 | return (e,g) |
---|
42 | |
---|
43 | def fromPixelToLL(self,px,zoom): |
---|
44 | e = self.zc[zoom] |
---|
45 | f = (px[0] - e[0])/self.Bc[zoom] |
---|
46 | g = (px[1] - e[1])/-self.Cc[zoom] |
---|
47 | h = RAD_TO_DEG * ( 2 * atan(exp(g)) - 0.5 * pi) |
---|
48 | return (f,h) |
---|
49 | |
---|
50 | |
---|
51 | |
---|
52 | class RenderThread: |
---|
53 | def __init__(self, tile_dir, mapfile, q, printLock, maxZoom): |
---|
54 | self.tile_dir = tile_dir |
---|
55 | self.q = q |
---|
56 | self.m = mapnik.Map(256, 256) |
---|
57 | self.printLock = printLock |
---|
58 | # Load style XML |
---|
59 | mapnik.load_map(self.m, mapfile, True) |
---|
60 | # Obtain <Map> projection |
---|
61 | self.prj = mapnik.Projection(self.m.srs) |
---|
62 | # Projects between tile pixel co-ordinates and LatLong (EPSG:4326) |
---|
63 | self.tileproj = GoogleProjection(maxZoom+1) |
---|
64 | |
---|
65 | |
---|
66 | def render_tile(self, tile_uri, x, y, z): |
---|
67 | # Calculate pixel positions of bottom-left & top-right |
---|
68 | p0 = (x * 256, (y + 1) * 256) |
---|
69 | p1 = ((x + 1) * 256, y * 256) |
---|
70 | |
---|
71 | # Convert to LatLong (EPSG:4326) |
---|
72 | l0 = self.tileproj.fromPixelToLL(p0, z); |
---|
73 | l1 = self.tileproj.fromPixelToLL(p1, z); |
---|
74 | |
---|
75 | # Convert to map projection (e.g. mercator co-ords EPSG:900913) |
---|
76 | c0 = self.prj.forward(mapnik.Coord(l0[0],l0[1])) |
---|
77 | c1 = self.prj.forward(mapnik.Coord(l1[0],l1[1])) |
---|
78 | |
---|
79 | # Bounding box for the tile |
---|
80 | bbox = mapnik.Envelope(c0.x,c0.y, c1.x,c1.y) |
---|
81 | render_size = 256 |
---|
82 | self.m.resize(render_size, render_size) |
---|
83 | self.m.zoom_to_box(bbox) |
---|
84 | self.m.buffer_size = 128 |
---|
85 | |
---|
86 | # Render image with default Agg renderer |
---|
87 | im = mapnik.Image(render_size, render_size) |
---|
88 | mapnik.render(self.m, im) |
---|
89 | im.save(tile_uri, 'png256') |
---|
90 | |
---|
91 | |
---|
92 | def loop(self): |
---|
93 | while True: |
---|
94 | #Fetch a tile from the queue and render it |
---|
95 | r = self.q.get() |
---|
96 | if (r == None): |
---|
97 | self.q.task_done() |
---|
98 | break |
---|
99 | else: |
---|
100 | (name, tile_uri, x, y, z) = r |
---|
101 | |
---|
102 | exists= "" |
---|
103 | if os.path.isfile(tile_uri): |
---|
104 | exists= "exists" |
---|
105 | else: |
---|
106 | self.render_tile(tile_uri, x, y, z) |
---|
107 | bytes=os.stat(tile_uri)[6] |
---|
108 | empty= '' |
---|
109 | if bytes == 103: |
---|
110 | empty = " Empty Tile " |
---|
111 | self.printLock.acquire() |
---|
112 | print name, ":", z, x, y, exists, empty |
---|
113 | self.printLock.release() |
---|
114 | self.q.task_done() |
---|
115 | |
---|
116 | |
---|
117 | |
---|
118 | def render_tiles(bbox, mapfile, tile_dir, minZoom=1,maxZoom=18, name="unknown", num_threads=NUM_THREADS): |
---|
119 | print "render_tiles(",bbox, mapfile, tile_dir, minZoom,maxZoom, name,")" |
---|
120 | |
---|
121 | # Launch rendering threads |
---|
122 | queue = Queue(32) |
---|
123 | printLock = threading.Lock() |
---|
124 | renderers = {} |
---|
125 | for i in range(num_threads): |
---|
126 | renderer = RenderThread(tile_dir, mapfile, queue, printLock, maxZoom) |
---|
127 | render_thread = threading.Thread(target=renderer.loop) |
---|
128 | render_thread.start() |
---|
129 | #print "Started render thread %s" % render_thread.getName() |
---|
130 | renderers[i] = render_thread |
---|
131 | |
---|
132 | if not os.path.isdir(tile_dir): |
---|
133 | os.mkdir(tile_dir) |
---|
134 | |
---|
135 | gprj = GoogleProjection(maxZoom+1) |
---|
136 | |
---|
137 | ll0 = (bbox[0],bbox[3]) |
---|
138 | ll1 = (bbox[2],bbox[1]) |
---|
139 | |
---|
140 | for z in range(minZoom,maxZoom + 1): |
---|
141 | px0 = gprj.fromLLtoPixel(ll0,z) |
---|
142 | px1 = gprj.fromLLtoPixel(ll1,z) |
---|
143 | |
---|
144 | # check if we have directories in place |
---|
145 | zoom = "%s" % z |
---|
146 | if not os.path.isdir(tile_dir + zoom): |
---|
147 | os.mkdir(tile_dir + zoom) |
---|
148 | for x in range(int(px0[0]/256.0),int(px1[0]/256.0)+1): |
---|
149 | # Validate x co-ordinate |
---|
150 | if (x < 0) or (x >= 2**z): |
---|
151 | continue |
---|
152 | # check if we have directories in place |
---|
153 | str_x = "%s" % x |
---|
154 | if not os.path.isdir(tile_dir + zoom + '/' + str_x): |
---|
155 | os.mkdir(tile_dir + zoom + '/' + str_x) |
---|
156 | for y in range(int(px0[1]/256.0),int(px1[1]/256.0)+1): |
---|
157 | # Validate x co-ordinate |
---|
158 | if (y < 0) or (y >= 2**z): |
---|
159 | continue |
---|
160 | str_y = "%s" % y |
---|
161 | tile_uri = tile_dir + zoom + '/' + str_x + '/' + str_y + '.png' |
---|
162 | # Submit tile to be rendered into the queue |
---|
163 | t = (name, tile_uri, x, y, z) |
---|
164 | queue.put(t) |
---|
165 | |
---|
166 | # Signal render threads to exit by sending empty request to queue |
---|
167 | for i in range(num_threads): |
---|
168 | queue.put(None) |
---|
169 | # wait for pending rendering jobs to complete |
---|
170 | queue.join() |
---|
171 | for i in range(num_threads): |
---|
172 | renderers[i].join() |
---|
173 | |
---|
174 | |
---|
175 | |
---|
176 | if __name__ == "__main__": |
---|
177 | home = os.environ['HOME'] |
---|
178 | try: |
---|
179 | mapfile = os.environ['MAPNIK_MAP_FILE'] |
---|
180 | except KeyError: |
---|
181 | mapfile = home + "/svn.openstreetmap.org/applications/rendering/mapnik/osm-local.xml" |
---|
182 | try: |
---|
183 | tile_dir = os.environ['MAPNIK_TILE_DIR'] |
---|
184 | except KeyError: |
---|
185 | tile_dir = home + "/osm/tiles/" |
---|
186 | |
---|
187 | if not tile_dir.endswith('/'): |
---|
188 | tile_dir = tile_dir + '/' |
---|
189 | |
---|
190 | #------------------------------------------------------------------------- |
---|
191 | # |
---|
192 | # Change the following for different bounding boxes and zoom levels |
---|
193 | # |
---|
194 | # Start with an overview |
---|
195 | # World |
---|
196 | bbox = (-180.0,-90.0, 180.0,90.0) |
---|
197 | |
---|
198 | render_tiles(bbox, mapfile, tile_dir, 0, 5, "World") |
---|
199 | |
---|
200 | minZoom = 10 |
---|
201 | maxZoom = 16 |
---|
202 | bbox = (-2, 50.0,1.0,52.0) |
---|
203 | render_tiles(bbox, mapfile, tile_dir, minZoom, maxZoom) |
---|
204 | |
---|
205 | # Muenchen |
---|
206 | bbox = (11.4,48.07, 11.7,48.22) |
---|
207 | render_tiles(bbox, mapfile, tile_dir, 1, 12 , "Muenchen") |
---|
208 | |
---|
209 | # Muenchen+ |
---|
210 | bbox = (11.3,48.01, 12.15,48.44) |
---|
211 | render_tiles(bbox, mapfile, tile_dir, 7, 12 , "Muenchen+") |
---|
212 | |
---|
213 | # Muenchen++ |
---|
214 | bbox = (10.92,47.7, 12.24,48.61) |
---|
215 | render_tiles(bbox, mapfile, tile_dir, 7, 12 , "Muenchen++") |
---|
216 | |
---|
217 | # Nuernberg |
---|
218 | bbox=(10.903198,49.560441,49.633534,11.038085) |
---|
219 | render_tiles(bbox, mapfile, tile_dir, 10, 16, "Nuernberg") |
---|
220 | |
---|
221 | # Karlsruhe |
---|
222 | bbox=(8.179113,48.933617,8.489252,49.081707) |
---|
223 | render_tiles(bbox, mapfile, tile_dir, 10, 16, "Karlsruhe") |
---|
224 | |
---|
225 | # Karlsruhe+ |
---|
226 | bbox = (8.3,48.95,8.5,49.05) |
---|
227 | render_tiles(bbox, mapfile, tile_dir, 1, 16, "Karlsruhe+") |
---|
228 | |
---|
229 | # Augsburg |
---|
230 | bbox = (8.3,48.95,8.5,49.05) |
---|
231 | render_tiles(bbox, mapfile, tile_dir, 1, 16, "Augsburg") |
---|
232 | |
---|
233 | # Augsburg+ |
---|
234 | bbox=(10.773251,48.369594,10.883834,48.438577) |
---|
235 | render_tiles(bbox, mapfile, tile_dir, 10, 14, "Augsburg+") |
---|
236 | |
---|
237 | # Europe+ |
---|
238 | bbox = (1.0,10.0, 20.6,50.0) |
---|
239 | render_tiles(bbox, mapfile, tile_dir, 1, 11 , "Europe+") |
---|
240 | |
---|