Skip to content
Merged

Dev #116

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
93 changes: 45 additions & 48 deletions trajan/traj.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,6 @@ def detect_time_dim(ds, obsdim):

raise ValueError("no time dimension detected")

def __require_obsdim__(f):
"""
This decorator is for methods of Traj that require a time or obs dimension to work.
"""

def wrapper(*args, **kwargs):
if args[0].obsdim is None:
raise ValueError(f'{f} requires an obs or time dimension')
return f(*args, **kwargs)

return wrapper

class Traj:
ds: xr.Dataset
Expand Down Expand Up @@ -343,13 +332,13 @@ def assign_cf_attrs(self,
return ds

def index_of_last(self):
"""Find index of last valid position along each trajectory"""
"""Find index of last valid position along each trajectory."""
return np.ma.notmasked_edges(np.ma.masked_invalid(self.ds.lon.values),
axis=1)[1][1]

@abstractmethod
def speed(self) -> xr.DataArray:
"""Returns the speed [m/s] along trajectories
"""Returns the speed [m/s] along trajectories.

Calculates the speed by dividing the distance between two points
along trajectory by the corresponding time step.
Expand All @@ -371,7 +360,7 @@ def speed(self) -> xr.DataArray:

@abstractmethod
def time_to_next(self) -> pd.Timedelta:
"""Returns the timedelta between time steps
"""Returns the timedelta between time steps.

Returns
-------
Expand Down Expand Up @@ -434,9 +423,8 @@ def distance_to(self, other) -> xr.Dataset:

return ds

@__require_obsdim__
def distance_to_next(self):
"""Returns distance in m from one position to the next
"""Returns distance in m from one position to the next.

Last time is repeated for last position (which has no next position)

Expand Down Expand Up @@ -467,9 +455,8 @@ def distance_to_next(self):
dim=self.obsdim) # repeating last time step to
return distance

@__require_obsdim__
def azimuth_to_next(self):
"""Returns azimution travel direction in degrees from one position to the next
"""Returns azimution travel direction in degrees from one position to the next.

Last time is repeated for last position (which has no next position)
"""
Expand All @@ -495,7 +482,7 @@ def azimuth_to_next(self):
return azimuth_forward

def velocity_components(self):
"""Returns velocity components [m/s] from one position to the next
"""Returns velocity components [m/s] from one position to the next.

Last time is repeated for last position (which has no next position)
"""
Expand All @@ -508,7 +495,13 @@ def velocity_components(self):
return u, v

def convex_hull(self):
"""Returns the scipy convex hull for all particles, in geographical coordinates"""
"""Return the scipy convex hull for all particles, in geographical coordinates.

Returns
-------
scipy.spatial.ConvexHull
Convex Hull around all positions of given Dataset.
"""

from scipy.spatial import ConvexHull

Expand All @@ -528,7 +521,18 @@ def convex_hull(self):
return ConvexHull(points)

def convex_hull_contains_point(self, lon, lat):
"""Returns True if given point is within the scipy convex hull for all particles"""
"""Return True if given point is within the scipy convex hull for all particles.

Parameters
----------
lon, lat scalars
longitude and latitude [degrees] of a position.

Returns
-------
bool
True if convex hull of positions in cataset contains given position.
"""
from matplotlib.patches import Polygon

hull = self.ds.traj.convex_hull()
Expand All @@ -539,7 +543,7 @@ def convex_hull_contains_point(self, lon, lat):
return p.contains_points(point)[0]

def get_area_convex_hull(self):
"""Returns the area [m2] of the convex hull spanned by all particles"""
"""Return the area [m2] of the convex hull spanned by all particles."""

from scipy.spatial import ConvexHull

Expand Down Expand Up @@ -589,7 +593,6 @@ def seltime(self, t0=None, t1=None) -> xr.Dataset:


