From 33d38b2084295a732f291e7dff15c4dbc260a6ad Mon Sep 17 00:00:00 2001 From: Riley Hales <39097632+rileyhales@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:55:40 -0600 Subject: [PATCH] Update to new living atlas layer (#1) * ngrok js layer * correct usages of river and reach * revise url for new map server dns * use segmented layers * clear charts on reload * correct river to latlon function * update to new esri endpoint * fix esri map * format revisions, show timezone on plots * linting --- install.yml | 4 +- tethysapp/hydroviewer/controllers.py | 49 +++++------- tethysapp/hydroviewer/public/css/main.css | 4 +- tethysapp/hydroviewer/public/js/main.js | 77 +++++++++++-------- .../templates/hydroviewer/base.html | 3 +- .../templates/hydroviewer/home.html | 53 ++++++------- 6 files changed, 93 insertions(+), 97 deletions(-) diff --git a/install.yml b/install.yml index 044aea9..b8aef1e 100644 --- a/install.yml +++ b/install.yml @@ -14,7 +14,9 @@ requirements: - cf-staging - defaults packages: - - geoglows>=1.1.0 + - geoglows>=1.7.0 + - natsort + - plotly pip: diff --git a/tethysapp/hydroviewer/controllers.py b/tethysapp/hydroviewer/controllers.py index 3fd2dfa..470cd6c 100644 --- a/tethysapp/hydroviewer/controllers.py +++ b/tethysapp/hydroviewer/controllers.py @@ -1,4 +1,4 @@ -import geoglows as gg +import geoglows as g from django.http import JsonResponse from django.shortcuts import render from tethys_sdk.routing import controller @@ -6,16 +6,9 @@ @controller def home(request): - """ - Controller for the app home page. - """ - dates = gg.data.dates().values.flatten() - dates = [(date, f'{date[0:4]}-{date[4:6]}-{date[6:8]}') for date in dates] - dates = sorted(dates, reverse=True) context = { - 'dates': dates, - 'endpoint': gg.data.DEFAULT_REST_ENDPOINT, - 'version': gg.data.DEFAULT_REST_ENDPOINT_VERSION, + 'endpoint': g.data.DEFAULT_REST_ENDPOINT, + 'version': g.data.DEFAULT_REST_ENDPOINT_VERSION, } return render(request, 'hydroviewer/home.html', context) @@ -31,40 +24,36 @@ def get_forecast(request): source = 'hydroviewer' reach_id = int(reach_id) try: - ens = gg.data.forecast_ensembles(reach_id, date=forecast_date, source=source) - rp = gg.data.return_periods(reach_id) - simple = gg.analyze.simple_forecast(ens) + ens = g.data.forecast_ensembles(reach_id, date=forecast_date, source=source) + rp = g.data.return_periods(reach_id) except Exception as e: print(e) return JsonResponse({'error': str(e)}) - return JsonResponse({ - 'ens': gg.plots.forecast_ensembles(ens, rp_df=rp, plot_type='html'), - 'simple': gg.plots.forecast(simple, rp_df=rp, plot_type='html'), - 'rpt': gg.tables.flood_probabilities(ens, rp), - }) + json_respones = { + 'ens': g.plots.forecast_ensembles(ens, rp_df=rp, plot_type='html'), + 'simple': g.plots.forecast(g.analyze.simple_forecast(ens), rp_df=rp, plot_type='html'), + 'rpt': g.tables.flood_probabilities(ens, rp), + } + return JsonResponse(json_respones) @controller(name='get-retrospective', url='get-retrospective') def get_retrospective(request): - reach_id = int(request.GET['reach_id']) - - df = gg.data.retrospective(river_id=reach_id) - dayavg_df = gg.analyze.daily_averages(df) - monavg_df = gg.analyze.monthly_averages(df) - annavg_df = gg.analyze.annual_averages(df) + river_id = int(request.GET['reach_id']) + df = g.data.retrospective(river_id=river_id) + rp = g.data.return_periods(river_id=river_id) json_response = { - 'retro': gg.plots.retrospective(df, plot_type='html'), - 'dayAvg': gg.plots.daily_averages(dayavg_df, plot_type='html'), - 'monAvg': gg.plots.monthly_averages(monavg_df, plot_type='html'), - 'annAvg': gg.plots.annual_averages(annavg_df, plot_type='html'), + 'retro': g.plots.retrospective(df, plot_type='html', rp_df=rp), + 'dayAvg': g.plots.daily_averages(g.analyze.daily_averages(df), plot_type='html'), + 'annAvg': g.plots.annual_averages(g.analyze.annual_averages(df), plot_type='html', decade_averages=True), + 'fdc': g.plots.flow_duration_curve(df, plot_type='html'), } - return JsonResponse(json_response) @controller(name='find-river', url='find-river') def find_river(request): - lat, lon = gg.streams.reach_to_latlon(int(request.GET['reach_id'])) + lat, lon = g.streams.river_to_latlon(int(request.GET['reach_id'])) return JsonResponse({'lat': lat, 'lon': lon}) diff --git a/tethysapp/hydroviewer/public/css/main.css b/tethysapp/hydroviewer/public/css/main.css index ce763ac..915d408 100644 --- a/tethysapp/hydroviewer/public/css/main.css +++ b/tethysapp/hydroviewer/public/css/main.css @@ -1,6 +1,6 @@ /* MAP STYLES */ .modal-chart { - height: min(3in, 550px) + height: min(4in, 550px) } .load-status { @@ -174,7 +174,7 @@ #fcProbTable { height: auto; - min-height: 250px; + min-height: 300px; align-items: center; display: flex; justify-content: center; diff --git a/tethysapp/hydroviewer/public/js/main.js b/tethysapp/hydroviewer/public/js/main.js index 7ce96ec..1567c3f 100644 --- a/tethysapp/hydroviewer/public/js/main.js +++ b/tethysapp/hydroviewer/public/js/main.js @@ -6,7 +6,7 @@ const app = (() => { // const endpoint = "{{ endpoint }}"; // const URL_getForecastData = "{% url 'hydroviewer:get-forecast' %}"; // const URL_getHistoricalData = "{% url 'hydroviewer:get-retrospective' %}"; - // const URL_findReachID = "{% url 'hydroviewer:find-river' %}"; + // const URL_findRiverID = "{% url 'hydroviewer:find-river' %}"; // const ESRI_LAYER_URL = = ""; let loadingStatus = {reachid: "clear", forecast: "clear", retro: "clear"} @@ -129,7 +129,8 @@ const app = (() => { to: endDateTime }) .addTo(mapObj) - L.control + L + .control .layers( basemapsJson, { @@ -141,32 +142,38 @@ const app = (() => { .addTo(mapObj) mapObj.on("click", event => { - if (mapObj.getZoom() <= 9.5) return mapObj.flyTo(event.latlng, 10) + if (mapObj.getZoom() < 16) { + mapObj.flyTo(event.latlng, 16) + mapObj.fire('zoomend') + return + } mapObj.flyTo(event.latlng) if (mapMarker) mapObj.removeLayer(mapMarker) mapMarker = L.marker(event.latlng).addTo(mapObj) - // updateStatusIcons({reachid: "load", forecast: "clear", retro: "clear"}) - // $("#chart_modal").modal("show") - // L.esri - // .identifyFeatures({url: ESRI_LAYER_URL}) - // .on(mapObj) - // .at([event.latlng["lat"], event.latlng["lng"]]) - // .tolerance(10) // map pixels to buffer search point - // .precision(3) // decimals in the returned coordinate pairs - // .run((error, featureCollection) => { - // if (error) { - // updateStatusIcons({reachid: "fail"}) - // alert("Error finding the reach_id") - // return - // } - // updateStatusIcons({reachid: "ready"}) - // selectedSegment.clearLayers() - // selectedSegment.addData(featureCollection.features[0].geometry) - // REACHID = featureCollection.features[0].properties["COMID (Stream Identifier)"] - // fetchData(REACHID) - // }) + updateStatusIcons({reachid: "load", forecast: "clear", retro: "clear"}) + $("#chart_modal").modal("show") + + L + .esri + .identifyFeatures({url: ESRI_LAYER_URL}) + .on(mapObj) + .at([event.latlng["lat"], event.latlng["lng"]]) + .tolerance(10) // map pixels to buffer search point + .precision(6) // decimals in the returned coordinate pairs + .run((error, featureCollection) => { + if (error) { + updateStatusIcons({reachid: "fail"}) + alert("Error finding the reach_id") + return + } + updateStatusIcons({reachid: "ready"}) + selectedSegment.clearLayers() + selectedSegment.addData(featureCollection.features[0].geometry) + REACHID = featureCollection.features[0].properties["TDX Hydro Link Number"] + fetchData(REACHID) + }) }) //////////////////////////////////////////////////////////////////////// OTHER UTILITIES ON THE LEFT COLUMN @@ -182,7 +189,7 @@ const app = (() => { $.ajax({ type: "GET", async: true, - url: `${URL_findReachID}${L.Util.getParamString({reach_id: prompt("Please enter a Reach ID to search for")})}`, + url: `${URL_findRiverID}${L.Util.getParamString({reach_id: prompt("Please enter a Reach ID to search for")})}`, success: response => { if (mapMarker) mapObj.removeLayer(mapMarker) mapMarker = L.marker(L.latLng(response.lat, response.lon)).addTo(mapObj) @@ -233,8 +240,8 @@ const app = (() => { $("#fcProbTable"), $("#retroPlot"), $("#dayAvgPlot"), - $("#monAvgPlot"), - $("#annAvgPlot"), + $("#annAvgPlot "), + $("#fdcPlot") ] const getForecastData = reachID => { @@ -244,14 +251,17 @@ const app = (() => { let simpleForecast = chartDivs[0] let ensembleForecast = chartDivs[1] let probabilityTable = chartDivs[2] + $("#chart_modal").modal("show") ftl.tab("show") simpleForecast.html(``) + ensembleForecast.html('') + probabilityTable.html('') simpleForecast.css("text-align", "center") updateStatusIcons({forecast: "load"}) $.ajax({ type: "GET", async: true, - data: {reach_id: REACHID, forecast_date: $("#forecast_date").val()}, + data: {reach_id: REACHID, timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, forecast_date: $("#forecast_date").val().replaceAll("-", "")}, url: URL_getForecastData, success: response => { ftl.tab("show") @@ -273,7 +283,6 @@ const app = (() => { error: () => { updateStatusIcons({forecast: "fail"}) giveForecastRetryButton(REACHID) - REACHID = null } }) } @@ -282,8 +291,10 @@ const app = (() => { if (!REACHID) return updateStatusIcons({retro: "load"}) updateDownloadLinks("clear") - let tl = $("#historical_tab_link") // select divs with jquery so we can reuse them - let plotdiv = chartDivs[3] + let tl = $("#historical_tab_link") // select divs with jquery so we can reuse them + chartDivs.slice(3).forEach(div => div.html("")) // clear the historical data divs + $("#chart_modal").modal("show") + $("#retroPlot").html(``) $.ajax({ type: "GET", async: true, @@ -292,10 +303,10 @@ const app = (() => { success: response => { tl.tab("show") tl.click() - plotdiv.html(response.retro) + $("#retroPlot").html(response.retro) $("#dayAvgPlot").html(response.dayAvg) - $("#monAvgPlot").html(response.monAvg) $("#annAvgPlot").html(response.annAvg) + $("#fdcPlot").html(response.fdc) updateDownloadLinks("set") updateStatusIcons({retro: "ready"}) }, @@ -390,7 +401,7 @@ const app = (() => { $("#forecast_tab_link").on("click", () => fix_buttons("forecast")) $("#historical_tab_link").on("click", () => fix_buttons("historical")) - $("#forecast_date").on("change", getForecastData) + $("#forecast_date").on("change", () => getForecastData()) const clearMarkers = () => { if (mapMarker) mapObj.removeLayer(mapMarker) diff --git a/tethysapp/hydroviewer/templates/hydroviewer/base.html b/tethysapp/hydroviewer/templates/hydroviewer/base.html index 6be907f..2d978de 100644 --- a/tethysapp/hydroviewer/templates/hydroviewer/base.html +++ b/tethysapp/hydroviewer/templates/hydroviewer/base.html @@ -30,9 +30,8 @@ Find River by ID Remove Map Marker
VIIRS imagery is a web mapping service layer derived from the VIIRS satellites' optical sensors.
VIIRS - Flood Detection Map Quick Guide (PDF) (noaa.gov)