StandardStepMethod.java [src/java/m/hydraulics/standardstep] Revision: 4fc13d47e981f11a30d3ae7c1afae3532c21bb12  Date: Fri Feb 05 10:16:38 MST 2016
package m.hydraulics.standardstep;

import hydraulics.ChannelGeometry;
import hydraulics.GeneralFunctions;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
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.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

/**
* Last Updated: 5-February-2016
* @author Chris Dumler
* @since 22-June-2015
*/
public class StandardStepMethod {
    //Inputs
    String mainFolder = "U:/Java Projects/StandardStepMethod/RegularGeometryTest";
    boolean irregularGeometry = false;
    boolean downstreamCalculation = true;
    boolean useThalwegElevation = true;
    boolean createGraph = true;
    boolean createSummaryFile = true;
    double measuredDownstreamDepth = 6.369;
    double discharge_DS = 200;
    
    //Uniform Geometry Cross-Section Characteristics
    double bottomWidth = 10;
    double sideSlope = 2;

    //Uniform Geometry Reach Characteristics
    double manning_n = .035;
    double bedSlope = 0.0004;
    double seepage = 0.0;
    double channelLength = 40000; //Distance between sample x-sections
    int numberOfCrossSections = 200; //Length between calculated x-section
    
    //Sets
    public void setBottomWidth(double bottomWidth){
        this.bottomWidth = bottomWidth;
    }
    public void setSideSlope(double sideSlope){
        this.sideSlope = sideSlope;
    }
    public void setDepth_DS(double measuredDownstreamDepth){
        this.measuredDownstreamDepth = measuredDownstreamDepth;
    }
    public void setDischarge_DS(double discharge_DS){
        this.discharge_DS = discharge_DS;
    }
    public void setManningRoughness(double manning_n){
        this.manning_n = manning_n;
    }
    public void setBedSlope(double bedSlope){
        this.bedSlope = bedSlope;
    }
    public void setSeepage(double seepage){
        this.seepage = seepage;
    }
    public void setChannelLength(double channelLength){
        this.channelLength = channelLength;
    }
    public void setNumberOfCrossSections(int numberOfCrossSections){
        this.numberOfCrossSections = numberOfCrossSections;
    }
    public void setMainFolder(String mainFolder){
        this.mainFolder = mainFolder;
    }
    public void setIrregularGeometry(boolean irregularGeometry){
        this.irregularGeometry = irregularGeometry;
    }
    public void setDownstreamCalculation(boolean downstreamCalculation){
        this.downstreamCalculation = downstreamCalculation;
    }
    public void setUseThalwegElevation(boolean useThalwegElevation){
        this.useThalwegElevation = useThalwegElevation;
    }
    public void setCreateGraph(boolean createGraph){
        this.createSummaryFile = createGraph;
    }
    public void setCreateSummaryFile(boolean createSummaryFile){
        this.createGraph = createSummaryFile;
    }
    
    //gets
    public String getElevationGraphTitle(){
        if (downstreamCalculation){
            return "Downstream Elevation Profile";
        }
        else{
            return "Upstream Elevation Profile";
        }
    }
    public String getElevationGraph(){
        if (downstreamCalculation){
            String title = "_" + measuredDownstreamDepth + "m_StartDepth_DownstreamElevationProfile.jpg";
            return title;
        }
        else{
            String title = "_" + measuredDownstreamDepth + "m_StartDepth_UpstreamElevationProfile.jpg";
            return title;
        }
    }
    
