def test_transform_bounds_densify(): # This transform is non-linear along the edges, so densification produces # a different result than otherwise src_crs = {'init': 'EPSG:4326'} dst_crs = {'init': 'EPSG:32610'} assert numpy.allclose( transform_bounds( src_crs, dst_crs, -120, 40, -80, 64, densify_pts=0 ), ( 646695.227266598, 4432069.056898901, 4201818.984205882, 7807592.187464975 ) ) assert numpy.allclose( transform_bounds( src_crs, dst_crs, -120, 40, -80, 64, densify_pts=100 ), ( 646695.2272665979, 4432069.056898901, 4201818.984205882, 7807592.187464977 ) )
def test_transform_bounds_densify_out_of_bounds(): with pytest.raises(ValueError): transform_bounds( {'init': 'EPSG:4326'}, {'init': 'EPSG:32610'}, -120, 40, -80, 64, densify_pts=-10 )
def test_transform_bounds(): """CRSError is raised.""" left, bottom, right, top = ( -11740727.544603072, 4852834.0517692715, -11584184.510675032, 5009377.085697309) src_crs = 'EPSG:3857' dst_crs = {'proj': 'foobar'} with pytest.raises(CRSError): transform_bounds(src_crs, dst_crs, left, bottom, right, top)
def test_transform_bounds_densify_out_of_bounds(): with pytest.raises(ValueError): transform_bounds( {"init": "epsg:4326"}, {"init": "epsg:32610"}, -120, 40, -80, 64, densify_pts=-10, )
def test_transform_bounds_densify(): # This transform is non-linear along the edges, so densification produces # a different result than otherwise src_crs = {"init": "epsg:4326"} dst_crs = {"init": "epsg:2163"} assert np.allclose( transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=0), (-1684649.41338, -350356.81377, 1684649.41338, 2234551.18559), ) assert np.allclose( transform_bounds(src_crs, dst_crs, -120, 40, -80, 64, densify_pts=100), (-1684649.41338, -555777.79210, 1684649.41338, 2234551.18559), )
def generate_chunk_tasks(image_source, tile_dim): tasks = [] zoom = image_source.zoom (min_col, max_col) = (image_source.tile_bounds[0], image_source.tile_bounds[2]) (min_row, max_row) = (image_source.tile_bounds[1], image_source.tile_bounds[3]) for tile_col in range(min_col, min(max_col + 1, 2**zoom)): for tile_row in range(min_row, min(max_row + 1, 2**zoom)): tile_bounds = mercantile.bounds(tile_col, tile_row, zoom) (wm_left, wm_bottom, wm_right, wm_top) = warp.transform_bounds("EPSG:4326", "EPSG:3857", tile_bounds.west, tile_bounds.south, tile_bounds.east, tile_bounds.north) affine = transform.from_bounds(wm_left, wm_bottom, wm_right, wm_top, tile_dim, tile_dim) target_meta = { "transform": affine[:6], "width": tile_dim, "height": tile_dim } target = os.path.join(image_source.image_folder, "%d/%d/%d.tif" % (zoom, tile_col, tile_row)) task = ChunkTask(source_uri=image_source.source_uri, target_meta=target_meta, target=target) tasks.append(task) return tasks
def test_transform_bounds__esri_wkt(): left, bottom, right, top = \ (-78.95864996545055, 23.564991210854686, -76.57492370013823, 25.550873767433984) dst_projection_string = ( 'PROJCS["USA_Contiguous_Albers_Equal_Area_Conic_USGS_version",' 'GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",' 'SPHEROID["GRS_1980",6378137.0,298.257222101]],' 'PRIMEM["Greenwich",0.0],' 'UNIT["Degree",0.0174532925199433]],' 'PROJECTION["Albers"],' 'PARAMETER["false_easting",0.0],' 'PARAMETER["false_northing",0.0],' 'PARAMETER["central_meridian",-96.0],' 'PARAMETER["standard_parallel_1",29.5],' 'PARAMETER["standard_parallel_2",45.5],' 'PARAMETER["latitude_of_origin",23.0],' 'UNIT["Meter",1.0],' 'VERTCS["NAVD_1988",' 'VDATUM["North_American_Vertical_Datum_1988"],' 'PARAMETER["Vertical_Shift",0.0],' 'PARAMETER["Direction",1.0],UNIT["Centimeter",0.01]]]') assert np.allclose( transform_bounds({"init": "epsg:4326"}, dst_projection_string, left, bottom, right, top), ( 1721263.7931814701, 219684.49332178483, 2002926.56696663, 479360.16562217404), )
def test_transform_bounds_no_change(): """Make sure that going from and to the same crs causes no change.""" with rasterio.open('tests/data/RGB.byte.tif') as src: l, b, r, t = src.bounds assert np.allclose( transform_bounds(src.crs, src.crs, l, b, r, t), src.bounds )
def get_bounds(path): """ Retrun bounds in WGS84 system """ with rasterio.drivers(): src = rasterio.open(path) return transform_bounds( src.crs, {'init': 'EPSG:4326'}, *src.bounds)
def test_transform_bounds(): with rasterio.open('tests/data/RGB.byte.tif') as src: l, b, r, t = src.bounds assert np.allclose( transform_bounds(src.crs, {'init': 'EPSG:4326'}, l, b, r, t), ( -78.95864996545055, 23.564991210854686, -76.57492370013823, 25.550873767433984 ) )
def clip(self): """ Clip images based on bounds provided Implementation is borrowed from https://github.com/brendan-ward/rasterio/blob/e3687ce0ccf8ad92844c16d913a6482d5142cf48/rasterio/rio/convert.py """ self.output("Clipping", normal=True) # create new folder for clipped images path = check_create_folder(join(self.scene_path, 'clipped')) try: temp_bands = copy(self.bands) temp_bands.append('QA') for i, band in enumerate(temp_bands): band_name = self._get_full_filename(band) band_path = join(self.scene_path, band_name) self.output("Band %s" % band, normal=True, color='green', indent=1) with rasterio.open(band_path) as src: bounds = transform_bounds( { 'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84', 'no_defs': True }, src.crs, *self.bounds ) if disjoint_bounds(bounds, src.bounds): bounds = adjust_bounding_box(src.bounds, bounds) window = src.window(*bounds) out_kwargs = src.meta.copy() out_kwargs.update({ 'driver': 'GTiff', 'height': window[0][1] - window[0][0], 'width': window[1][1] - window[1][0], 'transform': src.window_transform(window) }) with rasterio.open(join(path, band_name), 'w', **out_kwargs) as out: out.write(src.read(window=window)) # Copy MTL to the clipped folder copyfile(join(self.scene_path, self.scene + '_MTL.txt'), join(path, self.scene + '_MTL.txt')) return path except IOError as e: exit(e.message, 1)
def test_transform_bounds(): with rasterio.open("tests/data/RGB.byte.tif") as src: l, b, r, t = src.bounds assert np.allclose( transform_bounds(src.crs, {"init": "epsg:4326"}, l, b, r, t), ( -78.95864996545055, 23.564991210854686, -76.57492370013823, 25.550873767433984, ), )
def tile( src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT], x: int, y: int, z: int, tilesize: int = 256, **kwargs, ) -> Tuple[numpy.ndarray, numpy.ndarray]: """ Read mercator tile from an image. Attributes ---------- src_dst : rasterio.io.DatasetReader rasterio.io.DatasetReader object x : int Mercator tile X index. y : int Mercator tile Y index. z : int Mercator tile ZOOM level. tilesize : int, optional Output tile size. Default is 256. kwargs : Any, optional Additional options to forward to part() Returns ------- data : numpy ndarray mask: numpy array """ warnings.warn( "'rio_tiler.reader.tile' will be deprecated in rio-tiler 2.0", DeprecationWarning, ) bounds = transform_bounds( src_dst.crs, constants.WGS84_CRS, *src_dst.bounds, densify_pts=21 ) if not tile_exists(bounds, z, x, y): raise TileOutsideBounds(f"Tile {z}/{x}/{y} is outside image bounds") tile_bounds = mercantile.xy_bounds(mercantile.Tile(x=x, y=y, z=z)) return part( src_dst, tile_bounds, tilesize, tilesize, dst_crs=constants.WEB_MERCATOR_CRS, **kwargs, )
def get_zooms( src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT], ensure_global_max_zoom: bool = False, tilesize: int = 256, ) -> Tuple[int, int]: """ Calculate raster min/max mercator zoom level. Parameters ---------- src_dst: rasterio.io.DatasetReader Rasterio io.DatasetReader object ensure_global_max_zoom: bool, optional Apply latitude correction factor to ensure max_zoom equality for global datasets covering different latitudes (default: False). tilesize: int, optional Mercator tile size (default: 256). Returns ------- min_zoom, max_zoom: Tuple Min/Max Mercator zoom levels. """ bounds = transform_bounds(src_dst.crs, constants.WGS84_CRS, *src_dst.bounds, densify_pts=21) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] lat = center[1] if ensure_global_max_zoom else 0 dst_affine, w, h = calculate_default_transform( src_dst.crs, constants.WEB_MERCATOR_CRS, src_dst.width, src_dst.height, *src_dst.bounds, ) mercator_resolution = max(abs(dst_affine[0]), abs(dst_affine[4])) # Correction factor for web-mercator projection latitude scale change latitude_correction_factor = math.cos(math.radians(lat)) adjusted_resolution = mercator_resolution * latitude_correction_factor max_zoom = zoom_for_pixelsize(adjusted_resolution, tilesize=tilesize) overview_level = get_maximum_overview_level(w, h, minsize=tilesize) ovr_resolution = adjusted_resolution * (2**overview_level) min_zoom = zoom_for_pixelsize(ovr_resolution, tilesize=tilesize) return (min_zoom, max_zoom)
def tile(address, tile_x, tile_y, tile_z, indexes=None, tilesize=256, nodata=None): """ Create mercator tile from any images. Attributes ---------- address : str file url. tile_x : int Mercator tile X index. tile_y : int Mercator tile Y index. tile_z : int Mercator tile ZOOM level. indexes : tuple, int, optional (default: (1, 2, 3)) Bands indexes for the RGB combination. tilesize : int, optional (default: 256) Output image size. nodata: int or float, optional Overwrite nodata value for mask creation. Returns ------- data : numpy ndarray mask: numpy array """ with rasterio.open(address) as src: wgs_bounds = transform_bounds(*[src.crs, 'epsg:4326'] + list(src.bounds), densify_pts=21) indexes = indexes if indexes is not None else src.indexes if not utils.tile_exists(wgs_bounds, tile_z, tile_x, tile_y): raise TileOutsideBounds( 'Tile {}/{}/{} is outside image bounds'.format( tile_z, tile_x, tile_y)) mercator_tile = mercantile.Tile(x=tile_x, y=tile_y, z=tile_z) tile_bounds = mercantile.xy_bounds(mercator_tile) return utils.tile_read(src, tile_bounds, tilesize, indexes=indexes, nodata=nodata)
def get_sources(self, bounds, resolution): bounds, bounds_crs = bounds zoom = get_zoom(max(resolution)) left, bottom, right, top = warp.transform_bounds( bounds_crs, WGS84_CRS, *bounds) if (self._bounds[0] <= left <= self._bounds[2] or self._bounds[0] <= right <= self._bounds[2]) and ( self._bounds[1] <= bottom <= self._bounds[3] or self._bounds[1] <= top <= self._bounds[3]) and ( self._minzoom <= zoom <= self._maxzoom): yield Source(self._source, self._name, self._resolution, {}, {}, {"imagery": True})
def test_get_bounding_box(): src_crs = CRS(init='epsg:4326') dst_crs = CRS(init='epsg:32718') src_bounds = dict(xmin=-73.309037, ymin=-40.665865, xmax=-72.723835, ymax=-40.026434) gv = GeoVector.from_bounds(crs=src_crs, **src_bounds) bounds = transform_bounds(src_crs=src_crs, dst_crs=dst_crs, left=src_bounds['xmin'], bottom=src_bounds['ymin'], right=src_bounds['xmax'], top=src_bounds['ymax']) assert gv.get_bounding_box(dst_crs).almost_equals( GeoVector.from_bounds(*bounds, crs=dst_crs))
def metadata(sceneid, pmin=2, pmax=98, **kwargs): """ Retrieve image bounds and band statistics. Attributes ---------- sceneid : str Sentinel-2 sceneid. pmin : int, optional, (default: 2) Histogram minimum cut. pmax : int, optional, (default: 98) Histogram maximum cut. kwargs : optional These are passed to 'rio_tiler.sentinel2._sentinel_stats' e.g: histogram_bins=20' Returns ------- out : dict Dictionary with image bounds and bands statistics. """ scene_params = _sentinel_parse_scene_id(sceneid) path_prefix = os.path.join(scene_params["aws_bucket"], scene_params["aws_prefix"]) preview_file = os.path.join(path_prefix, scene_params["preview_file"]) dst_crs = CRS({"init": "EPSG:4326"}) with rasterio.open(preview_file) as src: bounds = transform_bounds(*[src.crs, dst_crs] + list(src.bounds), densify_pts=21) info = {"sceneid": sceneid} info["bounds"] = {"value": bounds, "crs": dst_crs.to_string()} addresses = [ "{}/{}/B{}.jp2".format(path_prefix, scene_params["preview_prefix"], band) for band in scene_params["bands"] ] _stats_worker = partial(_sentinel_stats, percentiles=(pmin, pmax), **kwargs) with futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: responses = executor.map(_stats_worker, addresses) info["statistics"] = { b: v for b, d in zip(scene_params["bands"], responses) for k, v in d.items() } return info
def _get_warped_array( input_file=None, band_idx=None, dst_bounds=None, dst_shape=None, dst_affine=None, dst_crs=None, resampling="nearest" ): """Extract a numpy array from a raster file.""" LOGGER.debug("read array using rasterio") with rasterio.open(input_file, "r") as src: if dst_crs == src.crs: src_left, src_bottom, src_right, src_top = dst_bounds else: # Return empty array if destination bounds don't intersect with # file bounds. file_bbox = box(*src.bounds) tile_bbox = reproject_geometry( box(*dst_bounds), src_crs=dst_crs, dst_crs=src.crs) if not file_bbox.intersects(tile_bbox): LOGGER.debug("file bounding box does not intersect with tile") return ma.MaskedArray( data=ma.zeros(dst_shape, dtype=src.profile["dtype"]), mask=ma.ones(dst_shape), fill_value=src.nodata) # Reproject tile bounds to source file SRS. src_left, src_bottom, src_right, src_top = transform_bounds( dst_crs, src.crs, *dst_bounds, densify_pts=21) if float('Inf') in (src_left, src_bottom, src_right, src_top): # Maybe not the best way to deal with it, but if bounding box # cannot be translated, it is assumed that data is emtpy LOGGER.debug("tile seems to be outside of input CRS bounds") return ma.MaskedArray( data=ma.zeros(dst_shape, dtype=src.profile["dtype"]), mask=ma.ones(dst_shape), fill_value=src.nodata) # Read data window. window = src.window( src_left, src_bottom, src_right, src_top, boundless=True) start = time.time() src_band = src.read(band_idx, window=window, boundless=True) LOGGER.debug("window read in %ss" % round(time.time() - start, 3)) # Quick fix because None nodata is not allowed. nodataval = 0 if not src.nodata else src.nodata # Prepare reprojected array. dst_band = np.empty(dst_shape, src.dtypes[band_idx-1]) # Run rasterio's reproject(). start = time.time() reproject( src_band, dst_band, src_transform=src.window_transform(window), src_crs=src.crs, src_nodata=nodataval, dst_transform=dst_affine, dst_crs=dst_crs, dst_nodata=nodataval, resampling=RESAMPLING_METHODS[resampling]) LOGGER.debug( "window reprojected in %ss" % round(time.time() - start, 3)) return ma.MaskedArray(dst_band, mask=dst_band == nodataval)
def __call__(self): for i, path in enumerate(input): with rasterio.open(path) as src: bounds = src.bounds if dst_crs: bbox = transform_bounds(src.crs, dst_crs, *bounds) elif projection == 'mercator': bbox = transform_bounds(src.crs, {'init': 'epsg:3857'}, *bounds) elif projection == 'geographic': bbox = transform_bounds(src.crs, {'init': 'epsg:4326'}, *bounds) else: bbox = bounds if precision >= 0: bbox = [round(b, precision) for b in bbox] yield { 'type': 'Feature', 'bbox': bbox, 'geometry': { 'type': 'Polygon', 'coordinates': [[[bbox[0], bbox[1]], [bbox[2], bbox[1]], [bbox[2], bbox[3]], [bbox[0], bbox[3]], [bbox[0], bbox[1]]]] }, 'properties': { 'id': str(i), 'title': path, 'filename': os.path.basename(path) } } self._xs.extend(bbox[::2]) self._ys.extend(bbox[1::2])
def get_gz_scene_bounds(fn): assert _exists(fn), fn assert fn.endswith('.tar.gz') tf = tarfile.open(fn, 'r|gz') member = tf.next() while not member.name.lower().endswith('.tif'): member = tf.next() data = io.BytesIO(tf.extractfile(member).read()) ds = MemoryFile(data).open() return transform_bounds(ds.crs, 'EPSG:4326', *ds.bounds)
def get_zoom(input): input = re.sub("s3://([^/]+)/", "http://\\1.s3.amazonaws.com/", input) with rasterio.Env(): with rasterio.open(input) as src: # grab the lowest resolution dimension if src.crs.is_geographic: # convert from degrees to "meters" (close enough for our use, esp. since we're using "meters" too) bounds = transform_bounds(src.crs, "EPSG:3857", *src.bounds) resolution = max((bounds[2] - bounds[0]) / src.width, (bounds[3] - bounds[1]) / src.height) else: resolution = max((src.bounds.right - src.bounds.left) / src.width, (src.bounds.top - src.bounds.bottom) / src.height) return min(22, int(math.ceil(math.log((2 * math.pi * 6378137) / (resolution * 256)) / math.log(2))))
def tile(sceneid, tile_x, tile_y, tile_z, rgb=(7, 6, 5), tilesize=256): """Create mercator tile from CBERS data. Attributes ---------- sceneid : str CBERS sceneid. tile_x : int Mercator tile X index. tile_y : int Mercator tile Y index. tile_z : int Mercator tile ZOOM level. rgb : tuple, int, optional (default: ('5', '6', '7')) Bands index for the RGB combination. tilesize : int, optional (default: 256) Output image size. Returns ------- data : numpy ndarray mask: numpy array """ if not isinstance(rgb, tuple): rgb = tuple((rgb, )) scene_params = utils.cbers_parse_scene_id(sceneid) cbers_address = '{}/{}'.format(CBERS_BUCKET, scene_params['key']) with rasterio.open('{}/{}_BAND6.tif'.format(cbers_address, sceneid)) as src: wgs_bounds = transform_bounds( *[src.crs, 'epsg:4326'] + list(src.bounds), densify_pts=21) if not utils.tile_exists(wgs_bounds, tile_z, tile_x, tile_y): raise TileOutsideBounds('Tile {}/{}/{} is outside image bounds'.format( tile_z, tile_x, tile_y)) mercator_tile = mercantile.Tile(x=tile_x, y=tile_y, z=tile_z) tile_bounds = mercantile.xy_bounds(mercator_tile) addresses = ['{}/{}_BAND{}.tif'.format(cbers_address, sceneid, band) for band in rgb] _tiler = partial(utils.tile_band_worker, bounds=tile_bounds, tilesize=tilesize, nodata=0) with futures.ThreadPoolExecutor(max_workers=3) as executor: data, masks = zip(*list(executor.map(_tiler, addresses))) mask = np.all(masks, axis=0).astype(np.uint8) * 255 return np.concatenate(data), mask
def test_transform_bounds__ignore_inf(): # Depending on the GDAL version we might get an exception or inf values try: assert not numpy.isinf( transform_bounds( "EPSG:4326", "ESRI:102036", -180.0, -90.0, 180.0, 0.0, )).any() except CPLE_BaseError: pass
def test_transform_bounds__antimeridian(): transformed_bounds = transform_bounds( "EPSG:4167", "EPSG:3851", 160.6, -55.95, -171.2, -25.88, ) assert_almost_equal( transformed_bounds, (1722483.900174921, 5228058.6143420935, 4624385.494808555, 8692574.544944234), ) inverse_transformed_bounds = transform_bounds( "EPSG:3851", "EPSG:4167", *transformed_bounds, ) assert_almost_equal( inverse_transformed_bounds, (153.2799922, -56.7471249, -162.1813873, -24.6148194), )
def _grid_datasets(datasets, bounds_override, grid_proj, grid_size): tiles = defaultdict(list) for dataset in datasets: dataset_proj = dataset.crs dataset_bounds = dataset.bounds bounds = bounds_override or BoundingBox(*transform_bounds(dataset_proj, grid_proj, *dataset_bounds)) for y in range(int(bounds.bottom // grid_size[1]), int(bounds.top // grid_size[1]) + 1): for x in range(int(bounds.left // grid_size[0]), int(bounds.right // grid_size[0]) + 1): tile_index = (x, y) if _check_intersect(tile_index, grid_size, grid_proj, dataset_bounds, dataset_proj): tiles[tile_index].append(dataset) return tiles
async def test_cog_read(infile, create_cog_reader): async with create_cog_reader(infile) as cog: with rasterio.open(infile) as ds: _, zoom = get_zooms(ds) centroid = Polygon.from_bounds( *transform_bounds(cog.epsg, "EPSG:4326", *cog.bounds)).centroid tile = mercantile.tile(centroid.x, centroid.y, zoom) tile_native_bounds = transform_bounds("EPSG:4326", cog.epsg, *mercantile.bounds(tile)) arr = await cog.read(tile_native_bounds, (256, 256)) with cogeo_reader(infile) as ds: rio_tile_arr, rio_tile_mask = ds.tile(tile.x, tile.y, tile.z, tilesize=256, resampling_method="bilinear") if cog.is_masked: tile_arr = np.ma.getdata(arr) tile_mask = np.ma.getmask(arr) # Make sure image data is the same assert pytest.approx(tile_arr - rio_tile_arr, 1) == np.zeros(tile_arr.shape) # Make sure mask data is the same rio_mask_counts = np.unique(rio_tile_mask, return_counts=True) tile_mask_counts = np.unique(tile_mask, return_counts=True) assert len(rio_mask_counts[0]) == len(tile_mask_counts[0]) assert (rio_mask_counts[1][0] * cog.profile["count"] == tile_mask_counts[1][0]) else: assert pytest.approx(arr - rio_tile_arr, 1) == np.zeros(arr.shape)
def __call__(self): for i, path in enumerate(input): with rasterio.open(path) as src: bounds = src.bounds if dst_crs: bbox = transform_bounds(src.crs, dst_crs, *bounds) elif projection == 'mercator': bbox = transform_bounds(src.crs, {'init': 'epsg:3857'}, *bounds) elif projection == 'geographic': bbox = transform_bounds(src.crs, {'init': 'epsg:4326'}, *bounds) else: bbox = bounds if precision >= 0: bbox = [round(b, precision) for b in bbox] yield { 'type': 'Feature', 'bbox': bbox, 'geometry': { 'type': 'Polygon', 'coordinates': [[ [bbox[0], bbox[1]], [bbox[2], bbox[1]], [bbox[2], bbox[3]], [bbox[0], bbox[3]], [bbox[0], bbox[1]]]]}, 'properties': { 'id': str(i), 'title': path, 'filename': os.path.basename(path)}} self._xs.extend(bbox[::2]) self._ys.extend(bbox[1::2])
def test_cog_translate_web(): """ Test Web-Optimized COG. - Test COG size is a multiple of 256 (mercator tile size) - Test COG bounds are aligned with mercator grid at max zoom """ tms = morecantile.tms.get("WebMercatorQuad") runner = CliRunner() with runner.isolated_filesystem(): web_profile = cog_profiles.get("raw") web_profile.update({"blockxsize": 256, "blockysize": 256}) config = dict(GDAL_TIFF_OVR_BLOCKSIZE="256") cog_translate( raster_path_web, "cogeo.tif", web_profile, quiet=True, web_optimized=True, config=config, aligned_levels=0, ) with rasterio.open(raster_path_web) as src_dst: with rasterio.open("cogeo.tif") as out_dst: blocks = list(set(out_dst.block_shapes)) assert len(blocks) == 1 ts = blocks[0][0] assert not out_dst.width % ts assert not out_dst.height % ts _, max_zoom = get_zooms(out_dst) bounds = list( transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21)) ulTile = tms.xy_bounds(tms.tile(bounds[0], bounds[3], max_zoom)) assert out_dst.bounds.left == ulTile.left assert out_dst.bounds.top == ulTile.top lrTile = tms.xy_bounds(tms.tile(bounds[2], bounds[1], max_zoom)) assert out_dst.bounds.right == lrTile.right assert round(out_dst.bounds.bottom, 6) == round(lrTile.bottom, 6)
def test_nonearthbody(): """COGReader should work with non-earth dataset.""" with pytest.warns(UserWarning): with COGReader(COG_EUROPA) as cog: assert cog.minzoom == 0 assert cog.maxzoom == 24 with pytest.warns(None) as warnings: with COGReader(COG_EUROPA) as cog: assert cog.info() assert len(warnings) == 2 img = cog.read() assert numpy.array_equal(img.data, cog.dataset.read(indexes=(1, ))) assert img.width == cog.dataset.width assert img.height == cog.dataset.height assert img.count == cog.dataset.count img = cog.preview() assert img.bounds == cog.bounds part = cog.part(cog.bounds, bounds_crs=cog.crs) assert part.bounds == cog.bounds lon = (cog.bounds[0] + cog.bounds[2]) / 2 lat = (cog.bounds[1] + cog.bounds[3]) / 2 assert cog.point(lon, lat, coord_crs=cog.crs)[0] is not None europa_crs = CRS.from_authority("ESRI", 104915) tms = TileMatrixSet.custom( crs=europa_crs, extent=europa_crs.area_of_use.bounds, matrix_scale=[2, 1], ) with pytest.warns(None) as warnings: with COGReader(COG_EUROPA, tms=tms) as cog: assert cog.minzoom == 4 assert cog.maxzoom == 6 # Get Tile covering the UL corner bounds = transform_bounds(cog.crs, tms.rasterio_crs, *cog.bounds) t = tms._tile(bounds[0], bounds[1], cog.minzoom) img = cog.tile(t.x, t.y, t.z) assert img.height == 256 assert img.width == 256 assert img.crs == tms.rasterio_crs assert len(warnings) == 0
def __enter__(self): """Support using with Context Managers.""" self.dataset = self.dataset or rasterio.open(self.filepath) self.bounds = transform_bounds(self.dataset.crs, constants.WGS84_CRS, *self.dataset.bounds, densify_pts=21) if self.minzoom is None or self.maxzoom is None: self._get_zooms() if self.colormap is None: self._get_colormap() return self
def get_img_bounds(img, jsonFile, dst_crs=None): """Get the projected top left and bottom right coordinates of an image Parameters: img (ndarray): image to generate bounding coordinates for jsonFile (str): path to json file defining crs and image size dst_crs (str): epsg code for output crs Return: tpl: [[lat min, lon min],[lat max, lon max]] """ # Get a single string w/ newlines from the IPython.utils.text.SList with open(jsonFile, ) as f: mixer = json.load(f) # mixer = json.loads(jsonText.nlstr) transform = mixer['projection']['affine']['doubleMatrix'] print(transform) src_crs = CRS.from_string(mixer['projection']['crs']) print(src_crs) affine = rio.Affine(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]) H, W = [0, 0] if type(img) == np.ndarray: print('input image is numpy') H, W = img.shape print('image shape is ', H, W) bounds = array_bounds(H, W, affine) elif type(img) == str: print('input image is geotiff') with rio.open(img) as src: bounds = src.bounds # H, W = src.shape print(bounds) lon_min, lat_min, lon_max, lat_max = bounds # if we need to transform the bounds, such as for folium ('EPSG:3857') if dst_crs: dst_crs = CRS.from_string(dst_crs) out_bounds = transform_bounds(src_crs, dst_crs, left=lon_min, bottom=lat_min, right=lon_max, top=lat_max, densify_pts=21) lon_min, lat_min, lon_max, lat_max = out_bounds print(out_bounds) return [[lat_min, lon_min], [lat_max, lon_max]]
def _calculate_default_transform(src_crs: Union[Dict[str, str], str], _TARGET_CRS: Union[Dict[str, str], str], width: int, height: int, *bounds: Number) -> Tuple[Affine, int, int]: """A more stable version of GDAL's default transform. Ensures that the number of pixels along the image's shortest diagonal remains the same in both CRS, without enforcing square pixels. Bounds are in order (west, south, east, north). """ from rasterio import warp, transform if len(bounds) != 4: raise ValueError('Bounds must contain 4 values') if src_crs is None: src_crs = _TARGET_CRS # transform image corners to target CRS dst_corner_sw, dst_corner_nw, dst_corner_se, dst_corner_ne = (list( zip(*warp.transform(src_crs, _TARGET_CRS, [bounds[0], bounds[0], bounds[2], bounds[2]], [bounds[1], bounds[3], bounds[1], bounds[3]])))) # determine inner bounding box of corners in target CRS dst_corner_bounds = [ max(dst_corner_sw[0], dst_corner_nw[0]), max(dst_corner_sw[1], dst_corner_se[1]), min(dst_corner_se[0], dst_corner_ne[0]), min(dst_corner_nw[1], dst_corner_ne[1]) ] # compute target resolution dst_corner_transform = transform.from_bounds(*dst_corner_bounds, width=width, height=height) target_res = (dst_corner_transform.a, dst_corner_transform.e) # get transform spanning whole bounds (not just projected corners) dst_bounds = warp.transform_bounds(src_crs, _TARGET_CRS, *bounds) dst_width = math.ceil((dst_bounds[2] - dst_bounds[0]) / target_res[0]) dst_height = math.ceil((dst_bounds[1] - dst_bounds[3]) / target_res[1]) dst_transform = transform.from_bounds(*dst_bounds, width=dst_width, height=dst_height) return dst_transform, dst_width, dst_height
async def part(self, bounds: Tuple[float, float, float, float], bounds_crs: CRS = WGS84, width: int = None, height: int = None) -> np.ndarray: if bounds_crs != self.cog.epsg: bounds = transform_bounds(bounds_crs, CRS.from_epsg(self.cog.epsg), *bounds) if not height or not width: width = math.ceil( (bounds[2] - bounds[0]) / self.profile['transform'].a) height = math.ceil( (bounds[3] - bounds[1]) / -self.profile['transform'].e) arr = await self.cog.read(bounds=bounds, shape=(width, height)) return arr
def toRTM(self): # todo mask cloud srcList = [rio.open(i) for i in self.srcList] with rio.open(self.qaBand) as qaSrc: # srcList = [redset, greenset, blueset] meta = srcList[0].meta.copy() self.crs = meta['crs'] meta.update({ 'COMPRESS': 'LZW', 'dtype': 'uint8', 'count': self.count, 'nodata': 0 }) with rio.open(self.tempOutName, 'w', **meta) as dst: for ji, window in srcList[0].block_windows(1): imBands = [src.read(1, window=window) for src in srcList] im = np.stack(imBands) imqa = qaSrc.read(1, window=window) if self.maskcloud: clearMask = np.zeros(imBands[0].shape, dtype=np.bool) for clearValue in self.clearQaValues: clearMask[imqa == clearValue] = True _, rows, cols = im.shape if self.pixel_sunangle: bbox = rio.coords.BoundingBox(*transform_bounds( self.crs, {'init': u'epsg:4326'}, * srcList[0].window_bounds(window), 0)) E = sun_elevation(bbox, (rows, cols), self.date_collected, self.time_collected_utc).reshape( rows, cols, 1) else: E = np.array([self.E for i in range(self.count)]) if self.maskcloud: mask = clearMask == False else: mask = imqa == 1 mask[imqa == 0] = True rgb = reflectance(im, self.M, self.A, E).clip( min=0, max=0.55) * (254 / 0.55) + 1 rgb[:, mask] = 0 dst.write(rgb.astype('uint8'), list(range(1, self.count + 1)), window=window) if self.maskcloud: qaSrc = None
def _get_info(src_path): with rasterio.open(src_path) as src_dst: bounds = transform_bounds( *[src_dst.crs, "epsg:4326"] + list(src_dst.bounds), densify_pts=21 ) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] minzoom, maxzoom = get_zooms(src_dst) def _get_name(ix): name = src_dst.descriptions[ix - 1] if not name: name = f"band{ix}" return name band_descriptions = [_get_name(ix) for ix in src_dst.indexes] return bounds, center, minzoom, maxzoom, band_descriptions
async def info(self) -> COGInfo: wgs84_bounds = transform_bounds(CRS.from_epsg(self.cog.epsg), CRS.from_epsg(4326), *self.cog.bounds) mercator_resolution = max(self.profile["transform"][0], abs(self.profile["transform"][4])) max_zoom = zoom_for_pixelsize(mercator_resolution) min_zoom = zoom_for_pixelsize( mercator_resolution * max(self.profile["width"], self.profile["height"]) / 256) return COGInfo( min_zoom=min_zoom, max_zoom=max_zoom, bounds=list(wgs84_bounds), dtype=self.profile["dtype"], color_interp=self.profile["photometric"], )
def _read_window(self, vrt: WarpedVRT, dst_window: Window) -> MaskedArray: """Read window of input raster.""" dst_bounds: Bounds = bounds(dst_window, self.dst[self.default_format].transform) window = vrt.window(*dst_bounds) src_bounds = transform_bounds( self.dst[self.default_format].crs, self.src.crs, *dst_bounds ) LOGGER.debug( f"Read {dst_window} for Tile {self.tile_id} - this corresponds to bounds {src_bounds} in source" ) shape = ( len(self.layer.input_bands), int(round(dst_window.height)), int(round(dst_window.width)), ) try: return vrt.read( window=window, out_shape=shape, masked=True, ) except rasterio.RasterioIOError as e: if "Access window out of range" in str(e) and ( shape[1] == 1 or shape[2] == 1 ): LOGGER.warning( f"Access window out of range while reading {dst_window} for Tile {self.tile_id}. " "This is most likely due to subpixel misalignment. " "Returning empty array instead." ) return np.ma.array( data=np.zeros(shape=shape), mask=np.ones(shape=shape) ) else: LOGGER.warning( f"RasterioIO error while reading {dst_window} for Tile {self.tile_id}. " "Will make attempt to retry." ) raise
def __init__( self, bounds, zoom=11, bounds_crs="EPSG:4326", dst_crs="EPSG:4326", resolution=None, cache_dir=None, resampling="bilinear", ): # create a temp folder for vrts self.tempfolder = tempfile.mkdtemp(prefix="terrain-tiles-") # zoom must be 1-15 if type(zoom) != int or zoom < 1 or zoom > 15: raise ValueError("Zoom must be an integer from 1-15") # if cache is not specified if not cache_dir: # first look for environment variable if "TERRAINCACHE" in os.environ.keys(): cache_dir = os.environ["TERRAINCACHE"] # then default to temp folder else: cache_dir = self.tempfolder self.cache_dir = cache_dir self.zoom = zoom self.bounds = bounds if bounds_crs != "EPSG:4326": self.bounds_ll = transform_bounds(bounds_crs, "EPSG:4326", *bounds) else: self.bounds_ll = bounds self.bounds_crs = bounds_crs self.url = "http://s3.amazonaws.com/elevation-tiles-prod/geotiff/" self.tiles = [t for t in mercantile.tiles(*self.bounds_ll, self.zoom)] self.files = [] self.merged = False self.warped = False self.resolution = resolution self.dst_crs = dst_crs self.resampling = resampling self.cache()
def _reproject_dst_window(self, dst_window: Window) -> Window: """Reproject window into same projection as source raster.""" dst_bounds: Bounds = bounds( window=dst_window, transform=self.dst[self.default_format].transform, height=self.grid.blockysize, width=self.grid.blockxsize, ) src_bounds: Bounds = transform_bounds( self.dst[self.default_format].crs, self.src.crs, *dst_bounds ) src_window: Window = from_bounds(*src_bounds, transform=self.src.transform) LOGGER.debug( f"Source window for {dst_window} of tile {self.tile_id} is {src_window}" ) return src_window
def wmts_handler( url: str = None, tile_format: str = "png", tile_scale: int = 1, title: str = "Cloud Optimizied GeoTIFF", **kwargs: Any, ) -> Tuple[str, str, str]: """Handle /wmts requests.""" if tile_scale is not None and isinstance(tile_scale, str): tile_scale = int(tile_scale) # Remove QGIS arguments kwargs.pop("SERVICE", None) kwargs.pop("REQUEST", None) kwargs.update(dict(url=url)) query_string = urllib.parse.urlencode(list(kwargs.items())) # & is an invalid character in XML query_string = query_string.replace("&", "&") with rasterio.Env(aws_session): with rasterio.open(url) as src_dst: bounds = list( warp.transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21)) minzoom, maxzoom = get_zooms(src_dst) return ( "OK", "application/xml", wmts_template( app.host, query_string, minzoom=minzoom, maxzoom=maxzoom, bounds=bounds, tile_scale=tile_scale, tile_format=tile_format, title=title, ), )
def get_zooms(src_dst, ensure_global_max_zoom=False, tilesize=256): """ Calculate raster min/max mercator zoom level. Parameters ---------- src_dst: rasterio.io.DatasetReader Rasterio io.DatasetReader object ensure_global_max_zoom: bool, optional Apply latitude correction factor to ensure max_zoom equality for global datasets covering different latitudes (default: False). tilesize: int, optional Mercator tile size (default: 256). Returns ------- min_zoom, max_zoom: Tuple Min/Max Mercator zoom levels. """ bounds = transform_bounds(src_dst.crs, "epsg:5514", *src_dst.bounds, densify_pts=21) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] lat = center[1] if ensure_global_max_zoom else 0 dst_affine, w, h = calculate_default_transform(src_dst.crs, "epsg:5514", src_dst.width, src_dst.height, *src_dst.bounds) mercator_resolution = max(abs(dst_affine[0]), abs(dst_affine[4])) # Correction factor for web-mercator projection latitude scale change latitude_correction_factor = math.cos(math.radians(lat)) adjusted_resolution = mercator_resolution * latitude_correction_factor max_zoom = zoom_for_pixelsize(adjusted_resolution, tilesize=tilesize) ovr_resolution = adjusted_resolution * max(h, w) / tilesize min_zoom = zoom_for_pixelsize(ovr_resolution, tilesize=tilesize) return (min_zoom, max_zoom)
def __init__(self, uri, rgb=None, nodata=None, linear_stretch=None): self._uri = uri self._rgb = rgb self._nodata = nodata self._linear_stretch = linear_stretch self._meta = {} with get_source(self._uri) as src: self._bounds = warp.transform_bounds(src.crs, WGS84_CRS, *src.bounds) self._resolution = get_resolution_in_meters( Bounds(src.bounds, src.crs), (src.height, src.width)) approximate_zoom = get_zoom(max(self._resolution), op=math.ceil) global_min = src.get_tag_item("TIFFTAG_MINSAMPLEVALUE") global_max = src.get_tag_item("TIFFTAG_MAXSAMPLEVALUE") for band in xrange(0, src.count): self._meta["values"] = self._meta.get("values", {}) self._meta["values"][band] = {} min_val = src.get_tag_item("STATISTICS_MINIMUM", bidx=band + 1) max_val = src.get_tag_item("STATISTICS_MAXIMUM", bidx=band + 1) mean_val = src.get_tag_item("STATISTICS_MEAN", bidx=band + 1) if min_val is not None: self._meta["values"][band]["min"] = float(min_val) elif global_min is not None: self._meta["values"][band]["min"] = float(global_min) if max_val is not None: self._meta["values"][band]["max"] = float(max_val) elif global_max is not None: self._meta["values"][band]["max"] = float(global_max) if mean_val is not None: self._meta["values"][band]["mean"] = float(mean_val) self._center = [ (self._bounds[0] + self.bounds[2]) / 2, (self._bounds[1] + self.bounds[3]) / 2, approximate_zoom - 3, ] self._maxzoom = approximate_zoom + 3 self._minzoom = approximate_zoom - 10
def clip_from_wgs(input_path, output_path, bounds): """ Clips a raster based on WGS coordinates. Heavily borrowed from: https://github.com/mapbox/rasterio/blob/master/rasterio/rio/clip.py :param input_path The path to the raster to clip out of :param output_path The path to place the clipped raster :param bounds (left, bottom, right, top) bounds in WGS 84 projection """ wgs_crs = CRS({'init': 'epsg:4326'}) # WGS 84 Projection with rasterio.open(input_path, 'r') as src: window = src.window(*transform_bounds(wgs_crs, src.crs, *bounds)) height = window[0][1] - window[0][0] width = window[1][1] - window[1][0] t = 2048 # Threshold, for if the user selects an area greater than 2048 pixels in size # If so, select the center of the area at size of 2048 by 2048 if width > t: if width % 2 != 0: width -= 1 trim = int((width - t) / 2) width = t window = (window[0], (window[1][0] + trim, window[1][0] + t + trim)) if height > t: if height % 2 != 0: height -= 1 trim = int((height - t) / 2) height = t window = ((window[0][0] + trim, window[0][0] + t + trim), window[1]) out_kwargs = src.meta.copy() out_kwargs.update({ 'height': height, 'width': width, 'transform': src.window_transform(window), 'dtype': 'int32', # SyncroSim needs a signed int32 'nodata': 0 }) with rasterio.open(output_path, 'w', **out_kwargs) as dst: dst.write(src.read(1, window=window).astype('int32'), 1)
def test_crs_should_be_set(path_rgb_byte_tif, tmpdir, complex): """When ``dst_height``, ``dst_width``, and ``dst_transform`` are set :py:class:`rasterio.warp.WarpedVRT` calls ``GDALCreateWarpedVRT()``, which requires the caller to then set a projection with ``GDALSetProjection()``. Permalink to ``GDALCreateWarpedVRT()`` call: https://github.com/mapbox/rasterio/blob/1f759e5f67628f163ea2550d8926b91545245712/rasterio/_warp.pyx#L753 """ vrt_path = str(tmpdir.join("test_crs_should_be_set.vrt")) with rasterio.open(path_rgb_byte_tif) as src: dst_crs = "EPSG:4326" dst_height = dst_width = 10 dst_bounds = transform_bounds(src.crs, dst_crs, *src.bounds) # Destination transform left, bottom, right, top = dst_bounds xres = (right - left) / dst_width yres = (top - bottom) / dst_height dst_transform = affine.Affine(xres, 0.0, left, 0.0, -yres, top) # The 'complex' test case hits the affected code path vrt_options = {"dst_crs": dst_crs} if complex: vrt_options.update( dst_crs=dst_crs, dst_height=dst_height, dst_width=dst_width, dst_transform=dst_transform, resampling=Resampling.nearest, ) with WarpedVRT(src, **vrt_options) as vrt: rio_shutil.copy(vrt, vrt_path, driver="VRT") with rasterio.open(vrt_path) as src: assert src.crs
def test_windows(runner, path_rgb_byte_tif): result = runner.invoke(main_group, [ 'blocks', path_rgb_byte_tif]) assert result.exit_code == 0 fc = json.loads(result.output) with rasterio.open(path_rgb_byte_tif) as src: block_windows = tuple(src.block_windows()) # Check the coordinates of the first output feature actual_first = fc['features'][0] expected_first = block_windows[0] bounds = src.window_bounds(expected_first[1]) xmin, ymin, xmax, ymax = transform_bounds(src.crs, 'EPSG:4326', *bounds) coordinates = [[xmin, ymin], [xmin, ymax], [xmax, ymax], [xmax, ymin]] assert np.array_equal( actual_first['geometry']['coordinates'][0], coordinates) assert len(fc['features']) == len(block_windows) assert check_features_block_windows(fc['features'], src, bidx=1)
def test_windows_sequence(runner, path_rgb_byte_tif): result = runner.invoke(main_group, [ 'blocks', path_rgb_byte_tif, '--sequence']) assert result.exit_code == 0 features = tuple(map(json.loads, result.output.splitlines())) with rasterio.open(path_rgb_byte_tif) as src: # Check the coordinates of the first output feature actual_first = features[0] expected_first = next(src.block_windows()) bounds = src.window_bounds(expected_first[1]) xmin, ymin, xmax, ymax = transform_bounds(src.crs, 'EPSG:4326', *bounds) coordinates = [[xmin, ymin], [xmin, ymax], [xmax, ymax], [xmax, ymin]] assert np.array_equal( actual_first['geometry']['coordinates'][0], coordinates) assert check_features_block_windows(features, src, bidx=1)
def _make_tiles(bbox, src_crs, minz, maxz): ''' Given a bounding box, zoom range, and source crs, find all tiles that would intersect Parameters ----------- bbox: list [w, s, e, n] bounds src_crs: str the source crs of the input bbox minz: int minumum zoom to find tiles for maxz: int maximum zoom to find tiles for Returns -------- tiles: generator generator of [x, y, z] tiles that intersect the provided bounding box ''' w, s, e, n = transform_bounds(*[src_crs, 'epsg:4326'] + bbox, densify_pts=0) EPSILON = 1.0e-10 w += EPSILON s += EPSILON e -= EPSILON n -= EPSILON for z in range(minz, maxz + 1): for x, y in _tile_range( mercantile.tile(w, n, z), mercantile.tile(e, s, z)): yield [x, y, z]
def clip(ctx, files, output, bounds, like, driver, projection, creation_options): """Clips a raster using projected or geographic bounds. \b $ rio clip input.tif output.tif --bounds xmin ymin xmax ymax $ rio clip input.tif output.tif --like template.tif The values of --bounds are presumed to be from the coordinate reference system of the input dataset unless the --geographic option is used, in which case the values may be longitude and latitude bounds. Either JSON, for example "[west, south, east, north]", or plain text "west south east north" representations of a bounding box are acceptable. If using --like, bounds will automatically be transformed to match the coordinate reference system of the input. It can also be combined to read bounds of a feature dataset using Fiona: \b $ rio clip input.tif output.tif --bounds $(fio info features.shp --bounds) """ from rasterio.warp import transform_bounds with ctx.obj['env']: output, files = resolve_inout(files=files, output=output) input = files[0] with rasterio.open(input) as src: if bounds: if projection == 'geographic': bounds = transform_bounds('epsg:4326', src.crs, *bounds) if disjoint_bounds(bounds, src.bounds): raise click.BadParameter('must overlap the extent of ' 'the input raster', param='--bounds', param_hint='--bounds') elif like: with rasterio.open(like) as template_ds: bounds = template_ds.bounds if template_ds.crs != src.crs: bounds = transform_bounds(template_ds.crs, src.crs, *bounds) if disjoint_bounds(bounds, src.bounds): raise click.BadParameter('must overlap the extent of ' 'the input raster', param='--like', param_hint='--like') else: raise click.UsageError('--bounds or --like required') bounds_window = src.window(*bounds) bounds_window = bounds_window.intersection( Window(0, 0, src.width, src.height)) # Get the window with integer height # and width that contains the bounds window. out_window = bounds_window.round_lengths(op='ceil') height = int(out_window.height) width = int(out_window.width) out_kwargs = src.meta.copy() out_kwargs.update({ 'driver': driver, 'height': height, 'width': width, 'transform': src.window_transform(out_window)}) out_kwargs.update(**creation_options) with rasterio.open(output, 'w', **out_kwargs) as out: out.write(src.read(window=out_window, out_shape=(src.count, height, width)))
def warp(ctx, input, output, driver, like, dst_crs, dimensions, bounds, res, resampling, threads, creation_options): """ Warp a raster dataset. Currently, the output is always overwritten. This will be changed in a later version. If a template raster is provided using the --like option, the coordinate reference system, affine transform, and dimensions of that raster will be used for the output. In this case --dst-crs, --bounds, --res, and --dimensions options are ignored. \b $ rio warp input.tif output.tif --like template.tif The output coordinate reference system may be either a PROJ.4 or EPSG:nnnn string, \b --dst-crs EPSG:4326 --dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84' or a JSON text-encoded PROJ.4 object. \b --dst-crs '{"proj": "utm", "zone": 18, ...}' If --dimensions are provided, --res and --bounds are ignored. Resolution is calculated based on the relationship between the raster bounds in the target coordinate system and the dimensions, and may produce rectangular rather than square pixels. \b $ rio warp input.tif output.tif --dimensions 100 200 --dst-crs EPSG:4326 If --bounds are provided, --res is required if --dst-crs is provided (defaults to source raster resolution otherwise). Bounds are in the source coordinate reference system. \b $ rio warp input.tif output.tif --bounds -78 22 -76 24 --dst-crs \\ EPSG:4326 --res 0.1 """ verbosity = (ctx.obj and ctx.obj.get("verbosity")) or 1 resampling = getattr(RESAMPLING, resampling) # get integer code for method if not len(res): # Click sets this as an empty tuple if not provided res = None else: # Expand one value to two if needed res = (res[0], res[0]) if len(res) == 1 else res with rasterio.drivers(CPL_DEBUG=verbosity > 2): with rasterio.open(input) as src: l, b, r, t = src.bounds out_kwargs = src.meta.copy() out_kwargs["driver"] = driver if like: with rasterio.open(like) as template_ds: dst_crs = template_ds.crs dst_transform = template_ds.affine dst_height = template_ds.height dst_width = template_ds.width elif dst_crs: try: dst_crs = crs.from_string(dst_crs) except ValueError: raise click.BadParameter("invalid crs format", param=dst_crs, param_hint=dst_crs) if dimensions: # Calculate resolution appropriate for dimensions in target dst_width, dst_height = dimensions xmin, ymin, xmax, ymax = transform_bounds(src.crs, dst_crs, *src.bounds) dst_transform = Affine( (xmax - xmin) / float(dst_width), 0, xmin, 0, (ymin - ymax) / float(dst_height), ymax ) elif bounds: if not res: raise click.BadParameter("Required when using --bounds", param="res", param_hint="res") xmin, ymin, xmax, ymax = transform_bounds(src.crs, dst_crs, *bounds) dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) else: dst_transform, dst_width, dst_height = calculate_default_transform( src.crs, dst_crs, src.width, src.height, *src.bounds, resolution=res ) elif dimensions: # Same projection, different dimensions, calculate resolution dst_crs = src.crs dst_width, dst_height = dimensions dst_transform = Affine((r - l) / float(dst_width), 0, l, 0, (b - t) / float(dst_height), t) elif bounds: # Same projection, different dimensions and possibly different # resolution if not res: res = (src.affine.a, -src.affine.e) dst_crs = src.crs xmin, ymin, xmax, ymax = bounds dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) elif res: # Same projection, different resolution dst_crs = src.crs dst_transform = Affine(res[0], 0, l, 0, -res[1], t) dst_width = max(int(ceil((r - l) / res[0])), 1) dst_height = max(int(ceil((t - b) / res[1])), 1) else: dst_crs = src.crs dst_transform = src.affine dst_width = src.width dst_height = src.height out_kwargs.update( { "crs": dst_crs, "transform": dst_transform, "affine": dst_transform, "width": dst_width, "height": dst_height, } ) out_kwargs.update(**creation_options) with rasterio.open(output, "w", **out_kwargs) as dst: for i in range(1, src.count + 1): reproject( source=rasterio.band(src, i), destination=rasterio.band(dst, i), src_transform=src.affine, src_crs=src.crs, # src_nodata=#TODO dst_transform=out_kwargs["transform"], dst_crs=out_kwargs["crs"], # dst_nodata=#TODO resampling=resampling, num_threads=threads, )
def clip( ctx, files, output, bounds, like, driver, creation_options): """Clips a raster using bounds input directly or from a template raster. \b $ rio clip input.tif output.tif --bounds xmin ymin xmax ymax $ rio clip input.tif output.tif --like template.tif If using --bounds, values must be in coordinate reference system of input. If using --like, bounds will automatically be transformed to match the coordinate reference system of the input. It can also be combined to read bounds of a feature dataset using Fiona: \b $ rio clip input.tif output.tif --bounds $(fio info features.shp --bounds) """ from rasterio.warp import transform_bounds verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1 with rasterio.drivers(CPL_DEBUG=verbosity > 2): output, files = resolve_inout(files=files, output=output) input = files[0] with rasterio.open(input) as src: if bounds: if disjoint_bounds(bounds, src.bounds): raise click.BadParameter('must overlap the extent of ' 'the input raster', param='--bounds', param_hint='--bounds') elif like: with rasterio.open(like) as template_ds: bounds = template_ds.bounds if template_ds.crs != src.crs: bounds = transform_bounds(template_ds.crs, src.crs, *bounds) if disjoint_bounds(bounds, src.bounds): raise click.BadParameter('must overlap the extent of ' 'the input raster', param='--like', param_hint='--like') else: raise click.UsageError('--bounds or --like required') window = src.window(*bounds) out_kwargs = src.meta.copy() out_kwargs.update({ 'driver': driver, 'height': window[0][1] - window[0][0], 'width': window[1][1] - window[1][0], 'transform': src.window_transform(window) }) out_kwargs.update(**creation_options) with rasterio.open(output, 'w', **out_kwargs) as out: out.write(src.read(window=window))
def test_transform_bounds_identity(): """Confirm fix of #1411""" bounds = (12978395.906596646, 146759.09430753812, 12983287.876406897, 151651.06411778927) assert transform_bounds("+init=epsg:3857", "+init=epsg:3857", *bounds) == bounds
def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds, x_dst_bounds, bounds, res, resampling, threads, force_overwrite, creation_options): """ Warp a raster dataset. If a template raster is provided using the --like option, the coordinate reference system, affine transform, and dimensions of that raster will be used for the output. In this case --dst-crs, --bounds, --res, and --dimensions options are ignored. \b $ rio warp input.tif output.tif --like template.tif The output coordinate reference system may be either a PROJ.4 or EPSG:nnnn string, \b --dst-crs EPSG:4326 --dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84' or a JSON text-encoded PROJ.4 object. \b --dst-crs '{"proj": "utm", "zone": 18, ...}' If --dimensions are provided, --res and --bounds are ignored. Resolution is calculated based on the relationship between the raster bounds in the target coordinate system and the dimensions, and may produce rectangular rather than square pixels. \b $ rio warp input.tif output.tif --dimensions 100 200 \\ > --dst-crs EPSG:4326 If --bounds are provided, --res is required if --dst-crs is provided (defaults to source raster resolution otherwise). \b $ rio warp input.tif output.tif \\ > --bounds -78 22 -76 24 --res 0.1 --dst-crs EPSG:4326 """ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1 logger = logging.getLogger('rio') output, files = resolve_inout( files=files, output=output, force_overwrite=force_overwrite) resampling = Resampling[resampling] # get integer code for method if not len(res): # Click sets this as an empty tuple if not provided res = None else: # Expand one value to two if needed res = (res[0], res[0]) if len(res) == 1 else res with rasterio.drivers(CPL_DEBUG=verbosity > 2): with rasterio.open(files[0]) as src: l, b, r, t = src.bounds out_kwargs = src.meta.copy() out_kwargs['driver'] = driver # Sort out the bounds options. src_bounds = bounds or src_bounds dst_bounds = x_dst_bounds if src_bounds and dst_bounds: raise click.BadParameter( "Source and destination bounds may not be specified " "simultaneously.") if like: with rasterio.open(like) as template_ds: dst_crs = template_ds.crs dst_transform = template_ds.affine dst_height = template_ds.height dst_width = template_ds.width elif dst_crs: try: dst_crs = crs.from_string(dst_crs) except ValueError: raise click.BadParameter("invalid crs format.", param=dst_crs, param_hint=dst_crs) if dimensions: # Calculate resolution appropriate for dimensions # in target. dst_width, dst_height = dimensions xmin, ymin, xmax, ymax = transform_bounds(src.crs, dst_crs, *src.bounds) dst_transform = Affine( (xmax - xmin) / float(dst_width), 0, xmin, 0, (ymin - ymax) / float(dst_height), ymax ) elif src_bounds or dst_bounds: if not res: raise click.BadParameter( "Required when using --bounds.", param='res', param_hint='res') if src_bounds: xmin, ymin, xmax, ymax = transform_bounds( src.crs, dst_crs, *src_bounds) else: xmin, ymin, xmax, ymax = dst_bounds dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) else: dst_transform, dst_width, dst_height = calculate_default_transform( src.crs, dst_crs, src.width, src.height, *src.bounds, resolution=res) elif dimensions: # Same projection, different dimensions, calculate resolution. dst_crs = src.crs dst_width, dst_height = dimensions dst_transform = Affine( (r - l) / float(dst_width), 0, l, 0, (b - t) / float(dst_height), t ) elif src_bounds or dst_bounds: # Same projection, different dimensions and possibly # different resolution. if not res: res = (src.affine.a, -src.affine.e) dst_crs = src.crs xmin, ymin, xmax, ymax = (src_bounds or dst_bounds) dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) elif res: # Same projection, different resolution. dst_crs = src.crs dst_transform = Affine(res[0], 0, l, 0, -res[1], t) dst_width = max(int(ceil((r - l) / res[0])), 1) dst_height = max(int(ceil((t - b) / res[1])), 1) else: dst_crs = src.crs dst_transform = src.affine dst_width = src.width dst_height = src.height # When the bounds option is misused, extreme values of # destination width and height may result. if (dst_width < 0 or dst_height < 0 or dst_width > MAX_OUTPUT_WIDTH or dst_height > MAX_OUTPUT_HEIGHT): raise click.BadParameter( "Invalid output dimensions: {0}.".format( (dst_width, dst_height))) out_kwargs.update({ 'crs': dst_crs, 'transform': dst_transform, 'affine': dst_transform, 'width': dst_width, 'height': dst_height }) out_kwargs.update(**creation_options) with rasterio.open(output, 'w', **out_kwargs) as dst: for i in range(1, src.count + 1): reproject( source=rasterio.band(src, i), destination=rasterio.band(dst, i), src_transform=src.affine, src_crs=src.crs, # src_nodata=#TODO dst_transform=out_kwargs['transform'], dst_crs=out_kwargs['crs'], # dst_nodata=#TODO resampling=resampling, num_threads=threads)
def process_tile(tile): """Process a single MBTiles tile Parameters ---------- tile : mercantile.Tile Returns ------- tile : mercantile.Tile The input tile. bytes : bytearray Image bytes corresponding to the tile. """ global base_kwds, resampling, src # Get the bounds of the tile. ulx, uly = mercantile.xy( *mercantile.ul(tile.x, tile.y, tile.z)) lrx, lry = mercantile.xy( *mercantile.ul(tile.x + 1, tile.y + 1, tile.z)) kwds = base_kwds.copy() kwds['transform'] = transform_from_bounds(ulx, lry, lrx, uly, kwds['width'], kwds['height']) src_nodata = kwds.pop('src_nodata', None) dst_nodata = kwds.pop('dst_nodata', None) warnings.simplefilter('ignore') with MemoryFile() as memfile: with memfile.open(**kwds) as tmp: # determine window of source raster corresponding to the tile # image, with small buffer at edges try: west, south, east, north = transform_bounds(TILES_CRS, src.crs, ulx, lry, lrx, uly) tile_window = window_from_bounds(west, south, east, north, transform=src.transform) adjusted_tile_window = Window( tile_window.col_off - 1, tile_window.row_off - 1, tile_window.width + 2, tile_window.height + 2) tile_window = adjusted_tile_window.round_offsets().round_shape() # if no data in window, skip processing the tile if not src.read_masks(1, window=tile_window).any(): return tile, None except ValueError: log.info("Tile %r will not be skipped, even if empty. This is harmless.", tile) reproject(rasterio.band(src, tmp.indexes), rasterio.band(tmp, tmp.indexes), src_nodata=src_nodata, dst_nodata=dst_nodata, num_threads=1, resampling=resampling) return tile, memfile.read()
def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds, dst_bounds, res, resampling, src_nodata, dst_nodata, threads, check_invert_proj, overwrite, creation_options, target_aligned_pixels): """ Warp a raster dataset. If a template raster is provided using the --like option, the coordinate reference system, affine transform, and dimensions of that raster will be used for the output. In this case --dst-crs, --bounds, --res, and --dimensions options are not applicable and an exception will be raised. \b $ rio warp input.tif output.tif --like template.tif The output coordinate reference system may be either a PROJ.4 or EPSG:nnnn string, \b --dst-crs EPSG:4326 --dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84' or a JSON text-encoded PROJ.4 object. \b --dst-crs '{"proj": "utm", "zone": 18, ...}' If --dimensions are provided, --res and --bounds are not applicable and an exception will be raised. Resolution is calculated based on the relationship between the raster bounds in the target coordinate system and the dimensions, and may produce rectangular rather than square pixels. \b $ rio warp input.tif output.tif --dimensions 100 200 \\ > --dst-crs EPSG:4326 If --bounds are provided, --res is required if --dst-crs is provided (defaults to source raster resolution otherwise). \b $ rio warp input.tif output.tif \\ > --bounds -78 22 -76 24 --res 0.1 --dst-crs EPSG:4326 """ output, files = resolve_inout( files=files, output=output, overwrite=overwrite) resampling = Resampling[resampling] # get integer code for method if not len(res): # Click sets this as an empty tuple if not provided res = None else: # Expand one value to two if needed res = (res[0], res[0]) if len(res) == 1 else res if target_aligned_pixels: if not res: raise click.BadParameter( '--target-aligned-pixels requires a specified resolution') if src_bounds or dst_bounds: raise click.BadParameter( '--target-aligned-pixels cannot be used with ' '--src-bounds or --dst-bounds') # Check invalid parameter combinations if like: invalid_combos = (dimensions, dst_bounds, dst_crs, res) if any(p for p in invalid_combos if p is not None): raise click.BadParameter( "--like cannot be used with any of --dimensions, --bounds, " "--dst-crs, or --res") elif dimensions: invalid_combos = (dst_bounds, res) if any(p for p in invalid_combos if p is not None): raise click.BadParameter( "--dimensions cannot be used with --bounds or --res") with ctx.obj['env']: setenv(CHECK_WITH_INVERT_PROJ=check_invert_proj) with rasterio.open(files[0]) as src: l, b, r, t = src.bounds out_kwargs = src.profile.copy() out_kwargs['driver'] = driver # Sort out the bounds options. if src_bounds and dst_bounds: raise click.BadParameter( "--src-bounds and destination --bounds may not be " "specified simultaneously.") if like: with rasterio.open(like) as template_ds: dst_crs = template_ds.crs dst_transform = template_ds.transform dst_height = template_ds.height dst_width = template_ds.width elif dst_crs is not None: try: dst_crs = CRS.from_string(dst_crs) except ValueError as err: raise click.BadParameter( str(err), param='dst_crs', param_hint='dst_crs') if dimensions: # Calculate resolution appropriate for dimensions # in target. dst_width, dst_height = dimensions try: xmin, ymin, xmax, ymax = transform_bounds( src.crs, dst_crs, *src.bounds) except CRSError as err: raise click.BadParameter( str(err), param='dst_crs', param_hint='dst_crs') dst_transform = Affine( (xmax - xmin) / float(dst_width), 0, xmin, 0, (ymin - ymax) / float(dst_height), ymax ) elif src_bounds or dst_bounds: if not res: raise click.BadParameter( "Required when using --bounds.", param='res', param_hint='res') if src_bounds: try: xmin, ymin, xmax, ymax = transform_bounds( src.crs, dst_crs, *src_bounds) except CRSError as err: raise click.BadParameter( str(err), param='dst_crs', param_hint='dst_crs') else: xmin, ymin, xmax, ymax = dst_bounds dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) else: try: if src.transform.is_identity and src.gcps: src_crs = src.gcps[1] kwargs = {'gcps': src.gcps[0]} else: src_crs = src.crs kwargs = src.bounds._asdict() dst_transform, dst_width, dst_height = calcdt( src_crs, dst_crs, src.width, src.height, resolution=res, **kwargs) except CRSError as err: raise click.BadParameter( str(err), param='dst_crs', param_hint='dst_crs') elif dimensions: # Same projection, different dimensions, calculate resolution. dst_crs = src.crs dst_width, dst_height = dimensions dst_transform = Affine( (r - l) / float(dst_width), 0, l, 0, (b - t) / float(dst_height), t ) elif src_bounds or dst_bounds: # Same projection, different dimensions and possibly # different resolution. if not res: res = (src.transform.a, -src.transform.e) dst_crs = src.crs xmin, ymin, xmax, ymax = (src_bounds or dst_bounds) dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) elif res: # Same projection, different resolution. dst_crs = src.crs dst_transform = Affine(res[0], 0, l, 0, -res[1], t) dst_width = max(int(ceil((r - l) / res[0])), 1) dst_height = max(int(ceil((t - b) / res[1])), 1) else: dst_crs = src.crs dst_transform = src.transform dst_width = src.width dst_height = src.height if target_aligned_pixels: dst_transform, dst_width, dst_height = aligned_target(dst_transform, dst_width, dst_height, res) # If src_nodata is not None, update the dst metadata NODATA # value to src_nodata (will be overridden by dst_nodata if it is not None if src_nodata is not None: # Update the dst nodata value out_kwargs.update({ 'nodata': src_nodata }) # Validate a manually set destination NODATA value # against the input datatype. if dst_nodata is not None: if src_nodata is None and src.meta['nodata'] is None: raise click.BadParameter( "--src-nodata must be provided because dst-nodata is not None") else: # Update the dst nodata value out_kwargs.update({'nodata': dst_nodata}) # When the bounds option is misused, extreme values of # destination width and height may result. if (dst_width < 0 or dst_height < 0 or dst_width > MAX_OUTPUT_WIDTH or dst_height > MAX_OUTPUT_HEIGHT): raise click.BadParameter( "Invalid output dimensions: {0}.".format( (dst_width, dst_height))) out_kwargs.update({ 'crs': dst_crs, 'transform': dst_transform, 'width': dst_width, 'height': dst_height }) # Adjust block size if necessary. if ('blockxsize' in out_kwargs and dst_width < out_kwargs['blockxsize']): del out_kwargs['blockxsize'] if ('blockysize' in out_kwargs and dst_height < out_kwargs['blockysize']): del out_kwargs['blockysize'] out_kwargs.update(**creation_options) with rasterio.open(output, 'w', **out_kwargs) as dst: reproject( source=rasterio.band(src, list(range(1, src.count + 1))), destination=rasterio.band( dst, list(range(1, src.count + 1))), src_transform=src.transform, src_crs=src.crs, src_nodata=src_nodata, dst_transform=out_kwargs['transform'], dst_crs=out_kwargs['crs'], dst_nodata=dst_nodata, resampling=resampling, num_threads=threads)
def test_transform_bounds_src_crs_none(): with pytest.raises(CRSError): transform_bounds(None, WGS84_crs, 0, 0, 0, 0)
def test_transform_bounds_dst_crs_none(): with pytest.raises(CRSError): transform_bounds(WGS84_crs, None, 0, 0, 0, 0)
def _normalize_bounds(self, bounds): if self._geographic: bounds = transform_bounds(self._src.crs, 'EPSG:4326', *bounds) if self._precision >= 0: bounds = (round(v, self._precision) for v in bounds) return bounds