From c1749808782998e99f50feb74d5ec7f79b30a561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Cobi=C3=A1n?= Date: Tue, 7 Jan 2025 09:56:36 +0100 Subject: [PATCH] Get items in the temporal context from static catalog --- .../klab/stac/STACEncoder.java | 86 +++++++++++++++++-- .../klab/stac/STACUtils.java | 17 ++++ 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/adapters/klab.ogc/src/main/java/org/integratedmodelling/klab/stac/STACEncoder.java b/adapters/klab.ogc/src/main/java/org/integratedmodelling/klab/stac/STACEncoder.java index 9d7d277c0..fab0ea5b6 100644 --- a/adapters/klab.ogc/src/main/java/org/integratedmodelling/klab/stac/STACEncoder.java +++ b/adapters/klab.ogc/src/main/java/org/integratedmodelling/klab/stac/STACEncoder.java @@ -2,7 +2,9 @@ import java.io.IOException; import java.io.OutputStream; +import java.time.Instant; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -11,6 +13,7 @@ import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.data.FeatureSource; import org.geotools.geometry.jts.ReferencedEnvelope; +import org.hortonmachine.gears.io.stac.HMStacAsset; import org.hortonmachine.gears.io.stac.HMStacCollection; import org.hortonmachine.gears.io.stac.HMStacItem; import org.hortonmachine.gears.io.stac.HMStacManager; @@ -43,7 +46,9 @@ import org.integratedmodelling.klab.exceptions.KlabIllegalStateException; import org.integratedmodelling.klab.exceptions.KlabInternalErrorException; import org.integratedmodelling.klab.exceptions.KlabResourceAccessException; +import org.integratedmodelling.klab.exceptions.KlabResourceNotFoundException; import org.integratedmodelling.klab.exceptions.KlabUnimplementedException; +import org.integratedmodelling.klab.exceptions.KlabValidationException; import org.integratedmodelling.klab.ogc.STACAdapter; import org.integratedmodelling.klab.ogc.vector.files.VectorEncoder; import org.integratedmodelling.klab.raster.files.RasterEncoder; @@ -52,6 +57,7 @@ import org.integratedmodelling.klab.stac.extensions.STACIIASAExtension; import org.integratedmodelling.klab.utils.s3.S3URLUtils; import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Polygon; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; @@ -180,6 +186,12 @@ private AmazonS3 buildS3Client(String bucketRegion) throws IOException { return s3Client; } + private boolean isDateInsideRange(Time rangeTime, Date date) { + long rangeStart = rangeTime.getStart().getMilliseconds(); + long rangeEnd = rangeTime.getEnd().getMilliseconds(); + return (rangeStart <= date.getTime() && rangeEnd <= date.getTime()); + } + @Override public void getEncodedData(IResource resource, Map urnParameters, IGeometry geometry, Builder builder, IContextualizationScope scope) { @@ -188,19 +200,20 @@ public void getEncodedData(IResource resource, Map urnParameters String collectionId = collectionData.getString("id"); String catalogUrl = STACUtils.getCatalogUrl(collectionUrl, collectionId, collectionData); JSONObject catalogData = STACUtils.requestMetadata(catalogUrl, "catalog"); + String assetId = resource.getParameters().get("asset", String.class); boolean hasSearchOption = STACUtils.containsLinkTo(catalogData, "search"); // This is part of a WIP that will be removed in the future boolean isIIASA = catalogUrl.contains("iiasa.blob"); - if (!hasSearchOption && !isIIASA) { - // TODO implement how to read static collections - throw new KlabUnimplementedException("Cannot read a static collection."); - } Space space = (Space) geometry.getDimensions().stream().filter(d -> d instanceof Space) .findFirst().orElseThrow(); IEnvelope envelope = space.getEnvelope(); List bbox = List.of(envelope.getMinX(), envelope.getMaxX(), envelope.getMinY(), envelope.getMaxY()); + Time time = (Time) geometry.getDimensions().stream().filter(d -> d instanceof Time) + .findFirst().orElseThrow(); + Time resourceTime = (Time) Scale.create(resource.getGeometry()).getDimension(Type.TIME); + if (isIIASA) { FeatureSource source; try { @@ -213,6 +226,32 @@ public void getEncodedData(IResource resource, Map urnParameters return; } + if (!hasSearchOption) { + List features = getFeaturesFromStaticCollection(collectionUrl, collectionData, collectionId); + Time time2 = time; //TODO make the time and query time different + features = features.stream().filter(f -> { + Geometry fGeometry = (Geometry) f.getDefaultGeometry(); + return fGeometry.intersects(space.getShape().getJTSGeometry()); + }).toList(); + features = features.stream().filter(f -> isFeatureInTimeRange(time2, f)).toList(); + if (features.isEmpty()) { + throw new KlabResourceNotFoundException("There are no items in this context for the collection " + collectionId); + } + List assets = features.stream().map(f -> { + try { + return HMStacItem.fromSimpleFeature(f).getAssetForBand(assetId); + } catch (Exception e) { + throw new KlabIOException("Cannot get item from feature. Reason " + e.getMessage()); + } + }).toList(); + HMStacAsset asset = assets.stream().filter(as -> as.getId().equals(assetId)).findFirst().get(); + HMRaster.fromGridCoverage(null); + encoder = new RasterEncoder(); + GridCoverage2D coverage = null; + // TODO get coverage from the raster + ((RasterEncoder)encoder).encodeFromCoverage(resource, urnParameters, coverage, geometry, builder, scope); + } + LogProgressMonitor lpm = new LogProgressMonitor(); HMStacManager manager = new HMStacManager(catalogUrl, lpm); HMStacCollection collection = null; @@ -236,10 +275,6 @@ public void getEncodedData(IResource resource, Map urnParameters Polygon poly = GeometryUtilities.createPolygonFromEnvelope(env); collection.setGeometryFilter(poly); - Time time = (Time) geometry.getDimensions().stream().filter(d -> d instanceof Time) - .findFirst().orElseThrow(); - Time resourceTime = (Time) Scale.create(resource.getGeometry()).getDimension(Type.TIME); - if (resourceTime.getStart() != null && resourceTime.getEnd() != null && resourceTime.getCoveredExtent() > 0) { time = validateTemporalDimension(time, resourceTime); } @@ -286,7 +321,6 @@ public void getEncodedData(IResource resource, Map urnParameters // Allow transform ensures the process to finish, but I would not bet on the resulting // data. final boolean allowTransform = true; - String assetId = resource.getParameters().get("asset", String.class); HMRaster outRaster = collection.readRasterBandOnRegion(regionTransformed, assetId, items, allowTransform, mergeMode, lpm); coverage = outRaster.buildCoverage(); manager.close(); @@ -297,6 +331,40 @@ public void getEncodedData(IResource resource, Map urnParameters ((RasterEncoder)encoder).encodeFromCoverage(resource, urnParameters, coverage, geometry, builder, scope); } + private boolean isFeatureInTimeRange(Time time2, SimpleFeature f) { + Date datetime = (Date) f.getAttribute("datetime"); + if (datetime != null) { + if (isDateInsideRange(time2, datetime)) { + return true; + } + } + + Date itemStart = (Date) f.getAttribute("start_datetime"); + if (itemStart == null) { + return false; + } + Date itemEnd = (Date) f.getAttribute("end_datetime"); + if (itemEnd == null) { + return itemStart.toInstant().getEpochSecond() <= time2.getStart().getMilliseconds(); + } + if (isDateInsideRange(time2, itemStart) || isDateInsideRange(time2, itemEnd)) { + return false; + } + return true; + } + + private List getFeaturesFromStaticCollection(String collectionUrl, JSONObject collectionData, String collectionId) { + List links = collectionData.getJSONArray("links").toList().stream().filter(link -> ((JSONObject)link).getString("rel").equalsIgnoreCase("item")).toList(); + List urlOfLinks = links.stream().map(link -> STACUtils.getUrlOfItem(collectionUrl, collectionId, link.getString("href"))).toList(); + return urlOfLinks.stream().map(i -> { + try { + return STACUtils.getItemAsFeature(i); + } catch (Exception e) { + throw new KlabValidationException("Item at " + i + " cannot be parsed."); + } + }).toList(); + } + @Override public void listDetail(IResource resource, OutputStream stream, boolean verbose, IMonitor monitor) { // TODO Auto-generated method stub diff --git a/adapters/klab.ogc/src/main/java/org/integratedmodelling/klab/stac/STACUtils.java b/adapters/klab.ogc/src/main/java/org/integratedmodelling/klab/stac/STACUtils.java index 078b94dff..f0856fa00 100644 --- a/adapters/klab.ogc/src/main/java/org/integratedmodelling/klab/stac/STACUtils.java +++ b/adapters/klab.ogc/src/main/java/org/integratedmodelling/klab/stac/STACUtils.java @@ -1,14 +1,19 @@ package org.integratedmodelling.klab.stac; +import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.geotools.data.geojson.GeoJSONReader; import org.integratedmodelling.klab.api.provenance.IArtifact.Type; import org.integratedmodelling.klab.exceptions.KlabResourceAccessException; import org.integratedmodelling.klab.utils.DOIReader; +import org.opengis.feature.simple.SimpleFeature; + +import com.fasterxml.jackson.core.JsonParseException; import kong.unirest.HttpResponse; import kong.unirest.JsonNode; @@ -125,9 +130,21 @@ public static String getCatalogUrl(String collectionUrl, String collectionId, JS throw new KlabResourceAccessException("STAC collection is missing a relationship to the root catalog"); } String href = rootLink.get().getString("href"); + return getUrlOfItem(collectionUrl, collectionId, href); + } + + public static String getUrlOfItem(String collectionUrl, String collectionId, String href) { if (href.startsWith("..")) { return collectionUrl.replace("/collection.json", "").replace(collectionId, "") + href.replace("../", ""); } + if (href.startsWith(".")) { + return collectionUrl.replace("collection.json", "") + href.replace("./", ""); + } return href; } + + public static SimpleFeature getItemAsFeature(String itemUrl) throws JsonParseException, IOException { + HttpResponse response = Unirest.get(itemUrl).asJson(); + return GeoJSONReader.parseFeature(response.getBody().toString()); + } }