BaseflowResults.java [src/java/cfa] Revision: 4daefd1ac3a5cce6d2af07d219b133db7ce0b7a4  Date: Thu Sep 26 16:17:42 MDT 2013
package cfa;
import java.awt.BasicStroke;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
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.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;

/**
* Last Updated: 8-August-2013
* @author Tyler Wible
* @since 18-June-2012
*/
public class BaseflowResults {
    /**
     * Graph the 3-pass base-flow separation results from "BFLOW"
     * @param mainFolder  the file location where the input file is and where graph will be saved
     * @param organizationName  the name of the supervising agency for the current station
     * @param stationID  the station ID of the current station, used for graph labels and error catches
     * @param stationName  the name of the current station, used for graph labels
     * @throws IOException
     */
    public void graphBFLOWresults(String mainFolder, 
                                String organizationName, 
                                String stationID, 
                                String stationName, 
                                String[][] sortedData_user) throws IOException{
        DoubleMath doubleMath = new DoubleMath();
        DurationCurve durationCurve = new DurationCurve();

        //Pull out results to be graphed
        String[][] baseFlowResults = readBFLOWresults(mainFolder);


        //Create TimeSeries to graph of the stream flow, baseflow1, baseflow2, and baseflow3
        TimeSeries streamflow_series = new TimeSeries("Streamflow");
        TimeSeries baseflow1_series = new TimeSeries("Base-flow Pass 1");
        TimeSeries baseflow2_series = new TimeSeries("Base-flow Pass 2");
        TimeSeries baseflow3_series = new TimeSeries("Base-flow Pass 3");
        TimeSeries streamflow_series_user = new TimeSeries("Streamflow User Data");
        TimeSeries baseflow1_series_user = new TimeSeries("Base-flow Pass 1 User Data");
        TimeSeries baseflow2_series_user = new TimeSeries("Base-flow Pass 2 User Data");
        TimeSeries baseflow3_series_user = new TimeSeries("Base-flow Pass 3 User Data");
        ArrayList<Double> baseflowPass1 = new ArrayList<Double>();
        int day = 1, month = 1, year = 1900, ctr = 0;
        double d =1, m =1, y=1;
        for(int i=0; i < baseFlowResults.length; i++) {
            double streamFlow = Double.parseDouble(baseFlowResults[i][3]);
            double baseFlow1 = Double.parseDouble(baseFlowResults[i][4]);
            double baseFlow2 = Double.parseDouble(baseFlowResults[i][5]);
            double baseFlow3 = Double.parseDouble(baseFlowResults[i][6]);
            baseflowPass1.add(baseFlow1);
            //Re-format date for graph
            y = Double.parseDouble(baseFlowResults[i][0]);
            m = Double.parseDouble(baseFlowResults[i][1]);
            d = Double.parseDouble(baseFlowResults[i][2]);
            year = (int)y;
            month = (int)m;
            day =  (int)d;
            Day date = new Day(day,month,year);//day,month,year
            streamflow_series.add(date, streamFlow);
            baseflow1_series.add(date, baseFlow1);
            baseflow2_series.add(date, baseFlow2);
            baseflow3_series.add(date, baseFlow3);
            
            //Create a date string of the current date to compare against dates of user data
            String date_str = String.valueOf(year);
            if(month < 10){
                date_str = date_str + "-0" + String.valueOf(month);
            }else{
                date_str = date_str + "-" + String.valueOf(month);                
            }
            if(day < 10){
                date_str = date_str + "-0" + String.valueOf(day);
            }else{
                date_str = date_str + "-" + String.valueOf(day);
            }
            
            //Create an XYSeries only if user data is not empty
            if(sortedData_user.length > 0 && sortedData_user.length > ctr){
                String userDate = sortedData_user[ctr][0];
                if(date_str.equalsIgnoreCase(userDate)){
                    streamflow_series_user.add(date, streamFlow);
                    baseflow1_series_user.add(date, baseFlow1);
                    baseflow2_series_user.add(date, baseFlow2);
                    baseflow3_series_user.add(date, baseFlow3);
                    ctr++;
                }
            }
        }
        double baseflowMax = doubleMath.max(baseflowPass1);

        //Graph the baseflow on a timeseries axis
        XYPlot plotTime = new XYPlot();
        plotTime = graphTimeData(plotTime, streamflow_series, true, Color.LIGHT_GRAY, false, false, true, 7);
        plotTime = graphTimeData(plotTime, baseflow1_series, true, Color.GRAY, true, true, true, 6);
        plotTime = graphTimeData(plotTime, baseflow2_series, true, Color.DARK_GRAY, true, true, true, 5);
        plotTime = graphTimeData(plotTime, baseflow3_series, true, Color.black, true, true, true, 4);
        
        //Create user data points
        if(sortedData_user.length != 0){//only show user points if it is not zero
            plotTime = graphTimeData(plotTime, streamflow_series_user, false, Color.LIGHT_GRAY, false, false, true, 3);
            plotTime = graphTimeData(plotTime, baseflow1_series_user, false, Color.GRAY, false, false, true, 2);
            plotTime = graphTimeData(plotTime, baseflow2_series_user, false, Color.DARK_GRAY, false, false, true, 1);
            plotTime = graphTimeData(plotTime, baseflow3_series_user, false, Color.black, false, false, true, 0);
            
        }
        
        //Define Y Axis
        ValueAxis rangeTime = new NumberAxis("Flow [cfs]");
        rangeTime.setRange(0, baseflowMax);
        plotTime.setRangeAxis(0, rangeTime);

        //Define X Axis
        DateAxis domainTime = new DateAxis("Date");
        domainTime.setLowerMargin(0.03);
        domainTime.setUpperMargin(0.03);
        plotTime.setDomainAxis(0, domainTime);

        //Set other graph preferences
        plotTime.setOutlinePaint(Color.black);
        plotTime.setDomainGridlinePaint(Color.black);
        plotTime.setRangeGridlinePaint(Color.black);
        plotTime.setRangeMinorGridlinesVisible(true);
        plotTime.setRangeMinorGridlinePaint(Color.gray);
        setAxisFonts(plotTime);


        //Graph plot onto JfreeChart
        String title = "BFLOW Base-flow Separation For Station: " + stationID + ", " + stationName +  " Agency: " + organizationName;
        JFreeChart parentChart = new JFreeChart(title, durationCurve.titleFont, plotTime, true);


        //Set legend Font
        LegendTitle legendTitle = parentChart.getLegend();
        legendTitle.setItemFont(durationCurve.masterFont);

        //Save resulting graph
        try {
            guiBaseflow_Model model = new guiBaseflow_Model();
            String path = mainFolder + File.separator + model.getGraph();
            ChartUtilities.saveChartAsJPEG(new File(path), parentChart, 1280, 800);
            System.out.println("JFreeChart created properly at: " + path);

        } catch (IOException e) {
            System.out.println("Problem occurred creating chart");
            System.out.println(e);
        }
    }
    /**
     * Reads the output file of BFLOW and reformats it to be used in later functions
     * @param path  the file location of the output file
     * @param fileName  the name of the output file
     * @return  a String[][] of the results of the BFLOW analysis where:
     * the first column contains the year of the date, the second column the month, third column the day, 
     * fourth column the initial streamflow value, the fifth column the 1st baseflow analysis pass, the 
     * sixth column the 2nd baseflow analysis pass, and the seventh column the 3rd baseflow analysis pass
     * @throws IOException
     */
    private String[][] readBFLOWresults(String path) throws IOException{
        //Open a reader for the results file
        FileReader file_to_read = new FileReader(path + "/baseflow.out");
        BufferedReader reader = new BufferedReader(file_to_read);

        String currentLine;
        int ctr = 0;
        ArrayList<String> resultList = new ArrayList<String>();

        //Read out the contents of the results file
        while((currentLine = reader.readLine()) !=null){
            if(ctr >= 2){//Ignore the first two rows of headers
                String[] columns = currentLine.split(",");
                String year = columns[0];
                String month = columns[1];
                String day = columns[2];

                if(!year.equalsIgnoreCase("   0") && !month.equalsIgnoreCase(" 0") && !day.equalsIgnoreCase(" 0")){
                    resultList.add(currentLine);
                    //System.out.println(currentLine);
                }
            }
            ctr++;
        }
        reader.close();


        String[][] resultArray = new String[resultList.size()][7];
        Iterator<String> iterate = resultList.iterator();
        ctr = 0;
        while(iterate.hasNext()){
            String[] columns = iterate.next().split(",");
            //System.out.println(ctr + "\t" + currentLine);

            //substring out the results of year, month, day, Streamflow, Baseflow Pass1, Baseflow Pass2, Baseflow Pass3 
            //based on csv columns
            //dates
            String year = columns[0];
            String month = columns[1];
            String day = columns[2];
            
            //flows
            String streamFlow = columns[3];
            String baseFlow1 = columns[4];
            String baseFlow2 = columns[5];
            String baseFlow3 = columns[6];

            //Trim off extra white space characters
            year = year.trim();
            month = month.trim();
            day = day.trim();

            streamFlow = streamFlow.trim();
            baseFlow1 = baseFlow1.trim();
            baseFlow2 = baseFlow2.trim();
            baseFlow3 = baseFlow3.trim();

            //Add values to the result array
            resultArray[ctr][0] = year;
            resultArray[ctr][1] = month;
            resultArray[ctr][2] = day;
            resultArray[ctr][3] = streamFlow;
            resultArray[ctr][4] = baseFlow1;
            resultArray[ctr][5] = baseFlow2;
            resultArray[ctr][6] = baseFlow3;

            ctr++;
        }

        return resultArray;
    }
    /**
     * Graph the hydrograph separation results from "HYSEP
     * @param mainFolder  the file location where the input file is and where graph will be saved
     * @param organizationName  the name of the supervising agency for the current station
     * @param stationID  the station ID of the current station, used for graph labels and error catches
     * @param stationName  the name of the current station, used for graph labels
     * @throws IOException
     */
    public void graphHYSEPresults(String mainFolder, String organizationName, String stationID, String stationName) throws IOException{
        DurationCurve durationCurve = new DurationCurve();


        //Pull out results to be graphed
        String[][] baseFlowResults = readHYSEPresults(mainFolder, ".bsf");//hysep.bsf
        String[][] streamFlowResults = readHYSEPresults(mainFolder, ".sro");//hysep.sro


        //Create TimeSeries to graph of the stream flow, baseflow and total flow to be graphed
        TimeSeries totalFlow_series = new TimeSeries("Total Stream flow");
        TimeSeries streamflow_series = new TimeSeries("Stream flow");
        TimeSeries baseflow_series = new TimeSeries("Base-flow");
        int day = 1, month = 1, year = 1900;
        double d=1, m=1, y=1;
        for(int i=0; i < baseFlowResults.length; i++) {
            double streamFlow = Double.parseDouble(streamFlowResults[i][3]);
            double baseFlow = Double.parseDouble(baseFlowResults[i][3]);

            //Re-format date for graph
            y = Double.parseDouble(baseFlowResults[i][0]);
            m = Double.parseDouble(baseFlowResults[i][1]);
            d = Double.parseDouble(baseFlowResults[i][2]);
            year = (int)y;
            month = (int)m;
            day =  (int)d;
            Day date = new Day(day,month,year);//day,month,year
            streamflow_series.add(date, streamFlow);
            baseflow_series.add(date, baseFlow);
            totalFlow_series.add(date, streamFlow + baseFlow);
        }

        //Graph the baseflow on a timeseries axis
        XYPlot plotTime = new XYPlot();
        plotTime = graphTimeData(plotTime, totalFlow_series, true, Color.black, false, false, true, 2);
//      plotTime = graphTimeData(plotTime, streamflow_series, true, Color.lightGray, false, false, true, 1);
        plotTime = graphTimeData(plotTime, baseflow_series, true, Color.gray, false, false, true, 0);

        //Define Y Axis
        ValueAxis rangeTime = new NumberAxis("Flow [cfs]");
        plotTime.setRangeAxis(0, rangeTime);

        //Define X Axis
        DateAxis domainTime = new DateAxis("Date");
        domainTime.setLowerMargin(0.03);
        domainTime.setUpperMargin(0.03);
        plotTime.setDomainAxis(0, domainTime);

        //Set other graph preferences
        setAxisFonts(plotTime);


        //Graph plot onto JfreeChart
        String title = "USGS-HYSEP Hydrograph Separation For Station: " + stationID + ", " + stationName +  " By: " + organizationName;
        JFreeChart parentChart = new JFreeChart(title, durationCurve.titleFont, plotTime, true);


        //Set legend Font
        LegendTitle legendTitle = parentChart.getLegend();
        legendTitle.setItemFont(durationCurve.masterFont);


        //Save resulting graph
        try {
            String path = mainFolder + "/baseflow_graph.jpg";
            ChartUtilities.saveChartAsJPEG(new File(path), parentChart, 1280, 800);
            System.out.println("JFreeChart created properly at: " + path);

        } catch (IOException e) {
            System.out.println("Problem occurred creating chart");
            System.out.println(e);
        }
    }
    /**
     * Reads one of the WATSTORE formated output files of HYSEP and reformats it into a daily timeseries to be used in later functions
     * @param mainFolder  the file location of the output file
     * @param fileType  the type of HYSEP output file being read (".bsf" for baseflow or ".sro" for stream flow)
     * @return  a String[][] of the results of the HYSEOP analysis where:
     * the first column contains the year of the date, the second column the month, third column the day, 
     * fourth column the values of the file (if the file is .bsf then baseflow values, if the file is .sro then streamflow values)
     * @throws IOException
     */
    private String[][] readHYSEPresults(String mainFolder, String fileType) throws IOException{
        //Open a reader for the results file
        FileReader file_to_read = new FileReader(mainFolder + "/baseflow" + fileType);
        BufferedReader reader = new BufferedReader(file_to_read);

        String currentLine;
        int ctr=0, dayCtr=1;
        ArrayList<String> yearList = new ArrayList<String>();
        ArrayList<String> monthList = new ArrayList<String>();
        ArrayList<String> dayList = new ArrayList<String>();
        ArrayList<String> flowList = new ArrayList<String>();

        //Read out the contents of the results file
        while((currentLine = reader.readLine()) !=null){
            if(ctr >= 1){//Ignore the first row header
                //System.out.println(currentLine);

                //substring out the results of year, month, day, Streamflow, Baseflow Pass1, Baseflow Pass2, Baseflow Pass3 
                //based on fixed width columns
                String year = currentLine.substring(16,20).trim();
                String month = currentLine.substring(20,22).trim();
                String week = currentLine.substring(22,24).trim();

                //Loop through the 'week' and pull out the values for each day
                int numberOfDays = checkMonthYear(year, month, week);
                for(int i=1; i<=numberOfDays; i++){
                    String dayFlow = currentLine.substring(24 + (i-1)*7, 24 + i*7).trim();

                    //Add the day's flow value to the list
                    yearList.add(year);
                    monthList.add(month);
                    dayList.add(String.valueOf(dayCtr));
                    flowList.add(dayFlow);
                    dayCtr++;
                }
                if(Integer.parseInt(week) == 4){
                    dayCtr=1;
                }
            }
            ctr++;
        }
        reader.close();


        String[][] resultArray = new String[dayList.size() - 1][4];//The minus one is there because if 10 days are given only 9 will have baseflow calculations because the 10th day is the calculation for the 9th day
        for(int i=0; i<resultArray.length; i++){
            //System.out.println(i + "\t" + currentLine);

            //Add values to the result array
            resultArray[i][0] = yearList.get(i);
            resultArray[i][1] = monthList.get(i);
            resultArray[i][2] = dayList.get(i);
            resultArray[i][3] = flowList.get(i);
        }

        return resultArray;
    }
    /**
     * Checks based on the year, month, and week how many days are in the current 'week' of the WATSTORE (.gsd) file
     * @param year  the current year
     * @param month  the current month
     * @param week  the current WATSTORE file 'week'
     * @return  an integer number of days in the current 'week' of the WATSTORE (.gsd) file
     */
    private int checkMonthYear(String year, String month, String week){
        //If week = 1, 2, or 3 it has 8 days
        if(Integer.parseInt(week) < 4){
            return 8;
        }

        //If week = 4 then its more complicated
        int numberOfDays = 0;
        switch(Integer.parseInt(month)){//Determine the number of days per the 4th 'week' in each month (gsd/WATSTORE files use an 8 or less day week to allow each month to have exactly 4 'weeks' keeping a consistent number of lines per year in the file)
            case 1:  numberOfDays = 7;	break;
            case 2:  numberOfDays = checkLeapYear(Integer.parseInt(year));break;
            case 3:  numberOfDays = 7;	break;
            case 4:  numberOfDays = 6;	break;
            case 5:  numberOfDays = 7;	break;
            case 6:  numberOfDays = 6;	break;
            case 7:  numberOfDays = 7;	break;
            case 8:  numberOfDays = 7;	break;
            case 9:  numberOfDays = 6;	break;
            case 10: numberOfDays = 7;	break;
            case 11: numberOfDays = 6;	break;
            case 12: numberOfDays = 7;	break;
            default:                    break;
        }

        return numberOfDays;
    }
    /**
     * Determines if the provided year (as an integer) is a leap year or not taking into 
     * account for leap years every 4 years, not every 100 years, and leap years every 
     * 400 years (this has to do with round off errors in the length of a day that propagate 
     * over time).  If support for >400 year leap year information is desired modify this subfunction.
     * @param currentYear  the current year
     * @return  an integer number of days in the forth 'week' of february (a WATSTORE file 'week')
     */
    private int checkLeapYear(int currentYear){
        //Determine how many days February should have based on if it is a leap year or not
        boolean leapYear = false;
        double currentYear_db = (double) currentYear;

        //Determine if this year is a leap year (divide by 4) and take into account every 100 years it is not a leap year and every 400 it is
        double yearUp4		= Math.ceil(currentYear_db/4);
        double yearDown4	= Math.floor(currentYear_db/4);
        double yearUp100	= Math.ceil(currentYear_db/100);
        double yearDown100	= Math.floor(currentYear_db/100);
        double yearUp400	= Math.ceil(currentYear_db/400);
        double yearDown400	= Math.floor(currentYear_db/400);
        if(yearUp400 == yearDown400){
            leapYear = true;
        }else if(yearUp100 == yearDown100){
            leapYear = false;
        }else if(yearUp4 == yearDown4){
            leapYear = true;
        }

        //Based on the leap year or not determine the number of days in February
        int feb = 4;
        if(leapYear){
            feb = 5;
        }
        return feb;
    }
    /**
     * Graphs the TimeSeries on to the provided plot with the below properties for rendering, color, series index
     * @param plot  The XYPlot to graph the XYSeries on
     * @param currentLine  the XYSeries containing the x,y points of the current line series
     * @param lineTrue  a boolean, true if the series is to have lines connecting the points, otherwise no line
     * @param seriesColor  The desired color of the series (Java.Color)
     * @param dashedLine  if true then the XYSeries will have a dashed line instead of a solid one, if false it will be the normal solid line
     * @param shortDash  if true a dashed line like the major grid lines will be drawn, otherwise a long dashed line will be used
     * @param visibleInLegend  if true then the XYSeries' name will be visible in the legend, if false this XYSeries' name will not be visible in the legend
     * @param graphIndex  the series index for the current line, this should be incrementally increased 
     * each time this function is call so that the new dataset does not replace the old one
     * @return the XYPlot with the added TimeSeries
     */
    private XYPlot graphTimeData(XYPlot plot, 
                                TimeSeries currentLine, 
                                boolean lineTrue, 
                                Color seriesColor, 
                                boolean dashedLine,
                                boolean shortDash,
                                boolean visibleInLegend,
                                int graphIndex){
        //Create Line Data and renderer
        TimeSeriesCollection currentDataset = new TimeSeriesCollection(currentLine);

        //Check if this series should have a dashed line or solid line and change the renderer accordingly

        XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(lineTrue, !lineTrue);
        currentRenderer.setSeriesPaint(0, seriesColor);
        currentRenderer.setSeriesVisibleInLegend(0, visibleInLegend);

        //Check if this series should have a dashed line, if so change the renderer
        if(dashedLine){
            if(shortDash){
                //Change the renderer's stroke to a short dashed line
                currentRenderer.setSeriesStroke(0, 
                    new BasicStroke(
                        1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
                        1.0f, new float[] {1.0f, 2.0f}, 0.0f
                ));
            }else{
            //Change the renderer's stroke to a long dashed line
                currentRenderer.setSeriesStroke(0, 
                    new BasicStroke(
                        1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
                        1.0f, new float[] {6.0f, 6.0f}, 0.0f
                    ));
            }
        }

        //Put the LDC line data, renderer, and axis into plot
        plot.setDataset(graphIndex, currentDataset);
        plot.setRenderer(graphIndex, currentRenderer);

        //Put the line on the first Domain and first Range
        plot.mapDatasetToDomainAxis(0, 0);
        plot.mapDatasetToRangeAxis(0, 0);

        return plot;
    }
    /**
     * This sub-function takes the provided plot and changes the fonts of the axis to a standardized new font
     * @param plot  the XYPlot containing the graph on which the fonts will be changed
     * @return the original plot with the modifications to the axis fonts
     */
    private XYPlot setAxisFonts(XYPlot plot){
        DurationCurve durationCurve = new DurationCurve();

        //Set Y axis fonts
        ValueAxis yAxis = plot.getRangeAxis();
        yAxis.setLabelFont(durationCurve.masterFont);
        yAxis.setTickLabelFont(durationCurve.masterFont);

        //Set X axis fonts
        DateAxis xAxis = (DateAxis) plot.getDomainAxis();
        xAxis.setLabelFont(durationCurve.masterFont);
        xAxis.setTickLabelFont(durationCurve.masterFont);	

        return plot;
    }
}