@abstractmethod
@__require_obsdim__
def skill(self, other, method='liu-weissberg', **kwargs) -> xr.DataArray:
"""
Compare the skill score between this trajectory and `other`.
Expand Down Expand Up @@ -618,32 +621,26 @@ def skill(self, other, method='liu-weissberg', **kwargs) -> xr.DataArray:
The datasets must have the same number of trajectories. If you wish to compare a single trajectory to many others, duplicate it along the trajectory dimension to match the trajectory dimension of the other. See further down for an example.


.. testcode::

import xarray as xr
import trajan as _
import lzma

b = lzma.open('examples/barents.nc.xz')
ds = xr.open_dataset(b)

other = ds.copy()

ds = ds.traj.gridtime('1H')

other = other.traj.gridtime(ds.time)
skill = ds.traj.skill(other)

print(skill)

.. testoutput::
Examples
--------

<xarray.DataArray 'Skill-score' (trajectory: 2)>
array([1., 1.], dtype=float32)
Coordinates:
* trajectory (trajectory) int64 0 1
Attributes:
method: liu-weissberg
>>> import xarray as xr
>>> import trajan as _
>>> import lzma
>>> b = lzma.open('examples/barents.nc.xz')
>>> ds = xr.open_dataset(b)
>>> other = ds.copy()
>>> ds = ds.traj.gridtime('1h')
>>> other = other.traj.gridtime(ds.time)
>>> skill = ds.traj.skill(other)

>>> print(skill)
<xarray.DataArray 'Skill-score' (trajectory: 2)> Size: 8B
array([1., 1.], dtype=float32)
Coordinates:
* trajectory (trajectory) int64 16B 0 1
Attributes:
method: liu-weissberg


If you need to broadcast a dataset with a single drifter to one with many you can use `xarray.broadcast` or `xarray.Dataset.broadcast_like`:
Expand Down
9 changes: 2 additions & 7 deletions trajan/traj1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np
import pandas as pd
import logging
from .traj import Traj, __require_obsdim__
from .traj import Traj
from . import skill

logger = logging.getLogger(__name__)
Expand All @@ -18,9 +18,7 @@ def __init__(self, ds, obsdim, timedim):
super().__init__(ds, obsdim, timedim)

def timestep(self):
"""
Time step between observations in seconds.
"""
"""Time step between observations in seconds."""
return ((self.ds.time[1] - self.ds.time[0]) /
np.timedelta64(1, 's')).values

Expand Down Expand Up @@ -85,7 +83,6 @@ def rotary_spectrum(self):
plt.xlim([0, 30])
plt.show()

@__require_obsdim__
def skill(self, other, method='liu-weissberg', **kwargs):
if self.ds.sizes['trajectory'] != other.sizes['trajectory']:
raise ValueError(
Expand Down Expand Up @@ -129,11 +126,9 @@ def skill(self, other, method='liu-weissberg', **kwargs):
coords={'trajectory': self.ds.trajectory},
attrs={'method': method})

@__require_obsdim__
def seltime(self, t0=None, t1=None):
return self.ds.sel({self.timedim: slice(t0, t1)})

@__require_obsdim__
def gridtime(self, times, timedim=None, round=True):
if isinstance(times, str) or isinstance(
times, pd.Timedelta): # Make time series with given interval
Expand Down
20 changes: 16 additions & 4 deletions trajan/traj2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@
import pandas as pd
import logging

from .traj import Traj, __require_obsdim__
from .traj import Traj

logger = logging.getLogger(__name__)


def __require_obsdim__(f):
"""This decorator is for methods of Traj that require a time or obs dimension to work."""

def wrapper(*args, **kwargs):
if args[0].obsdim is None:
raise ValueError(f'{f} requires an obs or time dimension')
return f(*args, **kwargs)

return wrapper



class Traj2d(Traj):
"""
A unstructured dataset, where each trajectory may have observations at different times. Typically from a collection of drifters.
Expand All @@ -25,7 +37,7 @@ def timestep(self, average=np.nanmedian):
return td

def time_to_next(self):
"""Returns time from one position to the next
"""Return time from one position to the next.

Returned datatype is np.timedelta64
Last time is repeated for last position (which has no next position)
Expand All @@ -45,7 +57,7 @@ def is_2d(self):
return True

def insert_nan_where(self, condition):
"""Insert NaN-values in trajectories after given positions, shifting rest of trajectory"""
"""Insert NaN-values in trajectories after given positions, shifting rest of trajectory."""

index_of_last = self.index_of_last()
num_inserts = condition.sum(dim='obs')
Expand Down Expand Up @@ -102,7 +114,7 @@ def insert_nan_where(self, condition):
return nd

def drop_where(self, condition):
"""Remove positions where condition is True, shifting rest of trajectory"""
"""Remove positions where condition is True, shifting rest of trajectory."""

trajs = []
newlen = 0
Expand Down
Loading