Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package au.org.aodn.ogcapi.server.core.service;

import au.org.aodn.ogcapi.features.model.FeatureGeoJSON;
import au.org.aodn.ogcapi.server.core.model.EsFeatureCollectionModel;
import au.org.aodn.stac.model.SearchSuggestionsModel;
import au.org.aodn.stac.model.StacCollectionModel;
import au.org.aodn.ogcapi.server.core.model.enumeration.*;
Expand All @@ -22,7 +20,6 @@
import org.geotools.filter.text.commons.CompilerUtil;
import org.geotools.filter.text.commons.Language;
import org.geotools.filter.text.cql2.CQLException;
import org.openapitools.jackson.nullable.JsonNullable;
import org.opengis.filter.Filter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
Expand Down Expand Up @@ -601,297 +598,7 @@ protected static FieldValue toFieldValue(String s) {
// we will prefix it with STR_INDICATOR
return FieldValue.of(s.replaceFirst(STR_INDICATOR, "").trim());
}
// Assume it is string
// Assume it is a string
return FieldValue.of(s.trim());
}
/**
* We will need to create a aggregation for each of the feature query, this one target the summary feature
* which create a summary of the indexed count group by geometry and date range for the cloud optimized data.
* Below code equals this:
* {
* "aggregations": {
* "coordinates": {
* "aggregations": {
* "total_count": {
* "sum": {
* "field": "properties.count"
* }
* },
* "max_time": {
* "max": {
* "field": "properties.time"
* }
* },
* "min_time": {
* "min": {
* "field": "properties.time"
* }
* },
* "coordinates": {
* "top_hits": {
* "size": 1,
* "sort": [
* {
* "collection.keyword": {
* "order": "asc"
* }
* },
* {
* "geometry.geometry.coordinates": {
* "order": "asc"
* }
* },
* ]
* }
* }
* },
* "composite": {
* "size": 2200,
* "sources": [
* {
* "collection": {
* "terms": {
* "field": "collection.keyword"
* }
* }
* },
* {
* "coordinates": {
* "terms": {
* "script": {
* "source": "doc['geometry.geometry.coordinates'].value.toString()",
* "lang": "painless"
* }
* }
* }
* }
* ]
* }
* }
* },
* "size": 0
* }
*
* @param collectionId - The metadata set id
* @param properties - The field you want to return
* @param filter - Any filter applied to the summary operation
* @return - Result
*/
// @Override
// public ElasticSearchBase.SearchResult<StacItemModel> searchFeatureSummary(String collectionId, List<String> properties, String filter) {
//
// final String COORDINATES = "coordinates";
// final String TOTAL_COUNT = "total_count";
// final String MIN_TIME = "min_time";
// final String MAX_TIME = "max_time";
//
// BiFunction<Map<String, FieldValue>, Map<String, FieldValue>, SearchRequest.Builder> builderSupplier = (
// arguments, afterKey) -> {
//
// SearchRequest.Builder builder = new SearchRequest.Builder();
//
// builder.query(q -> q
// .term(t -> t
// .field(CQLFeatureFields.collection.searchField)
// .value(arguments.get("collectionId"))
// )
// );
//
// // Group by lng
// CompositeAggregationSource lng = CompositeAggregationSource.of(c -> c.terms(t -> t
// .field(CQLFeatureFields.lng.searchField)));
//
// // Group by lat
// CompositeAggregationSource lat = CompositeAggregationSource.of(c -> c.terms(t -> t
// .field(CQLFeatureFields.lat.searchField)));
//
// // Use afterKey to page to another batch of records if exist
// Aggregation compose = afterKey == null ?
// new Aggregation.Builder().composite(c -> c
// .sources(List.of(
// Map.of(CQLFeatureFields.lng.name(), lng),
// Map.of(CQLFeatureFields.lat.name(), lat))
// )
// .size(pageSize)
// ).build()
// :
// new Aggregation.Builder().composite(c -> c
// .sources(List.of(
// Map.of(CQLFeatureFields.lng.name(), lng),
// Map.of(CQLFeatureFields.lat.name(), lat))
// )
// .size(pageSize)
// .after(afterKey)
// ).build();
//
//
// // Sum of count
// Aggregation sum = SumAggregation.of(s -> s.field(CQLFeatureFields.count.searchField))._toAggregation();
//
// // Min value of field
// Aggregation min = MinAggregation.of(s -> s.field(CQLFeatureFields.temporal.searchField))._toAggregation();
//
// // Max value of field
// Aggregation max = MaxAggregation.of(s -> s.field(CQLFeatureFields.temporal.searchField))._toAggregation();
//
// // Field value to return, think of it as select part of SQL
// Aggregation field = new Aggregation.Builder().topHits(th -> th.size(1)
// .sort(createSortOptions(
// String.format("%s,%s", CQLFeatureFields.lng.name(), CQLFeatureFields.lat.name()),
// CQLFeatureFields.class)))
// .build();
//
// Aggregation aggregation = new Aggregation.Builder()
// .composite(compose.composite())
// .aggregations(Map.of(
// TOTAL_COUNT, sum,
// MIN_TIME, min,
// MAX_TIME, max,
// COORDINATES, field
// ))
// .build();
//
// // There is a limitation that all sort field, assume to be inside the properties
// Aggregation nested = new Aggregation.Builder().nested(n -> n
// .path("properties")
// )
// .aggregations(COORDINATES, aggregation)
// .build();
//
//
// builder.index(dataIndexName)
// .size(0) // Do not return hits, only aggregations, that is the hits().hit() section will be empty
// .aggregations(COORDINATES, nested);
//
// return builder;
// };
//
// try {
// var queryTimer = new StopWatch();
// queryTimer.start("query timer");
// ElasticSearchBase.SearchResult<StacItemModel> result = new ElasticSearchBase.SearchResult<>();
// result.setCollections(new ArrayList<>());
//
// Map<String, FieldValue> arguments = Map.of(
// "collectionId", FieldValue.of(collectionId),
// "aggKey", FieldValue.of(COORDINATES)
// );
// Iterable<CompositeBucket> response = pageableAggregation(builderSupplier, CompositeBucket.class, arguments, null);
//
// queryTimer.stop();
// log.info(queryTimer.prettyPrint());
// var analyzingTimer = new StopWatch();
// analyzingTimer.start("analyzing timer");
// for (CompositeBucket node : response) {
// if (node != null) {
// StacItemModel. StacItemModelBuilder model = StacItemModel.builder();
//
// result.setTotal(result.getTotal() + node.docCount());
//
// TopHitsAggregate th = node.aggregations().get(COORDINATES).topHits();
// model.uuid(th.hits().hits().get(0).id());
//
// JsonData jd = th.hits().hits().get(0).source();
// if(jd != null) {
// Map<?, ?> map = jd.to(Map.class);
// BigDecimal lng = BigDecimal.valueOf((double)map.get("lng"));
// BigDecimal lat = BigDecimal.valueOf((double)map.get("lat"));
// model.geometry(Map.of("geometry", Map.of(
// "coordinates", List.of(lng, lat)
// )));
// }
//
// SumAggregate sa = node.aggregations().get(TOTAL_COUNT).sum();
// MinAggregate min = node.aggregations().get(MIN_TIME).min();
// MaxAggregate max = node.aggregations().get(MAX_TIME).max();
//
// model.properties(Map.of(
// FeatureProperty.COUNT.getValue(), sa.value(),
// FeatureProperty.START_TIME.getValue(), min.valueAsString() == null ? "" : min.valueAsString(),
// FeatureProperty.END_TIME.getValue(), max.valueAsString() == null ? "" : max.valueAsString()
// ));
//
// result.getCollections().add(model.build());
// }
// }
// analyzingTimer.stop();
// log.info(analyzingTimer.prettyPrint());
// return result;
// }
// catch (Exception e) {
// log.error("Error while searching dataset.", e);
// }
// return null;
// }

