subsurface-terra 2025.1.0rc13__tar.gz → 2025.1.0rc15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/.teamcity/Subsurface/buildTypes/Subsurface_ReleaseSubsurface_2.xml +4 -12
- {subsurface_terra-2025.1.0rc13/subsurface_terra.egg-info → subsurface_terra-2025.1.0rc15}/PKG-INFO +1 -1
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/_version.py +2 -2
- subsurface_terra-2025.1.0rc15/subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py +234 -0
- subsurface_terra-2025.1.0rc15/subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py +163 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/geological_formats/boreholes/boreholes.py +32 -5
- subsurface_terra-2025.1.0rc15/subsurface/core/geological_formats/boreholes/survey.py +86 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15/subsurface_terra.egg-info}/PKG-INFO +1 -1
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface_terra.egg-info/SOURCES.txt +2 -0
- subsurface_terra-2025.1.0rc13/subsurface/core/geological_formats/boreholes/survey.py +0 -380
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/.env.example +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/.teamcity/Subsurface/buildTypes/Subsurface_ReleaseSubsurface.xml +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/.teamcity/Subsurface/buildTypes/Subsurface_Testing.xml +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/.teamcity/Subsurface/project-config.xml +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/.teamcity/Subsurface/vcsRoots/Subsurface_HttpsGithubComTerranigmaSolutionsSubsurfaceRefsHeadsMain.xml +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/.teamcity/Subsurface/vcsRoots/Subsurface_HttpsGithubComTerranigmaSolutionsSubsurfaceRefsHeadsMain1.xml +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/.teamcity/Subsurface/vcsRoots/Subsurface_HttpsGithubComTerranigmaSolutionsSubsurfaceRefsHeadsMain2.xml +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/LICENSE +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/README.rst +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements_all.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements_dev.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements_geospatial.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements_mesh.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements_opt.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements_plot.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements_traces.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements_volume.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/requirements/requirements_wells.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/setup.cfg +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/setup.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/api/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/api/interfaces/README.rst +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/api/interfaces/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/api/interfaces/stream.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/api/reader/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/api/reader/read_wells.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/geological_formats/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/geological_formats/boreholes/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/geological_formats/boreholes/_combine_trajectories.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/geological_formats/boreholes/collars.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/geological_formats/fault.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/reader_helpers/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/reader_helpers/reader_unstruct.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/reader_helpers/readers_data.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/reader_helpers/readers_wells.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/README.rst +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/base_structures/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/base_structures/_liquid_earth_mesh.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/base_structures/_unstructured_data_constructor.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/base_structures/base_structures_enum.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/base_structures/structured_data.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/base_structures/unstructured_data.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/structured_elements/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/structured_elements/octree_mesh.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/structured_elements/structured_grid.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/structured_elements/structured_mesh.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/unstructured_elements/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/unstructured_elements/line_set.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/unstructured_elements/point_set.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/unstructured_elements/tetrahedron_mesh.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/structs/unstructured_elements/triangular_surface.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/utils/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/core/utils/utils_core.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/README.rst +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/faults/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/faults/faults.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/from_binary.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/geo_object/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/NOTES.md +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/_GOCAD_mesh.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/_trimesh_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/csv_mesh_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/dxf_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/glb_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/mx_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/obj_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/omf_mesh_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/surface_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/mesh/surfaces_api.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/petrel/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/profiles/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/profiles/profiles_core.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/read_netcdf.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/topography/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/topography/topo_core.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/volume/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/volume/read_grav3d.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/volume/read_volume.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/volume/segy_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/volume/seismic.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/volume/volume_utils.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/DEP/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/DEP/_well_files_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/DEP/_wells_api.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/DEP/_welly_reader.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/DEP/pandas_to_welly.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/README.rst +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/_read_to_df.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/read_borehole_interface.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/reader/wells/wells_utils.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/tools/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/tools/mocking_aux.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/visualization/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/visualization/to_pyvista.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_binary.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_liquid_earth/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/__init__.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/common.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/data_struct.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/doc/rex-spec-v1.md +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/doc/right-handed.png +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/doc/sketchup_example.jpg +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/gempy_to_rexfile.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/material_encoder.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/mesh_encoder.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/to_rex.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/modules/writer/to_rex/utils.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface/optional_requirements.py +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface_terra.egg-info/dependency_links.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface_terra.egg-info/not-zip-safe +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface_terra.egg-info/requires.txt +0 -0
- {subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15}/subsurface_terra.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<build-type xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" uuid="c15fb876-7044-47cd-a3b0-676767e4ca63" xsi:noNamespaceSchemaLocation="https://www.jetbrains.com/teamcity/schemas/
|
|
2
|
+
<build-type xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" uuid="c15fb876-7044-47cd-a3b0-676767e4ca63" xsi:noNamespaceSchemaLocation="https://www.jetbrains.com/teamcity/schemas/2025.3/project-config.xsd">
|
|
3
3
|
<name>Release Subsurface</name>
|
|
4
4
|
<description />
|
|
5
5
|
<settings ref="ReleasePythonPackage">
|
|
6
|
-
<parameters
|
|
6
|
+
<parameters>
|
|
7
|
+
<param name="env.org/repo" value="terranigma-solutions/subsurface" />
|
|
8
|
+
</parameters>
|
|
7
9
|
<build-runners order="Git_Tagging, Build, Push_to_PyPi, Push_To_GitHub">
|
|
8
10
|
<runner id="Build" name="Build" type="simpleRunner">
|
|
9
11
|
<parameters>
|
|
@@ -18,20 +20,10 @@ python -m build]]></param>
|
|
|
18
20
|
<param name="use.custom.script" value="true" />
|
|
19
21
|
</parameters>
|
|
20
22
|
</runner>
|
|
21
|
-
<runner id="Push_To_GitHub" name="Push To GitHub" type="simpleRunner">
|
|
22
|
-
<parameters>
|
|
23
|
-
<param name="script.content" value="gh release create %env.PACKAGE_VERSION% dist/* --notes "Release notes here" --repo terranigma-solutions/subsurface" />
|
|
24
|
-
<param name="teamcity.step.mode" value="default" />
|
|
25
|
-
<param name="use.custom.script" value="true" />
|
|
26
|
-
</parameters>
|
|
27
|
-
</runner>
|
|
28
23
|
</build-runners>
|
|
29
24
|
<vcs-settings>
|
|
30
25
|
<vcs-entry-ref root-id="Subsurface_HttpsGithubComTerranigmaSolutionsSubsurfaceRefsHeadsMain1" />
|
|
31
26
|
</vcs-settings>
|
|
32
|
-
<requirements />
|
|
33
|
-
<build-triggers />
|
|
34
|
-
<cleanup />
|
|
35
27
|
</settings>
|
|
36
28
|
</build-type>
|
|
37
29
|
|
{subsurface_terra-2025.1.0rc13/subsurface_terra.egg-info → subsurface_terra-2025.1.0rc15}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: subsurface_terra
|
|
3
|
-
Version: 2025.1.
|
|
3
|
+
Version: 2025.1.0rc15
|
|
4
4
|
Summary: Subsurface data types and utilities. This version is the one used by Terranigma Solutions. Please feel free to take anything in this repository for the original one.
|
|
5
5
|
Home-page: https://softwareunderground.github.io/subsurface
|
|
6
6
|
Author: Software Underground
|
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '2025.1.
|
|
21
|
-
__version_tuple__ = version_tuple = (2025, 1, 0, '
|
|
20
|
+
__version__ = version = '2025.1.0rc15'
|
|
21
|
+
__version_tuple__ = version_tuple = (2025, 1, 0, 'rc15')
|
subsurface_terra-2025.1.0rc15/subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import xarray as xr
|
|
4
|
+
from scipy.interpolate import interp1d
|
|
5
|
+
from typing import Tuple, Optional, Union, List, Any
|
|
6
|
+
|
|
7
|
+
from ...structs.base_structures import UnstructuredData
|
|
8
|
+
from ...structs.base_structures._unstructured_data_constructor import raw_attributes_to_dict_data_arrays
|
|
9
|
+
from ...structs.unstructured_elements import LineSet
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def combine_survey_and_attrs(attrs: pd.DataFrame, survey_trajectory: LineSet,well_id_mapper: dict[str, int]) -> UnstructuredData:
|
|
13
|
+
# Import moved to top for clarity and possibly avoiding repeated imports if called multiple times
|
|
14
|
+
|
|
15
|
+
# Ensure all columns in lith exist in new_attrs, if not, add them as NaN
|
|
16
|
+
new_attrs = _map_attrs_to_measured_depths(attrs, survey_trajectory, well_id_mapper)
|
|
17
|
+
|
|
18
|
+
# Construct the final xarray dict without intermediate variable
|
|
19
|
+
points_attributes_xarray_dict: dict[str, xr.DataArray] = raw_attributes_to_dict_data_arrays(
|
|
20
|
+
default_attributes_name="vertex_attrs",
|
|
21
|
+
n_items=survey_trajectory.data.data["vertex_attrs"].shape[0], # TODO: Can I look this on new_attrs to remove line 11?
|
|
22
|
+
dims=["points", "vertex_attr"],
|
|
23
|
+
raw_attributes=new_attrs
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Inline construction of UnstructuredData
|
|
27
|
+
return UnstructuredData.from_data_arrays_dict(
|
|
28
|
+
xarray_dict={
|
|
29
|
+
"vertex" : survey_trajectory.data.data["vertex"],
|
|
30
|
+
"cells" : survey_trajectory.data.data["cells"],
|
|
31
|
+
"vertex_attrs": points_attributes_xarray_dict["vertex_attrs"],
|
|
32
|
+
"cell_attrs" : survey_trajectory.data.data["cell_attrs"]
|
|
33
|
+
},
|
|
34
|
+
xarray_attributes=survey_trajectory.data.data.attrs,
|
|
35
|
+
default_cells_attributes_name=survey_trajectory.data.cells_attr_name,
|
|
36
|
+
default_points_attributes_name=survey_trajectory.data.vertex_attr_name
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def _prepare_categorical_data(attrs: pd.DataFrame) -> pd.DataFrame:
|
|
40
|
+
"""
|
|
41
|
+
Prepare categorical data for interpolation by converting categorical columns to numeric IDs.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
attrs: DataFrame containing attribute data
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Modified DataFrame with categorical data prepared for interpolation
|
|
48
|
+
"""
|
|
49
|
+
# Create a copy to avoid modifying the original
|
|
50
|
+
attrs_copy = attrs.copy()
|
|
51
|
+
|
|
52
|
+
# If component lith exists but lith_ids doesn't, create lith_ids
|
|
53
|
+
if 'component lith' in attrs_copy.columns and 'lith_ids' not in attrs_copy.columns:
|
|
54
|
+
attrs_copy['lith_ids'], _ = pd.factorize(attrs_copy['component lith'], use_na_sentinel=True)
|
|
55
|
+
|
|
56
|
+
return attrs_copy
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _prepare_new_attributes(attrs: pd.DataFrame, survey_trajectory: LineSet) -> pd.DataFrame:
|
|
60
|
+
"""
|
|
61
|
+
Prepare the new attributes DataFrame by adding missing columns from attrs.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
attrs: DataFrame containing attribute data
|
|
65
|
+
survey_trajectory: LineSet containing trajectory data
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
New attributes DataFrame with all necessary columns
|
|
69
|
+
"""
|
|
70
|
+
# Start with a copy of the existing attributes DataFrame
|
|
71
|
+
new_attrs = survey_trajectory.data.points_attributes.copy()
|
|
72
|
+
|
|
73
|
+
# Add missing columns from attrs, preserving their dtypes
|
|
74
|
+
for col in attrs.columns.difference(new_attrs.columns):
|
|
75
|
+
new_attrs[col] = np.nan if pd.api.types.is_numeric_dtype(attrs[col]) else None
|
|
76
|
+
|
|
77
|
+
return new_attrs
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _get_interpolation_locations(attrs_well: pd.DataFrame, well_name: str) -> np.ndarray:
|
|
81
|
+
"""
|
|
82
|
+
Determine the locations to use for interpolation based on top and base values.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
attrs_well: DataFrame containing well attribute data
|
|
86
|
+
well_name: Name of the current well
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Array of location values to use for interpolation
|
|
90
|
+
"""
|
|
91
|
+
if "base" not in attrs_well.columns:
|
|
92
|
+
raise ValueError(f"Base column must be present in the file for well '{well_name}'.")
|
|
93
|
+
elif "top" not in attrs_well.columns:
|
|
94
|
+
return attrs_well['base'].values
|
|
95
|
+
else:
|
|
96
|
+
return ((attrs_well['top'] + attrs_well['base']) / 2).values
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _nearest_neighbor_categorical_interpolation(
|
|
100
|
+
x_locations: np.ndarray,
|
|
101
|
+
y_values: np.ndarray,
|
|
102
|
+
target_depths: np.ndarray
|
|
103
|
+
) -> np.ndarray:
|
|
104
|
+
"""
|
|
105
|
+
Custom nearest neighbor interpolation for categorical data.
|
|
106
|
+
|
|
107
|
+
This function finds the nearest source point for each target point
|
|
108
|
+
and assigns the corresponding categorical value.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
x_locations: Array of source locations
|
|
112
|
+
y_values: Array of categorical values at source locations
|
|
113
|
+
target_depths: Array of target depths for interpolation
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Array of interpolated categorical values
|
|
117
|
+
"""
|
|
118
|
+
# Initialize output array with NaN or None values
|
|
119
|
+
result = np.full(target_depths.shape, np.nan, dtype=object)
|
|
120
|
+
|
|
121
|
+
# For each target depth, find the nearest source location
|
|
122
|
+
for i, depth in enumerate(target_depths):
|
|
123
|
+
# Calculate distances to all source locations
|
|
124
|
+
distances = np.abs(x_locations - depth)
|
|
125
|
+
|
|
126
|
+
# Find the index of the minimum distance
|
|
127
|
+
if len(distances) > 0:
|
|
128
|
+
nearest_idx = np.argmin(distances)
|
|
129
|
+
result[i] = y_values[nearest_idx]
|
|
130
|
+
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _interpolate_attribute(
|
|
135
|
+
attr_values: pd.Series,
|
|
136
|
+
x_locations: np.ndarray,
|
|
137
|
+
target_depths: np.ndarray,
|
|
138
|
+
column_name: str,
|
|
139
|
+
is_categorical: bool
|
|
140
|
+
) -> np.ndarray:
|
|
141
|
+
"""
|
|
142
|
+
Interpolate attribute values to target depths.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
attr_values: Series containing attribute values
|
|
146
|
+
x_locations: Array of source locations for interpolation
|
|
147
|
+
target_depths: Array of target depths for interpolation
|
|
148
|
+
column_name: Name of the column being interpolated
|
|
149
|
+
is_categorical: Whether the attribute is categorical
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Array of interpolated values
|
|
153
|
+
"""
|
|
154
|
+
# For categorical data or specific columns, use custom nearest neighbor interpolation
|
|
155
|
+
if is_categorical or column_name in ['lith_ids', 'component lith']:
|
|
156
|
+
return _nearest_neighbor_categorical_interpolation(
|
|
157
|
+
x_locations=x_locations,
|
|
158
|
+
y_values=attr_values.values,
|
|
159
|
+
target_depths=target_depths
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
# For numerical data, use scipy's interp1d with linear interpolation
|
|
163
|
+
interp_func = interp1d(
|
|
164
|
+
x=x_locations,
|
|
165
|
+
y=attr_values.values,
|
|
166
|
+
bounds_error=False,
|
|
167
|
+
fill_value=np.nan,
|
|
168
|
+
kind='linear'
|
|
169
|
+
)
|
|
170
|
+
return interp_func(target_depths)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _map_attrs_to_measured_depths(attrs: pd.DataFrame, survey_trajectory: LineSet, well_id_mapper: dict[str, int]) -> pd.DataFrame:
|
|
174
|
+
"""
|
|
175
|
+
Map attributes to measured depths for each well.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
attrs: DataFrame containing attribute data
|
|
179
|
+
survey_trajectory: LineSet containing trajectory data
|
|
180
|
+
well_id_mapper: Dictionary mapping well names to IDs
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
DataFrame with attributes mapped to measured depths
|
|
184
|
+
"""
|
|
185
|
+
# Extract trajectory data
|
|
186
|
+
trajectory: xr.DataArray = survey_trajectory.data.data["vertex_attrs"]
|
|
187
|
+
trajectory_well_id: xr.DataArray = trajectory.sel({'vertex_attr': 'well_id'})
|
|
188
|
+
measured_depths: np.ndarray = trajectory.sel({'vertex_attr': 'measured_depths'}).values.astype(np.float64)
|
|
189
|
+
|
|
190
|
+
# Prepare data
|
|
191
|
+
attrs: pd.DataFrame = _prepare_categorical_data(attrs)
|
|
192
|
+
new_attrs: pd.DataFrame = _prepare_new_attributes(attrs, survey_trajectory)
|
|
193
|
+
|
|
194
|
+
# Process each well
|
|
195
|
+
for well_name in well_id_mapper:
|
|
196
|
+
# Skip wells not in the attributes DataFrame
|
|
197
|
+
if well_name not in attrs.index:
|
|
198
|
+
print(f"Well '{well_name}' does not exist in the attributes DataFrame.")
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
# Get well data
|
|
202
|
+
attrs_well = attrs.loc[[well_name]]
|
|
203
|
+
well_id = well_id_mapper.get(well_name)
|
|
204
|
+
well_mask = (trajectory_well_id == well_id).values
|
|
205
|
+
well_depths = measured_depths[well_mask]
|
|
206
|
+
|
|
207
|
+
# Get interpolation locations
|
|
208
|
+
interp_locations = _get_interpolation_locations(attrs_well, well_name)
|
|
209
|
+
|
|
210
|
+
# Interpolate each attribute
|
|
211
|
+
for col in attrs_well.columns:
|
|
212
|
+
# Skip location and ID columns
|
|
213
|
+
if col in ['top', 'base', 'well_id']:
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
attr_values = attrs_well[col]
|
|
217
|
+
is_categorical = attr_values.dtype == 'O' or isinstance(attr_values.dtype, pd.CategoricalDtype)
|
|
218
|
+
|
|
219
|
+
# Skip columns that can't be interpolated and aren't categorical
|
|
220
|
+
if is_categorical and col not in ['lith_ids', 'component lith']:
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
# Interpolate and assign values
|
|
224
|
+
interpolated_values = _interpolate_attribute(
|
|
225
|
+
attr_values,
|
|
226
|
+
interp_locations,
|
|
227
|
+
well_depths,
|
|
228
|
+
col,
|
|
229
|
+
is_categorical
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
new_attrs.loc[well_mask, col] = interpolated_values
|
|
233
|
+
|
|
234
|
+
return new_attrs
|
subsurface_terra-2025.1.0rc15/subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from typing import Hashable, Optional
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from subsurface import optional_requirements
|
|
7
|
+
from ...structs.base_structures import UnstructuredData
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def data_frame_to_unstructured_data(survey_df: 'pd.DataFrame', number_nodes: int, attr_df: Optional['pd.DataFrame'] = None,
|
|
11
|
+
duplicate_attr_depths: bool = False) -> UnstructuredData:
|
|
12
|
+
wp = optional_requirements.require_wellpathpy()
|
|
13
|
+
|
|
14
|
+
cum_vertex: np.ndarray = np.empty((0, 3), dtype=np.float32)
|
|
15
|
+
cells: np.ndarray = np.empty((0, 2), dtype=np.int_)
|
|
16
|
+
cell_attr: pd.DataFrame = pd.DataFrame(columns=['well_id'], dtype=np.float32)
|
|
17
|
+
vertex_attr: pd.DataFrame = pd.DataFrame()
|
|
18
|
+
|
|
19
|
+
for e, (borehole_id, data) in enumerate(survey_df.groupby(level=0)):
|
|
20
|
+
dev = wp.deviation(
|
|
21
|
+
md=data['md'].values,
|
|
22
|
+
inc=data['inc'].values,
|
|
23
|
+
azi=data['azi'].values
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
md_min = dev.md.min()
|
|
27
|
+
md_max = dev.md.max()
|
|
28
|
+
|
|
29
|
+
attr_depths = _grab_depths_from_attr(
|
|
30
|
+
attr_df=attr_df,
|
|
31
|
+
borehole_id=borehole_id,
|
|
32
|
+
duplicate_attr_depths=duplicate_attr_depths,
|
|
33
|
+
md_max=md_max,
|
|
34
|
+
md_min=md_min
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Now combine attr_depths with depths
|
|
38
|
+
md_min = dev.md.min()
|
|
39
|
+
md_max = dev.md.max()
|
|
40
|
+
depths = np.linspace(md_min, md_max, number_nodes)
|
|
41
|
+
depths = np.union1d(depths, attr_depths)
|
|
42
|
+
depths.sort()
|
|
43
|
+
|
|
44
|
+
# Resample positions at depths
|
|
45
|
+
pos = dev.minimum_curvature().resample(depths=depths)
|
|
46
|
+
vertex_count = cum_vertex.shape[0]
|
|
47
|
+
|
|
48
|
+
this_well_vertex = np.vstack([pos.easting, pos.northing, pos.depth]).T
|
|
49
|
+
cum_vertex = np.vstack([cum_vertex, this_well_vertex])
|
|
50
|
+
measured_depths = _calculate_distances(array_of_vertices=this_well_vertex)
|
|
51
|
+
|
|
52
|
+
n_vertex_shift_0 = np.arange(0, len(pos.depth) - 1, dtype=np.int_)
|
|
53
|
+
n_vertex_shift_1 = np.arange(1, len(pos.depth), dtype=np.int_)
|
|
54
|
+
cell_per_well = np.vstack([n_vertex_shift_0, n_vertex_shift_1]).T + vertex_count
|
|
55
|
+
cells = np.vstack([cells, cell_per_well])
|
|
56
|
+
|
|
57
|
+
attribute_values = np.isin(depths, attr_depths)
|
|
58
|
+
|
|
59
|
+
vertex_attr_per_well = pd.DataFrame({
|
|
60
|
+
'well_id' : [e] * len(pos.depth),
|
|
61
|
+
'measured_depths': measured_depths,
|
|
62
|
+
'is_attr_point' : attribute_values,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
vertex_attr = pd.concat([vertex_attr, vertex_attr_per_well], ignore_index=True)
|
|
66
|
+
|
|
67
|
+
# Add the id (e), to cell_attr
|
|
68
|
+
cell_attr = pd.concat([cell_attr, pd.DataFrame({'well_id': [e] * len(cell_per_well)})], ignore_index=True)
|
|
69
|
+
|
|
70
|
+
unstruct = UnstructuredData.from_array(
|
|
71
|
+
vertex=cum_vertex,
|
|
72
|
+
cells=cells.astype(int),
|
|
73
|
+
vertex_attr=vertex_attr.reset_index(drop=True),
|
|
74
|
+
cells_attr=cell_attr.reset_index(drop=True)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
unstruct.data.attrs["well_id_mapper"] = {well_id: e for e, well_id in enumerate(survey_df.index.unique(level=0))}
|
|
78
|
+
|
|
79
|
+
return unstruct
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _grab_depths_from_attr(
|
|
83
|
+
attr_df: pd.DataFrame,
|
|
84
|
+
borehole_id: Hashable,
|
|
85
|
+
duplicate_attr_depths: bool,
|
|
86
|
+
md_max: float,
|
|
87
|
+
md_min: float
|
|
88
|
+
) -> np.ndarray:
|
|
89
|
+
# Initialize attr_depths and attr_labels as empty arrays
|
|
90
|
+
attr_depths = np.array([], dtype=float)
|
|
91
|
+
attr_labels = np.array([], dtype='<U4') # Initialize labels for 'top' and 'base'
|
|
92
|
+
|
|
93
|
+
if attr_df is None or ("top" not in attr_df.columns and "base" not in attr_df.columns):
|
|
94
|
+
return attr_depths
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
vals = attr_df.loc[borehole_id]
|
|
98
|
+
|
|
99
|
+
tops = np.array([], dtype=float)
|
|
100
|
+
bases = np.array([], dtype=float)
|
|
101
|
+
|
|
102
|
+
if 'top' in vals:
|
|
103
|
+
if isinstance(vals, pd.DataFrame):
|
|
104
|
+
tops = vals['top'].values.flatten()
|
|
105
|
+
else:
|
|
106
|
+
tops = np.array([vals['top']])
|
|
107
|
+
# Convert to float and remove NaNs
|
|
108
|
+
tops = tops.astype(float)
|
|
109
|
+
tops = tops[~np.isnan(tops)]
|
|
110
|
+
# Clip to within md range
|
|
111
|
+
tops = tops[(tops >= md_min) & (tops <= md_max)]
|
|
112
|
+
|
|
113
|
+
if 'base' in vals:
|
|
114
|
+
if isinstance(vals, pd.DataFrame):
|
|
115
|
+
bases = vals['base'].values.flatten()
|
|
116
|
+
else:
|
|
117
|
+
bases = np.array([vals['base']])
|
|
118
|
+
# Convert to float and remove NaNs
|
|
119
|
+
bases = bases.astype(float)
|
|
120
|
+
bases = bases[~np.isnan(bases)]
|
|
121
|
+
# Clip to within md range
|
|
122
|
+
bases = bases[(bases >= md_min) & (bases <= md_max)]
|
|
123
|
+
|
|
124
|
+
# Combine tops and bases into attr_depths with labels
|
|
125
|
+
attr_depths = np.concatenate((tops, bases))
|
|
126
|
+
attr_labels = np.array(['top'] * len(tops) + ['base'] * len(bases))
|
|
127
|
+
|
|
128
|
+
# Drop duplicates while preserving order
|
|
129
|
+
_, unique_indices = np.unique(attr_depths, return_index=True)
|
|
130
|
+
attr_depths = attr_depths[unique_indices]
|
|
131
|
+
attr_labels = attr_labels[unique_indices]
|
|
132
|
+
|
|
133
|
+
except KeyError:
|
|
134
|
+
# No attributes for this borehole_id or missing columns
|
|
135
|
+
attr_depths = np.array([], dtype=float)
|
|
136
|
+
attr_labels = np.array([], dtype='<U4')
|
|
137
|
+
|
|
138
|
+
# If duplicate_attr_depths is True, duplicate attr_depths with a tiny offset
|
|
139
|
+
if duplicate_attr_depths and len(attr_depths) > 0:
|
|
140
|
+
tiny_offset = (md_max - md_min) * 1e-6 # A tiny fraction of the depth range
|
|
141
|
+
# Create offsets: +tiny_offset for 'top', -tiny_offset for 'base'
|
|
142
|
+
offsets = np.where(attr_labels == 'top', tiny_offset, -tiny_offset)
|
|
143
|
+
duplicated_attr_depths = attr_depths + offsets
|
|
144
|
+
# Ensure the duplicated depths are within the md range
|
|
145
|
+
valid_indices = (duplicated_attr_depths >= md_min) & (duplicated_attr_depths <= md_max)
|
|
146
|
+
duplicated_attr_depths = duplicated_attr_depths[valid_indices]
|
|
147
|
+
# Original attribute depths
|
|
148
|
+
original_attr_depths = attr_depths
|
|
149
|
+
# Combine originals and duplicates
|
|
150
|
+
attr_depths = np.hstack([original_attr_depths, duplicated_attr_depths])
|
|
151
|
+
|
|
152
|
+
return attr_depths
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _calculate_distances(array_of_vertices: np.ndarray) -> np.ndarray:
|
|
156
|
+
# Calculate the differences between consecutive points
|
|
157
|
+
differences = np.diff(array_of_vertices, axis=0)
|
|
158
|
+
|
|
159
|
+
# Calculate the Euclidean distance for each pair of consecutive points
|
|
160
|
+
distances = np.linalg.norm(differences, axis=1)
|
|
161
|
+
# Insert a 0 at the beginning to represent the starting point at the surface
|
|
162
|
+
measured_depths = np.insert(np.cumsum(distances), 0, 0)
|
|
163
|
+
return measured_depths
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import pandas as pd
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Hashable
|
|
4
|
+
from typing import Hashable, Literal
|
|
5
5
|
|
|
6
6
|
from ._combine_trajectories import create_combined_trajectory, MergeOptions
|
|
7
7
|
from .collars import Collars
|
|
@@ -69,10 +69,10 @@ class BoreholeSet:
|
|
|
69
69
|
# I need to implement the survey to and then name the files accordingly
|
|
70
70
|
bytearray_le_collars: bytes = self.collars.data.to_binary()
|
|
71
71
|
bytearray_le_trajectory: bytes = self.combined_trajectory.data.to_binary()
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
new_file = open(f"{path}_collars.le", "wb")
|
|
74
74
|
new_file.write(bytearray_le_collars)
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
new_file = open(f"{path}_trajectory.le", "wb")
|
|
77
77
|
new_file.write(bytearray_le_trajectory)
|
|
78
78
|
return True
|
|
@@ -88,10 +88,37 @@ class BoreholeSet:
|
|
|
88
88
|
|
|
89
89
|
return component_lith_arrays
|
|
90
90
|
|
|
91
|
-
def get_bottom_coords_for_each_lith(self) -> dict[Hashable, np.ndarray]:
|
|
91
|
+
def get_bottom_coords_for_each_lith(self, group_by: Literal['component lith', 'lith_ids'] = 'lith_ids') -> dict[Hashable, np.ndarray]:
|
|
92
|
+
"""
|
|
93
|
+
Retrieves the bottom coordinates for each lithological component or lith ID from
|
|
94
|
+
the merged vertex data arrays.
|
|
95
|
+
|
|
96
|
+
This function groups the merged data by either 'component lith' or 'lith_ids',
|
|
97
|
+
then extracts the coordinates of the bottommost vertices for each well. It
|
|
98
|
+
returns a dictionary where keys are either lithological component identifiers
|
|
99
|
+
or lith IDs, and values are arrays of 3D coordinates representing the bottom
|
|
100
|
+
vertices.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
group_by (Literal['component lith', 'lith_ids']): Specifies the grouping
|
|
104
|
+
column to use for lithological components. Acceptable values are either
|
|
105
|
+
'component lith' or 'lith_ids'. Defaults to 'lith_ids'.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
dict[Hashable, np.ndarray]: A dictionary mapping the lithological component
|
|
109
|
+
or lith ID to an array of 3D coordinates ([X, Y, Z]) corresponding to the
|
|
110
|
+
bottom vertices for each well.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: If no groups are found from the specified `group_by` column.
|
|
114
|
+
"""
|
|
92
115
|
merged_df = self._merge_vertex_data_arrays_to_dataframe()
|
|
93
116
|
component_lith_arrays = {}
|
|
94
|
-
|
|
117
|
+
group = merged_df.groupby(group_by)
|
|
118
|
+
|
|
119
|
+
if group.ngroups == 0:
|
|
120
|
+
raise ValueError("No components found")
|
|
121
|
+
for lith, group in group:
|
|
95
122
|
lith = int(lith)
|
|
96
123
|
first_vertices = group.groupby('well_id').last().reset_index()
|
|
97
124
|
array = first_vertices[['X', 'Y', 'Z']].values
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Union, Hashable, Optional
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from ._map_attrs_to_survey import combine_survey_and_attrs
|
|
7
|
+
from ._survey_to_unstruct import data_frame_to_unstructured_data
|
|
8
|
+
from ...structs.base_structures import UnstructuredData
|
|
9
|
+
from ...structs.unstructured_elements import LineSet
|
|
10
|
+
|
|
11
|
+
NUMBER_NODES = 30
|
|
12
|
+
RADIUS = 10
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Survey:
|
|
17
|
+
ids: list[str]
|
|
18
|
+
survey_trajectory: LineSet
|
|
19
|
+
well_id_mapper: dict[str, int] = None #: This is following the order of the survey csv that can be different that the collars
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def id_to_well_id(self):
|
|
23
|
+
# Reverse the well_id_mapper dictionary to map IDs to well names
|
|
24
|
+
id_to_well_name_mapper = {v: k for k, v in self.well_id_mapper.items()}
|
|
25
|
+
return id_to_well_name_mapper
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_df(cls, survey_df: 'pd.DataFrame', attr_df: Optional['pd.DataFrame'] = None, number_nodes: Optional[int] = NUMBER_NODES,
|
|
29
|
+
duplicate_attr_depths: bool = False) -> 'Survey':
|
|
30
|
+
"""
|
|
31
|
+
Create a Survey object from two DataFrames containing survey and attribute data.
|
|
32
|
+
|
|
33
|
+
:param survey_df: DataFrame containing survey data.
|
|
34
|
+
:param attr_df: DataFrame containing attribute data. This is used to make sure the raw data is perfectly aligned.
|
|
35
|
+
:param number_nodes: Optional parameter specifying the number of nodes.
|
|
36
|
+
:return: A Survey object representing the input data.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
trajectories: UnstructuredData = data_frame_to_unstructured_data(
|
|
40
|
+
survey_df=_correct_angles(survey_df),
|
|
41
|
+
attr_df=attr_df,
|
|
42
|
+
number_nodes=number_nodes,
|
|
43
|
+
duplicate_attr_depths=duplicate_attr_depths
|
|
44
|
+
)
|
|
45
|
+
# Grab the unique ids
|
|
46
|
+
unique_ids = trajectories.points_attributes["well_id"].unique()
|
|
47
|
+
|
|
48
|
+
return cls(
|
|
49
|
+
ids=unique_ids,
|
|
50
|
+
survey_trajectory=LineSet(data=trajectories, radius=RADIUS),
|
|
51
|
+
well_id_mapper=trajectories.data.attrs["well_id_mapper"]
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def get_well_string_id(self, well_id: int) -> str:
|
|
55
|
+
return self.ids[well_id]
|
|
56
|
+
|
|
57
|
+
def get_well_num_id(self, well_string_id: Union[str, Hashable]) -> int:
|
|
58
|
+
return self.well_id_mapper.get(well_string_id, None)
|
|
59
|
+
|
|
60
|
+
def update_survey_with_lith(self, lith: pd.DataFrame):
|
|
61
|
+
unstruct: UnstructuredData = combine_survey_and_attrs(lith, self.survey_trajectory, self.well_id_mapper)
|
|
62
|
+
self.survey_trajectory.data = unstruct
|
|
63
|
+
|
|
64
|
+
def update_survey_with_attr(self, attrs: pd.DataFrame):
|
|
65
|
+
self.survey_trajectory.data = combine_survey_and_attrs(attrs, self.survey_trajectory, self.well_id_mapper)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _correct_angles(df: pd.DataFrame) -> pd.DataFrame:
|
|
69
|
+
def correct_inclination(inc: float) -> float:
|
|
70
|
+
if inc < 0:
|
|
71
|
+
inc = inc % 360 # Normalize to 0-360 range first if negative
|
|
72
|
+
if 0 <= inc <= 180:
|
|
73
|
+
# add or subtract a very small number to make sure that 0 or 180 are never possible
|
|
74
|
+
return inc + 1e-10 if inc == 0 else inc - 1e-10
|
|
75
|
+
elif 180 < inc < 360:
|
|
76
|
+
return 360 - inc # Reflect angles greater than 180 back into the 0-180 range
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError(f'Inclination value {inc} is out of the expected range of 0 to 360 degrees')
|
|
79
|
+
|
|
80
|
+
def correct_azimuth(azi: float) -> float:
|
|
81
|
+
return azi % 360 # Normalize azimuth to 0-360 range
|
|
82
|
+
|
|
83
|
+
df['inc'] = df['inc'].apply(correct_inclination)
|
|
84
|
+
df['azi'] = df['azi'].apply(correct_azimuth)
|
|
85
|
+
|
|
86
|
+
return df
|
{subsurface_terra-2025.1.0rc13 → subsurface_terra-2025.1.0rc15/subsurface_terra.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: subsurface_terra
|
|
3
|
-
Version: 2025.1.
|
|
3
|
+
Version: 2025.1.0rc15
|
|
4
4
|
Summary: Subsurface data types and utilities. This version is the one used by Terranigma Solutions. Please feel free to take anything in this repository for the original one.
|
|
5
5
|
Home-page: https://softwareunderground.github.io/subsurface
|
|
6
6
|
Author: Software Underground
|
|
@@ -33,6 +33,8 @@ subsurface/core/geological_formats/__init__.py
|
|
|
33
33
|
subsurface/core/geological_formats/fault.py
|
|
34
34
|
subsurface/core/geological_formats/boreholes/__init__.py
|
|
35
35
|
subsurface/core/geological_formats/boreholes/_combine_trajectories.py
|
|
36
|
+
subsurface/core/geological_formats/boreholes/_map_attrs_to_survey.py
|
|
37
|
+
subsurface/core/geological_formats/boreholes/_survey_to_unstruct.py
|
|
36
38
|
subsurface/core/geological_formats/boreholes/boreholes.py
|
|
37
39
|
subsurface/core/geological_formats/boreholes/collars.py
|
|
38
40
|
subsurface/core/geological_formats/boreholes/survey.py
|