    public File getElevationGraphOutput(){
        if (downstreamCalculation){
            String title = "_" + measuredDownstreamDepth + "m_StartDepth_DownstreamElevationProfile.out";
            return new File(mainFolder, title);
        }
        else{
            String title = "_" + measuredDownstreamDepth + "m_StartDepth_UpstreamElevationProfile.out";
            return new File(mainFolder, title);
        }
    }
    public String getSummaryTitle(){
        if (downstreamCalculation){
            return " Downstream Summary Data";
        }
        else{
            return " Upstream Summary Data";
        }
    }
    public String getSummary(){
        if (downstreamCalculation){
            String title = "_" + measuredDownstreamDepth + "m_StartDepth_DownstreamData.txt";
            return title;
        }
        else{
            String title = "_" + measuredDownstreamDepth + "m_StartDepth_UpstreamData.txt";
            return title;
        }
    }
    /**
     * Calculates the water depth at each subsequent cross section using the
     * Standard Step Equation.
     * For irregular channel geometries, uses xy-point cross sections and channel
     * reach characteristics provided by text files in destination folder.
     * Cross-sections must be formatted as below:
     *      (0|100)(20|100)(30|95)(40|90)(50|90)(60|95)(70|100)(90|100)
     *      (0|100)(20|99.96)(35|94.96)(50|89.96)(65|89.96)(80|94.96)(95|99.96)(115|100)
     *      Note: Each pair of numbers within a set of parentheses represents an xy-point
     *      along the cross section. The y-point can be used to determine elevation and
     *      bed slope.
     * Reach data must be formatted as below:
     *      100|.035|.05|0
     *      Note: The first number is the reach length, the second is manning's n, the
     *      third is the seepage, and the forth is the reach slope. This is optional as it 
     *      can also be determined using the thalweg elevation from the given cross-sections.
     * For uniform-trapezoidal channels, the channel variables can be set using the
     * appropriate 'set' methods and the graphs and summary file will be created in
     * the designated folder.
     * 
     * Returns a graph of the longitudinal depth and elevation profiles as well as a text
     * file containing all the important information for each cross-section and reach. For irregular channels, the graphs 
     * and summary file are created in the same folder that contains the 'reaches' and 'crossSections'
     * text files. For uniform channels, the graphs and summary file are created in the designated folder.
     * 
     * @throws IOException
     * 
     */
    public void run() throws IOException {
        
        int numberOfReaches;
        if (irregularGeometry) {
            numberOfReaches = readLines(mainFolder + File.separator + "reaches.txt");
        }
        else{
            numberOfReaches = numberOfCrossSections-1;
        }

        double[][][] crossSectionResultsArray = new double[numberOfReaches + 1][2][4];
        double[][] reachResultsArray = new double[numberOfReaches + 1][2];
        
        double [][][] crossSectionGeometries;
        double [][] reachProperties;
        if (irregularGeometry) {
            crossSectionGeometries = setCrossSectionGeometries();
            reachProperties = setReachProperties();
        }
        else{
            crossSectionGeometries = new double[numberOfReaches+1][1][1];
            reachProperties = new double[numberOfReaches+1][1];
        }
        
        double downstreamDistance = 0;
        double bedElevation = 0;
        double depth_DS = measuredDownstreamDepth;
        
        for (int currentReach = 0; currentReach < numberOfReaches; currentReach++) {
            
            double[][] currentCrossSectionGeometry_DS = crossSectionGeometries[currentReach];
            double[][] currentCrossSectionGeometry_US = crossSectionGeometries[currentReach+1];
            double[] currentReachProperties = reachProperties[currentReach];
            
            double length;
            double reachSlope;
            double currentReachSeepage;
            
            if (irregularGeometry) {
                length = currentReachProperties[0];
                currentReachSeepage = currentReachProperties[2];
                if(useThalwegElevation){
                    reachSlope = calcThalwegElevationSlope(currentReach);
                }
                else{
                    reachSlope = currentReachProperties[3];
                }
            } else {
                length = channelLength/numberOfReaches;
                currentReachSeepage = seepage;
                reachSlope = bedSlope;
            }
            
            
            if (downstreamCalculation == false){
                length = -length;
            }
            
            ChannelGeometry DSValues = setDSValues(currentCrossSectionGeometry_DS,
                                                    currentReachProperties,
                                                    reachSlope,
                                                    depth_DS);
            double velocity_DS = calcVelocity(DSValues);
            double frictionSlope_DS = calcFrictionSlope(DSValues);
            double critDepth_DS = DSValues.calcCriticalDepth();
            double normDepth_DS = DSValues.calcNormalDepth();
            
            ChannelGeometry USValues = setUSValues(currentCrossSectionGeometry_US,
                                                    currentReachProperties,
                                                    length,
                                                    reachSlope,
                                                    depth_DS);
            double critDepth_US = USValues.calcCriticalDepth();
            double normDepth_US = USValues.calcNormalDepth();
            
            double[] bounds = calcBoundaryConditions(depth_DS, critDepth_US, normDepth_US);
            double lower = bounds[0];
            double upper = bounds[1];
            
            USValues = setUSValues(currentCrossSectionGeometry_US,
                                    currentReachProperties,
                                    length,
                                    reachSlope,
                                    lower);
            double velocity_US = calcVelocity(USValues);
            double frictionSlope_US = calcFrictionSlope(USValues);
            double err_lower = standardStepEquation(velocity_DS, 
                                                        velocity_US,
                                                        frictionSlope_DS, 
                                                        frictionSlope_US,
                                                        depth_DS,
                                                        lower,
                                                        reachSlope,
                                                        length);
            
            USValues = setUSValues(currentCrossSectionGeometry_US,
                                    currentReachProperties,
                                    length,
                                    reachSlope,
                                    upper);
            velocity_US = calcVelocity(USValues);
            frictionSlope_US = calcFrictionSlope(USValues);
            double err_upper = standardStepEquation(velocity_DS, 
                                                        velocity_US,
                                                        frictionSlope_DS,
                                                        frictionSlope_US,
                                                        depth_DS,
                                                        upper,
                                                        reachSlope,
                                                        length);

            double error = 1000;
            double guessDepth_US = (lower + upper) / 2;
            int ctr = 0;
            if (err_lower > 0 && err_upper < 0) {
                while (Math.abs(error) > .0001 && ctr < 100) {
                    
                    guessDepth_US = (lower + upper) / 2;
                    USValues = setUSValues(currentCrossSectionGeometry_US,
                                    currentReachProperties,
                                    length,
                                    reachSlope,
                                    guessDepth_US);
                    velocity_US = calcVelocity(USValues);
                    frictionSlope_US = calcFrictionSlope(USValues);
                    
                    error = standardStepEquation(velocity_DS, velocity_US,
                            frictionSlope_DS, frictionSlope_US,
                            depth_DS, guessDepth_US,
                            reachSlope,
                            length);
                    if (error <= 0) {
                        upper = guessDepth_US;
                    } else {
                        lower = guessDepth_US;
                    }
                    ctr = ctr + 1;
                }
            }
            else if (err_lower < 0 && err_upper > 0) {
                while (Math.abs(error) > .0001 && ctr < 100) {
                    
                    guessDepth_US = (lower + upper) / 2;
                    USValues = setUSValues(currentCrossSectionGeometry_US,
                                    currentReachProperties,
                                    length,
                                    reachSlope,
                                    guessDepth_US);
                    velocity_US = calcVelocity(USValues);
                    frictionSlope_US = calcFrictionSlope(USValues);
                    
                    error = standardStepEquation(velocity_DS, velocity_US,
                            frictionSlope_DS, frictionSlope_US,
                            depth_DS, guessDepth_US,
                            reachSlope,
                            length);
                    if (error >= 0) {
                        upper = guessDepth_US;
                    } else {
                        lower = guessDepth_US;
                    }
                    ctr = ctr + 1;
                }
            } 
            else {
                guessDepth_US = (lower + upper) / 2;
                System.out.println("Could not solve standard step equation " + downstreamDistance + " m downstream." );
            }
            
            
            
            

            if (currentReach == 0) {
                crossSectionResultsArray[currentReach] = writeCrossSectionResultsArray(depth_DS,
                                                                    critDepth_DS,
                                                                    normDepth_DS,
                                                                    downstreamDistance,
                                                                    bedElevation,
                                                                    discharge_DS);

                downstreamDistance = downstreamDistance + length;
                bedElevation = bedElevation - (reachSlope * length);
                discharge_DS = discharge_DS + (currentReachSeepage*length);
                
                crossSectionResultsArray[currentReach + 1] = writeCrossSectionResultsArray(guessDepth_US,
                                                                    critDepth_US,
                                                                    normDepth_US,
                                                                    downstreamDistance,
                                                                    bedElevation,
                                                                    discharge_DS);
                reachResultsArray[currentReach] = writeReachResultsArray(length,
                                                            reachSlope);

            } 
            else {
                downstreamDistance = downstreamDistance + length;
                bedElevation = bedElevation - (reachSlope * length);
                discharge_DS = discharge_DS + (currentReachSeepage*length);
                
                crossSectionResultsArray[currentReach + 1] = writeCrossSectionResultsArray(guessDepth_US,
                                                                        critDepth_US,
                                                                        normDepth_US,
                                                                        downstreamDistance,
                                                                        bedElevation,
                                                                        discharge_DS);
                reachResultsArray[currentReach] = writeReachResultsArray(length,
                                                            reachSlope);
                }
            
            depth_DS = guessDepth_US;
            
        } //end for statement
        if (createGraph){
        graphLongitudinalProfile(crossSectionResultsArray,
                numberOfReaches,
                getElevationGraphTitle(),
                getElevationGraph(),
                "Water Surface Elevation",
                "Critical Depth",
                "Normal Depth",
                "Channel Bed Elevation",
                "Elevation (m)");
        }
        if (createSummaryFile){
            writeSummaryFile(crossSectionResultsArray,
                             reachResultsArray);
        }
        
    }//end calcStandardStepMethod
    
