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

import csip.utils.Client;
import csip.api.server.Executable;
import static csip.ModelDataService.KEY_METAINFO;
import static csip.ModelDataService.KEY_PARAMETER;
import csip.api.client.ModelDataServiceCall;
import csip.api.server.ServiceException;
import csip.SessionLogger;
import csip.utils.JSONUtils;
import csip.utils.ZipFiles;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.codehaus.jettison.json.JSONObject;
import org.codehaus.jettison.json.JSONException;
import org.json.XML;
import java.io.Writer;
import java.nio.file.Files;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static m.weppws.ApplicationResources.USE64BITWEPP;
import org.codehaus.jettison.json.JSONArray;
import util.Grid;
import util.WeppConstants;

/**
 *
 * Represents a single flowpath, can have multiple managements or soils. Child
 * of a subcatchment or channel.
 *
 */
public class Flowpath {

  int id;

  float[] distance;  // distance between cell centers for each cell in path
  float[] slope;  // D8 slope between each cell on path
  int[] rows;  // row coordinate of each cell
  int[] cols;  // column coordinate of each cell
  int[] soils;  // soil ids for each cell
  int[] managements; // management ids for each cell
  float[] soilLoss; // soil loss at each cell

  List<Double> soilOFELengths = new ArrayList<>();
  List<Integer> soilOFETypes = new ArrayList<>();
  List<Double> manOFELengths = new ArrayList<>();
  List<Integer> manOFETypes = new ArrayList<>();

  float totalLengthMeters; // total flowpath length
  float avgSoilLoss;
  float avgRunoff;
  float avgSedYield;
  float avgIrrigation;
  boolean simulationDone;
  static int weightMethod;
  
  Channel parc;
  int chnEndRow;
  int chnEndCol;
  int chnEnd;
  String posString;
  WeppConstants.Orientation relativePosition;
  V1_0 par;


  Flowpath(int id, int rows[], int cols[], float slope[], int man[], int soil[], 
          float dist[], V1_0 par, int chnEndRow, int chnEndCol, int chnEnd) {
    this.rows = rows;
    this.cols = cols;
    this.slope = slope;
    distance = dist;
    managements = man;
    soils = soil;
    this.id = id;
    this.par = par;
    this.chnEndRow = chnEndRow;
    this.chnEndCol = chnEndCol;
    this.chnEnd = chnEnd;
    totalLengthMeters = 0;
    avgSoilLoss = 0;
    avgSedYield = 0;
    avgRunoff = 0;
    avgIrrigation = 0;
    parc = null;
    relativePosition = WeppConstants.Orientation.UNKNOWN;
    posString = "?-?-?";

    // convert slope from 0-1 to range 0-100%
    for (int i = 0; i < slope.length; i++) {
      slope[i] = ((float) ((int) (slope[i] * 10000))) / 100;
      if (slope[i] < 0.1) {
        slope[i] = (float) 0.1;
      }
      distance[i] = ((float) ((int) (distance[i] * 100))) / 100;
      totalLengthMeters += distance[i];
    }

    simulationDone = false;
    weightMethod = 0;  // weighted average, gives more wieght to longer flowpaths when they merge
    //weightMethod = 1;  // average - all flowpaths that merege are averaged
    //weightMethod = 2;  // maximum - the highest loss at a cell is used for merged cells
  }

  WeppConstants.Orientation getDir() {
      return relativePosition;
  }
  
  void print(Writer outfile) throws IOException, JSONException {
    outfile.write(String.format("{\"id\":%d,", id));
    switch (relativePosition) {
        case UNKNOWN:
             outfile.write(String.format("{\"DIR\":%s-%s,", "UNK", posString));
             break;
        case NOTFOUND:
             outfile.write(String.format("{\"DIR\":%s-%s,", "BAD", posString));
             break;
        case TOP:
            outfile.write(String.format("{\"DIR\":%s-%s,", "TOP",posString));
            break;
        case LEFT:
            outfile.write(String.format("{\"DIR\":%s-%s,", "LEFT",posString));
            break;
        case RIGHT:
            outfile.write(String.format("{\"DIR\":%s-%s,", "RIGHT",posString));
            break;
    }
    outfile.write(String.format("\"rows\":%s,", Arrays.toString(rows)));
    outfile.write(String.format("\"cols\":%s,", Arrays.toString(cols)));
    outfile.write(String.format("\"soils\":%s,", Arrays.toString(soils)));
    outfile.write(String.format("\"mans\":%s,", Arrays.toString(managements)));
    if (soilLoss != null) {
      outfile.write(String.format("\"loss\":%s,", Arrays.toString(soilLoss)));
    }

    String line = "\"dist\":[";
    for (int i = 0; i < distance.length; i++) {
      if (i > 0) {
        line = line + "," + distance[i];
      } else {
        line = line + distance[i];
      }
    }

    line = line + "],";
    outfile.write(line);
    line = "\"slope\":[";
    for (int i = 0; i < slope.length; i++) {
      if (i > 0) {
        line = line + "," + slope[i]; //String.format(",%f",slope[i]*100);
      } else {
        line = line + slope[i]; //String.format("%f",slope[i]*100);
      }
    }
    line = line + "],";
    line = line + "\"soilLoss\":" + avgSoilLoss + "}\n";
    outfile.write(line);
  }

