def test_quadkey_failure(recwarn): """expect a deprecation warning""" warnings.simplefilter("always") with pytest.raises(mercantile.QuadKeyError): mercantile.quadkey_to_tile("lolwut") assert len(recwarn) == 1 assert recwarn.pop(DeprecationWarning)
def _get_geojson(mosaicid: str = None, url: str = None) -> Tuple[str, str, str]: """ Handle /geojson requests. Attributes ---------- url : str, required Mosaic definition url. Returns ------- status : str Status of the request (e.g. OK, NOK). MIME type : str response body MIME type (e.g. application/json). body : str String encoded JSON metata """ bucket = os.environ["MOSAIC_DEF_BUCKET"] url = f"s3://{bucket}/mosaics/{mosaicid}.json.gz" mosaic_definition = get_mosaic_content(url) geojson = { "type": "FeatureCollection", "features": [ mercantile.feature(mercantile.quadkey_to_tile(qk), props=dict(quadkey=qk, files=files)) for qk, files in mosaic_definition["tiles"].items() ], } return ("OK", "application/json", json.dumps(geojson))
def create(data): """ Bulk inserts prediction tiles :params prediction, data :returns None """ for prediction in data['predictions']: if prediction.get('quadkey_geom') is not None: polygon = prediction.get('quadkey_geom') bounds = [ polygon['coordinates'][0][0][0], polygon['coordinates'][0][0][1], polygon['coordinates'][0][2][0], polygon['coordinates'][0][2][1] ] prediction[ "quadkey_geom"] = "SRID=4326;POLYGON(({0} {1},{0} {3},{2} {3},{2} {1},{0} {1}))".format( bounds[0], bounds[1], bounds[2], bounds[3]) else: bounds = mercantile.bounds( mercantile.quadkey_to_tile(prediction.get('quadkey'))) prediction[ "quadkey_geom"] = "SRID=4326;POLYGON(({0} {1},{0} {3},{2} {3},{2} {1},{0} {1}))".format( bounds[0], bounds[1], bounds[2], bounds[3]) connection = db.engine.connect() connection.execute(PredictionTile.__table__.insert(), data['predictions'])
def quadkey(ctx, input): """Takes a [x, y, z] tile or a quadkey as input and writes a quadkey or a [x, y, z] tile to stdout, respectively. $ echo "[486, 332, 10]" | mercantile quadkey Output: 0313102310 $ echo "0313102310" | mercantile quadkey Output: [486, 332, 10] """ src = normalize_input(input) try: for line in iter_lines(src): if line[0] == '[': tile = json.loads(line)[:3] output = mercantile.quadkey(tile) else: tile = mercantile.quadkey_to_tile(line) output = json.dumps(tile) click.echo(output) except ValueError: raise click.BadParameter("{0}".format(input), param=input, param_hint='input')
def quadkey(ctx, input): """Takes a [x, y, z] tile or a quadkey as input and writes a quadkey or a [x, y, z] tile to stdout, respectively. $ echo "[486, 332, 10]" | mercantile quadkey Output: 0313102310 $ echo "0313102310" | mercantile quadkey Output: [486, 332, 10] """ src = normalize_input(input) try: for line in iter_lines(src): if line[0] == '[': tile = json.loads(line)[:3] output = mercantile.quadkey(tile) else: tile = mercantile.quadkey_to_tile(line) output = json.dumps(tile) click.echo(output) except ValueError: raise click.BadParameter( "{0}".format(input), param=input, param_hint='input')
def _info(mosaicid: str = None, url: str = None) -> Tuple: """Handle /info requests.""" if not mosaicid and not url: return ("NOK", "text/plain", "Missing 'MosaicID or URL' parameter") mosaic_path = _create_mosaic_path(mosaicid) if mosaicid else url with MosaicBackend(mosaic_path) as mosaic: meta = mosaic.metadata response = { "bounds": meta["bounds"], "center": meta["center"], "maxzoom": meta["maxzoom"], "minzoom": meta["minzoom"], "name": mosaicid or url, } if not mosaic_path.startswith("dynamodb://"): mosaic_quadkeys = set(mosaic._quadkeys) tile = mercantile.quadkey_to_tile(random.sample(mosaic_quadkeys, 1)[0]) assets = mosaic.tile(*tile) with rasterio.open(assets[0]) as src_dst: layer_names = _get_layer_names(src_dst) dtype = src_dst.dtypes[0] response["quadkeys"] = list(mosaic_quadkeys) response["layers"] = layer_names response["dtype"] = dtype else: warnings.warn( "Cannot retrieve 'quadkeys,layers and dtype' from dynamoDB mosaic." ) return ("OK", "application/json", json.dumps(response, separators=(",", ":")))
def quadkey(ctx, input): """Takes [x, y, z] tiles or quadkeys as input and writes quadkeys or a [x, y, z] tiles to stdout, respectively. Input may be a compact newline-delimited sequences of JSON or a pretty-printed ASCII RS-delimited sequence of JSON (like https://tools.ietf.org/html/rfc8142 and https://tools.ietf.org/html/rfc7159). Examples: \b echo "[486, 332, 10]" | mercantile quadkey 0313102310 \b echo "0313102310" | mercantile quadkey [486, 332, 10] """ src = normalize_input(input) try: for line in iter_lines(src): if line[0] == "[": tile = json.loads(line)[:3] output = mercantile.quadkey(tile) else: tile = mercantile.quadkey_to_tile(line) output = json.dumps(tile) click.echo(output) except mercantile.QuadKeyError: raise click.BadParameter("{0}".format(input), param=input, param_hint="input")
def quadkey(ctx, input): """Takes [x, y, z] tiles or quadkeys as input and writes quadkeys or a [x, y, z] tiles to stdout, respectively. Input may be a compact newline-delimited sequences of JSON or a pretty-printed ASCII RS-delimited sequence of JSON (like https://tools.ietf.org/html/rfc8142 and https://tools.ietf.org/html/rfc7159). $ echo "[486, 332, 10]" | mercantile quadkey Output: 0313102310 $ echo "0313102310" | mercantile quadkey Output: [486, 332, 10] """ src = normalize_input(input) try: for line in iter_lines(src): if line[0] == '[': tile = json.loads(line)[:3] output = mercantile.quadkey(tile) else: tile = mercantile.quadkey_to_tile(line) output = json.dumps(tile) click.echo(output) except ValueError: raise click.BadParameter( "{0}".format(input), param=input, param_hint='input')
def shapes(qk: str = Query(...)): """return info.""" x, y, z = mercantile.quadkey_to_tile(qk) with MosaicBackend(self.src_path) as mosaic: assets = mosaic.assets_for_tile(x, y, z) features = get_footprints(assets, max_threads=MAX_THREADS) return FeatureCollection(features=features)
def quadkeys_to_poly(quadkeys): quadkeys = sorted(set(quadkeys)) tiles = [mercantile.quadkey_to_tile(qk) for qk in quadkeys] tiles = mercantile.simplify(tiles) quadkeys = [mercantile.quadkey(t) for t in tiles] polys = quadkeys_to_polys(quadkeys) poly = shapely.ops.unary_union(polys) return poly
def update( self, features: Sequence[Dict], add_first: bool = True, quiet: bool = False, **kwargs, ): """Update existing MosaicJSON on backend.""" logger.debug(f"Updating {self.mosaic_name}...") new_mosaic = MosaicJSON.from_features( features, self.mosaic_def.minzoom, self.mosaic_def.maxzoom, quadkey_zoom=self.quadkey_zoom, quiet=quiet, **kwargs, ) bounds = bbox_union(new_mosaic.bounds, self.mosaic_def.bounds) self.mosaic_def._increase_version() self.mosaic_def.bounds = bounds self.mosaic_def.center = ( (bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, self.mosaic_def.minzoom, ) self.bounds = bounds items: List[Dict[str, Any]] = [] # Create Metadata item # Note: `parse_float=Decimal` is required because DynamoDB requires all numbers to be # in Decimal type (ref: https://blog.ruanbekker.com/blog/2019/02/05/convert-float-to-decimal-data-types-for-boto3-dynamodb-using-python/) meta = json.loads(self.mosaic_def.json(exclude={"tiles"}), parse_float=Decimal) items.append({ "quadkey": self._metadata_quadkey, "mosaicId": self.mosaic_name, **meta }) # Create Tile items for quadkey, new_assets in new_mosaic.tiles.items(): tile = mercantile.quadkey_to_tile(quadkey) assets = self.assets_for_tile(*tile) assets = [*new_assets, *assets ] if add_first else [*assets, *new_assets] items.append({ "mosaicId": self.mosaic_name, "quadkey": quadkey, "assets": assets }) self._write_items(items)
def geojson(self) -> dict: """Get Raster metadata.""" return { "type": "FeatureCollection", "features": [ mercantile.feature( mercantile.quadkey_to_tile(qk), props=dict(files=files) ) for qk, files in self.mosaic["tiles"].items() ], }
def generate_npz(): nonlocal req_threshold labels_dict ={} for row in stream: if req_inferences != 'all' and row[3].get(req_inferences) is None: continue if req_inferences != 'all' and row[3].get(req_inferences) <= req_threshold: continue if row[4]: i_lst = pred.inf_list.split(",") #convert raw predictions into 0 or 1 based on threshold raw_pred = [] for num, inference in enumerate(i_lst): raw_pred.append(row[3][inference]) if req_inferences == 'all': req_threshold = request.args.get('threshold', '0.5') req_threshold = float(req_threshold) l = [1 if score >= req_threshold else 0 for score in raw_pred] #convert quadkey to x-y-z t = '-'.join([str(i) for i in mercantile.quadkey_to_tile(row[1])]) # special case for binary if (pred.inf_binary) and (len(i_lst) != 2): return err(400, "binary models must have two catagories"), 400 if (len(i_lst) == 2) and (pred.inf_binary): if list(row[4].values())[0]: #validated and true, keep original labels_dict.update({t:l}) else: if l == [1, 0]: l = [0, 1] else: l = [1, 0] labels_dict.update({t:l}) else: # for multi-label for key in list(row[4].keys()): i = i_lst.index(key) if not row[4][key]: if l[i] == 0: l[i] = 1 else: l[i] = 0 labels_dict.update({t:l}) if not labels_dict: raise NoValid bytestream = io.BytesIO() np.savez(bytestream, **labels_dict) return bytestream.getvalue()
def children(quadkeys, levels=1): if not type(quadkeys) is list: quadkeys = [ quadkeys, ] for level in range(levels): childrenKeys = [] for qk in quadkeys: childrenKeys.extend([ mercantile.quadkey(t) \ for t in mercantile.children(mercantile.quadkey_to_tile(qk)) ]) quadkeys = childrenKeys return quadkeys
def optimize_group(group, quadkey): """Try to find the minimal number of assets to cover tile This optimization implies _both_ that - assets will be ordered in the MosaicJSON in order of sort of the entire tile - the total number of assets is kept to a minimum Computing the absolute minimum of assets to cover the tile may not in general be possible in finite time, so this is a naive method that should work relatively well for this use case. Returns group also sorted with respect to intersection of entire tile. """ tile = mercantile.quadkey_to_tile(quadkey) tile_geom = box(*mercantile.xy_bounds(tile)) final_assets = [] while True: # Find intersection percent group['int_pct'] = group.geometry.intersection( tile_geom).area / tile_geom.area # Remove features with no tile overlap group = group.loc[group['int_pct'] > 0] if len(group) == 0: # There are many ocean/border tiles on the edges of available maps # that by definition don't have full coverage break # Sort by cover of region of tile that is left group = group.sort_values('int_pct', ascending=False) # Remove top asset and add to final_assets top_asset = group.iloc[0] group = group.iloc[1:] final_assets.append(top_asset) # Recompute tile_geom, removing overlap with top_asset tile_geom = tile_geom.difference(top_asset.geometry) # When total area is covered, stop if tile_geom.area - 1e-4 < 0: break if len(group) == 0: # There are many ocean/border tiles on the edges of available maps # that by definition don't have full coverage break return gpd.GeoDataFrame(final_assets)
async def get_values_for_quadkey(self, session, prediction): quadkey = prediction['quadkey'] tile = mercantile.quadkey_to_tile(prediction['quadkey']) osm_building_area = await get_building_area(session, tile, self.overpass_url) return { 'quadkey': quadkey, 'centroid': prediction['centroid'], 'predictions': { **prediction['predictions'], **{ 'osm_building_area': osm_building_area } } }
def subset_mosaic(mosaic, overview_qk, overview_zoom): """Create subset of mosaic within a single overview quadkey Args: - overview_qk: zoom 6 quadkey """ qk_tiles = { k: v for k, v in mosaic['tiles'].items() if k[:overview_zoom] == overview_qk } bounds = mercantile.bounds(mercantile.quadkey_to_tile(overview_qk)) # The new mosaic needs to be the same minzoom, quadkey zoom as new_mosaic = deepcopy(mosaic) new_mosaic['tiles'] = qk_tiles new_mosaic['bounds'] = bounds return MosaicJSON(**new_mosaic)
def update( self, features: Sequence[Dict], add_first: bool = True, quiet: bool = False, **kwargs, ): """Update existing MosaicJSON on backend.""" logger.debug(f"Updating {self.mosaic_name}...") new_mosaic = self.mosaic_def.from_features( features, self.mosaic_def.minzoom, self.mosaic_def.maxzoom, quadkey_zoom=self.quadkey_zoom, quiet=quiet, **kwargs, ) fout = os.devnull if quiet else sys.stderr with click.progressbar( # type: ignore new_mosaic.tiles.items(), file=fout, show_percent=True, label=f"Updating mosaic {self.table_name}:{self.mosaic_name}", ) as items: for quadkey, new_assets in items: tile = mercantile.quadkey_to_tile(quadkey) assets = self.assets_for_tile(*tile) assets = [*new_assets, *assets ] if add_first else [*assets, *new_assets] # add custom sorting algorithm (e.g based on path name) self._update_quadkey(quadkey, assets) bounds = bbox_union(new_mosaic.bounds, self.mosaic_def.bounds) self.mosaic_def._increase_version() self.mosaic_def.bounds = bounds self.mosaic_def.center = ( (bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, self.mosaic_def.minzoom, ) self._update_metadata()
def _geojson(mosaicid: str = None, url: str = None) -> Tuple: """Handle /geojson requests.""" if not mosaicid and not url: return ("NOK", "text/plain", "Missing 'MosaicID or URL' parameter") mosaic_path = _create_mosaic_path(mosaicid) if mosaicid else url with MosaicBackend(mosaic_path) as mosaic: geojson = { "type": "FeatureCollection", "features": [ mercantile.feature( mercantile.quadkey_to_tile(qk), props=dict(files=files) ) for qk, files in mosaic.mosaic_def.tiles.items() ], } return ("OK", "application/json", json.dumps(geojson, separators=(",", ":")))
def __init__(self, xtile, ytile, zoom, uri=None): super(Syncher, self).__init__() self.uri = uri or get_uri(xtile, ytile, zoom) self.tile = {'x': xtile, 'y': ytile, 'z': zoom} tile_bounds = mc.bounds(mc.quadkey_to_tile(mc.quadkey(xtile, ytile, zoom))) keys = ('w', 's', 'e', 'n',) self.bbox = dict(zip(keys, map(str, ( tile_bounds.west, tile_bounds.south, tile_bounds.east, tile_bounds.north, )))) # minx, miny, maxx, maxy self.base_query = { 'query': [[{"k": "qwertyuiop", "modv": "not", "regv": "."}]], 'bbox': self.bbox, 'gtypes': ['node', 'way', 'relation'], }
def _geojson(mosaicid: str = None, url: str = None) -> Tuple[str, str, str]: """Handle /geojson requests.""" if mosaicid: url = _create_path(mosaicid) elif url is None: return ("NOK", "text/plain", "Missing 'URL' parameter") mosaic_def = fetch_mosaic_definition(url) geojson = { "type": "FeatureCollection", "features": [ mercantile.feature(mercantile.quadkey_to_tile(qk), props=dict(files=files)) for qk, files in mosaic_def["tiles"].items() ], } return ("OK", "application/json", json.dumps(geojson))
async def get_values_for_quadkey(self, session, quadkey): ''' Returns consolidated data values for a quadkey ''' filtered_quadkeys = list( filter(lambda d: d['quadkey'].startswith(quadkey), self.source_data)) total_ml_building_area = functools.reduce( lambda a, b: int(a + b['predictions']['ml_prediction']), filtered_quadkeys, 0) tile = mercantile.quadkey_to_tile(quadkey) osm_building_area = await get_building_area(session, tile, self.overpass_url) return { 'quadkey': quadkey, 'centroid': get_tile_center(tile), 'predictions': { 'ml_prediction': total_ml_building_area, 'osm_building_area': osm_building_area } }
def update( self, features: Sequence[Dict], add_first: bool = True, quiet: bool = False, **kwargs, ): """Update existing MosaicJSON on backend.""" new_mosaic = self.mosaic_def.from_features( features, self.mosaic_def.minzoom, self.mosaic_def.maxzoom, quadkey_zoom=self.quadkey_zoom, quiet=quiet, **kwargs, ) for quadkey, new_assets in new_mosaic.tiles.items(): tile = mercantile.quadkey_to_tile(quadkey) assets = self.assets_for_tile(*tile) assets = [*new_assets, *assets ] if add_first else [*assets, *new_assets] # add custom sorting algorithm (e.g based on path name) self.mosaic_def.tiles[quadkey] = assets bounds = bbox_union(new_mosaic.bounds, self.mosaic_def.bounds) self.mosaic_def._increase_version() self.mosaic_def.bounds = bounds self.mosaic_def.center = ( (bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, self.mosaic_def.minzoom, ) # We only write if path is set if self.path: self.write(overwrite=True) return
def to_geojson(input, collect): """Read MosaicJSON document and create GeoJSON features.""" features = [] with MosaicBackend(input) as mosaic: for qk, assets in mosaic.mosaic_def.tiles.items(): tile = mercantile.quadkey_to_tile(qk) west, south, east, north = mercantile.bounds(tile) geom = { "type": "Polygon", "coordinates": [[ [west, south], [west, north], [east, north], [east, south], [west, south], ]], } feature = { "type": "Feature", "id": str(tile), "geometry": geom, "properties": { "nb_assets": len(assets), "assets": assets }, } if collect: features.append(feature) else: click.echo(json.dumps(feature)) if collect and features: click.echo( json.dumps({ "type": "FeatureCollection", "features": features }, ))
def quadkeys_to_bounds(quadkeys: List[str]): """Convert list of quadkeys to bounds Args: - quadkeys: List of quadkeys """ tile_bounds = [ mercantile.bounds(mercantile.quadkey_to_tile(qk)) for qk in quadkeys ] minx = 180 miny = 90 maxx = -180 maxy = -90 for tb in tile_bounds: minx = min(minx, tb[0]) miny = min(miny, tb[1]) maxx = max(maxx, tb[2]) maxy = max(maxy, tb[3]) return [minx, miny, maxx, maxy]
def create_overview_mosaic(urls, quadkey_zoom, min_zoom, max_zoom): """Create mosaic representing overview """ # Input is file object urls = [l.strip() for l in urls.readlines()] quadkeys = [parse_url(url, quadkey_zoom) for url in urls] # Find bounds of quadkeys bboxes = [ mercantile.bounds(mercantile.quadkey_to_tile(qk)) for qk in quadkeys ] minx = min(bboxes, key=lambda bbox: bbox[0])[0] miny = min(bboxes, key=lambda bbox: bbox[1])[1] maxx = max(bboxes, key=lambda bbox: bbox[2])[2] maxy = max(bboxes, key=lambda bbox: bbox[3])[3] bounds = [minx, miny, maxx, maxy] # Find center center = [(minx + maxx) / 2, (miny + maxy) / 2, min_zoom] tiles = {} for qk, url in zip(quadkeys, urls): tiles[qk] = [url] mosaic = { "mosaicjson": "0.0.2", "minzoom": min_zoom, "maxzoom": max_zoom, "quadkey_zoom": 6, "bounds": bounds, "center": center, "tiles": tiles } # Validation mosaic = MosaicJSON(**mosaic).dict(exclude_none=True) print(json.dumps(mosaic, separators=(',', ':')))
def missing_quadkeys(mosaic: Dict, shp_path: str, bounds: List[float] = None, simplify: bool = True) -> Dict: """Find quadkeys over land missing from mosaic Args: - mosaic: mosaic definition - shp_path: path to Natural Earth shapefile of land boundaries - bounds: force given bounds - simplify: reduce size of the tileset as much as possible by merging leaves into parents Returns: - GeoJSON FeatureCollection of missing tiles """ bounds = bounds or mosaic['bounds'] top_tile = mercantile.bounding_tile(*bounds) gdf = gpd.read_file(shp_path) quadkey_zoom = mosaic.get('quadkey_zoom', mosaic['minzoom']) # Remove null island # Keep the landmasses that are visible at given zoom gdf = gdf[gdf['max_zoom'] <= quadkey_zoom] land_tiles = find_child_land_tiles(top_tile, gdf, quadkey_zoom) quadkeys = {mercantile.quadkey(tile) for tile in land_tiles} mosaic_quadkeys = set(mosaic['tiles'].keys()) not_in_mosaic = quadkeys.difference(mosaic_quadkeys) not_in_mosaic = [mercantile.quadkey_to_tile(qk) for qk in not_in_mosaic] if simplify: not_in_mosaic = mercantile.simplify(not_in_mosaic) features = [mercantile.feature(tile) for tile in not_in_mosaic] return {'type': 'FeatureCollection', 'features': features}
def test_empty_quadkey_to_tile(): qk = "" expected = mercantile.Tile(0, 0, 0) assert mercantile.quadkey_to_tile(qk) == expected
Field( 'modified_on', 'datetime', # required = True, # notnull=True, update=now, default=now, # compute = lambda _=None: now(), writable=False, readable=True), Field("is_active", "boolean", default=True, readable=False, writable=False), # Field('task_id', "reference scheduler_task", notnull=True, requires=None), Field.Virtual( 'feature', lambda row: mc.feature(mc.quadkey_to_tile( mc.quadkey(row.tracked_tile.xtile, row.tracked_tile.ytile, row. tracked_tile.zoom)), fid=row.tracked_tile.uri, props={ 'created': row.tracked_tile.created_on, 'updated': row.tracked_tile.modified_on })), # Field.Virtual('last_update', lambda row: get_last_update(row.tile.uri)) ) db.define_table("queued_tile", Field("tile_id", "reference tracked_tile", unique=True)) def track_tiles(lon, lat, maxdist, buffer=4): """ """
def create_overview_cogs( mosaic_path: str, output_profile: Dict, prefix: str = "mosaic_ovr", max_overview_level: int = 6, method: str = "first", config: Dict = None, threads=1, in_memory: bool = True, ) -> None: """ Create Low resolution mosaic image from a mosaicJSON. The output will be a web optimized COG with bounds matching the mosaicJSON bounds and with its resolution matching the mosaic MinZoom - 1. Attributes ---------- mosaic_path : str, required Mosaic definition path. output_profile : dict, required prefix : str max_overview_level : int method: str, optional pixel_selection method name (default is 'first'). config : dict Rasterio Env options. threads: int, optional maximum number of threads to use (default is 1). in_memory: bool, optional Force COG creation in memory (default is True). """ pixel_method = PIXSEL_METHODS[method] with MosaicBackend(mosaic_path) as mosaic: base_zoom = mosaic.metadata["minzoom"] - 1 mosaic_quadkey_zoom = mosaic.quadkey_zoom bounds = mosaic.metadata["bounds"] mosaic_quadkeys = set(mosaic._quadkeys) # Select a random quakey/asset and get dataset info tile = mercantile.quadkey_to_tile(random.sample(mosaic_quadkeys, 1)[0]) assets = mosaic.assets_for_tile(*tile) info = _get_info(assets[0]) extrema = tile_extrema(bounds, base_zoom) tilesize = 256 resolution = _meters_per_pixel(base_zoom, 0, tilesize=tilesize) # Create multiples files if coverage is too big extremas = _split_extrema(extrema, max_ovr=max_overview_level) for ix, extrema in enumerate(extremas): click.echo(f"Part {1 + ix}/{len(extremas)}", err=True) output_path = f"{prefix}_{ix}.tif" blocks = list(_get_blocks(extrema, tilesize)) random.shuffle(blocks) width = (extrema["x"]["max"] - extrema["x"]["min"]) * tilesize height = (extrema["y"]["max"] - extrema["y"]["min"]) * tilesize w, n = mercantile.xy(*mercantile.ul( extrema["x"]["min"], extrema["y"]["min"], base_zoom)) params = dict( driver="GTiff", dtype=info["dtype"], count=len(info["band_descriptions"]), width=width, height=height, crs="epsg:3857", transform=Affine(resolution, 0, w, 0, -resolution, n), nodata=info["nodata_value"], ) params.update(**output_profile) config = config or {} with rasterio.Env(**config): with ExitStack() as ctx: if in_memory: tmpfile = ctx.enter_context(MemoryFile()) tmp_dst = ctx.enter_context(tmpfile.open(**params)) else: tmpfile = ctx.enter_context( TemporaryRasterFile(output_path)) tmp_dst = ctx.enter_context( rasterio.open(tmpfile.name, "w", **params)) def _get_tile(wind): idx, window = wind x = extrema["x"]["min"] + idx[1] y = extrema["y"]["min"] + idx[0] t = mercantile.Tile(x, y, base_zoom) kds = set(find_quadkeys(t, mosaic_quadkey_zoom)) if not mosaic_quadkeys.intersection(kds): return window, None, None try: (tile, mask), _ = mosaic.tile( t.x, t.y, t.z, tilesize=tilesize, pixel_selection=pixel_method(), ) except NoAssetFoundError: return window, None, None return window, tile, mask with futures.ThreadPoolExecutor( max_workers=threads) as executor: future_work = [ executor.submit(_get_tile, item) for item in blocks ] with click.progressbar( futures.as_completed(future_work), length=len(future_work), show_percent=True, label="Loading tiles", ) as future: for res in future: pass for f in _filter_futures(future_work): window, tile, mask = f if tile is None: continue tmp_dst.write(tile, window=window) if info["nodata_type"] == "Mask": tmp_dst.write_mask(mask.astype("uint8"), window=window) min_tile_size = tilesize = min( int(output_profile["blockxsize"]), int(output_profile["blockysize"]), ) overview_level = get_maximum_overview_level( tmp_dst.width, tmp_dst.height, minsize=min_tile_size) overviews = [2**j for j in range(1, overview_level + 1)] tmp_dst.build_overviews(overviews) copy(tmp_dst, output_path, copy_src_overviews=True, **params)
def test_quadkey_failure(): with pytest.raises(ValueError): mercantile.quadkey_to_tile('lolwut')
def test_quadkey_to_tile(): qk = "0313102310" expected = mercantile.Tile(486, 332, 10) assert mercantile.quadkey_to_tile(qk) == expected
def quadkey_to_poly(quadkey): x0, y0, x1, y1 = mercantile.bounds(mercantile.quadkey_to_tile(quadkey)) poly = shapely.geometry.Polygon([[x0, y0], [x0, y1], [x1, y1], [x1, y0]]) return poly
def quadkey_to_centroid(quadkey): return centroid(*mercantile.bounds(mercantile.quadkey_to_tile(quadkey)))