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

Improve arguments of get_patrols #275

Merged
merged 5 commits into from
Sep 30, 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
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
Loading