use of de.ii.ogcapi.foundation.domain.TemporalExtent in project ldproxy by interactive-instruments.
the class CapabilityFeaturesCore method computeInterval.
private Optional<TemporalExtent> computeInterval(OgcApiDataV2 apiData, String collectionId) {
FeatureTypeConfigurationOgcApi collectionData = apiData.getCollections().get(collectionId);
Optional<FeatureProvider2> featureProvider = providers.getFeatureProvider(apiData, collectionData);
if (featureProvider.map(FeatureProvider2::supportsExtents).orElse(false)) {
List<String> temporalQueryables = collectionData.getExtension(FeaturesCoreConfiguration.class).flatMap(FeaturesCoreConfiguration::getQueryables).map(FeaturesCollectionQueryables::getTemporal).orElse(ImmutableList.of());
if (!temporalQueryables.isEmpty()) {
Optional<Interval> interval;
if (temporalQueryables.size() >= 2) {
interval = featureProvider.get().extents().getTemporalExtent(collectionId, temporalQueryables.get(0), temporalQueryables.get(1));
} else {
interval = featureProvider.get().extents().getTemporalExtent(collectionId, temporalQueryables.get(0));
}
return interval.map(TemporalExtent::of);
}
}
return Optional.empty();
}
use of de.ii.ogcapi.foundation.domain.TemporalExtent in project ldproxy by interactive-instruments.
the class EndpointCollection method onStartup.
@Override
public ValidationResult onStartup(OgcApi api, MODE apiValidation) {
ValidationResult result = super.onStartup(api, apiValidation);
if (apiValidation == MODE.NONE)
return result;
ImmutableValidationResult.Builder builder = ImmutableValidationResult.builder().from(result).mode(apiValidation);
for (FeatureTypeConfigurationOgcApi collectionData : api.getData().getCollections().values()) {
builder = FoundationValidator.validateLinks(builder, collectionData.getAdditionalLinks(), "/collections/" + collectionData.getId());
Optional<String> persistentUriTemplate = collectionData.getPersistentUriTemplate();
if (persistentUriTemplate.isPresent()) {
Pattern valuePattern = Pattern.compile("\\{\\{[\\w\\.]+( ?\\| ?[\\w]+(:'[^']*')*)*\\}\\}");
Matcher matcher = valuePattern.matcher(persistentUriTemplate.get());
if (!matcher.find()) {
builder.addStrictErrors(MessageFormat.format("Persistent URI template ''{0}'' in collection ''{1}'' does not have a valid value pattern.", persistentUriTemplate.get(), collectionData.getId()));
}
}
Optional<CollectionExtent> extent = api.getData().getExtent(collectionData.getId());
if (extent.isPresent()) {
Optional<BoundingBox> spatial = extent.get().getSpatial();
if (spatial.isPresent() && Objects.nonNull(spatial.get())) {
BoundingBox bbox = spatial.get();
if (!ImmutableSet.of(4326, 4979).contains(bbox.getEpsgCrs().getCode()) || bbox.getEpsgCrs().getForceAxisOrder() != EpsgCrs.Force.LON_LAT) {
builder.addStrictErrors(MessageFormat.format("The spatial extent in collection ''{0}'' must be in CRS84 or CRS84h. Found: ''{1}, {2}''.", collectionData.getId(), bbox.getEpsgCrs().toSimpleString(), bbox.getEpsgCrs().getForceAxisOrder()));
}
if (bbox.getXmin() < -180.0 || bbox.getXmin() > 180.0) {
builder.addStrictErrors(MessageFormat.format("The spatial extent in collection ''{0}'' has a longitude value that is not between -180 and 180. Found: ''{1}''.", collectionData.getId(), bbox.getXmin()));
}
if (bbox.getXmax() < -180.0 || bbox.getXmax() > 180.0) {
builder.addStrictErrors(MessageFormat.format("The spatial extent in collection ''{0}'' has a longitude value that is not between -180 and 180. Found: ''{1}''.", collectionData.getId(), bbox.getXmax()));
}
if (bbox.getYmin() < -90.0 || bbox.getYmin() > 90.0) {
builder.addStrictErrors(MessageFormat.format("The spatial extent in collection ''{0}'' has a latitude value that is not between -90 and 90. Found: ''{1}''.", collectionData.getId(), bbox.getYmin()));
}
if (bbox.getYmax() < -90.0 || bbox.getYmax() > 90.0) {
builder.addStrictErrors(MessageFormat.format("The spatial extent in collection ''{0}'' has a latitude value that is not between -90 and 90. Found: ''{1}''.", collectionData.getId(), bbox.getYmax()));
}
if (bbox.getYmax() < bbox.getYmin()) {
builder.addStrictErrors(MessageFormat.format("The spatial extent in collection ''{0}'' has a maxmimum latitude value ''{1}'' that is lower than the minimum value ''{2}''.", collectionData.getId(), bbox.getYmax(), bbox.getYmin()));
}
}
Optional<TemporalExtent> temporal = extent.get().getTemporal();
if (temporal.isPresent() && Objects.nonNull(temporal.get())) {
long start = Objects.nonNull(temporal.get().getStart()) ? temporal.get().getStart() : Long.MIN_VALUE;
long end = Objects.nonNull(temporal.get().getEnd()) ? temporal.get().getEnd() : Long.MAX_VALUE;
if (end < start) {
builder.addStrictErrors(MessageFormat.format("The temporal extent in collection ''{0}'' has an end ''{1}'' before the start ''{2}''.", collectionData.getId(), Instant.ofEpochMilli(end).truncatedTo(ChronoUnit.SECONDS).toString(), Instant.ofEpochMilli(start).truncatedTo(ChronoUnit.SECONDS).toString()));
}
}
}
}
return builder.build();
}
use of de.ii.ogcapi.foundation.domain.TemporalExtent in project ldproxy by interactive-instruments.
the class CollectionExtensionFeatures method process.
@Override
public ImmutableOgcApiCollection.Builder process(Builder collection, FeatureTypeConfigurationOgcApi featureType, OgcApi api, URICustomizer uriCustomizer, boolean isNested, ApiMediaType mediaType, List<ApiMediaType> alternateMediaTypes, Optional<Locale> language) {
collection.title(featureType.getLabel()).description(featureType.getDescription()).itemType(featureType.getExtension(FeaturesCoreConfiguration.class).filter(ExtensionConfiguration::isEnabled).flatMap(FeaturesCoreConfiguration::getItemType).map(Enum::toString).orElse(FeaturesCoreConfiguration.ItemType.unknown.toString()));
api.getItemCount(featureType.getId()).filter(count -> count >= 0).ifPresent(count -> collection.putExtensions("itemCount", count));
URICustomizer uriBuilder = uriCustomizer.copy().ensureNoTrailingSlash().clearParameters();
if (isNested) {
// also add an untyped a self link in the Collections resource, otherwise the standard links are already there
collection.addLinks(new ImmutableLink.Builder().href(uriBuilder.copy().ensureLastPathSegments("collections", featureType.getId()).removeParameters("f").toString()).rel("self").title(i18n.get("selfLinkCollection", language).replace("{{collection}}", featureType.getLabel())).build());
}
List<ApiMediaType> featureMediaTypes = extensionRegistry.getExtensionsForType(FeatureFormatExtension.class).stream().filter(outputFormatExtension -> outputFormatExtension.isEnabledForApi(api.getData())).map(outputFormatExtension -> outputFormatExtension.getMediaType()).collect(Collectors.toList());
featureMediaTypes.stream().forEach(mtype -> collection.addLinks(new ImmutableLink.Builder().href(uriBuilder.ensureLastPathSegments("collections", featureType.getId(), "items").setParameter("f", mtype.parameter()).toString()).rel("items").type(mtype.type().toString()).title(i18n.get("itemsLink", language).replace("{{collection}}", featureType.getLabel()).replace("{{type}}", mtype.label())).build()));
Optional<String> describeFeatureTypeUrl = Optional.empty();
if (describeFeatureTypeUrl.isPresent()) {
collection.addLinks(new ImmutableLink.Builder().href(describeFeatureTypeUrl.get()).rel("describedby").type("application/xml").title(i18n.get("describedByXsdLink", language)).build());
}
// only add extents for cases where we can filter using spatial / temporal predicates
Optional<FeaturesCollectionQueryables> queryables = featureType.getExtension(FeaturesCoreConfiguration.class).flatMap(FeaturesCoreConfiguration::getQueryables);
boolean hasSpatialQueryable = queryables.map(FeaturesCollectionQueryables::getSpatial).filter(spatial -> !spatial.isEmpty()).isPresent();
boolean hasTemporalQueryable = queryables.map(FeaturesCollectionQueryables::getTemporal).filter(temporal -> !temporal.isEmpty()).isPresent();
Optional<BoundingBox> spatial = api.getSpatialExtent(featureType.getId());
Optional<TemporalExtent> temporal = api.getTemporalExtent(featureType.getId());
if (hasSpatialQueryable && hasTemporalQueryable && spatial.isPresent() && temporal.isPresent()) {
collection.extent(new OgcApiExtent(temporal.get().getStart(), temporal.get().getEnd(), spatial.get().getXmin(), spatial.get().getYmin(), spatial.get().getXmax(), spatial.get().getYmax()));
} else if (hasSpatialQueryable && spatial.isPresent()) {
collection.extent(new OgcApiExtent(spatial.get().getXmin(), spatial.get().getYmin(), spatial.get().getXmax(), spatial.get().getYmax()));
} else if (hasTemporalQueryable && temporal.isPresent()) {
collection.extent(new OgcApiExtent(temporal.get().getStart(), temporal.get().getEnd()));
}
return collection;
}
use of de.ii.ogcapi.foundation.domain.TemporalExtent in project ldproxy by interactive-instruments.
the class CapabilityFeaturesCore method onStartup.
@Override
public ValidationResult onStartup(OgcApi api, MODE apiValidation) {
// TODO: add capability to periodically reinitialize metadata from the feature data (to account
// for lost notifications,
// because extent changes because of deletes are not taken into account, etc.)
// initialize dynamic collection metadata
OgcApiDataV2 apiData = api.getData();
apiData.getCollections().entrySet().forEach(entry -> {
final String collectionId = entry.getKey();
final Optional<CollectionExtent> optionalExtent = apiData.getExtent(collectionId);
Optional<BoundingBox> optionalBoundingBox;
if (optionalExtent.isEmpty() || optionalExtent.get().getSpatialComputed().orElse(true)) {
optionalBoundingBox = computeBbox(apiData, collectionId);
} else {
optionalBoundingBox = optionalExtent.get().getSpatial();
}
optionalBoundingBox.ifPresent(bbox -> api.updateSpatialExtent(collectionId, bbox));
Optional<TemporalExtent> optionalTemporalExtent;
if (optionalExtent.isEmpty() || optionalExtent.get().getTemporalComputed().orElse(true)) {
optionalTemporalExtent = computeInterval(apiData, collectionId);
} else {
optionalTemporalExtent = optionalExtent.get().getTemporal();
}
optionalTemporalExtent.ifPresent(interval -> api.updateTemporalExtent(collectionId, interval));
final FeatureTypeConfigurationOgcApi collectionData = apiData.getCollections().get(collectionId);
final Optional<FeatureProvider2> provider = providers.getFeatureProvider(apiData, collectionData);
if (provider.map(FeatureProvider2::supportsQueries).orElse(false)) {
final String featureTypeId = collectionData.getExtension(FeaturesCoreConfiguration.class).map(cfg -> cfg.getFeatureType().orElse(collectionId)).orElse(collectionId);
final long count = ((FeatureQueries) provider.get()).getFeatureCount(featureTypeId);
api.updateItemCount(collectionId, count);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Number of items in collection '{}': {}", collectionId, count);
}
}
});
providers.getFeatureProvider(apiData).ifPresent(provider -> provider.getFeatureChangeHandler().addListener(onFeatureChange(api)));
return ValidationResult.of();
}
use of de.ii.ogcapi.foundation.domain.TemporalExtent in project ldproxy by interactive-instruments.
the class QueriesHandlerCommonImpl method getLandingPageResponse.
private Response getLandingPageResponse(QueryInputLandingPage queryInput, ApiRequestContext requestContext) {
final LandingPageLinksGenerator linksGenerator = new LandingPageLinksGenerator();
OgcApi api = requestContext.getApi();
OgcApiDataV2 apiData = api.getData();
List<Link> links = linksGenerator.generateLinks(requestContext.getUriCustomizer().copy(), // TODO: support schema links, e.g. for WFS provider new WFSRequest(service.getWfsAdapter(), new DescribeFeatureType()).getAsUrl()
Optional.empty(), requestContext.getMediaType(), requestContext.getAlternateMediaTypes(), i18n, requestContext.getLanguage());
Optional<BoundingBox> bbox = api.getSpatialExtent();
Optional<TemporalExtent> interval = api.getTemporalExtent();
OgcApiExtent spatialExtent = bbox.isPresent() && interval.isPresent() ? new OgcApiExtent(interval.get().getStart(), interval.get().getEnd(), bbox.get().getXmin(), bbox.get().getYmin(), bbox.get().getXmax(), bbox.get().getYmax()) : bbox.map(boundingBox -> new OgcApiExtent(boundingBox.getXmin(), boundingBox.getYmin(), boundingBox.getXmax(), boundingBox.getYmax())).orElseGet(() -> interval.map(temporalExtent -> new OgcApiExtent(temporalExtent.getStart(), temporalExtent.getEnd())).orElse(null));
;
Builder builder = new Builder().title(apiData.getLabel()).description(apiData.getDescription().orElse("")).attribution(apiData.getMetadata().flatMap(Metadata::getAttribution)).externalDocs(apiData.getExternalDocs()).extent(Optional.ofNullable(spatialExtent)).links(links).addAllLinks(queryInput.getAdditionalLinks());
for (LandingPageExtension ogcApiLandingPageExtension : getDatasetExtenders()) {
builder = ogcApiLandingPageExtension.process(builder, api, requestContext.getUriCustomizer().copy(), requestContext.getMediaType(), requestContext.getAlternateMediaTypes(), requestContext.getLanguage());
}
CommonFormatExtension outputFormatExtension = api.getOutputFormat(CommonFormatExtension.class, requestContext.getMediaType(), "/", Optional.empty()).orElseThrow(() -> new NotAcceptableException(MessageFormat.format("The requested media type {0} cannot be generated.", requestContext.getMediaType().type())));
LandingPage apiLandingPage = builder.build();
Object entity = outputFormatExtension.getLandingPageEntity(apiLandingPage, requestContext.getApi(), requestContext);
Date lastModified = getLastModified(queryInput, api);
EntityTag etag = !outputFormatExtension.getMediaType().type().equals(MediaType.TEXT_HTML_TYPE) || api.getData().getExtension(HtmlConfiguration.class).map(HtmlConfiguration::getSendEtags).orElse(false) ? getEtag(apiLandingPage, PageRepresentation.FUNNEL, outputFormatExtension) : null;
Response.ResponseBuilder response = evaluatePreconditions(requestContext, lastModified, etag);
if (Objects.nonNull(response))
return response.build();
return prepareSuccessResponse(requestContext, queryInput.getIncludeLinkHeader() ? apiLandingPage.getLinks() : null, lastModified, etag, queryInput.getCacheControl().orElse(null), queryInput.getExpires().orElse(null), null, true, String.format("landing-page.%s", outputFormatExtension.getMediaType().fileExtension())).entity(entity).build();
}
Aggregations