https://colab.research.google.com/assets/colab-badge.svg

Relocations and Trajectories#

Setup#

Ecoscope#

[ ]:
ECOSCOPE_RAW = "https://raw.githubusercontent.com/wildlife-dynamics/ecoscope/master"

# !pip install ecoscope
[ ]:
import os
import sys

import geopandas as gpd
import pandas as pd
import shapely

import ecoscope

ecoscope.init()

Google Drive Setup#

[ ]:
output_dir = "Ecoscope-Outputs"

if "google.colab" in sys.modules:
    from google.colab import drive

    drive.mount("/content/drive/", force_remount=True)
    output_dir = os.path.join("/content/drive/MyDrive/", output_dir)

os.makedirs(output_dir, exist_ok=True)

Relocations#

Read in sample data in MovBank format#

[ ]:
ecoscope.io.download_file(
    f"{ECOSCOPE_RAW}/tests/sample_data/vector/movbank_data.csv",
    os.path.join(output_dir, "movbank_data.csv"),
)

data = pd.read_csv(os.path.join(output_dir, "movbank_data.csv"))

Make Data Timezone Aware and Sort by Time#

[ ]:
data["timestamp"] = pd.to_datetime(data["timestamp"], utc=True)
data.sort_values(["timestamp"], inplace=True)

Create GeoDataFrame Using Lat/Lon Point Coordinates#

[ ]:
gdf = gpd.GeoDataFrame(data, geometry=gpd.points_from_xy(x=data["location-long"], y=data["location-lat"]), crs=4326)

Display columns:

[ ]:
gdf.columns

Create Relocations from Specified Time, Subject Id, and Observation Id#

[ ]:
relocs = ecoscope.base.Relocations.from_gdf(
    gdf,
    groupby_col="individual-local-identifier",
    time_col="timestamp",
    uuid_col="event-id",
)

When the dataframe becomes a Relocations it will gain fixtime, groupby_col, junk_status columns that are expected by Ecoscope analyses

[ ]:
relocs.columns

Optionally drop Non-Essential Columns#

[ ]:
relocs = relocs[["groupby_col", "fixtime", "junk_status", "geometry"]]
[ ]:
relocs

Which unique subjects are contained in the groupby_col column?

[ ]:
relocs["groupby_col"].unique()

Visualize Data#

[ ]:
relocs[["groupby_col", "fixtime", "geometry"]].explore()

Mark Points as Junk Based on Coordinates#

[ ]:
coord_filter = ecoscope.base.RelocsCoordinateFilter(
    min_x=-5,
    max_x=1,
    min_y=12,
    max_y=18,
    filter_point_coords=[[180, 90], [0, 0]],
)
relocs.apply_reloc_filter(coord_filter, inplace=True)

Count values marked as junk:

[ ]:
relocs["junk_status"].value_counts()

Mark Points as Junk Based on Speed#

[ ]:
speed_filter = ecoscope.base.RelocsSpeedFilter(max_speed_kmhr=4.0)
relocs.apply_reloc_filter(speed_filter, inplace=True)
[ ]:
relocs["junk_status"].value_counts()

Remove Flagged Fixes#

[ ]:
relocs.remove_filtered(inplace=True)

Remove Duplicate Fixes#

[ ]:
print(relocs.duplicated(subset=["fixtime", "geometry"]).any())

relocs = relocs.drop_duplicates(subset=["fixtime", "geometry"])

Visualize:

[ ]:
relocs["geometry"].explore()

Determine whether each relocation is a day or night#

[ ]:
relocs["is_night"] = ecoscope.analysis.astronomy.is_night(relocs.geometry, relocs.fixtime)

Break Relocations Geometry Into X and Y#

[ ]:
relocs["lat"] = relocs["geometry"].y
relocs["lon"] = relocs["geometry"].x

Convert to UTM and Back to WGS 84#

[ ]:
relocs = relocs.to_crs(relocs.estimate_utm_crs())
relocs["extra__northing"] = relocs["geometry"].y
relocs["extra__easting"] = relocs["geometry"].x
relocs = relocs.to_crs(4326)
[ ]:
relocs.columns

Export to Compressed CSV#

[ ]:
relocs.to_csv(os.path.join(output_dir, "relocs_clean.csv.zip"), header=True, index=True, compression="zip")

Trajectory#

Read in Relocations#

relocs from the previous steps can be used instead of redefining it here.

[ ]:
ecoscope.io.download_file(
    f"{ECOSCOPE_RAW}/tests/sample_data/vector/er_relocs.csv.zip",
    os.path.join(output_dir, "er_relocs.csv.zip"),
)

data = pd.read_csv(os.path.join(output_dir, "er_relocs.csv.zip"), header=0, index_col=0)

Parse Geometry from WKT#

[ ]:
gdf = gpd.GeoDataFrame(data, geometry=data["geometry"].map(lambda x: shapely.wkt.loads(x)), crs=4326)
[ ]:
relocs = ecoscope.base.Relocations.from_gdf(gdf)

Create Trajectory from Relocations#

[ ]:
traj = ecoscope.base.Trajectory.from_relocations(relocs)
[ ]:
traj
[ ]:
traj.columns

Visualize:

[ ]:
traj["geometry"].explore()

Filter Segments#

[ ]:
traj_seg_filter = ecoscope.base.TrajSegFilter(
    min_length_meters=0.0,
    max_length_meters=float("inf"),
    min_time_secs=0.0,
    max_time_secs=4 * 60 * 60,
    min_speed_kmhr=0.0,
    max_speed_kmhr=8.0,
)
traj.apply_traj_filter(traj_seg_filter, inplace=True)
[ ]:
traj["junk_status"].value_counts()
[ ]:
traj.remove_filtered(inplace=True)

Visualize:

[ ]:
traj["geometry"].explore()

Optionally Drop Non-Essential Columns#

[ ]:
traj = traj[
    [
        "groupby_col",
        "segment_start",
        "segment_end",
        "timespan_seconds",
        "dist_meters",
        "speed_kmhr",
        "heading",
        "geometry",
    ]
]

Calculate Summary Statistics#

[ ]:
traj.groupby("groupby_col").apply(pd.DataFrame.describe)

Export to GeoPackage for ArcGIS or QGIS#

[ ]:
traj.to_file(os.path.join(output_dir, "traj.gpkg"))

Convert a Trajectory object to a Relocation object#

[ ]:
new_relocs = traj.to_relocations()

Upsampling#

[ ]:
upsampled_relocs = traj.upsample("180S")

Downsampling, with interpolation#

[ ]:
downsampled_relocs_int = traj.downsample("10800S", interpolation=True)

Downsampling, without interpolation#

[ ]:
downsampled_relocs_noint = traj.downsample("10800S", tolerance="900S")