Watershed.java [src/java/m/weppws] Revision: default  Date:
package m.weppws;

import csip.Config;
import csip.api.server.Executable;
import csip.api.server.ServiceException;
import csip.SessionLogger;
import csip.utils.Parallel;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.apache.commons.io.FileUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import util.Grid;
import util.WeppConstants;

/**
 * Represents a WEPP watershed, holds channels, subcatchments
 *
 */
public class Watershed {

  List<Subcatchment> subcatchments;
  List<Channel> channels;
  List<Impoundment> impoundments;
  SessionLogger LOG;
  File sessionWorkDir;
  float precip;
  float dischargeVol;
  float sedimentYld;
  float sedimentYldArea;
  float delRatio;
  float precipVol;
  float discharge;
  String weppVersion;
  String preVersion;
  boolean isWatershed;
  int cells;
  double area;
  V1_0 par;
  boolean subarea;

  double irrigation; // total over all representative hillslopes
  double fieldErosion; // total over all representative hillslopes
  double fieldNRCSErosion; // total over all representative hillslopes
  double fieldSedYield; // total over all representative hillslopes


  Watershed(SessionLogger log, File sessionWorkDir, boolean isWatershed, V1_0 par) {
    this.LOG = log;
    this.isWatershed = isWatershed;
    channels = new ArrayList<>();
    subcatchments = new ArrayList<>();
    impoundments = new ArrayList<>();
    this.sessionWorkDir = sessionWorkDir;
    this.par = par;
    subarea = false;
  }


  void setChannelFlowpaths() throws ServiceException {
    for (int i = 0; i < channels.size(); i++) {
      par.walkChannelFlowpath(channels.get(i));
    }
  }


  void createFlowpathLossGrid(Grid lossGrid, Grid sedWeightGrid) throws ServiceException {
    for (int i = 0; i < subcatchments.size(); i++) {
      if (subcatchments.get(i).getInuse())
        subcatchments.get(i).outputFlowpaths(lossGrid, sedWeightGrid);

    }
  }


  void createSoilLossGrid(Grid lossGrid, Grid subs) throws ServiceException {
    for (int i = 0; i < subcatchments.size(); i++) {
      int key = subcatchments.get(i).id;
      float val = subcatchments.get(i).getAvgSoilLoss();
      if (subcatchments.get(i).getInuse())
        lossGrid.update(subs, key, val);

    }
  }


  void createSoilLossGridSubareas(Grid lossGrid, Grid subs) throws ServiceException {
    int hills = 0;
    for (int i = 0; i < subcatchments.size(); i++) {
      Subcatchment s = subcatchments.get(i);
      int key;
      float val;
      boolean found = false;
      if (s.getInuse()) {
        if (s.topsub != null) {
          val = s.topsub.getAvgSoilLoss();
          key = s.topsub.topazid;
          lossGrid.update(subs, key, val);
          hills++;
          found = true;
        }
        if (s.leftsub != null) {
          val = s.leftsub.getAvgSoilLoss();
          key = s.leftsub.topazid;
          lossGrid.update(subs, key, val);
          hills++;
          found = true;
        }
        if (s.rightsub != null) {
          val = s.rightsub.getAvgSoilLoss();
          key = s.rightsub.topazid;
          lossGrid.update(subs, key, val);
          hills++;
          found = true;
        }

        if (found == false)
          LOG.info("  ERROR: Could not update sub3 grid " + s.id);
      }
    }
  }


  void createSedYieldGrid(Grid sedGrid, Grid subs) throws ServiceException {
    for (int i = 0; i < subcatchments.size(); i++) {
      int key = subcatchments.get(i).id;
      float val = subcatchments.get(i).getAvgSedYield();
      if (subcatchments.get(i).getInuse())
        sedGrid.update(subs, key, val);

    }
  }


  void createSedYieldGridSubareas(Grid sedGrid, Grid subs) throws ServiceException {
    int hills = 0;
    for (int i = 0; i < subcatchments.size(); i++) {
      Subcatchment s = subcatchments.get(i);
      int key;
      float val;
      boolean found = false;
      if (s.getInuse()) {
        if (s.topsub != null) {
          val = s.topsub.getAvgSedYield();
          key = s.topsub.topazid;
          sedGrid.update(subs, key, val);
          hills++;
          found = true;
        }
        if (s.leftsub != null) {
          val = s.leftsub.getAvgSedYield();
          key = s.leftsub.topazid;
          sedGrid.update(subs, key, val);
          hills++;
          found = true;
        }
        if (s.rightsub != null) {
          val = s.rightsub.getAvgSedYield();
          key = s.rightsub.topazid;
          sedGrid.update(subs, key, val);
          hills++;
          found = true;
        }
        if (found == false)
          LOG.info("  ERROR: Could not update sub3 grid " + s.id);
      }
    }
  }


  void createRunoffGrid(Grid runoffGrid, Grid subs) throws ServiceException {
    for (int i = 0; i < subcatchments.size(); i++) {
      int key = subcatchments.get(i).id;
      float val = subcatchments.get(i).getAvgRunoff();
      if (subcatchments.get(i).getInuse()) {
        runoffGrid.update(subs, key, val);
      }
    }
  }


  void createRunoffGridSubareas(Grid runoffGrid, Grid subs) throws ServiceException {
    int hills = 0;

    for (int i = 0; i < subcatchments.size(); i++) {
      Subcatchment s = subcatchments.get(i);
      int key;
      float val;
      boolean found = false;
      if (s.getInuse()) {
        if (s.topsub != null) {
          val = s.topsub.getAvgRunoff();
          key = s.topsub.topazid;
          runoffGrid.update(subs, key, val);
          hills++;
          found = true;
        }
        if (s.leftsub != null) {
          val = s.leftsub.getAvgRunoff();
          key = s.leftsub.topazid;
          runoffGrid.update(subs, key, val);
          hills++;
          found = true;
        }
        if (s.rightsub != null) {
          val = s.rightsub.getAvgRunoff();
          key = s.rightsub.topazid;
          runoffGrid.update(subs, key, val);
          hills++;
          found = true;
        }

        if (found == false)
          LOG.info("  ERROR: Could not update sub3 grid " + s.id);
      }
    }
  }


  Grid createsub3grid(Grid sub3) throws ServiceException {
    for (int i = 0; i < subcatchments.size(); i++) {
      LOG.info((">Processing subcatchment: " + subcatchments.get(i).id));
      if (subcatchments.get(i).getInuse()) {
        subcatchments.get(i).fillsub3grid(LOG,sub3);
      } else {
          LOG.info(">Unused subcatchment: " + subcatchments.get(i).id);
      }
    }
    for (int i = 0; i < channels.size(); i++) {
      channels.get(i).fillsub3grid(sub3);
    }
    return sub3;
  }


  void addChannel(Channel ch) {
    channels.add(ch);
  }


  void addImpoundment(Impoundment imp) {
    impoundments.add(imp);
    linkImpoundmentToChannel(imp);
  }


  void addSubcatchment(Subcatchment s) {
    s.weppid = subcatchments.size() + 1;
    subcatchments.add(s);
  }


  void dumpChannels() {
    for (int i = 0; i < channels.size(); i++) {
      LOG.info("Channel: " + channels.get(i).id);
    }
  }


  void dumpSubcatchments() throws ServiceException, JSONException {
    try {
      for (int i = 0; i < subcatchments.size(); i++) {
        subcatchments.get(i).writeFlowpaths(sessionWorkDir);
      }
    } catch (IOException ex1) {
      throw new ServiceException("Can not create flowpaths files.");
    }
  }


  Subcatchment getSubcatchment(int id) {
    for (int i = 0; i < subcatchments.size(); i++) {
      if (subcatchments.get(i).id == id)
        return subcatchments.get(i);
    }
    return null;
  }


  void removeFiles() {
    for (int i = 0; i < subcatchments.size(); i++) {
      new File(sessionWorkDir, "flowpaths_" + Integer.toString(subcatchments.get(i).id)).delete();
    }
    new File(sessionWorkDir, "slope_d8.asc").delete();
  }


