Skip to content

Commit

Permalink
Merge pull request #445 from jjrom/add-json-ld
Browse files Browse the repository at this point in the history
Add JSON-LD support - see radiantearth/stac-spec#378
  • Loading branch information
jjrom authored Oct 9, 2024
2 parents acf1b87 + 7717242 commit 552d985
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 6 deletions.
3 changes: 2 additions & 1 deletion app/resto/core/RestoCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,8 @@ public function toArray()
$collectionArray['summaries'] = $summaries;
}

return $collectionArray;
return $this->context->core['useJSONLD'] ? JSONLDUtil::addDataCatalogMetadata($collectionArray) : $collectionArray;

}

/**
Expand Down
4 changes: 4 additions & 0 deletions app/resto/core/RestoCollections.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ public function toArray()
'collections' => array()
);

if ($this->context->core['useJSONLD']) {
$collections = JSONLDUtil::addDataCatalogMetadata($collections);
}

$totalMatched = 0;

// Compute global summaries
Expand Down
3 changes: 3 additions & 0 deletions app/resto/core/RestoContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion app/resto/core/RestoFeature.php
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ private function load($options)
else {
$this->featureArray = $options['featureArray'];
}

/*
* Empty feature or feature is not in input collection
*/
Expand Down
11 changes: 10 additions & 1 deletion app/resto/core/RestoModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
6 changes: 4 additions & 2 deletions app/resto/core/api/STACAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -286,6 +286,7 @@ public function getCatalogs($params)
$this->getRootCatalogLinks($params)
)
);
return $this->context->core['useJSONLD'] ? JSONLDUtil::addDataCatalogMetadata($catalog) : $catalog;
}

// This is /catalogs/*
Expand Down Expand Up @@ -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'] ?? '',
Expand All @@ -1347,6 +1348,7 @@ private function processPath($segments, $params = array())
)
);

return $this->context->core['useJSONLD'] ? JSONLDUtil::addDataCatalogMetadata($catalog) : $catalog;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion app/resto/core/api/ServicesAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public function conformance()
public function hello()
{

return array(
$hello = array(
'stac_version' => STACAPI::STAC_VERSION,
'id' => 'root',
'type' => 'Catalog',
Expand Down Expand Up @@ -269,6 +269,8 @@ public function hello()
),
'conformsTo' => $this->conformsTo()
);

return $this->context->core['useJSONLD'] ? JSONLDUtil::addDataCatalogMetadata($hello) : $hello;
}

/**
Expand Down
226 changes: 226 additions & 0 deletions app/resto/core/utils/JSONLDUtil.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
<?php
/*
* Copyright 2018 Jérôme Gasperi
*
* Licensed under the Apache License, version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

/**
* JSON-LD additionnal metadata
*/
class JSONLDUtil
{

public static $STAC_BROWSER_URL = 'https://radiantearth.github.io/stac-browser/#/external/';

/**
* Add additionnal DataCatalog JSON-LD metadata
*
* See STAC discussion here - https://github.com/radiantearth/stac-spec/issues/378
*
* @param array $catalog
* @return array
*/
public static function addDataCatalogMetadata($catalog)
{

// Parse links
$isPartOf = array();
$hasPart = array();
$dataset = array();
$url = null;

for ($i = 0, $ii = count($catalog['links']); $i < $ii; $i++) {

if ($catalog['links'][$i]['rel'] === 'self') {
$url = $catalog['links'][$i]['href'];
}

else if ($catalog['links'][$i]['rel'] === 'parent') {
$isPartOf = 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'] === '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;

}

}
1 change: 1 addition & 0 deletions build/resto/config.php.template
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
3 changes: 3 additions & 0 deletions config.env
Original file line number Diff line number Diff line change
Expand Up @@ -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
### =====================================================================
Expand Down

0 comments on commit 552d985

Please sign in to comment.