def align(src_rst, template_rst, out_rst, verbose=False): """ Align a source raster on a template Args : src_rst (str) : path to the source raster template_rst (str) : path to the template raster out_rst (str) : path to the output raster Return : out_rst """ # apply the verbose option v_print = custom_print(verbose) # get template crs and transform with rio.open(template_rst) as tplt, rio.open(src_rst) as src: transform, width, height = warp.calculate_default_transform( src.crs, tplt.crs, tplt.width, tplt.height, *tplt.bounds ) kwargs = src.meta.copy() kwargs.update( driver = 'GTiff', height = height, width = width, transform = transform, compress = 'lzw' ) #destination with rio.open(out_rst, 'w', **kwargs) as dst: for i in range(1, dst.count+1): warp.reproject( source = rio.band(src, i), destination = rio.band(dst, i), src_transform = src.transform, src_crs = src.crs, dst_transform = transform, dst_crs = tplt.crs, resampling = warp.Resampling.bilinear ) v_print(f'The raster {src_rst} has been align on {template_rst} in {out_rst}') return
def cutline(src_rst, shape_vector, out_rst, verbose=False): """ Cut the raster on a .shp vector file Args : src_rst (str) : path to the source raster shape_vector (str) : path to the cutting shapefile out_rst (str) : path to the output raster verbose (bool) : wether to display the print info Return : out_rst """ # apply the verbose option v_print = custom_print(verbose) # cut the source raster with rio.open(src_rst) as src: # get the crs crs = src.crs # read the shapefile gdf = gpd.read_file(shape_vector).to_crs(crs) shapes = gdf.geometry out_image, out_transform = mask(src, shapes, all_touched=True, crop=True) out_meta = src.meta.copy() out_meta.update(driver='GTiff', height=out_image.shape[1], width=out_image.shape[2], transform=out_transform, compress='lzw') # write the croped image into the dst file with rio.open(out_rst, "w", **out_meta) as dst: dst.write(out_image) v_print( f'The raster has been cut to {shape_vector} extends and saved in {out_rst}' ) return
def rasterize(src_vector, out_rst, res=30, column=None, verbose=False): """ Burns vector geometries into a raster. Args : src_vector (str) : path to the source vector shapefile out_rst (str, optional) : path to the output raster res (int, optional) : the resolution of the output raster. If none, the default landsat 7 30m res will be used column (str, optional) : the name of the column to use as value in the output raster. default ot the first one """ # apply the verbose option v_print = custom_print(verbose) # read the vector data gdf = gpd.read_file(src_vector).to_crs("EPSG:4326") # identify the column to be burn in the raster if not column: column = gdf.columns[0] # optimize dtype dtype = rio.dtypes.get_minimum_dtype(gdf[column]) # optimize the nodata value to meet the dtype fill = np.nan if np.issubdtype(dtype, np.integer): fill = 0 # convert the metric resolution into deg (as we work in EPSG 4326) # consider the equator approximation : 1° = 111 Km res = (res / 111) * (10**(-3)) out_grid = make_geocube(vector_data=gdf, measurements=[column], resolution=(-res, res), fill=fill) # write the column to raster file out_grid[column].rio.to_raster(out_rst, dtype=dtype) v_print( f'The vector geometries have been burn into the raster : {out_rst}') return
def add_pct(src_rst, colors, out_rst=None, verbose=False): """ Add a palette color table to the first band of a raster image Args : src_rst (str) : path to the source raster (need to be in bytes (unint8)) colors (str) : path to the color table out_rst (str, optional) : path to the output raster, will overwrite the input file if None verbose (bool, optional) : "wether the function must output information or not" Return : out_rst """ # apply the verbose option v_print = custom_print(verbose) # read the colors palette header = ['value', 'red', 'green', 'blue', 'alpha'] df = pd.read_csv(colors, names= header).fillna(255, downcast='infer') # transform the df into a dictionnary compatible with rasterio colormap = {} for index, row in df.iterrows(): colormap[row.value] = (row.red, row.green, row.blue, row.alpha) # select output file if not out_rst: out_rst = src_rst # add the pct to the output file with rio.open(src_rst) as src: out_meta = src.meta.copy() data = src.read() with rio.open(out_rst, "w", **out_meta) as dst: dst.write(data) dst.write_colormap(1, colormap) v_print(f'The palette color table has been added to {out_rst}') return
def grid(src_vector, out_vector, size, verbose=False): """ create a grid out of a shapefile and write it into another shapefile Args : src_vector (str) : path to the source vector shapefile out_vector (str) : path to the output vector file size (float) : the grid size in meters """ # apply the verbose option v_print = custom_print(verbose) # read the vector data in mercator proj gdf = gpd.read_file(src_vector).to_crs('EPSG:3857') # extract bounds from gdf min_lon, min_lat, max_lon, max_lat = gdf.total_bounds # compute the longitudes and latitudes top left corner coordinates longitudes = np.arange(min_lon, max_lon, size) latitudes = np.arange(min_lat, max_lat, size) # create the grid geometry points = [] for coords in product(longitudes, latitudes): points.append(Point(coords[0], coords[1])) # create a buffer grid in lat-long grid = gpd.GeoDataFrame({ 'geometry': points }, crs='EPSG:3857').buffer(size, cap_style=3) grid = gpd.clip(grid, gdf) grid = grid.to_crs('EPSG:4326') # export as shapefile grid.to_file(out_vector) v_print( f'The vector geometries have been transfor into grid : {out_vector}') return out_vector
def clump(src_rst, out_rst, band=1, mask_rst=None, verbose=False): """ Add spatial coherency to existing classes by combining adjacent similar classified areas. Args : src_rst (str) : path to the source raster out_rst (str) : path to the output raster band (int, optionnal) : use determined band of the image (if not defaulted to the first one) mask_rst (str, optional) : use maskfile and process only areas having mask value >0 verbose (bool) : wether to display the print info """ # apply the verbose option v_print = custom_print(verbose) # default to 8 neigbours struct = ndi.generate_binary_structure(2, 2) with rio.open(src_rst) as src: raster = src.read(band) meta = src.meta.copy() # mask raster if needed if mask_rst: with rio.open(mask_rst) as src: mask = src.read(1) raster = (mask != 0) * raster # adapt the raster size to optimize memory usage dtype = rio.dtypes.get_minimum_dtype(raster) raster = raster.astype(dtype) # the raster can be non-binary each value need to be clump separately # identify the features count = np.bincount(raster.flatten()) features = np.where(count != 0)[0] del count # init offset = 0 raster_labeled = np.zeros(raster.shape, dtype=raster.dtype) # loop in values for feature in features[1:]: # label the filtered dataset label = ndi.label(raster == feature, structure=struct)[0] label[label != 0] = offset + label[label != 0] # add it to the dataset raster_labeled = raster_labeled.astype(label.dtype) raster_labeled += label # increase coef offset = np.amax(raster_labeled) # free memory del raster # adapt the raster size to optimize memory usage dtype = rio.dtypes.get_minimum_dtype(raster_labeled) raster_labeled = raster_labeled.astype(dtype) meta.update(dtype=dtype) with rio.open(out_rst, 'w', **meta) as dst: dst.write(raster_labeled, 1) del raster_labeled v_print(f'The raster has been clumped in {out_rst}') return
def zonal_stat(src_rst, mask_vector, out_vector, measurments=available_stats[:4], verbose=False): """Compute statistics over value of the source raster for each geometry defined in the mask. results are displayed in a vector file. The user can choose the statistics he want to inclued in the final file Args : src_rst (str) : path to the source raster mask_vector (str) : path to the vector mask out_vector (str) : path to the output vector measurment ([str], optional) : list of the measurment you want to perform. available measurments are : - min - max - mean - count - sum - std - median - majority - minority - unique - range - nodata - percentile the first 4 are the defaulted values. 'all' key word can also be used to perform all measurments. ['hist'] will perform an histogram of the categorical value of the raster. it will be the only stat performed if it is found in the list verbose (bool) : wether or not to display text """ # apply the verbose option v_print = custom_print(verbose) # get the file parameters with rio.open(src_rst) as f: crs = f.crs nodata = f.nodata # open the mask and project it into the raster crs gdf = gpd.read_file(mask_vector).to_crs(crs) categorical = False stats = None # filter the measurments if measurments: if 'hist' in measurments: categorical = True elif 'all' in measurments: stats = available_stats[:len(available_stats) - 2 - 1] else: tmp = [stat for stat in measurments if stat in available_stats] stats = None if len(tmp) == 0 else tmp # if categorical I need to fetch the potential values if categorical: with rio.open(src_rst) as f: count = np.bincount(f.read(1).flatten()) features = np.where(count != 0)[0] # remove the nodata value from the feature features = features[~np.isin(features, nodata)] del count # loop over the different geometry # longer than using just rasterstats but then I cannot display any evolution informations tmp_res = {str(i): [] for i in features} if categorical else {i: [] for i in stats} with tqdm(total=len(gdf), disable=not verbose) as pbar: for index, row in gdf.iterrows(): #update tqdm pbar.update(1) # get the geometry coordinates left, bottom, right, top = row.geometry.bounds with rio.open(src_rst) as f: # window inside the extends of the raster left = max(left, f.bounds.left) top = min(top, f.bounds.top) right = min(right, f.bounds.right) bottom = max(bottom, f.bounds.bottom) # creat the window over the geometry tlx, tly = f.index(left, top) brx, bry = f.index(right, bottom) win = Window.from_slices((tlx, brx), (tly, bry)) win_transform = f.window_transform(win) # read the data in the window data = f.read(1, window=win) # do a zonal stat on 1 geometry zs = rstats.zonal_stats( [row.geometry], #there is only one geometry data, affine=win_transform, nodata=nodata, all_touched=True, stats=stats, categorical=categorical)[0] # free memory del data # for categorical measurments I need to transform the index in int if categorical: zs = {str(int(i)): zs[i] for i in zs} # fill the dictionary for idx in tmp_res: val = zs[idx] if idx in zs.keys() else 0 tmp_res[idx].append(val) # create the result geodataframe stat_gdf = gpd.GeoDataFrame( tmp_res, geometry=gdf.geometry, columns=[str(i) for i in tmp_res] + ['geometry'], # geopandas refuse int as column key crs=crs) # save it stat_gdf.to_file(out_vector) return