diff --git a/DESCRIPTION b/DESCRIPTION index 3e6877c..bee2c1b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -6,7 +6,7 @@ Authors@R: c(person("Emmanuel", "Blondel", role = c("aut", "cre"), email = "emma person("Norbert", "Billet", role = c("ctb"))) Maintainer: Emmanuel Blondel Depends: R (>= 2.15) -Imports: R6, httr, XML (>= 3.96-1.1), sf, rgdal +Imports: R6, httr, XML (>= 3.96-1.1), sf, rgdal, geometa Suggests: testthat Description: Provides an interface to OGC Web-Services (OWS). In a first step, the package supports the Common OGC Web-Services specifications the Web Feature Service (WFS). ows4R will progressively support other OGC web diff --git a/NAMESPACE b/NAMESPACE index aacdb45..cfedd0c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ export(CSWCapabilities) export(CSWClient) +export(CSWGetRecordById) export(OWSCapabilities) export(OWSClient) export(OWSOperation) @@ -16,6 +17,7 @@ export(WFSFeatureType) export(WFSFeatureTypeElement) export(WFSGetFeature) import(XML) +import(geometa) import(httr) import(rgdal) import(sf) diff --git a/R/CSWClient.R b/R/CSWClient.R index 3a2f4a9..c1f0fc1 100644 --- a/R/CSWClient.R +++ b/R/CSWClient.R @@ -24,6 +24,9 @@ #' \item{\code{getCapabilities()}}{ #' Get service capabilities. Inherited from OWS Client #' } +#' \item{\code{getRecordById(id, ...)}}{ +#' Get a record by Id. +#' } #' } #' #' @author Emmanuel Blondel @@ -46,8 +49,17 @@ CSWClient <- R6Class("CSWClient", }, #getRecordById - getRecordById = function(id, outputSchema){ - stop("Not yet implemented") + getRecordById = function(id, ...){ + message(sprintf("Fetching record '%s' ...", id)) + operations <- self$capabilities$getOperationsMetadata()$getOperations() + op <- operations[sapply(operations,function(x){x$getName()=="GetRecordById"})] + if(length(op)>0){ + op <- op[[1]] + }else{ + stop("Operation 'GetRecordById' not supported by this service") + } + request <- CSWGetRecordById$new(op, self$getUrl(), self$getVersion(), id = id, ...) + return(request$response) }, #getRecords diff --git a/R/CSWGetRecordById.R b/R/CSWGetRecordById.R new file mode 100644 index 0000000..daa1c00 --- /dev/null +++ b/R/CSWGetRecordById.R @@ -0,0 +1,86 @@ +#' CSWGetRecordById +#' +#' @docType class +#' @export +#' @keywords OGC CSW GetRecordById +#' @return Object of \code{\link{R6Class}} for modelling a CSW GetRecordById request +#' @format \code{\link{R6Class}} object. +#' +#' @section Methods: +#' \describe{ +#' \item{\code{new(url, version, id)}}{ +#' This method is used to instantiate a CSWGetRecordById object +#' } +#' \item{\code{getRequest()}}{ +#' Get GetRecordById request +#' } +#' } +#' +#' @author Emmanuel Blondel +#' +CSWGetRecordById <- R6Class("CSWGetRecordById", + inherit = OWSRequest, + private = list( + name = "GetRecordById", + defaultOutputSchema = "http://www.opengis.net/cat/csw/2.0.2" + ), + public = list( + initialize = function(op, url, version, id, ...) { + namedParams <- list(request = private$name, version = version, id = id) + outputSchema <- list(...)$outputSchema + if(is.null(outputSchema)){ + outputSchema <- private$defaultOutputSchema + namedParams <- c(namedParams, outputSchema = outputSchema) + } + super$initialize(op, url, namedParams, mimeType = "text/xml", ...) + + #check response in case of ISO + isoSchemas <- c("http://www.isotc211.org/2005/gmd","http://www.isotc211.org/2005/gfc") + if(outputSchema %in% isoSchemas){ + xmltxt <- as(self$response, "character") + isMetadata <- regexpr("MD_Metadata", xmltxt)>0 + isFeatureCatalogue <- regexpr("FC_FeatureCatalogue", xmltxt)>0 + if(isMetadata && outputSchema == isoSchemas[2]){ + outputSchema <- isoSchemas[1] + message(sprintf("Metadata detected! Switch to schema '%s'!", outputSchema)) + } + if(isFeatureCatalogue && outputSchema == isoSchemas[1]){ + outputSchema <- isoSchemas[2] + message(sprintf("FeatureCatalogue detected! Switch to schema '%s'!", outputSchema)) + } + } + + #bindings + self$response <- switch(outputSchema, + "http://www.isotc211.org/2005/gmd" = { + out <- NULL + xmlObjs <- getNodeSet(self$response, "//ns:MD_Metadata", c(ns = outputSchema)) + if(length(xmlObjs)>0){ + xmlObj <- xmlObjs[[1]] + out <- geometa::ISOMetadata$new() + out$decode(xml = xmlObj) + } + out + }, + "http://www.isotc211.org/2005/gfc" = { + out <- NULL + xmlObjs <- getNodeSet(self$response, "//ns:FC_FeatureCatalogue", c(ns = outputSchema)) + if(length(xmlObjs)>0){ + xmlObj <- xmlObjs[[1]] + out <- geometa::ISOFeatureCatalogue$new() + out$decode(xml = xml) + } + out + }, + "http://www.opengis.net/cat/csw/2.0.2" = { + warnings(sprintf("R binding not yet supported for '%s'", outputSchema)) + self$response + }, + "http://www.w3.org/ns/dcat#" = { + warnings(sprintf("R binding not yet supported for '%s'", outputSchema)) + self$response + } + ) + } + ) +) \ No newline at end of file diff --git a/R/OWSCapabilities.R b/R/OWSCapabilities.R index 50b32e5..e6f0242 100644 --- a/R/OWSCapabilities.R +++ b/R/OWSCapabilities.R @@ -49,7 +49,7 @@ OWSCapabilities <- R6Class("OWSCapabilities", #buildRequest buildRequest = function(url, service, version){ namedParams <- list(request = "GetCapabilities", service, version = version) - request <- OWSRequest$new(url, namedParams, "text/xml") + request <- OWSRequest$new(op = NULL, url, namedParams, "text/xml") return(request) } ), diff --git a/R/OWSClient.R b/R/OWSClient.R index db42d4a..33ea369 100644 --- a/R/OWSClient.R +++ b/R/OWSClient.R @@ -6,6 +6,7 @@ #' @import XML #' @import sf #' @import rgdal +#' @import geometa #' @export #' @keywords OGC Common OWS #' @return Object of \code{\link{R6Class}} with methods for interfacing diff --git a/R/OWSOperation.R b/R/OWSOperation.R index 70af93c..cc825a7 100644 --- a/R/OWSOperation.R +++ b/R/OWSOperation.R @@ -11,6 +11,15 @@ #' \item{\code{new(xmlObj, service, version)}}{ #' This method is used to instantiate an OWSOperation object #' } +#' \item{\code{getName()}}{ +#' Get name +#' } +#' \item{\code{getParameters()}}{ +#' Get the list of parameters +#' } +#' \item{\code{getParameter(name)}}{ +#' Get a given parameter +#' } #' } #' #' @author Emmanuel Blondel @@ -41,6 +50,11 @@ OWSOperation <- R6Class("OWSOperation", #getParameters getParameters = function(){ return(private$parameters) + }, + + #getParameter + getParameter = function(name){ + return(private$parameters[[name]]) } ) ) \ No newline at end of file diff --git a/R/OWSRequest.R b/R/OWSRequest.R index f24e370..5c9bc22 100644 --- a/R/OWSRequest.R +++ b/R/OWSRequest.R @@ -30,12 +30,12 @@ OWSRequest <- R6Class("OWSRequest", r <- GET(request) responseContent <- NULL if(is.null(mimeType)){ - responseContent <- content(r) + responseContent <- content(r, encoding = "UTF-8") }else{ if(regexpr("xml",mimeType)>0){ - responseContent <- xmlParse(content(r, type = "text")) + responseContent <- xmlParse(content(r, type = "text", encoding = "UTF-8")) }else{ - responseContent <- content(r, type = mimeType) + responseContent <- content(r, type = mimeType, encoding = "UTF-8") } } response <- list(request = request, status = status_code(r), response = responseContent) @@ -48,12 +48,28 @@ OWSRequest <- R6Class("OWSRequest", status = NA, response = NA, #initialize - initialize = function(url, namedParams, mimeType = "text/xml") { + initialize = function(op, url, namedParams, mimeType = "text/xml", ...) { + vendorParams <- list(...) + if(!is.null(op)){ + for(param in names(vendorParams)){ + if(!(param %in% names(op$getParameters()))){ + stop(sprintf("Parameter '%s' is not among allowed parameters [%s]", + param, paste(paste0("'",names(op$getParameters()),"'"), collapse=","))) + } + value <- vendorParams[[param]] + paramAllowedValues <- op$getParameter(param) + if(!(value %in% paramAllowedValues)){ + stop(sprintf("'%s' parameter value '%s' is not among allowed values [%s]", + param, value, paste(paste0("'",paramAllowedValues,"'"), collapse=","))) + } + } + } + namedParams <- c(namedParams, vendorParams) req <- private$buildRequest(url, namedParams, mimeType) self$request <- req$request self$status <- req$status self$response <- req$response } - ), + ) ) \ No newline at end of file diff --git a/R/WFSCapabilities.R b/R/WFSCapabilities.R index 3b67b9d..1982fd4 100644 --- a/R/WFSCapabilities.R +++ b/R/WFSCapabilities.R @@ -45,7 +45,7 @@ WFSCapabilities <- R6Class("WFSCapabilities", featureTypesXML <- getNodeSet(xmlObj, "//ns:FeatureType", wfsNs) featureTypesList <- lapply(featureTypesXML, function(x){ - WFSFeatureType$new(x, url, version) + WFSFeatureType$new(x, self, url, version) }) return(featureTypesList) diff --git a/R/WFSClient.R b/R/WFSClient.R index 9c63ab7..c092f64 100644 --- a/R/WFSClient.R +++ b/R/WFSClient.R @@ -28,7 +28,7 @@ #' Get the description of a given featureType #' } #' \item{\code{getFeatures(typeName)}}{ -#' Retrieves the features for a given feature type +#' Retrieves the features for a given feature type. #' } #' } #' diff --git a/R/WFSDescribeFeatureType.R b/R/WFSDescribeFeatureType.R index b29f264..d538c52 100644 --- a/R/WFSDescribeFeatureType.R +++ b/R/WFSDescribeFeatureType.R @@ -8,56 +8,22 @@ #' #' @section Methods: #' \describe{ -#' \item{\code{new(url, version, typeName)}}{ +#' \item{\code{new(op, url, version, typeName)}}{ #' This method is used to instantiate a WFSDescribeFeatureType object #' } -#' \item{\code{getRequest()}}{ -#' Get DescribeFeatureType request -#' } -#' \item{\code{getContent()}}{ -#' Get content -#' } #' } #' #' @author Emmanuel Blondel #' WFSDescribeFeatureType <- R6Class("WFSDescribeFeatureType", + inherit = OWSRequest, private = list( - request = NA, - content = NA, - - #buildRequest - buildRequest = function(url, version, typeName){ - namedParams <- list(request = "DescribeFeatureType", version = version, typeName = typeName) - request <- OWSRequest$new(url, namedParams, "text/xml") - return(request) - }, - - #fetchFeatureTypeDescription - fetchFeatureTypeDescription = function(xmlObj){ - namespaces <- OWSUtils$getNamespaces(xmlObj) - xsdNs <- OWSUtils$findNamespace(namespaces, "XMLSchema") - elementXML <- getNodeSet(xmlObj, "//ns:sequence/ns:element", xsdNs) - elements <- lapply(elementXML, WFSFeatureTypeElement$new) - return(elements) - } + name = "DescribeFeatureType" ), public = list( - initialize = function(url, version, typeName) { - private$request <- private$buildRequest(url, version, typeName) - xmlObj <- private$request$response - private$content = private$fetchFeatureTypeDescription(xmlObj) - }, - - #getRequest - getRequest = function(){ - return(private$request) - }, - - #getContent - getContent = function(){ - return(private$content) + initialize = function(op, url, version, typeName, ...) { + namedParams <- list(request = private$name, version = version, typeName = typeName) + super$initialize(op, url, namedParams, mimeType = "text/xml", ...) } ) - ) \ No newline at end of file diff --git a/R/WFSFeatureType.R b/R/WFSFeatureType.R index eb66b16..fdc4644 100644 --- a/R/WFSFeatureType.R +++ b/R/WFSFeatureType.R @@ -10,7 +10,7 @@ #' #' @section Methods: #' \describe{ -#' \item{\code{new(xmlObj, url, version)}}{ +#' \item{\code{new(xmlObj, op, url, version)}}{ #' This method is used to instantiate a \code{WFSFeatureType} object #' } #' \item{\code{getName()}}{ @@ -46,6 +46,7 @@ WFSFeatureType <- R6Class("WFSFeatureType", private = list( gmlIdAttributeName = "gml_id", + capabilities = NULL, url = NA, version = NA, @@ -56,9 +57,6 @@ WFSFeatureType <- R6Class("WFSFeatureType", defaultCRS = NA, WGS84BoundingBox = NA, - description = NA, - features = NULL, - #fetchFeatureType fetchFeatureType = function(xmlObj, version){ @@ -138,20 +136,96 @@ WFSFeatureType <- R6Class("WFSFeatureType", return(featureType) + } + + ), + public = list( + description = NULL, + features = NULL, + initialize = function(xmlObj, capabilities, url, version){ + + private$capabilities = capabilities + private$url = url + private$version = version + + featureType = private$fetchFeatureType(xmlObj, version) + private$name = featureType$name + private$title = featureType$title + private$abstract = featureType$abstract + private$keywords = featureType$keywords + private$defaultCRS = featureType$defaultCRS + private$WGS84BoundingBox = featureType$WGS84BoundingBox + }, - #fetchDescription - fetchDescription = function(){ - ftDescription <- WFSDescribeFeatureType$new(private$url, private$version, private$name) - return(ftDescription); + #getName + getName = function(){ + return(private$name) }, - #fetchFeatures - fetchFeatures = function(){ - - description <- self$getDescription() - ftFeatures <- WFSGetFeature$new(private$url, private$version, private$name) - xmlObj <- ftFeatures$getRequest()$response + #getTitle + getTitle = function(){ + return(private$title) + }, + + #getAbstract + getAbstract = function(){ + return(private$abstract) + }, + + #getKeywords + getKeywords = function(){ + return(private$keywords) + }, + + #getDefaultCRS + getDefaultCRS = function(){ + return(private$defaultCRS) + }, + + #getBoundingBox + getBoundingBox = function(){ + return(private$WGS84BoundingBox) + }, + + #getDescription + getDescription = function(){ + message("Fetching FeatureType description...") + op <- NULL + operations <- private$capabilities$getOperationsMetadata()$getOperations() + if(length(operations)>0){ + op <- operations[sapply(operations,function(x){x$getName()=="DescribeFeatureType"})] + if(length(op)>0){ + op <- op[[1]] + }else{ + stop("Operation 'DescribeFeatureType' not supported by this service") + } + } + ftDescription <- WFSDescribeFeatureType$new(op = op, private$url, private$version, private$name) + xmlObj <- ftDescription$response + namespaces <- OWSUtils$getNamespaces(xmlObj) + xsdNs <- OWSUtils$findNamespace(namespaces, "XMLSchema") + elementXML <- getNodeSet(xmlObj, "//ns:sequence/ns:element", xsdNs) + element <- lapply(elementXML, WFSFeatureTypeElement$new) + self$description <- elements + return(self$description) + }, + + #getFeatures + getFeatures = function(){ + message("Fetching FeatureType data...") + op <- NULL + operations <- private$capabilities$getOperationsMetadata()$getOperations() + if(length(operations)>0){ + op <- operations[sapply(operations,function(x){x$getName()=="GetFeature"})] + if(length(op)>0){ + op <- op[[1]] + }else{ + stop("Operation 'GetFeature' not supported by this service") + } + } + ftFeatures <- WFSGetFeature$new(op = op, private$url, private$version, private$name) + xmlObj <- ftFeatures$response #write the file to disk tempf = tempfile() @@ -208,74 +282,8 @@ WFSFeatureType <- R6Class("WFSFeatureType", ftFeatures[[attrName]] <- as(ftFeatures[[attrName]],attrType) } } - return(ftFeatures); + self$features <- ftFeatures; + return(self$features) } - - ), - public = list( - - initialize = function(xmlObj, url, version){ - - private$url = url - private$version = version - - featureType = private$fetchFeatureType(xmlObj, version) - private$name = featureType$name - private$title = featureType$title - private$abstract = featureType$abstract - private$keywords = featureType$keywords - private$defaultCRS = featureType$defaultCRS - private$WGS84BoundingBox = featureType$WGS84BoundingBox - - }, - - #getName - getName = function(){ - return(private$name) - }, - - #getTitle - getTitle = function(){ - return(private$title) - }, - - #getAbstract - getAbstract = function(){ - return(private$abstract) - }, - - #getKeywords - getKeywords = function(){ - return(private$keywords) - }, - - #getDefaultCRS - getDefaultCRS = function(){ - return(private$defaultCRS) - }, - - #getBoundingBox - getBoundingBox = function(){ - return(private$WGS84BoundingBox) - }, - - #getDescription - getDescription = function(){ - if(typeof(private$description) != "environment"){ - message("Fetching FeatureType description...") - private$description <- private$fetchDescription() - } - return(private$description) - }, - - #getFeatures - getFeatures = function(){ - if(is.null(private$features)){ - message("Fetching FeatureType data...") - private$features <- private$fetchFeatures() - } - return(private$features) - } - ) ) \ No newline at end of file diff --git a/R/WFSGetFeature.R b/R/WFSGetFeature.R index 84ce43d..4de84d9 100644 --- a/R/WFSGetFeature.R +++ b/R/WFSGetFeature.R @@ -8,36 +8,22 @@ #' #' @section Methods: #' \describe{ -#' \item{\code{new(url, version, typeName)}}{ +#' \item{\code{new(op, url, version, typeName, ...)}}{ #' This method is used to instantiate a WFSGetFeature object #' } -#' \item{\code{getRequest()}}{ -#' Get GetFeature request -#' } #' } #' #' @author Emmanuel Blondel #' WFSGetFeature <- R6Class("WFSGetFeature", + inherit = OWSRequest, private = list( - request = NA, - - #buildRequest - buildRequest = function(url, version, typeName){ - namedParams <- list(request = "GetFeature", version = version, typeName = typeName) - request <- OWSRequest$new(url, namedParams, "text/xml") - return(request) - } - + name = "GetFeature" ), public = list( - initialize = function(url, version, typeName) { - private$request <- private$buildRequest(url, version, typeName) - }, - - #getRequest - getRequest = function(){ - return(private$request) + initialize = function(op, url, version, typeName, ...) { + namedParams <- list(request = private$name, version = version, typeName = typeName) + super$initialize(op, url, namedParams, mimeType = "text/xml", ...) } ) ) \ No newline at end of file diff --git a/man/CSWClient.Rd b/man/CSWClient.Rd index 2fa4eb3..c32ea81 100644 --- a/man/CSWClient.Rd +++ b/man/CSWClient.Rd @@ -28,6 +28,9 @@ CSWClient \item{\code{getCapabilities()}}{ Get service capabilities. Inherited from OWS Client } + \item{\code{getRecordById(id, ...)}}{ + Get a record by Id. + } } } diff --git a/man/CSWGetRecordById.Rd b/man/CSWGetRecordById.Rd new file mode 100644 index 0000000..acbb320 --- /dev/null +++ b/man/CSWGetRecordById.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CSWGetRecordById.R +\docType{class} +\name{CSWGetRecordById} +\alias{CSWGetRecordById} +\title{CSWGetRecordById} +\format{\code{\link{R6Class}} object.} +\usage{ +CSWGetRecordById +} +\value{ +Object of \code{\link{R6Class}} for modelling a CSW GetRecordById request +} +\description{ +CSWGetRecordById +} +\section{Methods}{ + +\describe{ + \item{\code{new(url, version, id)}}{ + This method is used to instantiate a CSWGetRecordById object + } + \item{\code{getRequest()}}{ + Get GetRecordById request + } +} +} + +\author{ +Emmanuel Blondel +} +\keyword{CSW} +\keyword{GetRecordById} +\keyword{OGC} diff --git a/man/OWSOperation.Rd b/man/OWSOperation.Rd index 2afb042..c80b530 100644 --- a/man/OWSOperation.Rd +++ b/man/OWSOperation.Rd @@ -20,6 +20,15 @@ OWSOperation \item{\code{new(xmlObj, service, version)}}{ This method is used to instantiate an OWSOperation object } + \item{\code{getName()}}{ + Get name + } + \item{\code{getParameters()}}{ + Get the list of parameters + } + \item{\code{getParameter(name)}}{ + Get a given parameter + } } } diff --git a/man/WFSClient.Rd b/man/WFSClient.Rd index 7805170..f84d2e3 100644 --- a/man/WFSClient.Rd +++ b/man/WFSClient.Rd @@ -32,7 +32,7 @@ WFSClient Get the description of a given featureType } \item{\code{getFeatures(typeName)}}{ - Retrieves the features for a given feature type + Retrieves the features for a given feature type. } } } diff --git a/man/WFSDescribeFeatureType.Rd b/man/WFSDescribeFeatureType.Rd index b05e8dc..a106802 100644 --- a/man/WFSDescribeFeatureType.Rd +++ b/man/WFSDescribeFeatureType.Rd @@ -17,15 +17,9 @@ WFSDescribeFeatureType \section{Methods}{ \describe{ - \item{\code{new(url, version, typeName)}}{ + \item{\code{new(op, url, version, typeName)}}{ This method is used to instantiate a WFSDescribeFeatureType object } - \item{\code{getRequest()}}{ - Get DescribeFeatureType request - } - \item{\code{getContent()}}{ - Get content - } } } diff --git a/man/WFSFeatureType.Rd b/man/WFSFeatureType.Rd index e500787..78d13b3 100644 --- a/man/WFSFeatureType.Rd +++ b/man/WFSFeatureType.Rd @@ -20,7 +20,7 @@ Class used internally by ows4R. \section{Methods}{ \describe{ - \item{\code{new(xmlObj, url, version)}}{ + \item{\code{new(xmlObj, op, url, version)}}{ This method is used to instantiate a \code{WFSFeatureType} object } \item{\code{getName()}}{ diff --git a/man/WFSGetFeature.Rd b/man/WFSGetFeature.Rd index 12def01..5f530b7 100644 --- a/man/WFSGetFeature.Rd +++ b/man/WFSGetFeature.Rd @@ -17,12 +17,9 @@ WFSGetFeature \section{Methods}{ \describe{ - \item{\code{new(url, version, typeName)}}{ + \item{\code{new(op, url, version, typeName, ...)}}{ This method is used to instantiate a WFSGetFeature object } - \item{\code{getRequest()}}{ - Get GetFeature request - } } } diff --git a/tests/testthat/test_CSWClient.R b/tests/testthat/test_CSWClient.R index 6b7a6db..0ad0127 100644 --- a/tests/testthat/test_CSWClient.R +++ b/tests/testthat/test_CSWClient.R @@ -4,12 +4,19 @@ # Description: Integration tests for CSW Client #======================= require(ows4R, quietly = TRUE) +require(geometa) require(testthat) -context("WFS") +context("CSW") -test_that("CSW 2.0.2",{ +test_that("CSW 2.0.2 - GetCapabilities",{ csw <- CSWClient$new("http://localhost:8282/geonetwork/srv/eng/csw", "2.0.2") expect_is(csw, "CSWClient") caps <- csw$getCapabilities() expect_is(caps, "CSWCapabilities") +}) + +test_that("CSW 2.0.2 - GetRecordById",{ + csw <- CSWClient$new("http://www.fao.org/geonetwork/srv/en/csw", "2.0.2") + md <- csw$getRecordById("fao-species-map-tth", outputSchema = "http://www.isotc211.org/2005/gmd") + expect_is(md, "ISOMetadata") }) \ No newline at end of file