# sos4R: Extending package functions and advanced configuration

#### Daniel Nüst

#### 2019-04-13

Abstract

The sos4R package provides simple yet powerful access to OGC Sensor Observation Service instances. The package supports both encapsulation and abstraction from the service interface for novice users as well as powerful request building for specialists. sos4R is motivated by the idea to close the gap between the Sensor Web and tools for (geo-)statistical analyses.

This document shows how to do advanced configurations, such as adding data parsing functions or implementing & registering functions for encoding and decoding.

The package is published under GPL 2 license within the geostatistics community of 52°North Initiative for Geospatial Open Source Software.

    library("sos4R")
    mySOS <- SOS(url = "http://sensorweb.demo.52north.org/52n-sos-webapp/service/pox",
                 binding = "POX", dcpFilter = list("POX" = "/pox"))

    ## [sos4R] Created SOS for URL http://sensorweb.demo.52north.org/52n-sos-webapp/service/pox

## Changing Handling Functions

The flexibility of the specifications that model the markup requests and responses, especially the observation encoding, is too high to handle all possible cases within `sos4R`. Thus an equally flexible mechanism for users to adopt the steps of encoding and decoding documents to their needs is needed.

The process of data download comprises

1. building the request,
2. encoding the request,
3. sending encoded request and receiving response,
4. decoding the responses, and
5. applying the correct R data type to the respective data values.

This can be seen as a fixed, ordered workflow a user has to follow where each step build upon the input of the previous. To ensure flexibility within these steps of the workflow but also to maximize reusability of existing functionality, a mechanism to exchange the functions that are used in these steps is provided.

Step 1, the building of requests, is the assembly of the request parameters passed to the `sos4R` functions into an R object. It is documented in section GetObservation. Step 3, the sending receiving of documents to respectively from a service, does not need to be changed directly but the user, who only can configure the binding.

The remainder of this document explains how to configure steps 2 (encoding), 4 (decoding) and 5 (data parsing) of the process.

## Include and Exclude Own Functions

The functions used in the exchangeable steps are organized in lists. To base your own list of functions on the existing ones, thereby not having to start from scratch, you can combine the default list of functions with your own. Use the following functions:

To add your own function, simply add it as a named argument. You can add as many as you like in the `...` parameter. If a function with that identifier already exists in the default list it will be replaced by your function. For further adjustments you can explicitly include and exclude functions by identifier. Please be aware that inclusion is applied first, then exclusion. It is also important that you also have to include that functions you just added manually!

Examples of function list generation with parsing functions:

    parsers <- SosParsingFunctions(
        "ExceptionReport" = function() {
            return("Got Exception!")
        },
        include = c("GetObservation", "ExceptionReport"))
    print(names(parsers))

    ## [1] "GetObservation"  "ExceptionReport"

    parsers <- SosParsingFunctions(
            "ExceptionReport" = function() {
                return("Got Exception!")
            },
            include = c("GetCapabilities"))
    print(names(parsers))

    ## [1] "GetCapabilities"

The following snipped shows how to remove a large part of parsers using `exclude` and then prints the names of the remaining ones.

    parsers <- SosParsingFunctions(
            exclude = names(SosParsingFunctions())[5:29])
    print(names(parsers))

    ## [1] "GetCapabilities"
    ## [2] "DescribeSensor"
    ## [3] "GetObservation"
    ## [4] "GetObservationResponse"
    ## [5] "application/vnd.google-earth.kml+xml"
    ## [6] "kml"
    ## [7] "text/xml"

## Encoders

The current list of a connection’s encoders can be accessed with

    sosEncoders(mySOS)

A complete list of the existing encoders names:

    names(sosEncoders(mySOS))

    ## [1] "SOAP" "POX"  "KVP"

