From 1000736b9d35b056bf59c7c123d50a2769e11435 Mon Sep 17 00:00:00 2001 From: Michal Migurski Date: Fri, 5 Mar 2010 23:39:52 +0000 Subject: [PATCH 1/3] Use of "__package__" was causing problems in python 2.6 --- wscompose/__init__.py | 1 - wscompose/validate.py | 1 - 2 files changed, 2 deletions(-) diff --git a/wscompose/__init__.py b/wscompose/__init__.py index 221890b..cac0bf7 100644 --- a/wscompose/__init__.py +++ b/wscompose/__init__.py @@ -1,6 +1,5 @@ # -*-python-*- -__package__ = "wscompose/__init__.py" __version__ = "1.1" __author__ = "Aaron Straup Cope" __url__ = "http://www.aaronland.info/python/wscompose" diff --git a/wscompose/validate.py b/wscompose/validate.py index 6a35a99..21e1239 100644 --- a/wscompose/validate.py +++ b/wscompose/validate.py @@ -1,6 +1,5 @@ # -*-python-*- -__package__ = "wscompose/validate.py" __version__ = "1.1" __author__ = "Aaron Straup Cope" __url__ = "http://www.aaronland.info/python/wscompose" From bfefeae67ef6857fe12eedce8039bebb0d0fa486 Mon Sep 17 00:00:00 2001 From: Michal Migurski Date: Thu, 8 Apr 2010 17:27:13 -0700 Subject: [PATCH 2/3] Added download URL to setup.py --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 260da80..34d3d15 100644 --- a/setup.py +++ b/setup.py @@ -7,4 +7,7 @@ description='Modest Maps python port', author='Michal Migurski', url='http://modestmaps.com', - packages=['ModestMaps']) + requires=['PIL'], + packages=['ModestMaps'], + download_url='http://modestmaps.com/download/ModestMaps-Py-1.0-dist.tar.gz', + license='BSD') From 0159e8a6d0c3ccaa5c7c6fd88fd264f1664fe007 Mon Sep 17 00:00:00 2001 From: straup Date: Fri, 7 Jan 2011 14:27:58 -0800 Subject: [PATCH 3/3] add support for non-portable tilestache URLs; allow HTTPS URLs for templated providers (S3); remove white space on empty lines --- ModestMaps/Providers.py | 24 ++++--- ModestMaps/__init__.py | 136 ++++++++++++++++++++-------------------- ws-pinwin.py | 2 +- 3 files changed, 87 insertions(+), 75 deletions(-) diff --git a/ModestMaps/Providers.py b/ModestMaps/Providers.py index cf1db44..7ccbb4c 100644 --- a/ModestMaps/Providers.py +++ b/ModestMaps/Providers.py @@ -12,7 +12,7 @@ class IMapProvider: def __init__(self): raise NotImplementedError("Abstract method not implemented by subclass.") - + def getTileUrls(self, coordinate): raise NotImplementedError("Abstract method not implemented by subclass.") @@ -39,24 +39,25 @@ def sourceCoordinate(self, coordinate): while wrappedColumn < 0: wrappedColumn += pow(2, coordinate.zoom) - + return Coordinate(coordinate.row, wrappedColumn, coordinate.zoom) class TemplatedMercatorProvider(IMapProvider): """ Convert URI templates into tile URLs, using a tileUrlTemplate identical to: http://code.google.com/apis/maps/documentation/overlays.html#Custom_Map_Types """ - def __init__(self, template): + def __init__(self, template, tilestache_cached=False): # the spherical mercator world tile covers (-π, -π) to (π, π) t = deriveTransformation(-pi, pi, 0, 0, pi, pi, 1, 0, -pi, -pi, 0, 1) self.projection = MercatorProjection(0, t) - + self.templates = [] - + self.tilestache_cached = tilestache_cached + while template: - match = re.match(r'^(http://\S+?)(,http://\S+)?$', template) + match = re.match(r'^(https?://\S+?)(,http://\S+)?$', template) first = match.group(1) - + if match: self.templates.append(first) template = template[len(first):].lstrip(',') @@ -70,5 +71,14 @@ def tileHeight(self): return 256 def getTileUrls(self, coordinate): + x, y, z = str(int(coordinate.column)), str(int(coordinate.row)), str(int(coordinate.zoom)) + + if self.tilestache_cached: + x = "%0d6" % int(x) + y = "%0d6" % int(y) + + x = "%s/%s" % (x[:3], x[3:]) + y = "%s/%s" % (y[:3], y[3:]) + return [t.replace('{X}', x).replace('{Y}', y).replace('{Z}', z) for t in self.templates] diff --git a/ModestMaps/__init__.py b/ModestMaps/__init__.py index 689dac4..ce43103 100644 --- a/ModestMaps/__init__.py +++ b/ModestMaps/__init__.py @@ -72,8 +72,10 @@ import time try: - import PIL.Image + import Image except ImportError: + import PIL.Image as Image + # you need PIL to do any actual drawing, but # maybe that's not what you're using MMaps for? pass @@ -119,7 +121,7 @@ def mapByExtent(provider, locationA, locationB, dimensions): mapCoord, mapOffset = calculateMapExtent(provider, dimensions.x, dimensions.y, locationA, locationB) return Map(provider, dimensions, mapCoord, mapOffset) - + def mapByExtentZoom(provider, locationA, locationB, zoom): """ Return map instance given a provider, two corner locations, and zoom value. """ @@ -130,15 +132,15 @@ def mapByExtentZoom(provider, locationA, locationB, zoom): # precise width and height in pixels width = abs(coordA.column - coordB.column) * provider.tileWidth() height = abs(coordA.row - coordB.row) * provider.tileHeight() - + # nearest pixel actually dimensions = Core.Point(int(width), int(height)) - + # projected center of the map centerCoord = Core.Coordinate((coordA.row + coordB.row) / 2, (coordA.column + coordB.column) / 2, zoom) - + mapCoord, mapOffset = calculateMapCenter(provider, centerCoord) return Map(provider, dimensions, mapCoord, mapOffset) @@ -154,7 +156,7 @@ def calculateMapCenter(provider, centerCoord): initX = (initTileCoord.column - centerCoord.column) * provider.tileWidth() initY = (initTileCoord.row - centerCoord.row) * provider.tileHeight() initPoint = Core.Point(round(initX), round(initY)) - + return initTileCoord, initPoint def calculateMapExtent(provider, width, height, *args): @@ -163,7 +165,7 @@ def calculateMapExtent(provider, width, height, *args): relative to the map center. """ coordinates = map(provider.locationCoordinate, args) - + TL = Core.Coordinate(min([c.row for c in coordinates]), min([c.column for c in coordinates]), min([c.zoom for c in coordinates])) @@ -171,25 +173,25 @@ def calculateMapExtent(provider, width, height, *args): BR = Core.Coordinate(max([c.row for c in coordinates]), max([c.column for c in coordinates]), max([c.zoom for c in coordinates])) - + # multiplication factor between horizontal span and map width hFactor = (BR.column - TL.column) / (float(width) / provider.tileWidth()) # multiplication factor expressed as base-2 logarithm, for zoom difference hZoomDiff = math.log(hFactor) / math.log(2) - + # possible horizontal zoom to fit geographical extent in map width hPossibleZoom = TL.zoom - math.ceil(hZoomDiff) - + # multiplication factor between vertical span and map height vFactor = (BR.row - TL.row) / (float(height) / provider.tileHeight()) - + # multiplication factor expressed as base-2 logarithm, for zoom difference vZoomDiff = math.log(vFactor) / math.log(2) - + # possible vertical zoom to fit geographical extent in map height vPossibleZoom = TL.zoom - math.ceil(vZoomDiff) - + # initial zoom to fit extent vertically and horizontally initZoom = min(hPossibleZoom, vPossibleZoom) @@ -202,11 +204,11 @@ def calculateMapExtent(provider, width, height, *args): centerColumn = (TL.column + BR.column) / 2 centerZoom = (TL.zoom + BR.zoom) / 2 centerCoord = Core.Coordinate(centerRow, centerColumn, centerZoom).zoomTo(initZoom) - + return calculateMapCenter(provider, centerCoord) - + class TileRequest: - + # how many times to retry a failing tile MAX_ATTEMPTS = 5 @@ -215,37 +217,37 @@ def __init__(self, provider, coord, offset): self.provider = provider self.coord = coord self.offset = offset - + def loaded(self): return self.done - + def images(self): return self.imgs - + def load(self, lock, verbose, attempt=1): if self.done: # don't bother? return urls = self.provider.getTileUrls(self.coord) - + if verbose: print 'Requesting', urls, '- attempt no.', attempt, 'in thread', hex(thread.get_ident()) # this is the time-consuming part try: imgs = [] - + for (scheme, netloc, path, params, query, fragment) in map(urlparse.urlparse, urls): - conn = httplib.HTTPConnection(netloc) + conn = httplib.HTTPSConnection(netloc) conn.request('GET', path + ('?' + query).rstrip('?'), headers={'User-Agent': 'Modest Maps python branch (http://modestmaps.com)'}) response = conn.getresponse() - + if str(response.status).startswith('2'): - imgs.append(PIL.Image.open(StringIO.StringIO(response.read())).convert('RGBA')) + imgs.append(Image.open(StringIO.StringIO(response.read())).convert('RGBA')) except: - + if verbose: print 'Failed', urls, '- attempt no.', attempt, 'in thread', hex(thread.get_ident()) @@ -270,12 +272,12 @@ class TileQueue(list): def __getslice__(self, i, j): """ Return a TileQueue when a list slice is called-for. - + Python docs say that __getslice__ is deprecated, but its replacement __getitem__ doesn't seem to be doing anything. """ other = TileQueue() - + for t in range(i, j): if t < len(self): other.append(self[t]) @@ -292,16 +294,16 @@ class Map: def __init__(self, provider, dimensions, coordinate, offset): """ Instance of a map intended for drawing to an image. - + provider Instance of IMapProvider - + dimensions Size of output image, instance of Point - + coordinate Base tile, instance of Coordinate - + offset Position of base tile relative to map center, instance of Point """ @@ -309,7 +311,7 @@ def __init__(self, provider, dimensions, coordinate, offset): self.dimensions = dimensions self.coordinate = coordinate self.offset = offset - + def __str__(self): return 'Map(%(provider)s, %(dimensions)s, %(coordinate)s, %(offset)s)' % self.__dict__ @@ -318,54 +320,54 @@ def locationPoint(self, location): """ point = Core.Point(self.offset.x, self.offset.y) coord = self.provider.locationCoordinate(location).zoomTo(self.coordinate.zoom) - + # distance from the known coordinate offset point.x += self.provider.tileWidth() * (coord.column - self.coordinate.column) point.y += self.provider.tileHeight() * (coord.row - self.coordinate.row) - + # because of the center/corner business point.x += self.dimensions.x/2 point.y += self.dimensions.y/2 - + return point - + def pointLocation(self, point): """ Return a geographical location on the map image for a given x, y point. """ hizoomCoord = self.coordinate.zoomTo(Core.Coordinate.MAX_ZOOM) - + # because of the center/corner business point = Core.Point(point.x - self.dimensions.x/2, point.y - self.dimensions.y/2) - + # distance in tile widths from reference tile to point xTiles = (point.x - self.offset.x) / self.provider.tileWidth(); yTiles = (point.y - self.offset.y) / self.provider.tileHeight(); - + # distance in rows & columns at maximum zoom xDistance = xTiles * math.pow(2, (Core.Coordinate.MAX_ZOOM - self.coordinate.zoom)); yDistance = yTiles * math.pow(2, (Core.Coordinate.MAX_ZOOM - self.coordinate.zoom)); - + # new point coordinate reflecting that distance coord = Core.Coordinate(round(hizoomCoord.row + yDistance), round(hizoomCoord.column + xDistance), hizoomCoord.zoom) coord = coord.zoomTo(self.coordinate.zoom) - + location = self.provider.coordinateLocation(coord) - + return location # - + def draw_bbox(self, bbox, zoom=16, verbose=False) : sw = Geo.Location(bbox[0], bbox[1]) ne = Geo.Location(bbox[2], bbox[3]) nw = Geo.Location(ne.lat, sw.lon) se = Geo.Location(sw.lat, ne.lon) - + TL = self.provider.locationCoordinate(nw).zoomTo(zoom) # @@ -373,36 +375,36 @@ def draw_bbox(self, bbox, zoom=16, verbose=False) : tiles = TileQueue() cur_lon = sw.lon - cur_lat = ne.lat + cur_lat = ne.lat max_lon = ne.lon max_lat = sw.lat - + x_off = 0 y_off = 0 tile_x = 0 tile_y = 0 - + tileCoord = TL.copy() while cur_lon < max_lon : y_off = 0 tile_y = 0 - + while cur_lat > max_lat : - + tiles.append(TileRequest(self.provider, tileCoord, Core.Point(x_off, y_off))) y_off += self.provider.tileHeight() - + tileCoord = tileCoord.down() loc = self.provider.coordinateLocation(tileCoord) cur_lat = loc.lat tile_y += 1 - - x_off += self.provider.tileWidth() + + x_off += self.provider.tileWidth() cur_lat = ne.lat - + tile_x += 1 tileCoord = TL.copy().right(tile_x) @@ -424,11 +426,11 @@ def draw_bbox(self, bbox, zoom=16, verbose=False) : self.dimensions = Core.Point(width, height) return self.draw() - + # - + def draw(self, verbose=False): - """ Draw map out to a PIL.Image and return it. + """ Draw map out to a Image and return it. """ coord = self.coordinate.copy() corner = Core.Point(int(self.offset.x + self.dimensions.x/2), int(self.offset.y + self.dimensions.y/2)) @@ -436,13 +438,13 @@ def draw(self, verbose=False): while corner.x > 0: corner.x -= self.provider.tileWidth() coord = coord.left() - + while corner.y > 0: corner.y -= self.provider.tileHeight() coord = coord.up() - + tiles = TileQueue() - + rowCoord = coord.copy() for y in range(corner.y, self.dimensions.y, self.provider.tileHeight()): tileCoord = rowCoord.copy() @@ -454,28 +456,28 @@ def draw(self, verbose=False): return self.render_tiles(tiles, self.dimensions.x, self.dimensions.y, verbose) # - + def render_tiles(self, tiles, img_width, img_height, verbose=False): - + lock = thread.allocate_lock() threads = 32 - + for off in range(0, len(tiles), threads): pool = tiles[off:(off + threads)] - + for tile in pool: # request all needed images thread.start_new_thread(tile.load, (lock, verbose)) - + # if it takes any longer than 20 sec overhead + 10 sec per tile, give up due = time.time() + 20 + len(pool) * 10 - + while time.time() < due and pool.pending(): # hang around until they are loaded or we run out of time... time.sleep(1) - mapImg = PIL.Image.new('RGB', (img_width, img_height)) - + mapImg = Image.new('RGB', (img_width, img_height)) + for tile in tiles: try: for img in tile.images(): diff --git a/ws-pinwin.py b/ws-pinwin.py index 0c780fc..7f71f0d 100644 --- a/ws-pinwin.py +++ b/ws-pinwin.py @@ -6,7 +6,7 @@ parser = optparse.OptionParser() parser.add_option("-p", "--port", dest="port", help="port number that the ws-pinwin HTTP server will listen on", default=9999) - + (opts, args) = parser.parse_args() app = wscompose.server(wscompose.pinwin.handler, int(opts.port))