xquery version "3.1"; (: : NOSA HEADER START : : The contents of this file are subject to the terms of the NASA Open : Source Agreement (NOSA), Version 1.3 only (the "Agreement"). You may : not use this file except in compliance with the Agreement. : : You can obtain a copy of the agreement at : docs/NASA_Open_Source_Agreement_1.3.txt : or : https://cdaweb.gsfc.nasa.gov/WebServices/NASA_Open_Source_Agreement_1.3.txt. : : See the Agreement for the specific language governing permissions : and limitations under the Agreement. : : When distributing Covered Code, include this NOSA HEADER in each : file and include the Agreement file at : docs/NASA_Open_Source_Agreement_1.3.txt. If applicable, add the : following below this NOSA HEADER, with the fields enclosed by : brackets "[]" replaced with your own identifying information: : Portions Copyright [yyyy] [name of copyright owner] : : NOSA HEADER END : : Copyright (c) 2016-2024 United States Government as represented by : the National Aeronautics and Space Administration. No copyright is : claimed in the United States under Title 17, U.S.Code. All Other : Rights Reserved. : :) (:~ : This library module provides common constants and functions for : accessing the Coordinated Data Analysis System (CDAS) Web Services : @see https://cdaweb.gsfc.nasa.gov/WebServices/. : : Note: This module is incomplete and a mere demonstration. : : @author B. Harris :) module namespace cdasws = "http://cdaweb.gsfc.nasa.gov/cdasws"; declare namespace hc="http://expath.org/ns/http-client"; declare namespace util="http://exist-db.org/xquery/util"; (: For eXist-db 3.x, change above to the following declare namespace console="http://exist-db.org/xquery/console"; :) declare namespace json="http://www.json.org"; declare namespace cdas="http://cdaweb.gsfc.nasa.gov/schema"; declare namespace hapi="http://hpde.gsfc.nasa.gov/hapi"; import module namespace spdfdatetime = "http://spdf.gsfc.nasa.gov/datetime" at "https://cdaweb.gsfc.nasa.gov/WebServices/hapi/datetime.xql"; (:~ : The /Spase/NumericalData/AccessInformation/AccessURL/Name value : for CDAWeb. :) declare variable $cdasws:accessUrlName := "CDAWeb"; (:~ : Common part of URL for the CDAS web services. :) declare variable $cdasws:url := "https://cdaweb.gsfc.nasa.gov/WS/cdasr/1/dataviews/sp_phys/datasets"; (: "http://cdaweb-dev.sci.gsfc.nasa.gov/WS/cdasr/1/dataviews/sp_phys/datasets"; :) (:~ : Produces a sequence of HTTP query paramter strings for the given : paramter name and values. : : @param name query parameter name. : @param values query parameter values. : @return a sequence of HTTP query paramter strings of the form : "name=value" with value uri encoded. :) declare function cdasws:getQueryParams( $name as xs:string, $values as xs:string*) as xs:string* { for $value in $values return $name || "=" || encode-for-uri($value) }; (:~ : Gets the CDAS datasets. : : @param $observatoryGroups observatory group values. Omitting this : parameter indicates that no datasets are eliminated based upon : their observatoryGroup value. : @param $instrumentType an instrument type value. Omitting this : parameter indicates that no datasets are eliminated based upon : their instrumentType value. : @param $observatory an observatory value. Omitting this : parameter indicates that no datasets are eliminated based upon : their observatory value. : @param $instrument an instrument value. Omitting this : parameter indicates that no datasets are eliminated based upon : their instrument value. : @param $startDate the start of a time interval. If this parameter : is ommited, the time interval will begin infinitely in the past. : @param $stopDate the start of a time interval. If this parameter : is ommited, the time interval will begin infinitely in the future. : @param $idPattern a java.util.regex compatible regular expression : that must match the dataset's identifier value. Omitting this : parameter is equivalent to ".*". : @param $labelPattern a java.util.regex compatible regular expression : that must match the dataset's label value. Omitting this : parameter is equivalent to ".*". : @param $notesPattern a java.util.regex compatible regular expression : that must match the dataset's notes value. Omitting this : parameter is equivalent to ".*". : @return the requested cdas:DatasetDescription. :) declare function cdasws:getDatasets( $observatoryGroups as xs:string*, $instrumentType as xs:string?, $observatory as xs:string?, $instrument as xs:string?, $startDate as xs:dateTime?, $stopDate as xs:dateTime?, $idPattern as xs:string?, $labelPattern as xs:string?, $notesPattern as xs:string? ) as node()* { let $q1 := if (exists($observatoryGroups)) then cdasws:getQueryParams("observatoryGroup", $observatoryGroups) else () let $q2 := if (exists($instrumentType)) then ($q1, "intrumentType=" || encode-for-uri($instrumentType)) else ($q1) let $q3 := if (exists($observatory)) then ($q2, "observatory=" || encode-for-uri($observatory)) else ($q2) let $q4 := if (exists($instrument)) then ($q3, "intrument=" || encode-for-uri($instrument)) else ($q3) let $q5 := if (exists($startDate)) then ($q4, "startDate=" || spdfdatetime:basic8601($startDate)) else ($q4) let $q6 := if (exists($stopDate)) then ($q5, "stopDate=" || spdfdatetime:basic8601($stopDate)) else ($q5) let $q7 := if (exists($idPattern)) then ($q6, "idPattern=" || encode-for-uri($idPattern)) else ($q6) let $q8 := if (exists($labelPattern)) then ($q7, "labelPattern=" || encode-for-uri($labelPattern)) else ($q7) let $q9 := if (exists($notesPattern)) then ($q8, "notesPattern=" || encode-for-uri($notesPattern)) else ($q8) let $queryParams := string-join($q9, "&") let $datasetsUrl := if (string-length($queryParams) gt 0) then (xs:anyURI(string-join(($cdasws:url, $queryParams), '?'))) else (xs:anyURI($cdasws:url)) let $datasets := hc:send-request(, $datasetsUrl) let $status := $datasets[1]/@status return if ($status = 200) then ( $datasets[2]/cdas:Datasets ) else ( util:log("error", ("Call to ", $datasetsUrl, " failed with status ", $status)) ) }; (:~ : Determines if the given URL is an cdaweb URL. : : @param $url value to test. : @return true if $url is an cdaweb URL. Otherwise, false. :) declare function cdasws:isCdasUrl( $url as xs:string) as xs:boolean { let $result := matches($url, ".*cdaweb.*") return $result }; (:~ : Gets the inventory intervals from a cdas:Inventory fragment and : returns the corresponding HAPI intervals. : : @param $inventory cdas:Inventory fragment to search. : @return HAPI intervals obtained from given cdas:Inventory. :) declare function cdasws:getInventoryIntervals( $inventory as node()*) as node()* { for $interval in $inventory/cdas:InventoryDescription/cdas:TimeInterval return {$interval/cdas:Start/text()} {$interval/cdas:End/text()} }; (:~ : Gets the inventory for the specified CDAS dataset. : : @param $datasetId CDAS dataset identifier whose inventory is being : requested. : @return the time intervals when data is available for the specified : dataset. :) declare function cdasws:getCdasInventory( $datasetId as xs:string) as node()* { let $inventoryUrl := xs:anyURI(string-join(($cdasws:url, $datasetId, 'inventory'), '/')) let $inventory := hc:send-request(, $inventoryUrl) let $status := $inventory[1]/@status return if ($status = 200) then ( cdasws:getInventoryIntervals($inventory[2]/cdas:Inventory) ) else ( util:log("error", ("Call to ", $inventoryUrl, " failed with status ", $status)) ) }; (:~ : Gets descriptions of the variables for the specified CDAS dataset. : : @param $datasetId CDAS dataset identifier whose variable descriptions : are being requested. : @return descriptions of the variables for the specified CDAS dataset. :) declare function cdasws:getVariableDescriptions( $datasetId as xs:string) as node()* { let $varsUrl := xs:anyURI(string-join(($cdasws:url, $datasetId, 'variables'), '/')) let $vars := hc:send-request(, $varsUrl) let $status := $vars[1]/@status return if ($status = 200) then ( let $varDescriptions := $vars[2]/cdas:Variables/cdas:VariableDescription return $varDescriptions ) else ( util:log("error", ("Call to ", $varsUrl, " failed with status ", $status)) ) }; (:~ : Gets the names of the variables for the specified CDAS dataset. : : @param $datasetId CDAS dataset identifier whose variable names are being : requested. : @return names of the variables for the specified CDAS dataset. :) declare function cdasws:getVariableNames( $datasetId as xs:string) as xs:string* { let $varDescriptions := cdasws:getVariableDescriptions($datasetId) return $varDescriptions/cdas:Name/text() }; (:~ : Creates CDAS DataRequest/VariableName elements for the specified : variables. : : @param $names names of variables to include. : @return CDAS DataRequest/VariableName elements for the specified : variables. :) declare function cdasws:createVariableNames( $names as xs:string*) as node()* { let $modifiedNames := if (count($names) eq 0) then ( ("ALL-VARIABLES") ) else ( $names ) for $name in $names return {$name} }; (:~ : Calls the CDAS web services to get the specified data. : : @param $id CDAS dataset identifier. : @param $timeMin start of time range. : @param $timeMax end of time range. : @param $params names of variables whose data is to be obtained. : @param $ifModifiedSince If-Modified-Since header value. : @return hc:response from the call. :) declare function cdasws:getCdfmlData( $id as xs:string, $timeMin as xs:dateTime, $timeMax as xs:dateTime, $params as xs:string*, $ifModifiedSince as xs:string?) as node()* { let $url := xs:anyURI($cdasws:url) let $headers := ( element hc:header { attribute name {"Content-Type"}, attribute value {"application/xml"} }, element hc:header { attribute name {"Accept"}, attribute value {"application/xml"} }, if (string-length($ifModifiedSince) gt 0) then element hc:header { attribute name {"If-Modified-Since"}, attribute value {$ifModifiedSince} } else () ) let $dataRequest := {$headers} {$timeMin} {$timeMax} {$id} {cdasws:createVariableNames($params)} 3 CDFML let $response := hc:send-request($dataRequest, $url) let $status := $response[1]/@status return if ($status = 200 or $status = 304) then ( $response ) else ( util:log("error", ("Call to ", $url, " with ", $dataRequest, " failed with status ", $status)) ) }; (:~ : Gets the specified CDFML data. : : @param $url URL of CDFML data to get. : @param names names of variables whose record values are to be returned : from the CDFML. : @return the requested CDFML data. :) declare function cdasws:getCdfmlData( $url as xs:anyURI) as node()* { let $cdfml := hc:send-request(, $url) let $status := $cdfml[1]/@status return if ($status = 200) then ( $cdfml ) else ( util:log("error", ("Call to ", $url, " failed with status ", $status)), CDAWeb error: {$status}. ) }; (:~ : Gets the specified data. : : @param $id CDAS dataset idenifier of the data to get. : @param $timeMin start of time range. : @param $timeMax end of time range. : @param $params names of Parameters to get. : @param $ifModifiedSince If-Modified-Since header value. : @return the specified data (CDFML). :) declare function cdasws:getData( $id as xs:string, $timeMin as xs:dateTime, $timeMax as xs:dateTime, $params as xs:string*, $ifModifiedSince as xs:string?) as node()* { let $response := cdasws:getCdfmlData($id, $timeMin, $timeMax, $params, $ifModifiedSince) let $status := $response[1]/@status let $cdasBody := $response[2] let $lastModified := $response[1]/hc:header[@name = "last-modified"]/@value let $lastModifiedHdr := if (string-length($lastModified) gt 0) then response:set-header("Last-Modified", $lastModified) else () let $cdasError := $cdasBody/cdas:DataResult/cdas:Error let $cdfmlUrl := $cdasBody/cdas:DataResult/cdas:FileDescription/cdas:Name/text() return if ($status eq "304") then ( response:set-status-code(304), Not modified. ) else if (count($cdasError) gt 0) then ( (: this may need improving :) CDAWlib error: {$cdasError/text()} ) else if ($cdfmlUrl) then ( cdasws:getCdfmlData(xs:anyURI($cdfmlUrl)) ) else if (not($cdasBody/cdas:DataResult/node())) then ( (: This happens when you request non-existent variables. Do I really want to report this as an error ??? :) CDAWeb returned no data. ) else ( util:log("error", ("Call to cdasws:getCdfmlData", " failed with response ", $response)), Internal Server Error. ) };