Here the idea of organizing the encoding functions becomes clear: One base encoding function is given, which is a generic method that must exist for alle elements that need to be encoded.

    myPostEncoding <- function(object, sos, verbose) {
        return(str(object))
    }
    # Connection will not be established because of mising objects
    mySOS2 = SOS(sosUrl(mySOS),
        encoders = SosEncodingFunctions("POST" = myPostEncoding))

Encoding functions can be overriden for many specific objects. The signature of the encoding function consists of the object, `obj`, a SOS object, `sos`, and the optional `verbose` parameter.

    showMethods("encodeXML")

    ## Function: encodeXML (package sos4R)
    ## obj="GmlDirectPosition", sos="SOS"
    ## obj="GmlEnvelope", sos="SOS"
    ## obj="GmlEnvelope", sos="SOS_1.0.0"
    ##     (inherited from: obj="GmlEnvelope", sos="SOS")
    ## obj="GmlLineString", sos="SOS"
    ## obj="GmlPoint", sos="SOS"
    ## obj="GmlPointProperty", sos="SOS"
    ## obj="GmlPolygon", sos="SOS"
    ## obj="GmlTimeInstant", sos="SOS"
    ## obj="GmlTimeInstantProperty", sos="SOS"
    ## obj="GmlTimePeriod", sos="SOS"
    ## obj="GmlTimePeriod", sos="SOS_1.0.0"
    ##     (inherited from: obj="GmlTimePeriod", sos="SOS")
    ## obj="GmlTimePosition", sos="SOS"
    ## obj="OgcBBOX", sos="SOS"
    ## obj="OgcBBOX", sos="SOS_1.0.0"
    ##     (inherited from: obj="OgcBBOX", sos="SOS")
    ## obj="OgcComparisonOps", sos="SOS"
    ## obj="OgcContains", sos="SOS"
    ## obj="OgcIntersects", sos="SOS"
    ## obj="OgcOverlaps", sos="SOS"
    ## obj="POSIXct", sos="SOS_1.0.0"
    ##     (inherited from: obj="POSIXt", sos="SOS")
    ## obj="POSIXt", sos="SOS"
    ## obj="SosEventTime", sos="SOS"
    ## obj="SosEventTime", sos="SOS_1.0.0"
    ##     (inherited from: obj="SosEventTime", sos="SOS")
    ## obj="SosFeatureOfInterest", sos="SOS"
    ## obj="SosFeatureOfInterest", sos="SOS_1.0.0"
    ##     (inherited from: obj="SosFeatureOfInterest", sos="SOS")
    ## obj="TM_After", sos="SOS"
    ## obj="TM_Before", sos="SOS"
    ## obj="TM_During", sos="SOS"
    ## obj="TM_During", sos="SOS_1.0.0"
    ##     (inherited from: obj="TM_During", sos="SOS")
    ## obj="TM_Equals", sos="SOS"
    ## obj="character", sos="SOS"
    ## obj="xml_document", sos="SOS"
    ## obj="xml_node", sos="SOS"

    showMethods("encodeKVP")

    ## Function: encodeKVP (package sos4R)
    ## obj="OgcBinaryTemporalOp", sos="SOS"
    ## obj="POSIXct", sos="SOS_1.0.0"
    ##     (inherited from: obj="POSIXt", sos="SOS")
    ## obj="POSIXt", sos="SOS"
    ## obj="SosEventTime", sos="SOS"
    ## obj="SosEventTime", sos="SOS_1.0.0"
    ##     (inherited from: obj="SosEventTime", sos="SOS")
    ## obj="TM_During", sos="SOS_1.0.0"
    ##     (inherited from: obj="OgcBinaryTemporalOp", sos="SOS")