    /**
     * Calculates the max and min water elevations to plug into the Standard Step
     * Equation based on the water surface profile.
     * 
     * @param depth_DS The depth at the downstream cross-section.
     * @param criticalDepth The critical depth at the upstream cross-section.
     * @param normalDepth The normal depth at the upstream cross-section.
     * 
     * @return An array containing the minimum(a) and maximum(b) water depths.
     * 
     * @throws IOException
     */
    public double[] calcBoundaryConditions(double depth_DS,
                                            double criticalDepth,
                                            double normalDepth) throws IOException {

        String waterSurfaceProfile;
        double a = .01;
        double b = 999;

        if (normalDepth > criticalDepth && normalDepth < 900) { //test if slope is mild
            if (depth_DS < criticalDepth) {
                waterSurfaceProfile = "M3";
                a = 0.01;
                b = criticalDepth;
            } else if (depth_DS > normalDepth) {
                waterSurfaceProfile = "M1";
                a = normalDepth;
                b = (2.0 * depth_DS);
            } else {
                waterSurfaceProfile = "M2";
                a = criticalDepth;
                b = normalDepth;
            }
        } else if (normalDepth < criticalDepth) {//test if slope is steep
            if (depth_DS < normalDepth) {
                waterSurfaceProfile = "S3";
                a = .01;
                b = normalDepth;
            } else if (depth_DS > criticalDepth) {
                waterSurfaceProfile = "S1";
                a = criticalDepth;
                b = (2.0 * depth_DS);
            } else {
                waterSurfaceProfile = "S2";
                a = normalDepth;
                b = criticalDepth;
            }
        } else if (Math.abs(normalDepth - criticalDepth) < .001) {//test if slope is critical
            if (depth_DS < criticalDepth) {
                waterSurfaceProfile = "C3";
                a = .01;
                b = criticalDepth;
            } else {
                waterSurfaceProfile = "C1";
                a = criticalDepth;
                b = (2.0 * depth_DS);
            }
        } else if (normalDepth > 100 * depth_DS) { //test if slope is horizontal
            if (depth_DS < criticalDepth) {
                waterSurfaceProfile = "H3";
                a = .01;
                b = criticalDepth;
            } else {
                waterSurfaceProfile = "H2";
                a = criticalDepth;
                b = normalDepth;
            }
        } else {
            waterSurfaceProfile = "undefined";

        } //end if
        double[] boundaries = new double[2];
        boundaries[0] = a;
        boundaries[1] = b;
        return boundaries;
    }//end calcBoundaryConditions

