def _get_raster_tile(self, path: str, *, upsampling_method: str, downsampling_method: str, bounds: Tuple[float, float, float, float] = None, tile_size: Tuple[int, int] = (256, 256), preserve_values: bool = False) -> np.ma.MaskedArray: """Load a raster dataset from a file through rasterio. Heavily inspired by mapbox/rio-tiler """ import rasterio from rasterio import transform, windows, warp from rasterio.vrt import WarpedVRT from affine import Affine dst_bounds: Tuple[float, float, float, float] if preserve_values: upsampling_enum = downsampling_enum = self._get_resampling_enum('nearest') else: upsampling_enum = self._get_resampling_enum(upsampling_method) downsampling_enum = self._get_resampling_enum(downsampling_method) with contextlib.ExitStack() as es: es.enter_context(rasterio.Env(**self._RIO_ENV_KEYS)) try: with trace('open_dataset'): src = es.enter_context(rasterio.open(path)) except OSError: raise IOError('error while reading file {}'.format(path)) # compute suggested resolution and bounds in target CRS dst_transform, _, _ = self._calculate_default_transform( src.crs, self._TARGET_CRS, src.width, src.height, *src.bounds ) dst_res = (abs(dst_transform.a), abs(dst_transform.e)) dst_bounds = warp.transform_bounds(src.crs, self._TARGET_CRS, *src.bounds) if bounds is None: bounds = dst_bounds # pad tile bounds to prevent interpolation artefacts num_pad_pixels = 2 # compute tile VRT shape and transform dst_width = max(1, round((bounds[2] - bounds[0]) / dst_res[0])) dst_height = max(1, round((bounds[3] - bounds[1]) / dst_res[1])) vrt_transform = ( transform.from_bounds(*bounds, width=dst_width, height=dst_height) * Affine.translation(-num_pad_pixels, -num_pad_pixels) ) vrt_height, vrt_width = dst_height + 2 * num_pad_pixels, dst_width + 2 * num_pad_pixels # remove padding in output out_window = windows.Window( col_off=num_pad_pixels, row_off=num_pad_pixels, width=dst_width, height=dst_height ) # construct VRT vrt = es.enter_context( WarpedVRT( src, crs=self._TARGET_CRS, resampling=upsampling_enum, add_alpha=True, transform=vrt_transform, width=vrt_width, height=vrt_height ) ) # prevent loads of very sparse data out_window_bounds = windows.bounds(out_window, vrt_transform) cover_ratio = ( (dst_bounds[2] - dst_bounds[0]) / (out_window_bounds[2] - out_window_bounds[0]) * (dst_bounds[3] - dst_bounds[1]) / (out_window_bounds[3] - out_window_bounds[1]) ) if cover_ratio < 0.01: raise exceptions.TileOutOfBoundsError('dataset covers less than 1% of tile') # determine whether we are upsampling or downsampling pixel_ratio = min(out_window.width / tile_size[1], out_window.height / tile_size[0]) if pixel_ratio < 1: resampling_enum = upsampling_enum else: resampling_enum = downsampling_enum # read data with warnings.catch_warnings(), trace('read_from_vrt'): warnings.filterwarnings('ignore', message='invalid value encountered.*') tile_data = vrt.read( 1, resampling=resampling_enum, window=out_window, out_shape=tile_size ) # read alpha mask mask_idx = src.count + 1 mask = vrt.read(mask_idx, window=out_window, out_shape=tile_size) == 0 if src.nodata is not None: mask |= tile_data == src.nodata return np.ma.masked_array(tile_data, mask=mask)
def get_raster_tile( path: str, *, reprojection_method: str = "nearest", resampling_method: str = "nearest", tile_bounds: Tuple[float, float, float, float] = None, tile_size: Tuple[int, int] = (256, 256), preserve_values: bool = False, target_crs: str = 'epsg:3857', rio_env_options: Dict[str, Any] = None) -> np.ma.MaskedArray: """Load a raster dataset from a file through rasterio. Heavily inspired by mapbox/rio-tiler """ import rasterio from rasterio import transform, windows, warp from rasterio.vrt import WarpedVRT from affine import Affine dst_bounds: Tuple[float, float, float, float] if rio_env_options is None: rio_env_options = {} if preserve_values: reproject_enum = resampling_enum = get_resampling_enum('nearest') else: reproject_enum = get_resampling_enum(reprojection_method) resampling_enum = get_resampling_enum(resampling_method) with contextlib.ExitStack() as es: es.enter_context(rasterio.Env(**rio_env_options)) try: with trace('open_dataset'): src = es.enter_context(rasterio.open(path)) except OSError: raise IOError('error while reading file {}'.format(path)) # compute buonds in target CRS dst_bounds = warp.transform_bounds(src.crs, target_crs, *src.bounds) if tile_bounds is None: tile_bounds = dst_bounds # prevent loads of very sparse data cover_ratio = ((dst_bounds[2] - dst_bounds[0]) / (tile_bounds[2] - tile_bounds[0]) * (dst_bounds[3] - dst_bounds[1]) / (tile_bounds[3] - tile_bounds[1])) if cover_ratio < 0.01: raise exceptions.TileOutOfBoundsError( 'dataset covers less than 1% of tile') # compute suggested resolution in target CRS dst_transform, _, _ = warp.calculate_default_transform( src.crs, target_crs, src.width, src.height, *src.bounds) dst_res = (abs(dst_transform.a), abs(dst_transform.e)) # in some cases (e.g. at extreme latitudes), the default transform # suggests very coarse resolutions - in this case, fall back to native tile res tile_transform = transform.from_bounds(*tile_bounds, *tile_size) tile_res = (abs(tile_transform.a), abs(tile_transform.e)) if tile_res[0] < dst_res[0] or tile_res[1] < dst_res[1]: dst_res = tile_res resampling_enum = get_resampling_enum('nearest') # pad tile bounds to prevent interpolation artefacts num_pad_pixels = 2 # compute tile VRT shape and transform dst_width = max(1, round( (tile_bounds[2] - tile_bounds[0]) / dst_res[0])) dst_height = max(1, round((tile_bounds[3] - tile_bounds[1]) / dst_res[1])) vrt_transform = (transform.from_bounds( *tile_bounds, width=dst_width, height=dst_height) * Affine.translation(-num_pad_pixels, -num_pad_pixels)) vrt_height, vrt_width = dst_height + 2 * num_pad_pixels, dst_width + 2 * num_pad_pixels # remove padding in output out_window = windows.Window(col_off=num_pad_pixels, row_off=num_pad_pixels, width=dst_width, height=dst_height) # construct VRT vrt = es.enter_context( WarpedVRT(src, crs=target_crs, resampling=reproject_enum, transform=vrt_transform, width=vrt_width, height=vrt_height, add_alpha=not has_alpha_band(src))) # read data with warnings.catch_warnings(), trace('read_from_vrt'): warnings.filterwarnings('ignore', message='invalid value encountered.*') tile_data = vrt.read(1, resampling=resampling_enum, window=out_window, out_shape=tile_size) # assemble alpha mask mask_idx = vrt.count mask = vrt.read(mask_idx, window=out_window, out_shape=tile_size) == 0 if src.nodata is not None: mask |= tile_data == src.nodata return np.ma.masked_array(tile_data, mask=mask)