Skip to content

Commit

Permalink
Improve arguments of get_patrols (#275)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yun-Wu authored Sep 30, 2024
1 parent 3d2c41b commit 45aa020
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 20 deletions.
73 changes: 55 additions & 18 deletions ecoscope/io/earthranger.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
from tqdm.auto import tqdm

import ecoscope
from ecoscope.io.earthranger_utils import clean_kwargs, clean_time_cols, dataframe_to_dict, to_gdf
from ecoscope.io.earthranger_utils import (
clean_kwargs,
clean_time_cols,
dataframe_to_dict,
format_iso_time,
to_gdf,
)
from ecoscope.io.utils import pack_columns, to_hex


Expand Down Expand Up @@ -629,39 +635,53 @@ def get_patrol_types(self):
df = pd.DataFrame(self._get("activity/patrols/types"))
return df.set_index("id")

def get_patrols(self, since=None, until=None, patrol_type=None, status=None, **addl_kwargs):
def get_patrols(self, since=None, until=None, patrol_type=None, patrol_type_value=None, status=None, **addl_kwargs):
"""
Parameters
----------
since:
lower date range
Lower time range
until:
upper date range
Upper time range
patrol_type:
Comma-separated list of type of patrol UUID
A patrol type UUID or a list of UUIDs
patrol_type_value:
A patrol type value or a list of patrol type values
status
Comma-separated list of 'scheduled'/'active'/'overdue'/'done'/'cancelled'
'scheduled'/'active'/'overdue'/'done'/'cancelled'
Accept a status string or a list of statuses
Returns
-------
patrols : pd.DataFrame
DataFrame of queried patrols
"""

patrol_type_value_list = [patrol_type_value] if isinstance(patrol_type_value, str) else patrol_type_value
params = clean_kwargs(
addl_kwargs,
status=status,
patrol_type=[patrol_type] if isinstance(patrol_type, str) else patrol_type,
patrol_type_value=patrol_type_value_list,
return_data=True,
)

filter = {"date_range": {}, "patrol_type": []}

if since is not None:
filter["date_range"]["lower"] = since
filter["date_range"]["lower"] = format_iso_time(since)
if until is not None:
filter["date_range"]["upper"] = until
filter["date_range"]["upper"] = format_iso_time(until)
if patrol_type is not None:
filter["patrol_type"] = params["patrol_type"]
if patrol_type_value_list is not None:
patrol_types = self.get_patrol_types()
matching_rows = patrol_types[patrol_types["value"].isin(patrol_type_value_list)]
missing_values = set(patrol_type_value_list) - set(matching_rows["value"])
if missing_values:
raise ValueError(f"Failed to find IDs for values: {missing_values}")

filter["patrol_type"] = matching_rows.index.tolist()

params["filter"] = json.dumps(filter)

df = pd.DataFrame(
Expand All @@ -677,18 +697,23 @@ def get_patrols(self, since=None, until=None, patrol_type=None, status=None, **a
df = clean_time_cols(df)
return df

def get_patrol_events(self, since=None, until=None, patrol_type=None, status=None, **addl_kwargs):
def get_patrol_events(
self, since=None, until=None, patrol_type=None, patrol_type_value=None, status=None, **addl_kwargs
):
"""
Parameters
----------
since:
lower date range
Lower time range
until:
upper date range
Upper time range
patrol_type:
Comma-separated list of type of patrol UUID
A patrol type UUID or a list of UUIDs
patrol_type_value:
A patrol type value or a list of patrol type values
status
Comma-separated list of 'scheduled'/'active'/'overdue'/'done'/'cancelled'
'scheduled'/'active'/'overdue'/'done'/'cancelled'
Accept a status string or a list of statuses
Returns
-------
events : pd.DataFrame
Expand All @@ -698,6 +723,7 @@ def get_patrol_events(self, since=None, until=None, patrol_type=None, status=Non
since=since,
until=until,
patrol_type=patrol_type,
patrol_type_value=patrol_type_value,
status=status,
**addl_kwargs,
)
Expand Down Expand Up @@ -757,6 +783,7 @@ def get_patrol_observations_with_patrol_filter(
since=None,
until=None,
patrol_type=None,
patrol_type_value=None,
status=None,
include_patrol_details=False,
**kwargs,
Expand All @@ -767,13 +794,16 @@ def get_patrol_observations_with_patrol_filter(
Parameters
----------
since:
lower date range
Lower time range
until:
upper date range
Upper time range
patrol_type:
Comma-separated list of type of patrol UUID
A patrol type UUID or a list of UUIDs
patrol_type_value:
A patrol type value or a list of patrol type values
status
Comma-separated list of 'scheduled'/'active'/'overdue'/'done'/'cancelled'
'scheduled'/'active'/'overdue'/'done'/'cancelled'
Accept a status string or a list of statuses
include_patrol_details : bool, optional
Whether to merge patrol details into dataframe
kwargs
Expand All @@ -784,7 +814,14 @@ def get_patrol_observations_with_patrol_filter(
relocations : ecoscope.base.Relocations
"""

patrols_df = self.get_patrols(since=since, until=until, patrol_type=patrol_type, status=status, **kwargs)
patrols_df = self.get_patrols(
since=since,
until=until,
patrol_type=patrol_type,
patrol_type_value=patrol_type_value,
status=status,
**kwargs,
)
return self.get_patrol_observations(patrols_df, include_patrol_details=include_patrol_details, **kwargs)

def get_patrol_observations(self, patrols_df, include_patrol_details=False, **kwargs):
Expand Down
7 changes: 7 additions & 0 deletions ecoscope/io/earthranger_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,10 @@ def clean_time_cols(df):
# convert x is not None to pd.isna(x) is False
df[col] = df[col].apply(lambda x: pd.to_datetime(parser.parse(x)) if not pd.isna(x) else None)
return df


def format_iso_time(date_string: str) -> str:
try:
return pd.to_datetime(date_string).isoformat()
except ValueError:
raise ValueError(f"Failed to parse timestamp'{date_string}'")
59 changes: 57 additions & 2 deletions tests/test_earthranger_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import geopandas as gpd
import pandas as pd
import pytest
import pytz
from shapely.geometry import Point

import ecoscope
Expand Down Expand Up @@ -81,10 +82,63 @@ def test_das_client_method(er_io):
er_io.get_me()


def test_get_patrols(er_io):
patrols = er_io.get_patrols()
def test_get_patrols_datestr(er_io):
since_str = "2017-01-01"
since_time = pd.to_datetime(since_str).replace(tzinfo=pytz.UTC)
until_str = "2017-04-01"
until_time = pd.to_datetime(until_str).replace(tzinfo=pytz.UTC)
patrols = er_io.get_patrols(since=since_str, until=until_str)

assert len(patrols) > 0

time_ranges = [
segment["time_range"]
for segments in patrols["patrol_segments"]
for segment in segments
if "time_range" in segment
]

for time_range in time_ranges:
start = pd.to_datetime(time_range["start_time"])
end = pd.to_datetime(time_range["end_time"])

assert start <= until_time and end >= since_time


def test_get_patrols_datestr_invalid_format(er_io):
with pytest.raises(ValueError):
er_io.get_patrols(since="not a date")


def test_get_patrols_with_type_value(er_io):
patrols = er_io.get_patrols(since="2017-01-01", until="2017-04-01", patrol_type_value="ecoscope_patrol")

patrol_types = [
segment["patrol_type"]
for segments in patrols["patrol_segments"]
for segment in segments
if "patrol_type" in segment
]
assert all(value == "ecoscope_patrol" for value in patrol_types)


def test_get_patrols_with_type_value_list(er_io):
patrol_type_value_list = ["ecoscope_patrol", "MEP_Distance_Survey_Patrol"]
patrols = er_io.get_patrols(since="2024-01-01", until="2024-04-01", patrol_type_value=patrol_type_value_list)

patrol_types = [
segment["patrol_type"]
for segments in patrols["patrol_segments"]
for segment in segments
if "patrol_type" in segment
]
assert all(value in patrol_type_value_list for value in patrol_types)


def test_get_patrols_with_invalid_type_value(er_io):
with pytest.raises(ValueError):
er_io.get_patrols(since="2017-01-01", until="2017-04-01", patrol_type_value="invalid")


def test_get_patrol_events(er_io):
events = er_io.get_patrol_events(
Expand All @@ -96,6 +150,7 @@ def test_get_patrol_events(er_io):
assert "geometry" in events
assert "patrol_id" in events
assert "patrol_segment_id" in events
assert "time" in events


def test_post_observations(er_io):
Expand Down

0 comments on commit 45aa020

Please sign in to comment.