Skip to content
Open
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
13 changes: 12 additions & 1 deletion api/swotvis/app/routers/hydrocron/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down
1 change: 0 additions & 1 deletion frontend/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
34 changes: 29 additions & 5 deletions frontend/src/_helpers/hydroCron.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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,
Expand All @@ -95,9 +101,15 @@ 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)

// 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
}
Expand Down Expand Up @@ -134,19 +146,31 @@ 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
})
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
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/TheLeafletMap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/stores/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
71 changes: 53 additions & 18 deletions frontend/src/views/ChartsView.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<template v-if="activeFeatureIsReach">
<v-container v-if="hasData" fluid fill-height>
<v-container fluid fill-height>
<v-tabs v-model="chartStore.chartTab" align-tabs="center" fixed-tabs color="primary" grow>
<v-tab value="timeseries">
<v-icon :icon="mdiTimelineClock"></v-icon>
Expand All @@ -11,16 +11,41 @@
Node Profile
</v-tab>
</v-tabs>
<TimeSeriesCharts v-if="chartStore.chartTab === 'timeseries'" />
<DistanceCharts v-if="chartStore.chartTab === 'distance'" />
</v-container>

<v-container v-if="fetchingData">
<h2 class="text-center ma-2">
<v-progress-circular :size="50" color="primary" indeterminate></v-progress-circular>
Loading data...
</h2>
<v-skeleton-loader height="70vh" type="image, divider, list-item-two-line" />
<template v-if="chartStore.chartTab === 'timeseries'">
<v-container v-if="querying.hydrocron">
<h2 class="text-center ma-2">
<v-progress-circular :size="50" color="primary" indeterminate></v-progress-circular>
Loading Reach data...
</h2>
<v-skeleton-loader height="70vh" type="image, divider, list-item-two-line" />
</v-container>
<TimeSeriesCharts v-else-if="hasReachData" />
<v-container v-else>
<h2 class="text-center ma-2">
No reach level data available for this reach. Use the
<router-link :to="{ path: `/` }">Map</router-link> to select a different reach.
</h2>
</v-container>
</template>
<template v-if="chartStore.chartTab === 'distance'">
<DistanceCharts v-if="hasNodeData" />
<v-container v-if="querying.nodes">
<h2 class="text-center ma-2">
<v-progress-circular :size="50" color="primary" indeterminate></v-progress-circular>
Loading Node data...
</h2>
<v-skeleton-loader height="70vh" type="image, divider, list-item-two-line" />
</v-container>
<v-container v-else>
<h2 class="text-center ma-2">
No node data available for this reach. Use the
<router-link :to="{ path: `/` }">Map</router-link> to make selections, or
<a href="#" @click.prevent="chartStore.chartTab = 'timeseries'"
>view the reach-averaged plots</a
>.
</h2>
</v-container>
</template>
</v-container>
</template>
<template v-else>
Expand Down Expand Up @@ -94,20 +119,30 @@ onMounted(async () => {
const runQuery = async () => {
querying.value.hydrocron = true
await queryHydroCron(activeFeature.value)
chartStore.buildChart(selectedFeatures.value)
querying.value.hydrocron = false
chartStore.buildChart(selectedFeatures.value)
if (!hasReachData.value) {
return
}

querying.value.nodes = true
await getNodeDataForReach(activeFeature.value)

// check to see if any node data were actually found
if (hasNodeData.value) {
chartStore.buildDistanceChart(featuresStore.nodes)
// show stats if they are enabled
statsStore.toggleSeriesStatistics(showStatistics.value)
chartStore.hasNodeData = true
} else {
chartStore.hasNodeData = false
}
querying.value.nodes = false
chartStore.buildDistanceChart(featuresStore.nodes)
// show stats if they are enabled
statsStore.toggleSeriesStatistics(showStatistics.value)
}

let hasData = computed(() => chartStore.chartData && chartStore.chartData.datasets?.length > 0)
let fetchingData = computed(
() => !hasData.value && (querying.value.hydrocron || querying.value.nodes)
let hasReachData = computed(() => chartStore.chartData && chartStore.chartData.datasets?.length > 0)
let hasNodeData = computed(
() => chartStore.nodeChartData && chartStore.nodeChartData.datasets?.length > 0
)
let activeFeatureIsReach = computed(() => {
return activeFeature.value && activeFeature.value.feature_type.toLowerCase() === 'reach'
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/views/DistanceCharts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@
</v-row>
</v-container>
<v-container v-else>
<h2 class="text-center ma-2">
<v-progress-circular :size="50" color="primary" indeterminate></v-progress-circular>
Loading node level data...
</h2>
<h2 class="text-center ma-2">No node level data available for this reach.</h2>
<v-skeleton-loader height="70vh" type="image, divider, list-item-two-line" />
</v-container>
</template>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/views/TimeSeriesCharts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
</v-col>
</v-row>
</v-container>
<v-container v-else>
<h2 class="text-center ma-2">No data available for this reach.</h2>
<v-skeleton-loader height="70vh" type="image, divider, list-item-two-line" />
</v-container>
</template>

<script setup>
Expand Down