    /**
     * Creates a 2-dimensional array containing the calculated depth values for the current
     * cross-section. This array is then imported into a 3-dimensional array containing the 
     * depth values for every cross-section in a given channel.
     * 
     * @param waterDepth The calculated depth at the current cross-section.
     * @param critDepth The calculated critical depth at the given cross-section.
     * @param normDepth The calculated normal depth at the given cross-section.
     * @param distance_DS The distance of the given cross-section from the origin.
     * @param bedElevation The elevation of the channel bed above or below the origin.
     * 
     * @return A 2-dimensional array containing the calculated depth values for 
     * the current cross-section.
     */
    public double[][] writeCrossSectionResultsArray(double waterDepth,
                                        double critDepth,
                                        double normDepth,
                                        double distance_DS,
                                        double bedElevation,
                                        double discharge){
        
        double[][] results = new double[2][4];
        results[0][0] = distance_DS;
        results[0][1] = discharge;
        results[1][0] = waterDepth;
        results[1][1] = critDepth;
        results[1][2] = normDepth;
        results[1][3] = bedElevation;
        
        return results;
    }//end writeDepthArray
    
    public double[] writeReachResultsArray(double length,
                                        double slope){
        double[] results = new double[2];
        results[0] = length;
        results[1] = slope;
        return results;
    }
    
    /**
     * Determines the number of lines in a given text file (including blank lines).
     * 
     * @param file_path The location of the 'crossSections' or 'reaches' text file.
     * 
     * @return The number of lines in the text file.
     * 
     * @throws IOException
     */
    public int readLines(String file_path) throws IOException {
        
        int numberOfLines = 0;
        
        try{
            String aLine;
            FileReader fr = new FileReader(file_path);
            BufferedReader bf = new BufferedReader(fr);
            while ((aLine = bf.readLine()) != null) {
                numberOfLines++;
            }
            bf.close();
        }
        catch(IOException e){
            System.out.println(e.getMessage());
        }
        
        return numberOfLines;
    }//end readLines
    