  //
  // runFlowpath1()
  //
  // Run WEPP model directly, no service URL is started.
  //
   JSONObject runFlowpathLocal(File workspace, Executable weppserv,
      Watershed wspar, double cellsize, boolean usePassFiles) throws ServiceException, IOException, Exception {

    boolean skip_runs = false;
  
    buildSlopeInput(new File(workspace, "wepp.slp"), cellsize);
    buildProjectInput(new File(workspace, "wepp.prj"), usePassFiles);
    
    // reset soilloss
    soilLoss = new float[rows.length];

    simulationDone = true;
    
    if (!skip_runs) {
      int ret = weppserv.exec();
      if (ret != 0) {
        throw new ServiceException("WEPP run error: " + ret);
      } else {
        parseOutputs(new File(workspace, "output/weppout.txt"), new File(workspace, "output/plot_0.txt"), wspar);
      }

      // if you reuse the weppservobject, you need to reinit the
      // output streams, or call getRessourceExe again.
      weppserv.redirectDefaults();
    } else {
      avgSoilLoss = avgSedYield = avgRunoff = avgIrrigation = (float) -999;
      wspar.precip = (float) -999;
      wspar.weppVersion = "???";
      wspar.preVersion = "???";
    }

    return null;
  }

  //
  // runFlowpath()
  //
  // Run the flowpath service, flowpath URL services are started upto a maximum 
  // and then waits for some to finish before starting any new ones. 
  //
//  JSONObject runFlowpath(File workspace, Executable weppserv,
//      Watershed wspar, double cellsize, boolean usePassFiles) throws ServiceException, IOException, Exception {
////    boolean skip_runs = false;
////    String flowpathServiceURL = wspar.par.flowpathURL;
//
//    buildSlopeInput(new File(workspace, "wepp.slp"), cellsize);
//    buildProjectInput(new File(workspace, "wepp.prj"), usePassFiles);
// 
//    // reset soilloss
//    soilLoss = new float[rows.length];
//    
//    simulationDone = true;
//
//    JSONObject res = runFlowpathService(workspace, wspar);
//    return res;
//  }

