diff --git a/README.md b/README.md index 448972e3..c12e25b3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ +# GemGIS - Spatial data and information processing for geomodeling and subsurface data +

-> Spatial data and information processing for geomodeling and subsurface data [![PyPI](https://img.shields.io/badge/python-3-blue.svg)](https://www.python.org/downloads/) @@ -40,9 +41,9 @@ Furthermore, many [example models](https://gemgis.readthedocs.io/en/latest/getti ## Installation -It is recommended to use GemGIS with **python">=3.10"** in a separated environment. The main packages and its dependencies can be installed via the conda-forge channel. GemGIS is then available through PyPi or Conda. -1) `conda install -c conda-forge geopandas">=0.13.2" rasterio">=1.3.8"` -2) `conda install -c conda-forge pyvista">=0.42.1"` +It is recommended to use GemGIS with **python">=3.11"** in a separated environment. The main packages and its dependencies can be installed via the conda-forge channel. GemGIS is then available through PyPi or Conda. +1) `conda install -c conda-forge geopandas">=1.0.1" rasterio">=1.4.3"` +2) `conda install -c conda-forge pyvista">=0.44.2"` 3) `pip install gemgis` / `conda install -c conda-forge gemgis` Check out the [Installation Page](https://gemgis.readthedocs.io/en/latest/getting_started/installation.html) for more detailed instructions. @@ -55,13 +56,35 @@ The Contribution Guidelines for GemGIS can be found here: [Contribution Guidelin We welcome issue reports, questions, ideas for new features and pull-requests to fix issues or even add new features to the software. Once a pull-request is opened, we will guide through the review process. + +## Citation + +If you use GemGIS for any published work, please cite it using the reference below: + +Jüstel, A., Endlein Correira, A., Pischke, M., de la Varga, M., Wellmann, F.: GemGIS - Spatial Data Processing for Geomodeling. Journal of Open Source Software, 7(73), 3709, https://doi.org/10.21105/joss.03709, 2022. + +``` +@article{Jüstel2022, +doi = {10.21105/joss.03709}, +url = {https://doi.org/10.21105/joss.03709}, +year = {2022}, +publisher = {The Open Journal}, +volume = {7}, +number = {73}, +pages = {3709}, +author = {Alexander Jüstel and Arthur Endlein Correira and Marius Pischke and Miguel de la Varga and Florian Wellmann}, +title = {GemGIS - Spatial Data Processing for Geomodeling}, +journal = {Journal of Open Source Software} +} +``` + ## References -* Jüstel et al., (2023). From Maps to Models - Tutorials for structural geological modeling using GemPy and GemGIS. Journal of Open Source Education, 6(66), 185, https://doi.org/10.21105/jose.00185 -* Jüstel et al., (2022). GemGIS - Spatial Data Processing for Geomodeling. Journal of Open Source Software, 7(73), 3709, https://doi.org/10.21105/joss.03709 -* Jüstel, A., Endlein Correira, A., Wellmann, F. and Pischke, M.: GemGIS – GemPy Geographic: Open-Source Spatial Data Processing for Geological Modeling. EGU General Assembly 2021, https://doi.org/10.5194/egusphere-egu21-4613, 2021 -* Jüstel, A.: 3D Probabilistic Modeling and Data Analysis of the Aachen-Weisweiler Area: Implications for Deep Geothermal Energy Exploration, unpublished Master Thesis at RWTH Aachen University, 2020 -* de la Varga, M., Schaaf, A., and Wellmann, F.: GemPy 1.0: open-source stochastic geological modeling and inversion, Geosci. Model Dev., 12, 1-32, https://doi.org/10.5194/gmd-12-1-2019, 2019 -* Powell, D.: Interpretation of Geological Structures Through Maps: An Introductory Practical Manual, Longman, pp. 192, 1992 -* Bennison, G.M.: An Introduction to Geological Structures and Maps, Hodder Education Publication, pp. 78, 1990 +* Jüstel, A. et al.: From Maps to Models - Tutorials for structural geological modeling using GemPy and GemGIS. Journal of Open Source Education, 6(66), 185, https://doi.org/10.21105/jose.00185, 2023. +* Jüstel, A. et al.: GemGIS - Spatial Data Processing for Geomodeling. Journal of Open Source Software, 7(73), 3709, https://doi.org/10.21105/joss.03709, 2022. +* Jüstel, A., Endlein Correira, A., Wellmann, F. and Pischke, M.: GemGIS – GemPy Geographic: Open-Source Spatial Data Processing for Geological Modeling. EGU General Assembly 2021, https://doi.org/10.5194/egusphere-egu21-4613, 2021. +* Jüstel, A.: 3D Probabilistic Modeling and Data Analysis of the Aachen-Weisweiler Area: Implications for Deep Geothermal Energy Exploration, unpublished Master Thesis at RWTH Aachen University, 2020. +* de la Varga, M., Schaaf, A., and Wellmann, F.: GemPy 1.0: open-source stochastic geological modeling and inversion, Geosci. Model Dev., 12, 1-32, https://doi.org/10.5194/gmd-12-1-2019, 2019. +* Powell, D.: Interpretation of Geological Structures Through Maps: An Introductory Practical Manual, Longman, pp. 192, 1992. +* Bennison, G.M.: An Introduction to Geological Structures and Maps, Hodder Education Publication, pp. 78, 1990. diff --git a/docs/getting_started/api.rst b/docs/getting_started/api.rst index 1daf72d8..d5e60a31 100644 --- a/docs/getting_started/api.rst +++ b/docs/getting_started/api.rst @@ -42,9 +42,9 @@ or from single Shapely Polygons or multiple Shapely Polygons. :toctree: reference/vector_api gemgis.vector.extract_xy_from_polygon_intersections - gemgis.vector.intersection_polygon_polygon - gemgis.vector.intersections_polygon_polygons - gemgis.vector.intersections_polygons_polygons + gemgis.vector.intersect_polygon_polygon + gemgis.vector.intersect_polygon_polygons + gemgis.vector.intersect_polygons_polygons Calculating and extracting coordinates from cross sections diff --git a/environment.yml b/environment.yml index 69f597fd..a9af2506 100644 --- a/environment.yml +++ b/environment.yml @@ -1,13 +1,19 @@ -# Requirements as of October 2023 +# Requirements as of December 2024 name: gemgis_env channels: - conda-forge dependencies: - - python>=3.10 - # geopandas will also install numpy, pandas, shapely, fiona, and pyproj - - geopandas>=0.14.0 + - python>=3.11 + # geopandas will also install numpy, pandas, shapely, pyogrio, and pyproj + - geopandas>=1.0.1 + - shapely>=2.0.6 + - pandas>=2.2.3 + - numpy>=2.1.3 + - affine>=2.4.0 + - pyproj>=3.7.0 # rasterio will also install affine - - rasterio>=1.3.8 + - rasterio>=1.4.3 # pyvista also install pooch and matplotlib - - pyvista>=0.42.2 + - pyvista>=0.44.2 + - matplotlib>=3.9.3 - gemgis>=1.1 diff --git a/environment_dev.yml b/environment_dev.yml index 11c6b989..a67ec7c1 100644 --- a/environment_dev.yml +++ b/environment_dev.yml @@ -2,13 +2,19 @@ name: gemgis_dev_env channels: - conda-forge dependencies: - - python>=3.10 - # geopandas will also install numpy, pandas, shapely, fiona, and pyproj - - geopandas>=0.14.0 + - python>=3.11 + # geopandas will also install numpy, pandas, shapely, pyogrio, and pyproj + - geopandas>=1.0.1 + - shapely>=2.0.6 + - pandas>=2.2.3 + - numpy>=2.1.3 + - affine>=2.4.0 + - pyproj>=3.7.0 # rasterio will also install affine - #- rasterio>=1.3.8 will be installed through rioxarray + - rasterio>=1.4.3 # pyvista also install pooch and matplotlib - #- pyvista>=0.42.2 will be installed through pvgeo + - pyvista>=0.44.2 + - matplotlib>=3.9.3 - gemgis>=1.1 - rioxarray - scipy diff --git a/gemgis/download_gemgis_data.py b/gemgis/download_gemgis_data.py index a96c4d98..1c327a7d 100644 --- a/gemgis/download_gemgis_data.py +++ b/gemgis/download_gemgis_data.py @@ -23,11 +23,8 @@ import zipfile -def create_pooch(storage_url: str, - files: List[str], - target: str): - """ - Create pooch class to fetch files from a website. +def create_pooch(storage_url: str, files: List[str], target: str): + """Create pooch class to fetch files from a website. Parameters __________ @@ -46,58 +43,62 @@ def create_pooch(storage_url: str, See also ________ download_tutorial_data: Download the GemGIS data for each tutorial. + """ try: import pooch except ModuleNotFoundError: raise ModuleNotFoundError( - 'Pooch package is not installed. Use pip install pooch to install the latest version') + "Pooch package is not installed. Use pip install pooch to install the latest version" + ) # Create new pooch - pc = pooch.create(base_url=storage_url, - path=target, - registry={i: None for i in files}) + pc = pooch.create( + base_url=storage_url, path=target, registry={i: None for i in files} + ) return pc -def download_tutorial_data(filename: str, - dirpath: str = '', - storage_url: str = 'https://rwth-aachen.sciebo.de/s/AfXRsZywYDbUF34/download?path=%2F'): - """ - Download the GemGIS data for each tutorial. +def download_tutorial_data( + filename: str, + dirpath: str = "", + storage_url: str = "https://rwth-aachen.sciebo.de/s/AfXRsZywYDbUF34/download?path=%2F", +): + """Download the GemGIS data for each tutorial. Parameters __________ filename : str File name to be downloaded by pooch, e.g. ``filename='file.zip'``. + dirpath : str, default: ``''`` Path to the directory where the data is being stored, default to the directory where the notebook is located, e.g. ``dirpath='Documents/gemgis/'``. + storage_url : str, default 'https://rwth-aachen.sciebo.de/s/AfXRsZywYDbUF34/download?path=%2F' URL to the GemGIS data storage, default is the RWTH Aachen University Sciebo Cloud Storage. See also ________ create_pooch : Create pooch class to fetch files from a website. + """ try: - import pooch from pooch import HTTPDownloader + download = HTTPDownloader(progressbar=False) except ModuleNotFoundError: raise ModuleNotFoundError( - 'Pooch package is not installed. Use pip install pooch to install the latest version') + "Pooch package is not installed. Use pip install pooch to install the latest version" + ) # Creating pooch object - pooch_data = create_pooch(storage_url=storage_url, - files=[filename], - target=dirpath) + pooch_data = create_pooch(storage_url=storage_url, files=[filename], target=dirpath) # Downloading data to the defined folder - pooch_data.fetch(fname=filename, - downloader=download) + pooch_data.fetch(fname=filename, downloader=download) # Opening zip file and unzip in specified directory - with zipfile.ZipFile(dirpath + filename, 'r') as zip_ref: + with zipfile.ZipFile(dirpath + filename, "r") as zip_ref: zip_ref.extractall(dirpath) diff --git a/gemgis/gemgis.py b/gemgis/gemgis.py index ebef8c08..e2e10c62 100644 --- a/gemgis/gemgis.py +++ b/gemgis/gemgis.py @@ -20,9 +20,9 @@ """ import numpy as np + # import scooby import pandas as pd -from pandas.core import frame import rasterio import geopandas as gpd import rasterio.transform @@ -54,7 +54,7 @@ class GemPyData(object): """ This class creates an object with attributes containing i.e. the interfaces or orientations that can directly be passed to a GemPy Model - + The following attributes are available: - model_name: string - the name of the model - crs: string - the coordinate reference system of the model @@ -65,8 +65,8 @@ class GemPyData(object): - customsections: GeoDataFrame containing the Linestrings or Endpoints of custom sections - resolution: list - List containing the x,y and z resolution of the model - dem: Union[string, array] - String containing the path to the DEM or array containing DEM values - - stack: dict - Dictionary containing the layer stack associated with the model - - surface_colors: dict - Dictionary containing the surface colors for the model + - stack: dict - Dictionary containing the layer stack associated with the model + - surface_colors: dict - Dictionary containing the surface colors for the model - is_fault: list - list of surface that are classified as faults - geolmap: Union[GeoDataFrame,np.ndarray rasterio.io.Datasetreader] - GeoDataFrame or array containing the geological map either as vector or raster data set @@ -83,31 +83,33 @@ class GemPyData(object): - contours: GeoDataFrame containing the contour lines of the model area """ - def __init__(self, - model_name=None, - crs=None, - extent=None, - resolution=None, - interfaces=None, - orientations=None, - section_dict=None, - customsections=None, - dem=None, - stack=None, - surface_colors=None, - is_fault=None, - geolmap=None, - basemap=None, - faults=None, - tectonics=None, - raw_i=None, - raw_o=None, - raw_dem=None, - wms=None, - slope=None, - hillshades=None, - aspect=None, - contours=None): + def __init__( + self, + model_name=None, + crs=None, + extent=None, + resolution=None, + interfaces=None, + orientations=None, + section_dict=None, + customsections=None, + dem=None, + stack=None, + surface_colors=None, + is_fault=None, + geolmap=None, + basemap=None, + faults=None, + tectonics=None, + raw_i=None, + raw_o=None, + raw_dem=None, + wms=None, + slope=None, + hillshades=None, + aspect=None, + contours=None, + ): # Checking if data type are correct @@ -130,9 +132,13 @@ def __init__(self, if all(isinstance(n, (int, (int, float))) for n in extent): self.extent = extent else: - raise TypeError('Coordinates for extent must be provided as integers or floats') + raise TypeError( + "Coordinates for extent must be provided as integers or floats" + ) else: - raise ValueError('Length of extent must be 6 [minx,maxx,miny,maxy,minz,maxz]') + raise ValueError( + "Length of extent must be 6 [minx,maxx,miny,maxy,minz,maxz]" + ) self.extent = extent else: raise TypeError("Extent must be of type list") @@ -144,9 +150,11 @@ def __init__(self, if all(isinstance(n, int) for n in resolution): self.resolution = resolution else: - raise TypeError('Values for resolution must be provided as integers') + raise TypeError( + "Values for resolution must be provided as integers" + ) else: - raise ValueError('Length of resolution must be 3 [x,y,z]') + raise ValueError("Length of resolution must be 3 [x,y,z]") self.resolution = resolution else: raise TypeError("Resolution must be of type list") @@ -154,8 +162,11 @@ def __init__(self, # Checking if the interfaces object is a Pandas df containing all relevant columns if isinstance(interfaces, (type(None), pd.core.frame.DataFrame)): if isinstance(interfaces, pd.core.frame.DataFrame): - assert pd.Series(['X', 'Y', 'Z', 'formation']).isin( - interfaces.columns).all(), 'Interfaces DataFrame is missing columns' + assert ( + pd.Series(["X", "Y", "Z", "formation"]) + .isin(interfaces.columns) + .all() + ), "Interfaces DataFrame is missing columns" self.interfaces = interfaces else: raise TypeError("Interfaces df must be a Pandas DataFrame") @@ -163,8 +174,13 @@ def __init__(self, # Checking if the orientations object is Pandas df containing all relevant columns if isinstance(orientations, (type(None), pd.core.frame.DataFrame)): if isinstance(orientations, pd.core.frame.DataFrame): - assert pd.Series(['X', 'Y', 'Z', 'formation', 'dip', 'azimuth', 'polarity']).isin( - orientations.columns).all(), 'Orientations DataFrame is missing columns' + assert ( + pd.Series( + ["X", "Y", "Z", "formation", "dip", "azimuth", "polarity"] + ) + .isin(orientations.columns) + .all() + ), "Orientations DataFrame is missing columns" self.orientations = orientations else: raise TypeError("Orientations df must be a Pandas DataFrame") @@ -185,18 +201,32 @@ def __init__(self, if isinstance(dem, (type(None), np.ndarray, rasterio.io.DatasetReader, str)): self.dem = dem else: - raise TypeError("Digital Elevation Model must be a np Array, a raster loaded with rasterio or a string") + raise TypeError( + "Digital Elevation Model must be a np Array, a raster loaded with rasterio or a string" + ) # Checking if the provided surface colors object is of type dict if isinstance(surface_colors, (type(None), dict)): self.surface_colors = surface_colors elif isinstance(surface_colors, str): - self.surface_colors = create_surface_color_dict('../../gemgis/data/Test1/style1.qml') + self.surface_colors = create_surface_color_dict( + "../../gemgis/data/Test1/style1.qml" + ) else: - raise TypeError("Surface Colors Dict must be of type dict or a path directing to a qml file") + raise TypeError( + "Surface Colors Dict must be of type dict or a path directing to a qml file" + ) # Checking that the provided geological map is a gdf containing polygons - if isinstance(geolmap, (type(None), gpd.geodataframe.GeoDataFrame, rasterio.io.DatasetReader, np.ndarray)): + if isinstance( + geolmap, + ( + type(None), + gpd.geodataframe.GeoDataFrame, + rasterio.io.DatasetReader, + np.ndarray, + ), + ): if isinstance(geolmap, gpd.geodataframe.GeoDataFrame): if all(geolmap.geom_type == "Polygon"): self.geolmap = geolmap @@ -216,7 +246,9 @@ def __init__(self, else: self.basemap = basemap else: - raise TypeError('Base Map must be a Raster loaded with rasterio or a NumPy Array') + raise TypeError( + "Base Map must be a Raster loaded with rasterio or a NumPy Array" + ) # Checking the the provided faults are a gdf containing LineStrings if isinstance(faults, (type(None), gpd.geodataframe.GeoDataFrame)): @@ -235,10 +267,10 @@ def __init__(self, if all(isinstance(n, str) for n in is_fault): self.is_fault = is_fault else: - raise TypeError('Fault Names must be provided as strings') + raise TypeError("Fault Names must be provided as strings") self.is_fault = is_fault else: - TypeError('List of faults must be of type list') + TypeError("List of faults must be of type list") # Checking that the provided raw input data objects are of type gdf if isinstance(raw_i, (gpd.geodataframe.GeoDataFrame, type(None))): @@ -259,7 +291,9 @@ def __init__(self, # Setting the hillshades attribute if isinstance(hillshades, np.ndarray): self.hillshades = hillshades - elif isinstance(self.raw_dem, np.ndarray) and isinstance(hillshades, type(None)): + elif isinstance(self.raw_dem, np.ndarray) and isinstance( + hillshades, type(None) + ): self.hillshades = calculate_hillshades(self.raw_dem, self.extent) else: self.hillshades = hillshades @@ -274,18 +308,18 @@ def __init__(self, # Calculate model dimensions if not isinstance(self.extent, type(None)): - self.model_width = self.extent[1]-self.extent[0] - self.model_length = self.extent[3]-self.extent[2] - self.model_depth = self.extent[5]-self.extent[4] - self.model_area = self.model_width*self.model_length - self.model_volume = self.model_area*self.model_depth + self.model_width = self.extent[1] - self.extent[0] + self.model_length = self.extent[3] - self.extent[2] + self.model_depth = self.extent[5] - self.extent[4] + self.model_area = self.model_width * self.model_length + self.model_volume = self.model_area * self.model_depth # Calculate cell dimensions if not isinstance(self.resolution, type(None)): if not isinstance(self.extent, type(None)): - self.cell_width = self.model_width/self.resolution[0] - self.cell_length = self.model_length/self.resolution[1] - self.cell_depth = self.model_depth/self.resolution[2] + self.cell_width = self.model_width / self.resolution[0] + self.cell_length = self.model_length / self.resolution[1] + self.cell_depth = self.model_depth / self.resolution[2] # Setting the wms attribute if isinstance(wms, np.ndarray): @@ -303,7 +337,7 @@ def __init__(self, else: self.customsections = customsections else: - raise TypeError('Custom sections must be provided as GeoDataFrame') + raise TypeError("Custom sections must be provided as GeoDataFrame") # Setting the contours attribute if isinstance(contours, (type(None), gpd.geodataframe.GeoDataFrame)): @@ -312,13 +346,15 @@ def __init__(self, else: self.contours = contours else: - raise TypeError('Custom sections must be provided as GeoDataFrame') + raise TypeError("Custom sections must be provided as GeoDataFrame") # Function tested - def to_section_dict(self, - gdf: gpd.geodataframe.GeoDataFrame, - section_column: str = 'section_name', - resolution=None): + def to_section_dict( + self, + gdf: gpd.geodataframe.GeoDataFrame, + section_column: str = "section_name", + resolution=None, + ): """ Converting custom sections stored in shape files to GemPy section_dicts Args: @@ -334,50 +370,70 @@ def to_section_dict(self, # Checking if gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking if the section_column is of type string if not isinstance(section_column, str): - raise TypeError('Name for section_column must be of type string') + raise TypeError("Name for section_column must be of type string") # Checking if resolution is of type list if not isinstance(resolution, list): - raise TypeError('resolution must be of type list') + raise TypeError("resolution must be of type list") # Checking if X and Y values are in column - if np.logical_not(pd.Series(['X', 'Y']).isin(gdf.columns).all()): + if np.logical_not(pd.Series(["X", "Y"]).isin(gdf.columns).all()): gdf = extract_xy(gdf) # Checking the length of the resolution list if len(resolution) != 2: - raise ValueError('resolution list must be of length two') + raise ValueError("resolution list must be of length two") # Checking that a valid section name is provided - if not {'section_name'}.issubset(gdf.columns): + if not {"section_name"}.issubset(gdf.columns): if not {section_column}.issubset(gdf.columns): - raise ValueError('Section_column name needed to create section_dict') + raise ValueError("Section_column name needed to create section_dict") # Extracting Section names section_names = gdf[section_column].unique() # Create section dicts for Point Shape Files if all(gdf.geom_type == "Point"): - section_dict = {i: ([gdf[gdf[section_column] == i].X.iloc[0], gdf[gdf[section_column] == i].Y.iloc[0]], - [gdf[gdf[section_column] == i].X.iloc[1], gdf[gdf[section_column] == i].Y.iloc[1]], - resolution) for i in section_names} + section_dict = { + i: ( + [ + gdf[gdf[section_column] == i].X.iloc[0], + gdf[gdf[section_column] == i].Y.iloc[0], + ], + [ + gdf[gdf[section_column] == i].X.iloc[1], + gdf[gdf[section_column] == i].Y.iloc[1], + ], + resolution, + ) + for i in section_names + } # Create section dicts for Line Shape Files else: - section_dict = {i: ([gdf[gdf[section_column] == i].X.iloc[0], gdf[gdf[section_column] == i].Y.iloc[0]], - [gdf[gdf[section_column] == i].X.iloc[1], gdf[gdf[section_column] == i].Y.iloc[1]], - resolution) for i in section_names} + section_dict = { + i: ( + [ + gdf[gdf[section_column] == i].X.iloc[0], + gdf[gdf[section_column] == i].Y.iloc[0], + ], + [ + gdf[gdf[section_column] == i].X.iloc[1], + gdf[gdf[section_column] == i].Y.iloc[1], + ], + resolution, + ) + for i in section_names + } self.section_dict = section_dict # Function tested - def to_gempy_df(self, - gdf: gpd.geodataframe.GeoDataFrame, - cat: str, **kwargs): + def to_gempy_df(self, gdf: gpd.geodataframe.GeoDataFrame, cat: str, **kwargs): """ Converting a GeoDataFrame into a Pandas DataFrame ready to be read in for GemPy Args: @@ -391,91 +447,110 @@ def to_gempy_df(self, # Checking if gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking if type is of type string if not isinstance(cat, str): - raise TypeError('Type must be of type string') + raise TypeError("Type must be of type string") - if np.logical_not(pd.Series(['X', 'Y', 'Z']).isin(gdf.columns).all()): - dem = kwargs.get('dem', None) - extent = kwargs.get('extent', None) + if np.logical_not(pd.Series(["X", "Y", "Z"]).isin(gdf.columns).all()): + dem = kwargs.get("dem", None) + extent = kwargs.get("extent", None) if not isinstance(dem, type(None)): gdf = extract_xyz(gdf, dem, extent=extent) else: - raise FileNotFoundError('DEM not provided to obtain Z values for point data') - if np.logical_not(pd.Series(['formation']).isin(gdf.columns).all()): - raise ValueError('formation names not defined') + raise FileNotFoundError( + "DEM not provided to obtain Z values for point data" + ) + if np.logical_not(pd.Series(["formation"]).isin(gdf.columns).all()): + raise ValueError("formation names not defined") # Converting dip and azimuth columns to floats - if pd.Series(['dip']).isin(gdf.columns).all(): - gdf['dip'] = gdf['dip'].astype(float) + if pd.Series(["dip"]).isin(gdf.columns).all(): + gdf["dip"] = gdf["dip"].astype(float) - if pd.Series(['azimuth']).isin(gdf.columns).all(): - gdf['azimuth'] = gdf['azimuth'].astype(float) + if pd.Series(["azimuth"]).isin(gdf.columns).all(): + gdf["azimuth"] = gdf["azimuth"].astype(float) # Converting formation column to string - if pd.Series(['formation']).isin(gdf.columns).all(): - gdf['formation'] = gdf['formation'].astype(str) + if pd.Series(["formation"]).isin(gdf.columns).all(): + gdf["formation"] = gdf["formation"].astype(str) # Checking if dataframe is an orientation or interfaces df - if pd.Series(['dip']).isin(gdf.columns).all(): - if cat == 'orientations': - if (gdf['dip'] > 90).any(): - raise ValueError('dip values exceed 90 degrees') - if np.logical_not(pd.Series(['azimuth']).isin(gdf.columns).all()): - raise ValueError('azimuth values not defined') - if (gdf['azimuth'] > 360).any(): - raise ValueError('azimuth values exceed 360 degrees') + if pd.Series(["dip"]).isin(gdf.columns).all(): + if cat == "orientations": + if (gdf["dip"] > 90).any(): + raise ValueError("dip values exceed 90 degrees") + if np.logical_not(pd.Series(["azimuth"]).isin(gdf.columns).all()): + raise ValueError("azimuth values not defined") + if (gdf["azimuth"] > 360).any(): + raise ValueError("azimuth values exceed 360 degrees") # Create orientations dataframe - if np.logical_not(pd.Series(['polarity']).isin(gdf.columns).all()): - df = pd.DataFrame(gdf[['X', 'Y', 'Z', 'formation', 'dip', 'azimuth']].reset_index()) - df['polarity'] = 1 + if np.logical_not(pd.Series(["polarity"]).isin(gdf.columns).all()): + df = pd.DataFrame( + gdf[ + ["X", "Y", "Z", "formation", "dip", "azimuth"] + ].reset_index() + ) + df["polarity"] = 1 self.orientations = df else: - self.orientations = \ - pd.DataFrame(gdf[['X', 'Y', 'Z', 'formation', 'dip', 'azimuth', 'polarity']].reset_index()) + self.orientations = pd.DataFrame( + gdf[ + ["X", "Y", "Z", "formation", "dip", "azimuth", "polarity"] + ].reset_index() + ) else: - raise ValueError('GeoDataFrame contains orientations but type is interfaces') + raise ValueError( + "GeoDataFrame contains orientations but type is interfaces" + ) else: - if cat == 'interfaces': + if cat == "interfaces": # Create interfaces dataframe - self.interfaces = pd.DataFrame(gdf[['X', 'Y', 'Z', 'formation']].reset_index()) + self.interfaces = pd.DataFrame( + gdf[["X", "Y", "Z", "formation"]].reset_index() + ) else: - raise ValueError('GeoDataFrame contains interfaces but type is orientations') + raise ValueError( + "GeoDataFrame contains interfaces but type is orientations" + ) # Function tested - def set_extent(self, minx: Union[int, float] = 0, - maxx: Union[int, float] = 0, - miny: Union[int, float] = 0, - maxy: Union[int, float] = 0, - minz: Union[int, float] = 0, - maxz: Union[int, float] = 0, - **kwargs): + def set_extent( + self, + minx: Union[int, float] = 0, + maxx: Union[int, float] = 0, + miny: Union[int, float] = 0, + maxy: Union[int, float] = 0, + minz: Union[int, float] = 0, + maxz: Union[int, float] = 0, + **kwargs, + ): + """Setting the extent for a model. + Args: + minx - float defining the left border of the model + maxx - float defining the right border of the model + miny - float defining the upper border of the model + maxy - float defining the lower border of the model + minz - float defining the top border of the model + maxz - float defining the bottom border of the model + Kwargs: + gdf - GeoDataFrame from which bounds the extent will be set + Return: + extent - list with resolution values """ - Setting the extent for a model - Args: - minx - float defining the left border of the model - maxx - float defining the right border of the model - miny - float defining the upper border of the model - maxy - float defining the lower border of the model - minz - float defining the top border of the model - maxz - float defining the bottom border of the model - Kwargs: - gdf - GeoDataFrame from which bounds the extent will be set - Return: - extent - list with resolution values - """ - gdf = kwargs.get('gdf', None) + gdf = kwargs.get("gdf", None) if not isinstance(gdf, (type(None), gpd.geodataframe.GeoDataFrame)): - raise TypeError('gdf mus be of type GeoDataFrame') + raise TypeError("gdf mus be of type GeoDataFrame") # Checking if bounds are of type int or float - if not all(isinstance(i, (int, float)) for i in [minx, maxx, miny, maxy, minz, maxz]): - raise TypeError('bounds must be of type int or float') + if not all( + isinstance(i, (int, float)) for i in [minx, maxx, miny, maxy, minz, maxz] + ): + raise TypeError("bounds must be of type int or float") # Checking if the gdf is of type None if isinstance(gdf, type(None)): @@ -491,8 +566,14 @@ def set_extent(self, minx: Union[int, float] = 0, # Create extent from gdf of geom_type point or linestring else: bounds = gdf.bounds - extent = [round(bounds.minx.min(), 2), round(bounds.maxx.max(), 2), round(bounds.miny.min(), 2), - round(bounds.maxy.max(), 2), minz, maxz] + extent = [ + round(bounds.minx.min(), 2), + round(bounds.maxx.max(), 2), + round(bounds.miny.min(), 2), + round(bounds.maxy.max(), 2), + minz, + maxz, + ] self.extent = extent self.model_width = self.extent[1] - self.extent[0] @@ -518,15 +599,15 @@ def set_resolution(self, x: int, y: int, z: int): # Checking if x is of type int if not isinstance(x, int): - raise TypeError('X must be of type int') + raise TypeError("X must be of type int") # Checking if y is of type int if not isinstance(y, int): - raise TypeError('Y must be of type int') + raise TypeError("Y must be of type int") # Checking if y is of type int if not isinstance(z, int): - raise TypeError('Z must be of type int') + raise TypeError("Z must be of type int") self.resolution = [x, y, z] @@ -547,7 +628,7 @@ def to_surface_color_dict(self, path: str, **kwargs): # Checking that the path is of type str if not isinstance(path, str): - raise TypeError('path must be provided as string') + raise TypeError("path must be provided as string") # Parse qml columns, classes = parse_categorized_qml(path) @@ -558,14 +639,14 @@ def to_surface_color_dict(self, path: str, **kwargs): # Create surface_colors_dict surface_colors_dict = {k: v["color"] for k, v in styles.items() if k} - basement = kwargs.get('basement', None) + basement = kwargs.get("basement", None) # Checking if discarded formation is of type string if not isinstance(basement, (str, type(None))): - raise TypeError('Basement formation name must be of type string') + raise TypeError("Basement formation name must be of type string") # Pop oldest lithology from dict as it does not need a color in GemPy if isinstance(basement, str): - surface_colors_dict['basement'] = surface_colors_dict.pop(basement) + surface_colors_dict["basement"] = surface_colors_dict.pop(basement) self.surface_colors = surface_colors_dict diff --git a/gemgis/misc.py b/gemgis/misc.py index 16f895bb..bdb7a2cb 100644 --- a/gemgis/misc.py +++ b/gemgis/misc.py @@ -31,8 +31,8 @@ # Borehole logs can be requested at no charge from the Geological Survey from the database DABO: # https://www.gd.nrw.de/gd_archive_dabo.htm -def load_pdf(path: str, - save_as_txt: bool = True) -> str: + +def load_pdf(path: str, save_as_txt: bool = True) -> str: """ Load PDF file containing borehole data. @@ -73,17 +73,21 @@ def load_pdf(path: str, try: import pypdf except ModuleNotFoundError: - raise ModuleNotFoundError('PyPDF package is not installed. Use pip install pypdf to install the latest version') + raise ModuleNotFoundError( + "PyPDF package is not installed. Use pip install pypdf to install the latest version" + ) # Trying to import tqdm but returning error if tqdm is not installed try: from tqdm import tqdm except ModuleNotFoundError: - raise ModuleNotFoundError('tqdm package is not installed. Use pip install tqdm to install the latest version') + raise ModuleNotFoundError( + "tqdm package is not installed. Use pip install tqdm to install the latest version" + ) # Checking that the file path is of type string if not isinstance(path, str): - raise TypeError('Path/Name must be of type string') + raise TypeError("Path/Name must be of type string") # Getting the absolute path path = os.path.abspath(path=path) @@ -94,14 +98,14 @@ def load_pdf(path: str, # Checking that the file exists if not os.path.exists(path): - raise FileNotFoundError('File not found') + raise FileNotFoundError("File not found") # Checking that save_as_bool is of type bool if not isinstance(save_as_txt, bool): - raise TypeError('Save_as_txt variable must be of type bool') + raise TypeError("Save_as_txt variable must be of type bool") # Open the file as binary object - data = open(path, 'rb') + data = open(path, "rb") # Create new PdfFileReader object filereader = pypdf.PdfReader(data) @@ -110,7 +114,7 @@ def load_pdf(path: str, number_of_pages = len(filereader.pages) # Create empty string to store page content - page_content = '' + page_content = "" # Retrieve page content for each page for i in tqdm(range(number_of_pages)): @@ -121,14 +125,14 @@ def load_pdf(path: str, # Saving a txt-file of the retrieved page content for further usage if save_as_txt: # Split path to get original file name - name = path.split('.pdf')[0] + name = path.split(".pdf")[0] # Open new text file - with open(name + '.txt', "w") as text_file: + with open(name + ".txt", "w") as text_file: text_file.write(page_content) # Print out message if saving was successful - print('%s.txt successfully saved' % name) + print("%s.txt successfully saved" % name) return page_content @@ -181,7 +185,7 @@ def load_symbols(path: str) -> list: # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Getting the absolute path path = os.path.abspath(path=path) @@ -192,11 +196,11 @@ def load_symbols(path: str) -> list: # Checking that the file exists if not os.path.exists(path): - raise FileNotFoundError('File not found') + raise FileNotFoundError("File not found") # Opening file with open(path, "r") as text_file: - symbols = [(i, '') for i in text_file.read().splitlines()] + symbols = [(i, "") for i in text_file.read().splitlines()] return symbols @@ -237,7 +241,7 @@ def load_formations(path: str) -> list: # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Getting the absolute path path = os.path.abspath(path=path) @@ -248,13 +252,15 @@ def load_formations(path: str) -> list: # Checking that the file exists if not os.path.exists(path): - raise FileNotFoundError('File not found') + raise FileNotFoundError("File not found") # Opening file with open(path, "rb") as text_file: formations = text_file.read().decode("UTF-8").split() - formations = [(formations[i], formations[i + 1]) for i in range(0, len(formations) - 1, 2)] + formations = [ + (formations[i], formations[i + 1]) for i in range(0, len(formations) - 1, 2) + ] return formations @@ -319,155 +325,181 @@ def get_meta_data(page: List[str]) -> list: # Checking that the data is of type list if not isinstance(page, list): - raise TypeError('Page must be of type list') + raise TypeError("Page must be of type list") # Checking that all elements are of type str if not all(isinstance(n, str) for n in page): - raise TypeError('All elements of the list must be of type str') + raise TypeError("All elements of the list must be of type str") # Obtaining DABO Number - well_dabo = page[page.index('Bnum:') + 1:page.index('Bnum:') + 2] - well_dabo = ''.join(well_dabo) - well_dabo = well_dabo.split('Object')[0] - well_dabo = 'DABO_' + well_dabo + well_dabo = page[page.index("Bnum:") + 1 : page.index("Bnum:") + 2] + well_dabo = "".join(well_dabo) + well_dabo = well_dabo.split("Object")[0] + well_dabo = "DABO_" + well_dabo # Obtaining Name of Well - well_name = page[page.index('Name') + 1:page.index('Bohrungs-')] - well_name = ''.join(well_name).replace(':', '') + well_name = page[page.index("Name") + 1 : page.index("Bohrungs-")] + well_name = "".join(well_name).replace(":", "") # Obtaining Number of Well - well_number = page[page.index('Aufschluß-Nr.') + 1:page.index('Aufschluß-Nr.') + 4] - well_number = ''.join(well_number).replace(':', '') - well_number = well_number.split('Archiv-Nr.')[0] + well_number = page[ + page.index("Aufschluß-Nr.") + 1 : page.index("Aufschluß-Nr.") + 4 + ] + well_number = "".join(well_number).replace(":", "") + well_number = well_number.split("Archiv-Nr.")[0] # Obtaining Depth of well - well_depth = page[page.index('Endteufe') + 3:page.index('Endteufe') + 4] - well_depth = float(''.join(well_depth).replace(':', '')) + well_depth = page[page.index("Endteufe") + 3 : page.index("Endteufe") + 4] + well_depth = float("".join(well_depth).replace(":", "")) # Obtaining Stratigraphie der Endteufe - well_strat = page[page.index('Stratigraphie') + 3:page.index('Stratigraphie') + 4] - well_strat = ''.join(well_strat).replace(':', '') + well_strat = page[page.index("Stratigraphie") + 3 : page.index("Stratigraphie") + 4] + well_strat = "".join(well_strat).replace(":", "") # Obtaining Topographic Map Sheet Number - well_tk = page[page.index('TK') + 2:page.index('TK') + 5] - well_tk = ''.join(well_tk).replace(':', '') - well_tk = ''.join(well_tk).replace('[TK', ' [TK ') + well_tk = page[page.index("TK") + 2 : page.index("TK") + 5] + well_tk = "".join(well_tk).replace(":", "") + well_tk = "".join(well_tk).replace("[TK", " [TK ") # Obtaining Commune - well_gemarkung = page[page.index('Gemarkung') + 1:page.index('Gemarkung') + 2] - well_gemarkung = ''.join(well_gemarkung).replace(':', '') + well_gemarkung = page[page.index("Gemarkung") + 1 : page.index("Gemarkung") + 2] + well_gemarkung = "".join(well_gemarkung).replace(":", "") # Obtaining GK Coordinates of wells - well_coord_x_gk = page[page.index('Rechtswert/Hochwert') + 3:page.index('Rechtswert/Hochwert') + 4] - well_coord_x_gk = ''.join(well_coord_x_gk).replace(':', '') + well_coord_x_gk = page[ + page.index("Rechtswert/Hochwert") + 3 : page.index("Rechtswert/Hochwert") + 4 + ] + well_coord_x_gk = "".join(well_coord_x_gk).replace(":", "") - well_coord_y_gk = page[page.index('Rechtswert/Hochwert') + 5:page.index('Rechtswert/Hochwert') + 6] - well_coord_y_gk = ''.join(well_coord_y_gk).replace(':', '') + well_coord_y_gk = page[ + page.index("Rechtswert/Hochwert") + 5 : page.index("Rechtswert/Hochwert") + 6 + ] + well_coord_y_gk = "".join(well_coord_y_gk).replace(":", "") # Obtaining UTM Coordinates of wells - well_coord_x = page[page.index('East/North') + 3:page.index('East/North') + 4] - well_coord_x = ''.join(well_coord_x).replace(':', '') + well_coord_x = page[page.index("East/North") + 3 : page.index("East/North") + 4] + well_coord_x = "".join(well_coord_x).replace(":", "") - well_coord_y = page[page.index('East/North') + 5:page.index('East/North') + 6] - well_coord_y = ''.join(well_coord_y).replace(':', '') + well_coord_y = page[page.index("East/North") + 5 : page.index("East/North") + 6] + well_coord_y = "".join(well_coord_y).replace(":", "") - well_coord_z = page[page.index('Ansatzpunktes') + 3:page.index('Ansatzpunktes') + 4] - well_coord_z = ''.join(well_coord_z).replace(':', '') + well_coord_z = page[ + page.index("Ansatzpunktes") + 3 : page.index("Ansatzpunktes") + 4 + ] + well_coord_z = "".join(well_coord_z).replace(":", "") # Obtaining Coordinates Precision - well_coords = page[page.index('Koordinatenbestimmung') + 1:page.index('Koordinatenbestimmung') + 7] - well_coords = ' '.join(well_coords).replace(':', '') - well_coords = well_coords.split(' Hoehenbestimmung')[0] + well_coords = page[ + page.index("Koordinatenbestimmung") + + 1 : page.index("Koordinatenbestimmung") + + 7 + ] + well_coords = " ".join(well_coords).replace(":", "") + well_coords = well_coords.split(" Hoehenbestimmung")[0] # Obtaining height precision - well_height = page[page.index('Hoehenbestimmung') + 1:page.index('Hoehenbestimmung') + 8] - well_height = ' '.join(well_height).replace(':', '') - well_height = ''.join(well_height).replace(' .', '') - well_height = well_height.split(' Hauptzweck')[0] + well_height = page[ + page.index("Hoehenbestimmung") + 1 : page.index("Hoehenbestimmung") + 8 + ] + well_height = " ".join(well_height).replace(":", "") + well_height = "".join(well_height).replace(" .", "") + well_height = well_height.split(" Hauptzweck")[0] # Obtaining Purpose - well_zweck = page[page.index('Aufschlusses') + 1:page.index('Aufschlusses') + 4] - well_zweck = ' '.join(well_zweck).replace(':', '') - well_zweck = well_zweck.split(' Aufschlussart')[0] + well_zweck = page[page.index("Aufschlusses") + 1 : page.index("Aufschlusses") + 4] + well_zweck = " ".join(well_zweck).replace(":", "") + well_zweck = well_zweck.split(" Aufschlussart")[0] # Obtaining Kind - well_aufschlussart = page[page.index('Aufschlussart') + 1:page.index('Aufschlussart') + 3] - well_aufschlussart = ' '.join(well_aufschlussart).replace(':', '') - well_aufschlussart = well_aufschlussart.split(' Aufschlussverfahren')[0] + well_aufschlussart = page[ + page.index("Aufschlussart") + 1 : page.index("Aufschlussart") + 3 + ] + well_aufschlussart = " ".join(well_aufschlussart).replace(":", "") + well_aufschlussart = well_aufschlussart.split(" Aufschlussverfahren")[0] # Obtaining Procedure - well_aufschlussverfahren = page[page.index('Aufschlussverfahren') + 1:page.index('Aufschlussverfahren') + 4] - well_aufschlussverfahren = ' '.join(well_aufschlussverfahren).replace(':', '') - well_aufschlussverfahren = well_aufschlussverfahren.split(' Vertraulichkeit')[0] + well_aufschlussverfahren = page[ + page.index("Aufschlussverfahren") + 1 : page.index("Aufschlussverfahren") + 4 + ] + well_aufschlussverfahren = " ".join(well_aufschlussverfahren).replace(":", "") + well_aufschlussverfahren = well_aufschlussverfahren.split(" Vertraulichkeit")[0] # Obtaining Confidentiality - well_vertraulichkeit = page[page.index('Vertraulichkeit') + 1:page.index('Vertraulichkeit') + 14] - well_vertraulichkeit = ' '.join(well_vertraulichkeit).replace(':', '') - well_vertraulichkeit = well_vertraulichkeit.split(' Art')[0] + well_vertraulichkeit = page[ + page.index("Vertraulichkeit") + 1 : page.index("Vertraulichkeit") + 14 + ] + well_vertraulichkeit = " ".join(well_vertraulichkeit).replace(":", "") + well_vertraulichkeit = well_vertraulichkeit.split(" Art")[0] # Obtaining Type of Record - well_aufnahme = page[page.index('Aufnahme') + 1:page.index('Aufnahme') + 10] - well_aufnahme = ' '.join(well_aufnahme).replace(':', '') - well_aufnahme = well_aufnahme.split(' . Schichtenverzeichnis')[0] + well_aufnahme = page[page.index("Aufnahme") + 1 : page.index("Aufnahme") + 10] + well_aufnahme = " ".join(well_aufnahme).replace(":", "") + well_aufnahme = well_aufnahme.split(" . Schichtenverzeichnis")[0] # Obtaining Lithlog Version - well_version = page[page.index('Version') + 1:page.index('Version') + 3] - well_version = ' '.join(well_version).replace(':', '') - well_version = well_version.split(' Qualität')[0] + well_version = page[page.index("Version") + 1 : page.index("Version") + 3] + well_version = " ".join(well_version).replace(":", "") + well_version = well_version.split(" Qualität")[0] # Obtaining Quality - well_quality = page[page.index('Qualität') + 1:page.index('Qualität') + 9] - well_quality = ' '.join(well_quality).replace(':', '') - well_quality = well_quality.split(' erster')[0] + well_quality = page[page.index("Qualität") + 1 : page.index("Qualität") + 9] + well_quality = " ".join(well_quality).replace(":", "") + well_quality = well_quality.split(" erster")[0] # Obtaining Drilling Period - well_date = page[page.index('Bohrtag') + 1:page.index('Bohrtag') + 6] - well_date = ' '.join(well_date).replace(':', '') - well_date = well_date.split(' . Grundwasserstand')[0] + well_date = page[page.index("Bohrtag") + 1 : page.index("Bohrtag") + 6] + well_date = " ".join(well_date).replace(":", "") + well_date = well_date.split(" . Grundwasserstand")[0] # Obtaining Remarks - well_remarks = page[page.index('Bemerkung') + 1:page.index('Bemerkung') + 14] - well_remarks = ' '.join(well_remarks).replace(':', '') - well_remarks = well_remarks.split(' . Originalschichtenverzeichnis')[0] + well_remarks = page[page.index("Bemerkung") + 1 : page.index("Bemerkung") + 14] + well_remarks = " ".join(well_remarks).replace(":", "") + well_remarks = well_remarks.split(" . Originalschichtenverzeichnis")[0] # Obtaining Availability of Lithlog - well_lithlog = page[page.index('Originalschichtenverzeichnis') + 1:page.index('Originalschichtenverzeichnis') + 7] - well_lithlog = ' '.join(well_lithlog).replace(':', '') - well_lithlog = well_lithlog.split(' .Schichtdaten')[0] - well_lithlog = well_lithlog.split(' .Geologischer Dienst NRW')[0] + well_lithlog = page[ + page.index("Originalschichtenverzeichnis") + + 1 : page.index("Originalschichtenverzeichnis") + + 7 + ] + well_lithlog = " ".join(well_lithlog).replace(":", "") + well_lithlog = well_lithlog.split(" .Schichtdaten")[0] + well_lithlog = well_lithlog.split(" .Geologischer Dienst NRW")[0] # Create list with data - data = [well_dabo, - well_name, - well_number, - float(well_depth), - float(well_coord_x), - float(well_coord_y), - float(well_coord_z), - float(well_coord_x_gk), - float(well_coord_y_gk), - well_strat, - well_tk, - well_gemarkung, - well_coords, - well_height, - well_zweck, - well_aufschlussart, - well_aufschlussverfahren, - well_vertraulichkeit, - well_aufnahme, - well_version, - well_quality, - well_date, - well_remarks, - well_lithlog] + data = [ + well_dabo, + well_name, + well_number, + float(well_depth), + float(well_coord_x), + float(well_coord_y), + float(well_coord_z), + float(well_coord_x_gk), + float(well_coord_y_gk), + well_strat, + well_tk, + well_gemarkung, + well_coords, + well_height, + well_zweck, + well_aufschlussart, + well_aufschlussverfahren, + well_vertraulichkeit, + well_aufnahme, + well_version, + well_quality, + well_date, + well_remarks, + well_lithlog, + ] return data -def get_meta_data_df(data: str, - name: str = 'GD', - return_gdf: bool = True) -> Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame]: +def get_meta_data_df( + data: str, name: str = "GD", return_gdf: bool = True +) -> Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame]: """Function to create a dataframe with coordinates and meta data of the different boreholes Parameters @@ -524,110 +556,124 @@ def get_meta_data_df(data: str, # Checking that the data is of type list if not isinstance(data, str): - raise TypeError('Data must be provided as list of strings') + raise TypeError("Data must be provided as list of strings") # Checking that the name is of type string if not isinstance(name, str): - raise TypeError('Path/Name must be of type string') + raise TypeError("Path/Name must be of type string") # Checking that the return_gdf variable is of type bool if not isinstance(return_gdf, bool): - raise TypeError('Return_gdf variable must be of type bool') + raise TypeError("Return_gdf variable must be of type bool") # Split Data data = data.split() - data = '#'.join(data) - data = data.split('-#Stammdaten') - data = [item.split('|')[0] for item in data] - data = [item.split('#') for item in data] + data = "#".join(data) + data = data.split("-#Stammdaten") + data = [item.split("|")[0] for item in data] + data = [item.split("#") for item in data] # Filter out wells without Stratigraphic Column - data = [item for item in data if 'Beschreibung' in item] + data = [item for item in data if "Beschreibung" in item] # Get Coordinates of data coordinates = [get_meta_data(page=item) for item in data] # Create dataframe from coordinates - coordinates_dataframe = pd.DataFrame(data=coordinates, columns=['DABO No.', - 'Name', - 'Number', - 'Depth', - 'X', - 'Y', - 'Z', - 'X_GK', - 'Y_GK', - 'Last Stratigraphic Unit', - 'Map Sheet', - 'Commune', - 'Coordinates Precision', - 'Height Precision', - 'Purpose', - 'Kind', - 'Procedure', - 'Confidentiality', - 'Record Type', - 'Lithlog Version', - 'Quality', - 'Drilling Period', - 'Remarks', - 'Availability Lithlog']) + coordinates_dataframe = pd.DataFrame( + data=coordinates, + columns=[ + "DABO No.", + "Name", + "Number", + "Depth", + "X", + "Y", + "Z", + "X_GK", + "Y_GK", + "Last Stratigraphic Unit", + "Map Sheet", + "Commune", + "Coordinates Precision", + "Height Precision", + "Purpose", + "Kind", + "Procedure", + "Confidentiality", + "Record Type", + "Lithlog Version", + "Quality", + "Drilling Period", + "Remarks", + "Availability Lithlog", + ], + ) # Creating an empty list for indices index = [] # Filling index list with indices for i in range(len(coordinates_dataframe)): - index = np.append(index, [name + '{0:04}'.format(i + 1)]) - index = pd.DataFrame(data=index, columns=['Index']) + index = np.append(index, [name + "{0:04}".format(i + 1)]) + index = pd.DataFrame(data=index, columns=["Index"]) # Creating DataFrame coordinates_dataframe = pd.concat([coordinates_dataframe, index], axis=1) # Selecting columns - coordinates_dataframe = coordinates_dataframe[['Index', - 'DABO No.', - 'Name', - 'Number', - 'Depth', - 'X', - 'Y', - 'Z', - 'X_GK', - 'Y_GK', - 'Last Stratigraphic Unit', - 'Map Sheet', - 'Commune', - 'Coordinates Precision', - 'Height Precision', - 'Purpose', - 'Kind', - 'Procedure', - 'Confidentiality', - 'Record Type', - 'Lithlog Version', - 'Quality', - 'Drilling Period', - 'Remarks', - 'Availability Lithlog' - ]] + coordinates_dataframe = coordinates_dataframe[ + [ + "Index", + "DABO No.", + "Name", + "Number", + "Depth", + "X", + "Y", + "Z", + "X_GK", + "Y_GK", + "Last Stratigraphic Unit", + "Map Sheet", + "Commune", + "Coordinates Precision", + "Height Precision", + "Purpose", + "Kind", + "Procedure", + "Confidentiality", + "Record Type", + "Lithlog Version", + "Quality", + "Drilling Period", + "Remarks", + "Availability Lithlog", + ] + ] # Remove duplicates containing identical X, Y and Z coordinates - coordinates_dataframe = coordinates_dataframe[~coordinates_dataframe.duplicated(subset=['X', 'Y', 'Z'])] + coordinates_dataframe = coordinates_dataframe[ + ~coordinates_dataframe.duplicated(subset=["X", "Y", "Z"]) + ] # Convert df to gdf if return_gdf: - coordinates_dataframe = gpd.GeoDataFrame(data=coordinates_dataframe, - geometry=gpd.points_from_xy(x=coordinates_dataframe.X, - y=coordinates_dataframe.Y, - crs='EPSG:4647')) + coordinates_dataframe = gpd.GeoDataFrame( + data=coordinates_dataframe, + geometry=gpd.points_from_xy( + x=coordinates_dataframe.X, y=coordinates_dataframe.Y, crs="EPSG:4647" + ), + ) return coordinates_dataframe -def get_stratigraphic_data(text: list, - symbols: List[Tuple[str, str]], - formations: List[Tuple[str, str]], ) -> list: +def get_stratigraphic_data( + text: list, + symbols: List[Tuple[str, str]], + formations: List[Tuple[str, str]], +) -> list: """Function to retrieve the stratigraphic data from borehole logs Parameters @@ -672,15 +718,15 @@ def get_stratigraphic_data(text: list, # Checking if the provided text is of type list if not isinstance(text, list): - raise TypeError('The provided data must be of type list') + raise TypeError("The provided data must be of type list") # Checking if the provided symbols are of type list if not isinstance(symbols, list): - raise TypeError('The provided symbols must be of type list') + raise TypeError("The provided symbols must be of type list") # Checking if the provided formations are of type list if not isinstance(formations, list): - raise TypeError('The provided formations must be of type list') + raise TypeError("The provided formations must be of type list") # Creating empty lists depth = [] @@ -691,52 +737,120 @@ def get_stratigraphic_data(text: list, txt = text # Join elements of list - txt = ''.join(txt) + txt = "".join(txt) # Obtaining Name of Well - well_name = text[text.index('Name') + 1:text.index('Bohrungs-')] - well_name = ''.join(well_name).replace(':', '') + well_name = text[text.index("Name") + 1 : text.index("Bohrungs-")] + well_name = "".join(well_name).replace(":", "") # Obtaining Depth of well - well_depth = text[text.index('Endteufe') + 3:text.index('Endteufe') + 4] - well_depth = float(''.join(well_depth).replace(':', '')) + well_depth = text[text.index("Endteufe") + 3 : text.index("Endteufe") + 4] + well_depth = float("".join(well_depth).replace(":", "")) # Obtaining UTM Coordinates of wells - well_coord_x = text[text.index('East/North') + 3:text.index('East/North') + 4] - well_coord_x = ''.join(well_coord_x).replace(':', '') + well_coord_x = text[text.index("East/North") + 3 : text.index("East/North") + 4] + well_coord_x = "".join(well_coord_x).replace(":", "") - well_coord_y = text[text.index('East/North') + 5:text.index('East/North') + 6] - well_coord_y = ''.join(well_coord_y).replace(':', '') + well_coord_y = text[text.index("East/North") + 5 : text.index("East/North") + 6] + well_coord_y = "".join(well_coord_y).replace(":", "") - well_coord_z = text[text.index('Ansatzpunktes') + 3:text.index('Ansatzpunktes') + 4] - well_coord_z = ''.join(well_coord_z).replace(':', '') + well_coord_z = text[ + text.index("Ansatzpunktes") + 3 : text.index("Ansatzpunktes") + 4 + ] + well_coord_z = "".join(well_coord_z).replace(":", "") # Defining Phrases - phrases = ['Fachaufsicht:GeologischerDienstNRW', 'Auftraggeber:GeologischerDienstNRW', - 'Bohrunternehmer:GeologischerDienstNRW', 'aufgestelltvon:GeologischerDienstNRW', - 'geol./stratgr.bearbeitetvon:GeologischerDienstNRW', 'NachRh.W.B.-G.', 'Vol.-', 'Mst.-Bänke', 'Cen.-', - 'Tst.-Stücke', 'mit Mst. - Stücken', 'Flaserstruktur(O.-', 'FlaserstrukturO.-', 'Kalkst.-', - 'gca.-Mächtigkeit', 'ca.-', 'Karbonsst.-Gerölle', - 'Mst.-Stücken', 'Mst.-Bank17,1-17,2m', 'Tst.-Stücke', 'Mst.-Bank', 'Mst. - Stücken', 'hum.-torfig', - 'rötl.-ocker', 'Pfl.-Reste', 'Utbk.-Flözg', 'Glauk.-', 'Toneisensteinlagenu.-', 'Ostrac.-', 'Stromat.-', - 'u.-knötchen', 'U.-Camp.', 'Kalkmergelst.-Gerölle', 'Pfl.-Laden', 'Pfl.-Häcksel', 'ca.-Angabe,', 'Z.-', - 'Hgd.-Schiefer', 'Sdst.-Fame', 'Orig.-Schi', 'Mergels.-', 'Kst.-', 'Steink.-G', 'Steink.-', 'Sst.-', - 'bzw.-anfang', 'nd.-er', 'u.-knäuel', 'u.-konk', 'u.-knoten', 'ng.-Bür', 'Ton.-', 'org.-', 'FS.-', - 'dkl.-', 'Schluff.-', 'Erw.-', 'Abl.-', 'abl.-', 'Sch.-', 'alsU.-', 'Plänerkst.-', 'Süßw.-', 'KV.-', - 'duchläss.-', 'Verwitt.-', 'durchlass.-', 'San.-', 'Unterkr.-', 'grünl.-', 'Stringocephal.-', 'Zinkbl.-', - 'Amphip.-', 'Tonst.-', 'Öffn.-', 'Trennflä.-', 'Randkalku.-dolomit', - 'keineAngaben,Bemerkung:nachOrig.-SV:"Lehm",'] + phrases = [ + "Fachaufsicht:GeologischerDienstNRW", + "Auftraggeber:GeologischerDienstNRW", + "Bohrunternehmer:GeologischerDienstNRW", + "aufgestelltvon:GeologischerDienstNRW", + "geol./stratgr.bearbeitetvon:GeologischerDienstNRW", + "NachRh.W.B.-G.", + "Vol.-", + "Mst.-Bänke", + "Cen.-", + "Tst.-Stücke", + "mit Mst. - Stücken", + "Flaserstruktur(O.-", + "FlaserstrukturO.-", + "Kalkst.-", + "gca.-Mächtigkeit", + "ca.-", + "Karbonsst.-Gerölle", + "Mst.-Stücken", + "Mst.-Bank17,1-17,2m", + "Tst.-Stücke", + "Mst.-Bank", + "Mst. - Stücken", + "hum.-torfig", + "rötl.-ocker", + "Pfl.-Reste", + "Utbk.-Flözg", + "Glauk.-", + "Toneisensteinlagenu.-", + "Ostrac.-", + "Stromat.-", + "u.-knötchen", + "U.-Camp.", + "Kalkmergelst.-Gerölle", + "Pfl.-Laden", + "Pfl.-Häcksel", + "ca.-Angabe,", + "Z.-", + "Hgd.-Schiefer", + "Sdst.-Fame", + "Orig.-Schi", + "Mergels.-", + "Kst.-", + "Steink.-G", + "Steink.-", + "Sst.-", + "bzw.-anfang", + "nd.-er", + "u.-knäuel", + "u.-konk", + "u.-knoten", + "ng.-Bür", + "Ton.-", + "org.-", + "FS.-", + "dkl.-", + "Schluff.-", + "Erw.-", + "Abl.-", + "abl.-", + "Sch.-", + "alsU.-", + "Plänerkst.-", + "Süßw.-", + "KV.-", + "duchläss.-", + "Verwitt.-", + "durchlass.-", + "San.-", + "Unterkr.-", + "grünl.-", + "Stringocephal.-", + "Zinkbl.-", + "Amphip.-", + "Tonst.-", + "Öffn.-", + "Trennflä.-", + "Randkalku.-dolomit", + 'keineAngaben,Bemerkung:nachOrig.-SV:"Lehm",', + ] # Replace phrases for i in phrases: - txt = txt.replace(i, '') + txt = txt.replace(i, "") # Replace Symbols for a, b in symbols: if a in txt: txt = txt.replace(a, b) - if 'TiefeBeschreibungStratigraphie' in txt: + if "TiefeBeschreibungStratigraphie" in txt: # Every line ends with a '.' and every new line starts with '-', # the string will be separated there, the result is that every line of stratigraphy will be one string now @@ -752,27 +866,29 @@ def get_stratigraphic_data(text: list, # else: # txt = txt.split('TiefeBeschreibungStratigraphie..-')[1] - txt = txt.split('TiefeBeschreibungStratigraphie..-')[1] + txt = txt.split("TiefeBeschreibungStratigraphie..-")[1] except IndexError: # Create data - data = [well_name, - float(well_depth), - float(well_coord_x), - float(well_coord_y), - float(well_coord_z), - depth, - strings, - subs, - form] + data = [ + well_name, + float(well_depth), + float(well_coord_x), + float(well_coord_y), + float(well_coord_z), + depth, + strings, + subs, + form, + ] return data # Join txt - txt = ''.join(txt) + txt = "".join(txt) # Split text at .- - txt = txt.split('.-') + txt = txt.split(".-") # For loop over every string that contains layer information for a in range(len(txt)): @@ -781,35 +897,35 @@ def get_stratigraphic_data(text: list, break else: # Every string is combined to a sequence of characters - string = ''.join(txt[a]) - if string not in (None, ''): + string = "".join(txt[a]) + if string not in (None, ""): try: # The depth information is extracted from the string - depth.append(float(string.split('m', 1)[0])) + depth.append(float(string.split("m", 1)[0])) # The depth information is cut off from the string and # only the lithologies and stratigraphy is kept - string = string.split('m', 1)[1] + string = string.split("m", 1)[1] # Remove all numbers from string (e.g. von 10m bis 20m) - string = ''.join(f for f in string if not f.isdigit()) + string = "".join(f for f in string if not f.isdigit()) except ValueError: pass else: pass # Removing symbols from string - string = string.replace(':', '') - string = string.replace('-', '') - string = string.replace('.', '') - string = string.replace(',', '') - string = string.replace('?', '') - string = string.replace('/', '') + string = string.replace(":", "") + string = string.replace("-", "") + string = string.replace(".", "") + string = string.replace(",", "") + string = string.replace("?", "") + string = string.replace("/", "") # Replace PDF-formation with formation name forms = string for q, r in formations: if "..---.m" not in forms: - if 'keineAngaben' in forms: - formation = 'NichtEingestuft' + if "keineAngaben" in forms: + formation = "NichtEingestuft" elif q in forms: new_string = forms.split(q, 1) forma = forms.split(new_string[0], 1)[1] @@ -822,25 +938,29 @@ def get_stratigraphic_data(text: list, form.append(formation) # Create Data - data = [well_name, - float(well_depth), - float(well_coord_x), - float(well_coord_y), - float(well_coord_z), - depth, - strings, - subs, - form] + data = [ + well_name, + float(well_depth), + float(well_coord_x), + float(well_coord_y), + float(well_coord_z), + depth, + strings, + subs, + form, + ] return data -def get_stratigraphic_data_df(data: str, - name: str, - symbols: List[Tuple[str, str]], - formations: List[Tuple[str, str]], - remove_last: bool = False, - return_gdf: bool = True) -> Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame]: +def get_stratigraphic_data_df( + data: str, + name: str, + symbols: List[Tuple[str, str]], + formations: List[Tuple[str, str]], + remove_last: bool = False, + return_gdf: bool = True, +) -> Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame]: """Function to create a dataframe with coordinates and the stratigraphy of the different boreholes Parameters @@ -848,7 +968,7 @@ def get_stratigraphic_data_df(data: str, data : list List containing the strings of the borehole log - + name : str Name for index reference, e.g. ``name='GD'`` @@ -909,36 +1029,36 @@ def get_stratigraphic_data_df(data: str, # Checking that the data is provided as string if not isinstance(data, str): - raise TypeError('Data must be provided as string') + raise TypeError("Data must be provided as string") # Checking that the name of the index is provided as string if not isinstance(name, str): - raise TypeError('Index name must be provided as string') + raise TypeError("Index name must be provided as string") # Checking that the symbols are provided as list if not isinstance(symbols, list): - raise TypeError('Symbols must be provided as list of tuples of strings') + raise TypeError("Symbols must be provided as list of tuples of strings") # Checking that the formations are provided as list if not isinstance(formations, list): - raise TypeError('Formations must be provided as list of tuples of strings') + raise TypeError("Formations must be provided as list of tuples of strings") # Checking that the remove_last variable is of type bool if not isinstance(remove_last, bool): - raise TypeError('Remove_last variable must be of type bool') + raise TypeError("Remove_last variable must be of type bool") # Checking that the return_gdf variable is of type bool if not isinstance(return_gdf, bool): - raise TypeError('Return_gdf variable must be of type bool') + raise TypeError("Return_gdf variable must be of type bool") # Splitting the entire string into a list data = data.split() # Join all elements of list/all pages of the borehole logs and separate with # - data = '#'.join(data) + data = "#".join(data) # Split entire string at each new page into separate elements of a list - data = data.split('-#Stammdaten') + data = data.split("-#Stammdaten") # Cut off the last part of each element, this is not done for each page # Segment to filter out stratigraphic tables that have multiple versions and are on multiple pages @@ -951,137 +1071,217 @@ def get_stratigraphic_data_df(data: str, # else: # data = [item.split('|Geologischer#Dienst#NRW#')[0] for item in data] - data = [item.split('|Geologischer#Dienst#NRW#')[0] for item in data] + data = [item.split("|Geologischer#Dienst#NRW#")[0] for item in data] # Remove last part of each page if log stretches over multiple pages - data = [re.sub(r'Geologischer#Dienst#NRW#\d\d.\d\d.\d\d\d\d-#\d+#-#', '#', item) for item in data] - data = [re.sub(r'Geologischer#Dienst#NRW#\d\d.\d\d.\d\d\d\d-#\d+#-', '#', item) for item in data] + data = [ + re.sub(r"Geologischer#Dienst#NRW#\d\d.\d\d.\d\d\d\d-#\d+#-#", "#", item) + for item in data + ] + data = [ + re.sub(r"Geologischer#Dienst#NRW#\d\d.\d\d.\d\d\d\d-#\d+#-", "#", item) + for item in data + ] # Connect different parts of each element - data = [''.join(item) for item in data] + data = ["".join(item) for item in data] # Split each element at # - data = [item.split('#') for item in data] + data = [item.split("#") for item in data] # Filter out wells without Stratigraphic Column - data = [item for item in data if 'Beschreibung' in item] + data = [item for item in data if "Beschreibung" in item] # Create empty list for indices index = [] # Get stratigraphic data for each well - stratigraphy = [get_stratigraphic_data(text=item, - symbols=symbols, - formations=formations) for item in data] + stratigraphy = [ + get_stratigraphic_data(text=item, symbols=symbols, formations=formations) + for item in data + ] # Create DataFrame from list of stratigraphic data stratigraphy = pd.DataFrame(data=stratigraphy) # Create DataFrame for index for i in range(len(stratigraphy)): - index = np.append(index, [str(name + '{0:04}'.format(i + 1))]) + index = np.append(index, [str(name + "{0:04}".format(i + 1))]) index = pd.DataFrame(index) # Concatenate DataFrames stratigraphy_dataframe_new = pd.concat([stratigraphy, index], axis=1) # Label DataFrame Columns - stratigraphy_dataframe_new.columns = ['Name', 'Depth', 'X', 'Y', 'Altitude', 'Z', 'PDF-Formation', 'Subformation', - 'formation', 'Index'] + stratigraphy_dataframe_new.columns = [ + "Name", + "Depth", + "X", + "Y", + "Altitude", + "Z", + "PDF-Formation", + "Subformation", + "formation", + "Index", + ] # Select Columns stratigraphy_dataframe_new = stratigraphy_dataframe_new[ - ['Index', 'Name', 'X', 'Y', 'Z', 'Depth', 'Altitude', 'PDF-Formation', 'Subformation', 'formation']] + [ + "Index", + "Name", + "X", + "Y", + "Z", + "Depth", + "Altitude", + "PDF-Formation", + "Subformation", + "formation", + ] + ] # Adjust data - strati_depth = stratigraphy_dataframe_new[['Index', 'Z']] - lst_col1 = 'Z' - depth = pd.DataFrame({ - col: np.repeat(strati_depth['Index'].values, strati_depth[lst_col1].str.len()) - for col in strati_depth.columns.drop(lst_col1)} - ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[strati_depth.columns] - - strati_depth = stratigraphy_dataframe_new[['Name', 'Z']] - lst_col1 = 'Z' - names = pd.DataFrame({ - col: np.repeat(strati_depth['Name'].values, strati_depth[lst_col1].str.len()) - for col in strati_depth.columns.drop(lst_col1)} - ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[strati_depth.columns] - - strati_depth = stratigraphy_dataframe_new[['X', 'Z']] - lst_col1 = 'Z' - x_coord = pd.DataFrame({ - col: np.repeat(strati_depth['X'].values, strati_depth[lst_col1].str.len()) - for col in strati_depth.columns.drop(lst_col1)} - ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[strati_depth.columns] - - strati_depth = stratigraphy_dataframe_new[['Y', 'Z']] - lst_col1 = 'Z' - y_coord = pd.DataFrame({ - col: np.repeat(strati_depth['Y'].values, strati_depth[lst_col1].str.len()) - for col in strati_depth.columns.drop(lst_col1)} - ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[strati_depth.columns] - - strati_depth = stratigraphy_dataframe_new[['Altitude', 'Z']] - lst_col1 = 'Z' - altitude = pd.DataFrame({ - col: np.repeat(strati_depth['Altitude'].values, strati_depth[lst_col1].str.len()) - for col in strati_depth.columns.drop(lst_col1)} - ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[strati_depth.columns] - - strati_depth = stratigraphy_dataframe_new[['Depth', 'Z']] - lst_col1 = 'Z' - welldepth = pd.DataFrame({ - col: np.repeat(strati_depth['Depth'].values, strati_depth[lst_col1].str.len()) - for col in strati_depth.columns.drop(lst_col1)} - ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[strati_depth.columns] - - strati_formation = stratigraphy_dataframe_new[['Index', 'formation']] - lst_col4 = 'formation' - formation = pd.DataFrame({ - col: np.repeat(strati_formation['Index'].values, strati_formation[lst_col4].str.len()) - for col in strati_formation.columns.drop(lst_col4)} - ).assign(**{lst_col4: np.concatenate(strati_formation[lst_col4].values)})[strati_formation.columns] + strati_depth = stratigraphy_dataframe_new[["Index", "Z"]] + lst_col1 = "Z" + depth = pd.DataFrame( + { + col: np.repeat( + strati_depth["Index"].values, strati_depth[lst_col1].str.len() + ) + for col in strati_depth.columns.drop(lst_col1) + } + ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[ + strati_depth.columns + ] + + strati_depth = stratigraphy_dataframe_new[["Name", "Z"]] + lst_col1 = "Z" + names = pd.DataFrame( + { + col: np.repeat( + strati_depth["Name"].values, strati_depth[lst_col1].str.len() + ) + for col in strati_depth.columns.drop(lst_col1) + } + ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[ + strati_depth.columns + ] + + strati_depth = stratigraphy_dataframe_new[["X", "Z"]] + lst_col1 = "Z" + x_coord = pd.DataFrame( + { + col: np.repeat(strati_depth["X"].values, strati_depth[lst_col1].str.len()) + for col in strati_depth.columns.drop(lst_col1) + } + ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[ + strati_depth.columns + ] + + strati_depth = stratigraphy_dataframe_new[["Y", "Z"]] + lst_col1 = "Z" + y_coord = pd.DataFrame( + { + col: np.repeat(strati_depth["Y"].values, strati_depth[lst_col1].str.len()) + for col in strati_depth.columns.drop(lst_col1) + } + ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[ + strati_depth.columns + ] + + strati_depth = stratigraphy_dataframe_new[["Altitude", "Z"]] + lst_col1 = "Z" + altitude = pd.DataFrame( + { + col: np.repeat( + strati_depth["Altitude"].values, strati_depth[lst_col1].str.len() + ) + for col in strati_depth.columns.drop(lst_col1) + } + ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[ + strati_depth.columns + ] + + strati_depth = stratigraphy_dataframe_new[["Depth", "Z"]] + lst_col1 = "Z" + welldepth = pd.DataFrame( + { + col: np.repeat( + strati_depth["Depth"].values, strati_depth[lst_col1].str.len() + ) + for col in strati_depth.columns.drop(lst_col1) + } + ).assign(**{lst_col1: np.concatenate(strati_depth[lst_col1].values)})[ + strati_depth.columns + ] + + strati_formation = stratigraphy_dataframe_new[["Index", "formation"]] + lst_col4 = "formation" + formation = pd.DataFrame( + { + col: np.repeat( + strati_formation["Index"].values, strati_formation[lst_col4].str.len() + ) + for col in strati_formation.columns.drop(lst_col4) + } + ).assign(**{lst_col4: np.concatenate(strati_formation[lst_col4].values)})[ + strati_formation.columns + ] # Create DataFrame - strat = pd.concat([names, x_coord, y_coord, depth, altitude, welldepth, formation], - axis=1) + strat = pd.concat( + [names, x_coord, y_coord, depth, altitude, welldepth, formation], axis=1 + ) # Name Columns of DataFrame - strat = strat[['Index', 'Name', 'X', 'Y', 'Z', 'Altitude', 'Depth', 'formation']] + strat = strat[["Index", "Name", "X", "Y", "Z", "Altitude", "Depth", "formation"]] # Delete Duplicated columns (Index) strat = strat.loc[:, ~strat.columns.duplicated()] # Rename columns of Data Frame - strat.columns = ['Index', 'Name', 'X', 'Y', 'DepthLayer', 'Altitude', 'Depth', - 'formation'] + strat.columns = [ + "Index", + "Name", + "X", + "Y", + "DepthLayer", + "Altitude", + "Depth", + "formation", + ] # Create Depth Column Usable for GemPy - strat['Z'] = strat['Altitude'] - strat['DepthLayer'] + strat["Z"] = strat["Altitude"] - strat["DepthLayer"] # Reorder Columns of DataFrame - strat = strat[['Index', 'Name', 'X', 'Y', 'Z', 'Altitude', 'Depth', 'formation']] + strat = strat[["Index", "Name", "X", "Y", "Z", "Altitude", "Depth", "formation"]] # Delete Last - strat = strat.groupby(['Index', 'formation']).last().sort_values(by=['Index', 'Z'], - ascending=[True, False]).reset_index() + strat = ( + strat.groupby(["Index", "formation"]) + .last() + .sort_values(by=["Index", "Z"], ascending=[True, False]) + .reset_index() + ) # Selecting Data - strat = strat[['Index', 'Name', 'X', 'Y', 'Z', 'Altitude', 'Depth', 'formation']] + strat = strat[["Index", "Name", "X", "Y", "Z", "Altitude", "Depth", "formation"]] # Remove unusable entries - strat = strat[strat['formation'] != 'NichtEingestuft'] + strat = strat[strat["formation"] != "NichtEingestuft"] # Removing the last interfaces of each well since it does not represent a true interfaces if remove_last: - strat = strat[strat.groupby('Index').cumcount(ascending=False) > 0] + strat = strat[strat.groupby("Index").cumcount(ascending=False) > 0] # Convert df to gdf if return_gdf: - strat = gpd.GeoDataFrame(data=strat, - geometry=gpd.points_from_xy(x=strat.X, - y=strat.Y, - crs='EPSG:4647')) + strat = gpd.GeoDataFrame( + data=strat, + geometry=gpd.points_from_xy(x=strat.X, y=strat.Y, crs="EPSG:4647"), + ) return strat diff --git a/gemgis/postprocessing.py b/gemgis/postprocessing.py index 28bc19d2..cdfe72e1 100644 --- a/gemgis/postprocessing.py +++ b/gemgis/postprocessing.py @@ -30,9 +30,9 @@ import xml -def extract_lithologies(geo_model, - extent: list, - crs: Union[str, pyproj.crs.crs.CRS]) -> gpd.geodataframe.GeoDataFrame: +def extract_lithologies( + geo_model, extent: list, crs: Union[str, pyproj.crs.crs.CRS] +) -> gpd.geodataframe.GeoDataFrame: """Extracting the geological map as GeoDataFrame Parameters @@ -61,14 +61,15 @@ def extract_lithologies(geo_model, import matplotlib.pyplot as plt except ModuleNotFoundError: raise ModuleNotFoundError( - 'Matplotlib package is not installed. Use pip install matplotlib to install the latest version') + "Matplotlib package is not installed. Use pip install matplotlib to install the latest version" + ) - # Trying to import gempy but returning error if gempy is not installed - try: - import gempy as gp - except ModuleNotFoundError: - raise ModuleNotFoundError( - 'GemPy package is not installed. Use pip install gempy to install the latest version') + ## Trying to import gempy but returning error if gempy is not installed + # try: + # import gempy as gp + # except ModuleNotFoundError: + # raise ModuleNotFoundError( + # 'GemPy package is not installed. Use pip install gempy to install the latest version') shape = geo_model._grid.topography.values_2d[:, :, 2].shape @@ -92,8 +93,9 @@ def extract_lithologies(geo_model, fm = [] geo = [] for col, fm_name in zip( - contours.collections, - geo_model.surfaces.df.sort_values(by="order_surfaces", ascending=False).surface): + contours.collections, + geo_model.surfaces.df.sort_values(by="order_surfaces", ascending=False).surface, + ): # Loop through all polygons that have the same intensity level for contour_path in col.get_paths(): @@ -114,17 +116,17 @@ def extract_lithologies(geo_model, fm.append(fm_name) geo.append(poly) - lith = gpd.GeoDataFrame({"formation": fm}, - geometry=geo, - crs=crs) + lith = gpd.GeoDataFrame({"formation": fm}, geometry=geo, crs=crs) return lith -def extract_borehole(geo_model, #: gp.core.model.Project, - geo_data: gemgis.GemPyData, - loc: List[Union[int, float]], - **kwargs): +def extract_borehole( + geo_model, #: gp.core.model.Project, + geo_data: gemgis.GemPyData, + loc: List[Union[int, float]], + **kwargs, +): """Extracting a borehole at a provided location from a recalculated GemPy Model Parameters @@ -166,55 +168,62 @@ def extract_borehole(geo_model, #: gp.core.model.Project, from matplotlib.colors import ListedColormap except ModuleNotFoundError: raise ModuleNotFoundError( - 'Matplotlib package is not installed. Use pip install matplotlib to install the latest version') + "Matplotlib package is not installed. Use pip install matplotlib to install the latest version" + ) try: import gempy as gp except ModuleNotFoundError: raise ModuleNotFoundError( - 'GemPy package is not installed. Use pip install gempy to install the latest version') + "GemPy package is not installed. Use pip install gempy to install the latest version" + ) # Checking if geo_model is a GemPy geo_model if not isinstance(geo_model, gp.core.model.Project): - raise TypeError('geo_model must be a GemPy geo_model') + raise TypeError("geo_model must be a GemPy geo_model") # Checking if geo_data is a GemGIS GemPy Data Class if not isinstance(geo_data, gemgis.GemPyData): - raise TypeError('geo_data must be a GemPy Data object') + raise TypeError("geo_data must be a GemPy Data object") # Checking if loc is of type list if not isinstance(loc, list): - raise TypeError('Borehole location must be provided as a list of a x- and y- coordinate') + raise TypeError( + "Borehole location must be provided as a list of a x- and y- coordinate" + ) # Checking if elements of loc are of type int or float if not all(isinstance(n, (int, float)) for n in loc): - raise TypeError('Location values must be provided as integers or floats') + raise TypeError("Location values must be provided as integers or floats") # Selecting DataFrame columns and create deep copy of DataFrame - orientations_df = geo_model.orientations.df[['X', 'Y', 'Z', 'surface', 'dip', 'azimuth', 'polarity']].copy( - deep=True) + orientations_df = geo_model.orientations.df[ + ["X", "Y", "Z", "surface", "dip", "azimuth", "polarity"] + ].copy(deep=True) - interfaces_df = geo_model.surface_points.df[['X', 'Y', 'Z', 'surface']].copy(deep=True) + interfaces_df = geo_model.surface_points.df[["X", "Y", "Z", "surface"]].copy( + deep=True + ) # Creating formation column - orientations_df['formation'] = orientations_df['surface'] - interfaces_df['formation'] = interfaces_df['surface'] + orientations_df["formation"] = orientations_df["surface"] + interfaces_df["formation"] = interfaces_df["surface"] # Deleting surface column - del orientations_df['surface'] - del interfaces_df['surface'] + del orientations_df["surface"] + del interfaces_df["surface"] # Getting maximum depth and resolution - zmax = kwargs.get('zmax', geo_model.grid.regular_grid.extent[5]) - res = kwargs.get('res', geo_model.grid.regular_grid.resolution[2]) + zmax = kwargs.get("zmax", geo_model.grid.regular_grid.extent[5]) + res = kwargs.get("res", geo_model.grid.regular_grid.resolution[2]) # Checking if zmax is of type int or float if not isinstance(zmax, (int, float)): - raise TypeError('Maximum depth must be of type int or float') + raise TypeError("Maximum depth must be of type int or float") # Checking if res is of type int if not isinstance(res, (int, float, np.int32)): - raise TypeError('Resolution must be of type int') + raise TypeError("Resolution must be of type int") # Creating variable for maximum depth z = geo_model.grid.regular_grid.extent[5] - zmax @@ -223,118 +232,189 @@ def extract_borehole(geo_model, #: gp.core.model.Project, # sys.stdout = open(os.devnull, 'w') # Create GemPy Model - well_model = gp.create_model('Well_Model') + well_model = gp.create_model("Well_Model") # Initiate Data for GemPy Model - gp.init_data(well_model, - extent=[loc[0] - 5, loc[0] + 5, loc[1] - 5, loc[1] + 5, geo_model.grid.regular_grid.extent[4], - geo_model.grid.regular_grid.extent[5] - z], - resolution=[5, 5, res], - orientations_df=orientations_df.dropna(), - surface_points_df=interfaces_df.dropna(), - default_values=False) + gp.init_data( + well_model, + extent=[ + loc[0] - 5, + loc[0] + 5, + loc[1] - 5, + loc[1] + 5, + geo_model.grid.regular_grid.extent[4], + geo_model.grid.regular_grid.extent[5] - z, + ], + resolution=[5, 5, res], + orientations_df=orientations_df.dropna(), + surface_points_df=interfaces_df.dropna(), + default_values=False, + ) # Map Stack to surfaces - gp.map_stack_to_surfaces(well_model, - geo_data.stack, - remove_unused_series=True) + gp.map_stack_to_surfaces(well_model, geo_data.stack, remove_unused_series=True) # Add Basement surface - well_model.add_surfaces('basement') + well_model.add_surfaces("basement") # Change colors of surfaces well_model.surfaces.colors.change_colors(geo_data.surface_colors) # Set Interpolator - gp.set_interpolator(well_model, - compile_theano=True, - theano_optimizer='fast_run', dtype='float64', - update_kriging=False, - verbose=[]) + gp.set_interpolator( + well_model, + compile_theano=True, + theano_optimizer="fast_run", + dtype="float64", + update_kriging=False, + verbose=[], + ) # Set faults active - for i in geo_model.surfaces.df[geo_model.surfaces.df['isFault'] == True]['surface'].values.tolist(): + for i in geo_model.surfaces.df[geo_model.surfaces.df["isFault"] == True][ + "surface" + ].values.tolist(): well_model.set_is_fault([i]) # Compute Model sol = gp.compute_model(well_model, compute_mesh=False) # Reshape lith_block - well = sol.lith_block.reshape(well_model.grid.regular_grid.resolution[0], - well_model.grid.regular_grid.resolution[1], - well_model.grid.regular_grid.resolution[2]) + well = sol.lith_block.reshape( + well_model.grid.regular_grid.resolution[0], + well_model.grid.regular_grid.resolution[1], + well_model.grid.regular_grid.resolution[2], + ) # Select colors for plotting color_dict = well_model.surfaces.colors.colordict surface = well_model.surfaces.df.copy(deep=True) - surfaces = surface[~surface['id'].isin(np.unique(np.round(sol.lith_block)))] - for key in surfaces['surface'].values.tolist(): + surfaces = surface[~surface["id"].isin(np.unique(np.round(sol.lith_block)))] + for key in surfaces["surface"].values.tolist(): color_dict.pop(key) cols = list(color_dict.values()) # Calculate boundaries boundaries = np.where(np.round(well.T[:, 1])[:-1] != np.round(well.T[:, 1])[1:])[0][ - ::well_model.grid.regular_grid.resolution[0]] + :: well_model.grid.regular_grid.resolution[0] + ] # Create Plot plt.figure(figsize=(3, 10)) - plt.imshow(np.rot90(np.round(well.T[:, 1]), 2), - cmap=ListedColormap(cols), - extent=(0, - (well_model.grid.regular_grid.extent[5] - well_model.grid.regular_grid.extent[4]) / 8, - well_model.grid.regular_grid.extent[4], - well_model.grid.regular_grid.extent[5]), - ) + plt.imshow( + np.rot90(np.round(well.T[:, 1]), 2), + cmap=ListedColormap(cols), + extent=( + 0, + ( + well_model.grid.regular_grid.extent[5] + - well_model.grid.regular_grid.extent[4] + ) + / 8, + well_model.grid.regular_grid.extent[4], + well_model.grid.regular_grid.extent[5], + ), + ) list_values = np.unique(np.round(well.T[:, 1])[:, 0]).tolist() # Display depths of layer boundaries for i in boundaries: - plt.text((well_model.grid.regular_grid.extent[5] - well_model.grid.regular_grid.extent[4]) / 7, - i * geo_model.grid.regular_grid.dz + geo_model.grid.regular_grid.extent[ - 4] + geo_model.grid.regular_grid.dz, - '%d m' % (i * geo_model.grid.regular_grid.dz + geo_model.grid.regular_grid.extent[4]), fontsize=14) + plt.text( + ( + well_model.grid.regular_grid.extent[5] + - well_model.grid.regular_grid.extent[4] + ) + / 7, + i * geo_model.grid.regular_grid.dz + + geo_model.grid.regular_grid.extent[4] + + geo_model.grid.regular_grid.dz, + "%d m" + % ( + i * geo_model.grid.regular_grid.dz + + geo_model.grid.regular_grid.extent[4] + ), + fontsize=14, + ) del list_values[list_values.index(np.round(well.T[:, 1])[:, 0][i + 1])] # Plot last depth - plt.text((well_model.grid.regular_grid.extent[5] - well_model.grid.regular_grid.extent[4]) / 7, - geo_model.grid.regular_grid.extent[4] + geo_model.grid.regular_grid.dz, - '%d m' % (geo_model.grid.regular_grid.extent[4]), fontsize=14) + plt.text( + ( + well_model.grid.regular_grid.extent[5] + - well_model.grid.regular_grid.extent[4] + ) + / 7, + geo_model.grid.regular_grid.extent[4] + geo_model.grid.regular_grid.dz, + "%d m" % (geo_model.grid.regular_grid.extent[4]), + fontsize=14, + ) list_values = np.unique(np.round(well.T[:, 1])[:, 0]).tolist() # Display lithology IDs for i in boundaries: - plt.text((well_model.grid.regular_grid.extent[5] - well_model.grid.regular_grid.extent[4]) / 24, - i * geo_model.grid.regular_grid.dz + geo_model.grid.regular_grid.extent[ - 4] + 2 * geo_model.grid.regular_grid.dz, - 'ID: %d' % (np.round(well.T[:, 1])[:, 0][i + 1]), fontsize=14) + plt.text( + ( + well_model.grid.regular_grid.extent[5] + - well_model.grid.regular_grid.extent[4] + ) + / 24, + i * geo_model.grid.regular_grid.dz + + geo_model.grid.regular_grid.extent[4] + + 2 * geo_model.grid.regular_grid.dz, + "ID: %d" % (np.round(well.T[:, 1])[:, 0][i + 1]), + fontsize=14, + ) del list_values[list_values.index(np.round(well.T[:, 1])[:, 0][i + 1])] # Plot last ID - plt.text((well_model.grid.regular_grid.extent[5] - well_model.grid.regular_grid.extent[4]) / 24, - geo_model.grid.regular_grid.extent[4] + 1 * geo_model.grid.regular_grid.dz, 'ID: %d' % (list_values[0]), - fontsize=14) + plt.text( + ( + well_model.grid.regular_grid.extent[5] + - well_model.grid.regular_grid.extent[4] + ) + / 24, + geo_model.grid.regular_grid.extent[4] + 1 * geo_model.grid.regular_grid.dz, + "ID: %d" % (list_values[0]), + fontsize=14, + ) # Set legend handles patches = [ - mpatches.Patch(color=cols[i], label="{formation}".format( - formation=surface[surface['id'].isin(np.unique(np.round(sol.lith_block)))].surface.to_list()[i])) - for i in range(len(surface[surface['id'].isin(np.unique(np.round(sol.lith_block)))].surface.to_list()))] + mpatches.Patch( + color=cols[i], + label="{formation}".format( + formation=surface[ + surface["id"].isin(np.unique(np.round(sol.lith_block))) + ].surface.to_list()[i] + ), + ) + for i in range( + len( + surface[ + surface["id"].isin(np.unique(np.round(sol.lith_block))) + ].surface.to_list() + ) + ) + ] # Remove xticks - plt.tick_params(axis='x', labelsize=0, length=0) + plt.tick_params(axis="x", labelsize=0, length=0) # Set ylabel - plt.ylabel('Depth [m]') + plt.ylabel("Depth [m]") # Set legend plt.legend(handles=patches, bbox_to_anchor=(3, 1)) # Create depth dict - depth_dict = {int(np.round(well.T[:, 1])[:, 0][i + 1]): i * geo_model.grid.regular_grid.dz + - geo_model.grid.regular_grid.extent[4] for i in boundaries} + depth_dict = { + int(np.round(well.T[:, 1])[:, 0][i + 1]): i * geo_model.grid.regular_grid.dz + + geo_model.grid.regular_grid.extent[4] + for i in boundaries + } depth_dict[int(list_values[0])] = geo_model.grid.regular_grid.extent[4] depth_dict = dict(sorted(depth_dict.items())) @@ -342,7 +422,7 @@ def extract_borehole(geo_model, #: gp.core.model.Project, def save_model(geo_model, path): - """ Function to save the model parameters to files + """Function to save the model parameters to files Parameters ___________ @@ -363,15 +443,16 @@ def save_model(geo_model, path): import gempy as gp except ModuleNotFoundError: raise ModuleNotFoundError( - 'GemPy package is not installed. Use pip install gempy to install the latest version') + "GemPy package is not installed. Use pip install gempy to install the latest version" + ) # Checking if the geo_model is a GemPy Geo Model if not isinstance(geo_model, gp.core.model.Project): - raise TypeError('Geo Model must be a GemPy Geo Model') + raise TypeError("Geo Model must be a GemPy Geo Model") # Checking if the path is of type string if not isinstance(path, str): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") project_name = open(path + "01_project_name.txt", "w") project_name.write(geo_model.meta.project_name) @@ -381,8 +462,9 @@ def save_model(geo_model, path): np.save(path + "03_resolution.npy", geo_model.grid.regular_grid.resolution) -def extract_orientations_from_mesh(mesh: pv.core.pointset.PolyData, - crs: Union[str, pyproj.crs.crs.CRS]) -> gpd.geodataframe.GeoDataFrame: +def extract_orientations_from_mesh( + mesh: pv.core.pointset.PolyData, crs: Union[str, pyproj.crs.crs.CRS] +) -> gpd.geodataframe.GeoDataFrame: """Extracting orientations (dip and azimuth) from PyVista Mesh Parameters @@ -406,43 +488,51 @@ def extract_orientations_from_mesh(mesh: pv.core.pointset.PolyData, # Checking that the provided mesh is of type Polydata if not isinstance(mesh, pv.core.pointset.PolyData): - raise TypeError('Mesh must be provided as PyVista Polydata') + raise TypeError("Mesh must be provided as PyVista Polydata") # Checking that the provided mesh if of type string or a pyproj CRS object if not isinstance(crs, (str, pyproj.crs.crs.CRS)): - raise TypeError('CRS must be provided as string or pyproj CRS object') + raise TypeError("CRS must be provided as string or pyproj CRS object") # Computing the normals of the mesh mesh_normals = mesh.compute_normals() # Calculating the dips - dips = [90 - np.rad2deg(-np.arcsin(mesh_normals['Normals'][i][2])) * (-1) for i in - range(len(mesh_normals['Normals']))] + dips = [ + 90 - np.rad2deg(-np.arcsin(mesh_normals["Normals"][i][2])) * (-1) + for i in range(len(mesh_normals["Normals"])) + ] # Calculating the azimuths - azimuths = [np.rad2deg(np.arctan(mesh_normals['Normals'][i][0] / mesh_normals['Normals'][i][1])) + 180 for i in - range(len(mesh_normals['Normals']))] + azimuths = [ + np.rad2deg( + np.arctan(mesh_normals["Normals"][i][0] / mesh_normals["Normals"][i][1]) + ) + + 180 + for i in range(len(mesh_normals["Normals"])) + ] # Getting cell centers points_z = [geometry.Point(point) for point in mesh.cell_centers().points] # Creating GeoDataFrame - gdf_orientations = gpd.GeoDataFrame(geometry=points_z, - crs=crs) + gdf_orientations = gpd.GeoDataFrame(geometry=points_z, crs=crs) # Appending X, Y, Z Locations - gdf_orientations['X'] = mesh.cell_centers().points[:, 0] - gdf_orientations['Y'] = mesh.cell_centers().points[:, 1] - gdf_orientations['Z'] = mesh.cell_centers().points[:, 2] + gdf_orientations["X"] = mesh.cell_centers().points[:, 0] + gdf_orientations["Y"] = mesh.cell_centers().points[:, 1] + gdf_orientations["Z"] = mesh.cell_centers().points[:, 2] # Appending dips and azimuths - gdf_orientations['dip'] = dips - gdf_orientations['azimuth'] = azimuths + gdf_orientations["dip"] = dips + gdf_orientations["azimuth"] = azimuths return gdf_orientations -def calculate_dip_and_azimuth_from_mesh(mesh: pv.core.pointset.PolyData) -> pv.core.pointset.PolyData: +def calculate_dip_and_azimuth_from_mesh( + mesh: pv.core.pointset.PolyData, +) -> pv.core.pointset.PolyData: """Calculating dip and azimuth values for a mesh and setting them as scalars for subsequent plotting Parameters @@ -463,22 +553,29 @@ def calculate_dip_and_azimuth_from_mesh(mesh: pv.core.pointset.PolyData) -> pv.c # Checking that the provided mesh is of type Polydata if not isinstance(mesh, pv.core.pointset.PolyData): - raise TypeError('Mesh must be provided as PyVista Polydata') + raise TypeError("Mesh must be provided as PyVista Polydata") # Computing the normals of the mesh mesh.compute_normals(inplace=True) # Calculating the dips - dips = [90 - np.rad2deg(-np.arcsin(mesh['Normals'][i][2])) * (-1) for i in - range(len(mesh['Normals']))] + dips = [ + 90 - np.rad2deg(-np.arcsin(mesh["Normals"][i][2])) * (-1) + for i in range(len(mesh["Normals"])) + ] # Calculating the azimuths - azimuths = [np.rad2deg(np.arctan(mesh['Normals'][i][0] / mesh['Normals'][i][1])) + 180 for i in - range(len(mesh['Normals']))] + azimuths = [ + np.rad2deg(np.arctan2(mesh["Normals"][i][0], mesh["Normals"][i][1])) + for i in range(len(mesh["Normals"])) + ] + + # Shifting values + azimuths[azimuths < 0] += 360 # Assigning dips and azimuths to scalars - mesh['Dips [°]'] = dips - mesh['Azimuths [°]'] = azimuths + mesh["Dips [°]"] = dips + mesh["Azimuths [°]"] = azimuths return mesh @@ -502,41 +599,54 @@ def crop_block_to_topography(geo_model) -> pv.core.pointset.UnstructuredGrid: """ # Trying to import GemPy - try: - import gempy as gp - except ModuleNotFoundError: - raise ModuleNotFoundError( - 'GemPy package is not installed. Use pip install gempy to install the latest version') + # try: + # import gempy as gp + # except ModuleNotFoundError: + # raise ModuleNotFoundError( + # 'GemPy package is not installed. Use pip install gempy to install the latest version') # Trying to import PVGeo try: from PVGeo.grids import ExtractTopography except ModuleNotFoundError: raise ModuleNotFoundError( - 'PVGeo package is not installed. Use pip install pvgeo to install the lastest version') + "PVGeo package is not installed. Use pip install pvgeo to install the lastest version" + ) # Creating StructuredGrid grid = pv.UniformGrid() # Setting Grid Dimensions - grid.dimensions = np.array(geo_model.solutions.lith_block.reshape(geo_model.grid.regular_grid.resolution).shape) + 1 + grid.dimensions = ( + np.array( + geo_model.solutions.lith_block.reshape( + geo_model.grid.regular_grid.resolution + ).shape + ) + + 1 + ) # Setting Grid Origin - grid.origin = (geo_model.grid.regular_grid.extent[0], - geo_model.grid.regular_grid.extent[2], - geo_model.grid.regular_grid.extent[4]) + grid.origin = ( + geo_model.grid.regular_grid.extent[0], + geo_model.grid.regular_grid.extent[2], + geo_model.grid.regular_grid.extent[4], + ) # Setting Grid Spacing - grid.spacing = ((geo_model.grid.regular_grid.extent[1] - geo_model.grid.regular_grid.extent[0]) / - geo_model.grid.regular_grid.resolution[0], - (geo_model.grid.regular_grid.extent[3] - geo_model.grid.regular_grid.extent[2]) / - geo_model.grid.regular_grid.resolution[1], - (geo_model.grid.regular_grid.extent[5] - geo_model.grid.regular_grid.extent[4]) / - geo_model.grid.regular_grid.resolution[2]) + grid.spacing = ( + (geo_model.grid.regular_grid.extent[1] - geo_model.grid.regular_grid.extent[0]) + / geo_model.grid.regular_grid.resolution[0], + (geo_model.grid.regular_grid.extent[3] - geo_model.grid.regular_grid.extent[2]) + / geo_model.grid.regular_grid.resolution[1], + (geo_model.grid.regular_grid.extent[5] - geo_model.grid.regular_grid.extent[4]) + / geo_model.grid.regular_grid.resolution[2], + ) # Setting Cell Data - grid.cell_data['values'] = geo_model.solutions.lith_block.reshape(geo_model.grid.regular_grid.resolution).flatten( - order='F') + grid.cell_data["values"] = geo_model.solutions.lith_block.reshape( + geo_model.grid.regular_grid.resolution + ).flatten(order="F") # Creating Polydata Dataset topo = pv.PolyData(geo_model._grid.topography.values) @@ -544,14 +654,12 @@ def crop_block_to_topography(geo_model) -> pv.core.pointset.UnstructuredGrid: # Interpolating topography topo.delaunay_2d(inplace=True) - extracted = ExtractTopography(tolerance=5, - remove=True).apply(grid, topo) + extracted = ExtractTopography(tolerance=5, remove=True).apply(grid, topo) return extracted -def create_attributes(keys: list, - values: list) -> list: +def create_attributes(keys: list, values: list) -> list: """Creating a list of attribute dicts @@ -576,19 +684,19 @@ def create_attributes(keys: list, # Checking that the keys are of type list if not isinstance(keys, list): - raise TypeError('keys must be provided as list') + raise TypeError("keys must be provided as list") # Checking that all elements of the keys are of type str if not all(isinstance(n, str) for n in keys): - raise TypeError('key values must be of type str') + raise TypeError("key values must be of type str") # Checking that all elements of the values are of type list if not all(isinstance(n, list) for n in values): - raise TypeError('values must be of type list') + raise TypeError("values must be of type list") # Checking that the values are provided as list if not isinstance(values, list): - raise TypeError('values must be provided as list') + raise TypeError("values must be provided as list") # Resorting the values values = [[value[i] for value in values] for i in range(len(values[0]))] @@ -599,9 +707,7 @@ def create_attributes(keys: list, return dicts -def create_subelement(parent: xml.etree.ElementTree.Element, - name: str, - attrib: dict): +def create_subelement(parent: xml.etree.ElementTree.Element, name: str, attrib: dict): """Creating Subelement Parameters @@ -624,31 +730,31 @@ def create_subelement(parent: xml.etree.ElementTree.Element, try: import xml.etree.cElementTree as ET except ModuleNotFoundError: - raise ModuleNotFoundError('xml package is not installed') + raise ModuleNotFoundError("xml package is not installed") # Checking that the parent is a XML element if not isinstance(parent, xml.etree.ElementTree.Element): - raise TypeError('The parent must a xml.etree.ElementTree.Element') + raise TypeError("The parent must a xml.etree.ElementTree.Element") # Checking that the name is of type string if not isinstance(name, str): - raise TypeError('The element name must be of type string') + raise TypeError("The element name must be of type string") # Checking that the attributes are of type dict if not isinstance(attrib, dict): - raise TypeError('The attributes must be provided as dict') + raise TypeError("The attributes must be provided as dict") # Adding the element - ET.SubElement(parent, - name, - attrib) + ET.SubElement(parent, name, attrib) -def create_symbol(parent: xml.etree.ElementTree.Element, - color: str, - symbol_text: str, - outline_width: str = '0.26', - alpha: str = '1'): +def create_symbol( + parent: xml.etree.ElementTree.Element, + color: str, + symbol_text: str, + outline_width: str = "0.26", + alpha: str = "1", +): """Creating symbol entry Parameters @@ -677,167 +783,159 @@ def create_symbol(parent: xml.etree.ElementTree.Element, try: import xml.etree.cElementTree as ET except ModuleNotFoundError: - raise ModuleNotFoundError('xml package is not installed') + raise ModuleNotFoundError("xml package is not installed") # Checking that the parent is a XML element if not isinstance(parent, xml.etree.ElementTree.Element): - raise TypeError('The parent must a xml.etree.ElementTree.Element') + raise TypeError("The parent must a xml.etree.ElementTree.Element") # Checking that the color is of type string if not isinstance(color, str): - raise TypeError('The color values must be of type string') + raise TypeError("The color values must be of type string") # Checking that the symbol_text is of type string if not isinstance(symbol_text, str): - raise TypeError('The symbol_text must be of type string') + raise TypeError("The symbol_text must be of type string") # Checking that the outline_width is of type string if not isinstance(outline_width, str): - raise TypeError('The outline_width must be of type string') + raise TypeError("The outline_width must be of type string") # Checking that the opacity value is of type string if not isinstance(alpha, str): - raise TypeError('The opacity value alpha must be of type string') + raise TypeError("The opacity value alpha must be of type string") # Creating symbol element - symbol = ET.SubElement(parent, - 'symbol', - attrib={"force_rhr": "0", - "alpha": alpha, - "is_animated": "0", - "type": "fill", - "frame_rate": "10", - "name": symbol_text, - "clip_to_extent": "1"}) - - data_defined_properties1 = ET.SubElement(symbol, - 'data_defined_properties') - - option1 = ET.SubElement(data_defined_properties1, - 'Option', - attrib={"type": 'Map'}) - - option1_1 = ET.SubElement(option1, - 'Option', - attrib={"value": '', - "type": 'QString', - "name": 'name'}) - - option1_2 = ET.SubElement(option1, - 'Option', - attrib={"name": 'properties'}) - - option1_3 = ET.SubElement(option1, - 'Option', - attrib={"value": 'collection', - "type": 'QString', - "name": 'type'}) - - layer = ET.SubElement(symbol, - 'layer', - attrib={"locked": '0', - "pass": '0', - "class": 'SimpleFill', - "enabled": '1'}) - - option2 = ET.SubElement(layer, - 'Option', - attrib={"type": 'Map'}) - - option2_1 = ET.SubElement(option2, - 'Option', - attrib={"value": '3x:0,0,0,0,0,0', - "type": 'QString', - "name": 'border_width_map_unit_scale'}) - - option2_2 = ET.SubElement(option2, - 'Option', - attrib={"value": color, - "type": 'QString', - "name": 'color'}) - - option2_3 = ET.SubElement(option2, - 'Option', - attrib={"value": 'bevel', - "type": 'QString', - "name": 'joinstyle'}) - - option2_4 = ET.SubElement(option2, - 'Option', - attrib={"value": '0,0', - "type": 'QString', - "name": 'offset'}) - - option2_5 = ET.SubElement(option2, - 'Option', - attrib={"value": '3x:0,0,0,0,0,0', - "type": 'QString', - "name": 'offset_map_unit_scale'}) - - option2_6 = ET.SubElement(option2, - 'Option', - attrib={"value": 'MM', - "type": 'QString', - "name": 'offset_unit'}) - - option2_7 = ET.SubElement(option2, - 'Option', - attrib={"value": '35,35,35,255', - "type": 'QString', - "name": 'outline_color'}) - - option2_8 = ET.SubElement(option2, - 'Option', - attrib={"value": 'solid', - "type": 'QString', - "name": 'outline_style'}) - - option2_9 = ET.SubElement(option2, - 'Option', - attrib={"value": outline_width, - "type": 'QString', - "name": 'outline_width'}) - - option2_10 = ET.SubElement(option2, - 'Option', - attrib={"value": 'MM', - "type": 'QString', - "name": 'outline_width_unit'}) - - option2_11 = ET.SubElement(option2, - 'Option', - attrib={"value": 'solid', - "type": 'QString', - "name": 'style'}) - - data_defined_properties2 = ET.SubElement(layer, - 'data_defined_properties') - - option3 = ET.SubElement(data_defined_properties2, - 'Option', - attrib={"type": 'Map'}) - - option3_1 = ET.SubElement(option3, - 'Option', attrib={"value": '', - "type": 'QString', - "name": 'name'}) - option3_2 = ET.SubElement(option3, - 'Option', - attrib={"name": 'properties'}) - - option3_3 = ET.SubElement(option3, - 'Option', - attrib={"value": 'collection', - "type": 'QString', - "name": 'type'}) - - -def save_qgis_qml_file(gdf: gpd.geodataframe.GeoDataFrame, - value: str = 'formation', - color: str = 'color', - outline_width: Union[int, float] = 0.26, - alpha: Union[int, float] = 1, - path: str = ''): + symbol = ET.SubElement( + parent, + "symbol", + attrib={ + "force_rhr": "0", + "alpha": alpha, + "is_animated": "0", + "type": "fill", + "frame_rate": "10", + "name": symbol_text, + "clip_to_extent": "1", + }, + ) + + data_defined_properties1 = ET.SubElement(symbol, "data_defined_properties") + + option1 = ET.SubElement(data_defined_properties1, "Option", attrib={"type": "Map"}) + + option1_1 = ET.SubElement( + option1, "Option", attrib={"value": "", "type": "QString", "name": "name"} + ) + + option1_2 = ET.SubElement(option1, "Option", attrib={"name": "properties"}) + + option1_3 = ET.SubElement( + option1, + "Option", + attrib={"value": "collection", "type": "QString", "name": "type"}, + ) + + layer = ET.SubElement( + symbol, + "layer", + attrib={"locked": "0", "pass": "0", "class": "SimpleFill", "enabled": "1"}, + ) + + option2 = ET.SubElement(layer, "Option", attrib={"type": "Map"}) + + option2_1 = ET.SubElement( + option2, + "Option", + attrib={ + "value": "3x:0,0,0,0,0,0", + "type": "QString", + "name": "border_width_map_unit_scale", + }, + ) + + option2_2 = ET.SubElement( + option2, "Option", attrib={"value": color, "type": "QString", "name": "color"} + ) + + option2_3 = ET.SubElement( + option2, + "Option", + attrib={"value": "bevel", "type": "QString", "name": "joinstyle"}, + ) + + option2_4 = ET.SubElement( + option2, "Option", attrib={"value": "0,0", "type": "QString", "name": "offset"} + ) + + option2_5 = ET.SubElement( + option2, + "Option", + attrib={ + "value": "3x:0,0,0,0,0,0", + "type": "QString", + "name": "offset_map_unit_scale", + }, + ) + + option2_6 = ET.SubElement( + option2, + "Option", + attrib={"value": "MM", "type": "QString", "name": "offset_unit"}, + ) + + option2_7 = ET.SubElement( + option2, + "Option", + attrib={"value": "35,35,35,255", "type": "QString", "name": "outline_color"}, + ) + + option2_8 = ET.SubElement( + option2, + "Option", + attrib={"value": "solid", "type": "QString", "name": "outline_style"}, + ) + + option2_9 = ET.SubElement( + option2, + "Option", + attrib={"value": outline_width, "type": "QString", "name": "outline_width"}, + ) + + option2_10 = ET.SubElement( + option2, + "Option", + attrib={"value": "MM", "type": "QString", "name": "outline_width_unit"}, + ) + + option2_11 = ET.SubElement( + option2, "Option", attrib={"value": "solid", "type": "QString", "name": "style"} + ) + + data_defined_properties2 = ET.SubElement(layer, "data_defined_properties") + + option3 = ET.SubElement(data_defined_properties2, "Option", attrib={"type": "Map"}) + + option3_1 = ET.SubElement( + option3, "Option", attrib={"value": "", "type": "QString", "name": "name"} + ) + option3_2 = ET.SubElement(option3, "Option", attrib={"name": "properties"}) + + option3_3 = ET.SubElement( + option3, + "Option", + attrib={"value": "collection", "type": "QString", "name": "type"}, + ) + + +def save_qgis_qml_file( + gdf: gpd.geodataframe.GeoDataFrame, + value: str = "formation", + color: str = "color", + outline_width: Union[int, float] = 0.26, + alpha: Union[int, float] = 1, + path: str = "", +): """Creating and saving a QGIS Style File/QML File based on a GeoDataFrame Parameters @@ -867,56 +965,67 @@ def save_qgis_qml_file(gdf: gpd.geodataframe.GeoDataFrame, from PIL import ImageColor except ModuleNotFoundError: raise ModuleNotFoundError( - 'Pillow package is not installed. Use "pip install Pillow" to install the latest version') + 'Pillow package is not installed. Use "pip install Pillow" to install the latest version' + ) # Trying to import xml but returning an error if xml is not installed try: import xml.etree.cElementTree as ET except ModuleNotFoundError: - raise ModuleNotFoundError('xml package is not installed') + raise ModuleNotFoundError("xml package is not installed") # Checking that the gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be a GeoDataFrame') + raise TypeError("gdf must be a GeoDataFrame") # Checking that the geometry column is present in the gdf - if not 'geometry' in gdf: - raise ValueError('geometry column not present in GeoDataFrame') + if "geometry" not in gdf: + raise ValueError("geometry column not present in GeoDataFrame") # Checking that all geometries are Polygons - if not all(gdf.geom_type == 'Polygon'): - raise ValueError('All geometries of the GeoDataFrame must be polygons') + if not all(gdf.geom_type == "Polygon"): + raise ValueError("All geometries of the GeoDataFrame must be polygons") # Checking that the value used for the categorization in QGIS is of type string if not isinstance(value, str): - raise TypeError('value column name must be of type string') + raise TypeError("value column name must be of type string") # Checking that the value column is present in the gdf - if not value in gdf: + if value not in gdf: raise ValueError('"%s" not in gdf. Please provide a valid column name.' % value) # Checking that the color column is of type string if not isinstance(color, str): - raise TypeError('color column name must be of type string') + raise TypeError("color column name must be of type string") # Checking that the value column is present in the gdf - if not color in gdf: + if color not in gdf: raise ValueError('"%s" not in gdf. Please provide a valid column name.' % color) # Creating RGBA column from hex colors - gdf['RGBA'] = [str(ImageColor.getcolor(color, "RGBA")).lstrip('(').rstrip(')').replace(' ', '') for color in - gdf[color]] + gdf["RGBA"] = [ + str(ImageColor.getcolor(color, "RGBA")).lstrip("(").rstrip(")").replace(" ", "") + for color in gdf[color] + ] # Defining category subelement values - render_text = ['true'] * len(gdf['formation'].unique()) - value_text = gdf['formation'].unique().tolist() - type_text = ['string'] * len(gdf['formation'].unique()) - label_text = gdf['formation'].unique().tolist() - symbol_text = [str(value) for value in np.arange(0, len(gdf['formation'].unique())).tolist()] - outline_width = [str(outline_width)] * len(gdf['formation'].unique()) - alpha = [str(alpha)] * len(gdf['formation'].unique()) - - list_values_categories = [render_text, value_text, type_text, label_text, symbol_text] + render_text = ["true"] * len(gdf["formation"].unique()) + value_text = gdf["formation"].unique().tolist() + type_text = ["string"] * len(gdf["formation"].unique()) + label_text = gdf["formation"].unique().tolist() + symbol_text = [ + str(value) for value in np.arange(0, len(gdf["formation"].unique())).tolist() + ] + outline_width = [str(outline_width)] * len(gdf["formation"].unique()) + alpha = [str(alpha)] * len(gdf["formation"].unique()) + + list_values_categories = [ + render_text, + value_text, + type_text, + label_text, + symbol_text, + ] # Defining category subelement keys list_keys_categories = ["render", "value", "type", "label", "symbol"] @@ -925,92 +1034,92 @@ def save_qgis_qml_file(gdf: gpd.geodataframe.GeoDataFrame, category_name = "category" # Creating Root Element - root = ET.Element("qgis", attrib={"version": '3.28.1-Firenze', - "styleCategories": 'Symbology'}) + root = ET.Element( + "qgis", attrib={"version": "3.28.1-Firenze", "styleCategories": "Symbology"} + ) # Inserting Comment comment = ET.Comment("DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM''") root.insert(0, comment) # Creating renderer element - renderer = ET.SubElement(root, "renderer-v2", attrib={"attr": value, - "symbollevels": '0', - "type": 'categorizedSymbol', - "forecaster": '0', - "referencescale": '-1', - "enableorderby": '0'}) + renderer = ET.SubElement( + root, + "renderer-v2", + attrib={ + "attr": value, + "symbollevels": "0", + "type": "categorizedSymbol", + "forecaster": "0", + "referencescale": "-1", + "enableorderby": "0", + }, + ) # Creating categories element - categories = ET.SubElement(renderer, 'categories') + categories = ET.SubElement(renderer, "categories") # Creating elements and attributes - list_attributes = create_attributes(list_keys_categories, - list_values_categories) - [create_subelement(categories, - category_name, - attrib) for attrib in list_attributes] + list_attributes = create_attributes(list_keys_categories, list_values_categories) + [create_subelement(categories, category_name, attrib) for attrib in list_attributes] # Creating Symbols - symbols = ET.SubElement(renderer, - 'symbols') - - [create_symbol(symbols, - color, - symbol, - outline_w, - opacity) for color, symbol, outline_w, opacity in zip(gdf['RGBA'].unique(), - symbol_text, - outline_width, alpha)] - - source_symbol = ET.SubElement(renderer, - 'source_symbol') - - create_symbol(source_symbol, - color='152,125,183,255', - symbol_text='0', - outline_width=outline_width[0], - alpha=alpha[0]) - - roation = ET.SubElement(renderer, - 'rotation', - attrib={}) - - sizescale = ET.SubElement(renderer, - 'sizescale', - attrib={}) - - blendMode = ET.SubElement(root, - "blendMode", ) + symbols = ET.SubElement(renderer, "symbols") + + [ + create_symbol(symbols, color, symbol, outline_w, opacity) + for color, symbol, outline_w, opacity in zip( + gdf["RGBA"].unique(), symbol_text, outline_width, alpha + ) + ] + + source_symbol = ET.SubElement(renderer, "source_symbol") + + create_symbol( + source_symbol, + color="152,125,183,255", + symbol_text="0", + outline_width=outline_width[0], + alpha=alpha[0], + ) + + roation = ET.SubElement(renderer, "rotation", attrib={}) + + sizescale = ET.SubElement(renderer, "sizescale", attrib={}) + + blendMode = ET.SubElement( + root, + "blendMode", + ) blendMode.text = "0" - featureblendMode = ET.SubElement(root, - "featureBlendMode") + featureblendMode = ET.SubElement(root, "featureBlendMode") featureblendMode.text = "0" - layerGeometryType = ET.SubElement(root, - "layerGeometryType") + layerGeometryType = ET.SubElement(root, "layerGeometryType") layerGeometryType.text = "2" # Creating tree tree = ET.ElementTree(root) # Insert line breaks - ET.indent(tree, ' ') + ET.indent(tree, " ") # Saving file tree.write(path, encoding="utf-8", xml_declaration=False) - print('QML file successfully saved as %s' % path) + print("QML file successfully saved as %s" % path) -def clip_fault_of_gempy_model(geo_model, - fault: str, - which: str = 'first', - buffer_first: Union[int, float] = None, - buffer_last: Union[int, float] = None, - i_size: Union[int, float] = 1000, - j_size: Union[int, float] = 1000, - invert_first: bool = True, - invert_last: bool = False) -> Union[ - pv.core.pointset.PolyData, List[pv.core.pointset.PolyData]]: +def clip_fault_of_gempy_model( + geo_model, + fault: str, + which: str = "first", + buffer_first: Union[int, float] = None, + buffer_last: Union[int, float] = None, + i_size: Union[int, float] = 1000, + j_size: Union[int, float] = 1000, + invert_first: bool = True, + invert_last: bool = False, +) -> Union[pv.core.pointset.PolyData, List[pv.core.pointset.PolyData]]: """ Clip fault of a GemPy model. @@ -1058,143 +1167,170 @@ def clip_fault_of_gempy_model(geo_model, """ # Trying to import gempy but returning error if gempy is not installed - try: - import gempy as gp - except ModuleNotFoundError: - raise ModuleNotFoundError( - 'GemPy package is not installed. Use pip install gempy to install the latest version') + # try: + # import gempy as gp + # except ModuleNotFoundError: + # raise ModuleNotFoundError( + # 'GemPy package is not installed. Use pip install gempy to install the latest version') # Checking that the fault is provided as string if not isinstance(fault, str): - raise TypeError('Faults must be provided as one string for one fault ') + raise TypeError("Faults must be provided as one string for one fault ") # Checking that the fault is a fault of the geo_model if isinstance(fault, str): - if not fault in geo_model.surfaces.df['surface'][geo_model.surfaces.df['isFault'] == True].tolist(): - raise ValueError('Fault is not part of the GemPy geo_model') + if ( + fault + not in geo_model.surfaces.df["surface"][ + geo_model.surfaces.df["isFault"] == True + ].tolist() + ): + raise ValueError("Fault is not part of the GemPy geo_model") # Getting the fault DataFrames fault_df_interfaces = geo_model.surface_points.df[ - geo_model.surface_points.df['surface'] == fault].reset_index(drop=True) + geo_model.surface_points.df["surface"] == fault + ].reset_index(drop=True) fault_df_orientations = geo_model.orientations.df[ - geo_model.orientations.df['surface'] == fault].reset_index(drop=True) + geo_model.orientations.df["surface"] == fault + ].reset_index(drop=True) # Checking that the parameter which is of type string or list of strings if not isinstance(which, str): raise TypeError( - 'The parameter "which" must be provided as string. Options for each fault include "first", "last", or "both"') + 'The parameter "which" must be provided as string. Options for each fault include "first", "last", or "both"' + ) # Checking that the correct values are provided for the parameter which if isinstance(which, str): - if not which in ['first', 'last', 'both']: - raise ValueError('The options for the parameter "which" include "first", "last", or "both"') + if which not in ["first", "last", "both"]: + raise ValueError( + 'The options for the parameter "which" include "first", "last", or "both"' + ) # Checking that the i size is of type int or float if not isinstance(i_size, (int, float)): - raise TypeError('i_size must be provided as int or float') + raise TypeError("i_size must be provided as int or float") # Checking that the j size is of type int or float if not isinstance(j_size, (int, float)): - raise TypeError('j_size must be provided as int or float') + raise TypeError("j_size must be provided as int or float") # Extracting depth map - mesh = visualization.create_depth_maps_from_gempy(geo_model, - surfaces=fault) + mesh = visualization.create_depth_maps_from_gempy(geo_model, surfaces=fault) # Getting the first interface points - if which == 'first': + if which == "first": - fault_df_interfaces_selected = fault_df_interfaces.iloc[0:1].reset_index(drop=True) + fault_df_interfaces_selected = fault_df_interfaces.iloc[0:1].reset_index( + drop=True + ) # Creating plane from DataFrames - plane, azimuth = create_plane_from_interface_and_orientation_dfs(df_interface=fault_df_interfaces_selected, - df_orientations=fault_df_orientations, - i_size=i_size, - j_size=j_size) + plane, azimuth = create_plane_from_interface_and_orientation_dfs( + df_interface=fault_df_interfaces_selected, + df_orientations=fault_df_orientations, + i_size=i_size, + j_size=j_size, + ) # Translating Clipping Plane if buffer_first: # Checking that buffer_first is of type int or float if not isinstance(buffer_first, (int, float)): - raise TypeError('buffer_first must be provided as int or float') + raise TypeError("buffer_first must be provided as int or float") - plane = translate_clipping_plane(plane=plane, - azimuth=azimuth, - buffer=buffer_first) + plane = translate_clipping_plane( + plane=plane, azimuth=azimuth, buffer=buffer_first + ) # Clipping mesh - mesh[fault][0] = mesh[fault][0].clip_surface(plane, - invert=invert_first) + mesh[fault][0] = mesh[fault][0].clip_surface(plane, invert=invert_first) # Getting the last interface points - elif which == 'last': + elif which == "last": - fault_df_interfaces_selected = fault_df_interfaces.iloc[-1:].reset_index(drop=True) + fault_df_interfaces_selected = fault_df_interfaces.iloc[-1:].reset_index( + drop=True + ) # Creating plane from DataFrames - plane, azimuth = create_plane_from_interface_and_orientation_dfs(df_interface=fault_df_interfaces_selected, - df_orientations=fault_df_orientations, - i_size=i_size, - j_size=j_size) + plane, azimuth = create_plane_from_interface_and_orientation_dfs( + df_interface=fault_df_interfaces_selected, + df_orientations=fault_df_orientations, + i_size=i_size, + j_size=j_size, + ) # Translating Clipping Plane if buffer_last: # Checking that buffer_last is of type int or float if not isinstance(buffer_last, (int, float)): - raise TypeError('buffer_last must be provided as int or float') + raise TypeError("buffer_last must be provided as int or float") - plane = translate_clipping_plane(plane=plane, - azimuth=azimuth, - buffer=buffer_last) + plane = translate_clipping_plane( + plane=plane, azimuth=azimuth, buffer=buffer_last + ) # Clipping mesh - mesh[fault][0] = mesh[fault][0].clip_surface(plane, - invert_last) + mesh[fault][0] = mesh[fault][0].clip_surface(plane, invert_last) - if which == 'both': + if which == "both": # First point - fault_df_interfaces_selected = fault_df_interfaces.iloc[0:1].reset_index(drop=True) + fault_df_interfaces_selected = fault_df_interfaces.iloc[0:1].reset_index( + drop=True + ) # Creating plane from DataFrames - plane1, azimuth1 = create_plane_from_interface_and_orientation_dfs(df_interface=fault_df_interfaces_selected, - df_orientations=fault_df_orientations, - i_size=i_size, - j_size=j_size) + plane1, azimuth1 = create_plane_from_interface_and_orientation_dfs( + df_interface=fault_df_interfaces_selected, + df_orientations=fault_df_orientations, + i_size=i_size, + j_size=j_size, + ) # Translating Clipping Plane if buffer_first: - plane1 = translate_clipping_plane(plane=plane1, - azimuth=azimuth1, - buffer=buffer_first) + plane1 = translate_clipping_plane( + plane=plane1, azimuth=azimuth1, buffer=buffer_first + ) # Last Point - fault_df_interfaces_selected = fault_df_interfaces.iloc[-1:].reset_index(drop=True) + fault_df_interfaces_selected = fault_df_interfaces.iloc[-1:].reset_index( + drop=True + ) # Creating plane from DataFrames - plane2, azimuth2 = create_plane_from_interface_and_orientation_dfs(df_interface=fault_df_interfaces_selected, - df_orientations=fault_df_orientations, - i_size=i_size, - j_size=j_size) + plane2, azimuth2 = create_plane_from_interface_and_orientation_dfs( + df_interface=fault_df_interfaces_selected, + df_orientations=fault_df_orientations, + i_size=i_size, + j_size=j_size, + ) # Translating Clipping Plane if buffer_last: - plane2 = translate_clipping_plane(plane=plane2, - azimuth=azimuth2, - buffer=-buffer_last) + plane2 = translate_clipping_plane( + plane=plane2, azimuth=azimuth2, buffer=-buffer_last + ) # Clipping mesh - mesh[fault][0] = mesh[fault][0].clip_surface(plane1, - invert=invert_first).clip_surface(plane2, - invert=invert_last) + mesh[fault][0] = ( + mesh[fault][0] + .clip_surface(plane1, invert=invert_first) + .clip_surface(plane2, invert=invert_last) + ) return mesh -def create_plane_from_interface_and_orientation_dfs(df_interface: pd.DataFrame, - df_orientations: pd.DataFrame, - i_size: Union[int, float] = 1000, - j_size: Union[int, float] = 1000) -> pv.core.pointset.PolyData: +def create_plane_from_interface_and_orientation_dfs( + df_interface: pd.DataFrame, + df_orientations: pd.DataFrame, + i_size: Union[int, float] = 1000, + j_size: Union[int, float] = 1000, +) -> pv.core.pointset.PolyData: """ Create PyVista plane from GemPy interface and orientations DataFrames. @@ -1229,54 +1365,57 @@ def create_plane_from_interface_and_orientation_dfs(df_interface: pd.DataFrame, """ # Checking that the interface DataFrame is a DataFrame if not isinstance(df_interface, pd.DataFrame): - raise TypeError('Interface must be provided as Pandas DataFrame') + raise TypeError("Interface must be provided as Pandas DataFrame") # Checking that the orientations DataFrame is a DataFrame if not isinstance(df_orientations, pd.DataFrame): - raise TypeError('Orientations must be provided as Pandas DataFrame') + raise TypeError("Orientations must be provided as Pandas DataFrame") # Checking that the i size is of type int or float if not isinstance(i_size, (int, float)): - raise TypeError('i_size must be provided as int or float') + raise TypeError("i_size must be provided as int or float") # Checking that the j size is of type int or float if not isinstance(j_size, (int, float)): - raise TypeError('j_size must be provided as int or float') + raise TypeError("j_size must be provided as int or float") # Creating GeoDataFrame from interface - gdf_interface = gpd.GeoDataFrame(geometry=gpd.points_from_xy(x=df_interface['X'], - y=df_interface['Y']), - data=df_interface) + gdf_interface = gpd.GeoDataFrame( + geometry=gpd.points_from_xy(x=df_interface["X"], y=df_interface["Y"]), + data=df_interface, + ) # Creating GeoDataFrame from orientations - gdf_orientations = gpd.GeoDataFrame(geometry=gpd.points_from_xy(x=df_orientations['X'], - y=df_orientations['Y']), - data=df_orientations) + gdf_orientations = gpd.GeoDataFrame( + geometry=gpd.points_from_xy(x=df_orientations["X"], y=df_orientations["Y"]), + data=df_orientations, + ) # Finding nearest orientation to the respective interface to set the orientation of the plane - gdf_orientations_nearest = gpd.sjoin_nearest(gdf_interface, - gdf_orientations) + gdf_orientations_nearest = gpd.sjoin_nearest(gdf_interface, gdf_orientations) # Extracting azimuth for clipping plane - azimuth = gdf_orientations_nearest['azimuth'][0] + azimuth = gdf_orientations_nearest["azimuth"][0] # Extracting center of clipping plane - center = df_interface[['X', 'Y', 'Z']].values[0] + center = df_interface[["X", "Y", "Z"]].values[0] # Creating clipping plane, direction is created from the orientation of the fault. - plane = pv.Plane(center=center, - direction=(np.cos(np.radians(azimuth)), - np.sin(np.radians(azimuth)), - 0.0), - i_size=i_size, - j_size=j_size) + plane = pv.Plane( + center=center, + direction=(np.cos(np.radians(azimuth)), np.sin(np.radians(azimuth)), 0.0), + i_size=i_size, + j_size=j_size, + ) return plane, azimuth -def translate_clipping_plane(plane: pv.core.pointset.PolyData, - azimuth: Union[int, float, np.int64], - buffer: Union[int, float]) -> pv.core.pointset.PolyData: +def translate_clipping_plane( + plane: pv.core.pointset.PolyData, + azimuth: Union[int, float, np.int64], + buffer: Union[int, float], +) -> pv.core.pointset.PolyData: """ Translate clipping plane. @@ -1308,23 +1447,27 @@ def translate_clipping_plane(plane: pv.core.pointset.PolyData, """ # Checking that the plane is of type PyVista PolyData if not isinstance(plane, pv.core.pointset.PolyData): - raise TypeError('The clipping plane must be provided as PyVista PolyData') + raise TypeError("The clipping plane must be provided as PyVista PolyData") # Checking that the azimuth is of type int or float if not isinstance(azimuth, (int, float, np.int64)): - raise TypeError('The azimuth must be provided as int or float') + raise TypeError("The azimuth must be provided as int or float") # Checking that the buffer is of type int or float if not isinstance(buffer, (int, float, type(None))): - raise TypeError('The buffer must be provided as int or float') + raise TypeError("The buffer must be provided as int or float") # Calculating translation factor in X and Y Directio x_translation = -np.cos(np.radians(azimuth)) * buffer y_translation = -np.sin(np.radians(azimuth)) * buffer # Translating plane - plane = plane.translate((x_translation * np.cos(np.radians(azimuth)), - y_translation * np.sin(np.radians(azimuth)), - 0.0)) + plane = plane.translate( + ( + x_translation * np.cos(np.radians(azimuth)), + y_translation * np.sin(np.radians(azimuth)), + 0.0, + ) + ) return plane diff --git a/gemgis/raster.py b/gemgis/raster.py index 8f619bec..8a5b5f17 100644 --- a/gemgis/raster.py +++ b/gemgis/raster.py @@ -28,18 +28,19 @@ import pandas as pd import geopandas as gpd from typing import Union, List, Sequence, Optional, Iterable, Dict, Tuple -from rasterio.mask import mask -from shapely.geometry import box, Polygon, LineString +from shapely.geometry import Polygon, LineString import shapely from pathlib import Path import affine import pyproj -def sample_from_array(array: np.ndarray, - extent: Sequence[float], - point_x: Union[float, int, list, np.ndarray], - point_y: Union[float, int, list, np.ndarray], ) -> Union[np.ndarray, float]: +def sample_from_array( + array: np.ndarray, + extent: Sequence[float], + point_x: Union[float, int, list, np.ndarray], + point_y: Union[float, int, list, np.ndarray], +) -> Union[np.ndarray, float]: """Sampling the value of a np.ndarray at a given point and given the arrays true extent Parameters @@ -96,58 +97,58 @@ def sample_from_array(array: np.ndarray, # Checking is the array is a np.ndarray if not isinstance(array, np.ndarray): - raise TypeError('Object must be of type np.ndarray') + raise TypeError("Object must be of type np.ndarray") # Checking if the extent is a list if not isinstance(extent, Sequence): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking that the length of the list is either four or six if len(extent) not in [4, 6]: - raise ValueError('The extent must include only four or six values') + raise ValueError("The extent must include only four or six values") # Checking if the point coordinates are stored as a list if not isinstance(point_x, (list, np.ndarray, float, int)): - raise TypeError('Point_x must be of type list or np.ndarray') + raise TypeError("Point_x must be of type list or np.ndarray") # Checking if the point coordinates are stored as a list if not isinstance(point_y, (list, np.ndarray, float, int)): - raise TypeError('Point_y must be of type list or np.ndarray') + raise TypeError("Point_y must be of type list or np.ndarray") # Checking the length of the point list if not isinstance(point_x, (float, int)) and not isinstance(point_y, (float, int)): if len(point_x) != len(point_y): - raise ValueError('Length of both point lists/arrays must be equal') + raise ValueError("Length of both point lists/arrays must be equal") # Checking that all elements of the extent are of type int or float if not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Extent values must be of type int or float') + raise TypeError("Extent values must be of type int or float") # Checking that all elements of the point list are of type int or float if isinstance(point_x, (list, np.ndarray)): if not all(isinstance(n, (int, float, np.int32)) for n in point_x): - raise TypeError('Point values must be of type int or float') + raise TypeError("Point values must be of type int or float") # Checking that all elements of the point list are of type int or float if isinstance(point_y, (list, np.ndarray)): if not all(isinstance(n, (int, float)) for n in point_y): - raise TypeError('Point values must be of type int or float') + raise TypeError("Point values must be of type int or float") # Checking if the point is located within the provided extent if isinstance(point_x, (list, np.ndarray)): if any(x < extent[0] for x in point_x) or any(x > extent[1] for x in point_x): - raise ValueError('One or multiple points are located outside of the extent') + raise ValueError("One or multiple points are located outside of the extent") if isinstance(point_y, (list, np.ndarray)): if any(y < extent[2] for y in point_y) or any(y > extent[3] for y in point_y): - raise ValueError('One or multiple points are located outside of the extent') + raise ValueError("One or multiple points are located outside of the extent") # Checking if the point is located within the provided extent if isinstance(point_x, (float, int)): if point_x < extent[0] or point_x > extent[1]: - raise ValueError('One or multiple points are located outside of the extent') + raise ValueError("One or multiple points are located outside of the extent") if isinstance(point_y, (float, int)): if point_y < extent[2] or point_y > extent[3]: - raise ValueError('One or multiple points are located outside of the extent') + raise ValueError("One or multiple points are located outside of the extent") # Converting lists of coordinates to np.ndarrays if isinstance(point_x, list) and isinstance(point_y, list): @@ -155,15 +156,21 @@ def sample_from_array(array: np.ndarray, point_y = np.array(point_y) # Getting the column number based on the extent and shape of the array - column = np.int32(np.round((point_x - extent[0]) / (extent[1] - extent[0]) * array.shape[1])) + column = np.int32( + np.round((point_x - extent[0]) / (extent[1] - extent[0]) * array.shape[1]) + ) # Getting the row number based on the extent and shape of the array - row = np.int32(np.round((point_y - extent[2]) / (extent[3] - extent[2]) * array.shape[0])) + row = np.int32( + np.round((point_y - extent[2]) / (extent[3] - extent[2]) * array.shape[0]) + ) # Checking that all elements for the column and row numbers are of type int if isinstance(row, np.ndarray) and isinstance(column, np.ndarray): - if not all(isinstance(n, np.int32) for n in column) and not all(isinstance(n, np.int32) for n in row): - raise TypeError('Column and row values must be of type int for indexing') + if not all(isinstance(n, np.int32) for n in column) and not all( + isinstance(n, np.int32) for n in row + ): + raise TypeError("Column and row values must be of type int for indexing") # Flip array so that the column and row indices are correct array = np.flipud(array) @@ -179,11 +186,13 @@ def sample_from_array(array: np.ndarray, return sample -def sample_from_rasterio(raster: rasterio.io.DatasetReader, - point_x: Union[float, int, list, np.ndarray], - point_y: Union[float, int, list, np.ndarray], - sample_outside_extent: bool = True, - sample_all_bands: bool = False) -> Union[list, float]: +def sample_from_rasterio( + raster: rasterio.io.DatasetReader, + point_x: Union[float, int, list, np.ndarray], + point_y: Union[float, int, list, np.ndarray], + sample_outside_extent: bool = True, + sample_all_bands: bool = False, +) -> Union[list, float]: """Sampling the value of a rasterio object at a given point within the extent of the raster Parameters @@ -241,56 +250,68 @@ def sample_from_rasterio(raster: rasterio.io.DatasetReader, # Checking that the raster is a rasterio object if not isinstance(raster, rasterio.io.DatasetReader): - raise TypeError('Raster must be provided as rasterio object') + raise TypeError("Raster must be provided as rasterio object") # Checking if the point coordinates are stored as a list if not isinstance(point_x, (list, np.ndarray, float, int)): - raise TypeError('Point_x must be of type list or np.ndarray') + raise TypeError("Point_x must be of type list or np.ndarray") # Checking if the point coordinates are stored as a list if not isinstance(point_y, (list, np.ndarray, float, int)): - raise TypeError('Point_y must be of type list or np.ndarray') + raise TypeError("Point_y must be of type list or np.ndarray") # Checking the length of the point list if not isinstance(point_x, (float, int)) and not isinstance(point_y, (float, int)): if len(point_x) != len(point_y): - raise ValueError('Length of both point lists/arrays must be equal') + raise ValueError("Length of both point lists/arrays must be equal") # Checking that all elements of the point list are of type int or float if isinstance(point_x, (list, np.ndarray)): if not all(isinstance(n, (int, float, np.int32, np.float64)) for n in point_x): - raise TypeError('Point values must be of type int or float') + raise TypeError("Point values must be of type int or float") # Checking that all elements of the point list are of type int or float if isinstance(point_y, (list, np.ndarray)): if not all(isinstance(n, (int, float, np.int32, np.float64)) for n in point_y): - raise TypeError('Point values must be of type int or float') + raise TypeError("Point values must be of type int or float") # Checking that sample_outside_extent is of type bool if not isinstance(sample_outside_extent, bool): - raise TypeError('Sample_outside_extent argument must be of type bool') + raise TypeError("Sample_outside_extent argument must be of type bool") # Checking that sample_all_bands is of type bool if not isinstance(sample_all_bands, bool): - raise TypeError('Sample_all_bands argument must be of type bool') + raise TypeError("Sample_all_bands argument must be of type bool") # If sample_outside extent is true, a nodata value will be assigned if not sample_outside_extent: # Checking if the point is located within the provided raster extent if isinstance(point_x, (list, np.ndarray)): - if any(x < raster.bounds[0] for x in point_x) or any(x > raster.bounds[2] for x in point_x): - raise ValueError('One or multiple points are located outside of the extent') + if any(x < raster.bounds[0] for x in point_x) or any( + x > raster.bounds[2] for x in point_x + ): + raise ValueError( + "One or multiple points are located outside of the extent" + ) if isinstance(point_y, (list, np.ndarray)): - if any(y < raster.bounds[1] for y in point_y) or any(y > raster.bounds[3] for y in point_y): - raise ValueError('One or multiple points are located outside of the extent') + if any(y < raster.bounds[1] for y in point_y) or any( + y > raster.bounds[3] for y in point_y + ): + raise ValueError( + "One or multiple points are located outside of the extent" + ) # Checking if the point is located within the provided raster extent if isinstance(point_x, (float, int)): if point_x < raster.bounds[0] or point_x > raster.bounds[2]: - raise ValueError('One or multiple points are located outside of the extent') + raise ValueError( + "One or multiple points are located outside of the extent" + ) if isinstance(point_y, (float, int)): if point_y < raster.bounds[1] or point_y > raster.bounds[3]: - raise ValueError('One or multiple points are located outside of the extent') + raise ValueError( + "One or multiple points are located outside of the extent" + ) # Converting lists of coordinates to np.ndarrays if isinstance(point_x, list) and isinstance(point_y, list): @@ -315,10 +336,12 @@ def sample_from_rasterio(raster: rasterio.io.DatasetReader, return sample -def sample_randomly(raster: Union[np.ndarray, rasterio.io.DatasetReader], - n: int = 1, - extent: Optional[Sequence[float]] = None, - seed: int = None) -> tuple: +def sample_randomly( + raster: Union[np.ndarray, rasterio.io.DatasetReader], + n: int = 1, + extent: Optional[Sequence[float]] = None, + seed: int = None, +) -> tuple: """Sampling randomly from a raster (array or rasterio object) using sample_from_array or sample_from_rasterio and a randomly drawn point within the array/raster extent @@ -371,31 +394,31 @@ def sample_randomly(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if the array is of type np.ndarrays if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Array must be of type np.ndarray') + raise TypeError("Array must be of type np.ndarray") # Checking that n is of type int if not isinstance(n, int): - raise TypeError('Number of samples n must be provided as int') + raise TypeError("Number of samples n must be provided as int") # Checking if seed is of type int if not isinstance(seed, (int, type(None))): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking that if a seed was provided that the seed is of type int if seed is not None: if not isinstance(seed, int): - raise TypeError('Seed must be of type int') + raise TypeError("Seed must be of type int") np.random.seed(seed) # Checking if extent is a list if not isinstance(extent, (list, type(None))): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Sampling from Array # Checking that all values are either ints or floats if isinstance(raster, np.ndarray): if not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Extent values must be of type int or float') + raise TypeError("Extent values must be of type int or float") # Drawing random values x and y within the provided extent x = np.random.uniform(extent[0], extent[1], n) @@ -403,9 +426,9 @@ def sample_randomly(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if the drawn values are floats if not isinstance(x, np.ndarray): - raise TypeError('x must be of type np.ndarray') + raise TypeError("x must be of type np.ndarray") if not isinstance(y, np.ndarray): - raise TypeError('y must be of type np.ndarray') + raise TypeError("y must be of type np.ndarray") # Sampling from the provided array and the random point sample = sample_from_array(array=raster, extent=extent, point_x=x, point_y=y) @@ -420,9 +443,9 @@ def sample_randomly(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if the drawn values are floats if not isinstance(x, np.ndarray): - raise TypeError('x must be of type np.ndarray') + raise TypeError("x must be of type np.ndarray") if not isinstance(y, np.ndarray): - raise TypeError('y must be of type np.ndarray') + raise TypeError("y must be of type np.ndarray") sample = sample_from_rasterio(raster=raster, point_x=x, point_y=y) @@ -435,15 +458,17 @@ def sample_randomly(raster: Union[np.ndarray, rasterio.io.DatasetReader], return sample, [x, y] -def sample_orientations(raster: Union[np.ndarray, rasterio.io.DatasetReader], - extent: List[Union[int, float]] = None, - point_x: Union[float, int, list, np.ndarray] = None, - point_y: Union[float, int, list, np.ndarray] = None, - random_samples: int = None, - formation: str = None, - seed: int = None, - sample_outside_extent: bool = False, - crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None) -> gpd.geodataframe.GeoDataFrame: +def sample_orientations( + raster: Union[np.ndarray, rasterio.io.DatasetReader], + extent: List[Union[int, float]] = None, + point_x: Union[float, int, list, np.ndarray] = None, + point_y: Union[float, int, list, np.ndarray] = None, + random_samples: int = None, + formation: str = None, + seed: int = None, + sample_outside_extent: bool = False, + crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None, +) -> gpd.geodataframe.GeoDataFrame: """Sampling orientations from a raster Parameters @@ -515,92 +540,115 @@ def sample_orientations(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if the rasterio of type np.ndarray or a rasterio object if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster must be of type np.ndarray or a rasterio object') + raise TypeError("Raster must be of type np.ndarray or a rasterio object") # Checking if the extent is of type list if an array is provided if isinstance(raster, np.ndarray) and not isinstance(extent, list): - raise TypeError('Extent must be of type list when providing an array') + raise TypeError("Extent must be of type list when providing an array") # Checking that all elements of the extent are of type float or int - if isinstance(raster, np.ndarray) and not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Extent values must be of type int or float') + if isinstance(raster, np.ndarray) and not all( + isinstance(n, (int, float)) for n in extent + ): + raise TypeError("Extent values must be of type int or float") # Checking if the number of samples is of type int if point_x is None and point_y is None and not isinstance(random_samples, int): - raise TypeError('Number of samples must be of type int if no points are provided') + raise TypeError( + "Number of samples must be of type int if no points are provided" + ) # Checking if the points are of the correct type - if isinstance(random_samples, type(None)) and not isinstance(point_x, (float, int, list, np.ndarray)): - raise TypeError('Point_x must either be an int, float or a list or array of coordinates') + if isinstance(random_samples, type(None)) and not isinstance( + point_x, (float, int, list, np.ndarray) + ): + raise TypeError( + "Point_x must either be an int, float or a list or array of coordinates" + ) # Checking if the points are of the correct type - if isinstance(random_samples, type(None)) and not isinstance(point_y, (float, int, list, np.ndarray)): - raise TypeError('Point_y must either be an int, float or a list or array of coordinates') + if isinstance(random_samples, type(None)) and not isinstance( + point_y, (float, int, list, np.ndarray) + ): + raise TypeError( + "Point_y must either be an int, float or a list or array of coordinates" + ) # Checking if the seed is of type int if not isinstance(seed, (int, type(None))): - raise TypeError('Seed must be of type int') + raise TypeError("Seed must be of type int") # Checking that sampling outside extent is of type bool if not isinstance(sample_outside_extent, bool): - raise TypeError('Sampling_outside_extent must be of type bool') + raise TypeError("Sampling_outside_extent must be of type bool") # Checking that the crs is either a string or of type bool if not isinstance(crs, (str, pyproj.crs.crs.CRS, rasterio.crs.CRS, type(None))): - raise TypeError('CRS must be provided as string, pyproj or rasterio object') + raise TypeError("CRS must be provided as string, pyproj or rasterio object") # Calculate slope and aspect of raster - slope = calculate_slope(raster=raster, - extent=extent) + slope = calculate_slope(raster=raster, extent=extent) - aspect = calculate_aspect(raster=raster, - extent=extent) + aspect = calculate_aspect(raster=raster, extent=extent) # Sampling interfaces - gdf = sample_interfaces(raster=raster, - extent=extent, - point_x=point_x, - point_y=point_y, - random_samples=random_samples, - formation=formation, - seed=seed, - sample_outside_extent=sample_outside_extent, - crs=crs) + gdf = sample_interfaces( + raster=raster, + extent=extent, + point_x=point_x, + point_y=point_y, + random_samples=random_samples, + formation=formation, + seed=seed, + sample_outside_extent=sample_outside_extent, + crs=crs, + ) # Setting the array extent for the dip and azimuth sampling if isinstance(raster, rasterio.io.DatasetReader): - raster_extent = [raster.bounds[0], raster.bounds[2], raster.bounds[1], raster.bounds[3]] + raster_extent = [ + raster.bounds[0], + raster.bounds[2], + raster.bounds[1], + raster.bounds[3], + ] else: raster_extent = extent # Sampling dip and azimuth at the given locations - dip = sample_from_array(array=slope, - extent=raster_extent, - point_x=gdf['X'].values, - point_y=gdf['Y'].values) - - azimuth = sample_from_array(array=aspect, - extent=raster_extent, - point_x=gdf['X'].values, - point_y=gdf['Y'].values) + dip = sample_from_array( + array=slope, + extent=raster_extent, + point_x=gdf["X"].values, + point_y=gdf["Y"].values, + ) + + azimuth = sample_from_array( + array=aspect, + extent=raster_extent, + point_x=gdf["X"].values, + point_y=gdf["Y"].values, + ) # Adding columns to the GeoDataFrame - gdf['dip'] = dip - gdf['azimuth'] = azimuth - gdf['polarity'] = 1 + gdf["dip"] = dip + gdf["azimuth"] = azimuth + gdf["polarity"] = 1 return gdf -def sample_interfaces(raster: Union[np.ndarray, rasterio.io.DatasetReader], - extent: List[Union[int, float]] = None, - point_x: Union[float, int, list, np.ndarray] = None, - point_y: Union[float, int, list, np.ndarray] = None, - random_samples: int = None, - formation: str = None, - seed: int = None, - sample_outside_extent: bool = False, - crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None) -> gpd.geodataframe.GeoDataFrame: +def sample_interfaces( + raster: Union[np.ndarray, rasterio.io.DatasetReader], + extent: List[Union[int, float]] = None, + point_x: Union[float, int, list, np.ndarray] = None, + point_y: Union[float, int, list, np.ndarray] = None, + random_samples: int = None, + formation: str = None, + seed: int = None, + sample_outside_extent: bool = False, + crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None, +) -> gpd.geodataframe.GeoDataFrame: """Sampling interfaces from a raster Parameters @@ -672,60 +720,72 @@ def sample_interfaces(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if the rasterio of type np.ndarray or a rasterio object if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster must be of type np.ndarray or a rasterio object') + raise TypeError("Raster must be of type np.ndarray or a rasterio object") # Checking if the extent is of type list if an array is provided if isinstance(raster, np.ndarray) and not isinstance(extent, list): - raise TypeError('Extent must be of type list when providing an array') + raise TypeError("Extent must be of type list when providing an array") # Checking that all elements of the extent are of type float or int - if isinstance(raster, np.ndarray) and not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Extent values must be of type int or float') + if isinstance(raster, np.ndarray) and not all( + isinstance(n, (int, float)) for n in extent + ): + raise TypeError("Extent values must be of type int or float") # Checking if the number of samples is of type int if point_x is None and point_y is None and not isinstance(random_samples, int): - raise TypeError('Number of samples must be of type int if no points are provided') + raise TypeError( + "Number of samples must be of type int if no points are provided" + ) # Checking if the points are of the correct type - if isinstance(random_samples, type(None)) and not isinstance(point_x, (float, int, list, np.ndarray)): - raise TypeError('Point_x must either be an int, float or a list or array of coordinates') + if isinstance(random_samples, type(None)) and not isinstance( + point_x, (float, int, list, np.ndarray) + ): + raise TypeError( + "Point_x must either be an int, float or a list or array of coordinates" + ) # Checking if the points are of the correct type - if isinstance(random_samples, type(None)) and not isinstance(point_y, (float, int, list, np.ndarray)): - raise TypeError('Point_y must either be an int, float or a list or array of coordinates') + if isinstance(random_samples, type(None)) and not isinstance( + point_y, (float, int, list, np.ndarray) + ): + raise TypeError( + "Point_y must either be an int, float or a list or array of coordinates" + ) # Checking if the seed is of type int if not isinstance(seed, (int, type(None))): - raise TypeError('Seed must be of type int') + raise TypeError("Seed must be of type int") # Checking that sampling outside extent is of type bool if not isinstance(sample_outside_extent, bool): - raise TypeError('Sampling_outside_extent must be of type bool') + raise TypeError("Sampling_outside_extent must be of type bool") # Checking that the crs is either a string or of type bool if not isinstance(crs, (str, pyproj.crs.crs.CRS, rasterio.crs.CRS, type(None))): - raise TypeError('CRS must be provided as string, pyproj CRS or rasterio CRS') + raise TypeError("CRS must be provided as string, pyproj CRS or rasterio CRS") # Sampling by points if random_samples is None and point_x is not None and point_y is not None: # Sampling from Raster if isinstance(raster, rasterio.io.DatasetReader): - z = sample_from_rasterio(raster=raster, - point_x=point_x, - point_y=point_y, - sample_outside_extent=sample_outside_extent) + z = sample_from_rasterio( + raster=raster, + point_x=point_x, + point_y=point_y, + sample_outside_extent=sample_outside_extent, + ) # Sampling from array else: - z = sample_from_array(array=raster, - extent=extent, - point_x=point_x, - point_y=point_y) + z = sample_from_array( + array=raster, extent=extent, point_x=point_x, point_y=point_y + ) # Sampling randomly elif random_samples is not None and point_x is None and point_y is None: - samples = sample_randomly(raster=raster, - n=random_samples, - extent=extent, - seed=seed) + samples = sample_randomly( + raster=raster, n=random_samples, extent=extent, seed=seed + ) # Assigning X, Y and Z values z = [i for i in samples[0]] @@ -735,31 +795,31 @@ def sample_interfaces(raster: Union[np.ndarray, rasterio.io.DatasetReader], point_y = [i for i in samples[1][1]] else: - raise TypeError('Either provide only lists or array of points or a number of random samples, not both.') + raise TypeError( + "Either provide only lists or array of points or a number of random samples, not both." + ) # Creating GeoDataFrame if isinstance(point_x, Iterable) and isinstance(point_y, Iterable): - gdf = gpd.GeoDataFrame(data=pd.DataFrame(data=[point_x, point_y, z]).T, - geometry=gpd.points_from_xy(x=point_x, - y=point_y, - crs=crs) - ) + gdf = gpd.GeoDataFrame( + data=pd.DataFrame(data=[point_x, point_y, z]).T, + geometry=gpd.points_from_xy(x=point_x, y=point_y, crs=crs), + ) else: - gdf = gpd.GeoDataFrame(data=pd.DataFrame(data=[point_x, point_y, z]).T, - geometry=gpd.points_from_xy(x=[point_x], - y=[point_y], - crs=crs) - ) + gdf = gpd.GeoDataFrame( + data=pd.DataFrame(data=[point_x, point_y, z]).T, + geometry=gpd.points_from_xy(x=[point_x], y=[point_y], crs=crs), + ) # Setting the column names - gdf.columns = ['X', 'Y', 'Z', 'geometry'] + gdf.columns = ["X", "Y", "Z", "geometry"] # Assigning formation name if formation is not None: if isinstance(formation, str): - gdf['formation'] = formation + gdf["formation"] = formation else: - raise TypeError('Formation must be provided as string or set to None') + raise TypeError("Formation must be provided as string or set to None") return gdf @@ -768,11 +828,13 @@ def sample_interfaces(raster: Union[np.ndarray, rasterio.io.DatasetReader], ############################### -def calculate_hillshades(raster: Union[np.ndarray, rasterio.io.DatasetReader], - extent: List[Union[int, float]] = None, - azdeg: Union[int, float] = 225, - altdeg: Union[int, float] = 45, - band_no: int = 1) -> np.ndarray: +def calculate_hillshades( + raster: Union[np.ndarray, rasterio.io.DatasetReader], + extent: List[Union[int, float]] = None, + azdeg: Union[int, float] = 225, + altdeg: Union[int, float] = 45, + band_no: int = 1, +) -> np.ndarray: """Calculating Hillshades based on digital elevation model/raster Parameters @@ -827,27 +889,27 @@ def calculate_hillshades(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking that the raster is of type rasterio object or numpy array if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster must be provided as rasterio object or NumPy array') + raise TypeError("Raster must be provided as rasterio object or NumPy array") # Checking if extent is of type list if not isinstance(extent, (type(None), list)): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking that altdeg is of type float or int if not isinstance(altdeg, (float, int)): - raise TypeError('altdeg must be of type int or float') + raise TypeError("altdeg must be of type int or float") # Checking that azdeg is of type float or int if not isinstance(azdeg, (float, int)): - raise TypeError('azdeg must be of type int or float') + raise TypeError("azdeg must be of type int or float") # Checking that altdeg is not out of bounds if altdeg > 90 or altdeg < 0: - raise ValueError('altdeg must be between 0 and 90 degrees') + raise ValueError("altdeg must be between 0 and 90 degrees") # Checking that azdeg is not out of bounds if azdeg > 360 or azdeg < 0: - raise ValueError('azdeg must be between 0 and 360 degrees') + raise ValueError("azdeg must be between 0 and 360 degrees") # Checking if object is rasterio object if isinstance(raster, rasterio.io.DatasetReader): @@ -862,24 +924,25 @@ def calculate_hillshades(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if object is of type np.ndarray if not isinstance(raster, np.ndarray): - raise TypeError('Input object must be of type np.ndarray') + raise TypeError("Input object must be of type np.ndarray") # Checking if dimension of array is correct if not raster.ndim == 2: - raise ValueError('Array must be of dimension 2') + raise ValueError("Array must be of dimension 2") # Calculate hillshades azdeg = 360 - azdeg x, y = np.gradient(raster) x = x / res[0] y = y / res[1] - slope = np.pi / 2. - np.arctan(np.sqrt(x * x + y * y)) + slope = np.pi / 2.0 - np.arctan(np.sqrt(x * x + y * y)) aspect = np.arctan2(-x, y) - azimuthrad = azdeg * np.pi / 180. - altituderad = altdeg * np.pi / 180. + azimuthrad = azdeg * np.pi / 180.0 + altituderad = altdeg * np.pi / 180.0 - shaded = np.sin(altituderad) * np.sin(slope) + np.cos(altituderad) * np.cos(slope) * np.cos( - (azimuthrad - np.pi / 2.) - aspect) + shaded = np.sin(altituderad) * np.sin(slope) + np.cos(altituderad) * np.cos( + slope + ) * np.cos((azimuthrad - np.pi / 2.0) - aspect) # Calculate color values hillshades = 255 * (shaded + 1) / 2 @@ -887,9 +950,11 @@ def calculate_hillshades(raster: Union[np.ndarray, rasterio.io.DatasetReader], return hillshades -def calculate_slope(raster: Union[np.ndarray, rasterio.io.DatasetReader], - extent: List[Union[int, float]] = None, - band_no: int = 1) -> np.ndarray: +def calculate_slope( + raster: Union[np.ndarray, rasterio.io.DatasetReader], + extent: List[Union[int, float]] = None, + band_no: int = 1, +) -> np.ndarray: """Calculating the slope based on digital elevation model/raster Parameters @@ -938,11 +1003,11 @@ def calculate_slope(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking that the raster is of type rasterio object or numpy array if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster must be provided as rasterio object or NumPy array') + raise TypeError("Raster must be provided as rasterio object or NumPy array") # Checking if extent is of type list if not isinstance(extent, (type(None), list)): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking if object is rasterio object if isinstance(raster, rasterio.io.DatasetReader): @@ -957,11 +1022,11 @@ def calculate_slope(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if object is of type np.ndarray if not isinstance(raster, np.ndarray): - raise TypeError('Input object must be of type np.ndarray') + raise TypeError("Input object must be of type np.ndarray") # Checking if dimension of array is correct if not raster.ndim == 2: - raise ValueError('Array must be of dimension 2') + raise ValueError("Array must be of dimension 2") # Calculate slope y, x = np.gradient(raster) @@ -973,9 +1038,11 @@ def calculate_slope(raster: Union[np.ndarray, rasterio.io.DatasetReader], return slope -def calculate_aspect(raster: Union[np.ndarray, rasterio.io.DatasetReader], - extent: List[Union[int, float]] = None, - band_no: int = 1) -> np.ndarray: +def calculate_aspect( + raster: Union[np.ndarray, rasterio.io.DatasetReader], + extent: List[Union[int, float]] = None, + band_no: int = 1, +) -> np.ndarray: """Calculating the aspect based on a digital elevation model/raster Parameters @@ -1024,11 +1091,11 @@ def calculate_aspect(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking that the raster is of type rasterio object or numpy array if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster must be provided as rasterio object or NumPy array') + raise TypeError("Raster must be provided as rasterio object or NumPy array") # Checking if extent is of type list if not isinstance(extent, (type(None), list)): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking if object is rasterio object if isinstance(raster, rasterio.io.DatasetReader): @@ -1043,11 +1110,11 @@ def calculate_aspect(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if object is of type np.ndarray if not isinstance(raster, np.ndarray): - raise TypeError('Input object must be of type np.ndarray') + raise TypeError("Input object must be of type np.ndarray") # Checking if dimension of array is correct if not raster.ndim == 2: - raise ValueError('Array must be of dimension 2') + raise ValueError("Array must be of dimension 2") # Calculate aspect y, x = np.gradient(raster) @@ -1060,9 +1127,11 @@ def calculate_aspect(raster: Union[np.ndarray, rasterio.io.DatasetReader], return aspect -def calculate_difference(raster1: Union[np.ndarray, rasterio.io.DatasetReader], - raster2: Union[np.ndarray, rasterio.io.DatasetReader], - flip_array: bool = False) -> np.ndarray: +def calculate_difference( + raster1: Union[np.ndarray, rasterio.io.DatasetReader], + raster2: Union[np.ndarray, rasterio.io.DatasetReader], + flip_array: bool = False, +) -> np.ndarray: """Calculating the difference between two rasters Parameters @@ -1112,14 +1181,16 @@ def calculate_difference(raster1: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if array1 is of type np.ndarray or a rasterio object if not isinstance(raster1, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster1 must be of type np.ndarray or a rasterio object') + raise TypeError("Raster1 must be of type np.ndarray or a rasterio object") # Checking if array2 is of type np.ndarray or a rasterio object if not isinstance(raster2, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster2 must be of type np.ndarray or a rasterio object') + raise TypeError("Raster2 must be of type np.ndarray or a rasterio object") # Subtracting rasterio objects - if isinstance(raster1, rasterio.io.DatasetReader) and isinstance(raster2, rasterio.io.DatasetReader): + if isinstance(raster1, rasterio.io.DatasetReader) and isinstance( + raster2, rasterio.io.DatasetReader + ): array_diff = raster1.read() - raster2.read() else: @@ -1127,8 +1198,7 @@ def calculate_difference(raster1: Union[np.ndarray, rasterio.io.DatasetReader], if raster1.shape != raster2.shape: # Rescale array - array_rescaled = resize_by_array(raster=raster2, - array=raster1) + array_rescaled = resize_by_array(raster=raster2, array=raster1) # Flip array if flip_array is True if flip_array: array_rescaled = np.flipud(array_rescaled) @@ -1151,13 +1221,15 @@ def calculate_difference(raster1: Union[np.ndarray, rasterio.io.DatasetReader], ###################### -def clip_by_bbox(raster: Union[rasterio.io.DatasetReader, np.ndarray], - bbox: List[Union[int, float]], - raster_extent: List[Union[int, float]] = None, - save_clipped_raster: bool = False, - path: str = 'raster_clipped.tif', - overwrite_file: bool = False, - create_directory: bool = False) -> np.ndarray: +def clip_by_bbox( + raster: Union[rasterio.io.DatasetReader, np.ndarray], + bbox: List[Union[int, float]], + raster_extent: List[Union[int, float]] = None, + save_clipped_raster: bool = False, + path: str = "raster_clipped.tif", + overwrite_file: bool = False, + create_directory: bool = False, +) -> np.ndarray: """Clipping a rasterio raster or np.ndarray by a given extent Parameters @@ -1225,23 +1297,23 @@ def clip_by_bbox(raster: Union[rasterio.io.DatasetReader, np.ndarray], # Checking that the raster is of type np.ndarray or a rasterio object if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster must be of type np.ndarray or a rasterio object') + raise TypeError("Raster must be of type np.ndarray or a rasterio object") # Checking that the extent is of type list if not isinstance(bbox, list): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking that all values are either ints or floats if not all(isinstance(n, (int, float)) for n in bbox): - raise TypeError('Bounds values must be of type int or float') + raise TypeError("Bounds values must be of type int or float") # Checking that save_clipped_raster is of type bool if not isinstance(save_clipped_raster, bool): - raise TypeError('save_clipped_raster must either be True or False') + raise TypeError("save_clipped_raster must either be True or False") # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('The path must be provided as string') + raise TypeError("The path must be provided as string") # Getting the absolute path path = os.path.abspath(path=path) @@ -1258,75 +1330,110 @@ def clip_by_bbox(raster: Union[rasterio.io.DatasetReader, np.ndarray], if create_directory: os.makedirs(path_dir) else: - raise LookupError('Directory not found. Pass create_directory=True to create a new directory') + raise LookupError( + "Directory not found. Pass create_directory=True to create a new directory" + ) if not overwrite_file: if os.path.exists(path): - raise FileExistsError("The file already exists. Pass overwrite_file=True to overwrite the existing file") + raise FileExistsError( + "The file already exists. Pass overwrite_file=True to overwrite the existing file" + ) # Checking if raster is rasterio object if isinstance(raster, rasterio.io.DatasetReader): - raster_clipped, raster_transform = rasterio.mask.mask(dataset=raster, - shapes=[Polygon([(bbox[0], bbox[2]), - (bbox[1], bbox[2]), - (bbox[1], bbox[3]), - (bbox[0], bbox[3])])], - crop=True, - filled=False, - pad=False, - pad_width=0) + raster_clipped, raster_transform = rasterio.mask.mask( + dataset=raster, + shapes=[ + Polygon( + [ + (bbox[0], bbox[2]), + (bbox[1], bbox[2]), + (bbox[1], bbox[3]), + (bbox[0], bbox[3]), + ] + ) + ], + crop=True, + filled=False, + pad=False, + pad_width=0, + ) # Saving the raster if save_clipped_raster: # Updating meta data raster_clipped_meta = raster.meta - raster_clipped_meta.update({"driver": "GTiff", - "height": raster_clipped.shape[1], - "width": raster_clipped.shape[2], - "transform": raster_transform}) + raster_clipped_meta.update( + { + "driver": "GTiff", + "height": raster_clipped.shape[1], + "width": raster_clipped.shape[2], + "transform": raster_transform, + } + ) # Writing the file with rasterio.open(path, "w", **raster_clipped_meta) as dest: dest.write(raster_clipped) # Swap axes and remove dimension - raster_clipped = np.flipud(np.rot90(np.swapaxes(raster_clipped, 0, 2)[:, :, 0], 1)) + raster_clipped = np.flipud( + np.rot90(np.swapaxes(raster_clipped, 0, 2)[:, :, 0], 1) + ) else: # Checking that the extent is provided as list if not isinstance(raster_extent, list): - raise TypeError('The raster extent must be provided as list of corner values') + raise TypeError( + "The raster extent must be provided as list of corner values" + ) # Checking that all values are either ints or floats if not all(isinstance(n, (int, float)) for n in raster_extent): - raise TypeError('Bounds values must be of type int or float') + raise TypeError("Bounds values must be of type int or float") # Create column and row indices for clipping - column1 = int((bbox[0] - raster_extent[0]) / (raster_extent[1] - raster_extent[0]) * raster.shape[1]) - row1 = int((bbox[1] - raster_extent[2]) / (raster_extent[3] - raster_extent[2]) * raster.shape[0]) - column2 = int((bbox[2] - raster_extent[0]) / (raster_extent[1] - raster_extent[0]) * raster.shape[1]) - row2 = int((bbox[3] - raster_extent[2]) / (raster_extent[3] - raster_extent[2]) * raster.shape[0]) + column1 = int( + (bbox[0] - raster_extent[0]) + / (raster_extent[1] - raster_extent[0]) + * raster.shape[1] + ) + row1 = int( + (bbox[1] - raster_extent[2]) + / (raster_extent[3] - raster_extent[2]) + * raster.shape[0] + ) + column2 = int( + (bbox[2] - raster_extent[0]) + / (raster_extent[1] - raster_extent[0]) + * raster.shape[1] + ) + row2 = int( + (bbox[3] - raster_extent[2]) + / (raster_extent[3] - raster_extent[2]) + * raster.shape[0] + ) # Clip raster raster_clipped = raster[column1:row1, column2:row2] # Save raster if save_clipped_raster: - save_as_tiff(raster=raster_clipped, - path=path, - extent=bbox, - crs='EPSG:4326') + save_as_tiff(raster=raster_clipped, path=path, extent=bbox, crs="EPSG:4326") return raster_clipped -def clip_by_polygon(raster: Union[rasterio.io.DatasetReader, np.ndarray], - polygon: shapely.geometry.polygon.Polygon, - raster_extent: List[Union[int, float]] = None, - save_clipped_raster: bool = False, - path: str = 'raster_clipped.tif', - overwrite_file: bool = False, - create_directory: bool = False) -> np.ndarray: +def clip_by_polygon( + raster: Union[rasterio.io.DatasetReader, np.ndarray], + polygon: shapely.geometry.polygon.Polygon, + raster_extent: List[Union[int, float]] = None, + save_clipped_raster: bool = False, + path: str = "raster_clipped.tif", + overwrite_file: bool = False, + create_directory: bool = False, +) -> np.ndarray: """Clipping/masking a rasterio raster or np.ndarray by a given shapely Polygon Parameters @@ -1395,19 +1502,19 @@ def clip_by_polygon(raster: Union[rasterio.io.DatasetReader, np.ndarray], # Checking that the raster is of type np.ndarray or a rasterio object if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster must be of type np.ndarray or a rasterio object') + raise TypeError("Raster must be of type np.ndarray or a rasterio object") # Checking that the polygon is a Shapely Polygon if not isinstance(polygon, shapely.geometry.polygon.Polygon): - raise TypeError('Polygon must be a Shapely Polygon') + raise TypeError("Polygon must be a Shapely Polygon") # Checking that save_clipped_raster is of type bool if not isinstance(save_clipped_raster, bool): - raise TypeError('save_clipped_raster must either be True or False') + raise TypeError("save_clipped_raster must either be True or False") # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('The path must be provided as string') + raise TypeError("The path must be provided as string") # Getting the absolute path path = os.path.abspath(path=path) @@ -1424,48 +1531,66 @@ def clip_by_polygon(raster: Union[rasterio.io.DatasetReader, np.ndarray], if create_directory: os.makedirs(path_dir) else: - raise LookupError('Directory not found. Pass create_directory=True to create a new directory') + raise LookupError( + "Directory not found. Pass create_directory=True to create a new directory" + ) if not overwrite_file: if os.path.exists(path): raise FileExistsError( - "The file already exists. Pass overwrite_file=True to overwrite the existing file") + "The file already exists. Pass overwrite_file=True to overwrite the existing file" + ) # Masking raster if isinstance(raster, rasterio.io.DatasetReader): - raster_clipped, raster_transform = rasterio.mask.mask(dataset=raster, - shapes=[polygon], - crop=True, - filled=False, - pad=False, - pad_width=0) + raster_clipped, raster_transform = rasterio.mask.mask( + dataset=raster, + shapes=[polygon], + crop=True, + filled=False, + pad=False, + pad_width=0, + ) # Saving the raster if save_clipped_raster: # Updating meta data raster_clipped_meta = raster.meta - raster_clipped_meta.update({"driver": "GTiff", - "height": raster_clipped.shape[1], - "width": raster_clipped.shape[2], - "transform": raster_transform}) + raster_clipped_meta.update( + { + "driver": "GTiff", + "height": raster_clipped.shape[1], + "width": raster_clipped.shape[2], + "transform": raster_transform, + } + ) # Writing the raster to file with rasterio.open(path, "w", **raster_clipped_meta) as dest: dest.write(raster_clipped) # Swap axes and remove dimension - raster_clipped = np.flipud(np.rot90(np.swapaxes(raster_clipped, 0, 2)[:, :, 0], 1)) + raster_clipped = np.flipud( + np.rot90(np.swapaxes(raster_clipped, 0, 2)[:, :, 0], 1) + ) else: # Converting the polygon to a rectangular bbox - bbox = [polygon.bounds[0], polygon.bounds[2], polygon.bounds[1], polygon.bounds[2]] + bbox = [ + polygon.bounds[0], + polygon.bounds[2], + polygon.bounds[1], + polygon.bounds[2], + ] # Clipping raster - raster_clipped = clip_by_bbox(raster=raster, - bbox=bbox, - raster_extent=raster_extent, - save_clipped_raster=save_clipped_raster, - path=path) + raster_clipped = clip_by_bbox( + raster=raster, + bbox=bbox, + raster_extent=raster_extent, + save_clipped_raster=save_clipped_raster, + path=path, + ) return raster_clipped @@ -1474,8 +1599,10 @@ def clip_by_polygon(raster: Union[rasterio.io.DatasetReader, np.ndarray], ###################### -def resize_by_array(raster: Union[np.ndarray, rasterio.io.DatasetReader], - array: Union[np.ndarray, rasterio.io.DatasetReader]) -> np.ndarray: +def resize_by_array( + raster: Union[np.ndarray, rasterio.io.DatasetReader], + array: Union[np.ndarray, rasterio.io.DatasetReader], +) -> np.ndarray: """Rescaling raster to the size of another raster Parameters @@ -1525,23 +1652,23 @@ def resize_by_array(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if array1 is of type np.ndarray if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster must be of type np.ndarray or a rasterio object') + raise TypeError("Raster must be of type np.ndarray or a rasterio object") # Checking if array2 is of type np.ndarray if not isinstance(array, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('array must be of type np.ndarray or a rasterio object') + raise TypeError("array must be of type np.ndarray or a rasterio object") # Resize raster by shape of array - array_resized = resize_raster(raster=raster, - width=array.shape[1], - height=array.shape[0]) + array_resized = resize_raster( + raster=raster, width=array.shape[1], height=array.shape[0] + ) return array_resized -def resize_raster(raster: Union[np.ndarray, rasterio.io.DatasetReader], - width: int, - height: int) -> np.ndarray: +def resize_raster( + raster: Union[np.ndarray, rasterio.io.DatasetReader], width: int, height: int +) -> np.ndarray: """Resizing raster to given dimensions Parameters @@ -1592,11 +1719,12 @@ def resize_raster(raster: Union[np.ndarray, rasterio.io.DatasetReader], from skimage.transform import resize except ModuleNotFoundError: raise ModuleNotFoundError( - 'Scikit Image package is not installed. Use pip install scikit-image to install the latest version') + "Scikit Image package is not installed. Use pip install scikit-image to install the latest version" + ) # Checking if array1 is of type np.ndarray if not isinstance(raster, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Raster must be of type np.ndarray') + raise TypeError("Raster must be of type np.ndarray") # Converting rasterio object to array if isinstance(raster, rasterio.io.DatasetReader): @@ -1604,14 +1732,13 @@ def resize_raster(raster: Union[np.ndarray, rasterio.io.DatasetReader], # Checking if dimensions are of type int if not isinstance(width, int): - raise TypeError('Width must be of type int') + raise TypeError("Width must be of type int") if not isinstance(height, int): - raise TypeError('Height must be of type int') + raise TypeError("Height must be of type int") # Resizing the array - array_resized = resize(image=raster, - output_shape=(height, width)) + array_resized = resize(image=raster, output_shape=(height, width)) return array_resized @@ -1620,10 +1747,7 @@ def resize_raster(raster: Union[np.ndarray, rasterio.io.DatasetReader], ############################################# # Defining dtype Conversion -dtype_conversion = { - "Integer": np.int32, - "Double": np.float64 -} +dtype_conversion = {"Integer": np.int32, "Double": np.float64} def read_msh(path: Union[str, Path]) -> Dict[str, np.ndarray]: @@ -1676,7 +1800,7 @@ def read_msh(path: Union[str, Path]) -> Dict[str, np.ndarray]: # Checking that the path is of type string or a path if not isinstance(path, (str, Path)): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Getting the absolute path path = os.path.abspath(path=path) @@ -1694,15 +1818,16 @@ def read_msh(path: Union[str, Path]) -> Dict[str, np.ndarray]: f.seek(header_end + 0x14) # Extracting data from each line - for line in chunk[chunk.find(b"[index]") + 8:header_end].decode("utf-8").strip().split("\n"): + for line in ( + chunk[chunk.find(b"[index]") + 8 : header_end] + .decode("utf-8") + .strip() + .split("\n") + ): name, dtype, *shape = line.strip().rstrip(";").split() shape = list(map(int, reversed(shape))) dtype = dtype_conversion[dtype] - data[name] = np.fromfile( - f, - dtype, - np.prod(shape) - ).reshape(shape) + data[name] = np.fromfile(f, dtype, np.prod(shape)).reshape(shape) return data @@ -1761,7 +1886,7 @@ def read_ts(path: Union[str, Path]) -> Tuple[list, list]: # Checking that the path is of type string or a path if not isinstance(path, (str, Path)): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Getting the absolute path path = os.path.abspath(path=path) @@ -1790,7 +1915,10 @@ def read_ts(path: Union[str, Path]) -> Tuple[list, list]: if line_type == "PROPERTIES": columns += values - elif line_type == "TFACE": + # Deleting duplicate column names + columns = list(dict.fromkeys(columns)) + + elif line_type == "TFACE" or line_type == "END": # Creating array for faces faces = np.array(faces, dtype=np.int32) @@ -1868,7 +1996,7 @@ def read_asc(path: Union[str, Path]) -> dict: # Checking that the path is of type string or a path if not isinstance(path, (str, Path)): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Getting the absolute path path = os.path.abspath(path=path) @@ -1883,21 +2011,21 @@ def read_asc(path: Union[str, Path]) -> dict: if not line.strip(): continue line_value, *values = line.split() - if line_value == 'ncols': + if line_value == "ncols": ncols = int(values[0]) - if line_value == 'nrows': + if line_value == "nrows": nrows = int(values[0]) - if line_value == 'xllcenter': + if line_value == "xllcenter": xllcenter = float(values[0]) - if line_value == 'yllcenter': + if line_value == "yllcenter": yllcenter = float(values[0]) - if line_value == 'cellsize': + if line_value == "cellsize": res = float(values[0]) - if line_value == 'xllcorner': + if line_value == "xllcorner": xllcenter = float(values[0]) + 0.5 * res - if line_value == 'yllcorner': + if line_value == "yllcorner": yllcenter = float(values[0]) + 0.5 * res - if line_value == 'NODATA_value' or line_value == 'nodata_value': + if line_value == "NODATA_value" or line_value == "nodata_value": nodata_val = float(values[0]) # Load data and replace nodata_values with np.nan @@ -1905,10 +2033,17 @@ def read_asc(path: Union[str, Path]) -> dict: data[data == nodata_val] = np.nan # Creating dict and store data - data = {'Data': data, - 'Extent': [xllcenter, xllcenter + res * ncols, yllcenter, yllcenter + res * nrows], - 'Resolution': res, - 'Nodata_val': np.nan} + data = { + "Data": data, + "Extent": [ + xllcenter, + xllcenter + res * ncols, + yllcenter, + yllcenter + res * nrows, + ], + "Resolution": res, + "Nodata_val": np.nan, + } return data @@ -1966,7 +2101,7 @@ def read_zmap(path: Union[str, Path]) -> dict: # Checking that the path is of type string or a path if not isinstance(path, (str, Path)): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Getting the absolute path path = os.path.abspath(path=path) @@ -2007,20 +2142,22 @@ def read_zmap(path: Union[str, Path]) -> dict: # Getting array data data = [ - (float(d) if d.strip() != nodata else np.nan) for line in f for d in line.split() + (float(d) if d.strip() != nodata else np.nan) + for line in f + for d in line.split() ] # Creating dict for data data = { - 'Data': np.array(data).reshape((nrows, ncols), order="F"), - 'Extent': extent, - 'Resolution': resolution, - 'Nodata_val': float(nodata), - 'Dimensions': (nrows, ncols), - 'CRS': crs, - 'Creation_date': creation_date, - 'Creation_time': creation_time, - 'File_name': zmap_file_name + "Data": np.array(data).reshape((nrows, ncols), order="F"), + "Extent": extent, + "Resolution": resolution, + "Nodata_val": float(nodata), + "Dimensions": (nrows, ncols), + "CRS": crs, + "Creation_date": creation_date, + "Creation_time": creation_time, + "File_name": zmap_file_name, } return data @@ -2030,14 +2167,16 @@ def read_zmap(path: Union[str, Path]) -> dict: ################################ -def save_as_tiff(raster: np.ndarray, - path: str, - extent: Union[List[Union[int, float]], Tuple[Union[int, float]]], - crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS], - nodata: Union[float, int] = None, - transform=None, - overwrite_file: bool = False, - create_directory: bool = False): +def save_as_tiff( + raster: np.ndarray, + path: str, + extent: Union[List[Union[int, float]], Tuple[Union[int, float]]], + crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS], + nodata: Union[float, int] = None, + transform=None, + overwrite_file: bool = False, + create_directory: bool = False, +): """Saving a np.array as tif file Parameters @@ -2092,7 +2231,7 @@ def save_as_tiff(raster: np.ndarray, # Checking if path is of type string if not isinstance(path, str): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Checking that the file has the correct file ending if not path.endswith(".tif"): @@ -2109,57 +2248,62 @@ def save_as_tiff(raster: np.ndarray, if create_directory: os.makedirs(path_dir) else: - raise LookupError('Directory not found. Pass create_directory=True to create a new directory') + raise LookupError( + "Directory not found. Pass create_directory=True to create a new directory" + ) if not overwrite_file: if os.path.exists(path): raise FileExistsError( - "The file already exists. Pass overwrite_file=True to overwrite the existing file") + "The file already exists. Pass overwrite_file=True to overwrite the existing file" + ) # Checking if the array is of type np.ndarray if not isinstance(raster, np.ndarray): - raise TypeError('array must be of type np.ndarray') + raise TypeError("array must be of type np.ndarray") # Checking if the extent is of type list if not isinstance(extent, (list, tuple)): - raise TypeError('Extent must be of type list or be a tuple') + raise TypeError("Extent must be of type list or be a tuple") # Checking that all values are either ints or floats if not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Bound values must be of type int or float') + raise TypeError("Bound values must be of type int or float") # Checking if the crs is of type string if not isinstance(crs, (str, pyproj.crs.crs.CRS, rasterio.crs.CRS, dict)): - raise TypeError('CRS must be of type string, dict, rasterio CRS or pyproj CRS') + raise TypeError("CRS must be of type string, dict, rasterio CRS or pyproj CRS") # Extracting the bounds minx, miny, maxx, maxy = extent[0], extent[2], extent[1], extent[3] # Creating the transform if not transform: - transform = rasterio.transform.from_bounds(minx, miny, maxx, maxy, raster.shape[1], raster.shape[0]) + transform = rasterio.transform.from_bounds( + minx, miny, maxx, maxy, raster.shape[1], raster.shape[0] + ) # Creating and saving the array as tiff with rasterio.open( - path, - 'w', - driver='GTiff', - height=raster.shape[0], - width=raster.shape[1], - count=1, - dtype=raster.dtype, - crs=crs, - transform=transform, - nodata=nodata + path, + "w", + driver="GTiff", + height=raster.shape[0], + width=raster.shape[1], + count=1, + dtype=raster.dtype, + crs=crs, + transform=transform, + nodata=nodata, ) as dst: dst.write(np.flipud(raster), 1) - print('Raster successfully saved') + print("Raster successfully saved") -def create_filepaths(dirpath: str, - search_criteria: str, - create_directory: bool = False) -> List[str]: +def create_filepaths( + dirpath: str, search_criteria: str, create_directory: bool = False +) -> List[str]: """Retrieving the file paths of the tiles to load and to process them later Parameters @@ -2205,7 +2349,7 @@ def create_filepaths(dirpath: str, # Checking if dirpath is of type string if not isinstance(dirpath, str): - raise TypeError('Path to directory must be of type string') + raise TypeError("Path to directory must be of type string") # Getting the absolute path dirpath = os.path.abspath(path=dirpath) @@ -2218,11 +2362,13 @@ def create_filepaths(dirpath: str, if create_directory: os.makedirs(path_dir) else: - raise LookupError('Directory not found. Pass create_directory=True to create a new directory') + raise LookupError( + "Directory not found. Pass create_directory=True to create a new directory" + ) # Checking that the search criterion is of type string if not isinstance(search_criteria, str): - raise TypeError('Search Criterion must be of Type string') + raise TypeError("Search Criterion must be of Type string") # Join paths to form path to files source = os.path.join(dirpath, search_criteria) @@ -2233,10 +2379,12 @@ def create_filepaths(dirpath: str, return filepaths -def create_src_list(dirpath: str = '', - search_criteria: str = '', - filepaths: List[str] = None, - create_directory: bool = False) -> List[rasterio.io.DatasetReader]: +def create_src_list( + dirpath: str = "", + search_criteria: str = "", + filepaths: List[str] = None, + create_directory: bool = False, +) -> List[rasterio.io.DatasetReader]: """Creating a list of source files Parameters @@ -2293,7 +2441,7 @@ def create_src_list(dirpath: str = '', # Checking if dirpath is of type string if not isinstance(dirpath, str): - raise TypeError('Path to directory must be of type string') + raise TypeError("Path to directory must be of type string") # Getting the absolute path dirpath = os.path.abspath(path=dirpath) @@ -2306,24 +2454,27 @@ def create_src_list(dirpath: str = '', if create_directory: os.makedirs(path_dir) else: - raise LookupError('Directory not found. Pass create_directory=True to create a new directory') + raise LookupError( + "Directory not found. Pass create_directory=True to create a new directory" + ) # Checking that the search criterion is of type string if not isinstance(search_criteria, str): - raise TypeError('Search Criterion must be of Type string') + raise TypeError("Search Criterion must be of Type string") # Checking that the filepaths are of type list if not isinstance(filepaths, (list, type(None))): - raise TypeError('Filepaths must be of type list') + raise TypeError("Filepaths must be of type list") # Retrieving the file paths of the tiles - if not dirpath == '': - if not search_criteria == '': + if not dirpath == "": + if not search_criteria == "": if not filepaths: - filepaths = create_filepaths(dirpath=dirpath, - search_criteria=search_criteria) + filepaths = create_filepaths( + dirpath=dirpath, search_criteria=search_criteria + ) else: - raise ValueError('Either provide a file path or a list of filepaths') + raise ValueError("Either provide a file path or a list of filepaths") # Create empty list for source files src_files = [] @@ -2338,13 +2489,15 @@ def create_src_list(dirpath: str = '', return src_files -def merge_tiles(src_files: List[rasterio.io.DatasetReader], - extent: List[Union[float, int]] = None, - res: int = None, - nodata: Union[float, int] = None, - precision: int = None, - indices: int = None, - method: str = 'first') -> Tuple[np.ndarray, affine.Affine]: +def merge_tiles( + src_files: List[rasterio.io.DatasetReader], + extent: List[Union[float, int]] = None, + res: int = None, + nodata: Union[float, int] = None, + precision: int = None, + indices: int = None, + method: str = "first", +) -> Tuple[np.ndarray, affine.Affine]: """Merging downloaded tiles to mosaic Parameters @@ -2434,45 +2587,47 @@ def merge_tiles(src_files: List[rasterio.io.DatasetReader], # Checking if source files are stored in a list if not isinstance(src_files, list): - raise TypeError('Files must be stored as list') + raise TypeError("Files must be stored as list") # Checking if extent is a list if not isinstance(extent, (list, type(None))): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking that all values are either ints or floats if extent: if not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Extent values must be of type int or float') + raise TypeError("Extent values must be of type int or float") # Checking that the resolution is of type int if not isinstance(res, (int, type(None))): - raise TypeError('Resolution must be of type int') + raise TypeError("Resolution must be of type int") # Checking that the nodata value is of type int or float if not isinstance(nodata, (int, float, type(None))): - raise TypeError('Nodata value must be of type int or float') + raise TypeError("Nodata value must be of type int or float") # Checking that the precision is of type int if not isinstance(precision, (int, type(None))): - raise TypeError('Precision value must be of type int') + raise TypeError("Precision value must be of type int") # Checking that the indices for the bands are of type int if not isinstance(indices, (int, type(None))): - raise TypeError('Band indices must be of type int') + raise TypeError("Band indices must be of type int") # Checking that the method is of type string if not isinstance(method, (str, type(None))): - raise TypeError('Type of method must be provided as string') + raise TypeError("Type of method must be provided as string") # Merging tiles - mosaic, transformation = merge(src_files, - bounds=extent, - res=res, - nodata=nodata, - precision=precision, - indexes=indices, - method=method) + mosaic, transformation = merge( + src_files, + bounds=extent, + res=res, + nodata=nodata, + precision=precision, + indexes=indices, + method=method, + ) # Swap axes and remove dimension mosaic = np.flipud(np.rot90(np.swapaxes(mosaic, 0, 2)[:, 0:, 0], 1)) @@ -2480,11 +2635,13 @@ def merge_tiles(src_files: List[rasterio.io.DatasetReader], return mosaic, transformation -def reproject_raster(path_in: str, - path_out: str, - dst_crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS], - overwrite_file: bool = False, - create_directory: bool = False): +def reproject_raster( + path_in: str, + path_out: str, + dst_crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS], + overwrite_file: bool = False, + create_directory: bool = False, +): """Reprojecting a raster into different CRS Parameters @@ -2525,7 +2682,7 @@ def reproject_raster(path_in: str, # Checking that the path_in is of type string if not isinstance(path_in, str): - raise TypeError('The path of the source file must be of type string') + raise TypeError("The path of the source file must be of type string") # Getting the absolute path path_in = os.path.abspath(path=path_in) @@ -2536,7 +2693,7 @@ def reproject_raster(path_in: str, # Checking that the path_out is type string if not isinstance(path_out, str): - raise TypeError('The path of the destination file must be of type string') + raise TypeError("The path of the destination file must be of type string") # Getting the absolute path path_out = os.path.abspath(path=path_out) @@ -2553,31 +2710,34 @@ def reproject_raster(path_in: str, if create_directory: os.makedirs(path_dir_out) else: - raise LookupError('Directory not found. Pass create_directory=True to create a new directory') + raise LookupError( + "Directory not found. Pass create_directory=True to create a new directory" + ) if not overwrite_file: if os.path.exists(path_out): raise FileExistsError( - "The file already exists. Pass overwrite_file=True to overwrite the existing file") + "The file already exists. Pass overwrite_file=True to overwrite the existing file" + ) # Checking that the dst_crs is of type string or a pyproj object if not isinstance(dst_crs, (str, pyproj.crs.crs.CRS, rasterio.crs.CRS)): - raise TypeError('The destination CRS must be of type string, pyproj CRS or rasterio CRS') + raise TypeError( + "The destination CRS must be of type string, pyproj CRS or rasterio CRS" + ) # Opening the Source DataSet with rasterio.open(path_in) as src: transform, width, height = calculate_default_transform( - src.crs, dst_crs, src.width, src.height, *src.bounds) + src.crs, dst_crs, src.width, src.height, *src.bounds + ) kwargs = src.meta.copy() - kwargs.update({ - 'crs': dst_crs, - 'transform': transform, - 'width': width, - 'height': height - }) + kwargs.update( + {"crs": dst_crs, "transform": transform, "width": width, "height": height} + ) # Writing the Destination DataSet - with rasterio.open(path_out, 'w', **kwargs) as dst: + with rasterio.open(path_out, "w", **kwargs) as dst: for i in range(1, src.count + 1): reproject( source=rasterio.band(src, i), @@ -2586,14 +2746,16 @@ def reproject_raster(path_in: str, src_crs=src.crs, dst_transform=transform, dst_crs=dst_crs, - resampling=Resampling.nearest) + resampling=Resampling.nearest, + ) -def extract_contour_lines_from_raster(raster: Union[rasterio.io.DatasetReader, np.ndarray, str], - interval: int, - extent: Union[Optional[Sequence[float]], Optional[Sequence[int]]] = None, - target_crs: Union[ - str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None) -> gpd.GeoDataFrame: +def extract_contour_lines_from_raster( + raster: Union[rasterio.io.DatasetReader, np.ndarray, str], + interval: int, + extent: Union[Optional[Sequence[float]], Optional[Sequence[int]]] = None, + target_crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None, +) -> gpd.GeoDataFrame: """Extracting contour lines from raster with a provided interval. Parameters @@ -2625,11 +2787,14 @@ def extract_contour_lines_from_raster(raster: Union[rasterio.io.DatasetReader, n from skimage import measure except ModuleNotFoundError: raise ModuleNotFoundError( - 'skimage package is not installed. Use pip install skimage to install the latest version') + "skimage package is not installed. Use pip install skimage to install the latest version" + ) # Checking if provided raster is either a file loaded with rasterio, an np.ndarray or a path directing to a .tif file if not isinstance(raster, (rasterio.io.DatasetReader, np.ndarray, str)): - raise TypeError("Raster must be a raster loaded with rasterio or a path directing to a .tif file") + raise TypeError( + "Raster must be a raster loaded with rasterio or a path directing to a .tif file" + ) # Checking if provided raster is of type str. If provided raster is a path (directing to a .tif file), load the file with rasterio if isinstance(raster, str): @@ -2639,26 +2804,35 @@ def extract_contour_lines_from_raster(raster: Union[rasterio.io.DatasetReader, n if isinstance(raster, np.ndarray): if extent is None: raise UnboundLocalError( - "For np.ndarray an extent must be provided to extract contour lines from an array") + "For np.ndarray an extent must be provided to extract contour lines from an array" + ) if extent is not None and not isinstance(extent, Sequence): raise TypeError("extent values must be of type float or int") if len(extent) != 4: - raise TypeError("Not enough arguments in extent to extract contour lines from an array") + raise TypeError( + "Not enough arguments in extent to extract contour lines from an array" + ) if target_crs is None: raise UnboundLocalError("For np.ndarray a target crs must be provided") - if target_crs is not None and not isinstance(target_crs, (str, pyproj.crs.crs.CRS, rasterio.crs.CRS)): - raise TypeError("target_crs must be of type string, pyproj CRS or rasterio CRS") - - save_as_tiff(raster=np.flipud(raster), - path='input_raster.tif', - extent=extent, - crs=target_crs, - overwrite_file=True) - raster = rasterio.open('input_raster.tif') + if target_crs is not None and not isinstance( + target_crs, (str, pyproj.crs.crs.CRS, rasterio.crs.CRS) + ): + raise TypeError( + "target_crs must be of type string, pyproj CRS or rasterio CRS" + ) + + save_as_tiff( + raster=np.flipud(raster), + path="input_raster.tif", + extent=extent, + crs=target_crs, + overwrite_file=True, + ) + raster = rasterio.open("input_raster.tif") # Checking if provided interval is of type int if not isinstance(interval, int): @@ -2673,15 +2847,16 @@ def extract_contour_lines_from_raster(raster: Union[rasterio.io.DatasetReader, n values = [] # Calculating minimum and maximum value from the given raster value - min_val = int(interval * round(np.amin(raster.read(1)[~np.isnan(raster.read(1))]) / interval)) - max_val = int(interval * round(np.amax(raster.read(1)[~np.isnan(raster.read(1))]) / interval)) + min_val = int( + interval * round(np.amin(raster.read(1)[~np.isnan(raster.read(1))]) / interval) + ) + max_val = int( + interval * round(np.amax(raster.read(1)[~np.isnan(raster.read(1))]) / interval) + ) # Extracting contour lines and appending to lists - for value in range(min_val, - max_val, - interval): - contour = measure.find_contours(np.fliplr(raster.read(1).T), - value) + for value in range(min_val, max_val, interval): + contour = measure.find_contours(np.fliplr(raster.read(1).T), value) contours.append(contour) values.extend([value for i in range(len(contour))]) @@ -2696,28 +2871,32 @@ def extract_contour_lines_from_raster(raster: Union[rasterio.io.DatasetReader, n x_left, y_bottom, x_right, y_top = raster.bounds # Transforming and defining the coordinates of contours based on raster extent - x_new = [x_left + (x_right - x_left) / columns * contours_new[i][:, 0] for i in range(len(contours_new))] - y_new = [y_bottom + (y_top - y_bottom) / rows * contours_new[i][:, 1] for i in range(len(contours_new))] + x_new = [ + x_left + (x_right - x_left) / columns * contours_new[i][:, 0] + for i in range(len(contours_new)) + ] + y_new = [ + y_bottom + (y_top - y_bottom) / rows * contours_new[i][:, 1] + for i in range(len(contours_new)) + ] # Converting the contours to lines (LineStrings - Shapely) - lines = [LineString(np.array([x_new[i], - y_new[i]]).T) for i in range(len(x_new))] + lines = [LineString(np.array([x_new[i], y_new[i]]).T) for i in range(len(x_new))] # Creating GeoDataFrame from lines - gdf_lines = gpd.GeoDataFrame(geometry=lines, - crs=raster.crs) + gdf_lines = gpd.GeoDataFrame(geometry=lines, crs=raster.crs) # Adding value column to GeoDataframe - gdf_lines['Z'] = values + gdf_lines["Z"] = values return gdf_lines -def read_raster_gdb(path: str, - crs: Union[str, - pyproj.crs.crs.CRS, - rasterio.crs.CRS] = None, - path_out: str = ''): +def read_raster_gdb( + path: str, + crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None, + path_out: str = "", +): """Read Raster from OpenFileGDB. Parameters @@ -2736,15 +2915,15 @@ def read_raster_gdb(path: str, try: from osgeo import gdal, osr except ModuleNotFoundError: - raise ModuleNotFoundError('osgeo package is not installed') + raise ModuleNotFoundError("osgeo package is not installed") # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('Path to the OpenFileGDB must be provided as string') + raise TypeError("Path to the OpenFileGDB must be provided as string") # Checking that the output path is of type string if not isinstance(path_out, str): - raise TypeError('Output path must be provided as string') + raise TypeError("Output path must be provided as string") # Opening Database ds = gdal.Open(path) @@ -2766,26 +2945,30 @@ def read_raster_gdb(path: str, # Creating CRS from projection or manually if dataset.GetProjection(): proj = osr.SpatialReference(wkt=dataset.GetProjection()) - epsg = proj.GetAttrValue('AUTHORITY', 1) - crs = 'EPSG:' + epsg + epsg = proj.GetAttrValue("AUTHORITY", 1) + crs = "EPSG:" + epsg else: if not crs: raise ValueError( - 'Raster does not have a projection, please provide a valid coordinate reference system') + "Raster does not have a projection, please provide a valid coordinate reference system" + ) # Saving raster to file with rasterio.open( - path_out + ds.GetSubDatasets()[i][1].replace(' ', '') + '.tif', - 'w', - driver='GTiff', - height=raster.shape[0], - width=raster.shape[1], - count=1, - dtype=raster.dtype, - crs=crs, - transform=affine.Affine.from_gdal(*dataset.GetGeoTransform()), - nodata=raster_band.GetNoDataValue() + path_out + ds.GetSubDatasets()[i][1].replace(" ", "") + ".tif", + "w", + driver="GTiff", + height=raster.shape[0], + width=raster.shape[1], + count=1, + dtype=raster.dtype, + crs=crs, + transform=affine.Affine.from_gdal(*dataset.GetGeoTransform()), + nodata=raster_band.GetNoDataValue(), ) as dst: dst.write(raster, 1) - print(ds.GetSubDatasets()[i][1].replace(' ', '') + '.tif successfully saved to file') \ No newline at end of file + print( + ds.GetSubDatasets()[i][1].replace(" ", "") + + ".tif successfully saved to file" + ) diff --git a/gemgis/utils.py b/gemgis/utils.py index 596b3e60..4bc5f0d7 100644 --- a/gemgis/utils.py +++ b/gemgis/utils.py @@ -37,9 +37,11 @@ __all__ = [series, crs] -def to_section_dict(gdf: gpd.geodataframe.GeoDataFrame, - section_column: str = 'section_name', - resolution: List[int] = None) -> dict: +def to_section_dict( + gdf: gpd.geodataframe.GeoDataFrame, + section_column: str = "section_name", + resolution: List[int] = None, +) -> dict: """Converting custom sections stored in Shape files to GemPy section_dicts Parameters @@ -85,49 +87,73 @@ def to_section_dict(gdf: gpd.geodataframe.GeoDataFrame, # Checking if gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking if the section_column is of type string if not isinstance(section_column, str): - raise TypeError('Name for section_column must be of type string') + raise TypeError("Name for section_column must be of type string") # Checking if resolution is of type list if not isinstance(resolution, (list, type(None))): - raise TypeError('Resolution must be of type list') + raise TypeError("Resolution must be of type list") # Setting resolution if resolution is None: resolution = [100, 80] # Checking if X and Y values are in column - if not {'X', 'Y'}.issubset(gdf.columns): + if not {"X", "Y"}.issubset(gdf.columns): gdf = vector.extract_xy(gdf) # Checking the length of the resolution list if len(resolution) != 2: - raise ValueError('resolution list must be of length two') + raise ValueError("resolution list must be of length two") # Extracting Section names section_names = gdf[section_column].unique() # Create section dicts for Point Shape Files if all(gdf.geom_type == "Point"): - section_dict = {i: ([gdf[gdf[section_column] == i].X.iloc[0], gdf[gdf[section_column] == i].Y.iloc[0]], - [gdf[gdf[section_column] == i].X.iloc[1], gdf[gdf[section_column] == i].Y.iloc[1]], - resolution) for i in section_names} + section_dict = { + i: ( + [ + gdf[gdf[section_column] == i].X.iloc[0], + gdf[gdf[section_column] == i].Y.iloc[0], + ], + [ + gdf[gdf[section_column] == i].X.iloc[1], + gdf[gdf[section_column] == i].Y.iloc[1], + ], + resolution, + ) + for i in section_names + } # Create section dicts for Line Shape Files else: - section_dict = {i: ([gdf[gdf[section_column] == i].X.iloc[0], gdf[gdf[section_column] == i].Y.iloc[0]], - [gdf[gdf[section_column] == i].X.iloc[1], gdf[gdf[section_column] == i].Y.iloc[1]], - resolution) for i in section_names} + section_dict = { + i: ( + [ + gdf[gdf[section_column] == i].X.iloc[0], + gdf[gdf[section_column] == i].Y.iloc[0], + ], + [ + gdf[gdf[section_column] == i].X.iloc[1], + gdf[gdf[section_column] == i].Y.iloc[1], + ], + resolution, + ) + for i in section_names + } return section_dict -def convert_to_gempy_df(gdf: gpd.geodataframe.GeoDataFrame, - dem: Union[rasterio.io.DatasetReader, np.ndarray] = None, - extent: List[Union[float, int]] = None) -> pd.DataFrame: +def convert_to_gempy_df( + gdf: gpd.geodataframe.GeoDataFrame, + dem: Union[rasterio.io.DatasetReader, np.ndarray] = None, + extent: List[Union[float, int]] = None, +) -> pd.DataFrame: """Converting a GeoDataFrame into a Pandas DataFrame ready to be read in for GemPy Parameters @@ -190,80 +216,81 @@ def convert_to_gempy_df(gdf: gpd.geodataframe.GeoDataFrame, # Checking if gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking that the dem is a np.ndarray if not isinstance(dem, (np.ndarray, rasterio.io.DatasetReader, type(None))): - raise TypeError('DEM must be a numpy.ndarray or rasterio object') + raise TypeError("DEM must be a numpy.ndarray or rasterio object") # Checking if extent is a list if not isinstance(extent, (list, type(None))): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Extracting Coordinates from gdf - if not {'X', 'Y', 'Z'}.issubset(gdf.columns): + if not {"X", "Y", "Z"}.issubset(gdf.columns): # Extracting coordinates from array if isinstance(dem, np.ndarray): if not isinstance(extent, type(None)): - gdf = vector.extract_xyz(gdf=gdf, - dem=dem, - extent=extent) + gdf = vector.extract_xyz(gdf=gdf, dem=dem, extent=extent) else: - raise FileNotFoundError('Extent not provided') + raise FileNotFoundError("Extent not provided") # Extracting coordinates from raster elif isinstance(dem, rasterio.io.DatasetReader): - gdf = vector.extract_xyz(gdf=gdf, - dem=dem) + gdf = vector.extract_xyz(gdf=gdf, dem=dem) else: - raise FileNotFoundError('DEM not provided') + raise FileNotFoundError("DEM not provided") # Checking if the formation column is in the gdf and setting type - if 'formation' not in gdf: - raise ValueError('Formation names not defined') + if "formation" not in gdf: + raise ValueError("Formation names not defined") else: - gdf['formation'] = gdf['formation'].astype(str) + gdf["formation"] = gdf["formation"].astype(str) # Checking if the dip column is in the gdf and setting type - if 'dip' in gdf: - gdf['dip'] = gdf['dip'].astype(float) + if "dip" in gdf: + gdf["dip"] = gdf["dip"].astype(float) # Checking if the azimuth column is in the gdf and setting type - if 'azimuth' in gdf: - gdf['azimuth'] = gdf['azimuth'].astype(float) + if "azimuth" in gdf: + gdf["azimuth"] = gdf["azimuth"].astype(float) # Checking if DataFrame is an orientation or interfaces df - if 'dip' in gdf: + if "dip" in gdf: - if (gdf['dip'] > 90).any(): - raise ValueError('dip values exceed 90 degrees') - if 'azimuth' not in gdf: - raise ValueError('azimuth values not defined') - if (gdf['azimuth'] > 360).any(): - raise ValueError('azimuth values exceed 360 degrees') + if (gdf["dip"] > 90).any(): + raise ValueError("dip values exceed 90 degrees") + if "azimuth" not in gdf: + raise ValueError("azimuth values not defined") + if (gdf["azimuth"] > 360).any(): + raise ValueError("azimuth values exceed 360 degrees") # Create orientations dataframe - if 'polarity' not in gdf: - df = pd.DataFrame(gdf[['X', 'Y', 'Z', 'formation', 'dip', 'azimuth']]) - df['polarity'] = 1 + if "polarity" not in gdf: + df = pd.DataFrame(gdf[["X", "Y", "Z", "formation", "dip", "azimuth"]]) + df["polarity"] = 1 return df else: - df = pd.DataFrame(gdf[['X', 'Y', 'Z', 'formation', 'dip', 'azimuth', 'polarity']]) + df = pd.DataFrame( + gdf[["X", "Y", "Z", "formation", "dip", "azimuth", "polarity"]] + ) return df else: # Create interfaces dataframe - df = pd.DataFrame(gdf[['X', 'Y', 'Z', 'formation']]) + df = pd.DataFrame(gdf[["X", "Y", "Z", "formation"]]) return df -def set_extent(minx: Union[int, float] = 0, - maxx: Union[int, float] = 0, - miny: Union[int, float] = 0, - maxy: Union[int, float] = 0, - minz: Union[int, float] = 0, - maxz: Union[int, float] = 0, - gdf: gpd.geodataframe.GeoDataFrame = None) -> List[Union[int, float]]: +def set_extent( + minx: Union[int, float] = 0, + maxx: Union[int, float] = 0, + miny: Union[int, float] = 0, + maxy: Union[int, float] = 0, + minz: Union[int, float] = 0, + maxz: Union[int, float] = 0, + gdf: gpd.geodataframe.GeoDataFrame = None, +) -> List[Union[int, float]]: """Setting the extent for a model Parameters @@ -311,11 +338,13 @@ def set_extent(minx: Union[int, float] = 0, # Checking that the GeoDataFrame is a gdf or of type None if not isinstance(gdf, (type(None), gpd.geodataframe.GeoDataFrame)): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking if bounds are of type int or float - if not all(isinstance(i, (int, float)) for i in [minx, maxx, miny, maxy, minz, maxz]): - raise TypeError('bounds must be of type int or float') + if not all( + isinstance(i, (int, float)) for i in [minx, maxx, miny, maxy, minz, maxz] + ): + raise TypeError("bounds must be of type int or float") # Checking if the gdf is of type None if isinstance(gdf, type(None)): @@ -334,15 +363,17 @@ def set_extent(minx: Union[int, float] = 0, # Create extent from gdf of geom_type point or linestring else: bounds = gdf.bounds - extent = [round(bounds.minx.min(), 2), round(bounds.maxx.max(), 2), round(bounds.miny.min(), 2), - round(bounds.maxy.max(), 2)] + extent = [ + round(bounds.minx.min(), 2), + round(bounds.maxx.max(), 2), + round(bounds.miny.min(), 2), + round(bounds.maxy.max(), 2), + ] return extent -def set_resolution(x: int, - y: int, - z: int) -> List[int]: +def set_resolution(x: int, y: int, z: int) -> List[int]: """Setting the resolution for a model Parameters @@ -378,15 +409,15 @@ def set_resolution(x: int, # Checking if x is of type int if not isinstance(x, int): - raise TypeError('X must be of type int') + raise TypeError("X must be of type int") # Checking if y is of type int if not isinstance(y, int): - raise TypeError('Y must be of type int') + raise TypeError("Y must be of type int") # Checking if y is of type int if not isinstance(z, int): - raise TypeError('Z must be of type int') + raise TypeError("Z must be of type int") # Create list of resolution values resolution = [x, y, z] @@ -394,12 +425,14 @@ def set_resolution(x: int, return resolution -def read_csv_as_gdf(path: str, - crs: Union[str, pyproj.crs.crs.CRS], - x: str = 'X', - y: str = 'Y', - z: str = None, - delimiter: str = ',') -> gpd.geodataframe.GeoDataFrame: +def read_csv_as_gdf( + path: str, + crs: Union[str, pyproj.crs.crs.CRS], + x: str = "X", + y: str = "Y", + z: str = None, + delimiter: str = ",", +) -> gpd.geodataframe.GeoDataFrame: """Reading CSV files as GeoDataFrame Parameters @@ -449,7 +482,7 @@ def read_csv_as_gdf(path: str, # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('Path must be provided as string') + raise TypeError("Path must be provided as string") # Getting the absolute path path = os.path.abspath(path=path) @@ -460,45 +493,43 @@ def read_csv_as_gdf(path: str, # Checking that the file exists if not os.path.exists(path): - raise FileNotFoundError('File not found') + raise FileNotFoundError("File not found") # Checking that the x column is of type string if not isinstance(x, str): - raise TypeError('X column name must be provided as string') + raise TypeError("X column name must be provided as string") # Checking that the y column is of type string if not isinstance(y, str): - raise TypeError('Y column name must be provided as string') + raise TypeError("Y column name must be provided as string") # Checking that the z column is of type string if not isinstance(z, (str, type(None))): - raise TypeError('Z column name must be provided as string') + raise TypeError("Z column name must be provided as string") # Checking that the crs is provided as string if not isinstance(crs, (str, pyproj.crs.crs.CRS)): - raise TypeError('CRS must be provided as string or pyproj CRS object') + raise TypeError("CRS must be provided as string or pyproj CRS object") # Checking that the delimiter is of type string if not isinstance(delimiter, str): - raise TypeError('Delimiter must be of type string') + raise TypeError("Delimiter must be of type string") # Loading the csv file - df = pd.read_csv(filepath_or_buffer=path, - sep=delimiter) + df = pd.read_csv(filepath_or_buffer=path, sep=delimiter) # Checking that the file loaded is a DataFrame if not isinstance(df, pd.DataFrame): - raise TypeError('df must be of type DataFrame') + raise TypeError("df must be of type DataFrame") # Create GeoDataFrame if (x in df) and (y in df): - gdf = gpd.GeoDataFrame(data=df, - geometry=gpd.points_from_xy(x=df[x], - y=df[y], - crs=crs)) + gdf = gpd.GeoDataFrame( + data=df, geometry=gpd.points_from_xy(x=df[x], y=df[y], crs=crs) + ) else: - raise ValueError('X and/or Y columns could not be found') + raise ValueError("X and/or Y columns could not be found") return gdf @@ -541,11 +572,11 @@ def show_number_of_data_points(geo_model): """ # Trying to import gempy but returning error if gempy is not installed - try: - import gempy as gp - except ModuleNotFoundError: - raise ModuleNotFoundError( - 'GemPy package is not installed. Use pip install gempy to install the latest version') + # try: + # import gempy as gp + # except ModuleNotFoundError: + # raise ModuleNotFoundError( + # 'GemPy package is not installed. Use pip install gempy to install the latest version') # Create empty lists to store values no_int = [] @@ -553,26 +584,32 @@ def show_number_of_data_points(geo_model): # Store values of number of interfaces and orientations in list for i in geo_model.surfaces.df.surface.unique(): - length = len(geo_model.surface_points.df[geo_model.surface_points.df['surface'] == i]) + length = len( + geo_model.surface_points.df[geo_model.surface_points.df["surface"] == i] + ) no_int.append(length) - length = len(geo_model.orientations.df[geo_model.orientations.df['surface'] == i]) + length = len( + geo_model.orientations.df[geo_model.orientations.df["surface"] == i] + ) no_ori.append(length) # Copying GeoDataFrame gdf = geo_model.surfaces.df.copy(deep=True) # Add columns to geo_model surface table - gdf['No. of Interfaces'] = no_int - gdf['No. of Orientations'] = no_ori + gdf["No. of Interfaces"] = no_int + gdf["No. of Orientations"] = no_ori return gdf -def getfeatures(extent: Union[List[Union[int, float]], type(None)], - crs_raster: Union[str, dict], - crs_bbox: Union[str, dict], - bbox: shapely.geometry.polygon.Polygon = None) -> list: +def getfeatures( + extent: Union[List[Union[int, float]], type(None)], + crs_raster: Union[str, dict], + crs_bbox: Union[str, dict], + bbox: shapely.geometry.polygon.Polygon = None, +) -> list: """Creating a list containing a dict with keys and values to clip a raster Parameters @@ -603,23 +640,23 @@ def getfeatures(extent: Union[List[Union[int, float]], type(None)], # Checking if extent is of type list if not isinstance(extent, (list, type(None))): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking if bounds are of type int or float if not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Bounds must be of type int or float') + raise TypeError("Bounds must be of type int or float") # Checking if the raster crs is of type string or dict if not isinstance(crs_raster, (str, dict, rasterio.crs.CRS)): - raise TypeError('Raster CRS must be of type dict or string') + raise TypeError("Raster CRS must be of type dict or string") # Checking if the bbox crs is of type string or dict if not isinstance(crs_bbox, (str, dict, rasterio.crs.CRS)): - raise TypeError('Bbox CRS must be of type dict or string') + raise TypeError("Bbox CRS must be of type dict or string") # Checking if the bbox is of type none or a shapely polygon if not isinstance(bbox, (shapely.geometry.polygon.Polygon, type(None))): - raise TypeError('Bbox must be a shapely polygon') + raise TypeError("Bbox must be a shapely polygon") # Create bbox if bbox is not provided if isinstance(bbox, type(None)): @@ -628,7 +665,7 @@ def getfeatures(extent: Union[List[Union[int, float]], type(None)], # Checking if the bbox is a shapely box if not isinstance(bbox, shapely.geometry.polygon.Polygon): - raise TypeError('Bbox is not of type shapely box') + raise TypeError("Bbox is not of type shapely box") # Converting to dict if isinstance(crs_raster, rasterio.crs.CRS): @@ -639,17 +676,17 @@ def getfeatures(extent: Union[List[Union[int, float]], type(None)], # Converting raster crs to dict if isinstance(crs_raster, str): - crs_raster = {'init': crs_raster} + crs_raster = {"init": crs_raster} # Converting bbox raster to dict if isinstance(crs_bbox, str): - crs_bbox = {'init': crs_bbox} + crs_bbox = {"init": crs_bbox} # Creating GeoDataFrame - gdf = gpd.GeoDataFrame({'geometry': bbox}, index=[0], crs=crs_bbox) + gdf = gpd.GeoDataFrame({"geometry": bbox}, index=[0], crs=crs_bbox) gdf = gdf.to_crs(crs=crs_raster) - data = [json.loads(gdf.to_json())['features'][0]['geometry']] + data = [json.loads(gdf.to_json())["features"][0]["geometry"]] return data @@ -657,6 +694,7 @@ def getfeatures(extent: Union[List[Union[int, float]], type(None)], # Parsing QGIS Style Files ######################## + def parse_categorized_qml(qml_name: str) -> tuple: """Parsing a QGIS style file to retrieve surface color values @@ -714,11 +752,12 @@ def parse_categorized_qml(qml_name: str) -> tuple: import xmltodict except ModuleNotFoundError: raise ModuleNotFoundError( - 'xmltodict package is not installed. Use pip install xmltodict to install the latest version') + "xmltodict package is not installed. Use pip install xmltodict to install the latest version" + ) # Checking if the path was provided as string if not isinstance(qml_name, str): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Getting the absolute path path = os.path.abspath(path=qml_name) @@ -729,7 +768,7 @@ def parse_categorized_qml(qml_name: str) -> tuple: # Checking that the file exists if not os.path.exists(path): - raise FileNotFoundError('File not found') + raise FileNotFoundError("File not found") # Opening the file with open(qml_name, "rb") as f: @@ -740,15 +779,13 @@ def parse_categorized_qml(qml_name: str) -> tuple: # Extracting symbols symbols = { - symbol["@name"]: { - prop["@k"]: prop["@v"] for prop in symbol["layer"]["prop"] - } + symbol["@name"]: {prop["@k"]: prop["@v"] for prop in symbol["layer"]["prop"]} for symbol in qml["qgis"]["renderer-v2"]["symbols"]["symbol"] } # Extracting styles classes = { - category['@value']: symbols[category['@symbol']] + category["@value"]: symbols[category["@symbol"]] for category in qml["qgis"]["renderer-v2"]["categories"]["category"] } @@ -816,7 +853,7 @@ def build_style_dict(classes: dict) -> dict: # Checking if classes is of type dict if not isinstance(classes, dict): - raise TypeError('Classes must be of type dict') + raise TypeError("Classes must be of type dict") # Create empty styles dict styles_dict = {} @@ -832,14 +869,13 @@ def build_style_dict(classes: dict) -> dict: "opacity": opacity / 255, "weight": float(style["outline_width"]), "fillColor": f"#{fillColor[0]:02x}{fillColor[1]:02x}{fillColor[2]:02x}", - "fillOpacity": fill_opacity / 255 + "fillOpacity": fill_opacity / 255, } return styles_dict -def load_surface_colors(path: str, - gdf: gpd.geodataframe.GeoDataFrame) -> List[str]: +def load_surface_colors(path: str, gdf: gpd.geodataframe.GeoDataFrame) -> List[str]: """Loading surface colors from a QML file and storing the color values as list to be displayed with GeoPandas plots Parameters @@ -883,7 +919,7 @@ def load_surface_colors(path: str, # Checking that the path is of type str if not isinstance(path, str): - raise TypeError('path must be provided as string') + raise TypeError("path must be provided as string") # Getting the absolute path path = os.path.abspath(path=path) @@ -894,11 +930,11 @@ def load_surface_colors(path: str, # Checking that the file exists if not os.path.exists(path): - raise FileNotFoundError('File not found') + raise FileNotFoundError("File not found") # Checking that the gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('object must be of type GeoDataFrame') + raise TypeError("object must be of type GeoDataFrame") # Parse qml column, classes = parse_categorized_qml(qml_name=path) @@ -919,7 +955,7 @@ def load_surface_colors(path: str, gdf_copy = gdf_copy.groupby([column], as_index=False).last() # Create list of remaining colors - cols = gdf_copy['Color'].to_list() + cols = gdf_copy["Color"].to_list() return cols @@ -961,7 +997,7 @@ def create_surface_color_dict(path: str) -> dict: # Checking that the path is of type str if not isinstance(path, str): - raise TypeError('path must be provided as string') + raise TypeError("path must be provided as string") # Getting the absolute path path = os.path.abspath(path=path) @@ -972,7 +1008,7 @@ def create_surface_color_dict(path: str) -> dict: # Checking that the file exists if not os.path.exists(path): - raise FileNotFoundError('File not found') + raise FileNotFoundError("File not found") # Parse qml columns, classes = parse_categorized_qml(qml_name=path) @@ -1030,11 +1066,12 @@ def get_location_coordinate(name: str): import geopy except ModuleNotFoundError: raise ModuleNotFoundError( - 'GeoPy package is not installed. Use pip install geopy to install the latest version') + "GeoPy package is not installed. Use pip install geopy to install the latest version" + ) # Checking that the location name is of type string if not isinstance(name, str): - raise TypeError('Location name must be of type string') + raise TypeError("Location name must be of type string") # Create geocoder for OpenStreetMap data geolocator = geopy.geocoders.Nominatim(user_agent=name) @@ -1045,8 +1082,9 @@ def get_location_coordinate(name: str): return coordinates -def transform_location_coordinate(coordinates, - crs: Union[str, pyproj.crs.crs.CRS]) -> dict: +def transform_location_coordinate( + coordinates, crs: Union[str, pyproj.crs.crs.CRS] +) -> dict: """Transforming coordinates of GeoPy Location Parameters @@ -1097,18 +1135,19 @@ def transform_location_coordinate(coordinates, import geopy except ModuleNotFoundError: raise ModuleNotFoundError( - 'GeoPy package is not installed. Use pip install geopy to install the latest version') + "GeoPy package is not installed. Use pip install geopy to install the latest version" + ) # Checking that coordinates object is a GeoPy location object if not isinstance(coordinates, geopy.location.Location): - raise TypeError('The location must be provided as GeoPy Location object') + raise TypeError("The location must be provided as GeoPy Location object") # Checking that the target crs is provided as string if not isinstance(crs, str): - raise TypeError('Target CRS must be of type string') + raise TypeError("Target CRS must be of type string") # Setting source and target projection - transformer = pyproj.Transformer.from_crs('EPSG:4326', crs) + transformer = pyproj.Transformer.from_crs("EPSG:4326", crs) # Transforming coordinate systems long, lat = transformer.transform(coordinates.latitude, coordinates.longitude) @@ -1164,21 +1203,27 @@ def create_polygon_from_location(coordinates) -> shapely.geometry.polygon.Polygo import geopy except ModuleNotFoundError: raise ModuleNotFoundError( - 'GeoPy package is not installed. Use pip install geopy to install the latest version') + "GeoPy package is not installed. Use pip install geopy to install the latest version" + ) # Checking that coordinates object is a GeoPy location object if not isinstance(coordinates, geopy.location.Location): - raise TypeError('The location must be provided as GeoPy Location object') + raise TypeError("The location must be provided as GeoPy Location object") # Create polygon from boundingbox - polygon = box(float(coordinates.raw['boundingbox'][0]), float(coordinates.raw['boundingbox'][2]), - float(coordinates.raw['boundingbox'][1]), float(coordinates.raw['boundingbox'][3])) + polygon = box( + float(coordinates.raw["boundingbox"][0]), + float(coordinates.raw["boundingbox"][2]), + float(coordinates.raw["boundingbox"][1]), + float(coordinates.raw["boundingbox"][3]), + ) return polygon -def get_locations(names: Union[list, str], - crs: Union[str, pyproj.crs.crs.CRS] = 'EPSG:4326') -> dict: +def get_locations( + names: Union[list, str], crs: Union[str, pyproj.crs.crs.CRS] = "EPSG:4326" +) -> dict: """Obtaining coordinates for one city or a list of given cities. A CRS other than 'EPSG:4326' can be passed to transform the coordinates @@ -1224,35 +1269,43 @@ def get_locations(names: Union[list, str], # Checking that the location names are provided as list of strings or as string for one location if not isinstance(names, (list, str)): - raise TypeError('Names must be provided as list of strings') + raise TypeError("Names must be provided as list of strings") # Checking that the target CRS is provided as string if not isinstance(crs, str): - raise TypeError('Target CRS must be of type string') + raise TypeError("Target CRS must be of type string") if isinstance(names, list): # Create list of GeoPy locations coordinates_list = [get_location_coordinate(name=i) for i in names] # Transform CRS and create result_dict - if crs != 'EPSG:4326': - dict_list = [transform_location_coordinate(coordinates=i, - crs=crs) for i in coordinates_list] + if crs != "EPSG:4326": + dict_list = [ + transform_location_coordinate(coordinates=i, crs=crs) + for i in coordinates_list + ] result_dict = {k: v for d in dict_list for k, v in d.items()} else: - result_dict = {coordinates_list[i].address: (coordinates_list[i].longitude, - coordinates_list[i].latitude) - for i in range(len(coordinates_list))} + result_dict = { + coordinates_list[i].address: ( + coordinates_list[i].longitude, + coordinates_list[i].latitude, + ) + for i in range(len(coordinates_list)) + } else: # Create GeoPy Object coordinates = get_location_coordinate(name=names) - if crs != 'EPSG:4326': - result_dict = transform_location_coordinate(coordinates=coordinates, - crs=crs) + if crs != "EPSG:4326": + result_dict = transform_location_coordinate( + coordinates=coordinates, crs=crs + ) else: - result_dict = {coordinates.address: (coordinates.longitude, - coordinates.latitude)} + result_dict = { + coordinates.address: (coordinates.longitude, coordinates.latitude) + } return result_dict @@ -1299,21 +1352,21 @@ def convert_location_dict_to_gdf(location_dict: dict) -> gpd.geodataframe.GeoDat # Checking that the input data is of type dict if not isinstance(location_dict, dict): - raise TypeError('Input data must be provided as dict') + raise TypeError("Input data must be provided as dict") # Creating GeoDataFrame gdf = gpd.GeoDataFrame(data=location_dict).T.reset_index() # Assigning column names - gdf.columns = ['City', 'X', 'Y'] + gdf.columns = ["City", "X", "Y"] # Split city names to only show the name of the city - gdf['City'] = [i.split(',')[0] for i in gdf['City'].to_list()] + gdf["City"] = [i.split(",")[0] for i in gdf["City"].to_list()] # Recreate GeoDataFrame and set coordinates as geometry objects - gdf = gpd.GeoDataFrame(data=gdf, - geometry=gpd.points_from_xy(x=gdf['X'], - y=gdf['Y'])) + gdf = gpd.GeoDataFrame( + data=gdf, geometry=gpd.points_from_xy(x=gdf["X"], y=gdf["Y"]) + ) return gdf @@ -1322,8 +1375,7 @@ def convert_location_dict_to_gdf(location_dict: dict) -> gpd.geodataframe.GeoDat ############################ -def assign_properties(lith_block: np.ndarray, - property_dict: dict) -> np.ndarray: +def assign_properties(lith_block: np.ndarray, property_dict: dict) -> np.ndarray: """Replacing lith block IDs with physical properties Parameters @@ -1366,11 +1418,11 @@ def assign_properties(lith_block: np.ndarray, # Checking that the lith block is a NumPy array if not isinstance(lith_block, np.ndarray): - raise TypeError('Lith block must be a NumPy Array') + raise TypeError("Lith block must be a NumPy Array") # Checking that the properties dict is a dict if not isinstance(property_dict, dict): - raise TypeError('Properties must be provided as dict') + raise TypeError("Properties must be provided as dict") # Store shape shape = lith_block.shape @@ -1449,26 +1501,27 @@ def get_nearest_neighbor(x: np.ndarray, y: np.ndarray) -> np.int64: from sklearn.neighbors import NearestNeighbors except ModuleNotFoundError: raise ModuleNotFoundError( - 'Scikit Learn package is not installed. Use pip install scikit-learn to install the latest version') + "Scikit Learn package is not installed. Use pip install scikit-learn to install the latest version" + ) # Checking that the point data set x is of type np.ndarray if not isinstance(x, np.ndarray): - raise TypeError('Point data set must be of type np.ndarray') + raise TypeError("Point data set must be of type np.ndarray") # Checking that the shape of the array is correct if x.shape[1] != 2: - raise ValueError('Only coordinate pairs are allowed') + raise ValueError("Only coordinate pairs are allowed") # Checking that point y is of type np.ndarray if not isinstance(y, np.ndarray): - raise TypeError('Point data set must be of type np.ndarray') + raise TypeError("Point data set must be of type np.ndarray") # Checking that the shape of the array is correct if y.shape != (2,): - raise ValueError('y point must be of shape (2,)') + raise ValueError("y point must be of shape (2,)") # Finding the nearest neighbor with ball_tree algorithm - nbrs = NearestNeighbors(n_neighbors=1, algorithm='ball_tree').fit(y.reshape(1, -1)) + nbrs = NearestNeighbors(n_neighbors=1, algorithm="ball_tree").fit(y.reshape(1, -1)) # Calculating the distances and indices for to find the nearest neighbor distances, indices = nbrs.kneighbors(x) @@ -1479,9 +1532,11 @@ def get_nearest_neighbor(x: np.ndarray, y: np.ndarray) -> np.int64: return index -def calculate_number_of_isopoints(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], - increment: Union[float, int], - zcol: str = 'Z') -> int: +def calculate_number_of_isopoints( + gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], + increment: Union[float, int], + zcol: str = "Z", +) -> int: """Creating the number of isopoints to further interpolate strike lines Parameters @@ -1531,19 +1586,21 @@ def calculate_number_of_isopoints(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.D # Checking if gdf is of type GeoDataFrame if not isinstance(gdf, (gpd.geodataframe.GeoDataFrame, pd.DataFrame)): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking if the increment is of type float or int if not isinstance(increment, (float, int)): - raise TypeError('The increment must be provided as float or int') + raise TypeError("The increment must be provided as float or int") # Checking that the name of the Z column is provided as string if not isinstance(zcol, str): - raise TypeError('Z column name must be provided as string') + raise TypeError("Z column name must be provided as string") # Checking that the Z column is in the GeoDataFrame if zcol not in gdf: - raise ValueError('Provide name of Z column as kwarg as Z column could not be recognized') + raise ValueError( + "Provide name of Z column as kwarg as Z column could not be recognized" + ) # Creating a list with the unique heights of the GeoDataFrame heights = gdf[zcol].sort_values().unique().tolist() @@ -1554,11 +1611,13 @@ def calculate_number_of_isopoints(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.D return number -def calculate_lines(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], - increment: Union[float, int], - xcol: str = 'X', - ycol: str = 'Y', - zcol: str = 'Z') -> gpd.geodataframe.GeoDataFrame: +def calculate_lines( + gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], + increment: Union[float, int], + xcol: str = "X", + ycol: str = "Y", + zcol: str = "Z", +) -> gpd.geodataframe.GeoDataFrame: """Function to interpolate strike lines Parameters @@ -1606,27 +1665,27 @@ def calculate_lines(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], # Checking if gdf is of type GeoDataFrame if not isinstance(gdf, (gpd.geodataframe.GeoDataFrame, pd.DataFrame)): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking that all geometries are valid if not all(gdf.geometry.is_valid): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking if the increment is of type float or int if not isinstance(increment, (float, int)): - raise TypeError('The increment must be provided as float or int') + raise TypeError("The increment must be provided as float or int") # Checking that xcol is of type string if not isinstance(xcol, str): - raise TypeError('X column name must be of type string') + raise TypeError("X column name must be of type string") # Checking that ycol is of type string if not isinstance(ycol, str): - raise TypeError('Y column name must be of type string') + raise TypeError("Y column name must be of type string") # Checking that zcol is of type string if not isinstance(zcol, str): - raise TypeError('Z column name must be of type string') + raise TypeError("Z column name must be of type string") # Checking that the columns are in the GeoDataFrame # if not {xcol, ycol, zcol}.issubset(gdf.columns): @@ -1649,21 +1708,27 @@ def calculate_lines(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], # Calculating vertices of lines for i in range(len(gdf[gdf[zcol] == minval])): # Getting index for nearest neighbor - index = get_nearest_neighbor(np.array(gdf[gdf[zcol] == minval][[xcol, ycol]].values.tolist()), - np.array([gdf[gdf[zcol] == minval][xcol].values.tolist()[i], - gdf[gdf[zcol] == minval][ycol].values.tolist()[i]])) + index = get_nearest_neighbor( + np.array(gdf[gdf[zcol] == minval][[xcol, ycol]].values.tolist()), + np.array( + [ + gdf[gdf[zcol] == minval][xcol].values.tolist()[i], + gdf[gdf[zcol] == minval][ycol].values.tolist()[i], + ] + ), + ) # Creating x and y points from existing gdf - x1 = gdf[gdf['Z'] == minval][xcol].tolist()[i] - y1 = gdf[gdf['Z'] == minval][ycol].tolist()[i] - x2 = gdf[gdf['Z'] == maxval][xcol].tolist()[index] - y2 = gdf[gdf['Z'] == maxval][ycol].tolist()[index] + x1 = gdf[gdf["Z"] == minval][xcol].tolist()[i] + y1 = gdf[gdf["Z"] == minval][ycol].tolist()[i] + x2 = gdf[gdf["Z"] == maxval][xcol].tolist()[index] + y2 = gdf[gdf["Z"] == maxval][ycol].tolist()[index] # Calculating vertices of lines for j in range(num): # Calculating vertices - pointx = ((j + 1) / (num + 1) * x2 + (1 - (j + 1) / (num + 1)) * x1) - pointy = ((j + 1) / (num + 1) * y2 + (1 - (j + 1) / (num + 1)) * y1) + pointx = (j + 1) / (num + 1) * x2 + (1 - (j + 1) / (num + 1)) * x1 + pointy = (j + 1) / (num + 1) * y2 + (1 - (j + 1) / (num + 1)) * y1 # Append vertices to list pointsx.append(pointx) @@ -1676,8 +1741,9 @@ def calculate_lines(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], # Create linestring from point lists for i in range(0, int(len(pointsx) / 2)): # Creating linestrings - ls = LineString([Point(pointsx[i], pointsy[i]), - Point(pointsx[i + num], pointsy[i + num])]) + ls = LineString( + [Point(pointsx[i], pointsy[i]), Point(pointsx[i + num], pointsy[i + num])] + ) # Appending line strings ls_list.append(ls) heights.append(minval + i * increment + increment) @@ -1687,25 +1753,27 @@ def calculate_lines(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], lines = gpd.GeoDataFrame(gpd.GeoSeries(ls_list), crs=gdf.crs) # Setting geometry column of GeoDataFrame - lines['geometry'] = ls_list + lines["geometry"] = ls_list # Extracting X and Y coordinate and deleting first entry lines = vector.extract_xy(lines) del lines[0] # Adding formation and height information to GeoDataFrame - lines['formation'] = gdf['formation'].unique().tolist()[0] - lines['Z'] = heights - lines['id'] = heights + lines["formation"] = gdf["formation"].unique().tolist()[0] + lines["Z"] = heights + lines["id"] = heights return lines -def interpolate_strike_lines(gdf: gpd.geodataframe.GeoDataFrame, - increment: Union[float, int], - xcol: str = 'X', - ycol: str = 'Y', - zcol: str = 'Z') -> gpd.geodataframe.GeoDataFrame: +def interpolate_strike_lines( + gdf: gpd.geodataframe.GeoDataFrame, + increment: Union[float, int], + xcol: str = "X", + ycol: str = "Y", + zcol: str = "Z", +) -> gpd.geodataframe.GeoDataFrame: """Interpolating strike lines to calculate orientations Parameters @@ -1738,70 +1806,88 @@ def interpolate_strike_lines(gdf: gpd.geodataframe.GeoDataFrame, # Checking if gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking if the increment is of type float or int if not isinstance(increment, (float, int)): - raise TypeError('The increment must be provided as float or int') + raise TypeError("The increment must be provided as float or int") # Checking that xcol is of type string if not isinstance(xcol, str): - raise TypeError('X column name must be of type string') + raise TypeError("X column name must be of type string") # Checking that ycol is of type string if not isinstance(ycol, str): - raise TypeError('Y column name must be of type string') + raise TypeError("Y column name must be of type string") # Checking that zcol is of type string if not isinstance(zcol, str): - raise TypeError('Z column name must be of type string') + raise TypeError("Z column name must be of type string") # Create empty GeoDataFrame gdf_out = gpd.GeoDataFrame() # Extract vertices from gdf - gdf = vector.extract_xy(gdf, drop_id=False, reset_index=False).sort_values(by='id') + gdf = vector.extract_xy(gdf, drop_id=False, reset_index=False).sort_values(by="id") # Interpolate strike lines - for i in range(len(gdf['id'].unique().tolist()) - 1): + for i in range(len(gdf["id"].unique().tolist()) - 1): # Calculate distance between two strike lines in the original gdf - diff = gdf.loc[gdf.index.unique().values.tolist()[i]][zcol].values.tolist()[0] - \ - gdf.loc[gdf.index.unique().values.tolist()[i + 1]][zcol].values.tolist()[0] + diff = ( + gdf.loc[gdf.index.unique().values.tolist()[i]][zcol].values.tolist()[0] + - gdf.loc[gdf.index.unique().values.tolist()[i + 1]][zcol].values.tolist()[ + 0 + ] + ) # If the distance is larger than the increment, interpolate strike lines if np.abs(diff) > increment: gdf_strike = pd.concat( - [gdf.loc[gdf.index.unique().values.tolist()[i]], gdf.loc[gdf.index.unique().values.tolist()[i + 1]]]) + [ + gdf.loc[gdf.index.unique().values.tolist()[i]], + gdf.loc[gdf.index.unique().values.tolist()[i + 1]], + ] + ) # Calculate strike lines lines = calculate_lines(gdf_strike, increment) # Append interpolated lines to gdf that will be returned gdf_new = pd.concat( - [gdf.loc[gdf.index.unique().values.tolist()[i]], lines, - gdf.loc[gdf.index.unique().values.tolist()[i + 1]]]) + [ + gdf.loc[gdf.index.unique().values.tolist()[i]], + lines, + gdf.loc[gdf.index.unique().values.tolist()[i + 1]], + ] + ) gdf_out = gdf_out.append(gdf_new, ignore_index=True) # If the distance is equal to the increment, append line to the gdf that will be returned else: gdf_new = pd.concat( - [gdf.loc[gdf.index.unique().values.tolist()[i]], gdf.loc[gdf.index.unique().values.tolist()[i + 1]]]) + [ + gdf.loc[gdf.index.unique().values.tolist()[i]], + gdf.loc[gdf.index.unique().values.tolist()[i + 1]], + ] + ) gdf_out = gdf_out.append(gdf_new, ignore_index=True) # Drop duplicates - gdf_out = gdf_out.sort_values(by=['Y']).drop_duplicates('geometry') + gdf_out = gdf_out.sort_values(by=["Y"]).drop_duplicates("geometry") # Redefine ID column with interpolated strike lines - gdf_out['id'] = np.arange(1, len(gdf_out['id'].values.tolist()) + 1).tolist() + gdf_out["id"] = np.arange(1, len(gdf_out["id"].values.tolist()) + 1).tolist() return gdf_out -def convert_to_petrel_points_with_attributes(mesh: pv.core.pointset.PolyData, - path: str, - crs: Union[str, pyproj.crs.crs.CRS, type(None)] = None, - target_crs: Union[str, pyproj.crs.crs.CRS, type(None)] = None): +def convert_to_petrel_points_with_attributes( + mesh: pv.core.pointset.PolyData, + path: str, + crs: Union[str, pyproj.crs.crs.CRS, type(None)] = None, + target_crs: Union[str, pyproj.crs.crs.CRS, type(None)] = None, +): """Function to convert vertices of a PyVista Mesh to Petrel Points with Attributes Parameters @@ -1826,22 +1912,26 @@ def convert_to_petrel_points_with_attributes(mesh: pv.core.pointset.PolyData, # Checking that the mesh is a PyVista PolyData object if not isinstance(mesh, pv.core.pointset.PolyData): - raise TypeError('Mesh must be provided as PyVista PolyData object') + raise TypeError("Mesh must be provided as PyVista PolyData object") # Checking that the CRS is provided as proper type if not isinstance(crs, (str, pyproj.crs.crs.CRS, type(None))): - raise TypeError('CRS must be provided as string or pyproj CRS object') + raise TypeError("CRS must be provided as string or pyproj CRS object") # Checking that the target CRS is provided as proper type if not isinstance(target_crs, (str, pyproj.crs.crs.CRS, type(None))): - raise TypeError('CRS must be provided as string or pyproj CRS object') + raise TypeError("CRS must be provided as string or pyproj CRS object") # Selecting vertices vertices = np.array(mesh.points) # Creating GeoDataFrame from vertices - gdf = gpd.GeoDataFrame(geometry=gpd.points_from_xy(vertices[:, 0], vertices[:, 1]), data=vertices, - columns=['X', 'Y', 'Z'], crs=crs) + gdf = gpd.GeoDataFrame( + geometry=gpd.points_from_xy(vertices[:, 0], vertices[:, 1]), + data=vertices, + columns=["X", "Y", "Z"], + crs=crs, + ) # Reprojecting data and extracting X and Y coordinates if target_crs and target_crs != crs: @@ -1849,19 +1939,19 @@ def convert_to_petrel_points_with_attributes(mesh: pv.core.pointset.PolyData, gdf = vector.extract_xy(gdf=gdf) # Dropping Geometry Column - df = gdf.drop('geometry', axis=1) + df = gdf.drop("geometry", axis=1) - df.to_csv(fname=path, - index=False, - sep='\t') + df.to_csv(fname=path, index=False, sep="\t") - print('CSV-File successfully saved') + print("CSV-File successfully saved") -def ray_trace_one_surface(surface: Union[pv.core.pointset.PolyData, pv.core.pointset.UnstructuredGrid], - origin: Union[np.ndarray, list], - end_point: Union[np.ndarray, list], - first_point: bool = False) -> tuple: +def ray_trace_one_surface( + surface: Union[pv.core.pointset.PolyData, pv.core.pointset.UnstructuredGrid], + origin: Union[np.ndarray, list], + end_point: Union[np.ndarray, list], + first_point: bool = False, +) -> tuple: """Function to return the depth of one surface in one well using PyVista ray tracing Parameters @@ -1890,25 +1980,29 @@ def ray_trace_one_surface(surface: Union[pv.core.pointset.PolyData, pv.core.poin """ # Checking that the provided surface is of type PoyData or UnstructuredGrid - if not isinstance(surface, (pv.core.pointset.PolyData, pv.core.pointset.UnstructuredGrid)): - raise TypeError('Surface must be provided as PolyData or UnstructuredGrid') + if not isinstance( + surface, (pv.core.pointset.PolyData, pv.core.pointset.UnstructuredGrid) + ): + raise TypeError("Surface must be provided as PolyData or UnstructuredGrid") # Converting UnstructuredGrid to PolyData if isinstance(surface, pv.core.pointset.UnstructuredGrid): surface = surface.extract_surface() # Extracting the intersection between a PolyData set and a mesh - intersection_points, intersection_cells = surface.ray_trace(origin=origin, - end_point=end_point, - first_point=first_point) + intersection_points, intersection_cells = surface.ray_trace( + origin=origin, end_point=end_point, first_point=first_point + ) return intersection_points, intersection_cells -def ray_trace_multiple_surfaces(surfaces: list, - borehole_top: Union[np.ndarray, list], - borehole_bottom: Union[np.ndarray, list], - first_point: bool = False) -> list: +def ray_trace_multiple_surfaces( + surfaces: list, + borehole_top: Union[np.ndarray, list], + borehole_bottom: Union[np.ndarray, list], + first_point: bool = False, +) -> list: """Function to return the depth of multiple surfaces in one well using PyVista ray tracing Parameters @@ -1937,18 +2031,25 @@ def ray_trace_multiple_surfaces(surfaces: list, """ # Extracting multiple intersections from meshes - intersections = [ray_trace_one_surface(surface=surface, - origin=borehole_top, - end_point=borehole_bottom, - first_point=first_point) for surface in surfaces] + intersections = [ + ray_trace_one_surface( + surface=surface, + origin=borehole_top, + end_point=borehole_bottom, + first_point=first_point, + ) + for surface in surfaces + ] return intersections -def create_virtual_profile(names_surfaces: list, - surfaces: list, - borehole: pv.core.pointset.PolyData, - first_point: bool = False) -> pd.DataFrame: +def create_virtual_profile( + names_surfaces: list, + surfaces: list, + borehole: pv.core.pointset.PolyData, + first_point: bool = False, +) -> pd.DataFrame: """Function to filter and sort the resulting well tops Parameters @@ -1977,13 +2078,21 @@ def create_virtual_profile(names_surfaces: list, """ # Creating well segments - well_segments = [pv.Line(borehole.points[i], borehole.points[i + 1]) for i in range(len(borehole.points) - 1)] + well_segments = [ + pv.Line(borehole.points[i], borehole.points[i + 1]) + for i in range(len(borehole.points) - 1) + ] # Extracting well tops - well_tops = [ray_trace_multiple_surfaces(surfaces=surfaces, - borehole_top=segment.points[0], - borehole_bottom=segment.points[1], - first_point=first_point) for segment in well_segments] + well_tops = [ + ray_trace_multiple_surfaces( + surfaces=surfaces, + borehole_top=segment.points[0], + borehole_bottom=segment.points[1], + first_point=first_point, + ) + for segment in well_segments + ] # Flatten list well_tops = [item for sublist in well_tops for item in sublist] @@ -2023,14 +2132,18 @@ def create_virtual_profile(names_surfaces: list, # well_dict = dict(sorted(well_dict.items(), key=lambda item: item[1], reverse=True)) # Creating DataFrame - df = pd.DataFrame(list(zip(list_surfaces_filtered, z_values)), columns=['Surface', 'Z']) + df = pd.DataFrame( + list(zip(list_surfaces_filtered, z_values)), columns=["Surface", "Z"] + ) return df -def extract_zmap_data(surface: pv.core.pointset.PolyData, - cell_width: int, - nodata: Union[float, int] = -9999): +def extract_zmap_data( + surface: pv.core.pointset.PolyData, + cell_width: int, + nodata: Union[float, int] = -9999, +): """Function to extract a meshgrid of values from a PyVista mesh Parameters @@ -2071,29 +2184,40 @@ def extract_zmap_data(surface: pv.core.pointset.PolyData, y = np.arange(extent[2] + 0.5 * cell_width, extent[3], cell_width) # Calculating the intersections - intersections = [ray_trace_one_surface(surface=surface, - origin=[x_value, y_value, extent[4]], - end_point=[x_value, y_value, extent[5]], - first_point=True) for x_value in x for y_value in y] + intersections = [ + ray_trace_one_surface( + surface=surface, + origin=[x_value, y_value, extent[4]], + end_point=[x_value, y_value, extent[5]], + first_point=True, + ) + for x_value in x + for y_value in y + ] # Extracting the height values - z_values = np.flipud(np.array([z[0][2] if len(z[0]) == 3 else nodata for z in intersections]).reshape(x_no_cells, - y_no_cells).T) + z_values = np.flipud( + np.array([z[0][2] if len(z[0]) == 3 else nodata for z in intersections]) + .reshape(x_no_cells, y_no_cells) + .T + ) return z_values -def create_zmap_grid(surface: pv.core.pointset.PolyData, - cell_width: int, - comments: str = '', - name: str = 'ZMAP_Grid', - z_type: str = 'GRID', - nodes_per_line: int = 5, - field_width: int = 15, - nodata: Union[int, float] = -9999.00000, - nodata2: Union[int, float, str] = '', - decimal_places: int = 5, - start_column: int = 1): +def create_zmap_grid( + surface: pv.core.pointset.PolyData, + cell_width: int, + comments: str = "", + name: str = "ZMAP_Grid", + z_type: str = "GRID", + nodes_per_line: int = 5, + field_width: int = 15, + nodata: Union[int, float] = -9999.00000, + nodata2: Union[int, float, str] = "", + decimal_places: int = 5, + start_column: int = 1, +): """Function to write data to ZMAP Grid, This code is heavily inspired by https://github.com/abduhbm/zmapio Parameters @@ -2143,9 +2267,7 @@ def create_zmap_grid(surface: pv.core.pointset.PolyData, """ # Extracting z_values - z_values = extract_zmap_data(surface=surface, - cell_width=cell_width, - nodata=nodata) + z_values = extract_zmap_data(surface=surface, cell_width=cell_width, nodata=nodata) # Defining extent extent = surface.bounds @@ -2157,16 +2279,20 @@ def create_zmap_grid(surface: pv.core.pointset.PolyData, # Defining auxiliary function def chunks(x, n): for i in range(0, len(x), n): - yield x[i: i + n] + yield x[i : i + n] # Create list of lines with first comments - lines = ['!', '! This ZMAP Grid was created using the GemGIS Package', - '! See https://github.com/cgre-aachen/gemgis for more information', '!'] + lines = [ + "!", + "! This ZMAP Grid was created using the GemGIS Package", + "! See https://github.com/cgre-aachen/gemgis for more information", + "!", + ] # Appending comments to lines for comment in comments: - lines.append('! ' + comment) - lines.append('!') + lines.append("! " + comment) + lines.append("!") # Appending header information to lines lines.append("@{}, {}, {}".format(name, z_type, nodes_per_line)) @@ -2185,12 +2311,7 @@ def chunks(x, n): # Appending header information to lines lines.append( "{}, {}, {}, {}, {}, {}".format( - no_rows, - no_cols, - extent[0], - extent[1], - extent[2], - extent[3] + no_rows, no_cols, extent[0], extent[1], extent[2], extent[3] ) ) @@ -2203,15 +2324,21 @@ def chunks(x, n): for j in chunks(i, nodes_per_line): j_fmt = "0.{}f".format(decimal_places) j_fmt = "{0:" + j_fmt + "}" - j = [j_fmt.format(float(x)) if not x is np.nan else j_fmt.format(float(nodata)) for x in j] + j = [ + ( + j_fmt.format(float(x)) + if x is not np.nan + else j_fmt.format(float(nodata)) + ) + for x in j + ] line = "{:>" + "{}".format(field_width) + "}" lines.append("".join([line] * len(j)).format(*tuple(j))) return lines -def save_zmap_grid(zmap_grid: list, - path: str = 'ZMAP_Grid.dat'): +def save_zmap_grid(zmap_grid: list, path: str = "ZMAP_Grid.dat"): """Function to save ZMAP Grid information to file Parameters @@ -2228,20 +2355,22 @@ def save_zmap_grid(zmap_grid: list, """ # Writing the ZMAP Grid to file - with open(path, 'w') as f: + with open(path, "w") as f: f.write("\n".join(zmap_grid)) - print('ZMAP Grid successfully saved to file') + print("ZMAP Grid successfully saved to file") -def rotate_gempy_input_data(extent: Union[np.ndarray, shapely.geometry.Polygon, gpd.geodataframe.GeoDataFrame], - interfaces: Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame], - orientations: Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame], - zmin: Union[float, int] = None, - zmax: Union[float, int] = None, - rotate_reverse_direction: bool = False, - return_extent_gdf: bool = False, - manual_rotation_angle: Union[float, int] = None): +def rotate_gempy_input_data( + extent: Union[np.ndarray, shapely.geometry.Polygon, gpd.geodataframe.GeoDataFrame], + interfaces: Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame], + orientations: Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame], + zmin: Union[float, int] = None, + zmax: Union[float, int] = None, + rotate_reverse_direction: bool = False, + return_extent_gdf: bool = False, + manual_rotation_angle: Union[float, int] = None, +): """Function to rotate the GemPy Input Data horizontally or vertically Parameters @@ -2288,143 +2417,210 @@ def rotate_gempy_input_data(extent: Union[np.ndarray, shapely.geometry.Polygon, """ # Checking that the extent is of type list, Shapely Polygon, or GeoDataFrame - if not isinstance(extent, (np.ndarray, shapely.geometry.Polygon, gpd.geodataframe.GeoDataFrame)): - raise TypeError('The extent must be provided as NumPy array, Shapely Polygon oder GeoDataFrame') + if not isinstance( + extent, (np.ndarray, shapely.geometry.Polygon, gpd.geodataframe.GeoDataFrame) + ): + raise TypeError( + "The extent must be provided as NumPy array, Shapely Polygon oder GeoDataFrame" + ) # Checking the number of coordinates of the extent and convert extent to Shapely Polygpon if isinstance(extent, np.ndarray): if len(extent) != 4: - raise ValueError('Please only provide four corner coordinates as extent') + raise ValueError("Please only provide four corner coordinates as extent") extent_polygon = Polygon(extent) elif isinstance(extent, shapely.geometry.Polygon): - if not (len(list(extent.exterior.coords)) != 4) or (len(list(extent.exterior.coords)) != 5): - raise ValueError('Please only provide a polygon with four corner coordinates as extent') + if not (len(list(extent.exterior.coords)) != 4) or ( + len(list(extent.exterior.coords)) != 5 + ): + raise ValueError( + "Please only provide a polygon with four corner coordinates as extent" + ) extent_polygon = extent else: - if len(list(extent.iloc[0]['geometry'].exterior.coords)) != 5: - raise ValueError('Please only provide a polygon with four corner coordinates as extent') + if len(list(extent.iloc[0]["geometry"].exterior.coords)) != 5: + raise ValueError( + "Please only provide a polygon with four corner coordinates as extent" + ) - extent_polygon = extent.iloc[0]['geometry'] + extent_polygon = extent.iloc[0]["geometry"] # Checking that the interfaces are of type DataFrame or GeoDataFrame if not isinstance(interfaces, (pd.DataFrame, gpd.geodataframe.GeoDataFrame)): - raise TypeError('Interfaces must be provided as Pandas DataFrame or GeoPandas GeoDataFrame') + raise TypeError( + "Interfaces must be provided as Pandas DataFrame or GeoPandas GeoDataFrame" + ) # Extracting X, Y, Z coordinates if interfaces are of type GeoDataFrame - if (isinstance(interfaces, gpd.geodataframe.GeoDataFrame)) and (not {'X', 'Y', 'Z'}.issubset(interfaces.columns)): + if (isinstance(interfaces, gpd.geodataframe.GeoDataFrame)) and ( + not {"X", "Y", "Z"}.issubset(interfaces.columns) + ): interfaces = vector.extract_xy(interfaces) # Checking if X, Y, Z coordinates are in columns - if not {'X', 'Y', 'Z'}.issubset(interfaces.columns): - raise ValueError('Please provide all X, Y and Z coordinates in the Pandas DataFrame or GeoPandas GeoDataFrame') + if not {"X", "Y", "Z"}.issubset(interfaces.columns): + raise ValueError( + "Please provide all X, Y and Z coordinates in the Pandas DataFrame or GeoPandas GeoDataFrame" + ) # Checking that the orientations are of type DataFrame or GeoDataFrame if not isinstance(orientations, (pd.DataFrame, gpd.geodataframe.GeoDataFrame)): - raise TypeError('Orientations must be provided as Pandas DataFrame or GeoPandas GeoDataFrame') + raise TypeError( + "Orientations must be provided as Pandas DataFrame or GeoPandas GeoDataFrame" + ) # Extracting X, Y, Z coordinates if orientations are of type GeoDataFrame if (isinstance(orientations, gpd.geodataframe.GeoDataFrame)) and ( - not {'X', 'Y', 'Z'}.issubset(orientations.columns)): + not {"X", "Y", "Z"}.issubset(orientations.columns) + ): orientations = vector.extract_xy(orientations) # Checking if X, Y, Z coordinates are in columns - if not {'X', 'Y', 'Z'}.issubset(orientations.columns): - raise ValueError('Please provide all X, Y and Z coordinates in the Pandas DataFrame or GeoPandas GeoDataFrame') + if not {"X", "Y", "Z"}.issubset(orientations.columns): + raise ValueError( + "Please provide all X, Y and Z coordinates in the Pandas DataFrame or GeoPandas GeoDataFrame" + ) # Checking that zmin is of type float or int if not isinstance(zmin, (float, int)): - raise TypeError('zmin must be provided as float or int') + raise TypeError("zmin must be provided as float or int") # Checking that zmax is of type float or int if not isinstance(zmax, (float, int)): - raise TypeError('zmax must be provided as float or int') + raise TypeError("zmax must be provided as float or int") # Checking that rotate_reverse_direction is of type bool if not isinstance(rotate_reverse_direction, bool): - raise TypeError('rotate_reverse_direction must be of type bool') + raise TypeError("rotate_reverse_direction must be of type bool") # Checking that return_extent_gdf is of type bool if not isinstance(return_extent_gdf, bool): - raise TypeError('return_extent_gdf must be of type bool') + raise TypeError("return_extent_gdf must be of type bool") # Calculating the smallest angle to perform the rotation - min_angle = min([vector.calculate_angle(LineString((list(extent_polygon.exterior.coords)[i], - list(extent_polygon.exterior.coords)[i + 1]))) for i in - range(len(list(extent_polygon.exterior.coords)) - 1)]) + min_angle = min( + [ + vector.calculate_angle( + LineString( + ( + list(extent_polygon.exterior.coords)[i], + list(extent_polygon.exterior.coords)[i + 1], + ) + ) + ) + for i in range(len(list(extent_polygon.exterior.coords)) - 1) + ] + ) # Using the manual rotation angle if provided if manual_rotation_angle: min_angle = manual_rotation_angle # Creating GeoDataFrames from DataFrames - interfaces = gpd.GeoDataFrame(geometry=gpd.points_from_xy(x=interfaces['X'], - y=interfaces['Y']), - data=interfaces) + interfaces = gpd.GeoDataFrame( + geometry=gpd.points_from_xy(x=interfaces["X"], y=interfaces["Y"]), + data=interfaces, + ) - orientations = gpd.GeoDataFrame(geometry=gpd.points_from_xy(x=orientations['X'], - y=orientations['Y']), - data=orientations) + orientations = gpd.GeoDataFrame( + geometry=gpd.points_from_xy(x=orientations["X"], y=orientations["Y"]), + data=orientations, + ) # Creating Polygons from Interfaces and Orientations - interfaces_polygon = shapely.geometry.Polygon(interfaces['geometry']) - orientations_polygon = shapely.geometry.Polygon(orientations['geometry']) + interfaces_polygon = shapely.geometry.Polygon(interfaces["geometry"]) + orientations_polygon = shapely.geometry.Polygon(orientations["geometry"]) # Rotating extent to vertical or horizontal if not rotate_reverse_direction: # Rotating extent - extent_rotated = shapely.affinity.rotate(extent_polygon, -min_angle, 'center') + extent_rotated = shapely.affinity.rotate(extent_polygon, -min_angle, "center") # Rotating interfaces and orientations - interfaces_polygon_rotated = shapely.affinity.rotate(interfaces_polygon, - -min_angle, - (list(extent_polygon.centroid.coords)[0][0], - list(extent_polygon.centroid.coords)[0][1])) + interfaces_polygon_rotated = shapely.affinity.rotate( + interfaces_polygon, + -min_angle, + ( + list(extent_polygon.centroid.coords)[0][0], + list(extent_polygon.centroid.coords)[0][1], + ), + ) - orientations_polygon_rotated = shapely.affinity.rotate(orientations_polygon, - -min_angle, - (list(extent_polygon.centroid.coords)[0][0], - list(extent_polygon.centroid.coords)[0][1])) + orientations_polygon_rotated = shapely.affinity.rotate( + orientations_polygon, + -min_angle, + ( + list(extent_polygon.centroid.coords)[0][0], + list(extent_polygon.centroid.coords)[0][1], + ), + ) else: # Rotating extent - extent_rotated = shapely.affinity.rotate(extent_polygon, min_angle, 'center') + extent_rotated = shapely.affinity.rotate(extent_polygon, min_angle, "center") # Rotating interfaces and orientations - interfaces_polygon_rotated = shapely.affinity.rotate(interfaces_polygon, - min_angle, - (list(extent_polygon.centroid.coords)[0][0], - list(extent_polygon.centroid.coords)[0][1])) + interfaces_polygon_rotated = shapely.affinity.rotate( + interfaces_polygon, + min_angle, + ( + list(extent_polygon.centroid.coords)[0][0], + list(extent_polygon.centroid.coords)[0][1], + ), + ) - orientations_polygon_rotated = shapely.affinity.rotate(orientations_polygon, - min_angle, - (list(extent_polygon.centroid.coords)[0][0], - list(extent_polygon.centroid.coords)[0][1])) + orientations_polygon_rotated = shapely.affinity.rotate( + orientations_polygon, + min_angle, + ( + list(extent_polygon.centroid.coords)[0][0], + list(extent_polygon.centroid.coords)[0][1], + ), + ) # Creating Bounding Box - bbox = box(*extent_rotated.bounds) - extent = [extent_rotated.bounds[0], - extent_rotated.bounds[2], - extent_rotated.bounds[1], - extent_rotated.bounds[3], - zmin, - zmax] + extent = [ + extent_rotated.bounds[0], + extent_rotated.bounds[2], + extent_rotated.bounds[1], + extent_rotated.bounds[3], + zmin, + zmax, + ] # Converting Polygons back to Points and extracting Points interfaces_rotated = gpd.GeoDataFrame( - geometry=gpd.points_from_xy(x=[coords[0] for coords in list(interfaces_polygon_rotated.exterior.coords)[:-1]], - y=[coords[1] for coords in list(interfaces_polygon_rotated.exterior.coords)[:-1]]), - data=interfaces) + geometry=gpd.points_from_xy( + x=[ + coords[0] + for coords in list(interfaces_polygon_rotated.exterior.coords)[:-1] + ], + y=[ + coords[1] + for coords in list(interfaces_polygon_rotated.exterior.coords)[:-1] + ], + ), + data=interfaces, + ) interfaces_rotated = vector.extract_xy(interfaces_rotated) orientations_rotated = gpd.GeoDataFrame( - geometry=gpd.points_from_xy(x=[coords[0] for coords in list(orientations_polygon_rotated.exterior.coords)[:-1]], - y=[coords[1] for coords in - list(orientations_polygon_rotated.exterior.coords)[:-1]]), - data=orientations) + geometry=gpd.points_from_xy( + x=[ + coords[0] + for coords in list(orientations_polygon_rotated.exterior.coords)[:-1] + ], + y=[ + coords[1] + for coords in list(orientations_polygon_rotated.exterior.coords)[:-1] + ], + ), + data=orientations, + ) orientations_rotated = vector.extract_xy(orientations_rotated) # Return extent gdf if needed @@ -2463,48 +2659,62 @@ def open_mpk(path_in: str): import py7zr except ModuleNotFoundError: raise ModuleNotFoundError( - 'py7zr package is not installed. Use pip install py7zr to install the latest version') + "py7zr package is not installed. Use pip install py7zr to install the latest version" + ) # Checking that the file path is of type string if not isinstance(path_in, str): - raise TypeError('The file path must be provided as string') + raise TypeError("The file path must be provided as string") # Renaming .mpk file to .zip file - path_out = path_in.split('.mpk')[0] - os.rename(path_in, path_out + '.zip') + path_out = path_in.split(".mpk")[0] + os.rename(path_in, path_out + ".zip") # Unzipping files - with py7zr.SevenZipFile(path_out + '.zip', - 'r') as archive: + with py7zr.SevenZipFile(path_out + ".zip", "r") as archive: archive.extractall(path=path_out) # Getting vector data files - files_vector_data = [os.path.join(path, name) for path, subdirs, files in os.walk(path_out) - for name in files if name.endswith(".shp")] + files_vector_data = [ + os.path.join(path, name) + for path, subdirs, files in os.walk(path_out) + for name in files + if name.endswith(".shp") + ] # Creating vector data dict - dict_vector_data = {file.rsplit('\\')[-1]: gpd.read_file(file) for file in files_vector_data} + dict_vector_data = { + file.rsplit("\\")[-1]: gpd.read_file(file) for file in files_vector_data + } # TODO: Add support for .tif files if case arises # Getting raster data files - files_raster_data_adf = [os.path.join(path, name) for path, subdirs, files in os.walk(path_out) for name in files if - (name.endswith(".adf")) & (name.startswith("w001001."))] + files_raster_data_adf = [ + os.path.join(path, name) + for path, subdirs, files in os.walk(path_out) + for name in files + if (name.endswith(".adf")) & (name.startswith("w001001.")) + ] # Creating raster data dict - dict_raster_data = {file.rsplit('\\')[-1]: rasterio.open(file) for file in files_raster_data_adf} + dict_raster_data = { + file.rsplit("\\")[-1]: rasterio.open(file) for file in files_raster_data_adf + } return dict_vector_data, dict_raster_data -def convert_crs_seismic_data(path_in: str, - path_out: str, - crs_in: Union[str, pyproj.crs.crs.CRS], - crs_out: Union[str, pyproj.crs.crs.CRS], - cdpx: int = 181, - cdpy: int = 185, - vert_domain: str = 'TWT', - coord_scalar: int = None): +def convert_crs_seismic_data( + path_in: str, + path_out: str, + crs_in: Union[str, pyproj.crs.crs.CRS], + crs_out: Union[str, pyproj.crs.crs.CRS], + cdpx: int = 181, + cdpy: int = 185, + vert_domain: str = "TWT", + coord_scalar: int = None, +): """Convert CDP coordinates of seismic data to a new CRS. Parameters @@ -2534,45 +2744,44 @@ def convert_crs_seismic_data(path_in: str, from segysak.segy import segy_loader, segy_writer except ModuleNotFoundError: raise ModuleNotFoundError( - 'segysak package is not installed. Use pip install segysak to install the latest version') + "segysak package is not installed. Use pip install segysak to install the latest version" + ) # Checking that path_in is of type string if not isinstance(path_in, str): - raise TypeError('path_in must be provided as string') + raise TypeError("path_in must be provided as string") # Checking that path_out is of type string if not isinstance(path_out, str): - raise TypeError('path_out must be provided as string') + raise TypeError("path_out must be provided as string") # Checking that crs_in is of type string or pyproj CRS if not isinstance(crs_in, (str, pyproj.crs.crs.CRS)): - raise TypeError('crs_in must be provided as string or pyproj CRS') + raise TypeError("crs_in must be provided as string or pyproj CRS") # Checking that crs_out is of type string or pyproj CRS if not isinstance(crs_out, (str, pyproj.crs.crs.CRS)): - raise TypeError('crs_out must be provided as string or pyproj CRS') + raise TypeError("crs_out must be provided as string or pyproj CRS") # Checking that vert_domain is of type str if not isinstance(vert_domain, str): - raise TypeError('vert_domain must be provided as string') + raise TypeError("vert_domain must be provided as string") # Checking that the coord_scalar is of type int or None if not isinstance(coord_scalar, (int, type(None))): - raise TypeError('coord_scalar must be provided as int') + raise TypeError("coord_scalar must be provided as int") # Loading seismic data - seismic = segy_loader(path_in, - vert_domain=vert_domain, - cdpx=cdpx, - cdpy=cdpy) + seismic = segy_loader(path_in, vert_domain=vert_domain, cdpx=cdpx, cdpy=cdpy) # Converting Seismic to DataFrame df_seismic = seismic.to_dataframe() # Checking that the CDP coordinates are in the DataFrame - if not {'cdp_x', 'cdp_y'}.issubset(df_seismic.columns): + if not {"cdp_x", "cdp_y"}.issubset(df_seismic.columns): raise ValueError( - 'No coordinates found, please provide the byte positions where the X and Y data of the CDPs is stored') + "No coordinates found, please provide the byte positions where the X and Y data of the CDPs is stored" + ) # Extracting the length of the samples to reduce computing time samples = len(df_seismic.index.get_level_values(1).unique()) @@ -2581,36 +2790,39 @@ def convert_crs_seismic_data(path_in: str, df_seismic_resampled = df_seismic[::samples] # Reprojecting Coordinates - df_seismic_reprojected = gpd.GeoDataFrame(geometry=gpd.points_from_xy(x=df_seismic_resampled['cdp_x'].values, - y=df_seismic_resampled['cdp_y'].values), - crs=crs_in).to_crs(crs_out) + df_seismic_reprojected = gpd.GeoDataFrame( + geometry=gpd.points_from_xy( + x=df_seismic_resampled["cdp_x"].values, + y=df_seismic_resampled["cdp_y"].values, + ), + crs=crs_in, + ).to_crs(crs_out) # Extracting reprojected coordinates x = df_seismic_reprojected.geometry.x.values y = df_seismic_reprojected.geometry.y.values # Assigning new coordinates - seismic['cdp_x'][:] = x - seismic['cdp_y'][:] = y + seismic["cdp_x"][:] = x + seismic["cdp_y"][:] = y # Optionally setting a new coord_scalar if coord_scalar: - seismic.attrs['coord_scalar'] = coord_scalar + seismic.attrs["coord_scalar"] = coord_scalar # Saving reprojected seismic data to file - segy_writer(seismic, - path_out, - trace_header_map=dict(cdp_x=181, - cdp_y=185)) + segy_writer(seismic, path_out, trace_header_map=dict(cdp_x=181, cdp_y=185)) - print('Seismic data was successfully reprojected and saved to file') + print("Seismic data was successfully reprojected and saved to file") -def get_cdp_linestring_of_seismic_data(path_in: str, - crs_in: Union[str, pyproj.crs.crs.CRS], - cdpx: int = 181, - cdpy: int = 185, - vert_domain: str = 'TWT'): +def get_cdp_linestring_of_seismic_data( + path_in: str, + crs_in: Union[str, pyproj.crs.crs.CRS], + cdpx: int = 181, + cdpy: int = 185, + vert_domain: str = "TWT", +): """Extracting the path of the seismic data as LineString. Parameters @@ -2636,36 +2848,35 @@ def get_cdp_linestring_of_seismic_data(path_in: str, """ # Trying to import segysak but returning error if segysak is not installed try: - from segysak.segy import segy_loader, segy_writer + from segysak.segy import segy_loader except ModuleNotFoundError: raise ModuleNotFoundError( - 'segysak package is not installed. Use pip install segysak to install the latest version') + "segysak package is not installed. Use pip install segysak to install the latest version" + ) # Checking that path_in is of type string if not isinstance(path_in, str): - raise TypeError('path_in must be provided as string') + raise TypeError("path_in must be provided as string") # Checking that crs_in is of type string or pyproj CRS if not isinstance(crs_in, (str, pyproj.crs.crs.CRS)): - raise TypeError('crs_in must be provided as string or pyproj CRS') + raise TypeError("crs_in must be provided as string or pyproj CRS") # Checking that vert_domain is of type str if not isinstance(vert_domain, str): - raise TypeError('vert_domain must be provided as string') + raise TypeError("vert_domain must be provided as string") # Loading seismic data - seismic = segy_loader(path_in, - vert_domain=vert_domain, - cdpx=cdpx, - cdpy=cdpy) + seismic = segy_loader(path_in, vert_domain=vert_domain, cdpx=cdpx, cdpy=cdpy) # Converting Seismic to DataFrame df_seismic = seismic.to_dataframe() # Checking that the CDP coordinates are in the DataFrame - if not {'cdp_x', 'cdp_y'}.issubset(df_seismic.columns): + if not {"cdp_x", "cdp_y"}.issubset(df_seismic.columns): raise ValueError( - 'No coordinates found, please provide the byte positions where the X and Y data of the CDPs is stored') + "No coordinates found, please provide the byte positions where the X and Y data of the CDPs is stored" + ) # Extracting the length of the samples to reduce computing time samples = len(df_seismic.index.get_level_values(1).unique()) @@ -2674,23 +2885,27 @@ def get_cdp_linestring_of_seismic_data(path_in: str, df_seismic_resampled = df_seismic[::samples] # Creating LineString from coordinates - linestring = LineString(np.c_[(df_seismic_resampled['cdp_x'].values, - df_seismic_resampled['cdp_y'].values)]) + linestring = LineString( + np.c_[ + (df_seismic_resampled["cdp_x"].values, df_seismic_resampled["cdp_y"].values) + ] + ) # Reprojecting Coordinates - gdf_seismic = gpd.GeoDataFrame(geometry=[linestring], - crs=crs_in) + gdf_seismic = gpd.GeoDataFrame(geometry=[linestring], crs=crs_in) return gdf_seismic -def get_cdp_points_of_seismic_data(path_in: str, - crs_in: Union[str, pyproj.crs.crs.CRS], - cdpx: int = 181, - cdpy: int = 185, - vert_domain: str = 'TWT', - filter: int = None, - n_meter: Union[int, float] = None): +def get_cdp_points_of_seismic_data( + path_in: str, + crs_in: Union[str, pyproj.crs.crs.CRS], + cdpx: int = 181, + cdpy: int = 185, + vert_domain: str = "TWT", + filter: int = None, + n_meter: Union[int, float] = None, +): """Extracting the path of the seismic data as LineString. Parameters @@ -2720,36 +2935,35 @@ def get_cdp_points_of_seismic_data(path_in: str, """ # Trying to import segysak but returning error if segysak is not installed try: - from segysak.segy import segy_loader, segy_writer + from segysak.segy import segy_loader except ModuleNotFoundError: raise ModuleNotFoundError( - 'segysak package is not installed. Use pip install segysak to install the latest version') + "segysak package is not installed. Use pip install segysak to install the latest version" + ) # Checking that path_in is of type string if not isinstance(path_in, str): - raise TypeError('path_in must be provided as string') + raise TypeError("path_in must be provided as string") # Checking that crs_in is of type string or pyproj CRS if not isinstance(crs_in, (str, pyproj.crs.crs.CRS)): - raise TypeError('crs_in must be provided as string or pyproj CRS') + raise TypeError("crs_in must be provided as string or pyproj CRS") # Checking that vert_domain is of type str if not isinstance(vert_domain, str): - raise TypeError('vert_domain must be provided as string') + raise TypeError("vert_domain must be provided as string") # Loading seismic data - seismic = segy_loader(path_in, - vert_domain=vert_domain, - cdpx=cdpx, - cdpy=cdpy) + seismic = segy_loader(path_in, vert_domain=vert_domain, cdpx=cdpx, cdpy=cdpy) # Converting Seismic to DataFrame df_seismic = seismic.to_dataframe() # Checking that the CDP coordinates are in the DataFrame - if not {'cdp_x', 'cdp_y'}.issubset(df_seismic.columns): + if not {"cdp_x", "cdp_y"}.issubset(df_seismic.columns): raise ValueError( - 'No coordinates found, please provide the byte positions where the X and Y data of the CDPs is stored') + "No coordinates found, please provide the byte positions where the X and Y data of the CDPs is stored" + ) # Extracting the length of the samples to reduce computing time samples = len(df_seismic.index.get_level_values(1).unique()) @@ -2760,28 +2974,44 @@ def get_cdp_points_of_seismic_data(path_in: str, if n_meter: # Creating LineString from coordinates - linestring = LineString(np.c_[(df_seismic_resampled['cdp_x'].values, - df_seismic_resampled['cdp_y'].values)]) + linestring = LineString( + np.c_[ + ( + df_seismic_resampled["cdp_x"].values, + df_seismic_resampled["cdp_y"].values, + ) + ] + ) # Defining number of samples samples = np.arange(0, round(linestring.length / n_meter) + 1, 1) # Getting points every n_meter - points = [shapely.line_interpolate_point(linestring, n_meter * sample) for sample in samples] + points = [ + shapely.line_interpolate_point(linestring, n_meter * sample) + for sample in samples + ] # Creating GeoDataFrame from points - gdf_seismic = gpd.GeoDataFrame(geometry=points, - crs=crs_in) + gdf_seismic = gpd.GeoDataFrame(geometry=points, crs=crs_in) # Appending distance - gdf_seismic['distance'] = samples * n_meter + gdf_seismic["distance"] = samples * n_meter else: # Creating Points from coordinates - gdf_seismic = gpd.GeoDataFrame(geometry=gpd.points_from_xy(x=df_seismic_resampled['cdp_x'].values, - y=df_seismic_resampled['cdp_y'].values), - data=df_seismic_resampled, - crs=crs_in).reset_index().drop(['twt', 'data'], axis=1) + gdf_seismic = ( + gpd.GeoDataFrame( + geometry=gpd.points_from_xy( + x=df_seismic_resampled["cdp_x"].values, + y=df_seismic_resampled["cdp_y"].values, + ), + data=df_seismic_resampled, + crs=crs_in, + ) + .reset_index() + .drop(["twt", "data"], axis=1) + ) # Returning only every nth point if filter: diff --git a/gemgis/vector.py b/gemgis/vector.py index e421aec2..a45a731b 100644 --- a/gemgis/vector.py +++ b/gemgis/vector.py @@ -28,7 +28,6 @@ import geopandas as gpd from gemgis.raster import sample_from_array, sample_from_rasterio from typing import Union, List, Tuple, Optional, Sequence, Collection -import fiona import pyvista as pv __all__ = [geometry] @@ -36,61 +35,93 @@ try: import rasterio except ModuleNotFoundError: - raise ModuleNotFoundError('No valid rasterio installation found') + raise ModuleNotFoundError("No valid rasterio installation found") -pd.set_option('display.float_format', lambda x: '%.2f' % x) +pd.set_option("display.float_format", lambda x: "%.2f" % x) # Extracting X and Y coordinates from Vector Data ################################################# -def extract_xy_points(gdf: gpd.geodataframe.GeoDataFrame, - reset_index: bool = True, - drop_id: bool = True, - drop_index: bool = True, - overwrite_xy: bool = False, - target_crs: Union[str, pyproj.crs.crs.CRS] = None, - bbox: Optional[Sequence[float]] = None) -> gpd.geodataframe.GeoDataFrame: - """Extracting X and Y coordinates from a GeoDataFrame (Points) and returning a GeoDataFrame with X and Y - coordinates as additional columns +def extract_xy_points( + gdf: gpd.geodataframe.GeoDataFrame, + reset_index: bool = True, + drop_id: bool = True, + drop_index: bool = True, + overwrite_xy: bool = False, + target_crs: Union[str, pyproj.crs.crs.CRS] = None, + bbox: Optional[Sequence[float]] = None, +) -> gpd.geodataframe.GeoDataFrame: + """Extract X and Y coordinates from a GeoDataFrame (Points) and returning a GeoDataFrame with X and Y + coordinates as additional columns. Parameters ---------- gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame created from vector data containing elements of geom_type Point - - reset_index : bool - Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` + GeoDataFrame created from vector data containing exclusively elements of `geom_type` `'Point'`. + + +----+------+-----------+------------------------+ + | ID | id | formation | geometry | + +----+------+-----------+------------------------+ + | 0 | None | Ton | POINT (19.150 293.313) | + +----+------+-----------+------------------------+ + | 1 | None | Ton | POINT (61.934 381.459) | + +----+------+-----------+------------------------+ + | 2 | None | Ton | POINT (109.358 480.946)| + +----+------+-----------+------------------------+ + | 3 | None | Ton | POINT (157.812 615.999)| + +----+------+-----------+------------------------+ + | 4 | None | Ton | POINT (191.318 719.094)| + +----+------+-----------+------------------------+ + + reset_index : bool, default: ``True`` + Variable to reset the index of the resulting GeoDataFrame. + Options include: ``True`` or ``False``, default set to ``True``. - drop_id : bool - Variable to drop the id column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_id : bool, default: ``True`` + Variable to drop the id column. + Options include: ``True`` or ``False``, default set to ``True``. - drop_index : bool - Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_index : bool, default: ``True`` + Variable to drop the index column. + Options include: ``True`` or ``False``, default set to ``True``. - overwrite_xy : bool - Variable to overwrite existing X and Y values. - Options include: ``True`` or ``False``, default set to ``False`` + overwrite_xy : bool, default: ``False`` + Variable to overwrite existing X and Y values. + Options include: ``True`` or ``False``, default set to ``False``. target_crs : Union[str, pyproj.crs.crs.CRS] - Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'`` + Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'``. bbox : list - Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]`` + Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]``. Returns ------- gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame with appended X and Y coordinates as new columns and optional columns + GeoDataFrame with appended X and Y coordinates as new columns and optional columns. + + +----+-----------+-------------------------+-----------+-----------+ + | ID | formation | geometry | X | Y | + +====+===========+=========================+===========+===========+ + | 0 | Ton | POINT (19.150 293.313) | 19.150 | 293.313 | + +----+-----------+-------------------------+-----------+-----------+ + | 1 | Ton | POINT (61.934 381.459) | 61.934 | 381.459 | + +----+-----------+-------------------------+-----------+-----------+ + | 2 | Ton | POINT (109.358 480.946) | 109.358 | 480.946 | + +----+-----------+-------------------------+-----------+-----------+ + | 3 | Ton | POINT (157.812 615.999) | 157.812 | 615.999 | + +----+-----------+-------------------------+-----------+-----------+ + | 4 | Ton | POINT (191.318 719.094) | 191.318 | 719.094 | + +----+-----------+-------------------------+-----------+-----------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -99,90 +130,109 @@ def extract_xy_points(gdf: gpd.geodataframe.GeoDataFrame, >>> import geopandas as gpd >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id formation geometry - 0 None Ton POINT (19.150 293.313) - 1 None Ton POINT (61.934 381.459) - 2 None Ton POINT (109.358 480.946) - 3 None Ton POINT (157.812 615.999) - 4 None Ton POINT (191.318 719.094) - - >>> # Extracting X and Y Coordinates from Point Objects + + +----+------+-----------+------------------------+ + | ID | id | formation | geometry | + +----+------+-----------+------------------------+ + | 0 | None | Ton | POINT (19.150 293.313) | + +----+------+-----------+------------------------+ + | 1 | None | Ton | POINT (61.934 381.459) | + +----+------+-----------+------------------------+ + | 2 | None | Ton | POINT (109.358 480.946)| + +----+------+-----------+------------------------+ + | 3 | None | Ton | POINT (157.812 615.999)| + +----+------+-----------+------------------------+ + | 4 | None | Ton | POINT (191.318 719.094)| + +----+------+-----------+------------------------+ + + + >>> # Extracting X and Y Coordinates from Point GeoDataFrame >>> gdf_xy = gg.vector.extract_xy_points(gdf=gdf, reset_index=False) >>> gdf_xy - formation geometry X Y - 0 Ton POINT (19.150 293.313) 19.15 293.31 - 1 Ton POINT (61.934 381.459) 61.93 381.46 - 2 Ton POINT (109.358 480.946) 109.36 480.95 - 3 Ton POINT (157.812 615.999) 157.81 616.00 - 4 Ton POINT (191.318 719.094) 191.32 719.09 + + +----+-----------+-------------------------+-----------+-----------+ + | ID | formation | geometry | X | Y | + +====+===========+=========================+===========+===========+ + | 0 | Ton | POINT (19.150 293.313) | 19.150 | 293.313 | + +----+-----------+-------------------------+-----------+-----------+ + | 1 | Ton | POINT (61.934 381.459) | 61.934 | 381.459 | + +----+-----------+-------------------------+-----------+-----------+ + | 2 | Ton | POINT (109.358 480.946) | 109.358 | 480.946 | + +----+-----------+-------------------------+-----------+-----------+ + | 3 | Ton | POINT (157.812 615.999) | 157.812 | 615.999 | + +----+-----------+-------------------------+-----------+-----------+ + | 4 | Ton | POINT (191.318 719.094) | 191.318 | 719.094 | + +----+-----------+-------------------------+-----------+-----------+ See Also ________ - extract_xy_linestring : Extracting X and Y coordinates from a GeoDataFrame containing Shapely LineStrings and + extract_xy : Extract X and Y coordinates from Vector Data + extract_xy_linestring : Extract X and Y coordinates from a GeoDataFrame containing Shapely LineStrings and saving the X and Y coordinates as lists for each LineString - extract_xy_linestrings : Extracting X and Y coordinates from a GeoDataFrame containing Shapely LineStrings - extract_xy : Extracting X and Y coordinates from Vector Data - - """ + extract_xy_linestrings : Extract X and Y coordinates from a GeoDataFrame containing Shapely LineStrings + """ # Checking that gdf is of type GepDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Check that all entries of the gdf are of type Point if not all(shapely.get_type_id(gdf.geometry) == 0): - raise TypeError('All GeoDataFrame entries must be of geom_type Point') + raise TypeError("All GeoDataFrame entries must be of geom_type Point") # Checking that the bbox is of type None or list if bbox is not None: if not isinstance(bbox, Sequence): - raise TypeError('The bbox values must be provided as a sequence') + raise TypeError("The bbox values must be provided as a sequence") # Checking that the bbox list only has four elements if len(bbox) != 4: - raise ValueError('Provide minx, maxx, miny and maxy values for the bbox') + raise ValueError("Provide minx, maxx, miny and maxy values for the bbox") # Checking that all elements of the list are of type int or float if not all(isinstance(bound, (int, float)) for bound in bbox): - raise TypeError('Bbox values must be of type float or int') + raise TypeError("Bbox values must be of type float or int") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that none of the points have a Z component if any(shapely.has_z(gdf.geometry)): raise ValueError( - 'One or more Shapely objects contain a Z component. Use gg.vector.extract_xyz(...) to obtain all coordinates.') + "One or more Shapely objects contain a Z component. Use gg.vector.extract_xyz(...) to obtain all coordinates." + ) # Checking that drop_id is of type bool if not isinstance(drop_id, bool): - raise TypeError('Drop_id argument must be of type bool') + raise TypeError("Drop_id argument must be of type bool") # Checking that drop_index is of type bool if not isinstance(drop_index, bool): - raise TypeError('Drop_index argument must be of type bool') + raise TypeError("Drop_index argument must be of type bool") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that the target_crs is of type string if not isinstance(target_crs, (str, type(None), pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Checking that overwrite_xy is of type bool if not isinstance(overwrite_xy, bool): - raise TypeError('Overwrite_xy argument must be of type bool') + raise TypeError("Overwrite_xy argument must be of type bool") # Checking that X and Y are not in the GeoDataFrame - if not overwrite_xy and {'X', 'Y'}.issubset(gdf.columns): - raise ValueError('X and Y columns must not be present in GeoDataFrame before the extraction of coordinates') + if not overwrite_xy and {"X", "Y"}.issubset(gdf.columns): + raise ValueError( + "X and Y columns must not be present in GeoDataFrame before the extraction of coordinates" + ) # Copying GeoDataFrame gdf = gdf.copy(deep=True) @@ -192,56 +242,83 @@ def extract_xy_points(gdf: gpd.geodataframe.GeoDataFrame, gdf = gdf.to_crs(crs=target_crs) # Extracting x,y coordinates from point vector data - gdf['X'] = shapely.get_x(gdf.geometry) - gdf['Y'] = shapely.get_y(gdf.geometry) + gdf["X"] = shapely.get_x(gdf.geometry) + gdf["Y"] = shapely.get_y(gdf.geometry) # Limiting the extent of the data if bbox is not None: - gdf = gdf[(gdf.X > bbox[0]) & (gdf.X < bbox[1]) & (gdf.Y > bbox[2]) & (gdf.Y < bbox[3])] + gdf = gdf[ + (gdf.X > bbox[0]) + & (gdf.X < bbox[1]) + & (gdf.Y > bbox[2]) + & (gdf.Y < bbox[3]) + ] # Resetting the index if reset_index: gdf = gdf.reset_index() # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) # Dropping id column - if 'id' in gdf and drop_id: - gdf = gdf.drop(columns='id', - axis=1) + if "id" in gdf and drop_id: + gdf = gdf.drop(columns="id", axis=1) return gdf -def extract_xy_linestring(gdf: gpd.geodataframe.GeoDataFrame, - target_crs: Union[str, pyproj.crs.crs.CRS] = None, - bbox: Optional[Sequence[float]] = None) -> gpd.geodataframe.GeoDataFrame: - """Extracting the coordinates of Shapely LineStrings within a GeoDataFrame - and storing the X and Y coordinates in lists per LineString +def extract_xy_linestring( + gdf: gpd.geodataframe.GeoDataFrame, + target_crs: Union[str, pyproj.crs.crs.CRS] = None, + bbox: Optional[Sequence[float]] = None, +) -> gpd.geodataframe.GeoDataFrame: + """Extract the coordinates of Shapely LineStrings within a GeoDataFrame + and storing the X and Y coordinates in lists per LineString. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame created from vector data containing elements of geom_type LineString + GeoDataFrame created from vector data containing elements of geom_type LineString. + + +----+-----------+-----------+----------------------------------------------------+ + | | id | formation | geometry | + +----+-----------+-----------+----------------------------------------------------+ + | 0 | None | Sand1 | LINESTRING (0.256 264.862, 10.593 276.734, 17.... | + +----+-----------+----------------------------------------------------------------+ + | 1 | None | Ton | LINESTRING (0.188 495.787, 8.841 504.142, 41.0... | + +----+-----------+----------------------------------------------------------------+ + | 2 | None | Ton | LINESTRING (970.677 833.053, 959.372 800.023, ... | + +----+-----------+----------------------------------------------------------------+ target_crs : Union[str, pyproj.crs.crs.CRS] - Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'`` - + Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'``. bbox : Optional[Sequence[float]] - Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]`` + Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]``. Returns _______ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing the additional X and Y columns with lists of X and Y coordinates + GeoDataFrame containing the additional X and Y columns with lists of X and Y coordinates. + + +----+-----------+-----------+----------------------------------------------------+-----------------------------------------------------+-------------------------------------------------------------+ + | | id | formation | geometry | X | Y | + +----+-----------+-----------+----------------------------------------------------+-----------------------------------------------------+-------------------------------------------------------------+ + | 0 | None | Sand1 | LINESTRING (0.256 264.862, 10.593 276.734, 17.... | [0.256327195431048, 10.59346813871597, 17.1349...] | [264.86214748436396, 276.73370778641777, 289.0...] | + +----+-----------+-----------+-------------------------------------------+--------+-----------------------------------------------------+-------------------------------------------------------------+ + | 1 | None | Ton | LINESTRING (0.188 495.787, 8.841 504.142, 41.0... | [0.1881868620686138, 8.840672956663411, 41.092...] | [495.787213546976, 504.1418419288791, 546.4230...] | + +----+-----------+-----------+-------------------------------------------+--------+-----------------------------------------------------+-------------------------------------------------------------+ + | 2 | None | Ton | LINESTRING (970.677 833.053, 959.372 800.023, ... | [970.6766251230017, 959.3724321757514, 941.291...] | [833.052616499831, 800.0232029873156, 754.8012...] | + +----+-----------+-----------+-------------------------------------------+--------+-----------------------------------------------------+-------------------------------------------------------------+ + .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -250,132 +327,181 @@ def extract_xy_linestring(gdf: gpd.geodataframe.GeoDataFrame, >>> import geopandas as gpd >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id formation geometry - 0 None Sand1 LINESTRING (0.256 264.862, 10.593 276.734, 17.... - 1 None Ton LINESTRING (0.188 495.787, 8.841 504.142, 41.0... - 2 None Ton LINESTRING (970.677 833.053, 959.372 800.023, ... + + +----+-----------+-----------+----------------------------------------------------+ + | | id | formation | geometry | + +----+-----------+-----------+----------------------------------------------------+ + | 0 | None | Sand1 | LINESTRING (0.256 264.862, 10.593 276.734, 17.... | + +----+-----------+----------------------------------------------------------------+ + | 1 | None | Ton | LINESTRING (0.188 495.787, 8.841 504.142, 41.0... | + +----+-----------+----------------------------------------------------------------+ + | 2 | None | Ton | LINESTRING (970.677 833.053, 959.372 800.023, ... | + +----+-----------+----------------------------------------------------------------+ >>> # Extracting X and Y Coordinates from LineString Objects >>> gdf_xy = gg.vector.extract_xy_linestring(gdf=gdf) >>> gdf_xy - id formation geometry X Y - 0 None Sand1 LINESTRING (0.256 264.862, 10.593 276.734, 17.... [0.256327195431048, 10.59346813871597, 17.1349... [264.86214748436396, 276.73370778641777, 289.0... - 1 None Ton LINESTRING (0.188 495.787, 8.841 504.142, 41.0... [0.1881868620686138, 8.840672956663411, 41.092... [495.787213546976, 504.1418419288791, 546.4230... - 2 None Ton LINESTRING (970.677 833.053, 959.372 800.023, ... [970.6766251230017, 959.3724321757514, 941.291... [833.052616499831, 800.0232029873156, 754.8012... + + +----+-----------+-----------+----------------------------------------------------+-----------------------------------------------------+-------------------------------------------------------------+ + | | id | formation | geometry | X | Y | + +----+-----------+-----------+----------------------------------------------------+-----------------------------------------------------+-------------------------------------------------------------+ + | 0 | None | Sand1 | LINESTRING (0.256 264.862, 10.593 276.734, 17.... | [0.256327195431048, 10.59346813871597, 17.1349...] | [264.86214748436396, 276.73370778641777, 289.0...] | + +----+-----------+-----------+-------------------------------------------+--------+-----------------------------------------------------+-------------------------------------------------------------+ + | 1 | None | Ton | LINESTRING (0.188 495.787, 8.841 504.142, 41.0... | [0.1881868620686138, 8.840672956663411, 41.092...] | [495.787213546976, 504.1418419288791, 546.4230...] | + +----+-----------+-----------+-------------------------------------------+--------+-----------------------------------------------------+-------------------------------------------------------------+ + | 2 | None | Ton | LINESTRING (970.677 833.053, 959.372 800.023, ... | [970.6766251230017, 959.3724321757514, 941.291...] | [833.052616499831, 800.0232029873156, 754.8012...] | + +----+-----------+-----------+-------------------------------------------+--------+-----------------------------------------------------+-------------------------------------------------------------+ See Also ________ - extract_xy_linestrings : Extracting X and Y coordinates from a GeoDataFrame containing Shapely LineStrings - extract_xy_points : Extracting X and Y coordinates from a GeoDataFrame containing Shapely Points - extract_xy : Extracting X and Y coordinates from Vector Data + extract_xy : Extract X and Y coordinates from Vector Data + extract_xy_points : Extract X and Y coordinates from a GeoDataFrame containing Shapely Points + extract_xy_linestrings : Extract X and Y coordinates from a GeoDataFrame containing Shapely LineStrings """ - # Checking that gdf is of type GepDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Check that all entries of the gdf are of type LineString if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All GeoDataFrame entries must be of geom_type linestrings') + raise TypeError("All GeoDataFrame entries must be of geom_type linestrings") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that none of the points have a Z component if any(shapely.has_z(gdf.geometry)): - raise ValueError('One or more Shapely objects contain a Z component') + raise ValueError("One or more Shapely objects contain a Z component") # Checking that the bbox is of type None or list if bbox is not None: if not isinstance(bbox, Sequence): - raise TypeError('The bbox values must be provided as a sequence') + raise TypeError("The bbox values must be provided as a sequence") # Checking that the bbox list only has four elements if len(bbox) != 4: - raise ValueError('Provide minx, maxx, miny and maxy values for the bbox') + raise ValueError("Provide minx, maxx, miny and maxy values for the bbox") # Checking that all elements of the list are of type int or float if not all(isinstance(bound, (int, float)) for bound in bbox): - raise TypeError('Bbox values must be of type float or int') + raise TypeError("Bbox values must be of type float or int") # Checking that the target_crs is of type string if target_crs is not None and not isinstance(target_crs, (str, pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Reprojecting coordinates to provided the target_crs if target_crs is not None: gdf = gdf.to_crs(crs=target_crs) # Extracting X coordinates - gdf['X'] = [list(shapely.get_coordinates(gdf.geometry[i])[:, 0]) for i in range(len(gdf))] + gdf["X"] = [ + list(shapely.get_coordinates(gdf.geometry[i])[:, 0]) for i in range(len(gdf)) + ] # Extracting Y coordinates - gdf['Y'] = [list(shapely.get_coordinates(gdf.geometry[i])[:, 1]) for i in range(len(gdf))] + gdf["Y"] = [ + list(shapely.get_coordinates(gdf.geometry[i])[:, 1]) for i in range(len(gdf)) + ] # Limiting the extent of the data if bbox is not None: - gdf = gdf[(gdf.X > bbox[0]) & (gdf.X < bbox[1]) & (gdf.Y > bbox[2]) & (gdf.Y < bbox[3])] + gdf = gdf[ + (gdf.X > bbox[0]) + & (gdf.X < bbox[1]) + & (gdf.Y > bbox[2]) + & (gdf.Y < bbox[3]) + ] return gdf -def extract_xy_linestrings(gdf: gpd.geodataframe.GeoDataFrame, - reset_index: bool = True, - drop_id: bool = True, - drop_index: bool = True, - drop_points: bool = True, - drop_level0: bool = True, - drop_level1: bool = True, - overwrite_xy: bool = False, - target_crs: Union[str, pyproj.crs.crs.CRS] = None, - bbox: Optional[Sequence[float]] = None) -> gpd.geodataframe.GeoDataFrame: - """Extracting X and Y coordinates from a GeoDataFrame (LineStrings) and returning a GeoDataFrame with X and Y - coordinates as additional columns +def extract_xy_linestrings( + gdf: gpd.geodataframe.GeoDataFrame, + reset_index: bool = True, + drop_id: bool = True, + drop_index: bool = True, + drop_points: bool = True, + drop_level0: bool = True, + drop_level1: bool = True, + overwrite_xy: bool = False, + target_crs: Union[str, pyproj.crs.crs.CRS] = None, + bbox: Optional[Sequence[float]] = None, +) -> gpd.geodataframe.GeoDataFrame: + """Extract X and Y coordinates from a GeoDataFrame (LineStrings) and returning a GeoDataFrame with X and Y + coordinates as additional columns. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame created from vector data containing elements of geom_type LineString - reset_index : bool + GeoDataFrame created from vector data containing elements of geom_type LineString. + + +----+-----------+-----------+----------------------------------------------------+ + | | id | formation | geometry | + +----+-----------+-----------+----------------------------------------------------+ + | 0 | None | Sand1 | LINESTRING (0.256 264.862, 10.593 276.734, 17.... | + +----+-----------+----------------------------------------------------------------+ + | 1 | None | Ton | LINESTRING (0.188 495.787, 8.841 504.142, 41.0... | + +----+-----------+----------------------------------------------------------------+ + | 2 | None | Ton | LINESTRING (970.677 833.053, 959.372 800.023, ... | + +----+-----------+----------------------------------------------------------------+ + + reset_index : bool, default: ``True`` Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` - drop_id : bool + Options include: ``True`` or ``False``, default set to ``True``. + drop_id : bool, default: ``True`` Variable to drop the id column. - Options include: ``True`` or ``False``, default set to ``True`` - drop_index : bool + Options include: ``True`` or ``False``, default set to ``True``. + drop_index : bool, default: ``True`` Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` - drop_points : bool + Options include: ``True`` or ``False``, default set to ``True``. + drop_points : bool, default: ``True`` Variable to drop the points column. - Options include: ``True`` or ``False``, default set to ``True`` - drop_level0 : bool + Options include: ``True`` or ``False``, default set to ``True``. + drop_level0 : bool, default: ``True`` Variable to drop the level_0 column. - Options include: ``True`` or ``False``, default set to ``True`` - drop_level1 : bool + Options include: ``True`` or ``False``, default set to ``True``. + drop_level1 : bool, default: ``True`` Variable to drop the level_1 column. - Options include: ``True`` or ``False``, default set to ``True`` - overwrite_xy : bool + Options include: ``True`` or ``False``, default set to ``True``. + overwrite_xy : bool, default: ``False`` Variable to overwrite existing X and Y values. - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. target_crs : Union[str, pyproj.crs.crs.CRS] - Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'`` + Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'``. bbox : Optional[Sequence[float]] - Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]`` + Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]``. Returns ------- gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame with appended X and Y coordinates as additional columns and optional columns + GeoDataFrame with appended X and Y coordinates as additional columns and optional columns. + + +----+-----------+------------------------+-------+--------+ + | | formation | geometry | X | Y | + +----+-----------+------------------------+-------+--------+ + | 0 | Sand1 | POINT (0.256 264.862) | 0.26 | 264.86 | + +----+-----------+------------------------+-------+--------+ + | 1 | Sand1 | POINT (10.593 276.734) | 10.59 | 276.73 | + +----+-----------+------------------------+-------+--------+ + | 2 | Sand1 | POINT (17.135 289.090) | 17.13 | 289.09 | + +----+-----------+------------------------+-------+--------+ + | 3 | Sand1 | POINT (19.150 293.313) | 19.15 | 293.31 | + +----+-----------+------------------------+-------+--------+ + | 4 | Sand1 | POINT (27.795 310.572) | 27.80 | 310.57 | + +----+-----------+------------------------+-------+--------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ >>> # Loading Libraries and File @@ -383,27 +509,43 @@ def extract_xy_linestrings(gdf: gpd.geodataframe.GeoDataFrame, >>> import geopandas as gpd >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id formation geometry - 0 None Sand1 LINESTRING (0.256 264.862, 10.593 276.734, 17.... - 1 None Ton LINESTRING (0.188 495.787, 8.841 504.142, 41.0... - 2 None Ton LINESTRING (970.677 833.053, 959.372 800.023, ... + + +----+-----------+-----------+----------------------------------------------------+ + | | id | formation | geometry | + +----+-----------+-----------+----------------------------------------------------+ + | 0 | None | Sand1 | LINESTRING (0.256 264.862, 10.593 276.734, 17.... | + +----+-----------+----------------------------------------------------------------+ + | 1 | None | Ton | LINESTRING (0.188 495.787, 8.841 504.142, 41.0... | + +----+-----------+----------------------------------------------------------------+ + | 2 | None | Ton | LINESTRING (970.677 833.053, 959.372 800.023, ... | + +----+-----------+----------------------------------------------------------------+ >>> # Extracting X and Y Coordinates from LineString Objects >>> gdf_xy = gg.vector.extract_xy_linestrings(gdf=gdf, reset_index=False) >>> gdf_xy - formation geometry X Y - 0 Sand1 POINT (0.256 264.862) 0.26 264.86 - 1 Sand1 POINT (10.593 276.734) 10.59 276.73 - 2 Sand1 POINT (17.135 289.090) 17.13 289.09 - 3 Sand1 POINT (19.150 293.313) 19.15 293.31 - 4 Sand1 POINT (27.795 310.572) 27.80 310.57 + + +----+-----------+------------------------+-------+--------+ + | | formation | geometry | X | Y | + +----+-----------+------------------------+-------+--------+ + | 0 | Sand1 | POINT (0.256 264.862) | 0.26 | 264.86 | + +----+-----------+------------------------+-------+--------+ + | 1 | Sand1 | POINT (10.593 276.734) | 10.59 | 276.73 | + +----+-----------+------------------------+-------+--------+ + | 2 | Sand1 | POINT (17.135 289.090) | 17.13 | 289.09 | + +----+-----------+------------------------+-------+--------+ + | 3 | Sand1 | POINT (19.150 293.313) | 19.15 | 293.31 | + +----+-----------+------------------------+-------+--------+ + | 4 | Sand1 | POINT (27.795 310.572) | 27.80 | 310.57 | + +----+-----------+------------------------+-------+--------+ See Also ________ - extract_xy_points : Extracting X and Y coordinates from a GeoDataFrame containing Shapely Points - extract_xy_linestring : Extracting X and Y coordinates from a GeoDataFrame containing Shapely LineStrings and + + extract_xy : Extract X and Y coordinates from Vector Data + extract_xy_points : Extract X and Y coordinates from a GeoDataFrame containing Shapely Points + extract_xy_linestring : Extract X and Y coordinates from a GeoDataFrame containing Shapely LineStrings and saving the X and Y coordinates as lists for each LineString - extract_xy : Extracting X and Y coordinates from Vector Data + Note ____ @@ -413,68 +555,70 @@ def extract_xy_linestrings(gdf: gpd.geodataframe.GeoDataFrame, # Checking that gdf is of type GepDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Check that all entries of the gdf are of type LineString if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All GeoDataFrame entries must be of geom_type linestrings') + raise TypeError("All GeoDataFrame entries must be of geom_type linestrings") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that the bbox is of type None or list if bbox is not None: if not isinstance(bbox, Sequence): - raise TypeError('The bbox values must be provided as a sequence') + raise TypeError("The bbox values must be provided as a sequence") # Checking that the bbox list only has four elements if len(bbox) != 4: - raise ValueError('Provide minx, maxx, miny and maxy values for the bbox') + raise ValueError("Provide minx, maxx, miny and maxy values for the bbox") # Checking that all elements of the list are of type int or float if not all(isinstance(bound, (int, float)) for bound in bbox): - raise TypeError('Bbox values must be of type float or int') + raise TypeError("Bbox values must be of type float or int") # Checking that drop_index is of type bool if not isinstance(drop_index, bool): - raise TypeError('Drop_index argument must be of type bool') + raise TypeError("Drop_index argument must be of type bool") # Checking that drop_id is of type bool if not isinstance(drop_id, bool): - raise TypeError('Drop_id argument must be of type bool') + raise TypeError("Drop_id argument must be of type bool") # Checking that drop_level0 is of type bool if not isinstance(drop_level0, bool): - raise TypeError('Drop_index_level0 argument must be of type bool') + raise TypeError("Drop_index_level0 argument must be of type bool") # Checking that drop_level1 is of type bool if not isinstance(drop_level1, bool): - raise TypeError('Drop_index_level1 argument must be of type bool') + raise TypeError("Drop_index_level1 argument must be of type bool") # Checking that drop_points is of type bool if not isinstance(drop_points, bool): - raise TypeError('Drop_points argument must be of type bool') + raise TypeError("Drop_points argument must be of type bool") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that the target_crs is of type string if target_crs is not None and not isinstance(target_crs, (str, pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Checking that overwrite_xy is of type bool if not isinstance(overwrite_xy, bool): - raise TypeError('Overwrite_xy argument must be of type bool') + raise TypeError("Overwrite_xy argument must be of type bool") # Checking if overwrite_xy is False and if X and Y coordinates are already present in the GeoDataFrame - if not overwrite_xy and {'X', 'Y'}.issubset(gdf.columns): - raise ValueError('X and Y columns must not be present in GeoDataFrame before the extraction of coordinates') + if not overwrite_xy and {"X", "Y"}.issubset(gdf.columns): + raise ValueError( + "X and Y columns must not be present in GeoDataFrame before the extraction of coordinates" + ) # Copying GeoDataFrame gdf = gdf.copy(deep=True) @@ -488,79 +632,81 @@ def extract_xy_linestrings(gdf: gpd.geodataframe.GeoDataFrame, # Extracting x,y coordinates from line vector data if all(shapely.has_z(gdf.geometry)): - gdf['points'] = [shapely.get_coordinates(geometry=gdf.geometry[i], - include_z=True) for i in range(len(gdf))] + gdf["points"] = [ + shapely.get_coordinates(geometry=gdf.geometry[i], include_z=True) + for i in range(len(gdf)) + ] else: - gdf['points'] = [shapely.get_coordinates(geometry=gdf.geometry[i], - include_z=False) for i in range(len(gdf))] + gdf["points"] = [ + shapely.get_coordinates(geometry=gdf.geometry[i], include_z=False) + for i in range(len(gdf)) + ] # Creating DataFrame from exploded columns - df = pd.DataFrame(data=gdf).explode('points') + df = pd.DataFrame(data=gdf).explode("points") # Try creating the DataFrame for planar LineStrings if not all(shapely.has_z(gdf.geometry)): - df[['X', 'Y']] = pd.DataFrame(data=df['points'].tolist(), - index=df.index) + df[["X", "Y"]] = pd.DataFrame(data=df["points"].tolist(), index=df.index) # If LineStrings also contain Z value, then also append a Z column else: - df[['X', 'Y', 'Z']] = pd.DataFrame(data=df['points'].tolist(), - index=df.index) + df[["X", "Y", "Z"]] = pd.DataFrame(data=df["points"].tolist(), index=df.index) # Resetting index if reset_index: df = df.reset_index() # Creating new GeoDataFrame - gdf = gpd.GeoDataFrame(data=df, - geometry=gpd.points_from_xy(df.X, df.Y), - crs=crs) + gdf = gpd.GeoDataFrame(data=df, geometry=gpd.points_from_xy(df.X, df.Y), crs=crs) # Dropping id column - if 'id' in gdf and drop_id: - gdf = gdf.drop(columns='id', - axis=1) + if "id" in gdf and drop_id: + gdf = gdf.drop(columns="id", axis=1) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) # Dropping points column - if 'points' in gdf and drop_points: - gdf = gdf.drop(columns='points', - axis=1) + if "points" in gdf and drop_points: + gdf = gdf.drop(columns="points", axis=1) # Dropping level_0 column - if reset_index and drop_level0 and 'level_0' in gdf: - gdf = gdf.drop(columns='level_0', - axis=1) + if reset_index and drop_level0 and "level_0" in gdf: + gdf = gdf.drop(columns="level_0", axis=1) # Dropping level_1 column - if reset_index and drop_level1 and 'level_1' in gdf: - gdf = gdf.drop(columns='level_1', - axis=1) + if reset_index and drop_level1 and "level_1" in gdf: + gdf = gdf.drop(columns="level_1", axis=1) # Limiting the extent of the data if bbox is not None: - gdf = gdf[(gdf.X > bbox[0]) & (gdf.X < bbox[1]) & (gdf.Y > bbox[2]) & (gdf.Y < bbox[3])] + gdf = gdf[ + (gdf.X > bbox[0]) + & (gdf.X < bbox[1]) + & (gdf.Y > bbox[2]) + & (gdf.Y < bbox[3]) + ] return gdf -def extract_xy(gdf: gpd.geodataframe.GeoDataFrame, - reset_index: bool = True, - drop_index: bool = True, - drop_id: bool = True, - drop_points: bool = True, - drop_level0: bool = True, - drop_level1: bool = True, - overwrite_xy: bool = True, - target_crs: Union[str, pyproj.crs.crs.CRS] = None, - bbox: Optional[Sequence[float]] = None, - remove_total_bounds: bool = False, - threshold_bounds: Union[float, int] = 0.1) -> gpd.geodataframe.GeoDataFrame: - """Extracting X and Y coordinates from a GeoDataFrame (Points, LineStrings, MultiLineStrings, Polygons, Geometry +def extract_xy( + gdf: gpd.geodataframe.GeoDataFrame, + reset_index: bool = True, + drop_index: bool = True, + drop_id: bool = True, + drop_points: bool = True, + drop_level0: bool = True, + drop_level1: bool = True, + overwrite_xy: bool = True, + target_crs: Union[str, pyproj.crs.crs.CRS] = None, + bbox: Optional[Sequence[float]] = None, + remove_total_bounds: bool = False, + threshold_bounds: Union[float, int] = 0.1, +) -> gpd.geodataframe.GeoDataFrame: + """Extract X and Y coordinates from a GeoDataFrame (Points, LineStrings, MultiLineStrings, Polygons, Geometry Collections) and returning a GeoDataFrame with X and Y coordinates as additional columns Parameters @@ -568,62 +714,89 @@ def extract_xy(gdf: gpd.geodataframe.GeoDataFrame, gdf : gpd.geodataframe.GeoDataFrame GeoDataFrame created from vector data such as Shapely Points, LineStrings, MultiLineStrings or Polygons or - data loaded from disc with GeoPandas (i.e. Shape File) - - reset_index : bool + data loaded from disc with GeoPandas (i.e. Shape File). + + +----+-----------+------------------------+ + | id | formation | geometry | + +----+-----------+------------------------+ + | 0 | None | POINT (19.150 293.313) | + +----+-----------+------------------------+ + | 1 | None | POINT (61.934 381.459) | + +----+-----------+------------------------+ + | 2 | None | POINT (109.358 480.946)| + +----+-----------+------------------------+ + | 3 | None | POINT (157.812 615.999)| + +----+-----------+------------------------+ + | 4 | None | POINT (191.318 719.094)| + +----+-----------+------------------------+ + + + reset_index : bool, default: ``True`` Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - drop_level0 : bool + drop_level0 : bool, default: ``True`` Variable to drop the level_0 column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - drop_level1 : bool + drop_level1 : bool, default: ``True`` Variable to drop the level_1 column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - drop_index : bool + drop_index : bool, default: ``True`` Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - drop_id : bool + drop_id : bool, default: ``True`` Variable to drop the id column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - drop_points : bool + drop_points : bool, default: ``True`` Variable to drop the points column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - overwrite_xy : bool + overwrite_xy : bool, default: ``False`` Variable to overwrite existing X and Y values. - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. target_crs : Union[str, pyproj.crs.crs.CRS] - Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'`` + Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'``. bbox : list - Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]`` + Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]``. - remove_total_bounds: bool + remove_total_bounds: bool, default: ``False`` Variable to remove the vertices representing the total bounds of a GeoDataFrame consisting of Polygons - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. - threshold_bounds : Union[float, int] + threshold_bounds : Union[float, int], default: ``0.1`` Variable to set the distance to the total bound from where vertices are being removed, - e.g. ``threshold_bounds=10``, default set to ``0.1`` + e.g. ``threshold_bounds=10``, default set to ``0.1``. Returns _______ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame with appended x, y columns and Point geometry features + GeoDataFrame with appended x, y columns and Point geometry features. + + +----+-----------+------------------------+--------+--------+ + | ID | formation | geometry | X | Y | + +----+-----------+------------------------+--------+--------+ + | 0 | Ton | POINT (19.150 293.313) | 19.15 | 293.31 | + +----+-----------+------------------------+--------+--------+ + | 1 | Ton | POINT (61.934 381.459) | 61.93 | 381.46 | + +----+-----------+------------------------+--------+--------+ + | 2 | Ton | POINT (109.358 480.946)| 109.36 | 480.95 | + +----+-----------+------------------------+--------+--------+ + | 3 | Ton | POINT (157.812 615.999)| 157.81 | 616.00 | + +----+-----------+------------------------+--------+--------+ + | 4 | Ton | POINT (191.318 719.094)| 191.32 | 719.09 | + +----+-----------+------------------------+--------+--------+ .. versionadded:: 1.0.x - .. versionchanged:: 1.1 - If a GeoDataFrame contains LineStrings and MultiLineStrings, the index of the exploded GeoDataFrame will now - be reset. Not resetting the index will cause index errors later on. + .. versionchanged:: 1.2 Example _______ @@ -633,108 +806,128 @@ def extract_xy(gdf: gpd.geodataframe.GeoDataFrame, >>> import geopandas as gpd >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id formation geometry - 0 None Ton POINT (19.150 293.313) - 1 None Ton POINT (61.934 381.459) - 2 None Ton POINT (109.358 480.946) - 3 None Ton POINT (157.812 615.999) - 4 None Ton POINT (191.318 719.094) + + +----+-----------+------------------------+ + | id | formation | geometry | + +----+-----------+------------------------+ + | 0 | None | POINT (19.150 293.313) | + +----+-----------+------------------------+ + | 1 | None | POINT (61.934 381.459) | + +----+-----------+------------------------+ + | 2 | None | POINT (109.358 480.946)| + +----+-----------+------------------------+ + | 3 | None | POINT (157.812 615.999)| + +----+-----------+------------------------+ + | 4 | None | POINT (191.318 719.094)| + +----+-----------+------------------------+ + >>> # Extracting X and Y Coordinates from Shapely Base Geometries >>> gdf_xy = gg.vector.extract_xy(gdf=gdf, reset_index=False) >>> gdf_xy - formation geometry X Y - 0 Ton POINT (19.150 293.313) 19.15 293.31 - 1 Ton POINT (61.934 381.459) 61.93 381.46 - 2 Ton POINT (109.358 480.946) 109.36 480.95 - 3 Ton POINT (157.812 615.999) 157.81 616.00 - 4 Ton POINT (191.318 719.094) 191.32 719.09 + + +----+-----------+------------------------+--------+--------+ + | ID | formation | geometry | X | Y | + +----+-----------+------------------------+--------+--------+ + | 0 | Ton | POINT (19.150 293.313) | 19.15 | 293.31 | + +----+-----------+------------------------+--------+--------+ + | 1 | Ton | POINT (61.934 381.459) | 61.93 | 381.46 | + +----+-----------+------------------------+--------+--------+ + | 2 | Ton | POINT (109.358 480.946)| 109.36 | 480.95 | + +----+-----------+------------------------+--------+--------+ + | 3 | Ton | POINT (157.812 615.999)| 157.81 | 616.00 | + +----+-----------+------------------------+--------+--------+ + | 4 | Ton | POINT (191.318 719.094)| 191.32 | 719.09 | + +----+-----------+------------------------+--------+--------+ + See Also ________ - extract_xy_points : Extracting X and Y coordinates from a GeoDataFrame containing Shapely Points - extract_xy_linestring : Extracting X and Y coordinates from a GeoDataFrame containing Shapely LineStrings and + extract_xy_points : Extract X and Y coordinates from a GeoDataFrame containing Shapely Points + extract_xy_linestring : Extract X and Y coordinates from a GeoDataFrame containing Shapely LineStrings and saving the X and Y coordinates as lists for each LineString - extract_xy_linestrings : Extracting X and Y coordinates from a GeoDataFrame containing Shapely LineStrings - + extract_xy_linestrings : Extract X and Y coordinates from a GeoDataFrame containing Shapely LineStrings Note ____ GeoDataFrames that contain multiple types of geometries are currently not supported. Please use - ``gdf = gdf.explode().reset_index(drop=True)`` to create a GeoDataFrame with only one type of geometries - - """ + ``gdf = gdf.explode().reset_index(drop=True)`` to create a GeoDataFrame with only one type of geometries. + """ # Input object must be a GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Checking that overwrite_xy is of type bool if not isinstance(overwrite_xy, bool): - raise TypeError('Overwrite_xy argument must be of type bool') + raise TypeError("Overwrite_xy argument must be of type bool") # Checking that X and Y columns are not in the GeoDataFrame - if not overwrite_xy and {'X', 'Y'}.issubset(gdf.columns): - raise ValueError('X and Y columns must not be present in GeoDataFrame before the extraction of coordinates') + if not overwrite_xy and {"X", "Y"}.issubset(gdf.columns): + raise ValueError( + "X and Y columns must not be present in GeoDataFrame before the extraction of coordinates" + ) # Checking that drop_level0 is of type bool if not isinstance(drop_level0, bool): - raise TypeError('Drop_index_level0 argument must be of type bool') + raise TypeError("Drop_index_level0 argument must be of type bool") # Checking that drop_level1 is of type bool if not isinstance(drop_level1, bool): - raise TypeError('Drop_index_level1 argument must be of type bool') + raise TypeError("Drop_index_level1 argument must be of type bool") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that the bbox fulfills all criteria if bbox is not None: if not isinstance(bbox, Sequence): - raise TypeError('The bbox values must be provided as a sequence') + raise TypeError("The bbox values must be provided as a sequence") # Checking that the bbox list only has four elements if len(bbox) != 4: - raise ValueError('Provide minx, maxx, miny and maxy values for the bbox') + raise ValueError("Provide minx, maxx, miny and maxy values for the bbox") # Checking that all elements of the list are of type int or float if not all(isinstance(bound, (int, float)) for bound in bbox): - raise TypeError('Bbox values must be of type float or int') + raise TypeError("Bbox values must be of type float or int") # Checking that drop_id is of type bool if not isinstance(drop_id, bool): - raise TypeError('Drop_id argument must be of type bool') + raise TypeError("Drop_id argument must be of type bool") # Checking that drop_points is of type bool if not isinstance(drop_points, bool): - raise TypeError('Drop_points argument must be of type bool') + raise TypeError("Drop_points argument must be of type bool") # Checking that drop_index is of type bool if not isinstance(drop_index, bool): - raise TypeError('Drop_index argument must be of type bool') + raise TypeError("Drop_index argument must be of type bool") # Checking that the target_crs is of type string if not isinstance(target_crs, (str, type(None), pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Checking that remove_total_bounds is of type bool if not isinstance(remove_total_bounds, bool): - raise TypeError('Remove_total_bounds argument must be of type bool') + raise TypeError("Remove_total_bounds argument must be of type bool") # Checking that threshold_bounds is of type float or int if not isinstance(threshold_bounds, (float, int)): - raise TypeError('The value for the threshold for removing the total bounds must be of type float or int') + raise TypeError( + "The value for the threshold for removing the total bounds must be of type float or int" + ) # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Copying GeoDataFrame gdf = gdf.copy(deep=True) @@ -747,88 +940,95 @@ def extract_xy(gdf: gpd.geodataframe.GeoDataFrame, crs = gdf.crs # Exploding polygons to collection and saving total bounds - if all(gdf.geom_type == 'Polygon'): + if all(gdf.geom_type == "Polygon"): total_bounds = gdf.total_bounds gdf = explode_polygons(gdf=gdf) else: total_bounds = None # Exploding GeometryCollections to single geometry objects - if any(gdf.geom_type == 'GeometryCollection'): - gdf = explode_geometry_collections(gdf=gdf, - reset_index=True, - drop_level0=True, - drop_level1=True, - remove_points=True) + if any(gdf.geom_type == "GeometryCollection"): + gdf = explode_geometry_collections( + gdf=gdf, + reset_index=True, + drop_level0=True, + drop_level1=True, + remove_points=True, + ) # Converting MultiLineString to LineString for further processing - if gdf.geom_type.isin(('MultiLineString', 'LineString')).all(): - gdf = explode_multilinestrings(gdf=gdf, - reset_index=True, - drop_level0=False, - drop_level1=False) + if gdf.geom_type.isin(("MultiLineString", "LineString")).all(): + gdf = explode_multilinestrings( + gdf=gdf, reset_index=True, drop_level0=False, drop_level1=False + ) # Extracting x,y coordinates from line vector data if all(gdf.geom_type == "LineString"): - gdf = extract_xy_linestrings(gdf=gdf, - reset_index=False, - drop_id=False, - drop_index=False, - drop_points=False, - overwrite_xy=overwrite_xy, - target_crs=crs, - bbox=bbox) + gdf = extract_xy_linestrings( + gdf=gdf, + reset_index=False, + drop_id=False, + drop_index=False, + drop_points=False, + overwrite_xy=overwrite_xy, + target_crs=crs, + bbox=bbox, + ) # Extracting x,y coordinates from point vector data elif all(gdf.geom_type == "Point"): - gdf = extract_xy_points(gdf=gdf, - reset_index=False, - drop_id=False, - overwrite_xy=overwrite_xy, - target_crs=crs, - bbox=bbox) + gdf = extract_xy_points( + gdf=gdf, + reset_index=False, + drop_id=False, + overwrite_xy=overwrite_xy, + target_crs=crs, + bbox=bbox, + ) else: - raise TypeError('Input Geometry Type not supported') + raise TypeError("Input Geometry Type not supported") # Resetting the index if reset_index: gdf = gdf.reset_index() # Dropping level_0 column - if reset_index and drop_level0 and 'level_0' in gdf: - gdf = gdf.drop(columns='level_0', - axis=1) + if reset_index and drop_level0 and "level_0" in gdf: + gdf = gdf.drop(columns="level_0", axis=1) # Dropping level_1 column - if reset_index and drop_level1 and 'level_1' in gdf: - gdf = gdf.drop(columns='level_1', - axis=1) + if reset_index and drop_level1 and "level_1" in gdf: + gdf = gdf.drop(columns="level_1", axis=1) # Dropping id column - if 'id' in gdf and drop_id: - gdf = gdf.drop(columns='id', - axis=1) + if "id" in gdf and drop_id: + gdf = gdf.drop(columns="id", axis=1) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) # Dropping points column - if 'points' in gdf and drop_points: - gdf = gdf.drop(columns='points', - axis=1) + if "points" in gdf and drop_points: + gdf = gdf.drop(columns="points", axis=1) # Removing the total bounds from the gdf if remove_total_bounds and total_bounds is not None: - gdf = gdf[~(gdf['X'] <= total_bounds[0] + threshold_bounds) & - ~(gdf['X'] >= total_bounds[2] - threshold_bounds) & - ~(gdf['Y'] <= total_bounds[1] + threshold_bounds) & - ~(gdf['Y'] >= total_bounds[3] - threshold_bounds)] + gdf = gdf[ + ~(gdf["X"] <= total_bounds[0] + threshold_bounds) + & ~(gdf["X"] >= total_bounds[2] - threshold_bounds) + & ~(gdf["Y"] <= total_bounds[1] + threshold_bounds) + & ~(gdf["Y"] >= total_bounds[3] - threshold_bounds) + ] # Limiting the extent of the data if bbox is not None: - gdf = gdf[(gdf.X > bbox[0]) & (gdf.X < bbox[1]) & (gdf.Y > bbox[2]) & (gdf.Y < bbox[3])] + gdf = gdf[ + (gdf.X > bbox[0]) + & (gdf.X < bbox[1]) + & (gdf.Y > bbox[2]) + & (gdf.Y < bbox[3]) + ] # Checking and setting the dtypes of the GeoDataFrame gdf = set_dtype(gdf=gdf) @@ -840,23 +1040,43 @@ def extract_xy(gdf: gpd.geodataframe.GeoDataFrame, ############################################################### -def extract_xyz_points(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodataframe.GeoDataFrame: - """Extracting X, Y, and Z coordinates from a GeoDataFrame containing Shapely Points with Z components +def extract_xyz_points( + gdf: gpd.geodataframe.GeoDataFrame, +) -> gpd.geodataframe.GeoDataFrame: + """Extract X, Y, and Z coordinates from a GeoDataFrame containing Shapely Points with Z components. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing Shapely Points with X, Y, and Z components + GeoDataFrame containing Shapely Points with X, Y, and Z components. + + +----+-----------------------------------+ + | | geometry | + +----+-----------------------------------+ + | 0 | POINT Z (1.00000 2.00000 4.00000) | + +----+-----------------------------------+ + | 1 | POINT Z (1.00000 2.00000 4.00000) | + +----+-----------------------------------+ Returns _______ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing Shapely Points with appended X, Y, and Z columns + GeoDataFrame containing Shapely Points with appended X, Y, and Z columns. + + +----+-----------------------------------+-------+-------+-------+ + | ID | geometry | X | Y | Z | + +----+-----------------------------------+-------+-------+-------+ + | 0 | POINT Z (1.00000 2.00000 4.00000) | 1.00 | 2.00 | 4.00 | + +----+-----------------------------------+-------+-------+-------+ + | 1 | POINT Z (1.00000 2.00000 4.00000) | 1.00 | 2.00 | 4.00 | + +----+-----------------------------------+-------+-------+-------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -866,88 +1086,121 @@ def extract_xyz_points(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodataframe.G >>> import geopandas as gpd >>> point = Point(1,2,4) >>> point.wkt - 'POINT Z (0 0 0)' + 'POINT Z (1 2 4)' >>> # Creating GeoDataFrame from Point >>> gdf = gpd.GeoDataFrame(geometry=[point, point]) >>> gdf - geometry - 0 POINT Z (0.00000 0.00000 0.00000) - 1 POINT Z (0.00000 0.00000 0.00000) + + +----+-----------------------------------+ + | | geometry | + +----+-----------------------------------+ + | 0 | POINT Z (1.00000 2.00000 4.00000) | + +----+-----------------------------------+ + | 1 | POINT Z (1.00000 2.00000 4.00000) | + +----+-----------------------------------+ >>> # Extracting X, Y, and Z Coordinates from Point Objects >>> gdf = gg.vector.extract_xyz_points(gdf=gdf) >>> gdf - geometry X Y Z - 0 POINT Z (1.00000 2.00000 3.00000) 1.00 2.00 3.00 - 1 POINT Z (1.00000 2.00000 3.00000) 1.00 2.00 3.00 + + +----+-----------------------------------+-------+-------+-------+ + | ID | geometry | X | Y | Z | + +----+-----------------------------------+-------+-------+-------+ + | 0 | POINT Z (1.00000 2.00000 4.00000) | 1.00 | 2.00 | 4.00 | + +----+-----------------------------------+-------+-------+-------+ + | 1 | POINT Z (1.00000 2.00000 4.00000) | 1.00 | 2.00 | 4.00 | + +----+-----------------------------------+-------+-------+-------+ See Also ________ - extract_xyz_linestrings: Extracting X and Y coordinates from a GeoDataFrame containing Shapely LineStrings with - Z components - extract_xyz_polygons: Extracting X and Y coordinates from a GeoDataFrame containing Shapely Polygons with Z - component + extract_xyz_linestrings: Extract X and Y coordinates from a GeoDataFrame containing Shapely LineStrings with + Z components. + extract_xyz_polygons: Extract X and Y coordinates from a GeoDataFrame containing Shapely Polygons with Z + component. """ - # Checking that the input data is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Checking that all geometry objects are points - if not all(gdf.geom_type == 'Point'): - raise TypeError('All geometry objects must be Shapely Points') + if not all(gdf.geom_type == "Point"): + raise TypeError("All geometry objects must be Shapely Points") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that all points have a z component if not all(shapely.has_z(gdf.geometry)): - raise TypeError('Not all Shapely Objects have a z component') + raise TypeError("Not all Shapely Objects have a z component") # Appending coordinates - gdf['X'] = shapely.get_x(gdf.geometry) - gdf['Y'] = shapely.get_y(gdf.geometry) - gdf['Z'] = shapely.get_z(gdf.geometry) + gdf["X"] = shapely.get_x(gdf.geometry) + gdf["Y"] = shapely.get_y(gdf.geometry) + gdf["Z"] = shapely.get_z(gdf.geometry) return gdf -def extract_xyz_linestrings(gdf: gpd.geodataframe.GeoDataFrame, - reset_index: bool = True, - drop_index: bool = True) -> gpd.geodataframe.GeoDataFrame: - """Extracting X, Y, and Z coordinates from a GeoDataFrame containing Shapely LineStrings with Z components +def extract_xyz_linestrings( + gdf: gpd.geodataframe.GeoDataFrame, + reset_index: bool = True, + drop_index: bool = True, +) -> gpd.geodataframe.GeoDataFrame: + """Extract X, Y, and Z coordinates from a GeoDataFrame containing Shapely LineStrings with Z components. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing Shapely LineStrings with X, Y, and Z components + GeoDataFrame containing Shapely LineStrings with X, Y, and Z components. + + +----+-----------------------------------------------------+ + | | geometry | + +----+-----------------------------------------------------+ + | 0 | LINESTRING Z (1.00000 2.00000 3.00000, 4.00000 ...) | + +----+-----------------------------------------------------+ + | 1 | LINESTRING Z (1.00000 2.00000 3.00000, 4.00000 ...) | + +----+-----------------------------------------------------+ - reset_index : bool + reset_index : bool, default: ``True`` Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - drop_index : bool + drop_index : bool, default: ``True`` Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. Returns _______ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing Shapely Points with appended X, Y, and Z columns + GeoDataFrame containing Shapely Points with appended X, Y, and Z columns. + + +----+------------------------+----------------+-------+-------+-------+ + | | geometry | points | X | Y | Z | + +----+------------------------+----------------+-------+-------+-------+ + | 0 | POINT (1.00000 2.00000)| (1.0, 2.0, 3.0)| 1.00 | 2.00 | 3.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 1 | POINT (4.00000 5.00000)| (4.0, 5.0, 6.0)| 4.00 | 5.00 | 6.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 2 | POINT (1.00000 2.00000)| (1.0, 2.0, 3.0)| 1.00 | 2.00 | 3.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 3 | POINT (4.00000 5.00000)| (4.0, 5.0, 6.0)| 4.00 | 5.00 | 6.00 | + +----+------------------------+----------------+-------+-------+-------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -962,108 +1215,147 @@ def extract_xyz_linestrings(gdf: gpd.geodataframe.GeoDataFrame, >>> # Creating GeoDataFrame from LineString >>> gdf = gpd.GeoDataFrame(geometry=[linestring, linestring]) >>> gdf - geometry - 0 LINESTRING Z (1.00000 2.00000 3.00000, 4.00000... - 1 LINESTRING Z (1.00000 2.00000 3.00000, 4.00000... + + +----+-----------------------------------------------------+ + | | geometry | + +----+-----------------------------------------------------+ + | 0 | LINESTRING Z (1.00000 2.00000 3.00000, 4.00000 ...) | + +----+-----------------------------------------------------+ + | 1 | LINESTRING Z (1.00000 2.00000 3.00000, 4.00000 ...) | + +----+-----------------------------------------------------+ + >>> # Extracting X, Y, and Z Coordinates from Point Objects >>> gdf = gg.vector.extract_xyz_linestrings(gdf=gdf) >>> gdf - geometry points X Y Z - 0 POINT (1.00000 2.00000) (1.0, 2.0, 3.0) 1.00 2.00 3.00 - 1 POINT (4.00000 5.00000) (4.0, 5.0, 6.0) 4.00 5.00 6.00 - 2 POINT (1.00000 2.00000) (1.0, 2.0, 3.0) 1.00 2.00 3.00 - 3 POINT (4.00000 5.00000) (4.0, 5.0, 6.0) 4.00 5.00 6.00 + + +----+------------------------+----------------+-------+-------+-------+ + | | geometry | points | X | Y | Z | + +----+------------------------+----------------+-------+-------+-------+ + | 0 | POINT (1.00000 2.00000)| (1.0, 2.0, 3.0)| 1.00 | 2.00 | 3.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 1 | POINT (4.00000 5.00000)| (4.0, 5.0, 6.0)| 4.00 | 5.00 | 6.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 2 | POINT (1.00000 2.00000)| (1.0, 2.0, 3.0)| 1.00 | 2.00 | 3.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 3 | POINT (4.00000 5.00000)| (4.0, 5.0, 6.0)| 4.00 | 5.00 | 6.00 | + +----+------------------------+----------------+-------+-------+-------+ + See Also ________ - extract_xyz_points: Extracting X and Y coordinates from a GeoDataFrame containing Shapely Points with + extract_xyz_points: Extract X and Y coordinates from a GeoDataFrame containing Shapely Points with Z components - extract_xyz_polygons: Extracting X and Y coordinates from a GeoDataFrame containing Shapely Polygons with Z + extract_xyz_polygons: Extract X and Y coordinates from a GeoDataFrame containing Shapely Polygons with Z component """ # Checking that the input data is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Checking that all geometry objects are points if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All geometry objects must be Shapely LineStrings') + raise TypeError("All geometry objects must be Shapely LineStrings") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that all points have a z component if not all(shapely.has_z(gdf.geometry)): - raise TypeError('Not all Shapely Objects have a z component') + raise TypeError("Not all Shapely Objects have a z component") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that drop_index is of type bool if not isinstance(drop_index, bool): - raise TypeError('Drop_index argument must be of type bool') + raise TypeError("Drop_index argument must be of type bool") # Extracting x,y coordinates from line vector data - gdf['points'] = [shapely.get_coordinates(gdf.geometry[i], include_z=True) for i in range(len(gdf))] - df = pd.DataFrame(data=gdf).explode('points') + gdf["points"] = [ + shapely.get_coordinates(gdf.geometry[i], include_z=True) + for i in range(len(gdf)) + ] + df = pd.DataFrame(data=gdf).explode("points") # Appending Column to DataFrame - df[['X', 'Y', 'Z']] = pd.DataFrame(data=df['points'].tolist(), - index=df.index) + df[["X", "Y", "Z"]] = pd.DataFrame(data=df["points"].tolist(), index=df.index) # Resetting index if reset_index: df = df.reset_index() # Creating new GeoDataFrame - gdf = gpd.GeoDataFrame(data=df, - geometry=gpd.points_from_xy(df.X, df.Y), - crs=gdf.crs) + gdf = gpd.GeoDataFrame( + data=df, geometry=gpd.points_from_xy(df.X, df.Y), crs=gdf.crs + ) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) return gdf -def extract_xyz_polygons(gdf: gpd.geodataframe.GeoDataFrame, - reset_index: bool = True, - drop_index: bool = True) -> gpd.geodataframe.GeoDataFrame: - """Extracting X, Y, and Z coordinates from a GeoDataFrame containing Shapely Polygons with Z components +def extract_xyz_polygons( + gdf: gpd.geodataframe.GeoDataFrame, + reset_index: bool = True, + drop_index: bool = True, +) -> gpd.geodataframe.GeoDataFrame: + """Extract X, Y, and Z coordinates from a GeoDataFrame containing Shapely Polygons with Z components. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing Shapely Polygons with X, Y, and Z components + GeoDataFrame containing Shapely Polygons with X, Y, and Z components. + + +----+--------------------------------------------------+ + | | geometry | + +----+--------------------------------------------------+ + | 0 | POLYGON Z ((0.00000 0.00000 1.00000, 1.00000 ... | + +----+--------------------------------------------------+ + | 1 | POLYGON Z ((0.00000 0.00000 1.00000, 1.00000 ... | + +----+--------------------------------------------------+ - reset_index : bool + reset_index : bool, default: ``True`` Variable to reset the index of the resulting GeoDataFrame. Options include: ``True`` or ``False``, default set to ``True`` - drop_index : bool + drop_index : bool, default: ``True`` Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. Returns _______ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing Shapely Points with appended X, Y, and Z columns + GeoDataFrame containing Shapely Points with appended X, Y, and Z columns. + + +----+------------------------+----------------+-------+-------+-------+ + | ID | geometry | points | X | Y | Z | + +----+------------------------+----------------+-------+-------+-------+ + | 0 | POINT (0.00000 0.00000)| [0.0, 0.0, 1.0]| 0.00 | 0.00 | 1.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 1 | POINT (1.00000 0.00000)| [1.0, 0.0, 1.0]| 1.00 | 0.00 | 1.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 2 | POINT (1.00000 1.00000)| [1.0, 1.0, 1.0]| 1.00 | 1.00 | 1.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 3 | POINT (0.00000 1.00000)| [0.0, 1.0, 1.0]| 0.00 | 1.00 | 1.00 | + +----+------------------------+----------------+-------+-------+-------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -1078,152 +1370,183 @@ def extract_xyz_polygons(gdf: gpd.geodataframe.GeoDataFrame, >>> # Creating GeoDataFrame from LineString >>> gdf = gpd.GeoDataFrame(geometry=[polygon, polygon]) >>> gdf - geometry - 0 POLYGON Z ((0.00000 0.00000 1.00000, 1.00000 0... - 1 POLYGON Z ((0.00000 0.00000 1.00000, 1.00000 0... + + +----+--------------------------------------------------+ + | | geometry | + +----+--------------------------------------------------+ + | 0 | POLYGON Z ((0.00000 0.00000 1.00000, 1.00000 ... | + +----+--------------------------------------------------+ + | 1 | POLYGON Z ((0.00000 0.00000 1.00000, 1.00000 ... | + +----+--------------------------------------------------+ + >>> # Extracting X, Y, and Z Coordinates from Point Objects >>> gdf = gg.vector.extract_xyz_polygons(gdf=gdf) >>> gdf - geometry points X Y Z - 0 POINT (0.00000 0.00000) [0.0, 0.0, 1.0] 0.00 0.00 1.00 - 1 POINT (1.00000 0.00000) [1.0, 0.0, 1.0] 1.00 0.00 1.00 - 2 POINT (1.00000 1.00000) [1.0, 1.0, 1.0] 1.00 1.00 1.00 - 3 POINT (0.00000 1.00000) [0.0, 1.0, 1.0] 0.00 1.00 1.00 + + +----+------------------------+----------------+-------+-------+-------+ + | ID | geometry | points | X | Y | Z | + +----+------------------------+----------------+-------+-------+-------+ + | 0 | POINT (0.00000 0.00000)| [0.0, 0.0, 1.0]| 0.00 | 0.00 | 1.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 1 | POINT (1.00000 0.00000)| [1.0, 0.0, 1.0]| 1.00 | 0.00 | 1.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 2 | POINT (1.00000 1.00000)| [1.0, 1.0, 1.0]| 1.00 | 1.00 | 1.00 | + +----+------------------------+----------------+-------+-------+-------+ + | 3 | POINT (0.00000 1.00000)| [0.0, 1.0, 1.0]| 0.00 | 1.00 | 1.00 | + +----+------------------------+----------------+-------+-------+-------+ + See Also ________ - extract_xyz_points: Extracting X and Y coordinates from a GeoDataFrame containing Shapely Points with Z + extract_xyz_points: Extract X and Y coordinates from a GeoDataFrame containing Shapely Points with Z component - extract_xyz_linestrings: Extracting X and Y coordinates from a GeoDataFrame containing Shapely LineStrings with + extract_xyz_linestrings: Extract X and Y coordinates from a GeoDataFrame containing Shapely LineStrings with Z components """ - # Checking that the input data is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Checking that all geometry objects are points if not all(shapely.get_type_id(gdf.geometry) == 3): - raise TypeError('All geometry objects must be Shapely Polygons') + raise TypeError("All geometry objects must be Shapely Polygons") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that all points have a z component if not all(shapely.has_z(gdf.geometry)): - raise TypeError('Not all Shapely Objects have a z component') + raise TypeError("Not all Shapely Objects have a z component") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that drop_index is of type bool if not isinstance(drop_index, bool): - raise TypeError('Drop_index argument must be of type bool') + raise TypeError("Drop_index argument must be of type bool") # Extracting x,y coordinates from line vector data - gdf['points'] = [shapely.get_coordinates(gdf.geometry[i], include_z=True) for i in range(len(gdf))] - df = pd.DataFrame(data=gdf).explode('points') + gdf["points"] = [ + shapely.get_coordinates(gdf.geometry[i], include_z=True) + for i in range(len(gdf)) + ] + df = pd.DataFrame(data=gdf).explode("points") # Appending Column to DataFrame - df[['X', 'Y', 'Z']] = pd.DataFrame(data=df['points'].tolist(), - index=df.index) + df[["X", "Y", "Z"]] = pd.DataFrame(data=df["points"].tolist(), index=df.index) # Resetting index if reset_index: df = df.reset_index() # Creating new GeoDataFrame - gdf = gpd.GeoDataFrame(data=df, - geometry=gpd.points_from_xy(df.X, df.Y), - crs=gdf.crs) + gdf = gpd.GeoDataFrame( + data=df, geometry=gpd.points_from_xy(df.X, df.Y), crs=gdf.crs + ) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) return gdf -def extract_xyz_rasterio(gdf: gpd.geodataframe.GeoDataFrame, - dem: rasterio.io.DatasetReader, - minz: float = None, - maxz: float = None, - reset_index: bool = True, - drop_index: bool = True, - drop_id: bool = True, - drop_points: bool = True, - drop_level0: bool = True, - drop_level1: bool = True, - target_crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None, - bbox: Optional[Sequence[float]] = None, - remove_total_bounds: bool = False, - threshold_bounds: Union[float, int] = 0.1 - ) -> gpd.geodataframe.GeoDataFrame: - """Extracting X and Y coordinates from a GeoDataFrame (Points, LineStrings, MultiLineStrings Polygons) and z values - from a rasterio object and returning a GeoDataFrame with X, Y, and Z coordinates as additional columns +def extract_xyz_rasterio( + gdf: gpd.geodataframe.GeoDataFrame, + dem: rasterio.io.DatasetReader, + minz: float = None, + maxz: float = None, + reset_index: bool = True, + drop_index: bool = True, + drop_id: bool = True, + drop_points: bool = True, + drop_level0: bool = True, + drop_level1: bool = True, + target_crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None, + bbox: Optional[Sequence[float]] = None, + remove_total_bounds: bool = False, + threshold_bounds: Union[float, int] = 0.1, +) -> gpd.geodataframe.GeoDataFrame: + """Extract X and Y coordinates from a GeoDataFrame (Points, LineStrings, MultiLineStrings Polygons) and z values + from a rasterio object and returning a GeoDataFrame with X, Y, and Z coordinates as additional columns. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame created from vector data containing Shapely Points, LineStrings, MultiLineStrings or Polygons + GeoDataFrame created from vector data containing Shapely Points, LineStrings, MultiLineStrings or Polygons. + + +----+-----------+------------------------+ + | | formation | geometry | + +----+-----------+------------------------+ + | 0 | Ton | POINT (19.150 293.313) | + +----+-----------+------------------------+ + | 1 | Ton | POINT (61.934 381.459) | + +----+-----------+------------------------+ + | 2 | Ton | POINT (109.358 480.946)| + +----+-----------+------------------------+ + | 3 | Ton | POINT (157.812 615.999)| + +----+-----------+------------------------+ + | 4 | Ton | POINT (191.318 719.094)| + +----+-----------+------------------------+ dem : rasterio.io.DatasetReader - Rasterio object containing the height values + Rasterio object containing the height values. - minz : float - Value defining the minimum elevation the data needs to be returned, e.g. ``minz=50``, default ``None`` + minz : float, default: ``None`` + Value defining the minimum elevation the data needs to be returned, e.g. ``minz=50``, default ``None``. - maxz : float - Value defining the maximum elevation the data needs to be returned, e.g. ``maxz=500``, default ``None`` + maxz : float, default: ``None`` + Value defining the maximum elevation the data needs to be returned, e.g. ``maxz=500``, default ``None```. - reset_index : bool - Variable to reset the index of the resulting GeoDataFrame, default ``True`` + reset_index : bool, default: ``True`` + Variable to reset the index of the resulting GeoDataFrame, e.g. ``reset_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level0 : bool - Variable to drop the level_0 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level0 : bool, default: ``True`` + Variable to drop the level_0 column, e.g. ``drop_level0=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level1 : bool - Variable to drop the level_1 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level1 : bool, default: ``True`` + Variable to drop the level_1 column, e.g. ``drop_level1=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_index : bool - Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_index : bool, default: ``True`` + Variable to drop the index column, e.g. ``drop_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_id : bool + drop_id : bool, default: ``True`` Variable to drop the id column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - drop_points : bool - Variable to drop the points column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_points : bool, default: ``True`` + Variable to drop the points column, e.g. ``drop_points=True``. + Options include: ``True`` or ``False``, default set to ``True``. - target_crs : Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] - Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'`` + target_crs : Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS], default: ``None`` + Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'``. - bbox : list - Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]`` + bbox : list, default: ``None`` + Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]``. - remove_total_bounds: bool - Variable to remove the vertices representing the total bounds of a GeoDataFrame consisting of Polygons - Options include: ``True`` or ``False``, default set to ``False`` + remove_total_bounds: bool, default: ``False`` + Variable to remove the vertices representing the total bounds of a GeoDataFrame consisting of Polygons, + e.g. ``remove_total_bounds=False``. + Options include: ``True`` or ``False``, default set to ``False``. - threshold_bounds : Union[float, int] - Variable to set the distance to the total bound from where vertices are being removed, - e.g. ``threshold_bounds=10``, default set to ``0.1`` + threshold_bounds : Union[float, int], default: ``0.1`` + Variable to set the distance to the total bounds from where vertices are being removed, + e.g. ``threshold_bounds=10``, default set to ``0.1``. Returns _______ @@ -1242,12 +1565,21 @@ def extract_xyz_rasterio(gdf: gpd.geodataframe.GeoDataFrame, >>> import rasterio >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id formation geometry - 0 None Ton POINT (19.150 293.313) - 1 None Ton POINT (61.934 381.459) - 2 None Ton POINT (109.358 480.946) - 3 None Ton POINT (157.812 615.999) - 4 None Ton POINT (191.318 719.094) + + +----+-----------+------------------------+ + | | formation | geometry | + +----+-----------+------------------------+ + | 0 | Ton | POINT (19.150 293.313) | + +----+-----------+------------------------+ + | 1 | Ton | POINT (61.934 381.459) | + +----+-----------+------------------------+ + | 2 | Ton | POINT (109.358 480.946)| + +----+-----------+------------------------+ + | 3 | Ton | POINT (157.812 615.999)| + +----+-----------+------------------------+ + | 4 | Ton | POINT (191.318 719.094)| + +----+-----------+------------------------+ + >>> # Loading raster file >>> dem = rasterio.open(fp='dem.tif') @@ -1257,203 +1589,224 @@ def extract_xyz_rasterio(gdf: gpd.geodataframe.GeoDataFrame, >>> # Extracting X, Y, and Z Coordinates from Shapely Base Geometries and raster >>> gdf_xyz = gg.vector.extract_xyz_rasterio(gdf=gdf, dem=dem, reset_index=reset_index) >>> gdf_xyz - formation geometry X Y Z - 0 Ton POINT (19.150 293.313) 19.15 293.31 364.99 - 1 Ton POINT (61.934 381.459) 61.93 381.46 400.34 - 2 Ton POINT (109.358 480.946) 109.36 480.95 459.55 - 3 Ton POINT (157.812 615.999) 157.81 616.00 525.69 - 4 Ton POINT (191.318 719.094) 191.32 719.09 597.63 + + +----+-----------+------------------------+-------+-------+-------+ + | ID | formation | geometry | X | Y | Z | + +----+-----------+------------------------+-------+-------+-------+ + | 0 | Ton | POINT (19.150 293.313) | 19.15 | 293.31| 364.99| + +----+-----------+------------------------+-------+-------+-------+ + | 1 | Ton | POINT (61.934 381.459) | 61.93 | 381.46| 400.34| + +----+-----------+------------------------+-------+-------+-------+ + | 2 | Ton | POINT (109.358 480.946)| 109.36| 480.95| 459.55| + +----+-----------+------------------------+-------+-------+-------+ + | 3 | Ton | POINT (157.812 615.999)| 157.81| 616.00| 525.69| + +----+-----------+------------------------+-------+-------+-------+ + | 4 | Ton | POINT (191.318 719.094)| 191.32| 719.09| 597.63| + +----+-----------+------------------------+-------+-------+-------+ + See Also ________ - extract_xyz_array : Extracting X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model as array - extract_xyz : Extracting X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model + extract_xyz : Extract X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model + extract_xyz_array : Extract X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model as array """ - # Checking that the input data is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Checking that the dem is a rasterio object if not isinstance(dem, rasterio.io.DatasetReader): - raise TypeError('DEM must be a rasterio object') + raise TypeError("DEM must be a rasterio object") # Checking that the geometry types of the GeoDataFrame are the supported types - if not gdf.geom_type.isin(('MultiLineString', 'LineString', 'Point', 'Polygon', 'GeometryCollection')).all(): - raise TypeError('Geometry type within GeoDataFrame not supported') + if not gdf.geom_type.isin( + ("MultiLineString", "LineString", "Point", "Polygon", "GeometryCollection") + ).all(): + raise TypeError("Geometry type within GeoDataFrame not supported") # Checking that drop_level0 is of type bool if not isinstance(drop_level0, bool): - raise TypeError('Drop_index_level0 argument must be of type bool') + raise TypeError("Drop_index_level0 argument must be of type bool") # Checking that drop_level1 is of type bool if not isinstance(drop_level1, bool): - raise TypeError('Drop_index_level1 argument must be of type bool') + raise TypeError("Drop_index_level1 argument must be of type bool") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that drop_id is of type bool if not isinstance(drop_id, bool): - raise TypeError('Drop_id argument must be of type bool') + raise TypeError("Drop_id argument must be of type bool") # Checking that drop_points is of type bool if not isinstance(drop_points, bool): - raise TypeError('Drop_points argument must be of type bool') + raise TypeError("Drop_points argument must be of type bool") # Checking that remove_total_bounds is of type bool if not isinstance(remove_total_bounds, bool): - raise TypeError('Remove_total_bounds argument must be of type bool') + raise TypeError("Remove_total_bounds argument must be of type bool") # Checking that threshold_bounds is of type float or int if not isinstance(threshold_bounds, (float, int)): - raise TypeError('The value for the threshold for removing the total bounds must be of type float or int') + raise TypeError( + "The value for the threshold for removing the total bounds must be of type float or int" + ) # Checking the GeoDataFrame does not contain a Z value - if 'Z' in gdf: - raise ValueError('Data already contains Z-values') + if "Z" in gdf: + raise ValueError("Data already contains Z-values") # Checking that the bbox fulfills all criteria if bbox is not None: if not isinstance(bbox, Sequence): - raise TypeError('The bbox values must be provided as a sequence') + raise TypeError("The bbox values must be provided as a sequence") # Checking that the bbox list only has four elements if len(bbox) != 4: - raise ValueError('Provide minx, maxx, miny and maxy values for the bbox') + raise ValueError("Provide minx, maxx, miny and maxy values for the bbox") # Checking that all elements of the list are of type int or float if not all(isinstance(bound, (int, float)) for bound in bbox): - raise TypeError('Bbox values must be of type float or int') + raise TypeError("Bbox values must be of type float or int") # Checking that the target_crs is of type string - if not isinstance(target_crs, (str, type(None), pyproj.crs.crs.CRS, rasterio.crs.CRS)): - raise TypeError('target_crs must be of type string, pyproj CRS or rasterio CRS') + if not isinstance( + target_crs, (str, type(None), pyproj.crs.crs.CRS, rasterio.crs.CRS) + ): + raise TypeError("target_crs must be of type string, pyproj CRS or rasterio CRS") # Checking that the minz value is of type float if not isinstance(minz, (float, int, type(None))): - raise TypeError('minz value must be of type float or int') + raise TypeError("minz value must be of type float or int") # Checking that the max value is of type float if not isinstance(maxz, (float, int, type(None))): - raise TypeError('minz value must be of type float or int') + raise TypeError("minz value must be of type float or int") # Checking that minz is smaller than maxz if minz is not None and maxz is not None and minz >= maxz: - raise ValueError('minz must be smaller than maxz') + raise ValueError("minz must be smaller than maxz") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Create deep copy of gdf gdf = gdf.copy(deep=True) # Extracting X and Y coordinates if they are not present in the GeoDataFrame - if not {'X', 'Y'}.issubset(gdf.columns): - gdf = extract_xy(gdf=gdf, - reset_index=False, - drop_index=False, - drop_id=False, - drop_points=False, - drop_level0=False, - drop_level1=False, - overwrite_xy=False, - target_crs=None, - bbox=None, - remove_total_bounds=remove_total_bounds, - threshold_bounds=threshold_bounds) + if not {"X", "Y"}.issubset(gdf.columns): + gdf = extract_xy( + gdf=gdf, + reset_index=False, + drop_index=False, + drop_id=False, + drop_points=False, + drop_level0=False, + drop_level1=False, + overwrite_xy=False, + target_crs=None, + bbox=None, + remove_total_bounds=remove_total_bounds, + threshold_bounds=threshold_bounds, + ) # If the CRS of the gdf and the dem are identical, just extract the heights using the rasterio sample method # NB: for points outside the bounds of the raster, nodata values will be returned if gdf.crs == dem.crs: - gdf['Z'] = sample_from_rasterio(raster=dem, - point_x=gdf['X'].tolist(), - point_y=gdf['Y'].tolist()) + gdf["Z"] = sample_from_rasterio( + raster=dem, point_x=gdf["X"].tolist(), point_y=gdf["Y"].tolist() + ) # If the CRS of the gdf and the dem are not identical, the coordinates of the gdf will be reprojected and the # z values will be appended to the original gdf else: gdf_reprojected = gdf.to_crs(crs=dem.crs) - gdf_reprojected = extract_xy(gdf=gdf_reprojected, - reset_index=False, - drop_index=False, - drop_id=False, - drop_points=False, - drop_level0=False, - drop_level1=False, - overwrite_xy=True, - target_crs=None, - bbox=None, - remove_total_bounds=remove_total_bounds, - threshold_bounds=threshold_bounds - ) - - gdf['Z'] = sample_from_rasterio(raster=dem, - point_x=gdf_reprojected['X'].tolist(), - point_y=gdf_reprojected['Y'].tolist()) + gdf_reprojected = extract_xy( + gdf=gdf_reprojected, + reset_index=False, + drop_index=False, + drop_id=False, + drop_points=False, + drop_level0=False, + drop_level1=False, + overwrite_xy=True, + target_crs=None, + bbox=None, + remove_total_bounds=remove_total_bounds, + threshold_bounds=threshold_bounds, + ) + + gdf["Z"] = sample_from_rasterio( + raster=dem, + point_x=gdf_reprojected["X"].tolist(), + point_y=gdf_reprojected["Y"].tolist(), + ) # Reprojecting coordinates to provided target_crs if target_crs is not None: gdf = gdf.to_crs(crs=target_crs) # Extracting the X and Y coordinates of the reprojected gdf - gdf = extract_xy(gdf, - reset_index=False, - drop_index=False, - drop_id=False, - drop_points=False, - drop_level0=False, - drop_level1=False, - overwrite_xy=True, - target_crs=None, - bbox=None, - remove_total_bounds=remove_total_bounds, - threshold_bounds=threshold_bounds) + gdf = extract_xy( + gdf=gdf, + reset_index=False, + drop_index=False, + drop_id=False, + drop_points=False, + drop_level0=False, + drop_level1=False, + overwrite_xy=True, + target_crs=None, + bbox=None, + remove_total_bounds=remove_total_bounds, + threshold_bounds=threshold_bounds, + ) # Dropping level_0 column - if reset_index and drop_level0 and 'level_0' in gdf: - gdf = gdf.drop(columns='level_0', - axis=1) + if reset_index and drop_level0 and "level_0" in gdf: + gdf = gdf.drop(columns="level_0", axis=1) # Dropping level_1 column - if reset_index and drop_level1 and 'level_1' in gdf: - gdf = gdf.drop(columns='level_1', - axis=1) + if reset_index and drop_level1 and "level_1" in gdf: + gdf = gdf.drop(columns="level_1", axis=1) # Dropping id column - if 'id' in gdf and drop_id: - gdf = gdf.drop(columns='id', - axis=1) + if "id" in gdf and drop_id: + gdf = gdf.drop(columns="id", axis=1) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) # Dropping points column - if 'points' in gdf and drop_points: - gdf = gdf.drop(columns='points', - axis=1) + if "points" in gdf and drop_points: + gdf = gdf.drop(columns="points", axis=1) # Limiting the extent of the data if bbox is not None: - gdf = gdf[(gdf.X > bbox[0]) & (gdf.X < bbox[1]) & (gdf.Y > bbox[2]) & (gdf.Y < bbox[3])] + gdf = gdf[ + (gdf.X > bbox[0]) + & (gdf.X < bbox[1]) + & (gdf.Y > bbox[2]) + & (gdf.Y < bbox[3]) + ] # Limiting the data to specified elevations if minz is not None: - gdf = gdf[gdf['Z'] >= minz] + gdf = gdf[gdf["Z"] >= minz] if maxz is not None: - gdf = gdf[gdf['Z'] <= maxz] + gdf = gdf[gdf["Z"] <= maxz] # Resetting the index if reset_index: @@ -1465,79 +1818,94 @@ def extract_xyz_rasterio(gdf: gpd.geodataframe.GeoDataFrame, return gdf -def extract_xyz_array(gdf: gpd.geodataframe.GeoDataFrame, - dem: np.ndarray, - extent: List[float], - minz: float = None, - maxz: float = None, - reset_index: bool = True, - drop_index: bool = True, - drop_id: bool = True, - drop_points: bool = True, - drop_level0: bool = True, - drop_level1: bool = True, - target_crs: Union[str, pyproj.crs.crs.CRS] = None, - bbox: Optional[Sequence[float]] = None, - remove_total_bounds: bool = False, - threshold_bounds: Union[float, int] = 0.1 - ) -> gpd.geodataframe.GeoDataFrame: - """Extracting X and Y coordinates from a GeoDataFrame (Points, LineStrings, MultiLineStrings Polygons) and Z values from - a NumPy nd.array and returning a GeoDataFrame with X, Y, and Z coordinates as additional columns +def extract_xyz_array( + gdf: gpd.geodataframe.GeoDataFrame, + dem: np.ndarray, + extent: List[float], + minz: float = None, + maxz: float = None, + reset_index: bool = True, + drop_index: bool = True, + drop_id: bool = True, + drop_points: bool = True, + drop_level0: bool = True, + drop_level1: bool = True, + target_crs: Union[str, pyproj.crs.crs.CRS] = None, + bbox: Optional[Sequence[float]] = None, + remove_total_bounds: bool = False, + threshold_bounds: Union[float, int] = 0.1, +) -> gpd.geodataframe.GeoDataFrame: + """Extract X and Y coordinates from a GeoDataFrame (Points, LineStrings, MultiLineStrings Polygons) and Z values from + a NumPy nd.array and returning a GeoDataFrame with X, Y, and Z coordinates as additional columns. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame created from vector data containing Shapely Points, LineStrings, MultiLineStrings or Polygons + GeoDataFrame created from vector data containing Shapely Points, LineStrings, MultiLineStrings or Polygons. + + +----+-----------+------------------------+ + | | formation | geometry | + +----+-----------+------------------------+ + | 0 | Ton | POINT (19.150 293.313) | + +----+-----------+------------------------+ + | 1 | Ton | POINT (61.934 381.459) | + +----+-----------+------------------------+ + | 2 | Ton | POINT (109.358 480.946)| + +----+-----------+------------------------+ + | 3 | Ton | POINT (157.812 615.999)| + +----+-----------+------------------------+ + | 4 | Ton | POINT (191.318 719.094)| + +----+-----------+------------------------+ dem : np.ndarray - NumPy ndarray containing the height values + NumPy ndarray containing the height values. extent : list List containing the extent of the np.ndarray, - must be provided in the same CRS as the gdf, e.g. ``extent=[0, 972, 0, 1069]`` + must be provided in the same CRS as the gdf, e.g. ``extent=[0, 972, 0, 1069]``. - minz : float - Value defining the minimum elevation the data needs to be returned, e.g. ``minz=50``, default ``None`` + minz : float, default: ``None`` + Value defining the minimum elevation the data needs to be returned, e.g. ``minz=50``, default ``None``. - maxz : float - Value defining the maximum elevation the data needs to be returned, e.g. ``maxz=500``, default ``None`` + maxz : float, default: ``None`` + Value defining the maximum elevation the data needs to be returned, e.g. ``maxz=500``, default ``None``. - reset_index : bool - Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` + reset_index : bool, default: ``None`` + Variable to reset the index of the resulting GeoDataFrame, e.g. ``reset_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level0 : bool - Variable to drop the level_0 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level0 : bool, default: ``True`` + Variable to drop the level_0 column, e.g. ``drop_level0=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level1 : bool - Variable to drop the level_1 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level1 : bool, default: ``True`` + Variable to drop the level_1 column, e.g. ``drop_level1=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_index : bool - Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_index : bool, default: ``True`` + Variable to drop the index column, e.g. ``drop_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_id : bool - Variable to drop the id column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_id : bool, default: ``True`` + Variable to drop the id column, e.g. ``drop_id=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_points : bool - Variable to drop the points column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_points : bool, default: ``True`` + Variable to drop the points column, e.g. ``drop_points=True``. + Options include: ``True`` or ``False``, default set to ``True``. target_crs : Union[str, pyproj.crs.crs.CRS] - Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'`` + Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'``. bbox : list - Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]`` + Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]``. - remove_total_bounds: bool - Variable to remove the vertices representing the total bounds of a GeoDataFrame consisting of Polygons - Options include: ``True`` or ``False``, default set to ``False`` + remove_total_bounds: bool, default: ``False`` + Variable to remove the vertices representing the total bounds of a GeoDataFrame consisting of Polygons. + Options include: ``True`` or ``False``, default set to ``False``. - threshold_bounds : Union[float, int] + threshold_bounds : Union[float, int], default: ``0.1`` Variable to set the distance to the total bound from where vertices are being removed, e.g. ``threshold_bounds=10``, default set to ``0.1`` @@ -1547,8 +1915,24 @@ def extract_xyz_array(gdf: gpd.geodataframe.GeoDataFrame, gdf : gpd.geodataframe.GeoDataFrame GeoDataFrame containing the X, Y, and Z coordinates + +----+-----------+------------------------+-------+-------+-------+ + | ID | formation | geometry | X | Y | Z | + +----+-----------+------------------------+-------+-------+-------+ + | 0 | Ton | POINT (19.150 293.313) | 19.15 | 293.31| 364.99| + +----+-----------+------------------------+-------+-------+-------+ + | 1 | Ton | POINT (61.934 381.459) | 61.93 | 381.46| 400.34| + +----+-----------+------------------------+-------+-------+-------+ + | 2 | Ton | POINT (109.358 480.946)| 109.36| 480.95| 459.55| + +----+-----------+------------------------+-------+-------+-------+ + | 3 | Ton | POINT (157.812 615.999)| 157.81| 616.00| 525.69| + +----+-----------+------------------------+-------+-------+-------+ + | 4 | Ton | POINT (191.318 719.094)| 191.32| 719.09| 597.63| + +----+-----------+------------------------+-------+-------+-------+ + .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -1558,12 +1942,20 @@ def extract_xyz_array(gdf: gpd.geodataframe.GeoDataFrame, >>> import rasterio >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id formation geometry - 0 None Ton POINT (19.150 293.313) - 1 None Ton POINT (61.934 381.459) - 2 None Ton POINT (109.358 480.946) - 3 None Ton POINT (157.812 615.999) - 4 None Ton POINT (191.318 719.094) + + +----+-----------+------------------------+ + | | formation | geometry | + +----+-----------+------------------------+ + | 0 | Ton | POINT (19.150 293.313) | + +----+-----------+------------------------+ + | 1 | Ton | POINT (61.934 381.459) | + +----+-----------+------------------------+ + | 2 | Ton | POINT (109.358 480.946)| + +----+-----------+------------------------+ + | 3 | Ton | POINT (157.812 615.999)| + +----+-----------+------------------------+ + | 4 | Ton | POINT (191.318 719.094)| + +----+-----------+------------------------+ >>> # Loading raster file >>> dem = rasterio.open(fp='dem.tif') @@ -1576,201 +1968,215 @@ def extract_xyz_array(gdf: gpd.geodataframe.GeoDataFrame, >>> # Extracting X, Y, and Z Coordinates from Shapely Base Geometries and array >>> gdf_xyz = gg.vector.extract_xyz_array(gdf=gdf, dem=dem.read(1), extent=extent, reset_index=reset_index) >>> gdf_xyz - formation geometry X Y Z - 0 Ton POINT (19.150 293.313) 19.15 293.31 364.99 - 1 Ton POINT (61.934 381.459) 61.93 381.46 400.34 - 2 Ton POINT (109.358 480.946) 109.36 480.95 459.55 - 3 Ton POINT (157.812 615.999) 157.81 616.00 525.69 - 4 Ton POINT (191.318 719.094) 191.32 719.09 597.63 + + +----+-----------+------------------------+-------+-------+-------+ + | ID | formation | geometry | X | Y | Z | + +----+-----------+------------------------+-------+-------+-------+ + | 0 | Ton | POINT (19.150 293.313) | 19.15 | 293.31| 364.99| + +----+-----------+------------------------+-------+-------+-------+ + | 1 | Ton | POINT (61.934 381.459) | 61.93 | 381.46| 400.34| + +----+-----------+------------------------+-------+-------+-------+ + | 2 | Ton | POINT (109.358 480.946)| 109.36| 480.95| 459.55| + +----+-----------+------------------------+-------+-------+-------+ + | 3 | Ton | POINT (157.812 615.999)| 157.81| 616.00| 525.69| + +----+-----------+------------------------+-------+-------+-------+ + | 4 | Ton | POINT (191.318 719.094)| 191.32| 719.09| 597.63| + +----+-----------+------------------------+-------+-------+-------+ See Also ________ - extract_xyz_rasterio : Extracting X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model + extract_xyz : Extract X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model + extract_xyz_rasterio : Extract X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model as rasterio object - extract_xyz : Extracting X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model """ - # Checking that the input data is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Checking that the dem is a np.ndarray if not isinstance(dem, np.ndarray): - raise TypeError('DEM must be a numpy.ndarray') + raise TypeError("DEM must be a numpy.ndarray") # Checking that the geometry types of the GeoDataFrame are the supported types - if not gdf.geom_type.isin(('MultiLineString', 'LineString', 'Point', 'Polygon')).all(): - raise TypeError('Geometry type within GeoDataFrame not supported') + if not gdf.geom_type.isin( + ("MultiLineString", "LineString", "Point", "Polygon") + ).all(): + raise TypeError("Geometry type within GeoDataFrame not supported") # Checking that drop_level0 is of type bool if not isinstance(drop_level0, bool): - raise TypeError('Drop_index_level0 argument must be of type bool') + raise TypeError("Drop_index_level0 argument must be of type bool") # Checking that drop_level1 is of type bool if not isinstance(drop_level1, bool): - raise TypeError('Drop_index_level1 argument must be of type bool') + raise TypeError("Drop_index_level1 argument must be of type bool") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that drop_id is of type bool if not isinstance(drop_id, bool): - raise TypeError('Drop_id argument must be of type bool') + raise TypeError("Drop_id argument must be of type bool") # Checking that drop_points is of type bool if not isinstance(drop_points, bool): - raise TypeError('Drop_points argument must be of type bool') + raise TypeError("Drop_points argument must be of type bool") - # Checking that drop_id is of type bool + # Checking that drop_index is of type bool if not isinstance(drop_index, bool): - raise TypeError('Drop_index argument must be of type bool') + raise TypeError("Drop_index argument must be of type bool") # Checking that the extent is of type list if not isinstance(extent, list): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking that all elements of the extent are of type int or float if not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Extent values must be of type int or float') + raise TypeError("Extent values must be of type int or float") # Checking that remove_total_bounds is of type bool if not isinstance(remove_total_bounds, bool): - raise TypeError('Remove_total_bounds argument must be of type bool') + raise TypeError("Remove_total_bounds argument must be of type bool") # Checking that threshold_bounds is of type float or int if not isinstance(threshold_bounds, (float, int)): - raise TypeError('The value for the threshold for removing the total bounds must be of type float or int') + raise TypeError( + "The value for the threshold for removing the total bounds must be of type float or int" + ) # Checking that the length of the list is either four or six if extent is not None: if not len(extent) == 4: if not len(extent) == 6: - raise ValueError('The extent must include only four or six values') + raise ValueError("The extent must include only four or six values") # Checking that the bbox fulfills all criteria if bbox is not None: if not isinstance(bbox, Sequence): - raise TypeError('The bbox values must be provided as a sequence') + raise TypeError("The bbox values must be provided as a sequence") # Checking that the bbox list only has four elements if len(bbox) != 4: - raise ValueError('Provide minx, maxx, miny and maxy values for the bbox') + raise ValueError("Provide minx, maxx, miny and maxy values for the bbox") # Checking that all elements of the list are of type int or float if not all(isinstance(bound, (int, float)) for bound in bbox): - raise TypeError('Bbox values must be of type float or int') + raise TypeError("Bbox values must be of type float or int") # Checking that the target_crs is of type string if not isinstance(target_crs, (str, type(None), pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Selecting x and y bounds if bbox contains values for all three directions x, y, z extent = extent[:4] # Checking that the minz value is of type float if not isinstance(minz, (float, int, type(None))): - raise TypeError('minz value must be of type float or int') + raise TypeError("minz value must be of type float or int") # Checking that the max value is of type float if not isinstance(maxz, (float, int, type(None))): - raise TypeError('minz value must be of type float or int') + raise TypeError("minz value must be of type float or int") # Checking that minz is smaller than maxz if minz is not None and maxz is not None and minz >= maxz: - raise ValueError('minz must be smaller than maxz') + raise ValueError("minz must be smaller than maxz") # Checking that the GeoDataFrame does not contain a Z value - if 'Z' in gdf: - raise ValueError('Data already contains Z-values') + if "Z" in gdf: + raise ValueError("Data already contains Z-values") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Extracting X and Y coordinates if they are not present in the GeoDataFrame - if not {'X', 'Y'}.issubset(gdf.columns): - gdf = extract_xy(gdf=gdf, - reset_index=False, - drop_index=False, - drop_id=False, - drop_points=False, - drop_level0=False, - drop_level1=False, - overwrite_xy=False, - target_crs=None, - bbox=None, - remove_total_bounds=remove_total_bounds, - threshold_bounds=threshold_bounds) - - gdf['Z'] = sample_from_array(array=dem, - extent=extent, - point_x=gdf['X'].values, - point_y=gdf['Y'].values) + if not {"X", "Y"}.issubset(gdf.columns): + gdf = extract_xy( + gdf=gdf, + reset_index=False, + drop_index=False, + drop_id=False, + drop_points=False, + drop_level0=False, + drop_level1=False, + overwrite_xy=False, + target_crs=None, + bbox=None, + remove_total_bounds=remove_total_bounds, + threshold_bounds=threshold_bounds, + ) + + gdf["Z"] = sample_from_array( + array=dem, extent=extent, point_x=gdf["X"].values, point_y=gdf["Y"].values + ) # Reprojecting coordinates to provided target_crs if target_crs is not None: gdf = gdf.to_crs(crs=target_crs) # Extracting the X and Y coordinates of the reprojected gdf - gdf = extract_xy(gdf=gdf, - reset_index=False, - drop_index=False, - drop_id=False, - drop_points=False, - drop_level0=False, - drop_level1=False, - overwrite_xy=True, - target_crs=None, - bbox=None, - remove_total_bounds=remove_total_bounds, - threshold_bounds=threshold_bounds) + gdf = extract_xy( + gdf=gdf, + reset_index=False, + drop_index=False, + drop_id=False, + drop_points=False, + drop_level0=False, + drop_level1=False, + overwrite_xy=True, + target_crs=None, + bbox=None, + remove_total_bounds=remove_total_bounds, + threshold_bounds=threshold_bounds, + ) # Resetting the index if reset_index: gdf = gdf.reset_index() # Dropping level_0 column - if reset_index and drop_level0 and 'level_0' in gdf: - gdf = gdf.drop(columns='level_0', - axis=1) + if reset_index and drop_level0 and "level_0" in gdf: + gdf = gdf.drop(columns="level_0", axis=1) # Dropping level_1 column - if reset_index and drop_level1 and 'level_1' in gdf: - gdf = gdf.drop(columns='level_1', - axis=1) + if reset_index and drop_level1 and "level_1" in gdf: + gdf = gdf.drop(columns="level_1", axis=1) # Dropping id column - if 'id' in gdf and drop_id: - gdf = gdf.drop(columns='id', - axis=1) + if "id" in gdf and drop_id: + gdf = gdf.drop(columns="id", axis=1) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) # Dropping points column - if 'points' in gdf and drop_points: - gdf = gdf.drop(columns='points', - axis=1) + if "points" in gdf and drop_points: + gdf = gdf.drop(columns="points", axis=1) # Limiting the extent of the data if bbox is not None: - gdf = gdf[(gdf.X > bbox[0]) & (gdf.X < bbox[1]) & (gdf.Y > bbox[2]) & (gdf.Y < bbox[3])] + gdf = gdf[ + (gdf.X > bbox[0]) + & (gdf.X < bbox[1]) + & (gdf.Y > bbox[2]) + & (gdf.Y < bbox[3]) + ] # Limiting the data to specified elevations if minz is not None: - gdf = gdf[gdf['Z'] >= minz] + gdf = gdf[gdf["Z"] >= minz] if maxz is not None: - gdf = gdf[gdf['Z'] <= maxz] + gdf = gdf[gdf["Z"] <= maxz] # Checking and setting the dtypes of the GeoDataFrame gdf = set_dtype(gdf=gdf) @@ -1778,24 +2184,25 @@ def extract_xyz_array(gdf: gpd.geodataframe.GeoDataFrame, return gdf -def extract_xyz(gdf: gpd.geodataframe.GeoDataFrame, - dem: Union[np.ndarray, rasterio.io.DatasetReader] = None, - minz: float = None, - maxz: float = None, - extent: List[Union[float, int]] = None, - reset_index: bool = True, - drop_index: bool = True, - drop_id: bool = True, - drop_points: bool = True, - drop_level0: bool = True, - drop_level1: bool = True, - target_crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None, - bbox: Optional[Sequence[float]] = None, - remove_total_bounds: bool = False, - threshold_bounds: Union[float, int] = 0.1 - ) -> gpd.geodataframe.GeoDataFrame: - """Extracting X and Y coordinates from a GeoDataFrame (Points, LineStrings, MultiLineStrings Polygons) and Z values from - a NumPy nd.array or a Rasterio object and returning a GeoDataFrame with X, Y, and Z coordinates as additional columns +def extract_xyz( + gdf: gpd.geodataframe.GeoDataFrame, + dem: Union[np.ndarray, rasterio.io.DatasetReader] = None, + minz: float = None, + maxz: float = None, + extent: List[Union[float, int]] = None, + reset_index: bool = True, + drop_index: bool = True, + drop_id: bool = True, + drop_points: bool = True, + drop_level0: bool = True, + drop_level1: bool = True, + target_crs: Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] = None, + bbox: Optional[Sequence[float]] = None, + remove_total_bounds: bool = False, + threshold_bounds: Union[float, int] = 0.1, +) -> gpd.geodataframe.GeoDataFrame: + """Extract X and Y coordinates from a GeoDataFrame (Points, LineStrings, MultiLineStrings Polygons) and Z values from + a NumPy nd.array or a Rasterio object and returning a GeoDataFrame with X, Y, and Z coordinates as additional columns. Parameters __________ @@ -1805,55 +2212,69 @@ def extract_xyz(gdf: gpd.geodataframe.GeoDataFrame, dem : Union[np.ndarray, rasterio.io.DatasetReader] NumPy ndarray or Rasterio object containing the height values, default value is None in case geometries - contain Z values - - minz : float - Value defining the minimum elevation of the data that needs to be returned, e.g. ``minz=50``, default ``None`` - - maxz : float - Value defining the maximum elevation of the data that needs to be returned, e.g. ``maxz=500``, default ``None`` + contain Z values. + + +----+-----------+------------------------+ + | | formation | geometry | + +----+-----------+------------------------+ + | 0 | Ton | POINT (19.150 293.313) | + +----+-----------+------------------------+ + | 1 | Ton | POINT (61.934 381.459) | + +----+-----------+------------------------+ + | 2 | Ton | POINT (109.358 480.946)| + +----+-----------+------------------------+ + | 3 | Ton | POINT (157.812 615.999)| + +----+-----------+------------------------+ + | 4 | Ton | POINT (191.318 719.094)| + +----+-----------+------------------------+ + + minz : float, default: ``None`` + Value defining the minimum elevation of the data that needs to be returned, e.g. ``minz=50``, default ``None``. + + maxz : float, default: ``None`` + Value defining the maximum elevation of the data that needs to be returned, e.g. ``maxz=500``, default ``None``. extent : List[Union[float,int]] List containing the extent of the np.ndarray, - must be provided in the same CRS as the gdf, e.g. ``extent=[0, 972, 0, 1069]`` + must be provided in the same CRS as the gdf, e.g. ``extent=[0, 972, 0, 1069]``. - reset_index : bool - Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` + reset_index : bool, default: ``True`` + Variable to reset the index of the resulting GeoDataFrame, e.g. ``reset_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level0 : bool - Variable to drop the level_0 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level0 : bool, default: ``True`` + Variable to drop the level_0 column, e.g. ``drop_level0=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level1 : bool - Variable to drop the level_1 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level1 : bool, default: ``True`` + Variable to drop the level_1 column, e.g. ``drop_level1=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_index : bool - Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_index : bool, default: ``True`` + Variable to drop the index column, e.g. ``drop_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_id : bool - Variable to drop the id column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_id : bool, default: ``True`` + Variable to drop the id column, e.g. ``drop_id=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_points : bool - Variable to drop the points column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_points : bool, default: ``True`` + Variable to drop the points column, e.g. ``drop_points=True``. + Options include: ``True`` or ``False``, default set to ``True``. target_crs : Union[str, pyproj.crs.crs.CRS, rasterio.crs.CRS] - Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'`` + Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``target_crs='EPSG:4647'``. bbox : list - Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]`` + Values (minx, maxx, miny, maxy) to limit the extent of the data, e.g. ``bbox=[0, 972, 0, 1069]``. - remove_total_bounds: bool - Variable to remove the vertices representing the total bounds of a GeoDataFrame consisting of Polygons - Options include: ``True`` or ``False``, default set to ``False`` + remove_total_bounds: bool, default: ``False`` + Variable to remove the vertices representing the total bounds of a GeoDataFrame consisting of Polygons. + Options include: ``True`` or ``False``, default set to ``False``. - threshold_bounds : Union[float, int] + threshold_bounds : Union[float, int], default: ``0.1`` Variable to set the distance to the total bound from where vertices are being removed, - e.g. ``threshold_bounds=10``, default set to ``0.1`` + e.g. ``threshold_bounds=10``, default set to ``0.1``. Returns _______ @@ -1861,8 +2282,24 @@ def extract_xyz(gdf: gpd.geodataframe.GeoDataFrame, gdf : gpd.geodataframe.GeoDataFrame GeoDataFrame containing the X, Y, and Z coordinates as additional columns + +----+-----------+------------------------+-------+-------+-------+ + | ID | formation | geometry | X | Y | Z | + +----+-----------+------------------------+-------+-------+-------+ + | 0 | Ton | POINT (19.150 293.313) | 19.15 | 293.31| 364.99| + +----+-----------+------------------------+-------+-------+-------+ + | 1 | Ton | POINT (61.934 381.459) | 61.93 | 381.46| 400.34| + +----+-----------+------------------------+-------+-------+-------+ + | 2 | Ton | POINT (109.358 480.946)| 109.36| 480.95| 459.55| + +----+-----------+------------------------+-------+-------+-------+ + | 3 | Ton | POINT (157.812 615.999)| 157.81| 616.00| 525.69| + +----+-----------+------------------------+-------+-------+-------+ + | 4 | Ton | POINT (191.318 719.094)| 191.32| 719.09| 597.63| + +----+-----------+------------------------+-------+-------+-------+ + .. versionadded:: 1.0.x + .. versionchanged.: 1.2 + Example _______ @@ -1872,12 +2309,20 @@ def extract_xyz(gdf: gpd.geodataframe.GeoDataFrame, >>> import rasterio >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id formation geometry - 0 None Ton POINT (19.150 293.313) - 1 None Ton POINT (61.934 381.459) - 2 None Ton POINT (109.358 480.946) - 3 None Ton POINT (157.812 615.999) - 4 None Ton POINT (191.318 719.094) + + +----+-----------+------------------------+ + | | formation | geometry | + +----+-----------+------------------------+ + | 0 | Ton | POINT (19.150 293.313) | + +----+-----------+------------------------+ + | 1 | Ton | POINT (61.934 381.459) | + +----+-----------+------------------------+ + | 2 | Ton | POINT (109.358 480.946)| + +----+-----------+------------------------+ + | 3 | Ton | POINT (157.812 615.999)| + +----+-----------+------------------------+ + | 4 | Ton | POINT (191.318 719.094)| + +----+-----------+------------------------+ >>> # Loading raster file >>> dem = rasterio.open(fp='dem.tif') @@ -1887,74 +2332,87 @@ def extract_xyz(gdf: gpd.geodataframe.GeoDataFrame, >>> # Extracting X, Y, and Z Coordinates from Shapely Base Geometries and DEM >>> gdf_xyz = gg.vector.extract_xyz(gdf=gdf, dem=dem, reset_index=reset_index) >>> gdf_xyz - formation geometry X Y Z - 0 Ton POINT (19.150 293.313) 19.15 293.31 364.99 - 1 Ton POINT (61.934 381.459) 61.93 381.46 400.34 - 2 Ton POINT (109.358 480.946) 109.36 480.95 459.55 - 3 Ton POINT (157.812 615.999) 157.81 616.00 525.69 - 4 Ton POINT (191.318 719.094) 191.32 719.09 597.63 + + +----+-----------+------------------------+-------+-------+-------+ + | ID | formation | geometry | X | Y | Z | + +----+-----------+------------------------+-------+-------+-------+ + | 0 | Ton | POINT (19.150 293.313) | 19.15 | 293.31| 364.99| + +----+-----------+------------------------+-------+-------+-------+ + | 1 | Ton | POINT (61.934 381.459) | 61.93 | 381.46| 400.34| + +----+-----------+------------------------+-------+-------+-------+ + | 2 | Ton | POINT (109.358 480.946)| 109.36| 480.95| 459.55| + +----+-----------+------------------------+-------+-------+-------+ + | 3 | Ton | POINT (157.812 615.999)| 157.81| 616.00| 525.69| + +----+-----------+------------------------+-------+-------+-------+ + | 4 | Ton | POINT (191.318 719.094)| 191.32| 719.09| 597.63| + +----+-----------+------------------------+-------+-------+-------+ See Also ________ - extract_xyz_array : Extracting X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model as array - extract_xyz_rasterio : Extracting X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation + extract_xyz_array : Extract X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation Model as array + extract_xyz_rasterio : Extract X, Y, and Z coordinates from a GeoDataFrame and Digital Elevation as rasterio object """ - # Checking that the input data is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Checking that the dem is a np.ndarray or rasterio object if not isinstance(dem, (np.ndarray, rasterio.io.DatasetReader, type(None))): - raise TypeError('DEM must be a numpy.ndarray or rasterio object') + raise TypeError("DEM must be a numpy.ndarray or rasterio object") # Checking that the geometry types of the GeoDataFrame are the supported types - if not gdf.geom_type.isin(('MultiLineString', 'LineString', 'Point', 'Polygon', 'GeometryCollection')).all(): - raise TypeError('Geometry type within GeoDataFrame not supported') + if not gdf.geom_type.isin( + ("MultiLineString", "LineString", "Point", "Polygon", "GeometryCollection") + ).all(): + raise TypeError("Geometry type within GeoDataFrame not supported") # Checking that drop_level0 is of type bool if not isinstance(drop_level0, bool): - raise TypeError('Drop_index_level0 argument must be of type bool') + raise TypeError("Drop_index_level0 argument must be of type bool") # Checking that drop_level1 is of type bool if not isinstance(drop_level1, bool): - raise TypeError('Drop_index_level1 argument must be of type bool') + raise TypeError("Drop_index_level1 argument must be of type bool") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that drop_id is of type bool if not isinstance(drop_id, bool): - raise TypeError('Drop_id argument must be of type bool') + raise TypeError("Drop_id argument must be of type bool") # Checking that drop_points is of type bool if not isinstance(drop_points, bool): - raise TypeError('Drop_points argument must be of type bool') + raise TypeError("Drop_points argument must be of type bool") # Checking that drop_id is of type bool if not isinstance(drop_index, bool): - raise TypeError('Drop_index argument must be of type bool') + raise TypeError("Drop_index argument must be of type bool") # Checking that the target_crs is of type string - if not isinstance(target_crs, (str, type(None), pyproj.crs.crs.CRS, rasterio.crs.CRS)): - raise TypeError('target_crs must be of type string, pyproj CRS or rasterio CRS') + if not isinstance( + target_crs, (str, type(None), pyproj.crs.crs.CRS, rasterio.crs.CRS) + ): + raise TypeError("target_crs must be of type string, pyproj CRS or rasterio CRS") # Checking that the extent is of type list if isinstance(dem, np.ndarray) and not isinstance(extent, list): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking that all elements of the extent are of type int or float - if isinstance(dem, np.ndarray) and not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Extent values must be of type int or float') + if isinstance(dem, np.ndarray) and not all( + isinstance(n, (int, float)) for n in extent + ): + raise TypeError("Extent values must be of type int or float") # Checking that the length of the list is either four or six if extent is not None: if len(extent) not in (4, 6): - raise ValueError('The extent must include only four or six values') + raise ValueError("The extent must include only four or six values") # Selecting x and y bounds if bbox contains values for all three directions x, y, z if isinstance(dem, np.ndarray) and len(extent) == 6: @@ -1962,41 +2420,43 @@ def extract_xyz(gdf: gpd.geodataframe.GeoDataFrame, # Checking that the minz value is of type float if not isinstance(minz, (float, int, type(None))): - raise TypeError('minz value must be of type float or int') + raise TypeError("minz value must be of type float or int") # Checking that the max value is of type float if not isinstance(maxz, (float, int, type(None))): - raise TypeError('minz value must be of type float or int') + raise TypeError("minz value must be of type float or int") # Checking that minz is smaller than maxz if minz is not None and maxz is not None and minz >= maxz: - raise ValueError('minz must be smaller than maxz') + raise ValueError("minz must be smaller than maxz") # Checking that the bbox fulfills all criteria if bbox is not None: if not isinstance(bbox, Sequence): - raise TypeError('The bbox values must be provided as a sequence') + raise TypeError("The bbox values must be provided as a sequence") # Checking that the bbox list only has four elements if len(bbox) != 4: - raise ValueError('Provide minx, maxx, miny and maxy values for the bbox') + raise ValueError("Provide minx, maxx, miny and maxy values for the bbox") # Checking that all elements of the list are of type int or float if not all(isinstance(bound, (int, float)) for bound in bbox): - raise TypeError('Bbox values must be of type float or int') + raise TypeError("Bbox values must be of type float or int") # Checking the GeoDataFrame does not contain a Z value - if 'Z' in gdf and dem is not None: - raise ValueError('Data already contains Z-values. Please use dem=None to indicate that no DEM is needed or ' - 'remove Z values.') + if "Z" in gdf and dem is not None: + raise ValueError( + "Data already contains Z-values. Please use dem=None to indicate that no DEM is needed or " + "remove Z values." + ) # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Reprojecting coordinates to provided target_crs if target_crs is not None: @@ -2004,84 +2464,92 @@ def extract_xyz(gdf: gpd.geodataframe.GeoDataFrame, # Extracting xyz if isinstance(dem, rasterio.io.DatasetReader): - gdf = extract_xyz_rasterio(gdf=gdf, - dem=dem, - reset_index=False, - drop_id=False, - drop_index=False, - drop_level0=False, - drop_level1=False, - drop_points=False, - remove_total_bounds=remove_total_bounds, - threshold_bounds=threshold_bounds) + gdf = extract_xyz_rasterio( + gdf=gdf, + dem=dem, + reset_index=False, + drop_id=False, + drop_index=False, + drop_level0=False, + drop_level1=False, + drop_points=False, + remove_total_bounds=remove_total_bounds, + threshold_bounds=threshold_bounds, + ) elif isinstance(dem, np.ndarray): - gdf = extract_xyz_array(gdf=gdf, - dem=dem, - extent=extent, - reset_index=False, - drop_id=False, - drop_index=False, - drop_level0=False, - drop_level1=False, - drop_points=False, - remove_total_bounds=remove_total_bounds, - threshold_bounds=threshold_bounds) + gdf = extract_xyz_array( + gdf=gdf, + dem=dem, + extent=extent, + reset_index=False, + drop_id=False, + drop_index=False, + drop_level0=False, + drop_level1=False, + drop_points=False, + remove_total_bounds=remove_total_bounds, + threshold_bounds=threshold_bounds, + ) # Extracting XYZ from point consisting of a Z value - elif all(shapely.has_z(gdf.geometry)) and all(shapely.get_type_id(gdf.geometry) == 0): + elif all(shapely.has_z(gdf.geometry)) and all( + shapely.get_type_id(gdf.geometry) == 0 + ): gdf = extract_xyz_points(gdf=gdf) else: - gdf = extract_xy(gdf=gdf, - reset_index=False, - drop_id=False, - drop_index=False, - drop_level0=False, - drop_level1=False, - drop_points=False, - remove_total_bounds=remove_total_bounds, - threshold_bounds=threshold_bounds - ) + gdf = extract_xy( + gdf=gdf, + reset_index=False, + drop_id=False, + drop_index=False, + drop_level0=False, + drop_level1=False, + drop_points=False, + remove_total_bounds=remove_total_bounds, + threshold_bounds=threshold_bounds, + ) # Resetting the index if reset_index: gdf = gdf.reset_index() # Dropping level_0 column - if reset_index and drop_level0 and 'level_0' in gdf: - gdf = gdf.drop(columns='level_0', - axis=1) + if reset_index and drop_level0 and "level_0" in gdf: + gdf = gdf.drop(columns="level_0", axis=1) # Dropping level_1 column - if reset_index and drop_level1 and 'level_1' in gdf: - gdf = gdf.drop(columns='level_1', - axis=1) + if reset_index and drop_level1 and "level_1" in gdf: + gdf = gdf.drop(columns="level_1", axis=1) # Dropping id column - if 'id' in gdf and drop_id: - gdf = gdf.drop(columns='id', - axis=1) + if "id" in gdf and drop_id: + gdf = gdf.drop(columns="id", axis=1) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) # Dropping points column - if 'points' in gdf and drop_points: - gdf = gdf.drop(columns='points', axis=1) + if "points" in gdf and drop_points: + gdf = gdf.drop(columns="points", axis=1) # Limiting the extent of the data if bbox is not None: - gdf = gdf[(gdf.X > bbox[0]) & (gdf.X < bbox[1]) & (gdf.Y > bbox[2]) & (gdf.Y < bbox[3])] + gdf = gdf[ + (gdf.X > bbox[0]) + & (gdf.X < bbox[1]) + & (gdf.Y > bbox[2]) + & (gdf.Y < bbox[3]) + ] # Limiting the data to specified elevations if minz is not None: - gdf = gdf[gdf['Z'] >= minz] + gdf = gdf[gdf["Z"] >= minz] if maxz is not None: - gdf = gdf[gdf['Z'] <= maxz] + gdf = gdf[gdf["Z"] <= maxz] # Checking and setting the dtypes of the GeoDataFrame gdf = set_dtype(gdf=gdf) @@ -2092,24 +2560,29 @@ def extract_xyz(gdf: gpd.geodataframe.GeoDataFrame, # Exploding Geometries ############################################################### -def explode_linestring(linestring: shapely.geometry.linestring.LineString) -> List[shapely.geometry.point.Point]: - """Exploding a LineString to its vertices, also works for LineStrings with Z components + +def explode_linestring( + linestring: shapely.geometry.linestring.LineString, +) -> List[shapely.geometry.point.Point]: + """Explode a LineString to its vertices, also works for LineStrings with Z components. Parameters __________ linestring : shapely.geometry.linestring.LineString Shapely LineString from which vertices are extracted, - e.g. ``linestring = LineString([(0, 0), (10, 10), (20, 20)])`` + e.g. ``linestring = LineString([(0, 0), (10, 10), (20, 20)])``. Returns _______ points_list : List[shapely.geometry.point.Point] - List of extracted Shapely Points + List of extracted Shapely Points. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -2142,21 +2615,20 @@ def explode_linestring(linestring: shapely.geometry.linestring.LineString) -> Li See Also ________ - explode_linestring_to_elements : Exploding a LineString with more than two vertices into single LineStrings + explode_linestring_to_elements : Explode a LineString with more than two vertices into single LineStrings """ - # Checking that the input geometry is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapely LineString') + raise TypeError("Input geometry must be a Shapely LineString") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Extracting Points of LineString points_list = [geometry.Point(i) for i in list(linestring.coords)] @@ -2164,26 +2636,29 @@ def explode_linestring(linestring: shapely.geometry.linestring.LineString) -> Li return points_list -def explode_linestring_to_elements(linestring: shapely.geometry.linestring.LineString) -> \ - List[shapely.geometry.linestring.LineString]: - """Separating a LineString into its single elements and returning a list of LineStrings representing these elements, - also works for LineStrings with Z components +def explode_linestring_to_elements( + linestring: shapely.geometry.linestring.LineString, +) -> List[shapely.geometry.linestring.LineString]: + """Separate a LineString into its single elements and returning a list of LineStrings representing these elements, + also works for LineStrings with Z components. Parameters __________ - linestring : linestring: shapely.geometry.linestring.LineString + linestring : shapely.geometry.linestring.LineString Shapely LineString containing more than two vertices, - e.g. ``linestring = LineString([(0, 0), (10, 10), (20, 20)])`` + e.g. ``linestring = LineString([(0, 0), (10, 10), (20, 20)])``. Returns _______ splitted_linestrings : List[shapely.geometry.linestring.LineString] - List containing the separate elements of the original LineString stored as LineStrings + List containing the separate elements of the original LineString stored as LineStrings. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -2211,52 +2686,58 @@ def explode_linestring_to_elements(linestring: shapely.geometry.linestring.LineS See Also ________ - explode_linestring : Exploding a LineString into its single vertices + explode_linestring : Explode a LineString into its single vertices """ - # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapely LineString') + raise TypeError("Input geometry must be a Shapely LineString") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the LineString only consists of two vertices if len(linestring.coords) < 2: - raise ValueError('LineString must contain at least two vertices') + raise ValueError("LineString must contain at least two vertices") # Splitting the LineString into single elements and returning a list of LineStrings splitted_linestrings = list( - map(shapely.geometry.linestring.LineString, zip(linestring.coords[:-1], linestring.coords[1:]))) + map( + shapely.geometry.linestring.LineString, + zip(linestring.coords[:-1], linestring.coords[1:]), + ) + ) return splitted_linestrings -def explode_multilinestring(multilinestring: shapely.geometry.multilinestring.MultiLineString) \ - -> List[shapely.geometry.linestring.LineString]: - """Exploding a MultiLineString into a list of LineStrings +def explode_multilinestring( + multilinestring: shapely.geometry.multilinestring.MultiLineString, +) -> List[shapely.geometry.linestring.LineString]: + """Explode a MultiLineString into a list of LineStrings. Parameters __________ multilinestring : shapely.geometry.multilinestring.MultiLineString Shapely MultiLineString consisting of multiple LineStrings, - e.g. ``multilinestring = MultiLineString([((0, 0), (1, 1)), ((-1, 0), (1, 0))])`` + e.g. ``multilinestring = MultiLineString([((0, 0), (1, 1)), ((-1, 0), (1, 0))])``. Returns _______ splitted_multilinestring : List[shapely.geometry.linestring.LineString] - List of Shapely LineStrings + List of Shapely LineStrings. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -2284,26 +2765,27 @@ def explode_multilinestring(multilinestring: shapely.geometry.multilinestring.Mu See Also ________ - explode_multilinestrings : Exploding a GeoDataFrame containing MultiLineStrings into a GeoDataFrame containing + explode_multilinestrings : Explode a GeoDataFrame containing MultiLineStrings into a GeoDataFrame containing LineStrings only """ - # Checking that the MultiLineString is a Shapely MultiLineString - if not isinstance(multilinestring, shapely.geometry.multilinestring.MultiLineString): - raise TypeError('MultiLineString must be a Shapely MultiLineString') + if not isinstance( + multilinestring, shapely.geometry.multilinestring.MultiLineString + ): + raise TypeError("MultiLineString must be a Shapely MultiLineString") # Checking that the MultiLineString is valid if not multilinestring.is_valid: - raise ValueError('MultiLineString is not a valid object') + raise ValueError("MultiLineString is not a valid object") # Checking that the MultiLineString is not empty if multilinestring.is_empty: - raise ValueError('MultiLineString is an empty object') + raise ValueError("MultiLineString is an empty object") # Checking that there is at least one LineString in the MultiLineString if len(list(multilinestring.geoms)) < 1: - raise ValueError('MultiLineString must at least contain one LineString') + raise ValueError("MultiLineString must at least contain one LineString") # Creating a list of single LineStrings from MultiLineString splitted_multilinestring = list(multilinestring.geoms) @@ -2311,39 +2793,62 @@ def explode_multilinestring(multilinestring: shapely.geometry.multilinestring.Mu return splitted_multilinestring -def explode_multilinestrings(gdf: gpd.geodataframe.GeoDataFrame, - reset_index: bool = True, - drop_level0: bool = True, - drop_level1: bool = True, - ) -> gpd.geodataframe.GeoDataFrame: - """Exploding Shapely MultiLineStrings stored in a GeoDataFrame to Shapely LineStrings +def explode_multilinestrings( + gdf: gpd.geodataframe.GeoDataFrame, + reset_index: bool = True, + drop_level0: bool = True, + drop_level1: bool = True, +) -> gpd.geodataframe.GeoDataFrame: + """Explode Shapely MultiLineStrings stored in a GeoDataFrame to Shapely LineStrings. Parameters ---------- gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame created from vector data containing elements of geom_type MultiLineString + GeoDataFrame created from vector data containing elements of ``geom_type`` MultiLineString. + + +----+----------------------------------------+ + | | geometry | + +----+----------------------------------------+ + | 0 | MULTILINESTRING ((0.0 0.0, 1.0 1.0)) | + +----+----------------------------------------+ + | 1 | MULTILINESTRING ((0.0 0.0, 1.0 1.0)) | + +----+----------------------------------------+ - reset_index : bool + reset_index : bool, default: ``True`` Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - drop_level0 : bool + drop_level0 : bool, default: ``True`` Variable to drop the level_0 column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - drop_level1 : bool + drop_level1 : bool, default: ``True`` Variable to drop the level_1 column. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. Returns ------- gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing LineStrings + GeoDataFrame containing LineStrings. + + +----+------------------------------+ + | ID | geometry | + +----+------------------------------+ + | 0 | LINESTRING (0.0 0.0, 1.0 1.0)| + +----+------------------------------+ + | 1 | LINESTRING (-1.0 0.0, 1.0 0.0)| + +----+------------------------------+ + | 2 | LINESTRING (0.0 0.0, 1.0 1.0)| + +----+------------------------------+ + | 3 | LINESTRING (-1.0 0.0, 1.0 0.0)| + +----+------------------------------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -2352,53 +2857,68 @@ def explode_multilinestrings(gdf: gpd.geodataframe.GeoDataFrame, >>> import geopandas as gpd >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - geometry - 0 MULTILINESTRING ((0.0 0.0, 1.0 1.0)) - 1 MULTILINESTRING ((0.0 0.0, 1.0 1.0)) + + +----+----------------------------------------+ + | | geometry | + +----+----------------------------------------+ + | 0 | MULTILINESTRING ((0.0 0.0, 1.0 1.0)) | + +----+----------------------------------------+ + | 1 | MULTILINESTRING ((0.0 0.0, 1.0 1.0)) | + +----+----------------------------------------+ + >>> # Exploding MultiLineStrings into single LineStrings >>> gdf_linestrings = gg.vector.explode_multilinestrings(gdf=gdf, reset_index=True) >>> gdf_linestrings - geometry - 0 LINESTRING (0.0 0.0, 1.0 1.0) - 1 LINESTRING (-1.0 0.0, 1.0 0.0) - 2 LINESTRING (0.0 0.0, 1.0 1.0) - 3 LINESTRING (-1.0 0.0, 1.0 0.0) + + +----+------------------------------+ + | ID | geometry | + +----+------------------------------+ + | 0 | LINESTRING (0.0 0.0, 1.0 1.0)| + +----+------------------------------+ + | 1 | LINESTRING (-1.0 0.0, 1.0 0.0)| + +----+------------------------------+ + | 2 | LINESTRING (0.0 0.0, 1.0 1.0)| + +----+------------------------------+ + | 3 | LINESTRING (-1.0 0.0, 1.0 0.0)| + +----+------------------------------+ + See Also ________ - explode_multilinestring : Exploding a MultiLineString into a list of single LineStrings + explode_multilinestring : Explode a MultiLineString into a list of single LineStrings """ - # Checking that gdf is of type GepDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Check that all entries of the gdf are of type MultiLineString or LineString - if not all(gdf.geom_type.isin(['MultiLineString', 'LineString'])): - raise TypeError('All GeoDataFrame entries must be of geom_type MultiLineString or LineString') + if not all(gdf.geom_type.isin(["MultiLineString", "LineString"])): + raise TypeError( + "All GeoDataFrame entries must be of geom_type MultiLineString or LineString" + ) # Checking that drop_level0 is of type bool if not isinstance(drop_level0, bool): - raise TypeError('Drop_index_level0 argument must be of type bool') + raise TypeError("Drop_index_level0 argument must be of type bool") # Checking that drop_level1 is of type bool if not isinstance(drop_level1, bool): - raise TypeError('Drop_index_level1 argument must be of type bool') + raise TypeError("Drop_index_level1 argument must be of type bool") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Exploding MultiLineStrings gdf = gdf.explode(index_parts=True) @@ -2409,34 +2929,36 @@ def explode_multilinestrings(gdf: gpd.geodataframe.GeoDataFrame, # Dropping level_0 column if reset_index and drop_level0: - gdf = gdf.drop(columns='level_0', - axis=1) + gdf = gdf.drop(columns="level_0", axis=1) # Dropping level_1 column if reset_index and drop_level1: - gdf = gdf.drop(columns='level_1', - axis=1) + gdf = gdf.drop(columns="level_1", axis=1) return gdf -def explode_polygon(polygon: shapely.geometry.polygon.Polygon) -> List[shapely.geometry.point.Point]: - """Exploding Shapely Polygon to list of Points +def explode_polygon( + polygon: shapely.geometry.polygon.Polygon, +) -> List[shapely.geometry.point.Point]: + """Explode Shapely Polygon to list of Points. Parameters __________ polygon : shapely.geometry.polygon.Polygon - Shapely Polygon from which vertices are extracted, e.g. ``polygon = Polygon([(0, 0), (1, 1), (1, 0)])`` + Shapely Polygon from which vertices are extracted, e.g. ``polygon = Polygon([(0, 0), (1, 1), (1, 0)])``. Returns _______ point_list : List[shapely.geometry.point.Point] - List containing the vertices of a polygon as Shapely Points + List containing the vertices of a polygon as Shapely Points. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -2466,44 +2988,63 @@ def explode_polygon(polygon: shapely.geometry.polygon.Polygon) -> List[shapely.g See Also ________ - explode_polygons : Exploding a GeoDataFrame containing Polygons into a GeoDataFrame containing LineStrings + explode_polygons : Explode a GeoDataFrame containing Polygons into a GeoDataFrame containing LineStrings """ - # Checking that the input polygon is a Shapely object if not isinstance(polygon, shapely.geometry.polygon.Polygon): - raise TypeError('Polygon must be a Shapely Polygon') + raise TypeError("Polygon must be a Shapely Polygon") # Checking that all Shapely Objects are valid if not polygon.is_valid: - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if polygon.is_empty: - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") points_list = [geometry.Point(point) for point in list(polygon.exterior.coords)] return points_list -def explode_polygons(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodataframe.GeoDataFrame: - """Converting a GeoDataFrame containing elements of geom_type Polygons to a GeoDataFrame with LineStrings +def explode_polygons( + gdf: gpd.geodataframe.GeoDataFrame, +) -> gpd.geodataframe.GeoDataFrame: + """Convert a GeoDataFrame containing elements of ``geom_type`` Polygon to a GeoDataFrame with LineStrings. Parameters ___________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame created from vector data containing elements of geom_type Polygon + GeoDataFrame created from vector data containing elements of ``geom_type`` Polygon. + + +----+------------------------------------------------+ + | | geometry | + +----+------------------------------------------------+ + | 0 | POLYGON ((0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0)) | + +----+------------------------------------------------+ + | 1 | POLYGON ((0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0)) | + +----+------------------------------------------------+ Returns _______ gdf_linestrings : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing elements of type MultiLineString and LineString + GeoDataFrame containing elements of type MultiLineString and LineString. + + +----+-------------------------------------------------+ + | | geometry | + +----+-------------------------------------------------+ + | 0 | LINESTRING (0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0) | + +----+-------------------------------------------------+ + | 1 | LINESTRING (0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0) | + +----+-------------------------------------------------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -2512,68 +3053,79 @@ def explode_polygons(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodataframe.Geo >>> import geopandas as gpd >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - geometry - 0 POLYGON ((0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0)) - 1 POLYGON ((0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0)) + + +----+------------------------------------------------+ + | | geometry | + +----+------------------------------------------------+ + | 0 | POLYGON ((0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0)) | + +----+------------------------------------------------+ + | 1 | POLYGON ((0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0)) | + +----+------------------------------------------------+ + >>> # Exploding Polygons into LineStrings >>> gdf_exploded = gg.vector.explode_polygons(gdf=gdf) >>> gdf_exploded - geometry - 0 LINESTRING (0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0) - 1 LINESTRING (0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0) + +----+-------------------------------------------------+ + | | geometry | + +----+-------------------------------------------------+ + | 0 | LINESTRING (0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0) | + +----+-------------------------------------------------+ + | 1 | LINESTRING (0.0 0.0, 1.0 1.0, 1.0 0.0, 0.0 0.0) | + +----+-------------------------------------------------+ See Also ________ - explode_polygon : Exploding a Polygon into single Points + explode_polygon : Explod a Polygon into single Points """ - # Checking that the input is a GeoDataFrame: if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be a GeoDataFrame') + raise TypeError("gdf must be a GeoDataFrame") # Checking that the geometry types of the GeoDataFrame are the supported types - if not gdf.geom_type.isin(('Polygon', 'MultiPolygon')).all(): - raise TypeError('Geometry type within GeoDataFrame not supported') + if not gdf.geom_type.isin(("Polygon", "MultiPolygon")).all(): + raise TypeError("Geometry type within GeoDataFrame not supported") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Creating GeoDataFrame containing only LineStrings and appending remaining columns as Pandas DataFrame - gdf_linestrings = gpd.GeoDataFrame(data=gdf.drop(columns='geometry', - axis=1), - geometry=gdf.boundary, - crs=gdf.crs) + gdf_linestrings = gpd.GeoDataFrame( + data=gdf.drop(columns="geometry", axis=1), geometry=gdf.boundary, crs=gdf.crs + ) return gdf_linestrings -def explode_geometry_collection(collection: shapely.geometry.collection.GeometryCollection) \ - -> List[shapely.geometry.base.BaseGeometry]: - """Exploding a Shapely Geometry Collection to a List of Base Geometries +def explode_geometry_collection( + collection: shapely.geometry.collection.GeometryCollection, +) -> List[shapely.geometry.base.BaseGeometry]: + """Explode a Shapely Geometry Collection to a List of Base Geometries. Parameters __________ collection : shapely.geometry.collection.GeometryCollection - Shapely Geometry Collection consisting of different Base Geometries + Shapely Geometry Collection consisting of different Base Geometries. Returns _______ collection_exploded : List[shapely.geometry.base.BaseGeometry] - List of Base Geometries from the original Geometry Collection + List of Base Geometries from the original Geometry Collection. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -2603,21 +3155,20 @@ def explode_geometry_collection(collection: shapely.geometry.collection.Geometry See Also ________ - explode_geometry_collections : Exploding a GeoDataFrame containing different Base Geometries + explode_geometry_collections : Explode a GeoDataFrame containing different Base Geometries """ - # Checking that the Geometry Collection is a Shapely Geometry Collection if not isinstance(collection, shapely.geometry.collection.GeometryCollection): - raise TypeError('Geometry Collection must be a Shapely Geometry Collection') + raise TypeError("Geometry Collection must be a Shapely Geometry Collection") # Checking that the Geometry Collection is valid if not collection.is_valid: - raise ValueError('Geometry Collection is not a valid object') + raise ValueError("Geometry Collection is not a valid object") # Checking that the Geometry Collection is not empty if collection.is_empty: - raise ValueError('Geometry Collection is an empty object') + raise ValueError("Geometry Collection is an empty object") # Creating list of Base Geometries collection_exploded = list(collection.geoms) @@ -2625,44 +3176,71 @@ def explode_geometry_collection(collection: shapely.geometry.collection.Geometry return collection_exploded -def explode_geometry_collections(gdf: gpd.geodataframe.GeoDataFrame, - reset_index: bool = True, - drop_level0: bool = True, - drop_level1: bool = True, - remove_points: bool = True, - ) -> gpd.geodataframe.GeoDataFrame: - """Exploding Shapely Geometry Collections stored in GeoDataFrames to different Shapely Base Geometries +def explode_geometry_collections( + gdf: gpd.geodataframe.GeoDataFrame, + reset_index: bool = True, + drop_level0: bool = True, + drop_level1: bool = True, + remove_points: bool = True, +) -> gpd.geodataframe.GeoDataFrame: + """Explode Shapely Geometry Collections stored in a GeoDataFrame to different Shapely Base Geometries. Parameters ---------- gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame created from vector data containing elements of geom_type GeometryCollection - - reset_index : bool - Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` - - drop_level0 : bool - Variable to drop the level_0 column. - Options include: ``True`` or ``False``, default set to ``True`` - - drop_level1 : bool - Variable to drop the level_1 column. - Options include: ``True`` or ``False``, default set to ``True`` - - remove_points : bool - Variable to remove points from exploded GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` + GeoDataFrame created from vector data containing elements of geom_type GeometryCollection. + + +----+--------------------------------------------------------------+ + | | geometry | + +----+--------------------------------------------------------------+ + | 0 | LINESTRING (0.00000 0.00000, 1.00000 1.00000, ...) | + +----+--------------------------------------------------------------+ + | 1 | LINESTRING (0.00000 0.00000, 1.00000 1.00000, ...) | + +----+--------------------------------------------------------------+ + | 2 | GEOMETRYCOLLECTION (POINT (2.00000 2.00000), LINESTRING ...) | + +----+--------------------------------------------------------------+ + | 3 | POLYGON ((0.00000 0.00000, 10.00000 0.00000, 1...) | + +----+--------------------------------------------------------------+ + + reset_index : bool, default: ``True``> + Variable to reset the index of the resulting GeoDataFrame, e.g. ``reset_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. + + drop_level0 : bool, default: ``True`` + Variable to drop the level_0 column, e.g. ``drop_level0=True``. + Options include: ``True`` or ``False``, default set to ``True``. + + drop_level1 : bool, default: ``True`` + Variable to drop the level_1 column, e.g. ``drop_level1=True``. + Options include: ``True`` or ``False``, default set to ``True``. + + remove_points : bool, default: ``True`` + Variable to remove points from exploded GeoDataFrame, e.g. ``remove_points=True``. + Options include: ``True`` or ``False``, default set to ``True``. Returns ------- gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing different geometry types + GeoDataFrame containing different geometry types. + + +----+----------------------------------------------------+ + | | geometry | + +----+----------------------------------------------------+ + | 0 | LINESTRING (0.00000 0.00000, 1.00000 1.00000, ...) | + +----+----------------------------------------------------+ + | 1 | LINESTRING (0.00000 0.00000, 1.00000 1.00000, ...) | + +----+----------------------------------------------------+ + | 2 | LINESTRING (0.00000 0.00000, 1.00000 1.00000) | + +----+----------------------------------------------------+ + | 3 | POLYGON ((0.00000 0.00000, 10.00000 0.00000, 1...) | + +----+----------------------------------------------------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -2678,62 +3256,76 @@ def explode_geometry_collections(gdf: gpd.geodataframe.GeoDataFrame, >>> # Creating GeoDataFrame from Base Geometries >>> gdf = gpd.GeoDataFrame(geometry=[a, b, collection, polygon]) >>> gdf - geometry - 0 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... - 1 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... - 2 GEOMETRYCOLLECTION (POINT (2.00000 2.00000), L... - 3 POLYGON ((0.00000 0.00000, 10.00000 0.00000, 1.. + + +----+--------------------------------------------------------------+ + | | geometry | + +----+--------------------------------------------------------------+ + | 0 | LINESTRING (0.00000 0.00000, 1.00000 1.00000, ...) | + +----+--------------------------------------------------------------+ + | 1 | LINESTRING (0.00000 0.00000, 1.00000 1.00000, ...) | + +----+--------------------------------------------------------------+ + | 2 | GEOMETRYCOLLECTION (POINT (2.00000 2.00000), LINESTRING ...) | + +----+--------------------------------------------------------------+ + | 3 | POLYGON ((0.00000 0.00000, 10.00000 0.00000, 1...) | + +----+--------------------------------------------------------------+ + >>> # Explode Geometry Collection into single Base Geometries >>> gdf_exploded = gg.vector.explode_geometry_collections(gdf=gdf) >>> gdf_exploded - geometry - 0 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... - 1 LINESTRING (0.00000 0.00000, 1.00000 1.00000, ... - 2 LINESTRING (0.00000 0.00000, 1.00000 1.00000) - 3 POLYGON ((0.00000 0.00000, 10.00000 0.00000, 1... + + +----+----------------------------------------------------+ + | | geometry | + +----+----------------------------------------------------+ + | 0 | LINESTRING (0.00000 0.00000, 1.00000 1.00000, ...) | + +----+----------------------------------------------------+ + | 1 | LINESTRING (0.00000 0.00000, 1.00000 1.00000, ...) | + +----+----------------------------------------------------+ + | 2 | LINESTRING (0.00000 0.00000, 1.00000 1.00000) | + +----+----------------------------------------------------+ + | 3 | POLYGON ((0.00000 0.00000, 10.00000 0.00000, 1...) | + +----+----------------------------------------------------+ See Also ________ - explode_geometry_collection : Exploding a Shapely Geometry Collection Object into a list of Base Geometries + explode_geometry_collection : Explod a Shapely Geometry Collection Object into a list of Base Geometries """ - # Checking that gdf is of type GepDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Check that all entries of the gdf are of type MultiLineString or LineString if not any(gdf.geom_type == "GeometryCollection"): - raise TypeError('At least one geometry entry must be GeometryCollection') + raise TypeError("At least one geometry entry must be GeometryCollection") # Checking that drop_level0 is of type bool if not isinstance(drop_level0, bool): - raise TypeError('Drop_index_level0 argument must be of type bool') + raise TypeError("Drop_index_level0 argument must be of type bool") # Checking that drop_level1 is of type bool if not isinstance(drop_level1, bool): - raise TypeError('Drop_index_level1 argument must be of type bool') + raise TypeError("Drop_index_level1 argument must be of type bool") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Exploding MultiLineStrings gdf = gdf.explode(index_parts=True) # Remove Point geometries if remove_points: - gdf = gdf[np.invert(gdf.geom_type == 'Point')] + gdf = gdf[np.invert(gdf.geom_type == "Point")] # Resetting the index if reset_index: @@ -2741,13 +3333,11 @@ def explode_geometry_collections(gdf: gpd.geodataframe.GeoDataFrame, # Dropping level_0 column if reset_index and drop_level0: - gdf = gdf.drop(columns='level_0', - axis=1) + gdf = gdf.drop(columns="level_0", axis=1) # Dropping level_1 column if reset_index and drop_level1: - gdf = gdf.drop(columns='level_1', - axis=1) + gdf = gdf.drop(columns="level_1", axis=1) return gdf @@ -2755,28 +3345,43 @@ def explode_geometry_collections(gdf: gpd.geodataframe.GeoDataFrame, # Creating LineStrings with Z components from points #################################################### -def create_linestring_from_xyz_points(points: Union[np.ndarray, gpd.geodataframe.GeoDataFrame], - nodata: Union[int, float] = 9999.0, - xcol: str = 'X', - ycol: str = 'Y', - zcol: str = 'Z', - drop_nan: bool = True) -> shapely.geometry.linestring.LineString: - """ - Create LineString from an array or GeoDataFrame containing X, Y, and Z coordinates of points. + +def create_linestring_from_xyz_points( + points: Union[np.ndarray, gpd.geodataframe.GeoDataFrame], + nodata: Union[int, float] = 9999.0, + xcol: str = "X", + ycol: str = "Y", + zcol: str = "Z", + drop_nan: bool = True, +) -> shapely.geometry.linestring.LineString: + """Create LineString from an array or GeoDataFrame containing X, Y, and Z coordinates of points. Parameters __________ points : Union[np.ndarray, gpd.geodataframe.GeoDataFrame] NumPy Array or GeoDataFrame containing XYZ points. - nodata : Union[int, float]) + + +----+-------+-------+-------+ + | | X | Y | Z | + +----+-------+-------+-------+ + | 0 | 3.23 | 5.69 | 2.03 | + +----+-------+-------+-------+ + | 1 | 3.24 | 5.68 | 2.02 | + +----+-------+-------+-------+ + | 2 | 3.25 | 5.67 | 1.97 | + +----+-------+-------+-------+ + | 3 | 3.26 | 5.66 | 1.95 | + +----+-------+-------+-------+ + + nodata : Union[int, float]), default: ``9999.0`` Nodata value to filter out points outside a designated area, e.g. ``nodata=9999.0``, default is ``9999.0``. - xcol : str + xcol : str, default: ``'X'`` Name of the X column in the dataset, e.g. ``xcol='X'``, default is ``'X'``. - ycol : str + ycol : str, default: ``'Y'`` Name of the Y column in the dataset, e.g. ``ycol='Y'``, default is ``'Y'``. - zcol : str + zcol : str, default: ``'Z'`` Name of the Z column in the dataset, e.g. ``zcol='Z'``, default is ``'Z'``. - drop_nan : bool + drop_nan : bool, default: ``True`` Boolean argument to drop points that contain a ``nan`` value as Z value. Options include ``True`` and ``False``, default is ``True``. @@ -2784,12 +3389,11 @@ def create_linestring_from_xyz_points(points: Union[np.ndarray, gpd.geodataframe _______ line : shapely.geometry.linestring.LineString - LineString Z constructed from provided point values + LineString Z constructed from provided point values. .. versionadded:: 1.0.x - .. versionchanged:: 1.1 - Adding argument `drop_nan` and code to drop coordinates that contain ``nan`` values as Z coordinates. + .. versionchanged:: 1.2 Example _______ @@ -2798,34 +3402,56 @@ def create_linestring_from_xyz_points(points: Union[np.ndarray, gpd.geodataframe >>> import gemgis as gg >>> import numpy as np >>> points = np.array([[3.23, 5.69, 2.03],[3.24, 5.68, 2.02],[3.25, 5.67, 1.97],[3.26, 5.66, 1.95]]) + >>> points + + +----+-------+-------+-------+ + | | X | Y | Z | + +----+-------+-------+-------+ + | 0 | 3.23 | 5.69 | 2.03 | + +----+-------+-------+-------+ + | 1 | 3.24 | 5.68 | 2.02 | + +----+-------+-------+-------+ + | 2 | 3.25 | 5.67 | 1.97 | + +----+-------+-------+-------+ + | 3 | 3.26 | 5.66 | 1.95 | + +----+-------+-------+-------+ + >>> # Creating LineStrings from points >>> linestring = gg.vector.create_linestring_from_xyz_points(points=points) >>> linestring.wkt 'LINESTRING Z (3.23 5.69 2.03, 3.24 5.68 2.02, 3.25 5.67 1.97, 3.26 5.66 1.95)' + + See Also + ________ + + create_linestrings_from_xyz_points : Create LineStrings from a GeoDataFrame containing X, Y, and Z coordinates of vertices of multiple LineStrings + """ # Checking that the points are of type GeoDataFrame or a NumPy array if not isinstance(points, (np.ndarray, gpd.geodataframe.GeoDataFrame)): - raise TypeError('Input points must either be provided as GeoDataFrame or NumPy array') + raise TypeError( + "Input points must either be provided as GeoDataFrame or NumPy array" + ) # Checking of geometry objects are valid and converting GeoDataFrame to array if isinstance(points, gpd.geodataframe.GeoDataFrame): # Checking that all Shapely Objects are valid if not all(shapely.is_valid(points.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(points.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that all geometry objects are of type point if not all(shapely.get_type_id(points.geometry) == 0): - raise TypeError('All geometry objects must be of geom type Point') + raise TypeError("All geometry objects must be of geom type Point") # Checking that the Z column are present in GeoDataFrame if zcol not in points: - raise ValueError('Z values could not be found') + raise ValueError("Z values could not be found") # Extract X and Y coordinates from GeoDataFrame if not {xcol, ycol}.issubset(points.columns): @@ -2840,7 +3466,7 @@ def create_linestring_from_xyz_points(points: Union[np.ndarray, gpd.geodataframe # Checking that the NumPy array has the right dimensions if points.shape[1] != 3: - raise ValueError('Array must contain 3 values, X, Y, and Z values') + raise ValueError("Array must contain 3 values, X, Y, and Z values") # Getting indices where nodata values are present indices_nodata = np.where(points == nodata)[0] @@ -2857,55 +3483,71 @@ def create_linestring_from_xyz_points(points: Union[np.ndarray, gpd.geodataframe return linestring -def create_linestrings_from_xyz_points(gdf: gpd.geodataframe.GeoDataFrame, - groupby: str, - nodata: Union[int, float] = 9999.0, - xcol: str = 'X', - ycol: str = 'Y', - zcol: str = 'Z', - dem: Union[np.ndarray, rasterio.io.DatasetReader] = None, - extent: List[Union[float, int]] = None, - return_gdf: bool = True, - drop_nan: bool = True) -> Union[List[shapely.geometry.linestring.LineString], - gpd.geodataframe.GeoDataFrame]: - """Creating LineStrings from a GeoDataFrame containing X, Y, and Z coordinates of vertices of multiple LineStrings +def create_linestrings_from_xyz_points( + gdf: gpd.geodataframe.GeoDataFrame, + groupby: str, + nodata: Union[int, float] = 9999.0, + xcol: str = "X", + ycol: str = "Y", + zcol: str = "Z", + dem: Union[np.ndarray, rasterio.io.DatasetReader] = None, + extent: List[Union[float, int]] = None, + return_gdf: bool = True, + drop_nan: bool = True, +) -> Union[List[shapely.geometry.linestring.LineString], gpd.geodataframe.GeoDataFrame]: + """Create LineStrings from a GeoDataFrame containing X, Y, and Z coordinates of vertices of multiple LineStrings. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing extracted X, Y, and Z coordinates of LineStrings + GeoDataFrame containing extracted X, Y, and Z coordinates of LineStrings. + + +----+-----------+------------------------+-------+-------+-------+ + | ID | Object_ID | geometry | X | Y | Z | + +----+-----------+------------------------+-------+-------+-------+ + | 0 | 1 | POINT (19.150 293.313) | 19.15 | 293.31| 364.99| + +----+-----------+------------------------+-------+-------+-------+ + | 1 | 1 | POINT (61.934 381.459) | 61.93 | 381.46| 400.34| + +----+-----------+------------------------+-------+-------+-------+ + | 2 | 1 | POINT (109.358 480.946)| 109.36| 480.95| 459.55| + +----+-----------+------------------------+-------+-------+-------+ + | 3 | 2 | POINT (157.812 615.999)| 157.81| 616.00| 525.69| + +----+-----------+------------------------+-------+-------+-------+ + | 4 | 2 | POINT (191.318 719.094)| 191.32| 719.09| 597.63| + +----+-----------+------------------------+-------+-------+-------+ + groupby : str - Name of a unique identifier the LineStrings can be separated from each other, e.g. ``groupby='Object_ID'`` + Name of a unique identifier the LineStrings can be separated from each other, e.g. ``groupby='Object_ID'``. - nodata : Union[int, float]) - Nodata value to filter out points outside a designated area, e.g. ``nodata=9999.0``, default is ``9999.0`` + nodata : Union[int, float]), default: ``9999.0`` + Nodata value to filter out points outside a designated area, e.g. ``nodata=9999.0``, default is ``9999.0``. - xcol : str - Name of the X column in the dataset, e.g. ``xcol='X'``, default is ``'X'`` + xcol : str, default: ``'X'`` + Name of the X column in the dataset, e.g. ``xcol='X'``, default is ``'X'``. - ycol : str - Name of the Y column in the dataset, e.g. ``ycol='Y'``, default is ``'Y'`` + ycol : str, default: ``'Y'`` + Name of the Y column in the dataset, e.g. ``ycol='Y'``, default is ``'Y'``. - zcol : str - Name of the Z column in the dataset, e.g. ``zcol='Z'``, default is ``'Z'`` + zcol : str, default: ``'Z'`` + Name of the Z column in the dataset, e.g. ``zcol='Z'``, default is ``'Z'``. - dem : Union[np.ndarray, rasterio.io.DatasetReader] + dem : Union[np.ndarray, rasterio.io.DatasetReader], default: ``None`` NumPy ndarray or rasterio object containing the height values, default value is ``None`` in case geometries - contain Z values + contain Z values. - extent : List[Union[float, int]] + extent : List[Union[float, int]], default: ``None`` Values for minx, maxx, miny and maxy values to define the boundaries of the raster, - e.g. ``extent=[0, 972, 0, 1069]`` + e.g. ``extent=[0, 972, 0, 1069]``, default is ``None``. - return_gdf : bool - Variable to either return the data as GeoDataFrame or as list of LineStrings. - Options include: ``True`` or ``False``, default set to ``True`` + return_gdf : bool, default: ``True`` + Variable to either return the data as GeoDataFrame or as list of LineStrings, e.g. ``return_gdf=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_nan : bool - Boolean argument to drop points that contain a ``nan`` value as Z value. Options include ``True`` and - ``False``, default is ``True`` + drop_nan : bool, default: ``True`` + Boolean argument to drop points that contain a ``nan`` value as Z value, e.g. ``drop_nan=True`` + Options include ``True`` and ``False``, default is ``True``. Returns _______ @@ -2915,9 +3557,7 @@ def create_linestrings_from_xyz_points(gdf: gpd.geodataframe.GeoDataFrame, .. versionadded:: 1.0.x - .. versionchanged:: 1.1 - Removed manual dropping of additional columns. Now automatically drops unnecessary coloumns. - Adding argument `drop_nan` and code to drop coordinates that contain ``nan`` values as Z coordinates. + .. versionchanged:: 1.2 Example _______ @@ -2928,57 +3568,86 @@ def create_linestrings_from_xyz_points(gdf: gpd.geodataframe.GeoDataFrame, >>> gdf = gpd.read_file(filename='file.shp') >>> gdf + +----+-----------+------------------------+-------+-------+-------+ + | | Object_ID | geometry | X | Y | Z | + +----+-----------+------------------------+-------+-------+-------+ + | 0 | 1 | POINT (19.150 293.313) | 19.15 | 293.31| 364.99| + +----+-----------+------------------------+-------+-------+-------+ + | 1 | 1 | POINT (61.934 381.459) | 61.93 | 381.46| 400.34| + +----+-----------+------------------------+-------+-------+-------+ + | 2 | 1 | POINT (109.358 480.946)| 109.36| 480.95| 459.55| + +----+-----------+------------------------+-------+-------+-------+ + | 3 | 2 | POINT (157.812 615.999)| 157.81| 616.00| 525.69| + +----+-----------+------------------------+-------+-------+-------+ + | 4 | 2 | POINT (191.318 719.094)| 191.32| 719.09| 597.63| + +----+-----------+------------------------+-------+-------+-------+ + >>> # Creating LineStrings with Z component from gdf >>> gdf_linestring = gg.vector.create_linestrings_from_xyz_points(gdf=gdf, groupby='ABS') >>> gdf_linestring + +----+-----------+----------------------------------------------------------------------+ + | | formation | geometry | + +----+-----------+----------------------------------------------------------------------+ + | 0 | 1 | LINESTRING Z (19.150 293.310 364.990, 61.930 381.459 400.340, ...) | + +----+-----------+----------------------------------------------------------------------+ + | 1 | 2 | LINESTRING Z (157.810 616.000 525.690, 191.320 719.094 597.630, ...) | + +----+-----------+----------------------------------------------------------------------+ - """ + See Also + ________ + create_linestring_from_xyz_points : Create LineString from an array or GeoDataFrame containing X, Y, and Z coordinates of points. + """ # Checking that the input is a GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Input must be provided as GeoDataFrame') + raise TypeError("Input must be provided as GeoDataFrame") # Checking that the geometry types of the GeoDataFrame are the supported types - if not gdf.geom_type.isin(('LineString', 'Point')).all(): - raise TypeError('Geometry type within GeoDataFrame not supported, only Point or LineString allowed') + if not gdf.geom_type.isin(("LineString", "Point")).all(): + raise TypeError( + "Geometry type within GeoDataFrame not supported, only Point or LineString allowed" + ) # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that return gdfs is of type bool if not isinstance(return_gdf, bool): - raise TypeError('Return_gdf argument must be of type bool') + raise TypeError("Return_gdf argument must be of type bool") # Checking that the GeoDataFrame contains Z values if zcol not in gdf: # Checking that the provided DEM is not of type None if not isinstance(dem, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('Provide DEM as array or rasterio object to extract coordinates') + raise TypeError( + "Provide DEM as array or rasterio object to extract coordinates" + ) # Extracting Z values from dem - gdf = extract_xyz(gdf=gdf, - dem=dem, - extent=extent) + gdf = extract_xyz(gdf=gdf, dem=dem, extent=extent) # Checking if X and Y are in GeoDataFrame - if not {'X', 'Y'}.issubset(gdf.columns): - gdf = extract_xy(gdf=gdf, - reset_index=True) + if not {"X", "Y"}.issubset(gdf.columns): + gdf = extract_xy(gdf=gdf, reset_index=True) # Creating list of GeoDataFrames for the creating of LineStrings - list_gdfs = [gdf.groupby(by=groupby).get_group(group) for group in gdf[groupby].unique()] + list_gdfs = [ + gdf.groupby(by=groupby).get_group(group) for group in gdf[groupby].unique() + ] # Creating LineString for each GeoDataFrame in list_gdfs - list_linestrings = [create_linestring_from_xyz_points(points=geodf, - drop_nan=drop_nan) for geodf in list_gdfs] + list_linestrings = [ + create_linestring_from_xyz_points(points=geodf, drop_nan=drop_nan) + for geodf in list_gdfs + ] # Creating boolean list of empty geometries bool_empty_lines = [i.is_empty for i in list_linestrings] @@ -2987,48 +3656,95 @@ def create_linestrings_from_xyz_points(gdf: gpd.geodataframe.GeoDataFrame, indices_empty_lines = np.where(bool_empty_lines)[0].tolist() # Removing emtpy LineStrings from list of LineStrings by index - list_linestrings_new = [i for j, i in enumerate(list_linestrings) if j not in indices_empty_lines] + list_linestrings_new = [ + i for j, i in enumerate(list_linestrings) if j not in indices_empty_lines + ] # Removing GeoDataFrames at the indices of empty LineStrings list_gdfs_new = [i for j, i in enumerate(list_gdfs) if j not in indices_empty_lines] # Returning list of LineStrings as GeoDataFrame if return_gdf: - list_lines = [gpd.GeoDataFrame( - data=pd.DataFrame(data=list_gdfs_new[i].tail(1).drop(['geometry', xcol, ycol, zcol], axis=1)), - geometry=[list_linestrings_new[i]]) for i in range(len(list_linestrings_new))] + list_lines = [ + gpd.GeoDataFrame( + data=pd.DataFrame( + data=list_gdfs_new[i] + .tail(1) + .drop(["geometry", xcol, ycol, zcol], axis=1) + ), + geometry=[list_linestrings_new[i]], + ) + for i in range(len(list_linestrings_new)) + ] list_linestrings = pd.concat(list_lines).reset_index(drop=True) return list_linestrings -def create_linestrings_from_contours(contours: pv.core.pointset.PolyData, - return_gdf: bool = True, - crs: Union[str, pyproj.crs.crs.CRS] = None) \ - -> Union[List[shapely.geometry.linestring.LineString], gpd.geodataframe.GeoDataFrame]: - """Creating LineStrings from PyVista Contour Lines and save them as list or GeoDataFrame +def create_linestrings_from_contours( + contours: pv.core.pointset.PolyData, + return_gdf: bool = True, + crs: Union[str, pyproj.crs.crs.CRS] = None, +) -> Union[List[shapely.geometry.linestring.LineString], gpd.geodataframe.GeoDataFrame]: + """Create LineStrings from PyVista Contour Lines and save them as list or GeoDataFrame. Parameters __________ contours : pv.core.pointset.PolyData - PyVista PolyData dataset containing contour lines extracted from a mesh - - return_gdf : bool - Variable to create GeoDataFrame of the created list of Shapely Objects. - Options include: ``True`` or ``False``, default set to ``True`` - - crs : Union[str, pyproj.crs.crs.CRS] - Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``crs='EPSG:4647'`` + PyVista PolyData dataset containing contour lines extracted from a mesh. + + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | Header | | Data Array | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | PolyData | Information | Name | Field | Type | N Comp | Min | Max | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | N Cells | 580 | Depth [m] | Points | float64 | 1 | -1.710e+03 | 3.000e+02 | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | N Points | 586 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | N Strips | 0 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | X Bounds | 2.952e+05, 3.016e+05 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | Y Bounds | 5.619e+06, 5.627e+06 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | Z Bounds | -1.710e+03, 3.000e+02 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | N Arrays | 1 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + + return_gdf : bool, default: ``True`` + Variable to create GeoDataFrame of the created list of Shapely Objects, e.g. ``return_gdf=True``. + Options include: ``True`` or ``False``, default set to ``True``. + + crs : Union[str, pyproj.crs.crs.CRS], default: ``None`` + Name of the CRS provided to reproject coordinates of the GeoDataFrame, e.g. ``crs='EPSG:4647'``. Returns _______ linestrings : Union[List[shapely.geometry.linestring.LineString], gpd.geodataframe.GeoDataFrame] - List of LineStrings or GeoDataFrame containing the contours that were converted + List of LineStrings or GeoDataFrame containing the contours that were converted. + + +----+----------------------------------------------------+---------+ + | | geometry | Z | + +----+----------------------------------------------------+---------+ + | 0 | LINESTRING Z (32409587.930 5780538.824 -2350.0...) | -2350.00| + +----+----------------------------------------------------+---------+ + | 1 | LINESTRING Z (32407304.336 5777048.086 -2050.0...) | -2050.00| + +----+----------------------------------------------------+---------+ + | 2 | LINESTRING Z (32408748.977 5778005.047 -2200.0...) | -2200.00| + +----+----------------------------------------------------+---------+ + | 3 | LINESTRING Z (32403693.547 5786613.994 -2400.0...) | -2400.00| + +----+----------------------------------------------------+---------+ + | 4 | LINESTRING Z (32404738.664 5782672.480 -2350.0...) | -2350.00| + +----+----------------------------------------------------+---------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -3037,49 +3753,66 @@ def create_linestrings_from_contours(contours: pv.core.pointset.PolyData, >>> import pyvista as pv >>> contours = pv.read('file.vtk') >>> contours - Header - PolyData Information - N Cells 36337 - N Points 36178 - X Bounds 3.233e+07, 3.250e+07 - Y Bounds 5.704e+06, 5.798e+06 - Z Bounds -2.400e+03, 3.500e+02 - N Arrays 1 - Data Arrays - Name Field Type N Comp Min Max - Depth [m] Points float64 1 -2.400e+03 3.500e+02 + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | Header | | Data Array | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | PolyData | Information | Name | Field | Type | N Comp | Min | Max | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | N Cells | 580 | Depth [m] | Points | float64 | 1 | -1.710e+03 | 3.000e+02 | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | N Points | 586 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | N Strips | 0 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | X Bounds | 2.952e+05, 3.016e+05 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | Y Bounds | 5.619e+06, 5.627e+06 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | Z Bounds | -1.710e+03, 3.000e+02 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ + | N Arrays | 1 | | | | | | | + +--------------+-----------------------+-------------+---------+---------+--------+------------+-----------+ >>> # Extracting LineStrings from contours >>> gdf = gg.vector.create_linestrings_from_contours(contours=contours) >>> gdf - geometry Z - 0 LINESTRING Z (32409587.930 5780538.824 -2350.0... -2350.00 - 1 LINESTRING Z (32407304.336 5777048.086 -2050.0... -2050.00 - 2 LINESTRING Z (32408748.977 5778005.047 -2200.0... -2200.00 - 3 LINESTRING Z (32403693.547 5786613.994 -2400.0... -2400.00 - 4 LINESTRING Z (32404738.664 5782672.480 -2350.0... -2350.00 + +----+----------------------------------------------------+---------+ + | | geometry | Z | + +----+----------------------------------------------------+---------+ + | 0 | LINESTRING Z (32409587.930 5780538.824 -2350.0...) | -2350.00| + +----+----------------------------------------------------+---------+ + | 1 | LINESTRING Z (32407304.336 5777048.086 -2050.0...) | -2050.00| + +----+----------------------------------------------------+---------+ + | 2 | LINESTRING Z (32408748.977 5778005.047 -2200.0...) | -2200.00| + +----+----------------------------------------------------+---------+ + | 3 | LINESTRING Z (32403693.547 5786613.994 -2400.0...) | -2400.00| + +----+----------------------------------------------------+---------+ + | 4 | LINESTRING Z (32404738.664 5782672.480 -2350.0...) | -2350.00| + +----+----------------------------------------------------+---------+ - """ + """ # Checking that the input data is a PyVista PolyData dataset if not isinstance(contours, pv.core.pointset.PolyData): - raise TypeError('Input data must be a PyVista PolyData dataset') + raise TypeError("Input data must be a PyVista PolyData dataset") # Checking that the PolyData dataset does not contain any faces if contours.faces.size != 0: - raise TypeError('PolyData must not contain faces, only line, use mesh.contour() to extract contours') + raise TypeError( + "PolyData must not contain faces, only line, use mesh.contour() to extract contours" + ) # Checking that the PolyData dataset does contain lines if contours.lines.size == 0: - raise ValueError('Contours must contain lines') + raise ValueError("Contours must contain lines") # Checking that return gdfs is of type bool if not isinstance(return_gdf, bool): - raise TypeError('Return_gdf argument must be of type bool') + raise TypeError("Return_gdf argument must be of type bool") # Checking that the target_crs is of type string if not isinstance(crs, (str, type(None), pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Defining empty list for LineStrings linestrings = [] @@ -3097,7 +3830,10 @@ def create_linestrings_from_contours(contours: pv.core.pointset.PolyData, number_of_points_of_line = contours.lines[index_to_find_length_of_line] # Getting the index values to look up points in contours.points - index_values = [contours.lines[index_to_find_length_of_line + i + 1] for i in range(number_of_points_of_line)] + index_values = [ + contours.lines[index_to_find_length_of_line + i + 1] + for i in range(number_of_points_of_line) + ] # Creating list of vertices belonging to one LineString vertices = [contours.points[value] for value in index_values] @@ -3114,7 +3850,10 @@ def create_linestrings_from_contours(contours: pv.core.pointset.PolyData, linestrings = gpd.GeoDataFrame(geometry=linestrings, crs=crs) # Adding a Z column containing the altitude of the LineString for better plotting - linestrings['Z'] = [list(linestrings.loc[i].geometry.coords)[0][2] for i in range(len(linestrings))] + linestrings["Z"] = [ + list(linestrings.loc[i].geometry.coords)[0][2] + for i in range(len(linestrings)) + ] return linestrings @@ -3123,53 +3862,71 @@ def create_linestrings_from_contours(contours: pv.core.pointset.PolyData, ################################################# -def interpolate_raster(gdf: gpd.geodataframe.GeoDataFrame, - value: str = 'Z', - method: str = 'nearest', - n: int = None, - res: int = 1, - extent: List[Union[float, int]] = None, - seed: int = None, - **kwargs) -> np.ndarray: - """Interpolating a raster/digital elevation model from point or line Shape file +def interpolate_raster( + gdf: gpd.geodataframe.GeoDataFrame, + value: str = "Z", + method: str = "nearest", + n: int = None, + res: int = 1, + extent: List[Union[float, int]] = None, + seed: int = None, + **kwargs, +) -> np.ndarray: + """Interpolate a raster/digital elevation model from Point or LineString Shape file. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing vector data of geom_type Point or Line containing the Z values of an area - - value : str - Value to be interpolated, e.g. ``value='Z'``, default is ``'Z'`` - - method : string - Method used to interpolate the raster. - Options include: ``'nearest', 'linear', 'cubic', 'rbf'`` - - res : int - Resolution of the raster in X and Y direction, e.g. ``res=50`` - - seed : int - Seed for the drawing of random numbers, e.g. ``seed=1`` - - n : int - Number of samples used for the interpolation, e.g. ``n=100`` + GeoDataFrame containing vector data of geom_type Point or Line containing the Z values of an area. + + +----+------+---------------------------------------------------------+ + | ID | Z | geometry | + +----+------+---------------------------------------------------------+ + | 0 | None | 400 | LINESTRING (0.741 475.441, 35.629 429.247, 77.... | + +----+------+---------------------------------------------------------+ + | 1 | None | 300 | LINESTRING (645.965 0.525, 685.141 61.866, 724... | + +----+------+---------------------------------------------------------+ + | 2 | None | 400 | LINESTRING (490.292 0.525, 505.756 40.732, 519... | + +----+------+---------------------------------------------------------+ + | 3 | None | 600 | LINESTRING (911.433 1068.585, 908.856 1026.831... | + +----+------+---------------------------------------------------------+ + | 4 | None | 700 | LINESTRING (228.432 1068.585, 239.772 1017.037... | + +----+------+---------------------------------------------------------+ + + value : str, default: ``'Z'`` + Value to be interpolated, e.g. ``value='Z'``, default is ``'Z'``. + + method : string, default: ``'nearest'`` + Method used to interpolate the raster, e.g. ``method='nearest'``. + Options include: ``'nearest', 'linear', 'cubic', 'rbf'``. + + n : int, default: ``None`` + Number of samples used for the interpolation, e.g. ``n=100``, default is None. + + res : int, default: ``1`` + Resolution of the raster in X and Y direction, e.g. ``res=50``, default is ``'1'``. extent : List[Union[float, int]] Values for minx, maxx, miny and maxy values to define the boundaries of the raster, - e.g. ``extent=[0, 972, 0, 1069]`` + e.g. ``extent=[0, 972, 0, 1069]``. + + seed : int, default: ``None`` + Seed for the drawing of random numbers, e.g. ``seed=1``, default is None. **kwargs : optional keyword arguments - For kwargs for rbf and griddata see: https://docs.scipy.org/doc/scipy/reference/interpolate.html + For kwargs for rbf and griddata see: https://docs.scipy.org/doc/scipy/reference/interpolate.html. Returns _______ array : np.ndarray - Array representing the interpolated raster/digital elevation model + Array representing the interpolated raster/digital elevation model. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -3178,12 +3935,21 @@ def interpolate_raster(gdf: gpd.geodataframe.GeoDataFrame, >>> import geopandas as gpd >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id Z geometry - 0 None 400 LINESTRING (0.741 475.441, 35.629 429.247, 77.... - 1 None 300 LINESTRING (645.965 0.525, 685.141 61.866, 724... - 2 None 400 LINESTRING (490.292 0.525, 505.756 40.732, 519... - 3 None 600 LINESTRING (911.433 1068.585, 908.856 1026.831... - 4 None 700 LINESTRING (228.432 1068.585, 239.772 1017.037... + + +----+------+---------------------------------------------------------+ + | ID | Z | geometry | + +----+------+---------------------------------------------------------+ + | 0 | None | 400 | LINESTRING (0.741 475.441, 35.629 429.247, 77.... | + +----+------+---------------------------------------------------------+ + | 1 | None | 300 | LINESTRING (645.965 0.525, 685.141 61.866, 724... | + +----+------+---------------------------------------------------------+ + | 2 | None | 400 | LINESTRING (490.292 0.525, 505.756 40.732, 519... | + +----+------+---------------------------------------------------------+ + | 3 | None | 600 | LINESTRING (911.433 1068.585, 908.856 1026.831... | + +----+------+---------------------------------------------------------+ + | 4 | None | 700 | LINESTRING (228.432 1068.585, 239.772 1017.037... | + +----+------+---------------------------------------------------------+ + >>> # Interpolating vector data >>> raster = gg.vector.interpolate_raster(gdf=gdf, method='rbf') @@ -3194,42 +3960,45 @@ def interpolate_raster(gdf: gpd.geodataframe.GeoDataFrame, 398.16690286, 400.12027997]]) """ - # Trying to import scipy but returning error if scipy is not installed try: from scipy.interpolate import griddata, Rbf except ModuleNotFoundError: - raise ModuleNotFoundError('SciPy package is not installed. Use pip install scipy to install the latest version') + raise ModuleNotFoundError( + "SciPy package is not installed. Use pip install scipy to install the latest version" + ) # Checking if the gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf mus be of type GeoDataFrame') + raise TypeError("gdf mus be of type GeoDataFrame") # Checking that interpolation value is provided as string if not isinstance(value, str): - raise TypeError('Interpolation value must be provided as column name/string') + raise TypeError("Interpolation value must be provided as column name/string") # Checking if interpolation values are in the gdf if value not in gdf: - raise ValueError('Interpolation values not defined') + raise ValueError("Interpolation values not defined") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking if XY values are in the gdf - if not {'X', 'Y'}.issubset(gdf.columns): - gdf = extract_xy(gdf=gdf, - reset_index=True, - drop_index=False, - drop_level1=False, - drop_level0=False, - drop_id=False, - drop_points=True) + if not {"X", "Y"}.issubset(gdf.columns): + gdf = extract_xy( + gdf=gdf, + reset_index=True, + drop_index=False, + drop_level1=False, + drop_level0=False, + drop_id=False, + drop_points=True, + ) # Getting sample number n if n is None: @@ -3237,11 +4006,11 @@ def interpolate_raster(gdf: gpd.geodataframe.GeoDataFrame, # Checking that number of samples is of type int if not isinstance(n, int): - raise TypeError('Number of samples must be of type int') + raise TypeError("Number of samples must be of type int") # Checking that seed is of type int if not isinstance(seed, (int, type(None))): - raise TypeError('Seed must be of type int') + raise TypeError("Seed must be of type int") # Sampling gdf if n: @@ -3249,19 +4018,21 @@ def interpolate_raster(gdf: gpd.geodataframe.GeoDataFrame, if n <= len(gdf): gdf = gdf.sample(n=n) else: - raise ValueError('n must be smaller than the total number of points in the provided GeoDataFrame') + raise ValueError( + "n must be smaller than the total number of points in the provided GeoDataFrame" + ) # Checking that the method provided is of type string if not isinstance(method, str): - raise TypeError('Method must be of type string') + raise TypeError("Method must be of type string") # Checking that the resolution provided is of type int if not isinstance(res, int): - raise TypeError('Resolution must be of type int') + raise TypeError("Resolution must be of type int") # Checking that the extent provided is of type list or None if not isinstance(extent, (list, type(None))): - raise TypeError('Extent must be provided as list of corner values') + raise TypeError("Extent must be provided as list of corner values") # Creating a meshgrid based on the gdf bounds or a provided extent if extent: @@ -3277,70 +4048,105 @@ def interpolate_raster(gdf: gpd.geodataframe.GeoDataFrame, try: # Interpolating the raster if method in ["nearest", "linear", "cubic"]: - array = griddata((gdf['X'], gdf['Y']), gdf[value], (xx, yy), method=method, **kwargs) - elif method == 'rbf': - rbf = Rbf(gdf['X'], gdf['Y'], gdf[value], **kwargs) + array = griddata( + (gdf["X"], gdf["Y"]), gdf[value], (xx, yy), method=method, **kwargs + ) + elif method == "rbf": + rbf = Rbf(gdf["X"], gdf["Y"], gdf[value], **kwargs) array = rbf(xx, yy) else: - raise ValueError('No valid method defined') + raise ValueError("No valid method defined") except np.linalg.LinAlgError: - raise ValueError('LinAlgError: reduce the number of points by setting a value for n or check for duplicates') + raise ValueError( + "LinAlgError: reduce the number of points by setting a value for n or check for duplicates" + ) return array -def clip_by_bbox(gdf: gpd.geodataframe.GeoDataFrame, - bbox: List[Union[float, int]], - reset_index: bool = True, - drop_index: bool = True, - drop_id: bool = True, - drop_points: bool = True, - drop_level0: bool = True, - drop_level1: bool = True - ) -> gpd.geodataframe.GeoDataFrame: - """Clipping vector data contained in a GeoDataFrame to a provided bounding box/extent +def clip_by_bbox( + gdf: gpd.geodataframe.GeoDataFrame, + bbox: List[Union[float, int]], + reset_index: bool = True, + drop_index: bool = True, + drop_id: bool = True, + drop_points: bool = True, + drop_level0: bool = True, + drop_level1: bool = True, +) -> gpd.geodataframe.GeoDataFrame: + """Clip vector data contained in a GeoDataFrame to a provided bounding box/extent. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing vector data that will be clipped to a provided bounding box/extent + GeoDataFrame containing vector data that will be clipped to a provided bounding box/extent. + + +----+------------------------------------------------+ + | | geometry | + +----+------------------------------------------------+ + | 0 | POINT (281.526 902.087) | + +----+------------------------------------------------+ + | 1 | POINT (925.867 618.577) | + +----+------------------------------------------------+ + | 2 | POINT (718.131 342.799) | + +----+------------------------------------------------+ + | 3 | POINT (331.011 255.684) | + +----+------------------------------------------------+ + | 4 | POINT (300.083 600.535) | + +----+------------------------------------------------+ bbox : List[Union[float, int]] - Bounding box of minx, maxx, miny, maxy values to clip the GeoDataFrame, , e.g. ``bbox=[0, 972, 0, 1069]`` + Bounding box of minx, maxx, miny, maxy values to clip the GeoDataFrame, , e.g. ``bbox=[0, 972, 0, 1069]``. - reset_index : bool - Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` + reset_index : bool, default: ``True`` + Variable to reset the index of the resulting GeoDataFrame, e.g. ``reset_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level0 : bool - Variable to drop the level_0 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level0 : bool, default: ``True`` + Variable to drop the level_0 column, e.g. ``drop_level0=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level1 : bool - Variable to drop the level_1 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level1 : bool, default: ``True`` + Variable to drop the level_1 column, e.g. ``drop_level0=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_index : bool - Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_index : bool, default: ``True`` + Variable to drop the index column, e.g. ``drop_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_id : bool - Variable to drop the id column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_id : bool, default: ``True`` + Variable to drop the id column, e.g. ``drop_id=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_points : bool - Variable to drop the points column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_points : bool, default: ``True`` + Variable to drop the points column, e.g. ``drop_points=True``. + Options include: ``True`` or ``False``, default set to ``True``. Returns _______ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing vector data clipped by a bounding box + GeoDataFrame containing vector data clipped by a bounding box. + + +----+-----------------------------+---------+---------+ + | ID | geometry | X | Y | + +----+-----------------------------+---------+---------+ + | 0 | POINT (281.526 902.087) | 281.53 | 902.09 | + +----+-----------------------------+---------+---------+ + | 1 | POINT (925.867 618.577) | 925.87 | 618.58 | + +----+-----------------------------+---------+---------+ + | 2 | POINT (718.131 342.799) | 718.13 | 342.80 | + +----+-----------------------------+---------+---------+ + | 3 | POINT (331.011 255.684) | 331.01 | 255.68 | + +----+-----------------------------+---------+---------+ + | 4 | POINT (300.083 600.535) | 300.08 | 600.54 | + +----+-----------------------------+---------+---------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -3349,12 +4155,21 @@ def clip_by_bbox(gdf: gpd.geodataframe.GeoDataFrame, >>> import geopandas as gpd >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id geometry - 0 None POINT (281.526 902.087) - 1 None POINT (925.867 618.577) - 2 None POINT (718.131 342.799) - 3 None POINT (331.011 255.684) - 4 None POINT (300.083 600.535) + + +----+------------------------------------------------+ + | | geometry | + +----+------------------------------------------------+ + | 0 | POINT (281.526 902.087) | + +----+------------------------------------------------+ + | 1 | POINT (925.867 618.577) | + +----+------------------------------------------------+ + | 2 | POINT (718.131 342.799) | + +----+------------------------------------------------+ + | 3 | POINT (331.011 255.684) | + +----+------------------------------------------------+ + | 4 | POINT (300.083 600.535) | + +----+------------------------------------------------+ + >>> # Returning the length of the original gdf >>> len(gdf) @@ -3366,12 +4181,20 @@ def clip_by_bbox(gdf: gpd.geodataframe.GeoDataFrame, >>> # Clipping data by bounding box >>> gdf_clipped = gg.vector.clip_by_bbox(gdf=gdf, bbox=bbox) >>> gdf_clipped - geometry X Y - 0 POINT (281.526 902.087) 281.53 902.09 - 1 POINT (925.867 618.577) 925.87 618.58 - 2 POINT (718.131 342.799) 718.13 342.80 - 3 POINT (331.011 255.684) 331.01 255.68 - 4 POINT (300.083 600.535) 300.08 600.54 + + +----+-----------------------------+---------+---------+ + | ID | geometry | X | Y | + +----+-----------------------------+---------+---------+ + | 0 | POINT (281.526 902.087) | 281.53 | 902.09 | + +----+-----------------------------+---------+---------+ + | 1 | POINT (925.867 618.577) | 925.87 | 618.58 | + +----+-----------------------------+---------+---------+ + | 2 | POINT (718.131 342.799) | 718.13 | 342.80 | + +----+-----------------------------+---------+---------+ + | 3 | POINT (331.011 255.684) | 331.01 | 255.68 | + +----+-----------------------------+---------+---------+ + | 4 | POINT (300.083 600.535) | 300.08 | 600.54 | + +----+-----------------------------+---------+---------+ >>> # Returning the length of the clipped gdf >>> len(gdf_clipped) @@ -3380,175 +4203,210 @@ def clip_by_bbox(gdf: gpd.geodataframe.GeoDataFrame, See Also ________ - clip_by_polygon : Clipping vector data with a Shapely Polygon + clip_by_polygon : Clip vector data with a Shapely Polygon """ - # Checking that the input data is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Checking that the bounding box is a list if not isinstance(bbox, list): - raise TypeError('Bounding box must be of type list') + raise TypeError("Bounding box must be of type list") # Checking that all values are either ints or floats if not all(isinstance(n, (int, float)) for n in bbox): - raise TypeError('All bounding values must be of type int or float') + raise TypeError("All bounding values must be of type int or float") # Checking that the geometry types of the GeoDataFrame are the supported types - if not gdf.geom_type.isin(('MultiLineString', 'LineString', 'Point', 'Polygon')).all(): - raise TypeError('Geometry type within GeoDataFrame not supported') + if not gdf.geom_type.isin( + ("MultiLineString", "LineString", "Point", "Polygon") + ).all(): + raise TypeError("Geometry type within GeoDataFrame not supported") # Checking that drop_level0 is of type bool if not isinstance(drop_level0, bool): - raise TypeError('Drop_index_level0 argument must be of type bool') + raise TypeError("Drop_index_level0 argument must be of type bool") # Checking that drop_level1 is of type bool if not isinstance(drop_level1, bool): - raise TypeError('Drop_index_level1 argument must be of type bool') + raise TypeError("Drop_index_level1 argument must be of type bool") # Checking that reset_index is of type bool if not isinstance(reset_index, bool): - raise TypeError('Reset_index argument must be of type bool') + raise TypeError("Reset_index argument must be of type bool") # Checking that drop_id is of type bool if not isinstance(drop_id, bool): - raise TypeError('Drop_id argument must be of type bool') + raise TypeError("Drop_id argument must be of type bool") # Checking that drop_points is of type bool if not isinstance(drop_points, bool): - raise TypeError('Drop_points argument must be of type bool') + raise TypeError("Drop_points argument must be of type bool") # Checking that drop_index is of type bool if not isinstance(drop_index, bool): - raise TypeError('Drop_index argument must be of type bool') + raise TypeError("Drop_index argument must be of type bool") # Checking that the length of the list is either four or six if not len(bbox) == 4 or len(bbox) == 6: - raise ValueError('The bbox must include only four or six values') + raise ValueError("The bbox must include only four or six values") # Checking that all elements of the extent are of type int or float if not all(isinstance(n, (int, float)) for n in bbox): - raise TypeError('Extent values must be of type int or float') + raise TypeError("Extent values must be of type int or float") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Selecting x and y bounds if bbox contains values for all three directions x, y, z if len(bbox) == 6: bbox = bbox[:4] # If X and Y are not in the GeoDataFrame, extract them - if not {'X', 'Y'}.issubset(gdf.columns): - gdf = extract_xy(gdf=gdf, - reset_index=False, - drop_index=False, - drop_id=False, - drop_points=False, - drop_level0=False, - drop_level1=False, - overwrite_xy=False, - target_crs=None, - bbox=None) + if not {"X", "Y"}.issubset(gdf.columns): + gdf = extract_xy( + gdf=gdf, + reset_index=False, + drop_index=False, + drop_id=False, + drop_points=False, + drop_level0=False, + drop_level1=False, + overwrite_xy=False, + target_crs=None, + bbox=None, + ) # Clipping the data - gdf = gpd.clip(gdf=gdf, - mask=geometry.Polygon([(bbox[0], bbox[2]), - (bbox[1], bbox[2]), - (bbox[1], bbox[3]), - (bbox[0], bbox[3])])) + gdf = gpd.clip( + gdf=gdf, + mask=geometry.Polygon( + [ + (bbox[0], bbox[2]), + (bbox[1], bbox[2]), + (bbox[1], bbox[3]), + (bbox[0], bbox[3]), + ] + ), + ) # Resetting the index if reset_index: gdf = gdf.reset_index() # Dropping level_0 column - if reset_index and drop_level0 and 'level_0' in gdf: - gdf = gdf.drop(columns='level_0', - axis=1) + if reset_index and drop_level0 and "level_0" in gdf: + gdf = gdf.drop(columns="level_0", axis=1) # Dropping level_1 column - if reset_index and drop_level1 and 'level_1' in gdf: - gdf = gdf.drop(columns='level_1', - axis=1) + if reset_index and drop_level1 and "level_1" in gdf: + gdf = gdf.drop(columns="level_1", axis=1) # Dropping id column - if 'id' in gdf and drop_id: - gdf = gdf.drop(columns='id', - axis=1) + if "id" in gdf and drop_id: + gdf = gdf.drop(columns="id", axis=1) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) # Dropping points column - if 'points' in gdf and drop_points: - gdf = gdf.drop(columns='points', - axis=1) + if "points" in gdf and drop_points: + gdf = gdf.drop(columns="points", axis=1) return gdf -def clip_by_polygon(gdf: gpd.geodataframe.GeoDataFrame, - polygon: shapely.geometry.polygon.Polygon, - reset_index: bool = True, - drop_index: bool = True, - drop_id: bool = True, - drop_points: bool = True, - drop_level0: bool = True, - drop_level1: bool = True - ) -> gpd.geodataframe.GeoDataFrame: - """Clipping vector data contained in a GeoDataFrame to a provided bounding box/extent +def clip_by_polygon( + gdf: gpd.geodataframe.GeoDataFrame, + polygon: shapely.geometry.polygon.Polygon, + reset_index: bool = True, + drop_index: bool = True, + drop_id: bool = True, + drop_points: bool = True, + drop_level0: bool = True, + drop_level1: bool = True, +) -> gpd.geodataframe.GeoDataFrame: + """Clip vector data contained in a GeoDataFrame to a provided bounding box/extent. Parameters __________ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing vector data that will be clipped to a provided bounding box/extent - - polygon : polygon: shapely.geometry.polygon + GeoDataFrame containing vector data that will be clipped to a provided bounding box/extent. + + +----+-----------------------------+ + | ID | geometry | + +----+-----------------------------+ + | 0 | POINT (281.526 902.087) | + +----+-----------------------------+ + | 1 | POINT (925.867 618.577) | + +----+-----------------------------+ + | 2 | POINT (718.131 342.799) | + +----+-----------------------------+ + | 3 | POINT (331.011 255.684) | + +----+-----------------------------+ + | 4 | POINT (300.083 600.535) | + +----+-----------------------------+ + + polygon : shapely.geometry.Polygon Shapely Polygon defining the extent of the data, - e.g. ``polygon = Polygon([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]])`` + e.g. ``polygon = Polygon([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]])``. - reset_index : bool - Variable to reset the index of the resulting GeoDataFrame. - Options include: ``True`` or ``False``, default set to ``True`` + reset_index : bool, default: ``True`` + Variable to reset the index of the resulting GeoDataFrame, e.g. ``reset_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level0 : bool - Variable to drop the level_0 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level0 : bool, default: ``True`` + Variable to drop the level_0 column, e.g. ``drop_level0=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_level1 : bool - Variable to drop the level_1 column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_level1 : bool, default: ``True`` + Variable to drop the level_1 column, e.g. ``drop_level1=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_index : bool - Variable to drop the index column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_index : bool, default: ``True`` + Variable to drop the index column, e.g. ``drop_index=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_id : bool - Variable to drop the id column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_id : bool, default: ``True`` + Variable to drop the id column, e.g. ``drop_id=True``. + Options include: ``True`` or ``False``, default set to ``True``. - drop_points : bool - Variable to drop the points column. - Options include: ``True`` or ``False``, default set to ``True`` + drop_points : bool, default: ``True`` + Variable to drop the points column, e.g. ``drop_points=True``. + Options include: ``True`` or ``False``, default set to ``True``. Returns _______ gdf : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing vector data clipped by a bounding box + GeoDataFrame containing vector data clipped by a bounding box. + + +----+-----------------------------+---------+---------+ + | | geometry | X | Y | + +----+-----------------------------+---------+---------+ + | 0 | POINT (281.526 902.087) | 281.53 | 902.09 | + +----+-----------------------------+---------+---------+ + | 1 | POINT (925.867 618.577) | 925.87 | 618.58 | + +----+-----------------------------+---------+---------+ + | 2 | POINT (718.131 342.799) | 718.13 | 342.80 | + +----+-----------------------------+---------+---------+ + | 3 | POINT (331.011 255.684) | 331.01 | 255.68 | + +----+-----------------------------+---------+---------+ + | 4 | POINT (300.083 600.535) | 300.08 | 600.54 | + +----+-----------------------------+---------+---------+ .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -3557,12 +4415,21 @@ def clip_by_polygon(gdf: gpd.geodataframe.GeoDataFrame, >>> import geopandas as gpd >>> gdf = gpd.read_file(filename='file.shp') >>> gdf - id geometry - 0 None POINT (281.526 902.087) - 1 None POINT (925.867 618.577) - 2 None POINT (718.131 342.799) - 3 None POINT (331.011 255.684) - 4 None POINT (300.083 600.535) + + +----+-----------------------------+ + | ID | geometry | + +----+-----------------------------+ + | 0 | POINT (281.526 902.087) | + +----+-----------------------------+ + | 1 | POINT (925.867 618.577) | + +----+-----------------------------+ + | 2 | POINT (718.131 342.799) | + +----+-----------------------------+ + | 3 | POINT (331.011 255.684) | + +----+-----------------------------+ + | 4 | POINT (300.083 600.535) | + +----+-----------------------------+ + >>> # Returning the length of the original gdf >>> len(gdf) @@ -3577,12 +4444,20 @@ def clip_by_polygon(gdf: gpd.geodataframe.GeoDataFrame, >>> # Clipping data by the polygon >>> gdf_clipped = gg.vector.clip_by_polygon(gdf=gdf, polygon=polygon) >>> gdf_clipped - geometry X Y - 0 POINT (281.526 902.087) 281.53 902.09 - 1 POINT (925.867 618.577) 925.87 618.58 - 2 POINT (718.131 342.799) 718.13 342.80 - 3 POINT (331.011 255.684) 331.01 255.68 - 4 POINT (300.083 600.535) 300.08 600.54 + + +----+-----------------------------+---------+---------+ + | | geometry | X | Y | + +----+-----------------------------+---------+---------+ + | 0 | POINT (281.526 902.087) | 281.53 | 902.09 | + +----+-----------------------------+---------+---------+ + | 1 | POINT (925.867 618.577) | 925.87 | 618.58 | + +----+-----------------------------+---------+---------+ + | 2 | POINT (718.131 342.799) | 718.13 | 342.80 | + +----+-----------------------------+---------+---------+ + | 3 | POINT (331.011 255.684) | 331.01 | 255.68 | + +----+-----------------------------+---------+---------+ + | 4 | POINT (300.083 600.535) | 300.08 | 600.54 | + +----+-----------------------------+---------+---------+ >>> # Returning the length of the clipped gdf >>> len(gdf_clipped) @@ -3591,61 +4466,54 @@ def clip_by_polygon(gdf: gpd.geodataframe.GeoDataFrame, See Also ________ - clip_by_bbox : Clipping vector data with a bbox + clip_by_bbox : Clip vector data with a bbox """ - # Checking if the gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking if the polygon is of type GeoDataFrame if not isinstance(polygon, shapely.geometry.polygon.Polygon): - raise TypeError('Polygon must be of Shapely Polygon') + raise TypeError("Polygon must be of Shapely Polygon") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Create deep copy of gdf gdf = gdf.copy(deep=True) # Clipping the gdf - gdf = gpd.clip(gdf=gdf, - mask=polygon) + gdf = gpd.clip(gdf=gdf, mask=polygon) # Resetting the index if reset_index: gdf = gdf.reset_index() # Dropping level_0 column - if reset_index and drop_level0 and 'level_0' in gdf: - gdf = gdf.drop(columns='level_0', - axis=1) + if reset_index and drop_level0 and "level_0" in gdf: + gdf = gdf.drop(columns="level_0", axis=1) # Dropping level_1 column - if reset_index and drop_level1 and 'level_1' in gdf: - gdf = gdf.drop(columns='level_1', - axis=1) + if reset_index and drop_level1 and "level_1" in gdf: + gdf = gdf.drop(columns="level_1", axis=1) # Dropping id column - if 'id' in gdf and drop_id: - gdf = gdf.drop(columns='id', - axis=1) + if "id" in gdf and drop_id: + gdf = gdf.drop(columns="id", axis=1) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) # Dropping points column - if 'points' in gdf and drop_points: - gdf = gdf.drop(columns='points', - axis=1) + if "points" in gdf and drop_points: + gdf = gdf.drop(columns="points", axis=1) return gdf @@ -3654,28 +4522,30 @@ def clip_by_polygon(gdf: gpd.geodataframe.GeoDataFrame, ###################################### -def create_buffer(geom_object: shapely.geometry.base.BaseGeometry, - distance: Union[float, - int]) -> shapely.geometry.polygon.Polygon: - """Creating a buffer around a Shapely LineString or a Point +def create_buffer( + geom_object: shapely.geometry.base.BaseGeometry, distance: Union[float, int] +) -> shapely.geometry.polygon.Polygon: + """Create a buffer around a Shapely LineString or a Shapely Point. Parameters __________ geom_object : shapely.geometry.base.BaseGeometry - Shapely LineString or Point, e.g. ``geom_object=Point(0, 0)`` + Shapely LineString or Point, e.g. ``geom_object=Point(0, 0)``. distance : float, int - Distance of the buffer around the geometry object, e.g. ``distance=10`` + Distance of the buffer around the geometry object, e.g. ``distance=10``. Returns _______ polygon : shapely.geometry.polygon.Polygon - Polygon representing the buffered area around a geometry object + Polygon representing the buffered area around a geometry object. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -3695,17 +4565,18 @@ def create_buffer(geom_object: shapely.geometry.base.BaseGeometry, See Also ________ - create_unified_buffer : Creating a unified buffer around Shapely LineStrings or Points + create_unified_buffer : Create a unified buffer around Shapely LineStrings or Shapely Points """ - # Checking that the geometry object is a Shapely LineString or Point if not isinstance(geom_object, shapely.geometry.base.BaseGeometry): - raise TypeError('Geometry object must either be a Shapely LineString or Point object') + raise TypeError( + "Geometry object must either be a Shapely LineString or Point object" + ) # Checking that the distance is of type float or int if not isinstance(distance, (float, int)): - raise TypeError('Radius must be of type float or int') + raise TypeError("Radius must be of type float or int") # Creating buffer around object polygon = geom_object.buffer(distance=distance) @@ -3713,29 +4584,33 @@ def create_buffer(geom_object: shapely.geometry.base.BaseGeometry, return polygon -def create_unified_buffer(geom_object: Union[gpd.geodataframe.GeoDataFrame, - List[shapely.geometry.base.BaseGeometry]], - distance: Union[np.ndarray, List[Union[float, int]], Union[float, int]]) \ - -> shapely.geometry.multipolygon.MultiPolygon: - """Creating a unified buffer around Shapely LineStrings or Points +def create_unified_buffer( + geom_object: Union[ + gpd.geodataframe.GeoDataFrame, List[shapely.geometry.base.BaseGeometry] + ], + distance: Union[np.ndarray, List[Union[float, int]], Union[float, int]], +) -> shapely.geometry.multipolygon.MultiPolygon: + """Create a unified buffer around Shapely LineStrings or Shapely Points. Parameters __________ geom_object : Union[gpd.geodataframe.GeoDataFrame, List[shapely.geometry.base.BaseGeometry]] - GeoDataFrame or List of Shapely objects + GeoDataFrame or List of Shapely objects. distance : Union[np.ndarray, List[Union[float, int]], Union[float, int]] - Distance of the buffer around the geometry object, e.g. ``distance=10`` + Distance of the buffer around the geometry object, e.g. ``distance=10``. Returns _______ polygon : shapely.geometry.multipolygon.MultiPolygon - Polygon representing the buffered area around a geometry object + Polygon representing the buffered area around a geometry object. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -3763,36 +4638,41 @@ def create_unified_buffer(geom_object: Union[gpd.geodataframe.GeoDataFrame, See Also ________ - create_buffer : Creating a buffer around a Shapely LineString or Point + create_buffer : Create a buffer around a Shapely LineString or a Shapely Point """ - # Checking that the geometry object is a Shapely LineString or Point - if not isinstance(geom_object, (gpd.geodataframe.GeoDataFrame, - list, - shapely.geometry.base.BaseGeometry)): - raise TypeError('Geometry object must either be a Shapely LineString or Point object') + if not isinstance( + geom_object, + (gpd.geodataframe.GeoDataFrame, list, shapely.geometry.base.BaseGeometry), + ): + raise TypeError( + "Geometry object must either be a Shapely LineString or Point object" + ) # Checking that the distance is of type float or int if not isinstance(distance, (float, int)): - raise TypeError('Radius must be of type float or int') + raise TypeError("Radius must be of type float or int") # Converting GeoDataFrame into list of Shapely objects if isinstance(geom_object, gpd.geodataframe.GeoDataFrame): # Checking that all Shapely Objects are valid if not all(shapely.is_valid(geom_object.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(geom_object.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Converting geometry column of GeoDataFrame to list geom_object = geom_object.geometry.tolist() # Creating list of polygons - polygon_list = [create_buffer(geom_object=geomobject, distance=distance) for geomobject in geom_object] + polygon_list = [ + create_buffer(geom_object=geomobject, distance=distance) + for geomobject in geom_object + ] # Creating unified polygons unified_polygons = ops.unary_union(polygon_list) @@ -3800,30 +4680,33 @@ def create_unified_buffer(geom_object: Union[gpd.geodataframe.GeoDataFrame, return unified_polygons -def subtract_geom_objects(geom_object1: shapely.geometry.base.BaseGeometry, - geom_object2: shapely.geometry.base.BaseGeometry) \ - -> shapely.geometry.base.BaseGeometry: - """Subtracting Shapely geometry objects from each other and returning the left over object +def subtract_geom_objects( + geom_object1: shapely.geometry.base.BaseGeometry, + geom_object2: shapely.geometry.base.BaseGeometry, +) -> shapely.geometry.base.BaseGeometry: + """Subtract Shapely geometry objects from each other and returning the left over object. Parameters __________ geom_object1 : shapely.geometry.base.BaseGeometry Shapely object from which other object will be subtracted, - e.g. ``geom_object1 = Polygon([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]])`` + e.g. ``geom_object1 = Polygon([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]])``. geom_object2 : shapely.geometry.base.BaseGeometry Shapely object which will be subtracted from other object - e.g. ``geom_object2 = Polygon([[5, 0], [15, 0], [15, 10], [5, 10], [5, 0]])`` + e.g. ``geom_object2 = Polygon([[5, 0], [15, 0], [15, 10], [5, 10], [5, 0]])``. Returns _______ result : shapely.geometry.base.BaseGeometry - Shapely object from which the second object was subtracted + Shapely object from which the second object was subtracted. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -3845,14 +4728,17 @@ def subtract_geom_objects(geom_object1: shapely.geometry.base.BaseGeometry, 'POLYGON ((5 0, 0 0, 0 10, 5 10, 5 0))' """ - # Checking that the first geometry object is a Shapely Point, LineString or Polygon if not isinstance(geom_object1, shapely.geometry.base.BaseGeometry): - raise TypeError('First geometry object must be a Shapely Point, LineString or Polygon') + raise TypeError( + "First geometry object must be a Shapely Point, LineString or Polygon" + ) # Checking that the second geometry object is a Shapely Point, LineString or Polygon if not isinstance(geom_object2, shapely.geometry.base.BaseGeometry): - raise TypeError('Second geometry object must be a Shapely Point, LineString or Polygon') + raise TypeError( + "Second geometry object must be a Shapely Point, LineString or Polygon" + ) # Subtracting object 2 from object 1 result = geom_object1 - geom_object2 @@ -3860,43 +4746,44 @@ def subtract_geom_objects(geom_object1: shapely.geometry.base.BaseGeometry, return result -def remove_object_within_buffer(buffer_object: shapely.geometry.base.BaseGeometry, - buffered_object: shapely.geometry.base.BaseGeometry, - distance: Union[int, - float] = None, - buffer: bool = True) \ - -> Tuple[shapely.geometry.base.BaseGeometry, - shapely.geometry.base.BaseGeometry]: - """Removing object from a buffered object by providing a distance +def remove_object_within_buffer( + buffer_object: shapely.geometry.base.BaseGeometry, + buffered_object: shapely.geometry.base.BaseGeometry, + distance: Union[int, float] = None, + buffer: bool = True, +) -> Tuple[shapely.geometry.base.BaseGeometry, shapely.geometry.base.BaseGeometry]: + """Remove object from a buffered object by providing a distance. Parameters __________ buffer_object : shapely.geometry.base.BaseGeometry - Shapely object for which a buffer will be created, e.g. ``buffer_object=Point(0, 0)`` + Shapely object for which a buffer will be created, e.g. ``buffer_object=Point(0, 0)``. buffered_object: shapely.geometry.base.BaseGeometry Shapely object that will be removed from the buffer, - e.g. ``buffered_object=LineString([(0, 0), (10, 10), (20, 20)])`` + e.g. ``buffered_object=LineString([(0, 0), (10, 10), (20, 20)])``. - distance : Union[float, int] - Distance of the buffer around the geometry object, e.g. ``distance=10``, default is ``None`` + distance : Union[float, int], default: ``None`` + Distance of the buffer around the geometry object, e.g. ``distance=10``, default is ``None``. - buffer : bool + buffer : bool, default: ``True`` Variable to create a buffer. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. Returns _______ result_out : shapely.geometry.base.BaseGeometry - Shapely object that remains after the buffering (outside the buffer) + Shapely object that remains after the buffering (outside the buffer). result_in : shapely.geometry.base.BaseGeometry - Shapely object that was buffered (inside the buffer) + Shapely object that was buffered (inside the buffer). .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -3926,42 +4813,41 @@ def remove_object_within_buffer(buffer_object: shapely.geometry.base.BaseGeometr See Also ________ - remove_objects_within_buffer : Removing several objects from one buffered object - remove_interfaces_within_fault_buffers : Removing interfaces of layer boundaries within fault line buffers + remove_objects_within_buffer : Remove several objects from one buffered object + remove_interfaces_within_fault_buffers : Remove interfaces of layer boundaries within fault line buffers """ - # Checking that the buffer object is a Shapely Point or LineString if not isinstance(buffer_object, shapely.geometry.base.BaseGeometry): - raise TypeError('Buffer object must be a Shapely Point or LineString') + raise TypeError("Buffer object must be a Shapely Point or LineString") # Checking that the buffered object is a Shapely Point or LineString if not isinstance(buffered_object, shapely.geometry.base.BaseGeometry): - raise TypeError('Buffered object must be a Shapely Point or LineString') + raise TypeError("Buffered object must be a Shapely Point or LineString") # Checking that the buffer_object is valid if not buffer_object.is_valid: - raise ValueError('Buffer object is not a valid object') + raise ValueError("Buffer object is not a valid object") # Checking that the buffer_object is not empty if buffer_object.is_empty: - raise ValueError('Buffer object is an empty object') + raise ValueError("Buffer object is an empty object") # Checking that the buffered_object is valid if not buffered_object.is_valid: - raise ValueError('Buffered Object is not a valid object') + raise ValueError("Buffered Object is not a valid object") # Checking that the buffered_object is not empty if buffered_object.is_empty: - raise ValueError('Buffered Object is an empty object') + raise ValueError("Buffered Object is an empty object") # Checking that the distance is of type float or int if not isinstance(distance, (float, int, type(None))): - raise TypeError('Radius must be of type float or int') + raise TypeError("Radius must be of type float or int") # Checking that create_buffer is of type bool if not isinstance(buffer, bool): - raise TypeError('create_buffer must be of type bool') + raise TypeError("create_buffer must be of type bool") # Create buffer object if buffer and distance is not None: @@ -3976,59 +4862,64 @@ def remove_object_within_buffer(buffer_object: shapely.geometry.base.BaseGeometr return result_out, result_in -def remove_objects_within_buffer(buffer_object: shapely.geometry.base.BaseGeometry, - buffered_objects_gdf: Union[gpd.geodataframe.GeoDataFrame, - List[shapely.geometry.base.BaseGeometry]], - distance: Union[int, - float] = None, - return_gdfs: bool = False, - remove_empty_geometries: bool = False, - extract_coordinates: bool = False, - buffer: bool = True) \ - -> Tuple[Union[List[shapely.geometry.base.BaseGeometry], gpd.geodataframe.GeoDataFrame], - Union[List[shapely.geometry.base.BaseGeometry], gpd.geodataframe.GeoDataFrame]]: - """Removing objects from a buffered object by providing a distance +def remove_objects_within_buffer( + buffer_object: shapely.geometry.base.BaseGeometry, + buffered_objects_gdf: Union[ + gpd.geodataframe.GeoDataFrame, List[shapely.geometry.base.BaseGeometry] + ], + distance: Union[int, float] = None, + return_gdfs: bool = False, + remove_empty_geometries: bool = False, + extract_coordinates: bool = False, + buffer: bool = True, +) -> Tuple[ + Union[List[shapely.geometry.base.BaseGeometry], gpd.geodataframe.GeoDataFrame], + Union[List[shapely.geometry.base.BaseGeometry], gpd.geodataframe.GeoDataFrame], +]: + """Remove objects from a buffered object by providing a distance. Parameters __________ buffer_object : shapely.geometry.base.BaseGeometry - Shapely object for which a buffer will be created, e.g. ``buffer_object=Point(0, 0)`` + Shapely object for which a buffer will be created, e.g. ``buffer_object=Point(0, 0)``. buffered_object_gdf: Union[gpd.geodataframe.GeoDataFrame, List[shapely.geometry.base.BaseGeometry]] GeoDataFrame or List of Base Geometries containing Shapely objects that will be buffered by the buffer - object + object. - distance : float, int - Distance of the buffer around the geometry object, e.g. ``distance=10`` + distance : float, int, default: ``10`` + Distance of the buffer around the geometry object, e.g. ``distance=10``. - return_gdfs : bool + return_gdfs : bool, default: ``False`` Variable to create GeoDataFrames of the created list of Shapely Objects. - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. - remove_empty_geometries : bool + remove_empty_geometries : bool, default: ``False`` Variable to remove empty geometries. - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. - extract_coordinates : bool + extract_coordinates : bool, default: ``False`` Variable to extract X and Y coordinates from resulting Shapely Objects. - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. - buffer : bool + buffer : bool, default: ``True`` Variable to create a buffer. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. Returns _______ result_out : list, gpd.geodataframe.GeoDataFrame - List or GeoDataFrame of Shapely objects that remain after the buffering (outside the buffer) + List or GeoDataFrame of Shapely objects that remain after the buffering (outside the buffer). result_in : list, gpd.geodataframe.GeoDataFrame - List or GeoDataFrame of Shapely objects that was buffered (inside the buffer) + List or GeoDataFrame of Shapely objects that was buffered (inside the buffer). .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -4049,7 +4940,7 @@ def remove_objects_within_buffer(buffer_object: shapely.geometry.base.BaseGeomet >>> linestring2.wkt 'LINESTRING (0 0, 10 10, 20 20)' - >>> # Create list of buffer objects + >>> # Creating list of buffer objects >>> buffer_objects = [linestring1, linestring2] >>> # Removing objects within buffer @@ -4068,61 +4959,62 @@ def remove_objects_within_buffer(buffer_object: shapely.geometry.base.BaseGeomet See Also ________ - remove_object_within_buffer : Removing one object from one buffered object - remove_interfaces_within_fault_buffers : Removing interfaces of layer boundaries within fault line buffers + remove_object_within_buffer : Remove one object from one buffered object + remove_interfaces_within_fault_buffers : Remove interfaces of layer boundaries within fault line buffers """ # Checking that the buffer object is a Shapely Point or LineString if not isinstance(buffer_object, shapely.geometry.base.BaseGeometry): - raise TypeError('Buffer object must be a Shapely Point or LineString') + raise TypeError("Buffer object must be a Shapely Point or LineString") # Checking that the buffer_object is valid if not buffer_object.is_valid: - raise ValueError('Buffer object is not a valid object') + raise ValueError("Buffer object is not a valid object") # Checking that the buffer_object is not empty if buffer_object.is_empty: - raise ValueError('Buffer object is an empty object') + raise ValueError("Buffer object is an empty object") # Checking that the buffered objects are provided within a GeoDataFrame if not isinstance(buffered_objects_gdf, (gpd.geodataframe.GeoDataFrame, list)): - raise TypeError('Buffered objects must be stored as GeoSeries within a GeoDataFrame or as element in a list') + raise TypeError( + "Buffered objects must be stored as GeoSeries within a GeoDataFrame or as element in a list" + ) # Checking that the distance is of type float or int if not isinstance(distance, (float, int, type(None))): - raise TypeError('Radius must be of type float or int') + raise TypeError("Radius must be of type float or int") # Checking that return gdfs is of type bool if not isinstance(return_gdfs, bool): - raise TypeError('Return_gdf argument must be of type bool') + raise TypeError("Return_gdf argument must be of type bool") # Checking that remove empty geometries is of type bool if not isinstance(remove_empty_geometries, bool): - raise TypeError('Remove_emtpy_geometries argument must be of type bool') + raise TypeError("Remove_emtpy_geometries argument must be of type bool") # Checking that extract coordinates is of type bool if not isinstance(extract_coordinates, bool): - raise TypeError('Extract_coordinates argument must be of type bool') + raise TypeError("Extract_coordinates argument must be of type bool") # Checking that create_buffer is of type bool if not isinstance(buffer, bool): - raise TypeError('create_buffer must be of type bool') + raise TypeError("create_buffer must be of type bool") # Creating buffer if buffer and distance is not None: - buffer_object = create_buffer(geom_object=buffer_object, - distance=distance) + buffer_object = create_buffer(geom_object=buffer_object, distance=distance) # Converting the GeoDataFrame to a list if isinstance(buffered_objects_gdf, gpd.geodataframe.GeoDataFrame): # Checking that all Shapely Objects are valid if not all(shapely.is_valid(buffered_objects_gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(buffered_objects_gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Converting geometry column of the GeoDataFrame to a list buffered_objects_list = buffered_objects_gdf.geometry.tolist() @@ -4133,10 +5025,12 @@ def remove_objects_within_buffer(buffer_object: shapely.geometry.base.BaseGeomet buffered_objects_list = None # Creating tuples of buffered and non-buffered Shapely objects - results = [remove_object_within_buffer(buffer_object=buffer_object, - buffered_object=i, - distance=None, - buffer=False) for i in buffered_objects_list] + results = [ + remove_object_within_buffer( + buffer_object=buffer_object, buffered_object=i, distance=None, buffer=False + ) + for i in buffered_objects_list + ] # Creating lists of remaining and buffered geometry objects results_out = [results[i][0] for i in range(len(results))] @@ -4144,12 +5038,16 @@ def remove_objects_within_buffer(buffer_object: shapely.geometry.base.BaseGeomet # If return gdfs is true, create GeoDataFrames from list if return_gdfs: - results_out = gpd.GeoDataFrame(data=buffered_objects_gdf.drop('geometry', axis=1), - geometry=results_out, - crs=buffered_objects_gdf.crs) - results_in = gpd.GeoDataFrame(data=buffered_objects_gdf.drop('geometry', axis=1), - geometry=results_in, - crs=buffered_objects_gdf.crs) + results_out = gpd.GeoDataFrame( + data=buffered_objects_gdf.drop("geometry", axis=1), + geometry=results_out, + crs=buffered_objects_gdf.crs, + ) + results_in = gpd.GeoDataFrame( + data=buffered_objects_gdf.drop("geometry", axis=1), + geometry=results_in, + crs=buffered_objects_gdf.crs, + ) # Remove empty geometries if remove_empty_geometries: @@ -4166,13 +5064,13 @@ def remove_objects_within_buffer(buffer_object: shapely.geometry.base.BaseGeomet return results_out, results_in -def remove_interfaces_within_fault_buffers(fault_gdf: gpd.geodataframe.GeoDataFrame, - interfaces_gdf: gpd.geodataframe.GeoDataFrame, - distance: Union[int, - float] = None, - remove_empty_geometries: bool = True, - extract_coordinates: bool = True) \ - -> Tuple[gpd.geodataframe.GeoDataFrame, gpd.geodataframe.GeoDataFrame]: +def remove_interfaces_within_fault_buffers( + fault_gdf: gpd.geodataframe.GeoDataFrame, + interfaces_gdf: gpd.geodataframe.GeoDataFrame, + distance: Union[int, float] = None, + remove_empty_geometries: bool = True, + extract_coordinates: bool = True, +) -> Tuple[gpd.geodataframe.GeoDataFrame, gpd.geodataframe.GeoDataFrame]: """Function to create a buffer around a GeoDataFrame containing fault data and removing interface points that are located within this buffer @@ -4270,56 +5168,60 @@ def remove_interfaces_within_fault_buffers(fault_gdf: gpd.geodataframe.GeoDataFr # Checking that the buffer object is a Shapely Point or LineString if not isinstance(fault_gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Buffer object must be a Shapely Point or LineString') + raise TypeError("Buffer object must be a Shapely Point or LineString") # Checking that the buffered objects are provided within a GeoDataFrame if not isinstance(interfaces_gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Buffered objects must be stored as GeoSeries within a GeoDataFrame') + raise TypeError( + "Buffered objects must be stored as GeoSeries within a GeoDataFrame" + ) # Checking that the distance is of type float or int if not isinstance(distance, (float, int)): - raise TypeError('Radius must be of type float or int') + raise TypeError("Radius must be of type float or int") # Checking that remove empty geometries is of type bool if not isinstance(remove_empty_geometries, bool): - raise TypeError('Remove_emtpy_geometries argument must be of type bool') + raise TypeError("Remove_emtpy_geometries argument must be of type bool") # Checking that extract coordinates is of type bool if not isinstance(extract_coordinates, bool): - raise TypeError('Extract_coordinates argument must be of type bool') + raise TypeError("Extract_coordinates argument must be of type bool") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(fault_gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(fault_gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(interfaces_gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(interfaces_gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Creating list of fault lines faults_list = fault_gdf.geometry.tolist() # Exploding Polygons - if all(interfaces_gdf.geom_type == 'Polygon'): + if all(interfaces_gdf.geom_type == "Polygon"): interfaces_gdf = explode_polygons(gdf=interfaces_gdf) # Creating unified polygons unified_polygons = ops.unary_union(geoms=faults_list) - gdf_out, gdf_in = remove_objects_within_buffer(buffer_object=unified_polygons, - buffered_objects_gdf=interfaces_gdf, - distance=distance, - return_gdfs=True, - remove_empty_geometries=remove_empty_geometries, - extract_coordinates=extract_coordinates) + gdf_out, gdf_in = remove_objects_within_buffer( + buffer_object=unified_polygons, + buffered_objects_gdf=interfaces_gdf, + distance=distance, + return_gdfs=True, + remove_empty_geometries=remove_empty_geometries, + extract_coordinates=extract_coordinates, + ) return gdf_out, gdf_in @@ -4330,6 +5232,7 @@ def remove_interfaces_within_fault_buffers(fault_gdf: gpd.geodataframe.GeoDataFr # Calculating Angles and Directions ################################### + def calculate_angle(linestring: shapely.geometry.linestring.LineString) -> float: """Calculating the angle of a LineString to the vertical @@ -4380,27 +5283,33 @@ def calculate_angle(linestring: shapely.geometry.linestring.LineString) -> float # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString only consists of two vertices if len(linestring.coords) != 2: - raise ValueError('LineString must only contain a start and end point') + raise ValueError("LineString must only contain a start and end point") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Calculating angle - angle = np.rad2deg(np.arccos((linestring.coords[0][1] - linestring.coords[1][1]) / linestring.length)) + angle = np.rad2deg( + np.arccos( + (linestring.coords[0][1] - linestring.coords[1][1]) / linestring.length + ) + ) return angle -def calculate_strike_direction_straight_linestring(linestring: shapely.geometry.linestring.LineString) -> float: +def calculate_strike_direction_straight_linestring( + linestring: shapely.geometry.linestring.LineString, +) -> float: """Function to calculate the strike direction of a straight Shapely LineString. The strike will always be calculated from start to end point @@ -4451,28 +5360,37 @@ def calculate_strike_direction_straight_linestring(linestring: shapely.geometry. # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString only consists of two vertices if len(linestring.coords) != 2: - raise ValueError('LineString must only contain a start and end point') + raise ValueError("LineString must only contain a start and end point") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Calculating strike angle based on order and location of line vertices - if linestring.coords[0][0] < linestring.coords[1][0] and linestring.coords[0][1] >= linestring.coords[1][1]: + if ( + linestring.coords[0][0] < linestring.coords[1][0] + and linestring.coords[0][1] >= linestring.coords[1][1] + ): angle = 180 - calculate_angle(linestring=linestring) - elif linestring.coords[0][0] > linestring.coords[1][0] and linestring.coords[0][1] < linestring.coords[1][1]: + elif ( + linestring.coords[0][0] > linestring.coords[1][0] + and linestring.coords[0][1] < linestring.coords[1][1] + ): angle = 180 + calculate_angle(linestring=linestring) - elif linestring.coords[0][0] < linestring.coords[1][0] and linestring.coords[0][1] < linestring.coords[1][1]: + elif ( + linestring.coords[0][0] < linestring.coords[1][0] + and linestring.coords[0][1] < linestring.coords[1][1] + ): angle = 180 - calculate_angle(linestring=linestring) else: angle = 180 + calculate_angle(linestring=linestring) @@ -4484,7 +5402,9 @@ def calculate_strike_direction_straight_linestring(linestring: shapely.geometry. return angle -def calculate_strike_direction_bent_linestring(linestring: shapely.geometry.linestring.LineString) -> List[float]: +def calculate_strike_direction_bent_linestring( + linestring: shapely.geometry.linestring.LineString, +) -> List[float]: """Calculating the strike direction of a LineString with multiple elements Parameters @@ -4529,31 +5449,35 @@ def calculate_strike_direction_bent_linestring(linestring: shapely.geometry.line # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString only consists of two vertices if len(linestring.coords) < 2: - raise ValueError('LineString must contain at least two vertices') + raise ValueError("LineString must contain at least two vertices") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Split LineString into list of single LineStrings with two vertices each splitted_linestrings = explode_linestring_to_elements(linestring=linestring) # Calculate strike angle for each single LineString element - angles_splitted_linestrings = [calculate_strike_direction_straight_linestring(linestring=i) for i in - splitted_linestrings] + angles_splitted_linestrings = [ + calculate_strike_direction_straight_linestring(linestring=i) + for i in splitted_linestrings + ] return angles_splitted_linestrings -def calculate_dipping_angle_linestring(linestring: shapely.geometry.linestring.LineString): +def calculate_dipping_angle_linestring( + linestring: shapely.geometry.linestring.LineString, +): """Calculating the dipping angle of a LineString digitized on a cross section Parameters @@ -4603,30 +5527,38 @@ def calculate_dipping_angle_linestring(linestring: shapely.geometry.linestring.L # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString only consists of two vertices if len(linestring.coords) != 2: - raise ValueError('LineString must only contain a start and end point') + raise ValueError("LineString must only contain a start and end point") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Calculating the dip of LineString based on its slope - dip = np.abs(np.rad2deg(np.arctan((linestring.coords[1][1] - linestring.coords[0][1]) / - (linestring.coords[1][0] - linestring.coords[0][0])))) + dip = np.abs( + np.rad2deg( + np.arctan( + (linestring.coords[1][1] - linestring.coords[0][1]) + / (linestring.coords[1][0] - linestring.coords[0][0]) + ) + ) + ) return dip def calculate_dipping_angles_linestrings( - linestring_list: Union[gpd.geodataframe.GeoDataFrame, - List[shapely.geometry.linestring.LineString]]): + linestring_list: Union[ + gpd.geodataframe.GeoDataFrame, List[shapely.geometry.linestring.LineString] + ] +): """Calculating the dipping angles of LineStrings digitized on a cross section Parameters @@ -4678,30 +5610,34 @@ def calculate_dipping_angles_linestrings( # Checking that the list of LineStrings is either provided as list or within a GeoDataFrame if not isinstance(linestring_list, (list, gpd.geodataframe.GeoDataFrame)): - raise TypeError('LineStrings must be provided as list or within a GeoDataFrame') + raise TypeError("LineStrings must be provided as list or within a GeoDataFrame") # Convert LineStrings stored in GeoDataFrame to list if isinstance(linestring_list, gpd.geodataframe.GeoDataFrame): linestring_list = linestring_list.geometry.tolist() # Checking that all elements of the list are LineStrings - if not all(isinstance(n, shapely.geometry.linestring.LineString) for n in linestring_list): - raise TypeError('All list elements must be Shapely LineStrings') + if not all( + isinstance(n, shapely.geometry.linestring.LineString) for n in linestring_list + ): + raise TypeError("All list elements must be Shapely LineStrings") # Checking that all LineStrings only have two vertices if not all(len(n.coords) == 2 for n in linestring_list): - raise ValueError('All LineStrings must only have two vertices') + raise ValueError("All LineStrings must only have two vertices") # Checking that the LineString is valid if not all(n.is_valid for n in linestring_list): - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if any(n.is_empty for n in linestring_list): - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Calculating dipping angles - dipping_angles = [calculate_dipping_angle_linestring(linestring=i) for i in linestring_list] + dipping_angles = [ + calculate_dipping_angle_linestring(linestring=i) for i in linestring_list + ] return dipping_angles @@ -4709,9 +5645,11 @@ def calculate_dipping_angles_linestrings( # Calculating Coordinates for Vector Data from Cross Sections ############################################################ -def calculate_coordinates_for_point_on_cross_section(linestring: shapely.geometry.linestring.LineString, - point: Union[shapely.geometry.point.Point, - Tuple[float, float]]): + +def calculate_coordinates_for_point_on_cross_section( + linestring: shapely.geometry.linestring.LineString, + point: Union[shapely.geometry.point.Point, Tuple[float, float]], +): """Calculating the coordinates for one point digitized on a cross section provided as Shapely LineString Parameters @@ -4767,37 +5705,41 @@ def calculate_coordinates_for_point_on_cross_section(linestring: shapely.geometr # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the Point is a Shapely Point or a tuple if not isinstance(point, (shapely.geometry.point.Point, tuple)): - raise TypeError('Input geometry must be a Shapley Point or a tuple with X and Y coordinates') + raise TypeError( + "Input geometry must be a Shapley Point or a tuple with X and Y coordinates" + ) # Checking that all elements of the list are floats if isinstance(point, tuple) and not all(isinstance(n, float) for n in point): - raise TypeError('All tuple elements must be floats') + raise TypeError("All tuple elements must be floats") # Checking that the tuple only consists of two elements if isinstance(point, tuple) and len(point) != 2: - raise ValueError('The point tuple only takes X and Y coordinates') + raise ValueError("The point tuple only takes X and Y coordinates") # Converting the Shapely Point to a tuple if isinstance(point, shapely.geometry.point.Point): point = point.coords[0] # Creating Substrings from cross section LineString - substr = ops.substring(geom=linestring, - start_dist=point[0] / linestring.length, - end_dist=linestring.length, - normalized=True) + substr = ops.substring( + geom=linestring, + start_dist=point[0] / linestring.length, + end_dist=linestring.length, + normalized=True, + ) # Creating Shapely Point from Substring point = geometry.Point(substr.coords[0]) @@ -4805,8 +5747,10 @@ def calculate_coordinates_for_point_on_cross_section(linestring: shapely.geometr return point -def calculate_coordinates_for_linestring_on_cross_sections(linestring: shapely.geometry.linestring.LineString, - interfaces: shapely.geometry.linestring.LineString): +def calculate_coordinates_for_linestring_on_cross_sections( + linestring: shapely.geometry.linestring.LineString, + interfaces: shapely.geometry.linestring.LineString, +): """Calculating the coordinates of vertices for a LineString on a straight cross section provided as Shapely LineString @@ -4872,40 +5816,43 @@ def calculate_coordinates_for_linestring_on_cross_sections(linestring: shapely.g # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString is a Shapely LineString if not isinstance(interfaces, shapely.geometry.linestring.LineString): - raise TypeError('Input interfaces must be a Shapley LineString') + raise TypeError("Input interfaces must be a Shapley LineString") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the LineString is valid if not interfaces.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if interfaces.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Calculating the real world coordinates of points digitized on a cross section - points = [calculate_coordinates_for_point_on_cross_section(linestring=linestring, - point=interfaces.coords[i]) for i in - range(len(interfaces.coords))] + points = [ + calculate_coordinates_for_point_on_cross_section( + linestring=linestring, point=interfaces.coords[i] + ) + for i in range(len(interfaces.coords)) + ] return points -def calculate_coordinates_for_linestrings_on_cross_sections(linestring: shapely.geometry.linestring.LineString, - linestring_interfaces_list: List[ - shapely.geometry.linestring.LineString]) -> \ - List[shapely.geometry.point.Point]: +def calculate_coordinates_for_linestrings_on_cross_sections( + linestring: shapely.geometry.linestring.LineString, + linestring_interfaces_list: List[shapely.geometry.linestring.LineString], +) -> List[shapely.geometry.point.Point]: """Calculating the coordinates of vertices for LineStrings on a straight cross section provided as Shapely LineString @@ -4983,28 +5930,34 @@ def calculate_coordinates_for_linestrings_on_cross_sections(linestring: shapely. # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the LineString is a Shapely LineString if not isinstance(linestring_interfaces_list, list): - raise TypeError('Input interfaces must be a list containing Shapley LineString') + raise TypeError("Input interfaces must be a list containing Shapley LineString") # Checking that all elements of the list are LineStrings - if not all(isinstance(n, shapely.geometry.linestring.LineString) for n in linestring_interfaces_list): - raise TypeError('All list elements must be Shapely LineStrings') + if not all( + isinstance(n, shapely.geometry.linestring.LineString) + for n in linestring_interfaces_list + ): + raise TypeError("All list elements must be Shapely LineStrings") # Calculating the coordinates for LineStrings on a cross section - points = [calculate_coordinates_for_linestring_on_cross_sections(linestring=linestring, - interfaces=i) for i in - linestring_interfaces_list] + points = [ + calculate_coordinates_for_linestring_on_cross_sections( + linestring=linestring, interfaces=i + ) + for i in linestring_interfaces_list + ] # Create list of points from list of lists points = [points[i][j] for i in range(len(points)) for j in range(len(points[i]))] @@ -5012,10 +5965,11 @@ def calculate_coordinates_for_linestrings_on_cross_sections(linestring: shapely. return points -def extract_interfaces_coordinates_from_cross_section(linestring: shapely.geometry.linestring.LineString, - interfaces_gdf: gpd.geodataframe.GeoDataFrame, - extract_coordinates: bool = True) \ - -> gpd.geodataframe.GeoDataFrame: +def extract_interfaces_coordinates_from_cross_section( + linestring: shapely.geometry.linestring.LineString, + interfaces_gdf: gpd.geodataframe.GeoDataFrame, + extract_coordinates: bool = True, +) -> gpd.geodataframe.GeoDataFrame: """Extracting coordinates of interfaces digitized on a cross section Parameters @@ -5083,75 +6037,88 @@ def extract_interfaces_coordinates_from_cross_section(linestring: shapely.geomet # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the interfaces_gdf is a GeoDataFrame if not isinstance(interfaces_gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Interfaces must be stored as a GeoDataFrame') + raise TypeError("Interfaces must be stored as a GeoDataFrame") # Checking that all elements of the geometry column are LineStrings - if not all(isinstance(n, shapely.geometry.linestring.LineString) for n in interfaces_gdf.geometry.tolist()): - raise TypeError('All geometry elements must be Shapely LineStrings') + if not all( + isinstance(n, shapely.geometry.linestring.LineString) + for n in interfaces_gdf.geometry.tolist() + ): + raise TypeError("All geometry elements must be Shapely LineStrings") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(interfaces_gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(interfaces_gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Calculating coordinates for LineStrings on cross sections geom_objects = calculate_coordinates_for_linestrings_on_cross_sections( linestring=linestring, - linestring_interfaces_list=interfaces_gdf.geometry.tolist()) + linestring_interfaces_list=interfaces_gdf.geometry.tolist(), + ) # Resetting index of GeoDataFrame interfaces_gdf = interfaces_gdf.reset_index() # Creating column with lists of coordinates - interfaces_gdf['list_geoms'] = [list(interfaces_gdf.geometry[i].coords) for i in range(len(interfaces_gdf))] + interfaces_gdf["list_geoms"] = [ + list(interfaces_gdf.geometry[i].coords) for i in range(len(interfaces_gdf)) + ] # Creating DataFrame from interfaces_gdf without geometry column and explode column list_geoms - data_gdf = pd.DataFrame(interfaces_gdf.drop('geometry', axis=1)).explode('list_geoms') + data_gdf = pd.DataFrame(interfaces_gdf.drop("geometry", axis=1)).explode( + "list_geoms" + ) # Creating GeoDataFrame from data_gdf and geom_objects - gdf = gpd.GeoDataFrame(data=data_gdf, - geometry=geom_objects) + gdf = gpd.GeoDataFrame(data=data_gdf, geometry=geom_objects) # Extracting X and Y coordinates from Point objects if extract_coordinates: - gdf = extract_xy(gdf=gdf, - reset_index=True, - drop_index=True, - drop_id=True, - drop_points=True, - drop_level0=True, - drop_level1=True, - overwrite_xy=True, - ) + gdf = extract_xy( + gdf=gdf, + reset_index=True, + drop_index=True, + drop_id=True, + drop_points=True, + drop_level0=True, + drop_level1=True, + overwrite_xy=True, + ) # Creating Z column from - gdf['Z'] = [interfaces_gdf.geometry[i].coords[j][1] for i in range(len(interfaces_gdf)) for j in - range(len(list(interfaces_gdf.geometry[i].coords)))] + gdf["Z"] = [ + interfaces_gdf.geometry[i].coords[j][1] + for i in range(len(interfaces_gdf)) + for j in range(len(list(interfaces_gdf.geometry[i].coords))) + ] # Dropping the column with the geometry lists - gdf = gdf.drop('list_geoms', axis=1) + gdf = gdf.drop("list_geoms", axis=1) return gdf -def extract_xyz_from_cross_sections(profile_gdf: gpd.geodataframe.GeoDataFrame, - interfaces_gdf: gpd.geodataframe.GeoDataFrame, - profile_name_column: str = 'name') -> gpd.geodataframe.GeoDataFrame: +def extract_xyz_from_cross_sections( + profile_gdf: gpd.geodataframe.GeoDataFrame, + interfaces_gdf: gpd.geodataframe.GeoDataFrame, + profile_name_column: str = "name", +) -> gpd.geodataframe.GeoDataFrame: """Extracting X, Y, and Z coordinates from cross sections and digitized interfaces Parameters @@ -5230,60 +6197,80 @@ def extract_xyz_from_cross_sections(profile_gdf: gpd.geodataframe.GeoDataFrame, # Checking that the profile traces are provided as a GeoDataFrame if not isinstance(profile_gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Input geometry must be a GeoDataFrame') + raise TypeError("Input geometry must be a GeoDataFrame") # Checking that the column profile name column is present in the GeoDataFrame if profile_name_column not in profile_gdf: - raise ValueError('Column with profile names not found, provide profile_name_column') + raise ValueError( + "Column with profile names not found, provide profile_name_column" + ) # Checking that the column profile name column is present in the GeoDataFrame if profile_name_column not in interfaces_gdf: - raise ValueError('Column with profile names not found, provide profile_name_column') + raise ValueError( + "Column with profile names not found, provide profile_name_column" + ) # Checking that the interfaces_gdf is a GeoDataFrame if not isinstance(interfaces_gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Interfaces must be stored as a GeoDataFrame') + raise TypeError("Interfaces must be stored as a GeoDataFrame") # Checking that all elements of the geometry column are LineStrings - if not all(isinstance(n, shapely.geometry.linestring.LineString) for n in profile_gdf.geometry.tolist()): - raise TypeError('All geometry elements of the profile_gdf must be Shapely LineStrings') + if not all( + isinstance(n, shapely.geometry.linestring.LineString) + for n in profile_gdf.geometry.tolist() + ): + raise TypeError( + "All geometry elements of the profile_gdf must be Shapely LineStrings" + ) # Checking that all elements of the geometry column are LineStrings - if not all(isinstance(n, shapely.geometry.linestring.LineString) for n in interfaces_gdf.geometry.tolist()): - raise TypeError('All geometry elements of the interface_gdf must be Shapely LineStrings') + if not all( + isinstance(n, shapely.geometry.linestring.LineString) + for n in interfaces_gdf.geometry.tolist() + ): + raise TypeError( + "All geometry elements of the interface_gdf must be Shapely LineStrings" + ) # Checking that profile_name_column is in profile_gdf if profile_name_column not in profile_gdf: - raise ValueError('Profile Column not found, provide a valid name or add column') + raise ValueError("Profile Column not found, provide a valid name or add column") # Checking that the profile_name_column is in interfaces_gdf if profile_name_column not in interfaces_gdf: - raise ValueError('Profile Column not found, provide a valid name or add column') + raise ValueError("Profile Column not found, provide a valid name or add column") # Checking that the profile names are identical if not sorted(profile_gdf[profile_name_column].unique().tolist()) == sorted( - interfaces_gdf[profile_name_column].unique().tolist()): - raise ValueError('Profile names in DataFrames are not identical') + interfaces_gdf[profile_name_column].unique().tolist() + ): + raise ValueError("Profile names in DataFrames are not identical") # Creating a list of GeoDataFrames containing the X, Y, and Z coordinates of digitized interfaces - list_gdf = [extract_interfaces_coordinates_from_cross_section(profile_gdf.geometry[i], - interfaces_gdf[ - interfaces_gdf[profile_name_column] == - profile_gdf[profile_name_column][i]]) - for i in range(len(profile_gdf))] + list_gdf = [ + extract_interfaces_coordinates_from_cross_section( + profile_gdf.geometry[i], + interfaces_gdf[ + interfaces_gdf[profile_name_column] + == profile_gdf[profile_name_column][i] + ], + ) + for i in range(len(profile_gdf)) + ] # Concat list of GeoDataFrames to one large DataFrame df = pd.concat(list_gdf).reset_index(drop=True) # Creating GeoDataFrame - gdf = gpd.GeoDataFrame(data=df, - geometry=df['geometry'], - crs=interfaces_gdf.crs) + gdf = gpd.GeoDataFrame(data=df, geometry=df["geometry"], crs=interfaces_gdf.crs) return gdf -def calculate_midpoint_linestring(linestring: shapely.geometry.linestring.LineString) -> shapely.geometry.point.Point: +def calculate_midpoint_linestring( + linestring: shapely.geometry.linestring.LineString, +) -> shapely.geometry.point.Point: """Calculating the midpoint of a LineString with two vertices Parameters @@ -5330,25 +6317,24 @@ def calculate_midpoint_linestring(linestring: shapely.geometry.linestring.LineSt # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString only consists of two vertices if len(linestring.coords) != 2: - raise ValueError('LineString must only contain a start and end point') + raise ValueError("LineString must only contain a start and end point") # Checking that the LineString is valid if not linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Creating a substring at half the distance of the LineString - substr = ops.substring(geom=linestring, - start_dist=0.5, - end_dist=linestring.length, - normalized=True) + substr = ops.substring( + geom=linestring, start_dist=0.5, end_dist=linestring.length, normalized=True + ) # Extracting midpoint from substring point = geometry.Point(substr.coords[0]) @@ -5356,9 +6342,11 @@ def calculate_midpoint_linestring(linestring: shapely.geometry.linestring.LineSt return point -def calculate_midpoints_linestrings(linestring_gdf: Union[gpd.geodataframe.GeoDataFrame, - List[shapely.geometry.linestring.LineString]]) -> \ - List[shapely.geometry.point.Point]: +def calculate_midpoints_linestrings( + linestring_gdf: Union[ + gpd.geodataframe.GeoDataFrame, List[shapely.geometry.linestring.LineString] + ] +) -> List[shapely.geometry.point.Point]: """Calculating the midpoints of LineStrings with two vertices each Parameters @@ -5411,33 +6399,39 @@ def calculate_midpoints_linestrings(linestring_gdf: Union[gpd.geodataframe.GeoDa # Checking that the LineString is a Shapely LineString if not isinstance(linestring_gdf, (gpd.geodataframe.GeoDataFrame, list)): - raise TypeError('Input geometry must be a GeoDataFrame or a List containing LineStrings') + raise TypeError( + "Input geometry must be a GeoDataFrame or a List containing LineStrings" + ) # Converting LineStrings in GeoDataFrame to list of LineStrings if isinstance(linestring_gdf, gpd.geodataframe.GeoDataFrame): # Checking that all Shapely Objects are valid if not all(shapely.is_valid(linestring_gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(linestring_gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Creating list from geometry column linestring_gdf = linestring_gdf.geometry.tolist() # Checking that all LineStrings are valid if not all(i.is_valid for i in linestring_gdf): - raise ValueError('Not all Shapely LineStrings are valid') + raise ValueError("Not all Shapely LineStrings are valid") # Checking that no LineStrings are empty if any(i.is_empty for i in linestring_gdf): - raise ValueError('One or more LineString Objects are empty') + raise ValueError("One or more LineString Objects are empty") # Checking that all elements of the geometry column are LineStrings - if not all(isinstance(n, shapely.geometry.linestring.LineString) for n in linestring_gdf): - raise TypeError('All geometry elements of the linestring_gdf must be Shapely LineStrings') + if not all( + isinstance(n, shapely.geometry.linestring.LineString) for n in linestring_gdf + ): + raise TypeError( + "All geometry elements of the linestring_gdf must be Shapely LineStrings" + ) # Calculating midpoints midpoints = [calculate_midpoint_linestring(linestring=i) for i in linestring_gdf] @@ -5449,8 +6443,10 @@ def calculate_midpoints_linestrings(linestring_gdf: Union[gpd.geodataframe.GeoDa ####################################################################### -def calculate_orientation_from_cross_section(profile_linestring: shapely.geometry.linestring.LineString, - orientation_linestring: shapely.geometry.linestring.LineString) -> list: +def calculate_orientation_from_cross_section( + profile_linestring: shapely.geometry.linestring.LineString, + orientation_linestring: shapely.geometry.linestring.LineString, +) -> list: """Calculating the orientation for one LineString on one cross sections Parameters @@ -5508,44 +6504,50 @@ def calculate_orientation_from_cross_section(profile_linestring: shapely.geometr # Checking that the LineString is a Shapely LineString if not isinstance(profile_linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString is a Shapely LineString if not isinstance(orientation_linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString is valid if not profile_linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if profile_linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the LineString is valid if not orientation_linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if orientation_linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the LineString only consists of two vertices if len(orientation_linestring.coords) != 2: - raise ValueError('LineString must only contain a start and end point') + raise ValueError("LineString must only contain a start and end point") # Checking that the X coordinates/ the distances to the origin are always positive if list(orientation_linestring.coords)[0][0] < 0: - raise ValueError('X coordinates must always be positive, check the orientation of your profile') + raise ValueError( + "X coordinates must always be positive, check the orientation of your profile" + ) if list(orientation_linestring.coords)[1][0] < 0: - raise ValueError('X coordinates must always be positive, check the orientation of your profile') + raise ValueError( + "X coordinates must always be positive, check the orientation of your profile" + ) # Calculating midpoint of orientation LineString midpoint = calculate_midpoint_linestring(orientation_linestring) # Calculating the coordinates for the midpoint on the cross section - coordinates = calculate_coordinates_for_point_on_cross_section(profile_linestring, midpoint) + coordinates = calculate_coordinates_for_point_on_cross_section( + profile_linestring, midpoint + ) # Calculating the dipping angle for the orientation LineString dip = calculate_dipping_angle_linestring(orientation_linestring) @@ -5554,16 +6556,22 @@ def calculate_orientation_from_cross_section(profile_linestring: shapely.geometr azimuth_profile = calculate_strike_direction_straight_linestring(profile_linestring) # Calculating the azimuth of the orientation based on the dip direction of the orientation - if orientation_linestring.coords[0][0] < orientation_linestring.coords[1][0] and \ - orientation_linestring.coords[0][1] > orientation_linestring.coords[1][1]: + if ( + orientation_linestring.coords[0][0] < orientation_linestring.coords[1][0] + and orientation_linestring.coords[0][1] > orientation_linestring.coords[1][1] + ): azimuth = azimuth_profile - elif orientation_linestring.coords[0][0] > orientation_linestring.coords[1][0] and \ - orientation_linestring.coords[0][1] < orientation_linestring.coords[1][1]: + elif ( + orientation_linestring.coords[0][0] > orientation_linestring.coords[1][0] + and orientation_linestring.coords[0][1] < orientation_linestring.coords[1][1] + ): azimuth = azimuth_profile - elif orientation_linestring.coords[0][0] < orientation_linestring.coords[1][0] and \ - orientation_linestring.coords[0][1] < orientation_linestring.coords[1][1]: + elif ( + orientation_linestring.coords[0][0] < orientation_linestring.coords[1][0] + and orientation_linestring.coords[0][1] < orientation_linestring.coords[1][1] + ): azimuth = 180 + azimuth_profile else: @@ -5582,9 +6590,10 @@ def calculate_orientation_from_cross_section(profile_linestring: shapely.geometr return orientation -def calculate_orientation_from_bent_cross_section(profile_linestring: shapely.geometry.linestring.LineString, - orientation_linestring: shapely.geometry.linestring.LineString) \ - -> list: +def calculate_orientation_from_bent_cross_section( + profile_linestring: shapely.geometry.linestring.LineString, + orientation_linestring: shapely.geometry.linestring.LineString, +) -> list: """Calculating the orientation of a LineString on a bent cross section provided as Shapely LineString Parameters @@ -5641,44 +6650,49 @@ def calculate_orientation_from_bent_cross_section(profile_linestring: shapely.ge # Checking that the LineString is a Shapely LineString if not isinstance(profile_linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString is a Shapely LineString if not isinstance(orientation_linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString is valid if not profile_linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if profile_linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the LineString is valid if not orientation_linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if orientation_linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the LineString only consists of two vertices if len(orientation_linestring.coords) != 2: - raise ValueError('LineString must only contain a start and end point') + raise ValueError("LineString must only contain a start and end point") # Checking that the X coordinates/ the distances to the origin are always positive if list(orientation_linestring.coords)[0][0] < 0: - raise ValueError('X coordinates must always be positive, check the orientation of your profile') + raise ValueError( + "X coordinates must always be positive, check the orientation of your profile" + ) if list(orientation_linestring.coords)[1][0] < 0: - raise ValueError('X coordinates must always be positive, check the orientation of your profile') + raise ValueError( + "X coordinates must always be positive, check the orientation of your profile" + ) splitted_linestrings = explode_linestring_to_elements(linestring=profile_linestring) # Calculating real world coordinates of endpoints of orientation LineString - points = calculate_coordinates_for_linestring_on_cross_sections(linestring=profile_linestring, - interfaces=orientation_linestring) + points = calculate_coordinates_for_linestring_on_cross_sections( + linestring=profile_linestring, interfaces=orientation_linestring + ) # Setting the orientation to None orientation = None @@ -5690,12 +6704,18 @@ def calculate_orientation_from_bent_cross_section(profile_linestring: shapely.ge linestring = i # Calculating orientation for the previously created LineString and the original orientation linestring - orientation = calculate_orientation_from_cross_section(profile_linestring=linestring, - orientation_linestring=orientation_linestring) + orientation = calculate_orientation_from_cross_section( + profile_linestring=linestring, + orientation_linestring=orientation_linestring, + ) # Replace point of orientation value - midpoint = geometry.Point([((points[0].coords[0][0] + points[1].coords[0][0]) / 2), - ((points[0].coords[0][1] + points[1].coords[0][1]) / 2)]) + midpoint = geometry.Point( + [ + ((points[0].coords[0][0] + points[1].coords[0][0]) / 2), + ((points[0].coords[0][1] + points[1].coords[0][1]) / 2), + ] + ) orientation[0] = midpoint @@ -5705,15 +6725,20 @@ def calculate_orientation_from_bent_cross_section(profile_linestring: shapely.ge # If the orientation is none, hence either one or both points are too far away from the linestring, return an error if orientation is None: - raise ValueError('Orientations may have been digitized across a bent, no orientations were calculated') + raise ValueError( + "Orientations may have been digitized across a bent, no orientations were calculated" + ) return orientation -def calculate_orientations_from_cross_section(profile_linestring: shapely.geometry.linestring.LineString, - orientation_linestrings: Union[gpd.geodataframe.GeoDataFrame, List[ - shapely.geometry.linestring.LineString]], - extract_coordinates: bool = True) -> gpd.geodataframe.GeoDataFrame: +def calculate_orientations_from_cross_section( + profile_linestring: shapely.geometry.linestring.LineString, + orientation_linestrings: Union[ + gpd.geodataframe.GeoDataFrame, List[shapely.geometry.linestring.LineString] + ], + extract_coordinates: bool = True, +) -> gpd.geodataframe.GeoDataFrame: """Calculating orientations from a cross sections using multiple LineStrings Parameters @@ -5775,23 +6800,23 @@ def calculate_orientations_from_cross_section(profile_linestring: shapely.geomet # Checking that the LineString is a Shapely LineString if not isinstance(profile_linestring, shapely.geometry.linestring.LineString): - raise TypeError('Input geometry must be a Shapley LineString') + raise TypeError("Input geometry must be a Shapley LineString") # Checking that the LineString is valid if not profile_linestring.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if profile_linestring.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the input orientations are stored as list or GeoDataFrame if not isinstance(orientation_linestrings, (gpd.geodataframe.GeoDataFrame, list)): - raise TypeError('Orientations must be stored as a GeoDataFrame or in a list') + raise TypeError("Orientations must be stored as a GeoDataFrame or in a list") # Copying the GeoDataFrame Data if isinstance(orientation_linestrings, gpd.geodataframe.GeoDataFrame): - data = orientation_linestrings.copy(deep=True).drop('geometry', axis=1) + data = orientation_linestrings.copy(deep=True).drop("geometry", axis=1) else: data = None @@ -5800,37 +6825,50 @@ def calculate_orientations_from_cross_section(profile_linestring: shapely.geomet orientation_linestrings = orientation_linestrings.geometry.tolist() # Checking that all elements of the geometry column are LineStrings - if not all(isinstance(n, shapely.geometry.linestring.LineString) for n in orientation_linestrings): - raise TypeError('All geometry elements of the linestring_gdf must be Shapely LineStrings') + if not all( + isinstance(n, shapely.geometry.linestring.LineString) + for n in orientation_linestrings + ): + raise TypeError( + "All geometry elements of the linestring_gdf must be Shapely LineStrings" + ) # Checking that all LineStrings are valid if not all(i.is_valid for i in orientation_linestrings): - raise ValueError('Not all Shapely LineStrings are valid') + raise ValueError("Not all Shapely LineStrings are valid") # Checking that no LineStrings are empty if any(i.is_empty for i in orientation_linestrings): - raise ValueError('One or more LineString Objects are empty') + raise ValueError("One or more LineString Objects are empty") # Calculating the orientations - orientations_list = [calculate_orientation_from_bent_cross_section(profile_linestring, i) - for i in orientation_linestrings] + orientations_list = [ + calculate_orientation_from_bent_cross_section(profile_linestring, i) + for i in orientation_linestrings + ] # Creating a GeoDataFrame with the orientation data - gdf = gpd.GeoDataFrame(data=pd.DataFrame(data=[[orientations_list[i][1] for i in range(len(orientations_list))], - [orientations_list[i][2] for i in range(len(orientations_list))], - [orientations_list[i][3] for i in range(len(orientations_list))], - [orientations_list[i][4] for i in range(len(orientations_list))]]).T, - geometry=[orientations_list[i][0] for i in range(len(orientations_list))]) + gdf = gpd.GeoDataFrame( + data=pd.DataFrame( + data=[ + [orientations_list[i][1] for i in range(len(orientations_list))], + [orientations_list[i][2] for i in range(len(orientations_list))], + [orientations_list[i][3] for i in range(len(orientations_list))], + [orientations_list[i][4] for i in range(len(orientations_list))], + ] + ).T, + geometry=[orientations_list[i][0] for i in range(len(orientations_list))], + ) # Assigning column names - gdf.columns = ['Z', 'dip', 'azimuth', 'polarity', 'geometry'] + gdf.columns = ["Z", "dip", "azimuth", "polarity", "geometry"] # Extracting X and Y coordinates from point objects if extract_coordinates: gdf = extract_xy(gdf) # Sorting the columns - gdf = gdf[['X', 'Y', 'Z', 'dip', 'azimuth', 'polarity', 'geometry']] + gdf = gdf[["X", "Y", "Z", "dip", "azimuth", "polarity", "geometry"]] # If the input is a GeoDataFrame, append the remaining data to the orientations GeoDataFrame if data is not None: @@ -5839,9 +6877,11 @@ def calculate_orientations_from_cross_section(profile_linestring: shapely.geomet return gdf -def extract_orientations_from_cross_sections(profile_gdf: gpd.geodataframe.GeoDataFrame, - orientations_gdf: gpd.geodataframe.GeoDataFrame, - profile_name_column: str = 'name') -> gpd.geodataframe.GeoDataFrame: +def extract_orientations_from_cross_sections( + profile_gdf: gpd.geodataframe.GeoDataFrame, + orientations_gdf: gpd.geodataframe.GeoDataFrame, + profile_name_column: str = "name", +) -> gpd.geodataframe.GeoDataFrame: """Calculating orientations digitized from cross sections Parameters @@ -5906,69 +6946,89 @@ def extract_orientations_from_cross_sections(profile_gdf: gpd.geodataframe.GeoDa # Checking that the profile traces are provided as GeoDataFrame if not isinstance(profile_gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Profile traces must be provided as GeoDataFrame') + raise TypeError("Profile traces must be provided as GeoDataFrame") # Checking that the input orientations are stored as GeoDataFrame if not isinstance(orientations_gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Orientations must be provided as GeoDataFrame') + raise TypeError("Orientations must be provided as GeoDataFrame") # Checking that the column profile name column is present in the GeoDataFrame if profile_name_column not in profile_gdf: - raise ValueError('Column with profile names not found, provide profile_name_column') + raise ValueError( + "Column with profile names not found, provide profile_name_column" + ) # Checking that the column profile name column is present in the GeoDataFrame if profile_name_column not in orientations_gdf: - raise ValueError('Column with profile names not found, provide profile_name_column') + raise ValueError( + "Column with profile names not found, provide profile_name_column" + ) # Checking that all elements of the geometry column are LineStrings - if not all(isinstance(n, shapely.geometry.linestring.LineString) for n in profile_gdf.geometry.tolist()): - raise TypeError('All geometry elements of the profile_gdf must be Shapely LineStrings') + if not all( + isinstance(n, shapely.geometry.linestring.LineString) + for n in profile_gdf.geometry.tolist() + ): + raise TypeError( + "All geometry elements of the profile_gdf must be Shapely LineStrings" + ) # Checking that all elements of the geometry column are LineStrings - if not all(isinstance(n, shapely.geometry.linestring.LineString) for n in orientations_gdf.geometry.tolist()): - raise TypeError('All geometry elements of the orientations_gdf must be Shapely LineStrings') + if not all( + isinstance(n, shapely.geometry.linestring.LineString) + for n in orientations_gdf.geometry.tolist() + ): + raise TypeError( + "All geometry elements of the orientations_gdf must be Shapely LineStrings" + ) # Checking that all elements of the geometry column are valid if not all(n.is_valid for n in profile_gdf.geometry.tolist()): - raise ValueError('All Shapely LineStrings must be valid') + raise ValueError("All Shapely LineStrings must be valid") # Checking that all elements of the geometry column are not empty if any(n.is_empty for n in orientations_gdf.geometry.tolist()): - raise ValueError('One or more geometries are empty') + raise ValueError("One or more geometries are empty") # Checking that all elements of the geometry column are valid if not all(n.is_valid for n in profile_gdf.geometry.tolist()): - raise ValueError('All Shapely LineStrings must be valid') + raise ValueError("All Shapely LineStrings must be valid") # Checking that all elements of the geometry column are not empty if any(n.is_empty for n in orientations_gdf.geometry.tolist()): - raise ValueError('One or more geometries are empty') + raise ValueError("One or more geometries are empty") # Create list of GeoDataFrames containing orientation and location information for orientations on cross sections - list_gdf = [calculate_orientations_from_cross_section( - profile_gdf.geometry[i], - orientations_gdf[orientations_gdf[profile_name_column] == profile_gdf[profile_name_column][i]].reset_index()) - for i in range(len(profile_gdf))] + list_gdf = [ + calculate_orientations_from_cross_section( + profile_gdf.geometry[i], + orientations_gdf[ + orientations_gdf[profile_name_column] + == profile_gdf[profile_name_column][i] + ].reset_index(), + ) + for i in range(len(profile_gdf)) + ] # Merging the list of gdfs, resetting the index and dropping the index column gdf = pd.concat(list_gdf) # Dropping column if it is in the gdf - if 'level_0' in gdf: - gdf = gdf.drop('level_0', axis=1) + if "level_0" in gdf: + gdf = gdf.drop("level_0", axis=1) # Resetting index and dropping columns - gdf = gdf.reset_index().drop(['index', 'level_0'], axis=1) + gdf = gdf.reset_index().drop(["index", "level_0"], axis=1) # Creating GeoDataFrame - gdf = gpd.GeoDataFrame(data=gdf, - geometry=gdf['geometry'], - crs=orientations_gdf.crs) + gdf = gpd.GeoDataFrame(data=gdf, geometry=gdf["geometry"], crs=orientations_gdf.crs) return gdf -def calculate_orientation_for_three_point_problem(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodataframe.GeoDataFrame: +def calculate_orientation_for_three_point_problem( + gdf: gpd.geodataframe.GeoDataFrame, +) -> gpd.geodataframe.GeoDataFrame: """Calculating the orientation for a three point problem Parameters @@ -6008,35 +7068,34 @@ def calculate_orientation_for_three_point_problem(gdf: gpd.geodataframe.GeoDataF # Checking that the points are provided as GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Profile traces must be provided as GeoDataFrame') + raise TypeError("Profile traces must be provided as GeoDataFrame") # Checking that the GeoDataFrame consists of points if not all(shapely.get_type_id(gdf.geometry) == 0): - raise TypeError('All elements must be of geometry type Point') + raise TypeError("All elements must be of geometry type Point") # Checking that the length of the GeoDataFrame is 3 if not len(gdf) == 3: - raise ValueError('GeoDataFrame must only contain three points') + raise ValueError("GeoDataFrame must only contain three points") # Extracting X and Y values - if not {'X', 'Y'}.issubset(gdf.columns): + if not {"X", "Y"}.issubset(gdf.columns): gdf = extract_xy(gdf=gdf) # Checking that the Z column is in the GeoDataFrame - if 'Z' not in gdf: - raise ValueError('Z values missing in GeoDataFrame') + if "Z" not in gdf: + raise ValueError("Z values missing in GeoDataFrame") # Sorting the points by altitude and reset index - gdf = gdf.sort_values(by='Z', ascending=True).reset_index(drop=True) + gdf = gdf.sort_values(by="Z", ascending=True).reset_index(drop=True) # Getting the point values - point1 = gdf[['X', 'Y', 'Z']].loc[0].values - point2 = gdf[['X', 'Y', 'Z']].loc[1].values - point3 = gdf[['X', 'Y', 'Z']].loc[2].values + point1 = gdf[["X", "Y", "Z"]].loc[0].values + point2 = gdf[["X", "Y", "Z"]].loc[1].values + point3 = gdf[["X", "Y", "Z"]].loc[2].values # Calculating the normal for the points - normal = np.cross(a=point3 - point2, - b=point1 - point2) + normal = np.cross(a=point3 - point2, b=point1 - point2) normal /= np.linalg.norm(normal) @@ -6051,16 +7110,36 @@ def calculate_orientation_for_three_point_problem(gdf: gpd.geodataframe.GeoDataF azimuth = 180 - azimuth # Calculate location of orientation - x = np.mean(gdf['X'].values) - y = np.mean(gdf['Y'].values) - z = np.mean(gdf['Z'].values) + x = np.mean(gdf["X"].values) + y = np.mean(gdf["Y"].values) + z = np.mean(gdf["Z"].values) # Creating GeoDataFrame - orientation = gpd.GeoDataFrame(data=pd.DataFrame( - [float(z), gdf['formation'].unique()[0], float(azimuth), float(dip), float(1), float(x), float(y)]).T, - geometry=gpd.points_from_xy(x=[x], y=[y]), - crs=gdf.crs) - orientation.columns = ['Z', 'formation', 'azimuth', 'dip', 'polarity', 'X', 'Y', 'geometry'] + orientation = gpd.GeoDataFrame( + data=pd.DataFrame( + [ + float(z), + gdf["formation"].unique()[0], + float(azimuth), + float(dip), + float(1), + float(x), + float(y), + ] + ).T, + geometry=gpd.points_from_xy(x=[x], y=[y]), + crs=gdf.crs, + ) + orientation.columns = [ + "Z", + "formation", + "azimuth", + "dip", + "polarity", + "X", + "Y", + "geometry", + ] return orientation @@ -6069,9 +7148,10 @@ def calculate_orientation_for_three_point_problem(gdf: gpd.geodataframe.GeoDataF ######################################### -def intersection_polygon_polygon(polygon1: shapely.geometry.polygon.Polygon, - polygon2: shapely.geometry.polygon.Polygon) \ - -> Union[shapely.geometry.linestring.LineString, shapely.geometry.polygon.Polygon]: +def intersect_polygon_polygon( + polygon1: shapely.geometry.polygon.Polygon, + polygon2: shapely.geometry.polygon.Polygon, +) -> Union[shapely.geometry.linestring.LineString, shapely.geometry.polygon.Polygon]: """Calculating the intersection between to Shapely Polygons Parameters @@ -6093,6 +7173,8 @@ def intersection_polygon_polygon(polygon1: shapely.geometry.polygon.Polygon, .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -6109,42 +7191,42 @@ def intersection_polygon_polygon(polygon1: shapely.geometry.polygon.Polygon, 'POLYGON ((10 0, 20 0, 20 10, 10 10, 10 0))' >>> # Calculating the intersection between two polygons - >>> intersection = gg.vector.intersection_polygon_polygon(polygon1=polygon1, polygon2=polygon2) + >>> intersection = gg.vector.intersect_polygon_polygon(polygon1=polygon1, polygon2=polygon2) >>> intersection.wkt 'LINESTRING (10 0, 10 10)' See Also ________ - intersections_polygon_polygons : Intersecting a polygon with mutiple polygons - intersections_polygons_polygons : Intersecting multiple polygons with multiple polygons + intersect_polygon_polygons : Intersecting a polygon with mutiple polygons + intersect_polygons_polygons : Intersecting multiple polygons with multiple polygons extract_xy_from_polygon_intersections : Extracting intersections between multiple polygons """ # Checking that the input polygon is a Shapely Polygon if not isinstance(polygon1, shapely.geometry.polygon.Polygon): - raise TypeError('Input Polygon1 must a be Shapely Polygon') + raise TypeError("Input Polygon1 must a be Shapely Polygon") # Checking that the input polygon is a Shapely Polygon if not isinstance(polygon2, shapely.geometry.polygon.Polygon): - raise TypeError('Input Polygon2 must a be Shapely Polygon') + raise TypeError("Input Polygon2 must a be Shapely Polygon") # Checking if input geometries are valid if not polygon1.is_valid: - raise ValueError('Input polygon 1 is an invalid input geometry') + raise ValueError("Input polygon 1 is an invalid input geometry") # Checking if input geometries are valid if not polygon2.is_valid: - raise ValueError('Input polygon 2 is an invalid input geometry') + raise ValueError("Input polygon 2 is an invalid input geometry") # Checking if input geometries are empty if polygon1.is_empty: - raise ValueError('Input polygon 1 is an empty input geometry') + raise ValueError("Input polygon 1 is an empty input geometry") # Checking if input geometries are empty if polygon2.is_empty: - raise ValueError('Input polygon 2 is an empty input geometry') + raise ValueError("Input polygon 2 is an empty input geometry") # Calculating the intersections intersection = polygon1.intersection(polygon2) @@ -6152,10 +7234,12 @@ def intersection_polygon_polygon(polygon1: shapely.geometry.polygon.Polygon, return intersection -def intersections_polygon_polygons(polygon1: shapely.geometry.polygon.Polygon, - polygons2: Union[ - gpd.geodataframe.GeoDataFrame, List[shapely.geometry.polygon.Polygon]]) \ - -> List[shapely.geometry.base.BaseGeometry]: +def intersect_polygon_polygons( + polygon1: shapely.geometry.polygon.Polygon, + polygons2: Union[ + gpd.geodataframe.GeoDataFrame, List[shapely.geometry.polygon.Polygon] + ], +) -> List[shapely.geometry.base.BaseGeometry]: """Calculating the intersections between one polygon and a list of polygons Parameters @@ -6176,6 +7260,8 @@ def intersections_polygon_polygons(polygon1: shapely.geometry.polygon.Polygon, .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -6211,27 +7297,27 @@ def intersections_polygon_polygons(polygon1: shapely.geometry.polygon.Polygon, See Also ________ - intersection_polygon_polygon : Intersecting a polygon with a polygon - intersections_polygons_polygons : Intersecting multiple polygons with multiple polygons + intersect_polygon_polygon : Intersecting a polygon with a polygon + intersect_polygons_polygons : Intersecting multiple polygons with multiple polygons extract_xy_from_polygon_intersections : Extracting intersections between multiple polygons """ # Checking that the input polygon is a Shapely Polygon if not isinstance(polygon1, shapely.geometry.polygon.Polygon): - raise TypeError('Input Polygon1 must a be Shapely Polygon') + raise TypeError("Input Polygon1 must a be Shapely Polygon") # Checking if input geometries are valid if not polygon1.is_valid: - raise ValueError('Input polygon 1 is an invalid input geometry') + raise ValueError("Input polygon 1 is an invalid input geometry") # Checking if input geometries are empty if polygon1.is_empty: - raise ValueError('Input polygon 1 is an empty input geometry') + raise ValueError("Input polygon 1 is an empty input geometry") # Checking that the input polygon is a list or a GeoDataFrame if not isinstance(polygons2, (gpd.geodataframe.GeoDataFrame, list)): - raise TypeError('Input Polygon2 must a be GeoDataFrame or list') + raise TypeError("Input Polygon2 must a be GeoDataFrame or list") # Converting the Polygons stored in the GeoDataFrame into a list and removing invalid geometries if isinstance(polygons2, gpd.geodataframe.GeoDataFrame): @@ -6240,27 +7326,33 @@ def intersections_polygon_polygons(polygon1: shapely.geometry.polygon.Polygon, # Checking that all elements of the geometry column are Polygons if not all(isinstance(n, shapely.geometry.polygon.Polygon) for n in polygons2): - raise TypeError('All geometry elements of polygons2 must be Shapely Polygons') + raise TypeError("All geometry elements of polygons2 must be Shapely Polygons") # Checking that all elements of the geometry column are valid if not all(n.is_valid for n in polygons2): - raise TypeError('All geometry elements of polygons2 must be valid') + raise TypeError("All geometry elements of polygons2 must be valid") # Checking that all elements of the geometry column are not empty if any(n.is_empty for n in polygons2): - raise TypeError('None of the geometry elements of polygons2 must be empty') + raise TypeError("None of the geometry elements of polygons2 must be empty") # Creating the list of intersection geometries - intersections = [intersection_polygon_polygon(polygon1=polygon1, - polygon2=polygon) for polygon in polygons2] + intersections = [ + intersect_polygon_polygon(polygon1=polygon1, polygon2=polygon) + for polygon in polygons2 + ] return intersections -def intersections_polygons_polygons( - polygons1: Union[gpd.geodataframe.GeoDataFrame, List[shapely.geometry.polygon.Polygon]], - polygons2: Union[gpd.geodataframe.GeoDataFrame, List[shapely.geometry.polygon.Polygon]]) \ - -> List[shapely.geometry.base.BaseGeometry]: +def intersect_polygons_polygons( + polygons1: Union[ + gpd.geodataframe.GeoDataFrame, List[shapely.geometry.polygon.Polygon] + ], + polygons2: Union[ + gpd.geodataframe.GeoDataFrame, List[shapely.geometry.polygon.Polygon] + ], +) -> List[shapely.geometry.base.BaseGeometry]: """Calculating the intersections between a list of Polygons Parameters @@ -6280,6 +7372,8 @@ def intersections_polygons_polygons( .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -6302,7 +7396,7 @@ def intersections_polygons_polygons( >>> polygons2 = [polygon2, polygon2] >>> # Calculating intersections between polygons and polygons - >>> intersection = gg.vector.intersections_polygons_polygons(polygons1=polygons1, polygons2=polygons2) + >>> intersection = gg.vector.intersect_polygons_polygons(polygons1=polygons1, polygons2=polygons2) >>> intersection [, , @@ -6328,15 +7422,15 @@ def intersections_polygons_polygons( See Also ________ - intersection_polygon_polygon : Intersecting a polygon with a polygon - intersections_polygon_polygons : Intersecting a polygons with multiple polygons + intersect_polygon_polygon : Intersecting a polygon with a polygon + intersect_polygon_polygons : Intersecting a polygons with multiple polygons extract_xy_from_polygon_intersections : Extracting intersections between multiple polygons """ # Checking that the input polygon is a list or a GeoDataFrame if not isinstance(polygons1, (gpd.geodataframe.GeoDataFrame, list)): - raise TypeError('Input Polygon2 must a be Shapely Polygon') + raise TypeError("Input Polygon2 must a be Shapely Polygon") # Converting the Polygons stored in the GeoDataFrame into a list if isinstance(polygons1, gpd.geodataframe.GeoDataFrame): @@ -6346,19 +7440,19 @@ def intersections_polygons_polygons( # Checking that all elements of the geometry column are Polygons if not all(isinstance(n, shapely.geometry.polygon.Polygon) for n in polygons1): - raise TypeError('All geometry elements of polygons2 must be Shapely Polygons') + raise TypeError("All geometry elements of polygons2 must be Shapely Polygons") # Checking that all elements of the geometry column are valid if not all(n.is_valid for n in polygons1): - raise TypeError('All geometry elements of polygons1 must be valid') + raise TypeError("All geometry elements of polygons1 must be valid") # Checking that all elements of the geometry column are not empty if any(n.is_empty for n in polygons1): - raise TypeError('None of the geometry elements of polygons1 must be empty') + raise TypeError("None of the geometry elements of polygons1 must be empty") # Checking that the input polygon is a list or a GeoDataFrame if not isinstance(polygons2, (gpd.geodataframe.GeoDataFrame, list)): - raise TypeError('Input Polygon2 must a be Shapely Polygon') + raise TypeError("Input Polygon2 must a be Shapely Polygon") # Converting the Polygons stored in the GeoDataFrame into a list if isinstance(polygons2, gpd.geodataframe.GeoDataFrame): @@ -6368,29 +7462,37 @@ def intersections_polygons_polygons( # Checking that all elements of the geometry column are Polygons if not all(isinstance(n, shapely.geometry.polygon.Polygon) for n in polygons2): - raise TypeError('All geometry elements of polygons2 must be Shapely Polygons') + raise TypeError("All geometry elements of polygons2 must be Shapely Polygons") # Checking that all elements of the geometry column are valid if not all(n.is_valid for n in polygons2): - raise TypeError('All geometry elements of polygons2 must be valid') + raise TypeError("All geometry elements of polygons2 must be valid") # Checking that all elements of the geometry column are not empty if any(n.is_empty for n in polygons2): - raise TypeError('None of the geometry elements of polygons2 must be empty') + raise TypeError("None of the geometry elements of polygons2 must be empty") # Creating list with lists of intersections - intersections = [intersections_polygon_polygons(polygon1=polygon, - polygons2=polygons2) for polygon in polygons1] + intersections = [ + intersect_polygon_polygons(polygon1=polygon, polygons2=polygons2) + for polygon in polygons1 + ] # Creating single list from list of lists - intersections = [intersections[i][j] for i in range(len(intersections)) for j in range(len(intersections[i]))] + intersections = [ + intersections[i][j] + for i in range(len(intersections)) + for j in range(len(intersections[i])) + ] return intersections -def extract_xy_from_polygon_intersections(gdf: gpd.geodataframe.GeoDataFrame, - extract_coordinates: bool = False, - drop_index: bool = True) -> gpd.geodataframe.GeoDataFrame: +def extract_xy_from_polygon_intersections( + gdf: gpd.geodataframe.GeoDataFrame, + extract_coordinates: bool = False, + drop_index: bool = True, +) -> gpd.geodataframe.GeoDataFrame: """Calculating the intersections between Polygons; the table must be sorted by stratigraphic age Parameters @@ -6456,45 +7558,64 @@ def extract_xy_from_polygon_intersections(gdf: gpd.geodataframe.GeoDataFrame, # Checking that the polygons of the geological map are provided as GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Input Geometries must be stored as GeoDataFrame') + raise TypeError("Input Geometries must be stored as GeoDataFrame") # Checking that the formation name is in the GeoDataFrame - if 'formation' not in gdf: - raise ValueError('No formation column found') + if "formation" not in gdf: + raise ValueError("No formation column found") # Removing invalid geometries and resetting the index gdf = gdf[gdf.geometry.is_valid].reset_index(drop=True) # Creating a list of GeoDataFrames with intersections - intersections = [intersections_polygons_polygons( - polygons1=gdf[gdf['formation'].isin([gdf['formation'].unique().tolist()[i]])], - polygons2=gdf[gdf['formation'].isin(gdf['formation'].unique().tolist()[i + 1:])]) - for i in range(len(gdf['formation'].unique().tolist()))] + intersections = [ + intersect_polygons_polygons( + polygons1=gdf[ + gdf["formation"].isin([gdf["formation"].unique().tolist()[i]]) + ], + polygons2=gdf[ + gdf["formation"].isin(gdf["formation"].unique().tolist()[i + 1 :]) + ], + ) + for i in range(len(gdf["formation"].unique().tolist())) + ] # Creating list from list of lists - intersections = [intersections[i][j] for i in range(len(intersections)) for j in range(len(intersections[i]))] + intersections = [ + intersections[i][j] + for i in range(len(intersections)) + for j in range(len(intersections[i])) + ] # Counting the number of different sections - counts = [len(gdf[gdf['formation'] == gdf['formation'].unique().tolist()[i]]) for - i in range(len(gdf['formation'].unique()))] + counts = [ + len(gdf[gdf["formation"] == gdf["formation"].unique().tolist()[i]]) + for i in range(len(gdf["formation"].unique())) + ] # Counting the number of different sections - values = [(len(gdf[gdf['formation'] != gdf['formation'].unique().tolist()[i]]) - len( - gdf[gdf['formation'].isin(gdf['formation'].unique().tolist()[:i])])) for i in - range(len(gdf['formation'].unique()))] + values = [ + ( + len(gdf[gdf["formation"] != gdf["formation"].unique().tolist()[i]]) + - len(gdf[gdf["formation"].isin(gdf["formation"].unique().tolist()[:i])]) + ) + for i in range(len(gdf["formation"].unique())) + ] # Create array with repeated values - repeated_values = np.concatenate([np.ones(counts[i]) * values[i] for i in range(len(counts))]).astype(int) + repeated_values = np.concatenate( + [np.ones(counts[i]) * values[i] for i in range(len(counts))] + ).astype(int) # Create DataFrame from input gdf df = pd.DataFrame(gdf.values.repeat(repeated_values, axis=0)) df.columns = gdf.columns # Create gdf with intersections - gdf = gpd.GeoDataFrame(data=df.drop('geometry', axis=1), - geometry=intersections, - crs=gdf.crs) - gdf = gdf[(gdf.geom_type != 'Point') & (gdf.geom_type != 'GeometryCollection')] + gdf = gpd.GeoDataFrame( + data=df.drop("geometry", axis=1), geometry=intersections, crs=gdf.crs + ) + gdf = gdf[(gdf.geom_type != "Point") & (gdf.geom_type != "GeometryCollection")] gdf = gdf[~gdf.is_empty].reset_index() # Extracting coordinates @@ -6502,9 +7623,8 @@ def extract_xy_from_polygon_intersections(gdf: gpd.geodataframe.GeoDataFrame, gdf = extract_xy(gdf=gdf) # Dropping index column - if 'index' in gdf and drop_index: - gdf = gdf.drop(columns='index', - axis=1) + if "index" in gdf and drop_index: + gdf = gdf.drop(columns="index", axis=1) return gdf @@ -6513,8 +7633,11 @@ def extract_xy_from_polygon_intersections(gdf: gpd.geodataframe.GeoDataFrame, ############################################ -def calculate_azimuth(gdf: Union[gpd.geodataframe.GeoDataFrame, - List[shapely.geometry.linestring.LineString]]) -> List[Union[float, int]]: +def calculate_azimuth( + gdf: Union[ + gpd.geodataframe.GeoDataFrame, List[shapely.geometry.linestring.LineString] + ] +) -> List[Union[float, int]]: """Calculating the azimuth for an orientation Geodataframe represented by LineStrings Parameters @@ -6572,33 +7695,36 @@ def calculate_azimuth(gdf: Union[gpd.geodataframe.GeoDataFrame, # Checking that gdf is a GeoDataFrame if not isinstance(gdf, (gpd.geodataframe.GeoDataFrame, list)): - raise TypeError('Data must be a GeoDataFrame or a list of LineStrings') + raise TypeError("Data must be a GeoDataFrame or a list of LineStrings") # Converting the LineStrings stored in the GeoDataFrame into a list if isinstance(gdf, gpd.geodataframe.GeoDataFrame): # Checking that the pd_series contains a linestring if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All elements must be of geometry type LineString') + raise TypeError("All elements must be of geometry type LineString") gdf = gdf.geometry.tolist() # Checking that all elements of the geometry column are valid if not all(n.is_valid for n in gdf): - raise ValueError('All Shapely LineStrings must be valid') + raise ValueError("All Shapely LineStrings must be valid") # Checking that all elements of the geometry column are not empty if any(n.is_empty for n in gdf): - raise ValueError('One or more geometries are empty') + raise ValueError("One or more geometries are empty") # Calculating the azimuths - azimuth_list = [calculate_strike_direction_straight_linestring(linestring=linestring) for linestring in gdf] + azimuth_list = [ + calculate_strike_direction_straight_linestring(linestring=linestring) + for linestring in gdf + ] return azimuth_list -def create_linestring_from_points(gdf: gpd.geodataframe.GeoDataFrame, - formation: str, - altitude: Union[int, float]) -> shapely.geometry.linestring.LineString: +def create_linestring_from_points( + gdf: gpd.geodataframe.GeoDataFrame, formation: str, altitude: Union[int, float] +) -> shapely.geometry.linestring.LineString: """Creating a LineString object from a GeoDataFrame containing surface points at a given altitude and for a given formation @@ -6659,34 +7785,34 @@ def create_linestring_from_points(gdf: gpd.geodataframe.GeoDataFrame, # Checking if gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking geometry type of GeoDataFrame - if not all(gdf.geom_type == 'Point'): - raise ValueError('All objects of the GeoDataFrame must be of geom_type point') + if not all(gdf.geom_type == "Point"): + raise ValueError("All objects of the GeoDataFrame must be of geom_type point") # Checking if X and Y values are in column - if not {'formation', 'Z'}.issubset(gdf.columns): - raise ValueError('formation or Z column missing in GeoDataFrame') + if not {"formation", "Z"}.issubset(gdf.columns): + raise ValueError("formation or Z column missing in GeoDataFrame") # Checking if the formation is of type string if not isinstance(formation, str): - raise TypeError('formation must be of type string') + raise TypeError("formation must be of type string") # Checking that the formation is present in the GeoDataFrame - if formation not in gdf['formation'].unique().tolist(): - raise ValueError('Formation is not in GeoDataFrame') + if formation not in gdf["formation"].unique().tolist(): + raise ValueError("Formation is not in GeoDataFrame") # Checking if the altitude is of type int or float if not isinstance(altitude, (int, float)): - raise TypeError('Altitude must be of type int or float') + raise TypeError("Altitude must be of type int or float") # Creating a copy of the GeoDataFrame gdf_new = gdf.copy(deep=True) # Filtering GeoDataFrame by formation and altitude - gdf_new = gdf_new[gdf_new['formation'] == formation] - gdf_new = gdf_new[gdf_new['Z'] == altitude] + gdf_new = gdf_new[gdf_new["formation"] == formation] + gdf_new = gdf_new[gdf_new["Z"] == altitude] # Creating LineString from all available points linestring = geometry.LineString(gdf_new.geometry.to_list()) @@ -6694,7 +7820,9 @@ def create_linestring_from_points(gdf: gpd.geodataframe.GeoDataFrame, return linestring -def create_linestring_gdf(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodataframe.GeoDataFrame: +def create_linestring_gdf( + gdf: gpd.geodataframe.GeoDataFrame, +) -> gpd.geodataframe.GeoDataFrame: """Creating LineStrings from Points Parameters @@ -6750,43 +7878,45 @@ def create_linestring_gdf(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodatafram # Checking if gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking geometry type of GeoDataFrame - if not all(gdf.geom_type == 'Point'): - raise ValueError('All objects of the GeoDataFrame must be of geom_type point') + if not all(gdf.geom_type == "Point"): + raise ValueError("All objects of the GeoDataFrame must be of geom_type point") # Checking if X and Y values are in column - if not {'formation', 'Z'}.issubset(gdf.columns): - raise ValueError('formation or Z column missing in GeoDataFrame') + if not {"formation", "Z"}.issubset(gdf.columns): + raise ValueError("formation or Z column missing in GeoDataFrame") # Create copy of gdf gdf_new = gdf.copy(deep=True) # Sort by Z values - gdf_new = gdf_new.sort_values('Z') + gdf_new = gdf_new.sort_values("Z") # Create empty LineString list linestrings = [] # Create LineStrings and append to list - for i in gdf_new['formation'].unique().tolist(): - for j in gdf_new['Z'].unique().tolist(): - linestring = create_linestring_from_points(gdf=gdf_new, - formation=i, - altitude=j) + for i in gdf_new["formation"].unique().tolist(): + for j in gdf_new["Z"].unique().tolist(): + linestring = create_linestring_from_points( + gdf=gdf_new, formation=i, altitude=j + ) linestrings.append(linestring) # Create gdf - gdf_linestrings = gpd.GeoDataFrame(data=gdf_new.drop_duplicates(subset='id').drop(labels='geometry', axis=1), - geometry=linestrings, - crs=gdf_new.crs) + gdf_linestrings = gpd.GeoDataFrame( + data=gdf_new.drop_duplicates(subset="id").drop(labels="geometry", axis=1), + geometry=linestrings, + crs=gdf_new.crs, + ) # Add Z values - gdf_linestrings['Z'] = gdf_new['Z'].unique() + gdf_linestrings["Z"] = gdf_new["Z"].unique() # Add formation name - gdf_linestrings['formation'] = gdf['formation'].unique()[0] + gdf_linestrings["formation"] = gdf["formation"].unique()[0] # Resetting Index gdf_linestrings = gdf_linestrings.reset_index() @@ -6794,8 +7924,9 @@ def create_linestring_gdf(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodatafram return gdf_linestrings -def extract_orientations_from_map(gdf: gpd.geodataframe.GeoDataFrame, - dz: str = 'dZ') -> gpd.geodataframe.GeoDataFrame: +def extract_orientations_from_map( + gdf: gpd.geodataframe.GeoDataFrame, dz: str = "dZ" +) -> gpd.geodataframe.GeoDataFrame: """Calculating orientations from LineStrings Parameters @@ -6859,58 +7990,61 @@ def extract_orientations_from_map(gdf: gpd.geodataframe.GeoDataFrame, # Checking that gdf is a GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Data must be a GeoDataFrame') + raise TypeError("Data must be a GeoDataFrame") # Checking that the pd_series contains a linestring if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All elements must be of geometry type LineString') + raise TypeError("All elements must be of geometry type LineString") # Checking that all elements of the geometry column are valid if not all(n.is_valid for n in gdf.geometry.tolist()): - raise ValueError('All Shapely LineStrings must be valid') + raise ValueError("All Shapely LineStrings must be valid") # Checking that all elements of the geometry column are not empty if any(n.is_empty for n in gdf.geometry.tolist()): - raise ValueError('One or more geometries are empty') + raise ValueError("One or more geometries are empty") # Checking that the height difference column is of type str if not isinstance(dz, str): - raise TypeError('Height difference column must be of type str') + raise TypeError("Height difference column must be of type str") # Checking that the height difference column is in the gdf if dz not in gdf: - raise ValueError('Provide valid name for the height difference column dz') + raise ValueError("Provide valid name for the height difference column dz") # Copy gdf gdf = gdf.copy(deep=True) # Calculating the azimuths - gdf['azimuth'] = calculate_azimuth(gdf=gdf) + gdf["azimuth"] = calculate_azimuth(gdf=gdf) # Obtaining the lengths of LineStrings - gdf['length'] = gdf.geometry.length + gdf["length"] = gdf.geometry.length # Calculating the dip based on the height difference and length of the LineString - gdf['dip'] = np.rad2deg(np.arctan(gdf[dz] / gdf['length'])) + gdf["dip"] = np.rad2deg(np.arctan(gdf[dz] / gdf["length"])) # Calculating new geometry column - gdf['geometry'] = calculate_midpoints_linestrings(linestring_gdf=gdf) + gdf["geometry"] = calculate_midpoints_linestrings(linestring_gdf=gdf) # Recreating GeoDataFrame - gdf = gpd.GeoDataFrame(data=gdf.drop(labels=['dZ', 'length'], axis=1), geometry=gdf['geometry']) + gdf = gpd.GeoDataFrame( + data=gdf.drop(labels=["dZ", "length"], axis=1), geometry=gdf["geometry"] + ) # Extracting X and Y Coordinates - gdf = extract_xy(gdf=gdf, - reset_index=False) + gdf = extract_xy(gdf=gdf, reset_index=False) # Setting the polarity - gdf['polarity'] = 1 + gdf["polarity"] = 1 return gdf -def calculate_distance_linestrings(ls1: shapely.geometry.linestring.LineString, - ls2: shapely.geometry.linestring.LineString) -> float: +def calculate_distance_linestrings( + ls1: shapely.geometry.linestring.LineString, + ls2: shapely.geometry.linestring.LineString, +) -> float: """Calculating the minimal distance between two LineStrings Parameters @@ -6962,27 +8096,27 @@ def calculate_distance_linestrings(ls1: shapely.geometry.linestring.LineString, # Checking that ls1 is a Shapely LineString if not isinstance(ls1, shapely.geometry.linestring.LineString): - raise TypeError('Line Object must be a Shapely LineString') + raise TypeError("Line Object must be a Shapely LineString") # Checking that ls2 is a Shapely LineString if not isinstance(ls2, shapely.geometry.linestring.LineString): - raise TypeError('Line Object must be a Shapely LineString') + raise TypeError("Line Object must be a Shapely LineString") # Checking that the LineString is valid if not ls1.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if ls1.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Checking that the LineString is valid if not ls2.is_valid: - raise ValueError('LineString is not a valid object') + raise ValueError("LineString is not a valid object") # Checking that the LineString is not empty if ls2.is_empty: - raise ValueError('LineString is an empty object') + raise ValueError("LineString is an empty object") # Calculating the distance distance = ls1.distance(ls2) @@ -6990,7 +8124,9 @@ def calculate_distance_linestrings(ls1: shapely.geometry.linestring.LineString, return distance -def calculate_orientations_from_strike_lines(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodataframe.GeoDataFrame: +def calculate_orientations_from_strike_lines( + gdf: gpd.geodataframe.GeoDataFrame, +) -> gpd.geodataframe.GeoDataFrame: """Calculating orientations based on LineStrings representing strike lines Parameters @@ -7054,68 +8190,85 @@ def calculate_orientations_from_strike_lines(gdf: gpd.geodataframe.GeoDataFrame) # Checking that gdf is a GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Data must be a GeoDataFrame') + raise TypeError("Data must be a GeoDataFrame") # Checking that the pd_series contains a linestring if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All elements must be of geometry type LineString') + raise TypeError("All elements must be of geometry type LineString") # Checking that all geometry objects are valid if not all(n.is_valid for n in gdf.geometry.tolist()): - raise ValueError('Not all geometry objects are valid') + raise ValueError("Not all geometry objects are valid") # Checking that no geometry object is empty if any(n.is_empty for n in gdf.geometry.tolist()): - raise ValueError('One or more geometry objects are empty') + raise ValueError("One or more geometry objects are empty") # Checking that the Z column is present in the GeoDataFrame - if 'Z' not in gdf: - raise ValueError('Z column not found in GeoDataFrame') + if "Z" not in gdf: + raise ValueError("Z column not found in GeoDataFrame") # Checking that the id column is present in the GeoDataFrame - if 'id' not in gdf: - raise ValueError('id column must be present in GeoDataFrame to assign order of LineStrings') + if "id" not in gdf: + raise ValueError( + "id column must be present in GeoDataFrame to assign order of LineStrings" + ) # Sorting values by Z value and resetting index - gdf = gdf.sort_values(by='Z', ascending=True).reset_index(drop=True) + gdf = gdf.sort_values(by="Z", ascending=True).reset_index(drop=True) # Calculating distances between strike lines - distances = [calculate_distance_linestrings(ls1=gdf.loc[i].geometry, - ls2=gdf.loc[i + 1].geometry) for i in range(len(gdf) - 1)] + distances = [ + calculate_distance_linestrings( + ls1=gdf.loc[i].geometry, ls2=gdf.loc[i + 1].geometry + ) + for i in range(len(gdf) - 1) + ] # Calculating midpoints of LineStrings midpoints = calculate_midpoints_linestrings(linestring_gdf=gdf) # Creating new LineStrings between strike lines - linestrings_new = [shapely.geometry.LineString([midpoints[i], midpoints[i + 1]]) for i in range(len(midpoints) - 1)] + linestrings_new = [ + shapely.geometry.LineString([midpoints[i], midpoints[i + 1]]) + for i in range(len(midpoints) - 1) + ] # Calculating the location of orientations as midpoints of new LineStrings - orientations_locations = calculate_midpoints_linestrings(linestring_gdf=linestrings_new) + orientations_locations = calculate_midpoints_linestrings( + linestring_gdf=linestrings_new + ) # Calculating dips of orientations based on the height difference and distance between LineStrings dips = np.abs( - [np.rad2deg(np.arctan((gdf.loc[i + 1]['Z'] - gdf.loc[i]['Z']) / distances[i])) for i in range(len(gdf) - 1)]) + [ + np.rad2deg( + np.arctan((gdf.loc[i + 1]["Z"] - gdf.loc[i]["Z"]) / distances[i]) + ) + for i in range(len(gdf) - 1) + ] + ) # Calculating altitudes of new orientations - altitudes = [(gdf.loc[i + 1]['Z'] + gdf.loc[i]['Z']) / 2 for i in range(len(gdf) - 1)] + altitudes = [ + (gdf.loc[i + 1]["Z"] + gdf.loc[i]["Z"]) / 2 for i in range(len(gdf) - 1) + ] # Extracting XY coordinates - gdf_new = extract_xy(gdf=gdf, - drop_id=False, - reset_index=False) + gdf_new = extract_xy(gdf=gdf, drop_id=False, reset_index=False) # Creating empty list to store orientation values azimuths = [] # Calculating azimuth values - for i in range(len(gdf_new['id'].unique()) - 1): + for i in range(len(gdf_new["id"].unique()) - 1): # Get values for the first and second height - gdf_new1 = gdf_new[gdf_new['id'] == i + 1 + (gdf_new['id'].unique()[0] - 1)] - gdf_new2 = gdf_new[gdf_new['id'] == i + 2 + (gdf_new['id'].unique()[0] - 1)] + gdf_new1 = gdf_new[gdf_new["id"] == i + 1 + (gdf_new["id"].unique()[0] - 1)] + gdf_new2 = gdf_new[gdf_new["id"] == i + 2 + (gdf_new["id"].unique()[0] - 1)] # Convert coordinates to lists - gdf_new1_array = gdf_new1[['X', 'Y', 'Z']].values.tolist() - gdf_new2_array = gdf_new2[['X', 'Y', 'Z']].values.tolist() + gdf_new1_array = gdf_new1[["X", "Y", "Z"]].values.tolist() + gdf_new2_array = gdf_new2[["X", "Y", "Z"]].values.tolist() # Merge lists of points points = gdf_new1_array + gdf_new2_array @@ -7127,27 +8280,30 @@ def calculate_orientations_from_strike_lines(gdf: gpd.geodataframe.GeoDataFrame) # Convert vector to dip and azimuth sign_z = 1 if z > 0 else -1 - azimuth = (np.degrees(np.arctan2(sign_z * x, sign_z * y)) % 360) + azimuth = np.degrees(np.arctan2(sign_z * x, sign_z * y)) % 360 azimuths.append(azimuth) # Create new GeoDataFrame - gdf_orient = gpd.GeoDataFrame(data=pd.DataFrame(list(zip(dips, azimuths, altitudes))), - geometry=orientations_locations, - crs=gdf.crs) + gdf_orient = gpd.GeoDataFrame( + data=pd.DataFrame(list(zip(dips, azimuths, altitudes))), + geometry=orientations_locations, + crs=gdf.crs, + ) # Renaming Columns - gdf_orient.columns = ['dip', 'azimuth', 'Z', 'geometry'] + gdf_orient.columns = ["dip", "azimuth", "Z", "geometry"] # Setting polarity value - gdf_orient['polarity'] = 1 + gdf_orient["polarity"] = 1 # Appending remaining data of original GeoDataFrame - gdf_orient = gdf_orient.join(other=gdf.drop(labels=['geometry', 'Z'], axis=1).drop(gdf.tail(1).index)) + gdf_orient = gdf_orient.join( + other=gdf.drop(labels=["geometry", "Z"], axis=1).drop(gdf.tail(1).index) + ) # Extracting x and y coordinates of midpoints representing the location of orientation values - gdf_orient = extract_xy(gdf=gdf_orient, - reset_index=True) + gdf_orient = extract_xy(gdf=gdf_orient, reset_index=True) return gdf_orient @@ -7155,8 +8311,8 @@ def calculate_orientations_from_strike_lines(gdf: gpd.geodataframe.GeoDataFrame) # Loading GPX Files ################### -def load_gpx(path: str, - layer: Union[int, str] = 'tracks') -> Collection: + +def load_gpx(path: str, layer: Union[int, str] = "tracks") -> Collection: """Loading a GPX file as collection Parameters @@ -7192,34 +8348,45 @@ def load_gpx(path: str, load_gpx_as_dict : Loading a GPX file as dict load_gpx_as_geometry : Loading a GPX file as Shapely BaseGeometry + .. versionadded:: 1.0.x + + .. versionchanged:: 1.2 + """ + # Trying to import fiona but returning error if fiona is not installed + try: + import fiona + except ModuleNotFoundError: + raise ModuleNotFoundError( + "fiona package is not installed. Use pip install fiona to install the latest version" + ) + # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('The path must be provided as string') + raise TypeError("The path must be provided as string") # Checking that the layer is of type int or string if not isinstance(layer, (int, str)): - raise TypeError('Layer must be provided as integer index or as string') + raise TypeError("Layer must be provided as integer index or as string") # Getting the absolute path path = os.path.abspath(path=path) if not os.path.exists(path): - raise LookupError('Invalid path provided') + raise LookupError("Invalid path provided") # Checking that the file has the correct file ending if not path.endswith(".gpx"): raise TypeError("The data must be provided as gpx file") # Opening the file - gpx = fiona.open(path, mode='r', layer=layer) + gpx = fiona.open(path, mode="r", layer=layer) return gpx -def load_gpx_as_dict(path: str, - layer: Union[int, str] = 'tracks') -> Collection: +def load_gpx_as_dict(path: str, layer: Union[int, str] = "tracks") -> Collection: """Loading a GPX file as dict Parameters @@ -7240,6 +8407,8 @@ def load_gpx_as_dict(path: str, .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -7273,28 +8442,40 @@ def load_gpx_as_dict(path: str, load_gpx_as : Loading a GPX file as Collection load_gpx_as_geometry : Loading a GPX file as Shapely BaseGeometry + .. versionadded:: 1.0.x + + .. versionchanged:: 1.2 + """ + # Trying to import fiona but returning error if fiona is not installed + try: + import fiona + except ModuleNotFoundError: + raise ModuleNotFoundError( + "fiona package is not installed. Use pip install fiona to install the latest version" + ) + # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('The path must be provided as string') + raise TypeError("The path must be provided as string") # Checking that the layer is of type int or string if not isinstance(layer, (int, str)): - raise TypeError('Layer must be provided as integer index or as string') + raise TypeError("Layer must be provided as integer index or as string") # Getting the absolute path path = os.path.abspath(path=path) if not os.path.exists(path): - raise LookupError('Invalid path provided') + raise LookupError("Invalid path provided") # Checking that the file has the correct file ending if not path.endswith(".gpx"): raise TypeError("The data must be provided as gpx file") # Opening the file - gpx = fiona.open(path, mode='r', layer=layer) + gpx = fiona.open(path, mode="r", layer=layer) # Extracting dict from Collection gpx_dict = gpx[0] @@ -7302,8 +8483,9 @@ def load_gpx_as_dict(path: str, return gpx_dict -def load_gpx_as_geometry(path: str, - layer: Union[int, str] = 'tracks') -> shapely.geometry.base.BaseGeometry: +def load_gpx_as_geometry( + path: str, layer: Union[int, str] = "tracks" +) -> shapely.geometry.base.BaseGeometry: """Loading a GPX file as Shapely Geometry Parameters @@ -7311,7 +8493,6 @@ def load_gpx_as_geometry(path: str, path : str Path to the GPX file, e.g. ``path='file.gpx'`` - layer : Union[int, str] The integer index or name of a layer in a multi-layer dataset, e.g. ``layer='tracks'``, default is ``tracks`` @@ -7324,6 +8505,8 @@ def load_gpx_as_geometry(path: str, .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -7340,35 +8523,47 @@ def load_gpx_as_geometry(path: str, load_gpx : Loading a GPX file as Collection load_gpx_as_dict : Loading a GPX file as dict + .. versionchanged:: 1.2 + """ + # Trying to import fiona but returning error if fiona is not installed + try: + import fiona + except ModuleNotFoundError: + raise ModuleNotFoundError( + "fiona package is not installed. Use pip install fiona to install the latest version" + ) + # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('The path must be provided as string') + raise TypeError("The path must be provided as string") # Checking that the layer is of type int or string if not isinstance(layer, (int, str)): - raise TypeError('Layer must be provided as integer index or as string') + raise TypeError("Layer must be provided as integer index or as string") # Getting the absolute path path = os.path.abspath(path=path) if not os.path.exists(path): - raise LookupError('Invalid path provided') + raise LookupError("Invalid path provided") # Checking that the file has the correct file ending if not path.endswith(".gpx"): raise TypeError("The data must be provided as gpx file") # Opening the file - gpx = fiona.open(path, mode='r', layer=layer) + gpx = fiona.open(path, mode="r", layer=layer) # Extracting dict from Collection gpx_dict = gpx[0] # Extracting Geometry Data - data = {'type': gpx_dict['geometry']['type'], - 'coordinates': gpx_dict['geometry']['coordinates']} + data = { + "type": gpx_dict["geometry"]["type"], + "coordinates": gpx_dict["geometry"]["coordinates"], + } # Creating BaseGeometry shape = shapely.geometry.shape(data) @@ -7376,12 +8571,102 @@ def load_gpx_as_geometry(path: str, return shape +def load_gpx_as_gdf(path: str, layer: Union[int, str] = "tracks") -> gpd.geodataframe.GeoDataFrame: + """Load GPX File as GeoPandas GeoDataFrame. + + Parameters + __________ + path : str + Path to the GPX file, e.g. ``path='file.gpx'``. + layer : Union[int, str], default: `'tracks'` + The integer index or name of a layer in a multi-layer dataset, e.g. ``layer='tracks'``, default is + ``tracks``. + + Returns + _______ + gdf : gpd.geodataframe.GeoDataFrame + GeoPandas GeoDataFrame containing the GPX Data + + +----+-----------------------------+--------+-------------------------------+ + | ID | geometry | ele | time | + +----+-----------------------------+--------+-------------------------------+ + | 0 | POINT (-71.11928 42.43888) | 44.59 | 2001-11-28 21:05:28+00:00 | + +----+-----------------------------+--------+-------------------------------+ + | 1 | POINT (-71.11969 42.43923) | 57.61 | 2001-06-02 03:26:55+00:00 | + +----+-----------------------------+--------+-------------------------------+ + | 2 | POINT (-71.11615 42.43892) | 44.83 | 2001-11-16 23:03:38+00:00 | + +----+-----------------------------+--------+-------------------------------+ + + + .. versionadded:: 1.2 + + Example + _______ + + >>> # Loading Libraries and File + >>> import gemgis as gg + >>> gpx = gg.vector.load_gpx_as_gdf(path='file.gpx', layer='tracks') + >>> gpx + +----+-----------------------------+--------+-------------------------------+ + | ID | geometry | ele | time | + +----+-----------------------------+--------+-------------------------------+ + | 0 | POINT (-71.11928 42.43888) | 44.59 | 2001-11-28 21:05:28+00:00 | + +----+-----------------------------+--------+-------------------------------+ + | 1 | POINT (-71.11969 42.43923) | 57.61 | 2001-06-02 03:26:55+00:00 | + +----+-----------------------------+--------+-------------------------------+ + | 2 | POINT (-71.11615 42.43892) | 44.83 | 2001-11-16 23:03:38+00:00 | + +----+-----------------------------+--------+-------------------------------+ + + See Also + ________ + + load_gpx : Load a GPX file as Collection + load_gpx_as_dict : Load a GPX file as dict + load_gpx_as_geometry : Load a GPX file as geometry + + """ + # Trying to import pyogrio but returning error if pyogrio is not installed + try: + import pyogrio + except ModuleNotFoundError: + raise ModuleNotFoundError( + "pyogrio package is not installed. Use pip install pyogrio to install the latest version" + ) + + # Checking that the path is of type string + if not isinstance(path, str): + raise TypeError("The path must be provided as string") + + # Checking that the layer is of type int or string + if not isinstance(layer, (int, str)): + raise TypeError("Layer must be provided as integer index or as string") + + # Getting the absolute path + path = os.path.abspath(path=path) + + if not os.path.exists(path): + raise LookupError("Invalid path provided") + + # Checking that the file has the correct file ending + if not path.endswith(".gpx"): + raise TypeError("The data must be provided as gpx file") + + # Opening GPX File + gdf = pyogrio.read_datamframe(path_or_buffer=path, + layer=layer) + + return gdf + + # Miscellaneous Functions ######################### -def sort_by_stratigraphy(gdf: gpd.geodataframe.GeoDataFrame, - stratigraphy: List[str], - formation_column: str = 'formation') -> gpd.geodataframe.GeoDataFrame: + +def sort_by_stratigraphy( + gdf: gpd.geodataframe.GeoDataFrame, + stratigraphy: List[str], + formation_column: str = "formation", +) -> gpd.geodataframe.GeoDataFrame: """Sorting a GeoDataFrame by a provided list of Stratigraphic Units Parameters @@ -7442,33 +8727,37 @@ def sort_by_stratigraphy(gdf: gpd.geodataframe.GeoDataFrame, # Checking that the input data is provided as GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Input Geometries must be stored as GeoDataFrame') + raise TypeError("Input Geometries must be stored as GeoDataFrame") # Checking that all GeoDataFrame entries are of type polygon - if not all(gdf.geom_type == 'Polygon'): - raise TypeError('All GeoDataFrame entries must be of geom_type polygon') + if not all(gdf.geom_type == "Polygon"): + raise TypeError("All GeoDataFrame entries must be of geom_type polygon") # Checking that all geometry objects are valid if not all(n.is_valid for n in gdf.geometry.tolist()): - raise ValueError('Not all geometry objects are valid') + raise ValueError("Not all geometry objects are valid") # Checking that no geometry object is empty if any(n.is_empty for n in gdf.geometry.tolist()): - raise ValueError('One or more geometry objects are empty') + raise ValueError("One or more geometry objects are empty") if not isinstance(formation_column, str): - raise TypeError('Formation column name must be of type string') + raise TypeError("Formation column name must be of type string") # Checking that the formation column is in the GeoDataFrame if formation_column not in gdf: - raise ValueError('Formation_column not present in gdf') + raise ValueError("Formation_column not present in gdf") - gdf['formation_cat'] = pd.Categorical(values=gdf[formation_column], - categories=stratigraphy, - ordered=True) + gdf["formation_cat"] = pd.Categorical( + values=gdf[formation_column], categories=stratigraphy, ordered=True + ) - gdf = gdf[gdf['formation_cat'].notna()] - gdf_sorted = gdf.sort_values(by='formation_cat').reset_index(drop=True).drop('formation_cat', axis=1) + gdf = gdf[gdf["formation_cat"].notna()] + gdf_sorted = ( + gdf.sort_values(by="formation_cat") + .reset_index(drop=True) + .drop("formation_cat", axis=1) + ) return gdf_sorted @@ -7508,25 +8797,27 @@ def create_bbox(extent: List[Union[int, float]]) -> shapely.geometry.polygon.Pol # Checking if extent is a list if not isinstance(extent, list): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking that all values are either ints or floats if not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Bounds values must be of type int or float') + raise TypeError("Bounds values must be of type int or float") bbox = geometry.box(extent[0], extent[2], extent[1], extent[3]) return bbox -def set_dtype(gdf: gpd.geodataframe.GeoDataFrame, - dip: str = 'dip', - azimuth: str = 'azimuth', - formation: str = 'formation', - polarity: str = 'polarity', - x: str = 'X', - y: str = 'Y', - z: str = 'Z') -> gpd.geodataframe.GeoDataFrame: +def set_dtype( + gdf: gpd.geodataframe.GeoDataFrame, + dip: str = "dip", + azimuth: str = "azimuth", + formation: str = "formation", + polarity: str = "polarity", + x: str = "X", + y: str = "Y", + z: str = "Z", +) -> gpd.geodataframe.GeoDataFrame: """Checking and setting the dtypes of the input data GeoDataFrame Parameters @@ -7579,31 +8870,39 @@ def set_dtype(gdf: gpd.geodataframe.GeoDataFrame, # Input object must be a GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Checking that all elements of the input data is of type point if not all(gdf.geom_type == "Point"): - raise TypeError('Geometry type of input data must be og geom_type Points, please convert data beforehand') + raise TypeError( + "Geometry type of input data must be og geom_type Points, please convert data beforehand" + ) # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that the dip, azimuth and polarity column names are provided as string - if not isinstance(dip, str) and not isinstance(azimuth, str) and not isinstance(polarity, str): - raise TypeError('Dip, azimuth and polarity column names must be provided as string') + if ( + not isinstance(dip, str) + and not isinstance(azimuth, str) + and not isinstance(polarity, str) + ): + raise TypeError( + "Dip, azimuth and polarity column names must be provided as string" + ) # Checking that the formation column name is provided as string if not isinstance(formation, str): - raise TypeError('Formation column name must be provided as string') + raise TypeError("Formation column name must be provided as string") # Checking that the X, Y, Z column names are provided as string if not isinstance(x, str) and not isinstance(y, str) and not isinstance(z, str): - raise TypeError('X, Y, Z column names must be provided as string') + raise TypeError("X, Y, Z column names must be provided as string") # Converting dip column to floats if dip in gdf and gdf[dip].dtype != float: @@ -7636,10 +8935,11 @@ def set_dtype(gdf: gpd.geodataframe.GeoDataFrame, return gdf -def create_polygons_from_faces(mesh: pv.core.pointset.PolyData, - crs: Union[str, pyproj.crs.crs.CRS], - return_gdf: bool = True, - ) -> Union[List[shapely.geometry.polygon.Polygon], gpd.geodataframe.GeoDataFrame]: +def create_polygons_from_faces( + mesh: pv.core.pointset.PolyData, + crs: Union[str, pyproj.crs.crs.CRS], + return_gdf: bool = True, +) -> Union[List[shapely.geometry.polygon.Polygon], gpd.geodataframe.GeoDataFrame]: """Extracting faces from PyVista PolyData as Shapely Polygons Parameters @@ -7698,15 +8998,15 @@ def create_polygons_from_faces(mesh: pv.core.pointset.PolyData, # Checking that the input mesh is a PyVista PolyData dataset if not isinstance(mesh, pv.core.pointset.PolyData): - raise TypeError('Input mesh must be a PyVista PolyData dataset') + raise TypeError("Input mesh must be a PyVista PolyData dataset") # Checking that the crs is of type string or a pyproj object if not isinstance(crs, (str, type(None), pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Checking that return gdfs is of type bool if not isinstance(return_gdf, bool): - raise TypeError('Return_gdf argument must be of type bool') + raise TypeError("Return_gdf argument must be of type bool") # Reshaping the faces array and selecting index values faces_indices = mesh.faces.reshape(mesh.n_faces, 4)[:, 1:] @@ -7724,10 +9024,13 @@ def create_polygons_from_faces(mesh: pv.core.pointset.PolyData, return polygons -def unify_polygons(polygons: Union[List[shapely.geometry.polygon.Polygon], gpd.geodataframe.GeoDataFrame], - crs: Union[str, pyproj.crs.crs.CRS] = None, - return_gdf: bool = True, - ) -> Union[List[shapely.geometry.polygon.Polygon], gpd.geodataframe.GeoDataFrame]: +def unify_polygons( + polygons: Union[ + List[shapely.geometry.polygon.Polygon], gpd.geodataframe.GeoDataFrame + ], + crs: Union[str, pyproj.crs.crs.CRS] = None, + return_gdf: bool = True, +) -> Union[List[shapely.geometry.polygon.Polygon], gpd.geodataframe.GeoDataFrame]: """Unifying adjacent triangular polygons to form larger objects Parameters @@ -7780,36 +9083,38 @@ def unify_polygons(polygons: Union[List[shapely.geometry.polygon.Polygon], gpd.g # Checking that the polygons are of type list of a GeoDataFrame if not isinstance(polygons, (list, gpd.geodataframe.GeoDataFrame)): - raise TypeError('Polygons must be provided as list of Shapely Polygons or as GeoDataFrame') + raise TypeError( + "Polygons must be provided as list of Shapely Polygons or as GeoDataFrame" + ) # Checking GeoDataFrame if isinstance(polygons, gpd.geodataframe.GeoDataFrame): # Check that all entries of the gdf are of type Polygon - if not all(polygons.geom_type == 'Polygon'): - raise TypeError('All GeoDataFrame entries must be of geom_type Polygon') + if not all(polygons.geom_type == "Polygon"): + raise TypeError("All GeoDataFrame entries must be of geom_type Polygon") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(polygons.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(polygons.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Storing CRS crs = polygons.crs # Creating list of geometries - polygons = polygons['geometry'].tolist() + polygons = polygons["geometry"].tolist() # Checking that the crs is of type string or a pyproj object if not isinstance(crs, (str, type(None), pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Checking that return gdfs is of type bool if not isinstance(return_gdf, bool): - raise TypeError('Return_gdf argument must be of type bool') + raise TypeError("Return_gdf argument must be of type bool") # Creating MultiPolygon from Polygons multi_polygons = geometry.MultiPolygon(polygons) @@ -7822,16 +9127,18 @@ def unify_polygons(polygons: Union[List[shapely.geometry.polygon.Polygon], gpd.g # Creating GeoDataFrame if return_gdf: - polygons_merged = gpd.GeoDataFrame(geometry=polygons_merged, - crs=crs) + polygons_merged = gpd.GeoDataFrame(geometry=polygons_merged, crs=crs) return polygons_merged -def unify_linestrings(linestrings: Union[List[shapely.geometry.linestring.LineString], gpd.geodataframe.GeoDataFrame], - crs: Union[str, pyproj.crs.crs.CRS] = None, - return_gdf: bool = True - ) -> Union[List[shapely.geometry.linestring.LineString], gpd.geodataframe.GeoDataFrame]: +def unify_linestrings( + linestrings: Union[ + List[shapely.geometry.linestring.LineString], gpd.geodataframe.GeoDataFrame + ], + crs: Union[str, pyproj.crs.crs.CRS] = None, + return_gdf: bool = True, +) -> Union[List[shapely.geometry.linestring.LineString], gpd.geodataframe.GeoDataFrame]: """Unifying adjacent LineStrings to form LineStrings with multiple vertices Parameters @@ -7884,36 +9191,38 @@ def unify_linestrings(linestrings: Union[List[shapely.geometry.linestring.LineSt # Checking that the linestrings are of type list of a GeoDataFrame if not isinstance(linestrings, (list, gpd.geodataframe.GeoDataFrame)): - raise TypeError('Polygons must be provided as list of Shapely Polygons or as GeoDataFrame') + raise TypeError( + "Polygons must be provided as list of Shapely Polygons or as GeoDataFrame" + ) # Checking GeoDataFrame if isinstance(linestrings, gpd.geodataframe.GeoDataFrame): # Check that all entries of the gdf are of type LineString - if not all(linestrings.geom_type == 'LineString'): - raise TypeError('All GeoDataFrame entries must be of geom_type LineString') + if not all(linestrings.geom_type == "LineString"): + raise TypeError("All GeoDataFrame entries must be of geom_type LineString") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(linestrings.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(linestrings.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Storing CRS crs = linestrings.crs # Creating list of geometries - linestrings = linestrings['geometry'].tolist() + linestrings = linestrings["geometry"].tolist() # Checking that the crs is of type string or a pyproj object if not isinstance(crs, (str, type(None), pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Checking that return gdfs is of type bool if not isinstance(return_gdf, bool): - raise TypeError('Return_gdf argument must be of type bool') + raise TypeError("Return_gdf argument must be of type bool") # Unifying LineStrings unified_linestrings = ops.linemerge(lines=linestrings) @@ -7923,18 +9232,18 @@ def unify_linestrings(linestrings: Union[List[shapely.geometry.linestring.LineSt # Creating GeoDataFrame if return_gdf: - linestrings_merged = gpd.GeoDataFrame(geometry=linestrings_merged, - crs=crs) + linestrings_merged = gpd.GeoDataFrame(geometry=linestrings_merged, crs=crs) # Adding Z values as column - linestrings_merged['Z'] = [list(linestrings_merged.loc[i].geometry.coords)[0][2] for i in - range(len(linestrings_merged))] + linestrings_merged["Z"] = [ + list(linestrings_merged.loc[i].geometry.coords)[0][2] + for i in range(len(linestrings_merged)) + ] return linestrings_merged -def create_hexagon(center: shapely.geometry.Point, - radius: Union[int, float]): +def create_hexagon(center: shapely.geometry.Point, radius: Union[int, float]): """Function to create one hexagon Parameters @@ -7966,17 +9275,14 @@ def create_hexagon(center: shapely.geometry.Point, # Checking that the center point is provided as Shapely Point if not isinstance(center, geometry.Point): - raise TypeError('Center point of the hexagon must be provided as Shapely Point') + raise TypeError("Center point of the hexagon must be provided as Shapely Point") # Checking that the radius is of type int or float if not isinstance(radius, (int, float)): - raise TypeError('Radius of the hexagon must be provided as int or float') + raise TypeError("Radius of the hexagon must be provided as int or float") # Setting the hexagon angles - angles = np.linspace(start=0, - stop=2*np.pi, - num=6, - endpoint=False) + angles = np.linspace(start=0, stop=2 * np.pi, num=6, endpoint=False) # Calculating the coordinates of the hexagon's vertices x_coords = center.x + radius * np.cos(angles) @@ -7986,9 +9292,9 @@ def create_hexagon(center: shapely.geometry.Point, return geometry.Polygon(np.c_[x_coords, y_coords]) -def create_hexagon_grid(gdf: gpd.GeoDataFrame, - radius: Union[int, float], - crop_gdf: bool = True): +def create_hexagon_grid( + gdf: gpd.GeoDataFrame, radius: Union[int, float], crop_gdf: bool = True +): """Function to create a grid of hexagons based on a GeoDataFrame containing Polygons and a radius provided for the single hexagons Parameters @@ -8024,24 +9330,31 @@ def create_hexagon_grid(gdf: gpd.GeoDataFrame, # Checking that the gdf is of type GeoDataFrame if not isinstance(gdf, gpd.GeoDataFrame): - raise TypeError('gdf must be of type GeoDataFrame') + raise TypeError("gdf must be of type GeoDataFrame") # Checking - if not all(gdf.geom_type == 'Polygon'): - raise TypeError('All geometries in the gdf must be of geom_type Polygon') + if not all(gdf.geom_type == "Polygon"): + raise TypeError("All geometries in the gdf must be of geom_type Polygon") # Checking that the radius is of type int or float if not isinstance(radius, (int, float)): - raise TypeError('radius must be of type int or float') + raise TypeError("radius must be of type int or float") # Checking that crop_gdf is of type bool if not isinstance(crop_gdf, bool): - raise TypeError('crop_gdf must be either set to True or False') + raise TypeError("crop_gdf must be either set to True or False") # Calculating the number of rows and columns of the hexagon grid - columns = int(np.ceil((gdf.total_bounds[2] - gdf.total_bounds[0]) / (1.5 * radius)) + 1) + columns = int( + np.ceil((gdf.total_bounds[2] - gdf.total_bounds[0]) / (1.5 * radius)) + 1 + ) - rows = int(np.ceil((gdf.total_bounds[3] - gdf.total_bounds[1]) / (2 * np.sqrt(3) / 2 * radius)) + 1) + rows = int( + np.ceil( + (gdf.total_bounds[3] - gdf.total_bounds[1]) / (2 * np.sqrt(3) / 2 * radius) + ) + + 1 + ) # Creating emtpy lists to store the x and y coordinates of the centers of the hexagons x_coords = [] @@ -8055,33 +9368,35 @@ def create_hexagon_grid(gdf: gpd.GeoDataFrame, y_coord = gdf.total_bounds[3] - 2 * j * radius * (np.sqrt(3) / 2) else: x_coord = gdf.total_bounds[0] + i * radius * 1.5 - y_coord = gdf.total_bounds[3] - 2 * j * radius * (np.sqrt(3) / 2) - (np.sqrt(3) / 2) * radius + y_coord = ( + gdf.total_bounds[3] + - 2 * j * radius * (np.sqrt(3) / 2) + - (np.sqrt(3) / 2) * radius + ) # Appending coordinates to lists x_coords.append(x_coord) y_coords.append(y_coord) # Creating a list of Shapely Points representing the centers of the Hexagons - list_points = [geometry.Point(x, - y) for x, y in zip(x_coords, - y_coords)] + list_points = [geometry.Point(x, y) for x, y in zip(x_coords, y_coords)] # Creating the hexagon grid from the list of center points - list_hexagon = [create_hexagon(point, - radius) for point in list_points] + list_hexagon = [create_hexagon(point, radius) for point in list_points] # Creating GeoDataFrame from list of hexagons - hex_gdf = gpd.GeoDataFrame(geometry=list_hexagon, - crs=gdf.crs) + hex_gdf = gpd.GeoDataFrame(geometry=list_hexagon, crs=gdf.crs) # Cropping the GeoDataFrame to the outline if crop_gdf: - hex_gdf = hex_gdf.sjoin(gdf).reset_index()[['geometry']] + hex_gdf = hex_gdf.sjoin(gdf).reset_index()[["geometry"]] return hex_gdf -def create_voronoi_polygons(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodataframe.GeoDataFrame: +def create_voronoi_polygons( + gdf: gpd.geodataframe.GeoDataFrame, +) -> gpd.geodataframe.GeoDataFrame: """Function to create Voronoi Polygons from Point GeoDataFrame using the SciPy Spatial Voronoi class (https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.Voronoi.html#scipy.spatial.Voronoi) @@ -8113,43 +9428,43 @@ def create_voronoi_polygons(gdf: gpd.geodataframe.GeoDataFrame) -> gpd.geodatafr # Checking that the gdf is of type GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('gdf must be provided as GeoDataFrame') + raise TypeError("gdf must be provided as GeoDataFrame") # Checking that all geometry objects of the GeoDataFrame are of type Point if not all(shapely.get_type_id(gdf.geometry) == 0): - raise TypeError('All GeoDataFrame entries must be of geom_type Point') + raise TypeError("All GeoDataFrame entries must be of geom_type Point") # Trying to import scipy but returning error if scipy is not installed try: from scipy.spatial import Voronoi except ModuleNotFoundError: raise ModuleNotFoundError( - 'SciPy package is not installed. Use pip install scipy to install the latest version') + "SciPy package is not installed. Use pip install scipy to install the latest version" + ) # Checking if X and Y coordinates are in GeoDataFrame - if not {'X', 'Y'}.issubset(gdf.columns): + if not {"X", "Y"}.issubset(gdf.columns): gdf = extract_xy(gdf) # Getting Points from GeoDataFrame - points = gdf[['X', 'Y']].values + points = gdf[["X", "Y"]].values # Creating Voronoi vertices and regions vor = Voronoi(points) # Filtering invalid Voronoi regions - regions = [region for region in vor.regions if not -1 in region] + regions = [region for region in vor.regions if -1 not in region] # Creating Polygons from Voronoi regions polygons = [geometry.Polygon(vor.vertices[regions[i]]) for i in range(len(regions))] # Creating GeoDataFrame - gdf_polygons = gpd.GeoDataFrame(geometry=polygons, - crs=gdf.crs) + gdf_polygons = gpd.GeoDataFrame(geometry=polygons, crs=gdf.crs) # Removing empty Polygons gdf_polygons = gdf_polygons[~gdf_polygons.is_empty] # Calculating and appending area to GeoDataFrame - gdf_polygons['area'] = gdf_polygons.area + gdf_polygons["area"] = gdf_polygons.area return gdf_polygons diff --git a/gemgis/visualization.py b/gemgis/visualization.py index a95719ab..badd13bf 100644 --- a/gemgis/visualization.py +++ b/gemgis/visualization.py @@ -45,7 +45,9 @@ ############################################################## -def create_lines_3d_polydata(gdf: gpd.geodataframe.GeoDataFrame) -> pv.core.pointset.PolyData: +def create_lines_3d_polydata( + gdf: gpd.geodataframe.GeoDataFrame, +) -> pv.core.pointset.PolyData: """Creating lines with z-component for the plotting with PyVista Parameters @@ -98,19 +100,24 @@ def create_lines_3d_polydata(gdf: gpd.geodataframe.GeoDataFrame) -> pv.core.poin # Checking that the contour lines are a GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Line Object must be of type GeoDataFrame') + raise TypeError("Line Object must be of type GeoDataFrame") # Checking that all elements of the GeoDataFrame are of geom_type LineString if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All Shapely objects of the GeoDataFrame must be LineStrings') + raise TypeError("All Shapely objects of the GeoDataFrame must be LineStrings") # Creating list of points vertices_list = [list(gdf.geometry[i].coords) for i in range(len(gdf))] # Extracting Z values of all points if gdf has no Z but Z value is provided for each LineString in an additional column - if (all(gdf.has_z == False)) and ('Z' in gdf.columns): - vertices_list_z = [[vertices_list[j][i] + tuple([gdf['Z'].loc[j]]) for i in range(len(vertices_list[j]))] for j - in range(len(vertices_list))] + if (not all(gdf.has_z)) and ("Z" in gdf.columns): + vertices_list_z = [ + [ + vertices_list[j][i] + tuple([gdf["Z"].loc[j]]) + for i in range(len(vertices_list[j])) + ] + for j in range(len(vertices_list)) + ] vertices_list = vertices_list_z # Creating array of points @@ -131,9 +138,11 @@ def create_lines_3d_polydata(gdf: gpd.geodataframe.GeoDataFrame) -> pv.core.poin return poly -def create_lines_3d_linestrings(gdf: gpd.geodataframe.GeoDataFrame, - dem: Union[rasterio.io.DatasetReader, np.ndarray], - extent: List[Union[int, float]] = None) -> gpd.geodataframe.GeoDataFrame: +def create_lines_3d_linestrings( + gdf: gpd.geodataframe.GeoDataFrame, + dem: Union[rasterio.io.DatasetReader, np.ndarray], + extent: List[Union[int, float]] = None, +) -> gpd.geodataframe.GeoDataFrame: """Creating lines with z-component (LineString Z) Parameters @@ -194,43 +203,45 @@ def create_lines_3d_linestrings(gdf: gpd.geodataframe.GeoDataFrame, # Checking that gdf is of type GepDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Loaded object is not a GeoDataFrame') + raise TypeError("Loaded object is not a GeoDataFrame") # Check that all entries of the gdf are of type Point if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All GeoDataFrame entries must be of geom_type LineString') + raise TypeError("All GeoDataFrame entries must be of geom_type LineString") # Checking that the dem is a np.ndarray or rasterio object if not isinstance(dem, (np.ndarray, rasterio.io.DatasetReader)): - raise TypeError('DEM must be a numpy.ndarray or rasterio object') + raise TypeError("DEM must be a numpy.ndarray or rasterio object") # Checking that the extent is of type list if isinstance(dem, np.ndarray) and not isinstance(extent, list): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Add index to line for later merging again - gdf['index_lines'] = gdf.index + gdf["index_lines"] = gdf.index # Extracting X,Y,Z coordinates from LineStrings - gdf_xyz = extract_xyz(gdf=gdf, - dem=dem, - extent=extent) + gdf_xyz = extract_xyz(gdf=gdf, dem=dem, extent=extent) # Creating list of LineStrings with Z component - list_linestrings = [LineString(gdf_xyz[gdf_xyz['index_lines'] == i][['X', 'Y', 'Z']].values) for i in - gdf_xyz['index_lines'].unique()] + list_linestrings = [ + LineString(gdf_xyz[gdf_xyz["index_lines"] == i][["X", "Y", "Z"]].values) + for i in gdf_xyz["index_lines"].unique() + ] # Creating GeoDataFrame with LineStrings - gdf_3d = gpd.GeoDataFrame(geometry=list_linestrings, - data=gdf, - crs=gdf.crs).drop('index_lines', axis=1) + gdf_3d = gpd.GeoDataFrame(geometry=list_linestrings, data=gdf, crs=gdf.crs).drop( + "index_lines", axis=1 + ) return gdf_3d -def create_dem_3d(dem: Union[rasterio.io.DatasetReader, np.ndarray], - extent: List[Union[int, float]] = None, - res: int = 1) -> pv.core.pointset.StructuredGrid: +def create_dem_3d( + dem: Union[rasterio.io.DatasetReader, np.ndarray], + extent: List[Union[int, float]] = None, + res: int = 1, +) -> pv.core.pointset.StructuredGrid: """Plotting the dem in 3D with PyVista Parameters @@ -290,11 +301,11 @@ def create_dem_3d(dem: Union[rasterio.io.DatasetReader, np.ndarray], # Checking if dem is a rasterio object or NumPy array if not isinstance(dem, (rasterio.io.DatasetReader, np.ndarray)): - raise TypeError('DEM must be a rasterio object') + raise TypeError("DEM must be a rasterio object") # Checking if the extent is of type list if not isinstance(extent, (list, type(None))): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Converting rasterio object to array if isinstance(dem, rasterio.io.DatasetReader): @@ -307,11 +318,11 @@ def create_dem_3d(dem: Union[rasterio.io.DatasetReader, np.ndarray], # Checking if the extent is of type list if not isinstance(extent, list): - raise TypeError('Extent must be of type list') + raise TypeError("Extent must be of type list") # Checking that all values are either ints or floats if not all(isinstance(n, (int, float)) for n in extent): - raise TypeError('Bound values must be of type int or float') + raise TypeError("Bound values must be of type int or float") # Creating arrays for meshgrid creation x = np.arange(extent[0], extent[1], res) @@ -382,18 +393,18 @@ def create_points_3d(gdf: gpd.geodataframe.GeoDataFrame) -> pv.core.pointset.Pol # Checking if points is of type GeoDataFrame if not isinstance(gdf, (gpd.geodataframe.GeoDataFrame, pd.DataFrame)): - raise TypeError('Points must be of type GeoDataFrame or DataFrame') + raise TypeError("Points must be of type GeoDataFrame or DataFrame") # Checking if all necessary columns are in the GeoDataFrame - if not {'X', 'Y', 'Z'}.issubset(gdf.columns): - raise ValueError('Points are missing columns, XYZ needed') + if not {"X", "Y", "Z"}.issubset(gdf.columns): + raise ValueError("Points are missing columns, XYZ needed") # Checking that all elements of the GeoDataFrame are of geom_type Point if not all(shapely.get_type_id(gdf.geometry) == 0): - raise TypeError('All Shapely objects of the GeoDataFrame must be Points') + raise TypeError("All Shapely objects of the GeoDataFrame must be Points") # Creating PyVista PolyData - points_mesh = pv.PolyData(gdf[['X', 'Y', 'Z']].to_numpy()) + points_mesh = pv.PolyData(gdf[["X", "Y", "Z"]].to_numpy()) return points_mesh @@ -402,9 +413,11 @@ def create_points_3d(gdf: gpd.geodataframe.GeoDataFrame) -> pv.core.pointset.Pol ################################## -def create_mesh_from_cross_section(linestring: shapely.geometry.linestring.LineString, - zmax: Union[float, int], - zmin: Union[float, int]) -> pv.core.pointset.PolyData: +def create_mesh_from_cross_section( + linestring: shapely.geometry.linestring.LineString, + zmax: Union[float, int], + zmin: Union[float, int], +) -> pv.core.pointset.PolyData: """Creating a PyVista Mesh from one cross section Parameters @@ -462,15 +475,15 @@ def create_mesh_from_cross_section(linestring: shapely.geometry.linestring.LineS # Checking that the LineString is a Shapely LineString if not isinstance(linestring, shapely.geometry.linestring.LineString): - raise TypeError('Profile Trace must be provided as Shapely LineString') + raise TypeError("Profile Trace must be provided as Shapely LineString") # Checking that zmax is an int or float if not isinstance(zmax, (int, float, np.int64)): - raise TypeError('Maximum vertical extent zmax must be provided as int or float') + raise TypeError("Maximum vertical extent zmax must be provided as int or float") # Checking that zmax is an int or float if not isinstance(zmin, (int, float, np.int64)): - raise TypeError('Minimum vertical extent zmax must be provided as int or float') + raise TypeError("Minimum vertical extent zmax must be provided as int or float") # Getting the number of vertices of the LineString n = len(list(linestring.coords)) @@ -492,14 +505,16 @@ def create_mesh_from_cross_section(linestring: shapely.geometry.linestring.LineS # i --- i+1 faces = np.array( - [[3, i, i + 1, i + n] for i in range(n - 1)] + [[3, i + n + 1, i + n, i + 1] for i in range(n - 1)]) + [[3, i, i + 1, i + n] for i in range(n - 1)] + + [[3, i + n + 1, i + n, i + 1] for i in range(n - 1)] + ) # L should be the normalized to 1 cumulative sum of the segment lengths data = np.linalg.norm(coords[1:] - coords[:-1], axis=1).cumsum() data /= data[-1] uv = np.zeros((2 * n, 2)) uv[1:n, 0] = data - uv[n + 1:, 0] = data + uv[n + 1 :, 0] = data uv[:, 1] = np.repeat([0, 1], n) # Creating PyVista PolyData @@ -513,12 +528,16 @@ def create_mesh_from_cross_section(linestring: shapely.geometry.linestring.LineS else: surface.active_texture_coordinates = uv except AttributeError: - raise ImportError("Please make sure you are using a compatible version of PyVista") + raise ImportError( + "Please make sure you are using a compatible version of PyVista" + ) return surface -def create_meshes_from_cross_sections(gdf: gpd.geodataframe.GeoDataFrame) -> List[pv.core.pointset.PolyData]: +def create_meshes_from_cross_sections( + gdf: gpd.geodataframe.GeoDataFrame, +) -> List[pv.core.pointset.PolyData]: """Creating PyVista Meshes from multiple cross section Parameters @@ -574,24 +593,29 @@ def create_meshes_from_cross_sections(gdf: gpd.geodataframe.GeoDataFrame) -> Lis # Checking that the data is provided as GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Data must be provided as GeoDataFrame') + raise TypeError("Data must be provided as GeoDataFrame") # Checking that all elements of the GeoDataFrame are Shapely LineStrings if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All elements must be of type LineString') + raise TypeError("All elements must be of type LineString") # Checking that zmax is in the gdf - if 'zmax' not in gdf: - raise ValueError('zmax is not in the gdf') + if "zmax" not in gdf: + raise ValueError("zmax is not in the gdf") # Checking that zmin is in the gdf - if 'zmin' not in gdf: - raise ValueError('zmin is not in the gdf') + if "zmin" not in gdf: + raise ValueError("zmin is not in the gdf") # Creating the meshes - meshes = [create_mesh_from_cross_section(linestring=gdf.loc[i].geometry, - zmax=gdf.loc[i]['zmax'], - zmin=gdf.loc[i]['zmin']) for i in range(len(gdf))] + meshes = [ + create_mesh_from_cross_section( + linestring=gdf.loc[i].geometry, + zmax=gdf.loc[i]["zmax"], + zmin=gdf.loc[i]["zmin"], + ) + for i in range(len(gdf)) + ] return meshes @@ -600,9 +624,9 @@ def create_meshes_from_cross_sections(gdf: gpd.geodataframe.GeoDataFrame) -> Lis ###################################################### -def read_raster(path=str, - nodata_val: Union[float, int] = None, - name: str = 'Elevation [m]') -> pv.core.pointset.PolyData: +def read_raster( + path=str, nodata_val: Union[float, int] = None, name: str = "Elevation [m]" +) -> pv.core.pointset.PolyData: """Reading a raster and returning a mesh Parameters @@ -661,11 +685,12 @@ def read_raster(path=str, import rioxarray as rxr except ModuleNotFoundError: raise ModuleNotFoundError( - 'rioxarray package is not installed. Use pip install rioxarray to install the latest version') + "rioxarray package is not installed. Use pip install rioxarray to install the latest version" + ) # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Getting the absolute path path = os.path.abspath(path=path) @@ -676,15 +701,15 @@ def read_raster(path=str, # Checking that the file exists if not os.path.exists(path): - raise FileNotFoundError('File not found') + raise FileNotFoundError("File not found") # Checking that the nodata value is of type float or int if not isinstance(nodata_val, (float, int, type(None))): - raise TypeError('Nodata_val must be of type float or int') + raise TypeError("Nodata_val must be of type float or int") # Checking that the name of the array is provided as string if not isinstance(name, str): - raise TypeError('The name of the data array must be provided as string') + raise TypeError("The name of the data array must be provided as string") # Reading in the data data = rxr.open_rasterio(path) @@ -711,7 +736,7 @@ def read_raster(path=str, values[nans] = np.nan # Creating meshgrid - xx, yy = np.meshgrid(data['x'], data['y']) + xx, yy = np.meshgrid(data["x"], data["y"]) # Setting zz values zz = np.zeros_like(xx) @@ -720,7 +745,7 @@ def read_raster(path=str, mesh = pv.StructuredGrid(xx, yy, zz) # Assign Elevation Values - mesh[name] = values.ravel(order='F') + mesh[name] = values.ravel(order="F") return mesh @@ -793,19 +818,23 @@ def convert_to_rgb(array: np.ndarray) -> np.ndarray: # Checking that the array is a NumPy nd.array if not isinstance(array, np.ndarray): - raise TypeError('Input data must be of type NumPy nd.array') + raise TypeError("Input data must be of type NumPy nd.array") # Converting the array values to RGB values - array_stacked = (np.dstack((array[:, :, 0], array[:, :, 1], array[:, :, 2])) * 255.999).astype(np.uint8) + array_stacked = ( + np.dstack((array[:, :, 0], array[:, :, 1], array[:, :, 2])) * 255.999 + ).astype(np.uint8) return array_stacked -def drape_array_over_dem(array: np.ndarray, - dem: Union[rasterio.io.DatasetReader, np.ndarray], - extent: List[Union[float, int]] = None, - zmax: Union[float, int] = 10000, - resize_array: bool =True): +def drape_array_over_dem( + array: np.ndarray, + dem: Union[rasterio.io.DatasetReader, np.ndarray], + extent: List[Union[float, int]] = None, + zmax: Union[float, int] = 10000, + resize_array: bool = True, +): """Creating grid and texture to drape array over a digital elevation model Parameters @@ -907,19 +936,25 @@ def drape_array_over_dem(array: np.ndarray, # Checking that the map data is of type np.ndarray if not isinstance(array, np.ndarray): - raise TypeError('Map data must be provided as NumPy array') + raise TypeError("Map data must be provided as NumPy array") # Checking that the digital elevation model is a rasterio object or a NumPy array if not isinstance(dem, (rasterio.io.DatasetReader, np.ndarray)): - raise TypeError('The digital elevation model must be provided as rasterio object oder NumPy array') + raise TypeError( + "The digital elevation model must be provided as rasterio object oder NumPy array" + ) # Checking that the extent is of type list if the digital elevation model is provided as array if isinstance(dem, np.ndarray) and not isinstance(extent, list): - raise TypeError('The extent must be provided as list if the digital elevation model is a NumPy array') + raise TypeError( + "The extent must be provided as list if the digital elevation model is a NumPy array" + ) # Checking that all elements of the extent are of type float or int if the digital elevation model is an array - if isinstance(dem, np.ndarray) and not all(isinstance(n, (float, int)) for n in extent): - raise TypeError('All elements of the extent must be of type float or int') + if isinstance(dem, np.ndarray) and not all( + isinstance(n, (float, int)) for n in extent + ): + raise TypeError("All elements of the extent must be of type float or int") # Resizing array or DEM if the shapes do not match if dem.shape != array.shape: @@ -928,18 +963,21 @@ def drape_array_over_dem(array: np.ndarray, from skimage.transform import resize except ModuleNotFoundError: raise ModuleNotFoundError( - 'Scikit Image package is not installed. Use pip install scikit-image to install the latest version') + "Scikit Image package is not installed. Use pip install scikit-image to install the latest version" + ) if resize_array: - array = resize(image=array, - preserve_range=True, - output_shape=(dem.shape[0], - dem.shape[1])) + array = resize( + image=array, + preserve_range=True, + output_shape=(dem.shape[0], dem.shape[1]), + ) else: - dem = resize(image=dem, - preserve_range=True, - output_shape=(array.shape[0], - array.shape[1])) + dem = resize( + image=dem, + preserve_range=True, + output_shape=(array.shape[0], array.shape[1]), + ) # Creating Meshgrid from input data x = np.linspace(dem.bounds[0], dem.bounds[2], array.shape[1]) @@ -1026,31 +1064,34 @@ def create_polydata_from_msh(data: Dict[str, np.ndarray]) -> pv.core.pointset.Po # Checking that the data is a dict if not isinstance(data, dict): - raise TypeError('Data must be provided as dict') + raise TypeError("Data must be provided as dict") # Checking that the faces and vertices are in the dictionary - if 'Tri' not in data: - raise ValueError('Triangles are not in data. Check your input') - if 'Location' not in data: - raise ValueError('Vertices are not in data. Check your input') + if "Tri" not in data: + raise ValueError("Triangles are not in data. Check your input") + if "Location" not in data: + raise ValueError("Vertices are not in data. Check your input") # Creating faces for PyVista PolyData - faces = np.hstack(np.pad(data['Tri'], ((0, 0), (1, 0)), 'constant', constant_values=3)) + faces = np.hstack( + np.pad(data["Tri"], ((0, 0), (1, 0)), "constant", constant_values=3) + ) # Creating vertices for PyVista Polydata - vertices = data['Location'] + vertices = data["Location"] # Creating PolyData polydata = pv.PolyData(vertices, faces) # Adding depth scalars - polydata['Depth [m]'] = polydata.points[:, 2] + polydata["Depth [m]"] = polydata.points[:, 2] return polydata -def create_polydata_from_ts(data: Tuple[list, list], - concat: bool = False) -> pv.core.pointset.PolyData: +def create_polydata_from_ts( + data: Tuple[list, list], concat: bool = False +) -> pv.core.pointset.PolyData: """Converting loaded GoCAD mesh to PyVista PolyData Parameters @@ -1114,17 +1155,17 @@ def create_polydata_from_ts(data: Tuple[list, list], # Checking that the data is a tuple if not isinstance(data, tuple): - raise TypeError('Data must be provided as tuple of lists') + raise TypeError("Data must be provided as tuple of lists") # Checking that the concat parameter is provided as bool if not isinstance(concat, bool): - raise TypeError('Concat parameter must either be True or False') + raise TypeError("Concat parameter must either be True or False") # Checking that the faces and vertices are of the correct type if not isinstance(data[0], list): - raise TypeError('The vertices are in the wrong format. Check your input data') + raise TypeError("The vertices are in the wrong format. Check your input data") if not isinstance(data[1], list): - raise TypeError('The faces are in the wrong format. Check your input data') + raise TypeError("The faces are in the wrong format. Check your input data") if concat: @@ -1133,32 +1174,36 @@ def create_polydata_from_ts(data: Tuple[list, list], faces_list = np.vstack(data[1]) # Creating faces for PyVista PolyData - faces = np.hstack(np.pad(faces_list, ((0, 0), (1, 0)), 'constant', constant_values=3)) + faces = np.hstack( + np.pad(faces_list, ((0, 0), (1, 0)), "constant", constant_values=3) + ) # Creating vertices for PyVista Polydata - vertices = vertices_list[['X', 'Y', 'Z']].values + vertices = vertices_list[["X", "Y", "Z"]].values # Creating PolyData polydata = pv.PolyData(vertices, faces) # Adding depth scalars - polydata['Depth [m]'] = polydata.points[:, 2] + polydata["Depth [m]"] = polydata.points[:, 2] else: mesh_list = [] for i in range(len(data[0])): # Creating faces for PyVista PolyData - faces = np.hstack(np.pad(data[1][i], ((0, 0), (1, 0)), 'constant', constant_values=3)) + faces = np.hstack( + np.pad(data[1][i]-1, ((0, 0), (1, 0)), "constant", constant_values=3) + ) # Creating vertices for PyVista Polydata - vertices = data[0][i][['X', 'Y', 'Z']].values + vertices = data[0][i][["X", "Y", "Z"]].values # Creating PolyData mesh = pv.PolyData(vertices, faces) # Adding depth scalars - mesh['Depth [m]'] = mesh.points[:, 2] + mesh["Depth [m]"] = mesh.points[:, 2] mesh_list.append(mesh) @@ -1167,7 +1212,9 @@ def create_polydata_from_ts(data: Tuple[list, list], return polydata -def create_polydata_from_dxf(gdf: gpd.geodataframe.GeoDataFrame) -> pv.core.pointset.PolyData: +def create_polydata_from_dxf( + gdf: gpd.geodataframe.GeoDataFrame, +) -> pv.core.pointset.PolyData: """Converting loaded DXF object to PyVista PolyData Parameters @@ -1223,33 +1270,35 @@ def create_polydata_from_dxf(gdf: gpd.geodataframe.GeoDataFrame) -> pv.core.poin # Checking that the input data is a GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('The gdf must be provided as GeoDataFrame') + raise TypeError("The gdf must be provided as GeoDataFrame") # Checking that all elements of the gdf are LineStrings if not all(shapely.get_type_id(gdf.geometry) == 3): - raise TypeError('All geometries must be of geom_type Polygon') + raise TypeError("All geometries must be of geom_type Polygon") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Extracting XYZ gdf_lines = extract_xy(gdf=gdf) # Assigning vertices - vertices = gdf_lines[['X', 'Y', 'Z']].values + vertices = gdf_lines[["X", "Y", "Z"]].values # Assigning faces faces = np.pad( - np.arange(0, - len(gdf_lines[['X', 'Y', 'Z']].values)).reshape(int(len(gdf_lines[['X', 'Y', 'Z']].values) / 4), 4), + np.arange(0, len(gdf_lines[["X", "Y", "Z"]].values)).reshape( + int(len(gdf_lines[["X", "Y", "Z"]].values) / 4), 4 + ), ((0, 0), (1, 0)), - 'constant', - constant_values=4) + "constant", + constant_values=4, + ) # Creating PolyData dataset polydata = pv.PolyData(vertices, faces) @@ -1309,26 +1358,26 @@ def create_structured_grid_from_asc(data: dict) -> pv.core.pointset.StructuredGr # Checking that the input data is of type dict if not isinstance(data, dict): - raise TypeError('Input data must be a dict') + raise TypeError("Input data must be a dict") # Creating arrays for meshgrid - x = np.arange(data['Extent'][0], data['Extent'][1], data['Resolution']) - y = np.arange(data['Extent'][2], data['Extent'][3], data['Resolution']) + x = np.arange(data["Extent"][0], data["Extent"][1], data["Resolution"]) + y = np.arange(data["Extent"][2], data["Extent"][3], data["Resolution"]) # Creating meshgrid x, y = np.fliplr(np.meshgrid(x, y)) # Copying array data - data_nan = np.copy(data['Data']) + data_nan = np.copy(data["Data"]) # Replacing nodata_vals with np.nans for better visualization - data_nan[data_nan == data['Nodata_val']] = np.nan + data_nan[data_nan == data["Nodata_val"]] = np.nan # Creating StructuredGrid from Meshgrid - grid = pv.StructuredGrid(x, y, data['Data']) + grid = pv.StructuredGrid(x, y, data["Data"]) # Assign depth scalar with replaced nodata_vals - grid['Depth [m]'] = data_nan.ravel(order='F') + grid["Depth [m]"] = data_nan.ravel(order="F") return grid @@ -1385,32 +1434,41 @@ def create_structured_grid_from_zmap(data: dict) -> pv.core.pointset.StructuredG # Checking that the input data is of type dict if not isinstance(data, dict): - raise TypeError('Input data must be a dict') + raise TypeError("Input data must be a dict") # Creating arrays for meshgrid - x = np.arange(data['Extent'][0], data['Extent'][1] + data['Resolution'][0], data['Resolution'][0]) - y = np.arange(data['Extent'][2], data['Extent'][3] + data['Resolution'][1], data['Resolution'][1]) + x = np.arange( + data["Extent"][0], + data["Extent"][1] + data["Resolution"][0], + data["Resolution"][0], + ) + y = np.arange( + data["Extent"][2], + data["Extent"][3] + data["Resolution"][1], + data["Resolution"][1], + ) # Creating meshgrid x, y = np.fliplr(np.meshgrid(x, y)) # Copying array data - data_nan = np.copy(data['Data']) + data_nan = np.copy(data["Data"]) # Replacing nodata_vals with np.nans for better visualization - data_nan[data_nan == data['Nodata_val']] = np.nan + data_nan[data_nan == data["Nodata_val"]] = np.nan # Creating StructuredGrid from Meshgrid - grid = pv.StructuredGrid(x, y, data['Data']) + grid = pv.StructuredGrid(x, y, data["Data"]) # Assign depth scalar with replaced nodata_vals - grid['Depth [m]'] = data_nan.ravel(order='F') + grid["Depth [m]"] = data_nan.ravel(order="F") return grid -def create_delaunay_mesh_from_gdf(gdf: gpd.geodataframe.GeoDataFrame, - z: str = 'Z') -> pv.core.pointset.PolyData: +def create_delaunay_mesh_from_gdf( + gdf: gpd.geodataframe.GeoDataFrame, z: str = "Z" +) -> pv.core.pointset.PolyData: """Creating a delaunay triangulated mesh from surface contour lines Parameters @@ -1471,46 +1529,48 @@ def create_delaunay_mesh_from_gdf(gdf: gpd.geodataframe.GeoDataFrame, from scipy.spatial import Delaunay except ModuleNotFoundError: raise ModuleNotFoundError( - 'SciPy package is not installed. Use pip install scipy to install the latest version') + "SciPy package is not installed. Use pip install scipy to install the latest version" + ) # Checking that the gdf is a GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('The gdf must be provided as GeoDataFrame') + raise TypeError("The gdf must be provided as GeoDataFrame") # Checking that all elements of the gdf are LineStrings if not all(shapely.get_type_id(gdf.geometry) == 1): - raise TypeError('All geometries must be of geom_type LineString') + raise TypeError("All geometries must be of geom_type LineString") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that a Z column is present in the GeoDataFrame if z not in gdf: - raise ValueError('A valid column name for Z values must be provided') + raise ValueError("A valid column name for Z values must be provided") # Extracting X and Y values from LineStrings - gdf_xy = extract_xy(gdf=gdf, - reset_index=True) + gdf_xy = extract_xy(gdf=gdf, reset_index=True) # Creating Delaunay tessellation - tri = Delaunay(gdf_xy[['X', 'Y']].values) + tri = Delaunay(gdf_xy[["X", "Y"]].values) # Creating vertices - vertices = gdf_xy[['X', 'Y', 'Z']].values + vertices = gdf_xy[["X", "Y", "Z"]].values # Creating faces - faces = np.hstack(np.pad(tri.simplices, ((0, 0), (1, 0)), 'constant', constant_values=3)) + faces = np.hstack( + np.pad(tri.simplices, ((0, 0), (1, 0)), "constant", constant_values=3) + ) # Creating PyVista PolyData poly = pv.PolyData(vertices, faces) # Creating array with depth values - poly['Depth [m]'] = gdf_xy['Z'].values + poly["Depth [m]"] = gdf_xy["Z"].values return poly @@ -1518,8 +1578,10 @@ def create_delaunay_mesh_from_gdf(gdf: gpd.geodataframe.GeoDataFrame, # Creating Depth and Temperature Maps ##################################### -def create_depth_map(mesh: pv.core.pointset.PolyData, - name: str = 'Depth [m]') -> pv.core.pointset.PolyData: + +def create_depth_map( + mesh: pv.core.pointset.PolyData, name: str = "Depth [m]" +) -> pv.core.pointset.PolyData: """Extracting the depth values of the vertices and add them as scalars to the mesh Parameters @@ -1581,11 +1643,11 @@ def create_depth_map(mesh: pv.core.pointset.PolyData, # Checking that the mesh is a PyVista PolyData dataset if not isinstance(mesh, pv.core.pointset.PolyData): - raise TypeError('Mesh must be a PyVista PolyData dataset') + raise TypeError("Mesh must be a PyVista PolyData dataset") # Checking that the name is of type string if not isinstance(name, str): - raise TypeError('The provided name for the scalar must be of type string') + raise TypeError("The provided name for the scalar must be of type string") # Adding the depths values as data array to the mesh mesh[name] = mesh.points[:, 2] @@ -1593,9 +1655,9 @@ def create_depth_map(mesh: pv.core.pointset.PolyData, return mesh -def create_depth_maps_from_gempy(geo_model, - surfaces: Union[str, List[str]]) \ - -> Dict[str, List[Union[pv.core.pointset.PolyData, np.ndarray, List[str]]]]: +def create_depth_maps_from_gempy( + geo_model, surfaces: Union[str, List[str]] +) -> Dict[str, List[Union[pv.core.pointset.PolyData, np.ndarray, List[str]]]]: """Creating depth map of model surfaces, adapted from https://github.com/cgre-aachen/gempy/blob/20550fffdd1ccb3c6a9a402bc162e7eed3dd7352/gempy/plot/vista.py#L440-L477 @@ -1617,7 +1679,7 @@ def create_depth_maps_from_gempy(geo_model, .. versionadded:: 1.0.x - .. versionchanged:: 1.1.8 + .. versionchanged:: 1.2 Ensure compatibility with GemPy>=3 Example @@ -1625,7 +1687,7 @@ def create_depth_maps_from_gempy(geo_model, >>> # Loading Libraries and creating depth map >>> import gemgis as gg - >>> dict_sand1 = gg.visualization.create_depth_maps(geo_model=geo_model, surfaces='Sand1') + >>> dict_sand1 = gg.visualization.create_depth_maps_from_gempy(geo_model=geo_model, surfaces='Sand1') >>> dict_sand1 {'Sand1': [PolyData (0x2dd0f46c820) N Cells: 4174 @@ -1650,11 +1712,12 @@ def create_depth_maps_from_gempy(geo_model, import gempy as gp except ModuleNotFoundError: raise ModuleNotFoundError( - 'GemPy package is not installed. Use pip install gempy to install the latest version') + "GemPy package is not installed. Use pip install gempy to install the latest version" + ) # Checking if surface is of type string if not isinstance(surfaces, (str, list)): - raise TypeError('Surface name must be of type string') + raise TypeError("Surface name must be of type string") # Converting string to list if only one surface is provided if isinstance(surfaces, str): @@ -1664,39 +1727,49 @@ def create_depth_maps_from_gempy(geo_model, try: # GemPy<3 if not isinstance(geo_model, gp.core.model.Project): - raise TypeError('geo_model must be a GemPy geo_model') + raise TypeError("geo_model must be a GemPy geo_model") # Checking that the model was computed - if all(pd.isna(geo_model.surfaces.df.vertices)) == True and all(pd.isna(geo_model.surfaces.df.edges)) == True: - raise ValueError('Model must be created before depth map extraction') + if all(pd.isna(geo_model.surfaces.df.vertices)) and all( + pd.isna(geo_model.surfaces.df.edges) + ): + raise ValueError("Model must be created before depth map extraction") # Extracting surface data_df for all surfaces data_df = geo_model.surfaces.df.copy(deep=True) # Checking that surfaces are valid if not all(item in data_df.surface.unique().tolist() for item in surfaces): - raise ValueError('One or more invalid surface names provided') + raise ValueError("One or more invalid surface names provided") # Extracting geometric data of selected surfaces - geometric_data = pd.concat([data_df.groupby('surface').get_group(group) for group in surfaces]) + geometric_data = pd.concat( + [data_df.groupby("surface").get_group(group) for group in surfaces] + ) # Creating empty dict to store data surfaces_poly = {} - for idx, val in geometric_data[['vertices', 'edges', 'color', 'surface', 'id']].dropna().iterrows(): + for idx, val in ( + geometric_data[["vertices", "edges", "color", "surface", "id"]] + .dropna() + .iterrows() + ): # Creating PolyData from each surface - surf = pv.PolyData(val['vertices'][0], np.insert(val['edges'][0], 0, 3, axis=1).ravel()) + surf = pv.PolyData( + val["vertices"][0], np.insert(val["edges"][0], 0, 3, axis=1).ravel() + ) # Append depth to PolyData - surf['Depth [m]'] = val['vertices'][0][:, 2] + surf["Depth [m]"] = val["vertices"][0][:, 2] # Store mesh, depth values and color values in dict - surfaces_poly[val['surface']] = [surf, val['color']] + surfaces_poly[val["surface"]] = [surf, val["color"]] except AttributeError: # GemPy>=3 if not isinstance(geo_model, gp.core.data.geo_model.GeoModel): - raise TypeError('geo_model must be a GemPy geo_model') + raise TypeError("geo_model must be a GemPy geo_model") # TODO Add check that arrays are not empty @@ -1705,7 +1778,7 @@ def create_depth_maps_from_gempy(geo_model, # Checking that surfaces are valid if not all(item in list_surfaces for item in surfaces): - raise ValueError('One or more invalid surface names provided') + raise ValueError("One or more invalid surface names provided") # Getting indices of provided surfaces list_indices = [list_surfaces.index(surface) for surface in surfaces] @@ -1714,20 +1787,37 @@ def create_depth_maps_from_gempy(geo_model, surfaces_poly = {} for index in list_indices: - surf = pv.PolyData(geo_model.solutions.raw_arrays.vertices[index], - np.insert(geo_model.solutions.raw_arrays.edges[index], 0, 3, axis=1).ravel()) - # Append depth to PolyData - surf['Depth [m]'] = geo_model.solutions.raw_arrays.vertices[index][:, 2] + # Extracting vertices + vertices = geo_model.input_transform.apply_inverse( + geo_model.solutions.raw_arrays.vertices[index] + ) - # Store mesh, depth values and color values in dict - surfaces_poly[list_surfaces[index]] = [surf, geo_model.structural_frame.elements_colors[index]] + # Extracting faces + faces = np.insert( + geo_model.solutions.raw_arrays.edges[index], 0, 3, axis=1 + ).ravel() + + # Creating PolyData from vertices and faces + surf = pv.PolyData(vertices, faces) + + # Appending depth to PolyData + surf["Depth [m]"] = geo_model.input_transform.apply_inverse( + geo_model.solutions.raw_arrays.vertices[index] + )[:, 2] + + # Storing mesh, depth values and color values in dict + surfaces_poly[list_surfaces[index]] = [ + surf, + geo_model.structural_frame.elements_colors[index], + ] return surfaces_poly -def create_thickness_maps(top_surface: pv.core.pointset.PolyData, - base_surface: pv.core.pointset.PolyData) -> pv.core.pointset.PolyData: +def create_thickness_maps( + top_surface: pv.core.pointset.PolyData, base_surface: pv.core.pointset.PolyData +) -> pv.core.pointset.PolyData: """Creating a thickness map using https://docs.pyvista.org/examples/01-filter/distance-between-surfaces.html#sphx-glr-examples-01-filter-distance-between-surfaces-py Parameters @@ -1780,16 +1870,16 @@ def create_thickness_maps(top_surface: pv.core.pointset.PolyData, # Checking that the top_surface is a PyVista pv.core.pointset.PolyData if not isinstance(top_surface, pv.core.pointset.PolyData): - raise TypeError('Top Surface must be a PyVista PolyData set') + raise TypeError("Top Surface must be a PyVista PolyData set") # Checking that the base_surface is a PyVista pv.core.pointset.PolyData if not isinstance(base_surface, pv.core.pointset.PolyData): - raise TypeError('Base Surface must be a PyVista PolyData set') + raise TypeError("Base Surface must be a PyVista PolyData set") # Computing normals of lower surface - base_surface_normals = base_surface.compute_normals(point_normals=True, - cell_normals=False, - auto_orient_normals=True) + base_surface_normals = base_surface.compute_normals( + point_normals=True, cell_normals=False, auto_orient_normals=True + ) # Travel along normals to the other surface and compute the thickness on each vector base_surface_normals["Thickness [m]"] = np.empty(base_surface.n_points) @@ -1809,12 +1899,14 @@ def create_thickness_maps(top_surface: pv.core.pointset.PolyData, return base_surface_normals -def create_temperature_map(dem: rasterio.io.DatasetReader, - mesh: pv.core.pointset.PolyData, - name: str = 'Thickness [m]', - apply_threshold: bool = True, - tsurface: Union[float, int] = 10, - gradient: Union[float, int] = 0.03) -> pv.core.pointset.PolyData: +def create_temperature_map( + dem: rasterio.io.DatasetReader, + mesh: pv.core.pointset.PolyData, + name: str = "Thickness [m]", + apply_threshold: bool = True, + tsurface: Union[float, int] = 10, + gradient: Union[float, int] = 0.03, +) -> pv.core.pointset.PolyData: """Creating a temperature map for a surface at depth taking the topography into account Parameters @@ -1893,15 +1985,17 @@ def create_temperature_map(dem: rasterio.io.DatasetReader, # Checking that the raster is a rasterio object if not isinstance(dem, rasterio.io.DatasetReader): - raise TypeError('Provided Digital Elevation Model must be provided as rasterio object') + raise TypeError( + "Provided Digital Elevation Model must be provided as rasterio object" + ) # Checking that the mesh is PyVista PolyData dataset if not isinstance(mesh, pv.core.pointset.PolyData): - raise TypeError('Mesh must be a PyVista PolyData dataset') + raise TypeError("Mesh must be a PyVista PolyData dataset") # Checking that apply_threshold is of type bool if not isinstance(apply_threshold, bool): - raise TypeError('Variable to apply the threshold must be of type bool') + raise TypeError("Variable to apply the threshold must be of type bool") # Getting the x coordinates of the mesh vertices vertices_x = mesh.points[:, 0] @@ -1913,9 +2007,7 @@ def create_temperature_map(dem: rasterio.io.DatasetReader, vertices_z = mesh.points[:, 2] # Sampling the raster at the vertices locations - raster_z = sample_from_rasterio(raster=dem, - point_x=vertices_x, - point_y=vertices_y) + raster_z = sample_from_rasterio(raster=dem, point_x=vertices_x, point_y=vertices_y) # Calculating the thickness of the layer thickness = (vertices_z - raster_z) * (-1) @@ -1928,7 +2020,7 @@ def create_temperature_map(dem: rasterio.io.DatasetReader, mesh = mesh.threshold([0, mesh[name].max()]) # Calculating the temperature and adding it as data array to the mesh - mesh['Temperature [°C]'] = mesh[name] * gradient + tsurface + mesh["Temperature [°C]"] = mesh[name] * gradient + tsurface return mesh @@ -1936,6 +2028,7 @@ def create_temperature_map(dem: rasterio.io.DatasetReader, # Visualizing Boreholes in 3D ############################# + def group_borehole_dataframe(df: pd.DataFrame) -> List[pd.DataFrame]: """Grouping Borehole DataFrame by Index @@ -1967,14 +2060,14 @@ def group_borehole_dataframe(df: pd.DataFrame) -> List[pd.DataFrame]: # Checking that the input data is a (Geo-)DataFrame if not isinstance(df, (pd.DataFrame, gpd.geodataframe.GeoDataFrame)): - raise TypeError('Input data must be a (Geo-)DataFrame') + raise TypeError("Input data must be a (Geo-)DataFrame") # Checking that the index column is in the (Geo-)DataFrame - if 'Index' not in df: - raise ValueError('Index column not in (Geo-)DataFrame') + if "Index" not in df: + raise ValueError("Index column not in (Geo-)DataFrame") # Grouping df by Index - grouped = df.groupby(['Index']) + grouped = df.groupby(["Index"]) # Getting single (Geo-)DataFrames df_groups = [grouped.get_group(x) for x in grouped.groups] @@ -2036,30 +2129,35 @@ def add_row_to_boreholes(df_groups: List[pd.DataFrame]) -> List[pd.DataFrame]: try: from tqdm import tqdm except ModuleNotFoundError: - raise ModuleNotFoundError('tqdm package is not installed. Use pip install tqdm to install the latest version') + raise ModuleNotFoundError( + "tqdm package is not installed. Use pip install tqdm to install the latest version" + ) # Checking that df_groups is a list if not isinstance(df_groups, list): - raise TypeError('df_groups must be a list containing Pandas DataFrames') + raise TypeError("df_groups must be a list containing Pandas DataFrames") # Checking that all elements of the list are of type DataFrame if not all(isinstance(i, pd.DataFrame) for i in df_groups): - raise TypeError('All elements of df_groups must be of type Pandas DataFrame') + raise TypeError("All elements of df_groups must be of type Pandas DataFrame") # Adding additional row to DataFrame for i in tqdm(range(len(df_groups))): - index = df_groups[i]['Index'].unique()[0] - name = df_groups[i]['Name'].unique()[0] - x = df_groups[i]['X'].unique()[0] - y = df_groups[i]['Y'].unique()[0] - z = df_groups[i]['Altitude'].unique()[0] - altitude = df_groups[i]['Altitude'].unique()[0] - depth = df_groups[i]['Depth'].unique()[0] - formation = '' + index = df_groups[i]["Index"].unique()[0] + name = df_groups[i]["Name"].unique()[0] + x = df_groups[i]["X"].unique()[0] + y = df_groups[i]["Y"].unique()[0] + z = df_groups[i]["Altitude"].unique()[0] + altitude = df_groups[i]["Altitude"].unique()[0] + depth = df_groups[i]["Depth"].unique()[0] + formation = "" data = [[index, name, x, y, z, altitude, depth, formation]] - row = pd.DataFrame(data=data, columns=['Index', 'Name', 'X', 'Y', 'Z', 'Altitude', 'Depth', 'formation']) + row = pd.DataFrame( + data=data, + columns=["Index", "Name", "X", "Y", "Z", "Altitude", "Depth", "formation"], + ) df_groups[i] = pd.concat([df_groups[i], row]) - df_groups[i] = df_groups[i].sort_values(by=['Z'], ascending=False) + df_groups[i] = df_groups[i].sort_values(by=["Z"], ascending=False) return df_groups @@ -2127,10 +2225,10 @@ def create_lines_from_points(df: pd.DataFrame) -> pv.core.pointset.PolyData: # Checking if df is of a pandas DataFrame if not isinstance(df, pd.DataFrame): - raise TypeError('Borehole data must be provided as Pandas DataFrame') + raise TypeError("Borehole data must be provided as Pandas DataFrame") # Deleting not needed columns - df_copy = df.copy(deep=True)[['X', 'Y', 'Z']] + df_copy = df.copy(deep=True)[["X", "Y", "Z"]] # Creating line data set poly = pv.PolyData(df_copy.to_numpy()) @@ -2142,9 +2240,9 @@ def create_lines_from_points(df: pd.DataFrame) -> pv.core.pointset.PolyData: return poly -def create_borehole_tube(df: pd.DataFrame, - line: pv.core.pointset.PolyData, - radius: Union[float, int]) -> pv.core.pointset.PolyData: +def create_borehole_tube( + df: pd.DataFrame, line: pv.core.pointset.PolyData, radius: Union[float, int] +) -> pv.core.pointset.PolyData: """Creating a tube from a line for the 3D visualization of boreholes Parameters @@ -2229,15 +2327,15 @@ def create_borehole_tube(df: pd.DataFrame, # Checking if df is of a pandas DataFrame if not isinstance(df, pd.DataFrame): - raise TypeError('Borehole data must be provided as Pandas DataFrame') + raise TypeError("Borehole data must be provided as Pandas DataFrame") # Checking that the line data is a PolyData object if not isinstance(line, pv.core.pointset.PolyData): - raise TypeError('Line data must be a PolyData object') + raise TypeError("Line data must be a PolyData object") # Checking that the radius is of type float if not isinstance(radius, (float, int)): - raise TypeError('Radius must be of type float') + raise TypeError("Radius must be of type float") # Deleting the first row which does not contain a formation (see above) df_cols = df.copy(deep=True) @@ -2250,14 +2348,14 @@ def create_borehole_tube(df: pd.DataFrame, tube = line.tube(radius=radius) # Adding depth scalars - tube['Depth'] = tube.points[:, 2] + tube["Depth"] = tube.points[:, 2] return tube -def create_borehole_tubes(df: pd.DataFrame, - min_length: Union[float, int], - radius: Union[int, float] = 10) -> Tuple[List[pv.core.pointset.PolyData], List[pd.DataFrame]]: +def create_borehole_tubes( + df: pd.DataFrame, min_length: Union[float, int], radius: Union[int, float] = 10 +) -> Tuple[List[pv.core.pointset.PolyData], List[pd.DataFrame]]: """Creating PyVista Tubes for plotting boreholes in 3D Parameters @@ -2324,40 +2422,47 @@ def create_borehole_tubes(df: pd.DataFrame, # Checking if df is of a pandas DataFrame if not isinstance(df, pd.DataFrame): - raise TypeError('Borehole data must be provided as Pandas DataFrame') + raise TypeError("Borehole data must be provided as Pandas DataFrame") # Checking that all necessary columns are present in the DataFrame - if not {'Index', 'Name', 'X', 'Y', 'Z', 'Altitude', 'Depth', 'formation'}.issubset(df.columns): - raise ValueError('[%s, %s, %s, %s, %s, %s, %s, %s] need to be columns in the provided DataFrame' % ( - 'Index', 'Name', 'X', 'Y', 'Z', 'Altitude', 'Depth', 'formation')) + if not {"Index", "Name", "X", "Y", "Z", "Altitude", "Depth", "formation"}.issubset( + df.columns + ): + raise ValueError( + "[%s, %s, %s, %s, %s, %s, %s, %s] need to be columns in the provided DataFrame" + % ("Index", "Name", "X", "Y", "Z", "Altitude", "Depth", "formation") + ) # Checking that the min_limit is of type float or int if not isinstance(min_length, (float, int)): - raise TypeError('Minimum length for boreholes must be of type float or int') + raise TypeError("Minimum length for boreholes must be of type float or int") # Checking that the radius is of type int or float if not isinstance(radius, (int, float)): - raise TypeError('The radius must be provided as int or float') + raise TypeError("The radius must be provided as int or float") # Limiting the length of boreholes withing the DataFrame to a minimum length - df = df[df['Depth'] >= min_length] + df = df[df["Depth"] >= min_length] # Group each borehole by its index and return groups within a list, each item in the list is a pd.DataFrame - grouped = df.groupby(['Index']) + grouped = df.groupby(["Index"]) df_groups = [grouped.get_group(x) for x in grouped.groups] # Add additional row to each borehole df_groups = add_row_to_boreholes(df_groups=df_groups) lines = [create_lines_from_points(df=i) for i in df_groups] - tubes = [create_borehole_tube(df=df_groups[i], - line=lines[i], - radius=radius) for i in range(len(df_groups))] + tubes = [ + create_borehole_tube(df=df_groups[i], line=lines[i], radius=radius) + for i in range(len(df_groups)) + ] return tubes, df_groups -def create_borehole_labels(df: Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame]) -> pv.core.pointset.PolyData: +def create_borehole_labels( + df: Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame] +) -> pv.core.pointset.PolyData: """Create labels for borehole plots. Parameters @@ -2413,36 +2518,51 @@ def create_borehole_labels(df: Union[pd.DataFrame, gpd.geodataframe.GeoDataFrame """ # Checking if df is of a pandas DataFrame if not isinstance(df, (pd.DataFrame, gpd.geodataframe.GeoDataFrame)): - raise TypeError('Borehole data must be provided as Pandas DataFrame or GeoPandas GeoDataFrame') + raise TypeError( + "Borehole data must be provided as Pandas DataFrame or GeoPandas GeoDataFrame" + ) # Checking that X, Y and the Altitude of the borehole are present - if not {'X', 'Y', 'Altitude'}.issubset(df.columns): - raise ValueError('X, Y and Altitude columns must be provided for label creation') + if not {"X", "Y", "Altitude"}.issubset(df.columns): + raise ValueError( + "X, Y and Altitude columns must be provided for label creation" + ) # Creating array with coordinates from each group (equals to one borehole) coordinates = np.rot90( np.array( - df.groupby(['Index', 'Name'])[['X', 'Y', 'Altitude']].apply(lambda x: list(np.unique(x))).values.tolist()), - 2) + df.groupby(["Index", "Name"])[["X", "Y", "Altitude"]] + .apply(lambda x: list(np.unique(x))) + .values.tolist() + ), + 2, + ) # Creating borehole location PyVista PolyData Object borehole_locations = pv.PolyData(coordinates) # Creating borehole_location labels - list_tuples = df.groupby(['Index', 'Name'])[['X', 'Y', 'Altitude']].apply( - lambda x: list(np.unique(x))).index.tolist()[::-1] + list_tuples = ( + df.groupby(["Index", "Name"])[["X", "Y", "Altitude"]] + .apply(lambda x: list(np.unique(x))) + .index.tolist()[::-1] + ) - borehole_locations['Labels'] = [''.join(char for char in i[1] if ord(char) < 128) for i in list_tuples] + borehole_locations["Labels"] = [ + "".join(char for char in i[1] if ord(char) < 128) for i in list_tuples + ] return borehole_locations -def create_boreholes_3d(df: pd.DataFrame, - min_length: Union[float, int], - color_dict: dict, - radius: Union[float, int] = 10) -> Tuple[List[pv.core.pointset.PolyData], - pv.core.pointset.PolyData, - List[pd.DataFrame]]: +def create_boreholes_3d( + df: pd.DataFrame, + min_length: Union[float, int], + color_dict: dict, + radius: Union[float, int] = 10, +) -> Tuple[ + List[pv.core.pointset.PolyData], pv.core.pointset.PolyData, List[pd.DataFrame] +]: """Plotting boreholes in 3D Parameters @@ -2525,29 +2645,37 @@ def create_boreholes_3d(df: pd.DataFrame, # Checking if df is of a pandas DataFrame if not isinstance(df, pd.DataFrame): - raise TypeError('Borehole data must be provided as Pandas DataFrame') + raise TypeError("Borehole data must be provided as Pandas DataFrame") # Checking that all necessary columns are present in the DataFrame - if not pd.Series(['Index', 'Name', 'X', 'Y', 'Z', 'Altitude', 'Depth', 'formation']).isin(df.columns).all(): - raise ValueError('[%s, %s, %s, %s, %s, %s, %s, %s] need to be columns in the provided DataFrame' % ( - 'Index', 'Name', 'X', 'Y', 'Z', 'Altitude', 'Depth', 'formation')) + if ( + not pd.Series( + ["Index", "Name", "X", "Y", "Z", "Altitude", "Depth", "formation"] + ) + .isin(df.columns) + .all() + ): + raise ValueError( + "[%s, %s, %s, %s, %s, %s, %s, %s] need to be columns in the provided DataFrame" + % ("Index", "Name", "X", "Y", "Z", "Altitude", "Depth", "formation") + ) # Checking that the min_limit is of type float or int if not isinstance(min_length, (float, int)): - raise TypeError('Minimum length for boreholes must be of type float or int') + raise TypeError("Minimum length for boreholes must be of type float or int") # Checking that the color_dict is of type dict if not isinstance(color_dict, dict): - raise TypeError('Surface color dictionary must be of type dict') + raise TypeError("Surface color dictionary must be of type dict") # Checking that the radius is of type int or float if not isinstance(radius, (int, float)): - raise TypeError('The radius must be provided as int or float') + raise TypeError("The radius must be provided as int or float") # Creating tubes for later plotting - tubes, df_groups = create_borehole_tubes(df=df, - min_length=min_length, - radius=radius) + tubes, df_groups = create_borehole_tubes( + df=df, min_length=min_length, radius=radius + ) # Creating MultiBlock Object containing the tubes tubes = pv.MultiBlock(tubes) @@ -2558,8 +2686,7 @@ def create_boreholes_3d(df: pd.DataFrame, return tubes, labels, df_groups -def calculate_vector(dip: Union[float, int], - azimuth: Union[float, int]) -> np.ndarray: +def calculate_vector(dip: Union[float, int], azimuth: Union[float, int]) -> np.ndarray: """Calculating the plunge vector of a borehole section Parameters @@ -2598,25 +2725,31 @@ def calculate_vector(dip: Union[float, int], # Checking that the dip is type float or int if not isinstance(dip, (float, int)): - raise TypeError('Dip value must be of type float or int') + raise TypeError("Dip value must be of type float or int") # Checking that the azimuth is type float or int if not isinstance(azimuth, (float, int)): - raise TypeError('Azimuth value must be of type float or int') + raise TypeError("Azimuth value must be of type float or int") # Calculating plunging vector - vector = np.array([[np.sin(dip) * np.cos(azimuth)], - [np.cos(dip) * np.cos(azimuth)], - [np.sin(azimuth)]]) + vector = np.array( + [ + [np.sin(dip) * np.cos(azimuth)], + [np.cos(dip) * np.cos(azimuth)], + [np.sin(azimuth)], + ] + ) return vector -def create_deviated_borehole_df(df_survey: pd.DataFrame, - position: Union[np.ndarray, shapely.geometry.point.Point], - depth: str = 'depth', - dip: str = 'dip', - azimuth: str = 'azimuth') -> pd.DataFrame: +def create_deviated_borehole_df( + df_survey: pd.DataFrame, + position: Union[np.ndarray, shapely.geometry.point.Point], + depth: str = "depth", + dip: str = "dip", + azimuth: str = "azimuth", +) -> pd.DataFrame: """Creating Pandas DataFrame containing parameters to create 3D boreholes Parameters @@ -2673,75 +2806,91 @@ def create_deviated_borehole_df(df_survey: pd.DataFrame, # Checking that the input DataFrame is a Pandas DataFrame if not isinstance(df_survey, pd.DataFrame): - raise TypeError('Survey Input Data must be a Pandas DataFrame') + raise TypeError("Survey Input Data must be a Pandas DataFrame") # Checking that the position of the well is either provided as np.ndarray or as Shapely point if not isinstance(position, (np.ndarray, shapely.geometry.point.Point)): - raise TypeError('Borehole position must be provides as NumPy array or Shapely Point') + raise TypeError( + "Borehole position must be provides as NumPy array or Shapely Point" + ) # Checking that the column name is of type string if not isinstance(depth, str): - raise TypeError('Depth column name must be provided as string') + raise TypeError("Depth column name must be provided as string") # Checking that the column name is of type string if not isinstance(dip, str): - raise TypeError('Dip column name must be provided as string') + raise TypeError("Dip column name must be provided as string") # Checking that the column name is of type string if not isinstance(azimuth, str): - raise TypeError('Azimuth column name must be provided as string') + raise TypeError("Azimuth column name must be provided as string") # Converting Shapely Point to array if isinstance(position, shapely.geometry.point.Point): position = np.array(position.coords) # Calculating the bottom depth of each borehole segment - df_survey['depth_bottom'] = pd.concat([df_survey[depth], pd.Series(np.nan,index=[len(df_survey[depth])])]) + df_survey["depth_bottom"] = pd.concat( + [df_survey[depth], pd.Series(np.nan, index=[len(df_survey[depth])])] + ) # Calculating the plunging vector for each borehole segment - df_survey['vector'] = df_survey.apply(lambda row: calculate_vector(row[dip], - row[azimuth]), axis=1) + df_survey["vector"] = df_survey.apply( + lambda row: calculate_vector(row[dip], row[azimuth]), axis=1 + ) # Calculating the length of each segment - depths = df_survey['depth'].values[:-1] - df_survey['depth'].values[1:] + depths = df_survey["depth"].values[:-1] - df_survey["depth"].values[1:] depths = np.append(depths, 0) - df_survey['segment_length'] = depths + df_survey["segment_length"] = depths # Calculating the coordinates of each segment - x = np.cumsum(df_survey['segment_length'].values * df_survey['vector'].values) + x = np.cumsum(df_survey["segment_length"].values * df_survey["vector"].values) # Adding the position of the borehole at the surface to each point - df_survey['points'] = np.array([(element.T + position)[0] for element in x]).tolist() + df_survey["points"] = np.array( + [(element.T + position)[0] for element in x] + ).tolist() # Adding point coordinates as X, Y and Z columns to work with `create_lines_from_points' function - df_survey[['X', 'Y', 'Z']] = df_survey['points'].values.tolist() + df_survey[["X", "Y", "Z"]] = df_survey["points"].values.tolist() # Creating coordinates for first row df_row0 = pd.DataFrame([position[0], position[1], position[2]]).T - df_row0['points'] = [position] - df_row0.columns = ['X', 'Y', 'Z', 'points'] + df_row0["points"] = [position] + df_row0.columns = ["X", "Y", "Z", "points"] # Creating first row - df_extra = pd.concat([pd.DataFrame(df_survey.loc[0].drop(['points', 'X', 'Y', 'Z'])).T, df_row0], axis=1) + df_extra = pd.concat( + [pd.DataFrame(df_survey.loc[0].drop(["points", "X", "Y", "Z"])).T, df_row0], + axis=1, + ) # Adding first row to DataFrame - df_survey = pd.concat([df_extra, df_survey]).drop(df_survey.tail(1).index).reset_index(drop=True) + df_survey = ( + pd.concat([df_extra, df_survey]) + .drop(df_survey.tail(1).index) + .reset_index(drop=True) + ) return df_survey -def create_deviated_boreholes_3d(df_collar: pd.DataFrame, - df_survey: pd.DataFrame, - min_length: Union[float, int], - # color_dict: dict, - radius: Union[float, int] = 10, - collar_depth: str = 'Depth', - survey_depth: str = 'Depth', - index: str = 'Index', - dip: str = 'dip', - azimuth: str = 'azimuth') -> Tuple[List[pv.core.pointset.PolyData], - pv.core.pointset.PolyData, - List[pd.DataFrame]]: +def create_deviated_boreholes_3d( + df_collar: pd.DataFrame, + df_survey: pd.DataFrame, + min_length: Union[float, int], + # color_dict: dict, + radius: Union[float, int] = 10, + collar_depth: str = "Depth", + survey_depth: str = "Depth", + index: str = "Index", + dip: str = "dip", + azimuth: str = "azimuth", +) -> Tuple[ + List[pv.core.pointset.PolyData], pv.core.pointset.PolyData, List[pd.DataFrame] +]: """Plotting boreholes in 3D Parameters @@ -2798,15 +2947,15 @@ def create_deviated_boreholes_3d(df_collar: pd.DataFrame, # Checking if df is of a pandas DataFrame if not isinstance(df_collar, pd.DataFrame): - raise TypeError('Borehole data must be provided as Pandas DataFrame') + raise TypeError("Borehole data must be provided as Pandas DataFrame") # Checking if df is of a pandas DataFrame if not isinstance(df_survey, pd.DataFrame): - raise TypeError('Borehole data must be provided as Pandas DataFrame') + raise TypeError("Borehole data must be provided as Pandas DataFrame") # Checking that the min_limit is of type float or int if not isinstance(min_length, (float, int)): - raise TypeError('Minimum length for boreholes must be of type float or int') + raise TypeError("Minimum length for boreholes must be of type float or int") # Checking that the color_dict is of type dict # if not isinstance(color_dict, dict): @@ -2814,27 +2963,27 @@ def create_deviated_boreholes_3d(df_collar: pd.DataFrame, # Checking that the radius is of type int or float if not isinstance(radius, (int, float)): - raise TypeError('The radius must be provided as int or float') + raise TypeError("The radius must be provided as int or float") # Checking that the column name is of type string if not isinstance(collar_depth, str): - raise TypeError('Collar depth column name must be provided as string') + raise TypeError("Collar depth column name must be provided as string") # Checking that the column name is of type string if not isinstance(survey_depth, str): - raise TypeError('Survey depth column name must be provided as string') + raise TypeError("Survey depth column name must be provided as string") # Checking that the column name is of type string if not isinstance(dip, str): - raise TypeError('Dip column name must be provided as string') + raise TypeError("Dip column name must be provided as string") # Checking that the column name is of type string if not isinstance(azimuth, str): - raise TypeError('Azimuth column name must be provided as string') + raise TypeError("Azimuth column name must be provided as string") # Checking that the column name is of type string if not isinstance(index, str): - raise TypeError('Index column name must be provided as string') + raise TypeError("Index column name must be provided as string") # Limiting the length of boreholes withing the DataFrame to a minimum length df_collar = df_collar[df_collar[collar_depth] >= min_length] @@ -2850,17 +2999,24 @@ def create_deviated_boreholes_3d(df_collar: pd.DataFrame, # Group each borehole by its index and return groups within a list, each item in the list is a pd.DataFrame grouped_survey = df_survey.groupby([index]) - df_groups_survey = [grouped_survey.get_group(x).reset_index() for x in grouped_survey.groups] + df_groups_survey = [ + grouped_survey.get_group(x).reset_index() for x in grouped_survey.groups + ] # Creating deviated borehole DataFrames df_groups = [ - create_deviated_borehole_df(df_survey=df_groups_survey[i], position=df_collar.loc[i][['x', 'y', 'z']].values) - for i in range(len(df_collar))] + create_deviated_borehole_df( + df_survey=df_groups_survey[i], + position=df_collar.loc[i][["x", "y", "z"]].values, + ) + for i in range(len(df_collar)) + ] lines = [create_lines_from_points(df=i) for i in df_groups] - tubes = [create_borehole_tube(df=df_groups[i], - line=lines[i], - radius=radius) for i in range(len(df_groups))] + tubes = [ + create_borehole_tube(df=df_groups[i], line=lines[i], radius=radius) + for i in range(len(df_groups)) + ] # Creating MultiBlock Object containing the tubes tubes = pv.MultiBlock(tubes) @@ -2871,14 +3027,17 @@ def create_deviated_boreholes_3d(df_collar: pd.DataFrame, # Misc ######## -def plot_orientations(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], - show_planes: bool = True, - show_density_contours: bool = True, - show_density_contourf: bool = False, - formation: str = None, - method: str = 'exponential_kamb', - sigma: Union[float, int] = 1, - cmap: str = 'Blues_r'): + +def plot_orientations( + gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], + show_planes: bool = True, + show_density_contours: bool = True, + show_density_contourf: bool = False, + formation: str = None, + method: str = "exponential_kamb", + sigma: Union[float, int] = 1, + cmap: str = "Blues_r", +): """Plotting orientation values of a GeoDataFrame with mplstereonet Parameters @@ -2941,77 +3100,81 @@ def plot_orientations(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], import mplstereonet except ModuleNotFoundError: raise ModuleNotFoundError( - 'mplstereonet package is not installed. Use pip install mplstereonet to install the latest version') + "mplstereonet package is not installed. Use pip install mplstereonet to install the latest version" + ) # Trying to import matplotlib but returning error if matplotlib is not installed try: import matplotlib.pyplot as plt except ModuleNotFoundError: raise ModuleNotFoundError( - 'Matplotlib package is not installed. Use pip install matplotlib to install the latest version') + "Matplotlib package is not installed. Use pip install matplotlib to install the latest version" + ) # Checking if gdf is of type GeoDataFrame or DataFrame if not isinstance(gdf, (gpd.geodataframe.GeoDataFrame, pd.DataFrame)): - raise TypeError('Object must be of type GeoDataFrame or DataFrame') + raise TypeError("Object must be of type GeoDataFrame or DataFrame") # Checking if the formation, dip and azimuth columns are present - if not {'formation', 'dip', 'azimuth'}.issubset(gdf.columns): - raise ValueError('GeoDataFrame/DataFrame is missing columns (formation, dip, azimuth)') + if not {"formation", "dip", "azimuth"}.issubset(gdf.columns): + raise ValueError( + "GeoDataFrame/DataFrame is missing columns (formation, dip, azimuth)" + ) # Checking that the provided formation for contourf is of type string if not isinstance(formation, (str, type(None))): - raise TypeError('The provided formation must be of type string') + raise TypeError("The provided formation must be of type string") # Checking that show_planes is of type bool if not isinstance(show_planes, bool): - raise TypeError('Variable to show planes must be of type bool') + raise TypeError("Variable to show planes must be of type bool") # Checking that show_density_contours is of type bool if not isinstance(show_density_contours, bool): - raise TypeError('Variable to show density contours must be of type bool') + raise TypeError("Variable to show density contours must be of type bool") # Checking that show_density_contourf is of type bool if not isinstance(show_density_contourf, bool): - raise TypeError('Variable to show density contourf must be of type bool') + raise TypeError("Variable to show density contourf must be of type bool") # Checking that the provided method is of type str if not isinstance(method, str): - raise TypeError('The provided method must be of type string') + raise TypeError("The provided method must be of type string") # Checking that the provided sigma is of type float or int if not isinstance(sigma, (float, int)): - raise TypeError('Sigma must be of type float or int') + raise TypeError("Sigma must be of type float or int") # Checking that the provided cmap is of type string if not isinstance(cmap, str): - raise TypeError('Colormap must be provided as string') + raise TypeError("Colormap must be provided as string") # Converting dips to floats - if 'dip' in gdf: - gdf['dip'] = gdf['dip'].astype(float) + if "dip" in gdf: + gdf["dip"] = gdf["dip"].astype(float) # Converting azimuths to floats - if 'azimuth' in gdf: - gdf['azimuth'] = gdf['azimuth'].astype(float) + if "azimuth" in gdf: + gdf["azimuth"] = gdf["azimuth"].astype(float) # Converting formations to string - if 'formation' in gdf: - gdf['formation'] = gdf['formation'].astype(str) + if "formation" in gdf: + gdf["formation"] = gdf["formation"].astype(str) # Checking that dips do not exceed 90 degrees - if (gdf['dip'] > 90).any(): - raise ValueError('dip values exceed 90 degrees') + if (gdf["dip"] > 90).any(): + raise ValueError("dip values exceed 90 degrees") # Checking that azimuth do not exceed 360 degrees - if (gdf['azimuth'] > 360).any(): - raise ValueError('azimuth values exceed 360 degrees') + if (gdf["azimuth"] > 360).any(): + raise ValueError("azimuth values exceed 360 degrees") # Get unique formations - formations = gdf['formation'].unique() + formations = gdf["formation"].unique() # Define figure fig = plt.figure(figsize=(11, 5)) - ax = fig.add_subplot(121, projection='stereonet') + ax = fig.add_subplot(121, projection="stereonet") # Creating a set of points and planes for each formation for j, form in enumerate(formations): @@ -3020,64 +3183,81 @@ def plot_orientations(gdf: Union[gpd.geodataframe.GeoDataFrame, pd.DataFrame], color = "#%06x" % np.random.randint(0, 0xFFFFFF) # Select rows of the dataframe - gdf_form = gdf[gdf['formation'] == form] + gdf_form = gdf[gdf["formation"] == form] # Plot poles and planes - for i in range(len(gdf_form[['azimuth', 'dip']])): + for i in range(len(gdf_form[["azimuth", "dip"]])): # Plotting poles - ax.pole(gdf_form[['azimuth', 'dip']].iloc[i][0] - 90, - gdf_form[['azimuth', 'dip']].iloc[i][1], - color=color, - markersize=4, - markeredgewidth=0.5, - markeredgecolor='black', - label=formations[j]) + ax.pole( + gdf_form[["azimuth", "dip"]].iloc[i][0] - 90, + gdf_form[["azimuth", "dip"]].iloc[i][1], + color=color, + markersize=4, + markeredgewidth=0.5, + markeredgecolor="black", + label=formations[j], + ) # Plotting planes if show_planes: - ax.plane(gdf_form[['azimuth', 'dip']].iloc[i][0] - 90, - gdf_form[['azimuth', 'dip']].iloc[i][1], - linewidth=0.5, - color=color) + ax.plane( + gdf_form[["azimuth", "dip"]].iloc[i][0] - 90, + gdf_form[["azimuth", "dip"]].iloc[i][1], + linewidth=0.5, + color=color, + ) # Creating legend handles, labels = ax.get_legend_handles_labels() by_label = OrderedDict(zip(labels, handles)) - ax.legend(by_label.values(), by_label.keys(), loc='upper left', bbox_to_anchor=(1.05, 1)) + ax.legend( + by_label.values(), + by_label.keys(), + loc="upper left", + bbox_to_anchor=(1.05, 1), + ) # Creating density contours if show_density_contours: - ax.density_contour(gdf_form['azimuth'].to_numpy() - 90, - gdf_form['dip'].to_numpy(), - measurement='poles', - sigma=sigma, - method=method, - cmap=cmap) + ax.density_contour( + gdf_form["azimuth"].to_numpy() - 90, + gdf_form["dip"].to_numpy(), + measurement="poles", + sigma=sigma, + method=method, + cmap=cmap, + ) # Creating density contourf if show_density_contourf and formation is not None: - ax.density_contourf(gdf[gdf['formation'] == formation]['azimuth'].to_numpy() - 90, - gdf[gdf['formation'] == formation]['dip'].to_numpy(), - measurement='poles', - sigma=sigma, - method=method, - cmap=cmap) + ax.density_contourf( + gdf[gdf["formation"] == formation]["azimuth"].to_numpy() - 90, + gdf[gdf["formation"] == formation]["dip"].to_numpy(), + measurement="poles", + sigma=sigma, + method=method, + cmap=cmap, + ) elif not show_density_contourf and formation is not None: - raise ValueError('Formation must not be provided if show_density_contourf is set to False') + raise ValueError( + "Formation must not be provided if show_density_contourf is set to False" + ) elif show_density_contourf and formation is None: - raise ValueError('Formation name needed to plot density contourf') + raise ValueError("Formation name needed to plot density contourf") else: pass ax.grid() - ax.set_title('n = %d' % (len(gdf)), y=1.1) + ax.set_title("n = %d" % (len(gdf)), y=1.1) -def create_meshes_hypocenters(gdf: gpd.geodataframe.GeoDataFrame, - magnitude: str = 'Magnitude', - magnitude_factor: int = 200, - year: str = 'Year') -> pv.core.composite.MultiBlock: +def create_meshes_hypocenters( + gdf: gpd.geodataframe.GeoDataFrame, + magnitude: str = "Magnitude", + magnitude_factor: int = 200, + year: str = "Year", +) -> pv.core.composite.MultiBlock: """Plotting earthquake hypocenters with PyVista Parameters @@ -3137,40 +3317,52 @@ def create_meshes_hypocenters(gdf: gpd.geodataframe.GeoDataFrame, # Checking that the gdf is a GeoDataFrame if not isinstance(gdf, gpd.geodataframe.GeoDataFrame): - raise TypeError('Input data must be a GeoDataFrame') + raise TypeError("Input data must be a GeoDataFrame") # Checking that all geometry objects are points if not all(shapely.get_type_id(gdf.geometry) == 0): - raise TypeError('All geometry objects must be Shapely Points') + raise TypeError("All geometry objects must be Shapely Points") # Checking that all Shapely Objects are valid if not all(shapely.is_valid(gdf.geometry)): - raise ValueError('Not all Shapely Objects are valid objects') + raise ValueError("Not all Shapely Objects are valid objects") # Checking that no empty Shapely Objects are present if any(shapely.is_empty(gdf.geometry)): - raise ValueError('One or more Shapely objects are empty') + raise ValueError("One or more Shapely objects are empty") # Checking that X, Y and Z columns are present - if not {'X', 'Y', 'Z'}.issubset(gdf.columns): + if not {"X", "Y", "Z"}.issubset(gdf.columns): gdf = extract_xy(gdf=gdf) # Creating the spheres - spheres = pv.MultiBlock([pv.Sphere(radius=gdf.loc[i][magnitude] * magnitude_factor, - center=gdf.loc[i][['X', 'Y', 'Z']].tolist()) for i in range(len(gdf))]) + spheres = pv.MultiBlock( + [ + pv.Sphere( + radius=gdf.loc[i][magnitude] * magnitude_factor, + center=gdf.loc[i][["X", "Y", "Z"]].tolist(), + ) + for i in range(len(gdf)) + ] + ) # Adding magnitude array to spheres for i in range(len(spheres.keys())): - spheres[spheres.keys()[i]][magnitude] = np.zeros(len(spheres[spheres.keys()[i]].points)) + \ - gdf.loc[i][magnitude] + spheres[spheres.keys()[i]][magnitude] = ( + np.zeros(len(spheres[spheres.keys()[i]].points)) + gdf.loc[i][magnitude] + ) if year in gdf: for i in range(len(spheres.keys())): - spheres[spheres.keys()[i]][year] = np.zeros(len(spheres[spheres.keys()[i]].points)) + gdf.loc[i][year] + spheres[spheres.keys()[i]][year] = ( + np.zeros(len(spheres[spheres.keys()[i]].points)) + gdf.loc[i][year] + ) return spheres -def plane_through_hypocenters(spheres: pv.core.composite.MultiBlock) -> pv.core.pointset.PolyData: +def plane_through_hypocenters( + spheres: pv.core.composite.MultiBlock, +) -> pv.core.pointset.PolyData: """Fitting a plane through the hypocenters of earthquakes using Eigenvector analysis Parameters @@ -3215,10 +3407,12 @@ def plane_through_hypocenters(spheres: pv.core.composite.MultiBlock) -> pv.core. # Checking that the input data is a PyVista PolyData dataset if not isinstance(spheres, pv.core.composite.MultiBlock): - raise TypeError('Input data must be of type PyVista PolyData') + raise TypeError("Input data must be of type PyVista PolyData") # Creating array of centers of the spheres - centers = np.array([spheres.GetBlock(block).center for block in range(spheres.GetNumberOfBlocks())]) + centers = np.array( + [spheres.GetBlock(block).center for block in range(spheres.GetNumberOfBlocks())] + ) # Defining origin of plane as mean of the location of all hypocenters center = [centers[:, 0].mean(), centers[:, 1].mean(), centers[:, 2].mean()] @@ -3239,22 +3433,24 @@ def plane_through_hypocenters(spheres: pv.core.composite.MultiBlock) -> pv.core. # TODO: Refactor when refactoring GemGIS Data Object -def plot_data(geo_data, - show_basemap: bool = False, - show_geolmap: bool = False, - show_topo: bool = False, - show_interfaces: bool = False, - show_orientations: bool = False, - show_customsections: bool = False, - show_wms: bool = False, - show_legend: bool = True, - show_hillshades: bool = False, - show_slope: bool = False, - show_aspect: bool = False, - show_contours: bool = False, - add_to_extent: float = 0, - hide_topo_left: bool = False, - **kwargs): +def plot_data( + geo_data, + show_basemap: bool = False, + show_geolmap: bool = False, + show_topo: bool = False, + show_interfaces: bool = False, + show_orientations: bool = False, + show_customsections: bool = False, + show_wms: bool = False, + show_legend: bool = True, + show_hillshades: bool = False, + show_slope: bool = False, + show_aspect: bool = False, + show_contours: bool = False, + add_to_extent: float = 0, + hide_topo_left: bool = False, + **kwargs, +): """Plotting Input Data Parameters @@ -3333,103 +3529,133 @@ def plot_data(geo_data, import matplotlib.pyplot as plt except ModuleNotFoundError: raise ModuleNotFoundError( - 'Matplotlib package is not installed. Use pip install matplotlib to install the latest version') + "Matplotlib package is not installed. Use pip install matplotlib to install the latest version" + ) # Trying to import matplotlib but returning error if matplotlib is not installed try: from matplotlib.colors import ListedColormap except ModuleNotFoundError: raise ModuleNotFoundError( - 'Matplotlib package is not installed. Use pip install matplotlib to install the latest version') + "Matplotlib package is not installed. Use pip install matplotlib to install the latest version" + ) # Converting GeoDataFrame extent to list extent if isinstance(geo_data.extent, gpd.geodataframe.GeoDataFrame): geo_data.extent = set_extent(gdf=geo_data.extent) # Getting and checking kwargs - cmap_basemap = kwargs.get('cmap_basemap', 'gray') + cmap_basemap = kwargs.get("cmap_basemap", "gray") if not isinstance(cmap_basemap, (str, type(None))): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") # Getting and checking kwargs - cmap_geolmap = kwargs.get('cmap_geolmap', 'gray') + cmap_geolmap = kwargs.get("cmap_geolmap", "gray") if not isinstance(cmap_geolmap, (str, type(None), list)): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") - cmap_topo = kwargs.get('cmap_topo', 'gist_earth') + cmap_topo = kwargs.get("cmap_topo", "gist_earth") if not isinstance(cmap_topo, (str, type(None))): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") - cmap_contours = kwargs.get('cmap_contours', 'gist_earth') + cmap_contours = kwargs.get("cmap_contours", "gist_earth") if not isinstance(cmap_contours, (str, type(None))): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") - cmap_hillshades = kwargs.get('cmap_hillshades', 'gray') + cmap_hillshades = kwargs.get("cmap_hillshades", "gray") if not isinstance(cmap_hillshades, (str, type(None))): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") - cmap_slope = kwargs.get('cmap_slope', 'RdYlBu_r') + cmap_slope = kwargs.get("cmap_slope", "RdYlBu_r") if not isinstance(cmap_slope, (str, type(None))): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") - cmap_aspect = kwargs.get('cmap_aspect', 'twilight_shifted') + cmap_aspect = kwargs.get("cmap_aspect", "twilight_shifted") if not isinstance(cmap_aspect, (str, type(None))): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") - cmap_interfaces = kwargs.get('cmap_interfaces', 'gray') + cmap_interfaces = kwargs.get("cmap_interfaces", "gray") if not isinstance(cmap_interfaces, (list, str, type(None))): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") - cmap_orientations = kwargs.get('cmap_orientations', 'gray') + cmap_orientations = kwargs.get("cmap_orientations", "gray") if not isinstance(cmap_orientations, (list, str, type(None))): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") - cmap_wms = kwargs.get('cmap_wms', None) + cmap_wms = kwargs.get("cmap_wms", None) if not isinstance(cmap_wms, (str, type(None))): - raise TypeError('Colormap must be of type string') + raise TypeError("Colormap must be of type string") # Creating figure and axes - fig, (ax1, ax2) = plt.subplots(ncols=2, sharex='all', sharey='all', figsize=(20, 10)) + fig, (ax1, ax2) = plt.subplots( + ncols=2, sharex="all", sharey="all", figsize=(20, 10) + ) # Plot basemap if show_basemap: if not isinstance(geo_data.basemap, type(None)): - ax1.imshow(np.flipud(geo_data.basemap), origin='lower', cmap=cmap_basemap, extent=geo_data.extent[:4]) + ax1.imshow( + np.flipud(geo_data.basemap), + origin="lower", + cmap=cmap_basemap, + extent=geo_data.extent[:4], + ) # Plot geological map if show_geolmap: if isinstance(geo_data.geolmap, np.ndarray): - ax1.imshow(np.flipud(geo_data.geolmap), origin='lower', cmap=cmap_geolmap, extent=geo_data.extent[:4]) + ax1.imshow( + np.flipud(geo_data.geolmap), + origin="lower", + cmap=cmap_geolmap, + extent=geo_data.extent[:4], + ) else: - geo_data.geolmap.plot(ax=ax1, column='formation', alpha=0.75, legend=True, - cmap=ListedColormap(cmap_geolmap), aspect='equal') + geo_data.geolmap.plot( + ax=ax1, + column="formation", + alpha=0.75, + legend=True, + cmap=ListedColormap(cmap_geolmap), + aspect="equal", + ) # Plot WMS Layer if show_wms: if not isinstance(geo_data.wms, type(None)): - ax1.imshow(np.flipud(geo_data.wms), origin='lower', cmap=cmap_wms, extent=geo_data.extent[:4]) + ax1.imshow( + np.flipud(geo_data.wms), + origin="lower", + cmap=cmap_wms, + extent=geo_data.extent[:4], + ) # Plot topography if show_topo: if not hide_topo_left: if not isinstance(geo_data.raw_dem, type(None)): if isinstance(geo_data.raw_dem, np.ndarray): - ax1.imshow(np.flipud(geo_data.raw_dem), origin='lower', cmap=cmap_topo, extent=geo_data.extent[:4], - alpha=0.5) + ax1.imshow( + np.flipud(geo_data.raw_dem), + origin="lower", + cmap=cmap_topo, + extent=geo_data.extent[:4], + alpha=0.5, + ) # Set labels, grid and limits - ax1.set_xlabel('X') - ax1.set_ylabel('Y') + ax1.set_xlabel("X") + ax1.set_ylabel("Y") ax1.grid() ax1.set_ylim(geo_data.extent[2] - add_to_extent, geo_data.extent[3] + add_to_extent) ax1.set_xlim(geo_data.extent[0] - add_to_extent, geo_data.extent[1] + add_to_extent) @@ -3437,78 +3663,161 @@ def plot_data(geo_data, # Plot basemap if show_basemap: if not isinstance(geo_data.basemap, type(None)): - ax2.imshow(np.flipud(geo_data.basemap), origin='lower', cmap=cmap_basemap, extent=geo_data.extent[:4]) + ax2.imshow( + np.flipud(geo_data.basemap), + origin="lower", + cmap=cmap_basemap, + extent=geo_data.extent[:4], + ) # Plot geolmap if show_geolmap: if isinstance(geo_data.geolmap, np.ndarray): - ax2.imshow(np.flipud(geo_data.geolmap), origin='lower', cmap=cmap_geolmap, extent=geo_data.extent[:4]) + ax2.imshow( + np.flipud(geo_data.geolmap), + origin="lower", + cmap=cmap_geolmap, + extent=geo_data.extent[:4], + ) else: - geo_data.geolmap.plot(ax=ax2, column='formation', alpha=0.75, legend=True, - cmap=ListedColormap(cmap_geolmap), aspect='equal') + geo_data.geolmap.plot( + ax=ax2, + column="formation", + alpha=0.75, + legend=True, + cmap=ListedColormap(cmap_geolmap), + aspect="equal", + ) # Plot topography if show_topo: if not isinstance(geo_data.raw_dem, type(None)): if isinstance(geo_data.raw_dem, np.ndarray): - ax2.imshow(np.flipud(geo_data.raw_dem), origin='lower', cmap=cmap_topo, extent=geo_data.extent[:4], - alpha=0.5) + ax2.imshow( + np.flipud(geo_data.raw_dem), + origin="lower", + cmap=cmap_topo, + extent=geo_data.extent[:4], + alpha=0.5, + ) else: - geo_data.raw_dem.plot(ax=ax2, column='Z', legend=False, linewidth=5, cmap=cmap_topo, aspect='equal') + geo_data.raw_dem.plot( + ax=ax2, + column="Z", + legend=False, + linewidth=5, + cmap=cmap_topo, + aspect="equal", + ) # Plot contours if show_contours: if not isinstance(geo_data.contours, type(None)): - geo_data.contours.plot(ax=ax2, column='Z', legend=False, linewidth=5, cmap=cmap_contours, aspect='equal') + geo_data.contours.plot( + ax=ax2, + column="Z", + legend=False, + linewidth=5, + cmap=cmap_contours, + aspect="equal", + ) # Plot WMS Layer if show_wms: if not isinstance(geo_data.wms, type(None)): - ax2.imshow(np.flipud(geo_data.wms), origin='lower', cmap=cmap_wms, extent=geo_data.extent[:4]) + ax2.imshow( + np.flipud(geo_data.wms), + origin="lower", + cmap=cmap_wms, + extent=geo_data.extent[:4], + ) # Plot hillshades if show_hillshades: if not isinstance(geo_data.hillshades, type(None)): - ax2.imshow(np.flipud(geo_data.hillshades), origin='lower', cmap=cmap_hillshades, extent=geo_data.extent[:4]) + ax2.imshow( + np.flipud(geo_data.hillshades), + origin="lower", + cmap=cmap_hillshades, + extent=geo_data.extent[:4], + ) # Plot slope if show_slope: if not isinstance(geo_data.slope, type(None)): - ax2.imshow(np.flipud(geo_data.slope), origin='lower', cmap=cmap_slope, extent=geo_data.extent[:4]) + ax2.imshow( + np.flipud(geo_data.slope), + origin="lower", + cmap=cmap_slope, + extent=geo_data.extent[:4], + ) # Plot aspect if show_aspect: if not isinstance(geo_data.aspect, type(None)): - ax2.imshow(np.flipud(geo_data.aspect), origin='lower', cmap=cmap_aspect, extent=geo_data.extent[:4]) + ax2.imshow( + np.flipud(geo_data.aspect), + origin="lower", + cmap=cmap_aspect, + extent=geo_data.extent[:4], + ) # Plot interfaces and orientations if show_interfaces: if not isinstance(geo_data.raw_i, type(None)): - if all(geo_data.raw_i.geom_type == 'Point'): - geo_data.raw_i.plot(ax=ax2, column='formation', legend=show_legend, s=200, aspect='equal') - elif all(geo_data.raw_i.geom_type == 'LineString'): - geo_data.raw_i.plot(ax=ax2, column='formation', legend=show_legend, linewidth=5, - cmap=cmap_interfaces, aspect='equal') + if all(geo_data.raw_i.geom_type == "Point"): + geo_data.raw_i.plot( + ax=ax2, + column="formation", + legend=show_legend, + s=200, + aspect="equal", + ) + elif all(geo_data.raw_i.geom_type == "LineString"): + geo_data.raw_i.plot( + ax=ax2, + column="formation", + legend=show_legend, + linewidth=5, + cmap=cmap_interfaces, + aspect="equal", + ) else: if not cmap_interfaces: - geo_data.raw_i.plot(ax=ax2, column='formation', legend=show_legend, aspect='equal') + geo_data.raw_i.plot( + ax=ax2, column="formation", legend=show_legend, aspect="equal" + ) else: - geo_data.raw_i.plot(ax=ax2, column='formation', legend=show_legend, - cmap=ListedColormap(cmap_interfaces), aspect='equal') + geo_data.raw_i.plot( + ax=ax2, + column="formation", + legend=show_legend, + cmap=ListedColormap(cmap_interfaces), + aspect="equal", + ) if show_orientations: if not isinstance(geo_data.raw_o, type(None)): - geo_data.raw_o.plot(ax=ax2, column='formation', legend=True, s=200, aspect='equal', cmap=cmap_orientations) + geo_data.raw_o.plot( + ax=ax2, + column="formation", + legend=True, + s=200, + aspect="equal", + cmap=cmap_orientations, + ) # Plot custom sections if show_customsections: if not isinstance(geo_data.customsections, type(None)): - geo_data.customsections.plot(ax=ax2, legend=show_legend, linewidth=5, color='red', aspect='equal') + geo_data.customsections.plot( + ax=ax2, legend=show_legend, linewidth=5, color="red", aspect="equal" + ) # Set labels, grid and limits - ax2.set_xlabel('X') - ax2.set_ylabel('Y') + ax2.set_xlabel("X") + ax2.set_ylabel("Y") ax2.grid() ax2.set_ylim(geo_data.extent[2] - add_to_extent, geo_data.extent[3] + add_to_extent) ax2.set_xlim(geo_data.extent[0] - add_to_extent, geo_data.extent[1] + add_to_extent) @@ -3516,9 +3825,11 @@ def plot_data(geo_data, return fig, ax1, ax2 -def clip_seismic_data(seismic_data, - cdp_start: Union[int, type(None)] = None, - cdp_end: Union[int, type(None)] = None) -> pd.DataFrame: +def clip_seismic_data( + seismic_data, + cdp_start: Union[int, type(None)] = None, + cdp_end: Union[int, type(None)] = None, +) -> pd.DataFrame: """Clipping seismic data loaded with segysak to CDP defined start and end CDP values Parameters @@ -3548,20 +3859,22 @@ def clip_seismic_data(seismic_data, import xarray except ModuleNotFoundError: raise ModuleNotFoundError( - 'xarray package is not installed. Use pip install xarray to install the latest version') + "xarray package is not installed. Use pip install xarray to install the latest version" + ) # Checking that the seismic data is provided as xarray Dataset if not isinstance(seismic_data, xarray.core.dataset.Dataset): raise TypeError( - 'The seismic data must be provided as xarray Dataset loaded ideally with segysak and its segy_loader') + "The seismic data must be provided as xarray Dataset loaded ideally with segysak and its segy_loader" + ) # Checking that cdp_start ist of type int or None if not isinstance(cdp_start, (int, type(None))): - raise TypeError('The start CDP must be provided as int') + raise TypeError("The start CDP must be provided as int") # Checking that cdp_end ist of type int or None if not isinstance(cdp_end, (int, type(None))): - raise TypeError('The end CDP must be provided as int') + raise TypeError("The end CDP must be provided as int") # Converting xarray DataSet to DataFrame df_seismic_data = seismic_data.to_dataframe() @@ -3580,10 +3893,12 @@ def clip_seismic_data(seismic_data, return df_seismic_data_selection -def seismic_to_array(seismic_data, - cdp_start: Union[int, type(None)] = None, - cdp_end: Union[int, type(None)] = None, - max_depth: Union[int, float, type(None)] = None) -> np.ndarray: +def seismic_to_array( + seismic_data, + cdp_start: Union[int, type(None)] = None, + cdp_end: Union[int, type(None)] = None, + max_depth: Union[int, float, type(None)] = None, +) -> np.ndarray: """Converting seismic data loaded with segysak to a NumPy array Parameters @@ -3616,24 +3931,28 @@ def seismic_to_array(seismic_data, import xarray except ModuleNotFoundError: raise ModuleNotFoundError( - 'xarray package is not installed. Use pip install xarray to install the latest version') + "xarray package is not installed. Use pip install xarray to install the latest version" + ) # Checking that the seismic data is provided as xarray Dataset if not isinstance(seismic_data, xarray.core.dataset.Dataset): raise TypeError( - 'The seismic data must be provided as xarray Dataset loaded ideally with segysak and its segy_loader') + "The seismic data must be provided as xarray Dataset loaded ideally with segysak and its segy_loader" + ) # Checking that cdp_start ist of type int or None if not isinstance(cdp_start, (int, type(None))): - raise TypeError('The start CDP must be provided as int') + raise TypeError("The start CDP must be provided as int") # Checking that cdp_end ist of type int or None if not isinstance(cdp_end, (int, type(None))): - raise TypeError('The end CDP must be provided as int') + raise TypeError("The end CDP must be provided as int") # Checking that the max_depth is of type int or float if not isinstance(max_depth, (int, float, type(None))): - raise TypeError('The maximum depth in m or TWT must be provided as int or float') + raise TypeError( + "The maximum depth in m or TWT must be provided as int or float" + ) # Converting xarray DataSet to DataFrame df_seismic_data = seismic_data.to_dataframe() @@ -3647,46 +3966,54 @@ def seismic_to_array(seismic_data, cdp_end = int(df_seismic_data.index[-1][0]) # Clipping the seismic data - df_seismic_data_selection = clip_seismic_data(seismic_data=seismic_data, - cdp_start=cdp_start, - cdp_end=cdp_end) + df_seismic_data_selection = clip_seismic_data( + seismic_data=seismic_data, cdp_start=cdp_start, cdp_end=cdp_end + ) # Getting the number of rows per CDP and number of cdps len_cdp = int(len(df_seismic_data_selection.loc[cdp_start])) num_cdp = int(len(df_seismic_data_selection) / len_cdp) # Getting the seismic data - df_seismic_data_values = df_seismic_data_selection['data'].values + df_seismic_data_values = df_seismic_data_selection["data"].values # Reshaping the array - df_seismic_data_values_reshaped = df_seismic_data_values.reshape(num_cdp, - len_cdp) + df_seismic_data_values_reshaped = df_seismic_data_values.reshape(num_cdp, len_cdp) # Getting the max_depth if it is not provided if not max_depth: max_depth = df_seismic_data_selection.loc[cdp_start].index[-1] # Getting the number of samples based on max_depth - num_indices = int((len_cdp - 1) / ( - df_seismic_data_selection.loc[cdp_start].index.max() - df_seismic_data_selection.loc[ - cdp_start].index.min()) * max_depth + 1) + num_indices = int( + (len_cdp - 1) + / ( + df_seismic_data_selection.loc[cdp_start].index.max() + - df_seismic_data_selection.loc[cdp_start].index.min() + ) + * max_depth + + 1 + ) # Selecting samples - df_seismic_data_values_reshaped_selected = df_seismic_data_values_reshaped[:, :num_indices] + df_seismic_data_values_reshaped_selected = df_seismic_data_values_reshaped[ + :, :num_indices + ] return df_seismic_data_values_reshaped_selected -def seismic_to_mesh(seismic_data, - cdp_start: Union[int, type(None)] = None, - cdp_end: Union[int, type(None)] = None, - max_depth: Union[int, float] = None, - sampling_rate: Union[int, type(None)] = None, - shift: Union[int, float] = 0, - source_crs: Union[str, pyproj.crs.crs.CRS] = None, - target_crs: Union[str, pyproj.crs.crs.CRS] = None, - cdp_coords=None, - ) -> pv.core.pointset.StructuredGrid: +def seismic_to_mesh( + seismic_data, + cdp_start: Union[int, type(None)] = None, + cdp_end: Union[int, type(None)] = None, + max_depth: Union[int, float] = None, + sampling_rate: Union[int, type(None)] = None, + shift: Union[int, float] = 0, + source_crs: Union[str, pyproj.crs.crs.CRS] = None, + target_crs: Union[str, pyproj.crs.crs.CRS] = None, + cdp_coords=None, +) -> pv.core.pointset.StructuredGrid: """Converting seismic data loaded with segysak to a PyVista Mesh Parameters @@ -3731,42 +4058,46 @@ def seismic_to_mesh(seismic_data, # Checking that the sampling_rate is provided if not isinstance(sampling_rate, (int, type(None))): - raise TypeError('The sampling rate must be provided as integer') + raise TypeError("The sampling rate must be provided as integer") # Checking that the shift is of type int if not isinstance(shift, int): - raise TypeError('The shift must be provided as integer') + raise TypeError("The shift must be provided as integer") # Checking that the target_crs is of type string if not isinstance(source_crs, (str, type(None), pyproj.crs.crs.CRS)): - raise TypeError('source_crs must be of type string or a pyproj object') + raise TypeError("source_crs must be of type string or a pyproj object") # Checking that the target_crs is of type string if not isinstance(target_crs, (str, type(None), pyproj.crs.crs.CRS)): - raise TypeError('target_crs must be of type string or a pyproj object') + raise TypeError("target_crs must be of type string or a pyproj object") # Getting the sampling rate if it is not provided if not sampling_rate: - sampling_rate = seismic_data.to_dataframe().reset_index()['twt'][1] - \ - seismic_data.to_dataframe().reset_index()['twt'][0] + sampling_rate = ( + seismic_data.to_dataframe().reset_index()["twt"][1] + - seismic_data.to_dataframe().reset_index()["twt"][0] + ) # Getting the seismic data as array - seismic_data_array = seismic_to_array(seismic_data=seismic_data, - cdp_start=cdp_start, - cdp_end=cdp_end, - max_depth=max_depth) + seismic_data_array = seismic_to_array( + seismic_data=seismic_data, + cdp_start=cdp_start, + cdp_end=cdp_end, + max_depth=max_depth, + ) # Getting the number of traces and samples (columns and rows) ntraces, nsamples = seismic_data_array.shape # Clipping the seismic data - seismic_data = clip_seismic_data(seismic_data=seismic_data, - cdp_start=cdp_start, - cdp_end=cdp_end) + seismic_data = clip_seismic_data( + seismic_data=seismic_data, cdp_start=cdp_start, cdp_end=cdp_end + ) # Getting the CDP coordinates try: - cdp_coordinates = seismic_data[['cdp_x', 'cdp_y']].drop_duplicates().values + cdp_coordinates = seismic_data[["cdp_x", "cdp_y"]].drop_duplicates().values cdp_x = cdp_coordinates[:, 0] cdp_y = cdp_coordinates[:, 1] @@ -3777,12 +4108,13 @@ def seismic_to_mesh(seismic_data, cdp_coordinates = cdp_coords # Converting the coordinates if target_crs and source_crs: - gdf_coords = gpd.GeoDataFrame(geometry=gpd.points_from_xy(x=cdp_x, - y=cdp_y), - crs=source_crs).to_crs(target_crs) + gdf_coords = gpd.GeoDataFrame( + geometry=gpd.points_from_xy(x=cdp_x, y=cdp_y), crs=source_crs + ).to_crs(target_crs) - cdp_coordinates = np.array([gdf_coords.geometry.x.values, - gdf_coords.geometry.y.values]).T + cdp_coordinates = np.array( + [gdf_coords.geometry.x.values, gdf_coords.geometry.y.values] + ).T # Creating the path seismic_path = np.c_[cdp_coordinates, np.zeros(len(cdp_coordinates))] @@ -3819,262 +4151,266 @@ def get_seismic_cmap() -> matplotlib.colors.ListedColormap: """ - seismic = np.array([[0.63137255, 1., 1.], - [0.62745098, 0.97647059, 0.99607843], - [0.61960784, 0.95686275, 0.98823529], - [0.61568627, 0.93333333, 0.98431373], - [0.60784314, 0.91372549, 0.98039216], - [0.60392157, 0.89019608, 0.97254902], - [0.59607843, 0.87058824, 0.96862745], - [0.59215686, 0.85098039, 0.96078431], - [0.58431373, 0.83137255, 0.95686275], - [0.58039216, 0.81568627, 0.94901961], - [0.57254902, 0.79607843, 0.94117647], - [0.56862745, 0.77647059, 0.9372549], - [0.56078431, 0.76078431, 0.92941176], - [0.55686275, 0.74509804, 0.92156863], - [0.54901961, 0.72941176, 0.91764706], - [0.54509804, 0.70980392, 0.90980392], - [0.5372549, 0.69411765, 0.90196078], - [0.53333333, 0.68235294, 0.89803922], - [0.5254902, 0.66666667, 0.89019608], - [0.52156863, 0.65098039, 0.88235294], - [0.51372549, 0.63921569, 0.87843137], - [0.50980392, 0.62352941, 0.87058824], - [0.50196078, 0.61176471, 0.8627451], - [0.49803922, 0.59607843, 0.85490196], - [0.49411765, 0.58431373, 0.85098039], - [0.48627451, 0.57254902, 0.84313725], - [0.48235294, 0.56078431, 0.83529412], - [0.47843137, 0.54901961, 0.82745098], - [0.4745098, 0.54117647, 0.82352941], - [0.46666667, 0.52941176, 0.81568627], - [0.4627451, 0.51764706, 0.80784314], - [0.45882353, 0.50980392, 0.8], - [0.45490196, 0.49803922, 0.79607843], - [0.45098039, 0.49019608, 0.78823529], - [0.44705882, 0.48235294, 0.78039216], - [0.44313725, 0.4745098, 0.77254902], - [0.43921569, 0.46666667, 0.76862745], - [0.43529412, 0.45882353, 0.76078431], - [0.43529412, 0.45098039, 0.75294118], - [0.43137255, 0.44313725, 0.74901961], - [0.42745098, 0.43529412, 0.74117647], - [0.42352941, 0.43137255, 0.7372549], - [0.42352941, 0.42352941, 0.72941176], - [0.41960784, 0.41960784, 0.72156863], - [0.41960784, 0.41176471, 0.71764706], - [0.41568627, 0.40784314, 0.70980392], - [0.41568627, 0.4, 0.70588235], - [0.41176471, 0.39607843, 0.69803922], - [0.41176471, 0.39215686, 0.69411765], - [0.41176471, 0.38823529, 0.68627451], - [0.40784314, 0.38431373, 0.68235294], - [0.40784314, 0.38039216, 0.67843137], - [0.40784314, 0.38039216, 0.67058824], - [0.40784314, 0.37647059, 0.66666667], - [0.40784314, 0.37254902, 0.6627451], - [0.40392157, 0.37254902, 0.65490196], - [0.40392157, 0.36862745, 0.65098039], - [0.40392157, 0.36862745, 0.64705882], - [0.40392157, 0.36470588, 0.64313725], - [0.40784314, 0.36470588, 0.63529412], - [0.40784314, 0.36470588, 0.63137255], - [0.40784314, 0.36078431, 0.62745098], - [0.40784314, 0.36078431, 0.62352941], - [0.40784314, 0.36078431, 0.61960784], - [0.40784314, 0.36078431, 0.61568627], - [0.41176471, 0.36078431, 0.61176471], - [0.41176471, 0.36078431, 0.60784314], - [0.41176471, 0.36470588, 0.60392157], - [0.41568627, 0.36470588, 0.60392157], - [0.41568627, 0.36470588, 0.6], - [0.41960784, 0.36862745, 0.59607843], - [0.41960784, 0.36862745, 0.59215686], - [0.42352941, 0.36862745, 0.59215686], - [0.42352941, 0.37254902, 0.58823529], - [0.42745098, 0.37647059, 0.58431373], - [0.43137255, 0.37647059, 0.58431373], - [0.43137255, 0.38039216, 0.58039216], - [0.43529412, 0.38431373, 0.58039216], - [0.43921569, 0.38823529, 0.57647059], - [0.44313725, 0.39215686, 0.57647059], - [0.44705882, 0.39607843, 0.57647059], - [0.44705882, 0.4, 0.57254902], - [0.45098039, 0.40392157, 0.57254902], - [0.45490196, 0.40784314, 0.57254902], - [0.45882353, 0.41176471, 0.57254902], - [0.4627451, 0.41568627, 0.57254902], - [0.46666667, 0.41960784, 0.57254902], - [0.47058824, 0.42745098, 0.57254902], - [0.4745098, 0.43137255, 0.57254902], - [0.48235294, 0.43529412, 0.57254902], - [0.48627451, 0.44313725, 0.57254902], - [0.49019608, 0.44705882, 0.57254902], - [0.49411765, 0.45490196, 0.57254902], - [0.50196078, 0.4627451, 0.57647059], - [0.50588235, 0.46666667, 0.57647059], - [0.50980392, 0.4745098, 0.58039216], - [0.51764706, 0.48235294, 0.58039216], - [0.52156863, 0.49019608, 0.58431373], - [0.52941176, 0.49803922, 0.58431373], - [0.53333333, 0.50196078, 0.58823529], - [0.54117647, 0.50980392, 0.59215686], - [0.54509804, 0.51764706, 0.59607843], - [0.55294118, 0.52941176, 0.59607843], - [0.56078431, 0.5372549, 0.6], - [0.56862745, 0.54509804, 0.60392157], - [0.57647059, 0.55294118, 0.61176471], - [0.58039216, 0.56078431, 0.61568627], - [0.58823529, 0.57254902, 0.61960784], - [0.59607843, 0.58039216, 0.62352941], - [0.60392157, 0.58823529, 0.63137255], - [0.61176471, 0.6, 0.63529412], - [0.62352941, 0.60784314, 0.64313725], - [0.63137255, 0.61960784, 0.64705882], - [0.63921569, 0.62745098, 0.65490196], - [0.64705882, 0.63921569, 0.6627451], - [0.65882353, 0.65098039, 0.67058824], - [0.66666667, 0.65882353, 0.67843137], - [0.67843137, 0.67058824, 0.68627451], - [0.68627451, 0.68235294, 0.69411765], - [0.69803922, 0.69411765, 0.70196078], - [0.70588235, 0.70588235, 0.71372549], - [0.71764706, 0.71372549, 0.72156863], - [0.72941176, 0.7254902, 0.73333333], - [0.74117647, 0.7372549, 0.74117647], - [0.75294118, 0.74901961, 0.75294118], - [0.76470588, 0.76470588, 0.76470588], - [0.77647059, 0.77647059, 0.77647059], - [0.78823529, 0.78823529, 0.78823529], - [0.79215686, 0.78823529, 0.78039216], - [0.78431373, 0.77254902, 0.76078431], - [0.77647059, 0.76078431, 0.74117647], - [0.76862745, 0.74901961, 0.7254902], - [0.76078431, 0.73333333, 0.70588235], - [0.75294118, 0.72156863, 0.68627451], - [0.74509804, 0.70980392, 0.67058824], - [0.74117647, 0.69803922, 0.65490196], - [0.73333333, 0.68627451, 0.63529412], - [0.72941176, 0.6745098, 0.61960784], - [0.72156863, 0.66666667, 0.60392157], - [0.71764706, 0.65490196, 0.58823529], - [0.70980392, 0.64313725, 0.57254902], - [0.70588235, 0.63137255, 0.55686275], - [0.70196078, 0.62352941, 0.54509804], - [0.69411765, 0.61176471, 0.52941176], - [0.69019608, 0.60392157, 0.51372549], - [0.68627451, 0.59215686, 0.50196078], - [0.68235294, 0.58431373, 0.48627451], - [0.67843137, 0.57647059, 0.4745098], - [0.6745098, 0.56470588, 0.4627451], - [0.67058824, 0.55686275, 0.45098039], - [0.66666667, 0.54901961, 0.43921569], - [0.66666667, 0.54117647, 0.42745098], - [0.6627451, 0.53333333, 0.41568627], - [0.65882353, 0.5254902, 0.40392157], - [0.65490196, 0.51764706, 0.39215686], - [0.65490196, 0.50980392, 0.38039216], - [0.65098039, 0.50196078, 0.36862745], - [0.64705882, 0.49803922, 0.36078431], - [0.64705882, 0.49019608, 0.34901961], - [0.64313725, 0.48235294, 0.34117647], - [0.64313725, 0.47843137, 0.32941176], - [0.64313725, 0.47058824, 0.32156863], - [0.63921569, 0.46666667, 0.31372549], - [0.63921569, 0.45882353, 0.30196078], - [0.63921569, 0.45490196, 0.29411765], - [0.63529412, 0.45098039, 0.28627451], - [0.63529412, 0.44313725, 0.27843137], - [0.63529412, 0.43921569, 0.27058824], - [0.63529412, 0.43529412, 0.2627451], - [0.63529412, 0.43137255, 0.25490196], - [0.63529412, 0.42745098, 0.24705882], - [0.63529412, 0.42352941, 0.23921569], - [0.63529412, 0.41960784, 0.23137255], - [0.63529412, 0.41568627, 0.22745098], - [0.63529412, 0.41176471, 0.21960784], - [0.63529412, 0.40784314, 0.21176471], - [0.63921569, 0.40392157, 0.20784314], - [0.63921569, 0.40392157, 0.2], - [0.63921569, 0.4, 0.19607843], - [0.63921569, 0.39607843, 0.18823529], - [0.64313725, 0.39607843, 0.18431373], - [0.64313725, 0.39607843, 0.17647059], - [0.64705882, 0.39215686, 0.17254902], - [0.64705882, 0.39215686, 0.16862745], - [0.65098039, 0.38823529, 0.16078431], - [0.65098039, 0.38823529, 0.15686275], - [0.65490196, 0.38823529, 0.15294118], - [0.65490196, 0.38823529, 0.14901961], - [0.65882353, 0.38823529, 0.14117647], - [0.6627451, 0.38823529, 0.1372549], - [0.66666667, 0.38823529, 0.13333333], - [0.66666667, 0.38823529, 0.12941176], - [0.67058824, 0.38823529, 0.1254902], - [0.6745098, 0.39215686, 0.12156863], - [0.67843137, 0.39215686, 0.11764706], - [0.68235294, 0.39215686, 0.11372549], - [0.68627451, 0.39607843, 0.10980392], - [0.69019608, 0.39607843, 0.10588235], - [0.69411765, 0.4, 0.10196078], - [0.69803922, 0.4, 0.09803922], - [0.70196078, 0.40392157, 0.09411765], - [0.70588235, 0.40784314, 0.09019608], - [0.70980392, 0.41176471, 0.08627451], - [0.71372549, 0.41568627, 0.08235294], - [0.71764706, 0.41960784, 0.07843137], - [0.7254902, 0.42352941, 0.0745098], - [0.72941176, 0.42745098, 0.07058824], - [0.73333333, 0.43137255, 0.06666667], - [0.7372549, 0.43529412, 0.0627451], - [0.74509804, 0.43921569, 0.05882353], - [0.74901961, 0.44705882, 0.05882353], - [0.75294118, 0.45098039, 0.05490196], - [0.76078431, 0.45882353, 0.05098039], - [0.76470588, 0.4627451, 0.04705882], - [0.77254902, 0.47058824, 0.04313725], - [0.77647059, 0.47843137, 0.03921569], - [0.78431373, 0.48627451, 0.03529412], - [0.78823529, 0.49411765, 0.03137255], - [0.79607843, 0.50196078, 0.02745098], - [0.8, 0.50980392, 0.02352941], - [0.80784314, 0.51764706, 0.01960784], - [0.81176471, 0.5254902, 0.01568627], - [0.81960784, 0.53333333, 0.01568627], - [0.82352941, 0.54509804, 0.01176471], - [0.83137255, 0.55294118, 0.00784314], - [0.83921569, 0.56078431, 0.00392157], - [0.84313725, 0.57254902, 0.00392157], - [0.85098039, 0.58431373, 0.], - [0.85490196, 0.59215686, 0.], - [0.8627451, 0.60392157, 0.], - [0.86666667, 0.61568627, 0.], - [0.8745098, 0.62745098, 0.], - [0.88235294, 0.63921569, 0.], - [0.88627451, 0.65098039, 0.], - [0.89411765, 0.6627451, 0.], - [0.89803922, 0.67843137, 0.], - [0.90588235, 0.69019608, 0.], - [0.91372549, 0.70196078, 0.], - [0.91764706, 0.71764706, 0.], - [0.9254902, 0.73333333, 0.], - [0.92941176, 0.74509804, 0.], - [0.93333333, 0.76078431, 0.], - [0.94117647, 0.77647059, 0.], - [0.94509804, 0.79215686, 0.], - [0.95294118, 0.80784314, 0.], - [0.95686275, 0.82352941, 0.], - [0.96078431, 0.83921569, 0.], - [0.96862745, 0.85490196, 0.], - [0.97254902, 0.87058824, 0.], - [0.97647059, 0.89019608, 0.], - [0.98039216, 0.90588235, 0.], - [0.98431373, 0.9254902, 0.], - [0.98823529, 0.94509804, 0.], - [0.99215686, 0.96078431, 0.], - [0.99607843, 0.98039216, 0.], - [1., 1., 0.]]) + seismic = np.array( + [ + [0.63137255, 1.0, 1.0], + [0.62745098, 0.97647059, 0.99607843], + [0.61960784, 0.95686275, 0.98823529], + [0.61568627, 0.93333333, 0.98431373], + [0.60784314, 0.91372549, 0.98039216], + [0.60392157, 0.89019608, 0.97254902], + [0.59607843, 0.87058824, 0.96862745], + [0.59215686, 0.85098039, 0.96078431], + [0.58431373, 0.83137255, 0.95686275], + [0.58039216, 0.81568627, 0.94901961], + [0.57254902, 0.79607843, 0.94117647], + [0.56862745, 0.77647059, 0.9372549], + [0.56078431, 0.76078431, 0.92941176], + [0.55686275, 0.74509804, 0.92156863], + [0.54901961, 0.72941176, 0.91764706], + [0.54509804, 0.70980392, 0.90980392], + [0.5372549, 0.69411765, 0.90196078], + [0.53333333, 0.68235294, 0.89803922], + [0.5254902, 0.66666667, 0.89019608], + [0.52156863, 0.65098039, 0.88235294], + [0.51372549, 0.63921569, 0.87843137], + [0.50980392, 0.62352941, 0.87058824], + [0.50196078, 0.61176471, 0.8627451], + [0.49803922, 0.59607843, 0.85490196], + [0.49411765, 0.58431373, 0.85098039], + [0.48627451, 0.57254902, 0.84313725], + [0.48235294, 0.56078431, 0.83529412], + [0.47843137, 0.54901961, 0.82745098], + [0.4745098, 0.54117647, 0.82352941], + [0.46666667, 0.52941176, 0.81568627], + [0.4627451, 0.51764706, 0.80784314], + [0.45882353, 0.50980392, 0.8], + [0.45490196, 0.49803922, 0.79607843], + [0.45098039, 0.49019608, 0.78823529], + [0.44705882, 0.48235294, 0.78039216], + [0.44313725, 0.4745098, 0.77254902], + [0.43921569, 0.46666667, 0.76862745], + [0.43529412, 0.45882353, 0.76078431], + [0.43529412, 0.45098039, 0.75294118], + [0.43137255, 0.44313725, 0.74901961], + [0.42745098, 0.43529412, 0.74117647], + [0.42352941, 0.43137255, 0.7372549], + [0.42352941, 0.42352941, 0.72941176], + [0.41960784, 0.41960784, 0.72156863], + [0.41960784, 0.41176471, 0.71764706], + [0.41568627, 0.40784314, 0.70980392], + [0.41568627, 0.4, 0.70588235], + [0.41176471, 0.39607843, 0.69803922], + [0.41176471, 0.39215686, 0.69411765], + [0.41176471, 0.38823529, 0.68627451], + [0.40784314, 0.38431373, 0.68235294], + [0.40784314, 0.38039216, 0.67843137], + [0.40784314, 0.38039216, 0.67058824], + [0.40784314, 0.37647059, 0.66666667], + [0.40784314, 0.37254902, 0.6627451], + [0.40392157, 0.37254902, 0.65490196], + [0.40392157, 0.36862745, 0.65098039], + [0.40392157, 0.36862745, 0.64705882], + [0.40392157, 0.36470588, 0.64313725], + [0.40784314, 0.36470588, 0.63529412], + [0.40784314, 0.36470588, 0.63137255], + [0.40784314, 0.36078431, 0.62745098], + [0.40784314, 0.36078431, 0.62352941], + [0.40784314, 0.36078431, 0.61960784], + [0.40784314, 0.36078431, 0.61568627], + [0.41176471, 0.36078431, 0.61176471], + [0.41176471, 0.36078431, 0.60784314], + [0.41176471, 0.36470588, 0.60392157], + [0.41568627, 0.36470588, 0.60392157], + [0.41568627, 0.36470588, 0.6], + [0.41960784, 0.36862745, 0.59607843], + [0.41960784, 0.36862745, 0.59215686], + [0.42352941, 0.36862745, 0.59215686], + [0.42352941, 0.37254902, 0.58823529], + [0.42745098, 0.37647059, 0.58431373], + [0.43137255, 0.37647059, 0.58431373], + [0.43137255, 0.38039216, 0.58039216], + [0.43529412, 0.38431373, 0.58039216], + [0.43921569, 0.38823529, 0.57647059], + [0.44313725, 0.39215686, 0.57647059], + [0.44705882, 0.39607843, 0.57647059], + [0.44705882, 0.4, 0.57254902], + [0.45098039, 0.40392157, 0.57254902], + [0.45490196, 0.40784314, 0.57254902], + [0.45882353, 0.41176471, 0.57254902], + [0.4627451, 0.41568627, 0.57254902], + [0.46666667, 0.41960784, 0.57254902], + [0.47058824, 0.42745098, 0.57254902], + [0.4745098, 0.43137255, 0.57254902], + [0.48235294, 0.43529412, 0.57254902], + [0.48627451, 0.44313725, 0.57254902], + [0.49019608, 0.44705882, 0.57254902], + [0.49411765, 0.45490196, 0.57254902], + [0.50196078, 0.4627451, 0.57647059], + [0.50588235, 0.46666667, 0.57647059], + [0.50980392, 0.4745098, 0.58039216], + [0.51764706, 0.48235294, 0.58039216], + [0.52156863, 0.49019608, 0.58431373], + [0.52941176, 0.49803922, 0.58431373], + [0.53333333, 0.50196078, 0.58823529], + [0.54117647, 0.50980392, 0.59215686], + [0.54509804, 0.51764706, 0.59607843], + [0.55294118, 0.52941176, 0.59607843], + [0.56078431, 0.5372549, 0.6], + [0.56862745, 0.54509804, 0.60392157], + [0.57647059, 0.55294118, 0.61176471], + [0.58039216, 0.56078431, 0.61568627], + [0.58823529, 0.57254902, 0.61960784], + [0.59607843, 0.58039216, 0.62352941], + [0.60392157, 0.58823529, 0.63137255], + [0.61176471, 0.6, 0.63529412], + [0.62352941, 0.60784314, 0.64313725], + [0.63137255, 0.61960784, 0.64705882], + [0.63921569, 0.62745098, 0.65490196], + [0.64705882, 0.63921569, 0.6627451], + [0.65882353, 0.65098039, 0.67058824], + [0.66666667, 0.65882353, 0.67843137], + [0.67843137, 0.67058824, 0.68627451], + [0.68627451, 0.68235294, 0.69411765], + [0.69803922, 0.69411765, 0.70196078], + [0.70588235, 0.70588235, 0.71372549], + [0.71764706, 0.71372549, 0.72156863], + [0.72941176, 0.7254902, 0.73333333], + [0.74117647, 0.7372549, 0.74117647], + [0.75294118, 0.74901961, 0.75294118], + [0.76470588, 0.76470588, 0.76470588], + [0.77647059, 0.77647059, 0.77647059], + [0.78823529, 0.78823529, 0.78823529], + [0.79215686, 0.78823529, 0.78039216], + [0.78431373, 0.77254902, 0.76078431], + [0.77647059, 0.76078431, 0.74117647], + [0.76862745, 0.74901961, 0.7254902], + [0.76078431, 0.73333333, 0.70588235], + [0.75294118, 0.72156863, 0.68627451], + [0.74509804, 0.70980392, 0.67058824], + [0.74117647, 0.69803922, 0.65490196], + [0.73333333, 0.68627451, 0.63529412], + [0.72941176, 0.6745098, 0.61960784], + [0.72156863, 0.66666667, 0.60392157], + [0.71764706, 0.65490196, 0.58823529], + [0.70980392, 0.64313725, 0.57254902], + [0.70588235, 0.63137255, 0.55686275], + [0.70196078, 0.62352941, 0.54509804], + [0.69411765, 0.61176471, 0.52941176], + [0.69019608, 0.60392157, 0.51372549], + [0.68627451, 0.59215686, 0.50196078], + [0.68235294, 0.58431373, 0.48627451], + [0.67843137, 0.57647059, 0.4745098], + [0.6745098, 0.56470588, 0.4627451], + [0.67058824, 0.55686275, 0.45098039], + [0.66666667, 0.54901961, 0.43921569], + [0.66666667, 0.54117647, 0.42745098], + [0.6627451, 0.53333333, 0.41568627], + [0.65882353, 0.5254902, 0.40392157], + [0.65490196, 0.51764706, 0.39215686], + [0.65490196, 0.50980392, 0.38039216], + [0.65098039, 0.50196078, 0.36862745], + [0.64705882, 0.49803922, 0.36078431], + [0.64705882, 0.49019608, 0.34901961], + [0.64313725, 0.48235294, 0.34117647], + [0.64313725, 0.47843137, 0.32941176], + [0.64313725, 0.47058824, 0.32156863], + [0.63921569, 0.46666667, 0.31372549], + [0.63921569, 0.45882353, 0.30196078], + [0.63921569, 0.45490196, 0.29411765], + [0.63529412, 0.45098039, 0.28627451], + [0.63529412, 0.44313725, 0.27843137], + [0.63529412, 0.43921569, 0.27058824], + [0.63529412, 0.43529412, 0.2627451], + [0.63529412, 0.43137255, 0.25490196], + [0.63529412, 0.42745098, 0.24705882], + [0.63529412, 0.42352941, 0.23921569], + [0.63529412, 0.41960784, 0.23137255], + [0.63529412, 0.41568627, 0.22745098], + [0.63529412, 0.41176471, 0.21960784], + [0.63529412, 0.40784314, 0.21176471], + [0.63921569, 0.40392157, 0.20784314], + [0.63921569, 0.40392157, 0.2], + [0.63921569, 0.4, 0.19607843], + [0.63921569, 0.39607843, 0.18823529], + [0.64313725, 0.39607843, 0.18431373], + [0.64313725, 0.39607843, 0.17647059], + [0.64705882, 0.39215686, 0.17254902], + [0.64705882, 0.39215686, 0.16862745], + [0.65098039, 0.38823529, 0.16078431], + [0.65098039, 0.38823529, 0.15686275], + [0.65490196, 0.38823529, 0.15294118], + [0.65490196, 0.38823529, 0.14901961], + [0.65882353, 0.38823529, 0.14117647], + [0.6627451, 0.38823529, 0.1372549], + [0.66666667, 0.38823529, 0.13333333], + [0.66666667, 0.38823529, 0.12941176], + [0.67058824, 0.38823529, 0.1254902], + [0.6745098, 0.39215686, 0.12156863], + [0.67843137, 0.39215686, 0.11764706], + [0.68235294, 0.39215686, 0.11372549], + [0.68627451, 0.39607843, 0.10980392], + [0.69019608, 0.39607843, 0.10588235], + [0.69411765, 0.4, 0.10196078], + [0.69803922, 0.4, 0.09803922], + [0.70196078, 0.40392157, 0.09411765], + [0.70588235, 0.40784314, 0.09019608], + [0.70980392, 0.41176471, 0.08627451], + [0.71372549, 0.41568627, 0.08235294], + [0.71764706, 0.41960784, 0.07843137], + [0.7254902, 0.42352941, 0.0745098], + [0.72941176, 0.42745098, 0.07058824], + [0.73333333, 0.43137255, 0.06666667], + [0.7372549, 0.43529412, 0.0627451], + [0.74509804, 0.43921569, 0.05882353], + [0.74901961, 0.44705882, 0.05882353], + [0.75294118, 0.45098039, 0.05490196], + [0.76078431, 0.45882353, 0.05098039], + [0.76470588, 0.4627451, 0.04705882], + [0.77254902, 0.47058824, 0.04313725], + [0.77647059, 0.47843137, 0.03921569], + [0.78431373, 0.48627451, 0.03529412], + [0.78823529, 0.49411765, 0.03137255], + [0.79607843, 0.50196078, 0.02745098], + [0.8, 0.50980392, 0.02352941], + [0.80784314, 0.51764706, 0.01960784], + [0.81176471, 0.5254902, 0.01568627], + [0.81960784, 0.53333333, 0.01568627], + [0.82352941, 0.54509804, 0.01176471], + [0.83137255, 0.55294118, 0.00784314], + [0.83921569, 0.56078431, 0.00392157], + [0.84313725, 0.57254902, 0.00392157], + [0.85098039, 0.58431373, 0.0], + [0.85490196, 0.59215686, 0.0], + [0.8627451, 0.60392157, 0.0], + [0.86666667, 0.61568627, 0.0], + [0.8745098, 0.62745098, 0.0], + [0.88235294, 0.63921569, 0.0], + [0.88627451, 0.65098039, 0.0], + [0.89411765, 0.6627451, 0.0], + [0.89803922, 0.67843137, 0.0], + [0.90588235, 0.69019608, 0.0], + [0.91372549, 0.70196078, 0.0], + [0.91764706, 0.71764706, 0.0], + [0.9254902, 0.73333333, 0.0], + [0.92941176, 0.74509804, 0.0], + [0.93333333, 0.76078431, 0.0], + [0.94117647, 0.77647059, 0.0], + [0.94509804, 0.79215686, 0.0], + [0.95294118, 0.80784314, 0.0], + [0.95686275, 0.82352941, 0.0], + [0.96078431, 0.83921569, 0.0], + [0.96862745, 0.85490196, 0.0], + [0.97254902, 0.87058824, 0.0], + [0.97647059, 0.89019608, 0.0], + [0.98039216, 0.90588235, 0.0], + [0.98431373, 0.9254902, 0.0], + [0.98823529, 0.94509804, 0.0], + [0.99215686, 0.96078431, 0.0], + [0.99607843, 0.98039216, 0.0], + [1.0, 1.0, 0.0], + ] + ) cmap_seismic = matplotlib.colors.ListedColormap(seismic) @@ -4083,11 +4419,10 @@ def get_seismic_cmap() -> matplotlib.colors.ListedColormap: gradient = np.vstack((gradient, gradient)) fig, ax = plt.subplots(nrows=1, figsize=(6, 1)) - fig.subplots_adjust(top=0.5, bottom=0.15, - left=0.2, right=1) - ax.set_title('Seismic Colorbar', fontsize=14) + fig.subplots_adjust(top=0.5, bottom=0.15, left=0.2, right=1) + ax.set_title("Seismic Colorbar", fontsize=14) - ax.imshow(gradient, aspect='auto', cmap=cmap_seismic) + ax.imshow(gradient, aspect="auto", cmap=cmap_seismic) # Turn off *all* ticks & spines, not just the ones with colormaps. ax.set_axis_off() @@ -4108,262 +4443,266 @@ def get_batlow_cmap() -> matplotlib.colors.ListedColormap: """ - batlow = np.array([[0.005193, 0.098238, 0.349842], - [0.009065, 0.104487, 0.350933], - [0.012963, 0.110779, 0.351992], - [0.016530, 0.116913, 0.353070], - [0.019936, 0.122985, 0.354120], - [0.023189, 0.129035, 0.355182], - [0.026291, 0.135044, 0.356210], - [0.029245, 0.140964, 0.357239], - [0.032053, 0.146774, 0.358239], - [0.034853, 0.152558, 0.359233], - [0.037449, 0.158313, 0.360216], - [0.039845, 0.163978, 0.361187], - [0.042104, 0.169557, 0.362151], - [0.044069, 0.175053, 0.363084], - [0.045905, 0.180460, 0.364007], - [0.047665, 0.185844, 0.364915], - [0.049378, 0.191076, 0.365810], - [0.050795, 0.196274, 0.366684], - [0.052164, 0.201323, 0.367524], - [0.053471, 0.206357, 0.368370], - [0.054721, 0.211234, 0.369184], - [0.055928, 0.216046, 0.369974], - [0.057033, 0.220754, 0.370750], - [0.058032, 0.225340, 0.371509], - [0.059164, 0.229842, 0.372252], - [0.060167, 0.234299, 0.372978], - [0.061052, 0.238625, 0.373691], - [0.062060, 0.242888, 0.374386], - [0.063071, 0.247085, 0.375050], - [0.063982, 0.251213, 0.375709], - [0.064936, 0.255264, 0.376362], - [0.065903, 0.259257, 0.376987], - [0.066899, 0.263188, 0.377594], - [0.067921, 0.267056, 0.378191], - [0.069002, 0.270922, 0.378774], - [0.070001, 0.274713, 0.379342], - [0.071115, 0.278497, 0.379895], - [0.072192, 0.282249, 0.380434], - [0.073440, 0.285942, 0.380957], - [0.074595, 0.289653, 0.381452], - [0.075833, 0.293321, 0.381922], - [0.077136, 0.296996, 0.382376], - [0.078517, 0.300622, 0.382814], - [0.079984, 0.304252, 0.383224], - [0.081553, 0.307858, 0.383598], - [0.083082, 0.311461, 0.383936], - [0.084778, 0.315043, 0.384240], - [0.086503, 0.318615, 0.384506], - [0.088353, 0.322167, 0.384731], - [0.090281, 0.325685, 0.384910], - [0.092304, 0.329220, 0.385040], - [0.094462, 0.332712, 0.385116], - [0.096618, 0.336161, 0.385134], - [0.099015, 0.339621, 0.385090], - [0.101481, 0.343036, 0.384981], - [0.104078, 0.346410, 0.384801], - [0.106842, 0.349774, 0.384548], - [0.109695, 0.353098, 0.384217], - [0.112655, 0.356391, 0.383807], - [0.115748, 0.359638, 0.383310], - [0.118992, 0.362849, 0.382713], - [0.122320, 0.366030, 0.382026], - [0.125889, 0.369160, 0.381259], - [0.129519, 0.372238, 0.380378], - [0.133298, 0.375282, 0.379395], - [0.137212, 0.378282, 0.378315], - [0.141260, 0.381240, 0.377135], - [0.145432, 0.384130, 0.375840], - [0.149706, 0.386975, 0.374449], - [0.154073, 0.389777, 0.372934], - [0.158620, 0.392531, 0.371320], - [0.163246, 0.395237, 0.369609], - [0.167952, 0.397889, 0.367784], - [0.172788, 0.400496, 0.365867], - [0.177752, 0.403041, 0.363833], - [0.182732, 0.405551, 0.361714], - [0.187886, 0.408003, 0.359484], - [0.193050, 0.410427, 0.357177], - [0.198310, 0.412798, 0.354767], - [0.203676, 0.415116, 0.352253], - [0.209075, 0.417412, 0.349677], - [0.214555, 0.419661, 0.347019], - [0.220112, 0.421864, 0.344261], - [0.225707, 0.424049, 0.341459], - [0.231362, 0.426197, 0.338572], - [0.237075, 0.428325, 0.335634], - [0.242795, 0.430418, 0.332635], - [0.248617, 0.432493, 0.329571], - [0.254452, 0.434529, 0.326434], - [0.260320, 0.436556, 0.323285], - [0.266241, 0.438555, 0.320085], - [0.272168, 0.440541, 0.316831], - [0.278171, 0.442524, 0.313552], - [0.284175, 0.444484, 0.310243], - [0.290214, 0.446420, 0.306889], - [0.296294, 0.448357, 0.303509], - [0.302379, 0.450282, 0.300122], - [0.308517, 0.452205, 0.296721], - [0.314648, 0.454107, 0.293279], - [0.320834, 0.456006, 0.289841], - [0.327007, 0.457900, 0.286377], - [0.333235, 0.459794, 0.282937], - [0.339469, 0.461685, 0.279468], - [0.345703, 0.463563, 0.275998], - [0.351976, 0.465440, 0.272492], - [0.358277, 0.467331, 0.269037], - [0.364589, 0.469213, 0.265543], - [0.370922, 0.471085, 0.262064], - [0.377291, 0.472952, 0.258588], - [0.383675, 0.474842, 0.255131], - [0.390070, 0.476711, 0.251665], - [0.396505, 0.478587, 0.248212], - [0.402968, 0.480466, 0.244731], - [0.409455, 0.482351, 0.241314], - [0.415967, 0.484225, 0.237895], - [0.422507, 0.486113, 0.234493], - [0.429094, 0.488011, 0.231096], - [0.435714, 0.489890, 0.227728], - [0.442365, 0.491795, 0.224354], - [0.449052, 0.493684, 0.221074], - [0.455774, 0.495585, 0.217774], - [0.462539, 0.497497, 0.214518], - [0.469368, 0.499393, 0.211318], - [0.476221, 0.501314, 0.208148], - [0.483123, 0.503216, 0.205037], - [0.490081, 0.505137, 0.201976], - [0.497089, 0.507058, 0.198994], - [0.504153, 0.508984, 0.196118], - [0.511253, 0.510898, 0.193296], - [0.518425, 0.512822, 0.190566], - [0.525637, 0.514746, 0.187990], - [0.532907, 0.516662, 0.185497], - [0.540225, 0.518584, 0.183099], - [0.547599, 0.520486, 0.180884], - [0.555024, 0.522391, 0.178854], - [0.562506, 0.524293, 0.176964], - [0.570016, 0.526186, 0.175273], - [0.577582, 0.528058, 0.173775], - [0.585199, 0.529927, 0.172493], - [0.592846, 0.531777, 0.171449], - [0.600520, 0.533605, 0.170648], - [0.608240, 0.535423, 0.170104], - [0.615972, 0.537231, 0.169826], - [0.623739, 0.539002, 0.169814], - [0.631513, 0.540752, 0.170075], - [0.639301, 0.542484, 0.170622], - [0.647098, 0.544183, 0.171465], - [0.654889, 0.545863, 0.172603], - [0.662691, 0.547503, 0.174044], - [0.670477, 0.549127, 0.175747], - [0.678244, 0.550712, 0.177803], - [0.685995, 0.552274, 0.180056], - [0.693720, 0.553797, 0.182610], - [0.701421, 0.555294, 0.185478], - [0.709098, 0.556772, 0.188546], - [0.716731, 0.558205, 0.191851], - [0.724322, 0.559628, 0.195408], - [0.731878, 0.561011, 0.199174], - [0.739393, 0.562386, 0.203179], - [0.746850, 0.563725, 0.207375], - [0.754268, 0.565033, 0.211761], - [0.761629, 0.566344, 0.216322], - [0.768942, 0.567630, 0.221045], - [0.776208, 0.568899, 0.225930], - [0.783416, 0.570162, 0.230962], - [0.790568, 0.571421, 0.236160], - [0.797665, 0.572682, 0.241490], - [0.804709, 0.573928, 0.246955], - [0.811692, 0.575187, 0.252572], - [0.818610, 0.576462, 0.258303], - [0.825472, 0.577725, 0.264197], - [0.832272, 0.579026, 0.270211], - [0.838999, 0.580339, 0.276353], - [0.845657, 0.581672, 0.282631], - [0.852247, 0.583037, 0.289036], - [0.858747, 0.584440, 0.295572], - [0.865168, 0.585882, 0.302255], - [0.871505, 0.587352, 0.309112], - [0.877741, 0.588873, 0.316081], - [0.883878, 0.590450, 0.323195], - [0.889900, 0.592087, 0.330454], - [0.895809, 0.593765, 0.337865], - [0.901590, 0.595507, 0.345429], - [0.907242, 0.597319, 0.353142], - [0.912746, 0.599191, 0.360986], - [0.918103, 0.601126, 0.368999], - [0.923300, 0.603137, 0.377139], - [0.928323, 0.605212, 0.385404], - [0.933176, 0.607369, 0.393817], - [0.937850, 0.609582, 0.402345], - [0.942332, 0.611867, 0.411006], - [0.946612, 0.614218, 0.419767], - [0.950697, 0.616649, 0.428624], - [0.954574, 0.619137, 0.437582], - [0.958244, 0.621671, 0.446604], - [0.961696, 0.624282, 0.455702], - [0.964943, 0.626934, 0.464860], - [0.967983, 0.629639, 0.474057], - [0.970804, 0.632394, 0.483290], - [0.973424, 0.635183, 0.492547], - [0.975835, 0.638012, 0.501826], - [0.978052, 0.640868, 0.511090], - [0.980079, 0.643752, 0.520350], - [0.981918, 0.646664, 0.529602], - [0.983574, 0.649590, 0.538819], - [0.985066, 0.652522, 0.547998], - [0.986392, 0.655470, 0.557142], - [0.987567, 0.658422, 0.566226], - [0.988596, 0.661378, 0.575265], - [0.989496, 0.664329, 0.584246], - [0.990268, 0.667280, 0.593174], - [0.990926, 0.670230, 0.602031], - [0.991479, 0.673165, 0.610835], - [0.991935, 0.676091, 0.619575], - [0.992305, 0.679007, 0.628251], - [0.992595, 0.681914, 0.636869], - [0.992813, 0.684815, 0.645423], - [0.992967, 0.687705, 0.653934], - [0.993064, 0.690579, 0.662398], - [0.993111, 0.693451, 0.670810], - [0.993112, 0.696314, 0.679177], - [0.993074, 0.699161, 0.687519], - [0.993002, 0.702006, 0.695831], - [0.992900, 0.704852, 0.704114], - [0.992771, 0.707689, 0.712380], - [0.992619, 0.710530, 0.720639], - [0.992447, 0.713366, 0.728892], - [0.992258, 0.716210, 0.737146], - [0.992054, 0.719049, 0.745403], - [0.991837, 0.721893, 0.753673], - [0.991607, 0.724754, 0.761959], - [0.991367, 0.727614, 0.770270], - [0.991116, 0.730489, 0.778606], - [0.990855, 0.733373, 0.786976], - [0.990586, 0.736265, 0.795371], - [0.990307, 0.739184, 0.803810], - [0.990018, 0.742102, 0.812285], - [0.989720, 0.745039, 0.820804], - [0.989411, 0.747997, 0.829372], - [0.989089, 0.750968, 0.837979], - [0.988754, 0.753949, 0.846627], - [0.988406, 0.756949, 0.855332], - [0.988046, 0.759964, 0.864078], - [0.987672, 0.762996, 0.872864], - [0.987280, 0.766047, 0.881699], - [0.986868, 0.769105, 0.890573], - [0.986435, 0.772184, 0.899493], - [0.985980, 0.775272, 0.908448], - [0.985503, 0.778378, 0.917444], - [0.985002, 0.781495, 0.926468], - [0.984473, 0.784624, 0.935531], - [0.983913, 0.787757, 0.944626], - [0.983322, 0.790905, 0.953748], - [0.982703, 0.794068, 0.962895], - [0.982048, 0.797228, 0.972070], - [0.981354, 0.800406, 0.981267]]) + batlow = np.array( + [ + [0.005193, 0.098238, 0.349842], + [0.009065, 0.104487, 0.350933], + [0.012963, 0.110779, 0.351992], + [0.016530, 0.116913, 0.353070], + [0.019936, 0.122985, 0.354120], + [0.023189, 0.129035, 0.355182], + [0.026291, 0.135044, 0.356210], + [0.029245, 0.140964, 0.357239], + [0.032053, 0.146774, 0.358239], + [0.034853, 0.152558, 0.359233], + [0.037449, 0.158313, 0.360216], + [0.039845, 0.163978, 0.361187], + [0.042104, 0.169557, 0.362151], + [0.044069, 0.175053, 0.363084], + [0.045905, 0.180460, 0.364007], + [0.047665, 0.185844, 0.364915], + [0.049378, 0.191076, 0.365810], + [0.050795, 0.196274, 0.366684], + [0.052164, 0.201323, 0.367524], + [0.053471, 0.206357, 0.368370], + [0.054721, 0.211234, 0.369184], + [0.055928, 0.216046, 0.369974], + [0.057033, 0.220754, 0.370750], + [0.058032, 0.225340, 0.371509], + [0.059164, 0.229842, 0.372252], + [0.060167, 0.234299, 0.372978], + [0.061052, 0.238625, 0.373691], + [0.062060, 0.242888, 0.374386], + [0.063071, 0.247085, 0.375050], + [0.063982, 0.251213, 0.375709], + [0.064936, 0.255264, 0.376362], + [0.065903, 0.259257, 0.376987], + [0.066899, 0.263188, 0.377594], + [0.067921, 0.267056, 0.378191], + [0.069002, 0.270922, 0.378774], + [0.070001, 0.274713, 0.379342], + [0.071115, 0.278497, 0.379895], + [0.072192, 0.282249, 0.380434], + [0.073440, 0.285942, 0.380957], + [0.074595, 0.289653, 0.381452], + [0.075833, 0.293321, 0.381922], + [0.077136, 0.296996, 0.382376], + [0.078517, 0.300622, 0.382814], + [0.079984, 0.304252, 0.383224], + [0.081553, 0.307858, 0.383598], + [0.083082, 0.311461, 0.383936], + [0.084778, 0.315043, 0.384240], + [0.086503, 0.318615, 0.384506], + [0.088353, 0.322167, 0.384731], + [0.090281, 0.325685, 0.384910], + [0.092304, 0.329220, 0.385040], + [0.094462, 0.332712, 0.385116], + [0.096618, 0.336161, 0.385134], + [0.099015, 0.339621, 0.385090], + [0.101481, 0.343036, 0.384981], + [0.104078, 0.346410, 0.384801], + [0.106842, 0.349774, 0.384548], + [0.109695, 0.353098, 0.384217], + [0.112655, 0.356391, 0.383807], + [0.115748, 0.359638, 0.383310], + [0.118992, 0.362849, 0.382713], + [0.122320, 0.366030, 0.382026], + [0.125889, 0.369160, 0.381259], + [0.129519, 0.372238, 0.380378], + [0.133298, 0.375282, 0.379395], + [0.137212, 0.378282, 0.378315], + [0.141260, 0.381240, 0.377135], + [0.145432, 0.384130, 0.375840], + [0.149706, 0.386975, 0.374449], + [0.154073, 0.389777, 0.372934], + [0.158620, 0.392531, 0.371320], + [0.163246, 0.395237, 0.369609], + [0.167952, 0.397889, 0.367784], + [0.172788, 0.400496, 0.365867], + [0.177752, 0.403041, 0.363833], + [0.182732, 0.405551, 0.361714], + [0.187886, 0.408003, 0.359484], + [0.193050, 0.410427, 0.357177], + [0.198310, 0.412798, 0.354767], + [0.203676, 0.415116, 0.352253], + [0.209075, 0.417412, 0.349677], + [0.214555, 0.419661, 0.347019], + [0.220112, 0.421864, 0.344261], + [0.225707, 0.424049, 0.341459], + [0.231362, 0.426197, 0.338572], + [0.237075, 0.428325, 0.335634], + [0.242795, 0.430418, 0.332635], + [0.248617, 0.432493, 0.329571], + [0.254452, 0.434529, 0.326434], + [0.260320, 0.436556, 0.323285], + [0.266241, 0.438555, 0.320085], + [0.272168, 0.440541, 0.316831], + [0.278171, 0.442524, 0.313552], + [0.284175, 0.444484, 0.310243], + [0.290214, 0.446420, 0.306889], + [0.296294, 0.448357, 0.303509], + [0.302379, 0.450282, 0.300122], + [0.308517, 0.452205, 0.296721], + [0.314648, 0.454107, 0.293279], + [0.320834, 0.456006, 0.289841], + [0.327007, 0.457900, 0.286377], + [0.333235, 0.459794, 0.282937], + [0.339469, 0.461685, 0.279468], + [0.345703, 0.463563, 0.275998], + [0.351976, 0.465440, 0.272492], + [0.358277, 0.467331, 0.269037], + [0.364589, 0.469213, 0.265543], + [0.370922, 0.471085, 0.262064], + [0.377291, 0.472952, 0.258588], + [0.383675, 0.474842, 0.255131], + [0.390070, 0.476711, 0.251665], + [0.396505, 0.478587, 0.248212], + [0.402968, 0.480466, 0.244731], + [0.409455, 0.482351, 0.241314], + [0.415967, 0.484225, 0.237895], + [0.422507, 0.486113, 0.234493], + [0.429094, 0.488011, 0.231096], + [0.435714, 0.489890, 0.227728], + [0.442365, 0.491795, 0.224354], + [0.449052, 0.493684, 0.221074], + [0.455774, 0.495585, 0.217774], + [0.462539, 0.497497, 0.214518], + [0.469368, 0.499393, 0.211318], + [0.476221, 0.501314, 0.208148], + [0.483123, 0.503216, 0.205037], + [0.490081, 0.505137, 0.201976], + [0.497089, 0.507058, 0.198994], + [0.504153, 0.508984, 0.196118], + [0.511253, 0.510898, 0.193296], + [0.518425, 0.512822, 0.190566], + [0.525637, 0.514746, 0.187990], + [0.532907, 0.516662, 0.185497], + [0.540225, 0.518584, 0.183099], + [0.547599, 0.520486, 0.180884], + [0.555024, 0.522391, 0.178854], + [0.562506, 0.524293, 0.176964], + [0.570016, 0.526186, 0.175273], + [0.577582, 0.528058, 0.173775], + [0.585199, 0.529927, 0.172493], + [0.592846, 0.531777, 0.171449], + [0.600520, 0.533605, 0.170648], + [0.608240, 0.535423, 0.170104], + [0.615972, 0.537231, 0.169826], + [0.623739, 0.539002, 0.169814], + [0.631513, 0.540752, 0.170075], + [0.639301, 0.542484, 0.170622], + [0.647098, 0.544183, 0.171465], + [0.654889, 0.545863, 0.172603], + [0.662691, 0.547503, 0.174044], + [0.670477, 0.549127, 0.175747], + [0.678244, 0.550712, 0.177803], + [0.685995, 0.552274, 0.180056], + [0.693720, 0.553797, 0.182610], + [0.701421, 0.555294, 0.185478], + [0.709098, 0.556772, 0.188546], + [0.716731, 0.558205, 0.191851], + [0.724322, 0.559628, 0.195408], + [0.731878, 0.561011, 0.199174], + [0.739393, 0.562386, 0.203179], + [0.746850, 0.563725, 0.207375], + [0.754268, 0.565033, 0.211761], + [0.761629, 0.566344, 0.216322], + [0.768942, 0.567630, 0.221045], + [0.776208, 0.568899, 0.225930], + [0.783416, 0.570162, 0.230962], + [0.790568, 0.571421, 0.236160], + [0.797665, 0.572682, 0.241490], + [0.804709, 0.573928, 0.246955], + [0.811692, 0.575187, 0.252572], + [0.818610, 0.576462, 0.258303], + [0.825472, 0.577725, 0.264197], + [0.832272, 0.579026, 0.270211], + [0.838999, 0.580339, 0.276353], + [0.845657, 0.581672, 0.282631], + [0.852247, 0.583037, 0.289036], + [0.858747, 0.584440, 0.295572], + [0.865168, 0.585882, 0.302255], + [0.871505, 0.587352, 0.309112], + [0.877741, 0.588873, 0.316081], + [0.883878, 0.590450, 0.323195], + [0.889900, 0.592087, 0.330454], + [0.895809, 0.593765, 0.337865], + [0.901590, 0.595507, 0.345429], + [0.907242, 0.597319, 0.353142], + [0.912746, 0.599191, 0.360986], + [0.918103, 0.601126, 0.368999], + [0.923300, 0.603137, 0.377139], + [0.928323, 0.605212, 0.385404], + [0.933176, 0.607369, 0.393817], + [0.937850, 0.609582, 0.402345], + [0.942332, 0.611867, 0.411006], + [0.946612, 0.614218, 0.419767], + [0.950697, 0.616649, 0.428624], + [0.954574, 0.619137, 0.437582], + [0.958244, 0.621671, 0.446604], + [0.961696, 0.624282, 0.455702], + [0.964943, 0.626934, 0.464860], + [0.967983, 0.629639, 0.474057], + [0.970804, 0.632394, 0.483290], + [0.973424, 0.635183, 0.492547], + [0.975835, 0.638012, 0.501826], + [0.978052, 0.640868, 0.511090], + [0.980079, 0.643752, 0.520350], + [0.981918, 0.646664, 0.529602], + [0.983574, 0.649590, 0.538819], + [0.985066, 0.652522, 0.547998], + [0.986392, 0.655470, 0.557142], + [0.987567, 0.658422, 0.566226], + [0.988596, 0.661378, 0.575265], + [0.989496, 0.664329, 0.584246], + [0.990268, 0.667280, 0.593174], + [0.990926, 0.670230, 0.602031], + [0.991479, 0.673165, 0.610835], + [0.991935, 0.676091, 0.619575], + [0.992305, 0.679007, 0.628251], + [0.992595, 0.681914, 0.636869], + [0.992813, 0.684815, 0.645423], + [0.992967, 0.687705, 0.653934], + [0.993064, 0.690579, 0.662398], + [0.993111, 0.693451, 0.670810], + [0.993112, 0.696314, 0.679177], + [0.993074, 0.699161, 0.687519], + [0.993002, 0.702006, 0.695831], + [0.992900, 0.704852, 0.704114], + [0.992771, 0.707689, 0.712380], + [0.992619, 0.710530, 0.720639], + [0.992447, 0.713366, 0.728892], + [0.992258, 0.716210, 0.737146], + [0.992054, 0.719049, 0.745403], + [0.991837, 0.721893, 0.753673], + [0.991607, 0.724754, 0.761959], + [0.991367, 0.727614, 0.770270], + [0.991116, 0.730489, 0.778606], + [0.990855, 0.733373, 0.786976], + [0.990586, 0.736265, 0.795371], + [0.990307, 0.739184, 0.803810], + [0.990018, 0.742102, 0.812285], + [0.989720, 0.745039, 0.820804], + [0.989411, 0.747997, 0.829372], + [0.989089, 0.750968, 0.837979], + [0.988754, 0.753949, 0.846627], + [0.988406, 0.756949, 0.855332], + [0.988046, 0.759964, 0.864078], + [0.987672, 0.762996, 0.872864], + [0.987280, 0.766047, 0.881699], + [0.986868, 0.769105, 0.890573], + [0.986435, 0.772184, 0.899493], + [0.985980, 0.775272, 0.908448], + [0.985503, 0.778378, 0.917444], + [0.985002, 0.781495, 0.926468], + [0.984473, 0.784624, 0.935531], + [0.983913, 0.787757, 0.944626], + [0.983322, 0.790905, 0.953748], + [0.982703, 0.794068, 0.962895], + [0.982048, 0.797228, 0.972070], + [0.981354, 0.800406, 0.981267], + ] + ) cmap_batlow = matplotlib.colors.ListedColormap(batlow) @@ -4372,11 +4711,10 @@ def get_batlow_cmap() -> matplotlib.colors.ListedColormap: gradient = np.vstack((gradient, gradient)) fig, ax = plt.subplots(nrows=1, figsize=(6, 1)) - fig.subplots_adjust(top=0.5, bottom=0.15, - left=0.2, right=1) - ax.set_title('Batlow Colorbar', fontsize=14) + fig.subplots_adjust(top=0.5, bottom=0.15, left=0.2, right=1) + ax.set_title("Batlow Colorbar", fontsize=14) - ax.imshow(gradient, aspect='auto', cmap=cmap_batlow) + ax.imshow(gradient, aspect="auto", cmap=cmap_batlow) # Turn off *all* ticks & spines, not just the ones with colormaps. ax.set_axis_off() @@ -4397,262 +4735,266 @@ def get_petrel_cmap() -> matplotlib.colors.ListedColormap: """ - seismic = np.array([[255, 255, 0], - [255, 253, 0], - [254, 252, 0], - [254, 250, 0], - [253, 249, 0], - [253, 247, 0], - [253, 246, 0], - [252, 244, 0], - [252, 242, 0], - [251, 241, 0], - [251, 239, 0], - [251, 237, 0], - [250, 236, 0], - [250, 234, 0], - [249, 232, 0], - [249, 230, 0], - [248, 229, 0], - [248, 227, 0], - [247, 225, 0], - [247, 223, 0], - [246, 221, 0], - [246, 219, 0], - [246, 217, 0], - [245, 215, 0], - [245, 213, 0], - [244, 211, 0], - [243, 209, 0], - [243, 207, 0], - [242, 205, 0], - [242, 203, 0], - [241, 200, 0], - [241, 198, 0], - [240, 196, 0], - [240, 194, 0], - [239, 191, 0], - [238, 189, 0], - [238, 186, 0], - [237, 184, 0], - [237, 181, 0], - [236, 179, 0], - [235, 176, 0], - [235, 174, 0], - [234, 171, 0], - [233, 169, 0], - [233, 166, 0], - [232, 163, 0], - [231, 160, 0], - [231, 157, 0], - [230, 155, 0], - [229, 152, 0], - [228, 149, 0], - [228, 146, 0], - [227, 143, 0], - [226, 139, 0], - [225, 136, 0], - [225, 133, 0], - [224, 130, 0], - [223, 126, 0], - [222, 123, 0], - [221, 119, 0], - [220, 116, 0], - [219, 112, 0], - [218, 109, 0], - [217, 105, 0], - [217, 101, 0], - [216, 97, 0], - [215, 93, 0], - [214, 89, 0], - [213, 85, 0], - [211, 81, 0], - [210, 76, 0], - [209, 72, 0], - [208, 68, 0], - [207, 63, 0], - [206, 59, 0], - [205, 54, 0], - [203, 49, 0], - [202, 44, 0], - [201, 39, 0], - [200, 34, 0], - [198, 29, 0], - [197, 23, 0], - [196, 17, 0], - [194, 12, 0], - [193, 6, 0], - [191, 0, 0], - [186, 4, 0], - [180, 8, 0], - [175, 12, 0], - [169, 16, 0], - [164, 20, 0], - [158, 24, 0], - [152, 28, 0], - [147, 32, 0], - [141, 36, 0], - [136, 40, 0], - [130, 44, 0], - [125, 48, 0], - [119, 53, 0], - [114, 56, 0], - [108, 61, 0], - [103, 65, 0], - [97, 69, 0], - [101, 74, 8], - [105, 79, 16], - [110, 85, 24], - [114, 90, 32], - [118, 95, 40], - [122, 101, 48], - [126, 106, 56], - [130, 111, 64], - [135, 117, 72], - [139, 122, 80], - [143, 127, 88], - [147, 133, 96], - [151, 138, 104], - [156, 143, 112], - [160, 148, 120], - [164, 154, 128], - [168, 159, 136], - [173, 164, 144], - [177, 170, 152], - [181, 175, 160], - [185, 180, 168], - [190, 186, 176], - [194, 191, 184], - [198, 196, 192], - [202, 202, 200], - [201, 201, 201], - [196, 196, 196], - [191, 191, 191], - [186, 186, 186], - [181, 181, 181], - [176, 176, 176], - [171, 171, 171], - [166, 166, 166], - [161, 161, 161], - [156, 156, 156], - [151, 151, 151], - [146, 146, 146], - [141, 141, 141], - [136, 136, 136], - [131, 131, 131], - [126, 126, 126], - [121, 121, 121], - [116, 116, 116], - [111, 111, 111], - [106, 106, 106], - [101, 101, 101], - [96, 96, 96], - [91, 91, 91], - [86, 86, 86], - [81, 81, 81], - [77, 77, 77], - [72, 72, 83], - [67, 67, 90], - [63, 63, 97], - [58, 58, 104], - [54, 54, 110], - [49, 49, 117], - [45, 45, 124], - [40, 40, 131], - [36, 36, 138], - [32, 32, 144], - [27, 27, 151], - [22, 22, 158], - [18, 18, 164], - [13, 13, 171], - [9, 9, 178], - [5, 5, 184], - [0, 0, 191], - [4, 6, 193], - [8, 12, 194], - [11, 17, 196], - [14, 23, 197], - [18, 29, 198], - [21, 34, 200], - [24, 39, 201], - [28, 44, 202], - [31, 49, 203], - [34, 54, 205], - [37, 59, 206], - [40, 63, 207], - [43, 68, 208], - [46, 72, 209], - [48, 76, 210], - [51, 81, 211], - [54, 85, 213], - [56, 89, 214], - [59, 93, 215], - [61, 97, 216], - [64, 101, 217], - [66, 105, 217], - [68, 109, 218], - [71, 112, 219], - [73, 116, 220], - [75, 120, 221], - [78, 123, 222], - [80, 126, 223], - [82, 130, 224], - [84, 133, 225], - [86, 136, 225], - [88, 140, 226], - [90, 143, 227], - [92, 146, 228], - [94, 149, 228], - [96, 152, 229], - [98, 155, 230], - [99, 158, 231], - [101, 160, 231], - [103, 163, 232], - [105, 166, 233], - [106, 169, 233], - [108, 171, 234], - [110, 174, 235], - [111, 177, 235], - [113, 179, 236], - [114, 182, 237], - [116, 184, 237], - [118, 187, 238], - [119, 189, 238], - [121, 191, 239], - [122, 194, 240], - [123, 196, 240], - [125, 198, 241], - [126, 200, 241], - [128, 203, 242], - [129, 205, 242], - [130, 207, 243], - [132, 209, 244], - [133, 211, 244], - [134, 213, 245], - [136, 215, 245], - [137, 217, 246], - [138, 219, 246], - [139, 221, 247], - [140, 223, 247], - [142, 225, 247], - [143, 227, 248], - [144, 229, 248], - [145, 230, 249], - [146, 232, 249], - [147, 234, 250], - [148, 236, 250], - [150, 237, 251], - [151, 239, 251], - [152, 241, 251], - [153, 242, 252], - [154, 244, 252], - [155, 246, 253], - [156, 247, 253], - [157, 249, 253], - [158, 250, 254], - [159, 252, 254], - [160, 254, 255], - [161, 255, 255]]) + seismic = np.array( + [ + [255, 255, 0], + [255, 253, 0], + [254, 252, 0], + [254, 250, 0], + [253, 249, 0], + [253, 247, 0], + [253, 246, 0], + [252, 244, 0], + [252, 242, 0], + [251, 241, 0], + [251, 239, 0], + [251, 237, 0], + [250, 236, 0], + [250, 234, 0], + [249, 232, 0], + [249, 230, 0], + [248, 229, 0], + [248, 227, 0], + [247, 225, 0], + [247, 223, 0], + [246, 221, 0], + [246, 219, 0], + [246, 217, 0], + [245, 215, 0], + [245, 213, 0], + [244, 211, 0], + [243, 209, 0], + [243, 207, 0], + [242, 205, 0], + [242, 203, 0], + [241, 200, 0], + [241, 198, 0], + [240, 196, 0], + [240, 194, 0], + [239, 191, 0], + [238, 189, 0], + [238, 186, 0], + [237, 184, 0], + [237, 181, 0], + [236, 179, 0], + [235, 176, 0], + [235, 174, 0], + [234, 171, 0], + [233, 169, 0], + [233, 166, 0], + [232, 163, 0], + [231, 160, 0], + [231, 157, 0], + [230, 155, 0], + [229, 152, 0], + [228, 149, 0], + [228, 146, 0], + [227, 143, 0], + [226, 139, 0], + [225, 136, 0], + [225, 133, 0], + [224, 130, 0], + [223, 126, 0], + [222, 123, 0], + [221, 119, 0], + [220, 116, 0], + [219, 112, 0], + [218, 109, 0], + [217, 105, 0], + [217, 101, 0], + [216, 97, 0], + [215, 93, 0], + [214, 89, 0], + [213, 85, 0], + [211, 81, 0], + [210, 76, 0], + [209, 72, 0], + [208, 68, 0], + [207, 63, 0], + [206, 59, 0], + [205, 54, 0], + [203, 49, 0], + [202, 44, 0], + [201, 39, 0], + [200, 34, 0], + [198, 29, 0], + [197, 23, 0], + [196, 17, 0], + [194, 12, 0], + [193, 6, 0], + [191, 0, 0], + [186, 4, 0], + [180, 8, 0], + [175, 12, 0], + [169, 16, 0], + [164, 20, 0], + [158, 24, 0], + [152, 28, 0], + [147, 32, 0], + [141, 36, 0], + [136, 40, 0], + [130, 44, 0], + [125, 48, 0], + [119, 53, 0], + [114, 56, 0], + [108, 61, 0], + [103, 65, 0], + [97, 69, 0], + [101, 74, 8], + [105, 79, 16], + [110, 85, 24], + [114, 90, 32], + [118, 95, 40], + [122, 101, 48], + [126, 106, 56], + [130, 111, 64], + [135, 117, 72], + [139, 122, 80], + [143, 127, 88], + [147, 133, 96], + [151, 138, 104], + [156, 143, 112], + [160, 148, 120], + [164, 154, 128], + [168, 159, 136], + [173, 164, 144], + [177, 170, 152], + [181, 175, 160], + [185, 180, 168], + [190, 186, 176], + [194, 191, 184], + [198, 196, 192], + [202, 202, 200], + [201, 201, 201], + [196, 196, 196], + [191, 191, 191], + [186, 186, 186], + [181, 181, 181], + [176, 176, 176], + [171, 171, 171], + [166, 166, 166], + [161, 161, 161], + [156, 156, 156], + [151, 151, 151], + [146, 146, 146], + [141, 141, 141], + [136, 136, 136], + [131, 131, 131], + [126, 126, 126], + [121, 121, 121], + [116, 116, 116], + [111, 111, 111], + [106, 106, 106], + [101, 101, 101], + [96, 96, 96], + [91, 91, 91], + [86, 86, 86], + [81, 81, 81], + [77, 77, 77], + [72, 72, 83], + [67, 67, 90], + [63, 63, 97], + [58, 58, 104], + [54, 54, 110], + [49, 49, 117], + [45, 45, 124], + [40, 40, 131], + [36, 36, 138], + [32, 32, 144], + [27, 27, 151], + [22, 22, 158], + [18, 18, 164], + [13, 13, 171], + [9, 9, 178], + [5, 5, 184], + [0, 0, 191], + [4, 6, 193], + [8, 12, 194], + [11, 17, 196], + [14, 23, 197], + [18, 29, 198], + [21, 34, 200], + [24, 39, 201], + [28, 44, 202], + [31, 49, 203], + [34, 54, 205], + [37, 59, 206], + [40, 63, 207], + [43, 68, 208], + [46, 72, 209], + [48, 76, 210], + [51, 81, 211], + [54, 85, 213], + [56, 89, 214], + [59, 93, 215], + [61, 97, 216], + [64, 101, 217], + [66, 105, 217], + [68, 109, 218], + [71, 112, 219], + [73, 116, 220], + [75, 120, 221], + [78, 123, 222], + [80, 126, 223], + [82, 130, 224], + [84, 133, 225], + [86, 136, 225], + [88, 140, 226], + [90, 143, 227], + [92, 146, 228], + [94, 149, 228], + [96, 152, 229], + [98, 155, 230], + [99, 158, 231], + [101, 160, 231], + [103, 163, 232], + [105, 166, 233], + [106, 169, 233], + [108, 171, 234], + [110, 174, 235], + [111, 177, 235], + [113, 179, 236], + [114, 182, 237], + [116, 184, 237], + [118, 187, 238], + [119, 189, 238], + [121, 191, 239], + [122, 194, 240], + [123, 196, 240], + [125, 198, 241], + [126, 200, 241], + [128, 203, 242], + [129, 205, 242], + [130, 207, 243], + [132, 209, 244], + [133, 211, 244], + [134, 213, 245], + [136, 215, 245], + [137, 217, 246], + [138, 219, 246], + [139, 221, 247], + [140, 223, 247], + [142, 225, 247], + [143, 227, 248], + [144, 229, 248], + [145, 230, 249], + [146, 232, 249], + [147, 234, 250], + [148, 236, 250], + [150, 237, 251], + [151, 239, 251], + [152, 241, 251], + [153, 242, 252], + [154, 244, 252], + [155, 246, 253], + [156, 247, 253], + [157, 249, 253], + [158, 250, 254], + [159, 252, 254], + [160, 254, 255], + [161, 255, 255], + ] + ) cmap_seismic = matplotlib.colors.ListedColormap(seismic) @@ -4661,11 +5003,10 @@ def get_petrel_cmap() -> matplotlib.colors.ListedColormap: gradient = np.vstack((gradient, gradient)) fig, ax = plt.subplots(nrows=1, figsize=(6, 1)) - fig.subplots_adjust(top=0.5, bottom=0.15, - left=0.2, right=1) - ax.set_title('Seismic Colorbar', fontsize=14) + fig.subplots_adjust(top=0.5, bottom=0.15, left=0.2, right=1) + ax.set_title("Seismic Colorbar", fontsize=14) - ax.imshow(gradient, aspect='auto', cmap=cmap_seismic) + ax.imshow(gradient, aspect="auto", cmap=cmap_seismic) # Turn off *all* ticks & spines, not just the ones with colormaps. ax.set_axis_off() @@ -4673,11 +5014,13 @@ def get_petrel_cmap() -> matplotlib.colors.ListedColormap: return cmap_seismic -def get_color_lot(geo_model, - lith_c: pd.DataFrame = None, - index='surface', - is_faults: bool = True, - is_basement: bool = False) -> pd.Series: +def get_color_lot( + geo_model, + lith_c: pd.DataFrame = None, + index="surface", + is_faults: bool = True, + is_basement: bool = False, +) -> pd.Series: """Method to get the right color list depending on the type of plot. Borrowed from https://github.com/cgre-aachen/gempy/blob/6aed72a4dfa26830df142a0461294bd9d21a4fa4/gempy/plot/vista.py#L133-L167 @@ -4706,30 +5049,33 @@ def get_color_lot(geo_model, """ if lith_c is None: surf_df = geo_model._surfaces.df.set_index(index) - unique_surf_points = np.unique(geo_model._surface_points.df['id']).astype(int) + unique_surf_points = np.unique(geo_model._surface_points.df["id"]).astype(int) if len(unique_surf_points) != 0: bool_surf_points = np.zeros(surf_df.shape[0], dtype=bool) - bool_surf_points[unique_surf_points.astype('int') - 1] = True + bool_surf_points[unique_surf_points.astype("int") - 1] = True - surf_df['isActive'] = (surf_df['isActive'] | bool_surf_points) + surf_df["isActive"] = surf_df["isActive"] | bool_surf_points if is_faults is True and is_basement is True: - lith_c = surf_df.groupby('isActive').get_group(True)['color'] + lith_c = surf_df.groupby("isActive").get_group(True)["color"] elif is_faults is True and is_basement is False: - lith_c = surf_df.groupby(['isActive', 'isBasement']).get_group((True, False))['color'] + lith_c = surf_df.groupby(["isActive", "isBasement"]).get_group( + (True, False) + )["color"] else: - lith_c = surf_df.groupby(['isActive', 'isFault']).get_group((True, False))[ - 'color'] + lith_c = surf_df.groupby(["isActive", "isFault"]).get_group( + (True, False) + )["color"] color_lot = lith_c return color_lot -def get_mesh_geological_map(geo_model) -> Tuple[pv.core.pointset.PolyData, - matplotlib.colors.ListedColormap, - bool]: +def get_mesh_geological_map( + geo_model, +) -> Tuple[pv.core.pointset.PolyData, matplotlib.colors.ListedColormap, bool]: """Getting the geological map of a GemPy Model draped over the topography as mesh. Borrowed from https://github.com/cgre-aachen/gempy/blob/6aed72a4dfa26830df142a0461294bd9d21a4fa4/gempy/plot/vista.py#L512-L604 @@ -4762,30 +5108,28 @@ def get_mesh_geological_map(geo_model) -> Tuple[pv.core.pointset.PolyData, polydata = pv.PolyData(topography) # Getting color values - colors_hex = get_color_lot(geo_model=geo_model, - is_faults=False, - is_basement=True, index='id') + colors_hex = get_color_lot( + geo_model=geo_model, is_faults=False, is_basement=True, index="id" + ) colors_rgb_ = colors_hex.apply(lambda val: list(mcolors.hex2color(val))) - colors_rgb = pd.DataFrame(colors_rgb_.to_list(), - index=colors_hex.index) * 255 + colors_rgb = pd.DataFrame(colors_rgb_.to_list(), index=colors_hex.index) * 255 sel = np.round(geo_model.solutions.geological_map[0]).astype(int)[0] # Converting color values - scalars_val = pv.convert_array(colors_rgb.loc[sel].values, - array_type=3) + scalars_val = pv.convert_array(colors_rgb.loc[sel].values, array_type=3) # Creating colormap - cm = mcolors.ListedColormap(list(get_color_lot(geo_model=geo_model, - is_faults=True, - is_basement=True))) + cm = mcolors.ListedColormap( + list(get_color_lot(geo_model=geo_model, is_faults=True, is_basement=True)) + ) rgb = True # Interpolating the polydata and assigning values polydata.delaunay_2d(inplace=True) - polydata['id'] = scalars_val - polydata['height'] = topography[:, 2] + polydata["id"] = scalars_val + polydata["height"] = topography[:, 2] return polydata, cm, rgb @@ -4812,11 +5156,13 @@ def resample_between_well_deviation_points(coordinates: np.ndarray) -> np.ndarra # Checking that the coordinates are provided as np.ndarray if not isinstance(coordinates, np.ndarray): - raise TypeError('Coordinates must be provided as NumPy Array') + raise TypeError("Coordinates must be provided as NumPy Array") # Checking that three coordinates are provided for each point if coordinates.shape[1] != 3: - raise ValueError('Three coordinates X, Y, and Z must be provided for each point') + raise ValueError( + "Three coordinates X, Y, and Z must be provided for each point" + ) # Creating list for storing points list_points = [] @@ -4837,8 +5183,7 @@ def resample_between_well_deviation_points(coordinates: np.ndarray) -> np.ndarra return points_resampled -def get_points_along_spline(spline: pv.core.pointset.PolyData, - dist: np.ndarray): +def get_points_along_spline(spline: pv.core.pointset.PolyData, dist: np.ndarray): """Returning the closest point on the spline a given a length along a spline. Parameters @@ -4862,18 +5207,20 @@ def get_points_along_spline(spline: pv.core.pointset.PolyData, # Checking that the spline is a PyVista PolyData Pointset if not isinstance(spline, pv.core.pointset.PolyData): - raise TypeError('The well path/the spline must be provided as PyVista PolyData Pointset') + raise TypeError( + "The well path/the spline must be provided as PyVista PolyData Pointset" + ) # Checking that the distances are provided as np.ndarray if not isinstance(dist, np.ndarray): - raise TypeError('The distances must be provided as np.ndarray') + raise TypeError("The distances must be provided as np.ndarray") # Creating list for storing indices idx_list = [] # Getting index of spline that match with a measured value and append index to list of indices for distance in dist: - idx = np.argmin(np.abs(spline.point_data['arc_length'] - distance)) + idx = np.argmin(np.abs(spline.point_data["arc_length"] - distance)) idx_list.append(idx) points = spline.points[idx_list] @@ -4881,10 +5228,12 @@ def get_points_along_spline(spline: pv.core.pointset.PolyData, return points -def show_well_log_along_well(coordinates: np.ndarray, - dist: np.ndarray, - values: np.ndarray, - radius_factor: Union[int, float] = 2) -> pv.core.pointset.PolyData: +def show_well_log_along_well( + coordinates: np.ndarray, + dist: np.ndarray, + values: np.ndarray, + radius_factor: Union[int, float] = 2, +) -> pv.core.pointset.PolyData: """Function to return a tube representing well log values along a well path Parameters @@ -4915,23 +5264,25 @@ def show_well_log_along_well(coordinates: np.ndarray, # Checking that the coordinates are provided as np.ndarray if not isinstance(coordinates, np.ndarray): - raise TypeError('Coordinates must be provided as NumPy Array') + raise TypeError("Coordinates must be provided as NumPy Array") # Checking that three coordinates are provided for each point if coordinates.shape[1] != 3: - raise ValueError('Three coordinates X, Y, and Z must be provided for each point') + raise ValueError( + "Three coordinates X, Y, and Z must be provided for each point" + ) # Checking that the distances are provided as np.ndarray if not isinstance(dist, np.ndarray): - raise TypeError('The distances must be provided as np.ndarray') + raise TypeError("The distances must be provided as np.ndarray") # Checking that the values are provided as np.ndarray if not isinstance(values, np.ndarray): - raise TypeError('The well log values must be provided as np.ndarray') + raise TypeError("The well log values must be provided as np.ndarray") # Checking that the radius factor is provided as int or float if not isinstance(radius_factor, (int, float)): - raise TypeError('The radius factor must be provided as int or float') + raise TypeError("The radius factor must be provided as int or float") # Resampling points along the well path points_resampled = resample_between_well_deviation_points(coordinates=coordinates) @@ -4946,11 +5297,12 @@ def show_well_log_along_well(coordinates: np.ndarray, polyline_along_spline = polyline_from_points(points_along_spline) # Assigning values as data array to Polyline - polyline_along_spline['values'] = values + polyline_along_spline["values"] = values # Create Tube with radius as function of the scalar values - tube_along_spline = polyline_along_spline.tube(scalars='values', - radius_factor=radius_factor) + tube_along_spline = polyline_along_spline.tube( + scalars="values", radius_factor=radius_factor + ) return tube_along_spline @@ -4976,7 +5328,7 @@ def polyline_from_points(points: np.ndarray) -> pv.core.pointset.PolyData: # Checking that the points are of type PolyData Pointset if not isinstance(points, np.ndarray): - raise TypeError('The points must be provided as NumPy Array') + raise TypeError("The points must be provided as NumPy Array") # Creating PolyData Object poly = pv.PolyData() diff --git a/gemgis/web.py b/gemgis/web.py index 80fb94b0..8f9b60b8 100644 --- a/gemgis/web.py +++ b/gemgis/web.py @@ -33,23 +33,28 @@ ############################### -def load_wms(url: str): # -> owslib.wms.WebMapService: - """Loading a WMS Service by URL +def load_wms(url: str, version: str = "1.3.0"): # -> owslib.wms.WebMapService: + """Loading a WMS Service by URL. Parameters __________ url : str - Link of the WMS Service, e.g. ``url='https://ows.terrestris.de/osm/service?'`` + Link of the WMS Service, e.g. ``url='https://ows.terrestris.de/osm/service?'``. + + version : str, default: ``'1.3.0'`` + Version of the WMS Service, e.g. ``version='1.3.0'``. Returns _______ wms : owslib.map.wms111.WebMapService - OWSLib WebMapService Object + OWSLib WebMapService Object. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -69,90 +74,102 @@ def load_wms(url: str): # -> owslib.wms.WebMapService: """ # Trying to import owslib but returning error if owslib is not installed try: - import owslib from owslib.wms import WebMapService except ModuleNotFoundError: raise ModuleNotFoundError( - 'owslib package is not installed. Use pip install owslib to install the latest version') + "owslib package is not installed. Use pip install owslib to install the latest version" + ) # Trying to import requests but returning error if requests is not installed try: import requests except ModuleNotFoundError: raise ModuleNotFoundError( - 'requests package is not installed. Use pip install requests to install the latest version') + "requests package is not installed. Use pip install requests to install the latest version" + ) # Checking if url is of type string if not isinstance(url, str): - raise TypeError('URL must be of type string') + raise TypeError("URL must be of type string") + + # Checking that the version is provided as string + if not isinstance(version, str): + raise TypeError("The WMS Service version must be provided as string") # Requesting the WMS Service or returning an error if a module may be missing try: - wms = owslib.wms.WebMapService(url) + wms = WebMapService(url, version=version) return wms except requests.exceptions.SSLError: - print("GemGIS: SSL Error, potentially related to missing module - try:\n\n pip install -U openssl \n\n") + print( + "GemGIS: SSL Error, potentially related to missing module - try:\n\n pip install -U openssl \n\n" + ) raise -def load_as_map(url: str, - layer: str, - style: str, - crs: Union[str, dict], - bbox: List[Union[float, int]], - size: List[int], - filetype: str, - transparent: bool = True, - save_image: bool = False, - path: str = None, - overwrite_file: bool = False, - create_directory: bool = False): # -> owslib.util.ResponseWrapper: - """Loading a portion of a WMS as array +def load_as_map( + url: str, + version: str, + layer: str, + style: str, + crs: Union[str, dict], + bbox: List[Union[float, int]], + size: List[int], + filetype: str, + transparent: bool = True, + save_image: bool = False, + path: str = None, + overwrite_file: bool = False, + create_directory: bool = False, +): # -> owslib.util.ResponseWrapper: + """Load a portion of a WMS as array. Parameters __________ url : str - Link of the WMS Service, e.g. ``url='https://ows.terrestris.de/osm/service?'`` + Link of the WMS Service, e.g. ``url='https://ows.terrestris.de/osm/service?'``. + + version : str, default: ``'1.3.0'``. + Version of the WMS Service, e.g. ``version='1.3.0'``. layer : str - Name of layer to be requested, e.g. ``layer='OSM-WMS'`` + Name of layer to be requested, e.g. ``layer='OSM-WMS'``. style : str - Name of style of the layer, e.g. ``style='default'`` + Name of style of the layer, e.g. ``style='default'``. crs : str - String or dict containing the CRS, e.g. ``crs='EPSG:4647'`` + String or dict containing the CRS, e.g. ``crs='EPSG:4647'``. bbox : List[Union[float,int]] - List of bounding box coordinates, e.g. ``bbox=[0, 972, 0, 1069]`` + List of bounding box coordinates, e.g. ``bbox=[0, 972, 0, 1069]``. size : List[int] - List of x and y values defining the size of the image, e.g. ``size=[1000,1000]`` + List of x and y values defining the size of the image, e.g. ``size=[1000,1000]``. filetype : str - String of the image type to be downloaded, e.g. ``filetype='image/png'`` + String of the image type to be downloaded, e.g. ``filetype='image/png'``. - transparent : bool + transparent : bool, default: ``True`` Variable if layer is transparent. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - save_image : bool + save_image : bool, default: ``False`` Variable to save image. - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. - path : str - Path and file name of the file to be saved, e.g. ``path=map.tif`` + path : str, default: ``None`` + Path and file name of the file to be saved, e.g. ``path=map.tif``, default is ``None``. - overwrite_file : bool + overwrite_file : bool, default: ``False`` Variable to overwrite an already existing file. - Options include: ``True`` or ``False``, default set to ``False`` - - create_directory : bool - Variable to create a new directory of directory does not exist - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. + create_directory : bool, default: ``False`` + Variable to create a new directory of directory does not exist. + Options include: ``True`` or ``False``, default set to ``False``. Returns _______ @@ -162,6 +179,8 @@ def load_as_map(url: str, .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -178,66 +197,69 @@ def load_as_map(url: str, load_as_array : Load Map as array from WMS Service """ - # Trying to import owslib but returning error if owslib is not installed try: - import owslib from owslib import util - from owslib.wms import WebMapService - from owslib.wfs import WebFeatureService - from owslib.wcs import WebCoverageService + __all__ = [util] except ModuleNotFoundError: raise ModuleNotFoundError( - 'owslib package is not installed. Use pip install owslib to install the latest version') + "owslib package is not installed. Use pip install owslib to install the latest version" + ) # Checking if the url is of type string if not isinstance(url, str): - raise TypeError('URL must be of type string') + raise TypeError("URL must be of type string") + + # Checking that the version is provided as string + if not isinstance(version, str): + raise TypeError("The WMS Service version must be provided as string") # Checking if the layer name is of type string if not isinstance(layer, str): - raise TypeError('Layers must be of type string') + raise TypeError("Layers must be of type string") # Checking if the style is of type string if not isinstance(style, str): - raise TypeError('Style must be of type string') + raise TypeError("Style must be of type string") # Checking if the crs is of type string or dict if not isinstance(crs, (str, dict)): - raise TypeError('CRS must be of type str or dict') + raise TypeError("CRS must be of type str or dict") # Checking if bbox is of type list if not isinstance(bbox, list): - raise TypeError('Bbox must be of type list') + raise TypeError("Bbox must be of type list") # Checking the length of the bbox list if len(bbox) != 4: - raise ValueError('Provide minx, maxx, miny, and maxy values for the bounding box') + raise ValueError( + "Provide minx, maxx, miny, and maxy values for the bounding box" + ) # Checking if size is of type list if not isinstance(size, list): - raise TypeError('Size must be of type list') + raise TypeError("Size must be of type list") # Checking the length of the size list if len(size) != 2: - raise ValueError('Provide only a x- and y-value for the size') + raise ValueError("Provide only a x- and y-value for the size") # Checking if file type is of type string if not isinstance(filetype, str): - raise TypeError('File type must be of type string') + raise TypeError("File type must be of type string") # Checking if the transparency is of type book if not isinstance(transparent, bool): - raise TypeError('transparent must be of type bool') + raise TypeError("transparent must be of type bool") # Checking if save_image is of type bool if not isinstance(save_image, bool): - raise TypeError('Save_image must be of type bool') + raise TypeError("Save_image must be of type bool") # Checking is path is of type string if not isinstance(path, (str, type(None))): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") if isinstance(path, str): # Getting the absolute path @@ -256,100 +278,113 @@ def load_as_map(url: str, if create_directory: os.makedirs(path_dir) else: - raise LookupError('Directory not found. Pass create_directory=True to create a new directory') + raise LookupError( + "Directory not found. Pass create_directory=True to create a new directory" + ) if not overwrite_file: if os.path.exists(path): raise FileExistsError( - "The file already exists. Pass overwrite_file=True to overwrite the existing file") + "The file already exists. Pass overwrite_file=True to overwrite the existing file" + ) # Loading WMS Service - wms = load_wms(url) + wms = load_wms(url, version=version) # Creating map object - wms_map = wms.getmap(layers=[layer], styles=[style], srs=crs, bbox=tuple([bbox[0], bbox[2], bbox[1], bbox[3]]), - size=tuple(size), format=filetype, - transparent=transparent) + wms_map = wms.getmap( + layers=[layer], + styles=[style], + srs=crs, + bbox=tuple([bbox[0], bbox[2], bbox[1], bbox[3]]), + size=tuple(size), + format=filetype, + transparent=transparent, + ) # Saving an image if save_image is true and a path is provided if save_image: if isinstance(path, str): - out = open(path, 'wb') + out = open(path, "wb") out.write(wms_map.read()) out.close() else: - raise ValueError('Path is missing') + raise ValueError("Path is missing") else: if isinstance(path, str): - raise ValueError('Save_image was set to False') + raise ValueError("Save_image was set to False") return wms_map -def load_as_array(url: str, - layer: str, - style: str, - crs: Union[str, dict], - bbox: List[Union[float, int]], - size: List[int], - filetype: str, - transparent: bool = True, - save_image: bool = False, - path: str = None, - overwrite_file: bool = False, - create_directory: bool = False) -> np.ndarray: - """Loading a portion of a WMS as array +def load_as_array( + url: str, + version: str, + layer: str, + style: str, + crs: Union[str, dict], + bbox: List[Union[float, int]], + size: List[int], + filetype: str, + transparent: bool = True, + save_image: bool = False, + path: str = None, + overwrite_file: bool = False, + create_directory: bool = False, +) -> np.ndarray: + """Load. a portion of a WMS as array. Parameters __________ url : str - Link of the WMS Service, e.g. ``url='https://ows.terrestris.de/osm/service?'`` + Link of the WMS Service, e.g. ``url='https://ows.terrestris.de/osm/service?'``. + + version : str, default: ``'1.3.0'`` + Version of the WMS Service, e.g. ``version='1.3.0'``. layer : str - Name of layer to be requested, e.g. ``layer='OSM-WMS'`` + Name of layer to be requested, e.g. ``layer='OSM-WMS'``. style : str - Name of style of the layer, e.g. ``style='default'`` + Name of style of the layer, e.g. ``style='default'``. crs : str - String or dict containing the CRS, e.g. ``crs='EPSG:4647'`` + String or dict containing the CRS, e.g. ``crs='EPSG:4647'``. bbox : List[Union[float,int]] - List of bounding box coordinates, e.g. ``bbox=[0, 972, 0, 1069]`` + List of bounding box coordinates, e.g. ``bbox=[0, 972, 0, 1069]``. size : List[int] - List of x and y values defining the size of the image, e.g. ``size=[1000,1000]`` + List of x and y values defining the size of the image, e.g. ``size=[1000,1000]``. filetype : str - String of the image type to be downloaded, e.g. 'filetype='image/png'`` + String of the image type to be downloaded, e.g. 'filetype='image/png'``. - transparent : bool + transparent : bool, default: ``True`` Variable if layer is transparent. - Options include: ``True`` or ``False``, default set to ``True`` + Options include: ``True`` or ``False``, default set to ``True``. - save_image : bool + save_image : bool, default: ``False`` Variable to save image. - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. - path : str - Path and file name of the file to be saved, e.g. ``path=map.tif`` + path : str, default: ``None`` + Path and file name of the file to be saved, e.g. ``path=map.tif``, default is ``None``. - overwrite_file : bool + overwrite_file : bool, default: ``False`` Variable to overwrite an already existing file. - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. - create_directory : bool - Variable to create a new directory of directory does not exist - Options include: ``True`` or ``False``, default set to ``False`` + create_directory : bool, default: ``False`` + Variable to create a new directory of directory does not exist. + Options include: ``True`` or ``False``, default set to ``False``. Returns _______ wms_array: np.ndarray - OWSlib map object loaded as np.ndarray - - .. versionadded:: 1.0.x + OWSlib map object loaded as np.ndarray. Example _______ @@ -372,73 +407,82 @@ def load_as_array(url: str, load_wms : Load WMS Service load_as_map : Load Map from WMS Service - """ + .. versionadded:: 1.0.x + + .. versionchanged:: 1.2 + """ # Trying to import owslib but returning error if owslib is not installed try: - import owslib from owslib import util - from owslib.wms import WebMapService - from owslib.wfs import WebFeatureService - from owslib.wcs import WebCoverageService + __all__ = [util] except ModuleNotFoundError: raise ModuleNotFoundError( - 'owslib package is not installed. Use pip install owslib to install the latest version') + "owslib package is not installed. Use pip install owslib to install the latest version" + ) # Trying to import matplotlib but returning error if matplotlib is not installed try: import matplotlib.pyplot as plt except ModuleNotFoundError: - raise ModuleNotFoundError('Matplotlib package is not installed. Use pip install matplotlib to install the latest version') + raise ModuleNotFoundError( + "Matplotlib package is not installed. Use pip install matplotlib to install the latest version" + ) # Checking if the url is of type string if not isinstance(url, str): - raise TypeError('URL must be of type string') + raise TypeError("URL must be of type string") + + # Checking that the version is provided as string + if not isinstance(version, str): + raise TypeError("The WMS Service version must be provided as string") # Checking if the layer name is of type string if not isinstance(layer, str): - raise TypeError('Layers must be of type string') + raise TypeError("Layers must be of type string") # Checking if the style is of type string if not isinstance(style, str): - raise TypeError('Style must be of type string') + raise TypeError("Style must be of type string") # Checking if the crs is of type string or dict if not isinstance(crs, (str, dict)): - raise TypeError('CRS must be of type str or dict') + raise TypeError("CRS must be of type str or dict") # Checking if bbox is of type list if not isinstance(bbox, list): - raise TypeError('Bbox must be of type list') + raise TypeError("Bbox must be of type list") # Checking the length of the bbox list if len(bbox) != 4: - raise ValueError('Provide minx, maxx, miny and maxy values for the bounding box') + raise ValueError( + "Provide minx, maxx, miny and maxy values for the bounding box" + ) # Checking if size is of type list if not isinstance(size, list): - raise TypeError('Size must be of type list') + raise TypeError("Size must be of type list") # Checking the length of the size list if len(size) != 2: - raise ValueError('Provide only a x- and y-value for the size') + raise ValueError("Provide only a x- and y-value for the size") # Checking if file type is of type string if not isinstance(filetype, str): - raise TypeError('File type must be of type string') + raise TypeError("File type must be of type string") # Checking if the transparency is of type book if not isinstance(transparent, bool): - raise TypeError('transparent must be of type bool') + raise TypeError("transparent must be of type bool") # Checking if save_image is of type bool if not isinstance(save_image, bool): - raise TypeError('Save_image must be of type bool') + raise TypeError("Save_image must be of type bool") # Checking is path is of type string if not isinstance(path, (str, type(None))): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") if isinstance(path, str): # Getting the absolute path @@ -457,24 +501,30 @@ def load_as_array(url: str, if create_directory: os.makedirs(path_dir) else: - raise LookupError('Directory not found. Pass create_directory=True to create a new directory') + raise LookupError( + "Directory not found. Pass create_directory=True to create a new directory" + ) if not overwrite_file: if os.path.exists(path): raise FileExistsError( - "The file already exists. Pass overwrite_file=True to overwrite the existing file") + "The file already exists. Pass overwrite_file=True to overwrite the existing file" + ) # Creating WMS map object - wms_map = load_as_map(url=url, - layer=layer, - style=style, - crs=crs, - bbox=bbox, - size=size, - filetype=filetype, - transparent=transparent, - save_image=save_image, - path=path) + wms_map = load_as_map( + url=url, + version=version, + layer=layer, + style=style, + crs=crs, + bbox=bbox, + size=size, + filetype=filetype, + transparent=transparent, + save_image=save_image, + path=path, + ) # Converting WMS map object to array maps = io.BytesIO(wms_map.read()) @@ -488,22 +538,24 @@ def load_as_array(url: str, def load_wfs(url: str): # -> owslib.wfs.WebFeatureService: - """Loading a WFS Service by URL + """Load a WFS Service by URL. Parameters __________ url : str - Link of the WFS Service, e.g. ``url="https://nibis.lbeg.de/net3/public/ogc.ashx?NodeId=476&Service=WFS&"`` + Link of the WFS Service, e.g. ``url="https://nibis.lbeg.de/net3/public/ogc.ashx?NodeId=476&Service=WFS&"``. Returns _______ wfs : owslib.feature.wfs100.WebFeatureService_1_0_0 - OWSLib Feature object + OWSLib Feature object. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -519,67 +571,69 @@ def load_wfs(url: str): # -> owslib.wfs.WebFeatureService: load_as_gpd : Load information of a WFS Service as GeoDataFrame """ - # Trying to import owslib but returning error if owslib is not installed try: - import owslib from owslib import util - from owslib.wms import WebMapService from owslib.wfs import WebFeatureService - from owslib.wcs import WebCoverageService + __all__ = [util] except ModuleNotFoundError: raise ModuleNotFoundError( - 'owslib package is not installed. Use pip install owslib to install the latest version') + "owslib package is not installed. Use pip install owslib to install the latest version" + ) # Trying to import requests but returning error if requests is not installed try: import requests except ModuleNotFoundError: raise ModuleNotFoundError( - 'requests package is not installed. Use pip install requests to install the latest version') + "requests package is not installed. Use pip install requests to install the latest version" + ) # Checking if url is of type string if not isinstance(url, str): - raise TypeError('URL must be of type string') + raise TypeError("URL must be of type string") # Requesting the WMS Service or returning an error if a module may be missing try: - wfs = owslib.wfs.WebFeatureService(url) + wfs = WebFeatureService(url) return wfs except requests.exceptions.SSLError: - print("GemGIS: SSL Error, potentially related to missing module - try:\n\n pip install -U openssl \n\n") + print( + "GemGIS: SSL Error, potentially related to missing module - try:\n\n pip install -U openssl \n\n" + ) raise -def load_as_gpd(url: str, - typename: str = None, - outputformat: str = None - ) -> gpd.geodataframe.GeoDataFrame: - """Requesting data from a WFS Service +def load_as_gpd( + url: str, typename: str = None, outputformat: str = None +) -> gpd.geodataframe.GeoDataFrame: + """Request data from a WFS Service Parameters __________ url : str - URL of the Web Feature Service, e.g. ``url="https://nibis.lbeg.de/net3/public/ogc.ashx?NodeId=476&Service=WFS&"`` + URL of the Web Feature Service, e.g. ``url="https://nibis.lbeg.de/net3/public/ogc.ashx?NodeId=476&Service=WFS&"``. - typename : str - Name of the feature layer, e.g. ``typename='iwan:L383'``, default is ``None`` + typename : str, default: ``None`` + Name of the feature layer, e.g. ``typename='iwan:L383'``, default is ``None``. - outputformat : str - Output format of the feature layer, e.g. ``outputformat='xml/gml2'``, default is ``None`` + outputformat : str, default: ``None`` + Output format of the feature layer, e.g. ``outputformat='xml/gml2'``, default is ``None``. Returns _______ feature : gpd.geodataframe.GeoDataFrame - GeoDataFrame containing the feature data of the WFS Service + GeoDataFrame containing the feature data of the WFS Service. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -587,8 +641,12 @@ def load_as_gpd(url: str, >>> import gemgis as gg >>> wfs = gg.web.load_as_gpd(url="https://nibis.lbeg.de/net3/public/ogc.ashx?NodeId=476&Service=WFS&") >>> wfs - gml_id OBJECTID ID SURVEYNAME ARCHIV MESSJAHR OPERATOR OP_NACHFOL MESSFIRMA MESSPUNKTE UP_DATE geometry - 0 1541 1541 112 Jemgum 2007 0127494 2007 GdF Produktion Exploration Deutschland GmbH Neptune Energy Deutschland GmbH Geophysik und Geotechnik Leipzig GmbH 1340 2020-01-20T00:00:00+01:00 MULTIPOLYGON (((32395246.839 5907777.660, 3239... + + +---------+----------+----+------------+----------+----------+--------------------------------------------------------+--------------------------------------+----------------------------------------------+------------+----------------------------+----------------------------------------------------+ + | gml_id | OBJECTID | ID | SURVEYNAME | ARCHIV | MESSJAHR | OPERATOR | OP_NACHFOL | MESSFIRMA | MESSPUNKTE | UP_DATE | geometry | + +---------+----------+----+------------+----------+----------+--------------------------------------------------------+--------------------------------------+----------------------------------------------+------------+----------------------------+----------------------------------------------------+ + | 1541 | 1541 | 112| Jemgum 2007| 0127494 | 2007 | GdF Produktion Exploration Deutschland GmbH | Neptune Energy Deutschland GmbH | Geophysik und Geotechnik Leipzig GmbH | 1340 | 2020-01-20T00:00:00+01:00 | MULTIPOLYGON (((32395246.839 5907777.660, 3239... | + +---------+----------+----+------------+----------+----------+--------------------------------------------------------+--------------------------------------+----------------------------------------------+------------+----------------------------+----------------------------------------------------+ See Also ________ @@ -596,37 +654,35 @@ def load_as_gpd(url: str, load_wfs : Load WFS Service """ - # Trying to import owslib but returning error if owslib is not installed try: - import owslib from owslib import util - from owslib.wms import WebMapService - from owslib.wfs import WebFeatureService - from owslib.wcs import WebCoverageService + __all__ = [util] except ModuleNotFoundError: raise ModuleNotFoundError( - 'owslib package is not installed. Use pip install owslib to install the latest version') + "owslib package is not installed. Use pip install owslib to install the latest version" + ) # Trying to import requests but returning error if requests is not installed try: import requests except ModuleNotFoundError: raise ModuleNotFoundError( - 'requests package is not installed. Use pip install requests to install the latest version') + "requests package is not installed. Use pip install requests to install the latest version" + ) # Checking that the url is of type string if not isinstance(url, str): - raise TypeError('URL must be of type string') + raise TypeError("URL must be of type string") # Checking that the typename is of type string or None if not isinstance(typename, (str, type(None))): - raise TypeError('Name of the feature must be of type string') + raise TypeError("Name of the feature must be of type string") # Checking that the outputformat is of type string if not isinstance(outputformat, (str, type(None))): - raise TypeError('The output format must be of type string') + raise TypeError("The output format must be of type string") # Loading the wfs layer wfs = load_wfs(url=url) @@ -635,19 +691,26 @@ def load_as_gpd(url: str, if not typename: layer = list(wfs.contents)[0] else: - raise ValueError('No layer available') + raise ValueError("No layer available") # If the output format is not provided, take the last if not outputformat: - if wfs.getOperationByName('GetFeature').formatOptions == ['{http://www.opengis.net/wfs}GML2']: - outputformat = 'xml/gml2' + if wfs.getOperationByName("GetFeature").formatOptions == [ + "{http://www.opengis.net/wfs}GML2" + ]: + outputformat = "xml/gml2" # Specify the parameters for fetching the data - params = dict(service='WFS', version=wfs.version, request='GetFeature', - typeName=layer, outputFormat=outputformat) + params = dict( + service="WFS", + version=wfs.version, + request="GetFeature", + typeName=layer, + outputFormat=outputformat, + ) # Parse the URL with parameters - q = requests.Request('GET', url, params=params).prepare().url + q = requests.Request("GET", url, params=params).prepare().url # Read data from request feature = gpd.read_file(q) @@ -660,22 +723,24 @@ def load_as_gpd(url: str, def load_wcs(url: str): # -> owslib.wcs.WebCoverageService: - """Loading Web Coverage Service + """Load Web Coverage Service. Parameters __________ url : str - Link of the Web Coverage Service, e.g. ``url='https://www.wcs.nrw.de/geobasis/wcs_nw_dgm'`` + Link of the Web Coverage Service, e.g. ``url='https://www.wcs.nrw.de/geobasis/wcs_nw_dgm'``. Returns _______ wcs : owslib.coverage.wcs201.WebCoverageService_2_0_1 - OWSLib Web Coverage Object + OWSLib Web Coverage Object. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -693,67 +758,69 @@ def load_wcs(url: str): # -> owslib.wcs.WebCoverageService: load_as_files : Download WCS data files """ - # Trying to import owslib but returning error if owslib is not installed try: - import owslib from owslib import util - from owslib.wms import WebMapService - from owslib.wfs import WebFeatureService from owslib.wcs import WebCoverageService + __all__ = [util] except ModuleNotFoundError: raise ModuleNotFoundError( - 'owslib package is not installed. Use pip install owslib to install the latest version') + "owslib package is not installed. Use pip install owslib to install the latest version" + ) # Checking if URL is of type string if not isinstance(url, str): - raise TypeError('URL must be of type string') + raise TypeError("URL must be of type string") # Loading the WCS Layer - wcs = owslib.wcs.WebCoverageService(url) + wcs = WebCoverageService(url) return wcs -def create_request(wcs_url: str, - version: str, - identifier: str, - form: str, - extent: List[Union[float, int]], - name: str = 'test.tif') -> str: - """Creating URL to request data from WCS Server +def create_request( + wcs_url: str, + version: str, + identifier: str, + form: str, + extent: List[Union[float, int]], + name: str = "test.tif", +) -> str: + """Create URL to request data from WCS Server. Parameters __________ wcs_url : str - Url of the WCS server, e.g. ``url='https://www.wcs.nrw.de/geobasis/wcs_nw_dgm'`` + Url of the WCS server, e.g. ``url='https://www.wcs.nrw.de/geobasis/wcs_nw_dgm'``. version : str - Version number of the WCS as string, e.g. ``version='2.0.1'`` + Version number of the WCS as string, e.g. ``version='2.0.1'``. identifier : str - Name of the layer, e.g. ``identifier='nw_dgm'`` + Name of the layer, e.g. ``identifier='nw_dgm'``. form : str - Format of the layer, e.g. ``form='image/tiff'`` + Format of the layer, e.g. ``form='image/tiff'``. extent : List[Union[float,int]] Extent of the tile to be downloaded, size may be restricted by server, - e.g. ``extent=[0, 972, 0, 1069]`` + e.g. ``extent=[0, 972, 0, 1069]``. - name : str - Name of file, e.g. ``name='tile1.tif'``, default is ``'test.tif'`` + name : str, default: ``'test.tif'`` + Name of file, e.g. ``name='tile1.tif'``, default is ``'test.tif'``. Returns _______ url : str - Url for the WCS request + Url for the WCS request. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -774,77 +841,102 @@ def create_request(wcs_url: str, load_as_files : Download WCS data files """ - # Trying to import owslib but returning error if owslib is not installed try: - import owslib from owslib import util - from owslib.wms import WebMapService - from owslib.wfs import WebFeatureService - from owslib.wcs import WebCoverageService + __all__ = [util] except ModuleNotFoundError: raise ModuleNotFoundError( - 'owslib package is not installed. Use pip install owslib to install the latest version') + "owslib package is not installed. Use pip install owslib to install the latest version" + ) # Checking that the URL is of type string if not isinstance(wcs_url, str): - raise TypeError('URL must be of type string') + raise TypeError("URL must be of type string") # Checking that the version number is of type string if not isinstance(version, str): - raise TypeError('WCS Version must be of type string') + raise TypeError("WCS Version must be of type string") # Checking that the identifier is of type string if not isinstance(identifier, str): - raise TypeError('Layer Name/Identifier must be of type string') + raise TypeError("Layer Name/Identifier must be of type string") # Checking that the format is of type string if not isinstance(form, str): - raise TypeError('Download format must be of type string') + raise TypeError("Download format must be of type string") # Checking that the extent is of type list if not isinstance(extent, list): - raise TypeError('Extent must be provided as list of minx, maxx, miny, maxy') + raise TypeError("Extent must be provided as list of minx, maxx, miny, maxy") # Checking the length of the extent if len(extent) != 4: - raise ValueError('Extent must be provided as list of minx, maxx, miny, maxy') + raise ValueError("Extent must be provided as list of minx, maxx, miny, maxy") # Create URL for Request - url = wcs_url + '?' + 'REQUEST=GetCoverage' + '&' + 'SERVICE=WCS' + '&' + 'VERSION=' + str(version) + '&' + \ - 'COVERAGEID=' + identifier + '&' + 'FORMAT=' + form + '&' + \ - 'SUBSET=x(' + str(extent[0]) + ',' + str(extent[1]) + ')' + '&' + \ - 'SUBSET=y(' + str(extent[2]) + ',' + str(extent[3]) + ')' + '&' + 'OUTFILE=' + name + url = ( + wcs_url + + "?" + + "REQUEST=GetCoverage" + + "&" + + "SERVICE=WCS" + + "&" + + "VERSION=" + + str(version) + + "&" + + "COVERAGEID=" + + identifier + + "&" + + "FORMAT=" + + form + + "&" + + "SUBSET=x(" + + str(extent[0]) + + "," + + str(extent[1]) + + ")" + + "&" + + "SUBSET=y(" + + str(extent[2]) + + "," + + str(extent[3]) + + ")" + + "&" + + "OUTFILE=" + + name + ) return url -def load_as_file(url: str, - path: str, - overwrite_file: bool = False, - create_directory: bool = False): - """Executing WCS request and downloading file into specified folder +def load_as_file( + url: str, path: str, overwrite_file: bool = False, create_directory: bool = False +): + """Execute WCS request and downloading file into specified folder. Parameters __________ url: str - Url for request + Url for request. path: str - Path where file is saved, e.g. ``path='tile.tif'`` + Path where file is saved, e.g. ``path='tile.tif'``. - overwrite_file : bool + overwrite_file : bool, default: ``False`` Variable to overwrite an already existing file. - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. - create_directory : bool + create_directory : bool, default: ``False`` Variable to create a new directory of directory does not exist - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -868,32 +960,31 @@ def load_as_file(url: str, load_as_files : Download WCS data files """ - # Trying to import owslib but returning error if owslib is not installed try: - import owslib from owslib import util - from owslib.wms import WebMapService - from owslib.wfs import WebFeatureService - from owslib.wcs import WebCoverageService + __all__ = [util] except ModuleNotFoundError: raise ModuleNotFoundError( - 'owslib package is not installed. Use pip install owslib to install the latest version') + "owslib package is not installed. Use pip install owslib to install the latest version" + ) # Trying to import urllib but returning error if urllib is not installed try: import urllib except ModuleNotFoundError: - raise ModuleNotFoundError('urllib package is not installed. Use pip install urllib to install the latest version') + raise ModuleNotFoundError( + "urllib package is not installed. Use pip install urllib to install the latest version" + ) # Checking that the url is of type string if not isinstance(url, str): - raise TypeError('URL must be of type string') + raise TypeError("URL must be of type string") # Checking that the path is of type string if not isinstance(path, str): - raise TypeError('Path must be of type string') + raise TypeError("Path must be of type string") # Getting the absolute path path = os.path.abspath(path=path) @@ -910,58 +1001,65 @@ def load_as_file(url: str, if create_directory: os.makedirs(path_dir) else: - raise LookupError('Directory not found. Pass create_directory=True to create a new directory') + raise LookupError( + "Directory not found. Pass create_directory=True to create a new directory" + ) if not overwrite_file: if os.path.exists(path): raise FileExistsError( - "The file already exists. Pass overwrite_file=True to overwrite the existing file") + "The file already exists. Pass overwrite_file=True to overwrite the existing file" + ) # Executing request and downloading files to the specified folder urllib.request.urlretrieve(url, path) -def load_as_files(wcs_url: str, - version: str, - identifier: str, - form: str, - extent: List[Union[float, int]], - size: int, - path: str = '', - create_directory: bool = False): - """Executing WCS requests and downloading files into specified folder +def load_as_files( + wcs_url: str, + version: str, + identifier: str, + form: str, + extent: List[Union[float, int]], + size: int, + path: str = "", + create_directory: bool = False, +): + """Execute WCS requests and downloading files into specified folder. Parameters __________ wcs_url : str - Url of the WCS server, e.g. ``url='https://www.wcs.nrw.de/geobasis/wcs_nw_dgm'`` + Url of the WCS server, e.g. ``url='https://www.wcs.nrw.de/geobasis/wcs_nw_dgm'``. version : str - Version number of the WCS as string, e.g. ``version='2.0.1'`` + Version number of the WCS as string, e.g. ``version='2.0.1'``. identifier : str - Name of the layer, e.g. ``identifier='nw_dgm'`` + Name of the layer, e.g. ``identifier='nw_dgm'``. form : str - Format of the layer, e.g. ``form='image/tiff'`` + Format of the layer, e.g. ``form='image/tiff'``. extent : List[Union[float,int]] Extent of the tile to be downloaded, size may be restricted by server, - e.g. ``extent=[0, 972, 0, 1069]`` + e.g. ``extent=[0, 972, 0, 1069]``. size : int - Size of the quadratic tile that is downloaded, e.g. ``size=2000`` + Size of the quadratic tile that is downloaded, e.g. ``size=2000``. path : str - Path where the file is going to be downloaded, e.g. ``name='tile1'`` + Path where the file is going to be downloaded, e.g. ``name='tile1'``. - create_directory : bool + create_directory : bool, default: ``False`` Variable to create a new directory of directory does not exist - Options include: ``True`` or ``False``, default set to ``False`` + Options include: ``True`` or ``False``, default set to ``False``. .. versionadded:: 1.0.x + .. versionchanged:: 1.2 + Example _______ @@ -985,49 +1083,49 @@ def load_as_files(wcs_url: str, # Trying to import owslib but returning error if owslib is not installed try: - import owslib from owslib import util - from owslib.wms import WebMapService - from owslib.wfs import WebFeatureService - from owslib.wcs import WebCoverageService + __all__ = [util] except ModuleNotFoundError: raise ModuleNotFoundError( - 'owslib package is not installed. Use pip install owslib to install the latest version') + "owslib package is not installed. Use pip install owslib to install the latest version" + ) # Trying to import tqdm but returning error if tqdm is not installed try: from tqdm import tqdm except ModuleNotFoundError: - raise ModuleNotFoundError('tqdm package is not installed. Use pip install tqdm to install the latest version') + raise ModuleNotFoundError( + "tqdm package is not installed. Use pip install tqdm to install the latest version" + ) # Checking that the URL is of type string if not isinstance(wcs_url, str): - raise TypeError('URL must be of type string') + raise TypeError("URL must be of type string") # Checking that the version number is of type string if not isinstance(version, str): - raise TypeError('WCS Version must be of type string') + raise TypeError("WCS Version must be of type string") # Checking that the identifier is of type string if not isinstance(identifier, str): - raise TypeError('Layer Name/Identifier must be of type string') + raise TypeError("Layer Name/Identifier must be of type string") # Checking that the format is of type string if not isinstance(form, str): - raise TypeError('Download format must be of type string') + raise TypeError("Download format must be of type string") # Checking that the extent is of type list if not isinstance(extent, list): - raise TypeError('Extent must be provided as list of minx, maxx, miny, maxy') + raise TypeError("Extent must be provided as list of minx, maxx, miny, maxy") # Checking the length of the extent if len(extent) != 4: - raise ValueError('Extent must be provided as list of minx, maxx, miny, maxy') + raise ValueError("Extent must be provided as list of minx, maxx, miny, maxy") # Checking that the provided size of each tile is of type int if not isinstance(size, int): - raise TypeError('Tile size must be provided as int') + raise TypeError("Tile size must be provided as int") # Calculating the x Extent x = extent[1] - extent[0] @@ -1036,41 +1134,56 @@ def load_as_files(wcs_url: str, y = extent[3] - extent[2] # Printing the extent and number of tiles that are going to be downloaded - print('Extent X: ', x, ' m') - print('Extent Y: ', y, ' m') - print('Number of tiles in X directions: ', int(x / size)) - print('Number of tiles in Y directions: ', int(y / size)) - print('Total Number of Tiles: ', int(x / size) * int(y / size)) + print("Extent X: ", x, " m") + print("Extent Y: ", y, " m") + print("Number of tiles in X directions: ", int(x / size)) + print("Number of tiles in Y directions: ", int(y / size)) + print("Total Number of Tiles: ", int(x / size) * int(y / size)) # Loop through each tile and download data for i in tqdm(range(int(x / size))): for j in range(int(y / size)): # Download data only if the tile does not exist yet - if not os.path.exists(path + 'tile_%d_%d_%d_%d.tif' % - (extent[0] + i * size, - extent[0] + (i + 1) * size, - extent[2] + j * size, - extent[2] + (j + 1) * size)): + if not os.path.exists( + path + + "tile_%d_%d_%d_%d.tif" + % ( + extent[0] + i * size, + extent[0] + (i + 1) * size, + extent[2] + j * size, + extent[2] + (j + 1) * size, + ) + ): # Create URL request - url = create_request(wcs_url=wcs_url, - version=version, - identifier=identifier, - form=form, - extent=[extent[0] + i * size, - extent[0] + (i + 1) * size, - extent[2] + j * size, - extent[2] + (j + 1) * size], - name=path) + url = create_request( + wcs_url=wcs_url, + version=version, + identifier=identifier, + form=form, + extent=[ + extent[0] + i * size, + extent[0] + (i + 1) * size, + extent[2] + j * size, + extent[2] + (j + 1) * size, + ], + name=path, + ) print(url) # Load file - load_as_file(url=url, - path=path + 'tile_%d_%d_%d_%d.tif' % (extent[0] + i * size, - extent[0] + (i + 1) * size, - extent[2] + j * size, - extent[2] + (j + 1) * size), - create_directory=create_directory) + load_as_file( + url=url, + path=path + + "tile_%d_%d_%d_%d.tif" + % ( + extent[0] + i * size, + extent[0] + (i + 1) * size, + extent[2] + j * size, + extent[2] + (j + 1) * size, + ), + create_directory=create_directory, + ) else: - print('All tiles have already been downloaded') + print("All tiles have already been downloaded") pass diff --git a/pyproject.toml b/pyproject.toml index 4d80497d..f3104f8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,15 +22,15 @@ keywords = ["dataprocessing", "modeling", "geospatial", "geographic-data", "spat readme = "README.md" license = {file = "LICENSE"} dynamic = ['version'] -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Science/Research', 'Topic :: Scientific/Engineering :: Information Analysis', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Operating System :: OS Independent', ] @@ -38,8 +38,14 @@ classifiers = [ # These dependencies will automatically install other packages like numpy, pandas or matplotlib dependencies = [ 'geopandas', + 'shapely', + 'pandas', + 'numpy', + 'affine', + 'pyproj', 'rasterio', 'pyvista', + 'matplotlib', ] [project.optional-dependencies] diff --git a/requirements.txt b/requirements.txt index 2af1968c..f2d0859a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,16 @@ -# Requirements as of October 2023 +# Requirements as of July 2024 -# geopandas will also install numpy, pandas, shapely, fiona, and pyproj -geopandas>=0.14.0 +# geopandas will also install numpy, pandas, shapely, pyogrio, and pyproj +geopandas>=1.0.1 +shapely>=2.0.6 +pandas>=2.2.3 +numpy>=2.1.3 +pyproj>=3.7.0 # rasterio will also install affine -rasterio>=1.3.8 +rasterio>=1.4.3 +affine>=2.4.0 # pyvista also install pooch and matplotlib -pyvista>=0.42.2 \ No newline at end of file +pyvista>=0.44.2 +matplotlib>=3.9.3 \ No newline at end of file diff --git a/tests/test_gemgis.py b/tests/test_gemgis.py index a00061a9..2677a8fc 100644 --- a/tests/test_gemgis.py +++ b/tests/test_gemgis.py @@ -27,7 +27,6 @@ import pandas as pd from shapely import geometry import geopandas as gpd -import gempy as gp import gemgis as gg gg.download_gemgis_data.download_tutorial_data(filename='test_gemgis.zip', diff --git a/tests/test_misc.py b/tests/test_misc.py index 11ca3b12..98fc92a9 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -370,7 +370,7 @@ def test_stratigraphic_table_list_comprehension(): try: pdf = load_pdf('../docs/getting_started/tutorial/data/test_misc/test_pdf.pdf') - assert type(pdf) == str + assert type(pdf) is str df = get_stratigraphic_data_df(data=pdf, name='Test', @@ -378,7 +378,7 @@ def test_stratigraphic_table_list_comprehension(): formations=formations, return_gdf=False) - assert type(df) == pd.DataFrame + assert type(df) is pd.DataFrame assert len(df) == 7 assert df.loc[0]['Depth'] == 1242 assert df.loc[4]['Depth'] == 1135 @@ -398,7 +398,7 @@ def test_stratigraphic_table_list_comprehension(): try: pdf = load_pdf('../docs/getting_started/tutorial/data/test_misc/test_pdf.pdf') - assert type(pdf) == str + assert type(pdf) is str df = get_stratigraphic_data_df(data=pdf, name='Test', @@ -407,7 +407,7 @@ def test_stratigraphic_table_list_comprehension(): remove_last=True, return_gdf=False) - assert type(df) == pd.DataFrame + assert type(df) is pd.DataFrame assert len(df) == 5 assert df.loc[0]['Depth'] == 1242 assert df.loc[4]['Depth'] == 1135 diff --git a/tests/test_utils.py b/tests/test_utils.py index 15de00bf..76c21d01 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -688,7 +688,7 @@ def test_get_nearest_neighbor(): index = get_nearest_neighbor(x=x, y=np.array([0, 0])) - assert type(index) == np.int64 + assert type(index) is np.int64 assert index == 0 diff --git a/tests/test_vector.py b/tests/test_vector.py index 9798d2e1..81f4475d 100644 --- a/tests/test_vector.py +++ b/tests/test_vector.py @@ -4910,17 +4910,17 @@ def test_extract_orientations_from_cross_sections(): assert {'X', 'Y', 'Z', 'dip', 'azimuth', 'polarity', 'geometry'}.issubset(orientation.columns) -# Testing intersection_polygon_polygon +# Testing intersect_polygon_polygon ########################################################## -def test_intersection_polygon_polygon(): - from gemgis.vector import intersection_polygon_polygon +def test_intersect_polygon_polygon(): + from gemgis.vector import intersect_polygon_polygon polygon1 = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)]) polygon2 = Polygon([(10, 0), (20, 0), (20, 10), (10, 10)]) - intersection = intersection_polygon_polygon(polygon1=polygon1, - polygon2=polygon2) + intersection = intersect_polygon_polygon(polygon1=polygon1, + polygon2=polygon2) assert isinstance(intersection, LineString) assert intersection.wkt == 'LINESTRING (10 0, 10 10)' @@ -4929,8 +4929,8 @@ def test_intersection_polygon_polygon(): polygon2 = Polygon([(5, 0), (15, 0), (15, 10), (5, 10)]) - intersection = intersection_polygon_polygon(polygon1=polygon1, - polygon2=polygon2) + intersection = intersect_polygon_polygon(polygon1=polygon1, + polygon2=polygon2) assert isinstance(intersection, Polygon) try: @@ -4938,20 +4938,19 @@ def test_intersection_polygon_polygon(): except AssertionError: assert intersection.wkt == 'POLYGON ((5 0, 5 10, 10 10, 10 0, 5 0))' - # Testing intersection_polygon_polygons - +# Testing intersect_polygon_polygons ########################################################## -def test_intersections_polygon_polygons(): - from gemgis.vector import intersections_polygon_polygons +def test_intersect_polygon_polygons(): + from gemgis.vector import intersect_polygon_polygons polygon1 = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)]) polygons2 = [Polygon([(10, 0), (20, 0), (20, 10), (10, 10)]), Polygon([(5, 0), (15, 0), (15, 10), (5, 10)])] - intersections = intersections_polygon_polygons(polygon1=polygon1, - polygons2=polygons2) + intersections = intersect_polygon_polygons(polygon1=polygon1, + polygons2=polygons2) assert isinstance(intersections, list) assert len(intersections) == 2 @@ -4965,17 +4964,17 @@ def test_intersections_polygon_polygons(): assert intersections[1].wkt == 'POLYGON ((5 0, 5 10, 10 10, 10 0, 5 0))' -# Testing intersection_polygon_polygons +# Testing intersect_polygon_polygons ########################################################## -def test_intersections_polygons_polygons(): - from gemgis.vector import intersections_polygons_polygons +def test_intersect_polygons_polygons(): + from gemgis.vector import intersect_polygons_polygons polygons = [Polygon([(0, 0), (10, 0), (10, 10), (0, 10)]), Polygon([(10, 0), (20, 0), (20, 10), (10, 10)]), Polygon([(5, 0), (15, 0), (15, 10), (5, 10)])] - intersections = intersections_polygons_polygons(polygons1=polygons, - polygons2=polygons) + intersections = intersect_polygons_polygons(polygons1=polygons, + polygons2=polygons) assert isinstance(intersections, list) assert len(intersections) == 9 diff --git a/tests/test_web.py b/tests/test_web.py index 254221c5..baa4ba85 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -478,7 +478,7 @@ def test_load_wfs(): wfs = load_wfs(url='https://nibis.lbeg.de/net3/public/ogc.ashx?NodeId=287&Service=WFS&Request=GetCapabilities&') - assert type(wfs) == owslib.feature.wfs100.WebFeatureService_1_0_0 + assert type(wfs) is owslib.feature.wfs100.WebFeatureService_1_0_0 assert wfs.version == '1.0.0' assert wfs.identification.version == '1.0.0' assert wfs.identification.type == 'LBEG' @@ -608,7 +608,7 @@ def test_create_request(): identifier='nw_dgm', form='image/tiff', extent=[292000, 298000, 5626000, 5632000]) - assert type(url) == str + assert type(url) is str assert url == 'https://www.wcs.nrw.de/geobasis/wcs_nw_dgm?REQUEST=GetCoverage&SERVICE=WCS&VERSION=2.0.1&COVERAGEID=nw_dgm&FORMAT=image/tiff&SUBSET=x(292000,298000)&SUBSET=y(5626000,5632000)&OUTFILE=test.tif' with pytest.raises(TypeError):