| 1 |
""" |
|---|
| 2 |
>>> m = Map(Microsoft.RoadProvider(), Core.Point(600, 600), Core.Coordinate(3165, 1313, 13), Core.Point(-144, -94)) |
|---|
| 3 |
>>> p = m.locationPoint(Geo.Location(37.804274, -122.262940)) |
|---|
| 4 |
>>> p |
|---|
| 5 |
(370.724, 342.549) |
|---|
| 6 |
>>> m.pointLocation(p) |
|---|
| 7 |
(37.804, -122.263) |
|---|
| 8 |
|
|---|
| 9 |
>>> c = Geo.Location(37.804274, -122.262940) |
|---|
| 10 |
>>> z = 12 |
|---|
| 11 |
>>> d = Core.Point(800, 600) |
|---|
| 12 |
>>> m = mapByCenterZoom(Microsoft.RoadProvider(), c, z, d) |
|---|
| 13 |
>>> m.dimensions |
|---|
| 14 |
(800.000, 600.000) |
|---|
| 15 |
>>> m.coordinate |
|---|
| 16 |
(1582.000, 656.000 @12.000) |
|---|
| 17 |
>>> m.offset |
|---|
| 18 |
(-235.000, -196.000) |
|---|
| 19 |
|
|---|
| 20 |
>>> sw = Geo.Location(36.893326, -123.533554) |
|---|
| 21 |
>>> ne = Geo.Location(38.864246, -121.208153) |
|---|
| 22 |
>>> d = Core.Point(800, 600) |
|---|
| 23 |
>>> m = mapByExtent(Microsoft.RoadProvider(), sw, ne, d) |
|---|
| 24 |
>>> m.dimensions |
|---|
| 25 |
(800.000, 600.000) |
|---|
| 26 |
>>> m.coordinate |
|---|
| 27 |
(98.000, 40.000 @8.000) |
|---|
| 28 |
>>> m.offset |
|---|
| 29 |
(-251.000, -218.000) |
|---|
| 30 |
|
|---|
| 31 |
>>> se = Geo.Location(36.893326, -121.208153) |
|---|
| 32 |
>>> nw = Geo.Location(38.864246, -123.533554) |
|---|
| 33 |
>>> d = Core.Point(1600, 1200) |
|---|
| 34 |
>>> m = mapByExtent(Microsoft.RoadProvider(), se, nw, d) |
|---|
| 35 |
>>> m.dimensions |
|---|
| 36 |
(1600.000, 1200.000) |
|---|
| 37 |
>>> m.coordinate |
|---|
| 38 |
(197.000, 81.000 @9.000) |
|---|
| 39 |
>>> m.offset |
|---|
| 40 |
(-246.000, -179.000) |
|---|
| 41 |
|
|---|
| 42 |
>>> sw = Geo.Location(36.893326, -123.533554) |
|---|
| 43 |
>>> ne = Geo.Location(38.864246, -121.208153) |
|---|
| 44 |
>>> z = 10 |
|---|
| 45 |
>>> m = mapByExtentZoom(Microsoft.RoadProvider(), sw, ne, z) |
|---|
| 46 |
>>> m.dimensions |
|---|
| 47 |
(1693.000, 1818.000) |
|---|
| 48 |
>>> m.coordinate |
|---|
| 49 |
(395.000, 163.000 @10.000) |
|---|
| 50 |
>>> m.offset |
|---|
| 51 |
(-236.000, -102.000) |
|---|
| 52 |
|
|---|
| 53 |
>>> se = Geo.Location(36.893326, -121.208153) |
|---|
| 54 |
>>> nw = Geo.Location(38.864246, -123.533554) |
|---|
| 55 |
>>> z = 9 |
|---|
| 56 |
>>> m = mapByExtentZoom(Microsoft.RoadProvider(), se, nw, z) |
|---|
| 57 |
>>> m.dimensions |
|---|
| 58 |
(846.000, 909.000) |
|---|
| 59 |
>>> m.coordinate |
|---|
| 60 |
(197.000, 81.000 @9.000) |
|---|
| 61 |
>>> m.offset |
|---|
| 62 |
(-246.000, -179.000) |
|---|
| 63 |
""" |
|---|
| 64 |
|
|---|
| 65 |
import sys, PIL.Image, urllib, httplib, urlparse, StringIO, math, thread, time |
|---|
| 66 |
|
|---|
| 67 |
import Tiles |
|---|
| 68 |
import Providers |
|---|
| 69 |
import Core |
|---|
| 70 |
import Geo |
|---|
| 71 |
import Google, Yahoo, Microsoft, BlueMarble, OpenStreetMap |
|---|
| 72 |
import time |
|---|
| 73 |
|
|---|
| 74 |
# a handy list of possible providers, which isn't |
|---|
| 75 |
# to say that you can't go writing your own. |
|---|
| 76 |
builtinProviders = { |
|---|
| 77 |
'OPENSTREETMAP': OpenStreetMap.Provider, |
|---|
| 78 |
'OPEN_STREET_MAP': OpenStreetMap.Provider, |
|---|
| 79 |
'BLUE_MARBLE': BlueMarble.Provider, |
|---|
| 80 |
'MICROSOFT_ROAD': Microsoft.RoadProvider, |
|---|
| 81 |
'MICROSOFT_AERIAL': Microsoft.AerialProvider, |
|---|
| 82 |
'MICROSOFT_HYBRID': Microsoft.HybridProvider, |
|---|
| 83 |
'GOOGLE_ROAD': Google.RoadProvider, |
|---|
| 84 |
'GOOGLE_AERIAL': Google.AerialProvider, |
|---|
| 85 |
'GOOGLE_HYBRID': Google.HybridProvider, |
|---|
| 86 |
'GOOGLE_TERRAIN': Google.TerrainProvider, |
|---|
| 87 |
'YAHOO_ROAD': Yahoo.RoadProvider, |
|---|
| 88 |
'YAHOO_AERIAL': Yahoo.AerialProvider, |
|---|
| 89 |
'YAHOO_HYBRID': Yahoo.HybridProvider |
|---|
| 90 |
} |
|---|
| 91 |
|
|---|
| 92 |
def mapByCenterZoom(provider, center, zoom, dimensions): |
|---|
| 93 |
""" Return map instance given a provider, center location, zoom value, and dimensions point. |
|---|
| 94 |
""" |
|---|
| 95 |
centerCoord = provider.locationCoordinate(center).zoomTo(zoom) |
|---|
| 96 |
mapCoord, mapOffset = calculateMapCenter(provider, centerCoord) |
|---|
| 97 |
|
|---|
| 98 |
return Map(provider, dimensions, mapCoord, mapOffset) |
|---|
| 99 |
|
|---|
| 100 |
def mapByExtent(provider, locationA, locationB, dimensions): |
|---|
| 101 |
""" Return map instance given a provider, two corner locations, and dimensions point. |
|---|
| 102 |
""" |
|---|
| 103 |
mapCoord, mapOffset = calculateMapExtent(provider, dimensions.x, dimensions.y, locationA, locationB) |
|---|
| 104 |
|
|---|
| 105 |
return Map(provider, dimensions, mapCoord, mapOffset) |
|---|
| 106 |
|
|---|
| 107 |
def mapByExtentZoom(provider, locationA, locationB, zoom): |
|---|
| 108 |
""" Return map instance given a provider, two corner locations, and zoom value. |
|---|
| 109 |
""" |
|---|
| 110 |
# a coordinate per corner |
|---|
| 111 |
coordA = provider.locationCoordinate(locationA).zoomTo(zoom) |
|---|
| 112 |
coordB = provider.locationCoordinate(locationB).zoomTo(zoom) |
|---|
| 113 |
|
|---|
| 114 |
# precise width and height in pixels |
|---|
| 115 |
width = abs(coordA.column - coordB.column) * provider.tileWidth() |
|---|
| 116 |
height = abs(coordA.row - coordB.row) * provider.tileHeight() |
|---|
| 117 |
|
|---|
| 118 |
# nearest pixel actually |
|---|
| 119 |
dimensions = Core.Point(int(width), int(height)) |
|---|
| 120 |
|
|---|
| 121 |
# projected center of the map |
|---|
| 122 |
centerCoord = Core.Coordinate((coordA.row + coordB.row) / 2, |
|---|
| 123 |
(coordA.column + coordB.column) / 2, |
|---|
| 124 |
zoom) |
|---|
| 125 |
|
|---|
| 126 |
mapCoord, mapOffset = calculateMapCenter(provider, centerCoord) |
|---|
| 127 |
|
|---|
| 128 |
return Map(provider, dimensions, mapCoord, mapOffset) |
|---|
| 129 |
|
|---|
| 130 |
def calculateMapCenter(provider, centerCoord): |
|---|
| 131 |
""" Based on a provider and center coordinate, returns the coordinate |
|---|
| 132 |
of an initial tile and its point placement, relative to the map center. |
|---|
| 133 |
""" |
|---|
| 134 |
# initial tile coordinate |
|---|
| 135 |
initTileCoord = centerCoord.container() |
|---|
| 136 |
|
|---|
| 137 |
# initial tile position, assuming centered tile well in grid |
|---|
| 138 |
initX = (initTileCoord.column - centerCoord.column) * provider.tileWidth() |
|---|
| 139 |
initY = (initTileCoord.row - centerCoord.row) * provider.tileHeight() |
|---|
| 140 |
initPoint = Core.Point(round(initX), round(initY)) |
|---|
| 141 |
|
|---|
| 142 |
return initTileCoord, initPoint |
|---|
| 143 |
|
|---|
| 144 |
def calculateMapExtent(provider, width, height, *args): |
|---|
| 145 |
""" Based on a provider, width & height values, and a list of locations, |
|---|
| 146 |
returns the coordinate of an initial tile and its point placement, |
|---|
| 147 |
relative to the map center. |
|---|
| 148 |
""" |
|---|
| 149 |
coordinates = map(provider.locationCoordinate, args) |
|---|
| 150 |
|
|---|
| 151 |
TL = Core.Coordinate(min([c.row for c in coordinates]), |
|---|
| 152 |
min([c.column for c in coordinates]), |
|---|
| 153 |
min([c.zoom for c in coordinates])) |
|---|
| 154 |
|
|---|
| 155 |
BR = Core.Coordinate(max([c.row for c in coordinates]), |
|---|
| 156 |
max([c.column for c in coordinates]), |
|---|
| 157 |
max([c.zoom for c in coordinates])) |
|---|
| 158 |
|
|---|
| 159 |
# multiplication factor between horizontal span and map width |
|---|
| 160 |
hFactor = (BR.column - TL.column) / (float(width) / provider.tileWidth()) |
|---|
| 161 |
|
|---|
| 162 |
# multiplication factor expressed as base-2 logarithm, for zoom difference |
|---|
| 163 |
hZoomDiff = math.log(hFactor) / math.log(2) |
|---|
| 164 |
|
|---|
| 165 |
# possible horizontal zoom to fit geographical extent in map width |
|---|
| 166 |
hPossibleZoom = TL.zoom - math.ceil(hZoomDiff) |
|---|
| 167 |
|
|---|
| 168 |
# multiplication factor between vertical span and map height |
|---|
| 169 |
vFactor = (BR.row - TL.row) / (float(height) / provider.tileHeight()) |
|---|
| 170 |
|
|---|
| 171 |
# multiplication factor expressed as base-2 logarithm, for zoom difference |
|---|
| 172 |
vZoomDiff = math.log(vFactor) / math.log(2) |
|---|
| 173 |
|
|---|
| 174 |
# possible vertical zoom to fit geographical extent in map height |
|---|
| 175 |
vPossibleZoom = TL.zoom - math.ceil(vZoomDiff) |
|---|
| 176 |
|
|---|
| 177 |
# initial zoom to fit extent vertically and horizontally |
|---|
| 178 |
initZoom = min(hPossibleZoom, vPossibleZoom) |
|---|
| 179 |
|
|---|
| 180 |
## additionally, make sure it's not outside the boundaries set by provider limits |
|---|
| 181 |
#initZoom = min(initZoom, provider.outerLimits()[1].zoom) |
|---|
| 182 |
#initZoom = max(initZoom, provider.outerLimits()[0].zoom) |
|---|
| 183 |
|
|---|
| 184 |
# coordinate of extent center |
|---|
| 185 |
centerRow = (TL.row + BR.row) / 2 |
|---|
| 186 |
centerColumn = (TL.column + BR.column) / 2 |
|---|
| 187 |
centerZoom = (TL.zoom + BR.zoom) / 2 |
|---|
| 188 |
centerCoord = Core.Coordinate(centerRow, centerColumn, centerZoom).zoomTo(initZoom) |
|---|
| 189 |
|
|---|
| 190 |
return calculateMapCenter(provider, centerCoord) |
|---|
| 191 |
|
|---|
| 192 |
class TileRequest: |
|---|
| 193 |
|
|---|
| 194 |
# how many times to retry a failing tile |
|---|
| 195 |
MAX_ATTEMPTS = 5 |
|---|
| 196 |
|
|---|
| 197 |
def __init__(self, provider, coord, offset): |
|---|
| 198 |
self.done = False |
|---|
| 199 |
self.provider = provider |
|---|
| 200 |
self.coord = coord |
|---|
| 201 |
self.offset = offset |
|---|
| 202 |
|
|---|
| 203 |
def loaded(self): |
|---|
| 204 |
return self.done |
|---|
| 205 |
|
|---|
| 206 |
def images(self): |
|---|
| 207 |
return self.imgs |
|---|
| 208 |
|
|---|
| 209 |
def load(self, lock, verbose, attempt=1): |
|---|
| 210 |
if self.done: |
|---|
| 211 |
# don't bother? |
|---|
| 212 |
return |
|---|
| 213 |
|
|---|
| 214 |
urls = self.provider.getTileUrls(self.coord) |
|---|
| 215 |
|
|---|
| 216 |
if verbose: |
|---|
| 217 |
print 'Requesting', urls, '- attempt no.', attempt, 'in thread', hex(thread.get_ident()) |
|---|
| 218 |
|
|---|
| 219 |
# this is the time-consuming part |
|---|
| 220 |
try: |
|---|
| 221 |
imgs = [] |
|---|
| 222 |
|
|---|
| 223 |
for (scheme, netloc, path, params, query, fragment) in map(urlparse.urlparse, urls): |
|---|
| 224 |
conn = httplib.HTTPConnection(netloc) |
|---|
| 225 |
conn.request('GET', path + ('?' + query).rstrip('?'), headers={'User-Agent': 'Modest Maps python branch (http://modestmaps.com)'}) |
|---|
| 226 |
response = conn.getresponse() |
|---|
| 227 |
|
|---|
| 228 |
if str(response.status).startswith('2'): |
|---|
| 229 |
imgs.append(PIL.Image.open(StringIO.StringIO(response.read())).convert('RGBA')) |
|---|
| 230 |
|
|---|
| 231 |
except: |
|---|
| 232 |
|
|---|
| 233 |
if verbose: |
|---|
| 234 |
print 'Failed', urls, '- attempt no.', attempt, 'in thread', hex(thread.get_ident()) |
|---|
| 235 |
|
|---|
| 236 |
if attempt < TileRequest.MAX_ATTEMPTS: |
|---|
| 237 |
time.sleep(1 * attempt) |
|---|
| 238 |
return self.load(lock, verbose, attempt+1) |
|---|
| 239 |
else: |
|---|
| 240 |
imgs = [None for url in urls] |
|---|
| 241 |
|
|---|
| 242 |
else: |
|---|
| 243 |
if verbose: |
|---|
| 244 |
print 'Received', urls, '- attempt no.', attempt, 'in thread', hex(thread.get_ident()) |
|---|
| 245 |
|
|---|
| 246 |
if lock.acquire(): |
|---|
| 247 |
self.imgs = imgs |
|---|
| 248 |
self.done = True |
|---|
| 249 |
lock.release() |
|---|
| 250 |
|
|---|
| 251 |
class TileQueue(list): |
|---|
| 252 |
""" List of TileRequest objects, that's sensitive to when they're loaded. |
|---|
| 253 |
""" |
|---|
| 254 |
|
|---|
| 255 |
def __getslice__(self, i, j): |
|---|
| 256 |
""" Return a TileQueue when a list slice is called-for. |
|---|
| 257 |
|
|---|
| 258 |
Python docs say that __getslice__ is deprecated, but its |
|---|
| 259 |
replacement __getitem__ doesn't seem to be doing anything. |
|---|
| 260 |
""" |
|---|
| 261 |
other = TileQueue() |
|---|
| 262 |
|
|---|
| 263 |
for t in range(i, j): |
|---|
| 264 |
if t < len(self): |
|---|
| 265 |
other.append(self[t]) |
|---|
| 266 |
|
|---|
| 267 |
return other |
|---|
| 268 |
|
|---|
| 269 |
def pending(self): |
|---|
| 270 |
""" True if any contained tile is still loading. |
|---|
| 271 |
""" |
|---|
| 272 |
remaining = [tile for tile in self if not tile.loaded()] |
|---|
| 273 |
return len(remaining) > 0 |
|---|
| 274 |
|
|---|
| 275 |
class Map: |
|---|
| 276 |
|
|---|
| 277 |
def __init__(self, provider, dimensions, coordinate, offset): |
|---|
| 278 |
""" Instance of a map intended for drawing to an image. |
|---|
| 279 |
|
|---|
| 280 |
provider |
|---|
| 281 |
Instance of IMapProvider |
|---|
| 282 |
|
|---|
| 283 |
dimensions |
|---|
| 284 |
Size of output image, instance of Point |
|---|
| 285 |
|
|---|
| 286 |
coordinate |
|---|
| 287 |
Base tile, instance of Coordinate |
|---|
| 288 |
|
|---|
| 289 |
offset |
|---|
| 290 |
Position of base tile relative to map center, instance of Point |
|---|
| 291 |
""" |
|---|
| 292 |
self.provider = provider |
|---|
| 293 |
self.dimensions = dimensions |
|---|
| 294 |
self.coordinate = coordinate |
|---|
| 295 |
self.offset = offset |
|---|
| 296 |
|
|---|
| 297 |
def __str__(self): |
|---|
| 298 |
return 'Map(%(provider)s, %(dimensions)s, %(coordinate)s, %(offset)s)' % self.__dict__ |
|---|
| 299 |
|
|---|
| 300 |
def locationPoint(self, location): |
|---|
| 301 |
""" Return an x, y point on the map image for a given geographical location. |
|---|
| 302 |
""" |
|---|
| 303 |
point = Core.Point(self.offset.x, self.offset.y) |
|---|
| 304 |
coord = self.provider.locationCoordinate(location).zoomTo(self.coordinate.zoom) |
|---|
| 305 |
|
|---|
| 306 |
# distance from the known coordinate offset |
|---|
| 307 |
point.x += self.provider.tileWidth() * (coord.column - self.coordinate.column) |
|---|
| 308 |
point.y += self.provider.tileHeight() * (coord.row - self.coordinate.row) |
|---|
| 309 |
|
|---|
| 310 |
# because of the center/corner business |
|---|
| 311 |
point.x += self.dimensions.x/2 |
|---|
| 312 |
point.y += self.dimensions.y/2 |
|---|
| 313 |
|
|---|
| 314 |
return point |
|---|
| 315 |
|
|---|
| 316 |
def pointLocation(self, point): |
|---|
| 317 |
""" Return a geographical location on the map image for a given x, y point. |
|---|
| 318 |
""" |
|---|
| 319 |
hizoomCoord = self.coordinate.zoomTo(Core.Coordinate.MAX_ZOOM) |
|---|
| 320 |
|
|---|
| 321 |
# because of the center/corner business |
|---|
| 322 |
point = Core.Point(point.x - self.dimensions.x/2, |
|---|
| 323 |
point.y - self.dimensions.y/2) |
|---|
| 324 |
|
|---|
| 325 |
# distance in tile widths from reference tile to point |
|---|
| 326 |
xTiles = (point.x - self.offset.x) / self.provider.tileWidth(); |
|---|
| 327 |
yTiles = (point.y - self.offset.y) / self.provider.tileHeight(); |
|---|
| 328 |
|
|---|
| 329 |
# distance in rows & columns at maximum zoom |
|---|
| 330 |
xDistance = xTiles * math.pow(2, (Core.Coordinate.MAX_ZOOM - self.coordinate.zoom)); |
|---|
| 331 |
yDistance = yTiles * math.pow(2, (Core.Coordinate.MAX_ZOOM - self.coordinate.zoom)); |
|---|
| 332 |
|
|---|
| 333 |
# new point coordinate reflecting that distance |
|---|
| 334 |
coord = Core.Coordinate(round(hizoomCoord.row + yDistance), |
|---|
| 335 |
round(hizoomCoord.column + xDistance), |
|---|
| 336 |
hizoomCoord.zoom) |
|---|
| 337 |
|
|---|
| 338 |
coord = coord.zoomTo(self.coordinate.zoom) |
|---|
| 339 |
|
|---|
| 340 |
location = self.provider.coordinateLocation(coord) |
|---|
| 341 |
|
|---|
| 342 |
return location |
|---|
| 343 |
|
|---|
| 344 |
# |
|---|
| 345 |
|
|---|
| 346 |
def draw_bbox(self, bbox, zoom=16, verbose=False) : |
|---|
| 347 |
|
|---|
| 348 |
sw = Geo.Location(bbox[0], bbox[1]) |
|---|
| 349 |
ne = Geo.Location(bbox[2], bbox[3]) |
|---|
| 350 |
nw = Geo.Location(ne.lat, sw.lon) |
|---|
| 351 |
se = Geo.Location(sw.lat, ne.lon) |
|---|
| 352 |
|
|---|
| 353 |
TL = self.provider.locationCoordinate(nw).zoomTo(zoom) |
|---|
| 354 |
|
|---|
| 355 |
# |
|---|
| 356 |
|
|---|
| 357 |
tiles = TileQueue() |
|---|
| 358 |
|
|---|
| 359 |
cur_lon = sw.lon |
|---|
| 360 |
cur_lat = ne.lat |
|---|
| 361 |
max_lon = ne.lon |
|---|
| 362 |
max_lat = sw.lat |
|---|
| 363 |
|
|---|
| 364 |
x_off = 0 |
|---|
| 365 |
y_off = 0 |
|---|
| 366 |
tile_x = 0 |
|---|
| 367 |
tile_y = 0 |
|---|
| 368 |
|
|---|
| 369 |
tileCoord = TL.copy() |
|---|
| 370 |
|
|---|
| 371 |
while cur_lon < max_lon : |
|---|
| 372 |
|
|---|
| 373 |
y_off = 0 |
|---|
| 374 |
tile_y = 0 |
|---|
| 375 |
|
|---|
| 376 |
while cur_lat > max_lat : |
|---|
| 377 |
|
|---|
| 378 |
tiles.append(TileRequest(self.provider, tileCoord, Core.Point(x_off, y_off))) |
|---|
| 379 |
y_off += self.provider.tileHeight() |
|---|
| 380 |
|
|---|
| 381 |
tileCoord = tileCoord.down() |
|---|
| 382 |
loc = self.provider.coordinateLocation(tileCoord) |
|---|
| 383 |
cur_lat = loc.lat |
|---|
| 384 |
|
|---|
| 385 |
tile_y += 1 |
|---|
| 386 |
|
|---|
| 387 |
x_off += self.provider.tileWidth() |
|---|
| 388 |
cur_lat = ne.lat |
|---|
| 389 |
|
|---|
| 390 |
tile_x += 1 |
|---|
| 391 |
tileCoord = TL.copy().right(tile_x) |
|---|
| 392 |
|
|---|
| 393 |
loc = self.provider.coordinateLocation(tileCoord) |
|---|
| 394 |
cur_lon = loc.lon |
|---|
| 395 |
|
|---|
| 396 |
width = int(self.provider.tileWidth() * tile_x) |
|---|
| 397 |
height = int(self.provider.tileHeight() * tile_y) |
|---|
| 398 |
|
|---|
| 399 |
# Quick, look over there! |
|---|
| 400 |
|
|---|
| 401 |
coord, offset = calculateMapExtent(self.provider, |
|---|
| 402 |
width, height, |
|---|
| 403 |
Geo.Location(bbox[0], bbox[1]), |
|---|
| 404 |
Geo.Location(bbox[2], bbox[3])) |
|---|
| 405 |
|
|---|
| 406 |
self.offset = offset |
|---|
| 407 |
self.coordinates = coord |
|---|
| 408 |
self.dimensions = Core.Point(width, height) |
|---|
| 409 |
|
|---|
| 410 |
return self.draw() |
|---|
| 411 |
|
|---|
| 412 |
# |
|---|
| 413 |
|
|---|
| 414 |
def draw(self, verbose=False): |
|---|
| 415 |
""" Draw map out to a PIL.Image and return it. |
|---|
| 416 |
""" |
|---|
| 417 |
coord = self.coordinate.copy() |
|---|
| 418 |
corner = Core.Point(int(self.offset.x + self.dimensions.x/2), int(self.offset.y + self.dimensions.y/2)) |
|---|
| 419 |
|
|---|
| 420 |
while corner.x > 0: |
|---|
| 421 |
corner.x -= self.provider.tileWidth() |
|---|
| 422 |
coord = coord.left() |
|---|
| 423 |
|
|---|
| 424 |
while corner.y > 0: |
|---|
| 425 |
corner.y -= self.provider.tileHeight() |
|---|
| 426 |
coord = coord.up() |
|---|
| 427 |
|
|---|
| 428 |
tiles = TileQueue() |
|---|
| 429 |
|
|---|
| 430 |
rowCoord = coord.copy() |
|---|
| 431 |
for y in range(corner.y, self.dimensions.y, self.provider.tileHeight()): |
|---|
| 432 |
tileCoord = rowCoord.copy() |
|---|
| 433 |
for x in range(corner.x, self.dimensions.x, self.provider.tileWidth()): |
|---|
| 434 |
tiles.append(TileRequest(self.provider, tileCoord, Core.Point(x, y))) |
|---|
| 435 |
tileCoord = tileCoord.right() |
|---|
| 436 |
rowCoord = rowCoord.down() |
|---|
| 437 |
|
|---|
| 438 |
return self.render_tiles(tiles, self.dimensions.x, self.dimensions.y, verbose) |
|---|
| 439 |
|
|---|
| 440 |
# |
|---|
| 441 |
|
|---|
| 442 |
def render_tiles(self, tiles, img_width, img_height, verbose=False): |
|---|
| 443 |
|
|---|
| 444 |
lock = thread.allocate_lock() |
|---|
| 445 |
threads = 32 |
|---|
| 446 |
|
|---|
| 447 |
for off in range(0, len(tiles), threads): |
|---|
| 448 |
pool = tiles[off:(off + threads)] |
|---|
| 449 |
|
|---|
| 450 |
for tile in pool: |
|---|
| 451 |
# request all needed images |
|---|
| 452 |
thread.start_new_thread(tile.load, (lock, verbose)) |
|---|
| 453 |
|
|---|
| 454 |
# if it takes any longer than 20 sec overhead + 10 sec per tile, give up |
|---|
| 455 |
due = time.time() + 20 + len(pool) * 10 |
|---|
| 456 |
|
|---|
| 457 |
while time.time() < due and pool.pending(): |
|---|
| 458 |
# hang around until they are loaded or we run out of time... |
|---|
| 459 |
time.sleep(1) |
|---|
| 460 |
|
|---|
| 461 |
mapImg = PIL.Image.new('RGB', (img_width, img_height)) |
|---|
| 462 |
|
|---|
| 463 |
for tile in tiles: |
|---|
| 464 |
try: |
|---|
| 465 |
for img in tile.images(): |
|---|
| 466 |
mapImg.paste(img, (tile.offset.x, tile.offset.y), img) |
|---|
| 467 |
except: |
|---|
| 468 |
# something failed to paste, so we ignore it |
|---|
| 469 |
pass |
|---|
| 470 |
|
|---|
| 471 |
return mapImg |
|---|
| 472 |
|
|---|
| 473 |
if __name__ == '__main__': |
|---|
| 474 |
import doctest |
|---|
| 475 |
doctest.testmod() |
|---|