  //
  //
  // Starts parallel runs of flowpaths using CSIP service in separate workspaces.
  // 
  ModelDataServiceCall runFlowpathService(File workspace, Watershed wspar, double cellsize, boolean usePassFiles)
      throws JSONException, ServiceException, Exception {

    File fp_ws = new File(workspace, Integer.toString(id));
    if (!fp_ws.exists()) 
      fp_ws.mkdirs();

    buildSlopeInput(new File(fp_ws, "wepp.slp"), cellsize);
    buildProjectInput(new File(fp_ws, "wepp.prj"), usePassFiles);

    // reset soilloss
    soilLoss = new float[rows.length];

    List<File> files = new ArrayList<>();
    files.add(new File(fp_ws, "wepp.prj"));
    files.add(new File(workspace, "wepp.cli"));
    files.add(new File(fp_ws, "wepp.slp"));

    Set<Integer> uniqueSoils = new HashSet<>();
    for (int i = 0; i < soilOFETypes.size(); i++) {
      if (!uniqueSoils.contains(soilOFETypes.get(i))) {
        uniqueSoils.add(soilOFETypes.get(i));
        files.add(new File(workspace, soilIndexToName(soilOFETypes.get(i))));
      }
    }

    Set<Integer> uniqueManagements = new HashSet<>();
    for (int i = 0; i < manOFETypes.size(); i++) {
      if (!uniqueManagements.contains(manOFETypes.get(i))) {
        uniqueManagements.add(manOFETypes.get(i));
        files.add(new File(workspace, manIndexToName(manOFETypes.get(i))));
      }
    }

    File zipFiles = ZipFiles.zip(new File(fp_ws, "weppfiles.zip"), files);

    ModelDataServiceCall res = new ModelDataServiceCall()
        .withDefaultLogger()
        .url(wspar.par.flowpathURL)
        .putMeta("csip.archive.enabled", "false")
        .put(USE64BITWEPP, wspar.par.use64BitWEPP)
        .put("id", id)
        .attach(zipFiles)
        .call();

    simulationDone = true;

    if (res.serviceFinished()) {
      avgSoilLoss = (float) res.getDouble("SoilLoss", -999);
      avgSedYield = (float) res.getDouble("SedimentYield", -999);
      avgRunoff = (float) res.getDouble("Runoff", -999);
      avgIrrigation = (float) res.getDouble("Irrigation", -999);

      wspar.precip = (float) res.getDouble("Precipitation", -999); // the same for all FP?
      wspar.weppVersion = res.getMetaString("WEPPVersion");
      wspar.preVersion = res.getMetaString("PreprocessorVersion");

      String cellOutputs = res.getString("SoilLossCells", "???");
      readLossPlotData(cellOutputs);
    }

    FileUtils.deleteDirectory(fp_ws);
    return res;
  }


//  JSONObject runFlowpathService(File workspace, Watershed wspar) throws JSONException, ServiceException, Exception {
//
////    String sessionWorkDir = workspace.getAbsolutePath();
//    String flowpathServiceURL = wspar.par.flowpathURL;
//    JSONObject responseMeta = null;
//
//    List<File> files = new ArrayList<>();
//    files.add(new File(workspace, "wepp.prj"));
//    files.add(new File(workspace, "wepp.cli"));
//    files.add(new File(workspace, "wepp.slp"));
//
//    Set<Integer> uniqueSoils = new HashSet<>();
//    for (int i = 0; i < soilOFETypes.size(); i++) {
//      if (!uniqueSoils.contains(soilOFETypes.get(i))) {
//        uniqueSoils.add(soilOFETypes.get(i));
//        File f = new File(workspace, soilIndexToName(soilOFETypes.get(i)));
//        if (f.exists()) {
//            files.add(f);
//        } else {
//             throw new ServiceException("Could not find local file for WEPP: " + f.getName());
//        }
//      }
//    }
//
//    Set<Integer> uniqueManagements = new HashSet<>();
//    for (int i = 0; i < manOFETypes.size(); i++) {
//      if (!uniqueManagements.contains(manOFETypes.get(i))) {
//        uniqueManagements.add(manOFETypes.get(i));
//        File f = new File(workspace, manIndexToName(manOFETypes.get(i)));
//        if (f.exists()) {
//            files.add(f);
//        } else {
//            throw new ServiceException("Could not find local file for WEPP: " + f.getName());
//        }
//      }
//    }
//
//    File zipFiles = ZipFiles.zip(new File(workspace, "weppfiles.zip"), files);
//
//    try {
//      JSONObject flowpathRequest = new JSONObject();
//      JSONObject fpResponse;
//      String names[] = new String[1];
//      File filesz[] = new File[1];
//      JSONObject meta = new JSONObject();
//      JSONArray paramObj = new JSONArray();
//      meta.put("mode", "async");
//      flowpathRequest.put(KEY_METAINFO, meta);
//      
//      if (wspar.par.use64BitWEPP) {
//        JSONObject opt = new JSONObject();
//        opt.put("name", "use64BitWEPP");
//        opt.put("value", true);
//        paramObj.put(opt);
//      }
//      
//      flowpathRequest.put(KEY_PARAMETER, paramObj);
//
//      Map<String, JSONObject> resultMap;
//
//      names[0] = "weppfiles.zip";
////      files[0] = new File(sessionWorkDir + "/weppfiles.zip");
//      filesz[0] = zipFiles;
//      
//      wspar.LOG.info("flowpathServiceURL: " + flowpathServiceURL);
//      
//      //wspar.LOG.info("flowpath request: " + flowpathRequest.toString());
//
//      fpResponse = new Client(300).doPOST(flowpathServiceURL, flowpathRequest, filesz, names);
//
//      responseMeta = fpResponse.getJSONObject(KEY_METAINFO);
//
//    } catch (JSONException e) {
//      e.printStackTrace();
//      throw new ServiceException("Process failed to start: " + id);
//    } catch (ServiceException e) {
//      e.printStackTrace();
//      throw new ServiceException("Process failed to start: " + id);
//    } catch (Exception e) {
//      e.printStackTrace();
//      throw new ServiceException("Process failed to start: " + id);
//    }
//    return responseMeta;
//    //return 0;
//  }


  public JSONObject checkProgress(SessionLogger LOG, JSONObject res, File workspace, Watershed wspar) throws Exception {
    JSONObject status = null;
    JSONObject fpResponse;
    JSONObject responseMeta;
    String resp;
    String state;
    String queryURL = wspar.par.queryURL;
    Map<String, JSONObject> resultMap;
    String suid = "";

    try {

      suid = res.getString("suid");
      queryURL = queryURL + suid;

      resp = new Client(10).doGET(queryURL);

      fpResponse = new JSONObject(resp);
      responseMeta = fpResponse.getJSONObject(KEY_METAINFO);
      state = responseMeta.getString("status");

      if (state.equals("Running")) {
        return fpResponse;
      } else {
        if (state.equals("Finished")) {
          resultMap = JSONUtils.getResults(fpResponse);
          LOG.info("Done : " + responseMeta.toString(2));
          parseOutputResp(resultMap, wspar);
        } else {
          if (state.equals("Failed")) {
            throw new ServiceException("Process failed for flowpath with SUID: " + suid);
          } else {
            throw new ServiceException("Process uknown state returned for flowpath with SUID: " + suid + " state= " + state);
          }
        }
      }
    } catch (JSONException ex) {
      throw new ServiceException("Could not find process information for flowpath with SUID: " + suid);
    }
    return status;
  }


