class TilesManager(object): def __init__(self, **kwargs): """ Manipulates tiles in general. Gives ability to list required tiles on a bounding box, download them, render them, extract them from other mbtiles... Keyword arguments: remote -- use remote tiles (default True) stylefile -- mapnik stylesheet file, only necessary if `remote` is `False` cache -- use a local cache to share tiles between runs (default True) tmp_dir -- temporary folder for gathering tiles (default DEFAULT_TMP_DIR) tiles_url -- remote URL to download tiles (default DEFAULT_TILES_URL) tile_size -- default tile size (default DEFAULT_TILE_SIZE) tiles_dir -- Local folder containing existing tiles, and where cached tiles will be stored (default DEFAULT_TILES_DIR) mbtiles_file -- A MBTiles providing tiles (overrides ``tiles_url``) wms_server -- A WMS server url wms_layers -- The list of layers to be requested wms_options -- WMS parameters to be requested (see ``landez.reader.WMSReader``) """ self.remote = kwargs.get('remote', True) self.stylefile = kwargs.get('stylefile') self.tmp_dir = kwargs.get('tmp_dir', DEFAULT_TMP_DIR) self.cache = kwargs.get('cache', True) self.tiles_dir = kwargs.get('tiles_dir', DEFAULT_TILES_DIR) self.tiles_url = kwargs.get('tiles_url', DEFAULT_TILES_URL) self.tiles_subdomains = kwargs.get('tiles_subdomains', DEFAULT_TILES_SUBDOMAINS) self.tile_size = kwargs.get('tile_size', DEFAULT_TILE_SIZE) self.mbtiles_file = kwargs.get('mbtiles_file') self.wms_server = kwargs.get('wms_server') self.wms_layers = kwargs.get('wms_layers', []) self.wms_options = kwargs.get('wms_options', {}) self.reader = None basename = '' if self.mbtiles_file: self.reader = MBTilesReader(self.mbtiles_file, self.tile_size) basename = os.path.basename(self.mbtiles_file) self.remote = False elif self.wms_server: assert self.wms_layers, _("Request at least one layer") self.reader = WMSReader(self.wms_server, self.wms_layers, self.tile_size, **self.wms_options) basename = '-'.join(self.wms_layers) self.remote = False elif self.stylefile: assert has_mapnik, _("Cannot render tiles without mapnik !") assert self.stylefile, _("A mapnik stylesheet is required") self.remote = False basename = os.path.basename(self.stylefile) else: url = urlparse(self.tiles_url) basename = url.netloc basename = re.sub(r'[^a-z^A-Z^0-9]+', '', basename) self.tmp_dir = os.path.join(self.tmp_dir, basename) self.tiles_dir = os.path.join(self.tiles_dir, basename) self.proj = GoogleProjection(self.tile_size) self._mapnik = None self._prj = None self._reader = None self._layers = [] # Number of tiles rendered/downloaded here self.rendered = 0 def tileslist(self, bbox, zoomlevels): """ Build the tiles list within the bottom-left/top-right bounding box (minx, miny, maxx, maxy) at the specified zoom levels. Return a list of tuples (z,x,y) """ if len(bbox) != 4 or len(zoomlevels) == 0: raise InvalidCoverageError(_("Wrong format of bounding box or zoom levels.")) xmin, ymin, xmax, ymax = bbox if abs(xmin) > 180 or abs(xmax) > 180 or \ abs(ymin) > 90 or abs(ymax) > 90: raise InvalidCoverageError(_("Some coordinates exceed [-180,+180], [-90, 90].")) if xmin >= xmax or ymin >= ymax: raise InvalidCoverageError(_("Bounding box format is (xmin, ymin, xmax, ymax)")) if max(zoomlevels) >= self.proj.maxlevel: self.proj = GoogleProjection(self.tile_size, zoomlevels) ll0 = (xmin, ymax) # left top ll1 = (xmax, ymin) # right bottom l = [] for z in zoomlevels: px0 = self.proj.fromLLtoPixel(ll0,z) px1 = self.proj.fromLLtoPixel(ll1,z) for x in range(int(px0[0]/self.tile_size), int(px1[0]/self.tile_size)+1): if (x < 0) or (x >= 2**z): continue for y in range(int(px0[1]/self.tile_size), int(px1[1]/self.tile_size)+1): if (y < 0) or (y >= 2**z): continue l.append((z, x, y)) return l def tile_file(self, (z, x, y)): """ Return folder (``z/x``) and name (``y.png``) for the specified tuple. """ tile_dir = os.path.join("%s" % z, "%s" % x) y_mercator = (2**z - 1) - y tile_name = "%s.png" % y_mercator return tile_dir, tile_name
class TilesManager(object): def __init__(self, **kwargs): """ Manipulates tiles in general. Gives ability to list required tiles on a bounding box, download them, render them, extract them from other mbtiles... Keyword arguments: remote -- use remote tiles (default True) stylefile -- mapnik stylesheet file, only necessary if `remote` is `False` cache -- use a local cache to share tiles between runs (default True) tmp_dir -- temporary folder for gathering tiles (default DEFAULT_TMP_DIR) tiles_url -- remote URL to download tiles (default DEFAULT_TILES_URL) tile_size -- default tile size (default DEFAULT_TILE_SIZE) tiles_dir -- Local folder containing existing tiles, and where cached tiles will be stored (default DEFAULT_TILES_DIR) mbtiles_file -- A MBTiles providing tiles (overrides ``tiles_url``) """ self.remote = kwargs.get('remote', True) self.stylefile = kwargs.get('stylefile') self.tmp_dir = kwargs.get('tmp_dir', DEFAULT_TMP_DIR) self.cache = kwargs.get('cache', True) self.tiles_dir = kwargs.get('tiles_dir', DEFAULT_TILES_DIR) self.tiles_url = kwargs.get('tiles_url', DEFAULT_TILES_URL) self.tile_size = kwargs.get('tile_size', DEFAULT_TILE_SIZE) self.mbtiles_file = kwargs.get('mbtiles_file') if self.mbtiles_file: self.remote = False if not self.remote and not self.mbtiles_file: assert has_mapnik, "Cannot render tiles without mapnik !" assert self.stylefile, "A mapnik stylesheet is required" self.proj = GoogleProjection(self.tile_size) self._mapnik = None self._prj = None # Number of tiles rendered/downloaded here self.rendered = 0 def tileslist(self, bbox, zoomlevels): """ Build the tiles list within the bottom-left/top-right bounding box (minx, miny, maxx, maxy) at the specified zoom levels. Return a list of tuples (z,x,y) """ if len(bbox) != 4 or len(zoomlevels) == 0: raise InvalidCoverageError() xmin, ymin, xmax, ymax = bbox if abs(xmin) > 180 or abs(xmax) > 180 or \ abs(ymin) > 90 or abs(ymax) > 90: raise InvalidCoverageError() if xmin >= xmax or ymin >= ymax: raise InvalidCoverageError() if max(zoomlevels) >= self.proj.maxlevel: self.proj = GoogleProjection(self.tile_size, zoomlevels) ll0 = (xmin, ymax) # left top ll1 = (xmax, ymin) # right bottom l = [] for z in zoomlevels: px0 = self.proj.fromLLtoPixel(ll0,z) px1 = self.proj.fromLLtoPixel(ll1,z) for x in range(int(px0[0]/self.tile_size), int(px1[0]/self.tile_size)+1): if (x < 0) or (x >= 2**z): continue for y in range(int(px0[1]/self.tile_size), int(px1[1]/self.tile_size)+1): if (y < 0) or (y >= 2**z): continue l.append((z, x, y)) return l def tile_file(self, (z, x, y)): """ Return folder (``z/x``) and name (``y.png``) for the specified tuple. """ tile_dir = os.path.join("%s" % z, "%s" % x) y_mercator = (2**z - 1) - y tile_name = "%s.png" % y_mercator return tile_dir, tile_name