Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 52 additions & 0 deletions autotest/test_binarygrid_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,3 +629,55 @@ def test_write_grb_disu_v1_upgrade_to_v2(tmp_path, mfgrd_test_path):
np.testing.assert_array_equal(grb_new.ja, grb_orig.ja)
np.testing.assert_allclose(grb_new.top, grb_orig.top)
np.testing.assert_allclose(grb_new.bot, grb_orig.bot)


def test_write_grb_export_is_keyword_only(tmp_path, mfgrd_test_path):
"""A positional call beyond filename raises."""
grb = MfGrdFile(mfgrd_test_path / "nwtp3.dis.grb", verbose=False)
with pytest.raises(TypeError):
grb.export(tmp_path / "out.grb", "double", 1, "EPSG:26916", True)


def test_write_grb_empty_crs_argument_falls_back(tmp_path, mfgrd_test_path):
"""An explicit empty/blank crs argument is treated like crs=None: it
falls back to the source file's CRS instead of overriding it."""
grb = MfGrdFile(mfgrd_test_path / "flow_v2.dis.grb", verbose=False)
assert grb.crs is not None

output_file = tmp_path / "out.grb"
grb.export(output_file, crs="")
grb_new = MfGrdFile(output_file, verbose=False)
assert grb_new.crs == grb.crs

output_file2 = tmp_path / "out2.grb"
grb.export(output_file2, crs=" ")
grb_new2 = MfGrdFile(output_file2, verbose=False)
assert grb_new2.crs == grb.crs


def test_write_grb_empty_crs_argument_no_source_crs(tmp_path, mfgrd_test_path):
"""An explicit empty/blank crs argument with no source CRS to fall
back on results in a version 1 file with no CRS, same as crs=None."""
grb = MfGrdFile(mfgrd_test_path / "nwtp3.dis.grb", verbose=False)
assert grb.crs is None

output_file = tmp_path / "out.grb"
grb.export(output_file, crs="")
grb_new = MfGrdFile(output_file, verbose=False)
assert grb_new.version == 1
assert grb_new.crs is None


def test_write_grb_blank_source_crs_treated_as_absent(tmp_path, mfgrd_test_path):
"""A source file's blank CRS is treated as absent on re-export:
version drops to 1 and no CRS field is written."""
grb = MfGrdFile(mfgrd_test_path / "flow_v2.dis.grb", verbose=False)
grb._datadict["CRS"] = ""

output_file = tmp_path / "blank_crs.grb"
grb.export(output_file)

grb_new = MfGrdFile(output_file, verbose=False)
assert grb_new.version == 1
assert grb_new.crs is None
assert "CRS" not in grb_new._datadict
26 changes: 23 additions & 3 deletions flopy/mf6/utils/binarygrid_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,15 @@ def cell2d(self):
vertices, cell2d = None, None
return vertices, cell2d

def export(self, filename, precision=None, version=None, crs=None, verbose=False):
def export(
self,
filename,
*,
precision=None,
version=None,
crs=None,
verbose=False,
):
"""
Export the binary grid file to a new file.

Expand All @@ -818,6 +826,11 @@ def export(self, filename, precision=None, version=None, crs=None, verbose=False
verbose : bool, optional
Print progress messages (default False)

All parameters except ``filename`` are keyword-only, so adding a
new parameter here in the future (e.g. a dedicated parameter and
property for a new GRB field, mirroring ``crs``/``.crs``) can
never silently change the meaning of an existing positional call.

Examples
--------
>>> from flopy.mf6.utils import MfGrdFile
Expand All @@ -832,10 +845,17 @@ def export(self, filename, precision=None, version=None, crs=None, verbose=False
if precision is None:
precision = self.precision

if isinstance(crs, str) and crs.strip() == "":
crs = None

raw_crs = crs if crs is not None else self.crs
effective_crs = _crs_to_string(raw_crs) if raw_crs is not None else None
if effective_crs is not None and effective_crs.strip() == "":
# A blank CRS carries no usable information. Treat it the
# same as no CRS rather than writing an empty crs string.
effective_crs = None
if version is None:
version = 2 if effective_crs else 1
version = 2 if effective_crs is not None else 1

float_type = "SINGLE" if precision.lower() == "single" else "DOUBLE"

Expand Down Expand Up @@ -864,7 +884,7 @@ def export(self, filename, precision=None, version=None, crs=None, verbose=False
data_dict[key] = self._datadict[key]

if version >= 2:
if not effective_crs:
if effective_crs is None:
raise ValueError(
"version=2 requires a CRS string. Provide crs= or use a "
"version 2 source file."
Expand Down
Loading