@@ -17,6 +17,8 @@ def mfgrd_test_path(example_data_path):
1717
1818def test_mfgrddis_MfGrdFile (mfgrd_test_path ):
1919 grb = MfGrdFile (mfgrd_test_path / "nwtp3.dis.grb" , verbose = True )
20+ assert grb .version == 1
21+
2022 nodes = grb .nodes
2123 ia = grb .ia
2224 shape = ia .shape [0 ]
@@ -30,6 +32,28 @@ def test_mfgrddis_MfGrdFile(mfgrd_test_path):
3032 modelgrid = grb .modelgrid
3133 assert isinstance (modelgrid , StructuredGrid )
3234
35+ assert grb .crs is None
36+
37+
38+ def test_mfgrddis_MfGrdFile_v2 (mfgrd_test_path ):
39+ grb = MfGrdFile (mfgrd_test_path / "flow_v2.dis.grb" , verbose = True )
40+ assert grb .version == 2
41+
42+ nodes = grb .nodes
43+ ia = grb .ia
44+ shape = ia .shape [0 ]
45+ assert shape == nodes + 1 , f"ia size ({ shape } ) not equal to { nodes + 1 } "
46+
47+ nnz = ia [- 1 ]
48+ ja = grb .ja
49+ shape = ja .shape [0 ]
50+ assert shape == nnz , f"ja size ({ shape } ) not equal to { nnz } "
51+
52+ modelgrid = grb .modelgrid
53+ assert isinstance (modelgrid , StructuredGrid )
54+
55+ assert grb .crs == "EPSG:26916"
56+
3357
3458def test_mfgrddis_modelgrid (mfgrd_test_path ):
3559 fn = mfgrd_test_path / "nwtp3.dis.grb"
@@ -62,6 +86,7 @@ def test_mfgrddis_modelgrid(mfgrd_test_path):
6286def test_mfgrddisv_MfGrdFile (mfgrd_test_path ):
6387 fn = mfgrd_test_path / "flow.disv.grb"
6488 grb = MfGrdFile (fn , verbose = True )
89+ assert grb .version == 1
6590
6691 nodes = grb .nodes
6792 ia = grb .ia
@@ -114,6 +139,7 @@ def test_mfgrddisv_modelgrid(mfgrd_test_path):
114139def test_mfgrddisu_MfGrdFile (mfgrd_test_path ):
115140 fn = mfgrd_test_path / "keating.disu.grb"
116141 grb = MfGrdFile (fn , verbose = True )
142+ assert grb .version == 1
117143
118144 nodes = grb .nodes
119145 ia = grb .ia
@@ -164,11 +190,13 @@ def test_mfgrddisu_modelgrid(mfgrd_test_path):
164190def test_write_grb_instance_method (tmp_path , mfgrd_test_path ):
165191 original_file = mfgrd_test_path / "nwtp3.dis.grb"
166192 grb_orig = MfGrdFile (original_file , verbose = False )
193+ _ = grb_orig .modelgrid # trigger _set_modelgrid so BOTM is reshaped before export
167194
168195 output_file = tmp_path / "test_instance.dis.grb"
169196 grb_orig .export (output_file , verbose = False )
170197
171198 grb_new = MfGrdFile (output_file , verbose = False )
199+ _ = grb_new .modelgrid
172200
173201 assert grb_new .grid_type == grb_orig .grid_type
174202 assert grb_new .nodes == grb_orig .nodes
@@ -191,6 +219,37 @@ def test_write_grb_instance_method(tmp_path, mfgrd_test_path):
191219 np .testing .assert_array_equal (grb_new .idomain , grb_orig .idomain )
192220
193221
222+ def test_write_grb_instance_method_disv (tmp_path , mfgrd_test_path ):
223+ # disv_layered.disv.grb has distinct BOTM values per layer (-10/-20/-30),
224+ # which distinguishes C from F flatten order and guards against order regression.
225+ original_file = mfgrd_test_path / "disv_layered.disv.grb"
226+ grb_orig = MfGrdFile (original_file , verbose = False )
227+ _ = grb_orig .modelgrid # trigger _set_modelgrid so BOTM is reshaped before export
228+
229+ output_file = tmp_path / "test_instance.disv.grb"
230+ grb_orig .export (output_file , verbose = False )
231+
232+ grb_new = MfGrdFile (output_file , verbose = False )
233+ _ = grb_new .modelgrid
234+
235+ assert grb_new .grid_type == grb_orig .grid_type
236+ assert grb_new .nodes == grb_orig .nodes
237+ assert grb_new .nlay == grb_orig .nlay
238+ assert grb_new .ncpl == grb_orig .ncpl
239+ assert grb_new .nja == grb_orig .nja
240+
241+ np .testing .assert_allclose (grb_new .xorigin , grb_orig .xorigin )
242+ np .testing .assert_allclose (grb_new .yorigin , grb_orig .yorigin )
243+ np .testing .assert_allclose (grb_new .angrot , grb_orig .angrot )
244+
245+ np .testing .assert_allclose (grb_new .top , grb_orig .top )
246+ np .testing .assert_allclose (grb_new .bot , grb_orig .bot )
247+
248+ np .testing .assert_array_equal (grb_new .ia , grb_orig .ia )
249+ np .testing .assert_array_equal (grb_new .ja , grb_orig .ja )
250+ np .testing .assert_array_equal (grb_new .idomain , grb_orig .idomain )
251+
252+
194253def test_write_grb_instance_method_precision_conversion (tmp_path , mfgrd_test_path ):
195254 original_file = mfgrd_test_path / "nwtp3.dis.grb"
196255 grb = MfGrdFile (original_file , verbose = False )
@@ -396,3 +455,177 @@ def test_write_grb_disu_precision_conversion(tmp_path, mfgrd_test_path):
396455 # Double precision should match exactly (same precision as original)
397456 np .testing .assert_allclose (grb_double .top , grb .top , rtol = 1e-12 )
398457 np .testing .assert_allclose (grb_double .bot , grb .bot , rtol = 1e-12 )
458+
459+
460+ def test_write_grb_v2_roundtrip (tmp_path , mfgrd_test_path ):
461+ """Round-trip a version 2 GRB: version and CRS are preserved."""
462+ grb_orig = MfGrdFile (mfgrd_test_path / "flow_v2.dis.grb" , verbose = False )
463+ assert grb_orig .version == 2
464+ assert grb_orig .crs == "EPSG:26916"
465+
466+ output_file = tmp_path / "flow_v2_copy.dis.grb"
467+ grb_orig .export (output_file , verbose = False )
468+
469+ grb_new = MfGrdFile (output_file , verbose = False )
470+ assert grb_new .version == 2
471+ assert grb_new .crs == "EPSG:26916"
472+ assert grb_new .nodes == grb_orig .nodes
473+ np .testing .assert_allclose (grb_new .top , grb_orig .top )
474+ np .testing .assert_allclose (grb_new .bot , grb_orig .bot )
475+ np .testing .assert_array_equal (grb_new .ia , grb_orig .ia )
476+ np .testing .assert_array_equal (grb_new .ja , grb_orig .ja )
477+
478+
479+ def test_write_grb_v1_upgrade_to_v2 (tmp_path , mfgrd_test_path ):
480+ """Exporting a v1 GRB with crs= automatically produces a v2 file."""
481+ grb_orig = MfGrdFile (mfgrd_test_path / "nwtp3.dis.grb" , verbose = False )
482+ assert grb_orig .version == 1
483+ assert grb_orig .crs is None
484+
485+ output_file = tmp_path / "nwtp3_v2.dis.grb"
486+ grb_orig .export (output_file , crs = "EPSG:26916" , verbose = False )
487+
488+ grb_new = MfGrdFile (output_file , verbose = False )
489+ assert grb_new .version == 2
490+ assert grb_new .crs == "EPSG:26916"
491+ assert grb_new .nodes == grb_orig .nodes
492+ np .testing .assert_array_equal (grb_new .ia , grb_orig .ia )
493+
494+
495+ def test_write_grb_v1_force_override (tmp_path , mfgrd_test_path ):
496+ """Explicit version=1 suppresses CRS even when crs= is provided."""
497+ grb = MfGrdFile (mfgrd_test_path / "nwtp3.dis.grb" , verbose = False )
498+ output_file = tmp_path / "forced_v1.dis.grb"
499+ grb .export (output_file , version = 1 , crs = "EPSG:26916" )
500+
501+ grb_new = MfGrdFile (output_file , verbose = False )
502+ assert grb_new .version == 1
503+ assert grb_new .crs is None
504+ assert grb_new .nodes == grb .nodes
505+
506+
507+ def test_write_grb_v2_requires_crs (tmp_path , mfgrd_test_path ):
508+ """Requesting version=2 without a CRS raises ValueError."""
509+ grb = MfGrdFile (mfgrd_test_path / "nwtp3.dis.grb" , verbose = False )
510+ with pytest .raises (ValueError , match = "version=2 requires a CRS" ):
511+ grb .export (tmp_path / "out.grb" , version = 2 )
512+
513+
514+ def test_write_grb_v2_crs_too_long (tmp_path , mfgrd_test_path ):
515+ """A CRS string exceeding 5000 characters raises ValueError."""
516+ grb = MfGrdFile (mfgrd_test_path / "nwtp3.dis.grb" , verbose = False )
517+ with pytest .raises (ValueError , match = "exceeds the MODFLOW 6 maximum" ):
518+ grb .export (tmp_path / "out.grb" , crs = "X" * 5001 )
519+
520+
521+ def test_crs_to_string ():
522+ """_crs_to_string normalizes CRS inputs to strings."""
523+ from flopy .mf6 .utils .binarygrid_util import _crs_to_string
524+
525+ # str pass-through
526+ assert _crs_to_string ("EPSG:26916" ) == "EPSG:26916"
527+ assert _crs_to_string ("some WKT..." ) == "some WKT..."
528+
529+ # int → EPSG authority string
530+ assert _crs_to_string (26916 ) == "EPSG:26916"
531+
532+ # pyproj.CRS with a known EPSG code → prefers short EPSG form
533+ pyproj = pytest .importorskip ("pyproj" )
534+ crs_obj = pyproj .CRS .from_epsg (26916 )
535+ assert _crs_to_string (crs_obj ) == "EPSG:26916"
536+
537+ # pyproj.CRS built from WKT that has an EPSG code still normalizes
538+ crs_from_wkt = pyproj .CRS .from_wkt (crs_obj .to_wkt ())
539+ assert _crs_to_string (crs_from_wkt ) == "EPSG:26916"
540+
541+
542+ def test_write_grb_v2_crs_int (tmp_path , mfgrd_test_path ):
543+ """Passing an integer EPSG code writes 'EPSG:N' to the GRB."""
544+ grb_orig = MfGrdFile (mfgrd_test_path / "nwtp3.dis.grb" , verbose = False )
545+ output_file = tmp_path / "nwtp3_v2_int.dis.grb"
546+ grb_orig .export (output_file , crs = 26916 )
547+
548+ grb_new = MfGrdFile (output_file , verbose = False )
549+ assert grb_new .version == 2
550+ assert grb_new .crs == "EPSG:26916"
551+
552+
553+ def test_write_grb_v2_crs_pyproj (tmp_path , mfgrd_test_path ):
554+ """Passing a pyproj.CRS object normalizes to EPSG string in the GRB."""
555+ pyproj = pytest .importorskip ("pyproj" )
556+ grb_orig = MfGrdFile (mfgrd_test_path / "nwtp3.dis.grb" , verbose = False )
557+ output_file = tmp_path / "nwtp3_v2_pyproj.dis.grb"
558+ grb_orig .export (output_file , crs = pyproj .CRS .from_epsg (26916 ))
559+
560+ grb_new = MfGrdFile (output_file , verbose = False )
561+ assert grb_new .version == 2
562+ assert grb_new .crs == "EPSG:26916"
563+
564+
565+ def test_write_grb_disv_v1_upgrade_to_v2 (tmp_path , mfgrd_test_path ):
566+ """Upgrading a v1 DISV GRB to v2 preserves geometry and writes CRS."""
567+ grb_orig = MfGrdFile (mfgrd_test_path / "flow.disv.grb" , verbose = False )
568+ assert grb_orig .version == 1
569+ assert grb_orig .crs is None
570+
571+ output_file = tmp_path / "flow_v2.disv.grb"
572+ grb_orig .export (output_file , crs = "EPSG:26916" )
573+
574+ grb_new = MfGrdFile (output_file , verbose = False )
575+ assert grb_new .version == 2
576+ assert grb_new .crs == "EPSG:26916"
577+ assert grb_new .grid_type == "DISV"
578+ assert grb_new .nodes == grb_orig .nodes
579+ assert grb_new .nja == grb_orig .nja
580+ np .testing .assert_array_equal (grb_new .ia , grb_orig .ia )
581+ np .testing .assert_array_equal (grb_new .ja , grb_orig .ja )
582+ np .testing .assert_allclose (grb_new .top , grb_orig .top )
583+ np .testing .assert_allclose (grb_new .bot , grb_orig .bot )
584+ np .testing .assert_allclose (
585+ grb_new ._datadict ["VERTICES" ], grb_orig ._datadict ["VERTICES" ]
586+ )
587+ np .testing .assert_allclose (grb_new ._datadict ["CELLX" ], grb_orig ._datadict ["CELLX" ])
588+ np .testing .assert_allclose (grb_new ._datadict ["CELLY" ], grb_orig ._datadict ["CELLY" ])
589+
590+
591+ def test_write_grb_disv_v2_roundtrip (tmp_path , mfgrd_test_path ):
592+ """Round-trip a v2 DISV GRB: version and CRS are preserved."""
593+ # First create a v2 DISV file by upgrading the v1 source
594+ v2_file = tmp_path / "flow_v2.disv.grb"
595+ MfGrdFile (mfgrd_test_path / "flow.disv.grb" ).export (v2_file , crs = "EPSG:26916" )
596+
597+ grb_orig = MfGrdFile (v2_file , verbose = False )
598+ assert grb_orig .version == 2
599+
600+ output_file = tmp_path / "flow_v2_copy.disv.grb"
601+ grb_orig .export (output_file , verbose = False )
602+
603+ grb_new = MfGrdFile (output_file , verbose = False )
604+ assert grb_new .version == 2
605+ assert grb_new .crs == "EPSG:26916"
606+ assert grb_new .nodes == grb_orig .nodes
607+ np .testing .assert_array_equal (grb_new .ia , grb_orig .ia )
608+ np .testing .assert_allclose (
609+ grb_new ._datadict ["VERTICES" ], grb_orig ._datadict ["VERTICES" ]
610+ )
611+
612+
613+ def test_write_grb_disu_v1_upgrade_to_v2 (tmp_path , mfgrd_test_path ):
614+ """Upgrading a v1 DISU GRB to v2 preserves connectivity and writes CRS."""
615+ grb_orig = MfGrdFile (mfgrd_test_path / "flow.disu.grb" , verbose = False )
616+ assert grb_orig .version == 1
617+ assert grb_orig .crs is None
618+
619+ output_file = tmp_path / "flow_v2.disu.grb"
620+ grb_orig .export (output_file , crs = "EPSG:26916" )
621+
622+ grb_new = MfGrdFile (output_file , verbose = False )
623+ assert grb_new .version == 2
624+ assert grb_new .crs == "EPSG:26916"
625+ assert grb_new .grid_type == "DISU"
626+ assert grb_new .nodes == grb_orig .nodes
627+ assert grb_new .nja == grb_orig .nja
628+ np .testing .assert_array_equal (grb_new .ia , grb_orig .ia )
629+ np .testing .assert_array_equal (grb_new .ja , grb_orig .ja )
630+ np .testing .assert_allclose (grb_new .top , grb_orig .top )
631+ np .testing .assert_allclose (grb_new .bot , grb_orig .bot )
0 commit comments