Exemple #1
0
 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
Exemple #2
0
        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])
Exemple #3
0
    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)
Exemple #4
0
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
Exemple #6
0
 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
Exemple #7
0
    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
                })
Exemple #8
0
    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))
Exemple #9
0
    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)
Exemple #10
0
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),
    )
Exemple #11
0
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)
Exemple #12
0
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])
Exemple #13
0
 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)
Exemple #14
0
 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)
Exemple #16
0
 def data(self):
     """Return rio-tiler image default profile."""
     return cmap.get(self._name_)