A useful example can be overriding the encoding method for time classes (`POSIXt`) as presented below – see the demo `southesk` for the application of this code.

    setMethod(f = "encodeXML",
      signature = signature(obj = "POSIXt", sos = "SOS"),
        def = function(obj, sos, verbose) {
          if(verbose) cat("Using my own time encoding... ")

          # time zone hack to fix that the time format option
          # %z does not work on windows machines:
          .time <- obj + 11 * 60 * 60 # add 11 hours
          formatted <- strftime(x = .time,
            format = sosTimeFormat(sos))
          formatted <- paste(formatted,
            "+11:00", sep = "") # append 11:00

          if(verbose) cat("Formatted ", toString(obj),
            " to ", formatted, "\n")
          return(formatted)
        }
    )

All later calls for encoding any classes with time will then reference this newly defined method. Be aware that this changes the encoding globally, in contrast to converters and parsers which can be changed for every instance of class `SOS`.

## Decoders

The terms parsing and decoding are used as synonyms for the process of processing an XML document to create an R object. XML documents are made out of hierarchical elements. That is why the decoding functions are organized in a listed, whose names are the XML elements’ names it parses.

The current list of a connection’s parsers can be accessed with the following function.

    sosParsers(mySOS)

A complete list of the elements with existing encoders is shown below. These are not only names of XML elements, but also MIME types. Here the idea of organizing the encoding functions becomes clear: For every XML element or document type that must be parsed there is a function given in the list.

    names(sosParsers(mySOS))

    ##  [1] "GetCapabilities"
    ##  [2] "DescribeSensor"
    ##  [3] "GetObservation"
    ##  [4] "GetObservationResponse"
    ##  [5] "GetFeatureOfInterestResponse"
    ##  [6] "GetObservationById"
    ##  [7] "ows:ExceptionReport"
    ##  [8] "GetDataAvailabilityResponse"
    ##  [9] "om:Measurement"
    ## [10] "om:member"
    ## [11] "om:Observation"
    ## [12] "om:ObservationCollection"
    ## [13] "om:result"
    ## [14] "swe:DataArray"
    ## [15] "swe:elementType"
    ## [16] "swe:encoding"
    ## [17] "swe:values"
    ## [18] "swe:Position"
    ## [19] "swe:location"
    ## [20] "swe:Vector"
    ## [21] "swe:coordinate"
    ## [22] "om:GeometryObservation"
    ## [23] "om:CategoryObservation"
    ## [24] "om:CountObservation"
    ## [25] "om:TruthObservation"
    ## [26] "om:TemporalObservation"
    ## [27] "om:ComplexObservation"
    ## [28] "text/csv"
    ## [29] "text/xml;subtype=\"om/1.0.0\""
    ## [30] "application/vnd.google-earth.kml+xml"
    ## [31] "kml"
    ## [32] "text/xml"

Parser selection can also be based on the mimeType of the returned document. Please be aware that this also can be a problem if you want to exchange a parse by operation name, which is done after switching the function based on the mime type. In other words, the exchange by operation name only works if the response type is as expected.

If you want to replace only selected parsers use the include parameter as described above. You can also base your own parsing functions on a variety of existing parsing functions. For example you can replace the base function for `om:ObservationCollection`, element name `ObservationCollection`, but still use the parsing function for `om:Observation` within your own function if you include it in the parser list. The existing parsing functions are all named in the pattern `parse<ElementName>()`. Please be aware that some parsers require a parameter `sos` of class `SOS` upon which they might rely for example for formatting information, and some also have `verbose` (type `logical`). In case of “unused arguments” errors, please try different signatures.

    # Create own parsing function:
    myER <- function(xml, sos, verbose) {
        return("EXCEPTION!!!11")
    }
    myParsers <- SosParsingFunctions("ows:ExceptionReport" = myER)
    exceptionParserSOS <- SOS(url = "http://sensorweb.demo.52north.org/52n-sos-webapp/service/pox",
                              parsers = myParsers,
                              binding = "POX", useDCPs = FALSE)

    ## [sos4R] Created SOS for URL http://sensorweb.demo.52north.org/52n-sos-webapp/service/pox

    # Triggers exception:
    erroneousResponse <- getObservation(exceptionParserSOS,
                                        #verbose = TRUE,
                                        offering = sosOfferings(exceptionParserSOS)[[1]],
                                        observedProperty = list("Bazinga!"))
    print(erroneousResponse)

    ## [1] "EXCEPTION!!!11"