  int runFlowpaths(Executable weppserv, Grid field, boolean statsOnly,
      int subsetPercentage, boolean parallelRuns) throws ServiceException, Exception {
    int total = 0;
    LOG.info(">>> Flowpath runs starting now for subcatchments: " + subcatchments.size());
    try {
      for (int i = 0; i < subcatchments.size(); i++) {
        LOG.info(">>> Subcatchment " + i);
        if (parallelRuns) {
          total += subcatchments.get(i).runFlowpaths0(LOG, sessionWorkDir, field, statsOnly);
        } else {
          //if (par.flowpathURL.length() > 0) {
          //  total += subcatchments.get(i).runFlowpaths(LOG, sessionWorkDir, weppserv, field, statsOnly, subsetPercentage);
          //} else {
          total += subcatchments.get(i).runFlowpathsLocal(LOG, sessionWorkDir, weppserv, field, statsOnly, subsetPercentage);
          //}
        }
      }
    } catch (IOException ex1) {
      throw new ServiceException("Can not run flowpaths.", ex1);
    }
    LOG.info("<<< Flowpath runs ending now.");
    return total;
  }


  int runFlowpathsSubset(Executable weppserv, Grid field, boolean statsOnly,
      int subsetPercentage, boolean parallelRuns, int[] detailsubcatchments) throws ServiceException, Exception {
    int total = 0;

    if (detailsubcatchments.length == 0) {
      LOG.info(">>> Flowpath runs starting now for subcatchments: " + subcatchments.size());
    } else {
      LOG.info(">>> Flowpath runs starting now for subcatchments: " + detailsubcatchments);
    }
    try {
      for (int i = 0; i < detailsubcatchments.length; i++) {
        for (int j = 0; j < subcatchments.size(); j++) {
          if (detailsubcatchments[i] == subcatchments.get(j).id) {
            LOG.info(">>> Subcatchment " + j);
            if (parallelRuns) {
              total += subcatchments.get(j).runFlowpaths0(LOG, sessionWorkDir, field, statsOnly);
            } else {
              total += subcatchments.get(j).runFlowpathsLocal(LOG, sessionWorkDir, weppserv, field, statsOnly, subsetPercentage);
            }
            break;
          }
        }
      }
    } catch (IOException ex1) {
      throw new ServiceException("Can not run flowpaths.", ex1);
    }
    LOG.info("<<< Flowpath runs ending now.");
    return total;
  }


  int runRepresentativeHillslopes(Executable weppserv, Grid field, boolean statsOnly, boolean parallelRuns, boolean splitSubcatchments, boolean usePassFiles) throws ServiceException, Exception {
    int total = 0;

    if (parallelRuns) {
      LOG.info(">>> Representative hillslope parallel runs starting now." + " Stats Only " + statsOnly);
      total = runRepresentativeHillslopes0(weppserv, field, statsOnly, splitSubcatchments, usePassFiles);
      LOG.info("<<< Representative hillslope parallel runs ending now." + total);
      return total;
    } else {
      String flowpathServiceURL = par.flowpathURL;
      //if (flowpathServiceURL.length() == 0) {
      LOG.info(">>> Representative hillslope single thread runs starting now.");
      // } else {
      //     LOG.info(">>> Representative hillslope non-parallel runs starting now on URL: " + flowpathServiceURL);
      //}
    }
    List<JSONObject> runningFlowpaths = new ArrayList<>();
    List<Integer> runningIndex = new ArrayList<>();
    try {
      for (int i = 0; i < subcatchments.size(); i++) {
        if (subcatchments.get(i).flowpaths.size() > 0) {
          JSONObject resp = subcatchments.get(i).runRepresentativeHillslope(LOG, sessionWorkDir, weppserv, field, statsOnly, splitSubcatchments, usePassFiles);
          if (resp != null) {
            runningFlowpaths.add(resp);
            runningIndex.add(i);
          }
          total++;
        }
      }
      waitForRuns(runningFlowpaths, runningIndex);
    } catch (IOException ex1) {
      throw new ServiceException("Can not run representative hillslope.");
    }
    LOG.info("<<< Representative hillslope runs ending now.");
    return total;
  }


  int runRepresentativeHillslopes0(Executable weppserv, Grid field, boolean statsOnly, boolean splitSubcatchments, boolean usePassFiles) throws Exception {

    int total = 0;
    AtomicInteger numRun = new AtomicInteger();
    int nthreads = Config.getInt("weppws.fp.par", 16);

    if (statsOnly) {
      // no need to run parallel  runs for this since it is only creating
      // input files, not doing a model run
      for (int i = 0; i < subcatchments.size(); i++) {
        if (subcatchments.get(i).flowpaths.size() > 0) {
          total += subcatchments.get(i).runRepresentativeHillslope0(sessionWorkDir, weppserv, field, statsOnly, splitSubcatchments, usePassFiles);
        } else {
          subcatchments.get(i).makeRepresentativeHillslopeNoData((float)field.getCellsize());
          total++;
        }
      }
      return total;
    }

    Parallel.run(nthreads, subcatchments.size(), new Function<Integer, Parallel.Run>() {
      @Override
      public Parallel.Run apply(Integer t) {
        par.progress("Running " + 0 + "/" + t);
        return () -> {
          Subcatchment sub = subcatchments.get(t);
          if (sub.flowpaths.size() > 0) {
            sub.runRepresentativeHillslope0(sessionWorkDir, weppserv, field, statsOnly, splitSubcatchments, usePassFiles);
            LOG.info(">>>>Representative flowpath run complete id=" + sub.repHillslopeFlowpath.id
                + " loss=" + sub.repHillslopeFlowpath.avgSoilLoss + " runoff=" + sub.repHillslopeFlowpath.avgRunoff
                + " sed=" + sub.repHillslopeFlowpath.avgSedYield);
            numRun.incrementAndGet();
          }
        };
      }
    });
    return numRun.get();
  }


  void waitForRuns(List<JSONObject> runningFlowpaths, List<Integer> runningIndex)
      throws ServiceException, IOException, Exception {
    int concount = runningFlowpaths.size();
    while (concount > 0) {
      Thread.sleep(2000);
      // check the status of all running representative flowpaths
      int removed = 0;
      int curIdx = 0;
      for (int j = 0; j < concount; j++) {
        if (curIdx < runningFlowpaths.size()) {
          if (runningFlowpaths.get(curIdx) != null) {
            JSONObject resp = subcatchments.get(runningIndex.get(curIdx)).
                checkProgress(LOG, runningFlowpaths.get(curIdx), sessionWorkDir);

            if (resp == null) {
              // run completed, remove from list
              runningIndex.remove(curIdx);
              runningFlowpaths.remove(curIdx);
              removed++;
            } else {
              curIdx = curIdx + 1;
            }
          } else {
            curIdx = curIdx + 1;
          }
        }
      }
      concount = concount - removed;
    }
  }


  int runWatershed(Executable weppserv, boolean statsOnly, boolean usePassFiles) throws ServiceException, IOException {
    File prjFile = new File(sessionWorkDir, "wepp.prw");
    try {
      buildProjectInput(prjFile, usePassFiles);
    } catch (IOException | ServiceException | JSONException ex) {
      throw new ServiceException("Can not build watershed project.");
    }
    if (statsOnly == false) {
      int ret = weppserv.exec();
      if (ret != 0) {
        throw new ServiceException("WEPP watershed run error: " + ret);
      } else {
        parseOutputs(new File(sessionWorkDir, "output/weppout.txt"));
      }
      // if you reuse the weppservobject, you need to reinit the
      // output streams, or call getRessourceExe again.
      weppserv.redirectDefaults();
    }
    return 0;
  }


