From 901fbffd28d70d0b47899f58a2a94a7ce1664ac7 Mon Sep 17 00:00:00 2001 From: akrherz Date: Tue, 7 Nov 2023 12:02:41 -0600 Subject: [PATCH 1/2] fix: xticks properly set --- scripts/nexrad/attr_histogram.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/nexrad/attr_histogram.py b/scripts/nexrad/attr_histogram.py index 22a16abb8f..b17e2b1e81 100644 --- a/scripts/nexrad/attr_histogram.py +++ b/scripts/nexrad/attr_histogram.py @@ -73,9 +73,7 @@ def run(nexrad, name, network, cname): ax[1].set_ylabel("Movement Direction (from)") ax[1].set_yticks((0, 90, 180, 270, 360)) ax[1].set_yticklabels(("N", "E", "S", "W", "N")) - ax[1].set_xticks( - (1, 32, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 365) - ) + ax[1].set_xticks((1, 32, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335)) ax[1].set_xticklabels(calendar.month_abbr[1:]) ax[1].set_xlim(0, 365) ax[1].grid(True) From 24d2f99fb3a1d82be8c745f9d345b411024c8c49 Mon Sep 17 00:00:00 2001 From: akrherz Date: Tue, 7 Nov 2023 12:03:06 -0600 Subject: [PATCH 2/2] feat: add VTEC list by state/wfo option https://mesonet.agron.iastate.edu/vtec/search.php#list --- htdocs/json/vtec_events.py | 45 +++++++---- htdocs/json/vtec_events_bystate.py | 45 ++++++----- htdocs/vtec/search.js | 116 ++++++++++++++++++++++++++++- htdocs/vtec/search.php | 50 ++++++++++++- htdocs/vtec/wfos.js | 24 +----- 5 files changed, 222 insertions(+), 58 deletions(-) diff --git a/htdocs/json/vtec_events.py b/htdocs/json/vtec_events.py index ca09dc0f84..5f1b2f0afc 100644 --- a/htdocs/json/vtec_events.py +++ b/htdocs/json/vtec_events.py @@ -1,15 +1,17 @@ """Listing of VTEC events for a WFO and year""" import datetime import json +from io import BytesIO, StringIO +import pandas as pd from pyiem.util import get_dbconnc, html_escape, utc from pyiem.webutil import iemapp -from pymemcache.client import Client ISO9660 = "%Y-%m-%dT%H:%M:%SZ" +EXL = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" -def run(wfo, year, phenomena, significance, combo): +def get_res(wfo, year, phenomena, significance, combo): """Generate a report of VTEC ETNs used for a WFO and year Args: @@ -95,7 +97,7 @@ def run(wfo, year, phenomena, significance, combo): ) ) pgconn.close() - return json.dumps(res) + return res @iemapp() @@ -119,21 +121,34 @@ def application(environ, start_response): cb = environ.get("callback") combo = int(environ.get("combo", 0)) - mckey = ( - f"/json/vtec_events/{wfo}/{year}/{phenomena}/{significance}/{combo}" - ) - mc = Client("iem-memcached:11211") - res = mc.get(mckey) - if not res: - res = run(wfo, year, phenomena, significance, combo) - mc.set(mckey, res, 60) - else: - res = res.decode("utf-8") - mc.close() + fmt = environ.get("fmt", "json") + res = get_res(wfo, year, phenomena, significance, combo) + + if fmt == "xlsx": + fn = f"vtec_{wfo}_{year}_{phenomena}_{significance}.xlsx" + headers = [ + ("Content-type", EXL), + ("Content-disposition", f"attachment; Filename={fn}"), + ] + start_response("200 OK", headers) + bio = BytesIO() + pd.DataFrame(res["events"]).to_excel(bio, index=False) + return [bio.getvalue()] + if fmt == "csv": + fn = f"vtec_{wfo}_{year}_{phenomena}_{significance}.csv" + headers = [ + ("Content-type", "application/octet-stream"), + ("Content-disposition", f"attachment; Filename={fn}"), + ] + start_response("200 OK", headers) + bio = StringIO() + pd.DataFrame(res["events"]).to_csv(bio, index=False) + return [bio.getvalue().encode("utf-8")] + res = json.dumps(res) if cb is not None: res = f"{html_escape(cb)}({res})" headers = [("Content-type", "application/json")] start_response("200 OK", headers) - return [res.encode("utf-8")] + return [res.encode("ascii")] diff --git a/htdocs/json/vtec_events_bystate.py b/htdocs/json/vtec_events_bystate.py index 2d3a245f0e..7940833c2e 100644 --- a/htdocs/json/vtec_events_bystate.py +++ b/htdocs/json/vtec_events_bystate.py @@ -1,14 +1,16 @@ """Listing of VTEC events for state and year""" import json +from io import BytesIO, StringIO +import pandas as pd from pyiem.util import get_dbconnc, html_escape from pyiem.webutil import iemapp -from pymemcache.client import Client ISO9660 = "%Y-%m-%dT%H:%M:%SZ" +EXL = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" -def run(state, year, phenomena, significance): +def get_res(state, year, phenomena, significance): """Generate a report of VTEC ETNs used for a WFO and year Args: @@ -82,7 +84,7 @@ def run(state, year, phenomena, significance): ) ) pgconn.close() - return json.dumps(res) + return res @iemapp() @@ -93,22 +95,31 @@ def application(environ, start_response): phenomena = environ.get("phenomena", "__")[:2] significance = environ.get("significance", "_")[:1] cb = environ.get("callback") + fmt = environ.get("fmt", "json") + res = get_res(state, year, phenomena, significance) - mckey = "/json/vtec_events_bystate/%s/%s/%s/%s" % ( - state, - year, - phenomena, - significance, - ) - mc = Client("iem-memcached:11211") - res = mc.get(mckey) - if not res: - res = run(state, year, phenomena, significance) - mc.set(mckey, res, 60) - else: - res = res.decode("utf-8") - mc.close() + if fmt == "xlsx": + fn = f"vtec_{state}_{year}_{phenomena}_{significance}.xlsx" + headers = [ + ("Content-type", EXL), + ("Content-disposition", f"attachment; Filename={fn}"), + ] + start_response("200 OK", headers) + bio = BytesIO() + pd.DataFrame(res["events"]).to_excel(bio, index=False) + return [bio.getvalue()] + if fmt == "csv": + fn = f"vtec_{state}_{year}_{phenomena}_{significance}.csv" + headers = [ + ("Content-type", "application/octet-stream"), + ("Content-disposition", f"attachment; Filename={fn}"), + ] + start_response("200 OK", headers) + bio = StringIO() + pd.DataFrame(res["events"]).to_csv(bio, index=False) + return [bio.getvalue().encode("utf-8")] + res = json.dumps(res) if cb is not None: res = f"{html_escape(cb)}({res})" diff --git a/htdocs/vtec/search.js b/htdocs/vtec/search.js index 4f7114dd76..9912fc560b 100644 --- a/htdocs/vtec/search.js +++ b/htdocs/vtec/search.js @@ -4,6 +4,7 @@ let mapwidget1 = null; let mapwidget2 = null; let table1 = null; let table2 = null; +let table3 = null; let table2IsByPoint = true; let hashlinkUGC = null; let edate = null; @@ -13,6 +14,8 @@ let sdate1 = null; const BACKEND_EVENTS_BYPOINT = '/json/vtec_events_bypoint.py'; const BACKEND_EVENTS_BYUGC = '/json/vtec_events_byugc.py'; const BACKEND_SBW_BYPOINT = '/json/sbw_by_point.py'; +const BACKEND_EVENTS = "/json/vtec_events.py"; +const BACKEND_EVENTS_BYSTATE = "/json/vtec_events_bystate.py"; const states = [["AL", "Alabama"], ["AK", "Alaska"], ["AZ", "Arizona"], ["AR", "Arkansas"], ["CA", "California"], ["CO", "Colorado"], @@ -156,12 +159,46 @@ function updateTable2ByPoint(){ }); } +function updateTable3(){ + // get currently selected by3 radio button + const by = text($("input[name='by3']:checked").val()); + const datum = (by == "state") ? text(stateSelect.val()) : text($("#wfo3").val()); + const year = text($("#year3").val()); + const ph = text($("#ph3").val()); + const sig = text($("#sig3").val()); + window.location.href = `#list/${by}/${datum}/${year}/${ph}/${sig}`; + $("#table3title").text(`Events for ${by} ${datum} in ${year}`); + // Do what we need to for table 3 + $.ajax({ + data: { + wfo: $("#wfo3").val(), + state: stateSelect.val(), + year: year, + phenomena: ph, + significance: sig + }, + url: (by == "wfo") ? BACKEND_EVENTS: BACKEND_EVENTS_BYSTATE, + dataType: "json", + method: "GET", + success: (data) => { + table3.clear(); + $.map(data.events, (row) => { + const uri = `${row.phenomena}.${row.significance}.${row.eventid}`; + table3.row.add( + [uri, row.wfo, row.locations, row.issue, row.expire]); + }); + table3.draw(); + } + }); +} + + function buildUI(){ // Export Buttons $(".iemtool").click(function(){ // this const btn = $(this); let url = BACKEND_SBW_BYPOINT; - const params = { + var params = { fmt: (btn.data("opt") == "csv") ? "csv" : "xlsx", lat: $("#lat").val(), lon: $("#lon").val(), @@ -179,6 +216,18 @@ function buildUI(){ params.lat = $("#lat2").val(); } } + if (btn.data("table") == "3"){ + const by = $("input[name='by3']:checked").val(); + url = (by == "state") ? BACKEND_EVENTS_BYSTATE: BACKEND_EVENTS; + params = { + fmt: (btn.data("opt") == "csv") ? "csv" : "xlsx", + wfo: $("#wfo3").val(), + state: stateSelect.val(), + year: $("#year3").val(), + phenomena: $("#ph3").val(), + significance: $("#sig3").val() + }; + } window.location = `${url}?${$.param(params)}`; }); // Tables @@ -192,6 +241,11 @@ function buildUI(){ "emptyTable": "Drag marker on map or select UGC to auto-populate this table" } }); + table3 = $("#table3").DataTable({ + "language": { + "emptyTable": "Select options to auto-populate this table" + } + }); // Date pickers sdate = $("input[name='sdate']").datepicker({ dateFormat:"mm/dd/yy", @@ -300,7 +354,46 @@ function buildUI(){ mapwidget2.marker.setPosition(latlng); updateMarkerPosition2(lo, la); }); - + $("#button3").click(() => { + updateTable3(); + }); + // Populate wfos select with iemdata.wfos data + const wfos = $.map(iemdata.wfos, (obj) => { + const ee = {}; + ee.id = obj[0]; + ee.text = `[${obj[0]}] ${obj[1]}`; + return ee; + }); + $("select[name='wfo']").select2({ + placeholder: "Select a WFO", + data: wfos + }); + const ph = $.map(iemdata.vtec_phenomena, (obj) => { + const ee = {}; + ee.id = obj[0]; + ee.text = obj[1]; + return ee; + }); + $("select[name='ph']").select2({ + placeholder: "Select a Phenomena", + data: ph + }); + const sig = $.map(iemdata.vtec_significance, (obj) => { + const ee = {}; + ee.id = obj[0]; + ee.text = obj[1]; + return ee; + }); + $("select[name='sig']").select2({ + placeholder: "Select a Phenomena", + data: sig + }); + // populate year3 select with values from 1986 to current year + const year3 = $("select[name='year']"); + const currentYear = new Date().getFullYear(); + for (let i = 1986; i <= currentYear; i++){ + year3.append(new Option(i, i, false, false)); + } }; function _load() { @@ -340,6 +433,25 @@ function _load() { updateMarkerPosition2(default_lon, default_lat); } } + if (tokens2.length == 6){ + const aTag = $("a[name='list']"); + $('html,body').animate({scrollTop: aTag.offset().top},'slow'); + const by = text(tokens2[1]); + const datum = text(tokens2[2]); + const year = text(tokens2[3]); + const ph = text(tokens2[4]); + const sig = text(tokens2[5]); + $("input[name='by3'][value='"+by+"']").prop("checked", true); + $("#year3").val(year); + $("#ph3").val(ph); + $("#sig3").val(sig); + if (by == "state"){ + stateSelect.val(datum).trigger("change"); + } else { + $("#wfo3").val(datum); + } + updateTable3(); + } } mapwidget1 = new MapMarkerWidget("map", default_lon, default_lat); diff --git a/htdocs/vtec/search.php b/htdocs/vtec/search.php index 4e5f736b3a..f930fa15a1 100644 --- a/htdocs/vtec/search.php +++ b/htdocs/vtec/search.php @@ -13,7 +13,8 @@ - + + EOF; $t->headextra = <<
  • 1. Search for Storm Based Warnings by Point
  • 2. Search of Watch/Warning/Advisories by County/Zone or by Point
  • +
  • 3. List Watch/Warning/Advisories by State/WFO by Year
  • - +

    1. Search for Storm Based Warnings by Point


    The official warned area for some products the NWS issues is a polygon. @@ -138,6 +140,48 @@ - + +
    +

    3. List NWS Watch/Warning/Advisories by State/WFO by Year

    +
    +

    This section generates a simple list of NWS Watch, Warning, and Advisories +by state and year.

    +
    + +
    +
    +
    + + +

    + +

    + +

    + +

    +

    + +

    +

    + +

    +

    + +
    +
    +
    +

    + + + + + + + +
    EventWFOLocationsIssuedExpired
    +
    +
    +
    EOF; $t->render('full.phtml'); diff --git a/htdocs/vtec/wfos.js b/htdocs/vtec/wfos.js index ca27cf8873..16d578cf57 100644 --- a/htdocs/vtec/wfos.js +++ b/htdocs/vtec/wfos.js @@ -1,13 +1,7 @@ -Ext.namespace('iemdata'); +const iemdata = {}; -iemdata.nws_products = [ - ['AFD','Area Forecast Discussion'], - ['HWO','Hazzardous Weather Outlook'], - ['NOW','Nowcast'] -]; - -iemdata.vtec_phenomena_dict = [ +iemdata.vtec_phenomena = [ ['SV','Severe Thunderstorm'], ['TO','Tornado'], ['MA','Marine'], @@ -72,13 +66,8 @@ iemdata.vtec_phenomena_dict = [ ['ZR','Freezing Rain'] ]; -iemdata.vtecPhenomenaStore = new Ext.data.SimpleStore({ - fields : ['abbr', 'name'], - idIndex: 0, - data : iemdata.vtec_phenomena_dict -}); -iemdata.vtec_sig_dict = [ +iemdata.vtec_significance = [ ['W','Warning'], ['Y','Advisory'], ['A','Watch'], @@ -88,13 +77,6 @@ iemdata.vtec_sig_dict = [ ['N','Synopsis'] ]; -iemdata.vtecSignificanceStore = new Ext.data.SimpleStore({ - fields : ['abbr', 'name'], - idIndex: 0, - data : iemdata.vtec_sig_dict -}); - - iemdata.wfos = [ ['ABQ','ALBUQUERQUE'], ['ABR','ABERDEEN'],