To disable all parsing, you can use the function `SosDisabledParsers()`. This effectively just “passes through”" all received data because the list returned by the function only contains the top-most parsing functions for SOS operations and exception reports.

    SosDisabledParsers()

    names(SosDisabledParsers())

    ## [1] "GetCapabilities"     "DescribeSensor"      "GetObservation"
    ## [4] "GetObservationById"  "ows:ExceptionReport"

This is also the recommended way to start if you want to set-up your own parsers (given you have responses in XML) and an alternative to debugging if you want to inspect responses directly.

The next example shows how the response (in this case the request is intentionally incorrent and triggers an exception) is passed through as an object of class `xml_document`:

    disabledParserSOS <- SOS(sosUrl(mySOS),
                             parsers = SosDisabledParsers(),
                             binding = sosBinding(mySOS),
                             dcpFilter = mySOS@dcpFilter)

    ## [sos4R] Created SOS for URL http://sensorweb.demo.52north.org/52n-sos-webapp/service/pox

    unparsed <- getObservation(disabledParserSOS,
                               offering = sosOfferings(disabledParserSOS)[[1]],
                               observedProperty = list("Bazinga!"))
    class(unparsed)

    ## [1] "xml_document" "xml_node"

    # (Using XML functions here for accesing the root of a
    # document and the name of an element.)
    unparsed

    ## {xml_document}
    ## <ExceptionReport version="1.0.0" schemaLocation="http://www.opengis.net/ows/1.1 http://schemas.opengis.net/ows/1.1.0/owsAll.xsd" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    ## [1] <ows:Exception exceptionCode="InvalidParameterValue" locator="observ ...

## Data Converters

A list of named functions to be used by the parsing methods to convert data values to the correct R type, which are mostly based on the unit of measurement code.

The conversion functions always take two parameters: `x` is the object to be converted, `sos` is the service where the request was received from.

The available functions are basically wrappers for coercion functions, for example `as.double()`. The only method exploiting the second argument is the one for conversion of time stamps which uses the time format saved with the object of class `SOS` in a call to `strptime`.

    value <- 2.0
    value.string <- sosConvertString(x = value, sos = mySOS)
    print(class(value.string))

    ## [1] "character"

    value <- "2.0"
    value.double <- sosConvertDouble(x = value, sos = mySOS)
    print(class(value.double))

    ## [1] "numeric"

    value <- "1"
    value.logical <- sosConvertLogical(x = value, sos = mySOS)
    print(class(value.logical))

    ## [1] "logical"

    value <- "2010-01-01T12:00:00.000"
    value.time <- sosConvertTime(x = value, sos = mySOS)
    print(class(value.time))

    ## [1] "POSIXct" "POSIXt"