  public void writeToZipFile(File path, ZipOutputStream zipStream)
      throws FileNotFoundException, IOException {

    System.out.println("Writing file : '" + path + "' to zip file");

    FileInputStream fis = new FileInputStream(path);
    ZipEntry zipEntry = new ZipEntry(path.getName());
    zipStream.putNextEntry(zipEntry);
    byte[] bytes = new byte[1024];
    int length;
    while ((length = fis.read(bytes)) >= 0) {
      zipStream.write(bytes, 0, length);
    }
    zipStream.closeEntry();
    fis.close();
  }


  protected String buildSlopeInput(File slpfile, double slopeWidth) throws IOException {
    StringBuilder output = new StringBuilder();
    double slopeAspect = 180;

    if (slpfile != null) {
      output.append("97.5\n#\n");
      output.append("#\n1\n");
    }
    output.append(slopeAspect);
    output.append(' ');
    output.append(slopeWidth);
    output.append('\n');
    if (distance.length < 100) {
      output.append(distance.length);
    } else {
      output.append("100");
    }
    output.append(' ');
    output.append(totalLengthMeters);
    output.append('\n');
    double slpDist = 0;
    double distPerc;
    if (distance.length > 1) {
      if (distance.length < WeppConstants.MAX_WEPP_SLOPE_POINTS) {
        for (int i = 0; i < distance.length; i++) {
          distPerc = slpDist / totalLengthMeters;
          if (i > 0) {
            output.append(", ");
            // make sure last point is at end of slope
            if (i == distance.length - 1) {
              distPerc = 1.0;
            }
          }
          output.append(String.format("%.3f %.3f", distPerc, slope[i] / 100));
          slpDist = slpDist + distance[i];
        }
      } else {
        // Too many cells to use a 1-to-1 mapping for the slope file, instead take
        // selected points along the profile.
        double stepSize = totalLengthMeters / WeppConstants.MAX_WEPP_SLOPE_POINTS;
        double pos = 0;
        int idx = 0;
        for (int i = 0; i < WeppConstants.MAX_WEPP_SLOPE_POINTS; i++) {
          distPerc = pos / totalLengthMeters;
          if (i > 0) {
            output.append(", ");
            if (i == (WeppConstants.MAX_WEPP_SLOPE_POINTS - 1)) {
              distPerc = 1.0;
            }
          }
          output.append(String.format("%.3f %.3f", distPerc, slope[idx] / 100));
          pos = pos + stepSize;

          while ((slpDist < pos) && (idx < (distance.length - 1))) {
            idx++;
            slpDist = slpDist + distance[idx];
          }
        }
      }
    } else {
      // only one point
      output.append(String.format("0.0 %.3f 1.0 %.3f", slope[0] / 100, slope[0] / 100));
    }
    if (slpfile != null) {
      FileUtils.writeStringToFile(slpfile, output.toString(), "UTF-8");
      return null;
    } else {
      return output.toString();
    }

  }


  protected void createOFELists(List<Double> lens, List<Integer> types, int dat[]) {
    lens.clear();
    types.clear();

    int prevType = dat[0];
    float totalLength = distance[0];

    lens.add(Double.valueOf(totalLength));
    types.add(prevType);

    for (int i = 1; i < dat.length; i++) {
      if (dat[i] == prevType) {
        // same soil or management, increase length of this OFE
        int idx = lens.size() - 1;
        lens.set(idx, lens.get(idx) + distance[i]);
      } else {
        // new OFE starts here
        totalLength = distance[i];
        prevType = dat[i];
        lens.add(Double.valueOf(totalLength));
        types.add(prevType);
      }
    }
  }


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

    createOFELists(soilOFELengths, soilOFETypes, soils);
    createOFELists(manOFELengths, manOFETypes, managements);

    StringBuilder output = new StringBuilder();
    output.append("Version = 98.6\n");
    output.append("Name = csip-weppws\n");
    output.append("Comments {\n}\n");
    output.append("Units = English\n");
    output.append("Landuse = 1\n");
    output.append("Length = ");
    output.append(totalLengthMeters);
    output.append("\n");
    output.append("Profile {\n");
    output.append("  File = \"wepp.slp\"\n");
    output.append("}\n");
    output.append("Climate {\n");
    output.append("  File = \"wepp.cli\"\n");
    output.append("}\n");
    output.append("Soil {\n");
    int ofes = soilOFETypes.size() - 1;
    output.append("  Breaks = " + ofes + "\n");

    for (int i = 0; i < soilOFETypes.size(); i++) {
      output.append("  Soil-");
      output.append(i);
      output.append(" {\n");
      output.append("      Distance = ");
      output.append(soilOFELengths.get(i));
      output.append("\n");
      output.append("      File = \"");
      output.append(soilIndexToName(soilOFETypes.get(i)));
      output.append("\"\n");
      output.append("  }\n");
    }

    output.append("}\n");

    output.append("Management {\n");

    ofes = soilOFETypes.size() - 1;
    output.append("  Breaks = " + ofes + "\n");

