# 2. Reading in an sftrack

Creating sftrack objects is relatively straight forward and can be read in a variety of ways including as a standard `data.frame`, `sf` object, or `ltraj` object (from `adehabitatLT`).

## Loading in raw data

To create `sftrack` objects data we use the `as_sftrack()` (track) or `as_sftraj()` (trajectory) function, depending on your desired output. Both have the same arguments but differ in the way the geometry field is calculated.

### Vector vs. data.frame inputs

`as_sftrack()`/`as_sftraj` accepts 2 kinds of raw data for each of the 4 required parts. Either a vector/list representing the data where length = nrow(data), or the column name where the data exists. For any `sftrack` component you can input either vector data or the column name for any variable, and can mix types between arguments.

Vector inputs to `as_sftrack` generally involve feeding as_sftrack the data itself where length(vector) == nrow(data). Or a list where each component adheres to this rule. If using entirely vector inputs for grouping, geometry, time, and error then `data` is not required.

data.frame inputs on the other hand are simply character vectors describing the column name in `data` where the information is found.

### Arguments

data: A data.frame containing your data. Data is optional if all inputs are done in vector mode where the appropriate vectors are given for geometry, grouping, and time. If inputting vector mode, sftrack creates column names `sft_group`, `sft_timestamp`, and `sft_error` in data frame. Names can be manually changed using `group_name`, `time_name`, and `error_name` and changing `overwrite_names` to FALSE will write over the data if the column already exists in `data`.

group (required): A list with named vectors to group the sftrack. One group must be named `id`, but otherwise can be infinite number of grouping variables. Or a vector naming the column names for each grouping categories. If only one variable is given then that vector is assumed to be the id column.

coords (required): The x,y,z coordinates to calculate geometries via `sf`. Accepts either a vector of `c(x,y,z)` describing which column the coordinates can be found, or a list(x=, y=, z=) with vectors for each coordinate. z is optional. NAs are allowed, alhough NAs must exist through the entire row otherwise an error is thrown. This is the same argument from `sf` functions and feeds into `st_as_sf()` internally.

time (required): Time information in either `POSIXct` or as an `integer`. Accepts either a vector of time, or the column name found in `data`. The outputted object will be sorted by the time column.

error: Error information for the associated xyz point. Accepts either a vector of the error, or the column name found in `data`. If not given, defaults to `NA`.

crs: The coordinate references system/projection of the data, as implemented by `sf`. It accepts an integer with the EPSG code, or a character with proj4string. If none is supplied `crs` is set as `NA` and can be set later using `sf::st_crs()` from `sf`.

active_group (required): A vector containing what groups are ‘active’. Meaning calculations and graphing will use these groupings. If no value is supplied it defaults to all grouping variables. Can change active_group later using `active_group() <- 'myvalue'`.

### Vector inputs