The full list of currently supported units can be seen below. It mostly contains common numerical units which are converted to type `double`.

    names(SosDataFieldConvertingFunctions())

    ##  [1] "fallBack"
    ##  [2] "urn:ogc:data:time:iso8601"
    ##  [3] "urn:ogc:property:time:iso8601"
    ##  [4] "urn:ogc:phenomenon:time:iso8601"
    ##  [5] "http://www.opengis.net/def/property/OGC/0/SamplingTime"
    ##  [6] "http://www.opengis.net/def/property/OGC/0/PhenomenonTime"
    ##  [7] "urn:ogc:def:parameter:x-istsos:1.0:time:iso8601"
    ##  [8] "sos:time"
    ##  [9] "m"
    ## [10] "m2"
    ## [11] "m3"
    ## [12] "s"
    ## [13] "ms"
    ## [14] "us"
    ## [15] "g"
    ## [16] "rad"
    ## [17] "K"
    ## [18] "C"
    ## [19] "cd"
    ## [20] "%"
    ## [21] "ppth"
    ## [22] "ppm"
    ## [23] "ppb"
    ## [24] "pptr"
    ## [25] "mol"
    ## [26] "sr"
    ## [27] "Hz"
    ## [28] "N"
    ## [29] "Pa"
    ## [30] "J"
    ## [31] "W"
    ## [32] "A"
    ## [33] "V"
    ## [34] "F"
    ## [35] "Ohm"
    ## [36] "S"
    ## [37] "Wb"
    ## [38] "Cel"
    ## [39] "T"
    ## [40] "H"
    ## [41] "lm"
    ## [42] "lx"
    ## [43] "Bq"
    ## [44] "Gy"
    ## [45] "Sv"
    ## [46] "gon"
    ## [47] "deg"
    ## [48] "'"
    ## [49] "''"
    ## [50] "l"
    ## [51] "L"
    ## [52] "ar"
    ## [53] "t"
    ## [54] "bar"
    ## [55] "u"
    ## [56] "eV"
    ## [57] "AU"
    ## [58] "pc"
    ## [59] "degF"
    ## [60] "hPa"
    ## [61] "mm"
    ## [62] "nm"
    ## [63] "cm"
    ## [64] "km"
    ## [65] "m/s"
    ## [66] "m2/s"
    ## [67] "m3/s"
    ## [68] "kg"
    ## [69] "mg"
    ## [70] "uom"
    ## [71] "urn:ogc:data:feature"
    ## [72] "http://www.opengis.net/def/property/OGC/0/FeatureOfInterest"
    ## [73] "ug/m3"
    ## [74] "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
    ## [75] "degC"
    ## [76] "°C"
    ## [77] "http://www.opengis.net/def/property/OGC/0/PhenomenonTime"

The current list of a SOS connection’s converters can be accessed with

    sosDataFieldConverters(mySOS)

The following connection shows a typical workflow of connecting to a new SOS for the first time, what the errors for missing converters look like, and how to add them to the SOS connection.

    testsos <- SOS("http://sensorweb.demo.52north.org/52n-sos-webapp/sos/pox", binding = "POX", dcpFilter = list("POX" = "/pox"))

    ## [sos4R] Created SOS for URL http://sensorweb.demo.52north.org/52n-sos-webapp/sos/pox

    testsosoffering <- sosOfferings(testsos)[["http___www.52north.org_test_offering_1"]]
    testsosobsprop <- sosObservedProperties(testsosoffering)[1]
    getObservation(sos = testsos, offering = testsosoffering, observedProperty = testsosobsprop)

    ## Warning in .valParser(values = xml2::xml_child(x = obj, search = sweValuesName, : No converter for the unit of measurement  test_unit_1  with the definition  http://www.52north.org/test/observableProperty/1 ! Trying a default, but you can add one when creating a SOS using SosDataFieldConvertingFunctions().

    ## [sos4R] Finished getObservation to http://sensorweb.demo.52north.org/52n-sos-webapp/sos/pox
    ##  --> received 1 observation(s) having 12 result values [ 12 ].

    ## Object of class OmObservationCollection with 1 members.

