From ac08ca0f79d402ebea221e46ea0783c5bf8f554c Mon Sep 17 00:00:00 2001 From: Devin Cowan Date: Tue, 4 Nov 2025 13:19:48 -0500 Subject: [PATCH 1/6] Request "Version D" SWOT data from HydroCron --- frontend/src/_helpers/hydroCron.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/_helpers/hydroCron.js b/frontend/src/_helpers/hydroCron.js index a4bff1b..f9bfa62 100644 --- a/frontend/src/_helpers/hydroCron.js +++ b/frontend/src/_helpers/hydroCron.js @@ -87,6 +87,12 @@ const queryHydroCron = async (swordFeature = null, output = 'geojson') => { const start_time = EARLIEST_HYDROCRON_DATETIME const end_time = new Date(Date.now() + MS_TO_KEEP_CACHE).toISOString().split('.')[0] + 'Z' + // determine which collection name to use based on feature type ('Reach' or 'PriorLake') + let collection_name = 'SWOT_L2_HR_RiverSP_D' + if (feature_type === 'PriorLake') { + collection_name = 'SWOT_L2_HR_LakeSP_D' + } + params = { feature: feature_type, feature_id, @@ -95,7 +101,9 @@ const queryHydroCron = async (swordFeature = null, output = 'geojson') => { output, fields, // https://podaac.github.io/hydrocron/timeseries.html#compact-string-required-no - compact: 'true' + compact: 'true', + // https://podaac.github.io/hydrocron/timeseries.html#collection-name-string-required-no + collection_name } let response = await fetchHydroCronData(HYDROCRON_URL, params, swordFeature) if (response == null) { From 2c6c2cb74b3e53ccbb3282814ac815da088d0512 Mon Sep 17 00:00:00 2001 From: Devin Cowan Date: Tue, 4 Nov 2025 13:19:48 -0500 Subject: [PATCH 2/6] Update WMS service URLs to version v17b for reaches and nodes layers --- frontend/src/components/TheLeafletMap.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/TheLeafletMap.vue b/frontend/src/components/TheLeafletMap.vue index 61983eb..11e8c22 100644 --- a/frontend/src/components/TheLeafletMap.vue +++ b/frontend/src/components/TheLeafletMap.vue @@ -140,7 +140,7 @@ onMounted(async () => { // add reaches layer to map url = - 'https://arcgis.cuahsi.org/arcgis/services/SWOT/world_SWORD_reaches_mercator/MapServer/WMSServer?' + 'https://arcgis.cuahsi.org/arcgis/services/SWOT/world_SWORD_reaches_mercator_v17b/MapServer/WMSServer?' const reachesWMS = L.tileLayer.wms(url, { layers: 0, transparent: 'true', @@ -153,7 +153,7 @@ onMounted(async () => { // add nodes layer to map url = - 'https://arcgis.cuahsi.org/arcgis/services/SWOT/world_SWORD_nodes_mercator/MapServer/WMSServer?' + 'https://arcgis.cuahsi.org/arcgis/services/SWOT/world_SWORD_nodes_mercator_v17b/MapServer/WMSServer?' L.tileLayer.wms(url, { layers: 0, transparent: 'true', From f13447f9deabfbcce815dd27bdf2ff0eaae8d0f9 Mon Sep 17 00:00:00 2001 From: Devin Cowan Date: Tue, 4 Nov 2025 13:19:48 -0500 Subject: [PATCH 3/6] Refactor HydroCron integration to use API proxy endpoint and update constants --- frontend/docker-entrypoint.sh | 1 - frontend/src/_helpers/hydroCron.js | 8 ++++++-- frontend/src/constants.js | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/docker-entrypoint.sh b/frontend/docker-entrypoint.sh index a6de2f8..f5703de 100755 --- a/frontend/docker-entrypoint.sh +++ b/frontend/docker-entrypoint.sh @@ -7,7 +7,6 @@ for file in $DIST_DIR/assets/*.js $DIST_DIR/index.html; do sed -i 's|VITE_APP_API_URL_PLACEHOLDER|'${VITE_APP_API_URL}'|g' $file sed -i 's|VITE_APP_FULL_URL_PLACEHOLDER|'${VITE_APP_FULL_URL}'|g' $file sed -i 's|VITE_APP_BASE_PLACEHOLDER|'${VITE_APP_BASE}'|g' $file - sed -i 's|VITE_HYDROCRON_URL_PLACEHOLDER|'${VITE_HYDROCRON_URL}'|g' $file sed -i 's|VITE_HYDROSHARE_NOTEBOOKS_COLLECTION_PLACEHOLDER|'${VITE_HYDROSHARE_NOTEBOOKS_COLLECTION}'|g' $file done diff --git a/frontend/src/_helpers/hydroCron.js b/frontend/src/_helpers/hydroCron.js index f9bfa62..a99005a 100644 --- a/frontend/src/_helpers/hydroCron.js +++ b/frontend/src/_helpers/hydroCron.js @@ -1,4 +1,4 @@ -import { HYDROCRON_URL } from '@/constants' +import { ENDPOINTS } from '@/constants' import { useFeaturesStore } from '@/stores/features' import { useAlertStore } from '@/stores/alerts' import { useHydrologicStore } from '@/stores/hydrologic' @@ -105,7 +105,11 @@ const queryHydroCron = async (swordFeature = null, output = 'geojson') => { // https://podaac.github.io/hydrocron/timeseries.html#collection-name-string-required-no collection_name } - let response = await fetchHydroCronData(HYDROCRON_URL, params, swordFeature) + + // Use our API proxy URL instead of the direct HydroCron URL + // This is due to CORS issues with the HydroCron server + // https://github.com/podaac/hydrocron/issues/306 + let response = await fetchHydroCronData(ENDPOINTS.hydrocron, params, swordFeature) if (response == null) { return } diff --git a/frontend/src/constants.js b/frontend/src/constants.js index 18db198..8673ad0 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -4,12 +4,12 @@ let APP_FULL_URL_IN = import.meta.env.VITE_APP_FULL_URL || 'VITE_APP_FULL_URL_PL export const APP_FULL_URL = APP_FULL_URL_IN.endsWith('/') ? APP_FULL_URL_IN : `${APP_FULL_URL_IN}/` export const APP_API_URL = import.meta.env.VITE_APP_API_URL || 'VITE_APP_API_URL_PLACEHOLDER' -export const HYDROCRON_URL = import.meta.env.VITE_HYDROCRON_URL || 'VITE_HYDROCRON_URL_PLACEHOLDER' export const VITE_HYDROSHARE_NOTEBOOKS_COLLECTION = import.meta.env.VITE_HYDROSHARE_NOTEBOOKS_COLLECTION || 'VITE_HYDROSHARE_NOTEBOOKS_COLLECTION_PLACEHOLDER' export const ENDPOINTS = { - openapi: `${APP_API_URL}/openapi.json` + openapi: `${APP_API_URL}/openapi.json`, + hydrocron: `${APP_API_URL}/hydrocron/timeseries` } export const NODE_DATETIME_VARIATION = 1 // minutes From 37ce7a284a04a5baecdeae2aee3eb38b55f54788 Mon Sep 17 00:00:00 2001 From: Devin Cowan Date: Tue, 4 Nov 2025 13:19:48 -0500 Subject: [PATCH 4/6] Enhance error handling in HydroCron API integration and improve alert messages for no data and fetch errors --- api/swotvis/app/routers/hydrocron/router.py | 13 ++++++++++++- frontend/src/_helpers/hydroCron.js | 16 ++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/api/swotvis/app/routers/hydrocron/router.py b/api/swotvis/app/routers/hydrocron/router.py index 0a50c81..7a323f4 100644 --- a/api/swotvis/app/routers/hydrocron/router.py +++ b/api/swotvis/app/routers/hydrocron/router.py @@ -47,6 +47,9 @@ async def proxy_hydrocron_timeseries( try: async with httpx.AsyncClient() as client: + print( + f"Original request url with params: {client.build_request('GET', HYDROCRON_BASE_URL, params=params).url}" + ) response = await client.get(HYDROCRON_BASE_URL, params=params, timeout=30.0) # If the response is successful, return the content @@ -57,7 +60,15 @@ async def proxy_hydrocron_timeseries( return JSONResponse(content=response.json()) else: # Forward the error from the external API - raise HTTPException(status_code=response.status_code, detail=f"HydroCron API error: {response.text}") + # check if response has json content + if response.headers.get("Content-Type") == "application/json": + error_detail = response.json() + else: + error_detail = response.text + # ensure we include statusText as well + if "statusText" in response.headers: + error_detail += f": {response.headers['statusText']}" + return JSONResponse(content=error_detail, status_code=response.status_code) except httpx.TimeoutException: raise HTTPException(status_code=504, detail="Request timeout") diff --git a/frontend/src/_helpers/hydroCron.js b/frontend/src/_helpers/hydroCron.js index a99005a..c588ded 100644 --- a/frontend/src/_helpers/hydroCron.js +++ b/frontend/src/_helpers/hydroCron.js @@ -146,9 +146,15 @@ const fetchHydroCronData = async (url, params, swordFeature) => { }) if (response.status < 500) { if (response.status == 400) { + let text = 'No data found for: ' + if (params.feature && params.feature_id) { + text += `${params.feature} ${params.feature_id}` + } else { + text += JSON.stringify(params) + } alertStore.displayAlert({ title: 'No data found', - text: `No data found for ${JSON.stringify(params)}`, + text, type: 'warning', closable: true, duration: 6 @@ -156,9 +162,15 @@ const fetchHydroCronData = async (url, params, swordFeature) => { return null } } else { + let text = 'Error while fetching SWOT data: ' + if (response.statusText) { + text += response.statusText + } else { + text += 'Unknown error' + } alertStore.displayAlert({ title: 'Error fetching SWOT data', - text: `Error while fetching SWOT data: ${response.statusText}`, + text, type: 'error', closable: true, duration: 3 From 721254ddbcdaa4032d3dc7581b493212783ebe35 Mon Sep 17 00:00:00 2001 From: Devin Cowan Date: Tue, 4 Nov 2025 13:19:48 -0500 Subject: [PATCH 5/6] feat: update PLD WMS and FeatureServer URLs in TheLeafletMap and map store --- frontend/src/components/TheLeafletMap.vue | 2 +- frontend/src/stores/map.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/TheLeafletMap.vue b/frontend/src/components/TheLeafletMap.vue index 11e8c22..3733f9a 100644 --- a/frontend/src/components/TheLeafletMap.vue +++ b/frontend/src/components/TheLeafletMap.vue @@ -118,7 +118,7 @@ onMounted(async () => { mapStore.generateLakesFeatures() - let url = 'https://arcgis.cuahsi.org/arcgis/services/SWOT/world_swot_lakes/MapServer/WmsServer?' + let url = 'https://arcgis.cuahsi.org/arcgis/services/SWOT/SWOT_PLD_v201/MapServer/WmsServer?' const lakesWMS = L.tileLayer.wms(url, { layers: 0, transparent: 'true', diff --git a/frontend/src/stores/map.js b/frontend/src/stores/map.js index 3d73ab7..3de50ff 100644 --- a/frontend/src/stores/map.js +++ b/frontend/src/stores/map.js @@ -219,8 +219,7 @@ export const useMapStore = defineStore('map', () => { console.warn('Lakes features already generated, skipping.') return lakesFeatures.value } - const url = - 'https://arcgis.cuahsi.org/arcgis/rest/services/SWOT/world_swot_lakes/FeatureServer/0' + const url = 'https://arcgis.cuahsi.org/arcgis/rest/services/SWOT/SWOT_PLD_v201/FeatureServer/0' lakesFeatures.value = esriLeaflet.featureLayer({ url: url, simplifyFactor: 0.35, From 457e5af4a91dd7296fdf26e9923905b1239d1aab Mon Sep 17 00:00:00 2001 From: Devin Cowan Date: Thu, 6 Nov 2025 12:01:53 -0500 Subject: [PATCH 6/6] feat: improve data loading feedback and error handling in charts view --- frontend/src/views/ChartsView.vue | 71 ++++++++++++++++++------- frontend/src/views/DistanceCharts.vue | 5 +- frontend/src/views/TimeSeriesCharts.vue | 4 ++ 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/frontend/src/views/ChartsView.vue b/frontend/src/views/ChartsView.vue index ddbb3e0..1408f48 100644 --- a/frontend/src/views/ChartsView.vue +++ b/frontend/src/views/ChartsView.vue @@ -1,6 +1,6 @@