    for (int i = 0; i < manOFETypes.size(); i++) {
      output.append("  Management-");
      output.append(i);
      output.append(" {\n");
      output.append("      Distance = ");
      output.append(manOFELengths.get(i));
      output.append("\n");
      output.append("      File = \"");
      output.append(manIndexToName(manOFETypes.get(i)));
      output.append("\"\n");
      output.append("  }\n");
    }
    output.append("}\n");
    output.append("RunOptions {\n");
    output.append("  Version = 1\n");
    output.append("  SoilLossOutputType = 1\n");
    output.append("  SoilLossOutputFile = AutoName\n");
    output.append("  PlotFile = AutoName\n");
    if (usePassFiles) {
        output.append("  HillSlopePassFile = AutoName\n");
    }
    output.append("  SimulationYears = ");
    output.append(years);
    output.append("\n");
    output.append("  Model = weppsm_2012\n");
    output.append("  SmallEventByPass = 1\n");
    output.append("}");

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


  String soilIndexToName(int idx) throws ServiceException {
    String name;
    try {
      name = par.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 = par.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;
  }


  void readLossPlotData(String cellOutputs) throws ServiceException {
    if (!cellOutputs.equals("???")) {
      String lines[] = cellOutputs.split("\\r?\\n");

      float distArr[] = new float[lines.length];
      float lossArr[] = new float[lines.length];

      int j = 0;
      int validLen = 0;
      // skip first 4 lines, header
      for (int i = 4; i < lines.length; i++) {
        String[] vals = lines[i].trim().split("\\s+");
        if (vals.length == 3) {
          // first value is distance (m) from top of flowpath, last value is soil loss (kg/m^2)
          distArr[j] = Float.parseFloat(vals[0]);
          lossArr[j++] = Float.parseFloat(vals[2]) * (float) 4.4609; // convert kg/m^2 to ton/Ac
          validLen = j;
        }
      }

      float endingDist = 0;
      for (int i = 0; i < distance.length; i++) {
        soilLoss[i] = getLossData(endingDist, endingDist + distance[i], distArr, lossArr, validLen);
        endingDist += distance[i];
      }
    }
  }


  public void parseOutputResp(Map<String, JSONObject> resultMap, Watershed wspar) throws ServiceException {
    String cellOutputs = "???";
    try {
      wspar.precip = (float) JSONUtils.getDoubleParam(resultMap, "Precipitation", -999);
      avgSoilLoss = (float) JSONUtils.getDoubleParam(resultMap, "SoilLoss", -999);
      avgSedYield = (float) JSONUtils.getDoubleParam(resultMap, "SedimentYield", -999);
      avgRunoff = (float) JSONUtils.getDoubleParam(resultMap, "Runoff", -999);
      avgIrrigation = (float) JSONUtils.getDoubleParam(resultMap, "Irrigation", -999);
      wspar.weppVersion = JSONUtils.getStringParam(resultMap, "WEPPVersion", "???");
      wspar.preVersion = JSONUtils.getStringParam(resultMap, "PreprocessorVersion", "???");
      cellOutputs = JSONUtils.getStringParam(resultMap, "SoilLossCells", "???");
    } catch (Exception ex) {
      throw new ServiceException("No results from flowpath: " + Integer.toString(id));
    }
    readLossPlotData(cellOutputs);
  }


  float getLossData(float start, float end, float X[], float loss[], int len) throws ServiceException {
    float winBeg = 0;
    float winEnd = X[0];
    float total = 0;
    float cells = 0;
    float frac;
    int idx = 0;

    while ((start > winEnd) && (idx < len)) {
      idx++;
      if (idx < X.length) {
        winBeg = winEnd;
        winEnd = X[idx];
      }
    }
    cells = 0;
    while ((end > winBeg) && (idx < len)) {
      if ((winEnd < end) && (winBeg >= start)) {
        total += loss[idx];
        cells = cells + 1;
      } else {
        if (winBeg < start) {
          frac = (winEnd - start) / (winEnd - winBeg);
          total += (loss[idx] * frac);
          cells = cells + frac;
        } else {
          if (winBeg < end) {
            frac = (end - winBeg) / (winEnd - winBeg);
            total += (loss[idx] * frac);
            cells = cells + frac;
          } else {
            // should not get here...
            throw new ServiceException("Error resampling soil loss to flowpath");
          }
        }
      }
      winBeg = winEnd;
      idx++;
      if (idx < len) {
        winEnd = X[idx];
      }
    }
    return total / cells;
  }


  /**
   * After a WEPP run a brief put file lists the main 4 outputs which as read
   * and saved.
   *
   * @param resultsFile WEPP results
   * @throws IOException error reading file
   */
  public void parseOutputs(File resultsFile, File plotFile, Watershed wspar) throws IOException {

    float precip, 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();

      String restring = XML.toJSONObject(fileContents.toString()).toString();
      JSONObject results = new JSONObject(restring);
      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");
      wspar.precip = (float) results.optDouble("PRECIP");
      avgSoilLoss = (float) results.optDouble("LOSS");
      avgSedYield = (float) results.optDouble("SEDYIELD");
      avgRunoff = (float) results.optDouble("RUNOFF");
      avgIrrigation = (float) results.optDouble("IRRIGATION");
      wspar.weppVersion = results.getString("WEPP_VERSION");
      wspar.preVersion = results.getString("PREPROCESSOR_VERSION");
      
      wspar.LOG.info("Flowpath: " + id + " " + avgSoilLoss);

      cellOutputs = new String(Files.readAllBytes(plotFile.toPath()));
      readLossPlotData(cellOutputs);
    } catch (Exception e) {
      precip = avgSoilLoss = avgSedYield = avgRunoff = avgIrrigation = (float) -999;
    }
  }