Looking at the raw response data gives us a hint at a suitable type.

    getObservation(sos = testsos, offering = testsosoffering, observedProperty = testsosobsprop,
                   inspect = TRUE)

    ## [.sosRequest_1.0.0] POST!
    ## [.sosRequest_1.0.0] REQUEST:
    ## {xml_document}
    ## <GetObservation service="SOS" version="1.0.0" xmlns="http://www.opengis.net/sos/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:sos="http://www.opengis.net/sos/1.0" xmlns:om="http://www.opengis.net/om/1.0" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml">
    ## [1] <sos:offering>http___www.52north.org_test_offering_1</sos:offering>
    ## [2] <sos:observedProperty>http://www.52north.org/test/observableProperty ...
    ## [3] <sos:responseFormat>text/xml;subtype="om/1.0.0"</sos:responseFormat>
    ## [.getObservation_1.0.0] RESPONSE DOC:
    ## {xml_document}
    ## <ObservationCollection id="oc_1555164519999" schemaLocation="http://www.opengis.net/om/1.0 http://schemas.opengis.net/om/1.0.0/om.xsd http://www.opengis.net/sampling/1.0 http://schemas.opengis.net/sampling/1.0.0/sampling.xsd http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosAll.xsd http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/base/gml.xsd http://www.opengis.net/swe/1.0.1 http://schemas.opengis.net/sweCommon/1.0.1/swe.xsd" xmlns:om="http://www.opengis.net/om/1.0" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sa="http://www.opengis.net/sampling/1.0" xmlns:swe="http://www.opengis.net/swe/1.0.1">
    ## [1] <gml:boundedBy>\n  <gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326 ...
    ## [2] <om:member>\n  <om:Observation gml:id="o_1555164519999">\n    <om:sa ...

    ## Warning in .valParser(values = xml2::xml_child(x = obj, search = sweValuesName, : No converter for the unit of measurement  test_unit_1  with the definition  http://www.52north.org/test/observableProperty/1 ! Trying a default, but you can add one when creating a SOS using SosDataFieldConvertingFunctions().

    ## [sos4R] Finished getObservation to http://sensorweb.demo.52north.org/52n-sos-webapp/sos/pox
    ##  --> received 1 observation(s) having 12 result values [ 12 ].

    ## Object of class OmObservationCollection with 1 members.

Converters may be matched by XML properties `swe:Quantity[@definition]` or `swe:uom[@code]`.

    testconverters <- SosDataFieldConvertingFunctions(
      # one of the following would suffice
      "test_unit_1" = sosConvertDouble,
        "http://www.52north.org/test/observableProperty/1" = sosConvertDouble
      )

    testsos <- SOS("http://sensorweb.demo.52north.org/52n-sos-webapp/sos/pox", binding = "POX", dcpFilter = list("POX" = "/pox"),
                   dataFieldConverters = testconverters)

    ## [sos4R] Created SOS for URL http://sensorweb.demo.52north.org/52n-sos-webapp/sos/pox

    testsosoffering <- sosOfferings(testsos)[["http___www.52north.org_test_offering_1"]]
    data <- getObservation(sos = testsos, offering = testsosoffering, observedProperty = testsosobsprop)

    ## [sos4R] Finished getObservation to http://sensorweb.demo.52north.org/52n-sos-webapp/sos/pox
    ##  --> received 1 observation(s) having 12 result values [ 12 ].

Then retrieve the data with the correct type.

    head(sosResult(data))

    ##                        phenomenonTime
    ## o_1555164520250.1 2012-11-19 13:00:00
    ## o_1555164520250.2 2012-11-19 13:01:00
    ## o_1555164520250.3 2012-11-19 13:02:00
    ## o_1555164520250.4 2012-11-19 13:03:00
    ## o_1555164520250.5 2012-11-19 13:04:00
    ## o_1555164520250.6 2012-11-19 13:05:00
    ##                   http://www.52north.org/test/observableProperty/1
    ## o_1555164520250.1                                              1.0
    ## o_1555164520250.2                                              1.1
    ## o_1555164520250.3                                              1.2
    ## o_1555164520250.4                                              1.3
    ## o_1555164520250.5                                              1.4
    ## o_1555164520250.6                                              1.5

The metadata of the observed property is also accessible.

    attributes(sosResult(data)[[1]])

    ## $class
    ## [1] "POSIXct" "POSIXt"
    ##
    ## $tzone
    ## [1] ""
    ##
    ## $name
    ## [1] "phenomenonTime"
    ##
    ## $definition
    ## [1] "http://www.opengis.net/def/property/OGC/0/PhenomenonTime"

