Subcatchment.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 csip.utils.Parallel.Run;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import util.Grid;
import util.WeppConstants;

/**
 * Represents a WEPP and TauDEM subcatchment. This can be modeled as flowpaths
 * or combined into a representative hillslope.
 *
 */
public class Subcatchment {

  int id;
  int weppid;
  int topazid;
  int topid, leftid, rightid;
  int lastFlowpathID;
  int topRow;   // bounding box of this subcatchment
  int botRow;   // bounding box of this subcatchment
  int leftCol;  // bounding box of this subcatchment
  int rightCol; // bounding box of this subcatchment
  int majorSoil;
  int majorLanduse;
  int order;
  List<Flowpath> flowpaths = new ArrayList<>();   // all flowpaths in this subcatchment
  V1_0 service;
  SubcatchmentSubarea topsub;
  SubcatchmentSubarea leftsub;
  SubcatchmentSubarea rightsub;

  float repProfileWeights[];
  float repProfileSlopes[];

  float avgSoilLoss;      // representative soil loss based on all flowpaths
  float avgRunoff;        // representative runoff based on all flowpaths
  float avgSedYield;      // representative sediment yld based on all flowpaths
  float nrcssoilloss;     // NRCS soil loss for planning based on represtative hillslope
  float irrigation;       // irrigation based on representative hillslope
  float irrigationVol;    // irrigation volume based on representative hillslope

  double area;
  int cells;
  Flowpath repHillslopeFlowpath;    // representative hillslope of all orientations
  double repHillslopeWidth;
  int flowpathsRun;
  boolean inUse;
  int weppAltID;

  String impoundmentAtEnd;


  Subcatchment(int id, V1_0 par, int idx) {
    this.id = id;
    this.service = par;
    weppid = idx;
    repHillslopeFlowpath = null;
    inUse = false;
    order = 0;
    weppAltID = 0;
    impoundmentAtEnd = "";
  }


  Subcatchment(int id, V1_0 par, int idx, WeppConstants.SubcatchmentType type, Subcatchment par2) {
    this.id = id;
    this.service = par;
    weppid = idx;
    repHillslopeFlowpath = null;
    inUse = false;
    order = 0;
  }


  int addFlowpath(List<Integer> rows, List<Integer> cols,
      List<Float> slp, List<Integer> man, List<Integer> soil, List<Float> dist,
      int chnEndRow, int chnEndCol, int chan) {
    int r[] = new int[rows.size()];
    int c[] = new int[cols.size()];
    float sl[] = new float[slp.size()];
    int s[] = new int[soil.size()];
    int m[] = new int[man.size()];
    float d[] = new float[dist.size()];

    lastFlowpathID++;

    for (int i = 0; i < rows.size(); i++) {
      r[i] = rows.get(i);
      c[i] = cols.get(i);
      sl[i] = slp.get(i);
      s[i] = soil.get(i);
      m[i] = man.get(i);
      d[i] = dist.get(i);
    }
    flowpaths.add(new Flowpath(lastFlowpathID, r, c, sl, m, s, d, service, chnEndRow, chnEndCol, chan));
    return lastFlowpathID;
  }


