Skip to content

Commit

Permalink
Implement MVP for OGC API - Coverages
Browse files Browse the repository at this point in the history
  • Loading branch information
m-mohr committed Oct 17, 2024
1 parent a06913c commit 1734c18
Show file tree
Hide file tree
Showing 4 changed files with 351 additions and 42 deletions.
53 changes: 35 additions & 18 deletions src/api/capabilities.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import API from '../utils/API.js';
import Utils from '../utils/utils.js';
import Coverages from './coverages.js';
const packageInfo = Utils.require('../../package.json');

export default class CapabilitiesAPI {
Expand Down Expand Up @@ -112,46 +113,62 @@ export default class CapabilitiesAPI {
type: 'application/json',
title: 'Supported API versions'
},
// STAC
{
rel: "data",
href: API.getUrl("/collections"),
type: "application/json",
title: "Datasets"
},
// OGC API - Coverages
{
rel: "http://www.opengis.net/def/rel/ogc/1.0/data",
href: API.getUrl("/collections"),
type: "application/json",
title: "Datasets"
},
// STAC and older OGC APIs
{
rel: "conformance",
href: API.getUrl("/conformance"),
type: "application/json",
title: "OGC Conformance classes"
},
// Some newer OGC APIs (including Coverages)
{
rel: "http://www.opengis.net/def/rel/ogc/1.0/conformance",
href: API.getUrl("/conformance"),
type: "application/json",
title: "OGC Conformance classes"
}
]
});
}

async getConformance(req, res) {
res.json({
"conformsTo": [
"https://api.openeo.org/1.2.0",
"https://api.stacspec.org/v1.0.0/core",
"https://api.stacspec.org/v1.0.0/collections",
"https://api.stacspec.org/v1.0.0/ogcapi-features",
"https://api.stacspec.org/v1.0.0/ogcapi-features#sort",
let conformsTo = [
"https://api.openeo.org/1.2.0",
"https://api.stacspec.org/v1.0.0/core",
"https://api.stacspec.org/v1.0.0/collections",
"https://api.stacspec.org/v1.0.0/ogcapi-features",
"https://api.stacspec.org/v1.0.0/ogcapi-features#sort",
// Item Filter
// "http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/features-filter",
// "http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/features-filter",
// Collection Search
// "https://api.stacspec.org/v1.0.0-rc.1/collection-search",
// "http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/simple-query",
// "https://api.stacspec.org/v1.0.0-rc.1/collection-search",
// "http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/simple-query",
// Collection Filter
// "https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter",
// "https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter",
// Collection Sorting
// "https://api.stacspec.org/v1.0.0-rc.1/collection-search#sort",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson",
// "https://api.stacspec.org/v1.0.0-rc.1/collection-search#sort",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson",
// CQL2 (for Item and Collection Filter)
// "http://www.opengis.net/spec/cql2/1.0/conf/cql2-text",
// "http://www.opengis.net/spec/cql2/1.0/conf/basic-cql2",
]
});
// "http://www.opengis.net/spec/cql2/1.0/conf/cql2-text",
// "http://www.opengis.net/spec/cql2/1.0/conf/basic-cql2",
];
conformsTo = Coverages.addConformanceClasses(conformsTo);
res.json({ conformsTo });
}