  int runWatershedSubarea(Executable weppserv, boolean statsOnly, int outletChannel, boolean usePassFiles) throws ServiceException, IOException {
    File prjFile = new File(sessionWorkDir, "wepp.prw");
    try {
      buildProjectInput(prjFile, usePassFiles);
    } catch (IOException | ServiceException | JSONException ex) {
      throw new ServiceException("Can not build watershed project.");
    }

    if (statsOnly == false) {
      int ret = weppserv.exec();
      if (ret != 0) {
        throw new ServiceException("WEPP watershed run error: " + ret);
      } else {
        parseOutputs(new File(sessionWorkDir, "output/weppout.txt"));
      }
      // if you reuse the weppservobject, you need to reinit the
      // output streams, or call getRessourceExe again.
      weppserv.redirectDefaults();
    }
    return 0;
  }


  int followTree(Channel chan, int lastid) {
    chan.setWeppID(lastid);
    lastid = lastid - 1;
    if (chan.leftChannel != null)
      lastid = followTree(chan.leftChannel, lastid);

    if (chan.rightChannel != null)
      lastid = followTree(chan.rightChannel, lastid);

    if (chan.topChannel != null)
      lastid = followTree(chan.topChannel, lastid);

    return lastid;
  }


  void labelWEPPChannels() throws ServiceException {
    int lastChannel = channels.size();
    Channel outlet = null;

    // find the outlet, this channel will have no downstream link
    for (int i = 0; i < channels.size(); i++) {
      if (channels.get(i).downstream == -1) {
        outlet = channels.get(i);
        break;
      }
    }

    if (outlet != null) {
      followTree(outlet, lastChannel);
      // check that all nodes have a weppID
      for (int i = 0; i < channels.size(); i++) {
        if (channels.get(i).weppID <= 0) {
          throw new ServiceException("Channel links incomplete.");
        }
      }
      // Attached subcatchments to channels, for single subcatchment for
      // each channel the numbers are the same. 
      for (int i = 0; i < channels.size(); i++) {
        if (channels.get(i).weppID <= 0) {
          throw new ServiceException("Channel links incomplete.");
        }
        for (int j = 0; j < subcatchments.size(); j++) {
          if (subcatchments.get(j).id == channels.get(i).id) {
            channels.get(i).setHillslope(subcatchments.get(j));
            break;
          }
        }
      }
    }
  }


  void labelWEPPChannelsSubarea(int outletID) throws ServiceException {
    int lastChannel = channels.size();
    Channel outlet = null;

    subarea = true;

    for (int i = 0; i < channels.size(); i++) {
      if (channels.get(i).id == outletID) {
        outlet = channels.get(i);
        break;
      }
    }

    if (outlet != null) {
      followTree(outlet, lastChannel);
      relabelChannels();
      // some of the channels will not have a weppID because they are not 
      // included in the subarea - this is ok.

      // Attached subcatchments to channels, for single subcatchment for
      // each channel the numbers are the same. 
      for (int i = 0; i < channels.size(); i++) {
        // some of the channels will not have a weppID because they are not 
        // included in the subarea - this is ok.
        if (channels.get(i).weppID > 0) {
          LOG.info("*** Checking channel: " + channels.get(i).weppID + " real id: " + channels.get(i).id);
          for (int j = 0; j < subcatchments.size(); j++) {
            if (subcatchments.get(j).id == channels.get(i).id) {
              channels.get(i).setHillslope(subcatchments.get(j));
              LOG.info("Set hillslope for channel " + channels.get(i).id + " " + subcatchments.get(j).weppid);
              break;
            }
          }
        }
      }
      relabelHillslopes();
    }
  }


  void relabelChannels() {
    int numChannels = 0;
    int minID = 9999;

    for (int i = 0; i < channels.size(); i++) {
      channels.get(i).setInuse(false);
      if (channels.get(i).weppID > 0) {
        numChannels++;
        if (channels.get(i).weppID < minID) {
          minID = channels.get(i).weppID;
          numChannels++;
        }
      }
    }
    int adjust = minID - 1;
    for (int i = 0; i < channels.size(); i++) {
      if (channels.get(i).weppID > 0) {
        channels.get(i).setWeppID(channels.get(i).weppID - adjust);
        channels.get(i).setInuse(true);
      }
    }
  }


  void relabelHillslopes() {
    int numHillslopes = 0;
    int minID = 9999;

    for (int i = 0; i < subcatchments.size(); i++) {
      if (subcatchments.get(i).getInuse()) {
        numHillslopes++;
      } else {
        subcatchments.get(i).weppid = -1;
      }
    }

    int curHillslope = 1;
    LOG.info("  *** Found " + numHillslopes + " subcatchments in use.");
    for (int i = 0; i < subcatchments.size(); i++) {
      if (subcatchments.get(i).getInuse()) {
        LOG.info("Relabel subcatchment: " + subcatchments.get(i).weppid + " to " + curHillslope);
        subcatchments.get(i).setWeppID(curHillslope);
        curHillslope++;
      }
    }
  }


  void setInuseSubcatchments() {
    for (int i = 0; i < subcatchments.size(); i++) {
      subcatchments.get(i).setInuse(true);
    }
  }


  void writeInputSummary(File f) throws IOException, ServiceException, JSONException {

    JSONObject oneRot;
    String elem;
    double larea;

    int numChannels = getNumChannels();
    int numHillslopes = getNumHillslopes();

    if (f.exists())
      f.delete();

    if (!f.createNewFile())
      throw new ServiceException("Could not create Input summary file");

    PrintStream o = new PrintStream(f);

    JSONObject jo = new JSONObject();
    jo.put("Field/Watershed Area (ac)", area * WeppConstants.CONV_HA_TO_AC);
    jo.put("Number of subcatchments/representative hillslopes", numHillslopes);
    jo.put("Number of channels", numChannels);
    String header = jo.toString(2);
    header = header.trim();
    header = header.substring(0, header.length() - 1);
    header = header + ",";

    o.println(header);
    o.println("\"Subcatchments\" : [");
    int curHillslope = 0;
    float percent = 0;
    int scells = 0;

    for (int i = 0; i < subcatchments.size(); i++) {
      Subcatchment mysub = subcatchments.get(i);
      if (mysub.getInuse()) {
        if (!par.splitSubcatchments) {
          // use TauDEM single area
          elem = mysub.summarizeInput();
          percent += mysub.getPercentageArea();
          curHillslope++;
          o.println(elem);
          if (curHillslope < numHillslopes) {
            o.println(",");
          }
        } else {
          // use TOPAZ subareas TOP, LEFT, RIGHT
          if (mysub.leftsub != null) {
            elem = mysub.leftsub.summarizeInput(curHillslope);
            percent += mysub.leftsub.getPercentageArea();
            scells += mysub.leftsub.getCells();
            curHillslope++;
            o.println(elem);
            if (curHillslope < numHillslopes) {
              o.println(",");
            }
          }
          if (mysub.rightsub != null) {
            elem = mysub.rightsub.summarizeInput(curHillslope);
            percent += mysub.rightsub.getPercentageArea();
            scells += mysub.rightsub.getCells();
            curHillslope++;
            o.println(elem);
            if (curHillslope < numHillslopes) {
              o.println(",");
            }
          }
          if (mysub.topsub != null) {
            elem = mysub.topsub.summarizeInput(curHillslope);
            percent += mysub.topsub.getPercentageArea();
            scells += mysub.topsub.getCells();
            curHillslope++;
            o.println(elem);
            if (curHillslope < numHillslopes) {
              o.println(",");
            }
          }
        }
      }
    }
    o.println("],");

    LOG.info("Check percentage of all subcatchments: " + percent);
    LOG.info("Check cell count subcatchments: " + scells + " aoi cells: " + cells);

    Channel mychan;
    int curChan = 0;

    o.println("\"Channels\" : [");
    if ((isWatershed) || (par.usechannels.length > 0)) {
      for (int i = 0; i < channels.size(); i++) {
        mychan = null;
        // for subareas the simulation will only use a fraction of the total 
        // number of channels, those channels with a valid weppID will be used,
        // other channels outside the area are not used.
        for (int j = 0; j < channels.size(); j++) {
          if (channels.get(j).weppID == i) {
            mychan = channels.get(j);
            curChan = curChan + 1;
            break;
          }
        }
        if (mychan != null) {
          elem = mychan.summarizeInput();
          o.println(elem);
          if (curChan < (numChannels - 1)) {
            o.println(",");
          }
        }
      }
    }
    o.println("],");

    o.println("\"Impoundments\" : [");
    if (isWatershed) {
      Impoundment myimp;
      for (int i = 0; i < impoundments.size(); i++) {
        myimp = impoundments.get(i);
        if (myimp != null) {
          elem = myimp.summarizeInput();
          o.println(elem);
          if (i < (impoundments.size() - 1)) {
            o.println(",");
          }
        }
      }
    }
    o.println("],");

    int numWritten = 0;
    o.println("\"Landuse Summary\" : [");
    for (int i = 0; i < par.managementDictAll.length(); i++) {
      oneRot = par.managementDictAll.getJSONObject(i);
      if (oneRot.getInt("cells") > 0) {
        jo = new JSONObject();
        jo.put("id", oneRot.getInt("id"));
        jo.put("name", oneRot.getString("name"));
        jo.put("grid cells", oneRot.getInt("cells"));
        larea = (oneRot.getInt("cells") * (par.field_grid.getCellsize() * par.field_grid.getCellsize())) / 10000;
        jo.put("Area (ac)", larea * WeppConstants.CONV_HA_TO_AC);
        if (area > 0) {
          jo.put("Percentage of total area", (larea / area) * 100);
        }
        if (numWritten > 0) {
          o.println(",");
        }
        o.println(jo.toString(2));
        numWritten++;
      }
    }
    o.println("],");

    o.println("\"Soils Summary\" : [");
    numWritten = 0;
    for (int i = 0; i < par.soilDictAll.length(); i++) {
      oneRot = par.soilDictAll.getJSONObject(i);
      if (oneRot.getInt("cells") > 0) {
        jo = new JSONObject();
        jo.put("id", oneRot.getInt("id"));
        jo.put("cokey", oneRot.getString("cokey"));
        jo.put("name", oneRot.getString("name"));
        jo.put("grid cells", oneRot.getInt("cells"));
        larea = (oneRot.getInt("cells") * (par.field_grid.getCellsize() * par.field_grid.getCellsize())) / 10000;
        jo.put("Area (ac)", larea * WeppConstants.CONV_HA_TO_AC);
        if (area > 0) {
          jo.put("Percentage of total area", (larea / area) * 100);
        }
        if (numWritten > 0) {
          o.println(",");
        }
        o.println(jo.toString(2));
        numWritten++;
      }
    }
    o.println("]");
    o.println("}");
    o.close();
  }


