From 84dd5dee31511fb26a30417fed2183b2cd435b5f Mon Sep 17 00:00:00 2001 From: Mike Taves Date: Thu, 30 May 2024 10:48:54 +1200 Subject: [PATCH] Change Grid(projection) to be None (#34) --- .github/workflows/tests.yml | 2 ++ gridit/array_from.py | 9 +++++++-- gridit/classmethods.py | 9 ++++++--- gridit/cli.py | 5 +---- gridit/display.py | 5 ++++- gridit/grid.py | 8 +++++--- gridit/spatial.py | 2 +- tests/data/nocrs.tif | Bin 0 -> 266 bytes tests/data/points.csv | 4 ++++ tests/test_array_from.py | 15 +++++++++++++-- tests/test_classmethods.py | 19 +++++++++++++++++++ tests/test_grid.py | 4 ++-- tests/test_modflow.py | 6 +++--- 13 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 tests/data/nocrs.tif create mode 100644 tests/data/points.csv diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d6551c3..6cc4895 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,6 +48,8 @@ jobs: - name: Run tests with optional packages run: | pip install -e .[optional] + # Avoid: Matplotlib is building the font cache; this may take a moment + python -c "import matplotlib; matplotlib.get_cachedir()" pytest -n2 -v --cov - name: Run doctest diff --git a/gridit/array_from.py b/gridit/array_from.py index 59f1884..d1abb7d 100644 --- a/gridit/array_from.py +++ b/gridit/array_from.py @@ -341,9 +341,14 @@ def __init__(self, grid, fname, layer, attribute=None): grid_crs = grid.projection do_transform = False if not grid_crs: - self.logger.info("assuming same projection: %s", shorten(grid_crs, 60)) + self.logger.info("geometries not transformed: grid has no projection") + elif not ds_crs: + self.logger.info("geometries not transformed: vector has no projection") elif is_same_crs(grid_crs, ds_crs): - self.logger.info("same projection: %s", shorten(grid_crs, 60)) + self.logger.info( + "geometries not transformed: same projection: %s", + shorten(grid_crs, 60), + ) else: do_transform = True self.logger.info( diff --git a/gridit/classmethods.py b/gridit/classmethods.py index f5fcac5..82b9631 100644 --- a/gridit/classmethods.py +++ b/gridit/classmethods.py @@ -1,6 +1,7 @@ """Grid from_* classmethods.""" from math import ceil, floor +from typing import Optional from gridit.logger import get_logger @@ -51,7 +52,7 @@ def from_bbox( maxy: float, resolution: float, buffer: float = 0.0, - projection: str = "", + projection: Optional[str] = None, logger=None, ): """Create grid information from a bounding box and resolution. @@ -68,7 +69,7 @@ def from_bbox( A grid resolution, e.g. 250.0 for 250m x 250m buffer : float, default 0.0 Add buffer to extents of bounding box. - projection : str, default "" + projection : optional str, default None Coordinate reference system described as a string either as (e.g.) EPSG:2193 or a WKT string. logger : logging.Logger, optional @@ -138,10 +139,12 @@ def from_raster( if logger is None: logger = get_logger(cls.__name__) logger.info("creating from raster: %s", fname) + projection = None with rasterio.open(fname, "r") as ds: t = ds.transform shape = ds.shape - projection = ds.crs.to_wkt() + if ds.crs: + projection = ds.crs.to_wkt() if t.e != -t.a: logger.error("expected e == -a, but %r != %r", t.e, t.a) if t.b != 0 or t.d != 0: diff --git a/gridit/cli.py b/gridit/cli.py index da296b0..5823156 100644 --- a/gridit/cli.py +++ b/gridit/cli.py @@ -161,10 +161,7 @@ def error_msg(msg: str, name: str = ""): if ":" in model and (split := model.rindex(":")) > 1: model = args.grid_from_modflow[:split] model_name = args.grid_from_modflow[(1 + split) :] - if args.projection == "": - projection = None - else: - projection = args.projection + projection = args.projection if args.projection else None grid = Grid.from_modflow(model, model_name, projection=projection) mask = grid.mask_from_modflow(model, model_name=model_name) else: diff --git a/gridit/display.py b/gridit/display.py index e8efa5b..758991e 100644 --- a/gridit/display.py +++ b/gridit/display.py @@ -49,7 +49,10 @@ def print_array(ar, logger=None): else: im -= im.min() im /= im.max() - msk = ndimage.zoom(ar.mask, zf, order=0, cval=True) + if ar.mask.shape: + msk = ndimage.zoom(ar.mask, zf, order=0, cval=True) + else: + msk = np.full(im.shape, ar.mask) col = ".;-:!>7?8CO$QHNM" string = "" height, width = im.shape diff --git a/gridit/grid.py b/gridit/grid.py index 0de7771..5e5b52f 100644 --- a/gridit/grid.py +++ b/gridit/grid.py @@ -1,5 +1,7 @@ """Grid class and spatial tools to read array datasets.""" +from typing import Optional + __all__ = ["Grid"] mask_cache = {} @@ -15,7 +17,7 @@ class Grid: 2D array shape (nrow, ncol). top_left : tuple, default (0.0, 0.0) Top left corner coordinate. - projection : str, default "" + projection : optional str, default None WKT coordinate reference system string. logger : logging.Logger, optional Logger to show messages. @@ -44,7 +46,7 @@ def __init__( resolution: float, shape: tuple, top_left: tuple = (0.0, 0.0), - projection: str = "", + projection: Optional[str] = None, logger=None, ): if logger is None: @@ -60,7 +62,7 @@ def __init__( if len(top_left) != 2: raise ValueError("expected top_left to contain two values") self.top_left = tuple(float(v) for v in top_left) - self.projection = str(projection) + self.projection = str(projection) if projection else None def __iter__(self): """Return object datasets with an iterator.""" diff --git a/gridit/spatial.py b/gridit/spatial.py index 08246d9..74f2abb 100644 --- a/gridit/spatial.py +++ b/gridit/spatial.py @@ -12,7 +12,7 @@ ] -def is_same_crs(wkt1, wkt2): +def is_same_crs(wkt1: str, wkt2: str) -> bool: """Determine if two CRS strings (as WKT) are nearly the same. First try to compare simple EPSG codes. Otherwise, use diff --git a/tests/data/nocrs.tif b/tests/data/nocrs.tif new file mode 100644 index 0000000000000000000000000000000000000000..6c9fefede781414192d37a5ba3c34a62ece3dafc GIT binary patch literal 266 zcmebD)MDUZU|`^7U|?isU<9(5ftV4>W&*OAplk(@9Fz@G$A-ifWMl#B{R9*jMG_Z- zvO(sEL)AzC*)m9Kf}w1Xy?o6)VEb1A*-b4x3~WI95D+7PiUWiVqY*OLSVuNlIfBH6 XPKAJJgqj9>Alm_mfVcsO7dQX_uec3< literal 0 HcmV?d00001 diff --git a/tests/data/points.csv b/tests/data/points.csv new file mode 100644 index 0000000..9c224ae --- /dev/null +++ b/tests/data/points.csv @@ -0,0 +1,4 @@ +wkt,elevation +POINT(1749679 5450940),105.74257 +POINT(1748906 5449151),77.4631 +POINT(1750155 5449639),11.318376 diff --git a/tests/test_array_from.py b/tests/test_array_from.py index a0f54be..9105ef6 100644 --- a/tests/test_array_from.py +++ b/tests/test_array_from.py @@ -17,6 +17,7 @@ mana_hk_nan_path = datadir / "Mana_hk_nan.tif" lines_path = datadir / "waitaku2_lines.shp" points_path = datadir / "waitaku2_points.shp" +nocrs_path = datadir / "nocrs.tif" @requires_pkg("rasterio") @@ -577,12 +578,22 @@ def test_array_from_vector_lines(caplog, attribute, refine, all_touched): @requires_pkg("rasterio") def test_array_from_raster_no_projection(): grid = Grid.from_bbox(1748762.8, 5448908.9, 1749509, 5449749, 25) - assert grid.projection == "" + assert grid.projection is None ar = grid.array_from_raster(mana_dem_path) assert ar.shape == (34, 31) assert ar.mask.sum() == 160 +@requires_pkg("rasterio") +def test_array_from_raster_without_projection(): + grid = Grid.from_raster(nocrs_path) + assert grid.projection is None + ar = grid.array_from_raster(nocrs_path) + assert ar.shape == (2, 3) + assert ar.mask.sum() == 0 + np.testing.assert_array_equal(ar, np.arange(6).reshape(2, 3)) + + @requires_pkg("rasterio") def test_array_from_raster_same_projection(): grid = Grid.from_bbox( @@ -608,7 +619,7 @@ def test_array_from_raster_different_projection(): @requires_pkg("fiona", "rasterio") def test_array_from_vector_no_projection(): grid = Grid.from_bbox(1748762.8, 5448908.9, 1749509, 5449749, 25) - assert grid.projection == "" + assert grid.projection is None ar = grid.array_from_vector(mana_polygons_path, attribute="K_m_d", refine=1) assert ar.shape == (34, 31) assert ar.mask.sum() == 146 diff --git a/tests/test_classmethods.py b/tests/test_classmethods.py index c14dde6..6887607 100644 --- a/tests/test_classmethods.py +++ b/tests/test_classmethods.py @@ -8,6 +8,8 @@ mana_polygons_path = datadir / "Mana_polygons.shp" lines_path = datadir / "waitaku2_lines.shp" points_path = datadir / "waitaku2_points.shp" +nocrs_path = datadir / "nocrs.tif" +nocrspoints_path = datadir / "points.csv" def test_grid_from_bbox(): @@ -61,6 +63,14 @@ def test_grid_from_raster_buffer(): assert grid == expected +@requires_pkg("rasterio") +def test_grid_from_raster_nocrs(): + grid = Grid.from_raster(nocrs_path) + expected = Grid(10.0, (2, 3), (1749700.0, 5449800.0)) + assert grid == expected + assert grid.projection is None + + @requires_pkg("fiona") def test_grid_from_vector_point(): # all @@ -83,6 +93,15 @@ def test_grid_from_vector_point(): assert grid == expected +@requires_pkg("fiona") +def test_grid_from_vector_nocrs(): + # all + grid = Grid.from_vector(nocrspoints_path, 250) + expected = Grid(250.0, (8, 6), (1748750.0, 5451000.0)) + assert grid == expected + assert grid.projection is None + + @requires_pkg("fiona") def test_grid_from_vector_polygon(): # all diff --git a/tests/test_grid.py b/tests/test_grid.py index 023f26a..e1a1abc 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -16,7 +16,7 @@ def test_grid_basic(grid_basic): assert grid.resolution == 10.0 assert grid.shape == (20, 30) assert grid.top_left == (1000.0, 2000.0) - assert grid.projection == "" + assert grid.projection is None def test_grid_dict(grid_basic): @@ -25,7 +25,7 @@ def test_grid_dict(grid_basic): assert grid_d["resolution"] == 10.0 assert grid_d["shape"] == (20, 30) assert grid_d["top_left"] == (1000.0, 2000.0) - assert grid_d["projection"] == "" + assert grid_d["projection"] is None def test_grid_repr(grid_basic): diff --git a/tests/test_modflow.py b/tests/test_modflow.py index 82c2c09..a960e97 100644 --- a/tests/test_modflow.py +++ b/tests/test_modflow.py @@ -57,7 +57,7 @@ def test_grid_from_modflow_6(caplog): grid = Grid.from_modflow(modflow_dir / "mfsim.nam", "h6") assert len(caplog.messages) == 0 assert grid == expected - assert grid.projection == "" + assert grid.projection is None grid = Grid.from_modflow(modflow_dir / "mfsim.nam", "h6", "EPSG:2193") # assert grid == expected @@ -67,7 +67,7 @@ def test_grid_from_modflow_6(caplog): grid = Grid.from_modflow(modflow_dir / "mfsim.nam") assert "a model name should be specified" in caplog.messages[-1] assert grid == expected - assert grid.projection == "" + assert grid.projection is None # also rasises logger warning grid = Grid.from_modflow(modflow_dir) @@ -77,7 +77,7 @@ def test_grid_from_modflow_6(caplog): with pytest.warns(UserWarning, match="model name should be specified"): model = get_modflow_model(modflow_dir / "mfsim.nam") grid = Grid.from_modflow(model.modelgrid) - assert grid.projection == "" + assert grid.projection is None assert grid.shape == (18, 17) assert grid.resolution == 1000.0