diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/exception/wfs/WfsErrorHandler.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/exception/wfs/WfsErrorHandler.java
index 7af55e9d..fac2ec7f 100644
--- a/server/src/main/java/au/org/aodn/ogcapi/server/core/exception/wfs/WfsErrorHandler.java
+++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/exception/wfs/WfsErrorHandler.java
@@ -2,6 +2,7 @@
import au.org.aodn.ogcapi.server.core.exception.GeoserverFieldsNotFoundException;
import au.org.aodn.ogcapi.server.core.exception.UnauthorizedServerException;
+import au.org.aodn.ogcapi.server.core.model.enumeration.SseEventName;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -50,7 +51,7 @@ public static void handleError(Exception e, String uuid, SseEmitter emitter, Run
case VALIDATION_ERROR -> {
log.warn("Invalid parameter error for UUID {}: {}", uuid, e.getMessage());
emitter.send(SseEmitter.event()
- .name("error")
+ .name(SseEventName.ERROR.getValue())
.data(Map.of(
"message", "Invalid parameter error",
"timestamp", System.currentTimeMillis()
@@ -61,7 +62,7 @@ public static void handleError(Exception e, String uuid, SseEmitter emitter, Run
case UNAUTHORIZED_SERVER_ERROR -> {
log.warn("Unauthorized wfs server for UUID {}", uuid, e);
emitter.send(SseEmitter.event()
- .name("error")
+ .name(SseEventName.ERROR.getValue())
.data(Map.of(
"message", "Unauthorized wfs server",
"timestamp", System.currentTimeMillis()
@@ -72,7 +73,7 @@ public static void handleError(Exception e, String uuid, SseEmitter emitter, Run
case DOWNLOADABLE_FIELDS_ERROR -> {
log.warn("No downloadable fields found for UUID {}", uuid, e);
emitter.send(SseEmitter.event()
- .name("error")
+ .name(SseEventName.ERROR.getValue())
.data(Map.of(
"message", "No downloadable fields found",
"timestamp", System.currentTimeMillis()
@@ -83,7 +84,7 @@ public static void handleError(Exception e, String uuid, SseEmitter emitter, Run
case UNKNOWN_ERROR -> {
log.warn("Unknown error for UUID {}", uuid, e);
emitter.send(SseEmitter.event()
- .name("error")
+ .name(SseEventName.ERROR.getValue())
.data(Map.of(
"message", "Unknown error: " + e.getMessage(),
"timestamp", System.currentTimeMillis()
diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/model/enumeration/ProcessIdEnum.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/model/enumeration/ProcessIdEnum.java
index a7e3966b..48221df2 100644
--- a/server/src/main/java/au/org/aodn/ogcapi/server/core/model/enumeration/ProcessIdEnum.java
+++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/model/enumeration/ProcessIdEnum.java
@@ -7,6 +7,7 @@ public enum ProcessIdEnum {
DOWNLOAD_DATASET("download"),
DOWNLOAD_WFS_SSE("downloadWfs"),
DOWNLOAD_WFS_ESTIMATE("estimateWfsDownload"),
+ DOWNLOAD_CO_ESTIMATE("estimateCOdownload"),
UNKNOWN("");
private final String value;
diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/model/enumeration/SseEventName.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/model/enumeration/SseEventName.java
new file mode 100644
index 00000000..5879f17a
--- /dev/null
+++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/model/enumeration/SseEventName.java
@@ -0,0 +1,28 @@
+package au.org.aodn.ogcapi.server.core.model.enumeration;
+
+import lombok.Getter;
+
+/**
+ * Names of the SSE events sent by the download / size-estimate streams.
+ *
+ * The frontend identifies events by these literal names, so they form a wire
+ * contract — change a value only together with the portal (and keep them
+ * aligned with the data-access-service sse_wrapper vocabulary).
+ */
+@Getter
+public enum SseEventName {
+ CONNECTION_ESTABLISHED("connection-established"),
+ KEEP_ALIVE("keep-alive"),
+ DOWNLOAD_STARTED("download-started"),
+ FILE_CHUNK("file-chunk"),
+ DOWNLOAD_COMPLETE("download-complete"),
+ ESTIMATE_COMPLETE("estimate-complete"),
+ ESTIMATE_FAILED("estimate-failed"),
+ ERROR("error");
+
+ private final String value;
+
+ SseEventName(String value) {
+ this.value = value;
+ }
+}
diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/service/DasService.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/service/DasService.java
index 38676654..0615da6b 100644
--- a/server/src/main/java/au/org/aodn/ogcapi/server/core/service/DasService.java
+++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/service/DasService.java
@@ -14,6 +14,7 @@
import java.net.URLEncoder;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
@Service("DataAccessService")
@@ -35,51 +36,97 @@ public void init() {
httpEntity = new HttpEntity<>(headers);
}
- public byte[] getWaveBuoys(String from, String to){
+ public byte[] getWaveBuoys(String from, String to) {
String waveBuoysUrlTemplate = UriComponentsBuilder.fromUriString(dasConfig.host + "/api/v1/das/data/feature-collection/wave-buoy")
- .queryParam("start_date","{start_date}")
- .queryParam("end_date","{end_date}")
+ .queryParam("start_date", "{start_date}")
+ .queryParam("end_date", "{end_date}")
.encode()
.toUriString();
- Map params = new HashMap<>();
+ Map params = new HashMap<>();
params.put("start_date", from);
- params.put("end_date",to);
+ params.put("end_date", to);
- return httpClient.exchange(waveBuoysUrlTemplate, HttpMethod.GET,httpEntity,byte[].class,params).getBody();
+ return httpClient.exchange(waveBuoysUrlTemplate, HttpMethod.GET, httpEntity, byte[].class, params).getBody();
}
- public byte[] getWaveBuoysLatestDate(){
+ public byte[] getWaveBuoysLatestDate() {
String waveBuoysUrlTemplate = UriComponentsBuilder.fromUriString(dasConfig.host + "/api/v1/das/data/feature-collection/wave-buoy/latest")
.encode()
.toUriString();
- return httpClient.exchange(waveBuoysUrlTemplate, HttpMethod.GET,httpEntity,byte[].class).getBody();
+ return httpClient.exchange(waveBuoysUrlTemplate, HttpMethod.GET, httpEntity, byte[].class).getBody();
}
- public byte[] getWaveBuoyData(String from, String to, String buoy){
+ public byte[] getWaveBuoyData(String from, String to, String buoy) {
String encodedBuoy = URLEncoder.encode(buoy, java.nio.charset.StandardCharsets.UTF_8);
String waveBuoyDataUrlTemplate = UriComponentsBuilder.fromUriString(dasConfig.host + "/api/v1/das/data/feature-collection/wave-buoy/" + encodedBuoy)
- .queryParam("start_date","{start_date}")
- .queryParam("end_date","{end_date}")
+ .queryParam("start_date", "{start_date}")
+ .queryParam("end_date", "{end_date}")
.encode()
.toUriString();
- Map params = new HashMap<>();
+ Map params = new HashMap<>();
params.put("start_date", from);
- params.put("end_date",to);
+ params.put("end_date", to);
- return httpClient.exchange(waveBuoyDataUrlTemplate, HttpMethod.GET,httpEntity,byte[].class,params).getBody();
+ return httpClient.exchange(waveBuoyDataUrlTemplate, HttpMethod.GET, httpEntity, byte[].class, params).getBody();
}
- public byte[] getLatestWaveBuoySites(){
+ public byte[] getLatestWaveBuoySites() {
String waveBuoysUrlTemplate = UriComponentsBuilder.fromUriString(dasConfig.host + "/api/v1/das/data/feature-collection/wave-buoy/all")
.encode()
.toUriString();
- return httpClient.exchange(waveBuoysUrlTemplate, HttpMethod.GET,httpEntity,byte[].class).getBody();
+ return httpClient.exchange(waveBuoysUrlTemplate, HttpMethod.GET, httpEntity, byte[].class).getBody();
}
- public boolean isCollectionSupported(String collectionId){
+ /**
+ * Call the data-access-service cloud-optimised size estimate endpoint.
+ * Returns the raw JSON response body so the SSE layer can forward
+ * it to the frontend unchanged.
+ */
+ public String estimateCloudOptimisedDownloadSize(
+ String uuid,
+ List keys,
+ String startDate,
+ String endDate,
+ Object multiPolygon,
+ List columns,
+ String outputFormat) {
+
+ String url = UriComponentsBuilder.fromUriString(dasConfig.host + "/api/v1/das/data/{uuid}/estimate_size")
+ .encode()
+ .toUriString();
+
+ Map body = new HashMap<>();
+ body.put("keys", keys);
+ body.put("start_date", startDate != null ? startDate : "non-specified");
+ body.put("end_date", endDate != null ? endDate : "non-specified");
+ body.put("output_format", outputFormat);
+ // multi_polygon is accepted as a GeoJSON object or string; forward as-is.
+ if (multiPolygon != null) {
+ body.put("multi_polygon", multiPolygon);
+ }
+ // columns is not sent today (frontend doesn't subset columns yet, and the
+ // batch download grabs all variables), keeping the estimate aligned.
+ if (columns != null) {
+ body.put("columns", columns);
+ }
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
+ headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+ headers.set("X-API-KEY", dasConfig.secret);
+
+ HttpEntity