def _reproject(self, eopatch, src_raster): """ Reprojects the raster data from Geopedia's CRS (POP_WEB) to EOPatch's CRS. """ height, width = src_raster.shape dst_raster = np.ones((height, width), dtype=self.raster_dtype) src_bbox = transform_bbox(eopatch.bbox, CRS.POP_WEB) src_transform = transform.from_bounds(*src_bbox, width=width, height=height) dst_bbox = eopatch.bbox dst_transform = transform.from_bounds(*dst_bbox, width=width, height=height) warp.reproject(src_raster, dst_raster, src_transform=src_transform, src_crs={'init': CRS.ogc_string(CRS.POP_WEB)}, src_nodata=0, dst_transform=dst_transform, dst_crs={'init': CRS.ogc_string(eopatch.bbox.crs)}, dst_nodata=self.no_data_val) return dst_raster
def get_vrt_transform( src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT], bounds: Tuple[float, float, float, float], height: Optional[int] = None, width: Optional[int] = None, dst_crs: CRS = constants.WEB_MERCATOR_CRS, ) -> Tuple[Affine, int, int]: """ Calculate VRT transform. Attributes ---------- src_dst : rasterio.io.DatasetReader Rasterio io.DatasetReader object bounds : list Bounds (left, bottom, right, top) in target crs ("dst_crs"). height : int, optional Desired output height of the array for the bounds. width : int, optional Desired output width of the array for the bounds. dst_crs: CRS or str, optional Target coordinate reference system (default "epsg:3857"). Returns ------- vrt_transform: Affine Output affine transformation matrix vrt_width, vrt_height: int Output dimensions """ dst_transform, _, _ = calculate_default_transform( src_dst.crs, dst_crs, src_dst.width, src_dst.height, *src_dst.bounds ) w, s, e, n = bounds if not height or not width: vrt_width = math.ceil((e - w) / dst_transform.a) vrt_height = math.ceil((s - n) / dst_transform.e) vrt_transform = from_bounds(w, s, e, n, vrt_width, vrt_height) return vrt_transform, vrt_width, vrt_height tile_transform = from_bounds(w, s, e, n, width, height) w_res = ( tile_transform.a if abs(tile_transform.a) < abs(dst_transform.a) else dst_transform.a ) h_res = ( tile_transform.e if abs(tile_transform.e) < abs(dst_transform.e) else dst_transform.e ) vrt_width = math.ceil((e - w) / w_res) vrt_height = math.ceil((s - n) / h_res) vrt_transform = from_bounds(w, s, e, n, vrt_width, vrt_height) return vrt_transform, vrt_width, vrt_height
def test_arr(self): img_first = Image(TEST_FILE, dimorder="first") img_first.mask(box(11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750,)) self.assertEqual(img_first.arr.shape, (1, 385, 502)) self.assertEqual( str(img_first.dataset.transform), str( from_bounds( 11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750, img_first.arr.shape[2], img_first.arr.shape[1], ) ), ) img_first.close() img_last = Image(TEST_FILE, dimorder="last") img_last.mask(box(11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750,)) self.assertEqual(img_last.arr.shape, (385, 502, 1)) self.assertEqual( str(img_last.dataset.transform), str( from_bounds( 11.9027457562112939, 51.4664152338322580, 11.9477435281016131, 51.5009522690838750, img_last.arr.shape[1], img_last.arr.shape[0], ) ), ) img_last.close() img_first = Image( np.ones((1, 385, 502)), dimorder="first", crs=self.img.dataset.crs, transform=self.img.dataset.transform ) self.assertEqual(img_first.arr.shape, (1, 385, 502)) img_first.close() img_last = Image( np.ones((385, 502, 1)), dimorder="last", crs=self.img.dataset.crs, transform=self.img.dataset.transform ) self.assertEqual(img_last.arr.shape, (385, 502, 1)) img_last.close()
def get_vrt_transform( src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT], bounds: Tuple[float, float, float, float], height: Optional[int] = None, width: Optional[int] = None, dst_crs: CRS = WEB_MERCATOR_CRS, ) -> Tuple[Affine, int, int]: """Calculate VRT transform. Args: src_dst (rasterio.io.DatasetReader or rasterio.io.DatasetWriter or rasterio.vrt.WarpedVRT): Rasterio dataset. bounds (tuple): Bounding box coordinates in target crs (**dst_crs**). height (int, optional): Desired output height of the array for the input bounds. width (int, optional): Desired output width of the array for the input bounds. dst_crs (rasterio.crs.CRS, optional): Target Coordinate Reference System. Defaults to `epsg:3857`. Returns: tuple: VRT transform (affine.Affine), width (int) and height (int) """ dst_transform, _, _ = calculate_default_transform( src_dst.crs, dst_crs, src_dst.width, src_dst.height, *src_dst.bounds ) w, s, e, n = bounds if not height or not width: vrt_width = math.ceil((e - w) / dst_transform.a) vrt_height = math.ceil((s - n) / dst_transform.e) vrt_transform = from_bounds(w, s, e, n, vrt_width, vrt_height) return vrt_transform, vrt_width, vrt_height tile_transform = from_bounds(w, s, e, n, width, height) w_res = ( tile_transform.a if abs(tile_transform.a) < abs(dst_transform.a) else dst_transform.a ) h_res = ( tile_transform.e if abs(tile_transform.e) < abs(dst_transform.e) else dst_transform.e ) vrt_width = math.ceil((e - w) / w_res) vrt_height = math.ceil((s - n) / h_res) vrt_transform = from_bounds(w, s, e, n, vrt_width, vrt_height) return vrt_transform, vrt_width, vrt_height
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
def transform(self): """Returns the affine transform.""" return ( from_bounds(*self.bounds, self.width, self.height) if self.bounds else Affine.scale(self.width, -self.height) )
def test_array_bounds(): with rasterio.open('tests/data/RGB.byte.tif') as src: w, s, e, n = src.bounds height = src.height width = src.width tr = transform.from_bounds(w, s, e, n, src.width, src.height) assert (w, s, e, n) == transform.array_bounds(height, width, tr)
def save_tile_mask(labels_poly, tile_poly, xyz, tile_size, save_path='', prefix='', display=False, greyscale=True): x, y, z = xyz img_save_path = f'{save_path}/{prefix}{z}_{x}_{y}.png' if os.path.exists(img_save_path): return # get affine transformation matrix for this tile using rasterio.transform.from_bounds: https://rasterio.readthedocs.io/en/stable/api/rasterio.transform.html#rasterio.transform.from_bounds tfm = from_bounds(*tile_poly.bounds, tile_size, tile_size) # crop geometries to what overlaps our tile polygon bounds cropped_polys = [poly for poly in labels_poly if poly.intersects(tile_poly)] cropped_polys_gdf = gpd.GeoDataFrame(geometry=cropped_polys, crs='epsg:4326') # burn a footprint/boundary/contact 3-channel mask with solaris: https://solaris.readthedocs.io/en/latest/tutorials/notebooks/api_masks_tutorial.html fbc_mask = sol.vector.mask.df_to_px_mask(df=cropped_polys_gdf, channels=['footprint', 'boundary', 'contact'], affine_obj=tfm, shape=(tile_size, tile_size), boundary_width=5, boundary_type='inner', contact_spacing=5, meters=True) if display: plt.imshow(fbc_mask) plt.show() if greyscale: skimage.io.imsave(img_save_path, fbc_mask[:,:,0], check_contrast=False) else: skimage.io.imsave(img_save_path, fbc_mask, check_contrast=False)
def geotiff_options( x: int, y: int, z: int, tilesize: int = 256, dst_crs: CRS = constants.WEB_MERCATOR_CRS, ) -> Dict: """ GeoTIFF options. Attributes ---------- 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. dst_crs: CRS, optional Target coordinate reference system, default is "epsg:3857". Returns ------- dict """ bounds = mercantile.xy_bounds(mercantile.Tile(x=x, y=y, z=z)) dst_transform = from_bounds(*bounds, tilesize, tilesize) return dict(crs=dst_crs, transform=dst_transform)
def burn_single(tile, feature, size, multicolors): bounds = mercantile.xy_bounds(tile) transform = from_bounds(*bounds, size, size) return rasterize(feature_to_mercator(feature), out_shape=(size, size), transform=transform)
def write_raster(self, fname='output', epsg=None): try: import rasterio from rasterio import transform except ImportError: print('This method requires Rasterio') return tfm = transform.from_bounds(self.xmin, self.ymin, self.xmax, self.ymax, self.ncol, self.nrow) if epsg is not None: crs = {'init': 'epsg:{}'.format(epsg)} else: crs = None with rasterio.drivers(): with rasterio.open(fname, 'w', driver='GTiff', width=self.ncol, height=self.nrow, count=1, dtype=np.float64, nodata=0, transform=tfm, crs=crs) as dst: dst.write_band(1, np.flipud(self.data))
def get_vrt_transform(src, bounds, bounds_crs='epsg:3857'): """Calculate VRT transform. Attributes ---------- src : rasterio.io.DatasetReader Rasterio io.DatasetReader object bounds : list Bounds (left, bottom, right, top) bounds_crs : str Coordinate reference system string (default "epsg:3857") Returns ------- vrt_transform: Affine Output affine transformation matrix vrt_width, vrt_height: int Output dimensions """ dst_transform, _, _ = calculate_default_transform(src.crs, bounds_crs, src.width, src.height, *src.bounds) w, s, e, n = bounds vrt_width = math.ceil((e - w) / dst_transform.a) vrt_height = math.ceil((s - n) / dst_transform.e) vrt_transform = transform.from_bounds(w, s, e, n, vrt_width, vrt_height) return vrt_transform, vrt_width, vrt_height
def get_transform( geometry: Union[Polygon, MultiPolygon, Tuple[float, float, float, float]], width: int, height: int, ) -> affine.Affine: """Get transform of a Polygon or bounding box. Parameters ---------- geometry : Polygon, MultiPolygon, or tuple of float The geometry or the bounding box, (west, south, east, north). width: int The width of the target raster in pixels. height: int The height of the target raster in pixels. Returns ------- affin.Affine The affine transform of the geometry. """ if isinstance(geometry, (Polygon, MultiPolygon)): west, south, east, north = geometry.bounds elif isinstance(geometry, tuple) and len(geometry) == 4: west, south, east, north = geometry else: raise InvalidInputType( "geometry", "Polygon, MultiPolygon, or (west, south, east, north)") return rio_transform.from_bounds(west, south, east, north, width, height)
def test_identity_gcps(): """Define an identity transform using GCPs""" # Tile: [53, 96, 8] src_crs = dst_crs = 'EPSG:3857' width = height = 1000 left, bottom, right, top = (-11740727.544603072, 4852834.0517692715, -11584184.510675032, 5009377.085697309) # For comparison only, these are not used to calculate the transform. transform = from_bounds(left, bottom, right, top, width, height) # Define 4 ground control points at the corners of the image. gcps = [ GroundControlPoint(row=0, col=0, x=left, y=top, z=0.0), GroundControlPoint(row=0, col=1000, x=right, y=top, z=0.0), GroundControlPoint(row=1000, col=1000, x=right, y=bottom, z=0.0), GroundControlPoint(row=1000, col=0, x=left, y=bottom, z=0.0) ] # Compute an output transform. res_transform, res_width, res_height = _calculate_default_transform( src_crs, dst_crs, height=height, width=width, gcps=gcps) assert res_width == width assert res_height == height for res, exp in zip(res_transform, transform): assert round(res, 3) == round(exp, 3)
def test_identity_gcps(): """Define an identity transform using GCPs""" # Tile: [53, 96, 8] src_crs = dst_crs = 'EPSG:3857' width = height = 1000 left, bottom, right, top = ( -11740727.544603072, 4852834.0517692715, -11584184.510675032, 5009377.085697309) # For comparison only, these are not used to calculate the transform. transform = from_bounds(left, bottom, right, top, width, height) # Define 4 ground control points at the corners of the image. gcps = [ GroundControlPoint(row=0, col=0, x=left, y=top, z=0.0), GroundControlPoint(row=0, col=1000, x=right, y=top, z=0.0), GroundControlPoint(row=1000, col=1000, x=right, y=bottom, z=0.0), GroundControlPoint(row=1000, col=0, x=left, y=bottom, z=0.0)] # Compute an output transform. res_transform, res_width, res_height = _calculate_default_transform( src_crs, dst_crs, height=height, width=width, gcps=gcps) assert res_width == width assert res_height == height for res, exp in zip(res_transform, transform): assert round(res, 3) == round(exp, 3)
def generate(self, bounds_polygon, raster_shape, resolution=30, hour: int = 8, **kwargs): risk_map, _ = self.make_strike_map(bounds_polygon, hour, raster_shape, resolution) bounds = bounds_polygon.bounds flipped_bounds = (bounds[1], bounds[0], bounds[3], bounds[2]) risk_raster = gv.Image(risk_map, vdims=['strike_risk'], bounds=flipped_bounds).options( alpha=0.7, colorbar=True, colorbar_opts={'title': 'Person Strike Risk [h^-1]'}, cmap='viridis', tools=['hover'], clipping_colors={ 'min': (0, 0, 0, 0)}) import rasterio from rasterio import transform trans = transform.from_bounds(*flipped_bounds, *raster_shape) p = os.path.expanduser(f'~/GroundRiskMaps') if not os.path.exists(p): os.mkdir(p) rds = rasterio.open(p + f'/strike_risk_{hour}h_ac{hash(self.aircraft)}.tif', 'w', driver='GTiff', count=1, dtype=rasterio.float64, crs='EPSG:4326', transform=trans, compress='lzw', width=raster_shape[0], height=raster_shape[1]) rds.write(risk_map, 1) rds.close() return risk_raster, risk_map, None
def execute(self, eopatch): """ Execute function which adds new vector layer to the EOPatch :param eopatch: input EOPatch :type eopatch: EOPatch :return: New EOPatch with added vector layer :rtype: EOPatch """ bbox_map = self._get_submap(eopatch) height, width = self._get_shape(eopatch) dst_transform = transform.from_bounds(*eopatch.bbox, width=width, height=height) if self.feature_name in eopatch[self.feature_type]: raster = eopatch[self.feature_type][self.feature_name].squeeze() else: raster = np.ones( (height, width), dtype=self.raster_dtype) * self.no_data_value if not bbox_map.empty: features.rasterize( [(bbox_map.cascaded_union.buffer(0), self.raster_value)], out=raster, transform=dst_transform, dtype=self.raster_dtype) eopatch[self.feature_type][self.feature_name] = raster[..., np.newaxis] return eopatch
def save_mask_image(label_polygons, tile_polygons, xyz, tile_size, folder, prefix): x, y, z = xyz # Create the geometry for the mask tile_transformation = from_bounds(*tile_polygons.bounds, tile_size, tile_size) cropped_polygons = [ poly for poly in label_polygons if poly.intersects(tile_polygons) ] cropped_polygons_df = gpd.GeoDataFrame(geometry=cropped_polygons, crs='epsg:4326') # Create a mask using the footprint of the geometry tile_mask = sol.vector.mask.df_to_px_mask(df=cropped_polygons_df, channels=['footprint'], affine_obj=tile_transformation, shape=(tile_size, tile_size), boundary_width=5, boundary_type='inner', contact_spacing=5, meters=True) # Save the mask mask_image_filename = ('%s/%s_%s_%s_%s.png' % (folder, prefix, x, y, z)) imsave(mask_image_filename, tile_mask, check_contrast=False) return (mask_image_filename)
def save_tile_mask(labels_poly, tile_poly, xyz, tile_size, save_path="", prefix="", border_thickness=20): x, y, z = xyz tfm = from_bounds(*tile_poly.bounds, tile_size, tile_size) # get poly inside the tile cropped_polys = [ poly for poly in labels_poly if poly.intersects(tile_poly) ] cropped_polys_gdf = gpd.GeoDataFrame(geometry=cropped_polys, crs=4326) # TODO: do manually because I don't like their definition of contact df = cropped_polys_gdf feature_list = list(zip(df["geometry"], [255] * len(df))) if len(feature_list) == 0: # if no buildings return zero mask mask = np.zeros((tile_size, tile_size), np.uint8) else: mask = rast_features.rasterize(shapes=feature_list, out_shape=(args.tile_size, args.tile_size), transform=tfm) dist = get_signed_distance_transform(mask, border_thickness) im = np.stack([mask, dist, dist], axis=2) imsave(f"{save_path}/{prefix}{z}_{x}_{y}.png", im, check_contrast=False)
def save_raster(Z, x_min, x_max, y_min, y_max, res, crs, fname, fmt='GTIFF'): ''' Save a raster at fname. I followed tutorial there to do so : https://rasterio.readthedocs.io/en/latest/quickstart.html#creating-data Arguments: Z (2d numpy array): the array x_min (float): x min y_min (float): y min x_max (float): x max y_max (float): y max res (float): resolution crs: coordinate system code (usually you will call it from a dem object, just give it dem.crs) fname: path+name+.tif according to your OS ex: Windows: C://Data/Albania/Holtas/Holt.tif, Linux: /home/Henri/Albania/Shenalvash/She_zoom.tif fmt (str): string defining the format. "GTIFF" for tif files, see GDAL for option. WARNING: few outputs are buggy, FOR EXAMPLE "ENVI" bil format can be with the wrong dimensions. Returns: Nothing but saves a raster with the given parameters Authors: B.G. Date: 08/2018 ''' transform = from_bounds(x_min, y_max, x_max, y_min, Z.shape[1], Z.shape[0]) new_dataset = rio.open(fname, 'w', driver=fmt, height=Z.shape[0], width=Z.shape[1], count=1, dtype=Z.dtype.type, crs=crs, transform=transform, nodata=-9999) new_dataset.write(Z, 1) new_dataset.close()
def burn(tile, features, size, multicolors): """Burn tile with features. Args: tile: the mercantile tile to burn. features: the geojson features to burn. size: the size of burned image. Returns: image: rasterized file of size with features burned. """ if multicolors is True: geometry_list = [] def add(geometry_list, geometry): geometry_list.append(geometry) return 1 + len(geometry_list) shapes = ((geometry, add(geometry_list, geometry)) for feature in features for geometry in feature_to_mercator(feature)) else: burnval = 1 shapes = ((geometry, burnval) for feature in features for geometry in feature_to_mercator(feature)) bounds = mercantile.xy_bounds(tile) transform = from_bounds(*bounds, size, size) return rasterize(shapes, out_shape=(size, size), transform=transform)
def execute(self, eopatch): """ Execute function which adds new vector layer to the EOPatch :param eopatch: input EOPatch :type eopatch: eolearn.core.EOPatch :return: New EOPatch with added vector layer :rtype: eolearn.core.EOPatch """ bbox_map = self._get_submap(eopatch) data_arr = eopatch.get_feature(FeatureType.MASK, 'IS_DATA') dst_shape = data_arr.shape dst_transform = transform.from_bounds(*eopatch.bbox, width=dst_shape[2], height=dst_shape[1]) if eopatch.feature_exists(self.feature_type, self.feature_name): raster = eopatch.get_feature(self.feature_type, self.feature_name) else: raster = np.ones(dst_shape[1:3], dtype=self.raster_dtype) * self.no_data_value if not bbox_map.empty: features.rasterize( [(bbox_map.cascaded_union.buffer(0), self.raster_value)], out=raster, transform=dst_transform, dtype=self.raster_dtype) eopatch.add_feature(self.feature_type, self.feature_name, raster[..., np.newaxis]) return eopatch
def dump_ref_et_rasters(ref_et_filepath, dst_dir): ref_et_da = xr.open_dataarray(ref_et_filepath) # prepare metadata to dump the potential evapotranspiration rasters ref_et_da.name = 'ref_et' grid = ref_et_da.salem.grid width = grid.nx height = grid.ny west, east, south, north = grid.extent # prepare metadata to dump the potential evapotranspiration rasters meta = dict(driver='GTiff', dtype=ref_et_da.dtype, nodata=np.nan, width=width, height=height, count=1, transform=transform.from_bounds(west, south, east, north, width, height), crs=ref_et_da.attrs['pyproj_srs']) ref_et_raster_filepath_dict = {} for date, ref_et_day_da in ref_et_da.groupby('time'): ref_et_raster_filepath = _get_ref_eto_filepath(date, dst_dir) with rio.open(ref_et_raster_filepath, 'w', **meta) as dst: dst.write(ref_et_day_da.values, 1) ref_et_raster_filepath_dict[date] = ref_et_raster_filepath return ref_et_raster_filepath_dict
def to_terrain(self, dx, dy=None, resampling=warp.Resampling.bilinear): """Load geospatial raster data and reproject onto specified grid Usage ===== dx,dy : float Grid spacings [m]. If dy is not specified, then uniform spacing is assumed. resampling : warp.Resampling value, optional See `list(warp.Resampling)`. """ if dy is None: dy = dx # load raster if not os.path.isfile(self.tiffdata): raise FileNotFoundError('Need to download()') dem_raster = rasterio.open(self.tiffdata) # get source coordinate reference system, transform west, south, east, north = self.bounds src_height, src_width = dem_raster.shape src_crs = dem_raster.crs src_transform = transform.from_bounds(*self.bounds, src_width, src_height) src = dem_raster.read(1) # calculate destination coordinate reference system, transform dst_crs = self.utm_crs print('Projecting from', src_crs, 'to', dst_crs) # - get origin (the _upper_ left corner) from bounds orix, oriy = self.to_xy(north, west) origin = (orix, oriy) self.origin = origin dst_transform = transform.from_origin(*origin, dx, dy) # - get extents from lower right corner SE_x, SE_y = self.to_xy(south, east) Lx = SE_x - orix Ly = oriy - SE_y Nx = int(Lx / dx) Ny = int(Ly / dy) # reproject to uniform grid in the UTM CRS dem_array = np.empty((Ny, Nx)) warp.reproject(src, dem_array, src_transform=src_transform, src_crs=src_crs, dst_transform=dst_transform, dst_crs=dst_crs, resampling=resampling) utmx = orix + np.arange(0, Nx * dx, dx) utmy = oriy + np.arange((-Ny + 1) * dy, dy, dy) self.x, self.y = np.meshgrid(utmx, utmy, indexing='ij') self.z = np.flipud(dem_array).T self.zfun = RectBivariateSpline(utmx, utmy, self.z) self.have_terrain = True return self.x, self.y, self.z
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 subdivide(self, root: str = "", name: str = "", tif: rio.io.DatasetReader = (), sub: int = 5) -> None: if sub is 0: return if type(tif) == tuple: arr, box, meta = tif #with open("./data_lookup.csv", "a") as csv: # csv.write("|".join((root,"ROOT", repr(box))) +"\n") else: arr, box, meta = np.array(tif.read(1)), Box(tif.bounds), tif.meta width = meta["width"] = box.width / 2 height = meta["height"] = box.height / 2 sub_bound = { 0: (0, 1, -1, 0), 1: (1, 1, 0, 0), 2: (0, 0, -1, -1), 3: (1, 0, 0, -1) } sub_slice = { 0: { "sx": slice(None, int(height)), "sy": slice(None, int(width)) }, 1: { "sx": slice(None, int(height)), "sy": slice(int(width), None) }, 2: { "sx": slice(int(height), None), "sy": slice(None, int(width)) }, 3: { "sx": slice(int(height), None), "sy": slice(int(width), None) } } for idx in range(4): sub_name = name + (str(idx) if name is "" else f"_{idx}") directory = f"./{root}/{sub_name}/" if not os.path.exists(directory): os.mkdir(directory) meta["transform"] = from_bounds( box.left + width * sub_bound[idx][0], box.bottom + height * sub_bound[idx][1], box.right + width * sub_bound[idx][2], box.top + height * sub_bound[idx][3], width, height) with rio.open(directory + f"{self.tif_type}.tif", "w+", **meta) as sub_tif: sub_tif.write(arr[sub_slice[idx]["sx"], sub_slice[idx]["sy"]], indexes=1) self.subdivide(root, sub_name, sub_tif, sub - 1) if sub > 1: os.remove(directory + f"{self.tif_type}.tif") os.rmdir(directory) continue
def _get_transform_from_xr(dataset): """Create a geotransform from an xarray dataset. """ from rasterio.transform import from_bounds geotransform = from_bounds(dataset.longitude[0], dataset.latitude[-1], dataset.longitude[-1], dataset.latitude[0], len(dataset.longitude), len(dataset.latitude)) return geotransform
def get_overview_level( src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT], bounds: Tuple[float, float, float, float], height: int, width: int, dst_crs: CRS = WEB_MERCATOR_CRS, ) -> int: """ Return the overview level corresponding to the tile resolution. Freely adapted from https://github.com/OSGeo/gdal/blob/41993f127e6e1669fbd9e944744b7c9b2bd6c400/gdal/apps/gdalwarp_lib.cpp#L2293-L2362 Attributes ---------- src_dst : rasterio.io.DatasetReader Rasterio io.DatasetReader object bounds : list Bounds (left, bottom, right, top) in target crs ("dst_crs"). height : int Output height. width : int Output width. dst_crs: CRS or str, optional Target coordinate reference system (default "epsg:3857"). Returns ------- ovr_idx: Int or None Overview level """ dst_transform, _, _ = calculate_default_transform(src_dst.crs, dst_crs, src_dst.width, src_dst.height, *src_dst.bounds) src_res = dst_transform.a # Compute what the "natural" output resolution # (in pixels) would be for this input dataset vrt_transform = from_bounds(*bounds, width, height) target_res = vrt_transform.a ovr_idx = -1 if target_res > src_res: res = [src_res * decim for decim in src_dst.overviews(1)] for ovr_idx in range(ovr_idx, len(res) - 1): ovrRes = src_res if ovr_idx < 0 else res[ovr_idx] nextRes = res[ovr_idx + 1] if (ovrRes < target_res) and (nextRes > target_res): break if abs(ovrRes - target_res) < 1e-1: break else: ovr_idx = len(res) - 1 return ovr_idx
def geojson_tile_burn(tile, features, tile_size, burn_value=1): """Burn tile with GeoJSON features.""" shapes = ((geometry, burn_value) for feature in features for geometry in geojson_to_mercator(feature)) bounds = mercantile.xy_bounds(tile) transform = from_bounds(*bounds, tile_size, tile_size) return rasterize(shapes, out_shape=(tile_size, tile_size), transform=transform)
def _get_transform_from_xr(data, x_coord='longitude', y_coord='latitude'): """Create a geotransform from an xarray.Dataset or xarray.DataArray. """ from rasterio.transform import from_bounds geotransform = from_bounds(data[x_coord][0], data[y_coord][-1], data[x_coord][-1], data[y_coord][0], len(data[x_coord]), len(data[y_coord])) return geotransform
def _format(pixels, data_format): data, (data_bounds, data_crs) = pixels if data_format is not "raw": raise Exception("raw data is required") (count, height, width) = data.shape if count == 1: resolution = get_resolution_in_meters(pixels.bounds, (height, width)) # downsample to int16 if ground resolution is more than 10 meters # (at the equator) if resolution[0] > 10 and resolution[1] > 10: data = data.astype(np.int16) data.fill_value = _nodata(data.dtype) if np.issubdtype(data.dtype, np.float): predictor = 3 else: predictor = 2 meta = { "blockxsize": blocksize if width >= blocksize else width, "blockysize": blocksize if height >= blocksize else height, "compress": "deflate", "count": count, "crs": data_crs, "dtype": data.dtype, "driver": "GTiff", "nodata": data.fill_value if data.dtype != np.uint8 else None, "predictor": predictor, "height": height, "width": width, "tiled": width >= blocksize and height >= blocksize, "transform": transform.from_bounds(*data_bounds, width=width, height=height), } with MemoryFile() as memfile: with memfile.open(**meta) as dataset: dataset.update_tags(AREA_OR_POINT=area_or_point) dataset.write(data.filled()) return (CONTENT_TYPE, memfile.read())
def _write((tile, data)): if not contains_data((tile, data)): return print("Writing", tile) # 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)) # TODO constantize tmp_path = "/vsimem/tile" # create GeoTIFF meta = creation_options.copy() meta["count"] = 1 meta["nodata"] = data.fill_value meta["dtype"] = data.dtype meta["width"] = CHUNK_SIZE meta["height"] = CHUNK_SIZE meta["transform"] = from_bounds(ulx, lry, lrx, uly, CHUNK_SIZE, CHUNK_SIZE) with rasterio.drivers(): with rasterio.open(tmp_path, "w", **meta) as tmp: tmp.write(data, 1) # write out output_uri = urlparse(out_dir) contents = bytearray(virtual_file_to_buffer(tmp_path)) if output_uri.scheme == "s3": # TODO use mapPartitions so that the client only needs to be # instantiated once per partition client = boto3.client("s3") bucket = output_uri.netloc # TODO strip out trailing slashes on the path if necessary key = "%s/%d/%d/%d.tif" % (output_uri.path[1:], tile.z, tile.x, tile.y) response = client.put_object( ACL="public-read", Body=bytes(contents), Bucket=bucket, # CacheControl="TODO", ContentType="image/tiff", Key=key ) else: output_path = os.path.join(out_dir, "%d/%d/%d.tif" % (tile.z, tile.x, tile.y)) mkdir_p(os.path.dirname(output_path)) f = open(output_path, "w") f.write(contents) f.close()
def test_from_bounds_two(): width = 80 height = 80 left = -120 top = 70 right = -80.5 bottom = 30.5 tr = transform.from_bounds(left, bottom, right, top, width, height) # pixelwidth, rotation, ULX, rotation, pixelheight, ULY expected = Affine(0.49375, 0.0, -120.0, 0.0, -0.49375, 70.0) assert [round(v, 7) for v in tr] == [round(v, 7) for v in expected] # Round right and bottom right = -80 bottom = 30 tr = transform.from_bounds(left, bottom, right, top, width, height) # pixelwidth, rotation, ULX, rotation, pixelheight, ULY expected = Affine(0.5, 0.0, -120.0, 0.0, -0.5, 70.0) assert [round(v, 7) for v in tr] == [round(v, 7) for v in expected]
def test_identity(): """Get the same transform and dimensions back for same crs.""" # Tile: [53, 96, 8] src_crs = dst_crs = 'EPSG:3857' width = height = 1000 left, bottom, right, top = ( -11740727.544603072, 4852834.0517692715, -11584184.510675032, 5009377.085697309) transform = from_bounds(left, bottom, right, top, width, height) res_transform, res_width, res_height = _calculate_default_transform( src_crs, dst_crs, width, height, left, bottom, right, top) assert res_width == width assert res_height == height for res, exp in zip(res_transform, transform): assert round(res, 3) == round(exp, 3)
def process_tile(tile): """Process a single MBTiles tile.""" global base_kwds, 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"] = from_bounds(ulx, lry, lrx, uly, 256, 256) with rasterio.open("/vsimem/tileimg", "w", **kwds) as tmp: # Reproject the src dataset into image tile. for bidx in tmp.indexes: reproject(rasterio.band(src, bidx), rasterio.band(tmp, bidx)) # Get contents of the virtual file. contents = bytearray(virtual_file_to_buffer("/vsimem/tileimg")) return tile, contents
def process_chunk(tile, input, creation_options, resampling): """Process a single tile.""" from rasterio.warp import RESAMPLING input = input.replace("s3://", "/vsicurl/http://s3.amazonaws.com/") print("Chunking initial image for", tile) # 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)) tmp_path = "/vsimem/tile" with rasterio.drivers(): with rasterio.open(input, "r") as src: meta = src.meta.copy() meta.update(creation_options) meta["height"] = CHUNK_SIZE meta["width"] = CHUNK_SIZE meta["transform"] = from_bounds(ulx, lry, lrx, uly, CHUNK_SIZE, CHUNK_SIZE) # write to a tmp file to allow GDAL to handle the transform with rasterio.open(tmp_path, "w", **meta) as tmp: # Reproject the src dataset into image tile. for bidx in src.indexes: reproject( source=rasterio.band(src, bidx), destination=rasterio.band(tmp, bidx), resampling=getattr(RESAMPLING, resampling), num_threads=multiprocessing.cpu_count(), ) # check for chunks containing only NODATA data = tmp.read(masked=True) if data.mask.all(): return # TODO hard-coded for the first band return (tile, data[0])
def downsample((tile, data)): if data is None: return print("Downsampling", tile) # 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)) # TODO constantize tmp_path = "/vsimem/tile" # create GeoTIFF meta = { "driver": "GTiff", "crs": "EPSG:3857", "nodata": data.fill_value, "count": 1, "dtype": data.dtype, "width": CHUNK_SIZE, "height": CHUNK_SIZE, "transform": from_bounds(ulx, lry, lrx, uly, CHUNK_SIZE, CHUNK_SIZE), } with rasterio.drivers(): with rasterio.open(tmp_path, "w", **meta) as tmp: # use GDAL to resample by writing an ndarray and immediately reading # it out into a smaller array tmp.write(data, 1) resampled = tmp.read( indexes=1, masked=True, out=ma.array(np.empty((CHUNK_SIZE / 2, CHUNK_SIZE / 2), data.dtype)), ) if resampled.mask.all(): return corner = CORNERS[(tile.x % 2, tile.y % 2)] return (mercantile.parent(tile), (corner, resampled))
def burn(tile, features, size): '''Burn tile with features. Args: tile: the mercantile tile to burn. features: the geojson features to burn. size: the size of burned image. Returns: image: rasterized file of size with features burned. ''' # the value you want in the output raster where a shape exists burnval = 1 shapes = ((geometry, burnval) for feature in features for geometry in feature_to_mercator(feature)) bounds = mercantile.xy_bounds(tile) transform = from_bounds(*bounds, size, size) result = rasterize(shapes, out_shape=(size, size), transform=transform) return Image.fromarray(result, mode='P')
def _tile_worker(tile): """ For each tile, and given an open rasterio src, plus a`global_args` dictionary with attributes of `base_val`, `interval`, and a `writer_func`, warp a continous single band raster to a 512 x 512 mercator tile, then encode this tile into RGB. Parameters ----------- tile: list [x, y, z] indices of tile Returns -------- tile, buffer tuple with the input tile, and a bytearray with the data encoded into the format created in the `writer_func` """ x, y, z = tile bounds = [c for i in (mercantile.xy(*mercantile.ul(x, y + 1, z)), mercantile.xy(*mercantile.ul(x + 1, y, z))) for c in i] toaffine = transform.from_bounds(*bounds + [512, 512]) out = np.empty((512, 512), dtype=src.meta['dtype']) reproject( rasterio.band(src, 1), out, dst_transform=toaffine, dst_crs="epsg:3857", resampling=RESAMPLING.bilinear) out = data_to_rgb(out, global_args['base_val'], global_args['interval']) return tile, global_args['writer_func'](out, global_args['kwargs'].copy(), toaffine)
raster_extent_fields = ['x_min', 'y_min', 'x_max', 'y_max', 'cell_height', 'cell_width', 'rows', 'cols'] RasterExtent = namedtuple('RasterExtent', raster_extent_fields) raster_extent = RasterExtent(562786.017, 4482993.928, 609786.017, 4530093.928, 50.0, 50.0, 942, 949) # rasterio kwargs raster_kwargs = {'blockxsize': 949, 'blockysize': 942, 'count': 1, 'crs': {'init': u'epsg:26918'}, 'driver': u'GTiff', 'dtype': 'float32', 'nodata': None, 'height': 949, 'width': 942, 'tiled': False, 'transform': from_bounds( raster_extent.x_min, raster_extent.y_min, raster_extent.x_max, raster_extent.y_max, raster_extent.cols, raster_extent.rows )} def get_weekday_dict(): """Return a dictionary that maps period => list of weekday obs Used to create indicator variables for day of week for each period """ current_day = datetime(2013, 01, 01) days_of_week = {} while current_day < datetime(2014, 01, 01): l = [0, 0, 0, 0, 0, 0, 0]
import numpy import rasterio from rasterio import transform from rasterio.warp import reproject, RESAMPLING tempdir = '/tmp' tiffname = os.path.join(tempdir, 'example.tif') with rasterio.drivers(): # Consider a 512 x 512 raster centered on 0 degrees E and 0 degrees N # with each pixel covering 15". rows, cols = src_shape = (512, 512) dpp = 1.0/240 # decimal degrees per pixel west, south, east, north = -cols*dpp/2, -rows*dpp/2, cols*dpp/2, rows*dpp/2 src_transform = transform.from_bounds(west, south, east, north, cols, rows) src_crs = {'init': 'EPSG:4326'} source = numpy.ones(src_shape, numpy.uint8)*255 # Prepare to reproject this rasters to a 1024 x 1024 dataset in # Web Mercator (EPSG:3857) with origin at -237481.5, 237536.4. dst_shape = (1024, 1024) dst_transform = transform.from_origin(-237481.5, 237536.4, 425.0, 425.0) dst_crs = {'init': 'EPSG:3857'} destination = numpy.zeros(dst_shape, numpy.uint8) reproject( source, destination, src_transform=src_transform, src_crs=src_crs,
def test_from_bounds(): with rasterio.open('tests/data/RGB.byte.tif') as src: w, s, e, n = src.bounds tr = transform.from_bounds(w, s, e, n, src.width, src.height) assert [round(v, 7) for v in tr] == [round(v, 7) for v in src.affine]