In the case of vector inputs, the vectors are cbinded to `data` if data is supplied. Sftrack returns an error if the column name already exists in the data.frame and `overwrite_names` remains FALSE. Default column names are: group = ‘sft_group’, time = ‘sft_timestamp’, error = ‘sft_error’. Names can be overwitten using `overwrite_names = TRUE`. At present Geometry defaults as ‘Geometry’ and at present can be changed later via `sf` but not via `sftrack` as we make a new sf object when we make an sftrack object.

    library("sftrack")

    #data
    data("raccoon", package = "sftrack")

    #xyz
    coords <- raccoon[,c("longitude", "latitude")]
    crs <- 4326
    #groupings
    group <- list(id = raccoon$animal_id,month = as.POSIXlt(raccoon$timestamp)$mon+1)
    active_group <- c("id","month")
    #time
    time <- as.POSIXct(raccoon$timestamp, tz = "EST")
    #error
    error <- raccoon$fix
    my_sftrack <- as_sftrack(data = raccoon, coords = coords, group = group,
                             active_group = active_group, time = time,
                             crs = crs, error = error)

    head(my_sftrack)

    ## Sftrack with 6 features and 12 fields (3 empty geometries)
    ## Geometry : "geometry" (XY, crs: WGS 84)
    ## Timestamp : "sft_timestamp" (POSIXct in EST)
    ## Groupings : "sft_group" (*id*, *month*)
    ## -------------------------------
    ##   animal_id latitude longitude           timestamp height hdop vdop fix
    ## 1   TTP-058       NA        NA 2019-01-19 00:02:30     NA  0.0  0.0  NO
    ## 2   TTP-058 26.06945 -80.27906 2019-01-19 01:02:30      7  6.2  3.2  2D
    ## 3   TTP-058       NA        NA 2019-01-19 02:02:30     NA  0.0  0.0  NO
    ## 4   TTP-058       NA        NA 2019-01-19 03:02:30     NA  0.0  0.0  NO
    ## 5   TTP-058 26.06769 -80.27431 2019-01-19 04:02:30    858  5.1  3.2  2D
    ## 6   TTP-058 26.06867 -80.27930 2019-01-19 05:02:30    350  1.9  3.2  3D
    ##         sft_timestamp sft_error               sft_group
    ## 1 2019-01-19 00:02:30        NO (id: TTP-058, month: 1)
    ## 2 2019-01-19 01:02:30        2D (id: TTP-058, month: 1)
    ## 3 2019-01-19 02:02:30        NO (id: TTP-058, month: 1)
    ## 4 2019-01-19 03:02:30        NO (id: TTP-058, month: 1)
    ## 5 2019-01-19 04:02:30        2D (id: TTP-058, month: 1)
    ## 6 2019-01-19 05:02:30        3D (id: TTP-058, month: 1)
    ##                     geometry
    ## 1                POINT EMPTY
    ## 2 POINT (-80.27906 26.06945)
    ## 3                POINT EMPTY
    ## 4                POINT EMPTY
    ## 5 POINT (-80.27431 26.06769)
    ## 6  POINT (-80.2793 26.06867)

As you can see in this case the data is not overwritten, but extra columns added with the correct data.

### data.frame inputs

Data.frame inputs generally describe the columns that represent the variables in `data`. If the columns are not found in `data`, an error is returned.

    raccoon$time <- as.POSIXct(raccoon$timestamp, tz = "EST")
    raccoon$month <- as.POSIXlt(raccoon$timestamp)$mon + 1

    coords <- c("longitude", "latitude")
    group <- c(id = "animal_id", month = "month")
    time <- "time"
    error <- "fix"

    my_sftraj <- as_sftraj(data = raccoon, coords = coords, group = group, time = time, error = error)

    head(my_sftraj)

    ## Sftraj with 6 features and 12 fields (3 empty geometries)
    ## Geometry : "geometry" (XY, crs: NA)
    ## Timestamp : "time" (POSIXct in EST)
    ## Grouping : "sft_group" (*id*, *month*)
    ## -------------------------------
    ##   animal_id latitude longitude           timestamp height hdop vdop fix
    ## 1   TTP-058       NA        NA 2019-01-19 00:02:30     NA  0.0  0.0  NO
    ## 2   TTP-058 26.06945 -80.27906 2019-01-19 01:02:30      7  6.2  3.2  2D
    ## 3   TTP-058       NA        NA 2019-01-19 02:02:30     NA  0.0  0.0  NO
    ## 4   TTP-058       NA        NA 2019-01-19 03:02:30     NA  0.0  0.0  NO
    ## 5   TTP-058 26.06769 -80.27431 2019-01-19 04:02:30    858  5.1  3.2  2D
    ## 6   TTP-058 26.06867 -80.27930 2019-01-19 05:02:30    350  1.9  3.2  3D
    ##                  time month               sft_group
    ## 1 2019-01-19 00:02:30     1 (id: TTP-058, month: 1)
    ## 2 2019-01-19 01:02:30     1 (id: TTP-058, month: 1)
    ## 3 2019-01-19 02:02:30     1 (id: TTP-058, month: 1)
    ## 4 2019-01-19 03:02:30     1 (id: TTP-058, month: 1)
    ## 5 2019-01-19 04:02:30     1 (id: TTP-058, month: 1)
    ## 6 2019-01-19 05:02:30     1 (id: TTP-058, month: 1)
    ##                         geometry
    ## 1                    POINT EMPTY
    ## 2     POINT (-80.27906 26.06945)
    ## 3                    POINT EMPTY
    ## 4                    POINT EMPTY
    ## 5 LINESTRING (-80.27431 26.06...
    ## 6 LINESTRING (-80.2793 26.068...

## Conversion mode