async getServices(req, res) {
Expand Down
122 changes: 112 additions & 10 deletions src/api/collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Utils from '../utils/utils.js';
import Errors from '../utils/errors.js';
import GeeProcessing from '../processes/utils/processing.js';
import HttpUtils from '../utils/http.js';
import Coverages from './coverages.js';

const sortPropertyMap = {
'properties.datetime': 'system:time_start',
Expand Down Expand Up @@ -36,6 +37,8 @@ export default class Data {
server.addEndpoint('get', ['/collections/{collection_id}', '/collections/*'], this.getCollectionById.bind(this));
server.addEndpoint('get', '/collections/{collection_id}/queryables', this.getCollectionQueryables.bind(this));
server.addEndpoint('get', '/collections/{collection_id}/items', this.getCollectionItems.bind(this));
server.addEndpoint('get', '/collections/{collection_id}/schema', this.getCollectionSchema.bind(this));
server.addEndpoint('get', '/collections/{collection_id}/coverage', this.getCoverage.bind(this));
server.addEndpoint('get', '/collections/{collection_id}/items/{item_id}', this.getCollectionItemById.bind(this));
if (this.context.stacAssetDownloadSize > 0) {
server.addEndpoint('get', ['/assets/{asset_id}', '/assets/*'], this.getAssetById.bind(this));
Expand Down Expand Up @@ -106,6 +109,12 @@ export default class Data {
else if (id.endsWith('/queryables')) {
return await this.getCollectionQueryables(req, res);
}
else if (id.endsWith('/schema')) {
return await this.getCollectionSchema(req, res);
}
else if (id.endsWith('/coverage')) {
return await this.getCoverage(req, res);
}
else if (id.endsWith('/items')) {
return await this.getCollectionItems(req, res);
}
Expand All @@ -121,28 +130,121 @@ export default class Data {
res.json(collection);
}

async getCollectionQueryables(req, res) {
getCollectionId(req, endpoint) {
let id = req.params.collection_id;
// Get the ID if this was a redirect from the /collections/{collection_id} endpoint
// Get the ID if this was a redirect from another endpoint
if (req.params['*'] && !id) {
id = req.params['*'].replace(/\/queryables$/, '');
endpoint = '/' + endpoint;
id = req.params['*'];
if (id.endsWith(endpoint)) {
id = id.substring(0, req.params['*'].length - endpoint.length);
}
}
return id;
}

const queryables = this.catalog.getSchema(id);
async getCollectionQueryables(req, res) {
const id = this.getCollectionId(req, 'queryables');
const queryables = this.catalog.getQueryables(id);
if (queryables === null) {
throw new Errors.CollectionNotFound();
}

res.json(queryables);
}

async getCollectionItems(req, res) {
let id = req.params.collection_id;
// Get the ID if this was a redirect from the /collections/{collection_id} endpoint
if (req.params['*'] && !id) {
id = req.params['*'].replace(/\/items$/, '');
async getCollectionSchema(req, res) {
const id = this.getCollectionId(req, 'schema');
const schema = this.catalog.getSchema(id);
if (schema === null) {
throw new Errors.CollectionNotFound();
}
res.json(schema);
}

async getCoverage(req, res) {
// if (!req.user._id) {
// throw new Errors.AuthenticationRequired();
// }

const id = this.getCollectionId(req, 'coverage');
const collection = this.catalog.getData(id);
if (collection === null) {
throw new Errors.CollectionNotFound();
}

const coverage = new Coverages(collection);
// Subsetting
try {
coverage.setDatetime(req.query.datetime);
} catch (e) {
throw new Errors.ParameterValueInvalid({parameter: 'datetime', reason: e.message});
}
try {
coverage.setBoundingBox(req.query.bbox, req.query['bbox-crs']);
} catch (e) {
throw new Errors.ParameterValueInvalid({parameter: 'bbox', reason: e.message});
}
try {
coverage.setSubset(req.query.subset, req.query['subset-crs']);
} catch (e) {
throw new Errors.ParameterValueInvalid({parameter: 'subset', reason: e.message});
}
// Scaling
try {
coverage.setScaleFactor(req.query['scale-factor']);
} catch (e) {
throw new Errors.ParameterValueInvalid({parameter: 'scale-factor', reason: e.message});
}
try {
coverage.setScaleAxes(req.query['scale-axes']);
} catch (e) {
throw new Errors.ParameterValueInvalid({parameter: 'scale-axes', reason: e.message});
}
try {
coverage.setScaleSize(req.query['scale-size']);
} catch (e) {
throw new Errors.ParameterValueInvalid({parameter: 'scale-size', reason: e.message});
}
try {
coverage.setDimensions(req.query.width, req.query.height);
} catch (e) {
throw new Errors.ParameterValueInvalid({parameter: 'width/height', reason: e.message});
}
// CRS
try {
coverage.setCrs(req.query.crs);
} catch (e) {
throw new Errors.ParameterValueInvalid({parameter: 'crs', reason: e.message});
}

const isPNG = req.accepts('image/png');
const isGTIFF = req.headers.accept && req.accepts([
'image/tiff',
'image/tiff; application=geotiff'
]);
if (!isPNG && !isGTIFF) {
throw new Errors.NotAcceptableError();
}
else if(isGTIFF) {
coverage.setFileFormat('GTIFF');
}
else {
coverage.setFileFormat('PNG');
}

const response = await coverage.execute();

res.header('Content-Type', response?.headers?.['content-type'] || 'application/octet-stream');
// res.header('Content-Crs', "EPSG:4326");
// res.header('Content-Bbox', bbox.join(","));
if (req.query.datetime) {
res.header('Content-Datetime', req.query.datetime);
}
response.data.pipe(res);
}

async getCollectionItems(req, res) {
const id = this.getCollectionId(req, 'items');
const collection = this.catalog.getData(id, true);
if (collection === null) {
throw new Errors.CollectionNotFound();
Expand Down
Loading

0 comments on commit 1734c18

Please sign in to comment.