  void filterSubarea() {
    subcatchments.removeIf(x -> (x.getInuse() == false));
    channels.removeIf(x -> (x.getInuse() == false));

    // recalculate watershed area
    area = 0;
    for (int i = 0; i < subcatchments.size(); i++) {
      area += subcatchments.get(i).getAreasOfSubs();
      LOG.config("   Area adding: " + i + " " + area);
    }
  }


  void writeOutputSummary(File f) throws IOException, ServiceException, JSONException {
    String elem;
    if (f.exists())
      f.delete();

    if (!f.createNewFile())
      throw new ServiceException("Could not create output summary file");

    PrintStream o = new PrintStream(f);

    int numChannels = getNumChannels();
    int numHillslopes = getNumHillslopes();

    JSONObject jo = new JSONObject();
    jo.put("Field/Watershed Area (ac)", area * WeppConstants.CONV_HA_TO_AC);
    jo.put("Number of subcatchments/representative hillslopes", numHillslopes);
    jo.put("Number of channels", numChannels);
    jo.put("Watershed discharge volume (ft^3/yr)", dischargeVol);
    jo.put("Watershed sediment yield (ton/yr)", sedimentYld);
    jo.put("Watershed sediment yield (ton/ac/yr)", sedimentYldArea);
    jo.put("Watershed sediment delivery ratio", delRatio);
    jo.put("Precipitation volume in watershed (ft^3/yr)", precipVol);
    jo.put("Precipitation (in)", precip);
    jo.put("Discharge at outlet (in/yr)", discharge);
    int totalFlowpaths = 0;
    int simFlowpaths = 0;
    for (int i = 0; i < subcatchments.size(); i++) {
      totalFlowpaths += subcatchments.get(i).flowpaths.size();
      simFlowpaths += subcatchments.get(i).flowpathsRun;
    }
    jo.put("Total Number of Flowapths", totalFlowpaths);
    jo.put("Flowpaths simulated", simFlowpaths);

    String header = jo.toString(2);
    header = header.trim();
    header = header.substring(0, header.length() - 1);
    header = header + ",";
    o.println(header);

    o.println("\"Representative hillslope subcatchment summary\" : [");

    int curHillslope = 0;
    for (int i = 0; i < subcatchments.size(); i++) {
      Subcatchment mysub = subcatchments.get(i);
      if (mysub.getInuse()) {
        if (!par.splitSubcatchments) {
          // use TauDEM single area
          elem = mysub.summarizeOutput(LOG);
          curHillslope++;
          o.println(elem);
          if (curHillslope < numHillslopes) {
            o.println(",");
          }
        } else {
          // use TOPAZ subareas TOP, LEFT, RIGHT
          if (mysub.leftsub != null) {
            elem = mysub.leftsub.summarizeOutput(LOG, curHillslope);
            curHillslope++;
            o.println(elem);
            if (curHillslope < numHillslopes) {
              o.println(",");
            }
          }
          if (mysub.rightsub != null) {
            elem = mysub.rightsub.summarizeOutput(LOG, curHillslope);
            curHillslope++;
            o.println(elem);
            if (curHillslope < numHillslopes) {
              o.println(",");
            }
          }
          if (mysub.topsub != null) {
            elem = mysub.topsub.summarizeOutput(LOG, curHillslope);
            curHillslope++;
            o.println(elem);
            if (curHillslope < numHillslopes) {
              o.println(",");
            }
          }
        }
      }
    }

    o.println("],");

    Channel mychan;
    int curChan = 0;
    o.println("\"Channels\" : [");
    if ((isWatershed) || (par.usechannels.length > 0)) {
      for (int i = 0; i < channels.size(); i++) {
        mychan = null;
        // for subareas the simulation will only use a fraction of the total 
        // number of channels, those channels with a valid weppID will be used,
        // other channels outside the area are not used.
        for (int j = 0; j < channels.size(); j++) {
          if (channels.get(j).weppID == i+1) {
            mychan = channels.get(j);
            curChan = curChan + 1;
            break;
          }
        }
        if (mychan != null) {
          elem = mychan.summarizeOutput(LOG);
          o.println(elem);
          if (curChan < numChannels) {
            o.println(",");
          }
        }
      }
    }

    o.println("],");

    o.println("\"Impoundments\" : [");
    if (isWatershed) {
      Impoundment myimp;
      for (int i = 0; i < impoundments.size(); i++) {
        myimp = impoundments.get(i);
        if (myimp != null) {
          elem = myimp.summarizeOutput(LOG);
          o.println(elem);
          if (i < (impoundments.size() - 1)) {
            o.println(",");
          }
        }
      }
    }
    o.println("]");
    o.println("}");
    o.close();
  }


