Skip to content
Snippets Groups Projects
Select Git revision
  • b1191322d0f22b41b1b7bbbdf22485c459ebedde
  • master default protected
  • use_flight_altitude
  • distribute
4 results

goesr_l1b.py

Blame
  • update.js NaN GiB
    var Plotly = require('Plotly');
    var ticktext = require('./ticks.js');
    var _ = require('underscore');
    
    
    function removeOldPoints(dataCache, removeCount) {
        for (var i = 0; i < dataCache.length; i++) {
            dataCache[i].x.splice(0, removeCount);
    
            if (dataCache[i].hasOwnProperty('z')) {
                for (var j = 0; j < dataCache[i].z.length; j++) {
                    dataCache[i].z[j].splice(0, removeCount);
                }
            } else if (dataCache[i].hasOwnProperty('y')) {
                // delete things from y *only* if we don't have a Z
                dataCache[i].y.splice(0, removeCount);
            }
            if (dataCache[i].hasOwnProperty('text')) {
                dataCache[i].text.splice(0, removeCount);
            }
        }
    }
    
    
    function updateHeader(dataCache, unitCount, layoutUpdates) {
       // Note: curr_date is 2 minutes behind the current time.
       var curr_date = new Date(dataCache[0]['x'][dataCache[0]['x'].length - 1]);
       var hour = Number(curr_date.toLocaleString("en-US", {hour: 'numeric', 'hour12': false}));
       // Make the screen go black between 1 and 5 am.
       if (0 < hour && hour < 5) {
           document.getElementById("overlay").style.opacity = 1;
       } else {
           document.getElementById("overlay").style.opacity = 0;
       }
       var date_local = curr_date.toLocaleString("en-US",
           { year: 'numeric', month: 'short', day: 'numeric',
             hour: 'numeric', minute: 'numeric', second: 'numeric' }).replace(/,([^,]*)$/, '$1');
       var utcMonth = Number(curr_date.getUTCMonth());
       utcMonth = utcMonth < 9 ? '0' + (utcMonth + 1) : utcMonth + 1;
       var utcDay = Number(curr_date.getUTCDate()) < 9 ? '0' + curr_date.getUTCDate() : curr_date.getUTCDate();
       var utcMinutes = Number(curr_date.getUTCMinutes()) < 9 ? '0' + curr_date.getUTCMinutes() : curr_date.getUTCMinutes();
       var utcSeconds = Number(curr_date.getUTCSeconds()) < 9 ? '0' + curr_date.getUTCSeconds() : curr_date.getUTCSeconds();
       var date_utc = curr_date.getUTCFullYear() + '-' + utcMonth + '-' + utcDay + ' ' +
           curr_date.getUTCHours() + ':' + utcMinutes + ':' + utcSeconds;
       var elevation = unitCount == 0 ? '327.5 meters' : '1074.5 feet';
       layoutUpdates['annotations.1.text'] = 'Data last recorded at: ' + date_utc + 'Z [' + date_local + ' Local]          RIG Elevation = ' + elevation;
    }
    
    
    function changeCamera() {
        var directions = {'north': 'east', 'east': 'south', 'south': 'west', 'west': 'north',};
        // Gets witch direction the current camera is showing.
        var newDirection = directions[document.getElementById('roofCam').src.split('/')[7]];
        // Default to North on first load.
        newDirection = newDirection ? newDirection : 'north';
        // Makes image update. Image updates every (about) 2 minutes and 15 seconds.
        document.getElementById('roofCam').alt = document.getElementById('dirText').innerHTML = newDirection;
        var date = new Date();
        // Update image every 5 minutes and let the final value be in seconds since epoch.
        var formatted_date = Math.floor(date.getTime() / 1000 / 300) * 300;
        document.getElementById('roofCam').src = METOBS_API_URL + '/pub/cache/aoss/cameras/' + newDirection +
                                                 '/latest_medium.jpg?t=' + formatted_date;
    }
    
    
    function changeUnits(dataCache, graphDiv, plotsInfo, unitCount) {
        // Note: unitCount alternates between 0 and 1.
        var plotName;
        var plotInfo;
        var currData;
        var tick_dates;
        // This changes the graphDiv y-axis we are getting.
        var axis_number;
        // This changes the plotInfo y-axis we are getting, which switches between units every time changeUnits is called.
        var yaxis = unitCount == 0 ? 'yaxis' : 'yaxis2';
        var converted_tickvals;
        var tickvals;
        var layoutUpdates = {};
        updateHeader(dataCache, unitCount, layoutUpdates);
        // Update  each subgraph's displayed y-axis and current data values.
        for (var i = 0; i < dataCache.length; i++) {
            axis_number = i == 0 ? '' : i + 1;
            tick_dates = dataCache[i]['x'];
            // Prevents '-6h' from falling off x-axis.
            layoutUpdates['xaxis' + axis_number + '.tickvals'] = [ tick_dates[0], tick_dates[Math.round(tick_dates.length / 2)], tick_dates[tick_dates.length - 1]]
            plotName = dataCache[i].plot_name;
            plotInfo = plotsInfo.plots[plotName];
            // Convert data to the correct units.
            currData = ticktext.getTickText([dataCache[i]['y'][dataCache[i]['y'].length - 1]], plotInfo[yaxis])[0];
            layoutUpdates['annotations[' + (3 + 2 * i) + '].text'] = currData + '' + plotInfo[yaxis]['units']
            tickvals = graphDiv.layout['yaxis' + axis_number]['tickvals']
            if (tickvals != undefined) {
                // Relative Humidity and Wind Direction ticktext and tickvals are static from site_configs_lobby.js.
                if (plotName == 'wind_direction' || plotName == 'rel_hum') {
                    converted_tickvals = plotInfo[yaxis]['ticktext'];
                } else {
                    converted_tickvals = [];
                    for (var j = 0; j < tickvals.length; j++) {
                        converted_tickvals.push(ticktext.getTickText([tickvals[j]], plotInfo[yaxis]))
                    }
                }
                layoutUpdates['yaxis' + axis_number + '.ticktext'] = converted_tickvals;
            }
        }
        // Updates everything at once to save time.
        Plotly.relayout(graphDiv, layoutUpdates);
        changeCamera();
    }
    
    
    function fitGraphs(dataCache, data, var_name, layoutUpdates, yaxis, defaultTickVals) {
        // Set the range of each graph if applicable.
        if (var_name != 'aoss.tower.rel_hum' && var_name != 'aoss.tower.wind_direction') {
            // data only contains new data. Thus we must keep track of old mins/maxs.
            if (dataCache['y'].length == 0) {
                // dataCache['y'] gets overridden somewhere.
                dataCache.yMin = Math.min(...data)
                dataCache.yMax = Math.max(...data)
            } else {
                dataCache.yMin = Math.min(...data, dataCache.yMin)
                dataCache.yMax = Math.max(...data, dataCache.yMax)
            }
    
            var spread = dataCache.yMax - dataCache.yMin;
            var bottom = dataCache.yMin - spread;
            var top = var_name == 'aoss.tower.solar_flux' ? dataCache.yMax : dataCache.yMax + spread;
            // Make graph visible if flat line: Add a margin equal to 10% of the value if non-zero, else scale from 0 to 1.
            if (spread == 0) {
                // Note: top == bottom since spread == 0.
                if (top > 0) {
                    top = top * 1.1
                    bottom = bottom * .9
                } else if (top < 0) { // Only applies to air_temp and dewpoint.
                    top = top * .9
                    bottom = bottom * 1.1
                } else { // Mainly for precipitation.
                    top = .1
                    bottom = -.1
                }
            }
            // Used to lower bottom so that the graph can be seen if it is 0.
            var tick0 = bottom
            // If bottom < 0: Make the bottom 0 since values cannot be negative,
            // and lower the bottom to see 0.
            if (var_name != 'aoss.tower.air_temp' && var_name != 'aoss.tower.dewpoint' && bottom <= 0) {
                bottom = 0;
                tick0 = 0;
            }
            // Shifts the bottom down so that tick0 doesn't overlap xaxis ticks, and graphs near zero can be seen.
            bottom -= .075 * spread + .001;
            layoutUpdates[yaxis + '.tickvals'] = [tick0, tick0 + (top - tick0) / 4, tick0 + 2 * (top - tick0) / 4,
                                                  tick0 + 3 * (top - tick0) / 4, top]
            layoutUpdates[yaxis + '.range'] = [bottom, top]
        } else {
            layoutUpdates[yaxis + '.tickvals'] = defaultTickVals
        }
        // layoutUpdates is changed without having to return it.
    }
    
    
    function replaceData(dataCache, graphDiv, plotsInfo, dataObj, update, maxPoints) {
        var dates = dataObj.dates;
        var plotName;
        var plotInfo;
        var i;
        var probeText;
        var plotHandled = [];
        var forceRedraw = false;
    
        // scatter updates
        var tracesToExtend = [];
        var extendData = {
            x: [],
            y: [],
            text: [],
        };
    
        // contour updates
        var newContourData;
        var contourTracesToExtend = [];
        var contourExtendData = {
            x: [],
            z: [],
            text: [],
        };
    
        var traceIndex = 0;
    
        // Variables used for lobby display.
        var layoutUpdates = {};
        var axis_number;
        /**
         *
         * If we are updating the plots instead of replacing them, then try
         * to use plotly's extendTraces to update the x and y coordinates as well
         * the hovering probe text. This should only add new trace points instead
         * of updating all points.
         *
         * Since we passed 'dataCache' to plotly's 'plot' function during
         * initialization any changes to 'dataCache' should be mirrored in Plotly's
         * plot and vice versa. See the below stackoverflow question for details.
         * https://stackoverflow.com/questions/45759582/plotly-how-to-discard-older-points
         * This is important since we use `.shift()` on the data arrays to remove
         * "old" elements.
         *
         * We have to operate per-plot because some of our plots hover/probe labels
         * use the trace values to come up with one probe box
         * (air temp + dewpoint -> relative humidity).
         *
         */
        for (i = 0; i < dataCache.length; i++) {
            plotName = dataCache[i].plot_name;
            if (plotHandled[plotHandled.length - 1] == plotName) {
                // this happens when there are more than one trace in a plot
                continue;
            }
            plotInfo = plotsInfo.plots[plotName];
            _.each(plotInfo.traces, function (traceInfo, index) {
    
                if (dataCache[traceIndex + index].type == 'contour') {
                    // push new dates for each trace to be extended
                    contourExtendData.x.push(dates);
                    contourTracesToExtend.push(traceIndex + index);
                    // update the Z data instead of the traditional 'y' data
                    // assumes Y doesn't update between calls
                    newContourData = _.map(traceInfo.var_names, function(var_name) {return dataObj[var_name];});
                    dataCache[i].text.push(...ticktext.getProbeText(newContourData, plotInfo, traceInfo, dataObj));
                    for (var j = 0; j < traceInfo.var_names.length; j++) {
                        while (j >= dataCache[i].z.length) {
                            dataCache[i].z.push([]);
                        }
                        dataCache[i].z[j].push(...dataObj[traceInfo.var_names[j]]);  // ES6-style push
                    }
                    contourExtendData.z.push(dataCache[i].z);
                    contourExtendData.text.push(dataCache[i].text);
                    // If plotly could handle extendTraces in the x-dimension we'd
                    // probably do something like this:
                    // newContourData = _.map(traceInfo.var_names, function(var_name) {return dataObj[var_name];});
                    // contourExtendData.z.push(newContourData);
                    // contourExtendData.text.push(
                    //     [ticktext.getProbeText(newContourData, plotInfo, traceInfo, dataObj)]
                    // );
                } else {
                    // push new dates for each trace to be extended
                    extendData['x'].push(dates);
                    tracesToExtend.push(traceIndex + index);
                    // scatter plots, assume the y data is the data being updated
                    _.each(traceInfo.var_names, function (var_name) {
                        if (plotsInfo['isLobbyDisplay']) {
                            axis_number = i == 0 ? '': i + 1;
                            fitGraphs(dataCache[i], dataObj[var_name], var_name, layoutUpdates, 'yaxis' + axis_number, plotInfo['yaxis2']['tickvals'])
                        }
                        // push the entire new data array as one element to update
                        extendData.y.push(dataObj[var_name]);
    
                        // Get new probe text shown when hovering over the traces
                        if ('probe_text' in traceInfo && traceInfo['probe_text'] === null) {
                            // we don't want probe text for this trace
                            probeText = [undefined];
                        } else {
                            probeText = ticktext.getProbeText(dataObj[var_name], plotInfo, traceInfo, dataObj);
                        }
                        extendData.text.push(probeText);
                    });
                }
            });
            traceIndex += plotInfo.traces.length;
            plotHandled.push(plotName);
        }
    
        // Have plotly update the graph with the new points in extendData
        // This will add data to 'dataCache' because of how we originally
        // created the plots.
        if (tracesToExtend.length > 0) {
            Plotly.extendTraces(graphDiv, extendData, tracesToExtend);
        }
        if (contourTracesToExtend.length > 0) {
            Plotly.extendTraces(graphDiv, contourExtendData, contourTracesToExtend);
            // we are currently cheating and re-adding every "row" of data because
            // plotly doesn't support extendTrace'ing in the x-dimension
            // we want to remove the previous "depth" amount of rows
            for (i = 0; i < contourTracesToExtend.length; i++) {
                dataCache[contourTracesToExtend[i]].z.splice(0, dataCache[contourTracesToExtend[i]].y.length);
                dataCache[contourTracesToExtend[i]].text.splice(0, dataCache[contourTracesToExtend[i]].y.length);
            }
        }
    
        if (maxPoints == 0 && !update) {
            // we aren't doing realtime updates and the caller wants us to remove
            // all previous points and keep all new points
            maxPoints = dataObj.dates.length;
            // we have to force a redraw of the plot because the removed plots
            // will still exist on the the graph otherwise (until the next update)
            forceRedraw = true;
        }
        // If adding these new points gave us more points than we need
        // remove the points at the beginning of our data
        if (maxPoints > 0 && dataCache[0]['x'].length > maxPoints) {
            removeOldPoints(dataCache, dataCache[0].x.length - maxPoints);
        }
    
        // If lobby display and data to update.
        if (Object.keys(layoutUpdates).length != 0) {
            // Updates everything at once to save time. Should be done after all dataCache manipulations.
            Plotly.relayout(graphDiv, layoutUpdates);
            // The first time through, call changeUnits to display current data.
            if (!update) {
                changeUnits(dataCache, graphDiv, plotsInfo, 0);
            }
        }
        if (forceRedraw) {
            Plotly.redraw(graphDiv);
        }
    }
    
    
    function updatePlot(dataCache, graphDiv, plotsInfo, dataObj, update, maxPoints) {
        replaceData(dataCache, graphDiv, plotsInfo, dataObj, update, maxPoints);
    }
    
    module.exports.updatePlot = updatePlot;
    module.exports.changeUnits = changeUnits;