    /**
     * Transfers a text file to a one 1-dimensional array. Each line of the text file
     * is written to its corresponding array location.
     * 
     * @param file_path The location of the 'crossSections' or 'reaches' text file.
     * 
     * @return A 1-dimensional array containing every line in a given text file.
     * 
     * @throws IOException
     */
    public String[] transferTXTFile(String file_path) throws IOException {
        
        String[] crossSections = new String[0];
        
        try{
            FileReader fr = new FileReader(file_path);
            BufferedReader textReader = new BufferedReader(fr);
            int numberOfLines = readLines(file_path);
            crossSections = new String[numberOfLines];
            int i;
            for (i = 0; i < numberOfLines; i++) {
                crossSections[i] = textReader.readLine();
            }
            textReader.close();
        }
        catch(IOException e){
            System.out.println(e.getMessage());
        }
        
        return crossSections;
    }//end transferTXTFile
    
    /**
     * Determines the largest number of xy-points in any one of the provided cross-sections. 
     * This value is used to create the cross-sections array.
     * 
     * @return The largest number of points in any of the given cross-sections.
     * 
     * @throws IOException
     */
    public int calcLargestCrossSection() throws IOException {
        String[] sections = transferTXTFile(mainFolder + File.separator + "crossSections.txt");
        int numberOfLines = readLines(mainFolder + File.separator + "crossSections.txt");

        int i;
        int n;
        int m = 0;
        for (i = 0; i < numberOfLines; i++) {
            String[] xypoints = sections[i].split("\\(");
            n = xypoints.length;
            if (n > m) {
                m = n;
            }
        }
        m = m - 1;
        return m;
    }//end calcLargestCrossSection
    
    /**
     * Splits the xy-point strings to creates a 3-dimensional array containing 
     * all the x and y points for each cross-section in a given channel.
     * 
     * @return A 3-dimensional array containing all x and y points for every given cross-section
     * along the channel length.
     * 
     * @throws IOException
     */
    public double[][][] setCrossSectionGeometries() throws IOException {
        int numberOfSections = readLines(mainFolder + File.separator + "crossSections.txt");
        String[] sections = transferTXTFile(mainFolder + File.separator + "crossSections.txt");
        int length = calcLargestCrossSection();
        String[][] xypointlist;
        int i;
        double[][][] dimensionalArray = new double[numberOfSections][2][length];
        for (i = 0; i < numberOfSections; i++) {
            String crossSection = sections[i];
            String newCrossSection = crossSection.replaceAll("\\)", "");
            String[] xypoints = newCrossSection.split("\\(");
            double[][] xyPoints_new = new double[length][2];
            int j;
            for (j = 1; j < xypoints.length; j++) {
                String[] xy = xypoints[j].split("\\|");
                xyPoints_new[j - 1][0] = Double.parseDouble(xy[0]);
                xyPoints_new[j - 1][1] = Double.parseDouble(xy[1]);
            }
            dimensionalArray[i] = xyPoints_new;
        }
        return dimensionalArray;
    }//end setCrossSectionGeometries
    
    /**
     * Splits the reach property strings to creates a 2-dimensional array containing 
     * all the reach properties for every reach along the channel length.
     * 
     * @return A 2-dimensional array containing all the reach properties for every 
     * reach along the channel length.
     * 
     * @throws IOException
     */
    public double[][] setReachProperties() throws IOException {
        int numberOfSections = readLines(mainFolder + File.separator + "reaches.txt");
        String[] reaches = transferTXTFile(mainFolder + File.separator + "reaches.txt");
        double[][] reachProperties = new double[numberOfSections][4];
        int i;
        for (i = 0; i < numberOfSections; i++) {
            String reach = reaches[i];
            String[] new_reach = reach.split("\\|");
            if(useThalwegElevation){
                for (int j = 0; j < 3; j++) {
                    reachProperties[i][j] = Double.parseDouble(new_reach[j]);
                }
            }
            else{
                for (int j = 0; j < 4; j++) {
                    reachProperties[i][j] = Double.parseDouble(new_reach[j]);
                }
            }
            }
        return reachProperties;
    }//end setReachProperties
    
    /**
     * Calculates the velocity at the current cross-section using the calculated area
     * and discharge.
     * 
     * @param values The ChannelGeometry object containing the correct values for current
     * cross-section. Created by the 'setUSValues' and 'setDSValues' methods.
     * 
     * @return The water velocity at the current cross-section.
     */
    public double calcVelocity(ChannelGeometry values) { //Need to move to ChannelGeomety Class?

        if (irregularGeometry) {
            double area = values.calcIrregularArea();
            double velocity = values.discharge / area;
            return velocity;
        } else {
            double area = values.calcTrapezoidalArea();
            double velocity = values.discharge / area;
            return velocity;
        }
        
    }//end calcVelocity
    
