gui15minTimeseries_Model.java [src/java/m/cfa/timeseries15min] Revision: 58a0240aa5d8488148f00b000851d611ccb404ce  Date: Thu Apr 28 13:33:22 MDT 2016
package m.cfa.timeseries15min;

import m.cfa.DateComparator;
import m.cfa.Data;
import m.cfa.DoubleArray;
import m.cfa.DoubleMath;
import m.cfa.Graphing;
import m.cfa.User_Data;
import java.awt.Color;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.general.SeriesException;
import org.jfree.data.time.Day;
import org.jfree.data.time.Minute;
import org.jfree.data.time.TimeSeries;

/**
* Last Updated: 4-February-2016
* @author Tyler Wible
* @since 23-June-2014
*/
public class gui15minTimeseries_Model {
    String mainFolder = "C:/Projects/TylerWible/CodeDirectories/NetBeans/data/CFA/Timeseries";
    String database = "USGS";//"CDWR";//"STORET";//"UserData";//
    String stationID = "06752260";//"CLAGRECO";//"000028";//
    String stationName = "CACHE LA POUDRE RIVER AT FORT COLLINS, CO";//"Cache La Poudre Near Greeley";//"BIG THOMPSON R NEAR MOUTH";//
    String beginDate = "";//yyyy-MM-dd
    String endDate = "";//yyyy-MM-dd
    String userData = "";//"Date\tFlow\n1999-04-29 00:00\t8.3\n1999-05-09 00:00\t60.2\n1999-05-29 00:00\t20.1";//
    boolean mergeDatasets = false;//true;//
    String mergeMethod = "user";//"public";//"max";//"average";//"min";//
    
    
    //Outputs
    String len = "-1";
    String start = "?";
    String end = "?";
    String units = "?";
    String dataSource = "?";
    double max = -1;
    double min = -1;
    double range = -1;
    double mean = -1;
    double standardDeviation = -1;
    
    //Gets
    public File getOutputSummary() {
        return new File(mainFolder, "timeseries15min_summary.csv");
    }
    public String getGraph() {
        return "timeseries15min_graph.jpg";
    }
    public File getTimeseriesOutput(){
        //This output file is for use with JSHighCharts
        return new File(mainFolder, "timeseries15min_graph.out");
    }
    public String getLen(){ return len; }
    public String getStart(){ return start; }
    public String getEnd(){ return end; }
    public String getUnits(){ return units; }
    public String getDataSource(){ return dataSource; }
    public String getMax(){ return String.valueOf(max); }
    public String getMin(){ return String.valueOf(min); }
    public String getRange(){ return String.valueOf(range); }
    public String getMean(){ return String.valueOf(mean); }
    public String getStandardDeviation(){ return String.valueOf(standardDeviation); }
    
    //Sets
    public void setMainFolder(String mainFolder_str){ mainFolder = mainFolder_str; }
    public void setDatabase(String database_str){ database = database_str; }
    public void setStationID(String stationID_str){ stationID = stationID_str; }
    public void setStationName(String stationName_str){ stationName = stationName_str; }
    public void setBeginDate(String beginDate_str){ beginDate = beginDate_str; }
    public void setEndDate(String endDate_str){ endDate = endDate_str; }
    public void setUserData(String userData_str){ userData = userData_str; }
    public void setMergeDatasets(boolean mergeDatasets_TF){ mergeDatasets = mergeDatasets_TF; }
    public void setMergeMethod(String mergeMethod_str){ mergeMethod = mergeMethod_str; }
    
