Skip to content
Merged

3.3.0 #223

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ff57f41
Refactor Map2ModelWarraper into Topology class (removed dependency to…
rabii-chaarani Jul 9, 2025
dfc36c2
fix from codex/refactor-extract-methods-into-contactextractor-class
noellehmcheng Jul 9, 2025
8766569
fix test_contact_extractor.py
noellehmcheng Jul 9, 2025
6982a45
style: style fixes by ruff and autoformatting by black
noellehmcheng Jul 9, 2025
c927e85
return basal contact in extract_geology_contacts functions
noellehmcheng Jul 9, 2025
b44b05e
Merge remote-tracking branch 'origin/v3.3.0' into v3.3.0
noellehmcheng Jul 9, 2025
a5d4789
fix typing in ContactExtractor parameter for python 3.9 compatibility
noellehmcheng Jul 9, 2025
4acab31
fix conda package file extension
noellehmcheng Jul 14, 2025
8e5c580
refactor: update basal contacts extraction
rabii-chaarani Jul 17, 2025
3790784
working linting_and_testing.yml from testing_v3.3.0
noellehmcheng Jul 18, 2025
9d15044
Merge branch 'v3.3.0' of https://github.com/Loop3D/map2loop into v3.3.0
noellehmcheng Jul 18, 2025
c4d7cd5
Thickness Calculators - Removed Mapdata Dependency (#224)
rabii-chaarani Aug 6, 2025
0fe029e
style: style fixes by ruff and autoformatting by black
rabii-chaarani Aug 6, 2025
99181da
fix: use correct type hint
rabii-chaarani Aug 6, 2025
65b3c0c
fix sampled_contacts in thickness tests
noellehmcheng Aug 8, 2025
a3936b5
style: style fixes by ruff and autoformatting by black
noellehmcheng Aug 8, 2025
adc99ca
fix test_calculate_unit_thicknesses
noellehmcheng Aug 8, 2025
dad8da7
Merge branch 'v3.3.0' of https://github.com/Loop3D/map2loop into v3.3.0
noellehmcheng Aug 8, 2025
fd0d554
fix: update fault and fold allowed keys to include additional columns
rabii-chaarani Aug 11, 2025
8fc0779
fix: add regex handling
rabii-chaarani Aug 13, 2025
7f9dffd
fix: thickness calculator to use DTM data and bounding box
rabii-chaarani Aug 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
conda install -c conda-forge conda-build scikit-build-core numpy anaconda-client conda-libmamba-solver -y
conda config --set solver libmamba
conda build --output-folder conda conda --python ${{matrix.python-version}}
anaconda upload --label main conda/*/*.tar.bz2
anaconda upload --label main conda/*/*.conda

- name: upload artifacts
uses: actions/upload-artifact@v4
Expand Down
21 changes: 13 additions & 8 deletions .github/workflows/linting_and_testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,33 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
conda-remove-defaults: "true"


- name: Install dependencies for windows python 3.10
if: ${{ matrix.os == 'windows-latest' && matrix.python-version == '3.10' }}

- name: Install dependencies for windows python 3.9 and 3.10
if: ${{ matrix.os == 'windows-latest' && (matrix.python-version == '3.9' || matrix.python-version == '3.10') }}
run: |
conda run -n test conda info
conda run -n test conda install -c loop3d -c conda-forge "gdal=3.4.3" python=${{ matrix.python-version }} -y
conda run -n test conda install -c loop3d -c conda-forge --file dependencies.txt python=${{ matrix.python-version }} -y
conda run -n test conda install pytest python=${{ matrix.python-version }} -y

- name: Install dependencies for other environments
if: ${{ matrix.os != 'windows-latest' || matrix.python-version != '3.10' }}
- name: Install dependencies for windows python 3.11
if: ${{ matrix.os == 'windows-latest' && matrix.python-version == '3.11' }}
run: |
conda run -n test conda info
conda run -n test conda install -c loop3d -c conda-forge gdal python=${{ matrix.python-version }} -y
conda run -n test conda install -c loop3d -c conda-forge gdal "netcdf4=*=nompi_py311*" python=${{ matrix.python-version }} -y
conda run -n test conda install -c loop3d -c conda-forge --file dependencies.txt python=${{ matrix.python-version }} -y
conda run -n test conda install pytest python=${{ matrix.python-version }} -y

- name: Install dependencies for other environments
if: ${{ !(matrix.os == 'windows-latest' && (matrix.python-version == '3.9' || matrix.python-version == '3.10' || matrix.python-version == '3.11')) }}
run: |
conda run -n test conda info
conda run -n test conda install -c loop3d -c conda-forge python=${{ matrix.python-version }} gdal pytest --file dependencies.txt -y

- name: Install map2loop
run: |
conda run -n test python -m pip install .

- name: Run tests
run: |
conda run -n test pytest
conda run -n test pytest -v
96 changes: 96 additions & 0 deletions map2loop/contact_extractor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import geopandas
import pandas
import shapely
from .logging import getLogger
from typing import Optional

logger = getLogger(__name__)

class ContactExtractor:
def __init__(self, geology: geopandas.GeoDataFrame, faults: Optional[geopandas.GeoDataFrame] = None):
self.geology = geology
self.faults = faults
self.contacts = None
self.basal_contacts = None
self.all_basal_contacts = None

def extract_all_contacts(self, save_contacts: bool = True) -> geopandas.GeoDataFrame:
logger.info("Extracting contacts")
geology = self.geology.copy()
geology = geology.dissolve(by="UNITNAME", as_index=False)
geology = geology[~geology["INTRUSIVE"]]
geology = geology[~geology["SILL"]]
if self.faults is not None:
faults = self.faults.copy()
faults["geometry"] = faults.buffer(50)
geology = geopandas.overlay(geology, faults, how="difference", keep_geom_type=False)
units = geology["UNITNAME"].unique().tolist()
column_names = ["UNITNAME_1", "UNITNAME_2", "geometry"]
contacts = geopandas.GeoDataFrame(crs=geology.crs, columns=column_names, data=None)
while len(units) > 1:
unit1 = units[0]
units = units[1:]
for unit2 in units:
if unit1 != unit2:
join = geopandas.overlay(
geology[geology["UNITNAME"] == unit1],
geology[geology["UNITNAME"] == unit2],
keep_geom_type=False,
)[column_names]
join["geometry"] = join.buffer(1)
buffered = geology[geology["UNITNAME"] == unit2][["geometry"]].copy()
buffered["geometry"] = buffered.boundary
end = geopandas.overlay(buffered, join, keep_geom_type=False)
if len(end):
contacts = pandas.concat([contacts, end], ignore_index=True)
contacts["length"] = [row.length for row in contacts["geometry"]]
if save_contacts:
self.contacts = contacts
return contacts

def extract_basal_contacts(self,
stratigraphic_column: list,
save_contacts: bool = True) -> geopandas.GeoDataFrame:

logger.info("Extracting basal contacts")
units = stratigraphic_column

if self.contacts is None:
self.extract_all_contacts(save_contacts=True)
basal_contacts = self.contacts.copy()
else:
basal_contacts = self.contacts.copy()
if any(unit not in units for unit in basal_contacts["UNITNAME_1"].unique()):
missing_units = (
basal_contacts[~basal_contacts["UNITNAME_1"].isin(units)]["UNITNAME_1"]
.unique()
.tolist()
)
logger.error(
"There are units in the Geology dataset, but not in the stratigraphic column: "
+ ", ".join(missing_units)
+ ". Please readjust the stratigraphic column if this is a user defined column."
)
raise ValueError(
"There are units in stratigraphic column, but not in the Geology dataset: "
+ ", ".join(missing_units)
+ ". Please readjust the stratigraphic column if this is a user defined column."
)
basal_contacts["ID"] = basal_contacts.apply(
lambda row: min(units.index(row["UNITNAME_1"]), units.index(row["UNITNAME_2"])), axis=1
)
basal_contacts["basal_unit"] = basal_contacts.apply(lambda row: units[row["ID"]], axis=1)
basal_contacts["stratigraphic_distance"] = basal_contacts.apply(
lambda row: abs(units.index(row["UNITNAME_1"]) - units.index(row["UNITNAME_2"])), axis=1
)
basal_contacts["type"] = basal_contacts.apply(
lambda row: "ABNORMAL" if abs(row["stratigraphic_distance"]) > 1 else "BASAL", axis=1
)
basal_contacts = basal_contacts[["ID", "basal_unit", "type", "geometry"]]
basal_contacts["geometry"] = [
shapely.line_merge(shapely.snap(geo, geo, 1)) for geo in basal_contacts["geometry"]
]
if save_contacts:
self.all_basal_contacts = basal_contacts
self.basal_contacts = basal_contacts[basal_contacts["type"] == "BASAL"]
return basal_contacts
11 changes: 7 additions & 4 deletions map2loop/data_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,11 +471,13 @@ def check_keys(d: dict, parent_key=""):
"fault": {
"structtype_column", "fault_text", "dip_null_value",
"dipdir_flag", "dipdir_column", "dip_column", "orientation_type",
"dipestimate_column", "dipestimate_text", "name_column",
"objectid_column", "minimum_fault_length", "ignore_fault_codes",
"dipestimate_column", "dipestimate_text","displacement_column",
"displacement_text", "name_column","objectid_column", "featureid_column", "minimum_fault_length",
"fault_length_column", "fault_length_text", "ignore_fault_codes",
},
"fold": {
"structtype_column", "fold_text", "description_column",
"structtype_column", "fold_text", "axial_plane_dipdir_column",
"axial_plane_dip_column","tightness_column", "description_column",
"synform_text", "foldname_column","objectid_column",
},
}
Expand Down Expand Up @@ -853,6 +855,7 @@ def validate_structtype_column(
if text_keys:
for text_key, config_key in text_keys.items():
text_value = config.get(config_key, None)
text_pattern = '|'.join(text_value) if text_value else None
if text_value:
if not isinstance(text_value, str):
error_msg = (
Expand All @@ -862,7 +865,7 @@ def validate_structtype_column(
logger.error(error_msg)
return (True, error_msg)

if not geodata[structtype_column].str.contains(text_value, na=False).any():
if not geodata[structtype_column].str.contains(text_pattern, case=False, regex=True, na=False).any():
if text_key == "synform_text":
warning_msg = (
f"Datatype {datatype_name.upper()}: The '{text_key}' value '{text_value}' is not found in column '{structtype_column}'. "
Expand Down
Loading
Loading