Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enclosure addition in feature collection links #230

Merged
merged 8 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/main/java/ogc/rs/apiserver/ApiServerVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,28 @@ private JsonObject buildCollectionFeatureResult(List<JsonObject> success) {
.put("templated","true")))
.put("itemType", "feature")
.put("crs", collection.getJsonArray("crs"));
if (collection.getJsonArray("enclosure") != null && !collection.getJsonArray("enclosure").isEmpty()) {
collection.getJsonArray("enclosure")
.forEach(enclosureJson -> {
// Ensure the enclosure is not null
if (enclosureJson != null) {
JsonObject enclosure = new JsonObject();
enclosure.mergeIn((JsonObject) enclosureJson);
String href =
hostName + ogcBasePath + "assets/" + enclosure.getString("id");
enclosure.put("href", href);
enclosure.put("rel", "enclosure");
enclosure.put("length", enclosure.getInteger("size"));
enclosure.remove("size");
enclosure.remove("id");
enclosure.remove("collections_id");
// enclosure.remove("role");
collection.getJsonArray("links").add(enclosure);
}
});
collection.remove("enclosure");
}

if (success.get(0).getJsonArray("type").contains("COVERAGE")) {
collection
.getJsonArray("links")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,30 +72,4 @@ public JsonObject generateOgcOasBlock() {
return block;
}

/**
*
* Generate OpenAPI JSON block for all STAC routes that this collection can have.
*
* @return JSON object containing OpenAPI paths for all STAC routes for this collection.
*/
public JsonObject generateStacOasBlock() {
JsonObject block = new JsonObject();

/* GET /stac/collections/<collection-ID> */
JsonObject collectionSpecific = new JsonObject();

collectionSpecific.put("tags", new JsonArray().add(title));
collectionSpecific.put("summary", STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_SUMMARY.get());
collectionSpecific.put("operationId", STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OPERATION_ID.get());
collectionSpecific.put("responses",
new JsonObject()
.put("200", new JsonObject().put("$ref", "#/components/responses/stacCollection"))
.put("404", new JsonObject().put("$ref", "#/components/responses/NotFound"))
.put("500", new JsonObject().put("$ref", "#/components/responses/ServerError")));

block.put(STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_ENDPOINT.get(),
new JsonObject().put("get", collectionSpecific));

return block;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,6 @@ public void giveOgcRoutes(OgcRouterBuilder ogcRouterBuilder) {
@Override
public void giveStacRoutes(StacRouterBuilder stacRouterBuilder) {

RouterBuilder builder = stacRouterBuilder.routerBuilder;
ApiServerVerticle apiServerVerticle = stacRouterBuilder.apiServerVerticle;
FailureHandler failureHandler = stacRouterBuilder.failureHandler;

List<String> collectionSpecificOpIds = builder.operations().stream()
.filter(op -> op.getOperationId().matches(OgcCollectionMetadata.STAC_OP_ID_PREFIX_REGEX))
.map(op -> op.getOperationId()).collect(Collectors.toList());

collectionSpecificOpIds.forEach(opId -> {
if (opId.matches(OgcCollectionMetadata.STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OP_ID_REGEX)) {
builder.operation(opId).handler(apiServerVerticle::getStacCollection)
.handler(apiServerVerticle::putCommonResponseHeaders)
.handler(apiServerVerticle::buildResponse)
.failureHandler(failureHandler);
}
});
}

@Override
Expand Down Expand Up @@ -108,16 +92,13 @@ public Future<OasFragments> generateNewSpecFragments(JsonObject existingOgcSpec,

Future<OasFragments> result = metadataObjList.compose(list -> {
List<JsonObject> ogcFrags = new ArrayList<JsonObject>();
List<JsonObject> stacFrags = new ArrayList<JsonObject>();

list.forEach(obj -> {
ogcFrags.add(obj.generateOgcOasBlock());
stacFrags.add(obj.generateStacOasBlock());
});

OasFragments fragments = new OasFragments();
fragments.setOgc(ogcFrags);
fragments.setStac(stacFrags);

return Future.succeededFuture(fragments);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package ogc.rs.apiserver.router.gisentities.staccollections;

import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

import java.util.UUID;
import java.util.function.Supplier;

/** Class used to hold STAC Collection metadata */
public class StacCollectionMetadata {
public static final String STAC_OP_ID_PREFIX_REGEX = "^itemlessStacCollection-.*";
public static final String STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OP_ID_REGEX =
"^itemlessStacCollection-.*-get-specific-collection$";
public static final String STAC_GET_ITEMS_COLLECTION_OP_ID_REGEX =
"^itemlessStacCollection-.*-get-items$";

private UUID id;
private String title;
private String description;

private final Supplier<String> STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_SUMMARY =
() -> "Metadata about " + description;
private final Supplier<String> STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OPERATION_ID =
() -> "itemlessStacCollection-" + id.toString() + "-get-specific-collection";
private final Supplier<String> STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_ENDPOINT =
() -> "/stac/collections/" + id.toString();

private final Supplier<String> STAC_GET_COLLECTION_ITEMS_SUMMARY =
() -> "Get items from " + description;
private final Supplier<String> STAC_GET_COLLECTION_ITEMS_OPERATION_ID =
() -> "itemlessStacCollection-" + id.toString() + "-get-items";
private final Supplier<String> STAC_GET_COLLECTION_ITEMS_ENDPOINT =
() -> "/stac/collections/" + id.toString() + "/items";

public StacCollectionMetadata(JsonObject obj) {
id = UUID.fromString(obj.getString("id"));
title = obj.getString("title", "Undefined title");
description = obj.getString("description", "Undefined description");
}

public UUID getId() {
return id;
}

/**
* Generate OpenAPI JSON block for all STAC routes that this collection can have.
*
* @return JSON object containing OpenAPI paths for all STAC routes for this collection.
*/
public JsonObject generateStacOasBlock() {
JsonObject block = new JsonObject();

/* GET /stac/collections/<collection-ID> */
JsonObject collectionSpecific = new JsonObject();

collectionSpecific.put("tags", new JsonArray().add(title));
collectionSpecific.put("summary", STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_SUMMARY.get());
collectionSpecific.put("operationId", STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OPERATION_ID.get());
collectionSpecific.put(
"responses",
new JsonObject()
.put("200", new JsonObject().put("$ref", "#/components/responses/stacCollection"))
.put("404", new JsonObject().put("$ref", "#/components/responses/NotFound"))
.put("500", new JsonObject().put("$ref", "#/components/responses/ServerError")));

block.put(
STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_ENDPOINT.get(),
new JsonObject().put("get", collectionSpecific));

/* GET stac/collections/<collection-ID>/items */
JsonObject stacItems = new JsonObject();
stacItems.put("tags", new JsonArray().add(title));
stacItems.put("summary", STAC_GET_COLLECTION_ITEMS_SUMMARY.get());
stacItems.put("operationId", STAC_GET_COLLECTION_ITEMS_OPERATION_ID.get());
stacItems.put(
"responses",
new JsonObject()
.put("200", new JsonObject().put("$ref", "#/components/responses/StacFeatures"))
.put("404", new JsonObject().put("$ref", "#/components/responses/NotFound"))
.put("500", new JsonObject().put("$ref", "#/components/responses/ServerError")));
block.put(STAC_GET_COLLECTION_ITEMS_ENDPOINT.get(), new JsonObject().put("get", stacItems));

return block;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package ogc.rs.apiserver.router.gisentities.staccollections;

import com.google.auto.service.AutoService;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.openapi.RouterBuilder;
import ogc.rs.apiserver.ApiServerVerticle;
import ogc.rs.apiserver.handlers.FailureHandler;
import ogc.rs.apiserver.router.gisentities.GisEntityInterface;
import ogc.rs.apiserver.router.gisentities.ogccollections.OgcCollectionMetadata;
import ogc.rs.apiserver.router.gisentities.ogccollections.OgcCollectionsEntity;
import ogc.rs.apiserver.router.routerbuilders.OgcRouterBuilder;
import ogc.rs.apiserver.router.routerbuilders.StacRouterBuilder;
import ogc.rs.apiserver.router.util.OasFragments;
import ogc.rs.database.DatabaseService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;
import java.util.stream.Collectors;

/**
* Class to handle creation of routes for /stac/collection/<collection-id></em> for OGC collections.
*/
@AutoService(GisEntityInterface.class)
public class StacCollectionsEntity implements GisEntityInterface {

private static final String UUID_REGEX =
"[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}";
private static final String STAC_COLLECTION_PATH_REGEX = "^stac/collections/" + UUID_REGEX;

private static final Logger LOGGER = LogManager.getLogger(StacCollectionsEntity.class);

@Override
public void giveOgcRoutes(OgcRouterBuilder routerBuilder) {}

@Override
public void giveStacRoutes(StacRouterBuilder stacRouterBuilder) {
RouterBuilder builder = stacRouterBuilder.routerBuilder;
ApiServerVerticle apiServerVerticle = stacRouterBuilder.apiServerVerticle;
FailureHandler failureHandler = stacRouterBuilder.failureHandler;

List<String> collectionSpecificOpIds =
builder.operations().stream()
.filter(
op -> op.getOperationId().matches(StacCollectionMetadata.STAC_OP_ID_PREFIX_REGEX))
.map(op -> op.getOperationId())
.collect(Collectors.toList());

collectionSpecificOpIds.forEach(
opId -> {
if (opId.matches(
StacCollectionMetadata.STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OP_ID_REGEX)) {
builder
.operation(opId)
.handler(apiServerVerticle::getStacCollection)
.handler(apiServerVerticle::putCommonResponseHeaders)
.handler(apiServerVerticle::buildResponse)
.failureHandler(failureHandler);
} else if (opId.matches(StacCollectionMetadata.STAC_GET_ITEMS_COLLECTION_OP_ID_REGEX)) {
builder
.operation(opId)
.handler(apiServerVerticle::getStacItems)
.handler(apiServerVerticle::putCommonResponseHeaders)
.handler(apiServerVerticle::buildResponse)
.failureHandler(failureHandler);
}
});
}

@Override
public Future<OasFragments> generateNewSpecFragments(
JsonObject existingOgcSpec,
JsonObject existingStacSpec,
DatabaseService dbService,
JsonObject config) {
Promise<OasFragments> promise = Promise.promise();
Set<UUID> existingCollectionIds = getExistingCollectionsFromOgcSpec(existingStacSpec);

Future<List<JsonObject>> stacCollectionMetadata =
dbService.getStacCollectionMetadataForOasSpec(
existingCollectionIds.stream().map(i -> i.toString()).collect(Collectors.toList()));

Future<List<StacCollectionMetadata>> metadataObjList =
stacCollectionMetadata.compose(
res -> {
List<StacCollectionMetadata> list =
res.stream().map(i -> new StacCollectionMetadata(i)).collect(Collectors.toList());

List<String> foundCollectionIds =
list.stream().map(obj -> obj.getId().toString()).collect(Collectors.toList());

if (foundCollectionIds.isEmpty()) {
LOGGER.info("No new STAC collections found!");
} else {
LOGGER.info("New STAC collections found : {}", foundCollectionIds);
}

return Future.succeededFuture(list);
});

Future<OasFragments> result =
metadataObjList.compose(
list -> {
List<JsonObject> stacFrags = new ArrayList<JsonObject>();

list.forEach(
obj -> {
stacFrags.add(obj.generateStacOasBlock());
});

OasFragments fragments = new OasFragments();
fragments.setStac(stacFrags);

return Future.succeededFuture(fragments);
});

result.onSuccess(i -> promise.complete(i)).onFailure(err -> promise.fail(err));
return promise.future();
}

private Set<UUID> getExistingCollectionsFromOgcSpec(JsonObject ogcSpec) {
Set<UUID> existingIds = new HashSet<UUID>();

ogcSpec
.getJsonObject("paths")
.forEach(
k -> {
String key = k.getKey();
if (key.matches(STAC_COLLECTION_PATH_REGEX)) {
String collectionId = key.split("/")[2];
existingIds.add(UUID.fromString(collectionId));
}
});

return existingIds;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package ogc.rs.apiserver.router.gisentities.staccollections;
12 changes: 10 additions & 2 deletions src/main/java/ogc/rs/database/DatabaseService.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,16 @@ Future<JsonObject> getFeature(String collectionId, Integer featureId, Map<String
* @return list of {@link JsonObject}, which is cast to the required type by the caller.
*/
Future<List<JsonObject>> getCollectionMetadataForOasSpec(List<String> existingCollectionUuidIds);



/**
* Get all STAC collections metadata to be used for OpenAPI spec generation.
*
* @param existingCollectionUuidIds UUID IDs of collections that are already part of the spec.
* @return list of {@link JsonObject}, which is cast to the required type by the caller.
*/
Future<List<JsonObject>> getStacCollectionMetadataForOasSpec(List<String> existingCollectionUuidIds);


/**
* Run STAC Item Search query given query params in {@link StacItemSearchParams} object.
*
Expand Down
Loading