  int writeFlowpaths(File baseDir) throws IOException, JSONException {
    File fileName = new File(baseDir, "flowpaths_" + Integer.toString(id));
    try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
      for (int i = 0; i < flowpaths.size(); i++) {
        flowpaths.get(i).print(writer);
      }
      writer.close();
    }
    return flowpaths.size();
  }


  String summarizeInput() throws JSONException {
    JSONObject so = new JSONObject()
        .put("id", id)
        .put("weppid", weppid)
        .put("topid", topid)
        .put("leftid", leftid)
        .put("rightid", rightid)
        .put("Area (ac)", area * WeppConstants.CONV_HA_TO_AC)
        .put("Grid cells", cells)
        .put("Major Soil", service.soilIndexToShortName(majorSoil))
        .put("Major Soil id", majorSoil)
        .put("Major Landuse", service.manIndexToShortName(majorLanduse))
        .put("Major Landuse id", majorLanduse)
        .put("Percentage of total area", ((double) cells / (double) service.aoi.getArea()) * 100);
    return so.toString(2);
  }


  String summarizeOutput(SessionLogger log) throws JSONException {
    double avgRunoffVol;
    try {
      avgRunoffVol = (repHillslopeFlowpath.totalLengthMeters * WeppConstants.CONV_M_TO_FT)
          * (repHillslopeWidth * WeppConstants.CONV_M_TO_FT) * (avgRunoff / 12.0);
    } catch (Exception e) {
      avgRunoffVol = -999;
      log.info(e.toString());
      if (repHillslopeFlowpath != null) {
        log.info(repHillslopeFlowpath.toString());
      } else {
        log.info("No representative hillslope created for subcatchment " + id);
        log.info("Number of flowpaths: " + flowpaths.size());
        JSONObject so = new JSONObject()
            .put("id", id)
            .put("weppid", weppid)
            .put("inuse", inUse)
            .put("Area (ac)", area * WeppConstants.CONV_HA_TO_AC)
            .put("Grid cells", cells)
            .put("Major Soil", service.soilIndexToShortName(majorSoil))
            .put("Major Soil id", majorSoil)
            .put("Major Landuse", service.manIndexToShortName(majorLanduse))
            .put("Major Landuse id", majorLanduse)
            .put("Percentage of total area", ((double) cells / (double) service.aoi.getArea()) * 100)
            .put("Representative Hillslope Length (ft)", -999)
            .put("Representative Hillslope Width (ft)", -999)
            .put("Soil Loss (ton/yr)", -999)
            .put("Soil Loss per Area (ton/ac/yr)", -999)
            .put("NRCS Soil Loss for Conservation Planning (ton/ac/yr)", -999)
            .put("Sediment Yield (ton/yr)", -999)
            .put("Sediment Yield per Area (ton/ac/yr)", -999)
            .put("Runoff (in/yr)", -999)
            .put("Runoff Volume (ft^3/yr)", -999)
            .put("Irrigation (in/yr)", -999)
            .put("Irrigation Volume (ft3/yr)", -999)
            .put("Flowpaths within subcatchment", flowpaths.size())
            .put("Message", "No flowpaths calculated in subcatchment");
        return so.toString(2);
      }
    }
    JSONObject so = new JSONObject()
        .put("id", id)
        .put("weppid", weppid)
        .put("inuse", inUse)
        .put("Area (ac)", area * WeppConstants.CONV_HA_TO_AC)
        .put("Grid cells", cells)
        .put("Major Soil", service.soilIndexToShortName(majorSoil))
        .put("Major Soil id", majorSoil)
        .put("Major Landuse", service.manIndexToShortName(majorLanduse))
        .put("Major Landuse id", majorLanduse)
        .put("Percentage of total area", ((double) cells / (double) service.aoi.getArea()) * 100)
        .put("Representative Hillslope Length (ft)", repHillslopeFlowpath.totalLengthMeters * WeppConstants.CONV_M_TO_FT)
        .put("Representative Hillslope Width (ft)", repHillslopeWidth * WeppConstants.CONV_M_TO_FT)
        .put("Soil Loss (ton/yr)", avgSoilLoss)
        .put("Soil Loss per Area (ton/ac/yr)", avgSoilLoss / (area * WeppConstants.CONV_HA_TO_AC))
        .put("NRCS Soil Loss for Conservation Planning (ton/ac/yr)", nrcssoilloss)
        .put("Sediment Yield (ton/yr)", avgSedYield)
        .put("Sediment Yield per Area (ton/ac/yr)", avgSedYield / (area * WeppConstants.CONV_HA_TO_AC))
        .put("Runoff (in/yr)", avgRunoff)
        .put("Runoff Volume (ft^3/yr)", avgRunoffVol)
        .put("Irrigation (in/yr)", irrigation)
        .put("Irrigation Volume (ft3/yr)", irrigationVol)
        .put("Flowpaths within subcatchment", flowpaths.size())
        .put("Message", "");
    return so.toString(2);
  }


  //
  // runFlowpaths0()
  //
  // Uses CSIP services to make parallel WEPP runs.
  //
  int runFlowpaths0(SessionLogger LOG, File workspace, Grid field, boolean statsOnly) throws Exception {

    LOG.info("Parallel Flowpath runs for subcatchment " + id);
    AtomicInteger numRun = new AtomicInteger();
    int nthreads = Config.getInt("weppws.fp.par", 16);

    if (!flowpaths.isEmpty()) {
      Parallel.run(nthreads, flowpaths.size(), new Function<Integer, Run>() {
        @Override
        public Run apply(Integer t) {
          System.out.println("Created FP Run: " + t);
          service.progress("Running " + id + "/" + t);
          return () -> {
            Flowpath fp = flowpaths.get(t);
            if (!statsOnly && fp.intersectsField(field)) {
              System.out.println("Running FP: >>>>>>>>>>>>>>>>>  " + t);
              fp.runFlowpathService(workspace, service.aoi, service.flow_d8_grid.getCellsize(), false);
              numRun.incrementAndGet();
            }
          };
        }
      });
    }

    return flowpathsRun = numRun.get();
  }


  //
  // runFlowpaths1()
  //
  // Runs all, or a subset percentage, of the flowpaths in a the subcatchment.
  // The runs are done by calling the local executable, a service call is not 
  // done to run WEPP. Every call must wait until the previous run is complete.
  //
  int runFlowpathsLocal(SessionLogger LOG, File workspace, Executable weppserv, Grid field,
      boolean statsOnly, int subsetPercentage) throws ServiceException, IOException, Exception {

    LOG.info("Exe Flowpath runs for subcatchment " + id);
    int numRun = 0;
    double div = ((double) subsetPercentage) / (double) 100;
    int freq = (int) (1.0 / div);

    flowpathsRun = 0;
    int i = 0;
    while (i < flowpaths.size()) {
      if (flowpaths.get(i).intersectsField(field)) {
        LOG.info(">>> Starting flowpath: " + i);
        if (statsOnly == false) {
          // may only be running a subset of the flowpaths
          if ((subsetPercentage == 100) || ((i % freq) == 0)) {
            flowpaths.get(i).runFlowpathLocal(workspace, weppserv,
                service.aoi, service.flow_d8_grid.getCellsize(), false);
            numRun++;
          }
        } else {
          numRun++;
        }
      }
      i++;
    }
    flowpathsRun = numRun;
    return numRun;
  }


  //
  // runFlowpaths()
  //
  // Original version before CSIP parallel version. Makes a service call to run
  // each flowpath WEPP model run. At most 16 instances of WEPP are running at
  // once, new run requests will block until a slot opens up.
  // @deprecated
  //
  //
