diff --git a/.Rbuildignore b/.Rbuildignore
new file mode 100644
index 0000000000000000000000000000000000000000..ad139cf7ede21f1a37d2bbdfcdb045c8dfbeef48
--- /dev/null
+++ b/.Rbuildignore
@@ -0,0 +1,13 @@
+^.*\.Rproj$
+^\.Rproj\.user$
+^_pkgdown\.yml$
+^docs$
+^pkgdown$
+data/.prep/
+docs/
+info/
+notes/
+Rtodo/
+R_nested/
+.tests.r
+.render.r
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d1c0d941c83ee768c0fe755022cbb1178c2817db
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,43 @@
+# Folders
+data/.prep/
+notes/
+old/
+R_nested/
+Rtodo/
+
+# Temp files
+.tests.r
+.render.r
+
+# History files
+.Rhistory
+.Rapp.history
+
+# Session Data files
+.RData
+
+# User-specific files
+.Ruserdata
+
+# Output files from R CMD build
+/*.tar.gz
+
+# Output files from R CMD check
+/*.Rcheck/
+
+# RStudio files
+.Rproj.user/
+
+# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
+.httr-oauth
+
+# knitr and R markdown default cache directories
+*_cache/
+/cache/
+
+# Temporary files created by R markdown
+*.utf8.md
+*.knit.md
+
+# R Environment Variables
+.Renviron
diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000000000000000000000000000000000000..0b2e03b8ffdfec3f78104b0115ab719b771c7db4
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,44 @@
+Package: ctdp
+Type: Package
+Title: ctdp - a package to work with camtrap-dp data
+Version: 0.4.0
+Author: Henjo de Knegt <henjo.deknegt@wur.nl>
+Maintainer: Henjo de Knegt <henjo.deknegt@wur.nl>
+Description: ctdp - a package to work with camtrap-dp data
+License: GPL (>= 2)
+Encoding: UTF-8
+LazyData: true
+Suggests: 
+    knitr,
+    rmarkdown,
+    pckgdown
+VignetteBuilder: knitr,
+Depends:
+    R (>= 4.2.1),
+    stats,
+    utils,
+    data.table,
+    tidyverse,
+    lubridate,
+    tidyfast,
+    frictionless,
+    camtraptor,
+    shiny,
+    bslib,
+	DT,
+    flexdashboard,
+    magick,
+    leaflet,
+    leaflegend,
+    scales,
+    RColorBrewer,
+    webshot,
+    cowplot,
+    dygraphs,
+    xts,
+    plotly,
+	curl,
+	jsonlite,
+	maptools,
+	activity
+RoxygenNote: 7.2.3
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000000000000000000000000000000000000..e582e02694c176ce1ac8b81341e79c174f91550f
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,82 @@
+# Generated by roxygen2: do not edit by hand
+
+S3method(distinct,ctdp)
+S3method(is.na,ctdp)
+S3method(print,ctdp)
+S3method(print,seqnest)
+export(activity_ctdp)
+export(add_location_info)
+export(argcheck_order)
+export(as_ctdp)
+export(by_argcheck)
+export(by_void)
+export(calc_effort)
+export(captures)
+export(checkCodedColNames)
+export(check_annotations)
+export(check_deployments)
+export(check_duplicates)
+export(check_joins)
+export(class_argcheck)
+export(class_void)
+export(column_names)
+export(ctdp_interval)
+export(decimal_time)
+export(deployments)
+export(drop_columns)
+export(drop_media)
+export(drop_na_cols)
+export(dttmColNames)
+export(duplicateNames)
+export(effort_table)
+export(filter_apply)
+export(filter_station)
+export(filter_timerange)
+export(filter_void)
+export(forceUniqueNames)
+export(fraction_annotated)
+export(get_table)
+export(hardCodedColNames)
+export(has_media)
+export(integrate_effort)
+export(intervalColNames)
+export(intervalCols)
+export(is.ctdp)
+export(is_ctdp)
+export(keycolNames)
+export(linkStructure)
+export(list2tbl)
+export(locations)
+export(media)
+export(merge_tibbles)
+export(nest_tibbles)
+export(observations)
+export(order_void)
+export(pathProperties)
+export(plot_effort)
+export(plot_locations)
+export(plot_status)
+export(plot_time_coverage)
+export(point_effort)
+export(primary_keys)
+export(printTimeZones)
+export(read_ctdp)
+export(removeEmptyCols)
+export(sequences)
+export(setCodedColNames)
+export(set_GMT_offset)
+export(species_argcheck)
+export(species_void)
+export(summarise_deployments)
+export(tableInfo)
+export(taxon_classes)
+export(taxon_id)
+export(taxon_level)
+export(taxon_orders)
+export(taxon_species)
+export(taxonomy)
+export(time_range)
+export(time_zone)
+export(timestampCols)
+export(utc_offset)
+export(write_tutorial)
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000000000000000000000000000000000000..52cb7b07f55124f5c1bab4e876814eb0e7406545
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,6 @@
+# version 0.3.0
+
+* bla bla
+
+
+
diff --git a/R/activity_ctdp.r b/R/activity_ctdp.r
new file mode 100644
index 0000000000000000000000000000000000000000..86010e5ad49762ac8ae8dc9a3662d34b28b451af
--- /dev/null
+++ b/R/activity_ctdp.r
@@ -0,0 +1,186 @@
+#' @title title
+#' @description title
+#' @details NULL
+#' @param x an object of class \code{ctdp}
+#' @param doTimelapse whether or not to include time-lapse sequences in the analyses, defaults to \code{FALSE}
+#' @param doUnclassified whether or not to include unclassified sequences in the analyses, defaults to \code{TRUE}
+#' @param doHuman whether or not to include humans in the analyses, defaults to \code{TRUE}
+#' @inheritParams class_void
+#' @inheritParams species_void
+#' @param doPlot \code{logical} whether or not to plot the results, defaults to \code{TRUE}
+#' @param byTaxon \code{logical} whether or not to fit an activity pattern per taxon, defaults to \code{TRUE}
+#' @param n_min \code{integer,numeric} minimum number of records for activity pattern to be fitted, defaults to 25
+#' @inheritParams filter_timerange
+#' @inheritParams filter_station
+#' @inheritParams by_void
+#' @return invisibly returns a nested tibble with the fitted activity patterns per group
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' activity_ctdp(camsample)
+#' activity_ctdp(camsample, class = "Mammalia")
+#' activity_ctdp(camsample, species = c("CollaredPeccary","OCELOT"))
+#' activity_ctdp(camsample, species = c("CollaredPeccary","OCELOT"), by = "locationName")
+#' activity_ctdp(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+#'
+#' test <- activity_ctdp(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+#' test
+#' }
+#' @export
+activity_ctdp <- function(x,
+                          doTimelapse = FALSE,
+                          doUnclassified = TRUE,
+                          doHuman = TRUE,
+                          
+                          class = NULL,
+                          species = NULL,
+                          
+                          doPlot = TRUE,
+                          byTaxon = TRUE,
+                          n_min = 25,
+                          
+                          start = NULL, end = NULL, orders = "%Y/%m/%d", # filter_timerange
+                          subset = NULL, # filter_station
+                          by = NULL # by_void
+                          ) {
+  # Collect arguments
+  allArgs <- as.list(match.call(expand.dots = FALSE))
+  
+  # filter time range?
+  x <- filter_timerange(x, start = start, end = end, orders = orders)
+  
+  # filter on station?
+  if(!is.null(allArgs$subset)){
+    x <- do.call(filter_station,
+                 allArgs[c("x","subset")],
+                 quote = FALSE,
+                 envir = parent.frame(n = 1L))
+  }
+  
+  # Get data
+  y <- merge_tibbles(x)
+  
+  # filter out time lapse?
+  # y %>% count(captureMethod)
+  if(doTimelapse == FALSE) {
+    y <- y %>% 
+      filter(captureMethod != "time lapse")  
+  }
+  
+  # remove blanks
+  # y %>% count(observationType)
+  y <- y %>% filter(observationType != "blank")
+  
+  # Filter out unclassified?
+  if(doUnclassified == FALSE) {
+    y <- y %>% 
+      filter(observationType != "unclassified")
+  }
+  
+  # Filter out human?
+  if(doHuman == FALSE) {
+    y <- y %>% 
+      filter(observationType != "human")
+  }
+  
+  # Filter to class?
+  # y %>% count(class)
+  if(!is.null(class)){
+    doClass <- class_argcheck(x, class)
+    y <- y %>% 
+      filter(class == doClass | is.na(class))
+  }
+  # y %>% count(class)
+  
+  # Filter to species?
+  if(!is.null(species)){
+    doSpecies <- taxon_id(x, species)
+    y <- y %>% 
+      filter(observationType != "animal" | 
+               (observationType == "animal" & taxonID %in% doSpecies))
+    
+  }
+  # y %>% count(observationType, taxonID)
+  
+  # Add label (based on byTaxon)
+  if(byTaxon){
+    y <- y %>% 
+      mutate(label = case_when(is.na(vernacularNames.en) ~ as.character(observationType),
+                               TRUE ~ vernacularNames.en))
+  }else{
+    y <- y %>% 
+      mutate(label = as.character(observationType))
+  }
+  # y %>% count(label)
+  
+  # combine label and byLabel
+  if(!is.null(by)){
+    y <- y %>% 
+      rowwise() %>%
+      mutate(byLabel = paste(c_across(all_of(by)), collapse = "_")) %>% 
+      mutate(label = str_c(byLabel, label, sep = "_")) %>% 
+      ungroup() %>% 
+      select(-byLabel)
+  }
+  # y %>% count(label)
+
+  # label to factor and arrange
+  # y %>% count(observationType)
+  y <- y %>% 
+    arrange(desc(observationType), vernacularNames.en)
+  y <- y %>% mutate(label = factor(label, levels = unique(label)))
+  # y %>% count(label)
+  # y
+  
+  # calculate solar time 
+  ysolar <- solartime(dat = int_start(y$sequence_interval),
+                      lat = y$latitude,
+                      long = y$longitude,
+                      tz = as.numeric(utc_offset(int_start(y$sequence_interval))))
+  
+  # add to y and nest per observationType / vernacularNames.en
+  y$solartime <- ysolar$solar
+  
+  # Nest to each group
+  selectCols <- c("observationType", "vernacularNames.en", "solartime", "label", by)
+  groupCols <- selectCols[selectCols != "solartime"]
+  y <- y %>% 
+    select(all_of(selectCols)) %>% 
+    group_by(across(all_of(groupCols))) %>% 
+    nest() %>% 
+    ungroup()
+  
+  # Check sample size and filter to only those >= n_min
+  y <- y %>% 
+    mutate(n = map_int(data, nrow)) %>% 
+    filter(n >= n_min) %>% 
+    select(-n)
+  
+  # Fit act for each group/row
+  y <- y %>% 
+    mutate(fact = map(data, function(z){fitact(z$solartime)}))
+  # y
+  # plot(y$fact[[1]])
+
+  # plot
+  if(doPlot){
+    # Get maximum density
+    maxDens <- numeric(length(y$fact))
+    for(i in seq_along(y$fact)){
+      maxDens[[i]] <- max(y$fact[[i]]@pdf[,2], na.rm=TRUE)
+    }
+    maxDens <- max(maxDens) * (2*pi) / 24 # correction radians >> 24hr clock
+    for(i in seq_along(y$fact)){
+      if(i == 1){
+        plot(y$fact[[i]], xunit = "clock", yunit = "density", data = "none", ylim = c(0, 1.1*maxDens))
+      }else{
+        plot(y$fact[[i]], xunit = "clock", yunit = "density", data = "none", add = TRUE, tline=list(col=i))
+      }
+    }
+    legend(x = "topleft", horiz=FALSE, bty="n", lty = 1, col = seq_along(y$fact), 
+           legend = y$label, cex = 0.75, x.intersp = 0.2, text.width = 2)
+  }
+
+  # insivibly return
+  invisible(y)
+}
diff --git a/R/add_location_info.r b/R/add_location_info.r
new file mode 100644
index 0000000000000000000000000000000000000000..b06e67636b743c32d5590a4386cb5dffb0667f68
--- /dev/null
+++ b/R/add_location_info.r
@@ -0,0 +1,35 @@
+#' Add metadata to camtrap locations
+#' @param x an object of class \code{\link{ctdp}}
+#' @param info a \code{tibble} with information to add to the locations of \code{x}. Will be matched to \code{x$locations} by \code{left_join}
+#' @return x an object of class \code{\link{ctdp}}
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples NULL
+#' @export
+add_location_info <- function(x, info) {
+  # checks
+  if(missing(x)){stop("provide x")}
+  if(missing(info)){stop("provide info")}
+  if(! is.data.frame(info)){stop("provide info as a data.frame-like object")}
+  
+  # Get table with station properties
+  y <- x$locations
+  ynames <- names(y)
+  
+  # Any overlap in names?
+  if(! any(names(info) %in% ynames)) { stop("no shared column to join data") }
+  
+  # Join via left_join
+  z <- y %>%
+    left_join(info)
+  
+  # check identical locationID
+  if(nrow(y) != nrow(z)){stop("dimensions do not match")}
+  if(!all(y$locationID == z$locationID)){stop("identifiers do not match")}
+  
+  # all okay, add to x
+  x$locations <- z
+  
+  # return
+  return(x)
+}
\ No newline at end of file
diff --git a/R/as_ctdp.r b/R/as_ctdp.r
new file mode 100644
index 0000000000000000000000000000000000000000..ebc72cdced4f55eede09646299308b91605f2a8c
--- /dev/null
+++ b/R/as_ctdp.r
@@ -0,0 +1,279 @@
+#' @title Convert a camtraptor object to ctdp object
+#' @description Convert a camtraptor object to ctdp object
+#' @details Converts an object from the \code{camtraptor} package (see \url{https://inbo.github.io/camtraptor/} and \url{https://docs.ropensci.org/frictionless/}) to a \code{\link{ctdp}} object
+#' @param x a \code{camtraptor} object
+#' @param tz \code{character} string with time-zone code, defaults to \code{"Etc/GMT-2"}. See \code{\link{set_GMT_offset}}
+#' @param verbose \code{logical}, defaults to \code{TRUE}: printing information on progress
+#' @param rmEmpty \code{logical}, defaults to \code{TRUE}: remove columns that only contain NAs
+#' @param pathInfo file path information internally called from \code{\link{read_ctdp}}
+#' @param dropMedia \code{logical}, defaults to \code{FALSE}: whether or not to drop the media tibble.
+#' @return an object of class \code{\link{ctdp}}.
+#' @describeIn ctdp Convert a camtraptor object to ctdp object
+#' @author Henjo de Knegt
+#' @seealso \code{\link{read_ctdp}}, \code{\link{read_camtrap_dp}}
+#' @examples NULL
+#' @export
+as_ctdp <- function(x,
+                    tz = "Etc/GMT-2",
+                    verbose = TRUE,
+                    rmEmpty = FALSE,
+                    pathInfo = NULL,
+                    dropMedia = FALSE) {
+  ### Checks
+  if(missing(x)) { stop("supply input x")}
+
+  
+  ### Create copy of x: y
+  y <- x
+  
+  
+  ### Get pathInfo if NULL (supplied via read_ctdp)
+  if(is.null(pathInfo)){
+    pathInfo <- pathProperties(y$directory)
+  }
+  
+  
+  ### Check input y
+  {
+    ## Checks
+    # names(y)
+    strNames <- c("name","id","profile","created","sources","contributors","organizations","project",
+                  "spatial","temporal","taxonomic","platform","resources","directory","data")
+    
+    nameMatch <- all(strNames %in% names(y))
+    lengthMatch <- length(strNames) <= length(names(y))
+    if(nameMatch == FALSE) { stop("inspect specification of data structure when loading from datapackage: names of list do not match") }
+    if(lengthMatch == FALSE) { stop("inspect specification of data structure when loading from datapackage: length of returned list not as specified") }
+    
+    ## Exploring contents
+    if(FALSE) {
+      # Character properties
+      y$name
+      y$id
+      y$profile
+      y$created
+      y$sources
+      
+      y$contributors
+      y$organizations
+      
+      # Project settings
+      y$project
+      
+      # Spatial-temporal delineation
+      y$spatial
+      y$temporal
+      
+      # Taxonomid info
+      y$taxonomic
+      
+      y$platform
+      
+      # frictionless schema for data
+      y$resources
+      
+      # Location of the file(s)
+      y$directory
+      
+      # list with the 3 tibbles
+      y$data
+    }
+  }
+
+  
+  if(verbose){ cat(" - post-processing\n") }
+  
+  
+  ### Gather elements in list
+  x <- list(taxonomy = list2tbl(y$taxonomic),
+            deployments = y$data$deployments,
+            observations = y$data$observations,
+            media = y$data$media,
+            is_interval = FALSE)
+
+  
+  ### Check column names
+  x <- setCodedColNames(x)
+  # printTimeZones(x)
+  
+  
+  ### Remove 'timestamp' from observations if it is there:
+  # it is the timestamp of the media (start) thus will be in sequence start/end/interval
+  x$observations <- x$observations %>%
+    select(-any_of("timestamp"))
+  
+  
+  ### Get locations from deployments
+  x[["locations"]] <- x$deployments %>%
+    select(contains("location"),
+           "longitude", "latitude") %>%
+    distinct()
+
+  
+  ### Remove taxonomy columns from $observations (excl key columns)
+  taxNames <- names(x$taxonomy)
+  taxNames <- taxNames[! taxNames %in% as.character(unlist(keycolNames()))]
+  taxNames <- c(taxNames,"deploymentID","mediaID")
+  x$observations <- x$observations %>% select(-any_of(taxNames))
+  
+  
+  ### Remove location columns from data$deployments (excl key columns)
+  locNames <- names(x$locations)
+  locNames <- locNames[! locNames %in% as.character(unlist(keycolNames()))]
+  x$deployments <- x$deployments %>% 
+    select(-any_of(locNames))
+
+
+  ### Add taxonomic level (class and order) to each record in $taxonomy
+  x$taxonomy$class <- unlist(sapply(x$taxonomy$taxonID, function(y){
+    taxon_level(y, level = "class")
+  }, simplify = TRUE, USE.NAMES = FALSE))
+  x$taxonomy$order <- unlist(sapply(x$taxonomy$taxonID, function(y){
+    taxon_level(y, level = "order")
+  }, simplify = TRUE, USE.NAMES = FALSE))
+  
+  
+  ### Make taxonRank an ordered factor
+  x$taxonomy <- x$taxonomy %>% 
+    mutate(taxonRank = factor(taxonRank,
+                              levels = c("class","order","family","genus","species","subspecies"), 
+                              ordered = TRUE))
+
+  
+  ### Remove fully empty columns
+  if(rmEmpty) {
+    x <- removeEmptyCols(x)
+  }
+  
+  
+  ### Set timezone to specified by tz for all dttm columns
+  dttm_colNames <- dttmColNames(x)
+  for(i in names(dttm_colNames)) {
+    for(j in names(x[[i]])) {
+      if("POSIXt" %in% class(x[[i]][[j]])) {
+        x[[i]][[j]] <- with_tz(x[[i]][[j]], tzone = tz)
+      }
+    }  
+  }
+  # printTimeZones(x)
+  
+  
+  ### Convert to interval objects, and standardize to positive length
+  x$deployments <- x$deployments %>% 
+    mutate(deployment_interval = interval(deployment_start, deployment_end),
+           deployment_interval = int_standardize(deployment_interval)) %>% 
+    relocate(deployment_interval, .before = deployment_start) %>% 
+    select(-deployment_start, -deployment_end)
+  x$is_interval <- TRUE
+  # x$deployments$deployment_interval[[1]]
+  # printTimeZones(x)
+  
+
+  ### Set camerasetup==TRUE to observationType=human (now "unclassified")
+  # x$observations %>% count(observationType)
+  # x$observations %>% count(cameraSetup)
+  # x$observations %>% count(cameraSetup, observationType)
+  # x$observations$observationType[x$observations$cameraSetup == TRUE] <- "human"
+  # DETERMINE cameraSetupType:  setup, calibration or else NA
+  # x$observations %>% select(observationType, cameraSetup, count, classificationMethod) %>% filter(cameraSetup == FALSE) %>% distinct() %>% as.data.frame()
+  # observationType cameraSetup count classificationMethod
+  # 1     unclassified       FALSE     1                human >> actually calibration
+  # 2     unclassified       FALSE    NA                 <NA> >> REALLY unclassified
+  x$observations <- x$observations %>% 
+    mutate(cameraSetupType = 
+             case_when(
+               cameraSetup == TRUE ~ "setup",
+               observationType == "unclassified" & classificationMethod == "human" ~ "calibration",
+               TRUE ~ NA_character_
+               ),
+           cameraSetupType = factor(cameraSetupType, levels = c("calibration","setup")))
+  
+  
+  ### Get sequences
+  {
+    if(verbose){ cat(" - retrieving sequences from media\n") }
+    
+    # data.table is considerably faster
+    
+    # Get data and convert to data.table with sequenceID as key
+    sequences <- x$media %>% 
+      distinct() %>% 
+      select(deploymentID, sequenceID, media_timestamp, captureMethod) %>% 
+      data.table(key = "sequenceID")
+    
+    # summarize per key
+    sequences <- sequences[, list(deploymentID = unique(deploymentID),
+                                  captureMethod = unique(captureMethod),
+                                  start = min(media_timestamp),
+                                  end = max(media_timestamp),
+                                  nrphotos = length(media_timestamp)),
+                           by = sequenceID]
+    
+    # convert to tibble, arrange, and convert start/end to interval object
+    sequences <- sequences %>%
+      as_tibble() %>%
+      arrange(deploymentID, sequenceID) %>% 
+      mutate(sequence_interval = interval(start, end)) %>% 
+      relocate(sequence_interval, .before =  start) %>% 
+      select(-start, -end)
+  }
+  # sequences
+  
+  
+  ### Add to x
+  x$sequences <- sequences
+  
+  
+  ### Remove sequence columns from $media (excl key columns)
+  seqNames <- names(x$sequences)
+  seqNames <- seqNames[! seqNames %in% as.character(unlist(keycolNames()))]
+  seqNames <- seqNames[seqNames != "deploymentID"]
+  x$media <- x$media %>% 
+    select(-any_of(seqNames))
+  
+
+  if(verbose){ cat(" - gathering data and returning\n") }
+  
+  
+  ### Add settings and reorder elements
+  x$settings = list(path = file.path(pathInfo$path,
+                                     pathInfo$fileFolder),
+                    tz = tz,
+                    interval = TRUE,
+                    media = !dropMedia)
+  # names(x)
+  x <- x[c("locations", "deployments", "sequences", "observations", "media", "taxonomy", "settings")]
+  # names(x)
+  
+  
+  ### UNIQUENESS OF NAMES (not key columns)
+  # duplicateNames(x)
+  x <- forceUniqueNames(x)
+  # duplicateNames(x) %>% unlist() %>% as.character()
+  
+  
+  ### Drop media?
+  if(dropMedia) {
+    x$media <- x$media %>% 
+      slice_head(n = 1L)
+    x$media <- x$media[-1,]
+  }
+  
+  
+  ### assign class ctdp
+  class(x) <- c("ctdp","list")
+  
+  
+  if(verbose){ cat(" - DONE\n") }
+  
+  
+  
+  #### FOR later development:
+  # camtraptor info as element (hidden) in list or object (attribute)
+  # with list y (data elements stripped to few rows?)
+  # renaming info (hardcoded, duplicate names)
+  # interval conversions
+  
+  ### Return
+  return(x)
+}
\ No newline at end of file
diff --git a/R/by_argcheck.r b/R/by_argcheck.r
new file mode 100644
index 0000000000000000000000000000000000000000..14338972b75b99b5d2f53844e832190a94643b49
--- /dev/null
+++ b/R/by_argcheck.r
@@ -0,0 +1,22 @@
+#' @title Argument check on by
+#' @inheritParams ctdp
+#' @inheritParams by_void
+#' @return same as \code{x} or an error
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @keywords internal
+#' @examples \dontrun{
+#' by_argcheck(camsample, by = c("locationName","array"))
+#' }
+#' @export
+by_argcheck <- function(x, by) {
+  # input checks
+  if(missing(x)) { stop("supply input x")}
+  if(missing(by)) { stop("supply input by")}
+
+  # Argument check
+  by <- match.arg(by, choices = unlist(column_names(x, verbose = FALSE), recursive = TRUE, use.names = FALSE), several.ok = TRUE)
+  
+  # return
+  return(by)
+}
\ No newline at end of file
diff --git a/R/by_void.r b/R/by_void.r
new file mode 100644
index 0000000000000000000000000000000000000000..de4678fcbaee178936514eab633689b1551ea776
--- /dev/null
+++ b/R/by_void.r
@@ -0,0 +1,11 @@
+#' Specify grouping columns
+#' @param by an optional \code{character} vector specifying the column names by which to group analyses, defaults to \code{NULL}
+#' @return NULL
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples NULL
+#' @keywords internal
+#' @export
+by_void <- function(by = NULL) {
+  NULL
+}
\ No newline at end of file
diff --git a/R/calc_effort.r b/R/calc_effort.r
new file mode 100644
index 0000000000000000000000000000000000000000..d28b1abe1b1a8d8dc6d630ca6aa5326b84e3959d
--- /dev/null
+++ b/R/calc_effort.r
@@ -0,0 +1,60 @@
+#' Calculate effort
+#' @param x an object of class \code{\link{ctdp}}
+#' @inheritParams filter_timerange
+#' @param subset optional expression for subsetting passed on to \code{\link{filter_station}}
+#' @param groupBy an optional \code{character} vector with the column name(s) of the grouping variables (from the locations and deployments tables)
+#' @inheritParams filter_timerange
+#' @inheritParams filter_station
+#' @inheritParams by_void
+#' @return a \code{tibble} with columns specified in \code{by} and column "effort", which holds the total camera-days effort.
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}, \code{\link{filter_station}}
+#' @examples \dontrun{
+#' calc_effort(camsample)
+#' calc_effort(camsample, by = "locationName")
+#' calc_effort(camsample, start = "2017/4/1", end = "2017/8/1")
+#' calc_effort(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+#' }
+#' @export
+calc_effort <- function(x, 
+                        start = NULL, end = NULL, orders = "%Y/%m/%d", # filter_timerange
+                        subset = NULL, # filter_station
+                        by = NULL # by_void
+                        ) {
+  # checks
+  if(missing(x)){stop("provide input x")}
+  
+  # filter time range?
+  x <- filter_timerange(x, start = start, end = end, orders = orders)
+  
+  # filter on station?
+  argList <- as.list(match.call(expand.dots = FALSE))[c("x","subset")]
+  if(!is.null(argList$subset)){
+    x <- do.call(filter_station,
+                 argList,
+                 quote = FALSE,
+                 envir = parent.frame(n = 1L))
+  }
+  
+  # Force interval
+  x <- ctdp_interval(x)
+  
+  # get deployment info
+  xdep <- x$deployments %>% 
+    left_join(x$locations, by = "locationID") %>% 
+    mutate(dep_length = int_length(deployment_interval) / 60 / 60 / 24) %>%  # DAYS
+    select(-deployment_interval)
+  
+  # Grouped tibble?
+  if(!is.null(by)){
+    xdep <- xdep %>% 
+      group_by(across(all_of(by)))
+  }
+    
+  # Sum effort over the groups
+  xdepEffort <- xdep %>% 
+    summarise(effort = sum(dep_length))
+  
+  # Return
+  return(xdepEffort)
+}
\ No newline at end of file
diff --git a/R/captures.r b/R/captures.r
new file mode 100644
index 0000000000000000000000000000000000000000..97c1a33ed4d787c43d55a11668be09b6d49dc3a6
--- /dev/null
+++ b/R/captures.r
@@ -0,0 +1,108 @@
+#' Get captures
+#' @param x an object of class \code{\link{ctdp}}
+#' @param onlyAnimal \code{logical}, defaults to \code{TRUE}, indicating whether or not to only focus on \code{observationType == "animal"}
+#' @param class \code{character} string with the class of species to compute the captures for (e.g. "Mammalia"), defaults to NULL
+#' @inheritParams species_void
+#' @inheritParams filter_timerange
+#' @inheritParams filter_station
+#' @inheritParams by_void
+#' @return a \code{tibble} with counts per species (with columns added: "observationType","class","order", as well as the columns specified in \code{by}).
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}},\code{\link{taxon_id}},  \code{\link{filter_timerange}}
+#' @examples \dontrun{
+#' captures(camsample)
+#' captures(camsample, class = "Mammalia")
+#' captures(camsample, species = c("CollaredPeccary","OCELOT"))
+#' captures(camsample, by = "locationName")
+#' captures(camsample, species = c("CollaredPeccary","OCELOT"), by = "locationName")
+#' captures(camsample, start = "2017/4/1", end = "2017/8/1")
+#' captures(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+#' }
+#' @export
+captures <- function(x,
+                     
+                     onlyAnimal = TRUE,
+                     class = NULL, # class_void
+                     species = NULL, # species_void
+                     
+                     start = NULL, end = NULL, orders = "%Y/%m/%d", # filter_timerange
+                     subset = NULL, # filter_station
+                     by = NULL # by_void
+                     ) {
+  # Collect arguments
+  allArgs <- as.list(match.call(expand.dots = FALSE))
+
+  # filter time range?
+  x <- filter_timerange(x, start = start, end = end, orders = orders)
+ 
+  # filter on station?
+  if(!is.null(allArgs$subset)){
+    x <- do.call(filter_station,
+                 allArgs[c("x","subset")],
+                 quote = FALSE,
+                 envir = parent.frame(n = 1L))
+  }
+
+  # compute effort table (only "by" - rest has been dealt with)
+  if(!is.null(allArgs$by)){
+    effortTable <- calc_effort(x, by = allArgs$by)
+  }else{
+    effortTable <- calc_effort(x)
+  }
+
+  # Merge tibbles
+  y <- merge_tibbles(x, dropMedia = TRUE)
+  
+  # only animals?
+  if(onlyAnimal) {
+    y <- y %>% 
+      filter(observationType == "animal")
+  }
+
+  # for specific class?
+  if(!is.null(class)){
+    doClass <- class_argcheck(x, class); rm(class)  
+    y <- y %>% 
+      filter(class == doClass)
+  }
+  
+  # Focus on specific species?
+  if(!is.null(species)){
+    doSpecies <- species_argcheck(x, species); rm(species)
+    doTaxonIDs <- taxon_id(x, doSpecies)
+    y <- y %>% 
+      filter(taxonID %in% doTaxonIDs)
+  }
+  
+  # select columns
+  y <- y %>% 
+    select(taxonID, captureMethod, observationType, count, classificationMethod,
+           scientificName, vernacularNames.en, vernacularNames.nl, class, order,
+           all_of(by))
+  
+  # Specify group by
+  groupBy <- by
+  by <- unique(c("observationType","class","order", by))
+  y <- y %>% 
+    group_by(across(all_of(by)))
+  
+  # Count
+  z <- y %>% 
+    count(taxonID,vernacularNames.en,vernacularNames.nl) %>% 
+    ungroup() %>% 
+    rename(captures = n)
+  
+  # Add effort
+  if(!is.null(groupBy)){
+    z <- z %>% 
+      left_join(effortTable, by = groupBy)
+  }else{
+    z <- z %>% 
+      mutate(effort = effortTable$effort[1])
+  }
+  z <- z %>% 
+    mutate(capture_rate = captures / effort)
+
+  # return
+  return(z)
+}
\ No newline at end of file
diff --git a/R/check_annotations.r b/R/check_annotations.r
new file mode 100644
index 0000000000000000000000000000000000000000..28e19cae7d642a56f949968f81bcafaab9115a89
--- /dev/null
+++ b/R/check_annotations.r
@@ -0,0 +1,35 @@
+#' @title Check integrity of observation types
+#' @description Check integrity of observation types
+#' @details NULL
+#' @param x a \code{ctdp} object
+#' @return a named \code{list} with elements:
+#' \itemize{
+#'   \item \code{noNAs}: whether or not \code{observationType} column contains NAs
+#'   \item \code{isFactor}: whether or not \code{observationType} columns is a factor
+#'   \item \code{correctLevels}: whether or not the levels are one of: "animal","human","vehicle","blank","unknown","unclassified"
+#' }
+#' logical values indicating whether each table in \code{x} contains distinct (value TRUE) values or duplicates (value FALSE).
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' check_annotations(camsample)
+#' }
+#' @export
+check_annotations <- function(x) {
+  # input checks
+  if(missing(x)) { stop("supply input x")}
+
+  # Check annotation type
+  y <- x$observations %>% count(observationType)
+
+  # checks
+  checkAnnotations <- list(
+    noNAs = all(!is.na(y$observationType)),
+    isFactor = is.factor(y$observationType),
+    correctLevels = ifelse(is.factor(y$observationType),
+                           all(levels(y$observationType) %in% c("animal","human","vehicle","blank","unknown","unclassified")),
+                           all(y$observationType %in% c("animal","human","vehicle","blank","unknown","unclassified"))))
+
+  # Return
+  return(checkAnnotations)
+}
\ No newline at end of file
diff --git a/R/check_deployments.r b/R/check_deployments.r
new file mode 100644
index 0000000000000000000000000000000000000000..4efa743c540242754fd16f133c151e3f286f9d25
--- /dev/null
+++ b/R/check_deployments.r
@@ -0,0 +1,127 @@
+#' @title Check integrity of deployments
+#' @description Check integrity of deployments
+#' @details NULL
+#' @param x a \code{ctdp} object
+#' @param duration_threshold the threshold duration, in days, above which deployments are flagged
+#' @param sea_threshold the sun elevation angle, in degrees, above the horizon below which deployment start/end are flagged
+#' @return a \code{tibble} with information columns:
+#' \itemize{
+#'   \item \code{deploymentID}: identifier
+#'   \item \code{nr_false}: the number of validity check columns (see below) that contain value FALSE
+#'   \item \code{deployment_interval}: interval
+#'   \item \code{longitude,latitude}: lon/lat coordinates
+#'   \item \code{locationName}: name
+#'   \item \code{seq_start,seq_end}: timestamp of the first/last sequence in the deployment
+#'   \item \code{duration}: duration (in days) of the deployment
+#'   \item \code{sea_start,sea_end}: solar elevation angle (in degrees above horizon) at the time of start/end of deployment
+#'   \item \code{dt_start,dt_end}: time difference (in days) between start/end of deployment and seq_start,seq_end
+#' }
+#' as well as validity check columns (prefix "ok_"):
+#' \itemize{
+#'   \item \code{start,end}: no NAs
+#'   \item \code{startend}: end > start (thus length > 0 sec)
+#'   \item \code{lon,lat}: no NAs
+#'   \item \code{name}: no NAs
+#'   \item \code{sea_start,sea_end}: start/end of deployment when sun is >= sea_threshold
+#'   \item \code{duration}: difference between duration (days) <= duration_threshold
+#' }
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' \dontrun{
+#' test <- check_deployments(camsample)
+#' test %>% filter(nr_false > 0)
+#' }
+#' @export
+check_deployments <- function(x,
+                              duration_threshold = 20*7, # days
+                              sea_threshold = -6 # sun elevation angle in degrees above horizon
+                              ) {
+  # input checks
+  if(missing(x)) { stop("supply input x")}
+
+  # Force to interval
+  x <- ctdp_interval(x)
+  
+  # Get data
+  y <- x$deployments %>% 
+    left_join(x$locations, by = "locationID") %>% 
+    select(deploymentID,
+           locationID,
+           deployment_interval,
+           longitude,
+           latitude,
+           locationName)
+  
+  # Get deployment_start/end
+  x_int <- y$deployment_interval
+  x_start <- int_start(x_int)
+  x_end <- int_end(x_int)
+
+  # Get sun elevation angle at deployment start and end
+  spos_start <- solarpos(crds = cbind(y$longitude,
+                                      y$latitude),
+                         dateTime = with_tz(x_start, "UTC"))
+  spos_end <- solarpos(crds = cbind(y$longitude,
+                                    y$latitude),
+                       dateTime = with_tz(x_end, "UTC"))
+  spos <- tibble(start = x_start, 
+                 e_start = spos_start[,2],
+                 end = x_end, 
+                 e_end = spos_end[,2])
+  # spos
+  if(FALSE) {
+    plot(spos$start, spos$e_start, col = "2", pch = 16, ylim = range(c(spos$e_start, spos$e_end)) + c(0, 5),
+         xlab = "deployment start", ylab = "solar elevation angle")
+    points(spos$start, spos$e_end, col = 3, pch = 16)
+    abline(h = 0)
+    legend(x = "top", horiz = TRUE, bty = "n", pch=16, col = c(2,3), legend = c("start","end"))
+  }
+  
+  # start/end according to sequences
+  y_seqs_startend <- x$sequences %>% 
+    group_by(deploymentID) %>% 
+    summarise(seq_start = min(int_start(sequence_interval)),
+              seq_end = max(int_end(sequence_interval)),
+              .groups = "drop")
+  
+  # gather in summary tibble
+  y_checks <- y %>% 
+    left_join(y_seqs_startend, by = "deploymentID") %>% 
+    mutate(
+      duration = int_length(deployment_interval), # seconds
+      duration = duration / 60 / 60 / 24, # days
+      sea_start = spos$e_start,
+      sea_end = spos$e_end,
+      dt_start = as.numeric(difftime(int_start(deployment_interval),
+                                     seq_start,
+                                     units = "days")),
+      dt_end = as.numeric(difftime(int_end(deployment_interval),
+                                   seq_end,
+                                   units = "days"))
+    ) %>% 
+    mutate(
+      ok_start = !is.na(x_start),
+      ok_end = !is.na(x_end),
+      ok_startend = x_start < x_end,
+      ok_lon = !is.na(longitude),
+      ok_lat = !is.na(latitude),
+      ok_name = !is.na(locationName),
+      ok_sea_start = sea_start >= sea_threshold,
+      ok_sea_end = sea_end >= sea_threshold,
+      ok_duration = duration <= duration_threshold,
+    )
+  # glimpse(y_checks)
+
+  # Summarize OK columns: how many are FALSE?
+  y_false <- y_checks %>% 
+    select(starts_with("ok_"))
+  y_false <- ncol(y_false) - rowSums(y_false)
+  y_checks <- y_checks %>% 
+    mutate(nr_false = y_false) %>% 
+    relocate(starts_with("ok_"), .after = deploymentID) %>% 
+    relocate(nr_false, .after = deploymentID)
+  # y_checks
+  
+  # Return
+  return(y_checks)
+}
diff --git a/R/check_duplicates.r b/R/check_duplicates.r
new file mode 100644
index 0000000000000000000000000000000000000000..4de13eb43b58584a996407460b6fed765bfbe6ab
--- /dev/null
+++ b/R/check_duplicates.r
@@ -0,0 +1,36 @@
+#' @title Check presence of duplicates
+#' @description Check presence of duplicates
+#' @details NULL
+#' @param x a \code{ctdp} object
+#' @return a named \code{list} logical values indicating whether each table in \code{x} contains distinct (value TRUE) values or duplicates (value FALSE).
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' check_duplicates(camsample)
+#' }
+#' @export
+check_duplicates <- function(x) {
+  # input checks
+  if(missing(x)) { stop("supply input x")}
+
+  # store in list
+  duplicateChecks <- list()
+ 
+  # function to check duplication (of all rows/columns of a tibble)
+  fnDuplicates <- function(y) {
+    # check first based on first column, which is primary key column
+    if(length(y[[1]]) == length(unique(y[[1]]))){
+      return(TRUE)
+    }else{
+      # if first column is not unique, then proceed with unique entire tibble
+      return(nrow(y) == nrow(unique(y)))
+    }
+  }
+  
+  for(i in c("locations","deployments","sequences","observations","media","taxonomy")) {
+    duplicateChecks[[i]] <- fnDuplicates(x[[i]])
+  }
+  
+  # Return
+  return(duplicateChecks)
+}
\ No newline at end of file
diff --git a/R/check_integrity.r b/R/check_integrity.r
new file mode 100644
index 0000000000000000000000000000000000000000..415c27681b3bbeee82a3805eee29d811c69bbffd
--- /dev/null
+++ b/R/check_integrity.r
@@ -0,0 +1,13 @@
+#' @title Check the data integrity of a ctdp object
+#' @description Check the data integrity of a ctdp object
+#' @details NULL
+#' @param x a \code{ctdp} object
+#' @return NULL
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' check_integrity(camsample)
+#' }
+check_integrity <- function(x) {
+  cat("Still to write")
+}
\ No newline at end of file
diff --git a/R/check_joins.r b/R/check_joins.r
new file mode 100644
index 0000000000000000000000000000000000000000..dc5822fddc872e6b11e81d57fe35a78455d9847e
--- /dev/null
+++ b/R/check_joins.r
@@ -0,0 +1,83 @@
+#' @title Check data integrity of joins
+#' @description Check data integrity of joins
+#' @details NULL
+#' @param x a \code{ctdp} object
+#' @return a \code{list} with 4 elements:
+#' \itemize{
+#'   \item \code{allunique}: a \code{logical} indicating whether or not all primary key columns contain unique values
+#'   \item \code{alljoins}: a \code{logical} indicating whether or not the tibbles have matching keys
+#'   \item \code{unique}: a \code{tibble} with extra info on whether which element in \code{x} contains unique primary keys
+#'   \item \code{anti_joins}: a named \code{list} with information on anti-joins (a character vector of length 0 if there are not anti-joins)
+#' }
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' check_joins(camsample)
+#' }
+#' @export
+check_joins <- function(x) {
+  # input checks
+  if(missing(x)) { stop("supply input x")}
+
+  # Get key columns and link structure
+  keys <- keycolNames()
+  links <- linkStructure()
+  
+  # structure to hold data
+  joinChecks <- list()
+  
+  ### Uniqueness of ID cols
+  allUnique <- function(y){
+    length(y) == length(unique(y))
+  }
+  joinChecks[["unique"]] <- tibble(
+    locations = allUnique(x$locations$locationID),
+    deployments = allUnique(x$deployments$deploymentID),
+    sequences = allUnique(x$sequences$sequenceID),
+    observations = allUnique(x$observations$observationID),
+    media = allUnique(x$media$mediaID),
+    taxonomy = allUnique(x$taxonomy$taxonID)
+  )
+
+  ### Anti-joins
+  joinChecks[["anti_joins"]] <- list()
+  
+  fnNotin <- function(y, z, na.rm = TRUE) {
+    if(na.rm){
+      y <- y[!is.na(y)]
+    }
+    y <- unique(y)
+    z <- unique(z)
+    ynotinz <- y[! y %in% z]
+    return(ynotinz)
+  }
+  # locs in deps
+  joinChecks[["anti_joins"]][["loc_notin_dep"]] <- fnNotin(x$locations$locationID, x$deployments$locationID)
+  joinChecks[["anti_joins"]][["dep_notin_loc"]] <- fnNotin(x$deployments$locationID, x$locations$locationID)
+
+  # deps in seqs
+  joinChecks[["anti_joins"]][["dep_notin_seq"]] <- fnNotin(x$deployments$deploymentID, x$sequences$deploymentID)
+  joinChecks[["anti_joins"]][["seq_notin_dep"]] <- fnNotin(x$sequences$deploymentID, x$deployments$deploymentID)
+  
+  # seqs in obs
+  joinChecks[["anti_joins"]][["seq_notin_obs"]] <- fnNotin(x$sequences$sequenceID, x$observations$sequenceID)
+  joinChecks[["anti_joins"]][["obs_notin_seq"]] <- fnNotin(x$observations$sequenceID, x$sequences$sequenceID)
+  
+  # seqs in media
+  if(x$settings$media) {
+    joinChecks[["anti_joins"]][["seq_notin_med"]] <- fnNotin(x$sequences$sequenceID, x$media$sequenceID)
+    joinChecks[["anti_joins"]][["med_notin_seq"]] <- fnNotin(x$media$sequenceID, x$sequences$sequenceID)
+    
+  }
+  
+  # taxonomy in observation 
+  joinChecks[["anti_joins"]][["obs_notin_tax"]] <- fnNotin(x$observations$taxonID, x$taxonomy$taxonID)
+  
+  # Summarize and rearrange
+  joinChecks[["alljoins"]] <- all(as.integer(unlist(lapply(joinChecks[["anti_joins"]], length))) == 0L)
+  joinChecks[["allunique"]] <- all(as.logical(joinChecks[["unique"]][1,,drop=TRUE]))
+  joinChecks <- joinChecks[c("allunique","alljoins","unique","anti_joins")]
+  
+  # Return
+  return(joinChecks)
+}
\ No newline at end of file
diff --git a/R/class_argcheck.r b/R/class_argcheck.r
new file mode 100644
index 0000000000000000000000000000000000000000..a802026ae97576ab8e03fe156b6ff743f8907da4
--- /dev/null
+++ b/R/class_argcheck.r
@@ -0,0 +1,22 @@
+#' @title Argument check on class
+#' @inheritParams ctdp
+#' @inheritParams class_void
+#' @return same as \code{x} or an error
+#' @author Henjo de Knegt
+#' @seealso \code{\link{taxon_classes}}
+#' @keywords internal
+#' @examples \dontrun{
+#' class_argcheck(camsample, class = "Mammalia")
+#' }
+#' @export
+class_argcheck <- function(x, class) {
+  # input checks
+  if(missing(x)) { stop("supply input x")}
+  if(missing(class)) { stop("supply input class")}
+
+  # Argument check
+  class <- match.arg(class, choices = taxon_classes(x), several.ok = TRUE)
+  
+  # return
+  return(class)
+}
\ No newline at end of file
diff --git a/R/class_void.r b/R/class_void.r
new file mode 100644
index 0000000000000000000000000000000000000000..1fa66cefdae5196d11b1a5ee784f7cc994f10162
--- /dev/null
+++ b/R/class_void.r
@@ -0,0 +1,11 @@
+#' Specify class
+#' @param class optional \code{character} string with the class of species (e.g. "Mammalia") to focus analyses on, defaults to \code{NULL}
+#' @return NULL
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples NULL
+#' @keywords internal
+#' @export
+class_void <- function(class = NULL) {
+  NULL
+}
\ No newline at end of file
diff --git a/R/column_names.r b/R/column_names.r
new file mode 100644
index 0000000000000000000000000000000000000000..81ad4d64f91b82629b2b5c12d5f4827482a87656
--- /dev/null
+++ b/R/column_names.r
@@ -0,0 +1,30 @@
+#' Retrieve the column names of the ctdp tibbles
+#' @inheritParams print.ctdp
+#' @param verbose \code{logical}, defaults to \code{TRUE}, indicating whether or not to print information to the console
+#' @return a named \code{list} with the names of the tibbles (returned invisibly when \code{verbose=TRUE})
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' column_names(camsample)
+#'   
+#' xnames <- column_names(camsample, verbose = FALSE)
+#' xnames
+#' }
+#' @export
+column_names <- function(x, verbose=TRUE) {
+  y <- list()
+  ynames <- names(x)
+  for(i in seq_len(length(x)-1))
+  {
+    if(verbose){cat(paste0("\n\nTable $", ynames[i], ":\n"))}
+    y[[ynames[i]]] <- names(x[[i]])
+    if(verbose){cat(paste0(" - ",paste(y[[ynames[i]]], collapse="\n - ")))}
+  }
+  if(verbose)
+  {
+    invisible(y)
+  }else
+  {
+    return(y)
+  }
+}
\ No newline at end of file
diff --git a/R/ctdp_interval.r b/R/ctdp_interval.r
new file mode 100644
index 0000000000000000000000000000000000000000..16d2f8c04c471efb6d963c41851cd24756f3f9c5
--- /dev/null
+++ b/R/ctdp_interval.r
@@ -0,0 +1,50 @@
+#' @title Conversion between start/end and Interval objects
+#' @description Convert between start/end and Interval objects for deployments and sequences
+#' @param x an object of class \code{\link{ctdp}}
+#' @param rev \code{logical} indicating to reverse the conversion: thus when \code{TRUE} the interval object is converted back to start/end
+#' @return an object of class \code{\link{ctdp}}
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#' ctdp_interval(camsample)
+#' ctdp_interval(camsample, rev = TRUE)
+#' }
+#' @export
+ctdp_interval <- function(x, rev = FALSE) {
+  if(x$settings$interval) {
+    if(rev){
+      # deployment_interval >> deployment_start/deployment_end
+      x$deployments <- x$deployments %>% 
+        mutate(deployment_start = int_start(deployment_interval),
+               deployment_end   = int_end(deployment_interval)) %>%
+        relocate(deployment_start:deployment_end, .before = deployment_interval) %>% 
+        select(-deployment_interval)
+      # sequence_interval >> sequence_start/deployment_end
+      x$sequences <- x$sequences %>% 
+        mutate(sequence_start = int_start(sequence_interval),
+               sequence_end   = int_end(sequence_interval)) %>%
+        relocate(sequence_start:sequence_end, .before = sequence_interval) %>% 
+        select(-sequence_interval)
+      # Update settings
+      x$settings$interval <- FALSE
+    }
+  }else{
+    if(rev == FALSE){
+      # deployment_start/deployment_end >> deployment_interval
+      x$deployments <- x$deployments %>% 
+        mutate(deployment_interval = interval(deployment_start, deployment_end)) %>%
+        relocate(deployment_interval, .before = deployment_start) %>% 
+        select(-deployment_start, deployment_end)
+      # sequence_start/deployment_end >> sequence_interval
+      x$sequences <- x$sequences %>% 
+        mutate(sequence_interval = interval(sequence_start, sequence_end)) %>%
+        relocate(sequence_interval, .before = sequence_start) %>% 
+        select(-sequence_start, sequence_end)
+      # Update settings
+      x$settings$interval <- TRUE
+    }
+  }
+
+  ### return
+  return(x)
+}
\ No newline at end of file
diff --git a/R/ctdp_package.r b/R/ctdp_package.r
new file mode 100644
index 0000000000000000000000000000000000000000..4c509129c8f7509b639ea2e3e29841872de05ca1
--- /dev/null
+++ b/R/ctdp_package.r
@@ -0,0 +1,9 @@
+#' @title
+#' ctdp - a package to work with camtrap-dp data
+#'
+#' @description
+#' ctdp - a package to work with camtrap-dp data
+#'
+#' @docType package
+#' @name ctdp
+NULL
diff --git a/R/data.r b/R/data.r
new file mode 100644
index 0000000000000000000000000000000000000000..0b9d79c4b5cc9151210bc0ba63f93cf7f5b78b57
--- /dev/null
+++ b/R/data.r
@@ -0,0 +1,19 @@
+#' @title The main columns of a ctdp object
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @docType data
+#' @keywords datasets
+#' @name ctdp_mainCols
+#' @usage NULL
+#' @format \code{tibble} with the main columns of a \code{ctdp} object, per table.
+NULL
+
+#' @title Example ctdp dataset
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @docType data
+#' @keywords datasets
+#' @name bcnm2017
+#' @usage NULL
+#' @format an object of class \code{ctdp} with the observations from 6 camera stations at the Barro Colorado Nature Monument (BCNM) during 2017.
+NULL
diff --git a/R/decimal_time.r b/R/decimal_time.r
new file mode 100644
index 0000000000000000000000000000000000000000..d0a63fe9c70679f56a858bfa934fef391fefca34
--- /dev/null
+++ b/R/decimal_time.r
@@ -0,0 +1,19 @@
+#' @title Convert a datetime object to decimal time
+#' @description Convert a datetime object to decimal time
+#' @param t an object of class \code{POSIXct}
+#' @param units a \code{character} string with the units, defaults to \code{"hours"}, but can also be \code{"days"}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @return a \code{numeric} value with the decimal time
+#' @examples \dontrun{
+#'   decimal_time(Sys.time())
+#'   decimal_time(Sys.time(), units="days")
+#' }
+#' @export
+decimal_time <- function(t, units = "hours") {
+  units <- match.arg(units, choices = c("hours","days"))
+  t0 <- trunc.POSIXt(t, units = "days")
+  y <- difftime(t, t0, units = units)
+  y <- as.numeric(y)
+  return(y)
+}
\ No newline at end of file
diff --git a/R/deployments.r b/R/deployments.r
new file mode 100644
index 0000000000000000000000000000000000000000..faa6464371478c7cb77e3984c9934f80fd6ca08f
--- /dev/null
+++ b/R/deployments.r
@@ -0,0 +1,13 @@
+#' Retrieve the deployments table of a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @return a \code{tibble} with the deployments table
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' deployments(camsample)
+#' }
+#' @export
+deployments <- function(x) {
+  y <- x$deployments
+  return(y)
+}
\ No newline at end of file
diff --git a/R/distinct.r b/R/distinct.r
new file mode 100644
index 0000000000000000000000000000000000000000..73c7ccff3710653e42e47f59107de07eb0d3a11a
--- /dev/null
+++ b/R/distinct.r
@@ -0,0 +1,19 @@
+#' @title Remove duplicate records from the tables of a ctdp object
+#' @description Remove duplicate records from the tables of a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @return an object of class \code{\link{ctdp}}
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#' distinct(camsample)
+#' }
+#' @describeIn ctdp Remove duplicate records from the tables of a ctdp object
+#' @export
+distinct.ctdp <- function(x) {
+  for(i in names(keycolNames())) {
+    x[[i]] <- x[[i]] %>% distinct()
+  }
+  
+  # return
+  return(x)
+}
\ No newline at end of file
diff --git a/R/drop_columns.r b/R/drop_columns.r
new file mode 100644
index 0000000000000000000000000000000000000000..332047986b92453fab2707ba779b906994546bc4
--- /dev/null
+++ b/R/drop_columns.r
@@ -0,0 +1,34 @@
+#' drop columns from a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @return x an object of class \code{\link{ctdp}} with columns removed (except for important columns)
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}, \code{\link{ctdp_mainCols}}
+#' @examples \dontrun{
+#' drop_columns(camsample)
+#' }
+#' @export
+drop_columns <- function(x){
+  # Get tibble with list-column per table to keep
+  keepCols <- ctdp_mainCols %>% 
+    group_by(table) %>% 
+    nest() %>% 
+    ungroup()
+  
+  # copy x to y
+  y <- x
+
+  # remove columns
+  for(i in keepCols$table){
+    pullCols <- keepCols %>% 
+      filter(table == i) %>% 
+      pull(data) %>% 
+      .[[1]] %>% 
+      pull(column)
+    
+    y[[i]] <- y[[i]] %>% 
+      select(any_of(pullCols))
+  }
+  
+  # return y
+  return(y)
+}
\ No newline at end of file
diff --git a/R/drop_media.r b/R/drop_media.r
new file mode 100644
index 0000000000000000000000000000000000000000..be4303fe8233aa635f52fd151fb3ce1061b65e63
--- /dev/null
+++ b/R/drop_media.r
@@ -0,0 +1,22 @@
+#' @title drop the media slot of a ctdp object
+#' @description drop the media slot of a ctdp object
+#' @param x an object of class \code{ctdp}
+#' @return NULL
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' drop_media(camsample)
+#' }
+#' @export
+drop_media <- function(x) {
+  # drop media element
+  x$media <- x$media %>% 
+    slice_head(n = 1L)
+  x$media <- x$media[-1,]
+  
+  # update settings
+  x$settings$media <- FALSE
+  
+  # return
+  return(x)
+}
\ No newline at end of file
diff --git a/R/drop_na_cols.r b/R/drop_na_cols.r
new file mode 100644
index 0000000000000000000000000000000000000000..8a135261d02efccebfa22c4b3222f84fa8b73132
--- /dev/null
+++ b/R/drop_na_cols.r
@@ -0,0 +1,16 @@
+#' @title drop columns that only contain NAs
+#' @description drop columns that only contain NAs
+#' @param x an object of class \code{ctdp}
+#' @return an object of class \code{ctdp}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' drop_na_cols(camsample)
+#' }
+#' @export
+drop_na_cols <- function(x) {
+  x <- removeEmptyCols(x, keepMainCols = TRUE)
+  
+  # return
+  return(x)
+}
\ No newline at end of file
diff --git a/R/effort_table.r b/R/effort_table.r
new file mode 100644
index 0000000000000000000000000000000000000000..b58655d7852db9b878751518918813544eb22f51
--- /dev/null
+++ b/R/effort_table.r
@@ -0,0 +1,51 @@
+#' Get a step function with the number of active cameras over time
+#' @param x an object of class \code{\link{ctdp}}
+#' @param startend \code{logical}, defaults to FALSE, whether or not to include the endpoints of the stepfunction, or just the switches to new values.
+#' @return a \code{tibble} with effort data
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#' effort_table(camsample)
+#' effort_table(camsample, startend = TRUE)
+#' }
+#' @keywords internal
+#' @export
+effort_table <- function(x, startend = FALSE) {
+  x <- ctdp_interval(x)
+  x_start <- int_start(x$deployments$deployment_interval)
+  x_end <- int_end(x$deployments$deployment_interval)
+  if(length(start) != length(end)) { stop("start and end have different lengths") }
+  n <- length(start)
+  dt <- as.numeric(max(x_end)) - as.numeric(min(x_start))
+  effort <- bind_rows(
+    tibble(time = min(x_start) - 0.025 * dt,
+           add = 0L),
+    tibble(time = x_start,
+           add = +1L),
+    tibble(time = x_end,
+           add = -1L),
+    tibble(time = max(x_end) + 0.025 * dt,
+           add = 0L))
+  effort <- effort %>% 
+    group_by(time) %>% 
+    summarize(add = sum(add)) %>% 
+    ungroup()
+  effort <- effort %>% 
+    arrange(time) %>% 
+    mutate(nrCams = cumsum(add))
+  # plot(effort$time, effort$nrCams, type="s")
+  effort <- effort %>% 
+    select(-add)
+  
+  # only start of new value, or start and end of value?
+  if(startend){
+    n <- nrow(effort)
+    effort <- tibble(time = c(effort$time[1],
+                              rep(effort$time[-1], each = 2),
+                              effort$time[n]),
+                     nrCams = rep(effort$nrCams, each=2))
+  }
+  
+  # Return
+  return(effort)
+}
\ No newline at end of file
diff --git a/R/filter_apply.r b/R/filter_apply.r
new file mode 100644
index 0000000000000000000000000000000000000000..b95b58417cb8c752a4f06e912de3ae64b3bea60b
--- /dev/null
+++ b/R/filter_apply.r
@@ -0,0 +1,22 @@
+#' Internal function to apply filter expression
+#' @param x a data.frame or tibble
+#' @inheritParams filter_void
+#' @return NULL
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' filter_apply(taxonomy(camsample), class == "Mammalia")
+#' filter_apply(locations(camsample), str_sub(locationName, 5, 5) == "3")
+#' }
+#' @keywords internal
+#' @export
+filter_apply <- function(x, filter = NULL) {
+  dofilter <- match.call(expand.dots = FALSE)$filter
+  if(!is.null(dofilter)){
+    r <- eval(dofilter, x, parent.frame(1L))
+    if(!is.logical(r)) { stop("'filter' must be logical") }
+    r <- r & !is.na(r)
+    x <- x[r, , drop=FALSE]
+  }
+  return(x)
+}
\ No newline at end of file
diff --git a/R/filter_station.r b/R/filter_station.r
new file mode 100644
index 0000000000000000000000000000000000000000..ae6731e160bd9177c0758c24a222cb118d338c8a
--- /dev/null
+++ b/R/filter_station.r
@@ -0,0 +1,35 @@
+#' Filter a ctdp object based on camera station properties
+#' @param x an object of class \code{\link{ctdp}}
+#' @param subset arguments used for filtering
+#' @return x an object of class \code{\link{ctdp}}
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#' locations(camsample)
+#' str_sub(locations(camsample)$locationName, 5, 5)
+#' filter_station(camsample, str_sub(locationName, 5, 5) == "3")
+#' }
+#' @export
+filter_station <- function(x, subset = NULL) {
+  # Get locations table
+  xloc <- x$locations
+  
+  # Perform the filtering on it
+  dofilter <- match.call(expand.dots = FALSE)$subset
+  r <- eval(dofilter, xloc, parent.frame(1L))
+  xsub <- xloc[r,]
+  
+  # Filter the rest of object x accordingly
+  x$locations <- xsub
+  x$deployments <- x$deployments %>%
+    filter(locationID %in% xsub$locationID)
+  x$sequences <- x$sequences %>%
+    filter(deploymentID %in% x$deployments$deploymentID)
+  x$observations <- x$observations %>%
+    filter(sequenceID %in% x$sequences$sequenceID)
+  x$media <- x$media %>%
+    filter(sequenceID %in% x$sequences$sequenceID)
+  
+  # Return x
+  return(x)
+}
\ No newline at end of file
diff --git a/R/filter_timerange.r b/R/filter_timerange.r
new file mode 100644
index 0000000000000000000000000000000000000000..f86bdb2f310b4416945ebb612e30e5796367a6c1
--- /dev/null
+++ b/R/filter_timerange.r
@@ -0,0 +1,82 @@
+#' Filter a ctdp object based on a specified time range
+#' @details This function first filters sequences, where sequences are kept whose START falls inside the specified interval, and then filters the other elements to the sequences that are kept. Then, the sequence and deployment intervals are truncated to [start, end]
+#' @param x an object of class \code{\link{ctdp}}
+#' @param start \code{character} datetime: sequences are kept when the start of the sequences is > start, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}
+#' @param end \code{character} datetime: sequences are kept when the start of the sequences is < end, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}
+#' @param orders \code{character} string with the orders passed on to \code{\link{parse_date_time}}, defaults to "Ymd"
+#' @return x an object of class \code{\link{ctdp}}
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#'   filter_timerange(x, start = "2021/6/21", end = "2022/6/21")
+#' }
+#' @export
+filter_timerange <- function(x, start = NULL, end = NULL, orders = "%Y/%m/%d") {
+  ### Checks
+  if(is.null(start) & is.null(end)){
+    return(x)
+  }else {
+    # Get start/end as datetime object in same timezone
+    {
+      if(! is.null(start)) {
+        if(! is.character(start)) { stop("start should be of class character")}
+        start <- parse_date_time(start, orders = orders, tz=x$settings$tz)
+      }else {
+        start <- min(int_start(x$sequences$sequence_interval))
+      }
+      if(! is.null(end)) {
+        if(! is.character(end)) { stop("end should be of class character")}
+        end <- parse_date_time(end, orders = orders, tz=x$settings$tz)
+      }else {
+        end <- max(int_end(x$sequences$sequence_interval))
+      }
+    }
+    # start
+    # end
+    
+    
+    ### Force to interval interval
+    wasInterval <- x$settings$interval
+    x <- ctdp_interval(x)
+    
+
+    ### Subset to > start, < end
+    {
+      # FIRST: filter sequences: START of a sequence will be <INSIDE> the specified interval
+      x$sequences <- x$sequences %>%
+        filter(int_start(sequence_interval) > start,
+               int_start(sequence_interval) < end)
+      # x$sequences %>% pull(sequence_interval) %>% int_start() %>% min()
+      # x$sequences %>% pull(sequence_interval) %>% int_start() %>% max()
+      
+      # THEN: filter the deployments, locations, observations and media accordingly
+      x$deployments <- x$deployments %>%
+        filter(deploymentID %in% x$sequences$deploymentID)
+      x$locations <- x$locations %>%
+        filter(locationID %in% x$deployments$locationID)
+      x$observations <- x$observations %>%
+        filter(sequenceID %in% x$sequences$sequenceID)
+      x$media <- x$media %>%
+        filter(sequenceID %in% x$sequences$sequenceID)
+    }
+    # x
+    
+    
+    ### Truncate intervals to [start,end]
+    {
+      truncInterval <- lubridate::interval(start, end)
+      x$deployments$deployment_interval <- lubridate::intersect(x$deployments$deployment_interval, truncInterval)
+      x$sequences$sequence_interval <- lubridate::intersect(x$sequences$sequence_interval, truncInterval)
+    }
+    
+    
+    ### Reset interval if it was not
+    if(wasInterval == FALSE) {
+      x <- ctdp_interval(x, rev = TRUE)
+    }
+    
+
+    ### return
+    return(x)
+  }
+}
\ No newline at end of file
diff --git a/R/filter_void.r b/R/filter_void.r
new file mode 100644
index 0000000000000000000000000000000000000000..498ad6643297a56cd563062ce5c74aaf6a44d4dc
--- /dev/null
+++ b/R/filter_void.r
@@ -0,0 +1,11 @@
+#' Specify filter expression
+#' @param filter optional expression for subsetting, defaults to \code{NULL}
+#' @return NULL
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples NULL
+#' @keywords internal
+#' @export
+filter_void <- function(filter = NULL) {
+  NULL
+}
\ No newline at end of file
diff --git a/R/fraction_annotated.r b/R/fraction_annotated.r
new file mode 100644
index 0000000000000000000000000000000000000000..d087c3861f9d711d0f56a81d9232fbe3504e1722
--- /dev/null
+++ b/R/fraction_annotated.r
@@ -0,0 +1,119 @@
+#' Compute the fraction of sequences that is annotated
+#' @param x an object of class \code{\link{ctdp}}
+#' @param onlyMotion \code{logical}, whether or not to only use motion detection images, defaults to \code{TRUE}
+#' @param omitSetupType \code{logical}, whether or not to omit sequences that are marked as some form of setup (`observations` column `cameraSetupType` is than not NA), defaults to \code{TRUE}
+#' @inheritParams filter_timerange
+#' @inheritParams filter_station
+#' @inheritParams by_void
+#' @return a tibble with summary data per deployment (or group specified in \code{by}), with columns:
+#' \itemize{
+#'   \item deploymentID, or columns specified in \code{by}
+#'   \item fracAnnotated: the fraction of all sequences in this group that have been annotated (observationType != "unclassified")
+#'   \item nrSeqs: number of sequences in this group (optionally after filter for only motion detection)
+#'   \item nrSeqsAnnotated: number of annotated sequences in this group (optionally after filter for only motion detection)
+#'   \item nrPhotos: number of photos in this group (optionally after filter for only motion detection)
+#' }
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#' fraction_annotated(camsample)
+#' fraction_annotated(camsample, by = "locationName")
+#' fraction_annotated(camsample, by = "transect")
+#' fraction_annotated(camsample, start = "2017/4/1", end = "2017/8/1")
+#' fraction_annotated(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+#' }
+#' @export
+fraction_annotated <- function(x,
+                               onlyMotion = TRUE,
+                               omitSetupType = TRUE,
+
+                               start = NULL, end = NULL, orders = "%Y/%m/%d", # filter_timerange
+                               subset = NULL, # filter_station
+                               by = NULL # by_void
+                               ) {
+  # Collect arguments
+  allArgs <- as.list(match.call(expand.dots = FALSE))
+  
+  # filter time range?
+  x <- filter_timerange(x, start = start, end = end, orders = orders)
+  
+  # filter on station?
+  if(!is.null(allArgs$subset)){
+    x <- do.call(filter_station,
+                 allArgs[c("x","subset")],
+                 quote = FALSE,
+                 envir = parent.frame(n = 1L))
+  }
+  
+  # Get subset of data in big tibble
+  y <- merge_tibbles(x, dropMedia = TRUE)
+  keepCols <- c(by,
+                "deploymentID", "sequenceID", "captureMethod", "observationType", "cameraSetupType", "nrphotos") %>% 
+    unique()
+  y <- y %>% 
+    select(all_of(keepCols))
+  
+  # remove cameraSetupType != NA?
+  if(omitSetupType){
+    y <- y %>% 
+      filter(is.na(cameraSetupType))
+  }
+  y <- y %>% 
+    select(-cameraSetupType)
+  
+  # groupings; per sequence and per deployment
+  groups1 <- c("deploymentID", "sequenceID")
+  groups2 <- c("deploymentID")
+  if(!is.null(by)){
+    groups1 <- unique(c(by, groups1))
+    groups2 <- unique(c(by, groups2))
+  }
+  
+  # check whether or not annotated
+  y <- y %>% 
+    mutate(isAnnotated = observationType != "unclassified")
+  
+  # Only motion detections?
+  if(onlyMotion){
+    y <- y %>% 
+      filter(captureMethod == "motion detection")
+  }
+  
+  # Summarize per sequence
+  y <- y %>%
+    group_by(across(all_of(groups1))) %>% 
+    summarize(nrPhotos = mean(nrphotos),
+              isAnnotated = any(isAnnotated),
+              .groups = "drop")
+  
+  # Summarize per deployment
+  y <- y %>% 
+    group_by(across(all_of(groups2))) %>% 
+    summarize(nrSeqs = n(),
+              nrSeqsAnnotated = sum(isAnnotated),
+              nrPhotos = sum(nrPhotos),
+              .groups = "drop")
+  # y
+  
+  # relocate
+  y <- y %>% 
+    relocate(deploymentID, .before = nrSeqs)
+  
+  # Summarise on 'by'?
+  if(!is.null(by)){
+    y <- y %>% 
+      group_by(across(all_of(by))) %>% 
+      summarize(nrSeqs = sum(nrSeqs),
+                nrSeqsAnnotated = sum(nrSeqsAnnotated),
+                nrPhotos = sum(nrPhotos),
+                .groups = "drop")
+  }
+  
+  # Compute fraction
+  y <- y %>% 
+    mutate(fracAnnotated = nrSeqsAnnotated / nrSeqs) %>% 
+    relocate(fracAnnotated, .before = nrSeqs)
+  
+  # return
+  return(y)
+}
\ No newline at end of file
diff --git a/R/get_table.r b/R/get_table.r
new file mode 100644
index 0000000000000000000000000000000000000000..5a1f5a9225db7c26ec30815f680a726610040921
--- /dev/null
+++ b/R/get_table.r
@@ -0,0 +1,25 @@
+#' Retrieve a table from a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @param table \code{character} name of the table to retrieve: one of "locations", "deployments", "sequences", "observations", "media", "taxonomy" 
+#' @return a \code{tibble} with the selected information table
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' get_table(camsample, table = "deployments")
+#' }
+#' @export
+get_table <- function(x, table) {
+  # checks
+  if(missing(x)){stop("provide input x")}
+  if(missing(table)){stop("provide input table")}
+  table <- match.arg(table, choices = c("locations", "deployments", "sequences", "observations", "media", "taxonomy"))
+  
+  if(table == "media" & x$settings$media == FALSE) {
+    warning("media has been dropped from this object, thus returning the tibble with 0 rows")
+  }
+  # Retrieve table
+  y <- x[[table]]
+  
+  # Return
+  return(y)
+}
\ No newline at end of file
diff --git a/R/has_media.r b/R/has_media.r
new file mode 100644
index 0000000000000000000000000000000000000000..12739585a3ac5fbef69f82330dd2911acdbff60f
--- /dev/null
+++ b/R/has_media.r
@@ -0,0 +1,12 @@
+#' Check whether a ctdp object has media information
+#' @inheritParams print.ctdp
+#' @return a \code{character} string with the time zone information of the date-time objects in \code{x}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' has_media(camsample)
+#' }
+#' @export
+has_media <- function(x) {
+  x$settings$media
+}
\ No newline at end of file
diff --git a/R/hidden_helper_functions.r b/R/hidden_helper_functions.r
new file mode 100644
index 0000000000000000000000000000000000000000..4a0353a6b6ad8f23b652979662572a7b7d36e015
--- /dev/null
+++ b/R/hidden_helper_functions.r
@@ -0,0 +1,395 @@
+#' Hidden helper functions
+#' @param x \code{character} path, or an object of class \code{\link{ctdp}}
+#' @return for \code{pathProperties} a \code{list} with folder/file information
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples NULL
+#' @keywords internal
+#' @name helpers
+
+#' @rdname helpers
+#' @export
+is.ctdp <- function(x) {
+  y <- "ctdp" %in% class(x)
+  return(y)
+}
+
+#' @rdname helpers
+#' @export
+pathProperties <- function(x) {
+  # x is either ctdp object or character string of length 1
+  if(is.ctdp(x)) {
+    x <- x$settings$path
+  }else{
+    if(is.character(x)) {
+      if(length(x) != 1) {
+        stop("x should be either ctdp object, or character string of length 1")
+      }
+    }else{
+      stop("x should be either ctdp object, or character string of length 1")
+    }
+  }
+  
+  # Get dirname and basename
+  y <- list(path = dirname(x),
+            fileFolder = basename(x))
+  
+  # Get extension
+  ex <- strsplit(basename(x), split="\\.")[[1]]
+  ex <- ex[length(ex)]
+  
+  # if same as basename, then no extension
+  if(ex == basename(x)) { ex <- NA_character_ }
+  
+  # add info to list
+  y[["extension"]] <- ex
+  y[["isZip"]] <- (tolower(ex) == "zip")
+  if(is.na(y[["isZip"]])){ y[["isZip"]] <- FALSE}  
+  
+  # return list y
+  return(y)
+}
+
+
+#' @rdname helpers
+#' @export
+list2tbl <- function(x) {
+  y <- lapply(x, function(y) { unlist(y) %>%
+      as.list() %>%
+      as_tibble()}) %>%
+    bind_rows()
+  return(y)
+}
+
+
+#' @rdname helpers
+#' @export
+dttmColNames <- function(x) {
+  dttmCols <- list()
+  doElements <- c("deployments","sequences","media","observations")
+  doElements <- doElements[doElements %in% names(x)]
+  for(i in doElements) {
+    icols <- character()
+    for(j in names(x[[i]])) {
+      if(is.POSIXct(x[[i]][[j]])){
+        icols <- c(icols, j)
+      }
+    }
+    dttmCols[[i]] <- icols
+  }
+  return(dttmCols)
+}
+
+
+#' @rdname helpers
+#' @export
+intervalColNames <- function(x) {
+  intervalCols <- list()
+  doElements <- c("deployments","sequences","media","observations")
+  doElements <- doElements[doElements %in% names(x)]
+  for(i in doElements) {
+    icols <- character()
+    for(j in names(x[[i]])) {
+      if(is.interval(x[[i]][[j]])){
+        icols <- c(icols, j)
+      }
+    }
+    intervalCols[[i]] <- icols
+  }
+  return(intervalCols)
+}
+
+
+#' @rdname helpers
+#' @export
+printTimeZones <- function(x) {
+  cat("<dttm> columns:\n")
+  dttm_colNames <- dttmColNames(x)
+  for(i in names(dttm_colNames)) {
+    for(j in dttm_colNames[[i]]) {
+      cat(paste0(i, "$", j , ": ", attr(x[[i]][[j]][[1]], 'tzon'), "\n"))
+    }  
+  }
+  
+  cat("\n\n<Interval> columns:\n")
+  interval_colNames <- intervalColNames(x)
+  for(i in names(interval_colNames)) {
+    for(j in interval_colNames[[i]]) {
+      cat(paste0(i, "$", j , ": ", attr(x[[i]][[j]][[1]], 'tzon'), "\n"))
+    }  
+  }
+}
+
+
+#' @rdname helpers
+#' @export
+hardCodedColNames <- function() {
+  # keycolNames()
+  # linkStructure()
+  
+  # named list with named elements used for parsing names
+  y <- list(
+    # Deployments/locations
+    deployments = c(locationID = "locationID", ### name of element is NEW name, value is camtrap-dp name
+                    deploymentID = "deploymentID",
+                    locationName = "locationName",
+                    longitude = "longitude",
+                    latitude = "latitude",
+                    deployment_start = "start",
+                    deployment_end = "end",
+                    cameraID = "cameraID",
+                    timestampIssues = "timestampIssues"),
+    
+    # Sequences/media
+    media = c(deploymentID = "deploymentID",
+              sequenceID = "sequenceID",
+              mediaID = "mediaID",
+              media_timestamp = "timestamp",
+              captureMethod = "captureMethod",
+              filePath = "filePath",
+              fileName = "fileName",
+              fileMediatype = "fileMediatype",
+              favourite = "favourite"),
+    
+    # Observations
+    observations = c(observationID = "observationID",
+                     sequenceID = "sequenceID",
+                     taxonID = "taxonID",
+                     observation_timestamp = "classificationTimestamp",
+                     observationType = "observationType",
+                     classificationMethod = "classificationMethod",
+                     count = "count",
+                     cameraSetup = "cameraSetup"),
+    
+    # Taxonomy
+    taxonomy = c(taxonID = "taxonID",
+                 scientificName = "scientificName",
+                 taxonRank = "taxonRank",
+                 vernacularNames.en = "vernacularNames.en",
+                 vernacularNames.nl = "vernacularNames.nl"))
+  
+  # Return
+  return(y)
+}
+
+
+#' @rdname helpers
+#' @export
+checkCodedColNames <- function(x) {
+  # which names should it have?
+  y <- hardCodedColNames()
+  noMatch = list()
+  
+  # for each element in y, check the names
+  for(iname in names(y)) {
+    matchNames <- as.character(y[[iname]]) %in% names(x[[iname]])
+    if(all(matchNames) == FALSE) {
+      noMatch[[iname]] = y[[iname]][which(matchNames == FALSE)]
+    }
+  }
+  
+  # return list
+  return(list(pass = length(noMatch) == 0,
+              noMatch = noMatch))
+}
+
+
+#' @rdname helpers
+#' @export
+setCodedColNames <- function(x) {
+  
+  # Checks
+  xchecks <- checkCodedColNames(x)
+  if(xchecks$pass == FALSE) {
+    stop("coded names do not pass check")
+  }
+  
+  # named vector with recoding values
+  y <- hardCodedColNames()
+  
+  # Make copy 
+  z <- x
+  
+  # Recode using !!! notation
+  for(i in names(y)) {
+    inames <- names(z[[i]])
+    setNms <- names(y[[i]])
+    names(setNms) <- as.character(y[[i]])
+    inames <- recode(inames, !!!setNms)
+    names(z[[i]]) <- inames
+  }
+  
+  # return
+  return(z)
+}
+
+
+#' @rdname helpers
+#' @export
+keycolNames <- function() {
+  y <- list(locations = "locationID",
+            deployments = "deploymentID",
+            sequences = "sequenceID",
+            observations = "observationID",
+            taxonomy = "taxonID",
+            media = "mediaID")
+  return(y)
+}
+
+
+#' @rdname helpers
+#' @export
+linkStructure <- function() {
+  y <- list(locations = c("locations"),
+            deployments = c("locations","deployments"),
+            sequences = c("deployments","sequences"),
+            observations = c("sequences","observations","taxonomy"),
+            media = c("sequences","media"),
+            taxonomy = c("taxonomy"))
+  return(y)
+}
+
+
+#' @rdname helpers
+#' @export
+timestampCols <- function() {
+  y <- list(deployments = c("deployment_start","deployment_end"),
+            observations = "observation_timestamp",
+            media = "media_timestamp")
+  return(y)
+}
+
+
+#' @rdname helpers
+#' @export
+intervalCols <- function() {
+  y <- list(deployments = "deployment_interval",
+            sequences = "sequence_interval")
+  return(y)
+}
+
+
+#' @rdname helpers
+#' @export
+removeEmptyCols <- function(x, keepMainCols = TRUE) {
+  y <- x
+  if(keepMainCols) {
+    keepCols <- c(
+      unlist(lapply(hardCodedColNames(), names), use.names = FALSE),
+      unlist(keycolNames(), use.names = FALSE),
+      unlist(timestampCols(), use.names = FALSE),
+      unlist(intervalCols(), use.names = FALSE)
+      ) %>% 
+      unique() %>% 
+      sort()
+  }else{
+    keepCols <- character(0)
+  }
+  
+  for(i in seq_along(y)) {
+    if(is_tibble(y[[i]])) {
+      nrNAs <- colSums(is.na(y[[i]]))
+      nrRows <- nrow(y[[i]])
+      if(any(nrNAs == nrRows)) {
+        rmWhich <- which(nrNAs == nrRows)
+        rmWhich <- names(rmWhich)
+        rmWhich <- rmWhich[! rmWhich %in% keepCols]
+        if(length(rmWhich) > 0) {
+          y[[i]] <- y[[i]] %>% 
+            select(-all_of(rmWhich))
+        }
+      }
+    }
+  }
+  
+  return(y)
+}
+
+
+
+
+#' @rdname helpers
+#' @export
+intervalColNames <- function(x) {
+  intCols <- list()
+  for(i in c("deployments","sequences")) {
+    icols <- character()
+    for(j in names(x[[i]])) {
+      if(is.interval(x[[i]][[j]])) {
+        icols <- c(icols, j)
+      }
+    }
+    intCols[[i]] <- icols
+  }
+  return(intCols)
+}
+
+
+#' @rdname helpers
+#' @export
+tableInfo <- function(x, table, verbose = TRUE) {
+  table <- match.arg(table, names(x)[names(x) != "settings"])
+  all_columns <- names(x[[table]])
+  dttm_colNames <- dttmColNames(x)
+  dttm_colNames <- dttm_colNames[[table]]
+  int_colNames <- intervalColNames(x)
+  int_colNames <- int_colNames[[table]]
+  key_colNames <- keycolNames()
+  key_main <- key_colNames[[table]]
+  key_other <- as.character(unlist(key_colNames[-match(table, names(key_colNames))]))
+  key_other <- key_other[key_other %in% all_columns]
+  all_other_columns <- all_columns[! all_columns %in% c(dttm_colNames,
+                                                        key_main,
+                                                        key_other)]
+  tblinfo <- list(key_main = key_main,
+                  key_other = key_other,
+                  dttm = dttm_colNames,
+                  interval = int_colNames,
+                  other = all_other_columns)
+  if(verbose){ print(tblinfo) }
+  invisible(tblinfo)
+}
+
+
+#' @rdname helpers
+#' @export
+duplicateNames <- function(x, keyNames = keycolNames()) {
+  # x is output of read_camtrap_dp
+  duplNameList <- list()
+  for(i in names(x)){
+    if(is.data.frame(x[[i]])){
+      duplNameList[[i]] <- list()
+      for(j in names(x)){
+        if(i != j){
+          if(is.data.frame(x[[j]]))
+          {
+            i_names <- names(x[[i]]); i_names <- i_names[! i_names %in% keyNames]
+            j_names <- names(x[[j]]); j_names <- j_names[! j_names %in% keyNames]
+            duplNameList[[i]][[j]] <- i_names[i_names %in% j_names]
+          }
+        }
+      }
+    }
+  }
+  return(duplNameList)
+}
+
+
+#' @rdname helpers
+#' @export
+forceUniqueNames <- function(x, keyNames = keycolNames()) {
+  # Get duplicated column names
+  x_duplicated <- duplicateNames(x, keyNames)
+  
+  # update names
+  for(i in names(x)[-1]){
+    renameCols <- x_duplicated[[i]] %>% 
+      unlist() %>% 
+      as.character() %>% 
+      unique()
+    names(x[[i]])[names(x[[i]]) %in% renameCols] <- paste(i,  names(x[[i]])[names(x[[i]]) %in% renameCols], sep="_")
+  }
+  
+  # return
+  return(x)
+}
diff --git a/R/integrate_effort.r b/R/integrate_effort.r
new file mode 100644
index 0000000000000000000000000000000000000000..d6d68c2d0ebbf6470759032061e87b333370e394
--- /dev/null
+++ b/R/integrate_effort.r
@@ -0,0 +1,76 @@
+#' Integrate effort over a specified time span
+#' @param x an object of class \code{\link{ctdp}}
+#' @inheritParams filter_timerange
+#' @inheritParams filter_station
+#' @return a \code{tibble} with effort data
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}, \code{\link{filter_station}}
+#' @examples \code{
+#' integrate_effort(camsample)
+#' integrate_effort(camsample, start = "2017/4/1", end = "2017/8/1")
+#' integrate_effort(camsample, subset = str_sub(locationName, 5, 5) == "3")
+#' integrate_effort(camsample,
+#'   start = "2017/4/1", end = "2017/8/1", 
+#'   subset = str_sub(locationName, 5, 5) == "3")
+#' }
+#' @export
+integrate_effort <- function(x, 
+                             start = NULL, end = NULL, orders = "%Y/%m/%d",
+                             subset = NULL) {
+  # checks
+  if(missing(x)){stop("provide input x")}
+  
+  # filter time range?
+  x <- filter_timerange(x, start = start, end = end, orders = orders)
+  
+  # filter on station?
+  argList <- as.list(match.call(expand.dots = FALSE))[c("x","subset")]
+  if(!is.null(argList$subset)){
+    x <- do.call(filter_station,
+                 argList,
+                 quote = FALSE,
+                 envir = parent.frame(n = 1L))
+  }
+
+  # Get table with effort data
+  y <- effort_table(x, startend = TRUE)
+  
+  # Get/set start/end
+  if(is.null(start)){
+    start <- head(y$time, 1)
+  }else{
+    if(is.character(start)){
+      start <- parse_date_time(start, orders = orders, tz = x$settings$tz)
+    }
+  }
+  if(is.null(end)){
+    end <- tail(y$time, 1)
+  }else{
+    if(is.character(end)){
+      end <- parse_date_time(end, orders = orders, tz = x$settings$tz)
+    }
+  }
+  # start
+  # end
+  
+  # Truncate
+  ystart <- max(which(y$time <= start))
+  yend   <- min(which(y$time >= end))
+  # y$time[c(ystart, yend)]
+  z <- y[ystart:yend,]
+  z$time[1] <- start
+  z$time[nrow(z)] <- end
+  # z
+  
+  # compute total effort as integration (i.e. area under curve)
+  totalEffort <- z %>% 
+    mutate(timeNext = lead(time, n = 1L),
+           duration = as.numeric(difftime(timeNext, time, units = "days")),
+           duration = replace_na(duration, replace = 0.0),
+           prod = nrCams * duration) %>% 
+    pull(prod) %>% 
+    sum()
+  
+  # return
+  return(totalEffort)
+}
\ No newline at end of file
diff --git a/R/is.na.ctdp.r b/R/is.na.ctdp.r
new file mode 100644
index 0000000000000000000000000000000000000000..fb78f6ccc647a590b465d7cf54afa28d70fff852
--- /dev/null
+++ b/R/is.na.ctdp.r
@@ -0,0 +1,12 @@
+#' Check whether an a ctdp object is NA
+#' @inheritParams print.ctdp
+#' @return a \code{logical} whether or not the object is of class \code{\link{ctdp}}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' is.na(camsample)
+#' }
+#' @export
+is.na.ctdp <- function(x) {
+  FALSE
+}
\ No newline at end of file
diff --git a/R/is_ctdp.r b/R/is_ctdp.r
new file mode 100644
index 0000000000000000000000000000000000000000..b4f55fb87501f30c0eafb3a7f55e3eae1a808384
--- /dev/null
+++ b/R/is_ctdp.r
@@ -0,0 +1,12 @@
+#' Check whether an object if of class ctdp
+#' @param x an object
+#' @return a \code{logical} whether or not the object is of class \code{\link{ctdp}}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' is_ctdp(camsample)
+#' }
+#' @export
+is_ctdp <- function(x) {
+  "ctdp" %in% class(x)
+}
\ No newline at end of file
diff --git a/R/locations.r b/R/locations.r
new file mode 100644
index 0000000000000000000000000000000000000000..f2b2e036ffcc143fc08dfd7d26a0df80f154f75d
--- /dev/null
+++ b/R/locations.r
@@ -0,0 +1,21 @@
+#' @title Retrieve the locations of a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @return a \code{tibble} with the camera trap locations
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' locations(camsample)
+#' locations(camsample, addLonlat = FALSE)
+#' }
+#' @export
+locations <- function(x, addLonlat = TRUE) {
+  y <- x$locations
+  if(addLonlat == FALSE)
+  {
+    ynames = names(y)
+    ynames = ynames[! tolower(ynames) %in% c("longitude","latitude","lon","lat")]
+    y <- y %>%
+      select(all_of(ynames))
+  }
+  return(y)
+}
\ No newline at end of file
diff --git a/R/media.r b/R/media.r
new file mode 100644
index 0000000000000000000000000000000000000000..91d47d685c05664fb7465e8f7f394acbd5e38057
--- /dev/null
+++ b/R/media.r
@@ -0,0 +1,16 @@
+#' Retrieve the media table of a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @return a \code{tibble} with the media table
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' media(camsample)
+#' }
+#' @export
+media <- function(x) {
+  if(x$settings$media == FALSE){
+    warning("media has been dropped from this object, thus returning the tibble with 0 rows")
+  }
+  y <- x$media
+  return(y)
+}
\ No newline at end of file
diff --git a/R/merge_tibbles.r b/R/merge_tibbles.r
new file mode 100644
index 0000000000000000000000000000000000000000..79fd238e2d8792f100463b3f9727ec5d0d44cf03
--- /dev/null
+++ b/R/merge_tibbles.r
@@ -0,0 +1,105 @@
+#' @title Join the ctdp tibbles into 1 tibble with 1 row per observation
+#' @param x an object of class \code{ctdp}
+#' @param dropMedia \code{logical}, indicating whether or not to remove the \code{media} information. Defaults to \code{TRUE}
+#' @param onlyDistinct \code{logical}, indicating whether or not to remove the duplicate rows. Defaults to \code{TRUE}
+#' @param toInterval \code{logical}, indicating whether or not to return interval columns (for deployments and sequences start/end). Defaults to \code{TRUE}
+#' @return a single object of class \code{tibble} with all data merged
+#' @author Henjo de Knegt
+#' @seealso \code{\link{nest_tibbles}}
+#' @examples \dontrun{
+#' merge_tibbles(camsample)
+#' }
+#' @export
+merge_tibbles <- function(x, dropMedia = TRUE, onlyDistinct = TRUE, toInterval = TRUE) {
+  
+  # Force to interval?
+  if(toInterval){
+    x <- ctdp_interval(x)
+  }else{
+    x <- ctdp_interval(x, rev = TRUE)
+  }
+  
+  # has media?
+  if(has_media(x) == FALSE) {
+    dropMedia <- TRUE
+  }
+  
+  # Remove duplicates?
+  if(onlyDistinct) { x <- distinct(x) }
+  
+  # Get keys and links
+  keys <- keycolNames()
+  linkStructure <- linkStructure()
+  
+
+  ### Get subsets of tibbles (minus the keys that should not be preserved)
+  # y1 media
+  # y2 sequences
+  # y3 observations
+  # y4 deployment
+  # y5 locations
+  # y6 taxonomy
+  {
+    omitKeys <- keys[-which(names(keys) %in% linkStructure$media)] %>% unlist() %>% as.character()
+    y1 <- x$media %>% select(-any_of(omitKeys))
+    omitKeys <- keys[-which(names(keys) %in% linkStructure$sequences)] %>% unlist() %>% as.character()
+    y2 <- x$sequences %>% select(-any_of(omitKeys))
+    omitKeys <- keys[-which(names(keys) %in% linkStructure$observations)] %>% unlist() %>% as.character()
+    y3 <- x$observations %>% select(-any_of(omitKeys))
+    omitKeys <- keys[-which(names(keys) %in% linkStructure$deployment)] %>% unlist() %>% as.character()
+    y4 <- x$deployment %>% select(-any_of(omitKeys))
+    omitKeys <- keys[-which(names(keys) %in% linkStructure$locations)] %>% unlist() %>% as.character()
+    y5 <- x$locations %>% select(-any_of(omitKeys))
+    omitKeys <- keys[-which(names(keys) %in% linkStructure$taxonomy)] %>% unlist() %>% as.character()
+    y6 <- x$taxonomy %>% select(-any_of(omitKeys))
+  }
+  
+  
+  ### Get tibble with SEQUENCEID and MEDIA as list-column
+  if(dropMedia == FALSE){
+    # Get media data
+    omitKeys <- keys[-which(names(keys) %in% linkStructure$media)] %>% unlist() %>% as.character()
+    lc_media <- x$media %>%
+      select(-any_of(omitKeys)) %>%
+      select("sequenceID", everything()) %>%
+      data.table(key = "sequenceID")
+    lc_media <- lc_media %>%
+      dt_nest(sequenceID, .key="media")
+  }
+  # lc_media
+    
+  
+  ### Join deployments, observations, locations and taxonomy to sequences
+  {
+    # all with 2 columns: keys$sequences, and data (media, observations, deployments)
+    # observations: y3, y6
+    # deployments: y4, y5
+    # media: y1
+    
+    y <- y2 %>%
+      left_join(y3, by=keys$sequences, multiple = "all") %>%
+      left_join(y4, by=keys$deployments) %>%
+      left_join(y5, by=keys$locations) %>%
+      left_join(y6, by=keys$taxonomy)
+    # y
+
+    # reorder columns: first keys, then rest
+    y <- y %>%
+      select(all_of(c(as.character(unlist(keys[which(names(keys) != "media")])))),
+             everything())
+    # y
+  }
+  # y
+  
+  
+  ### Add media list column?
+  if(dropMedia == FALSE) {
+    y <- y %>%
+      left_join(lc_media, by=keys$sequences) %>%
+      relocate(media, .after = nrphotos)
+  }
+  
+
+  ### Return
+  return(y)
+}
\ No newline at end of file
diff --git a/R/nest_tibbles.r b/R/nest_tibbles.r
new file mode 100644
index 0000000000000000000000000000000000000000..81f32c96fda01901bbf43c6770cd3a9ccead046b
--- /dev/null
+++ b/R/nest_tibbles.r
@@ -0,0 +1,183 @@
+#' Join the ctdp tibbles into 1 tibble with 1 row per sequence
+#' @param x an object of class \code{\link{ctdp}}
+#' @param dropMedia \code{logical}, indicating whether or not to remove the \code{media} information. Defaults to \code{FALSE}
+#' @param onlyDistinct \code{logical}, indicating whether or not to remove the duplicate rows. Defaults to \code{TRUE}
+#' @param mainCols \code{character} vector with the names of those columns that should be kept outside a list-column (not from media). Defaults to \code{c("captureMethod","nrphotos")}.
+#' @return object of class \code{seqnest}, which is a \code{tibble} with columns \code{sequenceID}, all columns in \code{mainCols}, and \code{list-clumns} "locations", "deployments", "observations" and "media". All these \code{list-columns} contain data in \code{data.table} format.
+#' @author Henjo de Knegt
+#' @seealso \code{\link{merge_tibbles}}
+#' @examples \dontrun{
+#' nest_tibbles(camsample)
+#' }
+#' @export
+nest_tibbles <- function(x,
+                         dropMedia = FALSE,
+                         onlyDistinct = TRUE,
+                         mainCols = c("captureMethod","nrphotos")) {
+  ### Convert interval to _start/_end
+  x <- ctdp_interval(x)
+  x <- ctdp_interval(x, rev = TRUE)
+  
+  
+  # has media?
+  if(x$settings$media == FALSE) {
+    dropMedia <- TRUE
+  }
+  
+  # Update mainCols
+  intervalNames <- dttmColNames(x)
+  mainCols <- unique(c(as.character(unlist(intervalNames$sequences)), mainCols))
+
+  
+  ### get keys and
+  keys <- keycolNames()
+  linkStructure <- linkStructure()
+
+
+  ### First: convert to 1 big tibble
+  yall <- merge_tibbles(x, dropMedia = dropMedia, onlyDistinct = onlyDistinct, toInterval = FALSE)
+  # yall
+  ydrop <- yall
+
+  
+  ### get MEDIA
+  if(dropMedia == FALSE) {
+    # Get media data
+    omitKeys <- keys[-which(names(keys) %in% linkStructure$media)] %>% unlist() %>% as.character()
+    lc_media <- x$media %>%
+      select(-any_of(omitKeys)) %>%
+      select("sequenceID", everything()) %>%
+      distinct() %>%
+      data.table(key = "sequenceID")
+    lc_media <- lc_media %>%
+      dt_nest(sequenceID, .key="media")
+  }
+  # lc_media
+  # lc_media$media[[1]]
+  
+  
+  ### get MAIN
+  {
+    # CHECK uniqueness
+    lc_main <- yall %>%
+      select(all_of(c("sequenceID", mainCols))) %>%
+      distinct()
+    if(length(lc_main$sequenceID) != length(unique(lc_main$sequenceID))){
+      stop("non unique main columns found per sequence")
+    }
+    lc_main <- lc_main %>%
+      data.table(key = "sequenceID") %>%
+      dt_nest(sequenceID, .key="main")
+    ydrop <- ydrop %>%
+      select(-all_of(mainCols))
+  }
+  # lc_main
+  # lc_main$main[[1]]
+
+  
+  ### get OBSERVATIONS
+  {
+    useNames <- names(x$locations)
+    useNames <- useNames[! useNames %in% as.character(unlist(keys))]
+    useNames <- useNames[! useNames %in% mainCols]
+    lc_locations <- ydrop %>%
+      select(sequenceID, locationID, all_of(useNames)) %>%
+      distinct() %>%
+      data.table(key = "sequenceID") %>%
+      dt_nest(sequenceID, .key="locations")
+    ydrop <- ydrop %>%
+      select(-all_of(names(lc_locations$locations[[1]])))
+  }
+  # lc_locations
+  # lc_locations$locations[[1]]
+  
+  
+  ### get DEPLOYMENTS
+  {
+    useNames <- names(x$deployments)
+    useNames <- useNames[! useNames %in% as.character(unlist(keys))]
+    useNames <- useNames[! useNames %in% mainCols]
+    lc_deployments <- ydrop %>%
+      select(sequenceID, deploymentID, all_of(useNames)) %>%
+      distinct() %>%
+      data.table(key = "sequenceID") %>%
+      dt_nest(sequenceID, .key="deployments")
+    ydrop <- ydrop %>%
+      select(-all_of(names(lc_deployments$deployments[[1]])))
+  }
+  # lc_deployments
+  # lc_deployments$deployments[[1]]
+
+  
+  ### get OBSERVATIONS/TAXONOMY
+  {
+    useNames <- c(names(x$observations), names(x$taxonomy))
+    useNames <- useNames[! useNames %in% as.character(unlist(keys))]
+    useNames <- useNames[! useNames %in% mainCols]
+    lc_observations <- ydrop %>%
+      select(sequenceID, observationID, taxonID, all_of(useNames)) %>%
+      distinct() %>%
+      data.table(key = "sequenceID") %>%
+      dt_nest(sequenceID, .key="observations")
+    ydrop <- ydrop %>%
+      select(-all_of(names(lc_observations$observations[[1]])))
+  }
+  # lc_observations
+  # lc_observations$observations[[1]]
+
+  
+  ### what is left
+  # ydrop
+  
+  
+  ### COMBINE
+  {
+    # Checks
+    if(length(unique(unlist(lapply(list(lc_main, lc_locations, lc_deployments, lc_observations), nrow)))) != 1){stop("not all list-column tibbles have the same nrow")}
+    if(all(unlist(lapply(list(lc_main, lc_locations, lc_deployments, lc_observations), key)) == "sequenceID") == FALSE){stop("not all list-column tibbles have key sequenceID")}
+    
+    # Unnest main
+    lc_ALL <- lc_main %>%
+      dt_unnest(main)
+    # key(lc_ALL)
+    lc_ALL <- lc_ALL[lc_locations][lc_deployments][lc_observations]
+    if(dropMedia == FALSE) {
+      lc_mediaSUB <- lc_media[lc_main[,"sequenceID",with=FALSE]]
+      lc_ALL <- lc_ALL[lc_mediaSUB]
+    }
+    # lc_ALL
+    lc_ALL <- as_tibble(lc_ALL)
+  }
+  # lc_ALL
+
+  
+  ### Sequences start/end to interval
+  lc_ALL <- lc_ALL %>%
+    mutate(sequence_interval = interval(start = sequence_start, end = sequence_end)) %>%
+    relocate(sequence_interval, .before = sequence_start) %>%
+    select(-sequence_start, -sequence_end)
+
+  
+  ### Assign class seqnest
+  class(lc_ALL) <- c("seqnest", class(lc_ALL))
+  
+  
+  ### Check nrow of locations and deployments: should be 1!
+  lc_ALL_check <- lc_ALL %>%
+    mutate(nr_locs = map_int(locations, nrow),
+           nr_deps = map_int(deployments, nrow))
+  if(all(lc_ALL_check$nr_locs == 1L) == FALSE){stop("locations list column has elements with > 1 records")}
+  if(all(lc_ALL_check$nr_deps == 1L) == FALSE){stop("deployments list column has elements with > 1 records")}
+  
+
+  ### Return
+  return(lc_ALL)
+}
+
+#' @export
+print.seqnest <- function(x)
+{
+  cat("Object of class 'seqnest': a 'tibble' where each row represents a single sequence (with identifier: sequenceID), and corresponding data in list-columns.\n\n")
+  class(x) <- class(x)[-1]
+  print(x)
+}
\ No newline at end of file
diff --git a/R/observations.r b/R/observations.r
new file mode 100644
index 0000000000000000000000000000000000000000..f72135ca7aedcef503ba6da8033540628b8e7184
--- /dev/null
+++ b/R/observations.r
@@ -0,0 +1,13 @@
+#' Retrieve the observations table of a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @return a \code{tibble} with the observations table
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' observations(camsample)
+#' }
+#' @export
+observations <- function(x) {
+  y <- x$observations
+  return(y)
+}
\ No newline at end of file
diff --git a/R/order_argcheck.r b/R/order_argcheck.r
new file mode 100644
index 0000000000000000000000000000000000000000..2009fea9a43a3d0f9e8b51661603579c5c9a19e1
--- /dev/null
+++ b/R/order_argcheck.r
@@ -0,0 +1,22 @@
+#' @title Argument check on order
+#' @inheritParams ctdp
+#' @inheritParams order_void
+#' @return same as \code{x} or an error
+#' @author Henjo de Knegt
+#' @seealso \code{\link{taxon_orders}}
+#' @keywords internal
+#' @examples \dontrun{
+#' argcheck_order(camsample, order = "Primates")
+#' }
+#' @export
+argcheck_order <- function(x, order) {
+  # input checks
+  if(missing(x)) { stop("supply input x")}
+  if(missing(order)) { stop("supply input order")}
+
+  # Argument check
+  order <- match.arg(order, choices = taxon_orders(x), several.ok = TRUE)
+  
+  # return
+  return(order)
+}
\ No newline at end of file
diff --git a/R/order_void.r b/R/order_void.r
new file mode 100644
index 0000000000000000000000000000000000000000..48ebdfd44c88a66948b5ec8695da99a9a9a2ecdc
--- /dev/null
+++ b/R/order_void.r
@@ -0,0 +1,11 @@
+#' Specify order
+#' @param order optional \code{character} string with the order of species (e.g. "Carnivora") to focus analyses on, defaults to \code{NULL}
+#' @return NULL
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples NULL
+#' @keywords internal
+#' @export
+order_void <- function(order = NULL) {
+  NULL
+}
\ No newline at end of file
diff --git a/R/plot_effort.r b/R/plot_effort.r
new file mode 100644
index 0000000000000000000000000000000000000000..6ccc8865ff62884e2b33d659dceb41885c2c53f7
--- /dev/null
+++ b/R/plot_effort.r
@@ -0,0 +1,30 @@
+#' Plot a step function with the number of active cameras over time
+#' @param x an object of class \code{\link{ctdp}}
+#' @param dynamic \code{logical} whether or not to plot a dynamic graph (via \code{\link{dygraph}}, defaults to \code{TRUE}
+#' @param main \code{character} plot title, defaults to "Effort"
+#' @param xlab \code{character} x axis label, defaults to "time"
+#' @param ylab \code{character} y axis label, defaults to "nr of active cams"
+#' @param ... other arguments passed on to the plot function
+#' @return a plot (whenb \code{doPlot=TRUE}), and (silently when plotting) it returns a tibble with effort data
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#' plot_effort(camsample)
+#' }
+#' @export
+plot_effort <- function(x, dynamic = TRUE, main = "Effort", xlab = "time", ylab = "nr of active cams", ...) {
+  z <- effort_table(x, startend = TRUE)
+  if(dynamic){
+    series <- xts(z$nrCams, order.by = z$time, tz="GMT")
+    dygraph(series, main=main, xlab=xlab, ylab=ylab, ...) %>%
+      dyOptions(fillGraph = TRUE, fillAlpha = 0.4) %>% 
+      dyRangeSelector()
+  }else
+  {
+    plot(z$time, z$nrCams, type="n", main=main, xlab=xlab, ylab=ylab, ...)
+    xx <- c(z$time, rev(z$time))
+    yy <- c(z$nrCams, rep(0, length(z$nrCams)))
+    polygon(xx, yy, border = NA, col=8)
+    lines(z$time, z$nrCams, type="s")
+  }
+}
\ No newline at end of file
diff --git a/R/plot_locations.r b/R/plot_locations.r
new file mode 100644
index 0000000000000000000000000000000000000000..86dbbc3880c4d2a9ecdc4376e3721a47f083586c
--- /dev/null
+++ b/R/plot_locations.r
@@ -0,0 +1,44 @@
+#' Plot locations
+#' @param x an object of class \code{\link{ctdp}}
+#' @param doPlot \code{logical} whether or not to plot the leaflet map, defaults to \code{TRUE}
+#' @return silently returns the leaflet plot
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#' plot_locations(camsample)
+#' }
+#' @export
+plot_locations <- function(x, doPlot = TRUE) {
+  # get locations
+  z <- x$locations
+  
+  # Add popup column
+  z <- z %>% 
+    mutate(popup = str_c("ID: ",locationID,
+                         "<br>Name: ",locationName))
+  
+  # Prepare
+  symbols <- makeSymbolsSize(
+    values = 3,
+    shape = 'circle',
+    color = "black",
+    fillColor = "grey",
+    opacity = .75,
+    baseSize = 10
+  )
+  
+  # Plot
+  p <- z %>% 
+    leaflet() %>% 
+    addTiles() %>%
+    addMarkers(data = z,
+               icon = symbols,
+               lng = ~longitude,
+               lat = ~latitude,
+               popup = ~popup)
+  if(doPlot){
+    print(p)
+  }
+  
+  invisible(p)
+}
\ No newline at end of file
diff --git a/R/plot_status.r b/R/plot_status.r
new file mode 100644
index 0000000000000000000000000000000000000000..0afabd2b8c5a12e95daa46413258b1356625f1f4
--- /dev/null
+++ b/R/plot_status.r
@@ -0,0 +1,39 @@
+#' Plot annotation status per deployment
+#' @param x an object of class \code{\link{ctdp}}
+#' @return a plot
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#' plot_status(camsample)
+#' plot_status(camsample, grouping = "transect")
+#' }
+#' @export
+plot_status <- function(x,
+                        IDcol = "locationName",
+                        onlyMotion = TRUE,
+                        xlab = "Time", ylab = "Location", 
+                        
+                        grouping = NULL,
+                        dynamic = TRUE,
+                        doPlot = TRUE) {
+  # Get annotation status
+  df <- fraction_annotated(x, onlyMotion = onlyMotion)
+  
+  # Add annotation fraction to x$deployments
+  x$deployments <- x$deployments %>% 
+    left_join(df, by = "deploymentID")
+
+  # PLOT
+  y <- plot_time_coverage(x,
+                          IDcol = IDcol,
+                          xlab = xlab, ylab = ylab, 
+                          grouping = grouping,
+                          colour = "fracAnnotated",
+                          dynamic = dynamic,
+                          addSequences = FALSE,
+                          doPlot = doPlot)
+  
+  
+  # invisibly return
+  invisible(y)
+}
\ No newline at end of file
diff --git a/R/plot_time_coverage.r b/R/plot_time_coverage.r
new file mode 100644
index 0000000000000000000000000000000000000000..ecef0f7a6aec0a0e3a9936254df0d09e1e3299a5
--- /dev/null
+++ b/R/plot_time_coverage.r
@@ -0,0 +1,237 @@
+#' Plot activity of the camera-stations over time
+#' @param x an object of class \code{\link{ctdp}}
+#' @param IDcol \code{character} name of the column that groups the deployments, defaults to "locationName
+#' @param xlab,ylab \code{character} names of the axis labels (defaults to "Time" and "Location")
+#' @param grouping an optional \code{character} vector with the name(s) of the grouping columns, defaults to \code{NULL}
+#' @param colour an optional \code{character} name of the column in the deployments table to be used for colouring the polygons (this overwrites the \code{grouping} argument, which will then only be used for the y-axis ordering). Defaults to \code{NULL}
+#' @param dynamic \code{logical} whether or not to plot a dynamic graph (via \code{ggplotly}, defaults to \code{TRUE}
+#' @param addSequences \code{logical} whether or not to add the times of sequences to the plots (with jittering in y-direction), defaults to \code{FALSE}
+#' @param jitter \code{numeric} value of the amount of vertical jittering (sd of normal distribution centered on 0), defaults to 0.05.
+#' @param size \code{numeric} value of the size of the points, defaults to 0.25
+#' @param doTimelapse \code{logical} whether or not to plot the time lapse sequences, defaults to \code{FALSE}
+#' @param doPlot \code{logical} whether or not to plot, defaults to \code{TRUE}
+#' @return invisibly returns the plot
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples \dontrun{
+#' # dynamic ggplotly plots
+#' plot_time_coverage(camsample)
+#' plot_time_coverage(camsample, addSequences = TRUE)
+#' 
+#' # only ggplot
+#' plot_time_coverage(camsample, dynamic = FALSE)
+#' plot_time_coverage(camsample, addSequences = TRUE, dynamic = FALSE)
+#' 
+#' # colour by grouping
+#' plot_time_coverage(camsample, grouping = "transect")
+#' }
+#' @export
+plot_time_coverage <- function(x,
+                               IDcol = "locationName", 
+                               xlab = "Time", ylab = "Location", 
+                               grouping = NULL,
+                               colour = NULL,
+                               dynamic = TRUE,
+                               
+                               addSequences = FALSE,
+                               jitter = 0.05,
+                               size = 0.25,
+                               doTimelapse = FALSE,
+                               
+                               doPlot = TRUE) {
+  # Force to interval
+  x <- ctdp_interval(x)
+  
+  # Prep data for deployments
+  dfCols <- unique(c("deploymentID","locationName","deployment_interval",IDcol,grouping,colour))
+  df <- x$deployments %>% 
+    left_join(x$locations, by = "locationID") %>% 
+    select(all_of(dfCols)) %>% 
+    rename(id = deploymentID,
+           int = deployment_interval)
+  # df
+  df[,"label"] <- df[,IDcol]
+  df[,IDcol] <- NULL
+  df$label = as.character(df$label)
+  
+  # set colour
+  if(!is.null(colour)){
+    df[,"colour"] <- df[,colour]
+    if(colour != "colour") { df[,colour] <- NULL }
+  }else{
+    df[,"colour"] <- 1
+  }
+    
+  # set grouping
+  if(!is.null(grouping)){
+    df[,"group"] <- df[,grouping]
+    if(grouping != "group") { df[,grouping] <- NULL }
+    df$group <- as.factor(df$group)
+    df$z = interaction(df$group, df$label)
+  }else{
+    df$group <- as.factor(df$label)
+    df$z = as.factor(df$label) 
+  }
+  df$z <- droplevels(df$z)
+  df$znr = as.numeric(as.integer(df$z))
+  df <- df %>% 
+    arrange(znr)
+  # plot(df$znr)
+
+  
+  ### Summarise to prepare for plotting
+  dfsum <- df %>% 
+    group_by(z, znr, id, label, group, colour) %>% 
+    summarise(xmin = int_start(int),
+              xmax = int_end(int),
+              ymin = znr - 0.4,
+              ymax = znr + 0.4,
+              .groups = "drop")
+  # dfsum
+
+  # Add identifier
+  dfsum <- dfsum %>% 
+    mutate(location = paste(label, id, sep = ": deployment: "))
+
+    
+  ### set up base ggplot
+  p <- ggplot()
+  # p
+  
+  
+  ### Add deployment rectangles
+  if(!is.null(grouping)){
+    if(is.null(colour)){
+      p <- p +
+        geom_rect(mapping = aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
+                                group = location,
+                                fill = group),
+                  data = dfsum)
+    }else{
+      p <- p +
+        geom_rect(mapping = aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
+                                group = location,
+                                fill = colour),
+                  data = dfsum)
+    }
+  }else{
+    if(is.null(colour)){
+      p <- p +
+        geom_rect(mapping = aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
+                                group = location),
+                  data = dfsum)
+    }else{
+      p <- p +
+        geom_rect(mapping = aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
+                                group = location,
+                                fill = colour),
+                  data = dfsum)
+    }
+  }
+  # p
+  
+  # Add colour scale if colour = "fracAnnotated"
+  if(!is.null(colour)){
+    if(colour == "fracAnnotated"){
+      mycols <- c("navy", "blue", "cyan", "lightcyan", "yellow", "red", "red4")
+      p <- p +
+        scale_fill_gradientn(colours = mycols, limits = c(0,1)) + 
+        labs(fill = "Annotated")
+    }
+  }else{
+    if(!is.null(grouping)){
+      p <- p +
+        labs(fill = str_to_sentence(grouping))
+    }
+  }
+ 
+  ### Add points from sequences?
+  if(addSequences){
+    dfSeq <- x$sequences %>% 
+      left_join(x$deployments, by = "deploymentID") %>% 
+      left_join(x$locations, by = "locationID") %>% 
+      select(all_of(c("sequence_interval", "captureMethod", dfCols))) %>% 
+      select(-deployment_interval) %>% 
+      rename(id = deploymentID,
+             int = sequence_interval)
+    
+    # Filter out the time lapse sequences
+    if(doTimelapse == FALSE){
+      dfSeq <- dfSeq %>% 
+        filter(captureMethod != "time lapse")
+    }
+    
+    # dfSeq
+    dfSeq[,"label"] <- dfSeq[,IDcol]
+    dfSeq[,IDcol] <- NULL
+    dfSeq$label = as.character(dfSeq$label)
+    
+    # Get grouping
+    if(!is.null(grouping)){
+      dfSeq[,"group"] <- dfSeq[,grouping]
+      if(grouping != "group") { dfSeq[,grouping] <- NULL }
+      dfSeq$z = interaction(df$group, dfSeq$label)
+    }else{
+      dfSeq$group <- as.character(dfSeq$label)
+      dfSeq$z = as.character(dfSeq$label)
+    }
+    dfSeq$z = factor(dfSeq$z, levels = levels(df$z)) 
+    dfSeq$group <- factor(as.character(dfSeq$group), levels = levels(df$group))
+    dfSeq$znr = as.numeric(as.integer(dfSeq$z))
+    dfSeq <- dfSeq %>% 
+      arrange(znr)
+    
+    # add jitter and subset cols
+    dfSeq <- dfSeq %>% 
+      mutate(location = paste(label, id, sep = ": deployment: ")) %>% 
+      mutate(seq_start = int_start(int),
+             znr_jitter = znr + rnorm(n = n(), mean = 0, sd = jitter)) %>% 
+      select(id, seq_start, znr_jitter)
+    # dfSeq %>% filter(is.na(seq_start))
+    # dfSeq %>% filter(is.na(znr_jitter))
+    # summary(dfSeq$znr_jitter)
+    
+    # add points
+    p <- p + 
+      geom_point(data = dfSeq,
+                 mapping = aes(x = seq_start,
+                               y = znr_jitter),
+                 col = "red", size = size)
+  }else{
+    p <- p
+  }
+  # p
+  
+
+  ## add labels etc
+  p <- p +
+    xlab(xlab) +
+    ylab(ylab) + 
+    theme_linedraw()
+  
+  # update y-axis (x because of coord flip) to labels
+  yAxVals <- dfsum %>% 
+    select(znr,label) %>% 
+    distinct() %>% 
+    arrange(znr)
+  p2 <- p + 
+    scale_y_discrete(limits = as.character(yAxVals$znr),
+                     labels = yAxVals$label)
+  # p2
+  
+
+  ### dynamic ggplotly plot or just ggplot?
+  if(dynamic) {
+    p2 <- ggplotly(p2, tooltip = "location")
+  }
+  
+  
+  ### Plot?
+  if(doPlot){
+    print(p2)
+  }
+
+  
+  ### Invisibly return
+  invisible(p2)
+}
\ No newline at end of file
diff --git a/R/point_effort.r b/R/point_effort.r
new file mode 100644
index 0000000000000000000000000000000000000000..48269233637af43a71907c619bae63390f326b83
--- /dev/null
+++ b/R/point_effort.r
@@ -0,0 +1,26 @@
+#' Find the number of active cameras at a given set of times
+#' @param x an object of class \code{\link{ctdp}}
+#' @param t \code{POXIXct} or \code{character} timepoints for which to retrieve effort
+#' @param orders \code{character} string with the orders passed on to \code{\link{parse_date_time}}
+#' @return a plot, and silently it returns a tibble with effort data
+#' @author Henjo de Knegt
+#' @seealso \code{\link{plot_effort}}
+#' @examples \dontrun{
+#' point_effort(camsample, "2017/6/21 12:00:00")
+#' }
+#' @export
+point_effort <- function(x, t, orders = "%Y/%m/%d %H:%M:%S") {
+  # Get effort data
+  effortData <- effort_table(x)
+  
+  # Get t if it is character
+  if(is.character(t)){
+    t <- parse_date_time(t, orders = orders, tz = x$settings$tz)
+  }
+  
+  # Get interval
+  i_t <- findInterval(t, effortData$time)
+  
+  # Return nr of active cams for each i_t
+  return(effortData$nrCams[i_t])
+}
\ No newline at end of file
diff --git a/R/primary_keys.r b/R/primary_keys.r
new file mode 100644
index 0000000000000000000000000000000000000000..d7b36d4e4a4ed8edc2122e8e97a3c304129e8e52
--- /dev/null
+++ b/R/primary_keys.r
@@ -0,0 +1,12 @@
+#' Retrieve the primary keys of the ctdp tables
+#' @param x an object of class \code{\link{ctdp}}
+#' @return a named \code{list} with \code{character} names of the primary keys of the tibbles in \code{x}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' primary_keys(camsample)
+#' }
+#' @export
+primary_keys <- function(x) {
+  keycolNames()
+}
\ No newline at end of file
diff --git a/R/print.ctdp.r b/R/print.ctdp.r
new file mode 100644
index 0000000000000000000000000000000000000000..5dd9824ec0b9befc72f3137e4b3079ebeb87a5f2
--- /dev/null
+++ b/R/print.ctdp.r
@@ -0,0 +1,56 @@
+#' @rdname ctdp
+#' @param x an object of class \code{\link{ctdp}}
+#' @export
+print.ctdp <- function(x) {
+  cat(paste0("ctdp datapackage which is a list with elements: \n"))
+  for(i in seq_along(x)) {
+    if(! is.data.frame(x[[i]])) {
+      cat(" - ",names(x)[i], "\n")
+    }
+  }
+  cat("\nand tables:\n")
+  for(i in seq_along(x)) {
+    if(is.data.frame(x[[i]])) {
+      cat(" - ",names(x)[i], paste0("(n = ",formatC(nrow(x[[i]]), big.mark=","),")"), "\n")
+    }
+  }
+  
+  cat("\n")
+  
+  pathprops <- pathProperties(x$settings$path)
+  if(pathprops$isZip) {
+    cat("Zip file:",pathprops$fileFolder,"\n")
+  }else {
+    cat("Folder:",pathprops$fileFolder,"\n")
+  }
+  
+  cat("Timezone:",x$settings$tz,"\n")
+  cat("Interval:",x$settings$interval,"\n")
+  cat("Primary keys:", paste(keycolNames(), collapse=", "),"\n")
+  cat("Nr of stations:", nrow(x$locations),"\n")
+  cat("Nr of species:", nrow(x$taxonomy),"\n")
+  
+  if(x$settings$media == FALSE) {
+    cat("Media have been dropped\n")
+  }
+  
+  tstart <- NULL
+  tend <- NULL
+  if(x$settings$interval == FALSE){
+    x <- ctdp_interval(x, rev = FALSE)
+  }
+  if(nrow(x$deployments) > 0) {
+    tstart <- x$deployments %>% pull(deployment_interval) %>% int_start() %>% min(na.rm = TRUE)
+    tend   <- x$deployments %>% pull(deployment_interval) %>% int_end() %>% max(na.rm = TRUE)
+  }
+  
+  cat("Time range:",
+      ifelse(is.null(tstart),
+             "",
+             format(min(tstart), "%Y/%m/%d")),
+      " -- ",
+      ifelse(is.null(tend),
+             "",
+             format(max(tend), "%Y/%m/%d")),
+      "\n")
+}
\ No newline at end of file
diff --git a/R/read_ctdp.r b/R/read_ctdp.r
new file mode 100644
index 0000000000000000000000000000000000000000..5693ad7d553edb792aaf0c5bc2f21a07d202de64
--- /dev/null
+++ b/R/read_ctdp.r
@@ -0,0 +1,90 @@
+#' @title Loading data in camptrap-dp format
+#' @description This function loads a frictionless datapackage into an object of class \code{ctdp}, using the 'frictionless' and 'camtraptor' packages (see \url{https://inbo.github.io/camtraptor/} and \url{https://docs.ropensci.org/frictionless/}).
+#' @details NULL
+#' @param path \code{character} string with the path to the folder or zip-file holding the data
+#' @return an object of class \code{ctdp}, which is a \code{list} with elements:
+#' \itemize{
+#'   \item \code{locations}: a \code{tibble} with info on camera locations
+#'   \item \code{deployments}: a \code{tibble} with info on deployments
+#'   \item \code{sequences}: a \code{tibble} with info on sequences
+#'   \item \code{observations}: a \code{tibble} with info on observations
+#'   \item \code{media}: a \code{tibble} with info on captured images
+#'   \item \code{taxonomy}: a \code{tibble} with the used taxonomy table
+#'   \item \code{settings}: a \code{list} with settings, including the path to the folder or file with source data, time zone, whether or not the object holds timestamps as \code{Interval} objects, and whether or not the object contains the media information.
+#' }
+#' @author Henjo de Knegt
+#' @seealso \code{\link{as_ctdp}}
+#' @examples \dontrun{
+#'   data(camsample)
+#'   camsample
+#' }
+#' @name ctdp
+#' @aliases ctdp read_ctdp
+
+#' @rdname ctdp
+#' @export
+read_ctdp <- function(path,
+                      tz = "Etc/GMT-2",
+                      verbose = TRUE,
+                      rmEmpty = FALSE,
+                      dropMedia = FALSE) {
+  ### Checks
+  if(missing(path)) { stop("supply input path")}
+
+  
+  ### Get file/folder info
+  pathInfo <- pathProperties(path)
+  if(verbose){ cat("Loading file",pathInfo$fileFolder,"\n") }
+  
+  
+  ### unzip (if from zip)
+  if(pathInfo$isZip) {
+    if(verbose){ cat(" - unzipping file\n") }
+    
+    # create temp dir, unzip file into it
+    tmpDir <- tempdir(check = TRUE)
+    tmpfile <- tempfile(fileext = ".zip")
+    file.copy(from=path, to=tmpfile)
+    currentWD <- getwd()
+    setwd(tmpDir)
+    unzip(tmpfile, exdir=gsub(tmpfile, pattern=".zip", replacement=""))
+    path <- gsub(tmpfile, pattern=".zip", replacement="")
+    
+    path <- gsub(path, pattern="\\\\", replacement="/")
+    tmpfile <- gsub(tmpfile, pattern="\\\\", replacement="/")
+    tmpDir <- gsub(tmpDir, pattern="\\\\", replacement="/")
+    unzipped_folder <- gsub(tmpfile, pattern=".zip", replacement="")
+    setwd(currentWD)
+  }else{
+    unzipped_folder <- file.path(pathInfo$path, pathInfo$fileFolder)
+  }
+  # unzipped_folder
+  
+  
+  ### Load data into camtraptor object from unzipped_folder
+  if(verbose){ cat(" - loading data\n") }
+  {
+    ## Checks on files
+    fileNames <- list.files(unzipped_folder)
+    if(all(c("datapackage.json","deployments.csv","media.csv","observations.csv") %in% fileNames) == FALSE){
+      stop('the unzipped folder should contain the following files: "datapackage.json","deployments.csv","media.csv","observations.csv"')
+    }
+
+    ## Load from unzipped folder using camtraptor function
+    y <- camtraptor::read_camtrap_dp(file = file.path(unzipped_folder,"datapackage.json"),
+                                       media = TRUE)
+  }
+
+  
+  ### Convert from camtraptor to ctdp
+  x <- as_ctdp(y, 
+               tz = tz,
+               verbose = verbose,
+               rmEmpty = rmEmpty,
+               pathInfo = pathInfo,
+               dropMedia = dropMedia)
+  
+  
+  ### Return
+  return(x)
+}
\ No newline at end of file
diff --git a/R/sequences.r b/R/sequences.r
new file mode 100644
index 0000000000000000000000000000000000000000..773cd85b118ddff9c7ccbd51024806f5c9958311
--- /dev/null
+++ b/R/sequences.r
@@ -0,0 +1,13 @@
+#' Retrieve the sequences table of a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @return a \code{tibble} with the sequences table
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' sequences(camsample)
+#' }
+#' @export
+sequences <- function(x) {
+  y <- x$sequences
+  return(y)
+}
\ No newline at end of file
diff --git a/R/set_timezone.r b/R/set_timezone.r
new file mode 100644
index 0000000000000000000000000000000000000000..d237ca1579549c8ec6fae9a82a34d1e0feef9e3d
--- /dev/null
+++ b/R/set_timezone.r
@@ -0,0 +1,29 @@
+#' @title Specify a timezone via an offset relative to GMT/UTC
+#' @description Specify a timezone via an offset relative to GMT/UTC
+#' @param offset a (signed) \code{integer} or \code{numeric} (that can be parsed to integer) value with the offset relative to GMT/UTC (in hours)
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @return a \code{character} notation of the timezone
+#' @examples \dontrun{
+#' set_GMT_offset(+2)
+#' set_GMT_offset(-5)
+#' }
+#' @export
+set_GMT_offset <- function(offset) {
+  # Checks
+  if(is.numeric(offset) == FALSE){stop("offset must be numeric/integer")}
+  if(is.integer(offset) == FALSE){
+    if(offset %% 1 == 0){
+      offset <- as.integer(offset)
+    }else{
+      stop("if offset is supplied as numeric value, it should be a whole number")
+    }
+  }
+  if(offset < -12 | offset > +12){stop("offset should be in range -12 -- +12")}
+  if(offset >= 0){
+    tz <- str_c("Etc/GMT-",abs(offset))
+  }else{
+    tz <- str_c("Etc/GMT+",abs(offset))
+  }
+  return(tz)
+}
\ No newline at end of file
diff --git a/R/species_argcheck.r b/R/species_argcheck.r
new file mode 100644
index 0000000000000000000000000000000000000000..d671312dafa3748783b68186f6dc79cea43d83e8
--- /dev/null
+++ b/R/species_argcheck.r
@@ -0,0 +1,25 @@
+#' @title Argument check on species
+#' @inheritParams ctdp
+#' @inheritParams species_void
+#' @return same as \code{x} or an error
+#' @author Henjo de Knegt
+#' @seealso \code{\link{taxon_id}}
+#' @keywords internal
+#' @examples \dontrun{
+#' species_argcheck(camsample, species = "LowlandPaca")
+#' species_argcheck(camsample, species = "LowlandPaca")
+#' }
+#' @export
+species_argcheck <- function(x, species) {
+  # input checks
+  if(missing(x)) { stop("supply input x")}
+  if(missing(species)) { stop("supply input species")}
+
+  # check
+  species <- tibble(taxonID = taxon_id(x, spp = species)) %>% 
+    left_join(taxonomy(x), by = "taxonID") %>% 
+    pull(vernacularNames.en)
+
+  # return
+  return(species)
+}
\ No newline at end of file
diff --git a/R/species_void.r b/R/species_void.r
new file mode 100644
index 0000000000000000000000000000000000000000..2bf98f57683ec7ee322890e8b8f117ceb15332c6
--- /dev/null
+++ b/R/species_void.r
@@ -0,0 +1,11 @@
+#' Specify species
+#' @param species an optional \code{character} vector with name of the species (e.g. "Ocelot") to focus on, defaults to \code{NULL}
+#' @return NULL
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples NULL
+#' @keywords internal
+#' @export
+species_void <- function(species = NULL) {
+  NULL
+}
\ No newline at end of file
diff --git a/R/summarise_deployments.r b/R/summarise_deployments.r
new file mode 100644
index 0000000000000000000000000000000000000000..30a1100d2b854ade83796a033fe9ee6ee98afc48
--- /dev/null
+++ b/R/summarise_deployments.r
@@ -0,0 +1,108 @@
+#' @title Summarize (groups of) deployments
+#' @description Summarize (groups of) deployments
+#' @details NULL
+#' @param x an object of class \code{\link{ctdp}}
+#' @param datatable \code{logical}, whether or not to show as interactive datatable in the viewer, defaults to \code{FALSE}
+#' @param onlyMotion \code{logical}, whether or not to only use motion detection images, defaults to \code{TRUE}
+#' @param omitSetupType \code{logical}, whether or not to omit sequences that are marked as some form of setup (`observations` column `cameraSetupType` is than not NA), defaults to \code{TRUE}
+#' @inheritParams filter_timerange
+#' @inheritParams filter_station
+#' @inheritParams by_void
+#' @return a \code{tibble} like returned by \code{\link{fraction_annotated}}, but with extra column "effort", which contains the total number of camera-trap days for each group (row)
+#' @author Henjo de Knegt
+#' @seealso \code{\link{fraction_annotated}}
+#' @examples \dontrun{
+#' summarise_deployments(camsample)
+#' summarise_deployments(camsample, by = "locationName")
+#' summarise_deployments(camsample, by = "transect")
+#' summarise_deployments(camsample, start = "2017/4/1", end = "2017/8/1")
+#' summarise_deployments(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3") 
+#' 
+#' # As datatable in viewer
+#' summarise_deployments(camsample, datatable = TRUE)
+#' }
+#' @export
+summarise_deployments <- function(x,
+                                  datatable = FALSE,
+                                  onlyMotion = TRUE,
+                                  omitSetupType = TRUE,
+                                  
+                                  start = NULL, end = NULL, orders = "%Y/%m/%d", # filter_timerange
+                                  subset = NULL, # filter_station
+                                  by = NULL # by_void
+                                  ) {
+  
+  # Collect arguments
+  allArgs <- as.list(match.call(expand.dots = FALSE))
+  
+  # filter time range?
+  x <- filter_timerange(x, start = start, end = end, orders = orders)
+  
+  # filter on station?
+  if(!is.null(allArgs$subset)){
+    x <- do.call(filter_station,
+                 allArgs[c("x","subset")],
+                 quote = FALSE,
+                 envir = parent.frame(n = 1L))
+  }
+  
+  # Force interval
+  x <- ctdp_interval(x)
+  
+  # annotation overview
+  y1 <- fraction_annotated(x, onlyMotion = onlyMotion, omitSetupType = omitSetupType, by = NULL)
+  
+  # Add deployment length (days)
+  y2 <- x$deployments %>% 
+    select(deploymentID, deployment_interval) %>% 
+    mutate(effort = int_length(deployment_interval) / 60 / 60 / 24) # DAYS
+  
+  # join y1 and y2
+  y <- y2 %>% 
+    left_join(y1, by = "deploymentID")
+  # y
+  
+  ### Add by info?
+  if(!is.null(by)){
+    yby <- x$deployments %>% 
+      left_join(x$locations, by = "locationID") %>% 
+      select(all_of(unique(c("deploymentID",by))))
+    y <- y %>% 
+      left_join(yby, by = "deploymentID") %>% 
+      relocate(all_of(by), .before = "deploymentID")
+  }
+  # y
+  
+  # Summarise by
+  if(!is.null(by)){
+    y <- y %>% 
+      group_by(across(all_of(by))) %>% 
+      summarise(effort = sum(effort),
+                fracAnnotated = mean(fracAnnotated),
+                nrSeqs = sum(nrSeqs),
+                nrSeqsAnnotated = sum(nrSeqsAnnotated),
+                nrPhotos = sum(nrPhotos),
+                .groups = "drop") %>% 
+      mutate(fracAnnotated = nrSeqsAnnotated / nrSeqs)
+  }else{
+    y <- y %>% 
+      select(-deployment_interval)
+  }
+  # y
+
+  # nrPhotos as integer
+  y <- y %>% 
+    mutate(nrPhotos = as.integer(nrPhotos))
+  
+  # show as datatable?
+  if(datatable){
+    y %>% 
+      mutate(effort = round(effort, 3),
+             fracAnnotated = round(fracAnnotated, 3)) %>% 
+      datatable(caption = "Deployment summary",
+                style = "auto")
+  }
+  
+  # Return
+  return(y)
+}
\ No newline at end of file
diff --git a/R/taxon_classes.r b/R/taxon_classes.r
new file mode 100644
index 0000000000000000000000000000000000000000..9a7bb182d0ca3e45e480886626bcc5453ededdd1
--- /dev/null
+++ b/R/taxon_classes.r
@@ -0,0 +1,16 @@
+#' Retrieve the taxonomic classes
+#' @inheritParams ctdp
+#' @return a \code{character} vector with the taxonomic classes present in \code{x}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' taxon_classes(camsample)
+#' }
+#' @export
+taxon_classes <- function(x) {
+  y <- x$taxonomy %>% 
+    count(class) %>% 
+    drop_na() %>% 
+    pull(class)
+  return(y)
+}
\ No newline at end of file
diff --git a/R/taxon_id.r b/R/taxon_id.r
new file mode 100644
index 0000000000000000000000000000000000000000..2bfa591cb37897b99a2707803c53f532a8d056a5
--- /dev/null
+++ b/R/taxon_id.r
@@ -0,0 +1,47 @@
+#' Get the taxonID for a species
+#' @param x an object of class \code{\link{ctdp}}
+#' @return a \code{character} vector with the corresponding taxonID values
+#' @author Henjo de Knegt
+#' @seealso \code{\link{taxon_species}}
+#' @examples \dontrun{
+#' taxon_id(camsample)
+#' taxon_id(camsample, spp = "lowland paca")
+#' taxon_id(camsample, spp = "LowlandPaca")
+#' taxon_id(camsample, spp = "_LowLand_ PaCa")
+#' }
+#' @export
+taxon_id <- function(x, spp = NULL) {
+  if(is.null(spp)){
+    return(x$taxonomy$taxonID)
+  }else{
+    if(length(spp) > 1){
+      ids <- sapply(spp, function(z){ taxon_id(x,z)}, USE.NAMES = FALSE)
+      return(ids)
+    }else
+    {
+      # Get taxonomy
+      xtax <- x$taxonomy
+      
+      # Prep
+      xtax_key <- xtax %>% select(taxonID)
+      xtax_rest <- xtax %>% select(-taxonID)
+      xtax_rest <- xtax_rest %>% 
+        mutate(across(.cols=where(is.character),.fns = tolower),
+               across(.cols=where(is.character),.fns = function(x){gsub(x, pattern=" ", replacement="")}))
+      xtax_all <- bind_cols(xtax_key, xtax_rest)
+      
+      # Prep spp
+      spp <- gsub(tolower(spp), pattern=" ", replacement="")
+      spp <- gsub(tolower(spp), pattern= "-|_|", replacement="")
+      
+      
+      ### Filter for ANY column (NOT taxonID) containing spp
+      xtax_sub <- xtax_all %>% 
+        rowwise() %>% 
+        filter(any(c_across(-taxonID) == spp))
+      
+      ### Return taxonID
+      return(xtax_sub$taxonID)
+    }
+  }
+}
\ No newline at end of file
diff --git a/R/taxon_level.r b/R/taxon_level.r
new file mode 100644
index 0000000000000000000000000000000000000000..143acda004c006b0253c10ecb6d623ada97d627b
--- /dev/null
+++ b/R/taxon_level.r
@@ -0,0 +1,50 @@
+#' @title Get taxonomic class of a species by its identifier
+#' @description Get taxonomic class of a species by its identifier
+#' @param taxonID a \code{character} string with taxonID
+#' @param level a \code{character} string with the desired level, with choices "species","genus","family","suborder","order","infraclass","subclass","class","superclass","megaclass","gigaclass","parvphylum","infraphylum","subphylum","phylum","kingdom","unranked"
+#' @author Henjo de Knegt
+#' @seealso API of \url{https://www.catalogueoflife.org/} called
+#' @return a \code{character} string with class of the taxon
+#' @examples \dontrun{
+#'   taxon_level("QLXL", level = "species")
+#'   taxon_level("QLXL", level = "genus")
+#'   taxon_level("QLXL", level = "family")
+#'   taxon_level("QLXL", level = "suborder")
+#'   taxon_level("QLXL", level = "order")
+#'   taxon_level("QLXL", level = "infraclass")
+#'   taxon_level("QLXL", level = "subclass")
+#'   taxon_level("QLXL", level = "class")
+#'   taxon_level("QLXL", level = "superclass")
+#'   taxon_level("QLXL", level = "megaclass")
+#'   taxon_level("QLXL", level = "gigaclass")
+#'   taxon_level("QLXL", level = "parvphylum")
+#'   taxon_level("QLXL", level = "infraphylum")
+#'   taxon_level("QLXL", level = "subphylum")
+#'   taxon_level("QLXL", level = "phylum")
+#'   taxon_level("QLXL", level = "kingdom")
+#'   taxon_level("QLXL", level = "unranked")
+#' }
+#' @export
+taxon_level <- function(taxonID, level = "class") {
+  # Checks on inputs
+  if(missing(taxonID)){stop("supply input taxonID")}
+  level <- match.arg(level, choices = c("species","genus","family","suborder","order","infraclass","subclass","class",
+                                        "superclass","megaclass","gigaclass","parvphylum","infraphylum","subphylum",
+                                        "phylum","kingdom","unranked"))
+  
+  # Get json string from api and parse to list
+  y <- fromJSON(curl(paste0("https://api.catalogueoflife.org/dataset/9854/tree/",taxonID)))
+  
+  # Get taxonomic level (if not empty list!)
+  if(is.data.frame(y)) {
+    y <- subset(y, rank == level)$name 
+    if(length(y) == 0) {
+      y <- NA_character_
+    }
+  }else{
+    y <- NA_character_
+  }
+  
+  # return
+  return(y)
+}
\ No newline at end of file
diff --git a/R/taxon_orders.r b/R/taxon_orders.r
new file mode 100644
index 0000000000000000000000000000000000000000..1c4d24fc7e1a686f3a1a4bd55ec89666de065a97
--- /dev/null
+++ b/R/taxon_orders.r
@@ -0,0 +1,29 @@
+#' Retrieve the taxonomic orders
+#' @param x an object of class \code{\link{ctdp}}
+#' @inheritParams class_void
+#' @return a \code{character} vector with the taxonomic orders present in \code{x}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' taxon_orders(camsample)
+#' taxon_orders(camsample, class = taxon_classes(camsample))
+#' taxon_orders(camsample, class = "Mammalia")
+#' }
+#' @export
+taxon_orders <- function(x, class = NULL) {
+  # Get taxonomy table
+  y <- x$taxonomy
+  
+  if(!is.null(class)){
+    doClass <- class_argcheck(x, class)
+    y <- y %>% 
+      filter(class %in% doClass)
+  }
+  
+  y <- y %>% 
+    count(order) %>% 
+    ungroup() %>% 
+    drop_na() %>% 
+    pull(order)
+  return(y)
+}
\ No newline at end of file
diff --git a/R/taxon_species.r b/R/taxon_species.r
new file mode 100644
index 0000000000000000000000000000000000000000..32f0ac4b1a256fb6bb842cd779798f1fef05824b
--- /dev/null
+++ b/R/taxon_species.r
@@ -0,0 +1,16 @@
+#' Retrieve the taxonomic species
+#' @inheritParams ctdp
+#' @return a \code{character} vector with the taxonomic species present in \code{x}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' taxon_species(camsample)
+#' }
+#' @export
+taxon_species <- function(x) {
+  y <- x$taxonomy %>% 
+    count(vernacularNames.en) %>% 
+    drop_na() %>% 
+    pull(vernacularNames.en)
+  return(y)
+}
\ No newline at end of file
diff --git a/R/taxonomy.r b/R/taxonomy.r
new file mode 100644
index 0000000000000000000000000000000000000000..03f5b02dd8bb674b59e5f72525437791ad6eb296
--- /dev/null
+++ b/R/taxonomy.r
@@ -0,0 +1,20 @@
+#' Retrieve the taxonomy table of a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @inheritParams species_void
+#' @return a \code{tibble} with the taxonomy table
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' taxonomy(camsample)
+#' taxonomy(camsample, species = "lowland paca")
+#' }
+#' @export
+taxonomy <- function(x, species = NULL) {
+  y <- x$taxonomy
+  if(!is.null(species)){
+    species <- species_argcheck(x, species = species)
+    y <- y %>% 
+      filter(vernacularNames.en %in% species)
+  }
+  return(y)
+}
\ No newline at end of file
diff --git a/R/time_range.r b/R/time_range.r
new file mode 100644
index 0000000000000000000000000000000000000000..1f4964c1ce2cf251756284f0879ea271329ff2dc
--- /dev/null
+++ b/R/time_range.r
@@ -0,0 +1,39 @@
+#' Retrieve the time range of a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @param based_on \code{character} vector based on which element the time range should be computed: one of "deployments" (default), "sequences", "media".
+#' @return a \code{dttm} vector of length 2 with the start/end of the ctdp object
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' time_range(camsample)
+#' }
+#' @export
+time_range <- function(x, based_on = c("deployments","sequences","media")[1]) {
+  # Prep
+  based_on <- match.arg(based_on, choices = c("deployments","sequences","media"))
+  x <- ctdp_interval(x)
+  
+  # Get times
+  if(based_on == "deployments"){
+    times <- x$deployments$deployment_interval
+    starts <- int_start(times)
+    ends   <- int_end(times)
+  }
+  if(based_on == "sequences"){
+    times <- x$sequences$sequence_interval
+    starts <- int_start(times)
+    ends   <- int_end(times)
+  }
+  if(based_on == "media"){
+    times <- x$media$media_timestamp
+    starts <- int_start(times)
+    ends   <- int_end(times)
+  }
+  
+  # Get range
+  trange <- range(starts)
+  trange[2] <- max(ends)
+  
+  # Return
+  return(trange)
+}
\ No newline at end of file
diff --git a/R/time_zone.r b/R/time_zone.r
new file mode 100644
index 0000000000000000000000000000000000000000..202113ee41ed855ba91d2db410d2b9a9e57c410f
--- /dev/null
+++ b/R/time_zone.r
@@ -0,0 +1,12 @@
+#' Retrieve the time zone of a ctdp object
+#' @param x an object of class \code{\link{ctdp}}
+#' @return a \code{character} string with the time zone information of the date-time objects in \code{x}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @examples \dontrun{
+#' time_zone(camsample)
+#' }
+#' @export
+time_zone <- function(x) {
+  x$settings$tz
+}
\ No newline at end of file
diff --git a/R/utc_offset.r b/R/utc_offset.r
new file mode 100644
index 0000000000000000000000000000000000000000..8f7cc0bb4a9acde9da6a9ad967b1c122fe423fa1
--- /dev/null
+++ b/R/utc_offset.r
@@ -0,0 +1,15 @@
+#' @title Get the offset in hours from UTC
+#' @description Get the offset in hours from UTC
+#' @param t an object of class \code{POSIXct}
+#' @author Henjo de Knegt
+#' @seealso NULL
+#' @return a \code{numeric} with the offset (in hours) compared to UTC: negative is west of UTC.
+#' @examples \dontrun{
+#'   utc_offset(Sys.time())
+#' }
+#' @export
+utc_offset <- function(t) {
+  tutc <- force_tz(t, tzone = "UTC")
+  tdiff <- as.numeric(difftime(tutc, t, units = "hours")) # tutc - t, as tutc is forced to utc
+  return(tdiff)
+}
\ No newline at end of file
diff --git a/R/write_tutorial.r b/R/write_tutorial.r
new file mode 100644
index 0000000000000000000000000000000000000000..6764ec493b1aa3aa4889f2a593479531d2d29283
--- /dev/null
+++ b/R/write_tutorial.r
@@ -0,0 +1,129 @@
+#' @title Create a tutorial in html format
+#' @description Create a tutorial in html format
+#' @details NULL
+#' @param x an object of class \code{ctdp}
+#' @param fileName an optional filename for the resultant html file, defaults to \code{NULL}
+#' @param title an optional title for the tutorial, defaults to "Package ctdp - tutorial"
+#' @param focalSpecies \code{character} name of the species to focus on, see \code{\link{taxon_id}}. Defaults to NULL, in which case the tutorial will focus on the 3 most abundant mammalian species.
+#' @return a .html tutorial is being saved to the working directory, with the filename of ctdp zip that was loaded (with suffix "_tutorial.html").
+#' @author Henjo de Knegt
+#' @seealso \code{\link{ctdp}}
+#' @examples NULL
+#' @export
+write_tutorial <- function(x,
+                           fileName = NULL,
+                           title = "Package ctdp - tutorial",
+                           overwrite = TRUE,
+                           focalSpecies = NULL,
+                           start = NULL,
+                           end = NULL) {
+  # Get project properties
+  fileNameInfo <- pathProperties(x)
+  zipName <- str_remove(fileNameInfo$fileFolder, pattern = ".zip")
+  
+  # Get folder/file to write tutorial to
+  if(is.null(fileName)){
+    saveFileName <- paste0(zipName,"_tutorial.html")
+    saveFolder <- getwd()
+  }else{
+    fileNameInfo <- pathProperties(fileName)
+    saveFileName <- str_remove(fileNameInfo$fileFolder, pattern = ".html")
+    saveFileName <- paste0(saveFileName,".html")
+    saveFolder <- fileNameInfo$path
+  }
+  # saveFileName
+  # saveFolder
+  
+  # Get focalSpecies as 3 most frequently observed mammalian species (if NULL)
+  if(is.null(focalSpecies)){
+    # Count most frequent 3 mammalian species
+    focalSpecies <- x$observations %>% 
+      left_join(x$taxonomy, by = "taxonID") %>% 
+      select(observationType, count, class, order, vernacularNames.en) %>% 
+      filter(observationType == "animal") %>% 
+      filter(class == "Mammalia") %>% 
+      drop_na() %>% 
+      group_by(vernacularNames.en) %>% 
+      summarise(count = sum(count)) %>% 
+      arrange(desc(count)) %>% 
+      slice_head(n = 3L) %>% 
+      pull(vernacularNames.en)
+  }
+  
+  # create temp folder and set wd in it
+  tmpDir <- tempdir(check = TRUE)
+  if(dir.exists(tmpDir) == FALSE) { stop("temporary directory not found") }
+  orgWD <- getwd()
+  setwd(tmpDir)
+  cat("Used temporary directory: ", gsub('"', "", gsub("\\\\", "/", tmpDir)), "\n")
+  
+  # Get parms list
+  parms <- list(path = NA,
+                data = x,
+                title = title,
+                focalSpecies = focalSpecies,
+                start = ifelse(!is.null(start), start, NA),
+                end = ifelse(!is.null(end), end, NA))
+  
+  # Copy Rmd file to tmpDir
+  file.copy(from = system.file("extdata", "ctdp_tutorial.Rmd", package = "ctdp"),
+            to = tmpDir,
+            overwrite = TRUE)
+  
+  # Local functions to catch result from rendering
+  .f_call <- function(f, ...) {
+    fArgList = list(...)
+    y = do.call(f, args=fArgList)
+    return(y)
+  }
+  .f_try = function(f, ...) {
+    # https://stackoverflow.com/questions/4948361/how-do-i-save-warnings-and-errors-as-output-from-a-function
+    warn <- err <- NULL
+    value <- withCallingHandlers(
+      tryCatch(.f_call(f, ...), error=function(e) {
+        err <<- e
+        NULL
+      }), warning=function(w) {
+        warn <<- w
+        invokeRestart("muffleWarning")
+      })
+    
+    # Return named list with value, warning and error
+    list(value=value,
+         warning=warn,
+         error=err)
+  }
+  .f_render <- function(p){
+    rmarkdown::render(input = "ctdp_tutorial.Rmd",
+                      output_format  = "html_document",
+                      output_file = "ctdp_tutorial.html",
+                      params = p,
+                      clean = FALSE)
+    return(TRUE)
+  }
+  
+  # Render tutorial (in trycatch, in order to catch any errors or warnings)
+  result <- FALSE
+  result <- .f_try(.f_render, p = parms)
+
+  # set back WD
+  setwd(orgWD)
+  
+  # Copy output html back to wd with new filename
+  if(is.null(result$error)){
+    # Copy
+    file.copy(from = file.path(tmpDir, "ctdp_tutorial.html"),
+              to = file.path(saveFolder, saveFileName),
+              overwrite = overwrite)
+
+    # Cat 
+    cat("Tutorial written to:",file.path(saveFolder, saveFileName),"\n")
+    
+    # Remove ctdp_tutorial.Rmd in tmp dir
+    file.remove(file.path(tmpDir, "ctdp_tutorial.Rmd"))
+    file.remove(file.path(tmpDir, "ctdp_tutorial.html"))
+    file.remove(file.path(tmpDir, "ctdp_tutorial.knit.md"))
+  }else{
+    stop(paste("Error occurred, see dir:",tmpDir))
+  }
+}
\ No newline at end of file
diff --git a/README.Rmd b/README.Rmd
new file mode 100644
index 0000000000000000000000000000000000000000..2586403a265e4d1f8d37758d383df59327bf31b7
--- /dev/null
+++ b/README.Rmd
@@ -0,0 +1,28 @@
+---
+output: github_document
+---
+
+<!-- README.md is generated from README.Rmd. Please edit that file -->
+
+```{r, echo = FALSE}
+knitr::opts_chunk$set(
+  collapse = TRUE,
+  comment = "#>",
+  fig.path = "man/figures/"
+)
+```
+
+# Package ctdp
+
+ctdp - an R package to load and process camera-trap data in [camtrap-dp](https://tdwg.github.io/camtrap-dp/) format
+
+# Documentation
+
+Documentation is being worked on, for now see [here](https://wec.wur.nl/r/ctdp/).
+
+Or, render the tutorial yourself via:
+
+```{r, echo = TRUE, eval = FALSE}
+write_tutorial(camsample)
+```
+
diff --git a/README.md b/README.md
index ca71d1c8638c998f6659206f69e9781b1c08c32d..dc0290de69b96c89dde3a1967069381321929082 100644
--- a/README.md
+++ b/README.md
@@ -1,92 +1,18 @@
-# ctdp
 
+<!-- README.md is generated from README.Rmd. Please edit that file -->
 
+# Package ctdp
 
-## Getting started
+ctdp - an R package to load and process camera-trap data in
+[camtrap-dp](https://tdwg.github.io/camtrap-dp/) format
 
-To make it easy for you to get started with GitLab, here's a list of recommended next steps.
+# Documentation
 
-Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
+Documentation is being worked on, for now see
+[here](https://wec.wur.nl/r/ctdp/).
 
-## Add your files
+Or, render the tutorial yourself via:
 
-- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
-- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
-
-```
-cd existing_repo
-git remote add origin https://git.wur.nl/camtrap/ctdp.git
-git branch -M main
-git push -uf origin main
+``` r
+write_tutorial(camsample)
 ```
-
-## Integrate with your tools
-
-- [ ] [Set up project integrations](https://git.wur.nl/camtrap/ctdp/-/settings/integrations)
-
-## Collaborate with your team
-
-- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
-- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
-- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
-- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
-- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
-
-## Test and Deploy
-
-Use the built-in continuous integration in GitLab.
-
-- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
-- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
-- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
-- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
-- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
-
-***
-
-# Editing this README
-
-When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
-
-## Suggestions for a good README
-Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
-
-## Name
-Choose a self-explaining name for your project.
-
-## Description
-Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
-
-## Badges
-On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
-
-## Visuals
-Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
-
-## Installation
-Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
-
-## Usage
-Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
-
-## Support
-Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
-
-## Roadmap
-If you have ideas for releases in the future, it is a good idea to list them in the README.
-
-## Contributing
-State if you are open to contributions and what your requirements are for accepting them.
-
-For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
-
-You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
-
-## Authors and acknowledgment
-Show your appreciation to those who have contributed to the project.
-
-## License
-For open source projects, say how it is licensed.
-
-## Project status
-If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
diff --git a/_pkgdown.yml b/_pkgdown.yml
new file mode 100644
index 0000000000000000000000000000000000000000..39c1aefac74d80c314de5366d60728f2e9b5f38e
--- /dev/null
+++ b/_pkgdown.yml
@@ -0,0 +1,5 @@
+template:
+  bootstrap: 5
+  bootswatch: flatly
+code:
+  width: 130
diff --git a/data/camsample.rda b/data/camsample.rda
new file mode 100644
index 0000000000000000000000000000000000000000..071462eb6d88d2f42d4ba6073d328b0e453e91cc
Binary files /dev/null and b/data/camsample.rda differ
diff --git a/data/ctdp_mainCols.rda b/data/ctdp_mainCols.rda
new file mode 100644
index 0000000000000000000000000000000000000000..814e145ae20939f21ab08f8192c0ed75758e769c
Binary files /dev/null and b/data/ctdp_mainCols.rda differ
diff --git a/inst/extdata/ctdp-structure.png b/inst/extdata/ctdp-structure.png
new file mode 100644
index 0000000000000000000000000000000000000000..1301abf089ea378a0d5640c2700a747f31d793b4
Binary files /dev/null and b/inst/extdata/ctdp-structure.png differ
diff --git a/inst/extdata/ctdp_tutorial.Rmd b/inst/extdata/ctdp_tutorial.Rmd
new file mode 100644
index 0000000000000000000000000000000000000000..341d440e479fbab57a24db9333c9bff645445522
--- /dev/null
+++ b/inst/extdata/ctdp_tutorial.Rmd
@@ -0,0 +1,595 @@
+---
+params:
+  path: NA
+  data: NA
+  title: NA
+  focalSpecies: NA
+  start : NA
+  end: NA
+output:
+  html_document:
+    toc: yes
+    toc_float: yes
+---
+
+```{r, include=FALSE}
+# load libraries
+library(ctdp)
+
+# set knitr options
+knitr::opts_chunk$set(echo = TRUE,
+                      eval = TRUE,
+                      warning = FALSE,
+                      message = FALSE)
+
+# options for width
+options(width = 100)
+
+# get elements from params
+title <- params$title
+dat <- params$data
+path <- params$path
+focalSpecies <- params$focalSpecies
+start <- params$start
+end <- params$end
+
+# Checks on inputs
+if(is.na(path) & is.na(dat)) { stop("supply either path or dat") }
+if(!is.na(dat) & is.na(path)) {
+  path <- dat$settings$path
+}
+if(is.na(dat)) {
+  dat <- read_ctdp(path)
+}
+```
+
+---
+title: `r title`
+subtitle: `r paste0("Project ", pathProperties(path)$fileFolder)`
+date: `r Sys.Date()`
+---
+
+<!-- ######################################################################################## -->
+# Installing the package
+
+The `ctdp` package is an R package to load and process camera-trap data stored in  [camtrap-dp](https://tdwg.github.io/camtrap-dp/) format. The package builds upon the [frictionless](https://docs.ropensci.org/frictionless/) and [camtraptor](https://inbo.github.io/camtraptor/) packages, which first need to be installed (using the `devtools` package, which may need installation first) from github:
+
+```{r, echo=TRUE, eval=FALSE}
+devtools::install_github("frictionlessdata/frictionless-r")
+devtools::install_github("inbo/camtraptor")
+```
+
+Then, install the `ctdp` package from the WUR gitlab repo:
+
+```{r, echo=TRUE, eval=FALSE}
+devtools::install_gitlab(repo = "camtrap/ctdp", host = "git.wur.nl")
+```
+
+<!-- ######################################################################################## -->
+# The ctdp package and data structure
+
+The main data structure of the `ctdp` package is an object of class `ctdp`, which essentially is a named `list` with a "settings" slot (a list with settings), and 6 `tibbles`, which together form a relational database: 
+
+![](`r system.file("extdata", "ctdp-structure.png", package = "ctdp")`)
+
+<!-- ######################################################################################## -->
+# Loading data {.tabset .tabset-fade}
+
+## Example dataset
+
+The `ctdp` package contains an example dataset: `camsample`:
+
+```{r}
+camsample
+```
+
+## Loading a camtrap-dp export
+
+A camtrap-dp export can be loaded Via the function `read_ctdp`
+
+single file (.zip)
+
+unzipped folder
+
+see function `set_GMT_offset`
+
+```{r}
+set_GMT_offset(-5)
+```
+
+<pre class="r"><code class="hljs">dat &lt;- read_ctdp("`r path`",
+                 tz = "`r time_zone(dat)`")</code></pre>
+
+```{r}
+dat
+```
+
+## Camtraptor conversion
+
+Using function `as_ctdp` you can convert an object read via the `read_camtrap_dp` of the `camptraptor` package into an object of class `ctdp`:
+
+```{r, eval = FALSE, echo = TRUE}
+x <- read_camtrap_dp("C:/data/ctdp/bcnm-mammal-monitoring-team-20230223112118/datapackage.json",
+                     tz = "Etc/GMT+5")
+dat <- as_ctdp(x)
+```
+               
+## Loading multiple exports
+
+Todo: function `read_ctdp_list` to load a series of exports into a single `ctdp` object.
+
+<!-- ######################################################################################## -->
+# Integrity checks {.tabset .tabset-fade}
+
+## Overview
+
+function `check_integrity` function will become available which will be a wrapper function perfoming various checks, see "Specifics" tab
+
+## Specifics
+
+```{r}
+check_annotations(dat)
+
+test <- check_deployments(dat)
+test %>% filter(nr_false != 0) %>% slice_head(n=1) %>% str()
+
+check_duplicates(dat)
+
+check_joins(dat)
+```
+
+<!-- ######################################################################################## -->
+# Retrieving information {.tabset .tabset-fade}
+
+## General elements
+
+Several functions allow you to retrieve general information from an object of class `ctdp`:
+
+```{r}
+is_ctdp(dat)
+is.na(dat)
+time_zone(dat)
+time_range(dat)
+primary_keys(dat)
+has_media(dat)
+column_names(dat)
+```
+
+## Locations
+
+Information on camera trap locations can be retrieved in different, equivalent, ways:
+
+```{r, eval = FALSE}
+locations(dat)
+get_table(dat, "locations")
+dat$locations
+```
+
+For example:
+
+```{r}
+locations(dat)
+```
+
+Retrieving the locations information without the `longitude` and `latitude` columns:
+
+```{r}
+locations(dat, addLonlat = FALSE)
+```
+
+## Deployments
+
+Information on camera trap deployments can be retrieved in different, equivalent, ways:
+
+```{r, eval = FALSE}
+deployments(dat)
+get_table(dat, "deployments")
+dat$deployments
+```
+
+For example:
+
+```{r}
+deployments(dat)
+```
+
+## Sequences
+
+Information on sequences can be retrieved in different, equivalent, ways:
+
+```{r, eval = FALSE}
+sequences(dat)
+get_table(dat, "sequences")
+dat$sequences
+```
+
+For example:
+
+```{r}
+sequences(dat)
+```
+
+## Observations
+Information on observations can be retrieved in different, equivalent, ways:
+
+```{r, eval = FALSE}
+observations(dat)
+get_table(dat, "observations")
+dat$observations
+```
+
+For example:
+
+```{r}
+observations(dat)
+```
+
+## Taxonomy
+
+Information on the used taxonomy can be retrieved in different, equivalent, ways:
+
+```{r, eval = FALSE}
+taxonomy(dat)
+get_table(dat, "taxonomy")
+dat$taxonomy
+```
+
+For example:
+
+```{r}
+taxonomy(dat)
+```
+
+To retrieve the classes, orders, species names, and taxon identifiers:
+
+```{r}
+taxon_classes(dat)
+taxon_orders(dat)
+taxon_species(dat)
+taxon_id(dat)
+```
+
+To get the taxon identifiers (column "taxonID") of specific species:
+
+```{r}
+taxon_id(dat, spp = "lowland paca")
+```
+
+The function also works with variations in writing:
+
+```{r}
+taxon_id(dat, spp = "lowland paca")
+taxon_id(dat, spp = "LowlandPaca")
+taxon_id(dat, spp = "_LowLand_ PaCa")
+```
+
+## Media
+
+Whether or not the object has media data included can be checked with the `has_media` function:
+
+```{r}
+has_media(dat)
+```
+
+Information on media can be retrieved in different, equivalent, ways:
+
+```{r, eval = FALSE}
+media(dat)
+get_table(dat, "media")
+dat$media
+```
+
+For example:
+
+```{r}
+media(dat)
+```
+
+<!-- ######################################################################################## -->
+# Basic manipulations {.tabset .tabset-fade}
+
+## General
+
+```{r}
+dat %>% 
+  ctdp_interval() %>% 
+  distinct() %>% 
+  drop_na_cols() %>% 
+  drop_media()
+```
+
+`drop_columns` removes all columns except those listed in data `ctdp_mainCols`
+
+## Adding location information
+
+The `add_location_info` function allows adding information to the `locations` table (automatically matched by the identifier and other matching columns:
+
+```{r}
+datInfo <- locations(dat)
+datInfo <- datInfo %>% 
+  mutate(group = str_sub(locationName, 1, 5))
+dat <- add_location_info(dat, datInfo)
+locations(dat)
+```
+
+<!-- ######################################################################################## -->
+# Filters and selectors {.tabset .tabset-fade}
+
+## Based on station properties
+
+The `filter_station` allows to filter an object of class `ctdp` by the properties of a camera station:
+
+```{r}
+filter_station(dat, transect == 1)
+```
+
+## filter based on time range
+
+The `filter_timerange` allows to truncate an object of class `ctdp` by specifying start and end points in time (see the `orders` argument for how to specify these):
+
+```{r}
+filter_timerange(dat, start = "2016/1/1", end = "2017/1/1")
+```
+
+## Strip columns
+
+Columns that are not part of the main columns as specified in the dataset `ctdp_mainCols` can be removed using `drop_columns`:
+
+```{r}
+drop_columns(dat)
+```
+
+<!-- ######################################################################################## -->
+# Exploration {.tabset .tabset-fade}
+
+## Locations
+
+Locations can be plotted on a leaflet map using function `plot_locations`:
+
+```{r, eval = FALSE}
+plot_locations(dat)
+```
+
+```{r, echo = FALSE, out.width = '100%'}
+p <- plot_locations(dat, doPlot = FALSE)
+p
+```
+
+## Time coverage
+
+The `plot_time_coverage` plot the time coverage of the depolyments:
+
+```{r, echo = TRUE, eval = FALSE}
+plot_time_coverage(dat)
+```
+```{r, echo = FALSE, eval = TRUE}
+p <- plot_time_coverage(dat, doPlot = FALSE)
+print(p)
+```
+
+or, when grouping by some column (y-axis ordering and colour):
+
+```{r}
+plot_time_coverage(dat, grouping = "transect")
+```
+
+```{r, echo = TRUE, eval = FALSE}
+plot_time_coverage(dat, grouping = "transect")
+```
+```{r, echo = FALSE, eval = TRUE}
+p <- plot_time_coverage(dat, grouping = "transect", doPlot = FALSE)
+print(p)
+```
+
+## Annotation status
+
+Some functions allow you to explore the annotation status: `plot_status`, `summarise_deployments` and `fraction_annotated`:
+```{r, echo = TRUE, eval = FALSE}
+plot_status(dat)
+```
+```{r, echo = FALSE, eval = TRUE}
+p <- plot_status(dat, doPlot = FALSE)
+p
+```
+
+or, when grouping by some column (y-axis ordering and colour):
+
+```{r, echo = TRUE, eval = FALSE}
+plot_status(dat, grouping = "transect")
+```
+```{r, echo = FALSE, eval = TRUE}
+p <- plot_status(dat, grouping = "transect", doPlot = FALSE)
+p
+```
+
+The status of deployments (or other groupings) can also be obtained in table form, using the `summarise_deployments` function:
+
+```{r}
+summarise_deployments(dat)
+```
+
+The `datatable` argument specifies whether or not to view the table in an interactive datatable in the viewer:
+
+```{r, echo = TRUE, eval = FALSE}
+summarise_deployments(dat, datatable = TRUE)
+```
+
+```{r, echo = FALSE, eval = TRUE}
+p <- summarise_deployments(dat, datatable = FALSE)
+p %>% 
+      mutate(effort = round(effort, 3),
+             fracAnnotated = round(fracAnnotated, 3)) %>% 
+      datatable(caption = "Deployment summary",
+                style = "auto")
+```
+
+Function `summarise_deployment` calls other function `fraction_annotated`:
+
+```{r}
+fraction_annotated(dat)
+```
+
+<!-- ######################################################################################## -->
+# Analyses {.tabset .tabset-fade}
+
+## Effort
+
+To plot the effort over time (defaults to a dynamic plot):
+
+```{r, out.width = '100%'}
+plot_effort(dat)
+```
+
+or, static ggplot plot, pass argument `dynamic = FALSE`, i.e. `plot_effort(dat, dynamic = FALSE)`.
+
+To get a point estimate: the number of active camera stations at a given time:
+
+```{r}
+point_effort(dat, "2017/6/21 12:00:00")
+```
+
+Tthe `calc_effort` function is the main function computing effort (total number of camera-trap days), which optionally takes arguments:
+
+* `by`: column name(s) of the station/deployment tables by which to group the effort computation
+* `start`,`end`: start and end timepoints to truncate the data, see function `filter_timerange`
+* `subset`: an expression to filter the camera station locations, see function `filter_station`
+
+Total effort:
+
+```{r}
+calc_effort(dat)
+```
+
+Total effort computer for each station identified by column "locationName":
+```{r}
+calc_effort(dat, by = "locationName")
+```
+
+Idem, but now for specified time range and transect:
+
+```{r}
+calc_effort(dat,
+            by = "locationName",
+            start = "2017/4/1",
+            end = "2017/8/1",
+            subset = transect == 3)
+```
+
+Another option to compute the total effort (total number of active camera-trap-days) is via the function `integrate_effort`:
+
+```{r}
+integrate_effort(dat)
+```
+
+Or, between 2 time points, integrate effort, thus compute total number of active camera-trap-days between these two points in time:
+
+```{r}
+integrate_effort(dat, start = "2017/4/1", end = "2017/8/1")
+```
+
+## Catpure rates
+
+The `captures` function is the main function to compute captures and capture rates:
+
+```{r}
+captures(dat)
+```
+
+Optionally, you can specify a taxonomic class to focus on:
+
+```{r}
+captures(dat, class = "Mammalia")
+```
+
+Or, focus on specific species (access via function `taxon_id`):
+
+```{r}
+captures(dat, species = c("Central American agouti","CollaredPeccary"))
+```
+
+Like in the computation of effort, capture rates can be computed using the arguments `by`, `start/end`, `subset` in order to group analyses by some identifier column, compute for a subset of the locations or truncate in time, e.g.:
+
+```{r}
+captures(dat,
+         species = c("Central American agouti","CollaredPeccary"),
+         by = "locationName",
+         start = "2017/4/1",
+         end = "2017/8/1",
+         subset = transect == 3)
+```
+
+## Activity patterns
+
+Activity patterns can be fitted and plotted using the `activity_ctdp` function, in a similar veign as the `captures` and `calc_effort` functions do:
+
+```{r}
+activity_ctdp(dat)
+```
+
+focussing on taxonomic class:
+
+```{r}
+activity_ctdp(dat, class = "Mammalia")
+```
+
+Or, for some specific species in a subset of the stations
+
+```{r}
+activity_ctdp(dat,
+         species = c("Central American agouti","CollaredPeccary"),
+         by = "locationName",
+         start = "2017/4/1",
+         end = "2017/8/1",
+         subset = transect == 3)
+```
+
+The function returns the data silently:
+
+```{r}
+test <- activity_ctdp(dat,
+                      species = c("Central American agouti","CollaredPeccary"),
+                      by = "locationName",
+                      start = "2017/4/1",
+                      end = "2017/8/1",
+                      subset = transect == 3)
+test
+```
+
+<!-- ######################################################################################## -->
+# Merges
+
+The `merge_tibbles` and `nest_tibbles` join all the tibbles of the relational database into 1 bit tibble, where the `merge_tibbles` results in 1 record (i.e. row) being a single _observation_, whereas when using the `nest_tibbles` function, 1 row equals to 1 _sequence_ where the observations and other information are stored as _list columns_.
+
+```{r}
+merge_tibbles(dat) # 1 row = 1 observation
+```
+
+```{r}
+nest_tibbles(dat) # 1 row = 1 sequence
+```
+
+To unnest a list-column, e.g. here the "locations" column:
+
+```{r}
+nest_tibbles(dat) %>% 
+  unnest(locations)
+```
+
+<!-- ######################################################################################## -->
+# Exports {.tabset .tabset-fade}
+
+## Tutorial
+
+this tutorial was generated using the command:
+
+```{r, echo=TRUE, eval=FALSE}
+write_tutorial(dat)
+```
+
+
+## Excel sheets
+
+todo
+
+<!-- ######################################################################################## -->
+
diff --git a/man/activity_ctdp.Rd b/man/activity_ctdp.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..e4cf6ad627a04184378cce8b3ee84e4973f07b21
--- /dev/null
+++ b/man/activity_ctdp.Rd
@@ -0,0 +1,73 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/activity_ctdp.r
+\name{activity_ctdp}
+\alias{activity_ctdp}
+\title{title}
+\usage{
+activity_ctdp(
+  x,
+  doTimelapse = FALSE,
+  doUnclassified = TRUE,
+  doHuman = TRUE,
+  class = NULL,
+  species = NULL,
+  doPlot = TRUE,
+  byTaxon = TRUE,
+  n_min = 25,
+  start = NULL,
+  end = NULL,
+  orders = "\%Y/\%m/\%d",
+  subset = NULL,
+  by = NULL
+)
+}
+\arguments{
+\item{x}{an object of class \code{ctdp}}
+
+\item{doTimelapse}{whether or not to include time-lapse sequences in the analyses, defaults to \code{FALSE}}
+
+\item{doUnclassified}{whether or not to include unclassified sequences in the analyses, defaults to \code{TRUE}}
+
+\item{doHuman}{whether or not to include humans in the analyses, defaults to \code{TRUE}}
+
+\item{class}{optional \code{character} string with the class of species (e.g. "Mammalia") to focus analyses on, defaults to \code{NULL}}
+
+\item{species}{an optional \code{character} vector with name of the species (e.g. "Ocelot") to focus on, defaults to \code{NULL}}
+
+\item{doPlot}{\code{logical} whether or not to plot the results, defaults to \code{TRUE}}
+
+\item{byTaxon}{\code{logical} whether or not to fit an activity pattern per taxon, defaults to \code{TRUE}}
+
+\item{n_min}{\code{integer,numeric} minimum number of records for activity pattern to be fitted, defaults to 25}
+
+\item{start}{\code{character} datetime: sequences are kept when the start of the sequences is > start, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{end}{\code{character} datetime: sequences are kept when the start of the sequences is < end, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{orders}{\code{character} string with the orders passed on to \code{\link{parse_date_time}}, defaults to "Ymd"}
+
+\item{subset}{arguments used for filtering}
+
+\item{by}{an optional \code{character} vector specifying the column names by which to group analyses, defaults to \code{NULL}}
+}
+\value{
+invisibly returns a nested tibble with the fitted activity patterns per group
+}
+\description{
+title
+}
+\examples{
+\dontrun{
+activity_ctdp(camsample)
+activity_ctdp(camsample, class = "Mammalia")
+activity_ctdp(camsample, species = c("CollaredPeccary","OCELOT"))
+activity_ctdp(camsample, species = c("CollaredPeccary","OCELOT"), by = "locationName")
+activity_ctdp(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+
+test <- activity_ctdp(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+test
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/add_location_info.Rd b/man/add_location_info.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..d0c3f7998328d0e9c61c2dd80d21d3f754f50fd4
--- /dev/null
+++ b/man/add_location_info.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/add_location_info.r
+\name{add_location_info}
+\alias{add_location_info}
+\title{Add metadata to camtrap locations}
+\usage{
+add_location_info(x, info)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{info}{a \code{tibble} with information to add to the locations of \code{x}. Will be matched to \code{x$locations} by \code{left_join}}
+}
+\value{
+x an object of class \code{\link{ctdp}}
+}
+\description{
+Add metadata to camtrap locations
+}
+\examples{
+NULL
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/argcheck_order.Rd b/man/argcheck_order.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..26f7c4ea063c6370ab6c3183f72a8a9a260d9f8c
--- /dev/null
+++ b/man/argcheck_order.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/order_argcheck.r
+\name{argcheck_order}
+\alias{argcheck_order}
+\title{Argument check on order}
+\usage{
+argcheck_order(x, order)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{order}{optional \code{character} string with the order of species (e.g. "Carnivora") to focus analyses on, defaults to \code{NULL}}
+}
+\value{
+same as \code{x} or an error
+}
+\description{
+Argument check on order
+}
+\examples{
+\dontrun{
+argcheck_order(camsample, order = "Primates")
+}
+}
+\seealso{
+\code{\link{taxon_orders}}
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/bcnm2017.Rd b/man/bcnm2017.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..c782c28e74b2ee6434f374dfa1dc240a9af285e2
--- /dev/null
+++ b/man/bcnm2017.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/data.r
+\docType{data}
+\name{bcnm2017}
+\alias{bcnm2017}
+\title{Example ctdp dataset}
+\format{
+an object of class \code{ctdp} with the observations from 6 camera stations at the Barro Colorado Nature Monument (BCNM) during 2017.
+}
+\description{
+Example ctdp dataset
+}
+\author{
+Henjo de Knegt
+}
+\keyword{datasets}
diff --git a/man/by_argcheck.Rd b/man/by_argcheck.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..5a0b1a01598396f45ffa282e8535b12f1cd5ab14
--- /dev/null
+++ b/man/by_argcheck.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/by_argcheck.r
+\name{by_argcheck}
+\alias{by_argcheck}
+\title{Argument check on by}
+\usage{
+by_argcheck(x, by)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{by}{an optional \code{character} vector specifying the column names by which to group analyses, defaults to \code{NULL}}
+}
+\value{
+same as \code{x} or an error
+}
+\description{
+Argument check on by
+}
+\examples{
+\dontrun{
+by_argcheck(camsample, by = c("locationName","array"))
+}
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/by_void.Rd b/man/by_void.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..dc6aea23a99e37bf730aeea6e6d6423cfab7b2c9
--- /dev/null
+++ b/man/by_void.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/by_void.r
+\name{by_void}
+\alias{by_void}
+\title{Specify grouping columns}
+\usage{
+by_void(by = NULL)
+}
+\arguments{
+\item{by}{an optional \code{character} vector specifying the column names by which to group analyses, defaults to \code{NULL}}
+}
+\description{
+Specify grouping columns
+}
+\examples{
+NULL
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/calc_effort.Rd b/man/calc_effort.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..8cf24a51302b784ae5b54a6f805432096c458e1e
--- /dev/null
+++ b/man/calc_effort.Rd
@@ -0,0 +1,50 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/calc_effort.r
+\name{calc_effort}
+\alias{calc_effort}
+\title{Calculate effort}
+\usage{
+calc_effort(
+  x,
+  start = NULL,
+  end = NULL,
+  orders = "\%Y/\%m/\%d",
+  subset = NULL,
+  by = NULL
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{start}{\code{character} datetime: sequences are kept when the start of the sequences is > start, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{end}{\code{character} datetime: sequences are kept when the start of the sequences is < end, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{orders}{\code{character} string with the orders passed on to \code{\link{parse_date_time}}, defaults to "Ymd"}
+
+\item{subset}{optional expression for subsetting passed on to \code{\link{filter_station}}}
+
+\item{by}{an optional \code{character} vector specifying the column names by which to group analyses, defaults to \code{NULL}}
+
+\item{groupBy}{an optional \code{character} vector with the column name(s) of the grouping variables (from the locations and deployments tables)}
+}
+\value{
+a \code{tibble} with columns specified in \code{by} and column "effort", which holds the total camera-days effort.
+}
+\description{
+Calculate effort
+}
+\examples{
+\dontrun{
+calc_effort(camsample)
+calc_effort(camsample, by = "locationName")
+calc_effort(camsample, start = "2017/4/1", end = "2017/8/1")
+calc_effort(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+}
+}
+\seealso{
+\code{\link{ctdp}}, \code{\link{filter_station}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/captures.Rd b/man/captures.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..16f5cc93f489a28cc55721ce17ee0eafcaf39908
--- /dev/null
+++ b/man/captures.Rd
@@ -0,0 +1,60 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/captures.r
+\name{captures}
+\alias{captures}
+\title{Get captures}
+\usage{
+captures(
+  x,
+  onlyAnimal = TRUE,
+  class = NULL,
+  species = NULL,
+  start = NULL,
+  end = NULL,
+  orders = "\%Y/\%m/\%d",
+  subset = NULL,
+  by = NULL
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{onlyAnimal}{\code{logical}, defaults to \code{TRUE}, indicating whether or not to only focus on \code{observationType == "animal"}}
+
+\item{class}{\code{character} string with the class of species to compute the captures for (e.g. "Mammalia"), defaults to NULL}
+
+\item{species}{an optional \code{character} vector with name of the species (e.g. "Ocelot") to focus on, defaults to \code{NULL}}
+
+\item{start}{\code{character} datetime: sequences are kept when the start of the sequences is > start, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{end}{\code{character} datetime: sequences are kept when the start of the sequences is < end, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{orders}{\code{character} string with the orders passed on to \code{\link{parse_date_time}}, defaults to "Ymd"}
+
+\item{subset}{arguments used for filtering}
+
+\item{by}{an optional \code{character} vector specifying the column names by which to group analyses, defaults to \code{NULL}}
+}
+\value{
+a \code{tibble} with counts per species (with columns added: "observationType","class","order", as well as the columns specified in \code{by}).
+}
+\description{
+Get captures
+}
+\examples{
+\dontrun{
+captures(camsample)
+captures(camsample, class = "Mammalia")
+captures(camsample, species = c("CollaredPeccary","OCELOT"))
+captures(camsample, by = "locationName")
+captures(camsample, species = c("CollaredPeccary","OCELOT"), by = "locationName")
+captures(camsample, start = "2017/4/1", end = "2017/8/1")
+captures(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+}
+}
+\seealso{
+\code{\link{ctdp}},\code{\link{taxon_id}},  \code{\link{filter_timerange}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/check_annotations.Rd b/man/check_annotations.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..288760226dfa3913034e2e72594bb3e0a76d5903
--- /dev/null
+++ b/man/check_annotations.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/check_annotations.r
+\name{check_annotations}
+\alias{check_annotations}
+\title{Check integrity of observation types}
+\usage{
+check_annotations(x)
+}
+\arguments{
+\item{x}{a \code{ctdp} object}
+}
+\value{
+a named \code{list} with elements:
+\itemize{
+  \item \code{noNAs}: whether or not \code{observationType} column contains NAs
+  \item \code{isFactor}: whether or not \code{observationType} columns is a factor
+  \item \code{correctLevels}: whether or not the levels are one of: "animal","human","vehicle","blank","unknown","unclassified"
+}
+logical values indicating whether each table in \code{x} contains distinct (value TRUE) values or duplicates (value FALSE).
+}
+\description{
+Check integrity of observation types
+}
+\examples{
+\dontrun{
+check_annotations(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/check_deployments.Rd b/man/check_deployments.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..4d702275848df18a291557a3ae9f68a83f0e4c86
--- /dev/null
+++ b/man/check_deployments.Rd
@@ -0,0 +1,51 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/check_deployments.r
+\name{check_deployments}
+\alias{check_deployments}
+\title{Check integrity of deployments}
+\usage{
+check_deployments(x, duration_threshold = 20 * 7, sea_threshold = -6)
+}
+\arguments{
+\item{x}{a \code{ctdp} object}
+
+\item{duration_threshold}{the threshold duration, in days, above which deployments are flagged}
+
+\item{sea_threshold}{the sun elevation angle, in degrees, above the horizon below which deployment start/end are flagged}
+}
+\value{
+a \code{tibble} with information columns:
+\itemize{
+  \item \code{deploymentID}: identifier
+  \item \code{nr_false}: the number of validity check columns (see below) that contain value FALSE
+  \item \code{deployment_interval}: interval
+  \item \code{longitude,latitude}: lon/lat coordinates
+  \item \code{locationName}: name
+  \item \code{seq_start,seq_end}: timestamp of the first/last sequence in the deployment
+  \item \code{duration}: duration (in days) of the deployment
+  \item \code{sea_start,sea_end}: solar elevation angle (in degrees above horizon) at the time of start/end of deployment
+  \item \code{dt_start,dt_end}: time difference (in days) between start/end of deployment and seq_start,seq_end
+}
+as well as validity check columns (prefix "ok_"):
+\itemize{
+  \item \code{start,end}: no NAs
+  \item \code{startend}: end > start (thus length > 0 sec)
+  \item \code{lon,lat}: no NAs
+  \item \code{name}: no NAs
+  \item \code{sea_start,sea_end}: start/end of deployment when sun is >= sea_threshold
+  \item \code{duration}: difference between duration (days) <= duration_threshold
+}
+}
+\description{
+Check integrity of deployments
+}
+\seealso{
+NULL
+\dontrun{
+test <- check_deployments(camsample)
+test %>% filter(nr_false > 0)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/check_duplicates.Rd b/man/check_duplicates.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..bbdbd14be062183344a2ac6e1229d8a950e6da68
--- /dev/null
+++ b/man/check_duplicates.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/check_duplicates.r
+\name{check_duplicates}
+\alias{check_duplicates}
+\title{Check presence of duplicates}
+\usage{
+check_duplicates(x)
+}
+\arguments{
+\item{x}{a \code{ctdp} object}
+}
+\value{
+a named \code{list} logical values indicating whether each table in \code{x} contains distinct (value TRUE) values or duplicates (value FALSE).
+}
+\description{
+Check presence of duplicates
+}
+\examples{
+\dontrun{
+check_duplicates(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/check_integrity.Rd b/man/check_integrity.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..55a0846a35a8b4a84879725064a1a0c83b921e26
--- /dev/null
+++ b/man/check_integrity.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/check_integrity.r
+\name{check_integrity}
+\alias{check_integrity}
+\title{Check the data integrity of a ctdp object}
+\usage{
+check_integrity(x)
+}
+\arguments{
+\item{x}{a \code{ctdp} object}
+}
+\description{
+Check the data integrity of a ctdp object
+}
+\examples{
+\dontrun{
+check_integrity(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/check_joins.Rd b/man/check_joins.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..c1f9f3e8b009656daf42285bbc79a9dd5d44b63a
--- /dev/null
+++ b/man/check_joins.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/check_joins.r
+\name{check_joins}
+\alias{check_joins}
+\title{Check data integrity of joins}
+\usage{
+check_joins(x)
+}
+\arguments{
+\item{x}{a \code{ctdp} object}
+}
+\value{
+a \code{list} with 4 elements:
+\itemize{
+  \item \code{allunique}: a \code{logical} indicating whether or not all primary key columns contain unique values
+  \item \code{alljoins}: a \code{logical} indicating whether or not the tibbles have matching keys
+  \item \code{unique}: a \code{tibble} with extra info on whether which element in \code{x} contains unique primary keys
+  \item \code{anti_joins}: a named \code{list} with information on anti-joins (a character vector of length 0 if there are not anti-joins)
+}
+}
+\description{
+Check data integrity of joins
+}
+\examples{
+\dontrun{
+check_joins(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/class_argcheck.Rd b/man/class_argcheck.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..c31afaeddff80f45e17afd439aedfd94021c8711
--- /dev/null
+++ b/man/class_argcheck.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/class_argcheck.r
+\name{class_argcheck}
+\alias{class_argcheck}
+\title{Argument check on class}
+\usage{
+class_argcheck(x, class)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{class}{optional \code{character} string with the class of species (e.g. "Mammalia") to focus analyses on, defaults to \code{NULL}}
+}
+\value{
+same as \code{x} or an error
+}
+\description{
+Argument check on class
+}
+\examples{
+\dontrun{
+class_argcheck(camsample, class = "Mammalia")
+}
+}
+\seealso{
+\code{\link{taxon_classes}}
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/class_void.Rd b/man/class_void.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..7705351d1b2e9d18733b05b549f0d01a3c536bfc
--- /dev/null
+++ b/man/class_void.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/class_void.r
+\name{class_void}
+\alias{class_void}
+\title{Specify class}
+\usage{
+class_void(class = NULL)
+}
+\arguments{
+\item{class}{optional \code{character} string with the class of species (e.g. "Mammalia") to focus analyses on, defaults to \code{NULL}}
+}
+\description{
+Specify class
+}
+\examples{
+NULL
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/column_names.Rd b/man/column_names.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..5a863e12a1eac250bd9fd1102e2baa1221aecf46
--- /dev/null
+++ b/man/column_names.Rd
@@ -0,0 +1,30 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/column_names.r
+\name{column_names}
+\alias{column_names}
+\title{Retrieve the column names of the ctdp tibbles}
+\usage{
+column_names(x, verbose = TRUE)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{verbose}{\code{logical}, defaults to \code{TRUE}, indicating whether or not to print information to the console}
+}
+\value{
+a named \code{list} with the names of the tibbles (returned invisibly when \code{verbose=TRUE})
+}
+\description{
+Retrieve the column names of the ctdp tibbles
+}
+\examples{
+\dontrun{
+column_names(camsample)
+  
+xnames <- column_names(camsample, verbose = FALSE)
+xnames
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/ctdp.Rd b/man/ctdp.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..9fa5a233674b03ab38e20ae24835944721727268
--- /dev/null
+++ b/man/ctdp.Rd
@@ -0,0 +1,106 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/as_ctdp.r, R/ctdp_package.r, R/distinct.r,
+%   R/print.ctdp.r, R/read_ctdp.r
+\docType{package}
+\name{as_ctdp}
+\alias{as_ctdp}
+\alias{ctdp}
+\alias{distinct.ctdp}
+\alias{print.ctdp}
+\alias{read_ctdp}
+\title{Convert a camtraptor object to ctdp object}
+\usage{
+as_ctdp(
+  x,
+  tz = "Etc/GMT-2",
+  verbose = TRUE,
+  rmEmpty = FALSE,
+  pathInfo = NULL,
+  dropMedia = FALSE
+)
+
+\method{distinct}{ctdp}(x)
+
+\method{print}{ctdp}(x)
+
+read_ctdp(
+  path,
+  tz = "Etc/GMT-2",
+  verbose = TRUE,
+  rmEmpty = FALSE,
+  dropMedia = FALSE
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{tz}{\code{character} string with time-zone code, defaults to \code{"Etc/GMT-2"}. See \code{\link{set_GMT_offset}}}
+
+\item{verbose}{\code{logical}, defaults to \code{TRUE}: printing information on progress}
+
+\item{rmEmpty}{\code{logical}, defaults to \code{TRUE}: remove columns that only contain NAs}
+
+\item{pathInfo}{file path information internally called from \code{\link{read_ctdp}}}
+
+\item{dropMedia}{\code{logical}, defaults to \code{FALSE}: whether or not to drop the media tibble.}
+
+\item{path}{\code{character} string with the path to the folder or zip-file holding the data}
+}
+\value{
+an object of class \code{\link{ctdp}}.
+
+an object of class \code{\link{ctdp}}
+
+an object of class \code{ctdp}, which is a \code{list} with elements:
+\itemize{
+  \item \code{locations}: a \code{tibble} with info on camera locations
+  \item \code{deployments}: a \code{tibble} with info on deployments
+  \item \code{sequences}: a \code{tibble} with info on sequences
+  \item \code{observations}: a \code{tibble} with info on observations
+  \item \code{media}: a \code{tibble} with info on captured images
+  \item \code{taxonomy}: a \code{tibble} with the used taxonomy table
+  \item \code{settings}: a \code{list} with settings, including the path to the folder or file with source data, time zone, whether or not the object holds timestamps as \code{Interval} objects, and whether or not the object contains the media information.
+}
+}
+\description{
+Convert a camtraptor object to ctdp object
+
+ctdp - a package to work with camtrap-dp data
+
+Remove duplicate records from the tables of a ctdp object
+
+This function loads a frictionless datapackage into an object of class \code{ctdp}, using the 'frictionless' and 'camtraptor' packages (see \url{https://inbo.github.io/camtraptor/} and \url{https://docs.ropensci.org/frictionless/}).
+}
+\details{
+Converts an object from the \code{camtraptor} package (see \url{https://inbo.github.io/camtraptor/} and \url{https://docs.ropensci.org/frictionless/}) to a \code{\link{ctdp}} object
+}
+\section{Methods (by generic)}{
+\itemize{
+\item \code{distinct(ctdp)}: Remove duplicate records from the tables of a ctdp object
+
+}}
+\section{Functions}{
+\itemize{
+\item \code{as_ctdp()}: Convert a camtraptor object to ctdp object
+
+}}
+\examples{
+NULL
+\dontrun{
+distinct(camsample)
+}
+\dontrun{
+  data(camsample)
+  camsample
+}
+}
+\seealso{
+\code{\link{read_ctdp}}, \code{\link{read_camtrap_dp}}
+
+\code{\link{ctdp}}
+
+\code{\link{as_ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/ctdp_interval.Rd b/man/ctdp_interval.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..71d399424556e8c62fcccd6552f95f26f761c69c
--- /dev/null
+++ b/man/ctdp_interval.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/ctdp_interval.r
+\name{ctdp_interval}
+\alias{ctdp_interval}
+\title{Conversion between start/end and Interval objects}
+\usage{
+ctdp_interval(x, rev = FALSE)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{rev}{\code{logical} indicating to reverse the conversion: thus when \code{TRUE} the interval object is converted back to start/end}
+}
+\value{
+an object of class \code{\link{ctdp}}
+}
+\description{
+Convert between start/end and Interval objects for deployments and sequences
+}
+\examples{
+\dontrun{
+ctdp_interval(camsample)
+ctdp_interval(camsample, rev = TRUE)
+}
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/ctdp_mainCols.Rd b/man/ctdp_mainCols.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..8d4a74ff513522c9a8494d2177a14bff78fd313c
--- /dev/null
+++ b/man/ctdp_mainCols.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/data.r
+\docType{data}
+\name{ctdp_mainCols}
+\alias{ctdp_mainCols}
+\title{The main columns of a ctdp object}
+\format{
+\code{tibble} with the main columns of a \code{ctdp} object, per table.
+}
+\description{
+The main columns of a ctdp object
+}
+\author{
+Henjo de Knegt
+}
+\keyword{datasets}
diff --git a/man/decimal_time.Rd b/man/decimal_time.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..c1ea7be427a6840494b3c602032e6e29f114ea2f
--- /dev/null
+++ b/man/decimal_time.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/decimal_time.r
+\name{decimal_time}
+\alias{decimal_time}
+\title{Convert a datetime object to decimal time}
+\usage{
+decimal_time(t, units = "hours")
+}
+\arguments{
+\item{t}{an object of class \code{POSIXct}}
+
+\item{units}{a \code{character} string with the units, defaults to \code{"hours"}, but can also be \code{"days"}}
+}
+\value{
+a \code{numeric} value with the decimal time
+}
+\description{
+Convert a datetime object to decimal time
+}
+\examples{
+\dontrun{
+  decimal_time(Sys.time())
+  decimal_time(Sys.time(), units="days")
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/deployments.Rd b/man/deployments.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..7ee7bfa70afd7311f4863ce1338569d17e4eacff
--- /dev/null
+++ b/man/deployments.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/deployments.r
+\name{deployments}
+\alias{deployments}
+\title{Retrieve the deployments table of a ctdp object}
+\usage{
+deployments(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{tibble} with the deployments table
+}
+\description{
+Retrieve the deployments table of a ctdp object
+}
+\examples{
+\dontrun{
+deployments(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/drop_columns.Rd b/man/drop_columns.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..3b50aba641aa93742da1ef8931df2c15921f4faf
--- /dev/null
+++ b/man/drop_columns.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/drop_columns.r
+\name{drop_columns}
+\alias{drop_columns}
+\title{drop columns from a ctdp object}
+\usage{
+drop_columns(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+x an object of class \code{\link{ctdp}} with columns removed (except for important columns)
+}
+\description{
+drop columns from a ctdp object
+}
+\examples{
+\dontrun{
+drop_columns(camsample)
+}
+}
+\seealso{
+\code{\link{ctdp}}, \code{\link{ctdp_mainCols}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/drop_media.Rd b/man/drop_media.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..9314009aa456c31c3800ff3aad08538afeb5b53b
--- /dev/null
+++ b/man/drop_media.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/drop_media.r
+\name{drop_media}
+\alias{drop_media}
+\title{drop the media slot of a ctdp object}
+\usage{
+drop_media(x)
+}
+\arguments{
+\item{x}{an object of class \code{ctdp}}
+}
+\description{
+drop the media slot of a ctdp object
+}
+\examples{
+\dontrun{
+drop_media(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/drop_na_cols.Rd b/man/drop_na_cols.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..f533d650c043e38fba0fdb0f436677f240f84abe
--- /dev/null
+++ b/man/drop_na_cols.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/drop_na_cols.r
+\name{drop_na_cols}
+\alias{drop_na_cols}
+\title{drop columns that only contain NAs}
+\usage{
+drop_na_cols(x)
+}
+\arguments{
+\item{x}{an object of class \code{ctdp}}
+}
+\value{
+an object of class \code{ctdp}
+}
+\description{
+drop columns that only contain NAs
+}
+\examples{
+\dontrun{
+drop_na_cols(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/effort_table.Rd b/man/effort_table.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..36c8054c5aa996446ee517719cdc15bcbce09b7d
--- /dev/null
+++ b/man/effort_table.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/effort_table.r
+\name{effort_table}
+\alias{effort_table}
+\title{Get a step function with the number of active cameras over time}
+\usage{
+effort_table(x, startend = FALSE)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{startend}{\code{logical}, defaults to FALSE, whether or not to include the endpoints of the stepfunction, or just the switches to new values.}
+}
+\value{
+a \code{tibble} with effort data
+}
+\description{
+Get a step function with the number of active cameras over time
+}
+\examples{
+\dontrun{
+effort_table(camsample)
+effort_table(camsample, startend = TRUE)
+}
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/filter_apply.Rd b/man/filter_apply.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..9b5a0715b8826663899c59160a8fca82a32c33a7
--- /dev/null
+++ b/man/filter_apply.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/filter_apply.r
+\name{filter_apply}
+\alias{filter_apply}
+\title{Internal function to apply filter expression}
+\usage{
+filter_apply(x, filter = NULL)
+}
+\arguments{
+\item{x}{a data.frame or tibble}
+
+\item{filter}{optional expression for subsetting, defaults to \code{NULL}}
+}
+\description{
+Internal function to apply filter expression
+}
+\examples{
+\dontrun{
+filter_apply(taxonomy(camsample), class == "Mammalia")
+filter_apply(locations(camsample), str_sub(locationName, 5, 5) == "3")
+}
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/filter_station.Rd b/man/filter_station.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..8f14bf6acef7a109dcb6dc035a3ffd025ae0c89e
--- /dev/null
+++ b/man/filter_station.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/filter_station.r
+\name{filter_station}
+\alias{filter_station}
+\title{Filter a ctdp object based on camera station properties}
+\usage{
+filter_station(x, subset = NULL)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{subset}{arguments used for filtering}
+}
+\value{
+x an object of class \code{\link{ctdp}}
+}
+\description{
+Filter a ctdp object based on camera station properties
+}
+\examples{
+\dontrun{
+locations(camsample)
+str_sub(locations(camsample)$locationName, 5, 5)
+filter_station(camsample, str_sub(locationName, 5, 5) == "3")
+}
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/filter_timerange.Rd b/man/filter_timerange.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..6fd10b8233cc6c85de0aac058ccc51383d6d2b26
--- /dev/null
+++ b/man/filter_timerange.Rd
@@ -0,0 +1,37 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/filter_timerange.r
+\name{filter_timerange}
+\alias{filter_timerange}
+\title{Filter a ctdp object based on a specified time range}
+\usage{
+filter_timerange(x, start = NULL, end = NULL, orders = "\%Y/\%m/\%d")
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{start}{\code{character} datetime: sequences are kept when the start of the sequences is > start, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{end}{\code{character} datetime: sequences are kept when the start of the sequences is < end, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{orders}{\code{character} string with the orders passed on to \code{\link{parse_date_time}}, defaults to "Ymd"}
+}
+\value{
+x an object of class \code{\link{ctdp}}
+}
+\description{
+Filter a ctdp object based on a specified time range
+}
+\details{
+This function first filters sequences, where sequences are kept whose START falls inside the specified interval, and then filters the other elements to the sequences that are kept. Then, the sequence and deployment intervals are truncated to [start, end]
+}
+\examples{
+\dontrun{
+  filter_timerange(x, start = "2021/6/21", end = "2022/6/21")
+}
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/filter_void.Rd b/man/filter_void.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..044f0004273c690b7a90ac7efd92da6f99ed4fad
--- /dev/null
+++ b/man/filter_void.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/filter_void.r
+\name{filter_void}
+\alias{filter_void}
+\title{Specify filter expression}
+\usage{
+filter_void(filter = NULL)
+}
+\arguments{
+\item{filter}{optional expression for subsetting, defaults to \code{NULL}}
+}
+\description{
+Specify filter expression
+}
+\examples{
+NULL
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/fraction_annotated.Rd b/man/fraction_annotated.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..0387ee2afb4f0432b140261620bcf1da40ac694a
--- /dev/null
+++ b/man/fraction_annotated.Rd
@@ -0,0 +1,62 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fraction_annotated.r
+\name{fraction_annotated}
+\alias{fraction_annotated}
+\title{Compute the fraction of sequences that is annotated}
+\usage{
+fraction_annotated(
+  x,
+  onlyMotion = TRUE,
+  omitSetupType = TRUE,
+  start = NULL,
+  end = NULL,
+  orders = "\%Y/\%m/\%d",
+  subset = NULL,
+  by = NULL
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{onlyMotion}{\code{logical}, whether or not to only use motion detection images, defaults to \code{TRUE}}
+
+\item{omitSetupType}{\code{logical}, whether or not to omit sequences that are marked as some form of setup (`observations` column `cameraSetupType` is than not NA), defaults to \code{TRUE}}
+
+\item{start}{\code{character} datetime: sequences are kept when the start of the sequences is > start, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{end}{\code{character} datetime: sequences are kept when the start of the sequences is < end, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{orders}{\code{character} string with the orders passed on to \code{\link{parse_date_time}}, defaults to "Ymd"}
+
+\item{subset}{arguments used for filtering}
+
+\item{by}{an optional \code{character} vector specifying the column names by which to group analyses, defaults to \code{NULL}}
+}
+\value{
+a tibble with summary data per deployment (or group specified in \code{by}), with columns:
+\itemize{
+  \item deploymentID, or columns specified in \code{by}
+  \item fracAnnotated: the fraction of all sequences in this group that have been annotated (observationType != "unclassified")
+  \item nrSeqs: number of sequences in this group (optionally after filter for only motion detection)
+  \item nrSeqsAnnotated: number of annotated sequences in this group (optionally after filter for only motion detection)
+  \item nrPhotos: number of photos in this group (optionally after filter for only motion detection)
+}
+}
+\description{
+Compute the fraction of sequences that is annotated
+}
+\examples{
+\dontrun{
+fraction_annotated(camsample)
+fraction_annotated(camsample, by = "locationName")
+fraction_annotated(camsample, by = "transect")
+fraction_annotated(camsample, start = "2017/4/1", end = "2017/8/1")
+fraction_annotated(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3")
+}
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/get_table.Rd b/man/get_table.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..4b8e8ed6d064accf483a95d80e4055b9aa531fa1
--- /dev/null
+++ b/man/get_table.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_table.r
+\name{get_table}
+\alias{get_table}
+\title{Retrieve a table from a ctdp object}
+\usage{
+get_table(x, table)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{table}{\code{character} name of the table to retrieve: one of "locations", "deployments", "sequences", "observations", "media", "taxonomy"}
+}
+\value{
+a \code{tibble} with the selected information table
+}
+\description{
+Retrieve a table from a ctdp object
+}
+\examples{
+\dontrun{
+get_table(camsample, table = "deployments")
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/has_media.Rd b/man/has_media.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..2801fd5890a2ae61d882dc2cb7c8850a230f435e
--- /dev/null
+++ b/man/has_media.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/has_media.r
+\name{has_media}
+\alias{has_media}
+\title{Check whether a ctdp object has media information}
+\usage{
+has_media(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{character} string with the time zone information of the date-time objects in \code{x}
+}
+\description{
+Check whether a ctdp object has media information
+}
+\examples{
+\dontrun{
+has_media(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/helpers.Rd b/man/helpers.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..a89bd148a59501566c23225a8b12df32ac5881f8
--- /dev/null
+++ b/man/helpers.Rd
@@ -0,0 +1,75 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/hidden_helper_functions.r
+\name{helpers}
+\alias{helpers}
+\alias{is.ctdp}
+\alias{pathProperties}
+\alias{list2tbl}
+\alias{dttmColNames}
+\alias{intervalColNames}
+\alias{printTimeZones}
+\alias{hardCodedColNames}
+\alias{checkCodedColNames}
+\alias{setCodedColNames}
+\alias{keycolNames}
+\alias{linkStructure}
+\alias{timestampCols}
+\alias{intervalCols}
+\alias{removeEmptyCols}
+\alias{tableInfo}
+\alias{duplicateNames}
+\alias{forceUniqueNames}
+\title{Hidden helper functions}
+\usage{
+is.ctdp(x)
+
+pathProperties(x)
+
+list2tbl(x)
+
+dttmColNames(x)
+
+intervalColNames(x)
+
+printTimeZones(x)
+
+hardCodedColNames()
+
+checkCodedColNames(x)
+
+setCodedColNames(x)
+
+keycolNames()
+
+linkStructure()
+
+timestampCols()
+
+intervalCols()
+
+removeEmptyCols(x, keepMainCols = TRUE)
+
+intervalColNames(x)
+
+tableInfo(x, table, verbose = TRUE)
+
+duplicateNames(x, keyNames = keycolNames())
+
+forceUniqueNames(x, keyNames = keycolNames())
+}
+\arguments{
+\item{x}{\code{character} path, or an object of class \code{\link{ctdp}}}
+}
+\value{
+for \code{pathProperties} a \code{list} with folder/file information
+}
+\description{
+Hidden helper functions
+}
+\examples{
+NULL
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/integrate_effort.Rd b/man/integrate_effort.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..93589ea0ae6b6a3963478cbb4863e8e0e1784ff6
--- /dev/null
+++ b/man/integrate_effort.Rd
@@ -0,0 +1,47 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/integrate_effort.r
+\name{integrate_effort}
+\alias{integrate_effort}
+\title{Integrate effort over a specified time span}
+\usage{
+integrate_effort(
+  x,
+  start = NULL,
+  end = NULL,
+  orders = "\%Y/\%m/\%d",
+  subset = NULL
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{start}{\code{character} datetime: sequences are kept when the start of the sequences is > start, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{end}{\code{character} datetime: sequences are kept when the start of the sequences is < end, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{orders}{\code{character} string with the orders passed on to \code{\link{parse_date_time}}, defaults to "Ymd"}
+
+\item{subset}{arguments used for filtering}
+}
+\value{
+a \code{tibble} with effort data
+}
+\description{
+Integrate effort over a specified time span
+}
+\examples{
+\code{
+integrate_effort(camsample)
+integrate_effort(camsample, start = "2017/4/1", end = "2017/8/1")
+integrate_effort(camsample, subset = str_sub(locationName, 5, 5) == "3")
+integrate_effort(camsample,
+  start = "2017/4/1", end = "2017/8/1", 
+  subset = str_sub(locationName, 5, 5) == "3")
+}
+}
+\seealso{
+\code{\link{ctdp}}, \code{\link{filter_station}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/is.na.ctdp.Rd b/man/is.na.ctdp.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..860addbfb276f981ad7af47fcd1d5c96fe383d21
--- /dev/null
+++ b/man/is.na.ctdp.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/is.na.ctdp.r
+\name{is.na.ctdp}
+\alias{is.na.ctdp}
+\title{Check whether an a ctdp object is NA}
+\usage{
+\method{is.na}{ctdp}(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{logical} whether or not the object is of class \code{\link{ctdp}}
+}
+\description{
+Check whether an a ctdp object is NA
+}
+\examples{
+\dontrun{
+is.na(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/is_ctdp.Rd b/man/is_ctdp.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..43362a45f821bcfaa6eb574744ecf1ce3404d43a
--- /dev/null
+++ b/man/is_ctdp.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/is_ctdp.r
+\name{is_ctdp}
+\alias{is_ctdp}
+\title{Check whether an object if of class ctdp}
+\usage{
+is_ctdp(x)
+}
+\arguments{
+\item{x}{an object}
+}
+\value{
+a \code{logical} whether or not the object is of class \code{\link{ctdp}}
+}
+\description{
+Check whether an object if of class ctdp
+}
+\examples{
+\dontrun{
+is_ctdp(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/locations.Rd b/man/locations.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..236ddbf9f83d5b193eb18da632a97373318f23cd
--- /dev/null
+++ b/man/locations.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/locations.r
+\name{locations}
+\alias{locations}
+\title{Retrieve the locations of a ctdp object}
+\usage{
+locations(x, addLonlat = TRUE)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{tibble} with the camera trap locations
+}
+\description{
+Retrieve the locations of a ctdp object
+}
+\examples{
+\dontrun{
+locations(camsample)
+locations(camsample, addLonlat = FALSE)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/media.Rd b/man/media.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..959580f69e682e44b21ef23fca0b74c963434626
--- /dev/null
+++ b/man/media.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/media.r
+\name{media}
+\alias{media}
+\title{Retrieve the media table of a ctdp object}
+\usage{
+media(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{tibble} with the media table
+}
+\description{
+Retrieve the media table of a ctdp object
+}
+\examples{
+\dontrun{
+media(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/merge_tibbles.Rd b/man/merge_tibbles.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..b6c18064323146e6091676e16289c500cc61a161
--- /dev/null
+++ b/man/merge_tibbles.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/merge_tibbles.r
+\name{merge_tibbles}
+\alias{merge_tibbles}
+\title{Join the ctdp tibbles into 1 tibble with 1 row per observation}
+\usage{
+merge_tibbles(x, dropMedia = TRUE, onlyDistinct = TRUE, toInterval = TRUE)
+}
+\arguments{
+\item{x}{an object of class \code{ctdp}}
+
+\item{dropMedia}{\code{logical}, indicating whether or not to remove the \code{media} information. Defaults to \code{TRUE}}
+
+\item{onlyDistinct}{\code{logical}, indicating whether or not to remove the duplicate rows. Defaults to \code{TRUE}}
+
+\item{toInterval}{\code{logical}, indicating whether or not to return interval columns (for deployments and sequences start/end). Defaults to \code{TRUE}}
+}
+\value{
+a single object of class \code{tibble} with all data merged
+}
+\description{
+Join the ctdp tibbles into 1 tibble with 1 row per observation
+}
+\examples{
+\dontrun{
+merge_tibbles(camsample)
+}
+}
+\seealso{
+\code{\link{nest_tibbles}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/nest_tibbles.Rd b/man/nest_tibbles.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..685a84bccbe9033f3fc60780ad70561b2c207e08
--- /dev/null
+++ b/man/nest_tibbles.Rd
@@ -0,0 +1,39 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/nest_tibbles.r
+\name{nest_tibbles}
+\alias{nest_tibbles}
+\title{Join the ctdp tibbles into 1 tibble with 1 row per sequence}
+\usage{
+nest_tibbles(
+  x,
+  dropMedia = FALSE,
+  onlyDistinct = TRUE,
+  mainCols = c("captureMethod", "nrphotos")
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{dropMedia}{\code{logical}, indicating whether or not to remove the \code{media} information. Defaults to \code{FALSE}}
+
+\item{onlyDistinct}{\code{logical}, indicating whether or not to remove the duplicate rows. Defaults to \code{TRUE}}
+
+\item{mainCols}{\code{character} vector with the names of those columns that should be kept outside a list-column (not from media). Defaults to \code{c("captureMethod","nrphotos")}.}
+}
+\value{
+object of class \code{seqnest}, which is a \code{tibble} with columns \code{sequenceID}, all columns in \code{mainCols}, and \code{list-clumns} "locations", "deployments", "observations" and "media". All these \code{list-columns} contain data in \code{data.table} format.
+}
+\description{
+Join the ctdp tibbles into 1 tibble with 1 row per sequence
+}
+\examples{
+\dontrun{
+nest_tibbles(camsample)
+}
+}
+\seealso{
+\code{\link{merge_tibbles}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/observations.Rd b/man/observations.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..e62037bb4c8bdd5372ab5a3e982804808a02cfe0
--- /dev/null
+++ b/man/observations.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/observations.r
+\name{observations}
+\alias{observations}
+\title{Retrieve the observations table of a ctdp object}
+\usage{
+observations(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{tibble} with the observations table
+}
+\description{
+Retrieve the observations table of a ctdp object
+}
+\examples{
+\dontrun{
+observations(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/order_void.Rd b/man/order_void.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..c74621c5e23cbeecfa26b1f68eaffeca94f979ef
--- /dev/null
+++ b/man/order_void.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/order_void.r
+\name{order_void}
+\alias{order_void}
+\title{Specify order}
+\usage{
+order_void(order = NULL)
+}
+\arguments{
+\item{order}{optional \code{character} string with the order of species (e.g. "Carnivora") to focus analyses on, defaults to \code{NULL}}
+}
+\description{
+Specify order
+}
+\examples{
+NULL
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/plot_effort.Rd b/man/plot_effort.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..130dcf72fe04165b3b47238eb7fd4547c1f7db68
--- /dev/null
+++ b/man/plot_effort.Rd
@@ -0,0 +1,45 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/plot_effort.r
+\name{plot_effort}
+\alias{plot_effort}
+\title{Plot a step function with the number of active cameras over time}
+\usage{
+plot_effort(
+  x,
+  dynamic = TRUE,
+  main = "Effort",
+  xlab = "time",
+  ylab = "nr of active cams",
+  ...
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{dynamic}{\code{logical} whether or not to plot a dynamic graph (via \code{\link{dygraph}}, defaults to \code{TRUE}}
+
+\item{main}{\code{character} plot title, defaults to "Effort"}
+
+\item{xlab}{\code{character} x axis label, defaults to "time"}
+
+\item{ylab}{\code{character} y axis label, defaults to "nr of active cams"}
+
+\item{...}{other arguments passed on to the plot function}
+}
+\value{
+a plot (whenb \code{doPlot=TRUE}), and (silently when plotting) it returns a tibble with effort data
+}
+\description{
+Plot a step function with the number of active cameras over time
+}
+\examples{
+\dontrun{
+plot_effort(camsample)
+}
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/plot_locations.Rd b/man/plot_locations.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..4ba171467cd8f9566d0fac84ba9aee6302b018dc
--- /dev/null
+++ b/man/plot_locations.Rd
@@ -0,0 +1,30 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/plot_locations.r
+\name{plot_locations}
+\alias{plot_locations}
+\title{Plot locations}
+\usage{
+plot_locations(x, doPlot = TRUE)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{doPlot}{\code{logical} whether or not to plot the leaflet map, defaults to \code{TRUE}}
+}
+\value{
+silently returns the leaflet plot
+}
+\description{
+Plot locations
+}
+\examples{
+\dontrun{
+plot_locations(camsample)
+}
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/plot_status.Rd b/man/plot_status.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..776b29980aecc60a8bd72fff66a9e29e8bc539ce
--- /dev/null
+++ b/man/plot_status.Rd
@@ -0,0 +1,38 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/plot_status.r
+\name{plot_status}
+\alias{plot_status}
+\title{Plot annotation status per deployment}
+\usage{
+plot_status(
+  x,
+  IDcol = "locationName",
+  onlyMotion = TRUE,
+  xlab = "Time",
+  ylab = "Location",
+  grouping = NULL,
+  dynamic = TRUE,
+  doPlot = TRUE
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a plot
+}
+\description{
+Plot annotation status per deployment
+}
+\examples{
+\dontrun{
+plot_status(camsample)
+plot_status(camsample, grouping = "transect")
+}
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/plot_time_coverage.Rd b/man/plot_time_coverage.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..85755fe84e8017e0750292d0dea52601c4baf9c8
--- /dev/null
+++ b/man/plot_time_coverage.Rd
@@ -0,0 +1,70 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/plot_time_coverage.r
+\name{plot_time_coverage}
+\alias{plot_time_coverage}
+\title{Plot activity of the camera-stations over time}
+\usage{
+plot_time_coverage(
+  x,
+  IDcol = "locationName",
+  xlab = "Time",
+  ylab = "Location",
+  grouping = NULL,
+  colour = NULL,
+  dynamic = TRUE,
+  addSequences = FALSE,
+  jitter = 0.05,
+  size = 0.25,
+  doTimelapse = FALSE,
+  doPlot = TRUE
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{IDcol}{\code{character} name of the column that groups the deployments, defaults to "locationName}
+
+\item{xlab, ylab}{\code{character} names of the axis labels (defaults to "Time" and "Location")}
+
+\item{grouping}{an optional \code{character} vector with the name(s) of the grouping columns, defaults to \code{NULL}}
+
+\item{colour}{an optional \code{character} name of the column in the deployments table to be used for colouring the polygons (this overwrites the \code{grouping} argument, which will then only be used for the y-axis ordering). Defaults to \code{NULL}}
+
+\item{dynamic}{\code{logical} whether or not to plot a dynamic graph (via \code{ggplotly}, defaults to \code{TRUE}}
+
+\item{addSequences}{\code{logical} whether or not to add the times of sequences to the plots (with jittering in y-direction), defaults to \code{FALSE}}
+
+\item{jitter}{\code{numeric} value of the amount of vertical jittering (sd of normal distribution centered on 0), defaults to 0.05.}
+
+\item{size}{\code{numeric} value of the size of the points, defaults to 0.25}
+
+\item{doTimelapse}{\code{logical} whether or not to plot the time lapse sequences, defaults to \code{FALSE}}
+
+\item{doPlot}{\code{logical} whether or not to plot, defaults to \code{TRUE}}
+}
+\value{
+invisibly returns the plot
+}
+\description{
+Plot activity of the camera-stations over time
+}
+\examples{
+\dontrun{
+# dynamic ggplotly plots
+plot_time_coverage(camsample)
+plot_time_coverage(camsample, addSequences = TRUE)
+
+# only ggplot
+plot_time_coverage(camsample, dynamic = FALSE)
+plot_time_coverage(camsample, addSequences = TRUE, dynamic = FALSE)
+
+# colour by grouping
+plot_time_coverage(camsample, grouping = "transect")
+}
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/point_effort.Rd b/man/point_effort.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..9af202e5b7bef98ace39f53b216568334057a25b
--- /dev/null
+++ b/man/point_effort.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/point_effort.r
+\name{point_effort}
+\alias{point_effort}
+\title{Find the number of active cameras at a given set of times}
+\usage{
+point_effort(x, t, orders = "\%Y/\%m/\%d \%H:\%M:\%S")
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{t}{\code{POXIXct} or \code{character} timepoints for which to retrieve effort}
+
+\item{orders}{\code{character} string with the orders passed on to \code{\link{parse_date_time}}}
+}
+\value{
+a plot, and silently it returns a tibble with effort data
+}
+\description{
+Find the number of active cameras at a given set of times
+}
+\examples{
+\dontrun{
+point_effort(camsample, "2017/6/21 12:00:00")
+}
+}
+\seealso{
+\code{\link{plot_effort}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/primary_keys.Rd b/man/primary_keys.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..cb5a79c897785d41ee233e26e306a3bf3b3df864
--- /dev/null
+++ b/man/primary_keys.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/primary_keys.r
+\name{primary_keys}
+\alias{primary_keys}
+\title{Retrieve the primary keys of the ctdp tables}
+\usage{
+primary_keys(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a named \code{list} with \code{character} names of the primary keys of the tibbles in \code{x}
+}
+\description{
+Retrieve the primary keys of the ctdp tables
+}
+\examples{
+\dontrun{
+primary_keys(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/sequences.Rd b/man/sequences.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..9ac6e6e39671752aaf176d2a9c44c23b9ffdc883
--- /dev/null
+++ b/man/sequences.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/sequences.r
+\name{sequences}
+\alias{sequences}
+\title{Retrieve the sequences table of a ctdp object}
+\usage{
+sequences(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{tibble} with the sequences table
+}
+\description{
+Retrieve the sequences table of a ctdp object
+}
+\examples{
+\dontrun{
+sequences(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/set_GMT_offset.Rd b/man/set_GMT_offset.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..b607397d64f34b13f44591f01d4561c185770dac
--- /dev/null
+++ b/man/set_GMT_offset.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/set_timezone.r
+\name{set_GMT_offset}
+\alias{set_GMT_offset}
+\title{Specify a timezone via an offset relative to GMT/UTC}
+\usage{
+set_GMT_offset(offset)
+}
+\arguments{
+\item{offset}{a (signed) \code{integer} or \code{numeric} (that can be parsed to integer) value with the offset relative to GMT/UTC (in hours)}
+}
+\value{
+a \code{character} notation of the timezone
+}
+\description{
+Specify a timezone via an offset relative to GMT/UTC
+}
+\examples{
+\dontrun{
+set_GMT_offset(+2)
+set_GMT_offset(-5)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/species_argcheck.Rd b/man/species_argcheck.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..5639b69c223a0a0a0034e60d1bbf1fed8937339b
--- /dev/null
+++ b/man/species_argcheck.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/species_argcheck.r
+\name{species_argcheck}
+\alias{species_argcheck}
+\title{Argument check on species}
+\usage{
+species_argcheck(x, species)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{species}{an optional \code{character} vector with name of the species (e.g. "Ocelot") to focus on, defaults to \code{NULL}}
+}
+\value{
+same as \code{x} or an error
+}
+\description{
+Argument check on species
+}
+\examples{
+\dontrun{
+species_argcheck(camsample, species = "LowlandPaca")
+species_argcheck(camsample, species = "LowlandPaca")
+}
+}
+\seealso{
+\code{\link{taxon_id}}
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/species_void.Rd b/man/species_void.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..dc6c2bd7137f910ab5192dbbe33068e54adf7fa6
--- /dev/null
+++ b/man/species_void.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/species_void.r
+\name{species_void}
+\alias{species_void}
+\title{Specify species}
+\usage{
+species_void(species = NULL)
+}
+\arguments{
+\item{species}{an optional \code{character} vector with name of the species (e.g. "Ocelot") to focus on, defaults to \code{NULL}}
+}
+\description{
+Specify species
+}
+\examples{
+NULL
+}
+\author{
+Henjo de Knegt
+}
+\keyword{internal}
diff --git a/man/summarise_deployments.Rd b/man/summarise_deployments.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..94f2a63d0e8047b498ac695f442e8d3e6b5301ce
--- /dev/null
+++ b/man/summarise_deployments.Rd
@@ -0,0 +1,61 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/summarise_deployments.r
+\name{summarise_deployments}
+\alias{summarise_deployments}
+\title{Summarize (groups of) deployments}
+\usage{
+summarise_deployments(
+  x,
+  datatable = FALSE,
+  onlyMotion = TRUE,
+  omitSetupType = TRUE,
+  start = NULL,
+  end = NULL,
+  orders = "\%Y/\%m/\%d",
+  subset = NULL,
+  by = NULL
+)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{datatable}{\code{logical}, whether or not to show as interactive datatable in the viewer, defaults to \code{FALSE}}
+
+\item{onlyMotion}{\code{logical}, whether or not to only use motion detection images, defaults to \code{TRUE}}
+
+\item{omitSetupType}{\code{logical}, whether or not to omit sequences that are marked as some form of setup (`observations` column `cameraSetupType` is than not NA), defaults to \code{TRUE}}
+
+\item{start}{\code{character} datetime: sequences are kept when the start of the sequences is > start, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{end}{\code{character} datetime: sequences are kept when the start of the sequences is < end, passed on to \code{\link{parse_date_time}}. Defaults to \code{NULL}}
+
+\item{orders}{\code{character} string with the orders passed on to \code{\link{parse_date_time}}, defaults to "Ymd"}
+
+\item{subset}{arguments used for filtering}
+
+\item{by}{an optional \code{character} vector specifying the column names by which to group analyses, defaults to \code{NULL}}
+}
+\value{
+a \code{tibble} like returned by \code{\link{fraction_annotated}}, but with extra column "effort", which contains the total number of camera-trap days for each group (row)
+}
+\description{
+Summarize (groups of) deployments
+}
+\examples{
+\dontrun{
+summarise_deployments(camsample)
+summarise_deployments(camsample, by = "locationName")
+summarise_deployments(camsample, by = "transect")
+summarise_deployments(camsample, start = "2017/4/1", end = "2017/8/1")
+summarise_deployments(camsample, by = "locationName", subset = str_sub(locationName, 5, 5) == "3") 
+
+# As datatable in viewer
+summarise_deployments(camsample, datatable = TRUE)
+}
+}
+\seealso{
+\code{\link{fraction_annotated}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/taxon_classes.Rd b/man/taxon_classes.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..b37db7c3ab38fa66d7c448db62b130f76c5a62b6
--- /dev/null
+++ b/man/taxon_classes.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/taxon_classes.r
+\name{taxon_classes}
+\alias{taxon_classes}
+\title{Retrieve the taxonomic classes}
+\usage{
+taxon_classes(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{character} vector with the taxonomic classes present in \code{x}
+}
+\description{
+Retrieve the taxonomic classes
+}
+\examples{
+\dontrun{
+taxon_classes(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/taxon_id.Rd b/man/taxon_id.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..e164d2564b8eae6ea2ba331a7b4ff9a773c7036a
--- /dev/null
+++ b/man/taxon_id.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/taxon_id.r
+\name{taxon_id}
+\alias{taxon_id}
+\title{Get the taxonID for a species}
+\usage{
+taxon_id(x, spp = NULL)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{character} vector with the corresponding taxonID values
+}
+\description{
+Get the taxonID for a species
+}
+\examples{
+\dontrun{
+taxon_id(camsample)
+taxon_id(camsample, spp = "lowland paca")
+taxon_id(camsample, spp = "LowlandPaca")
+taxon_id(camsample, spp = "_LowLand_ PaCa")
+}
+}
+\seealso{
+\code{\link{taxon_species}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/taxon_level.Rd b/man/taxon_level.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..49227ecd33f8a21e4307ed5e59fd478a81078f05
--- /dev/null
+++ b/man/taxon_level.Rd
@@ -0,0 +1,46 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/taxon_level.r
+\name{taxon_level}
+\alias{taxon_level}
+\title{Get taxonomic class of a species by its identifier}
+\usage{
+taxon_level(taxonID, level = "class")
+}
+\arguments{
+\item{taxonID}{a \code{character} string with taxonID}
+
+\item{level}{a \code{character} string with the desired level, with choices "species","genus","family","suborder","order","infraclass","subclass","class","superclass","megaclass","gigaclass","parvphylum","infraphylum","subphylum","phylum","kingdom","unranked"}
+}
+\value{
+a \code{character} string with class of the taxon
+}
+\description{
+Get taxonomic class of a species by its identifier
+}
+\examples{
+\dontrun{
+  taxon_level("QLXL", level = "species")
+  taxon_level("QLXL", level = "genus")
+  taxon_level("QLXL", level = "family")
+  taxon_level("QLXL", level = "suborder")
+  taxon_level("QLXL", level = "order")
+  taxon_level("QLXL", level = "infraclass")
+  taxon_level("QLXL", level = "subclass")
+  taxon_level("QLXL", level = "class")
+  taxon_level("QLXL", level = "superclass")
+  taxon_level("QLXL", level = "megaclass")
+  taxon_level("QLXL", level = "gigaclass")
+  taxon_level("QLXL", level = "parvphylum")
+  taxon_level("QLXL", level = "infraphylum")
+  taxon_level("QLXL", level = "subphylum")
+  taxon_level("QLXL", level = "phylum")
+  taxon_level("QLXL", level = "kingdom")
+  taxon_level("QLXL", level = "unranked")
+}
+}
+\seealso{
+API of \url{https://www.catalogueoflife.org/} called
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/taxon_orders.Rd b/man/taxon_orders.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..c19e7ce7bf77916dbd8741ed97e7b9244c9c9e82
--- /dev/null
+++ b/man/taxon_orders.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/taxon_orders.r
+\name{taxon_orders}
+\alias{taxon_orders}
+\title{Retrieve the taxonomic orders}
+\usage{
+taxon_orders(x, class = NULL)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{class}{optional \code{character} string with the class of species (e.g. "Mammalia") to focus analyses on, defaults to \code{NULL}}
+}
+\value{
+a \code{character} vector with the taxonomic orders present in \code{x}
+}
+\description{
+Retrieve the taxonomic orders
+}
+\examples{
+\dontrun{
+taxon_orders(camsample)
+taxon_orders(camsample, class = taxon_classes(camsample))
+taxon_orders(camsample, class = "Mammalia")
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/taxon_species.Rd b/man/taxon_species.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..7f84d19635c8a8cffd71de2b79c7a113f0b49cd8
--- /dev/null
+++ b/man/taxon_species.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/taxon_species.r
+\name{taxon_species}
+\alias{taxon_species}
+\title{Retrieve the taxonomic species}
+\usage{
+taxon_species(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{character} vector with the taxonomic species present in \code{x}
+}
+\description{
+Retrieve the taxonomic species
+}
+\examples{
+\dontrun{
+taxon_species(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/taxonomy.Rd b/man/taxonomy.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..e86de6c5c7dbb812b477caa9171f1e2a30eb7e38
--- /dev/null
+++ b/man/taxonomy.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/taxonomy.r
+\name{taxonomy}
+\alias{taxonomy}
+\title{Retrieve the taxonomy table of a ctdp object}
+\usage{
+taxonomy(x, species = NULL)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{species}{an optional \code{character} vector with name of the species (e.g. "Ocelot") to focus on, defaults to \code{NULL}}
+}
+\value{
+a \code{tibble} with the taxonomy table
+}
+\description{
+Retrieve the taxonomy table of a ctdp object
+}
+\examples{
+\dontrun{
+taxonomy(camsample)
+taxonomy(camsample, species = "lowland paca")
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/time_range.Rd b/man/time_range.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..225be0792315c3185288e8fd43363d537704ea50
--- /dev/null
+++ b/man/time_range.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/time_range.r
+\name{time_range}
+\alias{time_range}
+\title{Retrieve the time range of a ctdp object}
+\usage{
+time_range(x, based_on = c("deployments", "sequences", "media")[1])
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+
+\item{based_on}{\code{character} vector based on which element the time range should be computed: one of "deployments" (default), "sequences", "media".}
+}
+\value{
+a \code{dttm} vector of length 2 with the start/end of the ctdp object
+}
+\description{
+Retrieve the time range of a ctdp object
+}
+\examples{
+\dontrun{
+time_range(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/time_zone.Rd b/man/time_zone.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..0b9795b70d94b2233039d03cbd639c9337f1693d
--- /dev/null
+++ b/man/time_zone.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/time_zone.r
+\name{time_zone}
+\alias{time_zone}
+\title{Retrieve the time zone of a ctdp object}
+\usage{
+time_zone(x)
+}
+\arguments{
+\item{x}{an object of class \code{\link{ctdp}}}
+}
+\value{
+a \code{character} string with the time zone information of the date-time objects in \code{x}
+}
+\description{
+Retrieve the time zone of a ctdp object
+}
+\examples{
+\dontrun{
+time_zone(camsample)
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/utc_offset.Rd b/man/utc_offset.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..51c86b370d376ab0f5550fad6e377f37b89b20e4
--- /dev/null
+++ b/man/utc_offset.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/utc_offset.r
+\name{utc_offset}
+\alias{utc_offset}
+\title{Get the offset in hours from UTC}
+\usage{
+utc_offset(t)
+}
+\arguments{
+\item{t}{an object of class \code{POSIXct}}
+}
+\value{
+a \code{numeric} with the offset (in hours) compared to UTC: negative is west of UTC.
+}
+\description{
+Get the offset in hours from UTC
+}
+\examples{
+\dontrun{
+  utc_offset(Sys.time())
+}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/man/write_tutorial.Rd b/man/write_tutorial.Rd
new file mode 100644
index 0000000000000000000000000000000000000000..229dd72fe6fc39a6e4212d02e23cecbfd1d355db
--- /dev/null
+++ b/man/write_tutorial.Rd
@@ -0,0 +1,40 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/write_tutorial.r
+\name{write_tutorial}
+\alias{write_tutorial}
+\title{Create a tutorial in html format}
+\usage{
+write_tutorial(
+  x,
+  fileName = NULL,
+  title = "Package ctdp - tutorial",
+  overwrite = TRUE,
+  focalSpecies = NULL,
+  start = NULL,
+  end = NULL
+)
+}
+\arguments{
+\item{x}{an object of class \code{ctdp}}
+
+\item{fileName}{an optional filename for the resultant html file, defaults to \code{NULL}}
+
+\item{title}{an optional title for the tutorial, defaults to "Package ctdp - tutorial"}
+
+\item{focalSpecies}{\code{character} name of the species to focus on, see \code{\link{taxon_id}}. Defaults to NULL, in which case the tutorial will focus on the 3 most abundant mammalian species.}
+}
+\value{
+a .html tutorial is being saved to the working directory, with the filename of ctdp zip that was loaded (with suffix "_tutorial.html").
+}
+\description{
+Create a tutorial in html format
+}
+\examples{
+NULL
+}
+\seealso{
+\code{\link{ctdp}}
+}
+\author{
+Henjo de Knegt
+}
diff --git a/pkgdown/extra.css b/pkgdown/extra.css
new file mode 100644
index 0000000000000000000000000000000000000000..9a0f0b437f037c2d535111022a0767a276ca9f82
--- /dev/null
+++ b/pkgdown/extra.css
@@ -0,0 +1,31 @@
+.html {
+  width: 100%;
+}
+
+.body {
+  width: 100%;
+}
+
+div.main-container {
+  max-width: 1600px !important;
+}
+
+.container {
+  width: 80%;
+  max-width: 1600px;
+  margin-left: auto;
+  margin-right: auto;
+  border: 0px solid red;
+}
+
+.col-md-3 {
+  width: 20%;
+  margin-left: auto;
+  border: 0px solid green;
+}
+
+.row>main {
+  max-width: 80%;
+  width: 80%;
+}
+
diff --git a/vignettes/.gitignore b/vignettes/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..097b241637da023174b0f2e3715bd0291d9ded37
--- /dev/null
+++ b/vignettes/.gitignore
@@ -0,0 +1,2 @@
+*.html
+*.R
diff --git a/vignettes/01_first_vignette.Rmd b/vignettes/01_first_vignette.Rmd
new file mode 100644
index 0000000000000000000000000000000000000000..3d15b009ee018f07315178604aa5b4477ac451d6
--- /dev/null
+++ b/vignettes/01_first_vignette.Rmd
@@ -0,0 +1,17 @@
+---
+title: "first vignette"
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{first vignette}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
+
+```{r, include = FALSE}
+knitr::opts_chunk$set(
+  collapse = TRUE,
+  comment = "#>"
+)
+```
+
+first vignette bla bla bla .rmd
\ No newline at end of file
diff --git a/vignettes/ctdp.Rmd b/vignettes/ctdp.Rmd
new file mode 100644
index 0000000000000000000000000000000000000000..160c80e47050eb231e03d5ca1858b27b906a9032
--- /dev/null
+++ b/vignettes/ctdp.Rmd
@@ -0,0 +1,14 @@
+---
+title: "Quick Start Guide to the ctdp package"
+description: >
+  Learn how to get started with the basics of the ctdp package
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{Quick Start Guide to the ctdp package}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
+
+### Bla bla bla
+
+bla bla bla ctdp.Rmd here
\ No newline at end of file