diff --git a/.gitignore b/.gitignore
index abea78e1af02098b09375c46cf37b1c817188008..7225628642c96ccb0fce0745faf79fb4483a8ece 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,5 @@ log
*.log
*.swp
*.swo
+/js/tmp/
+was_dev_erose.json
diff --git a/assets/style.css b/assets/style.css
index 67b43361c53541ad73abec3e8fc9d2a6ff4f6983..c6e521271b9c9f8371509d5ed33d444ab0c9ee0c 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -1,3 +1,7 @@
+body {
+ background-color: #212529;
+}
+
a.modebar-btn {
font-size: 2rem !important;
margin: 1rem .2rem;
@@ -81,7 +85,7 @@ a.modebar-btn {
.description-title>p {
height: 35px;
- color: #2F4047;
+ color: #2Ferror_7;
font-family: "Libre Baskerville";
font-size: 24px;
font-weight: bold;
@@ -765,3 +769,23 @@ div.SingleDatePickerInput {
position: absolute;
right: 5px;
}
+
+/*styles for 404 page*/
+.error_links {
+ color: #F1B545;
+ padding-left: 20px;
+ text-decoration: underline;
+}
+
+#error_title {
+ font-family:"Libre Baskerville", serif;
+ font-size: 4rem;
+}
+
+#error_div {
+ position: absolute;
+ top: 30%;
+ left: 40%;
+ text-align:center;
+ color: white;
+}
diff --git a/assets/url.js b/assets/url.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e5a00eb21a7f3f45c772528e932188fb9b11f3b
--- /dev/null
+++ b/assets/url.js
@@ -0,0 +1,159 @@
+// ==================== ROUTING FUNCTIONS =============================
+//convert date from DD MON YYYY to YYYYMMDD
+function getDate() {
+ const dict = {
+ 'Jan': '01',
+ 'Feb': '02',
+ 'Mar': '03',
+ 'Apr': '04',
+ 'May': '05',
+ 'Jun': '06',
+ 'Jul': '07',
+ 'Aug': '08',
+ 'Sep': '09',
+ 'Oct': '10',
+ 'Nov': '11',
+ 'Dec': '12'
+ };
+ date = document.getElementById('date').value;
+ splitDate = date.split(' ');
+ month = dict[splitDate[1]];
+ return '&date=' + splitDate[2] + month + splitDate[0];
+}
+
+// LISTEN FOR MESSAGES FROM CMS
+window.addEventListener("message", (event) => {
+ // CREATE LIST OF ADDRESSES
+ const hosts = [
+ 'http://bscesdust02.bsc.es',
+ 'https://dust.aemet.es',
+ 'https://dust03.bsc.es'
+ ];
+ if (hosts.includes(event.origin)){
+ //console.log('DASH: Success!! ' + event.data);
+ window.history.pushState("Models", "Models", event.data);
+ return;
+ }else{
+// console.log('DASH: that is not the right address' + event.origin);
+ return;
+ }
+}, false);
+
+// NOW ASSESS HOW THE URL SHOULD BE OUTPUT
+function sendURL(url) {
+ window.history.pushState("Models", "Models", url);
+ parent.postMessage(url, '*');
+}
+
+//GET THE VARIABLE VALUE
+function getVar() {
+ curvar = document.querySelector('.Select-value-label').innerHTML;
+ curvar = curvar.split(' ').join('_')
+ return 'var=' + curvar.toLowerCase() + '&';
+}
+
+// GET MODELS CHECKED
+function getModels(){
+ const data = [...document.querySelectorAll('.custom-control-input:checked')].map(e => e.value);
+ data.forEach(function(item, index){
+ this[index] = 'model=' + item
+ }, data);
+ return data.join('&');
+}
+
+// CREATE OUTPUT URL FOR MODELS-APPLY
+$(document).ready(function () {
+ $(document).on('click', "#models-apply", function () {
+ curvar = getVar()
+ models = getModels()
+ outputDate = getDate();
+ url = "?" + curvar + models + outputDate;
+ sendURL(url);
+ })
+});
+
+// CREATE OUTPUT URL FOR TABS
+$(document).ready(function () {
+ $(document).on('click', ".tab--selected", function () {
+ tab = document.getElementsByClassName('tab--selected')[0].firstElementChild.innerHTML
+ tab = tab.toLowerCase()
+ url = "?tab=" + tab;
+ sendURL(url);
+ })
+});
+
+// CREATE OUTPUT URL FOR GROUP-1-TOGGLE(MODELS)
+$(document).ready(function () {
+ $(document).on('click', "#group-1-toggle", function () {
+ url = "?tab=forecast§ion=models";
+ sendURL(url);
+ })
+});
+
+// CREATE OUTPUT URL FOR GROUP-2-TOGGLE(PROBABILITY)
+$(document).ready(function () {
+ $(document).on('click', "#group-2-toggle", function () {
+ url = "?tab=forecast§ion=prob";
+ sendURL(url);
+ })
+});
+
+// CREATE OUTPUT URL FOR GROUP-3-TOGGLE(WAS)
+$(document).ready(function () {
+ $(document).on('click', "#group-3-toggle", function () {
+ url = "?tab=forecast§ion=was";
+ sendURL(url);
+ })
+});
+
+// CREATE WAS OUTPUT
+$(document).ready(function () {
+ $(document).on('click', "#was-apply", function () {
+ const checked = document.getElementById('was-dropdown');
+ var text = checked.querySelector('input[type="radio"]:checked').parentElement.innerText;
+ text = text.split(' ').join('_').toLowerCase();
+ url = "?tab=forecast§ion=was&country=" + text;
+ sendURL(url);
+ })
+});
+
+// CREATE OUTPUT URL FOR NRT-EVALUATION(VISUAL_COMPARISION)
+$(document).ready(function () {
+ $(document).on('click', "#nrt-evaluation", function () {
+ url = "?tab=evaluation§ion=visual_comparison";
+ sendURL(url);
+ })
+});
+
+// CREATE OUTPUT URL FOR SCORES-EVALUATION(STATISTICS)
+$(document).ready(function () {
+ $(document).on('click', "#scores-evaluation", function () {
+ url = "?tab=evaluation§ion=statistics";
+ sendURL(url);
+ })
+});
+
+// CREATE OUTPUT URL FOR SCORES-APPLY(STATISTICS)
+$(document).ready(function () {
+ $(document).on('click', "#scores-apply", function () {
+ url = "?tab=evaluation§ion=statistics";
+ sendURL(url);
+ })
+});
+
+// CREATE OUTPUT URL FOR RGB(EUMETSAT RGB)
+$(document).ready(function () {
+ $(document).on('click', "#rgb", function () {
+ url = "?tab=observations§ion=eumetsat-rgb";
+ sendURL(url);
+ })
+});
+
+// CREATE OUTPUT URL FOR VISIBILITY
+$(document).ready(function () {
+ $(document).on('click', "#visibility", function () {
+ url = "?tab=observations§ion=visibility";
+ sendURL(url);
+ })
+});
+
diff --git a/conf/was.json b/conf/was.json
deleted file mode 100644
index 64e9df938e013376ebd4f6730f6b8fa4ee3681cc..0000000000000000000000000000000000000000
--- a/conf/was.json
+++ /dev/null
@@ -1,253 +0,0 @@
-{
- "burkinafaso":
- {
- "name": "BURKINA FASO",
- "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/burkinafaso/shp/BFA_adm1.shp",
- "name_field": "NAME_1",
- "center": [12.30, -1.20],
- "zoom": 6.5,
- "model": "median",
- "var": "SCONC_DUST",
- "path": "/data/daily_dashboard/was/{was}/h5/{date}",
- "template": "{date}_{var}.h5",
- "colors": {
- "green" : "Normal",
- "gold" : "High",
- "darkorange": "Very High",
- "red" : "Extremely High"
- },
- "values": {
- "Boucle du Mouhoun" : [328, 411, 533],
- "Cascades" : [237, 304, 379],
- "Centre-Est" : [321, 387, 502],
- "Centre-Nord" : [359, 453, 582],
- "Centre-Ouest" : [308, 366, 456],
- "Centre-Sud" : [301, 366, 452],
- "Centre" : [301, 354, 460],
- "Est" : [391, 476, 619],
- "Haut-Bassins" : [287, 344, 448],
- "Nord" : [365, 447, 616],
- "Plateau-Central" : [310, 384, 465],
- "Sahel" : [473, 596, 766],
- "Sud-Ouest" : [258, 320, 392]
- },
- "title": "Barcelona Dust Regional Center - Burkina Faso WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
- },
- "senegal":
- {
- "name": "SENEGAL",
- "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/senegal/shp/SEN_adm1.shp",
- "name_field": "ADM1_FR",
- "center": [14.5, -14.4],
- "zoom": 6.5,
- "model": "median",
- "var": "SCONC_DUST",
- "path": "/data/daily_dashboard/was/{was}/h5/{date}",
- "template": "{date}_{var}.h5",
- "colors": {
- "green" : "Normal",
- "gold" : "High",
- "darkorange": "Very High",
- "red" : "Extremely High"
- },
- "values": {
- "Diourbel" : [365, 464, 655],
- "Fatick" : [314, 415, 549],
- "Kaffrine" : [323, 404, 543],
- "Kaolack" : [270, 343, 468],
- "Kedougou" : [235, 292, 421],
- "Kolda" : [250, 298, 410],
- "Louga" : [572, 715, 932],
- "Matam" : [566, 695, 935],
- "Saint Louis" : [657, 798, 1037],
- "Sedhiou" : [230, 285, 400],
- "Tambacounda" : [379, 460, 606],
- "Thies" : [322, 429, 616],
- "Ziguinchor" : [241, 300, 423]
- },
- "correspondence": {
- "Dakar": "Thies"
- },
- "title": "Barcelona Dust Regional Center - Senegal WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
- },
- "chad":
- {
- "name": "CHAD",
- "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/chad/shp/TCD_adm1.shp",
- "name_field": "admin1Na_1",
- "center": [15, 19],
- "zoom": 5.4,
- "model": "median",
- "var": "SCONC_DUST",
- "path": "/data/daily_dashboard/was/{was}/h5/{date}",
- "template": "{date}_{var}.h5",
- "colors": {
- "green" : "Normal",
- "gold" : "High",
- "darkorange": "Very High",
- "red" : "Extremely High"
- },
- "values": {
- "BATHA" : [1348, 1716, 2152],
- "BORKOU" : [2389, 2643, 3345],
- "CHARI-BAGUIRMI" : [552, 729, 889],
- "GUERA" : [367, 475, 711],
- "HADJER LAMIS" : [791, 969, 1213],
- "KANEM" : [1840, 2210, 2692],
- "LAC" : [1096, 1322, 1613],
- "LOGONE OCCIDENTAL": [277, 403, 574],
- "LOGONE ORIENTAL" : [238, 336, 518],
- "MANDOUL" : [185, 253, 424],
- "MAYO-KEBBI EST" : [439, 592, 790],
- "MAYO-KEBBI OUEST" : [396, 522, 705],
- "MOYEN-CHARI" : [174, 240, 398],
- "OUADDAI" : [347, 427, 590],
- "SALAMAT" : [225, 302, 501],
- "TANDJILE" : [299, 408, 615],
- "WADI FIRA" : [623, 757, 1096],
- "BARH-EL-GAZEL" : [1512, 1918, 2314],
- "ENNEDI EST" : [532, 689, 957],
- "SILA" : [223, 281, 440],
- "TIBESTI" : [1491, 1725, 2222],
- "ENNEDI OUEST" : [1309, 1697, 2263]
- },
- "correspondence": {
- "NDJAMENA": "HADJER LAMIS"
- },
- "title": "Barcelona Dust Regional Center - Chad WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
- },
- "mali":
- {
- "name": "MALI",
- "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/mali/shp/MLI_adm1.shp",
- "name_field": "ADM1_FR",
- "center": [17, -4],
- "zoom": 5.4,
- "model": "median",
- "var": "SCONC_DUST",
- "path": "/data/daily_dashboard/was/{was}/h5/{date}",
- "template": "{date}_{var}.h5",
- "colors": {
- "green" : "Normal",
- "gold" : "High",
- "darkorange": "Very High",
- "red" : "Extremely High"
- },
- "values": {
- "Gao" : [871, 1100, 1603],
- "Kayes" : [365, 453, 618],
- "Kidal" : [884, 1179, 1780],
- "Koulikoro" : [360, 466, 647],
- "Mopti" : [475, 586, 812],
- "Ségou" : [399, 500, 718],
- "Sikasso" : [292, 364, 474],
- "Tombouctou": [1003, 1279, 1676]
- },
- "correspondence": {
- "Bamako": "Koulikoro",
- "Menaka": "Kidal"
- },
- "title": "Barcelona Dust Regional Center - Mali WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
- },
- "niger":
- {
- "name": "NIGER",
- "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/niger/shp/NER_adm1.shp",
- "name_field": "NOMREG",
- "center": [17, 8],
- "zoom": 5.4,
- "model": "median",
- "var": "SCONC_DUST",
- "path": "/data/daily_dashboard/was/{was}/h5/{date}",
- "template": "{date}_{var}.h5",
- "colors": {
- "green" : "Normal",
- "gold" : "High",
- "darkorange": "Very High",
- "red" : "Extremely High"
- },
- "values": {
- "AGADEZ" : [1346, 1607, 2014],
- "DIFFA" : [1733, 2029, 2535],
- "DOSSO" : [474, 584, 752],
- "MARADI" : [592, 768, 976],
- "TAHOUA" : [751, 963, 1397],
- "TILLABERI" : [540, 671, 901],
- "ZINDER" : [1110, 1316, 1698]
- },
- "correspondence": {
- "NIAMEY": "TILLABERI"
- },
- "title": "Barcelona Dust Regional Center - Niger WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
- },
- "cabo_verde":
- {
- "name": "CAPE VERDE",
- "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/cabo_verde/shp/CPV_admX.shp",
- "name_field": "Ilha_1",
- "center": [15.5, -24],
- "zoom": 6,
- "model": "median",
- "var": "SCONC_DUST",
- "path": "/data/daily_dashboard/was/{was}/h5/{date}",
- "template": "{date}_{var}.h5",
- "colors": {
- "green" : "Normal",
- "gold" : "High",
- "darkorange": "Very High",
- "red" : "Extremely High"
- },
- "values": {
- "CaboVerdeBarlavento" : [137, 196, 345],
- "CaboVerdeSotavento" : [142, 203, 345]
- },
- "correspondence": {
- "BOAVISTA" : "CaboVerdeBarlavento",
- "BRAVA" : "CaboVerdeSotavento",
- "FOGO" : "CaboVerdeSotavento",
- "MAIO" : "CaboVerdeSotavento",
- "SAL" : "CaboVerdeBarlavento",
- "SANTIAGO" : "CaboVerdeSotavento",
- "SANTO ANTAO" : "CaboVerdeBarlavento",
- "SAO VICENTE" : "CaboVerdeBarlavento",
- "SANTA LUZIA" : "CaboVerdeBarlavento",
- "SAO NICOLAU" : "CaboVerdeBarlavento"
- },
- "exclude" : [ "CaboVerdeBarlavento", "CaboVerdeSotavento"],
- "title": "Barcelona Dust Regional Center - Cape Verde WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
- },
- "mauritania":
- {
- "name": "MAURITANIA",
- "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/mauritania/shp/MRT_adm1.shp",
- "name_field": "ADM1_EN",
- "center": [21, -10],
- "zoom": 5.4,
- "model": "median",
- "var": "SCONC_DUST",
- "path": "/data/daily_dashboard/was/{was}/h5/{date}",
- "template": "{date}_{var}.h5",
- "colors": {
- "green" : "Normal",
- "gold" : "High",
- "darkorange": "Very High",
- "red" : "Extremely High"
- },
- "values": {
- "Adrar" : [742, 921, 1213],
- "Assaba" : [579, 727, 970],
- "Brakna" : [770, 966, 1256],
- "Dakhlet-Nouadhibou": [760, 893, 1126],
- "Gorgol" : [584, 700, 947],
- "Guidimakha" : [366, 436, 612],
- "Hodh El Chargi" : [571, 707, 982],
- "Hodh El Gharbi" : [504, 651, 892],
- "Inchiri" : [800, 938, 1206],
- "Nouakchott" : [507, 661, 890],
- "Tagant" : [581, 766, 1048],
- "Trarza" : [945, 1160, 1406],
- "Tris-Zemmour" : [952, 1189, 1579]
- },
- "title": "Barcelona Dust Regional Center - Mauritania WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
- }
-}
diff --git a/conf/was.json b/conf/was.json
new file mode 120000
index 0000000000000000000000000000000000000000..63135090e3d4b56ff4a86280575f70b642bf6cb9
--- /dev/null
+++ b/conf/was.json
@@ -0,0 +1 @@
+was_dev_erose.json
\ No newline at end of file
diff --git a/conf/was_prod.json b/conf/was_prod.json
new file mode 100644
index 0000000000000000000000000000000000000000..64e9df938e013376ebd4f6730f6b8fa4ee3681cc
--- /dev/null
+++ b/conf/was_prod.json
@@ -0,0 +1,253 @@
+{
+ "burkinafaso":
+ {
+ "name": "BURKINA FASO",
+ "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/burkinafaso/shp/BFA_adm1.shp",
+ "name_field": "NAME_1",
+ "center": [12.30, -1.20],
+ "zoom": 6.5,
+ "model": "median",
+ "var": "SCONC_DUST",
+ "path": "/data/daily_dashboard/was/{was}/h5/{date}",
+ "template": "{date}_{var}.h5",
+ "colors": {
+ "green" : "Normal",
+ "gold" : "High",
+ "darkorange": "Very High",
+ "red" : "Extremely High"
+ },
+ "values": {
+ "Boucle du Mouhoun" : [328, 411, 533],
+ "Cascades" : [237, 304, 379],
+ "Centre-Est" : [321, 387, 502],
+ "Centre-Nord" : [359, 453, 582],
+ "Centre-Ouest" : [308, 366, 456],
+ "Centre-Sud" : [301, 366, 452],
+ "Centre" : [301, 354, 460],
+ "Est" : [391, 476, 619],
+ "Haut-Bassins" : [287, 344, 448],
+ "Nord" : [365, 447, 616],
+ "Plateau-Central" : [310, 384, 465],
+ "Sahel" : [473, 596, 766],
+ "Sud-Ouest" : [258, 320, 392]
+ },
+ "title": "Barcelona Dust Regional Center - Burkina Faso WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
+ },
+ "senegal":
+ {
+ "name": "SENEGAL",
+ "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/senegal/shp/SEN_adm1.shp",
+ "name_field": "ADM1_FR",
+ "center": [14.5, -14.4],
+ "zoom": 6.5,
+ "model": "median",
+ "var": "SCONC_DUST",
+ "path": "/data/daily_dashboard/was/{was}/h5/{date}",
+ "template": "{date}_{var}.h5",
+ "colors": {
+ "green" : "Normal",
+ "gold" : "High",
+ "darkorange": "Very High",
+ "red" : "Extremely High"
+ },
+ "values": {
+ "Diourbel" : [365, 464, 655],
+ "Fatick" : [314, 415, 549],
+ "Kaffrine" : [323, 404, 543],
+ "Kaolack" : [270, 343, 468],
+ "Kedougou" : [235, 292, 421],
+ "Kolda" : [250, 298, 410],
+ "Louga" : [572, 715, 932],
+ "Matam" : [566, 695, 935],
+ "Saint Louis" : [657, 798, 1037],
+ "Sedhiou" : [230, 285, 400],
+ "Tambacounda" : [379, 460, 606],
+ "Thies" : [322, 429, 616],
+ "Ziguinchor" : [241, 300, 423]
+ },
+ "correspondence": {
+ "Dakar": "Thies"
+ },
+ "title": "Barcelona Dust Regional Center - Senegal WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
+ },
+ "chad":
+ {
+ "name": "CHAD",
+ "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/chad/shp/TCD_adm1.shp",
+ "name_field": "admin1Na_1",
+ "center": [15, 19],
+ "zoom": 5.4,
+ "model": "median",
+ "var": "SCONC_DUST",
+ "path": "/data/daily_dashboard/was/{was}/h5/{date}",
+ "template": "{date}_{var}.h5",
+ "colors": {
+ "green" : "Normal",
+ "gold" : "High",
+ "darkorange": "Very High",
+ "red" : "Extremely High"
+ },
+ "values": {
+ "BATHA" : [1348, 1716, 2152],
+ "BORKOU" : [2389, 2643, 3345],
+ "CHARI-BAGUIRMI" : [552, 729, 889],
+ "GUERA" : [367, 475, 711],
+ "HADJER LAMIS" : [791, 969, 1213],
+ "KANEM" : [1840, 2210, 2692],
+ "LAC" : [1096, 1322, 1613],
+ "LOGONE OCCIDENTAL": [277, 403, 574],
+ "LOGONE ORIENTAL" : [238, 336, 518],
+ "MANDOUL" : [185, 253, 424],
+ "MAYO-KEBBI EST" : [439, 592, 790],
+ "MAYO-KEBBI OUEST" : [396, 522, 705],
+ "MOYEN-CHARI" : [174, 240, 398],
+ "OUADDAI" : [347, 427, 590],
+ "SALAMAT" : [225, 302, 501],
+ "TANDJILE" : [299, 408, 615],
+ "WADI FIRA" : [623, 757, 1096],
+ "BARH-EL-GAZEL" : [1512, 1918, 2314],
+ "ENNEDI EST" : [532, 689, 957],
+ "SILA" : [223, 281, 440],
+ "TIBESTI" : [1491, 1725, 2222],
+ "ENNEDI OUEST" : [1309, 1697, 2263]
+ },
+ "correspondence": {
+ "NDJAMENA": "HADJER LAMIS"
+ },
+ "title": "Barcelona Dust Regional Center - Chad WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
+ },
+ "mali":
+ {
+ "name": "MALI",
+ "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/mali/shp/MLI_adm1.shp",
+ "name_field": "ADM1_FR",
+ "center": [17, -4],
+ "zoom": 5.4,
+ "model": "median",
+ "var": "SCONC_DUST",
+ "path": "/data/daily_dashboard/was/{was}/h5/{date}",
+ "template": "{date}_{var}.h5",
+ "colors": {
+ "green" : "Normal",
+ "gold" : "High",
+ "darkorange": "Very High",
+ "red" : "Extremely High"
+ },
+ "values": {
+ "Gao" : [871, 1100, 1603],
+ "Kayes" : [365, 453, 618],
+ "Kidal" : [884, 1179, 1780],
+ "Koulikoro" : [360, 466, 647],
+ "Mopti" : [475, 586, 812],
+ "Ségou" : [399, 500, 718],
+ "Sikasso" : [292, 364, 474],
+ "Tombouctou": [1003, 1279, 1676]
+ },
+ "correspondence": {
+ "Bamako": "Koulikoro",
+ "Menaka": "Kidal"
+ },
+ "title": "Barcelona Dust Regional Center - Mali WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
+ },
+ "niger":
+ {
+ "name": "NIGER",
+ "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/niger/shp/NER_adm1.shp",
+ "name_field": "NOMREG",
+ "center": [17, 8],
+ "zoom": 5.4,
+ "model": "median",
+ "var": "SCONC_DUST",
+ "path": "/data/daily_dashboard/was/{was}/h5/{date}",
+ "template": "{date}_{var}.h5",
+ "colors": {
+ "green" : "Normal",
+ "gold" : "High",
+ "darkorange": "Very High",
+ "red" : "Extremely High"
+ },
+ "values": {
+ "AGADEZ" : [1346, 1607, 2014],
+ "DIFFA" : [1733, 2029, 2535],
+ "DOSSO" : [474, 584, 752],
+ "MARADI" : [592, 768, 976],
+ "TAHOUA" : [751, 963, 1397],
+ "TILLABERI" : [540, 671, 901],
+ "ZINDER" : [1110, 1316, 1698]
+ },
+ "correspondence": {
+ "NIAMEY": "TILLABERI"
+ },
+ "title": "Barcelona Dust Regional Center - Niger WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
+ },
+ "cabo_verde":
+ {
+ "name": "CAPE VERDE",
+ "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/cabo_verde/shp/CPV_admX.shp",
+ "name_field": "Ilha_1",
+ "center": [15.5, -24],
+ "zoom": 6,
+ "model": "median",
+ "var": "SCONC_DUST",
+ "path": "/data/daily_dashboard/was/{was}/h5/{date}",
+ "template": "{date}_{var}.h5",
+ "colors": {
+ "green" : "Normal",
+ "gold" : "High",
+ "darkorange": "Very High",
+ "red" : "Extremely High"
+ },
+ "values": {
+ "CaboVerdeBarlavento" : [137, 196, 345],
+ "CaboVerdeSotavento" : [142, 203, 345]
+ },
+ "correspondence": {
+ "BOAVISTA" : "CaboVerdeBarlavento",
+ "BRAVA" : "CaboVerdeSotavento",
+ "FOGO" : "CaboVerdeSotavento",
+ "MAIO" : "CaboVerdeSotavento",
+ "SAL" : "CaboVerdeBarlavento",
+ "SANTIAGO" : "CaboVerdeSotavento",
+ "SANTO ANTAO" : "CaboVerdeBarlavento",
+ "SAO VICENTE" : "CaboVerdeBarlavento",
+ "SANTA LUZIA" : "CaboVerdeBarlavento",
+ "SAO NICOLAU" : "CaboVerdeBarlavento"
+ },
+ "exclude" : [ "CaboVerdeBarlavento", "CaboVerdeSotavento"],
+ "title": "Barcelona Dust Regional Center - Cape Verde WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
+ },
+ "mauritania":
+ {
+ "name": "MAURITANIA",
+ "shp": "/home/fbeninca/interactive-forecast-viewer/conf/was/mauritania/shp/MRT_adm1.shp",
+ "name_field": "ADM1_EN",
+ "center": [21, -10],
+ "zoom": 5.4,
+ "model": "median",
+ "var": "SCONC_DUST",
+ "path": "/data/daily_dashboard/was/{was}/h5/{date}",
+ "template": "{date}_{var}.h5",
+ "colors": {
+ "green" : "Normal",
+ "gold" : "High",
+ "darkorange": "Very High",
+ "red" : "Extremely High"
+ },
+ "values": {
+ "Adrar" : [742, 921, 1213],
+ "Assaba" : [579, 727, 970],
+ "Brakna" : [770, 966, 1256],
+ "Dakhlet-Nouadhibou": [760, 893, 1126],
+ "Gorgol" : [584, 700, 947],
+ "Guidimakha" : [366, 436, 612],
+ "Hodh El Chargi" : [571, 707, 982],
+ "Hodh El Gharbi" : [504, 651, 892],
+ "Inchiri" : [800, 938, 1206],
+ "Nouakchott" : [507, 661, 890],
+ "Tagant" : [581, 766, 1048],
+ "Trarza" : [945, 1160, 1406],
+ "Tris-Zemmour" : [952, 1189, 1579]
+ },
+ "title": "Barcelona Dust Regional Center - Mauritania WAS.
Expected concentration of airborne dust.
Issued: %(rday)s %(rmonth)s %(ryear)s. Valid: %(sday)s %(smonth)s %(syear)s"
+ }
+}
diff --git a/dash_server.py b/dash_server.py
index 9ca676275a6de7b401861c4daa0bbda0c0fb4876..a760979c1282ee983a03d686803828c38011a6fc 100755
--- a/dash_server.py
+++ b/dash_server.py
@@ -12,45 +12,45 @@ from dash.dependencies import Input
from dash.dependencies import State
from dash.dependencies import ALL
from dash.dependencies import MATCH
+from dash.dependencies import ClientsideFunction
from dash.exceptions import PreventUpdate
import flask
from flask import g, make_response, request
from flask_caching import Cache
#from pyinstrument import Profiler
from pathlib import Path
+from datetime import datetime as dt
+from datetime import timedelta
+from urllib.parse import urlparse, parse_qs
from data_handler import DEFAULT_VAR
from data_handler import DEFAULT_MODEL
from data_handler import VARS
from data_handler import MODELS
from data_handler import DEBUG
+from data_handler import DATES
+from data_handler import cache, cache_timeout
+from data_handler import pathname
+from data_handler import ALIASES
+from data_handler import ROUTE_DEFAULTS
from tabs.forecast import tab_forecast
from tabs.forecast import sidebar_forecast
-from tabs.forecast_callbacks import register_callbacks as fcst_callbacks
from tabs.evaluation import tab_evaluation
from tabs.evaluation import sidebar_evaluation
-from tabs.evaluation_callbacks import register_callbacks as eval_callbacks
from tabs.observations import tab_observations
from tabs.observations import sidebar_observations
-from tabs.observations_callbacks import register_callbacks as obs_callbacks
from tabs.fullscreen import go_fullscreen
import uuid
import socket
-HOSTNAME = socket.gethostbyname_ex(socket.gethostname())[0]
-
-if HOSTNAME in ('bscesdust03.bsc.es', 'dust.hqads.aemet.es'):
- pathname = '/daily_dashboard/'
-else:
- pathname = '/dashboard/'
+from router import *
TIMEOUT = 10
fontawesome = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css'
leaflet = "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.css"
-# fontawesome = 'https://use.fontawesome.com/releases/v5.15.4/css/all.css'
srv = flask.Flask(__name__)
app = dash.Dash(__name__,
@@ -69,17 +69,7 @@ app.scripts.config.serve_locally = True
app.config.suppress_callback_exceptions = True
server = app.server
-cache_dir = "/dev/shm/{}".format(str(uuid.uuid1()))
-Path(cache_dir).mkdir(parents=True, exist_ok=True)
-
-cache_config = {
- "DEBUG": True,
- "CACHE_TYPE": "FileSystemCache",
- "CACHE_DIR": cache_dir,
-}
-
-cache = Cache(server, config=cache_config)
-cache_timeout = 86400
+cache.init_app(server)
try:
cache.clear()
@@ -87,7 +77,6 @@ except Exception as e:
print('CACHE CLEAR ERROR:', str(e))
pass
-
app.index_string = """
@@ -132,61 +121,15 @@ app.index_string = """
"""
if DEBUG: print('SERVER: start creating app layout')
-app.layout = html.Div(
- children=[
- html.Div(
- id='app-sidebar',
- children=sidebar_forecast(VARS, DEFAULT_VAR, MODELS, DEFAULT_MODEL),
- className='sidebar'
- ),
- dcc.Tabs(id='app-tabs', value='forecast-tab', children=[
- tab_forecast(),
- tab_evaluation(),
- tab_observations(),
- # go_fullscreen(pathname),
- ]),
- ],
- className="content",
-)
+#SET APP LAYOUT TO BE POPULATED
+app.layout = html.Div([dcc.Location(id="url", refresh=False)], id="content")
+
if DEBUG: print('SERVER: stop creating app layout')
-
-@app.callback(
- Output('app-sidebar', 'children'),
- [Input('app-tabs', 'value')],
-)
-def render_sidebar(tab):
- """ Function rendering requested tab """
- tabs = {
- 'forecast-tab' : [
- sidebar_forecast,
- (VARS, DEFAULT_VAR, MODELS, DEFAULT_MODEL)
- ],
- 'evaluation-tab' : [
- sidebar_evaluation,
- None
- ],
- 'observations-tab' : [
- sidebar_observations,
- None
- ],
- 'fullscreen-tab' : [
- 'Full'
- ]
- }
-
- if tabs[tab][0] is 'Full':
- return
- elif tabs[tab][1] is None:
- return tabs[tab][0]()
- return tabs[tab][0](*tabs[tab][1])
-
-
-fcst_callbacks(app, cache=cache, cache_timeout=cache_timeout)
-eval_callbacks(app, cache=cache, cache_timeout=cache_timeout)
-obs_callbacks(app, cache=cache, cache_timeout=cache_timeout)
-
+from tabs.forecast_callbacks import *
+from tabs.evaluation_callbacks import *
+from tabs.observations_callbacks import *
if __name__ == '__main__':
app.run_server(debug=True, # use_reloader=False, # processes=4, threaded=False,
diff --git a/data_handler.py b/data_handler.py
index da3b67af223fc8fc637fcd5d9a051ee54599eb8e..28479cf19eb3288fe36efd68c76639f196a34c5f 100644
--- a/data_handler.py
+++ b/data_handler.py
@@ -23,12 +23,41 @@ import requests
import calendar
import time
import os
+from datetime import datetime as dt
+from datetime import timedelta
from utils import concat_dataframes
from utils import retrieve_timeseries
from utils import retrieve_single_point
from utils import get_colorscale
+from pathlib import Path
+import flask
+from flask_caching import Cache
+import uuid
+import socket
+
+#SETUP BASE URL
+HOSTNAME = socket.gethostbyname_ex(socket.gethostname())[0]
+
+if HOSTNAME in ('bscesdust03.bsc.es', 'dust.hqads.aemet.es'):
+ pathname = '/daily_dashboard/'
+else:
+ pathname = '/dashboard/'
+
+#SETUP CACHE
+cache_dir = "/dev/shm/{}".format(str(uuid.uuid1()))
+Path(cache_dir).mkdir(parents=True, exist_ok=True)
+
+cache_config = {
+ "DEBUG": True,
+ "CACHE_TYPE": "FileSystemCache",
+ "CACHE_DIR": cache_dir,
+}
+
+cache = Cache(config=cache_config)
+cache_timeout = 86400
+
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
@@ -62,6 +91,35 @@ WAS = json.load(open(os.path.join(DIR_PATH, 'conf/was.json')))
PROB = json.load(open(os.path.join(DIR_PATH, 'conf/prob.json')))
DATES = json.load(open(os.path.join(DIR_PATH, 'conf/dates.json')))
+ALIASES= {
+ 'forecast': 'forecast-tab',
+ 'evaluation': 'evaluation-tab',
+ 'observations': 'observations-tab',
+ 'aod': 'OD550_DUST',
+ 'concentration': 'SCONC_DUST',
+ 'dry_deposition': 'DUST_DEPD',
+ 'wet_deposition': 'DUST_DEPW',
+ 'load': 'DUST_LOAD',
+ 'extinction':'DUST_EXT',
+ 'visual_comparison': 'nrt',
+ 'statistics': 'scores',
+ 'eumetsat-rgb': 'rgb',
+ 'burkina_faso': 'burkinafaso',
+ 'cape_verde': 'cabo_verde'
+ }
+
+ROUTE_DEFAULTS = {
+ 'tab':['forecast'],
+ 'var': ['OD550_DUST'],
+ 'model': ['median'],
+ 'for_option': ['models'],
+ 'eval_option': ['nrt'],
+ 'obs_option': ['rgb'],
+ 'country': ['burkinafaso'],
+ 'download': [None],
+ 'date': [DATES['end_date'] or (dt.now() - timedelta(days=1)).strftime("%Y%m%d")]
+ }
+
STATS = OrderedDict({ 'bias': 'BIAS', 'corr': 'CORR', 'rmse': 'RMSE', 'frge': 'FGE', 'totn': 'TOTAL CASES' })
STATS_CONF = OrderedDict(
{
diff --git a/router.py b/router.py
new file mode 100644
index 0000000000000000000000000000000000000000..2cce658a537f1122a7b04a2237aae03c623f8047
--- /dev/null
+++ b/router.py
@@ -0,0 +1,133 @@
+from urllib.parse import urlparse, parse_qs
+import dash
+import dash_bootstrap_components as dbc
+from dash import dcc
+from dash import html
+from dash.dependencies import Output
+from dash.dependencies import Input
+from dash.dependencies import State
+from dash.dependencies import ALL
+from data_handler import DEFAULT_VAR
+from data_handler import DEFAULT_MODEL
+from data_handler import VARS
+from data_handler import MODELS
+from data_handler import DEBUG
+from data_handler import DATES
+from data_handler import cache, cache_timeout
+from data_handler import pathname
+from data_handler import ALIASES
+from data_handler import ROUTE_DEFAULTS
+
+from tabs.forecast import tab_forecast
+from tabs.forecast import sidebar_forecast
+from tabs.evaluation import tab_evaluation
+from tabs.evaluation import sidebar_evaluation
+from tabs.observations import tab_observations
+from tabs.observations import sidebar_observations
+from tabs.fullscreen import go_fullscreen
+
+#-------------------- ADD ROUTING FUNCTIONS ----------------------
+def get_input_aliases(route_selections):
+ """ Change user inputs into internal variables"""
+ for key, value in route_selections.items():
+ #preserve the list of models by only changing the individual model name
+ if key == 'model':
+ route_selections[key] = ['median' if x=='multi-model' else x for x in route_selections[key]]
+ elif value[0] in ALIASES:
+ route_selections[key] = [ALIASES[value[0]]]
+ return route_selections
+
+def eval_section_query(queries):
+ """pull SECTION var from url query and assign value to appropriate option"""
+ if 'section' in queries:
+ if queries['tab']==['forecast-tab']:
+ queries['for_option'] = queries['section']
+ elif queries['tab']==['evaluation-tab']:
+ queries['eval_option'] = queries['section']
+ elif queries['tab']==['observations-tab']:
+ queries['obs_option'] = queries['section']
+ return queries
+
+def get_url_queries(url, ROUTE_DEFAULTS=ROUTE_DEFAULTS):
+ """ pull all of the keyword arguments from url and update ROUTE_DEFAULTS dict as necessary"""
+ parsed_url = urlparse(url, scheme='http', allow_fragments=False)
+ queries = parse_qs(parsed_url[4])
+ route_selections = ROUTE_DEFAULTS.copy()
+ route_selections.update(queries)
+ route_selections = get_input_aliases(route_selections)
+ route_selections = eval_section_query(route_selections)
+ return route_selections
+
+@dash.callback(
+ Output('app-sidebar', 'children'),
+ [Input('app-tabs', 'value')],
+ prevent_initial_call=True
+)
+def render_sidebar(tab='forecast-tab', route_selections=ROUTE_DEFAULTS):
+ """ Function rendering requested tab """
+ tabs = {
+ 'forecast-tab' : sidebar_forecast(VARS, route_selections['var'][0], MODELS, route_selections['model'], window=route_selections['for_option'][0], country=route_selections['country'][0]),
+ 'evaluation-tab' : sidebar_evaluation(route_selections['eval_option'][0]),
+ 'observations-tab' : sidebar_observations(route_selections['obs_option'][0]),
+ #'fullscreen-tab' : 'Full'
+ }
+ if tabs[tab][0] is 'Full':
+ return
+ return tabs[tab]
+
+def render404():
+ """Create a 404 page"""
+ #setup routes for links
+ forecast_link = pathname + "/?tab=forecast"
+ eval_link = pathname + "/?tab=evaluation"
+ obs_link = pathname + "/?tab=observations"
+
+ page = [html.Div(
+ className='background',
+ children=[
+ html.Div(
+ id='error_div',
+ children=[html.H2('404 Error', id='error_title'),
+ html.P("Sorry we can't find the page you were looking for."),
+ html.P("Here are some helpful links that might help:"),
+ dcc.Link("Forecast", id='forecast_link', href=forecast_link, className='error_links',target='_parent', refresh=True),
+ html.Br(),
+ html.Br(),
+ dcc.Link("Evaluation", id='evaluation_link', href=eval_link, className='error_links',target='_parent', refresh=True),
+ html.Br(),
+ html.Br(),
+ dcc.Link("Observations", id='observations_link', href=obs_link, className='error_links',target='_parent', refresh=True)
+ ],
+ )
+ ]
+ )
+ ]
+ return page
+
+@dash.callback(
+ Output("content", "children"),
+ Input("url", "href"),
+)
+def router(url):
+ """ Get url search queries and build layout for app"""
+ route_selections = get_url_queries(url)
+ print('===== route_selections', route_selections)
+ try:
+ children = [
+ html.Div(
+ id='app-sidebar',
+ children=render_sidebar(route_selections['tab'][0],
+ route_selections),
+ className='sidebar'
+ ),
+ dcc.Tabs(id='app-tabs', value=route_selections['tab'][0] , children=[
+ tab_forecast(window=route_selections['for_option'][0], end_date=route_selections['date'][0]),#'models', 'prob', 'was'
+ tab_evaluation(route_selections['eval_option'][0]), #nrt or scores
+ tab_observations(route_selections['obs_option'][0]),#rgb or visibility
+ # go_fullscreen(pathname),
+ ]),
+ ]
+ except: #This handles when user inputs incorrect URL params
+ children = render404()
+ return children
+
diff --git a/tabs/evaluation.py b/tabs/evaluation.py
index 3274af8497b0cf71910aa2a57a82524763bfe794..0d22557a162482ef5a8d0e0f551017b42c1445df 100644
--- a/tabs/evaluation.py
+++ b/tabs/evaluation.py
@@ -428,7 +428,16 @@ def tab_evaluation(window='nrt'):
)
-def sidebar_evaluation():
+def sidebar_evaluation(window='nrt'):
+ """ Build the evaluation sidebar"""
+
+ #Highlight the selected section
+ nrt_style = { 'fontWeight': 'bold' }
+ scores_style = { 'fontWeight': 'normal' }
+ if window == 'scores':
+ nrt_style={ 'fontWeight': 'normal' }
+ scores_style= { 'fontWeight': 'bold' }
+
return [html.Div([
html.Label("Variable"),
dcc.Dropdown(
@@ -447,57 +456,17 @@ def sidebar_evaluation():
html.Div([
dbc.Button("Visual comparison",
color="link",
- id='nrt-evaluation'
+ id='nrt-evaluation',
+ style = nrt_style
)],
className="sidebar-item",
),
html.Div([
dbc.Button("Statistics",
color="link",
- id='scores-evaluation'
+ id='scores-evaluation',
+ style = scores_style
)],
className="sidebar-item",
),
-# html.Div([
-# dbc.Row([
-# dbc.Col(
-# dbc.Button(
-# "",
-# id="info-button",
-# ),
-# width=12,
-# ),
-# ],
-# no_gutters=True,
-# ),
-# dbc.Row([
-# dbc.Col([
-# dbc.Collapse(
-# dbc.Card(dbc.CardBody(
-# [
-# html.Button('USER GUIDE',
-# id='btn-userguide-download',
-# n_clicks=0,
-# className='download-section',
-# ),
-# html.H6("Glossary"),
-# html.P("""
-# 1. Variables: Lorem ipsum dolor sit amet, consectetur adipiscing elit."""),
-# html.P("""
-# 2. Comparing: Lorem ipsum dolor sit , conssit amet, sit amet, consecte dolor elit."""),
-# html.P("""
-# 3. User Oriented Products: Lorem ipsum dolor sit amet, consectetur adipiscing elitLorem consectetur adipiscing elit"""),
-# ],
-# className="card-text",
-# )),
-# id="info-collapse",
-# is_open=False,
-# ),
-# ],
-# className="collapsible-cards",
-# ),
-# ],
-# className="sidebar-bottom",
-# )
-# ])
]
diff --git a/tabs/evaluation_callbacks.py b/tabs/evaluation_callbacks.py
index 2cda840be0596d79ed1a32f8887ff0ad896c5f7f..20238fab24f2c917d9e3ff2012683bf571a8b41d 100644
--- a/tabs/evaluation_callbacks.py
+++ b/tabs/evaluation_callbacks.py
@@ -38,6 +38,7 @@ import orjson
import os.path
from random import random
+from data_handler import cache, cache_timeout
SCORES = list(STATS.keys())
start_date = DATES['start_date']
@@ -54,718 +55,719 @@ def extend_l(l):
return res
-def register_callbacks(app, cache, cache_timeout):
- """ Registering callbacks """
-
- @app.callback(
- [Output('evaluation-tab', 'children'),
- Output('nrt-evaluation', 'style'),
- Output('scores-evaluation', 'style')],
- [Input('nrt-evaluation', 'n_clicks'),
- Input('scores-evaluation', 'n_clicks')],
- )
- def render_evaluation_tab(nrtbutton, scoresbutton):
- """ Function rendering requested tab """
- bold = { 'fontWeight': 'bold' }
- norm = { 'fontWeight': 'normal' }
- ctx = dash.callback_context
-
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
-
- if button_id == "nrt-evaluation" and nrtbutton:
- return tab_evaluation('nrt'), bold, norm
- elif button_id == "scores-evaluation" and scoresbutton:
- return tab_evaluation('scores'), norm, bold
-
- return dash.no_update, bold, norm
- #raise PreventUpdate
-
- @app.callback(
- [Output('obs-selection-dropdown','options'),
- Output('obs-selection-dropdown','placeholder')],
- [Input('obs-timescale-dropdown', 'value')],
- [State('obs-network-dropdown', 'value')],
- prevent_initial_call=True
- )
- def update_time_selection(timescale, network):
-
- if timescale is None:
- raise PreventUpdate
-
- if network == 'modis':
- start_date = '20180101'
- elif network == 'aeronet':
- start_date = '20120101'
-
- seasons = {
- '03': 'Spring',
- '06': 'Summer',
- '09': 'Autumn',
- '12': 'Winter'
- }
-
- if timescale == 'seasonal':
- ret = [{
- 'label' : '{} {}'.format(seasons[mon.strftime('%m')],
- mon.strftime('%Y')),
- 'value' : '{}-{}'.format(
- mon.strftime('%Y%m'), (mon + relativedelta(months=2)).strftime('%Y%m'))
- }
- for mon in pd.date_range(start_date, end_date, freq='Q')[::-1]][1:]
- placeholder = 'Select season'
- elif timescale == 'annual':
- ret = [{
- 'label': mon.strftime('%Y'),
- 'value': '{year}01-{year}12'.format(year=mon.strftime('%Y')),
- } for mon in
- pd.date_range(start_date, end_date, freq='A')[::-1]]
- placeholder = 'Select year'
- else: # timescale == 'monthly':
- ret = [{
- 'label': mon.strftime('%B %Y'),
- 'value': mon.strftime('%Y%m'),
- } for mon in
- pd.date_range(start_date, end_date, freq='M')[::-1]]
- placeholder = 'Select month'
-
- return ret, placeholder
-
- def _no_modis_data():
- """ Return the data needed to make MODIS table output NO DATA"""
- return [{'name': 'NO DATA', 'id': ''}], [], { 'display': 'block'}
-
- @app.callback(
- [Output('modis-scores-table', 'columns'),
- Output('modis-scores-table', 'data'),
- Output('modis-scores-table', 'style_table')],
- [Input('scores-apply', 'n_clicks')],
- [State('obs-models-dropdown', 'value'),
- State('obs-statistics-dropdown', 'value'),
- State('obs-network-dropdown', 'value'),
- State('obs-timescale-dropdown', 'value'),
- State('obs-selection-dropdown', 'value')],
- prevent_initial_call=True
- )
- def modis_scores_tables_retrieve(n, models, stat, network, timescale, selection):
- """ Read scores tables and show data """
-
- if not n or network != 'modis':
- return dash.no_update, dash.no_update, { 'display': 'none' }
-
- if isinstance(models, str):
- models = [models]
-
- if isinstance(stat, str):
- stat = [stat]
-
- stat = ['model'] + stat
-
- if DEBUG: print("###########", models, stat, network, timescale, selection, n)
- filedir = OBS[network]['path']
- filename = "{}_scores.h5".format(selection)
- tab_name = "total_{}".format(selection)
- filepath = os.path.join(filedir, "h5", filename)
- if not os.path.exists(filepath):
- return _no_modis_data()
- df = pd.read_hdf(filepath, tab_name)
- ret = df.loc[df['model'].isin(models), stat]
- ret['model'] = ret['model'].map({k:MODELS[k]['name'] for k in MODELS})
- if DEBUG: print('---', ret.columns)
- if DEBUG: print('---', ret.to_dict('records'))
- # Check if there is any data in returned table, return No Data if not
- if len(ret.to_dict('records')) < 1:
- return _no_modis_data()
- columns = [{'name': i in SCORES and
- STATS[i] or '', 'id': i} for
- i in stat]
- return columns, ret.replace('_', ' ', regex=True).to_dict('records'), { 'display': 'block' }
-
-
- @app.callback(
- [Output('scores-map-modalbody', 'figure'),
- Output('scores-map-modal', 'is_open'),
- Output('obs-models-dropdown-modal', 'value'),
- Output('obs-statistics-dropdown-modal', 'value')],
- [Input('scores-map-apply', 'n_clicks'),
- Input('obs-models-dropdown-modal', 'value'),
- Input('obs-statistics-dropdown-modal', 'value'),],
- [State('obs-network-dropdown', 'value'),
- State('obs-selection-dropdown', 'value'),
- State('obs-models-dropdown', 'value'),
- State('obs-statistics-dropdown', 'value')],
- )
- @cache.memoize(timeout=cache_timeout)
- def scores_maps_retrieve(n_clicks, model, score, network, selection, orig_model, orig_stats):
- """ Read scores tables and plot maps """
- from tools import get_scores_figure
- mb = MODEBAR_LAYOUT_TS
+#def register_callbacks(app, cache, cache_timeout):
+# """ Registering callbacks """
+
+@dash.callback(
+ [Output('evaluation-tab', 'children'),
+ Output('nrt-evaluation', 'style'),
+ Output('scores-evaluation', 'style')],
+ [Input('nrt-evaluation', 'n_clicks'),
+ Input('scores-evaluation', 'n_clicks')],
+ prevent_initial_call=True
+)
+def render_evaluation_tab(nrtbutton, scoresbutton):
+ """ Function rendering requested tab """
+ bold = { 'fontWeight': 'bold' }
+ norm = { 'fontWeight': 'normal' }
+ ctx = dash.callback_context
+
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+
+ if button_id == "nrt-evaluation" and nrtbutton:
+ return tab_evaluation('nrt'), bold, norm
+ elif button_id == "scores-evaluation" and scoresbutton:
+ return tab_evaluation('scores'), norm, bold
+
+ return dash.no_update, bold, norm
+ #raise PreventUpdate
+
+@dash.callback(
+ [Output('obs-selection-dropdown','options'),
+ Output('obs-selection-dropdown','placeholder')],
+ [Input('obs-timescale-dropdown', 'value')],
+ [State('obs-network-dropdown', 'value')],
+ prevent_initial_call=True
+)
+def update_time_selection(timescale, network):
+
+ if timescale is None:
+ raise PreventUpdate
- ctx = dash.callback_context
-
- if DEBUG: print(':::', n_clicks, model, score, network, selection)
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if button_id != "scores-map-apply":
- if model is not None and score is not None:
- if DEBUG: print('::: 1 :::')
- figure = get_scores_figure(network, model, score, selection)
- figure.update_layout(mb)
- return figure, True, model, score
-
- raise PreventUpdate
-
- if orig_model and orig_stats:
- if DEBUG: print(':::', orig_model, orig_stats, ':::')
- curr_model = [mod for mod in MODELS if mod in orig_model][0]
- curr_stat = [sc for sc in SCORES if sc in orig_stats][0]
- figure = get_scores_figure(network, curr_model, curr_stat, selection)
- figure.update_layout(mb)
- return figure, True, curr_model, curr_stat
- else:
- print('::: 2.5 :::')
+ if network == 'modis':
+ start_date = '20180101'
+ elif network == 'aeronet':
+ start_date = '20120101'
+
+ seasons = {
+ '03': 'Spring',
+ '06': 'Summer',
+ '09': 'Autumn',
+ '12': 'Winter'
+ }
+
+ if timescale == 'seasonal':
+ ret = [{
+ 'label' : '{} {}'.format(seasons[mon.strftime('%m')],
+ mon.strftime('%Y')),
+ 'value' : '{}-{}'.format(
+ mon.strftime('%Y%m'), (mon + relativedelta(months=2)).strftime('%Y%m'))
+ }
+ for mon in pd.date_range(start_date, end_date, freq='Q')[::-1]][1:]
+ placeholder = 'Select season'
+ elif timescale == 'annual':
+ ret = [{
+ 'label': mon.strftime('%Y'),
+ 'value': '{year}01-{year}12'.format(year=mon.strftime('%Y')),
+ } for mon in
+ pd.date_range(start_date, end_date, freq='A')[::-1]]
+ placeholder = 'Select year'
+ else: # timescale == 'monthly':
+ ret = [{
+ 'label': mon.strftime('%B %Y'),
+ 'value': mon.strftime('%Y%m'),
+ } for mon in
+ pd.date_range(start_date, end_date, freq='M')[::-1]]
+ placeholder = 'Select month'
+
+ return ret, placeholder
+
+def _no_modis_data():
+ """ Return the data needed to make MODIS table output NO DATA"""
+ return [{'name': 'NO DATA', 'id': ''}], [], { 'display': 'block'}
+
+@dash.callback(
+ [Output('modis-scores-table', 'columns'),
+ Output('modis-scores-table', 'data'),
+ Output('modis-scores-table', 'style_table')],
+ [Input('scores-apply', 'n_clicks')],
+ [State('obs-models-dropdown', 'value'),
+ State('obs-statistics-dropdown', 'value'),
+ State('obs-network-dropdown', 'value'),
+ State('obs-timescale-dropdown', 'value'),
+ State('obs-selection-dropdown', 'value')],
+ prevent_initial_call=True
+)
+def modis_scores_tables_retrieve(n, models, stat, network, timescale, selection):
+ """ Read scores tables and show data """
+
+ if not n or network != 'modis':
+ return dash.no_update, dash.no_update, { 'display': 'none' }
+
+ if isinstance(models, str):
+ models = [models]
+
+ if isinstance(stat, str):
+ stat = [stat]
+
+ stat = ['model'] + stat
+
+ if DEBUG: print("###########", models, stat, network, timescale, selection, n)
+ filedir = OBS[network]['path']
+ filename = "{}_scores.h5".format(selection)
+ tab_name = "total_{}".format(selection)
+ filepath = os.path.join(filedir, "h5", filename)
+ if not os.path.exists(filepath):
+ return _no_modis_data()
+ df = pd.read_hdf(filepath, tab_name)
+ ret = df.loc[df['model'].isin(models), stat]
+ ret['model'] = ret['model'].map({k:MODELS[k]['name'] for k in MODELS})
+ if DEBUG: print('---', ret.columns)
+ if DEBUG: print('---', ret.to_dict('records'))
+ # Check if there is any data in returned table, return No Data if not
+ if len(ret.to_dict('records')) < 1:
+ return _no_modis_data()
+ columns = [{'name': i in SCORES and
+ STATS[i] or '', 'id': i} for
+ i in stat]
+ return columns, ret.replace('_', ' ', regex=True).to_dict('records'), { 'display': 'block' }
+
+
+@dash.callback(
+ [Output('scores-map-modalbody', 'figure'),
+ Output('scores-map-modal', 'is_open'),
+ Output('obs-models-dropdown-modal', 'value'),
+ Output('obs-statistics-dropdown-modal', 'value')],
+ [Input('scores-map-apply', 'n_clicks'),
+ Input('obs-models-dropdown-modal', 'value'),
+ Input('obs-statistics-dropdown-modal', 'value'),],
+ [State('obs-network-dropdown', 'value'),
+ State('obs-selection-dropdown', 'value'),
+ State('obs-models-dropdown', 'value'),
+ State('obs-statistics-dropdown', 'value')],
+)
+@cache.memoize(timeout=cache_timeout)
+def scores_maps_retrieve(n_clicks, model, score, network, selection, orig_model, orig_stats):
+ """ Read scores tables and plot maps """
+ from tools import get_scores_figure
+ mb = MODEBAR_LAYOUT_TS
+
+ ctx = dash.callback_context
+
+ if DEBUG: print(':::', n_clicks, model, score, network, selection)
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if button_id != "scores-map-apply":
+ if model is not None and score is not None:
+ if DEBUG: print('::: 1 :::')
figure = get_scores_figure(network, model, score, selection)
figure.update_layout(mb)
return figure, True, model, score
+ raise PreventUpdate
+
+ if orig_model and orig_stats:
+ if DEBUG: print(':::', orig_model, orig_stats, ':::')
+ curr_model = [mod for mod in MODELS if mod in orig_model][0]
+ curr_stat = [sc for sc in SCORES if sc in orig_stats][0]
+ figure = get_scores_figure(network, curr_model, curr_stat, selection)
+ figure.update_layout(mb)
+ return figure, True, curr_model, curr_stat
+ else:
+ print('::: 2.5 :::')
+ figure = get_scores_figure(network, model, score, selection)
+ figure.update_layout(mb)
+ return figure, True, model, score
+
# if model is not None and score is not None:
# if DEBUG: print('::: 3 :::')
# figure = get_scores_figure(network, model, score, selection)
# figure.update_layout(mb)
# return figure, True, orig_model[0], orig_stats[0]
- return dash.no_update, False, dash.no_update, dash.no_update # PreventUpdate
-
-
- @app.callback(
- extend_l([
- [Output('aeronet-scores-table-{}'.format(score), 'columns'),
- Output('aeronet-scores-table-{}'.format(score), 'data'),
- Output('aeronet-scores-table-{}'.format(score), 'style_table'),
- Output('aeronet-scores-table-{}'.format(score), 'selected_cells'),
- Output('aeronet-scores-table-{}'.format(score), 'active_cell')]
- for score in SCORES]),
- [Input('scores-apply', 'n_clicks'),
- *[Input('aeronet-scores-table-{}'.format(score), 'active_cell')
- for score in SCORES]],
- [State('obs-models-dropdown', 'value'),
- State('obs-statistics-dropdown', 'value'),
- State('obs-network-dropdown', 'value'),
- State('obs-timescale-dropdown', 'value'),
- State('obs-selection-dropdown', 'value')] +
- extend_l([[State('aeronet-scores-table-{}'.format(score), 'columns'),
- State('aeronet-scores-table-{}'.format(score), 'data'),
- State('aeronet-scores-table-{}'.format(score), 'style_table')]
- for score in SCORES]),
- prevent_initial_call=True
- )
- def aeronet_scores_tables_retrieve(n, *args): # *activel_cells, models, stat, network, timescale, selection, *tables):
- """ Read scores tables and show data """
-
- ctx = dash.callback_context
- active_cells = list(args[:len(SCORES)])
- tables = list(args[-len(SCORES)*3:])
- if DEBUG: print("ACTIVES", active_cells)
-
- if DEBUG: print("###########", args[len(SCORES):-len(SCORES)*3])
- models, stat, network, timescale, selection = args[len(SCORES):-len(SCORES)*3]
-
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if DEBUG: print("BUTTON", button_id)
- if button_id not in ['scores-apply'] + ['aeronet-scores-table-{}'.format(score) for score in SCORES]:
- raise PreventUpdate
-
- if not n or network != 'aeronet':
- return extend_l([[dash.no_update, dash.no_update, { 'display': 'none' }, dash.no_update, dash.no_update] for score in SCORES])
-
- # ORDER is IMPORTANT
- areas = ['Europe', 'Mediterranean', 'MiddleEast', 'NAfrica', 'Total']
-
- if isinstance(models, str):
- models = [models]
-
- if isinstance(stat, str):
- stat = [stat]
-
- models = ['station'] + models
-
- if DEBUG: print("@@@@@@@@@@@", models, stat, network, timescale, selection, n, len(tables))
- filedir = OBS[network]['path']
-
- stat_idxs = [SCORES.index(st) for st in stat]
-
- # 5 outputs per score
- # 3 inputs (states) per score
- ret_tables = [None for _ in range(len(SCORES)*5)]
- for table_idx in range(int(len(tables)/3)):
- obj_idx = table_idx * 3
- ret_idx = table_idx * 5
- ret_tables[ret_idx] = tables[obj_idx]
- ret_tables[ret_idx+1] = tables[obj_idx+1]
- ret_tables[ret_idx+2] = tables[obj_idx+2]
- curr_columns = tables[obj_idx]
- curr_data = tables[obj_idx+1]
- if active_cells[table_idx] is not None and \
- active_cells[table_idx]['column_id'] == 'station':
- curr_active_cell = active_cells[table_idx]
- else:
- curr_active_cell = None
- if table_idx in stat_idxs:
- filename = "{}_{}.h5".format(selection, SCORES[table_idx])
- tab_name = "{}_{}".format(SCORES[table_idx], selection)
- filepath = os.path.join(filedir, "h5", filename)
- if not os.path.exists(filepath):
- if DEBUG: print ("TABLES 0", tables)
- #Build a no_data list of values to return
- no_data = []
- for i in range(len(SCORES)):
- no_data += [[], [], {'display': 'block'}, dash.no_update, None]
- # Add no data update for UI output only for 1 output
- no_data[0].append({'name':['NO DATA'], 'id':'station'})
- if DEBUG: print ("TABLES 1", tables)
- if DEBUG: print ("No Data Table ", no_data)
- return no_data
-
- df = pd.read_hdf(filepath, tab_name) # .replace('_', ' ', regex=True) # .round(decimals=2).fillna('-')
- # replace "tables" columns
- ret_tables[ret_idx] = [{'name': i in MODELS and
- [STATS[SCORES[table_idx]], MODELS[i]['name']] or
- [STATS[SCORES[table_idx]], ''], 'id': i} for
- i in models]
- # replace "tables" data
- if curr_active_cell is not None:
- if DEBUG: print("ACTIVE", curr_active_cell)
- curr_data = tables[obj_idx+1]
- if not curr_data:
- continue
- row_number = curr_active_cell['row']
- # 1st case:
- if DEBUG:
- print('CURRDATA', curr_data)
- print('ROWNUMBER', row_number)
- value = curr_data[row_number]['station']
- if value not in areas[:-1]:
- raise PreventUpdate
- val_idx = df.loc[df['station']==value].index[0]
- # check following data
- if row_number < len(curr_data)-1:
- foll_val = curr_data[row_number+1]['station']
- if foll_val in areas:
- foll_idx = df.loc[df['station']==foll_val].index[0]
- tables[obj_idx+1] = [table_row for table_row in curr_data if curr_data.index(table_row) < row_number] + df.iloc[val_idx:foll_idx-1][models].to_dict('rows') + [table_row for table_row in curr_data if curr_data.index(table_row) > row_number]
- else:
- foll_area = areas[areas.index(value)+1]
- if DEBUG:
- print("'''", curr_data)
- print("---", foll_area)
- foll_idx = curr_data.index([row for row in curr_data if row['station'] == foll_area][0])
- tables[obj_idx+1] = [table_row for table_row in curr_data if curr_data.index(table_row) <= row_number] + [table_row for table_row in curr_data if curr_data.index(table_row) >= foll_idx]
- ret_tables[ret_idx+1] = tables[obj_idx+1]
- ret_tables[ret_idx+2] = { 'display': 'block' }
- ret_tables[ret_idx+3] = []
- ret_tables[ret_idx+4] = None
- else:
- # ret_tables[ret_idx+1] = df.loc[df['station'].isin(areas), models].to_dict('records')
- ret_tables[ret_idx+1] = df[df['station'].isin(areas)].reindex(columns=models).to_dict('records')
- ret_tables[ret_idx+2] = { 'display': 'block' }
- ret_tables[ret_idx+3] = dash.no_update
- ret_tables[ret_idx+4] = dash.no_update
-
- else:
- ret_tables[ret_idx] = []
- ret_tables[ret_idx+1] = []
- ret_tables[ret_idx+2] = { 'display': 'block' }
- ret_tables[ret_idx+3] = dash.no_update
- ret_tables[ret_idx+4] = dash.no_update
-
-
- if DEBUG: print('LEN', len(ret_tables))
- if DEBUG: print ("TABLES RET", ret_tables)
- return ret_tables
-
-
- @app.callback(
- [Output('ts-eval-modis-modal', 'children'),
- Output('ts-eval-modis-modal', 'is_open')],
- [Input('ts-eval-modis-button', 'n_clicks')],
- [State('modis-clicked-coords', 'data'),
- State('eval-date-picker', 'date'),
- State('obs-dropdown', 'value'),
- State('obs-mod-dropdown', 'value')],
- prevent_initial_call=True
- )
- @cache.memoize(timeout=cache_timeout)
- def show_eval_modis_timeseries(nclicks, coords, date, obs, model):
- """ Retrieve MODIS evaluation timeseries according to station selected """
- from tools import get_timeseries
- if coords is None or nclicks == 0:
+ return dash.no_update, False, dash.no_update, dash.no_update # PreventUpdate
+
+
+@dash.callback(
+ extend_l([
+ [Output('aeronet-scores-table-{}'.format(score), 'columns'),
+ Output('aeronet-scores-table-{}'.format(score), 'data'),
+ Output('aeronet-scores-table-{}'.format(score), 'style_table'),
+ Output('aeronet-scores-table-{}'.format(score), 'selected_cells'),
+ Output('aeronet-scores-table-{}'.format(score), 'active_cell')]
+ for score in SCORES]),
+ [Input('scores-apply', 'n_clicks'),
+ *[Input('aeronet-scores-table-{}'.format(score), 'active_cell')
+ for score in SCORES]],
+ [State('obs-models-dropdown', 'value'),
+ State('obs-statistics-dropdown', 'value'),
+ State('obs-network-dropdown', 'value'),
+ State('obs-timescale-dropdown', 'value'),
+ State('obs-selection-dropdown', 'value')] +
+ extend_l([[State('aeronet-scores-table-{}'.format(score), 'columns'),
+ State('aeronet-scores-table-{}'.format(score), 'data'),
+ State('aeronet-scores-table-{}'.format(score), 'style_table')]
+ for score in SCORES]),
+ prevent_initial_call=True
+)
+def aeronet_scores_tables_retrieve(n, *args): # *activel_cells, models, stat, network, timescale, selection, *tables):
+ """ Read scores tables and show data """
+
+ ctx = dash.callback_context
+ active_cells = list(args[:len(SCORES)])
+ tables = list(args[-len(SCORES)*3:])
+ if DEBUG: print("ACTIVES", active_cells)
+
+ if DEBUG: print("###########", args[len(SCORES):-len(SCORES)*3])
+ models, stat, network, timescale, selection = args[len(SCORES):-len(SCORES)*3]
+
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if DEBUG: print("BUTTON", button_id)
+ if button_id not in ['scores-apply'] + ['aeronet-scores-table-{}'.format(score) for score in SCORES]:
raise PreventUpdate
- ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
- if DEBUG: print("CTXT", ctxt, type(ctxt))
- if not ctxt or ctxt is None or ctxt != 'ts-eval-modis-button': # or nclicks == 0:P:
- raise PreventUpdate
+ if not n or network != 'aeronet':
+ return extend_l([[dash.no_update, dash.no_update, { 'display': 'none' }, dash.no_update, dash.no_update] for score in SCORES])
- if DEBUG: print('TRIGGER', ctxt, type(ctxt))
- lat, lon, val = coords
- print(coords, date)
- models = [obs, model] # [model for model in MODELS]
- if DEBUG: print('SHOW MODIS EVAL TS"""""', coords)
- figure = get_timeseries(models, date, DEFAULT_VAR, lat, lon)
- mb = MODEBAR_LAYOUT_TS
- figure.update_layout(mb)
- return dbc.ModalBody(
- dcc.Graph(
- id='timeseries-eval-modal',
- figure=figure,
- config=MODEBAR_CONFIG_TS
- )
- ), True
-
- # return dash.no_update, False # PreventUpdate
-
- @app.callback(
- [#Output('alert-eval-popup2', 'is_open'),
- Output('modis-clicked-coords', 'data'),
- Output(dict(tag='modis-map', index='modis'), 'children')],
- [Input(dict(tag='modis-map', index='modis'), 'click_lat_lng')],
- [State(dict(tag='modis-map', index='modis'), 'children'),
- State('eval-date-picker', 'date'),
- State('obs-dropdown', 'value'),
- State('obs-mod-dropdown', 'value')],
- )
- def modis_popup(click_data, mapid, date, obs, model):
- from tools import get_single_point
- if DEBUG: print("CLICK:", str(click_data))
- if not click_data:
- raise PreventUpdate
- ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
- ctxt = orjson.loads(dash.callback_context.triggered[0]["prop_id"].split(".")[0])
- if DEBUG: print("CTXT", ctxt, type(ctxt))
- if not ctxt or ctxt is None or ctxt != {'index': 'modis', 'tag': 'modis-map'}:
- raise PreventUpdate
-
- lat, lon = click_data
- value = get_single_point(model, date, 0, DEFAULT_VAR, lat, lon)
- if DEBUG: print("VALUE", value)
-
- if not value:
- raise PreventUpdate
+ # ORDER is IMPORTANT
+ areas = ['Europe', 'Mediterranean', 'MiddleEast', 'NAfrica', 'Total']
- if DEBUG: print("VALUE", value)
- try:
- valid_dt = dt.strptime(date, '%Y%m%d') + timedelta(hours=12)
- except:
- valid_dt = dt.strptime(date, '%Y-%m-%d') + timedelta(hours=12)
-
- marker = dl.Popup(
- children=[
- html.Div([
- html.Span(html.P(
- '{:.2f}'.format(value*VARS[DEFAULT_VAR]['mul'])),
- className='popup-map-value',
- ),
- html.Span([
- html.B("Lat {:.2f}, Lon {:.2f}".format(lat, lon)), html.Br(),
- "DATE {:02d} {} {} {:02d}UTC".format(valid_dt.day, dt.strftime(valid_dt, '%b'), valid_dt.year, valid_dt.hour),
- html.Br(),
- html.Button("EXPLORE TIMESERIES",
- id='ts-eval-modis-button',
- n_clicks=0,
- className='popup-ts-button'
- )],
- className='popup-map-body',
- )],
- )
- ],
- id='modis-map-point',
- position=[lat, lon],
- autoClose=True,
- closeOnEscapeKey=True,
- closeOnClick=True,
- closeButton=True,
- className='popup-map-point'
- )
+ if isinstance(models, str):
+ models = [models]
- return [lat, lon, value], mapid + [marker]
+ if isinstance(stat, str):
+ stat = [stat]
+ models = ['station'] + models
- @app.callback(
- [#Output('alert-eval-popup', 'is_open'),
- Output('stations-clicked-coords', 'data'),
- Output(dict(tag='empty-map', index='None'), 'children')],
- [Input(dict(tag='empty-map', index='None'), 'click_lat_lng')],
- [State(dict(tag='empty-map', index='None'), 'children'),
- State('stations-dataframe', 'data')],
- prevent_initial_call=True
- )
- @cache.memoize(timeout=cache_timeout)
- def stations_popup(click_data, mapid, stations):
- if not click_data:
- raise PreventUpdate
+ if DEBUG: print("@@@@@@@@@@@", models, stat, network, timescale, selection, n, len(tables))
+ filedir = OBS[network]['path']
- if DEBUG: print("CLICK:", str(click_data))
-
- ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
- if DEBUG: print("CTXT", ctxt, type(ctxt))
- if not ctxt or ctxt is None:
- raise PreventUpdate
+ stat_idxs = [SCORES.index(st) for st in stat]
- trigger = orjson.loads(ctxt)
- if DEBUG: print('TRIGGER', trigger, type(trigger))
+ # 5 outputs per score
+ # 3 inputs (states) per score
+ ret_tables = [None for _ in range(len(SCORES)*5)]
+ for table_idx in range(int(len(tables)/3)):
+ obj_idx = table_idx * 3
+ ret_idx = table_idx * 5
+ ret_tables[ret_idx] = tables[obj_idx]
+ ret_tables[ret_idx+1] = tables[obj_idx+1]
+ ret_tables[ret_idx+2] = tables[obj_idx+2]
+ curr_columns = tables[obj_idx]
+ curr_data = tables[obj_idx+1]
+ if active_cells[table_idx] is not None and \
+ active_cells[table_idx]['column_id'] == 'station':
+ curr_active_cell = active_cells[table_idx]
+ else:
+ curr_active_cell = None
+ if table_idx in stat_idxs:
+ filename = "{}_{}.h5".format(selection, SCORES[table_idx])
+ tab_name = "{}_{}".format(SCORES[table_idx], selection)
+ filepath = os.path.join(filedir, "h5", filename)
+ if not os.path.exists(filepath):
+ if DEBUG: print ("TABLES 0", tables)
+ #Build a no_data list of values to return
+ no_data = []
+ for i in range(len(SCORES)):
+ no_data += [[], [], {'display': 'block'}, dash.no_update, None]
+ # Add no data update for UI output only for 1 output
+ no_data[0].append({'name':['NO DATA'], 'id':'station'})
+ if DEBUG: print ("TABLES 1", tables)
+ if DEBUG: print ("No Data Table ", no_data)
+ return no_data
+
+ df = pd.read_hdf(filepath, tab_name) # .replace('_', ' ', regex=True) # .round(decimals=2).fillna('-')
+ # replace "tables" columns
+ ret_tables[ret_idx] = [{'name': i in MODELS and
+ [STATS[SCORES[table_idx]], MODELS[i]['name']] or
+ [STATS[SCORES[table_idx]], ''], 'id': i} for
+ i in models]
+ # replace "tables" data
+ if curr_active_cell is not None:
+ if DEBUG: print("ACTIVE", curr_active_cell)
+ curr_data = tables[obj_idx+1]
+ if not curr_data:
+ continue
+ row_number = curr_active_cell['row']
+ # 1st case:
+ if DEBUG:
+ print('CURRDATA', curr_data)
+ print('ROWNUMBER', row_number)
+ value = curr_data[row_number]['station']
+ if value not in areas[:-1]:
+ raise PreventUpdate
+ val_idx = df.loc[df['station']==value].index[0]
+ # check following data
+ if row_number < len(curr_data)-1:
+ foll_val = curr_data[row_number+1]['station']
+ if foll_val in areas:
+ foll_idx = df.loc[df['station']==foll_val].index[0]
+ tables[obj_idx+1] = [table_row for table_row in curr_data if curr_data.index(table_row) < row_number] + df.iloc[val_idx:foll_idx-1][models].to_dict('rows') + [table_row for table_row in curr_data if curr_data.index(table_row) > row_number]
+ else:
+ foll_area = areas[areas.index(value)+1]
+ if DEBUG:
+ print("'''", curr_data)
+ print("---", foll_area)
+ foll_idx = curr_data.index([row for row in curr_data if row['station'] == foll_area][0])
+ tables[obj_idx+1] = [table_row for table_row in curr_data if curr_data.index(table_row) <= row_number] + [table_row for table_row in curr_data if curr_data.index(table_row) >= foll_idx]
+ ret_tables[ret_idx+1] = tables[obj_idx+1]
+ ret_tables[ret_idx+2] = { 'display': 'block' }
+ ret_tables[ret_idx+3] = []
+ ret_tables[ret_idx+4] = None
+ else:
+ # ret_tables[ret_idx+1] = df.loc[df['station'].isin(areas), models].to_dict('records')
+ ret_tables[ret_idx+1] = df[df['station'].isin(areas)].reindex(columns=models).to_dict('records')
+ ret_tables[ret_idx+2] = { 'display': 'block' }
+ ret_tables[ret_idx+3] = dash.no_update
+ ret_tables[ret_idx+4] = dash.no_update
- if trigger != {'index': 'None', 'tag': 'empty-map'}:
- raise PreventUpdate
+ else:
+ ret_tables[ret_idx] = []
+ ret_tables[ret_idx+1] = []
+ ret_tables[ret_idx+2] = { 'display': 'block' }
+ ret_tables[ret_idx+3] = dash.no_update
+ ret_tables[ret_idx+4] = dash.no_update
+
+
+ if DEBUG: print('LEN', len(ret_tables))
+ if DEBUG: print ("TABLES RET", ret_tables)
+ return ret_tables
+
+
+@dash.callback(
+ [Output('ts-eval-modis-modal', 'children'),
+ Output('ts-eval-modis-modal', 'is_open')],
+ [Input('ts-eval-modis-button', 'n_clicks')],
+ [State('modis-clicked-coords', 'data'),
+ State('eval-date-picker', 'date'),
+ State('obs-dropdown', 'value'),
+ State('obs-mod-dropdown', 'value')],
+ prevent_initial_call=True
+)
+@cache.memoize(timeout=cache_timeout)
+def show_eval_modis_timeseries(nclicks, coords, date, obs, model):
+ """ Retrieve MODIS evaluation timeseries according to station selected """
+ from tools import get_timeseries
+ if coords is None or nclicks == 0:
+ raise PreventUpdate
- df_stations = pd.DataFrame(stations)
- lat, lon = click_data
- curr_station = df_stations[(df_stations['lon'].round(2) == round(lon, 2)) & \
- (df_stations['lat'].round(2) == round(lat, 2))]['stations'].values
-
- if DEBUG: print("CURR_STATION", curr_station)
+ ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
+ if DEBUG: print("CTXT", ctxt, type(ctxt))
+ if not ctxt or ctxt is None or ctxt != 'ts-eval-modis-button': # or nclicks == 0:P:
+ raise PreventUpdate
- if not curr_station:
- raise PreventUpdate
+ if DEBUG: print('TRIGGER', ctxt, type(ctxt))
+ lat, lon, val = coords
+ print(coords, date)
+ models = [obs, model] # [model for model in MODELS]
+ if DEBUG: print('SHOW MODIS EVAL TS"""""', coords)
+ figure = get_timeseries(models, date, DEFAULT_VAR, lat, lon)
+ mb = MODEBAR_LAYOUT_TS
+ figure.update_layout(mb)
+ return dbc.ModalBody(
+ dcc.Graph(
+ id='timeseries-eval-modal',
+ figure=figure,
+ config=MODEBAR_CONFIG_TS
+ )
+ ), True
+
+ # return dash.no_update, False # PreventUpdate
+
+@dash.callback(
+ [#Output('alert-eval-popup2', 'is_open'),
+ Output('modis-clicked-coords', 'data'),
+ Output(dict(tag='modis-map', index='modis'), 'children')],
+ [Input(dict(tag='modis-map', index='modis'), 'click_lat_lng')],
+ [State(dict(tag='modis-map', index='modis'), 'children'),
+ State('eval-date-picker', 'date'),
+ State('obs-dropdown', 'value'),
+ State('obs-mod-dropdown', 'value')],
+)
+def modis_popup(click_data, mapid, date, obs, model):
+ from tools import get_single_point
+ if DEBUG: print("CLICK:", str(click_data))
+ if not click_data:
+ raise PreventUpdate
+ ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
+ ctxt = orjson.loads(dash.callback_context.triggered[0]["prop_id"].split(".")[0])
+ if DEBUG: print("CTXT", ctxt, type(ctxt))
+ if not ctxt or ctxt is None or ctxt != {'index': 'modis', 'tag': 'modis-map'}:
+ raise PreventUpdate
- curr_station = curr_station[0]
+ lat, lon = click_data
+ value = get_single_point(model, date, 0, DEFAULT_VAR, lat, lon)
+ if DEBUG: print("VALUE", value)
+
+ if not value:
+ raise PreventUpdate
- marker = dl.Popup(
- children=[
- html.Div([
+ if DEBUG: print("VALUE", value)
+ try:
+ valid_dt = dt.strptime(date, '%Y%m%d') + timedelta(hours=12)
+ except:
+ valid_dt = dt.strptime(date, '%Y-%m-%d') + timedelta(hours=12)
+
+ marker = dl.Popup(
+ children=[
+ html.Div([
+ html.Span(html.P(
+ '{:.2f}'.format(value*VARS[DEFAULT_VAR]['mul'])),
+ className='popup-map-value',
+ ),
html.Span([
html.B("Lat {:.2f}, Lon {:.2f}".format(lat, lon)), html.Br(),
- "STATION: ", html.B("{}".format(curr_station)), html.Br(),
+ "DATE {:02d} {} {} {:02d}UTC".format(valid_dt.day, dt.strftime(valid_dt, '%b'), valid_dt.year, valid_dt.hour),
+ html.Br(),
html.Button("EXPLORE TIMESERIES",
- id='ts-eval-button',
+ id='ts-eval-modis-button',
n_clicks=0,
className='popup-ts-button'
)],
- className='popup-map-eval-body',
- style={ 'z-index': '10001' }
+ className='popup-map-body',
)],
)
- ],
- id=dict(
- tag='empty-map-point',
- index=int(random()*100)
- ),
- position=[lat, lon],
- autoClose=True,
- closeOnEscapeKey=True,
- closeOnClick=True,
- closeButton=True,
- className='popup-map-point',
- )
+ ],
+ id='modis-map-point',
+ position=[lat, lon],
+ autoClose=True,
+ closeOnEscapeKey=True,
+ closeOnClick=True,
+ closeButton=True,
+ className='popup-map-point'
+ )
- curr_data = df_stations[(df_stations['lon'].round(2) == round(lon, 2)) & \
- (df_stations['lat'].round(2) == round(lat, 2))].to_dict()
+ return [lat, lon, value], mapid + [marker]
+
+
+@dash.callback(
+ [#Output('alert-eval-popup', 'is_open'),
+ Output('stations-clicked-coords', 'data'),
+ Output(dict(tag='empty-map', index='None'), 'children')],
+ [Input(dict(tag='empty-map', index='None'), 'click_lat_lng')],
+ [State(dict(tag='empty-map', index='None'), 'children'),
+ State('stations-dataframe', 'data')],
+ prevent_initial_call=True
+)
+@cache.memoize(timeout=cache_timeout)
+def stations_popup(click_data, mapid, stations):
+ if not click_data:
+ raise PreventUpdate
- print("MARKER", marker.to_plotly_json())
- last = mapid[-1]
- if last['type'] == 'Popup':
- mapid[-1] = marker.to_plotly_json()
- else:
- mapid.append(marker.to_plotly_json())
- print("LAST", type(mapid), type(last), last)
- for pos, log in enumerate(mapid):
- if log is not None:
- # mapid[pos]['id']['random'] =
- if DEBUG:
- print("********", type(log), log.keys())
- # print(log['type'])
- return curr_data, mapid
-
-
- @app.callback(
- [Output('ts-eval-modal', 'children'),
- Output('ts-eval-modal', 'is_open')],
- [Input('ts-eval-button', 'n_clicks')],
- [State('stations-clicked-coords', 'data'),
- State('eval-date-picker', 'start_date'),
- State('eval-date-picker', 'end_date'),
- State('obs-dropdown', 'value'),
- State('obs-mod-dropdown', 'value')],
- prevent_initial_call=True
- )
- @cache.memoize(timeout=cache_timeout)
- def show_eval_aeronet_timeseries(nclicks, cdata, start_date, end_date, obs, model):
- """ Retrieve AERONET evaluation timeseries according to station selected """
- from tools import get_eval_timeseries
- ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
- if DEBUG: print("CTXT", ctxt, type(ctxt))
- if not ctxt or ctxt is None:
- raise PreventUpdate
+ if DEBUG: print("CLICK:", str(click_data))
- if ctxt != 'ts-eval-button' or nclicks == 0:
- raise PreventUpdate
+ ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
+ if DEBUG: print("CTXT", ctxt, type(ctxt))
+ if not ctxt or ctxt is None:
+ raise PreventUpdate
- if not cdata:
- raise PreventUpdate
+ trigger = orjson.loads(ctxt)
+ if DEBUG: print('TRIGGER', trigger, type(trigger))
- print(start_date, end_date, obs, model, cdata)
- if DEBUG: print('EVAL AERONET CLICKDATA', cdata)
- cdata = pd.DataFrame(cdata)
- idx = int(cdata.index.values[0])
-# lon = cdata.lon.round(2).values[0]
-# lat = cdata.lat.round(2).values[0]
- stat = cdata.stations.values[0]
- if idx != 0:
- figure = get_eval_timeseries(obs, start_date, end_date, DEFAULT_VAR, idx, stat, model)
- mb = MODEBAR_LAYOUT_TS
- figure.update_layout(mb)
- if DEBUG: print('SHOW AERONET EVAL TS"""""', obs, idx, stat, start_date, end_date)
- return dbc.ModalBody(
- dcc.Graph(
- id='timeseries-eval-modal',
- figure=figure,
- config=MODEBAR_CONFIG_TS
- )
- ), True
+ if trigger != {'index': 'None', 'tag': 'empty-map'}:
+ raise PreventUpdate
+
+ df_stations = pd.DataFrame(stations)
+ lat, lon = click_data
+ curr_station = df_stations[(df_stations['lon'].round(2) == round(lon, 2)) & \
+ (df_stations['lat'].round(2) == round(lat, 2))]['stations'].values
+
+ if DEBUG: print("CURR_STATION", curr_station)
+ if not curr_station:
raise PreventUpdate
+ curr_station = curr_station[0]
+
+ marker = dl.Popup(
+ children=[
+ html.Div([
+ html.Span([
+ html.B("Lat {:.2f}, Lon {:.2f}".format(lat, lon)), html.Br(),
+ "STATION: ", html.B("{}".format(curr_station)), html.Br(),
+ html.Button("EXPLORE TIMESERIES",
+ id='ts-eval-button',
+ n_clicks=0,
+ className='popup-ts-button'
+ )],
+ className='popup-map-eval-body',
+ style={ 'z-index': '10001' }
+ )],
+ )
+ ],
+ id=dict(
+ tag='empty-map-point',
+ index=int(random()*100)
+ ),
+ position=[lat, lon],
+ autoClose=True,
+ closeOnEscapeKey=True,
+ closeOnClick=True,
+ closeButton=True,
+ className='popup-map-point',
+ )
- @app.callback(
- [Output('stations-dataframe', 'data'),
- Output('graph-eval-aeronet', 'children')],
- [Input('eval-apply', 'n_clicks')],
- [State('eval-date-picker', 'start_date'),
- State('eval-date-picker', 'end_date'),
- State('obs-dropdown', 'value')],
- prevent_initial_call=True)
- @cache.memoize(timeout=cache_timeout)
- def update_eval_aeronet(n_clicks, sdate, edate, obs):
- """ Update AERONET evaluation figure according to all parameters """
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if DEBUG: print("BUTTON", button_id)
- if button_id != 'eval-apply':
- raise PreventUpdate
- else:
- raise PreventUpdate
+ curr_data = df_stations[(df_stations['lon'].round(2) == round(lon, 2)) & \
+ (df_stations['lat'].round(2) == round(lat, 2))].to_dict()
+
+ print("MARKER", marker.to_plotly_json())
+ last = mapid[-1]
+ if last['type'] == 'Popup':
+ mapid[-1] = marker.to_plotly_json()
+ else:
+ mapid.append(marker.to_plotly_json())
+ print("LAST", type(mapid), type(last), last)
+ for pos, log in enumerate(mapid):
+ if log is not None:
+ # mapid[pos]['id']['random'] =
+ if DEBUG:
+ print("********", type(log), log.keys())
+ # print(log['type'])
+ return curr_data, mapid
+
+
+@dash.callback(
+ [Output('ts-eval-modal', 'children'),
+ Output('ts-eval-modal', 'is_open')],
+ [Input('ts-eval-button', 'n_clicks')],
+ [State('stations-clicked-coords', 'data'),
+ State('eval-date-picker', 'start_date'),
+ State('eval-date-picker', 'end_date'),
+ State('obs-dropdown', 'value'),
+ State('obs-mod-dropdown', 'value')],
+ prevent_initial_call=True
+)
+@cache.memoize(timeout=cache_timeout)
+def show_eval_aeronet_timeseries(nclicks, cdata, start_date, end_date, obs, model):
+ """ Retrieve AERONET evaluation timeseries according to station selected """
+ from tools import get_eval_timeseries
+ ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
+ if DEBUG: print("CTXT", ctxt, type(ctxt))
+ if not ctxt or ctxt is None:
+ raise PreventUpdate
+
+ if ctxt != 'ts-eval-button' or nclicks == 0:
+ raise PreventUpdate
+
+ if not cdata:
+ raise PreventUpdate
+
+ print(start_date, end_date, obs, model, cdata)
+ if DEBUG: print('EVAL AERONET CLICKDATA', cdata)
+ cdata = pd.DataFrame(cdata)
+ idx = int(cdata.index.values[0])
+# lon = cdata.lon.round(2).values[0]
+# lat = cdata.lat.round(2).values[0]
+ stat = cdata.stations.values[0]
+ if idx != 0:
+ figure = get_eval_timeseries(obs, start_date, end_date, DEFAULT_VAR, idx, stat, model)
+ mb = MODEBAR_LAYOUT_TS
+ figure.update_layout(mb)
+ if DEBUG: print('SHOW AERONET EVAL TS"""""', obs, idx, stat, start_date, end_date)
+ return dbc.ModalBody(
+ dcc.Graph(
+ id='timeseries-eval-modal',
+ figure=figure,
+ config=MODEBAR_CONFIG_TS
+ )
+ ), True
- if sdate is None or edate is None or obs != 'aeronet':
+ raise PreventUpdate
+
+
+@dash.callback(
+ [Output('stations-dataframe', 'data'),
+ Output('graph-eval-aeronet', 'children')],
+ [Input('eval-apply', 'n_clicks')],
+ [State('eval-date-picker', 'start_date'),
+ State('eval-date-picker', 'end_date'),
+ State('obs-dropdown', 'value')],
+ prevent_initial_call=True)
+@cache.memoize(timeout=cache_timeout)
+def update_eval_aeronet(n_clicks, sdate, edate, obs):
+ """ Update AERONET evaluation figure according to all parameters """
+ ctx = dash.callback_context
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if DEBUG: print("BUTTON", button_id)
+ if button_id != 'eval-apply':
raise PreventUpdate
+ else:
+ raise PreventUpdate
- from tools import get_figure
- from tools import get_obs1d
- if DEBUG: print('SERVER: calling figure from EVAL picker callback')
- if DEBUG: print('SERVER: SDATE', str(sdate))
+ if sdate is None or edate is None or obs != 'aeronet':
+ raise PreventUpdate
- sdate = sdate.split()[0]
+ from tools import get_figure
+ from tools import get_obs1d
+ if DEBUG: print('SERVER: calling figure from EVAL picker callback')
+ if DEBUG: print('SERVER: SDATE', str(sdate))
+
+ sdate = sdate.split()[0]
+ try:
+ sdate = dt.strptime(
+ sdate, "%Y-%m-%d").strftime("%Y%m%d")
+ except:
+ sdate = end_date
+ pass
+ if DEBUG: print('SERVER: callback start_date {}'.format(sdate))
+
+ if edate is not None:
+ edate = edate.split()[0]
try:
- sdate = dt.strptime(
- sdate, "%Y-%m-%d").strftime("%Y%m%d")
+ edate = dt.strptime(
+ edate, "%Y-%m-%d").strftime("%Y%m%d")
except:
- sdate = end_date
pass
- if DEBUG: print('SERVER: callback start_date {}'.format(sdate))
-
- if edate is not None:
- edate = edate.split()[0]
- try:
- edate = dt.strptime(
- edate, "%Y-%m-%d").strftime("%Y%m%d")
- except:
- pass
- if DEBUG: print('SERVER: callback end_date {}'.format(edate))
- else:
- edate = end_date
-
- stations, points_layer = get_obs1d(sdate, edate, obs, DEFAULT_VAR)
- fig = get_figure(model=None, var=DEFAULT_VAR, layer=points_layer)
- # if DEBUG: print("---", fig)
- return stations.to_dict(), fig
-
-
- @app.callback(
- [Output('graph-eval-modis-obs', 'children'),
- Output('graph-eval-modis-mod', 'children')],
- [Input('eval-apply', 'n_clicks')],
- [State('eval-date-picker', 'date'),
- State('obs-mod-dropdown', 'value'),
- State('obs-dropdown', 'value'),
- State('graph-eval-modis-mod', 'children')],
- prevent_initial_call=True)
- @cache.memoize(timeout=cache_timeout)
- def update_eval_modis(n_clicks, date, mod, obs, mod_div):
- """ Update MODIS evaluation figure according to all parameters """
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if DEBUG: print("BUTTON", button_id)
- if button_id != 'eval-apply':
- raise PreventUpdate
- else:
+ if DEBUG: print('SERVER: callback end_date {}'.format(edate))
+ else:
+ edate = end_date
+
+ stations, points_layer = get_obs1d(sdate, edate, obs, DEFAULT_VAR)
+ fig = get_figure(model=None, var=DEFAULT_VAR, layer=points_layer)
+ # if DEBUG: print("---", fig)
+ return stations.to_dict(), fig
+
+
+@dash.callback(
+ [Output('graph-eval-modis-obs', 'children'),
+ Output('graph-eval-modis-mod', 'children')],
+ [Input('eval-apply', 'n_clicks')],
+ [State('eval-date-picker', 'date'),
+ State('obs-mod-dropdown', 'value'),
+ State('obs-dropdown', 'value'),
+ State('graph-eval-modis-mod', 'children')],
+ prevent_initial_call=True)
+@cache.memoize(timeout=cache_timeout)
+def update_eval_modis(n_clicks, date, mod, obs, mod_div):
+ """ Update MODIS evaluation figure according to all parameters """
+ ctx = dash.callback_context
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if DEBUG: print("BUTTON", button_id)
+ if button_id != 'eval-apply':
raise PreventUpdate
+ else:
+ raise PreventUpdate
- if date is None or mod is None or obs != 'modis':
- raise PreventUpdate
+ if date is None or mod is None or obs != 'modis':
+ raise PreventUpdate
- from tools import get_figure
- if DEBUG: print('SERVER: calling figure from EVAL picker callback')
- if DEBUG: print(mod_div)
- mod_center = mod_div['props']['center']
- mod_zoom = mod_div['props']['zoom']
-
- if date is not None:
- date = date.split()[0]
- try:
- date = dt.strptime(
- date, "%Y-%m-%d").strftime("%Y%m%d")
- except:
- pass
- if DEBUG: print('SERVER: callback date {}'.format(date))
- else:
- date = end_date
+ from tools import get_figure
+ if DEBUG: print('SERVER: calling figure from EVAL picker callback')
+ if DEBUG: print(mod_div)
+ mod_center = mod_div['props']['center']
+ mod_zoom = mod_div['props']['zoom']
- if DEBUG: print("ZOOM", mod_zoom, "CENTER", mod_center)
- if MODELS[mod]['start'] == 12:
- tstep = 4
- else:
- tstep = 0
- fig_mod = get_figure(model=mod, var=DEFAULT_VAR, selected_date=date, tstep=tstep, hour=12, center=mod_center, zoom=mod_zoom)
- fig_obs = get_figure(model=obs, var=DEFAULT_VAR, selected_date=date, tstep=0, center=mod_center, zoom=mod_zoom)
-
- if DEBUG: print("MODIS", fig_obs)
- return fig_obs, fig_mod
-
-
- @app.callback(
- [Output('eval-date', 'children'),
- Output('eval-graph', 'children'),
- Output('obs-dropdown', 'value'),
- Output('obs-mod-dropdown-span', 'style')],
- [Input('obs-dropdown', 'value')],
- prevent_initial_call=True)
- @cache.memoize(timeout=cache_timeout)
- def update_eval(obs):
- """ Update evaluation figure according to all parameters """
- from tools import get_figure
- # from tools import get_obs1d
- if DEBUG: print('SERVER: calling figure from EVAL picker callback')
- # if DEBUG: print('SERVER: interval ' + str(n))
-
- if obs == 'aeronet':
-
- eval_date = [
- html.Label("Date range"),
- dcc.DatePickerRange(
- id='eval-date-picker',
- min_date_allowed=dt.strptime(start_date, "%Y%m%d"),
- max_date_allowed=dt.strptime(end_date, "%Y%m%d"),
- initial_visible_month=dt.strptime(end_date, "%Y%m%d"),
- display_format='DD MMM YYYY',
- # end_date=end_date,
- updatemode='bothdates',
- )]
-
- eval_graph = html.Div(
- get_figure(),
- id='graph-eval-aeronet',
- )
+ if date is not None:
+ date = date.split()[0]
+ try:
+ date = dt.strptime(
+ date, "%Y-%m-%d").strftime("%Y%m%d")
+ except:
+ pass
+ if DEBUG: print('SERVER: callback date {}'.format(date))
+ else:
+ date = end_date
+
+ if DEBUG: print("ZOOM", mod_zoom, "CENTER", mod_center)
+ if MODELS[mod]['start'] == 12:
+ tstep = 4
+ else:
+ tstep = 0
+ fig_mod = get_figure(model=mod, var=DEFAULT_VAR, selected_date=date, tstep=tstep, hour=12, center=mod_center, zoom=mod_zoom)
+ fig_obs = get_figure(model=obs, var=DEFAULT_VAR, selected_date=date, tstep=0, center=mod_center, zoom=mod_zoom)
+
+ if DEBUG: print("MODIS", fig_obs)
+ return fig_obs, fig_mod
+
+
+@dash.callback(
+ [Output('eval-date', 'children'),
+ Output('eval-graph', 'children'),
+ Output('obs-dropdown', 'value'),
+ Output('obs-mod-dropdown-span', 'style')],
+ [Input('obs-dropdown', 'value')],
+ prevent_initial_call=True)
+@cache.memoize(timeout=cache_timeout)
+def update_eval(obs):
+ """ Update evaluation figure according to all parameters """
+ from tools import get_figure
+ # from tools import get_obs1d
+ if DEBUG: print('SERVER: calling figure from EVAL picker callback')
+ # if DEBUG: print('SERVER: interval ' + str(n))
+
+ if obs == 'aeronet':
+
+ eval_date = [
+ html.Label("Date range"),
+ dcc.DatePickerRange(
+ id='eval-date-picker',
+ min_date_allowed=dt.strptime(start_date, "%Y%m%d"),
+ max_date_allowed=dt.strptime(end_date, "%Y%m%d"),
+ initial_visible_month=dt.strptime(end_date, "%Y%m%d"),
+ display_format='DD MMM YYYY',
+ # end_date=end_date,
+ updatemode='bothdates',
+ )]
+
+ eval_graph = html.Div(
+ get_figure(),
+ id='graph-eval-aeronet',
+ )
# eval_graph = [dbc.Spinner(
# get_graph(
# gid='graph-eval-aeronet',
@@ -773,58 +775,58 @@ def register_callbacks(app, cache, cache_timeout):
# )),
# ]
- style = { 'display': 'none' }
+ style = { 'display': 'none' }
- elif obs == 'modis':
+ elif obs == 'modis':
- eval_date = [
- html.Label("Date"),
- dcc.DatePickerSingle(
- id='eval-date-picker',
- min_date_allowed=dt.strptime(start_date, "%Y%m%d"),
- max_date_allowed=dt.strptime(end_date, "%Y%m%d"),
- initial_visible_month=dt.strptime(end_date, "%Y%m%d"),
- display_format='DD MMM YYYY',
- # date=end_date,
- # with_portal=True,
- )]
+ eval_date = [
+ html.Label("Date"),
+ dcc.DatePickerSingle(
+ id='eval-date-picker',
+ min_date_allowed=dt.strptime(start_date, "%Y%m%d"),
+ max_date_allowed=dt.strptime(end_date, "%Y%m%d"),
+ initial_visible_month=dt.strptime(end_date, "%Y%m%d"),
+ display_format='DD MMM YYYY',
+ # date=end_date,
+ # with_portal=True,
+ )]
# fig_mod = get_figure(model='median', var=DEFAULT_VAR,
# selected_date=end_date, tstep=4)
# fig_obs = get_figure(model=obs, var=DEFAULT_VAR,
# selected_date=end_date, tstep=0, zoom=fig_mod.zoom, center=fig_mod.center)
- fig_mod = get_figure()
- fig_obs = get_figure()
+ fig_mod = get_figure()
+ fig_obs = get_figure()
- graph_obs = html.Div([
- html.Div(
- fig_obs,
- id='graph-eval-modis-obs',
- ),
- html.Div(DISCLAIMER_OBS,
- className='disclaimer')
- ],
- )
- graph_mod = html.Div([
- html.Div(
- fig_mod,
- id='graph-eval-modis-mod',
- ),
- html.Div(DISCLAIMER_NO_FORECAST,
- className='disclaimer')
- ],
- )
- eval_graph = [dbc.Row([
- dbc.Col(graph_obs, width=6),
- dbc.Col(graph_mod, width=6)
- ],
- align='start',
- no_gutters=True
- )]
+ graph_obs = html.Div([
+ html.Div(
+ fig_obs,
+ id='graph-eval-modis-obs',
+ ),
+ html.Div(DISCLAIMER_OBS,
+ className='disclaimer')
+ ],
+ )
+ graph_mod = html.Div([
+ html.Div(
+ fig_mod,
+ id='graph-eval-modis-mod',
+ ),
+ html.Div(DISCLAIMER_NO_FORECAST,
+ className='disclaimer')
+ ],
+ )
+ eval_graph = [dbc.Row([
+ dbc.Col(graph_obs, width=6),
+ dbc.Col(graph_mod, width=6)
+ ],
+ align='start',
+ no_gutters=True
+ )]
- style = { 'display': 'table-cell' }
+ style = { 'display': 'table-cell' }
- else:
- raise PreventUpdate
+ else:
+ raise PreventUpdate
- return eval_date, eval_graph, obs, style
+ return eval_date, eval_graph, obs, style
diff --git a/tabs/forecast.py b/tabs/forecast.py
index 0ac932f7de6386b273fc17a813193f7f74b2b6bd..8baca6e2c48b9f57385ab91c7cc3287b12e27cee 100644
--- a/tabs/forecast.py
+++ b/tabs/forecast.py
@@ -125,47 +125,48 @@ layout_layers = html.Div([
),
)])
-time_slider = html.Div([
- html.Span(
- dcc.DatePickerSingle(
- id='model-date-picker',
- min_date_allowed=dt.strptime(start_date, "%Y%m%d"),
- max_date_allowed=dt.strptime(end_date, "%Y%m%d"),
- initial_visible_month=dt.strptime(end_date, "%Y%m%d"),
- display_format='DD MMM YYYY',
- date=end_date,
- with_portal=True,
+def time_slider(end_date=end_date):
+ return html.Div([
+ html.Span(
+ dcc.DatePickerSingle(
+ id='model-date-picker',
+ min_date_allowed=dt.strptime(start_date, "%Y%m%d"),
+ max_date_allowed=dt.strptime(end_date, "%Y%m%d"),
+ initial_visible_month=dt.strptime(end_date, "%Y%m%d"),
+ display_format='DD MMM YYYY',
+ date=end_date,
+ with_portal=True,
+ ),
+ className="timesliderline",
),
- className="timesliderline",
- ),
- html.Span(
- children=[
- html.Button(title='Play',
- id='btn-play', n_clicks=0,
- className='fa fa-play text-center'),
- html.Button(title='Stop',
- id='btn-stop', n_clicks=0,
- className='fa fa-pause text-center')],
- className="timesliderline anim-buttons",
- ),
- html.Span(
- dcc.Slider(
- id='slider-graph',
- min=0, max=72, step=3, value=0,
- marks={
- tstep: '{:d}'.format(tstep)
- # if tstep%2 == 0 else ''
- for tstep in range(0, 75, 3)
- },
- # updatemode='drag',
+ html.Span(
+ children=[
+ html.Button(title='Play',
+ id='btn-play', n_clicks=0,
+ className='fa fa-play text-center'),
+ html.Button(title='Stop',
+ id='btn-stop', n_clicks=0,
+ className='fa fa-pause text-center')],
+ className="timesliderline anim-buttons",
),
- className="timesliderline",
- ),
- html.Div(DISCLAIMER_MODELS,
- className='disclaimer'),
- ],
- className="timeslider"
-)
+ html.Span(
+ dcc.Slider(
+ id='slider-graph',
+ min=0, max=72, step=3, value=0,
+ marks={
+ tstep: '{:d}'.format(tstep)
+ # if tstep%2 == 0 else ''
+ for tstep in range(0, 75, 3)
+ },
+ # updatemode='drag',
+ ),
+ className="timesliderline",
+ ),
+ html.Div(DISCLAIMER_MODELS,
+ className='disclaimer'),
+ ],
+ className="timeslider"
+ )
prob_time_slider = html.Div([
@@ -229,8 +230,7 @@ was_time_slider = html.Div([
className="timeslider"
)
-
-def tab_forecast(window='models'):
+def tab_forecast(window='models', end_date=end_date):
models_children = [
html.Div(
id=dict(
@@ -284,7 +284,7 @@ def tab_forecast(window='models'):
disabled=True
)),
html.Div([
- time_slider,
+ time_slider(end_date),
layout_view,
# layout_layers,
],
@@ -354,7 +354,21 @@ def tab_forecast(window='models'):
)
-def sidebar_forecast(variables, default_var, models, default_model):
+def expand_dropdown(window):
+ """ Build a dictionary to return appropriate expanded sidebar dropdown"""
+
+ expand_dropdown = {
+ 'models':False,
+ 'prob':False,
+ 'was':False
+ }
+ expand_dropdown[window]=True
+ return expand_dropdown
+
+def sidebar_forecast(variables, default_var, models, default_model, window='models', country='burkinafaso'):
+ #get which sidebar dropdown should be expanded for complex url search
+ dropdown = expand_dropdown(window)
+
return [
html.Div([
html.Label("Variable"),
@@ -381,14 +395,15 @@ def sidebar_forecast(variables, default_var, models, default_model):
)),
dbc.Collapse(
id='collapse-1',
- is_open=True,
+ is_open=dropdown['models'],
children=[
dbc.CardBody([
dbc.Checklist(
id='model-dropdown',
options=[{'label': models[model]['name'],
'value': model} for model in models],
- value=[default_model,],
+ #value=[default_model,],
+ value=default_model,
className="sidebar-dropdown",
),
html.Span([
@@ -410,6 +425,7 @@ def sidebar_forecast(variables, default_var, models, default_model):
)),
dbc.Collapse(
id='collapse-2',
+ is_open=dropdown['prob'],
children=[
dbc.CardBody([
dcc.RadioItems(
@@ -437,13 +453,14 @@ def sidebar_forecast(variables, default_var, models, default_model):
)),
dbc.Collapse(
id='collapse-3',
+ is_open=dropdown['was'],
children=[
dbc.CardBody([
dcc.RadioItems(
id='was-dropdown',
options=[{'label': WAS[was]['name'],
'value': was} for was in WAS],
- value='burkinafaso',
+ value=country,
className="sidebar-dropdown"
),
html.Div(
diff --git a/tabs/forecast_callbacks.py b/tabs/forecast_callbacks.py
index 78d5273f980fc42e93dbcf97523e8c761ca08865..690d52b5acd5cb5ebf5921f3381345861e65ab76 100644
--- a/tabs/forecast_callbacks.py
+++ b/tabs/forecast_callbacks.py
@@ -46,109 +46,108 @@ from random import random
start_date = DATES['start_date']
end_date = DATES['end_date'] or (dt.now() - timedelta(days=1)).strftime("%Y%m%d")
-
-def register_callbacks(app, cache, cache_timeout):
- """ Registering callbacks """
-
-
- @app.callback(
- [Output('collapse-1', 'is_open'),
- Output('collapse-2', 'is_open'),
- Output('collapse-3', 'is_open'),
- Output('group-2-toggle', 'disabled'),
- Output('group-3-toggle', 'disabled'),
- Output('variable-dropdown-forecast','value')],
- [Input('group-1-toggle', 'n_clicks'),
- Input('group-2-toggle', 'n_clicks'),
- Input('group-3-toggle', 'n_clicks'),
- Input('variable-dropdown-forecast', 'value')],
- [State('collapse-1', 'is_open'),
- State('collapse-2', 'is_open'),
- State('collapse-3', 'is_open'),],
- prevent_initial_call=True
- )
- def render_forecast_tab(modbutton, probbutton, wasbutton, var, modopen, probopen, wasopen):
- """ Function rendering requested tab """
- ctx = dash.callback_context
-
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
-
- if button_id == "group-1-toggle" and modbutton:
- if modopen is True:
- return not modopen, False, False, dash.no_update, dash.no_update, dash.no_update
+from data_handler import cache, cache_timeout
+#def register_callbacks(app, cache, cache_timeout):
+# """ Registering callbacks """
+
+@dash.callback(
+ [Output('collapse-1', 'is_open'),
+ Output('collapse-2', 'is_open'),
+ Output('collapse-3', 'is_open'),
+ Output('group-2-toggle', 'disabled'),
+ Output('group-3-toggle', 'disabled'),
+ Output('variable-dropdown-forecast','value')],
+ [Input('group-1-toggle', 'n_clicks'),
+ Input('group-2-toggle', 'n_clicks'),
+ Input('group-3-toggle', 'n_clicks'),
+ Input('variable-dropdown-forecast', 'value')],
+ [State('collapse-1', 'is_open'),
+ State('collapse-2', 'is_open'),
+ State('collapse-3', 'is_open'),],
+ prevent_initial_call=True
+)
+def render_forecast_tab(modbutton, probbutton, wasbutton, var, modopen, probopen, wasopen):
+ """ Function rendering requested tab """
+ ctx = dash.callback_context
+
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+
+ if button_id == "group-1-toggle" and modbutton:
+ if modopen is True:
return not modopen, False, False, dash.no_update, dash.no_update, dash.no_update
- elif button_id == "group-2-toggle" and probbutton:
- if probopen is True:
- return False, not probopen, False, dash.no_update, dash.no_update, dash.no_update
+ return not modopen, False, False, dash.no_update, dash.no_update, dash.no_update
+ elif button_id == "group-2-toggle" and probbutton:
+ if probopen is True:
return False, not probopen, False, dash.no_update, dash.no_update, dash.no_update
- elif button_id == "group-3-toggle" and wasbutton:
- if wasopen is True:
- return False, False, not wasopen, dash.no_update, dash.no_update, 'SCONC_DUST'
+ return False, not probopen, False, dash.no_update, dash.no_update, dash.no_update
+ elif button_id == "group-3-toggle" and wasbutton:
+ if wasopen is True:
return False, False, not wasopen, dash.no_update, dash.no_update, 'SCONC_DUST'
+ return False, False, not wasopen, dash.no_update, dash.no_update, 'SCONC_DUST'
- if var == 'SCONC_DUST':
- # raise PreventUpdate
- return modopen, probopen, wasopen, False, False, dash.no_update
- elif var == 'OD550_DUST':
- # only models and prob can be opened
- if wasopen:
- return True, False, False, False, True, dash.no_update
- return modopen, probopen, wasopen, False, True, dash.no_update
- else:
- if modopen:
- return True, False, False, True, True, dash.no_update
+ if var == 'SCONC_DUST':
+ # raise PreventUpdate
+ return modopen, probopen, wasopen, False, False, dash.no_update
+ elif var == 'OD550_DUST':
+ # only models and prob can be opened
+ if wasopen:
+ return True, False, False, False, True, dash.no_update
+ return modopen, probopen, wasopen, False, True, dash.no_update
+ else:
+ if modopen:
return True, False, False, True, True, dash.no_update
-
- raise PreventUpdate
-
- @app.callback(
- [Output('prob-dropdown', 'options'),
- Output('prob-dropdown', 'value')],
- [Input('variable-dropdown-forecast', 'value')],
- prevent_initial_call=False
+ return True, False, False, True, True, dash.no_update
+
+ raise PreventUpdate
+
+@dash.callback(
+ [Output('prob-dropdown', 'options'),
+ Output('prob-dropdown', 'value')],
+ [Input('variable-dropdown-forecast', 'value')],
+ prevent_initial_call=False
+)
+def update_prob_dropdown(var):
+ """ Update Prob maps dropdown """
+ if var in ['OD550_DUST','SCONC_DUST']:
+ opt_list = PROB[var]['prob_thresh']
+ units = PROB[var]['units']
+ return [
+ {'label': '> {} {}'.format(prob, units),
+ 'value': 'prob_{}'.format(prob)}
+ for prob in opt_list], 'prob_{}'.format(opt_list[0])
+
+ raise PreventUpdate
+
+
+@dash.callback(
+ [ # Output('alert-models-auto', 'is_open'),
+ Output('model-dropdown', 'options'),
+ Output('model-dropdown', 'value'),
+ Output('btn-anim-download', 'style')],
+ [Input('variable-dropdown-forecast', 'value')],
+ [Input('model-dropdown', 'value'),],
+ prevent_initial_call=True
)
- def update_prob_dropdown(var):
- """ Update Prob maps dropdown """
- if var in ['OD550_DUST','SCONC_DUST']:
- opt_list = PROB[var]['prob_thresh']
- units = PROB[var]['units']
- return [
- {'label': '> {} {}'.format(prob, units),
- 'value': 'prob_{}'.format(prob)}
- for prob in opt_list], 'prob_{}'.format(opt_list[0])
-
- raise PreventUpdate
-
-
- @app.callback(
- [ # Output('alert-models-auto', 'is_open'),
- Output('model-dropdown', 'options'),
- Output('model-dropdown', 'value'),
- Output('btn-anim-download', 'style')],
- [Input('variable-dropdown-forecast', 'value')],
- [Input('model-dropdown', 'value'),],
- prevent_initial_call=True
- )
- def update_models_dropdown(variable, checked):
-
- btn_style = { 'display' : 'block' }
- models = VARS[variable]['models']
- if models == 'all':
- models = list(MODELS.keys())
- else:
- models = eval(models)
-
- options = [{
- 'label': MODELS[model]['name'],
- 'value': model,
- 'disabled': model not in models,
- } for model in MODELS]
-
- checked = [c for c in models if c in checked or len(models)==1]
- if len(checked) > 1:
- btn_style = { 'display' : 'none' }
- if DEBUG: print('MODELS', models, 'OPTS', type(options), options)
+def update_models_dropdown(variable, checked):
+
+ btn_style = { 'display' : 'block' }
+ models = VARS[variable]['models']
+ if models == 'all':
+ models = list(MODELS.keys())
+ else:
+ models = eval(models)
+
+ options = [{
+ 'label': MODELS[model]['name'],
+ 'value': model,
+ 'disabled': model not in models,
+ } for model in MODELS]
+
+ checked = [c for c in models if c in checked or len(models)==1]
+ if len(checked) > 1:
+ btn_style = { 'display' : 'none' }
+ if DEBUG: print('MODELS', models, 'OPTS', type(options), options)
# if len(checked) >= 16:
# options = [{
# 'label': MODELS[model]['name'],
@@ -158,75 +157,74 @@ def register_callbacks(app, cache, cache_timeout):
#
# return True, options, checked
- return options, checked, btn_style
-
- @app.callback(
- Output('caret1', 'style'),
- [Input('group-1-toggle', 'n_clicks')]
- )
- def rotate_models_caret(n_clicks):
- rotate_caret = {
- 'top':'.05rem',
- 'transform': 'rotate(180deg)',
- '-ms-transform': 'rotate(180deg)',
- '-webkit-transform': 'rotate(180deg)'
- }
- if n_clicks %2==1:
- return rotate_caret
-
- @app.callback(
- Output('caret2', 'style'),
- [Input('group-2-toggle', 'n_clicks')],
- )
- def rotate_prob_caret(n_clicks):
- rotate_caret = {
- 'transform': 'rotate(0deg)',
- '-ms-transform': 'rotate(0deg)',
- '-webkit-transform': 'rotate(0deg)'
- }
- if n_clicks %2==1:
- return rotate_caret
-
- @app.callback(
- Output('caret3', 'style'),
- [Input('group-3-toggle', 'n_clicks')]
- )
- def rotate_was_caret(n_clicks):
- rotate_caret = {
- 'transform': 'rotate(0deg)',
- '-ms-transform': 'rotate(0deg)',
- '-webkit-transform': 'rotate(0deg)'
- }
- if n_clicks %2==1:
- return rotate_caret
-
-
- @app.callback(
- [Output('info-collapse', 'is_open'),
- Output('download-collapse', 'is_open')],
- [Input('info-button', 'n_clicks'),
- Input('download-button', 'n_clicks')],
- [State('info-collapse', 'is_open'),
- State('download-collapse', 'is_open')],
- prevent_initial_call=True
- )
- def sidebar_bottom(n_info, n_download, open_info, open_download):
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
-
- if button_id == 'info-button':
- if DEBUG: print('clicked INFO', not open_info, False)
- return not open_info, False
- elif button_id == 'download-button':
- if DEBUG: print('clicked DOWN', False, not open_download)
- return False, not open_download
-
- if DEBUG: print('clicked NONE', False, False)
- raise PreventUdate
-
-
-# @app.callback(
+ return options, checked, btn_style
+
+@dash.callback(
+ Output('caret1', 'style'),
+ [Input('collapse-1', 'is_open')],
+)
+def rotate_models_caret(collapse_open):
+ rotate_caret = {
+ 'top':'.05rem',
+ 'transform': 'rotate(180deg)',
+ '-ms-transform': 'rotate(180deg)',
+ '-webkit-transform': 'rotate(180deg)'
+ }
+ if not collapse_open:
+ return rotate_caret
+
+@dash.callback(
+ Output('caret2', 'style'),
+ [Input('collapse-2', 'is_open')],
+)
+def rotate_prob_caret(collapse_open):
+ rotate_caret = {
+ 'transform': 'rotate(0deg)',
+ '-ms-transform': 'rotate(0deg)',
+ '-webkit-transform': 'rotate(0deg)'
+ }
+ if collapse_open:
+ return rotate_caret
+
+@dash.callback(
+ Output('caret3', 'style'),
+ [Input('collapse-3', 'is_open')],
+)
+def rotate_was_caret(collapse_open):
+ rotate_caret = {
+ 'transform': 'rotate(0deg)',
+ '-ms-transform': 'rotate(0deg)',
+ '-webkit-transform': 'rotate(0deg)'
+ }
+ if collapse_open:
+ return rotate_caret
+
+@dash.callback(
+ [Output('info-collapse', 'is_open'),
+ Output('download-collapse', 'is_open')],
+ [Input('info-button', 'n_clicks'),
+ Input('download-button', 'n_clicks')],
+ [State('info-collapse', 'is_open'),
+ State('download-collapse', 'is_open')],
+ prevent_initial_call=True
+)
+def sidebar_bottom(n_info, n_download, open_info, open_download):
+ ctx = dash.callback_context
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+
+ if button_id == 'info-button':
+ if DEBUG: print('clicked INFO', not open_info, False)
+ return not open_info, False
+ elif button_id == 'download-button':
+ if DEBUG: print('clicked DOWN', False, not open_download)
+ return False, not open_download
+
+ if DEBUG: print('clicked NONE', False, False)
+ raise PreventUdate
+
+
+# @dash.callback(
# [Output('login-modal', 'is_open'),
# Output('alert-login-error', 'is_open'),
# Output('alert-login-wrong', 'is_open'),
@@ -303,40 +301,40 @@ def register_callbacks(app, cache, cache_timeout):
#
# raise PreventUpdate
- @app.callback(
- Output('btn-anim-download', 'href'),
+@dash.callback(
+ Output('btn-anim-download', 'href'),
# Output('btn-all-frame-download', 'href'),
# Output('btn-all-anim-download', 'href')],
- [#Input('btn-anim-download', 'n_clicks'),
- Input('model-dropdown', 'value'),
- Input('variable-dropdown-forecast', 'value'),
- Input('model-date-picker', 'date'),
- Input('slider-graph', 'value')],
- prevent_initial_call=False,
- )
- def download_anim_link(models, variable, date, tstep):
- """ Download PNG frame """
- # from tools import get_figure
- from tools import download_image_link
+ [#Input('btn-anim-download', 'n_clicks'),
+ Input('model-dropdown', 'value'),
+ Input('variable-dropdown-forecast', 'value'),
+ Input('model-date-picker', 'date'),
+ Input('slider-graph', 'value')],
+ prevent_initial_call=False,
+)
+def download_anim_link(models, variable, date, tstep):
+ """ Download PNG frame """
+ # from tools import get_figure
+ from tools import download_image_link
# ctx = dash.callback_context
#
# if ctx.triggered:
# button_id = ctx.triggered[0]["prop_id"].split(".")[0]
# if button_id == 'btn-anim-download':
- if DEBUG: print('GIF', models, variable, date)
- try:
- curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
- except:
- curdate = date
- anim = download_image_link(models, variable, curdate, anim=True)
- #all_frame = download_image_link(['all',], variable, curdate, tstep=int(tstep/3), anim=False)
- #all_anim = download_image_link(['all',], variable, curdate, anim=True)
- if DEBUG: print('DOWNLOAD LINK', anim)
- return anim #, all_frame, all_anim
-
-
-# @app.callback(
+ if DEBUG: print('GIF', models, variable, date)
+ try:
+ curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
+ except:
+ curdate = date
+ anim = download_image_link(models, variable, curdate, anim=True)
+ #all_frame = download_image_link(['all',], variable, curdate, tstep=int(tstep/3), anim=False)
+ #all_anim = download_image_link(['all',], variable, curdate, anim=True)
+ if DEBUG: print('DOWNLOAD LINK', anim)
+ return anim #, all_frame, all_anim
+
+
+# @dash.callback(
# Output('anim-download', 'data'),
# [Input('btn-anim-download', 'n_clicks')],
# [State('model-dropdown', 'value'),
@@ -366,66 +364,66 @@ def register_callbacks(app, cache, cache_timeout):
# raise PreventUpdate
#
- @app.callback(
- Output('all-frame-download', 'data'),
- [Input('btn-all-frame-download', 'n_clicks')],
- [State('variable-dropdown-forecast', 'value'),
- State('model-date-picker', 'date'),
- State('slider-graph', 'value')
- ],
- prevent_initial_call=True,
- )
- def download_all_frame(btn, models, variable, date, tstep):
- """ Download PNG frame """
- # from tools import get_figure
- from tools import download_image
-
- ctx = dash.callback_context
-
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if button_id == 'btn-all-frame-download':
- if DEBUG: print('GIF', btn, models, variable, date)
- try:
- curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
- except:
- curdate = date
- data = download_image(models, variable, curdate, tstep=tstep, anim=False)
- if DEBUG: print('DATA', type(data), data.keys(), [data[k] for k in data if k != 'content'])
- return data
-
- raise PreventUpdate
+@dash.callback(
+ Output('all-frame-download', 'data'),
+ [Input('btn-all-frame-download', 'n_clicks')],
+ [State('variable-dropdown-forecast', 'value'),
+ State('model-date-picker', 'date'),
+ State('slider-graph', 'value')
+ ],
+ prevent_initial_call=True,
+)
+def download_all_frame(btn, models, variable, date, tstep):
+ """ Download PNG frame """
+ # from tools import get_figure
+ from tools import download_image
+
+ ctx = dash.callback_context
+
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if button_id == 'btn-all-frame-download':
+ if DEBUG: print('GIF', btn, models, variable, date)
+ try:
+ curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
+ except:
+ curdate = date
+ data = download_image(models, variable, curdate, tstep=tstep, anim=False)
+ if DEBUG: print('DATA', type(data), data.keys(), [data[k] for k in data if k != 'content'])
+ return data
+
+ raise PreventUpdate
+
+@dash.callback(
+ Output('all-anim-download', 'data'),
+ [Input('btn-all-anim-download', 'n_clicks')],
+ [State('variable-dropdown-forecast', 'value'),
+ State('model-date-picker', 'date')],
+ prevent_initial_call=True,
+)
+def download_all_anim(btn, variable, date):
+ """ Download PNG frame """
+ # from tools import get_figure
+ from tools import download_image
+
+ ctx = dash.callback_context
+
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if button_id == 'btn-all-anim-download':
+ if DEBUG: print('GIF', btn, variable, date)
+ try:
+ curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
+ except:
+ curdate = date
+ data = download_image(['all'], variable, curdate, anim=True)
+ if DEBUG: print('DATA', type(data), data.keys(), [data[k] for k in data if k != 'content'])
+ return data
- @app.callback(
- Output('all-anim-download', 'data'),
- [Input('btn-all-anim-download', 'n_clicks')],
- [State('variable-dropdown-forecast', 'value'),
- State('model-date-picker', 'date')],
- prevent_initial_call=True,
- )
- def download_all_anim(btn, variable, date):
- """ Download PNG frame """
- # from tools import get_figure
- from tools import download_image
+ raise PreventUpdate
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if button_id == 'btn-all-anim-download':
- if DEBUG: print('GIF', btn, variable, date)
- try:
- curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
- except:
- curdate = date
- data = download_image(['all'], variable, curdate, anim=True)
- if DEBUG: print('DATA', type(data), data.keys(), [data[k] for k in data if k != 'content'])
- return data
- raise PreventUpdate
-
-
-
# app.clientside_callback(
# ClientsideFunction(
# namespace='clientside',
@@ -436,7 +434,7 @@ def register_callbacks(app, cache, cache_timeout):
# prevent_initial_call=True,
# )
-# @app.callback(
+# @dash.callback(
# Output('frame-download', 'data'),
# [Input('btn-frame-download', 'n_clicks')],
# [State('model-dropdown', 'value'),
@@ -465,199 +463,202 @@ def register_callbacks(app, cache, cache_timeout):
#
# return download_image(models, variable, curdate, tstep=int(tstep/FREQ))
- @app.callback(
- [Output('was-graph', 'children'),
- Output('was-previous', 'data')],
- [Input('was-apply', 'n_clicks'),
- Input('was-date-picker', 'date'),
- Input('was-slider-graph', 'value')],
- [State('was-dropdown', 'value'),
- State('variable-dropdown-forecast', 'value'),
- State('was-previous', 'data'),
- State({'tag': 'view-style', 'index': ALL}, 'active'),
- State({'tag': 'empty-map', 'index': ALL}, 'zoom'),
- State({'tag': 'empty-map', 'index': ALL}, 'center')],
- prevent_initial_call=False
- )
- @cache.memoize(timeout=cache_timeout)
- def update_was_figure(n_clicks, date, day, was, var, previous, view, zoom, center):
- """ Update Warning Advisory Systems maps """
- if DEBUG: print('WAS', n_clicks, date, day, was, previous, view, zoom, center)
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if button_id != 'was-apply' and date is None and day is None:
- raise PreventUpdate
-
- from tools import get_was_figure
- from tools import get_figure
- if not zoom or previous != was:
- zoom = WAS[was]['zoom']
- else:
- zoom = zoom[0]
- if not center or previous != was:
- center = WAS[was]['center']
- else:
- center = center[0]
-
- previous = was
-
- print('WAS', was)
- if date is not None:
- date = date.split(' ')[0]
- try:
- date = dt.strptime(
- date, "%Y-%m-%d").strftime("%Y%m%d")
- except:
- pass
- if DEBUG: print('SERVER: callback date {}'.format(date))
- else:
- date = end_date
-
- if DEBUG: print("WAS figure " + date, was, day)
- if was:
- view = list(STYLES.keys())[view.index(True)]
- geojson, legend, info = get_was_figure(was, day, selected_date=date)
- fig = get_figure(model=None, var=var, layer=[geojson, legend, info], view=view, zoom=zoom, center=center)
- return fig, previous
-
- raise PreventUpdate
+@dash.callback(
+ [Output('was-graph', 'children'),
+ Output('was-previous', 'data')],
+ [Input('was-apply', 'n_clicks'),
+ Input('was-date-picker', 'date'),
+ Input('was-slider-graph', 'value')],
+ [State('was-dropdown', 'value'),
+ State('variable-dropdown-forecast', 'value'),
+ State('was-previous', 'data'),
+ State({'tag': 'view-style', 'index': ALL}, 'active'),
+ State({'tag': 'empty-map', 'index': ALL}, 'zoom'),
+ State({'tag': 'empty-map', 'index': ALL}, 'center')],
+ prevent_initial_call=False
+ )
+@cache.memoize(timeout=cache_timeout)
+def update_was_figure(n_clicks, date, day, was, var, previous, view, zoom, center):
+ """ Update Warning Advisory Systems maps """
+ if DEBUG: print('WAS', n_clicks, date, day, was, previous, view, zoom, center)
+ ctx = dash.callback_context
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if button_id != 'was-apply' and date is None and day is None:
+ raise PreventUpdate
+ from tools import get_was_figure
+ from tools import get_figure
+ if not zoom or previous != was:
+ zoom = WAS[was]['zoom']
+ else:
+ zoom = zoom[0]
+ if not center or previous != was:
+ center = WAS[was]['center']
+ else:
+ center = center[0]
+
+ previous = was
+
+ print('WAS', was)
+ if date is not None:
+ date = date.split(' ')[0]
+ try:
+ date = dt.strptime(
+ date, "%Y-%m-%d").strftime("%Y%m%d")
+ except:
+ pass
+ if DEBUG: print('SERVER: callback date {}'.format(date))
+ else:
+ date = end_date
+ if DEBUG: print("WAS figure " + date, was, day)
+ if was:
+ view = list(STYLES.keys())[view.index(True)]
+ geojson, legend, info = get_was_figure(was, day, selected_date=date)
+ fig = get_figure(model=None, var=var, layer=[geojson, legend, info], view=view, zoom=zoom, center=center)
+ return fig, previous
+
+ raise PreventUpdate
+
+
+@dash.callback(
+ Output('prob-graph', 'children'),
+ [Input('prob-apply', 'n_clicks'),
+ Input('prob-date-picker', 'date'),
+ Input('prob-slider-graph', 'value')],
+ [State('prob-dropdown', 'value'),
+ State('variable-dropdown-forecast', 'value'),
+ State({'tag': 'view-style', 'index': ALL}, 'active'),
+ State({'tag': 'empty-map', 'index': ALL}, 'zoom'),
+ State({'tag': 'empty-map', 'index': ALL}, 'center')],
+ prevent_initial_call=False
+)
+@cache.memoize(timeout=cache_timeout)
+def update_prob_figure(n_clicks, date, day, prob, var, view, zoom, center):
+ """ Update Warning Advisory Systems maps """
+ from tools import get_prob_figure
+ from tools import get_figure
+
+ # if not prob in case user navigates to section via URL
+ if not prob:
+ prob = 'prob_0.1'
+ if not zoom:
+ zoom = None
+ else:
+ zoom = zoom[0]
+ if not center:
+ center = None
+ else:
+ center = center[0]
+ if DEBUG: print('PROB', date, day, var, prob, var, view, zoom, center)
+ ctx = dash.callback_context
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if button_id != 'prob-apply' and var is None and day is None:
+ raise PreventUpdate
- @app.callback(
- Output('prob-graph', 'children'),
- [Input('prob-apply', 'n_clicks'),
- Input('prob-date-picker', 'date'),
- Input('prob-slider-graph', 'value')],
- [State('prob-dropdown', 'value'),
- State('variable-dropdown-forecast', 'value'),
- State({'tag': 'view-style', 'index': ALL}, 'active'),
- State({'tag': 'empty-map', 'index': ALL}, 'zoom'),
- State({'tag': 'empty-map', 'index': ALL}, 'center')],
- prevent_initial_call=False
- )
- @cache.memoize(timeout=cache_timeout)
- def update_prob_figure(n_clicks, date, day, prob, var, view, zoom, center):
- """ Update Warning Advisory Systems maps """
- from tools import get_prob_figure
- from tools import get_figure
- if not zoom:
- zoom = None
- else:
- zoom = zoom[0]
- if not center:
- center = None
- else:
- center = center[0]
- if DEBUG: print('PROB', date, day, var, prob, var, view, zoom, center)
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if button_id != 'prob-apply' and var is None and day is None:
- raise PreventUpdate
+ if date is not None:
+ date = date.split(' ')[0]
+ try:
+ date = dt.strptime(
+ date, "%Y-%m-%d").strftime("%Y%m%d")
+ except:
+ pass
+ if DEBUG: print('SERVER: callback date {}'.format(date))
+ else:
+ date = end_date
- if date is not None:
- date = date.split(' ')[0]
- try:
- date = dt.strptime(
- date, "%Y-%m-%d").strftime("%Y%m%d")
- except:
- pass
- if DEBUG: print('SERVER: callback date {}'.format(date))
- else:
- date = end_date
-
- if prob:
- prob = prob.replace('prob_', '')
- view = list(STYLES.keys())[view.index(True)]
- geojson, colorbar, info = get_prob_figure(var, prob, day, selected_date=date)
- fig = get_figure(model=None, var=var, layer=[geojson, colorbar, info], view=view, zoom=zoom, center=center)
- if DEBUG: print("FIG", fig)
- return fig
+ if prob:
+ prob = prob.replace('prob_', '')
+ view = list(STYLES.keys())[view.index(True)]
+ geojson, colorbar, info = get_prob_figure(var, prob, day, selected_date=date)
+ fig = get_figure(model=None, var=var, layer=[geojson, colorbar, info], view=view, zoom=zoom, center=center)
+ if DEBUG: print("FIG", fig)
+ return fig
# return html.Div(
# fig,
# id='prob-graph',
# className='graph-with-slider'
# )
- raise PreventUpdate
-
-
- @app.callback(
- [Output({'tag': 'empty-tile-layer', 'index': ALL}, 'url'),
- Output({'tag': 'empty-tile-layer', 'index': ALL}, 'attribution')],
- [Input({'tag': 'view-style', 'index': ALL}, 'n_clicks')],
- [State({'tag': 'view-style', 'index': ALL}, 'active'),
- State({'tag': 'empty-tile-layer', 'index': ALL}, 'url')],
- prevent_initial_call=True
- )
- @cache.memoize(timeout=cache_timeout)
- def update_styles_button(*args):
- """ Function updating styles button """
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = orjson.loads(ctx.triggered[0]["prop_id"].split(".")[0])
- if DEBUG: print("BUTTON ID", str(button_id), type(button_id))
- if button_id['index'] in STYLES:
- active = args[-2]
- graphs = args[-1]
- num_graphs = len(graphs)
- # if DEBUG: print("CURRENT ARGS", str(args))
- # if DEBUG: print("NUM GRAPHS", num_graphs)
-
- res = [False for i in active]
- st_idx = list(STYLES.keys()).index(button_id['index'])
- if active[st_idx] is False:
- res[st_idx] = True
- url = [STYLES[button_id['index']]['url'] for x in range(num_graphs)]
- attr = [STYLES[button_id['index']]['attribution'] for x in range(num_graphs)]
- if DEBUG:
- print(res, url, attr)
- return url, attr
- # return [True if i == button_id['index'] else False for i in active]
-
- if DEBUG: print('NOTHING TO DO')
- raise PreventUpdate
-
-
- @app.callback(
- [Output({'tag': 'model-tile-layer', 'index': ALL}, 'url'),
- Output({'tag': 'model-tile-layer', 'index': ALL}, 'attribution'),
- Output({'tag': 'view-style', 'index': ALL}, 'active')],
- [Input({'tag': 'view-style', 'index': ALL}, 'n_clicks')],
- [State({'tag': 'view-style', 'index': ALL}, 'active'),
- State({'tag': 'model-tile-layer', 'index': ALL}, 'url')],
- prevent_initial_call=True
- )
- @cache.memoize(timeout=cache_timeout)
- def update_models_styles_button(*args):
- """ Function updating styles button """
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = orjson.loads(ctx.triggered[0]["prop_id"].split(".")[0])
- if DEBUG: print("BUTTON ID", str(button_id), type(button_id))
- if button_id['index'] in STYLES:
- active = args[-2]
- graphs = args[-1]
- num_graphs = len(graphs)
- # if DEBUG: print("CURRENT ARGS", str(args))
- # if DEBUG: print("NUM GRAPHS", num_graphs)
-
- res = [False for i in active]
- st_idx = list(STYLES.keys()).index(button_id['index'])
- if active[st_idx] is False:
- res[st_idx] = True
- url = [STYLES[button_id['index']]['url'] for x in range(num_graphs)]
- attr = [STYLES[button_id['index']]['attribution'] for x in range(num_graphs)]
- if DEBUG:
- print(res, url, attr)
- return url, attr, res
- # return [True if i == button_id['index'] else False for i in active]
-
- if DEBUG: print('NOTHING TO DO')
- raise PreventUpdate
-
-
-# @app.callback(
+ raise PreventUpdate
+
+
+@dash.callback(
+ [Output({'tag': 'empty-tile-layer', 'index': ALL}, 'url'),
+ Output({'tag': 'empty-tile-layer', 'index': ALL}, 'attribution')],
+ [Input({'tag': 'view-style', 'index': ALL}, 'n_clicks')],
+ [State({'tag': 'view-style', 'index': ALL}, 'active'),
+ State({'tag': 'empty-tile-layer', 'index': ALL}, 'url')],
+ prevent_initial_call=True
+)
+@cache.memoize(timeout=cache_timeout)
+def update_styles_button(*args):
+ """ Function updating styles button """
+ ctx = dash.callback_context
+ if ctx.triggered:
+ button_id = orjson.loads(ctx.triggered[0]["prop_id"].split(".")[0])
+ if DEBUG: print("BUTTON ID", str(button_id), type(button_id))
+ if button_id['index'] in STYLES:
+ active = args[-2]
+ graphs = args[-1]
+ num_graphs = len(graphs)
+ # if DEBUG: print("CURRENT ARGS", str(args))
+ # if DEBUG: print("NUM GRAPHS", num_graphs)
+
+ res = [False for i in active]
+ st_idx = list(STYLES.keys()).index(button_id['index'])
+ if active[st_idx] is False:
+ res[st_idx] = True
+ url = [STYLES[button_id['index']]['url'] for x in range(num_graphs)]
+ attr = [STYLES[button_id['index']]['attribution'] for x in range(num_graphs)]
+ if DEBUG:
+ print(res, url, attr)
+ return url, attr
+ # return [True if i == button_id['index'] else False for i in active]
+
+ if DEBUG: print('NOTHING TO DO')
+ raise PreventUpdate
+
+
+@dash.callback(
+ [Output({'tag': 'model-tile-layer', 'index': ALL}, 'url'),
+ Output({'tag': 'model-tile-layer', 'index': ALL}, 'attribution'),
+ Output({'tag': 'view-style', 'index': ALL}, 'active')],
+ [Input({'tag': 'view-style', 'index': ALL}, 'n_clicks')],
+ [State({'tag': 'view-style', 'index': ALL}, 'active'),
+ State({'tag': 'model-tile-layer', 'index': ALL}, 'url')],
+ prevent_initial_call=True
+)
+@cache.memoize(timeout=cache_timeout)
+def update_models_styles_button(*args):
+ """ Function updating styles button """
+ ctx = dash.callback_context
+ if ctx.triggered:
+ button_id = orjson.loads(ctx.triggered[0]["prop_id"].split(".")[0])
+ if DEBUG: print("BUTTON ID", str(button_id), type(button_id))
+ if button_id['index'] in STYLES:
+ active = args[-2]
+ graphs = args[-1]
+ num_graphs = len(graphs)
+ # if DEBUG: print("CURRENT ARGS", str(args))
+ # if DEBUG: print("NUM GRAPHS", num_graphs)
+
+ res = [False for i in active]
+ st_idx = list(STYLES.keys()).index(button_id['index'])
+ if active[st_idx] is False:
+ res[st_idx] = True
+ url = [STYLES[button_id['index']]['url'] for x in range(num_graphs)]
+ attr = [STYLES[button_id['index']]['attribution'] for x in range(num_graphs)]
+ if DEBUG:
+ print(res, url, attr)
+ return url, attr, res
+ # return [True if i == button_id['index'] else False for i in active]
+
+ if DEBUG: print('NOTHING TO DO')
+ raise PreventUpdate
+
+
+# @dash.callback(
# [Output({'tag': 'model-tile-layer', 'index': MATCH}, 'url'),
# Output({'tag': 'model-tile-layer', 'index': MATCH}, 'attribution')],
# [Input('airports', 'n_clicks')] +
@@ -728,281 +729,281 @@ def register_callbacks(app, cache, cache_timeout):
# raise PreventUpdate
- @app.callback(
- [Output('model-clicked-coords', 'data'),
- Output('current-popups-stored', 'data'),
- Output(dict(tag='model-map', index=ALL, n_clicks=ALL), 'children')],
- [Input(dict(tag='model-map', index=ALL, n_clicks=ALL), 'click_lat_lng')],
- [State(dict(tag='model-map', index=ALL, n_clicks=ALL), 'id'),
- State(dict(tag='model-map', index=ALL, n_clicks=ALL), 'children'),
- State('model-date-picker', 'date'),
- State('slider-graph', 'value'),
- State('variable-dropdown-forecast', 'value'),
- State('model-clicked-coords', 'data'),
- State('current-popups-stored', 'data')],
- prevent_initial_call=False
- )
- def models_popup(click_data, map_ids, res_list, date, tstep, var, coords, popups):
- from tools import get_single_point
- if DEBUG: print("CLICK:", str(click_data))
- if click_data.count(None) == len(click_data):
- raise PreventUpdate
+@dash.callback(
+ [Output('model-clicked-coords', 'data'),
+ Output('current-popups-stored', 'data'),
+ Output(dict(tag='model-map', index=ALL, n_clicks=ALL), 'children')],
+ [Input(dict(tag='model-map', index=ALL, n_clicks=ALL), 'click_lat_lng')],
+ [State(dict(tag='model-map', index=ALL, n_clicks=ALL), 'id'),
+ State(dict(tag='model-map', index=ALL, n_clicks=ALL), 'children'),
+ State('model-date-picker', 'date'),
+ State('slider-graph', 'value'),
+ State('variable-dropdown-forecast', 'value'),
+ State('model-clicked-coords', 'data'),
+ State('current-popups-stored', 'data')],
+ prevent_initial_call=False
+)
+def models_popup(click_data, map_ids, res_list, date, tstep, var, coords, popups):
+ from tools import get_single_point
+ if DEBUG: print("CLICK:", str(click_data))
+ if click_data.count(None) == len(click_data):
+ raise PreventUpdate
- if DEBUG: print("MAPID:", str(map_ids), type(map_ids))
- # if DEBUG: print("RESLIST:", str(res_list), type(res_list))
+ if DEBUG: print("MAPID:", str(map_ids), type(map_ids))
+ # if DEBUG: print("RESLIST:", str(res_list), type(res_list))
- ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
- if DEBUG: print("CTXT", ctxt, type(ctxt))
- if not ctxt or ctxt is None:
- raise PreventUpdate
+ ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
+ if DEBUG: print("CTXT", ctxt, type(ctxt))
+ if not ctxt or ctxt is None:
+ raise PreventUpdate
- if popups is None:
- popups = {}
+ if popups is None:
+ popups = {}
- trigger = orjson.loads(ctxt)
- if DEBUG: print('TRIGGER', trigger, type(trigger))
+ trigger = orjson.loads(ctxt)
+ if DEBUG: print('TRIGGER', trigger, type(trigger))
- curr_models = [m['index'] for m in map_ids]
- res = res_list
- if trigger in map_ids:
- model = trigger['index']
- mod_idx = map_ids.index(trigger)
- click = click_data[mod_idx]
- if tstep is None:
- tstep = 0
- else:
- tstep = int(tstep/FREQ)
-
- if DEBUG: print("MODEL", model, "CLICK", click, "DATE", date, "STEP", tstep, "MODIDX", mod_idx)
-
- if click is not None and model is not None:
- lat, lon = click
- try:
- selected_date = dt.strptime(date, '%Y%m%d')
- except:
- selected_date = dt.strptime(date, '%Y-%m-%d')
-
- if model in MODELS and MODELS[model]['start'] == 12:
- if tstep < 4:
- date = (selected_date - timedelta(days=1)).strftime("%Y%m%d")
- tstep = 4 + int(tstep)
- else:
- date = selected_date.strftime("%Y%m%d")
- tstep = int(tstep) - 4
- valid_dt = dt.strptime(date, '%Y%m%d') + timedelta(hours=(tstep+4)*FREQ)
+ curr_models = [m['index'] for m in map_ids]
+ res = res_list
+ if trigger in map_ids:
+ model = trigger['index']
+ mod_idx = map_ids.index(trigger)
+ click = click_data[mod_idx]
+ if tstep is None:
+ tstep = 0
+ else:
+ tstep = int(tstep/FREQ)
+
+ if DEBUG: print("MODEL", model, "CLICK", click, "DATE", date, "STEP", tstep, "MODIDX", mod_idx)
+
+ if click is not None and model is not None:
+ lat, lon = click
+ try:
+ selected_date = dt.strptime(date, '%Y%m%d')
+ except:
+ selected_date = dt.strptime(date, '%Y-%m-%d')
+
+ if model in MODELS and MODELS[model]['start'] == 12:
+ if tstep < 4:
+ date = (selected_date - timedelta(days=1)).strftime("%Y%m%d")
+ tstep = 4 + int(tstep)
else:
date = selected_date.strftime("%Y%m%d")
- valid_dt = dt.strptime(date, '%Y%m%d') + timedelta(hours=tstep*FREQ)
-
- if DEBUG: print("MODEL", model, "CLICK", click, "DATE", date, "STEP", tstep)
- value = get_single_point(model, date, int(tstep), var, lat, lon)
- if DEBUG: print("VALUE:", str(value))
-
- marker = dl.Popup(
- children=[
- html.Div([
- html.Span(html.P(
- '{:.2f}'.format(value*VARS[var]['mul'])),
- className='popup-map-value',
+ tstep = int(tstep) - 4
+ valid_dt = dt.strptime(date, '%Y%m%d') + timedelta(hours=(tstep+4)*FREQ)
+ else:
+ date = selected_date.strftime("%Y%m%d")
+ valid_dt = dt.strptime(date, '%Y%m%d') + timedelta(hours=tstep*FREQ)
+
+ if DEBUG: print("MODEL", model, "CLICK", click, "DATE", date, "STEP", tstep)
+ value = get_single_point(model, date, int(tstep), var, lat, lon)
+ if DEBUG: print("VALUE:", str(value))
+
+ marker = dl.Popup(
+ children=[
+ html.Div([
+ html.Span(html.P(
+ '{:.2f}'.format(value*VARS[var]['mul'])),
+ className='popup-map-value',
+ ),
+ html.Span([
+ html.B("Lat {:.2f}, Lon {:.2f}".format(lat, lon)), html.Br(),
+ "DATE {:02d} {} {} {:02d}UTC".format(
+ valid_dt.day,
+ dt.strftime(valid_dt, '%b'),
+ valid_dt.year,
+ valid_dt.hour
),
- html.Span([
- html.B("Lat {:.2f}, Lon {:.2f}".format(lat, lon)), html.Br(),
- "DATE {:02d} {} {} {:02d}UTC".format(
- valid_dt.day,
- dt.strftime(valid_dt, '%b'),
- valid_dt.year,
- valid_dt.hour
+ html.Br(),
+ html.Button("EXPLORE TIMESERIES",
+ id=dict(
+ tag='ts-button',
+ index=model,
+ random=int(random()*100)
),
- html.Br(),
- html.Button("EXPLORE TIMESERIES",
- id=dict(
- tag='ts-button',
- index=model,
- random=int(random()*100)
- ),
- n_clicks=0,
- className='popup-ts-button'
- )],
- className='popup-map-body',
+ n_clicks=0,
+ className='popup-ts-button'
)],
- )
- ],
- position=[lat, lon],
- autoClose=True,
- closeOnEscapeKey=True,
- closeOnClick=False,
- closeButton=True,
- className='popup-map-point',
- )
-
- # if DEBUG: print("||||", res, "\n", res[mod_idx], type(res[mod_idx]))
- if DEBUG: print("||||", res[mod_idx][-1], type(res[mod_idx][-1]))
- if not res[mod_idx] or res[mod_idx] is None:
- if DEBUG: print("*** 1 ***")
- res[mod_idx] = marker
- elif res[mod_idx][-1] is None:
- if DEBUG: print("*** 2 ***")
- res[mod_idx][-1] = marker
+ className='popup-map-body',
+ )],
+ )
+ ],
+ position=[lat, lon],
+ autoClose=True,
+ closeOnEscapeKey=True,
+ closeOnClick=False,
+ closeButton=True,
+ className='popup-map-point',
+ )
+
+ # if DEBUG: print("||||", res, "\n", res[mod_idx], type(res[mod_idx]))
+ if DEBUG: print("||||", res[mod_idx][-1], type(res[mod_idx][-1]))
+ if not res[mod_idx] or res[mod_idx] is None:
+ if DEBUG: print("*** 1 ***")
+ res[mod_idx] = marker
+ elif res[mod_idx][-1] is None:
+ if DEBUG: print("*** 2 ***")
+ res[mod_idx][-1] = marker
# elif res[mod_idx][-1]['type'] == 'Popup':
# if DEBUG: print("*** 3 ***")
# res[mod_idx].pop(-1)
# res[mod_idx].append(marker)
- else:
- if DEBUG: print("*** 3 ***")
- res[mod_idx].append(marker)
- if not coords or coords is None:
- coords = { model: [lat, lon] }
- else:
- coords[model] = [lat, lon]
-
- popups[model] = sum([res[mod_idx][i]['type'] == 'Popup' and 1 or 0
- for i in range(len(res[mod_idx])) if 'type' in
- res[mod_idx][i]]) + 1
-
- if DEBUG: print("COORDS:", str(coords))
- if DEBUG: print("POPUPS:", str(popups))
- return coords, popups, res
-
- return {}, {}, dash.no_update
- # raise PreventUpdate
-
-
- # retrieve timeseries according to coordinates selected
- @app.callback(
- [Output('ts-modal', 'children'),
- Output('ts-modal', 'is_open'),
- Output({ 'tag': 'ts-button', 'index': ALL, 'random': ALL }, 'n_clicks')],
- [Input({ 'tag': 'ts-button', 'index': ALL, 'random': ALL }, 'n_clicks')],
- [State('model-dropdown', 'value'),
- State('model-date-picker', 'date'),
- State('variable-dropdown-forecast', 'value'),
- State('model-clicked-coords', 'data'),
- State('current-popups-stored', 'data')],
-# prevent_initial_call=False
- )
- @cache.memoize(timeout=cache_timeout)
- def show_timeseries(ts_button, mod, date, variable, coords, popups):
- """ Renders model comparison timeseries """
- from tools import get_timeseries
+ else:
+ if DEBUG: print("*** 3 ***")
+ res[mod_idx].append(marker)
+ if not coords or coords is None:
+ coords = { model: [lat, lon] }
+ else:
+ coords[model] = [lat, lon]
+
+ popups[model] = sum([res[mod_idx][i]['type'] == 'Popup' and 1 or 0
+ for i in range(len(res[mod_idx])) if 'type' in
+ res[mod_idx][i]]) + 1
+
+ if DEBUG: print("COORDS:", str(coords))
+ if DEBUG: print("POPUPS:", str(popups))
+ return coords, popups, res
+
+ return {}, {}, dash.no_update
+ # raise PreventUpdate
+
+
+# retrieve timeseries according to coordinates selected
+@dash.callback(
+ [Output('ts-modal', 'children'),
+ Output('ts-modal', 'is_open'),
+ Output({ 'tag': 'ts-button', 'index': ALL, 'random': ALL }, 'n_clicks')],
+ [Input({ 'tag': 'ts-button', 'index': ALL, 'random': ALL }, 'n_clicks')],
+ [State('model-dropdown', 'value'),
+ State('model-date-picker', 'date'),
+ State('variable-dropdown-forecast', 'value'),
+ State('model-clicked-coords', 'data'),
+ State('current-popups-stored', 'data')],
+ prevent_initial_call=True
+)
+@cache.memoize(timeout=cache_timeout)
+def show_timeseries(ts_button, mod, date, variable, coords, popups):
+ """ Renders model comparison timeseries """
+ from tools import get_timeseries
+
+ ctx = dash.callback_context
+ if ctx.triggered:
+ button_id = orjson.loads(ctx.triggered[0]["prop_id"].split(".")[0])
+ if DEBUG: print("BUTTONID:", mod, str(button_id), str(ts_button))
+ if DEBUG: print('SHOW TS COORDS:', coords, type(coords), mod)
+ ts_popups = 0
+ for m in popups:
+ if DEBUG: print("___", m, "___")
+ ts_popups += popups[m]
+ if m == button_id['index']:
+ break
+
+ if ts_popups == 0:
+ ts_popups = 1
+
+ if button_id['tag'] == 'ts-button' and ts_button[ts_popups-1] >= 1:
+ lat, lon = coords[button_id['index']]
+
+ if lat is None or lon is None:
+ raise PreventUpdate
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = orjson.loads(ctx.triggered[0]["prop_id"].split(".")[0])
- if DEBUG: print("BUTTONID:", mod, str(button_id), str(ts_button))
- if DEBUG: print('SHOW TS COORDS:', coords, type(coords), mod)
- ts_popups = 0
- for m in popups:
- if DEBUG: print("___", m, "___")
- ts_popups += popups[m]
- if m == button_id['index']:
- break
-
- if ts_popups == 0:
- ts_popups = 1
-
- if button_id['tag'] == 'ts-button' and ts_button[ts_popups-1] >= 1:
- lat, lon = coords[button_id['index']]
-
- if lat is None or lon is None:
- raise PreventUpdate
-
- if DEBUG: print('SHOW TS """""', mod, lat, lon)
- figure = get_timeseries(mod, date, variable, lat, lon, forecast=True)
- figure.update_layout(MODEBAR_LAYOUT_TS)
- ts_body = dbc.ModalBody(
- dcc.Graph(
- id='timeseries-modal',
- figure=figure,
- config=MODEBAR_CONFIG_TS
- )
+ if DEBUG: print('SHOW TS """""', mod, lat, lon)
+ figure = get_timeseries(mod, date, variable, lat, lon, forecast=True)
+ figure.update_layout(MODEBAR_LAYOUT_TS)
+ ts_body = dbc.ModalBody(
+ dcc.Graph(
+ id='timeseries-modal',
+ figure=figure,
+ config=MODEBAR_CONFIG_TS
)
+ )
- return ts_body, True, [0 for _ in ts_button]
- return dash.no_update, False, [0 for _ in ts_button]
+ return ts_body, True, [0 for _ in ts_button]
return dash.no_update, False, [0 for _ in ts_button]
- # raise PreventUpdate
-
-
- # start/stop animation
- @app.callback(
- [Output('slider-interval', 'disabled'),
- Output('slider-interval', 'n_intervals'),
- Output('open-timeseries', 'style'),
- #Output('div-collection', 'children'),
- ],
- [Input('btn-play', 'n_clicks'),
- Input('btn-stop', 'n_clicks')],
- [State('slider-interval', 'disabled'),
- State('slider-graph', 'value')],
- prevent_initial_call=True
- )
- def start_stop_autoslider(n_play, n_stop, disabled, value):
- """ Play/Pause map animation """
- ctx = dash.callback_context
- if DEBUG: print("VALUE", value)
- if not value:
- value = 0
-
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if button_id == 'btn-play' and disabled:
- ts_style = { 'display': 'none' }
- return not disabled, int(value/FREQ), ts_style
- elif button_id == 'btn-stop' and not disabled:
- ts_style = { 'display': 'block' }
- return not disabled, int(value/FREQ), ts_style
-
- raise PreventUpdate
-
-
- @app.callback(
- Output('slider-graph', 'value'),
- [Input('slider-interval', 'n_intervals')],
- prevent_initial_call=True
+ return dash.no_update, False, [0 for _ in ts_button]
+ # raise PreventUpdate
+
+
+# start/stop animation
+@dash.callback(
+ [Output('slider-interval', 'disabled'),
+ Output('slider-interval', 'n_intervals'),
+ Output('open-timeseries', 'style'),
+ #Output('div-collection', 'children'),
+ ],
+ [Input('btn-play', 'n_clicks'),
+ Input('btn-stop', 'n_clicks')],
+ [State('slider-interval', 'disabled'),
+ State('slider-graph', 'value')],
+ prevent_initial_call=True
)
- @cache.memoize(timeout=cache_timeout)
- def update_slider(n):
- """ Update slider value according to the number of intervals """
- if DEBUG: print('SERVER: updating slider-graph ' + str(n))
- if not n:
- return
- if n >= 24:
- tstep = int(round(24*math.modf(n/24)[0], 0))
- else:
- tstep = int(n)
- if DEBUG: print('SERVER: updating slider-graph ' + str(tstep*FREQ))
- return tstep*FREQ
-
-
- @app.callback(
- Output('forecast-tab', 'children'),
- [Input('models-apply', 'n_clicks'),
- Input('prob-apply', 'n_clicks'),
- Input('was-apply', 'n_clicks')],
- [State({'tag': 'tab-name', 'index': ALL}, 'id')],
- prevent_initial_call=True
- )
- @cache.memoize(timeout=cache_timeout)
- def update_tab_content(models_clicks, prob_clicks, was_clicks, curtab):
- ctx = dash.callback_context
-
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if button_id not in ('models-apply', 'prob-apply', 'was-apply'):
- raise PreventUpdate
+def start_stop_autoslider(n_play, n_stop, disabled, value):
+ """ Play/Pause map animation """
+ ctx = dash.callback_context
+ if DEBUG: print("VALUE", value)
+ if not value:
+ value = 0
+
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if button_id == 'btn-play' and disabled:
+ ts_style = { 'display': 'none' }
+ return not disabled, int(value/FREQ), ts_style
+ elif button_id == 'btn-stop' and not disabled:
+ ts_style = { 'display': 'block' }
+ return not disabled, int(value/FREQ), ts_style
+
+ raise PreventUpdate
+
+
+@dash.callback(
+ Output('slider-graph', 'value'),
+ [Input('slider-interval', 'n_intervals')],
+ prevent_initial_call=True
+)
+@cache.memoize(timeout=cache_timeout)
+def update_slider(n):
+ """ Update slider value according to the number of intervals """
+ if DEBUG: print('SERVER: updating slider-graph ' + str(n))
+ if not n:
+ return
+ if n >= 24:
+ tstep = int(round(24*math.modf(n/24)[0], 0))
+ else:
+ tstep = int(n)
+ if DEBUG: print('SERVER: updating slider-graph ' + str(tstep*FREQ))
+ return tstep*FREQ
+
+
+@dash.callback(
+ Output('forecast-tab', 'children'),
+ [Input('models-apply', 'n_clicks'),
+ Input('prob-apply', 'n_clicks'),
+ Input('was-apply', 'n_clicks')],
+ [State({'tag': 'tab-name', 'index': ALL}, 'id')],
+ prevent_initial_call=True
+ )
+@cache.memoize(timeout=cache_timeout)
+def update_tab_content(models_clicks, prob_clicks, was_clicks, curtab):
+ ctx = dash.callback_context
- if DEBUG: print("::::::::::::", len(curtab), curtab[0]['index'])
- curtab_name = curtab[0]['index']
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if button_id not in ('models-apply', 'prob-apply', 'was-apply'):
+ raise PreventUpdate
- nexttab_name = button_id.replace('-apply', '')
- if DEBUG: print("::::::::::::", curtab_name, nexttab_name)
- if curtab_name != nexttab_name:
- return tab_forecast(nexttab_name)
+ if DEBUG: print("::::::::::::", len(curtab), curtab[0]['index'])
+ curtab_name = curtab[0]['index']
- raise PreventUpdate
+ nexttab_name = button_id.replace('-apply', '')
+ if DEBUG: print("::::::::::::", curtab_name, nexttab_name)
+ if curtab_name != nexttab_name:
+ return tab_forecast(nexttab_name)
raise PreventUpdate
+ raise PreventUpdate
+
# app.clientside_callback(
# """
@@ -1014,7 +1015,6 @@ def register_callbacks(app, cache, cache_timeout):
# Output('graph-collection', 'children'),
# Input('clientside-graph-collection', 'data'),
# )
-
@app.callback(
Output('graph-collection', 'children'),
@@ -1042,97 +1042,99 @@ def register_callbacks(app, cache, cache_timeout):
raise PreventUpdate
elif tstep is None and date is None:
raise PreventUpdate
+ elif tstep is None and date is None:
+ raise PreventUpdate
- from tools import get_figure
- if DEBUG: print('SERVER: calling figure from picker callback')
+ from tools import get_figure
+ if DEBUG: print('SERVER: calling figure from picker callback')
- st_time = time.time()
+ st_time = time.time()
- # if DEBUG: print('SERVER: interval ' + str(n))
- if DEBUG: print('SERVER: tstep ' + str(tstep))
+ # if DEBUG: print('SERVER: interval ' + str(n))
+ if DEBUG: print('SERVER: tstep ' + str(tstep))
- if date is not None:
- date = date.split(' ')[0]
- try:
- date = dt.strptime(
- date, "%Y-%m-%d").strftime("%Y%m%d")
- except:
- pass
- if DEBUG: print('SERVER: callback date {}'.format(date))
- else:
- date = end_date
+ if date is not None:
+ date = date.split(' ')[0]
+ try:
+ date = dt.strptime(
+ date, "%Y-%m-%d").strftime("%Y%m%d")
+ except:
+ pass
+ if DEBUG: print('SERVER: callback date {}'.format(date))
+ else:
+ date = end_date
- if model is None:
- model = DEFAULT_MODEL
+ if model is None:
+ model = DEFAULT_MODEL
- if variable is None:
- variable = DEFAULT_VAR
+ if variable is None:
+ variable = DEFAULT_VAR
- if tstep is not None:
- tstep = int(tstep/FREQ)
- else:
- tstep = 0
+ if tstep is not None:
+ tstep = int(tstep/FREQ)
+ else:
+ tstep = 0
- if DEBUG: print('SERVER: tstep calc ' + str(tstep))
- if DEBUG: print('#### IDS, ZOOM, CENTER:', ids, zoom, center)
+ if DEBUG: print('SERVER: tstep calc ' + str(tstep))
+ if DEBUG: print('#### IDS, ZOOM, CENTER:', ids, zoom, center)
- curr_mods = { i['index']: { 'zoom': z, 'center': c }
- for i, z, c in zip(ids, zoom, center) }
+ curr_mods = { i['index']: { 'zoom': z, 'center': c }
+ for i, z, c in zip(ids, zoom, center) }
- figures = []
+ figures = []
- ncols, nrows = calc_matrix(len(model))
+ ncols, nrows = calc_matrix(len(model))
- if not zoom:
- zoom = [None]
- if not center:
- center = [None]
+ if not zoom:
+ zoom = [None]
+ if not center:
+ center = [None]
- if len(model) != len(zoom):
- if DEBUG: print("##############", len(model), len(zoom), "**********")
- zoom = [None for item in model]
- center = [None for item in model]
+ if len(model) != len(zoom):
+ if DEBUG: print("##############", len(model), len(zoom), "**********")
+ zoom = [None for item in model]
+ center = [None for item in model]
- if DEBUG: print('#### ZOOM, CENTER:', zoom, center, model, ncols, nrows)
- view = list(STYLES.keys())[view.index(True)]
+ if DEBUG: print('#### ZOOM, CENTER:', zoom, center, model, ncols, nrows)
+ view = list(STYLES.keys())[view.index(True)]
# for mod in model:
# mod_zoom = mod in curr_mods and curr_mods[mod]['zoom'] or None
# mod_center = mod in curr_mods and curr_mods[mod]['center'] or None
- for mod, mod_zoom, mod_center in zip(model, zoom, center):
- if DEBUG: print("MOD", mod, "ZOOM", mod_zoom, "CENTER", mod_center, 'VIEW', view)
-
- figure = get_figure(mod, variable, date, tstep,
- static=static, aspect=(nrows, ncols), view=view,
- center=mod_center, zoom=mod_zoom)
-
- figure.style = {
- 'height': '{}vh'.format(int(GRAPH_HEIGHT/nrows)),
- }
-
- # add n_clicks to id dict to reset the state of map, for centering
- figure.id['n_clicks'] = n_clicks
- # if DEBUG: print('FIGURE KEYS 2', figure.id)
- if DEBUG: print('STATIC', static, int(GRAPH_HEIGHT)/nrows)
- figures.append(figure)
-
- if DEBUG:
- for f in figures:
- print("************", f.id, f.style, "**************")
-
- res = [
- dbc.Row(
- [
- dbc.Col(
- figures[row+col+(row*(ncols-1))],
- width=int(12/ncols),
- )
- for col in range(ncols)
- if len(figures) > row+col+(row*(ncols-1))
- ],
- align="start",
- no_gutters=True,
- ) for row in range(nrows)
- ]
- if DEBUG: print(ncols, nrows, len(res), [(type(i), len(i.children)) for i in res])
- if DEBUG: print("**** REQUEST TIME", str(time.time() - st_time))
- return res
+ for mod, mod_zoom, mod_center in zip(model, zoom, center):
+ if DEBUG: print("MOD", mod, "ZOOM", mod_zoom, "CENTER", mod_center, 'VIEW', view)
+
+ figure = get_figure(mod, variable, date, tstep,
+ static=static, aspect=(nrows, ncols), view=view,
+ center=mod_center, zoom=mod_zoom)
+
+ figure.style = {
+ 'height': '{}vh'.format(int(GRAPH_HEIGHT/nrows)),
+ }
+
+ # add n_clicks to id dict to reset the state of map, for centering
+ figure.id['n_clicks'] = n_clicks
+ # if DEBUG: print('FIGURE KEYS 2', figure.id)
+ if DEBUG: print('STATIC', static, int(GRAPH_HEIGHT)/nrows)
+ figures.append(figure)
+
+ if DEBUG:
+ for f in figures:
+ print("************", f.id, f.style, "**************")
+
+ res = [
+ dbc.Row(
+ [
+ dbc.Col(
+ figures[row+col+(row*(ncols-1))],
+ width=int(12/ncols),
+ )
+ for col in range(ncols)
+ if len(figures) > row+col+(row*(ncols-1))
+ ],
+ align="start",
+ no_gutters=True,
+ ) for row in range(nrows)
+ ]
+ if DEBUG: print(ncols, nrows, len(res), [(type(i), len(i.children)) for i in res])
+ if DEBUG: print("**** REQUEST TIME", str(time.time() - st_time))
+ return res
diff --git a/tabs/observations.py b/tabs/observations.py
index d182a87c76c7b07bb8175602cd5f194e086dda09..576e4f2f9a7f4389e056ac53c616a740cb305185 100644
--- a/tabs/observations.py
+++ b/tabs/observations.py
@@ -237,7 +237,16 @@ def tab_observations(window='rgb'):
children=windows[window],
)
-def sidebar_observations():
+def sidebar_observations(option='rgb'):
+ """ Build the observations sidebar"""
+
+ #Highlight the selected section
+ rgb_style = { 'fontWeight': 'bold' }
+ visibility_style = { 'fontWeight': 'normal' }
+ if option == 'visibility':
+ rgb_style={ 'fontWeight': 'normal' }
+ visibility_style= { 'fontWeight': 'bold' }
+
return [html.Div([
html.Label("Variable"),
dcc.Dropdown(
@@ -247,7 +256,7 @@ def sidebar_observations():
# {'label': 'AOD', 'value': 'aod'},
{'label': 'Visibility', 'value': 'visibility'}
],
- value='rgb',
+ value=option,
clearable=False,
searchable=False,
disabled=True
@@ -260,7 +269,8 @@ def sidebar_observations():
html.Div([
dbc.Button("EUMETSAT RGB",
color="link",
- id='rgb'
+ id='rgb',
+ style=rgb_style
)],
className="sidebar-item",
),
@@ -275,7 +285,8 @@ def sidebar_observations():
html.Div([
dbc.Button("Visibility",
color="link",
- id='visibility'
+ id='visibility',
+ style=visibility_style
)],
className="sidebar-item",
),
diff --git a/tabs/observations_callbacks.py b/tabs/observations_callbacks.py
index bab1c339ab1e1dbfc155b2b91337d4b2ef2c6ba5..2735039c7cbe999746998d8a7f305b295120f3b6 100644
--- a/tabs/observations_callbacks.py
+++ b/tabs/observations_callbacks.py
@@ -34,44 +34,45 @@ import math
start_date = DATES['start_date']
end_date = DATES['end_date'] or dt.now().strftime("%Y%m%d")
+from data_handler import cache_timeout
-def register_callbacks(app, cache, cache_timeout):
- """ Registering callbacks """
+#def register_callbacks(app, cache, cache_timeout):
+# """ Registering callbacks """
- @app.callback(
- [Output('observations-tab', 'children'),
- Output('rgb', 'style'),
- # Output('aod', 'style'),
- Output('visibility', 'style'),
- Output('variable-dropdown-observation', 'value')],
- [Input('rgb', 'n_clicks'),
- # Input('aod', 'n_clicks'),
- Input('visibility', 'n_clicks')],
- prevent_initial_call=True
- )
- # def render_observations_tab(rgb_button, aod_button, vis_button):
- def render_observations_tab(rgb_button, vis_button):
- """ Function rendering requested tab """
- ctx = dash.callback_context
+@dash.callback(
+ [Output('observations-tab', 'children'),
+ Output('rgb', 'style'),
+ # Output('aod', 'style'),
+ Output('visibility', 'style'),
+ Output('variable-dropdown-observation', 'value')],
+ [Input('rgb', 'n_clicks'),
+ # Input('aod', 'n_clicks'),
+ Input('visibility', 'n_clicks')],
+ prevent_initial_call=True
+)
+# def render_observations_tab(rgb_button, aod_button, vis_button):
+def render_observations_tab(rgb_button, vis_button):
+ """ Function rendering requested tab """
+ ctx = dash.callback_context
- bold = { 'font-weight': 'bold' }
- norm = { 'font-weight': 'normal' }
+ bold = { 'font-weight': 'bold' }
+ norm = { 'font-weight': 'normal' }
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if button_id == "rgb" and rgb_button:
- return tab_observations('rgb'), bold, norm, button_id
+ if button_id == "rgb" and rgb_button:
+ return tab_observations('rgb'), bold, norm, button_id
# elif button_id == "aod" and aod_button:
# return tab_observations('aod'), norm, bold, norm, button_id
- elif button_id == "visibility" and vis_button:
- return tab_observations('visibility'), norm, bold, button_id
- else:
- raise PreventUpdate
+ elif button_id == "visibility" and vis_button:
+ return tab_observations('visibility'), norm, bold, button_id
+ else:
+ raise PreventUpdate
- return dash.no_update, bold, norm, 'rgb'
+ return dash.no_update, bold, norm, 'rgb'
-# @app.callback(
+# @dash.callback(
# Output('aod-image', 'src'),
# [Input('obs-aod-date-picker', 'date'),
# Input('obs-aod-slider-graph', 'value')],
@@ -90,7 +91,7 @@ def register_callbacks(app, cache, cache_timeout):
# return app.get_asset_url(path)
#
# # start/stop animation
-# @app.callback(
+# @dash.callback(
# [Output('obs-aod-slider-interval', 'disabled'),
# Output('obs-aod-slider-interval', 'n_intervals')],
# [Input('btn-obs-aod-play', 'n_clicks'),
@@ -115,7 +116,7 @@ def register_callbacks(app, cache, cache_timeout):
# raise PreventUpdate
#
#
-# @app.callback(
+# @dash.callback(
# Output('obs-aod-slider-graph', 'value'),
# [Input('obs-aod-slider-interval', 'n_intervals')],
# prevent_initial_call=True
@@ -133,96 +134,96 @@ def register_callbacks(app, cache, cache_timeout):
# return tstep
- @app.callback(
- [Output('rgb-image', 'src'),
- Output('btn-fulldisc', 'active'),
- Output('btn-middleeast', 'active')],
- [Input('btn-fulldisc', 'n_clicks'),
- Input('btn-middleeast', 'n_clicks'),
- Input('obs-date-picker', 'date'),
- Input('obs-slider-graph', 'value')],
- [State('btn-fulldisc', 'active'),
- State('btn-middleeast', 'active')],
- prevent_initial_call=True
- )
- def update_image_src(btn_fulldisc, btn_middleeast, date, tstep, btn_fulldisc_active, btn_middleeast_active):
+@dash.callback(
+[Output('rgb-image', 'src'),
+ Output('btn-fulldisc', 'active'),
+ Output('btn-middleeast', 'active')],
+[Input('btn-fulldisc', 'n_clicks'),
+ Input('btn-middleeast', 'n_clicks'),
+ Input('obs-date-picker', 'date'),
+ Input('obs-slider-graph', 'value')],
+[State('btn-fulldisc', 'active'),
+ State('btn-middleeast', 'active')],
+ prevent_initial_call=True
+)
+def update_image_src(btn_fulldisc, btn_middleeast, date, tstep, btn_fulldisc_active, btn_middleeast_active):
- if DEBUG:
- print('BUTTONS', date, tstep, btn_fulldisc_active, btn_middleeast_active)
- ctx = dash.callback_context
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- else:
- button_id = None
- if button_id not in ('btn-fulldisc', 'btn-middleeast'):
- if btn_middleeast_active:
- button_id = 'btn-middleeast'
- elif btn_fulldisc_active:
- button_id = 'btn-fulldisc'
+ if DEBUG:
+ print('BUTTONS', date, tstep, btn_fulldisc_active, btn_middleeast_active)
+ ctx = dash.callback_context
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ else:
+ button_id = None
+ if button_id not in ('btn-fulldisc', 'btn-middleeast'):
+ if btn_middleeast_active:
+ button_id = 'btn-middleeast'
+ elif btn_fulldisc_active:
+ button_id = 'btn-fulldisc'
- if DEBUG: print('BUTTONS', button_id)
+ if DEBUG: print('BUTTONS', button_id)
- if button_id == 'btn-middleeast':
- path_tpl = 'eumetsat/MiddleEast/archive/{date}/MET8_RGBDust_MiddleEast_{date}{tstep:02d}00.gif'
- btn_fulldisc_active = False
- btn_middleeast_active = True
- elif button_id == 'btn-fulldisc':
- path_tpl = 'eumetsat/FullDiscHD/archive/{date}/FRAME_OIS_RGB-dust-all_{date}{tstep:02d}00.gif'
- btn_fulldisc_active = True
- btn_middleeast_active = False
+ if button_id == 'btn-middleeast':
+ path_tpl = 'eumetsat/MiddleEast/archive/{date}/MET8_RGBDust_MiddleEast_{date}{tstep:02d}00.gif'
+ btn_fulldisc_active = False
+ btn_middleeast_active = True
+ elif button_id == 'btn-fulldisc':
+ path_tpl = 'eumetsat/FullDiscHD/archive/{date}/FRAME_OIS_RGB-dust-all_{date}{tstep:02d}00.gif'
+ btn_fulldisc_active = True
+ btn_middleeast_active = False
- try:
- date = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
- except:
- pass
- path = path_tpl.format(date=date, tstep=tstep)
- if DEBUG: print('......', path)
- return app.get_asset_url(path), btn_fulldisc_active, btn_middleeast_active
+ try:
+ date = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
+ except:
+ pass
+ path = path_tpl.format(date=date, tstep=tstep)
+ if DEBUG: print('......', path)
+ return app.get_asset_url(path), btn_fulldisc_active, btn_middleeast_active
- # start/stop animation
- @app.callback(
- [Output('obs-slider-interval', 'disabled'),
- Output('obs-slider-interval', 'n_intervals')],
- [Input('btn-obs-play', 'n_clicks'),
- Input('btn-obs-stop', 'n_clicks')],
- [State('obs-slider-interval', 'disabled'),
- State('obs-slider-graph', 'value')],
- prevent_initial_call=True
- )
- def start_stop_obs_autoslider(n_play, n_stop, disabled, value):
- """ Play/Pause map animation """
- ctx = dash.callback_context
- if DEBUG: print("VALUE", value)
- if not value:
- value = 0
- if ctx.triggered:
- button_id = ctx.triggered[0]["prop_id"].split(".")[0]
- if button_id == 'btn-obs-play' and disabled:
- return not disabled, int(value)
- elif button_id == 'btn-obs-stop' and not disabled:
- return not disabled, int(value)
+# start/stop animation
+@dash.callback(
+ [Output('obs-slider-interval', 'disabled'),
+ Output('obs-slider-interval', 'n_intervals')],
+ [Input('btn-obs-play', 'n_clicks'),
+ Input('btn-obs-stop', 'n_clicks')],
+ [State('obs-slider-interval', 'disabled'),
+ State('obs-slider-graph', 'value')],
+ prevent_initial_call=True
+)
+def start_stop_obs_autoslider(n_play, n_stop, disabled, value):
+ """ Play/Pause map animation """
+ ctx = dash.callback_context
+ if DEBUG: print("VALUE", value)
+ if not value:
+ value = 0
+ if ctx.triggered:
+ button_id = ctx.triggered[0]["prop_id"].split(".")[0]
+ if button_id == 'btn-obs-play' and disabled:
+ return not disabled, int(value)
+ elif button_id == 'btn-obs-stop' and not disabled:
+ return not disabled, int(value)
- raise PreventUpdate
+ raise PreventUpdate
- @app.callback(
- Output('obs-slider-graph', 'value'),
- [Input('obs-slider-interval', 'n_intervals')],
- prevent_initial_call=True
- )
- def update_obs_slider(n):
- """ Update slider value according to the number of intervals """
- if DEBUG: print('SERVER: updating slider-graph ' + str(n))
- if not n:
- return 0
- if n >= 24:
- tstep = int(round(24*math.modf(n/24)[0], 0))
- else:
- tstep = int(n)
- if DEBUG: print('SERVER: updating slider-graph ' + str(tstep))
- return tstep
+@dash.callback(
+ Output('obs-slider-graph', 'value'),
+ [Input('obs-slider-interval', 'n_intervals')],
+ prevent_initial_call=True
+ )
+def update_obs_slider(n):
+ """ Update slider value according to the number of intervals """
+ if DEBUG: print('SERVER: updating slider-graph ' + str(n))
+ if not n:
+ return 0
+ if n >= 24:
+ tstep = int(round(24*math.modf(n/24)[0], 0))
+ else:
+ tstep = int(n)
+ if DEBUG: print('SERVER: updating slider-graph ' + str(tstep))
+ return tstep
-# @app.callback(
+# @dash.callback(
# [Output({'tag': 'model-tile-layer', 'index': ALL}, 'url'),
# Output({'tag': 'model-tile-layer', 'index': ALL}, 'attribution'),
# Output({'tag': 'view-style', 'index': ALL}, 'active')],
@@ -260,36 +261,36 @@ def register_callbacks(app, cache, cache_timeout):
# raise PreventUpdate
- @app.callback(
- Output('obs-vis-graph', 'children'),
- [Input('obs-vis-date-picker', 'date'),
- Input('obs-vis-slider-graph', 'value')],
- [State('obs-vis-graph', 'zoom'),
- State('obs-vis-graph', 'center')],
- prevent_initial_call=False
- )
- def update_vis_figure(date, tstep, zoom, center):
- from tools import get_vis_figure
- from tools import get_figure
- if DEBUG: print("*************", date, tstep, zoom, center)
- if date is not None:
- date = date.split(' ')[0]
- try:
- date = dt.strptime(
- date, "%Y-%m-%d").strftime("%Y%m%d")
- except:
- pass
- else:
- date = end_date
+@dash.callback(
+ Output('obs-vis-graph', 'children'),
+ [Input('obs-vis-date-picker', 'date'),
+ Input('obs-vis-slider-graph', 'value')],
+ [State('obs-vis-graph', 'zoom'),
+ State('obs-vis-graph', 'center')],
+ prevent_initial_call=False
+)
+def update_vis_figure(date, tstep, zoom, center):
+ from tools import get_vis_figure
+ from tools import get_figure
+ if DEBUG: print("*************", date, tstep, zoom, center)
+ if date is not None:
+ date = date.split(' ')[0]
+ try:
+ date = dt.strptime(
+ date, "%Y-%m-%d").strftime("%Y%m%d")
+ except:
+ pass
+ else:
+ date = end_date
- # view = list(STYLES.keys())[view.index(True)]
- if DEBUG: print('SERVER: VIS callback date {}, tstep {}'.format(date, tstep))
- df, points_layer = get_vis_figure(tstep=tstep, selected_date=date)
- if DEBUG: print("POINTS LAYER", points_layer)
- fig = get_figure(model=None, var=None, layer=points_layer, zoom=zoom, center=center)
- return html.Div(
- fig,
- id='obs-vis-graph',
- className='graph-with-slider'
- )
- # return get_graph(index='vis-graph', figure=get_vis_figure(tstep=tstep, selected_date=date))
+ # view = list(STYLES.keys())[view.index(True)]
+ if DEBUG: print('SERVER: VIS callback date {}, tstep {}'.format(date, tstep))
+ df, points_layer = get_vis_figure(tstep=tstep, selected_date=date)
+ if DEBUG: print("POINTS LAYER", points_layer)
+ fig = get_figure(model=None, var=None, layer=points_layer, zoom=zoom, center=center)
+ return html.Div(
+ fig,
+ id='obs-vis-graph',
+ className='graph-with-slider'
+ )
+ # return get_graph(index='vis-graph', figure=get_vis_figure(tstep=tstep, selected_date=date))
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/test_evaluation.py b/tests/test_evaluation.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc25146abed294de2328fb88881bbc813610affc
--- /dev/null
+++ b/tests/test_evaluation.py
@@ -0,0 +1,15 @@
+import pytest
+import pandas as pd
+import importlib
+import numpy as np
+from contextvars import copy_context
+import dash
+from dash._callback_context import context_value
+from dash._utils import AttributeDict
+code = importlib.import_module('tabs.evaluation')
+
+
+#=================test sidebar evaluation ===========================
+def test_sidebar_evaluation():
+ assert "Button(children='Visual comparison', id='nrt-evaluation', color='link', style={'fontWeight': 'bold'}" in str(code.sidebar_evaluation('nrt'))
+ assert "children='Statistics', id='scores-evaluation', color='link', style={'fontWeight': 'bold'})], className='sidebar-item')]" in str(code.sidebar_evaluation('scores'))
diff --git a/tests/test_evaluation_callbacks.py b/tests/test_evaluation_callbacks.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8ab7bcfa28ed5162738e022df339f398b42037a
--- /dev/null
+++ b/tests/test_evaluation_callbacks.py
@@ -0,0 +1,143 @@
+import pytest
+import pandas as pd
+import importlib
+import numpy as np
+from contextvars import copy_context
+import dash
+from dash._callback_context import context_value
+from dash._utils import AttributeDict
+code = importlib.import_module('tabs.evaluation_callbacks')
+
+#add equality checker for objects
+def __eq__(self, other):
+ if isinstance(self, other.__class__):
+ return self.a == other.a and self.b == other.b
+ return False
+
+#============ TEST extend_l============================================
+def test_extend_l():
+ assert code.extend_l([[1],[2],[3]]) == [1,2,3]
+
+#============ TEST render_evaluation_tab=================================
+def test_render_evaluation_tab():
+ def run_callback():
+ context_value.set(AttributeDict(**{"triggered_inputs": [{"prop_id": "nrt-evaluation.n_clicks"},{"prop_id": "scores-evaluation.n_clicks"}]}))
+ return code.render_evaluation_tab(0, 0)
+
+ ctx = copy_context()
+ output = ctx.run(run_callback)
+ assert output[0] == dash.no_update
+ assert output[1:] == ({ 'fontWeight': 'bold' }, { 'fontWeight': 'normal' })
+
+
+#============ TEST _no_data_modis========================================
+def test_no_data_modis():
+ assert code._no_modis_data() == ([{'name': 'NO DATA', 'id': ''}], [], {'display': 'block'})
+
+#============ TEST update_time_selection===================================
+def test_update_time_selection():
+ def run_callback():
+ context_value.set(AttributeDict(**{"triggered_inputs": [{"prop_id": "obs-timescale-dropdown.value"},{"prop_id": "obs-network-dropdown.value"}]}))
+ return code.update_time_selection('seasonal', 'modis')
+
+ ctx = copy_context()
+ output = ctx.run(run_callback)
+ assert "'Spring 2018', 'value': '201803-201805'}], 'Select season')" in str(output)
+
+
+#============ TEST modis_scores_tables_retrieve========================
+def test_modis_scores_tables_retrieve_no_n_click():
+ def run_callback():
+ context_value.set(AttributeDict(**{"triggered_inputs":
+ [{"prop_id": "scores-apply.n_clicks"},
+ {"prop_id": "obs-models-dropdown.value"},
+ {"prop_id": "obs-statistics-dropdown.value"},
+ {"prop_id": "obs-network-dropdown.value"},
+ {"prop_id": "obs-timescale-dropdown.value"},
+ {"prop_id": "obs-selection-dropdown.value"}]}))
+ return code.modis_scores_tables_retrieve(0, ['median'],['bias'],'modis','monthly','202208' )
+
+ ctx = copy_context()
+ output = ctx.run(run_callback)
+ assert output[0] == dash.no_update
+ assert output[1] == dash.no_update
+ assert output[2] == {'display':'none'}
+
+def test_modis_scores_tables_retrieve_median_202206():
+ def run_callback():
+ context_value.set(AttributeDict(**{"triggered_inputs":
+ [{"prop_id": "scores-apply.n_clicks"},
+ {"prop_id": "obs-models-dropdown.value"},
+ {"prop_id": "obs-statistics-dropdown.value"},
+ {"prop_id": "obs-network-dropdown.value"},
+ {"prop_id": "obs-timescale-dropdown.value"},
+ {"prop_id": "obs-selection-dropdown.value"}]}))
+ return code.modis_scores_tables_retrieve(1, ['median'],['bias'],'modis','monthly','202206' )
+
+ ctx = copy_context()
+ output = ctx.run(run_callback)
+ assert output == ([{'id': 'model', 'name': ''}, {'id': 'bias', 'name': 'BIAS'}], [{'bias': '-0.09', 'model': 'MULTI-MODEL'}], {'display': 'block'})
+
+def test_modis_scores_tables_retrieve_no_data():
+ def run_callback():
+ context_value.set(AttributeDict(**{"triggered_inputs":
+ [{"prop_id": "scores-apply.n_clicks"},
+ {"prop_id": "obs-models-dropdown.value"},
+ {"prop_id": "obs-statistics-dropdown.value"},
+ {"prop_id": "obs-network-dropdown.value"},
+ {"prop_id": "obs-timescale-dropdown.value"},
+ {"prop_id": "obs-selection-dropdown.value"}]}))
+ return code.modis_scores_tables_retrieve(1, ['median'],['bias'],'modis','monthly','212208' )
+
+ ctx = copy_context()
+ output = ctx.run(run_callback)
+ assert output == ([{'id': '', 'name': 'NO DATA'}], [], {'display': 'block'})
+
+
+#============ TEST aeronet_scores_tables_retrieve========================
+def test_aeronet_scores_tables_retrieve_no_n_click():
+ def run_callback():
+ context_value.set(AttributeDict(**{"triggered_inputs":
+ [{"prop_id": "scores-apply.n_clicks"},
+ {"prop_id": "obs-models-dropdown.value"},
+ {"prop_id": "obs-statistics-dropdown.value"},
+ {"prop_id": "obs-network-dropdown.value"},
+ {"prop_id": "obs-timescale-dropdown.value"},
+ {"prop_id": "obs-selection-dropdown.value"}]}))
+ return code.aeronet_scores_tables_retrieve(0, None, None, None, None, None, ['median'], ['bias'], 'aeronet', 'monthly', '202208', [], [], None, [], [], None, [], [], None, [], [], None,[], [], None)
+ ctx = copy_context()
+ output = ctx.run(run_callback)
+ assert output[0] == dash.no_update
+ assert output[1] == dash.no_update
+ assert output[2] == {'display':'none'}
+
+def test_aeronet_scores_tables_retrieve_median_202206():
+ def run_callback():
+ context_value.set(AttributeDict(**{"triggered_inputs":
+ [{"prop_id": "scores-apply.n_clicks"},
+ {"prop_id": "obs-models-dropdown.value"},
+ {"prop_id": "obs-statistics-dropdown.value"},
+ {"prop_id": "obs-network-dropdown.value"},
+ {"prop_id": "obs-timescale-dropdown.value"},
+ {"prop_id": "obs-selection-dropdown.value"}]}))
+ return code.aeronet_scores_tables_retrieve(1, None, None, None, None, None, ['median'], ['bias'], 'aeronet', 'monthly', '202208', [], [], None, [], [], None, [], [], None, [], [], None,[], [], None)
+ ctx = copy_context()
+ output = ctx.run(run_callback)
+ assert output[0] == [{'id': 'station', 'name': ['BIAS', '']}, {'id': 'median', 'name': ['BIAS', 'MULTI-MODEL']}]
+ assert output[1] == [{'median': '-0.17', 'station': 'Europe'}, {'median': '-0.11', 'station': 'Mediterranean'}, {'median': '-0.31', 'station': 'MiddleEast'}, {'median': '-0.03', 'station': 'NAfrica'}, {'median': '-0.11', 'station': 'Total'}]
+
+
+def test_aeronet_scores_tables_retrieve_no_data():
+ def run_callback():
+ context_value.set(AttributeDict(**{"triggered_inputs":
+ [{"prop_id": "scores-apply.n_clicks"},
+ {"prop_id": "obs-models-dropdown.value"},
+ {"prop_id": "obs-statistics-dropdown.value"},
+ {"prop_id": "obs-network-dropdown.value"},
+ {"prop_id": "obs-timescale-dropdown.value"},
+ {"prop_id": "obs-selection-dropdown.value"}]}))
+ return code.aeronet_scores_tables_retrieve(1, None, None, None, None, None, ['median'], ['bias'], 'aeronet', 'monthly', '212208', [], [], None, [], [], None, [], [], None, [], [], None,[], [], None)
+
+ ctx = copy_context()
+ output = ctx.run(run_callback)
+ assert output[0] == [{'id': 'station', 'name': ['NO DATA']},{'id': 'station', 'name': ['NO DATA']}, {'id': 'station', 'name': ['NO DATA']}, {'id': 'station', 'name': ['NO DATA']}, {'id': 'station', 'name': ['NO DATA']}]
diff --git a/tests/test_nc2scores_aeronet.py b/tests/test_nc2scores_aeronet.py
new file mode 100644
index 0000000000000000000000000000000000000000..02a21078cb81ce193d30d4c20bb515efb488f504
--- /dev/null
+++ b/tests/test_nc2scores_aeronet.py
@@ -0,0 +1,21 @@
+import pytest
+import pandas as pd
+import importlib
+import numpy as np
+code = importlib.import_module('preproc.nc2scores_aeronet')
+
+def test_dropna_and_setindex():
+ d = {'time': [2,1,3],'col1':[np.nan, 22, 53]}
+ d = pd.DataFrame(d)
+ df = d.dropna().set_index('time')
+ res = code.dropna_and_setindex(d, 'Output string',column_name='col1')
+ assert res.shape == df.shape
+
+def test_verify_df_size_no_grpname():
+ df = pd.DataFrame()
+ assert code.verify_df_size(df) == ('Total', {'BIAS': '-', 'CORR': '-', 'RMSE': '-', 'FRGE': '-', 'TOTN': '0'})
+
+def test_verify_df_size_grpname():
+ df = pd.DataFrame(columns=['test'])
+ assert code.verify_df_size(df, 'test') == (None, {'BIAS': '-', 'CORR': '-', 'RMSE': '-', 'FRGE': '-', 'TOTN': '0'})
+
diff --git a/tests/test_observations.py b/tests/test_observations.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3cf5360deb5467c5224f2ccfe8f54af83be6c31
--- /dev/null
+++ b/tests/test_observations.py
@@ -0,0 +1,15 @@
+import pytest
+import pandas as pd
+import importlib
+import numpy as np
+from contextvars import copy_context
+import dash
+from dash._callback_context import context_value
+from dash._utils import AttributeDict
+code = importlib.import_module('tabs.observations')
+
+
+#=================test sidebar observations ===========================
+def test_sidebar_observations():
+ assert "[Button(children='EUMETSAT RGB', id='rgb', color='link', style={'fontWeight': 'bold'})]" in str(code.sidebar_observations('rgb'))
+ assert "Button(children='Visibility', id='visibility', color='link', style={'fontWeight': 'bold'})" in str(code.sidebar_observations('visibility'))
diff --git a/tests/test_router.py b/tests/test_router.py
new file mode 100644
index 0000000000000000000000000000000000000000..d47859590772e94a4ebd9575009c423c765f3b50
--- /dev/null
+++ b/tests/test_router.py
@@ -0,0 +1,76 @@
+import pytest
+import importlib
+code = importlib.import_module('router')
+from data_handler import ROUTE_DEFAULTS
+
+#============ TEST get_input_aliases=================================
+def test_get_input_aliases():
+ input = {'tab': ['forecast'], 'var': ['aod'], 'model': ['median', 'monarch', 'cams'], 'for_option': ['models'], 'eval_option': ['nrt'], 'obs_option': ['rgb'], 'date': ['20230110']}
+ assert code.get_input_aliases(input) == {'tab': ['forecast-tab'], 'var': ['OD550_DUST'], 'model': ['median', 'monarch', 'cams'], 'for_option': ['models'], 'eval_option': ['nrt'], 'obs_option': ['rgb'], 'date': ['20230110']}
+
+ route_selections = {'model': ['multi-model']}
+ result = code.get_input_aliases(route_selections)
+ assert result['model'] == ['median']
+
+ route_selections = {'var': ['aod']}
+ result = code.get_input_aliases(route_selections)
+ assert result['var'] == ['OD550_DUST']
+
+#============ TEST eval_section_query=================================
+def test_eval_section_query():
+ input = {'tab': ['forecast-tab'], 'var': ['aod'], 'section': ['was'], 'model': ['median', 'monarch', 'cams'], 'for_option': ['models'], 'eval_option': ['nrt'], 'obs_option': ['rgb'], 'date': ['20230110']}
+ assert code.eval_section_query(input) == {'tab': ['forecast-tab'], 'var': ['aod'], 'section': ['was'], 'model': ['median', 'monarch', 'cams'], 'for_option': ['was'], 'eval_option': ['nrt'], 'obs_option': ['rgb'], 'date': ['20230110']}
+
+ queries = {'tab': ['forecast-tab'], 'section': ['models']}
+ result = code.eval_section_query(queries)
+ assert result['for_option'] == ['models']
+
+ queries = {'tab': ['evaluation-tab'], 'section': ['visibility']}
+ result = code.eval_section_query(queries)
+ assert result['eval_option'] == ['visibility']
+
+#============ TEST get_url_queries=================================
+def test_get_url_queries():
+ url = '/dashboard/?var=aod&model=cams&date=20230110'
+ assert code.get_url_queries(url, ROUTE_DEFAULTS) == {'date': ['20230110'], 'eval_option': ['nrt'], 'for_option': ['models'], 'model': ['cams'], 'obs_option': ['rgb'], 'tab': ['forecast-tab'], 'var': ['OD550_DUST'], 'country':['burkinafaso'], 'download': [None]}
+
+ url = "http://localhost/?tab=forecast§ion=models&model=multi-model&var=concentration&country=chad"
+ result = code.get_url_queries(url)
+ expected = {'tab': ['forecast-tab'],
+ 'var': ['SCONC_DUST'],
+ 'model': ['median'],
+ 'for_option': ['models'],
+ 'eval_option': ['nrt'],
+ 'obs_option': ['rgb'],
+ 'country': ['chad'],
+ 'download': [None],
+ 'date': ['20220831'],
+ 'section': ['models']
+ }
+ assert result == expected
+
+#============ TEST render_sidebar=================================
+def test_render_sidebar():
+ #first test forecast tab
+ assert "className='collapsible-cards')], className='sidebar-bottom')]" in str(code.render_sidebar('forecast-tab'))
+ #test evaluation
+ assert "Div(children=[Button(children='Statistics', id='scores-evaluation', color='link'" in str(code.render_sidebar('evaluation-tab'))
+ #test observations
+ assert "id='observations-variable', className='sidebar-first-item')" in str(code.render_sidebar('observations-tab'))
+#============ TEST router=================================
+def test_router():
+ #verify forecast with selections returns correctly
+ url ='http://bscesdust02.bsc.es:7778/dashboard/?var=aod&model=median&model=monarch&model=cams&date=20230110'
+ assert "id='app-tabs', value='forecast-tab')]" in str(code.router(url))
+
+ #verify evaluation with section
+ url ='http://bscesdust02.bsc.es:7778/dashboard/?tab=evaluation§ion=statistics'
+ assert "id='app-tabs', value='evaluation-tab')]" in str(code.router(url))
+
+ #verify observations with section
+ url ='http://bscesdust02.bsc.es:7778/dashboard/?tab=observations§ion=visibility'
+ assert "id='app-tabs', value='observations-tab')]" in str(code.router(url))
+
+ #verify incorrect url lands on default
+ url ="children=[H2(children='404 Error', id='error_title"
+ assert "id='app-tabs', value='forecast-tab')]" in str(code.router(url))