  void findMajorComponents(Grid subgrid, Grid changrid, Grid soilgrid, Grid mangrid, Grid mask) throws ServiceException {
    int val;
    for (int i = 0; i < subcatchments.size(); i++) {
      val = soilgrid.majorValue(subcatchments.get(i).id, subgrid);
      if (val < 0) {
          val = par.defaultSoil;
      }
      subcatchments.get(i).setMajorSoil(val);
      val = mangrid.majorValue(subcatchments.get(i).id, subgrid);
      if (val < 0) {
          val = par.defaultManagement;
      }
      subcatchments.get(i).setMajorLanduse(val);
      // count number of cells for this subcatchment which is the area
      subcatchments.get(i).calcArea(subgrid, mask);
    }
    for (int i = 0; i < channels.size(); i++) {
      val = soilgrid.majorValue(channels.get(i).id, changrid);
      if (val < 0) {
          val = par.defaultSoil;
      }
      channels.get(i).setMajorSoil(val);
      val = mangrid.majorValue(channels.get(i).id, changrid);
      if (val < 0) {
          val = par.defaultManagement;
      }
      channels.get(i).setMajorLanduse(val);
      // count number of cells for this channel which is the area
      channels.get(i).calcArea();
    }
  }


  void findMajorComponentsTopaz(Grid subgrid, Grid soilgrid, Grid mangrid, Grid mask) throws ServiceException {
    int val;
    for (int i = 0; i < subcatchments.size(); i++) {
      Subcatchment mysub = subcatchments.get(i);
      if (mysub != null) {
        if (mysub.leftsub != null) {
          val = soilgrid.majorValue(mysub.leftsub.topazid, subgrid);
          if (val < 0) {
              LOG.info("Failed to find soil for TOPAZ ID: " + mysub.leftsub.topazid + " " + mysub.id);
              LOG.info("Deleting left subcatchment, can't determine flowpath orientations");
              mysub.leftsub = null;
          } else {
               LOG.info("Found soil for TOPAZ ID: " + mysub.leftsub.topazid + " " + mysub.id);
          }
          if (mysub.leftsub != null) {
            mysub.leftsub.setMajorSoil(val);
            val = mangrid.majorValue(mysub.leftsub.topazid, subgrid);
            if (val < 0) {
                LOG.info("Failed to find management for TOPAZ ID: " + mysub.leftsub.topazid + " " + mysub.id);
                LOG.info("Deleting left subcatchment, can't determine flowpath orientations");
                mysub.leftsub = null;
            } else {
                 LOG.info("Found management for TOPAZ ID: " + mysub.leftsub.topazid + " " + mysub.id);
            }
            if (mysub.leftsub != null) {
              mysub.leftsub.setMajorLanduse(val);
              mysub.leftsub.calcArea(subgrid, mask);
            }
          }
        }
        if (mysub.rightsub != null) {
          val = soilgrid.majorValue(mysub.rightsub.topazid, subgrid);
          if (val < 0) {
              LOG.info("Failed to find soil for TOPAZ ID: " + mysub.rightsub.topazid + " " + mysub.id);
              LOG.info("Deleting right subcatchment, can't determine flowpath orientations");
              mysub.rightsub = null;
          } else {
                LOG.info("Found soil for TOPAZ ID: " + mysub.rightsub.topazid + " " + mysub.id);
          }
          if (mysub.rightsub != null) {
            mysub.rightsub.setMajorSoil(val);
            val = mangrid.majorValue(mysub.rightsub.topazid, subgrid);
            if (val < 0) {
                LOG.info("Failed to find management for TOPAZ ID: " + mysub.rightsub.topazid);
                LOG.info("Deleting right subcatchment, can't determine flowpath orientations");
                mysub.rightsub = null;
            }
            if (mysub.rightsub != null) {
                mysub.rightsub.setMajorLanduse(val);
                mysub.rightsub.calcArea(subgrid, mask);
            }
          }
        }
        if (mysub.topsub != null) {
          val = soilgrid.majorValue(mysub.topsub.topazid, subgrid);
          if (val < 0) {
              LOG.info("Failed to find soil for TOPAZ ID: " + mysub.topsub.topazid);
              LOG.info("Deleting top subcatchment, can't determine flowpath orientations");
              mysub.topsub = null;
          }
          if (mysub.topsub != null) {
            mysub.topsub.setMajorSoil(val);
            val = mangrid.majorValue(mysub.topsub.topazid, subgrid);
            if (val < 0) {
                LOG.info("Failed to find management for TOPAZ ID: " + mysub.topsub.topazid);
                LOG.info("Deleting top subcatchment, can't determine flowpath orientations");
                mysub.topsub = null;
            }
            if (mysub.topsub != null) {
               mysub.topsub.setMajorLanduse(val);
               mysub.topsub.calcArea(subgrid, mask);
            }
          }
        }
      }
      val = soilgrid.majorValue(subcatchments.get(i).id, subgrid);
      if (val < 0) 
          val  = par.defaultSoil;
      subcatchments.get(i).setMajorSoil(val);
      val = mangrid.majorValue(subcatchments.get(i).id, subgrid);
      if (val < 0) 
          val = par.defaultManagement;
      subcatchments.get(i).setMajorLanduse(val);
      // count number of cells for this subcatchment which is the area
      subcatchments.get(i).calcArea(subgrid, mask);
    }

  }


  int calcArea(Grid mask) throws ServiceException {
    cells = mask.cellCount(1);
    area = (cells * (mask.getCellsize() * mask.getCellsize())) / 10000;
    return cells;
  }


  int getArea() {
    return cells;
  }


  void setChannelStart(int id, int row, int col) {
    for (int i = 0; i < channels.size(); i++) {
      Channel ch = channels.get(i);
      if (ch.id == id)
        ch.addStart(row, col);
    }
  }


  void connectChannels() {
    for (int i = 0; i < channels.size(); i++) {
      Channel ch = channels.get(i);
      if (ch.upstream1 != -1) {
        for (int j = 0; j < channels.size(); j++) {
          Channel ch1 = channels.get(j);
          if (ch1.id == ch.upstream1) {
            ch.topChannel = ch1;
            break;
          }
        }
      }
      if (ch.upstream2 != -1) {
        for (int j = 0; j < channels.size(); j++) {
          Channel ch1 = channels.get(j);
          if (ch1.id == ch.upstream2) {
            ch.leftChannel = ch1;
            break;
          }
        }
      }
    }
  }


  int sizeChannelsSubarea() {
    int numChannels;
    numChannels = 0;
    for (int i = 0; i < channels.size(); i++) {
      if (channels.get(i).weppID > 0) {
        LOG.info("    >> Add channel:  " + channels.get(i).weppID + " " + channels.get(i).id);
        numChannels++;
      }
    }
    return numChannels;
  }


  int sizeSubcatchmentsSubarea() {
    int numSubcatchments;
    numSubcatchments = 0;
    for (int i = 0; i < subcatchments.size(); i++) {
      if (subcatchments.get(i).getInuse()) {
        LOG.info("    >> Add Subcatchment:  " + subcatchments.get(i).weppid + "  " + subcatchments.get(i).id);
        numSubcatchments++;
      }
    }
    return numSubcatchments;
  }


  int getNumImpoundments() {
    int numImpoundments;
    numImpoundments = impoundments.size();
    return numImpoundments;
  }


  int getNumChannels() {
    int numChannels;
    if (subarea) {
      numChannels = sizeChannelsSubarea();
    } else {
      numChannels = channels.size();
    }
    return numChannels;
  }


  int getNumHillslopes() {
    int numHillslopes;
    if (subarea) {
      numHillslopes = sizeSubcatchmentsSubarea();
    } else {
      numHillslopes = subcatchments.size();
    }
    if (par.splitSubcatchments) {
      // need to count portions of each subcatchment, may have top, left, right
      // components
      numHillslopes = 0;
      for (int i = 0; i < subcatchments.size(); i++) {
        if (subcatchments.get(i).getInuse()) {
          numHillslopes += subcatchments.get(i).numTopazareas();
        }
      }
    }
    return numHillslopes;
  }


