Skip to content

Commit

Permalink
Merge pull request #58 from fema-ffrd/feature/geopandas-1.0
Browse files Browse the repository at this point in the history
Upgrade to GeoPandas 1.0
  • Loading branch information
thwllms authored Jul 13, 2024
2 parents ac4b788 + 7383792 commit 000829f
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 16 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ CLI help:
$ rashdf --help
```

Print the output formats supported by Fiona:
Print the output formats supported by pyorgio:
```
$ rashdf --fiona-drivers
$ rashdf --pyogrio-drivers
```

Help for a specific subcommand:
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ classifiers = [
"Programming Language :: Python :: 3.12",
]
version = "0.5.0"
dependencies = ["h5py", "geopandas>=0.14,<0.15", "pyarrow", "xarray"]
dependencies = ["h5py", "geopandas>=1.0,<2.0", "pyarrow", "xarray"]

[project.optional-dependencies]
dev = ["pre-commit", "ruff", "pytest", "pytest-cov"]
dev = ["pre-commit", "ruff", "pytest", "pytest-cov", "fiona"]
docs = ["sphinx", "numpydoc", "sphinx_rtd_theme"]

[project.urls]
Expand Down
53 changes: 47 additions & 6 deletions src/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from rashdf import RasGeomHdf, RasPlanHdf
from rashdf.utils import df_datetimes_to_str

import fiona
from geopandas import GeoDataFrame

import argparse
Expand Down Expand Up @@ -52,6 +51,20 @@ def docstring_to_help(docstring: Optional[str]) -> str:
return help_text


def pyogrio_supported_drivers() -> List[str]:
"""Return a list of drivers supported by pyogrio for writing output files.
Returns
-------
list
A list of drivers supported by pyogrio for writing output files.
"""
import pyogrio

drivers = pyogrio.list_drivers(write=True)
return sorted(drivers)


def fiona_supported_drivers() -> List[str]:
"""Return a list of drivers supported by Fiona for writing output files.
Expand All @@ -60,18 +73,34 @@ def fiona_supported_drivers() -> List[str]:
list
A list of drivers supported by Fiona for writing output files.
"""
import fiona

drivers = [d for d, s in fiona.supported_drivers.items() if "w" in s]
return drivers
return sorted(drivers)


def parse_args(args: str) -> argparse.Namespace:
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(description="Extract data from HEC-RAS HDF files.")
parser.add_argument(
"--fiona-drivers",
"--pyogrio-drivers",
action="store_true",
help="List the drivers supported by Fiona for writing output files.",
help="List the drivers supported by pyogrio for writing output files.",
)
fiona_installed = False
engines = ["pyogrio"]
try:
import fiona

fiona_installed = True
engines.append("fiona")
parser.add_argument(
"--fiona-drivers",
action="store_true",
help="List the drivers supported by Fiona for writing output files.",
)
except ImportError:
pass
subparsers = parser.add_subparsers(help="Sub-command help")
for command in COMMANDS:
f = getattr(RasGeomHdf, command)
Expand All @@ -93,6 +122,13 @@ def parse_args(args: str) -> argparse.Namespace:
output_group.add_argument(
"--feather", action="store_true", help="Output as Feather."
)
output_group.add_argument(
"--engine",
type=str,
choices=engines,
default="pyogrio",
help="Engine for writing output data.",
)
subparser.add_argument(
"--kwargs",
type=str,
Expand All @@ -107,7 +143,11 @@ def parse_args(args: str) -> argparse.Namespace:

def export(args: argparse.Namespace) -> Optional[str]:
"""Act on parsed arguments to extract data from HEC-RAS HDF files."""
if args.fiona_drivers:
if args.pyogrio_drivers:
for driver in pyogrio_supported_drivers():
print(driver)
return
if hasattr(args, "fiona_drivers") and args.fiona_drivers:
for driver in fiona_supported_drivers():
print(driver)
return
Expand Down Expand Up @@ -140,6 +180,7 @@ def export(args: argparse.Namespace) -> Optional[str]:
),
)
result = gdf.to_json(**kwargs)
print("No output file!")
print(result)
return result
elif args.parquet:
Expand All @@ -155,7 +196,7 @@ def export(args: argparse.Namespace) -> Optional[str]:
# convert any datetime columns to string.
# TODO: besides Geopackage, which of the standard Fiona drivers allow datetime?
gdf = df_datetimes_to_str(gdf)
gdf.to_file(args.output_file, **kwargs)
gdf.to_file(args.output_file, engine=args.engine, **kwargs)


def main():
Expand Down
56 changes: 55 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
from src.cli import parse_args, export, docstring_to_help, fiona_supported_drivers
from src.cli import (
parse_args,
export,
docstring_to_help,
fiona_supported_drivers,
pyogrio_supported_drivers,
)

import geopandas as gpd
from pyproj import CRS
import pytest

import builtins
import json
from pathlib import Path

Expand Down Expand Up @@ -30,6 +38,15 @@ def test_fiona_supported_drivers():
assert "ESRI Shapefile" in drivers
assert "GeoJSON" in drivers
assert "GPKG" in drivers
assert "MBTiles" not in drivers


def test_pyogrio_supported_drivers():
drivers = pyogrio_supported_drivers()
assert "ESRI Shapefile" in drivers
assert "GeoJSON" in drivers
assert "GPKG" in drivers
assert "MBTiles" in drivers


def test_parse_args():
Expand Down Expand Up @@ -114,3 +131,40 @@ def test_export_plan_hdf():
exported = json.loads(export(args))
gdf = gpd.GeoDataFrame.from_features(exported)
assert len(gdf) == 4425


def test_fiona_missing(monkeypatch):
# Test behavior when fiona isn't installed

def mock_import(name, globals=None, locals=None, fromlist=(), level=0):
if name == "fiona":
raise ImportError("No module named 'fiona'")
return real_import(name, globals, locals, fromlist, level)

real_import = builtins.__import__

# Replace the built-in __import__ function with our mock
monkeypatch.setattr(builtins, "__import__", mock_import)

# Verify that the --fiona-drivers argument is not available
# when fiona is not installed
args = parse_args(["structures", "fake_file.hdf"])
assert not hasattr(args, "fiona_drivers")


def test_print_pyogrio_supported_drivers(capfd):
export(parse_args(["--pyogrio-drivers"]))
captured = capfd.readouterr()
assert "ESRI Shapefile" in captured.out
assert "GeoJSON" in captured.out
assert "GPKG" in captured.out
assert "MBTiles" in captured.out


def test_print_fiona_supported_drivers(capfd):
export(parse_args(["--fiona-drivers"]))
captured = capfd.readouterr()
assert "ESRI Shapefile" in captured.out
assert "GeoJSON" in captured.out
assert "GPKG" in captured.out
assert "MBTiles" not in captured.out
10 changes: 5 additions & 5 deletions tests/test_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def test_mesh_cell_points_with_output(tmp_path):
)
temp_points = tmp_path / "temp-bald-eagle-mesh-cell-points.geojson"
gdf = gdf.to_crs(4326)
gdf.to_file(temp_points)
gdf.to_file(temp_points, engine="fiona")
valid = get_sha1_hash(TEST_JSON / "bald-eagle-mesh-cell-points.geojson")
test = get_sha1_hash(temp_points)
assert valid == test
Expand All @@ -161,7 +161,7 @@ def test_mesh_cell_polygons_with_output(tmp_path):
],
)
temp_polygons = tmp_path / "temp-bald-eagle-mesh-cell-polygons.geojson"
gdf.to_crs(4326).to_file(temp_polygons)
gdf.to_crs(4326).to_file(temp_polygons, engine="fiona")
valid = get_sha1_hash(TEST_JSON / "bald-eagle-mesh-cell-polygons.geojson")
test = get_sha1_hash(temp_polygons)
assert valid == test
Expand All @@ -177,7 +177,7 @@ def test_mesh_cell_faces_with_output(tmp_path):
],
)
temp_faces = tmp_path / "temp-bald-eagle-mesh-cell-faces.geojson"
gdf.to_crs(4326).to_file(temp_faces)
gdf.to_crs(4326).to_file(temp_faces, engine="fiona")
valid = get_sha1_hash(TEST_JSON / "bald-eagle-mesh-cell-faces.geojson")
test = get_sha1_hash(temp_faces)
assert valid == test
Expand Down Expand Up @@ -271,7 +271,7 @@ def test_reference_lines(tmp_path: Path):
plan_hdf = RasPlanHdf(BALD_EAGLE_P18_REF)
gdf = plan_hdf.reference_lines(datetime_to_str=True)
temp_lines = tmp_path / "temp-bald-eagle-reference-lines.geojson"
gdf.to_crs(4326).to_file(temp_lines)
gdf.to_crs(4326).to_file(temp_lines, engine="fiona")
with open(TEST_JSON / "bald-eagle-reflines.geojson") as f:
valid_lines = f.read()
with open(temp_lines) as f:
Expand Down Expand Up @@ -310,7 +310,7 @@ def test_reference_points(tmp_path: Path):
plan_hdf = RasPlanHdf(BALD_EAGLE_P18_REF)
gdf = plan_hdf.reference_points(datetime_to_str=True)
temp_lines = tmp_path / "temp-bald-eagle-reference-points.geojson"
gdf.to_crs(4326).to_file(temp_lines)
gdf.to_crs(4326).to_file(temp_lines, engine="fiona")
with open(TEST_JSON / "bald-eagle-refpoints.geojson") as f:
valid_points = f.read()
with open(temp_lines) as f:
Expand Down

0 comments on commit 000829f

Please sign in to comment.