    /**
     * Main statistics function calls other functions to calculate each statistic value then stores the results as global variables
     * @param sortedData_combined  data on which statistical values are desired
     */
    private void CalculateDailyStatistics(String[][] flowData) throws IOException, ParseException {
        //Calculate flow statistics for the entire analysis period
        ArrayList<Double> allData = new ArrayList<Double>();
        for(int i=0; i<flowData.length; i++){
            allData.add(Double.parseDouble(flowData[i][1]));
        }
        
        String[][] resultSummary = new String[6][1];
        resultSummary[0][0] = "Method";
        resultSummary[1][0] = "Maximum";
        resultSummary[2][0] = "Minimum";
        resultSummary[3][0] = "Range";
        resultSummary[4][0] = "Mean";
        resultSummary[5][0] = "Standard Deviation";
        
        resultSummary = CalculateStatistics(allData, resultSummary, "All Data " + start + " to " + end);
        max = Double.parseDouble(resultSummary[1][1]);
        min = Double.parseDouble(resultSummary[2][1]);
        range = Double.parseDouble(resultSummary[3][1]);
        mean = Double.parseDouble(resultSummary[4][1]);
        standardDeviation = Double.parseDouble(resultSummary[5][1]);
        
        //Calculate Flow statistics for each day in time period
        DoubleArray doubleArray = new DoubleArray();
        boolean moreDays = flowData.length > 0;
        String currentDay = flowData[0][0].substring(0,10);
        String finalDay = flowData[flowData.length - 1][0].substring(0,10);
        while(moreDays){
            //Get current year's data and calculate it's statistics
            ArrayList<Double> partialData = doubleArray.getDaysData(flowData, currentDay);
            resultSummary = CalculateStatistics(partialData, resultSummary, currentDay);
            
            String nextDay = doubleArray.getDay(currentDay, 1);
            if(finalDay.compareToIgnoreCase(String.valueOf(nextDay)) >= 0){
                currentDay = String.valueOf(nextDay);
            }else{
                moreDays = false;
            }
        }
        
        writeSummary(resultSummary);
    }
    private String[][] CalculateStatistics(ArrayList<Double> dataList, String[][] resultSummary, String header){
        DoubleMath doubleMath = new DoubleMath();
        DoubleArray doubleArray = new DoubleArray();
        
        double maxVal = doubleMath.max(dataList);
        double minVal = doubleMath.min(dataList);
        double rangeVal = maxVal - minVal;
        double aveVal = doubleMath.meanArithmetic(dataList);
        double stDev = doubleMath.StandardDeviationSample(dataList);
        
        //Append current results to summary
        String[] resultArray = {header, 
            String.valueOf(doubleMath.round(maxVal,3)), 
            String.valueOf(doubleMath.round(minVal,3)),
            String.valueOf(doubleMath.round(rangeVal,3)),
            String.valueOf(doubleMath.round(aveVal,3)),
            String.valueOf(doubleMath.round(stDev,3))};
        resultSummary = doubleArray.appendcolumn_Matrix(resultSummary, resultArray);
        
        return resultSummary;
    }
    /**
     * Graph the time series and user data and save the resulting graph to the specified location
     * @param sortedData  the String[][] containing sorted data for the time series 
     * (column 1 = dates (yyyy-mm-dd if timeStep = "daily", yyyy-mm if 
     * timeStep = "monthly", yyyy if timeStep = "yearly") column 2 = value
     * @param sortedData_user  the String[][] containing sorted user data for the 
     * time series (column 1 = dates (yyyy-mm-dd if timeStep = "daily", yyyy-mm 
     * if timeStep = "monthly", yyyy if timeStep = "yearly") column 2 = value
     * @param yAxisTitle  the String label for the y axis of the graph
     */
    private void createTimeseriesGraph(String[][] sortedData,
                                       String[][] sortedData_user) throws ParseException, IOException {
        //Create TimeSeries graph of merged data
        TimeSeries series = new TimeSeries(stationID + ": Data");
        DateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        Date jfreeChartDateLimit = desiredDateFormat.parse("1900-01-01 00:00");
        String[][] graphData = new String[sortedData.length][2];
        for(int i=0; i < sortedData.length; i++) {
            double value = Double.parseDouble(sortedData[i][1]);
            Date currentDate = desiredDateFormat.parse(sortedData[i][0]);
            Minute date =  new Minute(currentDate);
            try{
                series.add(date, value);
                graphData[i][0] = sortedData[i][0];
                graphData[i][1] = sortedData[i][1];
            }catch(SeriesException e){
                //Some times there are multiple data points for a single yyyy-MM-dd HH:mm so catch the error here and continue
                graphData[i][0] = "1900-01-01 00:00";
                graphData[i][1] = "-1";
            }catch(IllegalArgumentException e){
                //If the date is prior to 1900-01-01, JFreechart will throw and error trying to parse it into the graph.
                //However, keep the data for the dynamic graph which does not have this limit
                graphData[i][0] = sortedData[i][0];
                graphData[i][1] = sortedData[i][1];
            }
        }
        
        //Create Time Series graph of user data
        TimeSeries series2 = new TimeSeries(stationID + ": User Data");
        for(int i=0; i < sortedData_user.length; i++) {
            double value = Double.parseDouble(sortedData_user[i][1]);
            //artificial limit for JFreeCharts' "Day" class
            Date currentDate = desiredDateFormat.parse(sortedData_user[i][0]);
            if(currentDate.compareTo(jfreeChartDateLimit) >= 0){
                series2.add(new Day(currentDate), value);
            }
        }
        
        //Output XY data for use with JHighCharts
        DoubleArray doubleArray = new DoubleArray();
        doubleArray.writeTimeSeries(mainFolder, graphData, "15-min", getTimeseriesOutput().getName(), false);
		
        //Create renderer, and axis for timeseries graph
        Graphing graphing = new Graphing();
        XYPlot plotTime = new XYPlot();
        boolean showLegend = false;
        
        //Create user data points
        if(sortedData_user.length != 0){//only show user points if it is not zero
            plotTime = graphing.addTimeseriesData(plotTime, series, true, Color.blue, false, false, false, true, 0);
            plotTime = graphing.addTimeseriesData(plotTime, series2, true, Color.darkGray, false, false, false, true, 1);
            showLegend = true;
        }else{
            plotTime = graphing.addTimeseriesData(plotTime, series, true, Color.blue, false, false, false, true, 0);
        }
      
        //Create Y Axis
        ValueAxis rangeTime = new NumberAxis("Discharge [cfs]");
        plotTime.setRangeAxis(0, rangeTime);
        
        //Create X Axis
        DateAxis domainTime = new DateAxis("Date");
        domainTime.setLowerMargin(0.05);
        domainTime.setUpperMargin(0.05);
        plotTime.setDomainAxis(0, domainTime);

        //Set extra plot preferences
        plotTime = graphing.setTimeAxisPreferences(plotTime);

        //Create the chart with the plot and a legend
        String graphTitle = "Time Series for " + database + " Station " + stationID + "; " + stationName;
        JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plotTime, showLegend);
        