  protected void writeNetworkImpoundment(String id, StringBuilder output) throws ServiceException {
    output.append("   " + id + " (");
    int idx = Integer.parseInt(id.substring(1));
    Impoundment myimp;
    myimp = impoundments.get(idx - 1);
    if (par.splitSubcatchments) {
      output.append(myimp.getLeftHillslope()).append(", ");
      output.append(myimp.getRightHillslope() + ", ");
      output.append(myimp.getTopHillslope() + ", ");
    } else {
      output.append(myimp.getHillslope()).append(", ");
      output.append("0, ");
      output.append("0, ");
    }
    output.append(myimp.getLeftChannel() + ", ");
    output.append(myimp.getRightChannel() + ", ");
    output.append(myimp.getTopChannel() + ", ");
    output.append(myimp.getLeftImpoundment() + ", ");
    output.append(myimp.getRightImpoundment() + ", ");
    output.append(myimp.getTopImpoundment() + ") # I" + myimp.id + "\n");
    myimp.linked = true;
  }


  protected void buildProjectInput(File prjFile, boolean usePassFiles) throws IOException, ServiceException, JSONException {
    int years = par.run_years;

    StringBuilder output = new StringBuilder();
    output.append("#\n#  Watershed project from csip-weppws\n#\n");
    output.append("Version = 99.1\n");
    output.append("Name = csip-weppws\n");

    int numChannels = getNumChannels();
    int numHillslopes = getNumHillslopes();
    int numImpoundments = getNumImpoundments();

    output.append("Comments {\n  Hillslopes=" + numHillslopes
        + " and channels=" + numChannels + " derived from TauDEM\n}\n");

    output.append("Climate {\n");
    output.append("  File = \"wepp.cli\"\n");
    output.append("}\n");
    output.append("Size = (1000,1000)\n");
    output.append("Units = Meters\n");
    output.append("Orientation = 0.0\n");
    output.append("Scale = 1.0\n");

    output.append("\nChannelDB {\n");
    output.append("  File = \"channels.db\"\n");
    par.writeWEPPFormatChannelData("channels.db");
    //output.append(par.rawChannelDatabaseData);
    output.append("\n}\n\n");

    if (numImpoundments > 0) {
      output.append("\nImpoundmentDB {\n");
      output.append("  File = \"impoundments.db\"\n");
      par.buildWEPPFormatImpoundmentData("impoundments.db");
      output.append("\n}\n\n");
    }

    output.append("Channels {\n");
    output.append("  Number  = " + numChannels + "\n");
    Channel mychan;
    int curChan = 0;

    for (int i = 1; i <= channels.size(); i++) {
      mychan = null;
      // for subareas the simulation will only use a fraction of the total 
      // number of channels, those channels with a valid weppID will be used,
      // other channels outside the area are not used.
      for (int j = 0; j < channels.size(); j++) {
        if (channels.get(j).weppID == i) {
          mychan = channels.get(j);
          curChan = curChan + 1;
          break;
        }
      }
      if (mychan != null) {
        output.append("C" + curChan + "{\n");
        output.append("  Head = (1,1)\n");
        output.append("  Tail = (1,1)\n");
        if (mychan.flowpath != null) {
          output.append("  Length = " + mychan.flowpath.totalLengthMeters + "\n");
        } else {
          output.append("  Length = 0.1\n");
        }
        double defaultWidth = par.getChannelWidth(mychan.order);
        String defaultDef = par.getChannelName(mychan.order);
        if (par.useLanduseManInChannels) {
          // write default management, unless it is overridden in user
          // params (which is set in the next if statement
          if (mychan.userParameters != null) {
            if (mychan.userParameters.optString("management", "").equals("")) {
              output.append("  Management = \"" + par.manIndexToName(mychan.majorLanduse) + "\"\n");
            }
          } else {
              output.append("  Management = \"" + par.manIndexToName(mychan.majorLanduse) + "\"\n");
          }
        }
        if (mychan.userParameters == null) {
          output.append("  Width = " + defaultWidth + "\n");
          output.append("  Definition = \"" + defaultDef + "\"\n");
        } else {
          mychan.writeUserParameters(output, defaultWidth, defaultDef);
        }
        output.append("   Soil = \"");
        if (!par.soilIndexToName(mychan.majorSoil).equals("")) {
          output.append(par.soilIndexToName(mychan.majorSoil));
        } else {
          output.append("default.sol");
        }
        output.append("\"\n");
        output.append("  Profile = {\n");
        output.append(mychan.printProfile() + "\n");
        output.append("}\n");
        output.append("  Outlet = true\n");
        output.append("}\n");
      } else {
        if (!subarea) {
          LOG.info("Channel links incorrect, some are not set.");
          throw new ServiceException("Channel links incorrect.");
        }
      }
    }
    output.append("}\n");

    LOG.info("<<< Watershed project file - wrote channel section.");

    output.append("Impoundments {\n");
    output.append("  Number  = " + impoundments.size() + "\n");
    Impoundment myimp;
    for (int i = 1; i <= impoundments.size(); i++) {
      myimp = impoundments.get(i - 1);
      output.append("I" + i + "{\n");
      output.append("  Head = (1,1)\n");
      output.append("  Tail = (1,1)\n");
      output.append("  Height = 1\n");
      output.append("  Base = 1\n");
      output.append("  Direction = 0\n");
      output.append("  Definition = " + "\"" + myimp.getName() + "\"\n");
      output.append("}\n");
    }
    output.append("}\n");
    
    LOG.info("<<< Watershed project file - wrote impoundment section.");

    int curHillslope = 1;
    int subs = 1;
    output.append("Hillslopes {\n");
    output.append("  Number = " + numHillslopes + "\n");
    for (int i = 1; i <= subcatchments.size(); i++) {
      Subcatchment mysub = null;
      for (int j = 0; j < subcatchments.size(); j++) {
        if (subcatchments.get(j).weppid == i) {
          mysub = subcatchments.get(j);
          if (par.splitSubcatchments) {
            subs = mysub.numTopazareas();
          }
          mysub.setWeppAltID(curHillslope);
          break;
        }
      }
      if (mysub != null) {
        if (!par.splitSubcatchments) {
          // use TauDEM single area
          writeOneHillslope(output, curHillslope++, mysub, null);
        } else {
          // use TOPAZ subareas TOP, LEFT, RIGHT
          if (mysub.leftsub != null) {
            LOG.info("< Writing Left Subcatchment: " + mysub.id + " " +mysub.leftsub.id);
            mysub.leftsub.setWeppAltID(curHillslope);
            writeOneHillslope(output, curHillslope++, mysub, mysub.leftsub);
          }
          if (mysub.rightsub != null) {
           LOG.info("< Writing Right Subcatchment: " + mysub.id + " " +mysub.rightsub.id);
            mysub.rightsub.setWeppAltID(curHillslope);
            writeOneHillslope(output, curHillslope++, mysub, mysub.rightsub);
          }
          if (mysub.topsub != null) {
            LOG.info("< Writing Top Subcatchment: " + mysub.id + " " + mysub.topsub.id);
            mysub.topsub.setWeppAltID(curHillslope);
            writeOneHillslope(output, curHillslope++, mysub, mysub.topsub);
          }
        }
      }
    }
    output.append("}\n");

    LOG.info("<<< Step 4 - Wrote hillslope section");

    curChan = 0;
    output.append("Network {\n");
    // hillslopes (left,right,top) channels (left, right, top) impoundments (left,right, top)
    int total = channels.size() + impoundments.size();
    for (int i = 1; i <= channels.size(); i++) {
      // a downstream element must have a higher number than what is flowing into it.
      // The element number is implicit, it relys on the output order.
      // The channels are already ordered correcly, need to merge in the impoundments.

      mychan = null;
      for (int j = 0; j < channels.size(); j++) {
        if (channels.get(j).weppID == i) {
          mychan = channels.get(j);
          curChan = curChan + 1;
          break;
        }
      }
      if (mychan != null) {
        // check if any impoundments need to come before this entry
        if (!mychan.getLeftImpoundment().equals("0")) {
          writeNetworkImpoundment(mychan.getLeftImpoundment(), output);
        }
        if (!mychan.getRightImpoundment().equals("0")) {
          writeNetworkImpoundment(mychan.getRightImpoundment(), output);
        }
        if (!mychan.getTopImpoundment().equals("0")) {
          writeNetworkImpoundment(mychan.getTopImpoundment(), output);
        }

        // It is now safe to write out the channel
        output.append("   C" + curChan + " (");
        if (par.splitSubcatchments) {
          output.append(mychan.getLeftHillslope()).append(", ");
          output.append(mychan.getRightHillslope() + ", ");
          output.append(mychan.getTopHillslope() + ", ");
        } else {
          output.append(mychan.getHillslope()).append(", ");
          output.append("0, ");
          output.append("0, ");
        }
        output.append(mychan.getLeftChannel() + ", ");
        output.append(mychan.getRightChannel() + ", ");
        output.append(mychan.getTopChannel() + ", ");
        output.append(mychan.getLeftImpoundment() + ", ");
        output.append(mychan.getRightImpoundment() + ", ");
        output.append(mychan.getTopImpoundment() + ") # C" + mychan.id + "\n");
      } else {
        if (!subarea) {
          LOG.info("Channel links incorrect, setting network.");
          throw new ServiceException("Channel links incorrect, network.");
        }
      }
    }
    // It is possible that there is an impoundment that does not have any channels 
    // flowing into it (it at the outlet. This would be the last line.   
    for (int i = 1; i <= impoundments.size(); i++) {
      myimp = impoundments.get(i - 1);
      if (!myimp.linked) {
        writeNetworkImpoundment("I" + String.valueOf(i), output);
      }
    }
    output.append("}\n");

    LOG.info("<<< Step 5 - Wrote network section.");

    output.append("RunOptions {\n");
    output.append("  SoilLossOutputType = 1\n");
    output.append("  SoilLossOutputFile = AutoName\n");
    output.append("  EventFile = AutoName\n");
    output.append("  PlotFile = AutoName\n");
    output.append("  SimulationYears = " + years + "\n");
    output.append("  CalcMethod = " + par.runoffCalculation + "\n");
    output.append("  LenWidthRatio = 1.0\n");
    output.append("  ChanErodFlag = " + par.chanErodFlag + "\n");
    output.append("}\n");

    FileUtils.writeStringToFile(prjFile, output.toString(), "UTF-8");
  }


