Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More type hints #669

Merged
merged 7 commits into from
Mar 14, 2024
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
3 changes: 2 additions & 1 deletion mikeio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path
from platform import architecture
from collections.abc import Sequence
from typing import Any

# PEP0440 compatible formatted version, see:
# https://www.python.org/dev/peps/pep-0440/
Expand Down Expand Up @@ -135,7 +136,7 @@ def read(
return dfs.read(items=items, time=time, keepdims=keepdims, **kwargs)


def open(filename: str | Path, **kwargs):
def open(filename: str | Path, **kwargs: Any) -> Any:
"""Open a dfs/mesh file (and read the header)

The typical workflow for small dfs files is to read all data
Expand Down
57 changes: 24 additions & 33 deletions mikeio/dfs/_dfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,16 @@ class _Dfs123:

show_progress = False

def __init__(self, filename=None):
def __init__(self, filename: str | Path) -> None:
path = Path(filename)
if not path.exists():
raise FileNotFoundError(path)
self._filename = str(filename) if filename else None
self._end_time = None
self._is_equidistant = True
self._geometry = None # handled by subclass
dfs = DfsFileFactory.DfsGenericOpen(self._filename)
self._dfs = dfs
self._geometry: Any = None # Handled by subclass
n_items = len(dfs.ItemInfo)
self._items = self._get_item_info(list(range(n_items)))
if dfs.FileInfo.TimeAxis.TimeAxisType in {
Expand Down Expand Up @@ -310,17 +310,17 @@ def __init__(self, filename=None):
else:
self._timestep = None
self._time = None
self._n_timesteps = dfs.FileInfo.TimeAxis.NumberOfTimeSteps
self._n_timesteps: int = dfs.FileInfo.TimeAxis.NumberOfTimeSteps
projstr = dfs.FileInfo.Projection.WKTString
self._projstr = "NON-UTM" if not projstr else projstr
self._longitude = dfs.FileInfo.Projection.Longitude
self._latitude = dfs.FileInfo.Projection.Latitude
self._orientation = dfs.FileInfo.Projection.Orientation
self._deletevalue = dfs.FileInfo.DeleteValueFloat
self._projstr: str = "NON-UTM" if not projstr else projstr
self._longitude: float = dfs.FileInfo.Projection.Longitude
self._latitude: float = dfs.FileInfo.Projection.Latitude
self._orientation: float = dfs.FileInfo.Projection.Orientation
self._deletevalue: float = dfs.FileInfo.DeleteValueFloat

dfs.Close()

def __repr__(self):
def __repr__(self) -> str:
name = self.__class__.__name__
out = [f"<mikeio.{name}>"]

Expand Down Expand Up @@ -411,20 +411,17 @@ def read(

t_seconds[i] = itemdata.Time

time = pd.to_datetime(t_seconds, unit="s", origin=self.start_time) # type: ignore
time = pd.to_datetime(t_seconds, unit="s", origin=self.start_time)

items = _get_item_info(self._dfs.ItemInfo, item_numbers)

self._dfs.Close()
return Dataset(data_list, time, items, geometry=self.geometry, validate=False)

def _open(self):
def _open(self) -> None:
raise NotImplementedError("Should be implemented by subclass")

def _set_spatial_axis(self):
raise NotImplementedError("Should be implemented by subclass")

def _get_item_info(self, item_numbers):
def _get_item_info(self, item_numbers: Sequence[int]) -> List[ItemInfo]:
"""Read DFS ItemInfo

Parameters
Expand All @@ -444,8 +441,8 @@ def _get_item_info(self, item_numbers):
itemtype = EUMType(eumItem)
unit = EUMUnit(eumUnit)
data_value_type = self._dfs.ItemInfo[item].ValueType
item = ItemInfo(name, itemtype, unit, data_value_type)
items.append(item)
item_info = ItemInfo(name, itemtype, unit, data_value_type)
items.append(item_info)
return items

@property
Expand Down Expand Up @@ -490,37 +487,37 @@ def n_timesteps(self) -> int:
return self._n_timesteps

@property
def timestep(self) -> float:
def timestep(self) -> Any:
"""Time step size in seconds"""
# this will fail if the TimeAxisType is not calendar and equidistant, but that is ok
return self._dfs.FileInfo.TimeAxis.TimeStepInSeconds()

@property
def projection_string(self):
def projection_string(self) -> str:
return self._projstr

@property
def longitude(self):
def longitude(self) -> float:
"""Origin longitude"""
return self._longitude

@property
def latitude(self):
def latitude(self) -> float:
"""Origin latitude"""
return self._latitude

@property
def origin(self):
def origin(self) -> Any:
"""Origin (in own CRS)"""
return self.geometry.origin

@property
def orientation(self):
def orientation(self) -> Any:
"""Orientation (in own CRS)"""
return self.geometry.orientation

@property
def is_geo(self):
def is_geo(self) -> bool:
"""Are coordinates geographical (LONG/LAT)?"""
return self._projstr == "LONG/LAT"

Expand All @@ -530,17 +527,11 @@ def shape(self) -> Tuple[int, ...]:
"""Shape of the data array"""
pass

@property
@abstractmethod
def dx(self):
"""Step size in x direction"""
pass

def _validate_no_orientation_in_geo(self):
def _validate_no_orientation_in_geo(self) -> None:
if self.is_geo and abs(self._orientation) > 1e-6:
raise ValueError("Orientation is not supported for LONG/LAT coordinates")

def _origin_and_orientation_in_CRS(self):
def _origin_and_orientation_in_CRS(self) -> Tuple[Any, float]:
"""Project origin and orientation to projected CRS (if not LONG/LAT)"""
if self.is_geo:
origin = self._longitude, self._latitude
Expand All @@ -554,6 +545,6 @@ def _origin_and_orientation_in_CRS(self):
orientation=self._orientation,
)
# convert origin and orientation to projected CRS
origin = tuple(np.round(cart.Geo2Proj(lon, lat), 6))
origin = tuple(np.round(cart.Geo2Proj(lon, lat), 6)) # type: ignore
orientation = cart.OrientationProj
return origin, orientation
54 changes: 28 additions & 26 deletions mikeio/dfs/_dfs0.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def _write_dfs0(
if len(dataset.time) == 1:
dt = 1.0 # TODO
else:
dt = (dataset.time[1] - dataset.time[0]).total_seconds() # type: ignore
dt = (dataset.time[1] - dataset.time[0]).total_seconds()

temporal_axis = factory.CreateTemporalEqCalendarAxis(
TimeStepUnit.SECOND, system_start_time, 0, dt
Expand Down Expand Up @@ -65,7 +65,7 @@ def _write_dfs0(

delete_value = dfs.FileInfo.DeleteValueFloat

t_seconds = (dataset.time - dataset.time[0]).total_seconds().values # type: ignore
t_seconds = (dataset.time - dataset.time[0]).total_seconds().values

data = np.array(dataset.to_numpy(), order="F").astype(np.float64).T
data[np.isnan(data)] = delete_value
Expand Down Expand Up @@ -109,16 +109,16 @@ def __init__(self, filename: str | Path):
TimeAxisType.CalendarEquidistant,
TimeAxisType.CalendarNonEquidistant,
}:
self._start_time = dfs.FileInfo.TimeAxis.StartDateTime
self._start_time: datetime = dfs.FileInfo.TimeAxis.StartDateTime
else: # relative time axis
self._start_time = datetime(1970, 1, 1)

# time
self._n_timesteps = dfs.FileInfo.TimeAxis.NumberOfTimeSteps
self._n_timesteps: int = dfs.FileInfo.TimeAxis.NumberOfTimeSteps

dfs.Close()

def __repr__(self):
def __repr__(self) -> str:
out = ["<mikeio.Dfs0>"]
out.append(f"timeaxis: {repr(self._timeaxistype)}")

Expand Down Expand Up @@ -199,7 +199,9 @@ def read(

return ds

def _read(self, filename):
def _read(
self, filename: str
) -> tuple[list[np.ndarray], pd.DatetimeIndex, list[ItemInfo]]:
"""
Read all data from a dfs0 file.
"""
Expand Down Expand Up @@ -233,7 +235,7 @@ def _read(self, filename):
return data, time, items

@staticmethod
def _to_dfs_datatype(dtype):
def _to_dfs_datatype(dtype: Any = None) -> DfsSimpleType:
if dtype is None:
return DfsSimpleType.Float

Expand Down Expand Up @@ -339,7 +341,7 @@ def n_timesteps(self) -> int:
def timestep(self) -> float:
"""Time step size in seconds"""
if self._timeaxistype == TimeAxisType.CalendarEquidistant:
return self._source.FileInfo.TimeAxis.TimeStep
return self._source.FileInfo.TimeAxis.TimeStep # type: ignore
else:
raise ValueError("Time axis type not supported")

Expand All @@ -361,28 +363,28 @@ def time(self) -> pd.DatetimeIndex:


def series_to_dfs0(
self,
filename,
itemtype=None,
unit=None,
items=None,
title=None,
dtype=None,
):
self: pd.Series,
filename: str,
itemtype: EUMType | None = None,
unit: EUMUnit | None = None,
items: Sequence[ItemInfo] | None = None,
title: str | None = None,
dtype: Any | None = None,
) -> None:

df = pd.DataFrame(self)
df.to_dfs0(filename, itemtype, unit, items, title, dtype)


def dataframe_to_dfs0(
self,
filename,
itemtype=None,
unit=None,
items=None,
title=None,
dtype=None,
):
self: pd.DataFrame,
filename: str,
itemtype: EUMType | None = None,
unit: EUMUnit | None = None,
items: Sequence[ItemInfo] | None = None,
title: str = "",
dtype: Any | None = None,
) -> None:
"""
Create a dfs0

Expand Down Expand Up @@ -427,6 +429,6 @@ def dataframe_to_dfs0(


# Monkey patching onto Pandas classes
pd.DataFrame.to_dfs0 = dataframe_to_dfs0 # type: ignore
pd.DataFrame.to_dfs0 = dataframe_to_dfs0

pd.Series.to_dfs0 = series_to_dfs0 # type: ignore
pd.Series.to_dfs0 = series_to_dfs0
26 changes: 10 additions & 16 deletions mikeio/dfs/_dfs1.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ def _write_dfs1_header(filename: str | Path, ds: Dataset, title: str) -> DfsFile
class Dfs1(_Dfs123):
_ndim = 1

def __init__(self, filename):
def __init__(self, filename: str | Path) -> None:
super().__init__(filename)

self._dfs = DfsFileFactory.Dfs1FileOpen(str(filename))
self._x0 = self._dfs.SpatialAxis.X0
self._dx = self._dfs.SpatialAxis.Dx
self._nx = self._dfs.SpatialAxis.XCount
self._x0: float = self._dfs.SpatialAxis.X0
self._dx: float = self._dfs.SpatialAxis.Dx
self._nx: int = self._dfs.SpatialAxis.XCount

origin = self._longitude, self._latitude
self._geometry = Grid1D(
Expand All @@ -86,31 +86,25 @@ def __init__(self, filename):
orientation=self._orientation,
)

def _open(self):
def _open(self) -> None:
self._dfs = DfsFileFactory.Dfs1FileOpen(self._filename)

def _set_spatial_axis(self):
self._builder.SetSpatialAxis(
self._factory.CreateAxisEqD1(
eumUnit.eumUmeter, self._nx, self._x0, self._dx
)
)

@property
def geometry(self):
def geometry(self) -> Grid1D:
assert isinstance(self._geometry, Grid1D)
return self._geometry

@property
def x0(self):
def x0(self) -> float:
"""Start point of x values (often 0)"""
return self._x0

@property
def dx(self):
def dx(self) -> float:
"""Step size in x direction"""
return self._dx

@property
def nx(self):
def nx(self) -> int:
"""Number of node values"""
return self._nx
Loading
Loading