    /**
     * Calculates the friction slope at the current cross-section using the calculated area,
     * hydraulic radius, manning's n, and discharge.
     * 
     * @param values The ChannelGeometry object containing the correct values for current
     * cross-section. Created by the 'setUSValues' and 'setDSValues' methods.
     * 
     * @return The water friction slope at the current cross-section.
     */
    public double calcFrictionSlope(ChannelGeometry values) {

        if (irregularGeometry) {
            double area = values.calcIrregularArea();
            double hydrRadius = values.calcIrregularHydraulicRadius();
            double frictionSlope = Math.pow(((values.n * values.discharge)
                    / (area * Math.pow(hydrRadius, 2. / 3.))), 2.);
            return frictionSlope;
        } else {
            double area = values.calcTrapezoidalArea();
            double hydrRadius = values.calcTrapezoidalHydraulicRadius();
            double frictionSlope = Math.pow(((values.n * values.discharge)
                    / (area * Math.pow(hydrRadius, 2. / 3.))), 2.);
            return frictionSlope;
        }
    }//end calcFrictionSlope
    
    /**
     * Calculates the Thalweg Elevation of the current cross-section using the
     * elevation data from the xy-point cross-section geometries. Then uses the
     * thalweg elevation from the up and downstream cross-sections to determine the
     * average slope between the two cross-sections.
     * 
     * @param currentReach The reach being calculated by the current iteration of the 
     * Standard Step Method.
     * 
     * @return The reach slope based on the thalweg elevations of the up and downstream
     * cross-sections.
     * 
     * @throws IOException
     */
    public double calcThalwegElevationSlope(int currentReach)throws IOException {
        GeneralFunctions general = new GeneralFunctions();
        double[][][] geometries = setCrossSectionGeometries();
        double[][] properties = setReachProperties();
        
        double[][] crossSection1 = geometries[currentReach];
        double thalwegElevation1 = general.min(general.getColumn(crossSection1, 1));
        
        double[][] crossSection2 = geometries[currentReach + 1];
        double thalwegElevation2 = general.min(general.getColumn(crossSection2, 1));
        
        double currentReachLength = properties[currentReach][0];
        
        double slope = (thalwegElevation2 - thalwegElevation1)/currentReachLength;
        return slope;
    }//end calcThalwegElevation
    
    /**
     * Creates a new ChannelGeometry object and sets the ChannelGeometry variables
     * to the values of the cross-section downstream of the current reach.
     * 
     * @param currentCrossSectionGeometry_DS An array containing the xy-points for the
     * downstream cross-section.
     * @param currentReachProperties An array containing the properties of the current reach.
     * @param bedSlope
     * @param depth The calculated depth of the downstream cross-section.
     * 
     * @return A new ChannelGeometry object with the values set for the downstream cross-section.
     * 
     * @throws IOException 
     */    
    public ChannelGeometry setDSValues(double[][] currentCrossSectionGeometry_DS,
                                            double[] currentReachProperties,
                                            double bedSlope,
                                            double depth) throws IOException {
        
        ChannelGeometry DSValues = new ChannelGeometry();
        DSValues.setGeometryType(irregularGeometry);

        if (irregularGeometry) {
            DSValues.setXYPoints(currentCrossSectionGeometry_DS);
            DSValues.setDepth(depth);
            DSValues.setDischarge(discharge_DS);
            DSValues.setManningRoughness(currentReachProperties[1]);
            DSValues.setBedSlope(bedSlope);
        } else {
            DSValues.setBottomWidth(bottomWidth);
            DSValues.setSideSlope(sideSlope);
            DSValues.setDepth(depth);
            DSValues.setDischarge(discharge_DS);
            DSValues.setManningRoughness(manning_n);
            DSValues.setBedSlope(bedSlope);
        }
        return DSValues;
    }//end setDSValues
    
    /**
     * Creates a new ChannelGeometry object and sets the ChannelGeometry variables
     * to the values of the cross-section upstream of the current reach.
     * 
     * @param currentCrossSectionGeometry_US An array containing the xy-points for the
     * upstream cross-section.
     * @param currentReachProperties An array containing the properties of the current reach.
     * @param currentReachLength The distance between the up and downstream cross-sections.
     * @param bedSlope
     * @param depth The calculated depth of the upstream cross-section.
     * 
     * @return A new ChannelGeometry object with the values set for the upstream cross-section.
     * 
     * @throws IOException 
     */ 
    public ChannelGeometry setUSValues(double[][] currentCrossSectionGeometry_US,
                                            double[] currentReachProperties,
                                            double currentReachLength,
                                            double bedSlope,
                                            double depth) throws IOException {
        
        ChannelGeometry USValues = new ChannelGeometry();
        USValues.setGeometryType(irregularGeometry);

        if (irregularGeometry) {
            USValues.setXYPoints(currentCrossSectionGeometry_US);
            USValues.setDepth(depth);
            double currentReachSeepage = currentReachProperties[2];
            USValues.setDischarge(discharge_DS+(currentReachSeepage*currentReachLength));
            USValues.setManningRoughness(currentReachProperties[1]);
            USValues.setBedSlope(bedSlope);
        } else {
            USValues.setBottomWidth(bottomWidth);
            USValues.setSideSlope(sideSlope);
            USValues.setDepth(depth);
            USValues.setDischarge(discharge_DS+(seepage*currentReachLength));
            USValues.setManningRoughness(manning_n);
            USValues.setBedSlope(bedSlope);
        }
        return USValues;
    }//end setUSValues
    