  void writeOneHillslope(StringBuilder output, int curHillslope, Subcatchment mysub, SubcatchmentSubarea mysub2) throws ServiceException {
    String msoils;
    String mlanduse;
    output.append("  H" + curHillslope + "{\n");
    output.append("  File = \"ww2_H" + curHillslope + "\" {\n");
    output.append("  Landuse = 1\n");
    if (mysub2 == null) {
      if (mysub.repHillslopeFlowpath == null) {
          LOG.info("No Representative hillslope: " + curHillslope + " (" + mysub.id + ") need to check TauDEM grids for non contiguous cells.");
      }
      output.append("  Length = " + mysub.getRepHillslopeLength() + "\n");
    } else {
      output.append("  Length = " + mysub2.getRepHillslopeLength() + "\n");
    }
    output.append("  Profile {\n");
    output.append("    Data {\n");
    if (mysub2 == null) {
      output.append(mysub.printProfile() + "\n");
    } else {
      output.append(mysub2.printProfile() + "\n");
    }
    output.append("    }\n");
    output.append("  }\n");
    output.append("Climate {\n");
    output.append("  File = \"wepp.cli\"\n");
    output.append("}\n");
    output.append("Soil {\n");
    output.append("  Breaks = 0\n");
    output.append("  default {\n");
    if (mysub2 == null) {
      output.append("    Distance = " + mysub.getRepHillslopeLength() + "\n");
      msoils = mysub.majorSoilName();
      if (msoils != null) {
        output.append("    File = \"" + msoils + "\"\n");
      } else {
          LOG.info("Can't get major soil (mysub) for hillslope " + curHillslope);
      }
    } else {
      output.append("    Distance = " + mysub2.getRepHillslopeLength() + "\n");
      msoils = mysub2.majorSoilName();
       if (msoils != null) {
          output.append("    File = \"" + msoils + "\"\n");
       } else {
          LOG.info("Can't get major soil (mysub2) for hillslope " + curHillslope);
       }
    }
    output.append("  }\n");
    output.append("}\n");
    output.append("Management {\n");
    output.append("   Breaks = 0\n");
    output.append("   default {\n");
    if (mysub2 == null) {
      output.append("     Distance = " + mysub.getRepHillslopeLength() + "\n");
      mlanduse = mysub.majorLanduseName();
      if (mlanduse != null) {
         output.append("     File = \"" + mlanduse + "\"\n");
      } else {
         LOG.info("Can't get major landuse (mysub) for hillslope " + curHillslope);
      }
    } else {
      output.append("     Distance = " + mysub2.getRepHillslopeLength() + "\n");
      mlanduse = mysub2.majorLanduseName();
      if (mlanduse != null) {
        output.append("     File = \"" + mlanduse + "\"\n");
      } else {
        LOG.info("Can't get major landuse (mysub2) for hillslope " + curHillslope);
      }
    }
    output.append("   }\n");
    output.append("}\n");
    output.append("RunOptions {\n");
    output.append("  Version = 1\n");
    output.append("  SoilLossOutputType = 1\n");
    output.append("  SoilLosOutputFile = AutoName\n");
    output.append("  PlotFile = AutoName\n");
    output.append("  SimulationYears = " + par.run_years + "\n");
    output.append("  SmallEventByPass = 1\n");
    output.append("}\n");
    output.append("}\n");
    output.append("  Head = (400,400)\n");
    output.append("  Direction = 30\n");
    output.append("  Angle = 90\n ");
    if (mysub2 == null) {
      output.append("  Base = " + mysub.getRepHillslopeWidth() + "\n");
      output.append("  Height = " + mysub.getRepHillslopeLength() + "\n");
    } else {
      output.append("  Base = " + mysub2.getRepHillslopeWidth() + "\n");
      output.append("  Height = " + mysub2.getRepHillslopeLength() + "\n");
    }
    output.append("  Polygon = 5 {\n");
    output.append("   (408.0,577.0) (406.0,371.0) (287.0,373.0) (290.0,579.0) (408.0,577.0)\n");
    output.append("}\n");
    output.append("}\n");
  }


  void classifyFlowpaths() {
    LOG.info(">>> Classify Flowpath runs starting now.");
    for (int i = 0; i < subcatchments.size(); i++) {
      subcatchments.get(i).classifyFlowpaths(LOG);
    }
    LOG.info("<<< Classify Flowpath ending now.");
  }


  protected int findPrevChannelCellDir(int chnEnd, int chnEndRow, int chnEndCol) {
    int prev = -1;
    for (int i = 0; i < channels.size(); i++) {
      Channel ch = channels.get(i);
      if (ch.id == chnEnd) {
        prev = ch.getPrevDir(chnEndRow, chnEndCol);
      }
    }
    return prev;
  }