@Override
public SearchResult<FeatureGeoJSON> searchFeatureSummary(String collectionId, List<String> properties, String filter) {
try {
SearchRequest searchRequest = new SearchRequest.Builder()
.index(dataIndexName)
.query(q -> q.term(t -> t
.field("properties.collection.keyword")
.value(collectionId)
))
.size(1000)
.build();

var response = esClient.search(searchRequest, EsFeatureCollectionModel.class);

SearchResult<FeatureGeoJSON> result = new SearchResult<>();
List<FeatureGeoJSON> features = new ArrayList<>();
for (var hit : response.hits().hits()) {
EsFeatureCollectionModel hitFeatureCollection = hit.source();
if (hitFeatureCollection != null && hitFeatureCollection.getFeatures() != null) {
// A collectionID may map to several dataset key. So we need to identify features with dataset keys. TO get a dataset key which sits in hit.properties.key. For example:
// "properties": {
// "date": "2011-04",
// "collection": "4d3d4aca-472e-4616-88a5-df0f5ab401ba",
// "key": "mooring_acidification_realtime_qc.parquet"
// }
String datasetKey = null;
if (hitFeatureCollection.getProperties() != null) {
Object keyObj = hitFeatureCollection.getProperties().get("key");
if (keyObj != null) {
datasetKey = keyObj.toString();
}
}

List<FeatureGeoJSON> documentFeatures =
hitFeatureCollection.toFeatureCollectionGeoJSON().getFeatures();

for (FeatureGeoJSON feature : documentFeatures) {
// add key in property field for each feature
if (datasetKey != null) {
JsonNullable<Object> propertiesWrapper = feature.getProperties();
Map<String, Object> featurePropsMap = new HashMap<>();

if (propertiesWrapper != null
&& propertiesWrapper.isPresent()
&& propertiesWrapper.get() instanceof Map<?, ?> existingProps) {
existingProps.forEach((k, v) -> featurePropsMap.put(String.valueOf(k), v));
}

featurePropsMap.put("key", datasetKey);
feature.setProperties(JsonNullable.of(featurePropsMap));
}
features.add(feature);
}
}
}

log.info("feature size: {}", features.size());

result.setCollections(features);
if (response.hits().total() != null) {
result.setTotal(response.hits().total().value());
}

return result;

} catch (IOException e) {
log.error("Error while searching dataset.", e);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ public abstract class OGCApiService {
@Autowired
protected Search search;

// Hard coded dataset to avoid summary query, the AMSA should be skipped
private static final String EMPTY_SUMMARY_COLLECTION_ID = "2a5739e7-0cb8-444a-b83b-b2bc841b0ce8";

/**
* You can find conformance id
* <a href="https://docs.ogc.org/is/19-072/19-072.html#ats_core">here</a>
Expand All @@ -46,21 +43,6 @@ public ResponseEntity<FeatureCollectionGeoJSON> getFeature(String collectionId,
List<String> properties,
String filter) throws Exception {
switch(fid) {
case summary -> {
if (EMPTY_SUMMARY_COLLECTION_ID.equals(collectionId)) {
var featureCollection = new FeatureCollectionGeoJSON();
featureCollection.setType(FeatureCollectionGeoJSON.TypeEnum.FEATURECOLLECTION);
featureCollection.setFeatures(List.of());
return ResponseEntity.ok().body(featureCollection);
}

var result = search.searchFeatureSummary(collectionId, properties, filter);
var featureCollection = new FeatureCollectionGeoJSON();
featureCollection.setType(FeatureCollectionGeoJSON.TypeEnum.FEATURECOLLECTION);
featureCollection.setFeatures(result.getCollections());
return ResponseEntity.ok()
.body(featureCollection);
}
default -> {
// Individual item
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package au.org.aodn.ogcapi.server.core.service;

import au.org.aodn.ogcapi.features.model.FeatureGeoJSON;
import au.org.aodn.stac.model.StacCollectionModel;
import au.org.aodn.ogcapi.server.core.model.enumeration.CQLCrsType;
import co.elastic.clients.transport.endpoints.BinaryResponse;
Expand All @@ -18,7 +17,6 @@ public interface Search {
ElasticSearchBase.SearchResult<StacCollectionModel> searchCollections(String id);
ElasticSearchBase.SearchResult<StacCollectionModel> searchCollections(List<String> ids, String sortBy);
ElasticSearchBase.SearchResult<StacCollectionModel> searchAllCollections(String sortBy) throws Exception;
ElasticSearchBase.SearchResult<FeatureGeoJSON>searchFeatureSummary(String collectionId, List<String> properties, String filter) throws Exception;

ElasticSearchBase.SearchResult<StacCollectionModel> searchByParameters(
List<String> targets,
Expand Down
Loading
Loading