`as_sftrack()` and `as_sftraj()` also accept other data types, but the arguments for each differ depending on the class its converting from. It currently accepts, `sf`, `ltraj` (from adehabitatLT), and eventually `tibbles` (tidyverse) and `track` (trajectories).

### Import from ltraj

To read in an ltraj object all you need is an ltraj object created in `adehabitatLT`. All relevant information is taken from the object. The burst as defined in an ltraj is slightly different than sftracks groupings, so it assumes the ltraj ‘burst’ is the `id` field of the sftrack grouping object.

    library("adehabitatLT")

    ltraj_df <- as.ltraj(xy = raccoon[,c('longitude','latitude')], date = as.POSIXct(raccoon$timestamp),
     id = raccoon$animal_id, typeII = TRUE,
     infolocs = raccoon[,1:6])

    my_sf <- as_sftrack(ltraj_df)
    head(my_sf)

    ## Sftrack with 6 features and 12 fields (1 empty geometries)
    ## Geometry : "geometry" (XY, crs: NA)
    ## Timestamp : "sft_timestamp" (POSIXct in UTC)
    ## Groupings : "sft_group" (*id*)
    ## -------------------------------
    ##             x        y   burst       sft_timestamp animal_id latitude longitude
    ## 223        NA       NA TTP-041 2019-01-19 00:02:30   TTP-041       NA        NA
    ## 224 -80.27933 26.07096 TTP-041 2019-01-19 01:02:30   TTP-041 26.07096 -80.27933
    ## 225 -80.27932 26.07025 TTP-041 2019-01-19 02:02:22   TTP-041 26.07025 -80.27932
    ## 226 -80.27936 26.07071 TTP-041 2019-01-19 03:02:13   TTP-041 26.07071 -80.27936
    ## 227 -80.27924 26.07097 TTP-041 2019-01-19 04:02:30   TTP-041 26.07097 -80.27924
    ## 228 -80.27942 26.07072 TTP-041 2019-01-19 05:02:09   TTP-041 26.07072 -80.27942
    ##               timestamp height hdop     sft_group                   geometry
    ## 223 2019-01-19 00:02:30     NA  0.0 (id: TTP-041)                POINT EMPTY
    ## 224 2019-01-19 01:02:30      7  7.0 (id: TTP-041) POINT (-80.27933 26.07096)
    ## 225 2019-01-19 02:02:22      7  3.9 (id: TTP-041) POINT (-80.27932 26.07025)
    ## 226 2019-01-19 03:02:13      1  4.7 (id: TTP-041) POINT (-80.27936 26.07071)
    ## 227 2019-01-19 04:02:30     23  4.5 (id: TTP-041) POINT (-80.27924 26.07097)
    ## 228 2019-01-19 05:02:09     22  2.3 (id: TTP-041) POINT (-80.27942 26.07072)

### sf objects