  public void parseOutputs(File resultsFile) throws IOException {

    float sedYield, runoff;
    StringBuilder fileContents = new StringBuilder();
    String cellOutputs;

    try (BufferedReader reader = new BufferedReader(new FileReader(resultsFile))) {
      String line = null;
      while ((line = reader.readLine()) != null) {
        fileContents.append(line);
      }
      reader.close();

      JSONObject results = new JSONObject(fileContents.toString());
      if (results == null || !results.has("RESULTS")) {
        throw new IOException("The format of the results output file could not be read. Contents missing or \"RESULTS\" xml tag not found.");
      }

      results = results.optJSONObject("RESULTS");
      precip = (float) results.optDouble("PRECIP_IN", -999);
      dischargeVol = (float) results.optDouble("DISCHARGE_FT3", -999);
      sedimentYld = (float) results.optDouble("SEDIMENT_YLD_TON", -999);
      sedimentYldArea = (float) results.optDouble("SEDIMENT_UNIT_TONACYR", -999);
      delRatio = (float) results.optDouble("DEL_RATIO", -999);
      precipVol = (float) results.optDouble("PRECIP_FT3", -999);
      discharge = (float) results.optDouble("DISCHARGE_IN", -999);

      int id;
      Channel tempChan;
      JSONArray chns = results.getJSONArray("CHANNELS");
      for (int i = 0; i < chns.length(); i++) {
        JSONObject chan = chns.optJSONObject(i);
        id = chan.getInt("ID");
        for (int j = 0; j < channels.size(); j++) {
          tempChan = channels.get(j);
          if (tempChan.weppID == id) {
            tempChan.length = chan.getDouble("LENGTH_FT");
            tempChan.discharge = chan.getDouble("DISCHARGE_FT3");
            tempChan.sedYield = chan.getDouble("SED_YIELD_TON");
          }
        }
      }

      Impoundment tempImpound;
      JSONArray imps = results.getJSONArray("IMPOUNDMENTS");
      for (int i = 0; i < imps.length(); i++) {
        JSONObject onei = imps.optJSONObject(i);
        id = onei.getInt("ID");
        for (int j = 0; j < impoundments.size(); j++) {
          tempImpound = impoundments.get(j);
          if (tempImpound.weppID == id) {
            tempImpound.discharge = onei.getDouble("DISCHARGE_FT3");
            tempImpound.sedYield = onei.getDouble("SED_YIELD_TON");
          }
        }
      }

      JSONArray hills = results.getJSONArray("HILLSLOPES");
      Subcatchment tempSub;

      int validHills = 0;
      for (int i = 0; i < hills.length(); i++) {
        JSONObject hill = hills.optJSONObject(i);
        id = hill.getInt("ID");
        if (!par.splitSubcatchments) {
          for (int j = 0; j < subcatchments.size(); j++) {
            tempSub = subcatchments.get(j);
            if (tempSub.weppid == id) {
              //tempSub.length = hill.getDouble("LENGTH_FT");
              tempSub.avgRunoff = (float) hill.getDouble("RUNOFF_IN");
              tempSub.avgSedYield = (float) hill.getDouble("SED_YIELD_TONAC");
              tempSub.avgSoilLoss = (float) hill.getDouble("SOIL_LOSS_TONAC");
            }
          }
        } else {
          boolean found = false;
          for (int j = 0; j < subcatchments.size(); j++) {
            tempSub = subcatchments.get(j);
            if (tempSub.getInuse()) {
              if (tempSub.leftsub != null) {
                if (tempSub.leftsub.getWeppAltID() == id) {
                  tempSub.leftsub.avgRunoff = (float) hill.getDouble("RUNOFF_IN");
                  tempSub.leftsub.avgSedYield = (float) hill.getDouble("SED_YIELD_TONAC");
                  tempSub.leftsub.avgSoilLoss = (float) hill.getDouble("SOIL_LOSS_TONAC");
                  validHills++;
                  found = true;
                }
              }
              if (tempSub.rightsub != null) {
                if (tempSub.rightsub.getWeppAltID() == id) {
                  tempSub.rightsub.avgRunoff = (float) hill.getDouble("RUNOFF_IN");
                  tempSub.rightsub.avgSedYield = (float) hill.getDouble("SED_YIELD_TONAC");
                  tempSub.rightsub.avgSoilLoss = (float) hill.getDouble("SOIL_LOSS_TONAC");
                  validHills++;
                  found = true;
                }
              }
              if (tempSub.topsub != null) {
                if (tempSub.topsub.getWeppAltID() == id) {
                  tempSub.topsub.avgRunoff = (float) hill.getDouble("RUNOFF_IN");
                  tempSub.topsub.avgSedYield = (float) hill.getDouble("SED_YIELD_TONAC");
                  tempSub.topsub.avgSoilLoss = (float) hill.getDouble("SOIL_LOSS_TONAC");
                  validHills++;
                  found = true;
                }
              }
            } else {
              LOG.info("Omitted hillslope, not used: " + tempSub.id);
            }
          }
          if (found == false)
            LOG.info("**** ERROR: ID not found: " + id);
        }
      }

      LOG.info("Parsed " + validHills + " hillslopes.");

      /*
       * avgSoilLoss = (float) results.optDouble("LOSS"); avgSedYield = (float)
       * results.optDouble("SEDYIELD"); avgRunoff = (float)
       * results.optDouble("RUNOFF"); wspar.weppVersion =
       * results.getString("WEPP_VERSION"); wspar.preVersion =
       * results.getString("PREPROCESSOR_VERSION"); * cellOutputs = new
       * String(Files.readAllBytes(plotFile.toPath()));
       * readLossPlotData(cellOutputs);
       */
    } catch (Exception e) {
      precip = (float) -999;
    }
  }


  Grid createSubareaMask(Grid field_grid, Grid subcatch_grid) throws ServiceException, IOException {

    List<Integer> chs = new ArrayList<>();
    for (int i = 0; i < channels.size(); i++) {
      if (channels.get(i).getInuse() == true)
        chs.add(channels.get(i).id);

    }
    int[] vals = chs.stream().mapToInt(i -> i).toArray();
    field_grid.clear();

    String vstr = Arrays.toString(vals);
    LOG.info("**** Setting createSubareaMask: " + vstr);

    field_grid.maskSubareas(subcatch_grid, vals, 1);
    field_grid.writeGrid(new File(sessionWorkDir, "fieldgrid2.asc"));
    return field_grid;
  }


  void setTopazIDs() throws ServiceException {
    int topazid = 14;
    for (int i = 0; i < channels.size(); i++) {
      if (channels.get(i).getInuse() == true) {
        Channel ch = channels.get(i);
        ch.setTopazID(LOG,topazid);
        topazid += 10;
      }
    }
  }


  void modifySingleChannel(String id, JSONObject chdata) throws JSONException {
    int myid = Integer.parseInt(id);
    for (int i = 0; i < channels.size(); i++) {
      Channel ch = channels.get(i);
      if (ch.id == myid)
        ch.updateParameters(chdata);
    }
  }


  boolean usesChannelParms(String id) {
    for (int i = 0; i < channels.size(); i++) {
      Channel ch = channels.get(i);
      if (ch.usesChannelParms(id))
        return true;
    }
    return false;
  }


  boolean usesImpoundmentParms(String id) {
    for (int i = 0; i < impoundments.size(); i++) {
      Impoundment imp = impoundments.get(i);
      if (imp.name.equals(id))
        return true;
    }
    return false;
  }


  Channel getChannel(int id) {
    for (int i = 0; i < channels.size(); i++) {
      Channel ch = channels.get(i);
      if (channels.get(i).id == id)
        return ch;
    }
    return null;
  }


  int getImpoundmentBeforeChannel(int id) {
    for (int i = 0; i < impoundments.size(); i++) {
      Impoundment imp = impoundments.get(i);
      if (impoundments.get(i).chanid == id) {
        if (imp.position.equals("before-channel"))
          return imp.id;
      }
    }
    return 0;
  }


  void linkImpoundmentToChannel(Impoundment imp) {
    for (int i = 0; i < channels.size(); i++) {
      Channel ch = channels.get(i);
      if (ch.id == imp.chanid) {
        if (imp.position.equals("before-channel")) {
          ch.setTopImpoundment(imp);
        } else if (imp.position.equals("left-side")) {
          ch.setLeftImpoundment(imp);
        } else if (imp.position.equals("right-side")) {
          ch.setRightImpoundment(imp);
        }
      }
    }
  }


  int getImpoundmentAfterChannel(int id) {
    for (int i = 0; i < impoundments.size(); i++) {
      Impoundment imp = impoundments.get(i);
      if (impoundments.get(i).chanid == id) {
        if (imp.position.equals("after-channel"))
          return imp.id;
      }
    }
    return 0;
  }


  int getImpoundmentWeppIDAfterChannel(int id) {
    for (int i = 0; i < impoundments.size(); i++) {
      Impoundment imp = impoundments.get(i);
      if (impoundments.get(i).chanid == id) {
        if (imp.position.equals("after-channel"))
          return imp.weppID;
      }
    }
    return 0;
  }
}