diff --git a/app/resto/core/RestoCollection.php b/app/resto/core/RestoCollection.php index 7574092c..e610887c 100755 --- a/app/resto/core/RestoCollection.php +++ b/app/resto/core/RestoCollection.php @@ -992,7 +992,8 @@ public function toArray() $collectionArray['summaries'] = $summaries; } - return $collectionArray; + return $this->context->core['useJSONLD'] ? JSONLDUtil::addDataCatalogMetadata($collectionArray) : $collectionArray; + } /** diff --git a/app/resto/core/RestoCollections.php b/app/resto/core/RestoCollections.php index dd1db554..8846348c 100755 --- a/app/resto/core/RestoCollections.php +++ b/app/resto/core/RestoCollections.php @@ -271,6 +271,10 @@ public function toArray() 'collections' => array() ); + if ($this->context->core['useJSONLD']) { + $collections = JSONLDUtil::addDataCatalogMetadata($collections); + } + $totalMatched = 0; // Compute global summaries diff --git a/app/resto/core/RestoContext.php b/app/resto/core/RestoContext.php index 68e486ec..8f972dd2 100755 --- a/app/resto/core/RestoContext.php +++ b/app/resto/core/RestoContext.php @@ -58,6 +58,9 @@ class RestoContext // Use cache 'useCache' => false, + + // Display JSON-LD metadata in catalogs/collections/items + 'useJSONLD' => false, // Timezone for date display 'timezone' => 'Europe/Paris', diff --git a/app/resto/core/RestoFeature.php b/app/resto/core/RestoFeature.php index b336c507..441db09c 100755 --- a/app/resto/core/RestoFeature.php +++ b/app/resto/core/RestoFeature.php @@ -507,7 +507,7 @@ private function load($options) else { $this->featureArray = $options['featureArray']; } - + /* * Empty feature or feature is not in input collection */ diff --git a/app/resto/core/RestoModel.php b/app/resto/core/RestoModel.php index 296578db..5781bb40 100755 --- a/app/resto/core/RestoModel.php +++ b/app/resto/core/RestoModel.php @@ -686,9 +686,18 @@ public function remap($featureArray, $collection) } } - return array_merge($featureArray, array( + $featureArray = array_merge($featureArray, array( 'properties' => $properties )); + + /* + * JSON-LD additionnal metadata + */ + if ( isset($collection) && $collection->context->core['useJSONLD']) { + $featureArray = JSONLDUtil::addDatasetsMetadata($featureArray); + } + + return $featureArray; } /** diff --git a/app/resto/core/api/STACAPI.php b/app/resto/core/api/STACAPI.php index 6e7a5bc4..c8d61745 100644 --- a/app/resto/core/api/STACAPI.php +++ b/app/resto/core/api/STACAPI.php @@ -275,7 +275,7 @@ public function getCatalogs($params) { // This is /catalogs if ( !isset($params['segments']) ) { - return array( + $catalog = array( 'stac_version' => STACAPI::STAC_VERSION, 'id' => 'catalogs', 'type' => 'Catalog', @@ -286,6 +286,7 @@ public function getCatalogs($params) $this->getRootCatalogLinks($params) ) ); + return $this->context->core['useJSONLD'] ? JSONLDUtil::addDataCatalogMetadata($catalog) : $catalog; } // This is /catalogs/* @@ -1335,7 +1336,7 @@ private function processPath($segments, $params = array()) // The path is the catalog identifier $parentAndChilds = $this->getParentAndChilds(join('/', $segments), $params); - return array( + $catalog = array( 'stac_version' => STACAPI::STAC_VERSION, 'id' => $segments[count($segments) -1 ], 'title' => $parentAndChilds['parent']['title'] ?? '', @@ -1347,6 +1348,7 @@ private function processPath($segments, $params = array()) ) ); + return $this->context->core['useJSONLD'] ? JSONLDUtil::addDataCatalogMetadata($catalog) : $catalog; } /** diff --git a/app/resto/core/api/ServicesAPI.php b/app/resto/core/api/ServicesAPI.php index f6077f5a..6946f133 100755 --- a/app/resto/core/api/ServicesAPI.php +++ b/app/resto/core/api/ServicesAPI.php @@ -181,7 +181,7 @@ public function conformance() public function hello() { - return array( + $hello = array( 'stac_version' => STACAPI::STAC_VERSION, 'id' => 'root', 'type' => 'Catalog', @@ -269,6 +269,8 @@ public function hello() ), 'conformsTo' => $this->conformsTo() ); + + return $this->context->core['useJSONLD'] ? JSONLDUtil::addDataCatalogMetadata($hello) : $hello; } /** diff --git a/app/resto/core/utils/JSONLDUtil.php b/app/resto/core/utils/JSONLDUtil.php new file mode 100755 index 00000000..9da667c7 --- /dev/null +++ b/app/resto/core/utils/JSONLDUtil.php @@ -0,0 +1,226 @@ + 'DataCatalog', + 'name' => $catalog['links'][$i]['title'] ?? '', + 'isBaseOn' => $catalog['links'][$i]['href'], + 'url' => JSONLDUtil::$STAC_BROWSER_URL . (explode('://', $catalog['links'][$i]['href'])[1]) + ); + } + + else if ($catalog['links'][$i]['rel'] === 'child') { + $hasPart[] = array( + '@type' => 'DataCatalog', + 'name' => $catalog['links'][$i]['title'] ?? '', + 'isBaseOn' => $catalog['links'][$i]['href'], + 'url' => JSONLDUtil::$STAC_BROWSER_URL . (explode('://', $catalog['links'][$i]['href'])[1]) + ); + } + + else if ($catalog['links'][$i]['rel'] === 'item') { + $id = isset($catalog['links'][$i]['id']) ? $catalog['links'][$i]['id'] : end(explode('/', $catalog['links'][$i]['href'])); + $dataset[] = array( + 'identifier' => $i, + 'name' => $catalog['links'][$i]['title'] ?? $id, + 'isBaseOn' => $catalog['links'][$i]['href'], + 'url' => JSONLDUtil::$STAC_BROWSER_URL . (explode('://',$catalog['links'][$i]['href'])[1]) + ); + } + + } + + $jsonld = JSONLDUtil::getCommonMetadata($catalog, $url); + + if ( isset($catalog['extent'])) { + + if ( isset($catalog['extent']['spatial']['bbox']) ) { + $jsonld['spatialCoverage'] = array( + '@type' => 'Place', + 'geo' => array( + '@type' => 'GeoShape', + 'box' => join(' ', $catalog['extent']['spatial']['bbox'][0]) + ) + ); + } + + if ( isset($catalog['extent']['temporal']['interval']) ) { + $jsonld['temporalCoverage'] = join('/', array($catalog['extent']['temporal']['interval'][0][0] ?? '..', $catalog['extent']['temporal']['interval'][0][1] ?? '..')); + } + + } + + if ( isset($isPartOf) ) { + $jsonld['isPartOf'] = $isPartOf; + } + + if ( !empty($hasPart) ) { + $jsonld['hasPart'] = $hasPart; + } + + if ( !empty($dataset) ) { + $jsonld['dataset'] = $dataset; + } + + return array_merge($catalog, $jsonld); + } + + + /** + * Add additionnal Datasets JSON-LD metadata + * + * @param array $item + * @return array + */ + public static function addDatasetsMetadata($item) + { + + $url = null; + $thumbnail = null; + $includedInDataCatalog = array(); + $distribution = array(); + + for ($i = 0, $ii = count($item['links']); $i < $ii; $i++) { + if ($item['links'][$i]['rel'] === 'self') { + $url = $item['links'][$i]['href']; + } + else if ($item['links'][$i]['rel'] === 'parent') { + $includedInDataCatalog[] = array( + 'isBaseOn' => $item['links'][$i]['href'], + 'url' => JSONLDUtil::$STAC_BROWSER_URL . (explode('://', $item['links'][$i]['href'])[1]) + ); + } + } + + foreach (array_keys($item['assets']) as $key) { + + if ( !empty($item['assets'][$key]['roles']) && in_array('thumbnail', $item['assets'][$key]['roles']) ) { + $thumbnail = $item['assets'][$key]['href']; + } + + $distribution[] = array( + 'contentUrl' => $item['assets'][$key]['href'], + 'fileFormat' => $item['assets'][$key]['type'] ?? null, + 'title' => $item['assets'][$key]['title'] ?? null + ); + + } + + $jsonld = JSONLDUtil::getCommonMetadata($item, $url); + + if ( isset($item['bbox'])) { + $jsonld['spatialCoverage'] = array( + '@type' => 'Place', + 'geo' => array( + '@type' => 'GeoShape', + 'box' => join(' ', $item['bbox']) + ) + ); + } + + + if ( isset($catalog['extent']['temporal']['interval']) ) { + $jsonld['temporalCoverage'] = join('/', array($catalog['extent']['temporal']['interval'][0][0] ?? '..', $catalog['extent']['temporal']['interval'][0][1] ?? '..')); + } + + if ( isset($item['properties']['start_datetime']) ) { + $jsonld['temporalCoverage'] = join('/', array($item['properties']['start_datetime'] ?? '..', $item['properties']['end_datetime'] ?? '..')); + } + else if ( isset($item['properties']['datetime']) ) { + $jsonld['temporalCoverage'] = $item['properties']['datetime']; + } + + if ( isset($thumbnail) ) { + $jsonld['image'] = $thumbnail; + } + + if ( !empty($distribution) ) { + $jsonld['distribution'] = $distribution; + } + + return array_merge($item, $jsonld); + + } + + /** + * Return common catalog/item JSON-LD metadata + * + * @param array $obj + * @param string $url + */ + private static function getCommonMetadata($obj, $url) + { + + $jsonld = array( + // Required + '@context' => 'https://schema.org/', + '@type' => 'DataCatalog', + 'name' => $obj['title'] ?? $obj['id'], + // Recommended + 'identifier' => $obj['properties']['sci:doi'] ?? $obj['id'], + 'isBasedOn' => $url, + 'url' => JSONLDUtil::$STAC_BROWSER_URL . (explode('://', $url)[1]) + ); + + if ( isset($obj['properties']['sci:citation'] ) ) { + $jsonld['citation'] = $obj['properties']['sci:citation']; + } + + if ( isset($obj['properties']['sci:publications']) ) { + $jsonld['workExample'] = array( + 'identifier' => $obj['properties']['sci:publications']['doi'] ?? null, + 'citation' => $obj['properties']['sci:publications']['citation'] ?? null + ); + } + + return $jsonld; + + } + +} diff --git a/build/resto/config.php.template b/build/resto/config.php.template index b9f171ee..83fef522 100755 --- a/build/resto/config.php.template +++ b/build/resto/config.php.template @@ -19,6 +19,7 @@ return array( 'userAutoActivation' => ${USER_AUTOACTIVATION:-true}, 'splitGeometryOnDateLine' => ${SPLIT_GEOMETRY_ON_DATELINE:-true}, 'useCache' => ${USE_CACHE:-false}, + 'useJSONLD' => ${USE_JSONLD:-false}, 'corsWhiteList' => array(${CORS_WHITELIST}), 'htmlSearchEndpoint' => '${SEARCH_OPENSEARCH_HTML_ENDPOINT}', 'database' => array( diff --git a/config.env b/config.env index 441a4827..e43c6185 100644 --- a/config.env +++ b/config.env @@ -114,6 +114,9 @@ JWT_PASSPHRASE="Super secret passphrase" ### If null then no cache is used #USE_CACHE=false +### True to provide additionnal JSON-LD metadata within catalogs/collections/items +#USE_JSONLD=false + ### ===================================================================== ### Search engine configuration ### =====================================================================