def __init__( self, request: Request, bidx: Optional[str] = Query( None, title="Band indexes", description="comma (',') delimited band indexes", ), expression: Optional[str] = Query( None, title="Band Math expression", description="rio-tiler's band math expression (e.g B1/B2)", ), nodata: Optional[Union[str, int, float]] = Query( None, title="Nodata value", description="Overwrite internal Nodata value"), rescale: Optional[str] = Query( None, title="Min/Max data Rescaling", description="comma (',') delimited Min,Max bounds", ), color_formula: Optional[str] = Query( None, title="Color Formula", description= "rio-color formula (info: https://github.com/mapbox/rio-color)", ), color_map: Optional[ColorMapNames] = Query( None, description="rio-tiler's colormap name"), resampling_method: ResamplingNames = Query( ResamplingNames.nearest, description="Resampling method." # type: ignore ), ): """Populate Imager Params.""" self.indexes = tuple( int(s) for s in re.findall(r"\d+", bidx)) if bidx else None self.expression = expression if nodata is not None: nodata = numpy.nan if nodata == "nan" else float(nodata) self.nodata = nodata self.rescale = rescale self.color_formula = color_formula self.color_map = cmap.get(color_map.value) if color_map else None kwargs = dict(request.query_params) kwargs.pop("TileMatrixSetId", None) kwargs.pop("url", None) kwargs.pop("scale", None) kwargs.pop("format", None) kwargs.pop("bidx", None) kwargs.pop("expression", None) kwargs.pop("nodata", None) kwargs.pop("rescale", None) kwargs.pop("color_formula", None) kwargs.pop("color_map", None) kwargs.pop("assets", None) # For STAC self.kwargs = kwargs
async def stac_tile( z: int, x: int, y: int, scale: int = Query(2, gt=0, lt=4), ext: ImageType = None, assets: Any = Query( None, description="Coma (',') delimited band indexes"), indexes: Any = Query( None, description="Coma (',') delimited band indexes"), rescale: Any = Query( None, description="Coma (',') delimited Min,Max bounds"), color_formula: str = Query(None, description="rio-color formula"), color_map: str = Query(None, description="rio-tiler color map names"), resampling_method: str = Query("bilinear", description="rasterio resampling"), ): """Handle /tiles requests.""" if isinstance(assets, str): assets = assets.split(",") if isinstance(indexes, str): indexes = tuple(int(s) for s in re.findall(r"\d+", indexes)) tilesize = scale * 256 tile, mask = self.raster.read_tile( z, x, y, assets, tilesize=tilesize, indexes=indexes, resampling_method=resampling_method, ) tile = await _postprocess_tile(tile, mask, rescale=rescale, color_formula=color_formula) if not ext: ext = ImageType.jpg if mask.all() else ImageType.png driver = drivers[ext] options = img_profiles.get(driver.lower(), {}) if color_map: options["colormap"] = cmap.get(color_map) content = await _render(tile, mask, img_format=driver, **options) return TileResponse(content, media_type=mimetype[ext.value])
def __init__( self, raster=None, rasters=None, scale=None, colormap=None, tiles_format="png", gl_tiles_size=None, gl_tiles_minzoom=0, gl_tiles_maxzoom=22, port=8080, ): """Initialize Tornado app.""" self.raster = raster if raster else next(iter(rasters.values())) self.rasters = rasters if rasters else dict() self.port = port self.server = None self.tiles_format = tiles_format self.gl_tiles_size = gl_tiles_size if gl_tiles_size else self.raster.tiles_size self.gl_tiles_minzoom = gl_tiles_minzoom self.gl_tiles_maxzoom = gl_tiles_maxzoom settings = { "static_path": os.path.join(os.path.dirname(__file__), "static") } if colormap: colormap = cmap.get(name=colormap) tile_params = dict(raster=self.raster, scale=scale, colormap=colormap) local_tile_params = dict(rasters=rasters, scale=scale, colormap=colormap) template_params = dict( tiles_url=self.get_tiles_url(), tiles_bounds=self.raster.get_bounds(), gl_tiles_size=self.gl_tiles_size, gl_tiles_minzoom=self.gl_tiles_minzoom, gl_tiles_maxzoom=self.gl_tiles_maxzoom, ) self.app = web.Application([ (r"^/tiles/(\d+)/(\d+)/(\d+)\.(\w+)", RasterTileHandler, tile_params), (r"^/localtiles/(\w+)/(\d+)/(\d+)/(\d+)\.(\w+)", MultiRasterTileHandler, local_tile_params), (r"^/index.html", IndexTemplate, template_params), (r"^/playground.html", PlaygroundTemplate, template_params), (r"/.*", InvalidAddress), ], **settings)
def ColorMapParams( colormap_name: ColorMapName = Query(None, description="Colormap name"), colormap: str = Query(None, description="JSON encoded custom Colormap"), ) -> Optional[Dict]: """Colormap Dependency.""" if colormap_name: return cmap.get(colormap_name.value) if colormap: return json.loads( colormap, object_hook=lambda x: {int(k): parse_color(v) for k, v in x.items()}, ) return None
def ColorMapParams( colormap_name: ColorMapName = Query(None, description="Colormap name"), colormap: str = Query(None, description="JSON encoded custom Colormap"), ) -> Optional[Dict]: """Colormap Dependency.""" if colormap_name: return cmap.get(colormap_name.value) if colormap: try: return json.loads( colormap, object_hook=lambda x: {int(k): parse_color(v) for k, v in x.items()}, ) except json.JSONDecodeError: raise HTTPException(status_code=400, detail="Could not parse the colormap value.") return None
def __init__( self, bidx: Optional[str] = Query( None, title="Band indexes", description="Coma (',') delimited band indexes", ), expression: Optional[str] = Query( None, title="Band Math expression", description="rio-tiler's band math expression (e.g B1/B2)", ), nodata: Optional[Union[str, int, float]] = Query( None, title="Nodata value", description="Overwrite internal Nodata value"), rescale: Optional[str] = Query( None, title="Min/Max data Rescaling", description="Coma (',') delimited Min,Max bounds", ), color_formula: Optional[str] = Query( None, title="Color Formula", description= "rio-color formula (info: https://github.com/mapbox/rio-color)", ), color_map: Optional[ColorMapName] = Query( None, description="rio-tiler's colormap name"), ): """Populate Imager Params.""" self.indexes = tuple( int(s) for s in re.findall(r"\d+", bidx)) if bidx else None self.expression = expression if nodata is not None: nodata = numpy.nan if nodata == "nan" else float(nodata) self.nodata = nodata self.rescale = rescale self.color_formula = color_formula self.color_map = cmap.get(color_map.value) if color_map else None
def post(self, request, pk=None, project_pk=None, asset_type=None): """ Export assets (orthophoto, DEMs, etc.) after applying scaling formulas, shading, reprojections """ task = self.get_and_check_task(request, pk) formula = request.data.get('formula') bands = request.data.get('bands') rescale = request.data.get('rescale') export_format = request.data.get( 'format', 'laz' if asset_type == 'georeferenced_model' else 'gtiff') epsg = request.data.get('epsg') color_map = request.data.get('color_map') hillshade = request.data.get('hillshade') if formula == '': formula = None if bands == '': bands = None if rescale == '': rescale = None if epsg == '': epsg = None if color_map == '': color_map = None if hillshade == '': hillshade = None expr = None if asset_type in [ 'orthophoto', 'dsm', 'dtm' ] and not export_format in ['gtiff', 'gtiff-rgb', 'jpg', 'png', 'kmz']: raise exceptions.ValidationError( _("Unsupported format: %(value)s") % {'value': export_format}) if asset_type == 'georeferenced_model' and not export_format in [ 'laz', 'las', 'ply', 'csv' ]: raise exceptions.ValidationError( _("Unsupported format: %(value)s") % {'value': export_format}) # Default color map, hillshade if asset_type in ['dsm', 'dtm'] and export_format != 'gtiff': if color_map is None: color_map = 'viridis' if hillshade is None: hillshade = 6 if color_map is not None: try: colormap.get(color_map) except InvalidColorMapName: raise exceptions.ValidationError( _("Not a valid color_map value")) if epsg is not None: try: epsg = int(epsg) except ValueError: raise exceptions.ValidationError( _("Invalid EPSG code: %(value)s") % {'value': epsg}) if (formula and not bands) or (not formula and bands): raise exceptions.ValidationError( _("Both formula and bands parameters are required")) if formula and bands: try: expr, _discard_ = lookup_formula(formula, bands) except ValueError as e: raise exceptions.ValidationError(str(e)) if export_format in ['gtiff-rgb', 'jpg', 'png']: if formula is not None and rescale is None: rescale = "-1,1" if export_format == 'gtiff': rescale = None if rescale is not None: rescale = rescale.replace("%2C", ",") try: rescale = list(map(float, rescale.split(","))) except ValueError: raise exceptions.ValidationError( _("Invalid rescale value: %(value)s") % {'value': rescale}) if hillshade is not None: try: hillshade = float(hillshade) if hillshade < 0: raise Exception("Hillshade must be > 0") except: raise exceptions.ValidationError( _("Invalid hillshade value: %(value)s") % {'value': hillshade}) if asset_type == 'georeferenced_model': url = get_pointcloud_path(task) else: url = get_raster_path(task, asset_type) if not os.path.isfile(url): raise exceptions.NotFound() if epsg is not None and task.epsg is None: raise exceptions.ValidationError( _("Cannot use epsg on non-georeferenced dataset")) # Strip unsafe chars, append suffix extension = extension_for_export_format(export_format) filename = "{}{}.{}".format( get_asset_download_filename(task, asset_type), "-{}".format(formula) if expr is not None else "", extension) if asset_type in ['orthophoto', 'dsm', 'dtm']: # Shortcut the process if no processing is required if export_format == 'gtiff' and (epsg == task.epsg or epsg is None) and expr is None: return Response({ 'url': '/api/projects/{}/tasks/{}/download/{}.tif'.format( task.project.id, task.id, asset_type), 'filename': filename }) else: celery_task_id = export_raster.delay(url, epsg=epsg, expression=expr, format=export_format, rescale=rescale, color_map=color_map, hillshade=hillshade, asset_type=asset_type, name=task.name).task_id return Response({ 'celery_task_id': celery_task_id, 'filename': filename }) elif asset_type == 'georeferenced_model': # Shortcut the process if no processing is required if export_format == 'laz' and (epsg == task.epsg or epsg is None): return Response({ 'url': '/api/projects/{}/tasks/{}/download/{}.laz'.format( task.project.id, task.id, asset_type), 'filename': filename }) else: celery_task_id = export_pointcloud.delay( url, epsg=epsg, format=export_format).task_id return Response({ 'celery_task_id': celery_task_id, 'filename': filename })
def get(self, request, pk=None, project_pk=None, tile_type="", z="", x="", y="", scale=1): """ Get a tile image """ task = self.get_and_check_task(request, pk) z = int(z) x = int(x) y = int(y) scale = int(scale) ext = "png" driver = "jpeg" if ext == "jpg" else ext indexes = None nodata = None rgb_tile = None formula = self.request.query_params.get('formula') bands = self.request.query_params.get('bands') rescale = self.request.query_params.get('rescale') color_map = self.request.query_params.get('color_map') hillshade = self.request.query_params.get('hillshade') boundaries_feature = self.request.query_params.get('boundaries') if boundaries_feature == '': boundaries_feature = None if boundaries_feature is not None: try: boundaries_feature = json.loads(boundaries_feature) except json.JSONDecodeError: raise exceptions.ValidationError( _("Invalid boundaries parameter")) if formula == '': formula = None if bands == '': bands = None if rescale == '': rescale = None if color_map == '': color_map = None if hillshade == '' or hillshade == '0': hillshade = None try: expr, _discard_ = lookup_formula(formula, bands) except ValueError as e: raise exceptions.ValidationError(str(e)) if tile_type in ['dsm', 'dtm'] and rescale is None: rescale = "0,1000" if tile_type == 'orthophoto' and rescale is None: rescale = "0,255" if tile_type in ['dsm', 'dtm'] and color_map is None: color_map = "gray" if tile_type == 'orthophoto' and formula is not None: if color_map is None: color_map = "gray" if rescale is None: rescale = "-1,1" if nodata is not None: nodata = np.nan if nodata == "nan" else float(nodata) tilesize = scale * 256 url = get_raster_path(task, tile_type) if not os.path.isfile(url): raise exceptions.NotFound() with COGReader(url) as src: if not src.tile_exists(z, x, y): raise exceptions.NotFound(_("Outside of bounds")) with COGReader(url) as src: minzoom, maxzoom = get_zoom_safe(src) has_alpha = has_alpha_band(src.dataset) if z < minzoom - ZOOM_EXTRA_LEVELS or z > maxzoom + ZOOM_EXTRA_LEVELS: raise exceptions.NotFound() if boundaries_feature is not None: try: boundaries_cutline = create_cutline( src.dataset, boundaries_feature, CRS.from_string('EPSG:4326')) except: raise exceptions.ValidationError(_("Invalid boundaries")) else: boundaries_cutline = None # Handle N-bands datasets for orthophotos (not plant health) if tile_type == 'orthophoto' and expr is None: ci = src.dataset.colorinterp # More than 4 bands? if len(ci) > 4: # Try to find RGBA band order if ColorInterp.red in ci and \ ColorInterp.green in ci and \ ColorInterp.blue in ci: indexes = ( ci.index(ColorInterp.red) + 1, ci.index(ColorInterp.green) + 1, ci.index(ColorInterp.blue) + 1, ) else: # Fallback to first three indexes = ( 1, 2, 3, ) elif has_alpha: indexes = non_alpha_indexes(src.dataset) # Workaround for https://github.com/OpenDroneMap/WebODM/issues/894 if nodata is None and tile_type == 'orthophoto': nodata = 0 resampling = "nearest" padding = 0 if tile_type in ["dsm", "dtm"]: resampling = "bilinear" padding = 16 try: with COGReader(url) as src: if expr is not None: if boundaries_cutline is not None: tile = src.tile( x, y, z, expression=expr, tilesize=tilesize, nodata=nodata, padding=padding, resampling_method=resampling, vrt_options={'cutline': boundaries_cutline}) else: tile = src.tile(x, y, z, expression=expr, tilesize=tilesize, nodata=nodata, padding=padding, resampling_method=resampling) else: if boundaries_cutline is not None: tile = src.tile( x, y, z, tilesize=tilesize, nodata=nodata, padding=padding, resampling_method=resampling, vrt_options={'cutline': boundaries_cutline}) else: tile = src.tile(x, y, z, indexes=indexes, tilesize=tilesize, nodata=nodata, padding=padding, resampling_method=resampling) except TileOutsideBounds: raise exceptions.NotFound(_("Outside of bounds")) if color_map: try: colormap.get(color_map) except InvalidColorMapName: raise exceptions.ValidationError( _("Not a valid color_map value")) intensity = None try: rescale_arr = list(map(float, rescale.split(","))) except ValueError: raise exceptions.ValidationError(_("Invalid rescale value")) options = img_profiles.get(driver, {}) if hillshade is not None: try: hillshade = float(hillshade) if hillshade <= 0: hillshade = 1.0 except ValueError: raise exceptions.ValidationError(_("Invalid hillshade value")) if tile.data.shape[0] != 1: raise exceptions.ValidationError( _("Cannot compute hillshade of non-elevation raster (multiple bands found)" )) delta_scale = (maxzoom + ZOOM_EXTRA_LEVELS + 1 - z) * 4 dx = src.dataset.meta["transform"][0] * delta_scale dy = -src.dataset.meta["transform"][4] * delta_scale ls = LightSource(azdeg=315, altdeg=45) # Hillshading is not a local tile operation and # requires neighbor tiles to be rendered seamlessly elevation = get_elevation_tiles(tile.data[0], url, x, y, z, tilesize, nodata, resampling, padding) intensity = ls.hillshade(elevation, dx=dx, dy=dy, vert_exag=hillshade) intensity = intensity[tilesize:tilesize * 2, tilesize:tilesize * 2] if intensity is not None: rgb = tile.post_process(in_range=(rescale_arr, )) if colormap: rgb, _discard_ = apply_cmap(rgb.data, colormap.get(color_map)) if rgb.data.shape[0] != 3: raise exceptions.ValidationError( _("Cannot process tile: intensity image provided, but no RGB data was computed." )) intensity = intensity * 255.0 rgb = hsv_blend(rgb, intensity) if rgb is not None: return HttpResponse(render(rgb, tile.mask, img_format=driver, **options), content_type="image/{}".format(ext)) if color_map is not None: return HttpResponse( tile.post_process(in_range=(rescale_arr, )).render( img_format=driver, colormap=colormap.get(color_map), **options), content_type="image/{}".format(ext)) return HttpResponse(tile.post_process(in_range=(rescale_arr, )).render( img_format=driver, **options), content_type="image/{}".format(ext))
def get(self, request, pk=None, project_pk=None, tile_type=""): """ Get the metadata for this tasks's asset type """ task = self.get_and_check_task(request, pk) formula = self.request.query_params.get('formula') bands = self.request.query_params.get('bands') defined_range = self.request.query_params.get('range') boundaries_feature = self.request.query_params.get('boundaries') if formula == '': formula = None if bands == '': bands = None if defined_range == '': defined_range = None if boundaries_feature == '': boundaries_feature = None if boundaries_feature is not None: boundaries_feature = json.loads(boundaries_feature) try: expr, hrange = lookup_formula(formula, bands) if defined_range is not None: new_range = tuple(map(float, defined_range.split(",")[:2])) #Validate rescaling range if hrange is not None and (new_range[0] < hrange[0] or new_range[1] > hrange[1]): pass else: hrange = new_range except ValueError as e: raise exceptions.ValidationError(str(e)) pmin, pmax = 2.0, 98.0 raster_path = get_raster_path(task, tile_type) if not os.path.isfile(raster_path): raise exceptions.NotFound() try: with COGReader(raster_path) as src: band_count = src.dataset.meta['count'] if boundaries_feature is not None: boundaries_cutline = create_cutline( src.dataset, boundaries_feature, CRS.from_string('EPSG:4326')) boundaries_bbox = featureBounds(boundaries_feature) else: boundaries_cutline = None boundaries_bbox = None if has_alpha_band(src.dataset): band_count -= 1 nodata = None # Workaround for https://github.com/OpenDroneMap/WebODM/issues/894 if tile_type == 'orthophoto': nodata = 0 histogram_options = {"bins": 255, "range": hrange} if expr is not None: if boundaries_cutline is not None: data, mask = src.preview( expression=expr, vrt_options={'cutline': boundaries_cutline}) else: data, mask = src.preview(expression=expr) data = numpy.ma.array(data) data.mask = mask == 0 stats = { str(b + 1): raster_stats(data[b], percentiles=(pmin, pmax), bins=255, range=hrange) for b in range(data.shape[0]) } stats = {b: ImageStatistics(**s) for b, s in stats.items()} metadata = RioMetadata(statistics=stats, **src.info().dict()) else: if (boundaries_cutline is not None) and (boundaries_bbox is not None): metadata = src.metadata( pmin=pmin, pmax=pmax, hist_options=histogram_options, nodata=nodata, bounds=boundaries_bbox, vrt_options={'cutline': boundaries_cutline}) else: metadata = src.metadata(pmin=pmin, pmax=pmax, hist_options=histogram_options, nodata=nodata) info = json.loads(metadata.json()) except IndexError as e: # Caught when trying to get an invalid raster metadata raise exceptions.ValidationError( "Cannot retrieve raster metadata: %s" % str(e)) # Override min/max if hrange: for b in info['statistics']: info['statistics'][b]['min'] = hrange[0] info['statistics'][b]['max'] = hrange[1] cmap_labels = { "viridis": "Viridis", "jet": "Jet", "terrain": "Terrain", "gist_earth": "Earth", "rdylgn": "RdYlGn", "rdylgn_r": "RdYlGn (Reverse)", "spectral": "Spectral", "spectral_r": "Spectral (Reverse)", "discrete_ndvi": "Contrast NDVI", "better_discrete_ndvi": "Custom NDVI Index", "rplumbo": "Rplumbo (Better NDVI)", "pastel1": "Pastel", } colormaps = [] algorithms = [] if tile_type in ['dsm', 'dtm']: colormaps = ['viridis', 'jet', 'terrain', 'gist_earth', 'pastel1'] elif formula and bands: colormaps = [ 'rdylgn', 'spectral', 'rdylgn_r', 'spectral_r', 'rplumbo', 'discrete_ndvi', 'better_discrete_ndvi' ] algorithms = *get_algorithm_list(band_count), info['color_maps'] = [] info['algorithms'] = algorithms if colormaps: for cmap in colormaps: try: info['color_maps'].append({ 'key': cmap, 'color_map': colormap.get(cmap).values(), 'label': cmap_labels.get(cmap, cmap) }) except FileNotFoundError: raise exceptions.ValidationError( "Not a valid color_map value: %s" % cmap) info['name'] = task.name info['scheme'] = 'xyz' info['tiles'] = [ get_tile_url(task, tile_type, self.request.query_params) ] if info['maxzoom'] < info['minzoom']: info['maxzoom'] = info['minzoom'] info['maxzoom'] += ZOOM_EXTRA_LEVELS info['minzoom'] -= ZOOM_EXTRA_LEVELS info['bounds'] = {'value': src.bounds, 'crs': src.dataset.crs} return Response(info)
def _img( mosaicid: str = None, z: int = None, x: int = None, y: int = None, scale: int = 1, ext: str = None, url: str = None, indexes: Optional[Sequence[int]] = None, rescale: str = None, color_ops: str = None, color_map: str = None, pixel_selection: str = "first", resampling_method: str = "nearest", ) -> Tuple: """Handle tile 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: assets = mosaic.tile(x, y, z) if not assets: return ("EMPTY", "text/plain", f"No assets found for tile {z}-{x}-{y}") if indexes is not None and isinstance(indexes, str): indexes = list(map(int, indexes.split(","))) tilesize = 256 * scale if pixel_selection == "last": pixel_selection = "first" assets = list(reversed(assets)) with rasterio.Env(aws_session): pixsel_method = PIXSEL_METHODS[pixel_selection] tile, mask = mosaic_tiler( assets, x, y, z, cogeoTiler, indexes=indexes, tilesize=tilesize, pixel_selection=pixsel_method(), resampling_method=resampling_method, ) if tile is None: return ("EMPTY", "text/plain", "empty tiles") rtile = _postprocess(tile, mask, rescale=rescale, color_formula=color_ops) if not ext: ext = "jpg" if mask.all() else "png" driver = "jpeg" if ext == "jpg" else ext options = img_profiles.get(driver, {}) if ext == "tif": ext = "tiff" driver = "GTiff" options = geotiff_options(x, y, z, tilesize) if color_map: options["colormap"] = cmap.get(color_map) return ( "OK", f"image/{ext}", render(rtile, mask, img_format=driver, **options), )
def make_colormap(name): """Use rio-tiler colormap to create matplotlib colormap """ colormap = make_lut(cmap.get(name)) # rescale to 0-1 return ListedColormap(colormap / 255, name=name)
def export_raster(input, output, **opts): epsg = opts.get('epsg') expression = opts.get('expression') export_format = opts.get('format') rescale = opts.get('rescale') color_map = opts.get('color_map') hillshade = opts.get('hillshade') asset_type = opts.get('asset_type') name = opts.get('name', 'raster') # KMZ specific dem = asset_type in ['dsm', 'dtm'] with COGReader(input) as ds_src: src = ds_src.dataset profile = src.meta.copy() # Output format driver = "GTiff" compress = None max_bands = 9999 with_alpha = True rgb = False indexes = src.indexes output_raster = output jpg_background = 255 # white # KMZ is special, we just export it as jpg with EPSG:4326 # and then call GDAL to tile/package it kmz = export_format == "kmz" if kmz: export_format = "jpg" epsg = 4326 path_base, _ = os.path.splitext(output) output_raster = path_base + ".jpg" jpg_background = 0 # black if export_format == "jpg": driver = "JPEG" profile.update(quality=90) band_count = 3 with_alpha = False rgb = True elif export_format == "png": driver = "PNG" band_count = 4 rgb = True elif export_format == "gtiff-rgb": compress = "JPEG" profile.update(jpeg_quality=90) band_count = 4 rgb = True else: compress = "DEFLATE" band_count = src.count if compress is not None: profile.update(compress=compress) profile.update(predictor=2 if compress == "DEFLATE" else 1) if rgb and rescale is None: # Compute min max nodata = None if asset_type == 'orthophoto': nodata = 0 md = ds_src.metadata(pmin=2.0, pmax=98.0, hist_options={"bins": 255}, nodata=nodata) rescale = [md['statistics']['1']['min'], md['statistics']['1']['max']] ci = src.colorinterp if rgb and expression is None: # More than 4 bands? if len(ci) > 4: # Try to find RGBA band order if ColorInterp.red in ci and \ ColorInterp.green in ci and \ ColorInterp.blue in ci and \ ColorInterp.alpha in ci: indexes = (ci.index(ColorInterp.red) + 1, ci.index(ColorInterp.green) + 1, ci.index(ColorInterp.blue) + 1, ci.index(ColorInterp.alpha) + 1) if ColorInterp.alpha in ci: mask = src.read(ci.index(ColorInterp.alpha) + 1) else: mask = src.dataset_mask() cmap = None if color_map: try: cmap = colormap.get(color_map) except InvalidColorMapName: logger.warning("Invalid colormap {}".format(color_map)) def process(arr, skip_rescale=False, skip_alpha=False, skip_type=False): if not skip_rescale and rescale is not None: arr = linear_rescale(arr, in_range=rescale) if not skip_alpha and not with_alpha: arr[mask==0] = jpg_background if not skip_type and rgb and arr.dtype != np.uint8: arr = arr.astype(np.uint8) return arr def update_rgb_colorinterp(dst): if with_alpha: dst.colorinterp = [ColorInterp.red, ColorInterp.green, ColorInterp.blue, ColorInterp.alpha] else: dst.colorinterp = [ColorInterp.red, ColorInterp.green, ColorInterp.blue] profile.update(driver=driver, count=band_count) if rgb: profile.update(dtype=rasterio.uint8) if dem and rgb and profile.get('nodata') is not None: profile.update(nodata=None) # Define write band function # Reprojection needed? if src.crs is not None and epsg is not None and src.crs.to_epsg() != epsg: dst_crs = "EPSG:{}".format(epsg) transform, width, height = calculate_default_transform( src.crs, dst_crs, src.width, src.height, *src.bounds) profile.update( crs=dst_crs, transform=transform, width=width, height=height ) def write_band(arr, dst, band_num): reproject(source=arr, destination=rasterio.band(dst, band_num), src_transform=src.transform, src_crs=src.crs, dst_transform=transform, dst_crs=dst_crs, resampling=Resampling.nearest) else: # No reprojection needed def write_band(arr, dst, band_num): dst.write(arr, band_num) if expression is not None: # Apply band math if rgb: profile.update(dtype=rasterio.uint8, count=band_count) else: profile.update(dtype=rasterio.float32, count=1, nodata=-9999) bands_names = ["b{}".format(b) for b in tuple(sorted(set(re.findall(r"b(?P<bands>[0-9]{1,2})", expression))))] rgb_expr = expression.split(",") indexes = tuple([int(b.replace("b", "")) for b in bands_names]) alpha_index = None if has_alpha_band(src): try: alpha_index = src.colorinterp.index(ColorInterp.alpha) + 1 indexes += (alpha_index, ) except ValueError: pass data = src.read(indexes=indexes, out_dtype=np.float32) arr = dict(zip(bands_names, data)) arr = np.array([np.nan_to_num(ne.evaluate(bloc.strip(), local_dict=arr)) for bloc in rgb_expr]) # Set nodata values index_band = arr[0] if alpha_index is not None: # -1 is the last band = alpha index_band[data[-1] == 0] = -9999 # Remove infinity values index_band[index_band>1e+30] = -9999 index_band[index_band<-1e+30] = -9999 # Make sure this is float32 arr = arr.astype(np.float32) with rasterio.open(output_raster, 'w', **profile) as dst: # Apply colormap? if rgb and cmap is not None: rgb_data, _ = apply_cmap(process(arr, skip_alpha=True), cmap) band_num = 1 for b in rgb_data: write_band(process(b, skip_rescale=True), dst, band_num) band_num += 1 if with_alpha: write_band(mask, dst, band_num) update_rgb_colorinterp(dst) else: # Raw write_band(process(arr)[0], dst, 1) elif dem: # Apply hillshading, colormaps to elevation with rasterio.open(output_raster, 'w', **profile) as dst: arr = src.read() intensity = None if hillshade is not None and hillshade > 0: delta_scale = (ZOOM_EXTRA_LEVELS + 1) * 4 dx = src.meta["transform"][0] * delta_scale dy = -src.meta["transform"][4] * delta_scale ls = LightSource(azdeg=315, altdeg=45) intensity = ls.hillshade(arr[0], dx=dx, dy=dy, vert_exag=hillshade) intensity = intensity * 255.0 # Apply colormap? if rgb and cmap is not None: rgb_data, _ = apply_cmap(process(arr, skip_alpha=True), cmap) if intensity is not None: rgb_data = hsv_blend(rgb_data, intensity) band_num = 1 for b in rgb_data: write_band(process(b, skip_rescale=True), dst, band_num) band_num += 1 if with_alpha: write_band(mask, dst, band_num) update_rgb_colorinterp(dst) else: # Raw write_band(process(arr)[0], dst, 1) else: # Copy bands as-is with rasterio.open(output_raster, 'w', **profile) as dst: band_num = 1 for idx in indexes: ci = src.colorinterp[idx - 1] arr = src.read(idx) if ci == ColorInterp.alpha: if with_alpha: write_band(arr, dst, band_num) band_num += 1 else: write_band(process(arr), dst, band_num) band_num += 1 new_ci = [src.colorinterp[idx - 1] for idx in indexes] if not with_alpha: new_ci = [ci for ci in new_ci if ci != ColorInterp.alpha] dst.colorinterp = new_ci if kmz: subprocess.check_output(["gdal_translate", "-of", "KMLSUPEROVERLAY", "-co", "Name={}".format(name), "-co", "FORMAT=JPEG", output_raster, output])
def __post_init__(self): """Post Init.""" self.colormap = cmap.get( self.color_map.value) if self.color_map else None self.rescale_range = (list(map(float, self.rescale.split(","))) if self.rescale else None)
def __post_init__(self): """Post Init.""" self.colormap = cmap.get( self.color_map.value) if self.color_map else None
def _tile( z: int, x: int, y: int, scale: int = 1, ext: str = None, url: str = None, indexes: Optional[Union[str, Tuple]] = None, expr: Optional[str] = None, nodata: Optional[Union[str, int, float]] = None, rescale: Optional[str] = None, color_formula: Optional[str] = None, color_map: Optional[str] = None, resampling_method: str = "bilinear", **kwargs, ) -> Tuple[str, str, bytes]: """Handle /tiles requests.""" if indexes and expr: raise TilerError("Cannot pass indexes and expression") if not url: raise TilerError("Missing 'url' parameter") if nodata is not None: nodata = numpy.nan if nodata == "nan" else float(nodata) tilesize = scale * 256 if isinstance(indexes, str): indexes = tuple(map(int, indexes.split(","))) with rasterio.Env(aws_session): with COGReader(url) as cog: tile, mask = cog.tile( x, y, z, tilesize=tilesize, indexes=indexes, expression=expr, nodata=nodata, resampling_method=resampling_method, **kwargs, ) color_map = cmap.get(color_map) if color_map else cog.colormap if not ext: ext = "jpg" if mask.all() else "png" tile = utils.postprocess(tile, mask, rescale=rescale, color_formula=color_formula) if ext == "npy": sio = io.BytesIO() numpy.save(sio, (tile, mask)) sio.seek(0) content = sio.getvalue() else: driver = drivers[ext] options = img_profiles.get(driver.lower(), {}) if ext == "tif": options = geotiff_options(x, y, z, tilesize=tilesize) if color_map: options["colormap"] = color_map content = render(tile, mask, img_format=driver, **options) return ("OK", mimetype[ext], content)
def data(self): """Return rio-tiler image default profile.""" return cmap.get(self._name_)