+ title = project$title,
+ abstract = list(para = project$description), # Can be NULL
+ designDescription = list(description = list(para = design_para))
+ )
+
+ # Set external link to project URL (can be NULL)
+ if (!is.null(project$path)) {
+ eml$dataset$distribution = list(
+ scope = "document", online = list(
+ url = list("function" = "information", project$path)
+ )
+ )
+ }
+
+ # Read data from package
+ # Already read with read_camtrap_dp()
+
+ # Create database
+ message("Creating database and transforming to Darwin Core.")
+ con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
+ DBI::dbWriteTable(con, "deployments", dplyr::tibble(orig_package$deployments))
+ DBI::dbWriteTable(con, "media", dplyr::tibble(orig_package$media))
+ DBI::dbWriteTable(con, "observations", dplyr::tibble(orig_package$observations))
+
+ # Query database
+ dwc_occurrence_sql <- glue::glue_sql(
+ readr::read_file(
+ system.file("sql/dwc_occurrence.sql", package = "camtraptor")
+ ),
+ .con = con
+ )
+ dwc_multimedia_sql <- glue::glue_sql(
+ readr::read_file(
+ system.file("sql/dwc_multimedia.sql", package = "camtraptor")
+ ),
+ .con = con
+ )
+ dwc_occurrence <- DBI::dbGetQuery(con, dwc_occurrence_sql)
+ dwc_multimedia <- DBI::dbGetQuery(con, dwc_multimedia_sql)
+ DBI::dbDisconnect(con)
+
+ # Write files
+ if (!dir.exists(directory)) {
+ dir.create(directory, recursive = TRUE)
+ }
+ EML::write_eml(eml, file.path(directory, "eml.xml"))
+ readr::write_csv(
+ dwc_occurrence, file.path(directory, "dwc_occurrence.csv"), na = ""
+ )
+ readr::write_csv(
+ dwc_multimedia, file.path(directory, "dwc_multimedia.csv"), na = ""
+ )
+}
diff --git a/inst/sql/dwc_multimedia.sql b/inst/sql/dwc_multimedia.sql
new file mode 100644
index 00000000..b0e42fac
--- /dev/null
+++ b/inst/sql/dwc_multimedia.sql
@@ -0,0 +1,61 @@
+/*
+Schema: https://rs.gbif.org/extension/ac/audubon_2020_10_06.xml
+Camtrap DP terms and whether they are included in DwC (Y) or not (N):
+
+media.mediaID Y: as link to observation
+media.deploymentID N: included at observation level
+media.sequenceID Y: as link to observation
+media.captureMethod Y
+media.timestamp Y
+media.filePath Y
+media.fileName Y: to sort data
+media.fileMediatype Y
+media.exifData N
+media.favourite N
+media.comments Y
+media._id N
+*/
+
+-- Observations can be based on sequences (sequenceID) or individual files (mediaID)
+-- Make two joins and union to capture both cases without overlap
+WITH observations_media AS (
+-- Sequence based observations
+ SELECT obs.observationID, obs.timestamp AS observationTimestamp, med.*
+ FROM observations AS obs
+ LEFT JOIN media AS med ON obs.sequenceID = med.sequenceID
+ WHERE obs.observationType = 'animal' AND obs.mediaID IS NULL
+ UNION
+-- File based observations
+ SELECT obs.observationID, obs.timestamp AS observationTimestamp, med.*
+ FROM observations AS obs
+ LEFT JOIN media AS med ON obs.mediaID = med.mediaID
+ WHERE obs.observationType = 'animal' AND obs.mediaID IS NOT NULL
+)
+
+SELECT
+ obs_med.observationID AS occurrenceID,
+-- provider: can be org managing the platform, but that info is not available
+ {media_license_url} AS rights,
+ obs_med.mediaID AS identifier,
+ CASE
+ WHEN obs_med.fileMediatype LIKE '%video%' THEN 'MovingImage'
+ ELSE 'StillImage'
+ END AS type,
+ obs_med._id AS providerManagedID,
+ obs_med.comments AS comments,
+ dep.cameraModel AS captureDevice,
+ obs_med.captureMethod AS resourceCreationTechnique,
+ obs_med.filePath AS accessURI,
+ obs_med.fileMediatype AS format,
+ STRFTIME('%Y-%m-%dT%H:%M:%SZ', datetime(obs_med.timestamp, 'unixepoch')) AS CreateDate
+
+FROM
+ observations_media AS obs_med
+ LEFT JOIN deployments AS dep
+ ON obs_med.deploymentID = dep.deploymentID
+
+ORDER BY
+-- Order is not retained in observations_media, so important to sort
+ obs_med.observationTimestamp,
+ obs_med.timestamp,
+ obs_med.fileName
diff --git a/inst/sql/dwc_occurrence.sql b/inst/sql/dwc_occurrence.sql
new file mode 100644
index 00000000..aabfcba3
--- /dev/null
+++ b/inst/sql/dwc_occurrence.sql
@@ -0,0 +1,124 @@
+/*
+Schema: https://rs.gbif.org/core/dwc_occurrence_2022-02-02.xml
+Camtrap DP terms and whether they are included in DwC (Y) or not (N):
+
+deployments.deploymentID Y
+deployments.locationID Y
+deployments.locationName Y
+deployments.longitude Y
+deployments.latitude Y
+deployments.coordinateUncertainty Y
+deployments.start Y
+deployments.end Y
+deployments.setupBy N
+deployments.cameraID N
+deployments.cameraModel Y: in dwc_multimedia
+deployments.cameraInterval N
+deployments.cameraHeight N
+deployments.cameraTilt N
+deployments.cameraHeading N
+deployments.timestampIssues N
+deployments.baitUse Y
+deployments.session N
+deployments.array N
+deployments.featureType Y
+deployments.habitat Y
+deployments.tags Y
+deployments.comments Y
+deployments._id N
+observations.observationID Y
+observations.deploymentID Y
+observations.sequenceID Y
+observations.mediaID N: in dwc_multimedia
+observations.timestamp Y
+observations.observationType Y: as filter
+observations.cameraSetup N
+observations.taxonID Y
+observations.scientificName Y
+observations.count Y
+observations.countNew N
+observations.lifeStage Y
+observations.sex Y
+observations.behaviour Y
+observations.individualID Y
+observations.classificationMethod Y
+observations.classifiedBy Y
+observations.classificationTimestamp Y
+observations.classificationConfidence Y
+observations.comments Y
+observations._id N
+*/
+
+SELECT
+-- RECORD-LEVEL
+ 'Event' AS type,
+ {license_url} AS license,
+ {rights_holder} AS rightsHolder,
+-- bibliographicCitation: how *record* should be cited, so not package bibliographicCitation
+ {doi_url} AS datasetID,
+-- institutionCode: org managing the platform/collection, but that info is not available
+ {platform} AS collectionCode,
+ {title} AS datasetName,
+ 'MachineObservation' AS basisOfRecord,
+ 'see metadata' AS informationWithheld,
+-- OCCURRENCE
+ obs.observationID AS occurrenceID,
+ obs.count AS individualCount,
+ obs.sex AS sex,
+ obs.lifeStage AS lifeStage,
+ obs.behaviour AS behavior,
+ 'present' AS occurrenceStatus,
+ obs.comments AS occurrenceRemarks,
+-- ORGANISM
+ obs.individualID AS organismID,
+-- EVENT
+ obs.sequenceID AS eventID,
+ obs.deploymentID AS parentEventID,
+ strftime('%Y-%m-%dT%H:%M:%SZ', datetime(obs.timestamp, 'unixepoch')) AS eventDate,
+ dep.habitat AS habitat,
+ 'camera trap' ||
+ CASE
+ WHEN dep.baitUse IS 'none' THEN ' without bait'
+ WHEN dep.baitUse IS NOT NULL THEN ' with bait'
+ ELSE ''
+ END AS samplingProtocol,
+ strftime('%Y-%m-%dT%H:%M:%SZ', datetime(dep.start, 'unixepoch')) ||
+ '/' ||
+ strftime('%Y-%m-%dT%H:%M:%SZ', datetime(dep.end, 'unixepoch')) AS samplingEffort, -- Duration of deployment
+ COALESCE(
+ dep.comments || ' | tags: ' || dep.tags,
+ 'tags: ' || dep.tags,
+ dep.comments
+ ) AS eventRemarks,
+-- LOCATION
+ dep.locationID AS locationID,
+ dep.locationName AS locality,
+ dep.featureType AS locationRemarks,
+ dep.latitude AS decimalLatitude,
+ dep.longitude AS decimalLongitude,
+ 'WGS84' AS geodeticDatum,
+ dep.coordinateUncertainty AS coordinateUncertaintyInMeters,
+-- IDENTIFICATION
+ obs.classifiedBy AS identifiedBy,
+ strftime('%Y-%m-%dT%H:%M:%SZ', datetime(obs.classificationTimestamp, 'unixepoch')) AS dateIdentified,
+ COALESCE(
+ 'classified by ' || obs.classificationMethod || ' with ' || obs.classificationConfidence || ' confidence',
+ 'classified by ' || obs.classificationMethod
+ ) AS identificationRemarks,
+-- TAXON
+ obs.taxonID AS taxonID,
+ obs.scientificName AS scientificName,
+ 'Animalia' AS kingdom
+
+FROM
+ observations AS obs
+ LEFT JOIN deployments AS dep
+ ON obs.deploymentID = dep.deploymentID
+
+WHERE
+ -- Select biological observations only (excluding observations marked as human, blank, vehicle)
+ -- Same filter should be used in dwc_multimedia.sql
+ obs.observationType = 'animal'
+
+ORDER BY
+ obs.timestamp
diff --git a/man/filter_predicate.Rd b/man/filter_predicate.Rd
index bee6d4ff..8cd12bdf 100644
--- a/man/filter_predicate.Rd
+++ b/man/filter_predicate.Rd
@@ -117,7 +117,9 @@ Internally, the input to \verb{pred*} functions turn into a character string,
which forms the body of a filter expression.
For example:
-\code{pred("tags", "boven de stroom")} gives:\preformatted{$arg
+\code{pred("tags", "boven de stroom")} gives:
+
+\if{html}{\out{}}\preformatted{$arg
[1] "tags"
$value
@@ -129,16 +131,22 @@ $type
$expr
(tags == "boven de stroom")
-}
+}\if{html}{\out{
}}
-\code{pred_gt("latitude", 51.27)} gives, (only \code{expr} slot shown):\preformatted{(latitude > 51.27)
-}
+\code{pred_gt("latitude", 51.27)} gives, (only \code{expr} slot shown):
-\code{pred_or()} gives:\preformatted{((tags == "boven de stroom") | (latitude > 51.28))
-}
+\if{html}{\out{}}\preformatted{(latitude > 51.27)
+}\if{html}{\out{
}}
-\code{pred_or()} gives:\preformatted{((tags == "boven de stroom") & (latitude > 51.28))
-}
+\code{pred_or()} gives:
+
+\if{html}{\out{}}\preformatted{((tags == "boven de stroom") | (latitude > 51.28))
+}\if{html}{\out{
}}
+
+\code{pred_or()} gives:
+
+\if{html}{\out{}}\preformatted{((tags == "boven de stroom") & (latitude > 51.28))
+}\if{html}{\out{
}}
}
\section{Keys}{
diff --git a/man/get_species.Rd b/man/get_species.Rd
index 1660d0e6..8e99a165 100644
--- a/man/get_species.Rd
+++ b/man/get_species.Rd
@@ -19,5 +19,4 @@ Function to get all identified species
}
\examples{
get_species(mica)
-
}
diff --git a/man/read_camtrap_dp.Rd b/man/read_camtrap_dp.Rd
index 53224695..96bdecaf 100644
--- a/man/read_camtrap_dp.Rd
+++ b/man/read_camtrap_dp.Rd
@@ -2,7 +2,7 @@
% Please edit documentation in R/read_camtrap_dp.R
\name{read_camtrap_dp}
\alias{read_camtrap_dp}
-\title{Read camtrap-dp formatted data}
+\title{Read Camtrap DP formatted data}
\usage{
read_camtrap_dp(file = NULL, media = TRUE, path = lifecycle::deprecated())
}
@@ -26,7 +26,7 @@ A list containing three (tibble) data.frames:
and a list with metadata: \code{datapackage}.
}
\description{
-This function reads camera trap data formatted following the \href{https://github.com/tdwg/camtrap-dp}{Camera Trap Data Package (Camtrap DP)} format. The
+This function reads camera trap data formatted following the \href{https://tdwg.github.io/camtrap-dpdp}{Camera Trap Data Package (Camtrap DP)} format. The
function is built upon the functions \link[frictionless]{read_package} and
\link[frictionless]{read_resource}. This means a.o. that all datetime
information included in the camera trap data package is automatically
diff --git a/man/write_dwc.Rd b/man/write_dwc.Rd
new file mode 100644
index 00000000..a5819c6e
--- /dev/null
+++ b/man/write_dwc.Rd
@@ -0,0 +1,90 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/write_dwc.R
+\name{write_dwc}
+\alias{write_dwc}
+\title{Transform camera trap data to Darwin Core}
+\usage{
+write_dwc(
+ package,
+ directory = ".",
+ doi = package$id,
+ contact = NULL,
+ rights_holder = package$rightsHolder
+)
+}
+\arguments{
+\item{package}{A Camtrap DP, as read by \code{\link[=read_camtrap_dp]{read_camtrap_dp()}}.}
+
+\item{directory}{Path to local directory to write files to.}
+
+\item{doi}{DOI of the original dataset, used to get metadata.}
+
+\item{contact}{Person to be set as resource contact and metadata provider.
+To be provided as a \code{person()}.}
+
+\item{rights_holder}{Acronym of the organization owning or managing the
+rights over the data.}
+}
+\value{
+CSV (data) and EML (metadata) files written to disk.
+}
+\description{
+Transforms a published \href{https://github.com/tdwg/camtrap-dp}{Camera Trap Data Package (Camtrap DP)} to Darwin Core CSV and EML
+files that can be uploaded to a \href{https://www.gbif.org/ipt}{GBIF IPT} for
+publication.
+A \code{meta.xml} file is not created.
+}
+\section{Metadata}{
+
+
+Metadata are derived from the original dataset by looking up its \code{doi} in
+DataCite (\href{https://doi.org/10.5281/zenodo.5590881}{example}) and transforming
+these to EML.
+Uses \code{movepub::datacite_to_eml()} under the hood.
+The following properties are set:
+\itemize{
+\item \strong{title}: Original title + \verb{[animal observations]}.
+\item \strong{description}: Automatically created first paragraph describing this is
+a derived dataset, followed by the original dataset description.
+\item \strong{license}: License of the original dataset.
+\item \strong{creators}: Creators of the original dataset.
+\item \strong{contact}: \code{contact} or first creator of the original dataset.
+\item \strong{metadata provider}: \code{contact} or first creator of the original dataset.
+\item \strong{keywords}: Keywords of the original dataset.
+\item \strong{associated parties}: Organizations as defined in
+\code{package$organizations}.
+\item \strong{geographic coverage}: Bounding box as defined \code{package$spatial}.
+\item \strong{taxonomic coverage}: Species as defined in \code{package$taxonomic}.
+\item \strong{temporal coverage}: Date range as defined in \code{package$temporal}.
+\item \strong{project data}: Title, identifier, description, and sampling design
+information as defined in \code{package$project}.
+\item \strong{alternative identifier}: DOI of the original dataset. This way, no new
+DOI will be created when publishing to GBIF.
+\item \strong{external link}: URL of the project as defined in \code{package$project$path}.
+}
+
+To be set manually in the GBIF IPT: \strong{type}, \strong{subtype},
+\strong{update frequency}, and \strong{publishing organization}.
+
+Not set: sampling methods and citations.
+Not applicable: collection data.
+}
+
+\section{Data}{
+
+
+\code{package} is expected to contain the resources \code{deployments}, \code{media} and
+\code{observations}.
+Their CSV data are loaded in to a SQLite database,
+\href{https://github.com/inbo/camtraptor/tree/main/inst/sql}{transformed to Darwin Core using SQL}
+and written to disk as CSV file(s).
+
+Key features of the Darwin Core transformation:
+\itemize{
+\item TODO
+}
+}
+
+\examples{
+# TODO
+}