from collections.abc import Iterable from shapely import geometry, ops, prepared from .base import Tile def cover_geometry(tilescheme, geom, zooms): """Covers the provided geometry with tiles. Args: tilescheme: The tile scheme to use. This needs to implement the public protocal of the schemes defined within tiletanic. geom: The geometry we would like to cover. This should be a shapely geometry. zooms: The zoom levels of the tiles to cover geom with. If you provide an iterable of zoom levels, you'll get the biggest tiles available that cover the geometry at those levels. Yields: An iterator of Tile objects ((x, y, z) named tuples) that cover the input geometry. """ # Only shapely geometries allowed. if not isinstance(geom, geometry.base.BaseGeometry): raise ValueError("Input 'geom' is not a known shapely geometry type") if geom.is_empty: return zooms = zooms if isinstance(zooms, Iterable) else [zooms] # Generate the covering. prep_geom = prepared.prep(geom) if isinstance(geom, (geometry.Polygon, geometry.MultiPolygon)): for tile in _cover_polygonal(tilescheme, Tile(0, 0, 0), prep_geom, geom, zooms): yield tile else: for tile in _cover_geometry(tilescheme, Tile(0, 0, 0), prep_geom, geom, zooms): yield tile def _cover_geometry(tilescheme, curr_tile, prep_geom, geom, zooms): """Covers geometries with tiles by recursion. Args: tilescheme: The tile scheme to use. This needs to implement the public protocal of the schemes defined within tiletanic. curr_tile: The current tile in the recursion scheme. prep_geom: The prepared version of the geometry we would like to cover. geom: The shapely geometry we would like to cover. zooms: The zoom levels to recurse to. Yields: An iterator of Tile objects ((x, y, z) tuples) that cover the input geometry. """ if prep_geom.intersects(geometry.box(*tilescheme.bbox(curr_tile))): if curr_tile.z in zooms: yield curr_tile else: for tile in (tile for child_tile in tilescheme.children(curr_tile) for tile in _cover_geometry(tilescheme, child_tile, prep_geom, geom, zooms)): yield tile def _cover_polygonal(tilescheme, curr_tile, prep_geom, geom, zooms): """Covers polygonal geometries with tiles by recursion. This is method is slightly more efficient than _cover_geometry in that we can check if a tile is completely covered by a geometry and if so, skip directly to the max zoom level to fetch the covered tiles. Args: tilescheme: The tile scheme to use. This needs to implement the public protocal of the schemes defined within tiletanic. curr_tile: The current tile in the recursion scheme. prep_geom: The prepared version of the polygonal geometry we would like to cover. geom: The shapely polygonal geometry we would like to cover. zooms: The zoom levels to recurse to. Yields: An iterator of Tile objects ((x, y, z) tuples) that cover the input polygonal geometry. """ tile_geom = geometry.box(*tilescheme.bbox(curr_tile)) if prep_geom.intersects(tile_geom): if curr_tile.z == max(zooms): yield curr_tile elif prep_geom.contains(tile_geom): if curr_tile.z in zooms: yield curr_tile else: for tile in (tile for child_tile in tilescheme.children(curr_tile) for tile in _containing_tiles(tilescheme, child_tile, zooms)): yield tile else: tiles = [] coverage = 0 for tile in (tile for child_tile in tilescheme.children(curr_tile) for tile in _cover_polygonal(tilescheme, child_tile, prep_geom, geom, zooms)): if curr_tile.z in zooms: tiles.append(tile) coverage += 4 ** (max(zooms) - tile.z) else: yield tile if curr_tile.z in zooms and coverage == 4 ** (max(zooms) - curr_tile.z): yield curr_tile else: for tile in tiles: yield tile def _containing_tiles(tilescheme, curr_tile, zooms): """Given a Tile, returns the tiles that compose that tile at the zoom level provided. Args: tilescheme: The tile scheme to use. This needs to implement the public protocal of the schemes defined within tiletanic. curr_tile: The current tile in the recursion scheme. zooms: The zoom levels to recurse to. Yields: An iterator of Tile objects ((x, y, z) tuples) that compose the input tile. """ if curr_tile.z in zooms: yield curr_tile else: for tile in (tile for child_tile in tilescheme.children(curr_tile) for tile in _containing_tiles(tilescheme, child_tile, zooms)): yield tile