    /**
     * Calculates the standard step equation using the predetermined values for the downstream
     * cross-section and guess values for the upstream cross-section based on the set boundaries.
     * 
     * @param velocity_DS The calculated velocity at the downstream cross-section.
     * @param velocity_US The calculated velocity at the upstream cross-section.
     * @param frictionSlope_DS The calculated friction slope at the downstream cross-section. 
     * @param frictionSlope_US The calculated friction slope at the upstream cross-section.
     * @param measuredDownstreamDepth The calculated depth at the downstream cross-section.
     * @param depth_US The calculated depth at the upstream cross-section.
     * @param bedSlope The bed slope between the up and downstream cross-sections.
     * @param length The distance between the up and downstream cross sections.
     * 
     * @return An error value based on the guessed upstream depth value. The closer the error is to zero,
     * the closer the guess depth is to the actual depth.
     * 
     * @throws IOException 
     */
    private double standardStepEquation(double velocity_DS,
                                            double velocity_US,
                                            double frictionSlope_DS,
                                            double frictionSlope_US,
                                            double measuredDownstreamDepth,
                                            double depth_US,
                                            double bedSlope,
                                            double length) throws IOException {

        
        double error = (((depth_US + (Math.pow(velocity_US, 2.) / (2 * 9.81)))
                - (measuredDownstreamDepth + (Math.pow(velocity_DS, 2.) / (2. * 9.81))))
                / (((frictionSlope_US + frictionSlope_DS) / 2) - (bedSlope)))
                + length;

        return error;
    }//end standardStepEquation
    
    /**
     * Creates a graph of the longitudinal depth and elevation profiles of the given
     * channel reach and saves them to the designated folder as jpeg's.
     * 
     * @param resultsArray A 3-dimensional array containing the results of the 
     * Standard Step Method calculations.
     * @param numberOfReaches The total number of reaches calculated.
     * @param graphTitle The title of the graph.
     * @param graphOutputName The file name under which the graph jpeg will be saved.
     * @param series1_name The name of the depth or elevation series.
     * @param series2_name The name of the critical depth series.
     * @param series3_name The name of the normal depth series.
     * @param series4_name The name of the bed elevation series.
     * @param yAxis_name The name of the y-axis.
     * 
     * @throws IOException 
     */
    public void graphLongitudinalProfile(double[][][] resultsArray,
            int numberOfReaches,
            String graphTitle,
            String graphOutputName,
            String series1_name,
            String series2_name,
            String series3_name,
            String series4_name,
            String yAxis_name) throws IOException {

        GeneralFunctions general = new GeneralFunctions();

        //Graph data
        XYSeries series1 = new XYSeries(series1_name);
        for (int i = 0; i < numberOfReaches + 1; i++) {
            series1.add(resultsArray[i][0][0], resultsArray[i][1][0]+resultsArray[i][1][3]);
        }
        XYSeries series2 = new XYSeries(series2_name);
        for (int i = 0; i < numberOfReaches + 1; i++) {
            series2.add(resultsArray[i][0][0], resultsArray[i][1][1]+resultsArray[i][1][3]);
        }
        XYSeries series3 = new XYSeries(series3_name);
        for (int i = 0; i < numberOfReaches + 1; i++) {
            series3.add(resultsArray[i][0][0], resultsArray[i][1][2]+resultsArray[i][1][3]);
        }
        XYSeries series4 = new XYSeries(series4_name);
        for (int i = 0; i < numberOfReaches + 1; i++) {
            series4.add(resultsArray[i][0][0], resultsArray[i][1][3]);
        }

        XYPlot plot = new XYPlot();
        XYDataset dataset1 = new XYSeriesCollection(series1);
        XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(true, false);
        currentRenderer.setSeriesPaint(0, Color.blue);
        plot.setDataset(2, dataset1);
        plot.setRenderer(2, currentRenderer);

        XYDataset dataset2 = new XYSeriesCollection(series2);
        XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, false);
        currentRenderer2.setSeriesPaint(0, Color.red);
        plot.setDataset(1, dataset2);
        plot.setRenderer(1, currentRenderer2);

