def test_cover_geometry_multipoint2(tiler, mpt): """A MultiPoint geometry.""" tiles = [tile for tile in cover_geometry(tiler, mpt, 12)] assert len(tiles) == 9 assert set(tiles) == {(973, 1203, 12), (974, 1203, 12), (975, 1203, 12), (973, 1204, 12), (973, 1205, 12), (974, 1204, 12), (975, 1204, 12), (974, 1205, 12), (975, 1205, 12)}
def test_cover_geometry_poly_w_hole2(tiler, poly_w_hole): """A Polygon geometry with a hole in it.""" tiles = [tile for tile in cover_geometry(tiler, poly_w_hole, 9)] assert len(tiles) == 77 assert set(tiles) == set([(297, 82, 9), (301, 87, 9), (294, 87, 9), (299, 88, 9), (300, 85, 9), (292, 83, 9), (296, 83, 9), (298, 89, 9), (295, 82, 9), (290, 86, 9), (291, 87, 9), (297, 88, 9), (292, 87, 9), (298, 86, 9), (298, 84, 9), (294, 84, 9), (294, 88, 9), (299, 89, 9), (292, 85, 9), (300, 86, 9), (294, 82, 9), (290, 85, 9), (298, 82, 9), (295, 84, 9), (296, 87, 9), (293, 84, 9), (299, 85, 9), (291, 85, 9), (299, 86, 9), (296, 85, 9), (297, 85, 9), (296, 89, 9), (293, 89, 9), (292, 86, 9), (293, 87, 9), (291, 88, 9), (298, 88, 9), (298, 87, 9), (295, 87, 9), (296, 88, 9), (293, 83, 9), (301, 86, 9), (291, 86, 9), (297, 86, 9), (297, 89, 9), (292, 88, 9), (294, 86, 9), (294, 85, 9), (292, 82, 9), (300, 87, 9), (295, 89, 9), (290, 87, 9), (296, 82, 9), (298, 85, 9), (297, 83, 9), (291, 83, 9), (295, 83, 9), (300, 88, 9), (293, 86, 9), (299, 83, 9), (299, 84, 9), (297, 87, 9), (294, 83, 9), (297, 84, 9), (298, 83, 9), (293, 82, 9), (294, 89, 9), (296, 84, 9), (290, 84, 9), (293, 88, 9), (290, 83, 9), (295, 86, 9), (293, 85, 9), (295, 88, 9), (292, 84, 9), (291, 84, 9), (299, 87, 9)])
def test_cover_geometry_poly_w_hole1(tiler, poly_w_hole): """A Polygon geometry with a hole in it.""" tiles = [tile for tile in cover_geometry(tiler, poly_w_hole, 7)] assert len(tiles) == 11 assert set(tiles) == set([ (72, 22, 7), (74, 21, 7), (75, 22, 7), (73, 20, 7), (74, 22, 7), (73, 22, 7), (74, 20, 7), (73, 21, 7), (75, 21, 7), (72, 21, 7), (72, 20, 7) ])
def post(self, model_id, prediction_id): if CONFIG.EnvironmentConfig.ENVIRONMENT != "aws": return err(501, "stack must be in 'aws' mode to use this endpoint"), 501 payload = request.data tiler = tileschemes.WebMercator() try: prediction = PredictionService.get_prediction_by_id(prediction_id) poly = shape(geojson.loads(payload)) project = partial( pyproj.transform, pyproj.Proj(init='epsg:4326'), pyproj.Proj(init='epsg:3857') ) poly = transform(project, poly) tiles = tilecover.cover_geometry(tiler, poly, prediction.tile_zoom) queue_name = "{stack}-models-{model}-prediction-{prediction}-queue".format( stack=CONFIG.EnvironmentConfig.STACK, model=model_id, prediction=prediction_id ) queue = boto3.resource('sqs').get_queue_by_name( QueueName=queue_name ) cache = [] for tile in tiles: if len(cache) < 10: cache.append({ "Id": str(tile.z) + "-" + str(tile.x) + "-" + str(tile.y), "MessageBody": json.dumps({ "x": tile.x, "y": tile.y, "z": tile.z }) }) else: queue.send_messages( Entries=cache ) cache = [] return {}, 200 except Exception as e: error_msg = f'Predction Tiler Error: {str(e)}' current_app.logger.error(error_msg) return err(500, error_msg), 500
def test_cover_geometry_empty_geoms(tiler): """Empty geometries should return empty iterators.""" assert not cover_geometry(tiler, geometry.Point(), 0) == True assert not cover_geometry(tiler, geometry.MultiPoint(), 0) == True assert not cover_geometry(tiler, geometry.LineString(), 0) == True assert not cover_geometry(tiler, geometry.MultiLineString(), 0) == True assert not cover_geometry(tiler, geometry.Polygon(), 0) == True assert not cover_geometry(tiler, geometry.MultiPolygon(), 0) == True assert not cover_geometry(tiler, geometry.GeometryCollection(), 0) == True
def test_cover_geometry_linestring2(tiler, ls): """A LineString geometry.""" tiles = [tile for tile in cover_geometry(tiler, ls, 11)] assert len(tiles) == 30 assert set(tiles) == {(322, 768, 11), (322, 769, 11), (322, 770, 11), (323, 768, 11), (323, 769, 11), (323, 770, 11), (323, 771, 11), (323, 772, 11), (324, 767, 11), (324, 768, 11), (324, 769, 11), (324, 770, 11), (324, 771, 11), (325, 768, 11), (325, 769, 11), (325, 770, 11), (325, 771, 11), (325, 772, 11), (326, 767, 11), (326, 768, 11), (326, 769, 11), (326, 770, 11), (326, 771, 11), (326, 772, 11), (327, 768, 11), (327, 769, 11), (327, 770, 11), (327, 771, 11), (328, 768, 11), (328, 769, 11)}
def materialize_tif(project_name, version, aoi_geojson, output_filename='dynamic.tif'): gdal.SetConfigOption('GDAL_DISABLE_READDIR_ON_OPEN', 'TRUE') gdal.SetConfigOption('VSI_CACHE', 'TRUE') # gdal.SetConfigOption('CPL_CURL_VERBOSE', 'YES') # see the http calls gdal.SetConfigOption('CPL_VSIL_CURL_ALLOWED_EXTENSIONS', 'TIF') # a box in australia: #aoi_geojson = '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[153.42390060424805,-28.00989892967722],[153.43647480010986,-28.00989892967722],[153.43647480010986,-27.999933788434422],[153.42390060424805,-27.999933788434422],[153.42390060424805,-28.00989892967722]]]}}]}' aoi = ops.unary_union([ geometry.shape(f['geometry']) for f in json.loads(aoi_geojson)['features'] ]) #project_name = 'DYNAMIC-Australia-Q32017/1' bucket_name, prefix = 'flame-projects', project_name + '/' + version + '/raster_tiles/' s3 = boto3.resource('s3') tiler = tileschemes.DGTiling() paths = [] for key in (prefix + tiler.quadkey(tile) + '.tif' for tile in tilecover.cover_geometry(tiler, aoi, 12)): # Check if tif exists. print key try: s3.Object(bucket_name=bucket_name, key=key).load() except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == "404": continue else: raise paths.append('/vsis3/{}/{}'.format(bucket_name, key)) if len(paths) > 4: raise Exception('Too big of an AOI requested.') if not paths: raise Exception('No data found to materialize.') # Build an inmem vrt and translate. try: ds = gdal.BuildVRT('/vsimem/combo.vrt', paths, outputBounds=aoi.bounds) ds_out = gdal.Translate(output_filename, ds) finally: ds_out = None ds = None
def tile_generator(self, polygon, zoom=14): wgs84 = pyproj.CRS('EPSG:4326') webMarcator = pyproj.CRS('EPSG:3857') project = pyproj.Transformer.from_crs(wgs84, webMarcator, always_xy=True).transform polygon = transform(project, polygon) tiler = tileschemes.WebMercator() for t in tilecover.cover_geometry(tiler, polygon, zoom): # Keep tiles between Noth of Iceland and South of Americas if t.y > 4085 and t.y < 11200: yield [t.z, t.x, t.y]
def test_cover_geometry_multilinestring2(tiler, mls): """A MultiLineString geometry.""" tiles = [tile for tile in cover_geometry(tiler, mls, 12)] assert len(tiles) == 47 assert set(tiles) == {(2036, 1420, 12), (2036, 1421, 12), (2036, 1422, 12), (2036, 1423, 12), (2036, 1424, 12), (2037, 1421, 12), (2037, 1422, 12), (2038, 1420, 12), (2038, 1421, 12), (2038, 1422, 12), (2038, 1423, 12), (2038, 1424, 12), (2039, 1420, 12), (2039, 1421, 12), (2039, 1422, 12), (2039, 1423, 12), (2039, 1424, 12), (2040, 1420, 12), (2040, 1422, 12), (2040, 1424, 12), (2041, 1420, 12), (2041, 1422, 12), (2041, 1424, 12), (2042, 1420, 12), (2042, 1421, 12), (2042, 1422, 12), (2042, 1423, 12), (2042, 1424, 12), (2043, 1420, 12), (2044, 1420, 12), (2044, 1421, 12), (2044, 1422, 12), (2044, 1423, 12), (2044, 1424, 12), (2045, 1420, 12), (2046, 1420, 12), (2046, 1421, 12), (2046, 1422, 12), (2046, 1423, 12), (2047, 1420, 12), (2047, 1423, 12), (2047, 1424, 12), (2048, 1420, 12), (2048, 1421, 12), (2048, 1422, 12), (2048, 1423, 12), (2048, 1424, 12)}
def tiles_over(coordinates, altitude=0): """ Finds all the tiles for an area """ quads = [] x_web_m, y_web_m = to_web_mercator(*coordinates) zoom = zoom_from_altitude(altitude) print zoom tiler = WebMercator() center_tile = tiler.tile(x_web_m, y_web_m, zoom) bounding_box = tiler.bbox(center_tile) tiles = cover_geometry(tiler, geometry.box(*bounding_box), zoom+2) for row in to_grid(tiles): quads.append([]) for cell in row: quads[-1].append(tiler.quadkey(cell)) print '\n'.join(['|'.join([str(q) for q in quads])]) return quads
def test_cover_geometry_multipoly2(tiler, mpoly): """A MultiPolygon geometry.""" tiles = [tile for tile in cover_geometry(tiler, mpoly, 10)] assert len(tiles) == 44 assert set(tiles) == set([(23, 329, 10), (56, 318, 10), (70, 310, 10), (64, 315, 10), (67, 315, 10), (68, 310, 10), (63, 316, 10), (62, 316, 10), (71, 311, 10), (65, 314, 10), (68, 311, 10), (57, 318, 10), (70, 313, 10), (62, 317, 10), (4, 336, 10), (67, 314, 10), (64, 316, 10), (69, 310, 10), (66, 314, 10), (69, 313, 10), (68, 312, 10), (17, 330, 10), (63, 317, 10), (69, 312, 10), (58, 319, 10), (71, 312, 10), (68, 313, 10), (70, 311, 10), (69, 309, 10), (51, 321, 10), (70, 312, 10), (56, 317, 10), (61, 317, 10), (65, 315, 10), (65, 316, 10), (43, 323, 10), (68, 309, 10), (58, 318, 10), (34, 327, 10), (68, 314, 10), (66, 315, 10), (69, 311, 10), (68, 315, 10), (66, 316, 10)])
def test_cover_geometry_linestring1(tiler, ls): """A LineString geometry.""" tiles = [tile for tile in cover_geometry(tiler, ls, 4)] assert len(tiles) == 2 assert set(tiles) == {(2, 5, 4), (2, 6, 4)}
def test_cover_donut_webmercator(wmtiler, donut): tiles = [tile for tile in cover_geometry(wmtiler, donut, 16)] assert len(tiles) == 310 assert set(tiles) == set([(18899, 23458, 16), (18896, 23451, 16), (18888, 23457, 16), (18898, 23457, 16), (18890, 23442, 16), (18894, 23449, 16), (18888, 23454, 16), (18900, 23457, 16), (18898, 23458, 16), (18890, 23447, 16), (18894, 23450, 16), (18895, 23459, 16), (18891, 23460, 16), (18896, 23450, 16), (18900, 23462, 16), (18898, 23463, 16), (18898, 23449, 16), (18894, 23447, 16), (18895, 23462, 16), (18891, 23459, 16), (18892, 23459, 16), (18898, 23464, 16), (18893, 23442, 16), (18898, 23450, 16), (18887, 23453, 16), (18896, 23446, 16), (18892, 23456, 16), (18893, 23445, 16), (18898, 23455, 16), (18887, 23456, 16), (18896, 23466, 16), (18896, 23443, 16), (18905, 23457, 16), (18892, 23461, 16), (18888, 23456, 16), (18893, 23448, 16), (18896, 23458, 16), (18897, 23460, 16), (18897, 23446, 16), (18891, 23447, 16), (18897, 23463, 16), (18893, 23454, 16), (18894, 23460, 16), (18891, 23442, 16), (18887, 23455, 16), (18897, 23458, 16), (18897, 23452, 16), (18901, 23449, 16), (18907, 23456, 16), (18894, 23457, 16), (18903, 23452, 16), (18895, 23450, 16), (18892, 23441, 16), (18897, 23455, 16), (18901, 23452, 16), (18889, 23457, 16), (18885, 23454, 16), (18890, 23457, 16), (18902, 23460, 16), (18891, 23452, 16), (18895, 23449, 16), (18897, 23464, 16), (18892, 23446, 16), (18897, 23450, 16), (18901, 23455, 16), (18889, 23452, 16), (18901, 23457, 16), (18890, 23458, 16), (18902, 23457, 16), (18891, 23451, 16), (18889, 23455, 16), (18901, 23460, 16), (18886, 23456, 16), (18902, 23458, 16), (18899, 23454, 16), (18895, 23443, 16), (18892, 23448, 16), (18889, 23450, 16), (18890, 23446, 16), (18902, 23453, 16), (18890, 23448, 16), (18886, 23455, 16), (18899, 23453, 16), (18895, 23446, 16), (18892, 23453, 16), (18889, 23445, 16), (18893, 23458, 16), (18902, 23454, 16), (18890, 23453, 16), (18899, 23448, 16), (18903, 23453, 16), (18899, 23462, 16), (18900, 23450, 16), (18893, 23461, 16), (18902, 23451, 16), (18890, 23454, 16), (18899, 23447, 16), (18895, 23464, 16), (18899, 23461, 16), (18900, 23455, 16), (18888, 23458, 16), (18884, 23453, 16), (18898, 23454, 16), (18893, 23464, 16), (18890, 23443, 16), (18894, 23454, 16), (18899, 23456, 16), (18900, 23452, 16), (18888, 23455, 16), (18900, 23458, 16), (18898, 23459, 16), (18898, 23445, 16), (18890, 23444, 16), (18895, 23458, 16), (18888, 23452, 16), (18898, 23460, 16), (18898, 23446, 16), (18894, 23444, 16), (18895, 23457, 16), (18891, 23458, 16), (18893, 23455, 16), (18893, 23441, 16), (18898, 23451, 16), (18894, 23441, 16), (18895, 23460, 16), (18891, 23457, 16), (18896, 23447, 16), (18892, 23457, 16), (18904, 23458, 16), (18893, 23444, 16), (18898, 23452, 16), (18904, 23456, 16), (18902, 23450, 16), (18896, 23462, 16), (18896, 23444, 16), (18905, 23456, 16), (18892, 23462, 16), (18893, 23447, 16), (18896, 23459, 16), (18903, 23460, 16), (18897, 23451, 16), (18897, 23445, 16), (18894, 23464, 16), (18903, 23458, 16), (18896, 23456, 16), (18891, 23446, 16), (18897, 23462, 16), (18893, 23453, 16), (18906, 23455, 16), (18894, 23461, 16), (18891, 23445, 16), (18887, 23454, 16), (18897, 23457, 16), (18896, 23448, 16), (18894, 23462, 16), (18903, 23455, 16), (18892, 23452, 16), (18892, 23442, 16), (18897, 23454, 16), (18901, 23451, 16), (18889, 23456, 16), (18885, 23453, 16), (18894, 23459, 16), (18891, 23455, 16), (18895, 23448, 16), (18904, 23459, 16), (18892, 23447, 16), (18897, 23449, 16), (18901, 23454, 16), (18901, 23456, 16), (18890, 23459, 16), (18891, 23450, 16), (18895, 23455, 16), (18892, 23444, 16), (18903, 23456, 16), (18889, 23454, 16), (18901, 23459, 16), (18890, 23460, 16), (18902, 23459, 16), (18899, 23457, 16), (18895, 23442, 16), (18905, 23455, 16), (18889, 23449, 16), (18890, 23449, 16), (18899, 23452, 16), (18892, 23454, 16), (18903, 23457, 16), (18889, 23444, 16), (18893, 23457, 16), (18902, 23455, 16), (18890, 23450, 16), (18899, 23451, 16), (18895, 23444, 16), (18900, 23451, 16), (18896, 23461, 16), (18889, 23447, 16), (18893, 23460, 16), (18890, 23455, 16), (18904, 23452, 16), (18899, 23446, 16), (18899, 23460, 16), (18900, 23448, 16), (18884, 23454, 16), (18893, 23463, 16), (18894, 23455, 16), (18903, 23459, 16), (18899, 23459, 16), (18900, 23453, 16), (18896, 23460, 16), (18900, 23459, 16), (18898, 23456, 16), (18890, 23445, 16), (18894, 23448, 16), (18891, 23462, 16), (18904, 23453, 16), (18888, 23453, 16), (18900, 23456, 16), (18898, 23461, 16), (18906, 23457, 16), (18898, 23447, 16), (18894, 23445, 16), (18895, 23456, 16), (18891, 23461, 16), (18900, 23447, 16), (18900, 23461, 16), (18898, 23462, 16), (18898, 23448, 16), (18894, 23446, 16), (18904, 23457, 16), (18895, 23463, 16), (18891, 23456, 16), (18895, 23445, 16), (18892, 23458, 16), (18893, 23443, 16), (18898, 23453, 16), (18894, 23443, 16), (18896, 23463, 16), (18896, 23445, 16), (18892, 23463, 16), (18893, 23446, 16), (18894, 23458, 16), (18887, 23457, 16), (18905, 23458, 16), (18892, 23460, 16), (18897, 23444, 16), (18893, 23449, 16), (18894, 23465, 16), (18896, 23457, 16), (18896, 23455, 16), (18897, 23461, 16), (18897, 23447, 16), (18904, 23455, 16), (18896, 23464, 16), (18900, 23454, 16), (18903, 23451, 16), (18891, 23444, 16), (18896, 23452, 16), (18897, 23456, 16), (18894, 23463, 16), (18903, 23454, 16), (18891, 23443, 16), (18896, 23449, 16), (18897, 23459, 16), (18892, 23443, 16), (18897, 23453, 16), (18901, 23450, 16), (18894, 23456, 16), (18895, 23461, 16), (18894, 23442, 16), (18891, 23454, 16), (18895, 23451, 16), (18897, 23448, 16), (18901, 23453, 16), (18889, 23458, 16), (18885, 23455, 16), (18890, 23456, 16), (18891, 23453, 16), (18897, 23465, 16), (18892, 23445, 16), (18889, 23453, 16), (18901, 23458, 16), (18902, 23456, 16), (18891, 23448, 16), (18905, 23454, 16), (18889, 23448, 16), (18901, 23461, 16), (18886, 23453, 16), (18899, 23455, 16), (18892, 23455, 16), (18889, 23451, 16), (18893, 23456, 16), (18902, 23452, 16), (18890, 23451, 16), (18886, 23454, 16), (18899, 23450, 16), (18895, 23447, 16), (18900, 23460, 16), (18889, 23446, 16), (18893, 23459, 16), (18906, 23456, 16), (18890, 23452, 16), (18899, 23449, 16), (18895, 23466, 16), (18899, 23463, 16), (18900, 23449, 16), (18904, 23454, 16), (18893, 23462, 16), (18889, 23459, 16), (18896, 23465, 16), (18895, 23465, 16)])
def test_cover_geometry_point1(tiler, pt): """A Point geometry.""" tiles = [tile for tile in cover_geometry(tiler, pt, 4)] assert len(tiles) == 1 assert set(tiles) == {(3, 4, 4)}
def test_cover_geometry_multipoint1(tiler, mpt): """A MultiPoint geometry.""" tiles = [tile for tile in cover_geometry(tiler, mpt, 4)] assert len(tiles) == 1
def test_cover_geometry_point2(tiler, pt): """A Point geometry.""" tiles = [tile for tile in cover_geometry(tiler, pt, 12)] assert len(tiles) == 4 assert set(tiles) == {(973, 1204, 12), (973, 1205, 12), (974, 1204, 12), (974, 1205, 12)}
def test_cover_geometry_multilinestring1(tiler, mls): """A MultiLineString geometry.""" tiles = [tile for tile in cover_geometry(tiler, mls, 8)] assert len(tiles) == 4 assert set(tiles) == {(127, 88, 8), (127, 89, 8), (128, 88, 8), (128, 89, 8)}
def test_cover_geometry_nonshapely_geom(tiler): """Only accept shapely geometries.""" with pytest.raises(ValueError): for tile in cover_geometry(tiler, None, 0): pass
def test_cover_geometry_poly2(tiler, poly): """A Polygon geometry.""" tiles = [tile for tile in cover_geometry(tiler, poly, 7)] assert len(tiles) == 4 assert set(tiles) == {(90, 47, 7), (90, 48, 7), (91, 48, 7), (92, 48, 7)}
def post(self, project_id, prediction_id): """ Given a GeoJSON, xyz list, or tile list, submit it to the SQS queue --- produces: - application/json responses: 200: description: Status Update """ if CONFIG.EnvironmentConfig.ENVIRONMENT != "aws": return err(501, "stack must be in 'aws' mode to use this endpoint"), 501 payload = request.data tiler = tileschemes.WebMercator() try: prediction = PredictionService.get_prediction_by_id(prediction_id) imagery = ImageryService.get(prediction.imagery_id) queue_name = "{stack}-models-{model}-prediction-{prediction}-queue".format( stack=CONFIG.EnvironmentConfig.STACK, model=project_id, prediction=prediction_id, ) queue = boto3.resource("sqs").get_queue_by_name(QueueName=queue_name) tiles = [] payloadjson = json.loads(payload) if imagery["fmt"] == "wms": if type(payloadjson) is list: for tile in payloadjson: tile = tile.split("-") tiles.append( mercantile.Tile(int(tile[0]), int(tile[1]), int(tile[2])) ) else: poly = shape(geojson.loads(payload)) project = partial( pyproj.transform, pyproj.Proj(init="epsg:4326"), pyproj.Proj(init="epsg:3857"), ) poly = transform(project, poly) tiles = tilecover.cover_geometry(tiler, poly, prediction.tile_zoom) cache = [] for tile in tiles: cache.append( { "Id": str(tile.z) + "-" + str(tile.x) + "-" + str(tile.y), "MessageBody": json.dumps( { "name": "{x}-{y}-{z}".format( x=tile.x, y=tile.y, z=tile.z ), "url": imagery["url"].format( x=tile.x, y=tile.y, z=tile.z ), "bounds": mercantile.bounds(tile.x, tile.y, tile.z), "x": tile.x, "y": tile.y, "z": tile.z, } ), } ) if len(cache) == 10: queue.send_messages(Entries=cache) cache = [] if len(cache) > 0: queue.send_messages(Entries=cache) return {}, 200 elif imagery["fmt"] == "list": r = requests.get(imagery["url"]) r.raise_for_status() f = StringIO(r.text) cache = [] for row in csv.reader(f, delimiter=","): cache.append( { "Id": row[0], "MessageBody": json.dumps( { "name": row[0], "url": row[1], "bounds": row[2].split(","), } ), } ) if len(cache) == 10: queue.send_messages(Entries=cache) cache = [] if len(cache) > 0: queue.send_messages(Entries=cache) return {}, 200 else: return err(400, "Unknown imagery type"), 400 except Exception as e: current_app.logger.error(traceback.format_exc()) error_msg = f"Prediction Tiler Error: {str(e)}" return err(500, error_msg), 500
def test_cover_geometry_poly2(tiler, poly): """A Polygon geometry.""" tiles = [tile for tile in cover_geometry(tiler, poly, 7)] assert len(tiles) == 4 assert set(tiles) == {( 90, 47, 7), (90, 48, 7), (91, 48, 7), (92, 48, 7)}
def test_cover_geometry_poly_w_hole1(tiler, poly_w_hole): """A Polygon geometry with a hole in it.""" tiles = [tile for tile in cover_geometry(tiler, poly_w_hole, 7)] assert len(tiles) == 11 assert set(tiles) == set([(72, 22, 7), (74, 21, 7), (75, 22, 7), (73, 20, 7), (74, 22, 7), (73, 22, 7), (74, 20, 7), (73, 21, 7), (75, 21, 7), (72, 21, 7), (72, 20, 7)])
def test_cover_geometry_multipoly1(tiler, mpoly): """A MultiPolygon geometry.""" tiles = [tile for tile in cover_geometry(tiler, mpoly, 7)] assert len(tiles) == 8 assert set(tiles) == set([(8, 38, 7), (4, 40, 7), (2, 41, 7), (0, 42, 7), (5, 40, 7), (7, 39, 7), (8, 39, 7), (6, 40, 7)])