diff --git a/forecast_mhw.css b/forecast_mhw.css new file mode 100644 index 0000000..536a13b --- /dev/null +++ b/forecast_mhw.css @@ -0,0 +1,15 @@ +/* the leaflet iframe class css */ +.leafletIframe { + /* position: relative; */ + /* margin: auto; */ + width: 100%; + height: 400px; + border: 5px solid #d5d7d7; + border-radius: 1%; +} + +/* the time slider class css */ +.slider { + padding-right: 50px; + padding-left: 50px; +} \ No newline at end of file diff --git a/forecast_mhw.html b/forecast_mhw.html new file mode 100644 index 0000000..f16d257 --- /dev/null +++ b/forecast_mhw.html @@ -0,0 +1,177 @@ + + +

MOM6 COBALT - Marine Heatwave Forecast

+ +
+ +
+

Data Options

+ + + + + + + + + + + + + + + + + + + + + +
Region : + +
Initial year : + +
Initial month : + +
Statistics : + +
Analyses dashboard : + +
+
+ + +
+ + + +
+

Figure options

+ + + + + + + + + + + + + + + + + +
Maximum Value : + +
Minimum Value : + +
Number of Discrete Values: + +
Colorbar + : + + +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+

Initialization :

+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+

Forecast time :

+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+ +
+ +
+
+
+

Forecast Spread

+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/forecast_mhw.js b/forecast_mhw.js new file mode 100644 index 0000000..13d5f3b --- /dev/null +++ b/forecast_mhw.js @@ -0,0 +1,632 @@ +// global constant for object ID +const momCobaltMapFcastMHW = $('#momCobaltIFrameFcastMHW'); + +// global info +var mapDataFcastMHW = {}; // parsed html output +var locationDataFcastMHW; // lcoation info from the mom_folium + +// Initial initYear options (using forecast.js function) +window.createMomCobaltInitYearOpt('iniYearMOMCobaltFcastMHW'); +// Initial initMonth options (using forecast.js function) +window.createMomCobaltInitMonthOpt('iniMonthMOMCobaltFcastMHW'); + + +// global variable for initial time +let iniYearMHW = $("#iniYearMOMCobaltFcastMHW").val(); +let iniMonthMHW = $("#iniMonthMOMCobaltFcastMHW").find("option:selected").text(); + +// global time slider related variables +const timeSliderFcastMHW = $("#timeRangeFcastMHW"); +const tValueFcastMHW = $(".timeValueFcastMHW"); +const initTValueFcastMHW = $(".initTimeValueFcastMHW"); + +// default initial time +initTValueFcastMHW.text(iniMonthMHW +' '+ iniYearMHW) +// default tick lead time +const containerTickFcast = $(".ticksFcast"); + +// find default time tick info (using forecast.js function) +var leadMonthListMHW, leadIndexMHW; +[leadIndexMHW, leadMonthListMHW] = window.generateFcastLeadMonth( + iYear = parseInt($("#iniYearMOMCobaltFcastMHW").val()), + iMonth = parseInt($("#iniMonthMOMCobaltFcastMHW").val()) +); + +// set default time slider info +timeSliderFcastMHW.attr("min", 0); +timeSliderFcastMHW.attr("max", leadIndexMHW.length - 1); +timeSliderFcastMHW.val(0); + +// set forecast time title +var leadFoliumFcastMHW = leadMonthListMHW[timeSliderFcastMHW.val()]; // global +tValueFcastMHW.text(leadFoliumFcastMHW); + +// screen size specific adjustment +window.tickSpaceChangeFcast(leadMonthListMHW) + +// Initial stat options +window.createMomCobaltOpt_singleID('statMOMCobaltFcastMHW',momCobaltStatsFcastMHW) + +// setup colorbar option +window.createMomCobaltCbarOpt('cbarOptsFcastMHW','inferno'); + + +///////////////// event listener //////////////// +// screen size adjustment trigger by window resizing +$(window).resize(function() { + window.tickSpaceChangeFcast(leadMonthListMHW); +}); + +// add event listener on figure all clear button +$("#clearFigOptBtnFcastMHW").on("click", function () { + $("input.figOpt").val(''); +}); + +// add event listener on create map button +$("#momCobaltBtnFcastMHW").on("click", function () { + + // update map + replaceFoliumForecastMHW() + + // update initial time + iniYearMHW = $("#iniYearMOMCobaltFcastMHW").val(); + iniMonthMHW = $("#iniMonthMOMCobaltFcastMHW").find("option:selected").text(); + initTValueFcastMHW.text(iniMonthMHW +' '+ iniYearMHW) + + // update tick lead time reassign global variables + var newleadIndex, newleadMonthList; + [newleadIndex, newleadMonthList] = window.generateFcastLeadMonth( + iYear = parseInt($("#iniYearMOMCobaltFcastMHW").val()), + iMonth = parseInt($("#iniMonthMOMCobaltFcastMHW").val()) + ); + leadIndexMHW = newleadIndex + leadMonthListMHW = newleadMonthList + + // update forecast time title + leadFoliumFcastMHW = leadMonthListMHW[timeSliderFcastMHW.val()]; // global + tValueFcastMHW.text(leadFoliumFcastMHW); + + // remove marker on the leaflet map when ever there is figure update (resetting) + var removeMarker = { + type: 'removeMarker' + }; + momCobaltMapFcastMHW[0].contentWindow.postMessage(removeMarker, "*") + +}); + +// event listener for updating the figure (when mouse up the slider handle) +timeSliderFcastMHW.on("mouseup", function() { + // set forecast time (title is set when mouse move) + leadFoliumFcastMHW = leadMonthListMHW[$(this).val()]; + + // update map + replaceFoliumForecastMHW() + + // remove marker on the leaflet map when ever there is figure update (resetting) + var removeMarker = { + type: 'removeMarker' + }; + momCobaltMapFcastMHW[0].contentWindow.postMessage(removeMarker, "*") +}); + +// event listener for updating the slider value when sliding +timeSliderFcastMHW.on("input", function() { + // update forecast time title + leadFoliumFcastMHW = leadMonthListMHW[$(this).val()]; + tValueFcastMHW.text(leadFoliumFcastMHW); +}); + + +// event listener for analyses change +$("#analysisMOMCobaltFcastMHW").on("change", function(){ + var selectedValue = $('#analysisMOMCobaltFcastMHW :selected').val(); + $("#dashNavForecastMHW > ul.nav-pills > li.nav-item").removeClass("active"); + $("#"+selectedValue.slice(0, -3)+'Pill').addClass("active"); + $("#dashContentForecastMHW div.tab-pane").removeClass("active"); + $("#"+selectedValue.slice(0, -3)).addClass("active"); +}) + +// event listener for the "message" event when map location click +$(window).on("message", receiveMessageFcastMHW); + +// event listener for clicking the minitab +$('input[name="fcastAnalysestabs"]').on('click', function() { + console.log('Selected option id:', $(this).attr('id')); + // Check which radio button is clicked + if ($(this).is(':checked')) { + var selectedID = $(this).attr('id'); + window.changeSelectOpt(selectedID.slice(0, -3),'analysisMOMCobaltFcast','viewFcast') + // console.log('Selected option id:', $(this).attr('id')); + } +}); + + +/////////////////////// function section ///////////////////// +// function for advancing/recede to the next option in the list +// used directly in html page button with attribute onclick +function changeLeadTimeStepMHW(timeStep) { + var nextTime = parseInt(timeSliderFcastMHW.val())+timeStep; + timeSliderFcastMHW.val(nextTime); + leadFoliumFcastMHW = leadMonthListMHW[timeSliderFcastMHW.val()]; + tValueFcastMHW.text(leadFoliumFcastMHW); + replaceFoliumForecastMHW(); +}; + + +// function for replace folium overlap info (image and colorbar) +let statMapFcastMHW; +let statMapFcastNameMHW; +function replaceFoliumForecastMHW() { + showLoadingSpinner("loading-spinner-map-Fcastmhw"); + statMapFcastMHW = $("#statMOMCobaltFcastMHW").val(); + statMapFcastNameMHW = $('#statMOMCobaltFcastMHW').find('option:selected').text() + let cbar = $("#cbarOptsFcastMHW") + let maxval = $("#maxvalFcastMHW"); + let minval = $("#minvalFcastMHW"); + let nlevel = $("#nlevelFcastMHW"); + + var ajaxGet = "/cgi-bin/cefi_portal/mom_folium_fcast_mhw.py" + +"?region="+$("#regMOMCobaltFcastMHW").val() + +"&iniyear="+$("#iniYearMOMCobaltFcastMHW").val() + +"&inimonth="+$("#iniMonthMOMCobaltFcastMHW").val() + +"&lead="+timeSliderFcastMHW.val() + +"&stat="+statMapFcastMHW + +"&cbar="+cbar.val() + +"&maxval="+maxval.val() + +"&minval="+minval.val() + +"&nlevel="+nlevel.val() + console.log('https://webtest.psd.esrl.noaa.gov/'+ajaxGet) + + fetch(ajaxGet) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok for forecast map fetch'); + } + return response.text(); + }) + .then(data => { + // Process the response data here + + //replace image + var regexImg = /^\s*"data:image\/png;base64,[^,\n]*,\n/gm; + var matcheImg = data.match(regexImg); + var image = matcheImg[0].match(/"([^"]+)"/)[0].slice(1,-1) + // var image = extractText(matcheImg[0]); + + //replace colorbar + var regexDom = /^\s*\.domain\([^)]*\)\n/gm; + var matchDoms = data.match(regexDom); + var domainArray1 = text2Array(matchDoms[0]); + var domainArray2 = text2Array(matchDoms[1]); + var regexRange = /^\s*\.range\([^)]*\);\n/gm; + var matchRanges = data.match(regexRange); + var rangeArray = text2Array(matchRanges[0].replace(/'/g, '"')); + + //replace tickmark + var regexTickVal = /^\s*\.tickValues\([^)]*\);\n/gm; + var matchTickVal = data.match(regexTickVal); + var tickValArray = text2Array(matchTickVal[0]); + + //replace colorbar label + var regexCLabel = /^\s*\.text\([^)]*\);\n/gm; + var matchCLabel = data.match(regexCLabel); + var textVal = extractText(matchCLabel[0]); + + + mapDataFcastMHW = { + type: 'mapData', // used in hindcast_mom.js for type check + image: image, + domain1: domainArray1, + domain2: domainArray2, + range: rangeArray, + tick: tickValArray, + label: textVal + }; + // console.log(mapDataFcastMHW) + momCobaltMapFcastMHW[0].contentWindow.postMessage(mapDataFcastMHW, "*") + + hideLoadingSpinner("loading-spinner-map-Fcastmhw"); + }) + .catch(error => { + // Handle errors here + console.error('Processing mhw forecast folium map error:', error); + }); + + // momCobaltMap.attr("src", ajaxGet) +}; + +// function for retrieving lon lat from iframe leaflet +// getting the lon lat from iframe and send to +// server to get +// 1. value of the marker on the shading +var varDataFcastMHW = null +function receiveMessageFcastMHW(event) { + // Access the data sent from the iframe + if (event.originalEvent.origin === window.location.origin) { + // console.log(event.originalEvent) + if (event.originalEvent.data.type === 'locationData') { + locationDataFcastMHW = event.originalEvent.data; + // displaying the variable value on iframe marker + const promisevarValFcast = new Promise((resolve, reject) => { + getvarValFcastMHW(locationDataFcastMHW) // function return promise obj from fetch + .then(varDataFcastMHW => { + // send the value back to iframe + var dataJson = { + type: 'varValFcastData', + var: varDataFcastMHW + }; + momCobaltMapFcastMHW[0].contentWindow.postMessage(dataJson, "*") + resolve(); + }) + .catch(error => { + console.error('Error in promisevarValFcast:', error); + reject(error); + }); + }); + + // display forecast spread plotly + // plotTSFcast(locationDataFcastMHW) + + } + + // console.log("Received data from iframe:", locationDataFcastMHW); + // console.log(event.originalEvent.origin); + }; +}; + +// function to get variable value based on locationData and dataFolium +function getvarValFcastMHW(infoLonLat) { + var ajaxGet = "/cgi-bin/cefi_portal/mom_extract_variableValue_fcast_mhw.py" + +"?region="+$("#regMOMCobaltFcastMHW").val() + +"&stat="+statMapFcastMHW + +"&lon="+infoLonLat.longitude + +"&lat="+infoLonLat.latitude + +"&iniyear="+$("#iniYearMOMCobaltFcastMHW").val() + +"&inimonth="+$("#iniMonthMOMCobaltFcastMHW").val() + +"&lead="+timeSliderFcastMHW.val() + + console.log('https://webtest.psd.esrl.noaa.gov/'+ajaxGet) + + const ajaxGetPromise = fetch(ajaxGet) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.text(); + }) + .then(data => { + // Process the response data here + var lines = data.split('\n'); + + return lines[0] + + }) + .catch(error => { + // Handle errors here + console.error('Fetch MHW value error:', error); + }); + + + return ajaxGetPromise +}; + +// // function for setting up promises in Forecast TS spread(execute order for async) +// // 1. input lon lat location +// // 2. Fetch forecast TSs (setup promise) +// // 3. create plotly object on webpage (execute when promise resolved) +// function plotTSFcastMHW(infoLonLat) { +// showLoadingSpinner("loading-spinner-fcastmhw-spread"); +// getTSFcastsMHW(infoLonLat) // the function return a promise obj from fetch +// .then((jsonData)=>{ +// plotlyForecastSpread(jsonData) +// plotlyForecastBox(jsonData) +// hideLoadingSpinner("loading-spinner-fcastmhw-spread"); +// }) +// .catch((error)=>{ +// console.error(error); +// }) +// }; + +// // function to fetch all forecast spread based on locationData +// // response in the json format (testing) +// function getTSFcasts(infoLonLat) { +// var ajaxGet = "/cgi-bin/cefi_portal/mom_extract_timeseries_fcast.py" +// +"?variable="+varFoliumMapFcastMHW +// +"®ion="+$("#regMOMCobaltFcast").val() +// +"&stat="+statMapFcastMHW +// +"&depth="+depthMapFcast +// +"&lon="+infoLonLat.longitude +// +"&lat="+infoLonLat.latitude +// +"&iniyear="+$("#iniYearMOMCobaltFcast").val() +// +"&inimonth="+$("#iniMonthMOMCobaltFcast").val() + +// console.log('https://webtest.psd.esrl.noaa.gov/'+ajaxGet) + +// ajaxGetPromise = fetch(ajaxGet) +// .then(response => { +// if (!response.ok) { +// throw new Error('Network response was not ok'); +// } +// return response.json(); +// }) +// .catch(error => { +// // Handle errors here +// console.error('Fetch time series error:', error); +// }); + +// return ajaxGetPromise +// }; + +// // function for creating the plotly forecast spread time series +// function plotlyForecastSpread(jsonData) { +// var yformat = decimal_format(jsonData.mean); + +// var trace1Color = "rgba(113, 29, 176, 0.7)"; +// var trace = { +// x: leadMonthListMHW, +// y: jsonData.mean, +// type: 'scatter', +// mode: 'lines+markers', +// marker: { size: 5 }, +// line: { +// shape: 'linear', +// color: trace1Color, +// width: 3 +// }, +// // name: statMap+' time series', +// name: varnameFcast +// }; + +// var data = [trace]; + +// var trace2Color = "rgba(113, 29, 176, 0.1)"; +// for (var i=1; i<=10; i++) { +// var key = 'ens'+i +// var trace_ens = { +// x: leadMonthListMHW, +// y: jsonData[key], +// type: 'scatter', +// mode: 'lines+markers', +// marker: { size: 2 }, +// line: { +// shape: 'linear', +// color: trace2Color }, +// // name: statMap+' time series', +// name: key +// }; +// data.push(trace_ens) +// } + + +// var layout = { +// hovermode: 'closest', +// showlegend: false, +// title: +// varnameFcast +'
' + +// ' @ (lat:'+parseFloat(jsonData.lat).toFixed(2)+'N,'+ +// 'lon:'+parseFloat(jsonData.lon).toFixed(2)+'E)', +// // autosize: true, +// annotations: [{ +// x: 0, +// y: 0, +// xref: 'paper', +// yref: 'paper', +// text: 'Source: NOAA CEFI data portal', +// showarrow: false +// }], +// // width: 550, +// // height: 400, +// margin: { +// l: 80, +// r: 80, +// b: 80, +// t: 100, +// // pad: 4 +// }, +// xaxis: { +// title: 'Lead time' +// }, +// yaxis: { +// title: { +// text: statMapFcastNameMHW + ' (' + jsonData.units + ')', +// standoff: 10, +// font: { color: trace1Color } +// }, +// tickfont: { color: trace1Color }, +// tickformat: yformat, +// tickmode: 'auto' +// }, +// modebar: { +// remove: ["autoScale2d", "autoscale", "zoom", "zoom2d", ] +// }, +// dragmode: "select" +// // responsive: true +// }; + +// Plotly.newPlot('plotly-fcast-spread', data, layout); +// }; + +// // function for creating the plotly forecast ensemble range time series +// function plotlyForecastRange(jsonData) { +// var yformat = decimal_format(jsonData.spread); + +// var trace1Color = "rgba(113, 29, 176, 0.7)"; +// var trace = { +// x: leadMonthListMHW, +// y: jsonData.spread, +// type: 'scatter', +// mode: 'lines+markers', +// marker: { size: 5 }, +// line: { +// shape: 'linear', +// color: trace1Color, +// width: 3 +// }, +// // name: statMap+' time series', +// name: varnameFcast +// }; + +// var data = [trace]; + +// var layout = { +// hovermode: 'closest', +// showlegend: false, +// title: +// varnameFcast +'
' + +// ' @ (lat:'+parseFloat(jsonData.lat).toFixed(2)+'N,'+ +// 'lon:'+parseFloat(jsonData.lon).toFixed(2)+'E)', +// // autosize: true, +// annotations: [{ +// x: 0, +// y: 0, +// xref: 'paper', +// yref: 'paper', +// text: 'Source: NOAA CEFI data portal', +// showarrow: false +// }], +// // width: 1000, +// // height: 400, +// margin: { +// l: 80, +// r: 80, +// b: 80, +// t: 100, +// // pad: 4 +// }, +// xaxis: { +// title: 'Lead time' +// }, +// yaxis: { +// title: { +// text: statMapFcastNameMHW + ' (' + jsonData.units + ')', +// standoff: 10, +// font: { color: trace1Color } +// }, +// tickfont: { color: trace1Color }, +// tickformat: yformat, +// tickmode: 'auto' +// }, +// modebar: { +// remove: ["autoScale2d", "autoscale", "zoom", "zoom2d", ] +// }, +// dragmode: "select" +// // responsive: true +// }; +// var config = {responsive: true} +// Plotly.newPlot('plotly-fcast-spread', data, layout,config); +// }; + +// // function for creating the plotly time series box plot +// function plotlyForecastBox(jsonData) { +// var yformat = decimal_format(jsonData.mean); +// var trace1Color = "rgba(113, 29, 176, 0.8)"; +// var xData = leadMonthListMHW; +// var yData = []; +// for (var l=0; l<=11; l++) { +// var ens = [] +// for (var i=1; i<=10; i++) { +// var key = 'ens'+i +// ens.push(jsonData[key][l]); +// } +// yData.push(ens); +// }; + +// var data = []; + +// for ( var i = 0; i < xData.length; i ++ ) { +// var result = { +// type: 'box', +// y: yData[i], +// name: xData[i], +// boxpoints: false, +// // jitter: 0.3, +// // pointpos: -1.8, +// type: 'box', +// boxmean: 'sd', +// // boxpoints: 'all', +// // jitter: 0.5, +// whiskerwidth: 1, +// // fillcolor: none, +// marker: { +// size: 1, +// color: trace1Color +// }, +// line: { +// width: 1, +// color: trace1Color +// } +// }; +// data.push(result); +// }; + +// layout = { +// hovermode: 'closest', +// showlegend: false, +// title: 'Ensemble spread at each lead', +// annotations: [{ +// x: 0, +// y: 0, +// xref: 'paper', +// yref: 'paper', +// text: 'Source: NOAA CEFI data portal', +// showarrow: false +// }], +// // width: 530, +// // height: 400, +// margin: { +// l: 80, +// r: 80, +// b: 80, +// t: 100, +// // pad: 4 +// }, +// xaxis: { +// title: 'Lead time' +// }, +// yaxis: { +// title: { +// text: statMapFcastNameMHW + ' (' + jsonData.units + ')', +// standoff: 10, +// font: { color: trace1Color } +// }, +// tickfont: { color: trace1Color }, +// tickformat: yformat, +// tickmode: 'auto' +// }, +// modebar: { +// remove: ["autoScale2d", "autoscale", "zoom", "zoom2d", ] +// }, +// dragmode: "select" +// // responsive: true +// }; +// var config = {responsive: true} + +// Plotly.newPlot('plotly-fcast-box', data, layout,config); + +// }; + +// // function for deciding the y tick numerical format +// function decimal_format(numArray) { +// var format = '.2f'; +// var maxVal = Math.max(...numArray); +// var minVal = Math.min(...numArray); +// var diff = Math.abs(maxVal-minVal); +// if (diff< 0.01) { +// format = '.2e'; +// } +// return format +// }; + +///////// information function start ///////// + +function momCobaltStatsFcastMHW() { + stats_list = [ + 'MHW probability (90th)', + 'MHW magnitude' + ] + stats_value = [ + 'mhw_prob90', + 'ssta_avg' + ] + return [stats_list, stats_value]; +};