  void updateWeightedLossGrid(Grid lossGrid, Grid sedWeight) {
    if (simulationDone) {
      if (weightMethod == 1) {
        // plain average, all cells that merge are averages
        lossGrid.updatePathWeightedLoss(soilLoss, rows, cols, 1);
        sedWeight.updatePathWeight(rows, cols, 1);
      }
      if (weightMethod == 0) {
        // cell value is weighted based on total flowpath length
        float weight = totalLengthMeters;
        lossGrid.updatePathWeightedLoss(soilLoss, rows, cols, weight);
        sedWeight.updatePathWeight(rows, cols, weight);
      }
      if (weightMethod == 2) {
        // cell value is always the maximum loss
        lossGrid.updatePathMaxLoss(soilLoss, rows, cols);
      }
    }
  }


  boolean intersectsField(Grid field) {
    boolean rc = false;
    try {
      int fld[][] = field.getIntData();
      for (int i = 0; i < rows.length; i++) {
        int r = rows[i];
        int c = cols[i];
        if (fld[r][c] == 1) {
          rc = true;
          break;
        }
      }
    } catch (ServiceException ex) {
      throw new RuntimeException("Propagated:", ex);
    }
    return rc;
  }


  void updateRepProfile(double cellsize, float repProfileSlopes[], float repProfileWeights[]) {
    double weight = totalLengthMeters;
    int cnt = (int) (totalLengthMeters / cellsize);
    float resample[] = new float[cnt];

    for (int i = 0; i < cnt; i++) {
      float lower = (float) (i * cellsize);
      float upper = (float) (lower + cellsize);
      resample[i] = interpolateSlope(lower, upper);
    }
    for (int i = resample.length - 1; i >= 0; i--) {
      repProfileWeights[i] += weight;
      repProfileSlopes[i] += (weight * resample[i]);
    }
  }


  float interpolateSlope(float lower, float upper) {
    float newslope = 0;
    int last = distance.length - 1;
    int idx = last;
    float win1 = distance[last];

    // find first cell
    while (lower > win1) {
      win1 += distance[idx];
      idx--;
    }
    float start = win1 - distance[idx];
    if ((start >= lower) && (win1 >= upper)) {
      // completely contained in original cell
      newslope = slope[idx];
    } else {
      // somewhere between two slope points
      if (idx < last) {
        float win2 = win1 + distance[idx + 1];
        float frac1 = (win1 - lower) / (upper - lower);
        float frac2 = (win2 - upper) / (upper - lower);
        newslope = (slope[idx] * frac1) + (slope[idx + 1] * frac2);
      } else {
        // just use last value
        newslope = slope[last];
      }
    }
    if (newslope > 1) {
      //TODO: check this?
      newslope = newslope;
    }
    return newslope;
  }


  String printProfile(double width) {
    try {
      return buildSlopeInput(null, width);
    } catch (Exception e) {
      return "";
    }
  }
  
  int getPrevDir(int r, int c) throws ServiceException {
      int prev = 0;
      for (int i=0; i< rows.length; i++) {
          if ((rows[i] == r) && (cols[i] == c)) {
             if (i > 0) {
                int d8[][] = par.flow_d8_grid.getIntData();
                prev = d8[rows[i-1]][cols[i-1]];
             } else {
                prev = 0; // first cell of flowpath
             }
             break;
          }
      }
      return prev;
  }
  