        XYDataset dataset3 = new XYSeriesCollection(series3);
        XYItemRenderer currentRenderer3 = new XYLineAndShapeRenderer(true, false);
        currentRenderer3.setSeriesPaint(0, Color.green);
        plot.setDataset(0, dataset3);
        plot.setRenderer(0, currentRenderer3);

        XYDataset dataset4 = new XYSeriesCollection(series4);
        XYItemRenderer currentRenderer4 = new XYLineAndShapeRenderer(true, false);
        currentRenderer4.setSeriesPaint(0, Color.black);
        plot.setDataset(3, dataset4);
        plot.setRenderer(3, currentRenderer4);

        //Create the X Axis
        ValueAxis xAxis = new NumberAxis("Downstream Distance (m)");
        plot.setDomainAxis(0, xAxis);

        //Create the Y Axis
        ValueAxis yAxis = new NumberAxis(yAxis_name);
        plot.setRangeAxis(0, yAxis);

        //Set extra plot preferences
        general.setAxisPreferences(plot);

        //Create the chart with the plot
        JFreeChart chart = new JFreeChart(graphTitle, general.titleFont, plot, true);

        //Set legend Font
        LegendTitle legendTitle = chart.getLegend();
        legendTitle.setItemFont(general.masterFont);

        try {
            String path = mainFolder + File.separator + graphOutputName;
            ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
            System.out.println(path);
        } catch (IOException e) {
            System.err.println("A problem occurred while trying to create the chart.");
        }
    }//end graphLongitudinalProfile
    
    /**
     * Writes out the formatted output file as expected by eRAMS to be used for a JHighchart XY
     * @param crossSectionResultsArray
     * @param reachResultsArray
    
     * @throws IOException 
     */
    public void writeSummaryFile(double[][][] crossSectionResultsArray,
                                 double reachResultsArray[][]) throws IOException {
        //open the file writer and set path
        String summaryOutputName = getSummary();
        String path = mainFolder + File.separator + summaryOutputName;
        FileWriter writer =  new FileWriter(path, false);
        PrintWriter print_line = new PrintWriter(writer);
        
        //Print the needed values
        
        String summaryTitle = getSummaryTitle();
        print_line.printf("%s" + "\r\n", summaryTitle);
        print_line.printf("%s" + "\r\n", "");
                
        for(int i=0; i<crossSectionResultsArray.length; i++){
            print_line.printf("%-35s" + "\r\n", "");
            print_line.printf("%s" + "%.3f" + "%s" + "\r\n", "Cross Section " + i + ": ",crossSectionResultsArray[i][0][0]," m downstream");
            print_line.printf("%-35s" + "%15.6f" + "%s" + "\r\n","   Depth: ", crossSectionResultsArray[i][1][0]," m");
            print_line.printf("%-35s" + "%15.6f" + "%s" + "\r\n","   Discharge: ", crossSectionResultsArray[i][0][1]," m^3/s");
            print_line.printf("%-35s" + "%15.6f" + "%s" + "\r\n","   Critical Depth: ", crossSectionResultsArray[i][1][1]," m");
            print_line.printf("%-35s" + "%15.6f" + "%s" + "\r\n","   Normal Depth: ", crossSectionResultsArray[i][1][2]," m");
            print_line.printf("%-35s" + "%15.6f" + "%s" + "\r\n","   Bed Elevation (Above Origin): ", crossSectionResultsArray[i][1][3]," m");
            print_line.printf("%-35s" + "%15.6f" + "%s" + "\r\n","   Water Surface Elevation: ", crossSectionResultsArray[i][1][2]
                    +crossSectionResultsArray[i][1][0]," m");
            print_line.printf("%-35s" + "\r\n", "");
            print_line.printf("%-35s" + "\r\n","Reach " + (i+1));
            print_line.printf("%-35s" + "%15.6f" +"%s" + "\r\n","   Length: ", reachResultsArray[i][0]," m");
            print_line.printf("%-35s" + "%15.6f" +"%s" + "\r\n","   Slope: ", reachResultsArray[i][1]," m/m");
        }
      
        // Close the file writer 
        print_line.close();
        writer.close();
        System.out.println("Text File located at:\t" + path);
    }//end writeSummaryFile
    /**
     * Created new Standard Step Model object and starts 'run' method.
     * 
     * @param args
     * 
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        StandardStepMethod model = new StandardStepMethod();
        model.run();
    }//end main
}//end StandardStepMeth