Warnings may also include messages if no converter is available for a unit of measurement, for example:

    In .valParser(values = obj[[sweValuesName]], fields = .fields,  ... :
                    No converter for the unit of measurement  S/m  with the definition  http://mmisw.org/ont/cf/parameter/conductivity ! Trying a default, but you can add one when creating a SOS using SosDataFieldConvertingFunctions().
    In .valParser(values = obj[[sweValuesName]], fields = .fields,  ... :
                    No converter found! Skipping field Conductivity
    No converter found! Skipping field http://mmisw.org/ont/cf/parameter/conductivity
    No converter found! Skipping field S/m

This shows warnings about unknown units of measurement and a swe:Quantity element (which describes a numeric field) without a given unit of measurement (which it should have as a numeric field). The next example creates conversion functions for these fields and repeats the operation.

    myConverters <- SosDataFieldConvertingFunctions(
        "S/m" = sosConvertDouble,
        "http://mmisw.org/ont/cf/parameter/sea_water_salinity"
                = sosConvertDouble)

## Exception Handling

When working with `sos4R`, two kinds of errors must be handled: service exceptions and errors within the package. The former can occur when a request is invalid or a service encounters internal exceptions. The latter can mean a bug or illegal settings within the package.

To understand both types of errorenous states, this sections explains the contents of the exception reports returned by the service and the functionalities to investigate the inner workings of the package.

### OWS Service Exceptions

The service exceptions returned by a SOS are described in OGC Web Services Common (Whiteside, 2007) clause 8. The classes to handle the returned exceptions in sos4R are `OwsExceptionReport`, which contains a list of exception reports, and `OwsException`, which contains slots for the parameters exception text(s), exception code, and locator. These are defined as follows and can be implementation specific.

- ExceptionText Text describing specific exception represented by the exceptionCode
- exceptionCode Code representing type of this exception
- locator Indicator of location in the client’s operation request where this exception was encountered

The standard exception codes and meanings are accessible by calling the following function.

    library("knitr")
    knitr::kable(OwsExceptionsData())

exceptionCode | meaningOfCode | locator | httpStatusCode | httpMessage
--- | --- | --- | --- | ---
OperationNotSupported | Request is for an operation that is not supported by this server | Name of operation not supported | 501 | Not Implemented
MissingParameterValue | Operation request does not include a parameter value, and this server did not declare a default parameter value for that parameter | Name of missing parameter | 400 | Bad request
InvalidParameterValue | Operation request contains an invalid parameter value | Name of parameter with invalid value | 400 | Bad request
VersionNegotiationFailed | List of versions in ‘AcceptVersions’ parameter value in GetCapabilities operation request did not include any version supported by this server | None, omit ‘locator’ parameter | 400 | Bad request
InvalidUpdateSequence | Value of (optional) updateSequence parameter in GetCapabilities operation request is greater than current value of service metadata updateSequence number | None, omit ‘locator’ parameter | 400 | Bad request
OptionNotSupported | Request is for an option that is not supported by this server | Identifier of option not supported | 501 | Not implemented
NoApplicableCode | No other exceptionCode specified by this service and server applies to this exception | None, omit ‘locator’ parameter | 3xx, 4xx, 5xx | Internal Server Error

    response <- try(getObservationById(sos = mySOS,
                                       observationId = ""))

    ## Warning in .handleExceptionReport(sos, .response): Object of class OwsExceptionReport; version: 1.0.0; lang: NA;
    ##  1 exception(s) (code @ locator : text):
    ##   MissingParameterValue @ ObservationId :
    ##  The value for the parameter 'ObservationId' is missing in the request!

The exception is also stored in the `response` object.

    response

    ## Object of class OwsExceptionReport; version: 1.0.0; lang: NA;
    ##  1 exception(s) (code @ locator : text):
    ##   MissingParameterValue @ ObservationId :
    ##  The value for the parameter 'ObservationId' is missing in the request!
    ##