        //Set legend Font
        if(showLegend){
            LegendTitle legendTitle = chart.getLegend();
            legendTitle.setItemFont(graphing.masterFont);
        }
        
        //Save resulting graph for use later
        try{
            String path = mainFolder + File.separator + getGraph();
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println("JFreeChart created properly at: " + path);

        }catch(IOException e){
            System.err.println("A problem occurred while trying to creating the chart.");
        }
    }
    /**
     * Writes out the error message, if any, for finding the file and then exits the program
     * @param error  string array to be written as each line of an error message
     * @throws IOException
     */
    public void writeError(ArrayList<String> error) throws IOException{
        //Output data to text file
        String errorContents = error.get(0);
        for(int i=1; i<error.size(); i++){
            errorContents = errorContents + "\n" + error.get(i);
        }
        throw new IOException("Error encountered. Please see the following message for details: \n" + errorContents);
    }
    /**
     * Writes out the dynamically created stats summary of the 15min data
     * @param resultsSummary  string array to be written as each line and column of the csv file
     * @throws IOException
     */
    private void writeSummary(String[][] resultsSummary) throws IOException{
        //Open a file writer for the summary of the flow statistcs
        String path = mainFolder + File.separator + getOutputSummary().getName();
        FileWriter newFile =  new FileWriter(path, false);
        PrintWriter writer = new PrintWriter(newFile);
        
        for(int i=0; i<resultsSummary.length; i++){
            String currentLine = resultsSummary[i][0];
            for(int j=1; j<resultsSummary[i].length; j++){
                currentLine = currentLine + "," + resultsSummary[i][j];
            }
            writer.printf("%s" + "\r\n", 	currentLine);
        }
        
        //Close file writer
        newFile.close();
        writer.close();
        System.out.println("Text File located at:\t" + path);
    }
    public void run() throws IOException, InterruptedException, ParseException, Exception{
        //If no date input, make it the maximum of available data
        if(beginDate == null || beginDate.equalsIgnoreCase("")){
            if(!database.equalsIgnoreCase("UserData")){
                beginDate = "2007-10-01";
            }else{
                beginDate = "1850-01-01";
            }
        }
        if(endDate == null || endDate.equalsIgnoreCase("")){
            // Pull current date for upper limit of data search
            DateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Date currentDate = new Date();
            endDate = desiredDateFormat.format(currentDate);
        }
        
        //Check if any flow data exists
        Data data = new Data();
        String[][] sortableData = data.extractInstantaneousFlowData(mainFolder, database, stationID, beginDate, endDate, userData);
        
        //If the user wants the datasets (public and user) merged then retrieve the second dataset (user)
        String[][] sortableData_user = new String[0][0];
        if(mergeDatasets){
            User_Data user_Data = new User_Data();
            sortableData_user = user_Data.read15minUserFile(userData, "flow", beginDate, endDate);
        }
        
        //Sort the Data by date to remove duplicate date entries
        DoubleArray doubleArray = new DoubleArray();
        Arrays.sort(sortableData, new DateComparator());
        Arrays.sort(sortableData_user, new DateComparator());
        
        //Merge the two datasets (if user data is empty nothing will be merged)
        String[][] sortedData_combined = doubleArray.mergeData(sortableData, sortableData_user, mergeMethod);
        if(sortedData_combined.length == 0){
            ArrayList<String> errorMessage = new ArrayList<String>();
            if(sortableData.length == 0){
                errorMessage.add("There is no available 15-minute (instantaneous) flow data in the " + database + " database for station '" + stationID + "' and the specified date range.");
                if(database.equalsIgnoreCase("CDWR")){
                    errorMessage.add("The CDWR database is sensitive to the begin date used, try specifying a later begin date");
                }
            }
            if(sortableData_user.length == 0){
                errorMessage.add("There is no available 15-minute (instantaneous) uploaded data for station '" + stationID + "' and the specified date range");
            }
            writeError(errorMessage);
        }
        
        //Save analysis results
        this.start = sortedData_combined[0][0];
        this.end = sortedData_combined[sortedData_combined.length - 1][0];
        
        //Calculate stats of data
        CalculateDailyStatistics(sortedData_combined);
        
        //Graph the timeseries data
        createTimeseriesGraph(sortedData_combined, sortableData_user);
        
        //Get today's date for the source reference
        Date currentDate = new Date();
        SimpleDateFormat sourceDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        String today = sourceDateFormat.format(currentDate);
        if(database.equalsIgnoreCase("USGS")){
            this.dataSource = "Stream flow data retrieved from the U.S. Geological Survey, National Water Information System: Web Interface. http://waterdata.usgs.gov/nwis, accessed: " + today;
        }else if(database.equalsIgnoreCase("CDWR")){
            this.dataSource = "Stream flow data retrieved from the Colorado Division of Water Resources, CDWR. http://www.dwr.state.co.us accessed: " + today;
        }else{
            this.dataSource = "Stream flow data provided by the user. Flow analysis accessed: " + today;
        }
        this.len = String.valueOf(sortedData_combined.length);
        this.units = "cfs";
    }
    public static void main(String[] args) throws IOException, InterruptedException, Exception {
        gui15minTimeseries_Model Timeseries15min_Model = new gui15minTimeseries_Model();
        
        //Run model
        Timeseries15min_Model.run();
    }
}