def write_splitted_images(self, target_dir, filename, filter_fun=lambda x:True, idxs_to_be_kept=None): """Write all splitted images as tif file. Parameters ---------- target_dir: str The directory to save the splitted images. filename: str The prefix od the filename. The index number will be followed by the output filename you defined, e.g. <filename>_idx_idxh_idxw;. filter_fun: function, optional, default: lambda x:True Filter specific spllitted images and not save it. The input of the function is a splitted image. If the function output is True, the splitted image will be saved. idxs_to_be_kept: list, optional list of indexs of splitted image to save as file. """ df_attribute = self.get_geo_attribute(return_geo_transform=True) splitted_images = self.get_splitted_images() idxs_to_be_kept = range(len(df_attribute)) if idxs_to_be_kept is None else idxs_to_be_kept for idx, row in df_attribute.iterrows(): if idx in idxs_to_be_kept: target_img = splitted_images[idx].copy() idx_str = "_" + ("%3i"%idx).replace(" ", "0") idx_h_str = "_" + ("%3i"%row['idx_h']).replace(" ", "0") idx_w_str = "_" + ("%3i"%row['idx_w']).replace(" ", "0") path = os.path.join(target_dir, filename + idx_str + idx_h_str + idx_w_str + ".tif") gt = row['geo_transform'] if filter_fun(target_img): tgp.write_raster(path, target_img, gt, self.proj, self.gdaldtype, self.no_data_value)
def raster_pixel_to_polygon(src_tif_path, dst_shp_path, all_bands_as_feature=False, crs=None, return_gdf=False): """ crs should be dict type 'epsg:<epsg_code>', e.g. 'epsg:4326' """ rows, cols, bands, geo_transform, projection, dtype_gdal, no_data_value, metadata = tgp.get_raster_info( src_tif_path) X = tgp.get_raster_data(src_tif_path) idxs = np.where(np.ones_like(X[:, :, 0], dtype=bool)) rows = [] for row_idx, col_idx in zip(*idxs): row = {} npidx = (row_idx, col_idx) row['geometry'] = Polygon( tgp.npidxs_to_coord_polygons([npidx], geo_transform)[0]) if all_bands_as_feature: for i in range(X.shape[2]): row['band' + str(i + 1)] = X[row_idx, col_idx, i] rows.append(row) df_shp = gpd.GeoDataFrame(rows, geometry='geometry') if crs: df_shp.crs = crs if return_gdf: return df_shp else: df_shp.to_file(dst_shp_path)
def get_geo_attribute(self, return_geo_transform=False, crs=None): """Get geo_attributes (idx, idx_h, idx_w, geo_transform, geometry) of all splitted images. Parameters ---------- return_geo_transform: bool, optional, default: False Return gdal geo_transform for each geometry in the output GeoDataFrame. crs: str, optional The crs for the output GeoDataFrame e.g. 'epsg:4326'. Returns ------- df_attribute: gpd.GeoDataFrame The geo_attributes of all splitted images, which can be output as a shapefile. """ rows = [] for i in range(self.n_splitted_images): idx_h , idx_w = self.convert_order_to_location_index(i) h_start_inner, h_stop_inner = self.__convert_to_inner_index_h(idx_h, idx_h) w_start_inner, w_stop_inner = self.__convert_to_inner_index_w(idx_w, idx_w) left_top_coord = tgp.npidxs_to_coords([(h_start_inner, w_start_inner)], self.src_gt)[0] left_buttom_coord = tgp.npidxs_to_coords([(h_start_inner, w_stop_inner)], self.src_gt)[0] right_buttom_coord = tgp.npidxs_to_coords([(h_stop_inner, w_stop_inner)], self.src_gt)[0] right_top_coord = tgp.npidxs_to_coords([(h_stop_inner, w_start_inner)], self.src_gt)[0] x_min, y_max = left_top_coord row = { "idx":i, "idx_h":idx_h, "idx_w":idx_w, "geo_transform":(x_min, self.src_gt[1], self.src_gt[2], y_max, self.src_gt[4], self.src_gt[5]), "geometry": Polygon([left_top_coord, left_buttom_coord, right_buttom_coord, right_top_coord, left_top_coord]), } rows.append(row) df_attribute = gpd.GeoDataFrame(rows, geometry='geometry') if crs is not None: df_attribute.crs = crs elif self.proj is not None: try: df_attribute.crs = 'init:' + str(tgp.wkt_to_epsg(self.proj)) except: df_attribute.crs = self.proj if not return_geo_transform: df_attribute.drop('geo_transform', axis=1, inplace=True) return df_attribute
def to_file(self, fp): """Save the file. It is recommended to save the file using tif format, that is use '.tif' as its extension. Parameters ---------- fp: str File path. """ tgp.write_raster(fp, self.data, self.geo_transform, self.projection, self.gdaldtype, self.no_data_value, self.metadata)
def reproject(self, dst_crs='EPSG:4326', src_crs=None): """Reproject the raster data. Parameters ---------- dst_crs: str, optional, default: EPSG:4326 The target crs to transform the raster to. src_crs: str, optional The source crs to transform the raster from. If None, get the projection from src_raster. Returns ------- dst_raster: Raster Reprojected result. Examples -------- >>> import TronGisPy as tgp >>> src_raster_fp = tgp.get_testing_fp() >>> src_raster = tgp.read_raster(src_raster_fp) >>> print("project(before)", src_raster.projection) >>> dst_raster = src_raster.reproject(dst_crs='EPSG:4326', src_crs=None) >>> print("project(after)", tgp.wkt_to_epsg(dst_raster.projection)) """ src_ds = self.to_gdal_ds() if src_crs: dst_ds = gdal.Warp('', src_ds, srcSRS=src_crs, dstSRS=dst_crs, format='MEM') else: dst_ds = gdal.Warp('', src_ds, dstSRS=dst_crs, format='MEM') if dst_ds is None: try: src_crs = "EPSG:" + str(tgp.wkt_to_epsg(self.projection)) dst_ds = gdal.Warp('', src_ds, srcSRS=src_crs, dstSRS=dst_crs, format='MEM') assert dst_ds is not None, "Please provide src_crs since the raster does not contain valid crs information." print("Please provide src_crs to accelerate the process.") except: assert False, "Please provide src_crs since the raster does not contain crs information." dst_raster = tgp.read_gdal_ds(dst_ds) return dst_raster
def __getitem__(self, slice_value): if type(slice_value) in [int, slice]: h_start, h_stop = self.__process_slice_value(slice_value) w_start, w_stop = 0, self.n_steps_w elif type(slice_value) == tuple: h_start, h_stop = self.__process_slice_value(slice_value[0]) w_start, w_stop = self.__process_slice_value(slice_value[1]) h_start_inner, h_stop_inner = self.__convert_to_inner_index_h(h_start, h_stop) w_start_inner, w_stop_inner = self.__convert_to_inner_index_w(w_start, w_stop) data = self.src_image[h_start_inner:h_stop_inner, w_start_inner:w_stop_inner] gt = np.array(self.src_gt).copy() gt[[0, 3]] = tgp.npidxs_to_coords([(h_start_inner, w_start_inner)], self.src_gt)[0] raster = tgp.Raster(data, gt, self.proj, self.gdaldtype, self.no_data_value) return raster
def dem_to_aspect(src_raster, band=0, alg='Horn', trigonometric=False): """Calculate the aspect for the DEM. Parameters ---------- src_raster : Raster The dem used to calculate the aspect. band : int, optional, default: 0 source band number to use. alg : {'ZevenbergenThorne' or 'Horn'}, optional, default: Horn The literature suggests Zevenbergen & Thorne to be more suited to smooth landscapes, where Horn’s formula to perform better on rougher terrain. trigonometric: bool, optional, default: False whether to return trigonometric angle instead of azimuth. Thus 0deg means East, 90deg North, 180deg West, 270deg South. Returns ------- dst_raster: Raster Aspect calculated from the DEM. """ options = dict(band=band + 1, alg=alg, trigonometric=trigonometric, format='MEM') ds_src = src_raster.to_gdal_ds() ds = gdal.DEMProcessing('', ds_src, 'aspect', **options) dst_raster = tgp.read_gdal_ds(ds) return dst_raster
def apply(self, apply_fun, return_raster=False, gdaldtype=None, no_data_value=None): # apply functino to all images: """Apply a function to all splitted images. Parameters ---------- apply_fun: function The function used to apply to all splitted images. Returns ------- return_objs: nparray splitted images that have applied the function. """ return_objs = [] padded_image = self.padded_image geo_transforms = self.get_geo_attribute(True)['geo_transform'] for i in range(self.n_splitted_images): idx_h , idx_w = self.convert_order_to_location_index(i) h_start_inner, h_stop_inner = self.__convert_to_inner_index_h(idx_h, idx_h) w_start_inner, w_stop_inner = self.__convert_to_inner_index_w(idx_w, idx_w) splitted_img = padded_image[h_start_inner:h_stop_inner,w_start_inner:w_stop_inner].copy() data = apply_fun(splitted_img) if return_raster: gt = geo_transforms[i] pj = self.proj gdaldtype = self.gdaldtype if gdaldtype is None else gdaldtype no_data_value = self.no_data_value if no_data_value is None else no_data_value raster = tgp.Raster(data, gt, pj, gdaldtype, no_data_value) return_objs.append(raster) else: return_objs.append(data) return return_objs
def remap_by_ref_raster(self, ref_raster): """remap the raster on to another canvas drew by referenced raster. Parameters ---------- ref_raster: Raster The referenced raster. The extent, projection and resolution will be used. Returns ------- dst_raster: Raster Remapped result. """ src_ds = self.to_gdal_ds() src_projection, src_gdaldtype = self.projection, self.gdaldtype ref_geo_transform, ref_projection = ref_raster.geo_transform, ref_raster.projection output_bounds = ref_raster.extent_for_gdal # minX, minY, maxX, maxY x_res, y_res = ref_geo_transform[1], ref_geo_transform[5] output_type = src_gdaldtype dst_ds = gdal.Warp('', src_ds, xRes=x_res, yRes=y_res, outputBounds=output_bounds, format='MEM', srcSRS=src_projection, dstSRS=ref_projection, outputType=output_type, resampleAlg='bilinear') dst_raster = tgp.read_gdal_ds(dst_ds) return dst_raster
def dem_to_slope(src_raster, band=0, alg='Horn', slope_format='degree'): """Calculate the slope for the DEM. Parameters ---------- src_raster : Raster The dem used to calculate the slope. band : int, optional, default: 0 source band number to use. alg : {'ZevenbergenThorne' or 'Horn'}, optional, default: Horn The literature suggests Zevenbergen & Thorne to be more suited to smooth landscapes, where Horn’s formula to perform better on rougher terrain. slope_format: {"degree" or "percent"}, optional, default degree The format of the slope. Returns ------- dst_raster: Raster Slope calculated from the DEM. """ options = dict(band=band + 1, alg=alg, slopeFormat=slope_format, format='MEM') ds_src = src_raster.to_gdal_ds() ds = gdal.DEMProcessing('', ds_src, 'slope', **options) dst_raster = tgp.read_gdal_ds(ds) return dst_raster
def update_gdaldtype_by_npdtype(self): """Update gdaldtype according to gdaldtype using `TronGisPy.npdtype_to_gdaldtype`. For memory operation, numpy dtype will be used. For saving the file, gdal dtype will be used. If the data of raster object have been changed, its recomended to update the dtype before saveing the file. """ self.gdaldtype = tgp.npdtype_to_gdaldtype(self.data.dtype)
def dem_to_hillshade(src_raster, band=0, alg='Horn', azimuth=315, altitude=45): """Calculate the hillshade for the DEM. Parameters ---------- src_raster : Raster The dem used to calculate the hillshade. band : int, optional, default: 0 source band number to use. alg : {'ZevenbergenThorne' or 'Horn'}, optional, default: Horn The literature suggests Zevenbergen & Thorne to be more suited to smooth landscapes, where Horn’s formula to perform better on rougher terrain. azimuth : int, optional, default 315 Azimuth of the light, in degrees. 0 if it comes from the top of the raster, 90 from the east, ... The default value, 315, should rarely be changed as it is the value generally used to generate shaded maps. altitude : int, optional, default 45 Altitude of the light, in degrees. 90 if the light comes from above the DEM, 0 if it is raking light. Returns ------- dst_raster: Raster Hillshade calculated from the DEM. """ options = dict(band=band + 1, alg=alg, azimuth=azimuth, altitude=altitude, format='MEM') ds_src = src_raster.to_gdal_ds() ds = gdal.DEMProcessing('', ds_src, 'hillshade', **options) dst_raster = tgp.read_gdal_ds(ds) return dst_raster
def get_raster_extent(fp, return_type='poly'): """Get the boundary of the raster file. Parameters ---------- fp: str File path of the raster file. return_type: {'poly', 'plot', 'gdal'} If 'poly', return four corner coordinates. If plot, return (xmin, xmax, ymin, ymax). If 'gdal', return (xmin, ymin, xmax, ymax). Returns ------- extent: ndarray or tuple Depends on return_type. If 'poly', return four corner coordinates. If plot, return (xmin, xmax, ymin, ymax). If 'gdal', return (xmin, ymin, xmax, ymax). Examples -------- >>> import TronGisPy as tgp >>> from shapely.geometry import Polygon >>> raster_fp = tgp.get_testing_fp() >>> extent = tgp.get_raster_extent(raster_fp) >>> Polygon(extent).area 570976.9697303267 """ rows, cols, geo_transform = get_raster_info(fp, ['rows', 'cols', 'geo_transform']) return tgp.get_extent(rows, cols, geo_transform, return_type)
def remap_tif(src_tif_path, dst_tif_path, ref_tif_path): src_projection, src_gdaldtype = tgp.get_raster_info( src_tif_path, ['projection', 'gdaldtype']) ref_geo_transform, ref_projection = tgp.get_raster_info( ref_tif_path, ['geo_transform', 'projection']) output_bounds = tgp.get_raster_extent(ref_tif_path, 'gdal') # (minX, minY, maxX, maxY) x_res, y_res = ref_geo_transform[1], ref_geo_transform[5] output_type = src_gdaldtype gdal.Warp(dst_tif_path, src_tif_path, outputBounds=output_bounds, xRes=x_res, yRes=y_res, outputType=output_type, srcSRS=src_projection, dstSRS=ref_projection)
def write_combined_tif(self, X, dst_tif_path, gdaldtype=None, no_data_value=None): """Combine the model predict result on splitted images and write as tif file. Parameters ---------- X: array_like The splitted image prediction result. should have the same shape with the SplittedImage.get_splitted_images. dst_tif_path: str The location to save the tif file. gdaldtype: int, optional The type of the cell defined in gdal which will affect the information to be stored when saving the file. This can be generate from `gdal.GDT_XXX` such as `gdal.GDT_Int32` equals 5 and `gdal.GDT_Float32` equals 6. no_data_value: int or float, optional Define which value to replace nan in numpy array when saving a raster file. """ X_combined_bands = self.get_combined_image(X) gdaldtype = gdaldtype if gdaldtype is not None else self.gdaldtype no_data_value = no_data_value if no_data_value is not None else self.no_data_value tgp.write_raster(dst_tif_path, X_combined_bands, geo_transform=self.src_gt, projection=self.proj, gdaldtype=gdaldtype, no_data_value=no_data_value)
def tif_composition(ref_tif_path, src_tif_paths, dst_tif_path, dst_tif_dtype_gdal=None): """ ref_tif_path: should be used to create the canvas with final coordinate system, geo_transform and projection, src_tif_paths: should be in list type with elements with full path of tif images. dst_tif_path: output file path """ # get geo info rows, cols, bands, geo_transform, projection, dtype_gdal, no_data_value, metadata = tgp.get_raster_info( ref_tif_path) if dst_tif_dtype_gdal: dtype_gdal = dst_tif_dtype_gdal # cal bands count bands_for_each_tif = [ tgp.get_raster_info(tif_path)[2] for tif_path in src_tif_paths ] bands = sum(bands_for_each_tif) # bands compositions: create new tif dst_ds = gdal.GetDriverByName('GTiff').Create(dst_tif_path, cols, rows, bands, dtype_gdal) dst_ds.SetGeoTransform(geo_transform) dst_ds.SetProjection(projection) # bands compositions: write bands band_num = 1 for tif_path, bands_for_the_tif in zip(src_tif_paths, bands_for_each_tif): nparr = tgp.get_raster_data(tif_path) for band_num_for_the_tif in range(bands_for_the_tif): band = dst_ds.GetRasterBand(band_num) band.WriteArray(nparr[:, :, band_num_for_the_tif], 0, 0) band.FlushCache() if no_data_value: band.SetNoDataValue(no_data_value) band_num += 1 dst_ds = None
def __repr__(self): desc = "" desc += "shape: ({rows}, {cols}, {bands})\n".format(rows=self.rows, cols=self.cols, bands=self.bands) desc += "gdaldtype: {gdaldtype}\n".format( gdaldtype=tgp.get_gdaldtype_name(self.gdaldtype)) desc += "geo_transform: {geo_transform}\n".format( geo_transform=self.geo_transform) desc += "projection: {projection}\n".format(projection=self.projection) desc += "no_data_value: {no_data_value}\n".format( no_data_value=self.no_data_value) desc += "metadata: {metadata}".format(metadata=self.metadata) return desc
def to_gdal_ds(self): """Export raster object to `gdal.DataSource`. Returns ------- ds: gdal.DataSource DataSource converted from the raster object. """ ds = tgp.write_gdal_ds(self.data, geo_transform=self.geo_transform, projection=self.projection, gdaldtype=self.gdaldtype, no_data_value=self.no_data_value) return ds
def get_values_by_coords(self, coords): """get the data digital values of the coordinates Parameters ---------- coords: ndarray The coordinates with shape (n_points, 2). The order of last dimension is (lng, lat). Returns ------- coords: ndarray The data digital values of the coordinates. """ npidxs_row, npidxs_col = tgp.coords_to_npidxs( coords.astype(np.float32), self.geo_transform).T return self.data[npidxs_row, npidxs_col]
def clip_raster_with_extent(src_raster, extent): """Clip raster with extent. Parameters ---------- src_raster: Raster Which raster data to be clipped. extent: tuple extent to clip the data with (xmin, ymin, xmax, ymax) format. Returns ------- dst_raster: Raster. Clipped result. Examples -------- >>> import geopandas as gpd >>> import TronGisPy as tgp >>> from TronGisPy import ShapeGrid >>> from matplotlib import pyplot as plt >>> from shapely.geometry import Polygon >>> src_raster_fp = tgp.get_testing_fp('satellite_tif') >>> src_raster = tgp.read_raster(src_raster_fp) >>> ext = xmin, ymin, xmax, ymax = [329454.39272725, 2746809.43272727, 331715.57090906, 2748190.90181818] >>> dst_raster = ShapeGrid.clip_raster_with_extent(src_raster, ext) >>> fig, (ax1, ax2) = plt.subplots(1, 2) # plot the result >>> src_raster.plot(ax=ax1) >>> ext_poly = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)] >>> df_ext_poly = gpd.GeoDataFrame([Polygon(ext_poly)], columns=['geom'], geometry='geom') >>> df_ext_poly.boundary.plot(ax=ax1) >>> ax1.set_title('original image and clipper') >>> dst_raster.plot(ax=ax2) >>> ax2.set_title('clipped image') >>> plt.show() """ assert src_raster.geo_transform is not None, "src_raster.geo_transform should not be None" src_ds = src_raster.to_gdal_ds() dst_ds = gdal.Warp('', src_ds, format='MEM', outputBounds=extent, cropToCutline=True) dst_raster = tgp.read_gdal_ds(dst_ds) return dst_raster
def dem_to_TPI(src_raster, band=0): """Calculate the topographic position index (TPI) for the DEM. Parameters ---------- src_raster : Raster The dem used to calculate the TPI. band : int, optional, default: 0 source band number to use. Returns ------- dst_raster: Raster TPI calculated from the DEM. """ options = dict(band=band + 1, format='MEM') ds_src = src_raster.to_gdal_ds() ds = gdal.DEMProcessing('', ds_src, 'TPI', **options) dst_raster = tgp.read_gdal_ds(ds) return dst_raster
def dem_to_roughness(src_raster, band=0): """Calculate the roughness for the DEM. Parameters ---------- src_raster : Raster The dem used to calculate the roughness. band : int, optional, default: 0 source band number to use. Returns ------- dst_raster: Raster roughness calculated from the DEM. """ options = dict(band=band + 1, format='MEM') ds_src = src_raster.to_gdal_ds() ds = gdal.DEMProcessing('', ds_src, 'Roughness', **options) dst_raster = tgp.read_gdal_ds(ds) return dst_raster
def write_raster(fp, data, geo_transform=None, projection=None, gdaldtype=None, no_data_value=None, metadata=None): """Write raster file. Parameters ---------- fp: str File path of the raster file. geo_transform: tuple or list, optional Affine transform parameters (c, a, b, f, d, e = geo_transform). projection: str, optional The well known text (WKT) of the raster which can be generate from `TronGisPy.epsg_to_wkt(<epsg_code>)` gdaldtype: int, optional The type of the cell defined in gdal which will affect the information to be stored when saving the file. This can be generate from `gdal.GDT_XXX` such as `gdal.GDT_Int32` equals 5 and `gdal.GDT_Float32` equals 6. no_data_value: int or float, optional Define which value to replace nan in numpy array when saving a raster file. metadata: dict, optional Define the metadata of the raster file. """ if len(data.shape) == 2: data = np.expand_dims(data, axis=2) rows, cols, bands = data.shape gdaldtype = tgp.npdtype_to_gdaldtype(data.dtype) if gdaldtype is None else gdaldtype ds = gdal.GetDriverByName('GTiff').Create(fp, cols, rows, bands, gdaldtype) # dst_filename, xsize=512, ysize=512, bands=1, eType=gdal.GDT_Byte if geo_transform is not None: ds.SetGeoTransform(geo_transform) if projection is not None: ds.SetProjection(projection) if metadata is not None: ds.SetMetadata(metadata) for b in range(bands): band = ds.GetRasterBand(b+1) band.WriteArray(data[:, :, b], 0, 0) if no_data_value is not None: band.SetNoDataValue(no_data_value) band.FlushCache() ds = None
def __init__(self, data, geo_transform=None, projection=None, gdaldtype=None, no_data_value=None, metadata=None): """Initializing Raster object. Parameters ---------- data: array_like Digital number for each raster cell. Data is in (n_rows, n_cols, n_bands) shape. geo_transform: tuple or list, optional Affine transform parameters (c, a, b, f, d, e = geo_transform). projection: str, optional The well known text (WKT) of the raster which can be generate from `TronGisPy.epsg_to_wkt(<epsg_code>)` gdaldtype: int, optional The type of the cell defined in gdal which will affect the information to be stored when saving the file. This can be generate from `gdal.GDT_XXX` such as `gdal.GDT_Int32` equals 5 and `gdal.GDT_Float32` equals 6. no_data_value: int or float, optional Define which value to replace nan in numpy array when saving a raster file. metadata: dict, optional Define the metadata of the raster file. """ if len(data.shape) == 2: data = np.expand_dims(data, axis=2) self.data = data self.geo_transform = geo_transform if geo_transform is not None else [ 0, 1, 0, 0, 0, -1 ] self.gdaldtype = gdaldtype if gdaldtype is not None else tgp.npdtype_to_gdaldtype( data.dtype) self.projection = projection self.no_data_value = no_data_value self.metadata = metadata self.cache_data_for_plot = None
def plot(self, flush_cache=True, norm=True, clip_percentage=(0.02, 0.98), clip_min_max=None, log=False, rescale_percentage=None, bands=None, ax=None, title=None, cmap=None, figsize=None): """Plot the raster object. Parameters ---------- flush_cache: bool. Flush the cached processed result for quick plotting. norm: bool, optional, default: True Normalize the image for showing. clip_percentage: tuple of float, optional, default: (0.02, 0.98) The percentage to cut the data in head and tail e.g. (0.02, 0.98) log: bool, optional, default: False Get the log value of data to show the image. rescale_percentage: float, optional The percentage to recale (resize) the image for efficient showing. bands: list, optional Which bands to plot. Length of bands should be 1, 3 or 4. If 3 bands is used, each of them will be defined as rgb bands. If the forth band is used, it will be the opacity value. ax: matplotlib.axes._subplots.AxesSubplot, optional On which ax the raster will be plot. title: str, optional The title of the histgram. cmap: str, optional See Colormaps in Matplotlib https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html. figsize: tuple, optional Width and height of the histgram e.g.(10, 10). """ if bands is None: bands = [0, 1, 2] if self.bands >= 3 else [0] assert type(bands) is list, "type of bands should be list" assert len(bands) in [1, 3, 4], "length of bands should be 1, 3 or 4" if (self.cache_data_for_plot is None) or flush_cache: # reshape to valid shape for matplotlib if len(bands) == 1: data = self.data[:, :, bands[0]] else: data = self.data[:, :, bands] # deal with no data data = data.astype(np.float) data[data == self.no_data_value] = np.nan # detect single value if len(np.unique(data[~np.isnan(data)])) == 1: norm = False log = False clip_percentage = None # clip_percentage & clip_min_max if clip_percentage is not None: assert not ( clip_percentage is not None and clip_min_max is not None ), "should not set clip_percentage and clip_min_max at the same time!" assert len( clip_percentage) == 2, "clip_percentage two element tuple" data = tgp.Normalizer().clip_by_percentage( data, clip_percentage=clip_percentage) elif clip_min_max is not None: assert len(clip_min_max) == 2, "clip_min_max two element tuple" data = tgp.Normalizer().clip_by_min_max(data, min_max=clip_min_max) # log if log: if data[~np.isnan(data)].min() < 0: data -= data[~np.isnan(data)].min() data[~np.isnan(data)] = np.log(data[~np.isnan(data)] + 10**-6) # normalize if norm: data = tgp.Normalizer().fit_transform(data) if rescale_percentage is not None: import cv2 data = cv2.resize(data, (int(self.cols * rescale_percentage), int(self.rows * rescale_percentage))) self.cache_data_for_plot = data else: data = self.cache_data_for_plot # flip xy c, a, b, f, d, e = self.geo_transform # lng = a * col + b * row + c # lat = d * col + e * row + f # a = d(lng) / d(col) # b = d(lng) / d(row) # d = d(lat) / d(col) # e = d(lat) / d(row) if (np.abs(a) > np.abs(d)) and (np.abs(e) > np.abs(b)): lng_res, lat_res = a, e elif (np.abs(d) > np.abs(a)) and ( np.abs(b) > np.abs(e) ): # if d > a, 1 col move contribute more on lat, less on lng data = np.flipud(np.rot90(data)) lng_res, lat_res = d, b else: assert False, "not acceptable geotransform" if lng_res < 0: # general lng_res > 0 data = np.fliplr(data) if lat_res > 0: # general lat_res < 0 data = np.flipud(data) # plotting if ax is not None: img = ax.imshow(data, extent=self.extent_for_plot, cmap=cmap) ax.set_title(title) else: if figsize is not None: plt.figure(figsize=figsize) img = plt.imshow(data, extent=self.extent_for_plot, cmap=cmap) plt.title(title) plt.show() return img
def refine_resolution(self, dst_resolution, resample_alg='near', extent=None, rotate=True): """Refine the resolution of the raster. Parameters ---------- dst_resolution: int Target Resolution. resample_alg: {'near', 'bilinear', 'cubic', 'cubicspline', 'lanczos', 'average', 'mode'}. ``near``: nearest neighbour resampling (default, fastest algorithm, worst interpolation quality). ``bilinear``: bilinear resampling. ``cubic``: cubic resampling. ``cubicspline``: cubic spline resampling. ``lanczos``: Lanczos windowed sinc resampling. ``average``: average resampling, computes the weighted average of all non-NODATA contributing pixels. ``mode``: mode resampling, selects the value which appears most often of all the sampled points. extent: tuple extent to clip the data with (xmin, ymin, xmax, ymax) format. rotate: bool If True, the function will rotate the raster and adds noData values around it to make a new rectangular image matrix if the rotation in Raster.geo_transform is not zero, else it will keep the original rotation angle of Raster.geo_transform. Gdal will rotate the image by default . Please refer to the issue https://gis.stackexchange.com/questions/256081/why-does-gdalwarp-rotate-the-data and https://github.com/OSGeo/gdal/issues/1601. Returns ------- dst_raster: Raster Refined result. Examples -------- >>> import numpy as np >>> import TronGisPy as tgp >>> from TronGisPy import ShapeGrid >>> from matplotlib import pyplot as plt >>> src_raster_fp = tgp.get_testing_fp('dem_process_path') >>> src_raster = tgp.read_raster(src_raster_fp) >>> src_raster.data[src_raster.data == -999] = np.nan >>> dst_raster = src_raster.refine_resolution(dst_resolution=10, resample_alg='bilinear') >>> fig, (ax1, ax2) = plt.subplots(1, 2) # plot the result >>> src_raster.plot(ax=ax1) >>> ax1.set_title('original dem ' + str(src_raster.shape)) >>> dst_raster.plot(ax=ax2) >>> ax2.set_title('refined image ' + str(dst_raster.shape)) >>> plt.show() """ src_ds = self.to_gdal_ds() if rotate: dst_ds = gdal.Warp('', src_ds, xRes=dst_resolution, yRes=dst_resolution, outputBounds=extent, format='MEM', resampleAlg=resample_alg) dst_raster = tgp.read_gdal_ds(dst_ds) else: assert extent is None, "you cannot set the extent when rotate == False" zoom_in = dst_resolution / self.pixel_size[0] dst_ds = gdal.Warp('', src_ds, xRes=zoom_in, yRes=zoom_in, format='MEM', resampleAlg=resample_alg, transformerOptions=[ 'SRC_METHOD=NO_GEOTRANSFORM', 'DST_METHOD=NO_GEOTRANSFORM' ]) dst_geo_transform = np.array(self.geo_transform) dst_geo_transform[[1, 2, 4, 5]] *= zoom_in dst_raster = tgp.read_gdal_ds(dst_ds) dst_raster.geo_transform = tuple(dst_geo_transform) dst_raster.projection = self.projection return dst_raster
def hist(self, norm=False, clip_percentage=None, log=False, bands=None, ax=None, title=None, figsize=None): """plot digital value histgram (distribution) of raster object. Parameters ---------- norm: bool, optional, default: False Normalize the image for showing. clip_percentage: tuple of float, optional The percentage to cut the data in head and tail e.g. (0.02, 0.98) log: bool, optional, default: False Use the log value to plot the histgram. bands: list, optional Which bands to plot. if bands==None, use values from all bands. ax: matplotlib.axes._subplots.AxesSubplot, optional On which ax the raster will be plot. title: str, optional The title of the histgram. figsize: tuple, optional Width and height of the histgram e.g.(10, 10). """ if bands is None: data = self.data[self.data != self.no_data_value].flatten() else: data = self.data[:, :, bands] data = data[data != self.no_data_value].flatten() # clip_percentage if clip_percentage is not None: assert len( clip_percentage) == 2, "clip_percentage two element tuple" idx_st = int(len(data.flatten()) * clip_percentage[0]) idx_end = int(len(data.flatten()) * clip_percentage[1]) X_sorted = np.sort(data.flatten()) data_min = X_sorted[idx_st] data_max = X_sorted[idx_end] data[data < data_min] = data_min data[data > data_max] = data_max # log if log: data = np.log(data) # normalize if norm: data = tgp.Normalizer().fit_transform( data, clip_percentage=clip_percentage) if ax is not None: ax.hist(data) ax.set_title(title) else: if figsize is not None: plt.figure(figsize=figsize) plt.hist(data) plt.title(title) plt.show()
def extent_for_gdal(self): """(xmin, xmax, ymin, ymax) of the raster boundary.""" return tgp.get_extent(self.rows, self.cols, self.geo_transform, return_type='gdal')
def gdal_fillnodata(raster, band=0, no_data_value=999, max_distance=100, smoothing_iterations=0): """Interpolate values on specific cells (generally nan cell) using `gdal.FillNodata`. To be mentioned, this cannot accept zero in its data except its no_data_value is zero. Parameters ---------- raster: Raster The Raster object you want to fill the no_data_value. band: int, optional, default: 0 The band numnber of Raster object you want to fill the no_data_value which start from zero. no_data_value: int or float, optional, default: 999 The value to be filled with interpolated value. If no_data_value == None, use np.nan as no_data_value. max_distance: int, optional, default: 100 The cells within the max distance from no_data_value location will be calculated to fill the no_data_value. smoothing_iterations: int, optional, default: 0 The maximum limitation on loop. Returns ------- data_interp: ndarray. Interpolation result. Examples -------- >>> import numpy as np >>> import TronGisPy as tgp >>> from TronGisPy import Interpolation >>> from matplotlib import pyplot as plt >>> raster_fp = tgp.get_testing_fp('tif_forinterpolation') >>> raster = tgp.read_raster(raster_fp) >>> raster.data[np.isnan(raster.data)] = 999 >>> raster_interp = Interpolation.gdal_fillnodata(raster) >>> fig, (ax1, ax2) = plt.subplots(1,2) >>> raster.plot(ax=ax1) >>> raster_interp.plot(ax=ax2) >>> plt.show() """ # make dst_band ds_dst = raster.to_gdal_ds() dstband = ds_dst.GetRasterBand(band + 1) # make maskband raster_mask = raster.copy() raster_mask.data[raster_mask.data == no_data_value] = 0 raster_mask.data[raster_mask.data != 0] = 1 raster_mask.astype(bool) raster_mask.no_data_value = 0 ds_mask = raster_mask.to_gdal_ds() maskband = ds_mask.GetRasterBand(1) gdal.FillNodata(dstband, maskband, max_distance, smoothing_iterations) data_interp = tgp.read_gdal_ds(ds_dst) ds_dst = None ds_mask = None return data_interp
def write_gdal_ds(data=None, bands=None, cols=None, rows=None, geo_transform=None, projection=None, gdaldtype=None, no_data_value=None, metadata=None): """Build the gdal DataSource from geo-information attributes. Parameters ---------- data: array_like, optional The digital number for each cell of the raster. Data is in (n_rows, n_cols, n_bands) shape. bands: int, optional Number of bands. cols: int, optional Number of cols. rows: int, optional Number of rows. geo_transform: tuple or list, optional Affine transform parameters (c, a, b, f, d, e = geo_transform). projection: str, optional The well known text (WKT) of the raster which can be generate from `TronGisPy.epsg_to_wkt(<epsg_code>)` gdaldtype: int, optional The type of the cell defined in gdal which will affect the information to be stored when saving the file. This can be generate from `gdal.GDT_XXX` such as `gdal.GDT_Int32` equals 5 and `gdal.GDT_Float32` equals 6. no_data_value: int or float, optional Define which value to replace nan in numpy array when saving a raster file. metadata: dict, optional Define the metadata of the raster file. Returns ------- raster: Raster. output raster. Examples -------- >>> import TronGisPy as tgp >>> raster_fp = tgp.get_testing_fp() >>> data = tgp.get_raster_data(raster_fp) >>> geo_transform, projection, gdaldtype, no_data_value = tgp.get_raster_info(raster_fp, ["geo_transform", "projection", "gdaldtype", "no_data_value"]) >>> ds = tgp.write_gdal_ds(data, geo_transform=geo_transform, projection=projection, gdaldtype=gdaldtype, no_data_value=no_data_value) >>> raster = tgp.read_gdal_ds(ds) >>> raster shape: (677, 674, 3) geo_transform: (271982.8783, 1.1186219584569888, 0.0, 2769973.0653, 0.0, -1.1186305760705852) projection: PROJCS["TWD97 / TM2 zone 121",GEOGCS["TWD97",DATUM["Taiwan_Datum_1997",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","1026"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","3824"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",121],PARAMETER["scale_factor",0.9999],PARAMETER["false_easting",250000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","3826"]] no_data_value: -32768.0 metadata: {} """ if data is None: assert (bands is not None) and (cols is not None) and (rows is not None), "bands, cols, rows should not be None" assert (gdaldtype is not None), "gdaldtype should not be None" else: if len(data.shape) == 2: data = np.expand_dims(data, axis=2) bands = data.shape[2] if bands is None else bands cols = data.shape[1] if cols is None else cols rows = data.shape[0] if rows is None else rows gdaldtype = tgp.npdtype_to_gdaldtype(data.dtype) if gdaldtype is None else gdaldtype ds = gdal.GetDriverByName('MEM').Create('', cols, rows, bands, gdaldtype) # dst_filename, xsize=512, ysize=512, bands=1, eType=gdal.GDT_Byte if geo_transform is not None: ds.SetGeoTransform(geo_transform) if projection is not None: ds.SetProjection(projection) if metadata is not None: ds.SetMetadata(metadata) if no_data_value is not None: for b in range(bands): band = ds.GetRasterBand(b+1) band.SetNoDataValue(no_data_value) band.WriteArray(np.full((rows, cols), no_data_value), 0, 0) band.FlushCache() if data is not None: for b in range(data.shape[2]): band = ds.GetRasterBand(b+1) band.WriteArray(data[:, :, b], 0, 0) band.FlushCache() return ds