def test_cutline(): """Test rio_tiler.utils.create_cutline.""" feat = { "type": "Feature", "properties": {}, "geometry": { "type": "Polygon", "coordinates": [[ [-52.6025390625, 73.86761239709705], [-52.6025390625, 73.59679245247814], [-51.591796875, 73.60299628304274], [-51.591796875, 73.90420357134279], [-52.4267578125, 74.0437225981325], [-52.6025390625, 73.86761239709705], ]], }, } feature_bounds = featureBounds(feat) with COGReader(COGEO) as cog: cutline = utils.create_cutline(cog.dataset, feat, geometry_crs="epsg:4326") data, mask = cog.part(feature_bounds, vrt_options={"cutline": cutline}) assert not mask.all() cutline = utils.create_cutline(cog.dataset, feat["geometry"], geometry_crs="epsg:4326") data, mask = cog.part(feature_bounds, vrt_options={"cutline": cutline}) assert not mask.all() feat_line = { "type": "Feature", "properties": {}, "geometry": { "type": "LineString", "coordinates": [ [-55.37109374999999, 74.17607298699065], [-53.85498046874999, 75.06734898853098], [-54.16259765625, 75.11822201684025], [-54.228515625, 75.23066741281573], ], }, } with COGReader(COGEO) as cog: with pytest.raises(RioTilerError): utils.create_cutline(cog.dataset, feat_line, geometry_crs="epsg:4326")
def __enter__(self): """Support using with Context Managers.""" self.scene_params = s1_sceneid_parser(self.sceneid) prefix = self._prefix.format(**self.scene_params) self.productInfo = json.loads( get_object(self._hostname, f"{prefix}/productInfo.json", request_pays=True)) self.datageom = self.productInfo["footprint"] self.bounds = featureBounds(self.datageom) return self
def feature( self, shape: Dict, dst_crs: Optional[CRS] = None, shape_crs: CRS = WGS84_CRS, indexes: Optional[Indexes] = None, expression: Optional[str] = None, max_size: Optional[int] = None, height: Optional[int] = None, width: Optional[int] = None, **kwargs: Any, ) -> ImageData: """Read part of a COG defined by a geojson feature. Args: shape (dict): Valid GeoJSON feature. dst_crs (rasterio.crs.CRS, optional): Overwrite target coordinate reference system. shape_crs (rasterio.crs.CRS, optional): Input geojson coordinate reference system. Defaults to `epsg:4326`. indexes (sequence of int or int, optional): Band indexes. expression (str, optional): rio-tiler expression (e.g. b1/b2+b3). max_size (int, optional): Limit the size of the longest dimension of the dataset read, respecting bounds X/Y aspect ratio. height (int, optional): Output height of the array. width (int, optional): Output width of the array. kwargs (optional): Options to forward to the `COGReader.part` method. Returns: rio_tiler.models.ImageData: ImageData instance with data, mask and input spatial info. """ if not dst_crs: dst_crs = shape_crs # Get BBOX of the polygon bbox = featureBounds(shape) cutline = create_cutline(self.dataset, shape, geometry_crs=shape_crs) vrt_options = kwargs.pop("vrt_options", {}) vrt_options.update({"cutline": cutline}) return self.part( bbox, dst_crs=dst_crs, bounds_crs=shape_crs, indexes=indexes, expression=expression, max_size=max_size, width=width, height=height, vrt_options=vrt_options, **kwargs, )
def __enter__(self): """Support using with Context Managers.""" self.scene_params = s2_sceneid_parser(self.sceneid) prefix = self._prefix.format(**self.scene_params) self.tileInfo = json.loads( get_object(self._hostname, f"{prefix}/tileInfo.json", request_pays=True)) input_geom = self.tileInfo["tileDataGeometry"] input_crs = CRS.from_user_input( input_geom["crs"]["properties"]["name"]) self.datageom = transform_geom(input_crs, constants.WGS84_CRS, input_geom) self.bounds = featureBounds(self.datageom) return self
def feature( self, shape: Dict, dst_crs: Optional[CRS] = None, shape_crs: CRS = constants.WGS84_CRS, max_size: int = 1024, indexes: Optional[Union[int, Sequence]] = None, expression: Optional[str] = "", **kwargs: Any, ) -> ImageData: """ Read a COG for a geojson feature. Attributes ---------- shape: dict Valid GeoJSON feature. dst_crs: CRS or str, optional Target coordinate reference system, default is the bbox CRS. shape_crs: CRS or str, optional shape coordinate reference system, default is "epsg:4326" max_size: int, optional Limit output size array, default is 1024. indexes: int or sequence of int Band indexes (e.g. 1 or (1, 2, 3)) expression: str rio-tiler expression (e.g. b1/b2+b3) kwargs: dict, optional These will be passed to the 'rio_tiler.reader.part' function. Returns ------- data: numpy ndarray mask: numpy array """ kwargs = {**self._kwargs, **kwargs} if isinstance(indexes, int): indexes = (indexes,) if indexes and expression: warnings.warn( "Both expression and indexes passed; expression will overwrite indexes parameter.", ExpressionMixingWarning, ) if expression: indexes = parse_expression(expression) if not dst_crs: dst_crs = shape_crs # Get BBOX of the polygon bbox = featureBounds(shape) cutline = create_cutline(self.dataset, shape, geometry_crs=shape_crs) vrt_options = kwargs.pop("vrt_options", {}) vrt_options.update({"cutline": cutline}) data, mask = reader.part( self.dataset, bbox, max_size=max_size, bounds_crs=shape_crs, dst_crs=dst_crs, indexes=indexes, vrt_options=vrt_options, **kwargs, ) if expression: blocks = expression.lower().split(",") bands = [f"b{bidx}" for bidx in indexes] data = apply_expression(blocks, bands, data) if dst_crs == shape_crs: bounds = bbox else: bounds = transform_bounds(shape_crs, dst_crs, *bbox, densify_pts=21) return ImageData( data, mask, bounds=bounds, crs=dst_crs, assets=[self.filepath], )
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 test_cutline(): """Test rio_tiler.utils.create_cutline.""" feat = { "type": "Feature", "properties": {}, "geometry": { "type": "Polygon", "coordinates": [[ [-52.6025390625, 73.86761239709705], [-52.6025390625, 73.59679245247814], [-51.591796875, 73.60299628304274], [-51.591796875, 73.90420357134279], [-52.4267578125, 74.0437225981325], [-52.6025390625, 73.86761239709705], ]], }, } feature_bounds = featureBounds(feat) with COGReader(COGEO) as cog: cutline = utils.create_cutline(cog.dataset, feat, geometry_crs="epsg:4326") data, mask = cog.part(feature_bounds, vrt_options={"cutline": cutline}) assert not mask.all() cutline = utils.create_cutline(cog.dataset, feat["geometry"], geometry_crs="epsg:4326") data, mask = cog.part(feature_bounds, vrt_options={"cutline": cutline}) assert not mask.all() feat_line = { "type": "Feature", "properties": {}, "geometry": { "type": "LineString", "coordinates": [ [-55.37109374999999, 74.17607298699065], [-53.85498046874999, 75.06734898853098], [-54.16259765625, 75.11822201684025], [-54.228515625, 75.23066741281573], ], }, } with COGReader(COGEO) as cog: with pytest.raises(RioTilerError): utils.create_cutline(cog.dataset, feat_line, geometry_crs="epsg:4326") feat_mp = { "type": "MultiPolygon", "coordinates": [ [[ [7.305908203125, 52.14697334064471], [7.84423828125, 52.14697334064471], [7.84423828125, 52.52958999943304], [7.305908203125, 52.52958999943304], [7.305908203125, 52.14697334064471], ]], [[ [9.920654296875, 53.25206880589411], [10.404052734375, 53.25206880589411], [10.404052734375, 53.48804553605622], [9.920654296875, 53.48804553605622], [9.920654296875, 53.25206880589411], ]], ], } with COGReader(COGEO) as cog: c = utils.create_cutline(cog.dataset, feat_mp, geometry_crs="epsg:4326") assert "MULTIPOLYGON" in c bad_poly = { "type": "Polygon", "coordinates": [ [[ [7.305908203125, 52.14697334064471], [7.84423828125, 52.14697334064471], [7.84423828125, 52.52958999943304], [7.305908203125, 52.52958999943304], [7.305908203125, 52.14697334064471], ]], ], } with COGReader(COGEO) as cog: with pytest.raises(RioTilerError): utils.create_cutline(cog.dataset, bad_poly, geometry_crs="epsg:4326")
def feature( self, shape: Dict, dst_crs: Optional[CRS] = None, shape_crs: CRS = constants.WGS84_CRS, max_size: int = 1024, indexes: Optional[Union[int, Sequence]] = None, expression: Optional[str] = None, **kwargs: Any, ) -> ImageData: """Read part of a COG defined by a geojson feature. Args: shape (dict): Valid GeoJSON feature. dst_crs (rasterio.crs.CRS, optional): Overwrite target coordinate reference system. shape_crs (rasterio.crs.CRS, optional): Input geojson coordinate reference system. Defaults to `epsg:4326`. max_size (int, optional): Limit the size of the longest dimension of the dataset read, respecting bounds X/Y aspect ratio. Defaults to `1024`. indexes (sequence of int or int, optional): Band indexes. expression (str, optional): rio-tiler expression (e.g. b1/b2+b3). kwargs (optional): Options to forward to the `rio_tiler.reader.part` function. Returns: rio_tiler.models.ImageData: ImageData instance with data, mask and input spatial info. """ kwargs = {**self._kwargs, **kwargs} if isinstance(indexes, int): indexes = (indexes, ) if indexes and expression: warnings.warn( "Both expression and indexes passed; expression will overwrite indexes parameter.", ExpressionMixingWarning, ) if expression: indexes = parse_expression(expression) if not dst_crs: dst_crs = shape_crs # Get BBOX of the polygon bbox = featureBounds(shape) cutline = create_cutline(self.dataset, shape, geometry_crs=shape_crs) vrt_options = kwargs.pop("vrt_options", {}) vrt_options.update({"cutline": cutline}) data, mask = reader.part( self.dataset, bbox, max_size=max_size, bounds_crs=shape_crs, dst_crs=dst_crs, indexes=indexes, vrt_options=vrt_options, **kwargs, ) if expression: blocks = expression.lower().split(",") bands = [f"b{bidx}" for bidx in indexes] data = apply_expression(blocks, bands, data) if dst_crs == shape_crs: bounds = bbox else: bounds = transform_bounds(shape_crs, dst_crs, *bbox, densify_pts=21) return ImageData( data, mask, bounds=bounds, crs=dst_crs, assets=[self.filepath], )
def get_grid_bbox(name: str) -> Tuple[float, float, float, float]: """Get grid bbox.""" feat = list( filter(lambda x: x["properties"]["name"] == name, mgrs_grid["features"]) )[0] return featureBounds(feat["geometry"])