common module¶
The common module contains common functions and classes used by the other modules.
array2raster(array, metadata)
¶
Convert a numpy array and metadata to a rasterio object stored in memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
array |
array |
The input data array. |
required |
metadata |
Dict |
The metadata dictionary. |
required |
Returns:
| Type | Description |
|---|---|
Local raster file (raster) |
The rasterio object stored in memory. |
Source code in geonate/common.py
def array2raster(array, metadata: Dict):
"""
Convert a numpy array and metadata to a rasterio object stored in memory.
Args:
array (array): The input data array.
metadata (Dict): The metadata dictionary.
Returns:
Local raster file (raster): The rasterio object stored in memory.
"""
import rasterio
# Determine number of bands
if len(array.shape) == 3:
nbands = array.shape[0]
else:
nbands = 1
# Update metadata with the correct dtype and count
metadata.update({
'dtype': array.dtype,
'count': nbands
})
# Write image in memory file and read it back
memory_file = rasterio.MemoryFile()
dst = memory_file.open(**metadata)
if array.ndim == 2:
dst.write(array, 1)
elif array.ndim == 3:
for i in range(array.shape[0]):
dst.write(array[i, :, : ], i + 1)
dst.close()
# Read the dataset from memory
dataset_reader = rasterio.open(dst.name, mode="r")
return dataset_reader
center_scene(input)
¶
Computes the center latitude and longitude of a given geospatial input.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
raster | shapefile |
A geospatial object with a 'bounds' attribute that defines the spatial extent. |
required |
Returns:
| Type | Description |
|---|---|
tuple |
A tuple containing: - center_lat (float): The center latitude of the input. - center_lon (float): The center longitude of the input. |
Source code in geonate/common.py
def center_scene(input):
"""
Computes the center latitude and longitude of a given geospatial input.
Args:
input (raster | shapefile): A geospatial object with a 'bounds' attribute that defines the spatial extent.
Returns:
tuple: A tuple containing:
- center_lat (float): The center latitude of the input.
- center_lon (float): The center longitude of the input.
"""
import rasterio
import geopandas
# Define boundary
bounds, _ = get_extent_local(input)
min_lon, min_lat, max_lon, max_lat = bounds[0], bounds[1], bounds[2], bounds[3]
# Compute center latitude and longitude
center_lat = (min_lat + max_lat) / 2
center_lon = (min_lon + max_lon) / 2
return center_lat, center_lon
check_crs_consistency(input)
¶
Checks if all elements in the input list have the same Coordinate Reference System (CRS).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
list |
A list of file paths or local variables (rasterio.io.DatasetReader or geopandas.geodataframe.GeoDataFrame). |
required |
Returns:
| Type | Description |
|---|---|
tuple |
A tuple containing: - bool: True if all elements have the same CRS, False otherwise. - str or None: The CRS of the elements if all elements have the same CRS, otherwise None. |
Source code in geonate/common.py
def check_crs_consistency(input):
"""
Checks if all elements in the input list have the same Coordinate Reference System (CRS).
Args:
input (list): A list of file paths or local variables (rasterio.io.DatasetReader or geopandas.geodataframe.GeoDataFrame).
Returns:
tuple: A tuple containing:
- bool: True if all elements have the same CRS, False otherwise.
- str or None: The CRS of the elements if all elements have the same CRS, otherwise None.
"""
import numpy
from .geonate import rast, vect
if not isinstance(input, list):
raise ValueError('Input must be a list of local variables or file paths')
else:
_, inputType = check_datatype_consistency(input)
if str(inputType) == "<class 'str'>":
file_extensions = [x.split(".")[-1] for x in input]
file_extension = numpy.unique(file_extensions)
extension_len = len(file_extension)
if extension_len > 1:
raise ValueError('Input must have consistent data format')
else:
if str(file_extension[0]) == 'tif':
files = [rast(file) for file in input]
crs_list = [x.crs.to_string() for x in files]
if len(numpy.unique(crs_list)) == 1:
crs = numpy.unique(crs_list)
consistency = True
print(f"Input is Raster with consistent crs of {crs}")
return consistency, crs[0]
else:
consistency = False
print(f"Input is Raster with different crs")
return consistency, None
elif str(file_extension[0]) == 'shp':
files = [vect(file) for file in input]
crs_list = [file.crs.to_string() for file in files]
if len(numpy.unique(crs_list)) == 1:
crs = numpy.unique(crs_list)
consistency = True
print(f"Input is Shapefile with consistent crs of {crs}")
return consistency, crs[0]
else:
consistency = False
print(f"Input is Raster with different crs")
return consistency, None
else:
raise ValueError('This function only supports raster (.tif) and shapefile (.shp)')
elif str(inputType) == "<class 'rasterio.io.DatasetReader'>" or str(inputType) == "<class 'geopandas.geodataframe.GeoDataFrame'>":
crs_list = [x.crs.to_string() for x in input]
crs = numpy.unique(crs_list)
consistency = True
print(f"Input is Shapefile with consistent crs of {crs}")
return consistency, crs[0]
else:
raise ValueError(f"Input must have the same data format of raster (.tif) and shapefile (.shp)")
check_datatype_consistency(input)
¶
Checks if all elements in the input list have the same data type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
list |
A list of elements to check. |
required |
Returns:
| Type | Description |
|---|---|
tuple |
A tuple containing: - bool: True if all elements have the same type, False otherwise. - type or None: The data type of the elements if all elements have the same type, otherwise None. |
Source code in geonate/common.py
def check_datatype_consistency(input):
"""
Checks if all elements in the input list have the same data type.
Args:
input (list): A list of elements to check.
Returns:
tuple: A tuple containing:
- bool: True if all elements have the same type, False otherwise.
- type or None: The data type of the elements if all elements have the same type, otherwise None.
"""
if not isinstance(input, list):
raise ValueError("Input must be a list")
else:
first_element = type(input[0])
if all(isinstance(item, first_element) for item in input):
datatype = first_element
consistency = True
print(f"Checking datatype consistency\nInput have data types of {datatype}")
else:
datatype = None
consistency = False
print(f"Checking datatype consistency\nInput have different data types")
return consistency, str(datatype)
check_extension_consistency(input)
¶
Checks if all elements in the input list have the same file extension.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
list |
A list of file paths as strings. |
required |
Returns:
| Type | Description |
|---|---|
tuple |
A tuple containing: - bool: True if all elements have the same file extension, False otherwise. - str or None: The file extension if all elements have the same extension, otherwise None. |
Source code in geonate/common.py
def check_extension_consistency(input):
"""
Checks if all elements in the input list have the same file extension.
Args:
input (list): A list of file paths as strings.
Returns:
tuple: A tuple containing:
- bool: True if all elements have the same file extension, False otherwise.
- str or None: The file extension if all elements have the same extension, otherwise None.
"""
import numpy
# Check whether a list or not
if not isinstance(input, list):
raise ValueError("Input must be a list of file paths")
else:
# Check whether all are string
if not all(isinstance(x, str) for x in input):
raise ValueError('Input must contain only string of file paths')
else:
#
extensions = [e.split(".")[-1] for e in input]
no_extension = len(numpy.unique(extensions))
if no_extension == 1:
consistency = True
extension = str(extensions[0])
else:
consistency = False
extension = None
return consistency, extension
degree2meter(input, latitude=None)
¶
Convert distance from degrees to meters depending on latitude
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
numeric |
Input resolution in degrees |
required |
latitude |
numeric |
Latitude of the location. If latitude is None, the location is assumed near the Equator. Defaults to None. |
None |
Returns:
| Type | Description |
|---|---|
Distance length (numeric | float) |
Distance in meters corresponding to the input degree |
Source code in geonate/common.py
def degree2meter(input, latitude=None):
"""Convert distance from degrees to meters depending on latitude
Args:
input (numeric): Input resolution in degrees
latitude (numeric, optional): Latitude of the location. If latitude is None, the location is assumed near the Equator. Defaults to None.
Returns:
Distance length (numeric | float): Distance in meters corresponding to the input degree
"""
import numpy as np
if latitude is None:
# Equator location
meters = input * (111320 * np.cos(np.radians(0.0)))
else:
meters = input * (111320 * np.cos(np.radians(latitude)))
return meters
empty_dataframe(nrows, ncols, value='NA', name=None)
¶
Create an empty dataframe
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nrows |
numeric |
Numbers of rows |
required |
ncols |
numeric |
Number of columns |
required |
value |
str | numeric |
Input value in all cells. Defaults to 'NA'. |
'NA' |
name |
list |
Names of columns, if not given, it will return default as number of column. Defaults to None. |
None |
Returns:
| Type | Description |
|---|---|
Dataframe (pandas datafram) |
An empty filled with NA or user-defined number (e.g., 0) |
Source code in geonate/common.py
def empty_dataframe(nrows, ncols, value='NA', name=None):
"""Create an empty dataframe
Args:
nrows (numeric): Numbers of rows
ncols (numeric): Number of columns
value (str | numeric, optional): Input value in all cells. Defaults to 'NA'.
name (list, optional): Names of columns, if not given, it will return default as number of column. Defaults to None.
Returns:
Dataframe (pandas datafram): An empty filled with NA or user-defined number (e.g., 0)
"""
import pandas as pd
import numpy as np
# Check validity of column name
if name is None:
column_names = [f'Col_{i+1}' for i in range(ncols)]
elif len(name) == ncols:
column_names = name
else:
raise ValueError("Length of column names vector must match numbers of columns")
# check input value
try:
if isinstance(value, int):
val = value
elif isinstance(value, float):
val = value
else:
val = np.nan
except ValueError:
val = np.nan
# Create data and parse it into dataframe
data = [[val] * ncols for _ in range(nrows)]
dataframe = pd.DataFrame(data, columns= column_names)
return dataframe
get_extent_external(input)
¶
Computes the spatial extent of geospatial files and returns the bounding box and a GeoDataFrame of the bounding polygon.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
str or list |
A single file path or a list of file paths. Supported file types are GeoTIFF raster (tif) and shapefile (shp). |
required |
Returns:
| Type | Description |
|---|---|
tuple |
A tuple containing: - general_extent (tuple): The bounding box of the input files in the format (minx, miny, maxx, maxy). - bound_poly (geopandas.GeoDataFrame): A GeoDataFrame containing the bounding polygon. |
Source code in geonate/common.py
def get_extent_external(input):
"""
Computes the spatial extent of geospatial files and returns the bounding box and a GeoDataFrame of the bounding polygon.
Args:
input (str or list): A single file path or a list of file paths. Supported file types are GeoTIFF raster (tif) and shapefile (shp).
Returns:
tuple: A tuple containing:
- general_extent (tuple): The bounding box of the input files in the format (minx, miny, maxx, maxy).
- bound_poly (geopandas.GeoDataFrame): A GeoDataFrame containing the bounding polygon.
"""
import rasterio
import geopandas
from shapely.geometry import Polygon
from .geonate import rast, vect
#### Single path
if (not isinstance(input, list)) or ((isinstance(input, list) and (len(input)==1))):
# Check whether single list or string
if len(input) == 1:
input = input[0]
else:
input = input
# Extract file extension
extension = input.split(".")[-1]
# Raster files
if extension == 'tif':
tmp = rast(input)
general_extent = tmp.bounds
crs = tmp.crs
# Create bound polygon
poly_geom = Polygon([
(general_extent[0], general_extent[1]),
(general_extent[2], general_extent[1]),
(general_extent[2], general_extent[3]),
(general_extent[0], general_extent[3])
])
bound_poly = geopandas.GeoDataFrame(index=[0], geometry=[poly_geom])
bound_poly.crs = {'init': crs}
return general_extent, bound_poly
# Vector Shapefile
elif extension == 'shp':
tmp = vect(input)
ext = tmp.bounds
crs = tmp.crs
no_poly = ext.shape[0]
# Determine general bound in case single polygon or multiple polygons
if no_poly == 1:
general_extent = tuple(ext.loc[0, :])
elif no_poly >= 2:
general_extent = (ext.iloc[:, 0].min(), ext.iloc[:, 1].min(), ext.iloc[:, 2].max(),ext.iloc[:, 3].max())
# Create bound polygon
poly_geom = Polygon([
(general_extent[0], general_extent[1]),
(general_extent[2], general_extent[1]),
(general_extent[2], general_extent[3]),
(general_extent[0], general_extent[3])
])
bound_poly = geopandas.GeoDataFrame(index=[0], geometry=[poly_geom])
bound_poly.crs = {'init': crs}
return general_extent, bound_poly
# Other data types
else:
raise ValueError('It only supports geotif raster (tif) and shapefile (shp)')
#### List of multiple paths
else:
consistency, extension = check_extension_consistency(input)
# Check extension consistency
if consistency is True:
# Raster files
if extension == 'tif':
files = [rast(file) for file in input]
general_extent = None
# read each file
for file in files:
ext = file.bounds
crs = file.crs
# determine general extent
if general_extent is None:
general_extent = ext
else:
general_extent = (
min(general_extent[0], ext[0]),
min(general_extent[1], ext[1]),
max(general_extent[2], ext[2]),
max(general_extent[3], ext[3])
)
# Create bound polygon
poly_geom = Polygon([
(general_extent[0], general_extent[1]),
(general_extent[2], general_extent[1]),
(general_extent[2], general_extent[3]),
(general_extent[0], general_extent[3])
])
bound_poly = geopandas.GeoDataFrame(index=[0], geometry=[poly_geom])
bound_poly.crs = {'init': crs}
return general_extent, bound_poly
# Shapefile data
elif extension == 'shp':
files = [vect(file) for file in input]
general_extent = None
# read each file
for file in files:
ext = file.bounds
crs = file.crs
no_poly = ext.shape[0]
# Determine general bound in case single polygon or multiple polygons
if no_poly == 1:
ext = tuple(ext.loc[0, :])
elif no_poly >= 2:
ext = (ext.iloc[:, 0].min(), ext.iloc[:, 1].min(), ext.iloc[:, 2].max(),ext.iloc[:, 3].max())
# determine general extent
if general_extent is None:
general_extent = ext
else:
general_extent = (
min(general_extent[0], ext[0]),
min(general_extent[1], ext[1]),
max(general_extent[2], ext[2]),
max(general_extent[3], ext[3])
)
# Create bound polygon
poly_geom = Polygon([
(general_extent[0], general_extent[1]),
(general_extent[2], general_extent[1]),
(general_extent[2], general_extent[3]),
(general_extent[0], general_extent[3])
])
bound_poly = geopandas.GeoDataFrame(index=[0], geometry=[poly_geom])
bound_poly.crs = {'init': crs}
return general_extent, bound_poly
# Other data types
else:
raise ValueError('Input only supports geotif (tif) and shapefile (shp)')
# Other cases
else:
raise ValueError('Checking file extension consistency\nInput have different data extensions')
get_extent_local(input)
¶
Computes the spatial extent of a single or multiple geospatial files (GeoTIFF raster or shapefile).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
list or object |
A single rasterio.io.DatasetReader object, a single geopandas.geodataframe.GeoDataFrame object, or a list of such objects. |
required |
Returns:
| Type | Description |
|---|---|
tuple |
A tuple containing: - general_extent (tuple): The bounding box of the input(s) in the format (min_x, min_y, max_x, max_y). - bound_poly (geopandas.GeoDataFrame): A GeoDataFrame containing a single polygon representing the bounding box. |
Source code in geonate/common.py
def get_extent_local(input):
"""
Computes the spatial extent of a single or multiple geospatial files (GeoTIFF raster or shapefile).
Args:
input (list or object): A single rasterio.io.DatasetReader object, a single geopandas.geodataframe.GeoDataFrame object, or a list of such objects.
Returns:
tuple: A tuple containing:
- general_extent (tuple): The bounding box of the input(s) in the format (min_x, min_y, max_x, max_y).
- bound_poly (geopandas.GeoDataFrame): A GeoDataFrame containing a single polygon representing the bounding box.
"""
import rasterio
import geopandas
from shapely.geometry import Polygon
#### Single file
if (not isinstance(input, list)) or len(input)==1:
if (isinstance(input, rasterio.io.DatasetReader)):
general_extent = input.bounds
crs = input.crs
poly_geom = Polygon([
(general_extent[0], general_extent[1]),
(general_extent[2], general_extent[1]),
(general_extent[2], general_extent[3]),
(general_extent[0], general_extent[3])
])
bound_poly = geopandas.GeoDataFrame(index=[0], geometry=[poly_geom])
bound_poly.crs = {'init': crs}
return general_extent, bound_poly
elif (isinstance(input, geopandas.geodataframe.GeoDataFrame)):
ext = input.bounds
crs = input.crs
no_poly = ext.shape[0]
# Determine general bound in case single polygon or multiple polygons
if no_poly == 1:
general_extent = tuple(ext.loc[0, :])
elif no_poly >= 2:
general_extent = (ext.iloc[:, 0].min(), ext.iloc[:, 1].min(), ext.iloc[:, 2].max(),ext.iloc[:, 3].max())
poly_geom = Polygon([
(general_extent[0], general_extent[1]),
(general_extent[2], general_extent[1]),
(general_extent[2], general_extent[3]),
(general_extent[0], general_extent[3])
])
bound_poly = geopandas.GeoDataFrame(index=[0], geometry=[poly_geom])
bound_poly.crs = {'init': crs}
return general_extent, bound_poly
else:
raise ValueError('It only supports geotif raster and shapefile')
#### Multiple files
elif (isinstance(input, list)) or len(input) > 1:
consistency, datatype = check_datatype_consistency(input)
# Input are Raster files
if (consistency is True) and (datatype == "<class 'rasterio.io.DatasetReader'>"):
general_extent = None
# read each file
for file in input:
ext = file.bounds
crs = file.crs
# determine general extent
if general_extent is None:
general_extent = ext
else:
general_extent = (
min(general_extent[0], ext[0]),
min(general_extent[1], ext[1]),
max(general_extent[2], ext[2]),
max(general_extent[3], ext[3])
)
# Create bound polygon
poly_geom = Polygon([
(general_extent[0], general_extent[1]),
(general_extent[2], general_extent[1]),
(general_extent[2], general_extent[3]),
(general_extent[0], general_extent[3])
])
bound_poly = geopandas.GeoDataFrame(index=[0], geometry=[poly_geom])
bound_poly.crs = {'init': crs}
return general_extent, bound_poly
# Input are Shapefile files
elif (consistency is True) and (datatype == "<class 'geopandas.geodataframe.GeoDataFrame'>"):
general_extent = None
# read each file
for file in input:
ext = file.bounds
crs = file.crs
no_poly = ext.shape[0]
# Determine general bound in case single polygon or multiple polygons
if no_poly == 1:
ext = tuple(ext.loc[0, :])
elif no_poly >= 2:
ext = (ext.iloc[:, 0].min(), ext.iloc[:, 1].min(), ext.iloc[:, 2].max(),ext.iloc[:, 3].max())
# determine general extent
if general_extent is None:
general_extent = ext
else:
general_extent = (
min(general_extent[0], ext[0]),
min(general_extent[1], ext[1]),
max(general_extent[2], ext[2]),
max(general_extent[3], ext[3])
)
# Create bound polygon
poly_geom = Polygon([
(general_extent[0], general_extent[1]),
(general_extent[2], general_extent[1]),
(general_extent[2], general_extent[3]),
(general_extent[0], general_extent[3])
])
bound_poly = geopandas.GeoDataFrame(index=[0], geometry=[poly_geom])
bound_poly.crs = {'init': crs}
return general_extent, bound_poly
# Other data types
else:
raise ValueError('Input have different data types')
#### Other cases
else:
raise ValueError('It only supports geotif raster and shapefile')
listFiles(path, pattern, search_type='pattern', full_name=True)
¶
List all files with specific pattern within a folder path
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path |
AnyStr |
Folder path where files stored |
required |
pattern |
AnyStr |
Search pattern of files (e.g., '*.tif') |
required |
search_type |
AnyStr |
Search type whether by "extension" or name "pattern". Defaults to 'pattern'. |
'pattern' |
full_name |
bool |
Whether returning full name with path detail or only file name. Defaults to True. |
True |
Returns:
| Type | Description |
|---|---|
A string list (list) |
A list of file paths |
Source code in geonate/common.py
def listFiles(path: AnyStr, pattern: AnyStr, search_type: AnyStr = 'pattern', full_name: bool=True):
"""List all files with specific pattern within a folder path
Args:
path (AnyStr): Folder path where files stored
pattern (AnyStr): Search pattern of files (e.g., '*.tif')
search_type (AnyStr, optional): Search type whether by "extension" or name "pattern". Defaults to 'pattern'.
full_name (bool, optional): Whether returning full name with path detail or only file name. Defaults to True.
Returns:
A string list (list): A list of file paths
"""
import os
import fnmatch
# Create empty list to store list of files
files_list = []
# Check search type
if (search_type.upper() == 'EXTENSION') or (search_type.upper() == 'E'):
if '*' in pattern:
raise ValueError("Do not use '*' in the pattern of extension search")
else:
for root, dirs, files in os.walk(path):
for file in files:
if file.lower().endswith(pattern):
if full_name is True:
files_list.append(os.path.join(root, file))
else:
files_list.append(file)
elif (search_type.upper() == 'PATTERN') or (search_type.upper() == 'P'):
if '*' not in pattern:
raise ValueError("Pattern search requires '*' in pattern")
else:
for root, dirs, files in os.walk(path):
for file in fnmatch.filter(files, pattern):
if full_name is True:
files_list.append(os.path.join(root, file))
else:
files_list.append(file)
else:
raise ValueError('Search pattern must be one of these types (pattern, p, extension, e)')
return files_list
meter2degree(input, latitude=None)
¶
Convert image resolution from meter to acr-degree depending on location of latitude
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
numeric |
Input resolution of distance |
required |
latitude |
numeric |
Latitude presents location. If latitude is None, the location is assumed near Equator. Defaults to None. |
None |
Returns:
| Type | Description |
|---|---|
Degree (float) |
Degree corresponding to the distance length |
Source code in geonate/common.py
def meter2degree(input, latitude=None):
"""Convert image resolution from meter to acr-degree depending on location of latitude
Args:
input (numeric): Input resolution of distance
latitude (numeric, optional): Latitude presents location. If latitude is None, the location is assumed near Equator. Defaults to None.
Returns:
Degree (float): Degree corresponding to the distance length
"""
import numpy as np
if latitude is None:
# Equator location
degree = input / (111320 * np.cos(np.radians(0.0)))
else:
degree = input / (111320 * np.cos(np.radians(latitude)))
return degree
mimax(input, digit=3)
¶
Calculate maximum and minimum values of raster or array
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
raster | array |
Raster image or data array |
required |
digit |
int |
Precise digit number. Defaults to 3. |
3 |
Returns:
| Type | Description |
|---|---|
Min and Max values (numeric) |
Return 2 numbers of minvalue and maxvalue |
Source code in geonate/common.py
def mimax(input, digit=3):
"""Calculate maximum and minimum values of raster or array
Args:
input (raster | array): Raster image or data array
digit (int, optional): Precise digit number. Defaults to 3.
Returns:
Min and Max values (numeric): Return 2 numbers of minvalue and maxvalue
"""
import rasterio
import numpy as np
### Check input data
if isinstance(input, rasterio.DatasetReader):
dataset = input.read()
elif isinstance(input, np.ndarray):
dataset = input
else:
raise ValueError('Input data is not supported')
# Calculate min and max values
minValue = round(np.nanmin(dataset), digit)
maxValue = round(np.nanmax(dataset), digit)
# Convert min and max to string for print
min_round = str(round(minValue, digit))
max_round = str(round(maxValue, digit))
print(f"[Min: {min_round} | Max: {max_round}]")
return minValue, maxValue
reshape_raster(inputArray, mode='image')
¶
Reshapes a 3-dimensional numpy array between 'image' and 'raster' formats.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputArray |
array |
The input 3-dimensional array to be reshaped. |
required |
mode |
str |
The mode to reshape the array to. 'image' or 'img' reshapes to (height, width, bands), 'raster' or 'r' reshapes to (bands, height, width). Default is 'image'. |
'image' |
Returns:
| Type | Description |
|---|---|
Reshape array (array) |
The reshaped array. |
Source code in geonate/common.py
def reshape_raster(inputArray, mode:str="image"):
"""
Reshapes a 3-dimensional numpy array between 'image' and 'raster' formats.
Parameters:
inputArray (array): The input 3-dimensional array to be reshaped.
mode (str): The mode to reshape the array to. 'image' or 'img' reshapes to (height, width, bands), 'raster' or 'r' reshapes to (bands, height, width). Default is 'image'.
Returns:
Reshape array (array): The reshaped array.
"""
import numpy as np
# Check whether input are 3-dim data array
if len(inputArray.shape) == 3:
# Convert to image
if mode.lower() == 'image' or mode.lower() == 'img':
output = np.transpose(inputArray, (1,2,0))
# Convert to raster
elif mode.lower() == 'raster' or mode.lower() == 'r':
output = np.transpose(inputArray, (2,0,1))
return output
#Other cases
else:
raise ValueError('Input data array must have 3 dimensions')
unique_value(input, frequency=True, sort='frequency')
¶
Calculate unique pixel values from a raster image or numpy array, optionally with their frequencies, and sort them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input |
raster| array |
Input raster image or numpy array. |
required |
frequency |
bool |
If True, return the frequency of each unique value. Defaults to True. |
True |
sort |
str |
Sorting method for the unique values. Options are 'frequency' or 'value'. Defaults to 'frequency'. |
'frequency' |
Returns:
| Type | Description |
|---|---|
array or dataframe |
Array with unique value if frequency is False, otherwise it returns DataFrame with unique values and frequencies. |
Source code in geonate/common.py
def unique_value(input, frequency=True, sort: Optional[AnyStr]='frequency'):
"""
Calculate unique pixel values from a raster image or numpy array, optionally with their frequencies, and sort them.
Args:
input (raster| array): Input raster image or numpy array.
frequency (bool, optional): If True, return the frequency of each unique value. Defaults to True.
sort (str, optional): Sorting method for the unique values. Options are 'frequency' or 'value'. Defaults to 'frequency'.
Returns:
array or dataframe: Array with unique value if frequency is False, otherwise it returns DataFrame with unique values and frequencies.
"""
import numpy as np
import pandas as pd
import rasterio
from .processor import values
# Check input data
if isinstance(input, rasterio.DatasetReader):
dataset = input.read()
elif isinstance(input, np.ndarray):
dataset = input
else:
raise ValueError('Input data is not supported')
# Extract all values from raster or array
pixel_values = values(dataset, na_rm=True)
# Generate frequency and return
if frequency is False:
unique_values = np.sort(np.unique(pixel_values.values.flatten()))
else:
unique_values = pd.Series(pixel_values.values.ravel()).value_counts().reset_index()
unique_values.columns = ['Value', 'Frequency']
if sort.lower() == "frequency" or sort.lower() == "f":
unique_values = unique_values.sort_values(by='Frequency')
elif sort.lower() == "values" or sort.lower() == "value" or sort.lower() == "v":
unique_values = unique_values.sort_values(by='Value')
else:
raise ValueError('Sort method is not supported ["frequency", "value]')
return unique_values