//  int runFlowpaths(SessionLogger LOG, File workspace, Executable weppserv, Grid field,
//      boolean statsOnly, int subsetPercentage) throws ServiceException, IOException, Exception {
//
//    LOG.info("Non-Parallel Flowpath runs for subcatchment " + id);
//    int numRun = 0;
//    double div = ((double) subsetPercentage) / (double) 100;
//    int freq = (int) (1.0 / div);
//    int concurrentMax = 16;   // maximum number of flowpath runs at a time
//    int concount = 0;     // current number of running flowpaths
//
//    List<JSONObject> runningFlowpaths = new ArrayList<>();
//    List<Integer> runningIndex = new ArrayList<>();
//
//    flowpathsRun = 0;
//    int i = 0;
//    while (i < flowpaths.size()) {
//      if (flowpaths.get(i).intersectsField(field)) {
//        LOG.info(">>> Starting flowpath: " + i);
//        if (statsOnly == false) {
//          // may only be running a subse of the flowpaths
//          if ((subsetPercentage == 100) || ((i % freq) == 0)) {
//            // only start a run if we are running less than the maximum instances
//            if (concount < concurrentMax) {
//              JSONObject resp = flowpaths.get(i).runFlowpath(workspace, weppserv,
//                  service.aoi, service.flow_d8_grid.getCellsize(), false);
//              // a response other than null indicates this was done as an async and 
//              // needs to polled in the future.
//              if (resp != null) {
//                runningFlowpaths.add(resp);
//                runningIndex.add(i);
//                concount++;
//              }
//              numRun++;
//              i++;
//            } else {
//              // need to wait till some runs finish before continuing to add new simulations
//              // need to have a timeout if this fails
//              while (concount >= concurrentMax) {
//                Thread.sleep(2000);
//                // check the status of all running 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 = flowpaths.get(runningIndex.get(curIdx)).
//                          checkProgress(LOG, runningFlowpaths.get(curIdx), workspace, service.aoi);
//                      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;
//              }
//            }
//          } else {
//            i++;
//          }
//        } else {
//          numRun++;
//          i++;
//        }
//      } else {
//        i++;
//      }
//    }
//    // it may be that there are still some runs waiting to complete
//    while (concount > 0) {
//      Thread.sleep(2000);
//      // check the status of all running 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 = flowpaths.get(runningIndex.get(curIdx))
//                .checkProgress(LOG, runningFlowpaths.get(curIdx), workspace, service.aoi);
//            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;
//    }
//    flowpathsRun = numRun;
//    return numRun;
//  }
  void makeRepresentativeHillslopeNoData(float cellSize) {
    int[] r = {0};
    int[] c = {0};
    float[] sl = {(float) 0.001};
    int[] s = {majorSoil};
    int[] m = {majorLanduse};
    float[] d = {(float) cellSize};

    repHillslopeFlowpath = new Flowpath(id, r, c, sl, m, s, d, service, -1, -1, -1);
    repHillslopeWidth = cellSize;
  }


  void makeRepresentativeHillslope(float cellSize) {
    float longest = 0;

    for (int i = 0; i < flowpaths.size(); i++) {
      if (flowpaths.get(i).totalLengthMeters > longest) {
        longest = flowpaths.get(i).totalLengthMeters;
      }
    }
    int cellsNeeded = (int) ((double) longest / cellSize);
    repProfileWeights = new float[cellsNeeded];
    repProfileSlopes = new float[cellsNeeded];

    for (int i = 0; i < flowpaths.size(); i++) {
      flowpaths.get(i).updateRepProfile(cellSize, repProfileSlopes, repProfileWeights);
    }
    for (int i = 0; i < repProfileSlopes.length; i++) {
      repProfileSlopes[i] = repProfileSlopes[i] / repProfileWeights[i];
    }

    int r[] = new int[cellsNeeded];
    int c[] = new int[cellsNeeded];
    float sl[] = new float[cellsNeeded];
    int s[] = new int[cellsNeeded];
    int m[] = new int[cellsNeeded];
    float d[] = new float[cellsNeeded];

    for (int i = 0; i < cellsNeeded; i++) {
      r[i] = 0;
      c[i] = 0;
      sl[i] = repProfileSlopes[i] / (float) 100.0;   // slopes were calculated as %, need to be 0-1 for constructor
      s[i] = majorSoil;
      m[i] = majorLanduse;
      d[i] = (float) cellSize;
    }
    repHillslopeFlowpath = new Flowpath(id, r, c, sl, m, s, d, service, -1, -1, -1);
    repHillslopeWidth = (area * 10000) / repHillslopeFlowpath.totalLengthMeters;
  }


  int runRepresentativeHillslope0(File workspace, Executable weppserv,
      Grid field, boolean statsOnly, boolean splitArea, boolean usePassFiles) throws ServiceException, IOException, Exception {

    if (splitArea == false) {
      makeRepresentativeHillslope((float) field.getCellsize());
      if (repHillslopeFlowpath == null) {
        avgSoilLoss = 0;
        avgRunoff = 0;
        avgSedYield = 0;
        irrigation = 0;
        return 0;
      }
      if (statsOnly == false) {
        repHillslopeFlowpath.runFlowpathService(workspace, service.aoi, repHillslopeWidth, usePassFiles);
        avgSoilLoss = repHillslopeFlowpath.avgSoilLoss;
        avgRunoff = repHillslopeFlowpath.avgRunoff;
        avgSedYield = repHillslopeFlowpath.avgSedYield;
        irrigation = repHillslopeFlowpath.avgIrrigation;
      }
      return 1;
    } else {
      int runs = 0;
      if (topsub != null) {
        runs += topsub.runRepresentativeHillslope0(workspace, weppserv, field, statsOnly, usePassFiles);
      }
      if (leftsub != null) {
        runs += leftsub.runRepresentativeHillslope0(workspace, weppserv, field, statsOnly, usePassFiles);
      }
      if (rightsub != null) {
        runs += rightsub.runRepresentativeHillslope0(workspace, weppserv, field, statsOnly, usePassFiles);
      }
      return runs;
    }

  }


  // 
  // runRepresentativeHillslope()
  //
  // Runs the flowpath that is an aggregation of all flowpaths in a subcatchment.
  // Calls to either run the simulation as a service or directly (runflowpath1).
  //
  JSONObject runRepresentativeHillslope(SessionLogger log, File workspace, Executable weppserv,
      Grid field, boolean statsOnly, boolean splitArea, boolean usePassFiles) throws ServiceException, IOException, Exception {

    JSONObject resp = null;

    if (splitArea == false) {
      makeRepresentativeHillslope((float) field.getCellsize());
      if (repHillslopeFlowpath == null) {
        avgSoilLoss = 0;
        avgRunoff = 0;
        avgSedYield = 0;
        irrigation = 0;
        return null;
      }
      if (statsOnly == false) {
        //if (service.flowpathURL.length() > 0) {
        //     resp = repHillslopeFlowpath.runFlowpath(workspace, weppserv, service.aoi, repHillslopeWidth);
        //    if (resp != null) {
        //       avgSoilLoss = repHillslopeFlowpath.avgSoilLoss;
        //       avgRunoff = repHillslopeFlowpath.avgRunoff;
        //      avgSedYield = repHillslopeFlowpath.avgSedYield;
        //     } 
        //} else {
        resp = repHillslopeFlowpath.runFlowpathLocal(workspace, weppserv, service.aoi, repHillslopeWidth, usePassFiles);
        avgSoilLoss = repHillslopeFlowpath.avgSoilLoss;
        avgRunoff = repHillslopeFlowpath.avgRunoff;
        avgSedYield = repHillslopeFlowpath.avgSedYield;
        irrigation = repHillslopeFlowpath.avgIrrigation;
        //}

      }
      return resp;
    } else {
      log.info("*** runRepresentativeHillslope " + id);
      if (topsub != null) {
        log.info("*** runRepresentativeHillslope-TOP " + id);
        topsub.runRepresentativeHillslope(log, workspace, weppserv, field, statsOnly, usePassFiles);
      }
      if (leftsub != null) {
        log.info("*** runRepresentativeHillslope-LEFT " + id);
        leftsub.runRepresentativeHillslope(log, workspace, weppserv, field, statsOnly, usePassFiles);
      }
      if (rightsub != null) {
        log.info("*** runRepresentativeHillslope-RIGHT " + id);
        rightsub.runRepresentativeHillslope(log, workspace, weppserv, field, statsOnly, usePassFiles);
      }

      return null;
    }
  }


  //
  // checkProgress()
  //
  // Checksif the representaive hillslope run is complete if it was started from a
  // service call.
  //
  JSONObject checkProgress(SessionLogger LOG, JSONObject resp, File workspace)
      throws ServiceException, IOException, Exception {
    JSONObject resp2 = repHillslopeFlowpath.checkProgress(LOG, resp, workspace, service.aoi);
    if (resp2 == null) {
      avgSoilLoss = repHillslopeFlowpath.avgSoilLoss;
      avgRunoff = repHillslopeFlowpath.avgRunoff;
      avgSedYield = repHillslopeFlowpath.avgSedYield;
      irrigation = repHillslopeFlowpath.avgIrrigation;
    }
    return resp2;
  }


  float getAvgSoilLoss() {
    return avgSoilLoss;
  }


  float getAvgRunoff() {
    return avgRunoff;
  }


  float getAvgSedYield() {
    return avgSedYield;
  }


  float getAvgIrrigation() {
    return irrigation;
  }


  Grid outputFlowpaths(Grid lossGrid, Grid sedWeightGrid) {
    for (int i = 0; i < flowpaths.size(); i++) {
      flowpaths.get(i).updateWeightedLossGrid(lossGrid, sedWeightGrid);
    }
    return lossGrid;
  }


  void setMajorSoil(int val) {
    majorSoil = val;
  }


  void setMajorLanduse(int val) {
    majorLanduse = val;
  }


  double calcArea(Grid subcatchments, Grid bound) throws ServiceException {
    cells = subcatchments.cellCountWithMask(id, bound);
    area = (cells * subcatchments.getCellsize() * subcatchments.getCellsize()) / 10000; // total area in ha
    return area;
  }


  double getAreasOfSubs() {
    double area2 = 0;

    if (topsub != null) {
      area2 += topsub.area;
    }
    if (leftsub != null) {
      area2 += leftsub.area;
    }
    if (rightsub != null) {
      area2 += rightsub.area;
    }

    return area2;
  }


  String printProfile() {
    if (repHillslopeFlowpath != null) {
      return repHillslopeFlowpath.printProfile(repHillslopeWidth);
    } else {
      return ("Representative profile not set for hillslope." + String.valueOf(id));
    }
  }


  String soilIndexToName(int idx) throws ServiceException {
    String name;
    try {
      name = service.soilIndexToName(idx);
      if (name.equals("")) {
        throw new ServiceException("Could not find soil to match index: " + Integer.toString(idx));
      }
    } catch (JSONException je) {
      throw new ServiceException("Could not find soil to match index: " + Integer.toString(idx));
    }
    return name;
  }


  String manIndexToName(int idx) throws ServiceException {
    String name;
    try {
      name = service.manIndexToName(idx);
      if (name.equals("")) {
        throw new ServiceException("Could not find management to match index: " + Integer.toString(idx));
      }
    } catch (JSONException je) {
      throw new ServiceException("Could not find management to match index: " + Integer.toString(idx));
    }
    return name;
  }


  Grid fillsub3grid(SessionLogger LOG,Grid sub3) throws ServiceException {
    WeppConstants.Orientation mydir;
    int cellsFilled = 0;
    
    for (int i = 0; i < flowpaths.size(); i++) {
      cellsFilled = flowpaths.get(i).fillsub3grid(sub3, topazid);
      if (cellsFilled <= 0) {
          LOG.info(">>> Flowpath has no cells: " + id + " " + topazid);
      } 
      mydir = flowpaths.get(i).getDir();
      if (mydir == WeppConstants.Orientation.TOP) {
        if (topsub == null) {
          topsub = new SubcatchmentSubarea(topazid + 3, service, weppid, WeppConstants.SubcatchmentType.TOP, this);
        }
      } else if (mydir == WeppConstants.Orientation.LEFT) {
        if (leftsub == null) {
          leftsub = new SubcatchmentSubarea(topazid + 1, service, weppid, WeppConstants.SubcatchmentType.LEFT, this);
        }

      } else if (mydir == WeppConstants.Orientation.RIGHT) {
        if (rightsub == null) {
          rightsub = new SubcatchmentSubarea(topazid + 2, service, weppid, WeppConstants.SubcatchmentType.RIGHT, this);
        }
      } else if (mydir == WeppConstants.Orientation.UNKNOWN) {
            LOG.info(">>> Flowpath UNKNOWN subgrid3 cells: " + id + " " + Integer.toString(topazid) + " (" + cellsFilled + ")");
            LOG.info(">>> Unknown Coord: " + flowpaths.get(i).rows[0] + " " + flowpaths.get(i).cols[0]);
      } else if (mydir == WeppConstants.Orientation.NOTFOUND) {
            LOG.info(">>> Flowpath NOTFOUND subgrid3 cells: " + id + " " + Integer.toString(topazid) + " (" + cellsFilled + ")");
            LOG.info(">>> Unknown Coord: " + flowpaths.get(i).rows[0] + " " + flowpaths.get(i).cols[0]);
      }
    }
    return sub3;
  }


  void classifyFlowpaths(SessionLogger LOG) {
    int valid = 0;
    int invalid = 0;
    for (int i = 0; i < flowpaths.size(); i++) {
      try {
        flowpaths.get(i).classifyFlowpath(order);
        if ((flowpaths.get(i).relativePosition == WeppConstants.Orientation.NOTFOUND) || (flowpaths.get(i).relativePosition == WeppConstants.Orientation.UNKNOWN)) {
           LOG.info(">Flowpath orientation error in subcatchment: " + id);
           invalid = invalid + 1;
        } else {
            valid = valid + 1;
        }
      } catch (ServiceException ex) {
        System.out.println("ServiceException: " + ex.toString());
      }
    }
    LOG.info(">Classified Flowpaths for subcatchment: " + id + " valid: " + valid + " invalid: " + invalid);
  }


  double getRepHillslopeWidth() {
    return repHillslopeWidth;
  }


  double getRepHillslopeLength() {
    return repHillslopeFlowpath.totalLengthMeters;
  }


  String majorLanduseName() throws ServiceException {
    return manIndexToName(majorLanduse);
  }


  String majorSoilName() throws ServiceException {
    return soilIndexToName(majorSoil);
  }


  void setInuse(boolean val) {
    inUse = val;
  }


  boolean getInuse() {
    return inUse;
  }


  void setWeppID(int val) {
    weppid = val;
  }


  void setTopazID(int val) {
    topazid = val;
  }


  void setOrder(int val) {
    order = val;
  }


  int numTopazareas() {
    int areas = 0;
    if (inUse) {
      if (topsub != null) {
        areas++;
      }
      if (leftsub != null) {
        areas++;
      }
      if (rightsub != null) {
        areas++;
      }
    }

    return areas;
  }


  void setWeppAltID(int val) {
    weppAltID = val;
  }


  int getCells() {
    return cells;
  }


  double getPercentageArea() {
    return ((double) cells / (double) service.aoi.getArea()) * 100;
  }
}