`sf` objects are handled similarly as data.frame inputs except you do not need to input any information about the coordinates or projection. Grouping and time are still required. The sf spatial object must be in an `sfc_POINT` column.

    library("sf")

    ## Linking to GEOS 3.13.1, GDAL 3.10.3, PROJ 9.6.0; sf_use_s2() is TRUE

    df1 <- raccoon[!is.na(raccoon$latitude),]
    sf_df <- st_as_sf(df1, coords=c("longitude","latitude"), crs = crs)
    group <- c(id = "animal_id")
    time_col <- "time"

    new_sftraj <- as_sftraj(sf_df, group = group, time = time_col)
    head(new_sftraj)

    ## Sftrack with 6 features and 10 fields (0 empty geometries)
    ## Geometry : "geometry" (XY, crs: WGS 84)
    ## Timestamp : "time" (POSIXct in EST)
    ## Groupings : "sft_group" (*id*)
    ## -------------------------------
    ##    animal_id           timestamp height hdop vdop fix                time month
    ## 2    TTP-058 2019-01-19 01:02:30      7  6.2  3.2  2D 2019-01-19 01:02:30     1
    ## 5    TTP-058 2019-01-19 04:02:30    858  5.1  3.2  2D 2019-01-19 04:02:30     1
    ## 6    TTP-058 2019-01-19 05:02:30    350  1.9  3.2  3D 2019-01-19 05:02:30     1
    ## 7    TTP-058 2019-01-19 06:02:30     11  2.3  4.5  3D 2019-01-19 06:02:30     1
    ## 8    TTP-058 2019-01-19 07:02:04      9  2.7  3.9  3D 2019-01-19 07:02:04     1
    ## 10   TTP-058 2019-01-19 17:02:30     NA  2.0  3.3  3D 2019-01-19 17:02:30     1
    ##        sft_group                       geometry
    ## 2  (id: TTP-058) LINESTRING (-80.27906 26.06...
    ## 5  (id: TTP-058) LINESTRING (-80.27431 26.06...
    ## 6  (id: TTP-058) LINESTRING (-80.2793 26.068...
    ## 7  (id: TTP-058) LINESTRING (-80.27908 26.06...
    ## 8  (id: TTP-058) LINESTRING (-80.27902 26.06...
    ## 10 (id: TTP-058) LINESTRING (-80.279 26.0698...

    new_sftrack <- as_sftrack(sf_df, group = group, time = time_col)
    head(new_sftrack)

    ## Sftrack with 6 features and 10 fields (0 empty geometries)
    ## Geometry : "geometry" (XY, crs: WGS 84)
    ## Timestamp : "time" (POSIXct in EST)
    ## Groupings : "sft_group" (*id*)
    ## -------------------------------
    ##    animal_id           timestamp height hdop vdop fix                time month
    ## 2    TTP-058 2019-01-19 01:02:30      7  6.2  3.2  2D 2019-01-19 01:02:30     1
    ## 5    TTP-058 2019-01-19 04:02:30    858  5.1  3.2  2D 2019-01-19 04:02:30     1
    ## 6    TTP-058 2019-01-19 05:02:30    350  1.9  3.2  3D 2019-01-19 05:02:30     1
    ## 7    TTP-058 2019-01-19 06:02:30     11  2.3  4.5  3D 2019-01-19 06:02:30     1
    ## 8    TTP-058 2019-01-19 07:02:04      9  2.7  3.9  3D 2019-01-19 07:02:04     1
    ## 10   TTP-058 2019-01-19 17:02:30     NA  2.0  3.3  3D 2019-01-19 17:02:30     1
    ##        sft_group                   geometry
    ## 2  (id: TTP-058) POINT (-80.27906 26.06945)
    ## 5  (id: TTP-058) POINT (-80.27431 26.06769)
    ## 6  (id: TTP-058)  POINT (-80.2793 26.06867)
    ## 7  (id: TTP-058) POINT (-80.27908 26.06962)
    ## 8  (id: TTP-058) POINT (-80.27902 26.06963)
    ## 10 (id: TTP-058)   POINT (-80.279 26.06982)

### Intra-package conversion between classes

Additionally `as_sftrack` and `as_sftraj` can convert back and forth between classes with no loss in information.

    # Make tracks from raw data
    coords <- c("longitude","latitude")
    group <- c(id = "animal_id", month = "month")
    time <- "time"
    error <- "fix"

    my_sftraj <- as_sftraj(data = raccoon, coords = coords, group = group, time = time, error = error)
    my_sftrack <- as_sftrack(data = raccoon, coords = coords, group = group, time = time, error = error)

    # Convert between types
    new_sftrack <- as_sftrack(my_sftraj)
    #head(new_sftrack)
    new_sftraj <- as_sftraj(my_sftrack)
    #head(new_sftraj)

    identical(my_sftraj,new_sftraj)

    ## [1] TRUE

    identical(my_sftrack,new_sftrack)

    ## [1] TRUE

### Duplicated data

A common issue with movement data is when duplicated gps time stamps are logged for a sensor. When this happens it can be impossible for `sftrack` to know which point to use. For this reason, sftrack returns an error if any `grouping` + `time` combinations are duplicated.

    raccoon$time[1] <- raccoon$time[2]
    try(as_sftrack(data = raccoon, coords = coords, group = group, time = time, error = error))

    ## Error in dup_timestamp(time = data[[time_col]], x = group) :
    ##   groups: TTP-058_1 have duplicated time stamps

To help determine which rows are duplicated you can use the `which_duplicated` function to check your inputs. After which you can delete the superfluous rows and try again:

    which_duplicated(data = raccoon , group = group, time = time)

    ##       group                time which_row
    ## 1 TTP-041_1 2019-01-19 02:02:22       225

    raccoon <- raccoon[-2,]
    my_sftrack <- as_sftrack(data = raccoon, coords = coords, group = group, time = time, error = error)