  int fillsub3grid(Grid sub3, int subid) throws ServiceException {
    int id3 = subid;
    int cellsFilled = 0;
    if (relativePosition == WeppConstants.Orientation.LEFT) {
        id3 = id3 + 1;
    } else if (relativePosition == WeppConstants.Orientation.RIGHT) {
        id3 = id3 + 2;
    } else if (relativePosition == WeppConstants.Orientation.TOP) {
        id3 = id3 + 3;
    } else if (relativePosition == WeppConstants.Orientation.UNKNOWN) {
        id3 = -10;
    } else if (relativePosition == WeppConstants.Orientation.NOTFOUND) {
        id3 = -20;
    }
    int sub[][];
    sub = sub3.getIntData();
    for (int i=0; i< rows.length; i++) {
        sub[rows[i]][cols[i]] = id3;
        cellsFilled = cellsFilled + 1;
     }
     
    return cellsFilled; 
  }
  int fillsub3gridChannel(Grid sub3, int subid) throws ServiceException {
    int sub[][];
    sub = sub3.getIntData();
    int cellsFilled = 0;
    for (int i=0; i< rows.length; i++) {
        sub[rows[i]][cols[i]] = subid;
        cellsFilled = cellsFilled + 1;
     }
    return cellsFilled; 
  }
  void classifyFlowpath(int order) throws  ServiceException {
    int d8nodata = par.flow_d8_grid.getNoData();
    int d8[][] = par.flow_d8_grid.getIntData();
    int chan[][] = par.channel_grid.getIntData();
    if ((chnEndRow >= 0) && (chnEndCol >= 0)) {
       int prevChnDir = par.aoi.findPrevChannelCellDir(chnEnd, chnEndRow, chnEndCol);
       int curChnDir = d8[chnEndRow][chnEndCol];
       int lastRow = rows[rows.length-1];
       int lastCol = cols[cols.length-1];
       int flowDir = d8[lastRow][lastCol];
       
       if (prevChnDir > 0) {
          relativePosition = determineOrientation(prevChnDir, curChnDir, flowDir);
       } else {
           if (prevChnDir == 0) {
               if (order <= 1) {
                    relativePosition = WeppConstants.Orientation.TOP;
               } else {
                   relativePosition = WeppConstants.Orientation.NOTFOUND;  
               }
               posString = Integer.toString(curChnDir) + "-" + Integer.toString(prevChnDir) + "-" + Integer.toString(flowDir);
           } else {
               relativePosition = WeppConstants.Orientation.NOTFOUND; 
               posString = Integer.toString(curChnDir) + "-" + Integer.toString(prevChnDir) + "-" + Integer.toString(flowDir);
           }
       }
    } else {
       relativePosition = WeppConstants.Orientation.NOTFOUND;  
    }
    if ((relativePosition == WeppConstants.Orientation.NOTFOUND) || (relativePosition == WeppConstants.Orientation.UNKNOWN)) {
        // TBD - may want to handlle this and try to assign a defaut position
    }
  }
  WeppConstants.Orientation determineOrientation(int prevChnDir, int curChnDir, int flowDir) {
      WeppConstants.Orientation pos;
      
      pos = lookupDir(Integer.toString(curChnDir) + "-" + Integer.toString(prevChnDir), flowDir);
      
      posString = Integer.toString(curChnDir) + "-" + Integer.toString(prevChnDir) + "-" + Integer.toString(flowDir);
      
      return pos;
    
  }
   WeppConstants.Orientation lookupDir(String seq, int f) {
      WeppConstants.Orientation pos;
     
      pos = WeppConstants.Orientation.LEFT;  // assume left side
      
       switch (seq) {
           case "1-1":  // E
               if ((f == 2) || (f == 3) || (f == 4)) {
                   pos = WeppConstants.Orientation.RIGHT;
               }
               break;
           case "1-2":  // E
               if ((f == 3) || (f == 4)) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "1-3":  // E
               if (f == 4) {
                   pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "1-4":  // E
               break;  // everything is LEFT
           case "1-5":  // E
               pos = WeppConstants.Orientation.UNKNOWN; 
               break;  // does not make sense, revert to unknown
           case "1-6":  // E
               pos = WeppConstants.Orientation.RIGHT;
               break; // everything is on RIGHT
           case "1-7":  // E
               if (f != 6) {
                 pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "1-8":  // E
               if (f <= 4 ) {
                  pos = WeppConstants.Orientation.RIGHT;   
               }
               break;
               
           case "2-1":  // NE
               if ((f == 2) || (f == 3) || (f == 4) || (f ==5)) {
                   pos = WeppConstants.Orientation.RIGHT;   
               }
               break;
           case "2-2":  // NE
               if ((f == 3) || (f == 4) || (f == 5)) {
                  pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "2-3":  // NE
                if ((f == 4) || (f == 5)) {
                  pos = WeppConstants.Orientation.RIGHT;  
               }
                break;
           case "2-4":  // NE
               if (f == 5) {
                  pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "2-5":  // NE
               // all LEFT
               break;
           case "2-6":  // NE
               pos = WeppConstants.Orientation.UNKNOWN; 
               break;  // does not make sense, revert to unknown
           case "2-7":  // NE
               pos = WeppConstants.Orientation.RIGHT;
               break; // everything is on RIGHT
           case "2-8":  // NE
               if ((f <= 5)) {
                   pos = WeppConstants.Orientation.RIGHT;
               }
               break;
               
           case "3-1":  // N
               if ((f == 2) || (f == 3) || (f == 4) || (f == 5) || (f == 6)) {
                   pos = WeppConstants.Orientation.RIGHT;
               }
               break;
           case "3-2":  // N
               if ((f == 3) || (f == 4) || (f == 5) || (f == 6)) {
                   pos = WeppConstants.Orientation.RIGHT;
               }
               break;
           case "3-3":  // N
               if ((f == 4) || (f == 5) || (f == 6)) {
                   pos = WeppConstants.Orientation.RIGHT;
               }
               break;
           case "3-4":  // N
               if ((f == 5) || (f == 6)) {
                  pos = WeppConstants.Orientation.RIGHT;
               }
               break;
           case "3-5":  // N
               if (f == 6) {
                  pos = WeppConstants.Orientation.RIGHT;
               }
               break;
           case "3-6":  // N
               // all LEFT
               break;
           case "3-7":  // N
                pos = WeppConstants.Orientation.UNKNOWN; 
               break;  // does not make sense, revert to unknown
           case "3-8":  // N
               // all RIGHT
                pos = WeppConstants.Orientation.RIGHT;
                break;
                
           case "4-1":  // NW
               // all RIGHT
               pos = WeppConstants.Orientation.RIGHT;
               break;
           case "4-2":  // NW
               if ((f == 3) || (f == 4) || (f == 5) || (f == 6) || (f ==7)) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "4-3":  // NW
                if ((f == 4) || (f == 5) || (f == 6) || (f ==7)) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "4-4":  // NW
                if ((f == 5) || (f == 6) || (f ==7)) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "4-5":  // NW
                if ((f == 6) || (f ==7)) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "4-6":  // NW
                if (f ==7) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "4-7":  // NW
               // all LEFT
               break;
           case "4-8":  // NW
               pos = WeppConstants.Orientation.UNKNOWN; 
               break;  // does not make sense, revert to unknown
               
               
           case "5-1":  // W
               pos = WeppConstants.Orientation.UNKNOWN; 
               break;  // does not make sense, revert to unknown
           case "5-2":  // W
               // all RIGHT
               pos = WeppConstants.Orientation.RIGHT;
               break;
           case "5-3":  // W
               if ((f == 4) || (f == 5) || (f == 6) || (f ==7) || (f ==8)) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "5-4":  // W
               if ((f == 5) || (f == 6) || (f ==7) || (f ==8)) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "5-5":  // W
                if ((f == 6) || (f ==7) || (f ==8)) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "5-6":  // W
               if ((f ==7) || (f ==8)) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "5-7":  // W
               if (f ==8) {
                  pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "5-8":  // W
               // all LEFT
               break;
           
           case "6-1":  // SW
               // all LEFT
               break;
           case "6-2":  // SW
               pos = WeppConstants.Orientation.UNKNOWN; 
               break;  // does not make sense, revert to unknown
           case "6-3":  // SW
               // all right
               pos = WeppConstants.Orientation.RIGHT; 
               break;
           case "6-4":  // SW
               if ((f == 1) || (f == 5) || (f == 6) || (f == 7) || (f == 8)) {
                   pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "6-5":  // SW
                if ((f == 1) || (f == 6) || (f == 7) || (f == 8)) {
                   pos = WeppConstants.Orientation.RIGHT;  
               }
                break;
           case "6-6":  // SW
               if ((f == 1) || (f == 7) || (f == 8)) {
                   pos = WeppConstants.Orientation.RIGHT;  
               }
                break;
           case "6-7":  // SW
                if ((f == 1) || (f == 8)) {
                   pos = WeppConstants.Orientation.RIGHT;  
                }
                break;
           case "6-8":  // SW
               if (f == 1) {
                   pos = WeppConstants.Orientation.RIGHT;  
                }
                break;
               
           case "7-1":  // S
               if (f == 2) {
                   pos = WeppConstants.Orientation.RIGHT; 
               }
               break;
           case "7-2":  // S
               // all LEFT
               break;
           case "7-3":  // S
               pos = WeppConstants.Orientation.UNKNOWN; 
               break;  // does not make sense, revert to unknown
           case "7-4":  // S
               // all RIGHT
               pos = WeppConstants.Orientation.RIGHT; 
               break;
           case "7-5":  // S
               if ((f == 1) || (f == 2) || (f == 8) || (f == 7) || (f == 6)) {
                  pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "7-6":  // S
                if ((f == 1) || (f == 2) || (f == 8) || (f == 7)) {
                  pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "7-7":  // S
               if ((f == 1) || (f == 2) || (f == 8)) {
                  pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "7-8":  // S
               if ((f == 1) || (f == 2)) {
                  pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
               
           case "8-1":  // SE
               if ((f == 2) || (f == 3)) {
                  pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "8-2":  // SE
                if (f == 3) {
                  pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "8-3":  // SE
               // all LEFT
               break;
           case "8-4":  // SE
               pos = WeppConstants.Orientation.UNKNOWN; 
               break;  // does not make sense, revert to unknown
           case "8-5":  // SE
               // all RIGHT
               pos = WeppConstants.Orientation.RIGHT;  
               break;
           case "8-6":  // SE
               if ((f == 1) || (f == 2) || (f == 3) || (f == 8) || (f == 7)) {
                     pos = WeppConstants.Orientation.RIGHT;  
               }
               break;
           case "8-7":  // SE
                if ((f == 1) || (f == 2) || (f == 3) || (f == 8)) {
                     pos = WeppConstants.Orientation.RIGHT;  
               }
                break;
           case "8-8":  // SE
                if ((f == 1) || (f == 2) || (f == 3)) {
                     pos = WeppConstants.Orientation.RIGHT;  
               }
                break;
       }
       return pos;
   }
}