V2_0.java [src/java/m/hydrotools/efh2] Revision: default  Date:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package m.hydrotools.efh2;

import csip.Executable;
import csip.ModelDataService;
import csip.ServiceException;
import csip.annotations.*;
import static csip.annotations.ResourceType.EXECUTABLE;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.ws.rs.Path;
import org.apache.commons.io.FileUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

/**
 * TR20 Service, efh2 version.
 *
 * @author od
 */
@Name("Hydrotools")
@Description("EFH2 service based on TR20")
@Path("m/efh2/2.0")
@Resource(file = "/bin/lin-amd64/TR20.exe", type = EXECUTABLE, id = "tr20")
@Polling(first = 10000, next = 2000)
public class V2_0 extends ModelDataService {

  // parameter
  static final String WATERSHED_NAME = "watershed_name";
  static final String DRAINAGE_AREA = "drainage_area";
  static final String CURVE_NUMBER = "curve_number";
  static final String WATERSHED_SLOPE = "watershed_slope";
  static final String WATERSHED_LENGTH = "watershed_length";
  static final String STORM_NAME = "storm_name";
  static final String RAIN_DEPTH = "rain_depth";
  static final String RAIN_DIST = "rain_dist";

  // output
  static final String RUNOFF_VOLUME = "runoff_volume";
  static final String PEAK_DISCHARGE = "peak_discharge";
  static final String PEAK_TIME = "peak_time";
  static final String TOC = "time_of_concentration";

  // internal result string.
  private String[] res;
  String st_name;
  String toc;


  /**
   * Inclusive range check.
   *
   * @param val
   * @param name
   * @param min
   * @param max
   * @throws ServiceException
   */
  static void checkRange(double val, String name, double min, double max) throws ServiceException {
    if (val >= min && val <= max) {
      return;
    }
    throw new ServiceException(name + ": " + val + " not in range " + min + " ... " + max);
  }


  static void checkGT(double val, String name, double min) throws ServiceException {
    if (val > min) {
      return;
    }
    throw new ServiceException(name + ": " + val + " must be > " + min);
  }


  @Override
  protected void preProcess() throws Exception {
    // watershed name
    String ws_name = parameter().getString(WATERSHED_NAME);
    st_name = parameter().getString(STORM_NAME);

    double dr_area = parameter().getDouble(DRAINAGE_AREA);
//    checkRange(dr_area, DRAINAGE_AREA, 0, 3.125);
//    checkGT(dr_area, DRAINAGE_AREA, 0);

    double cu_number = parameter().getDouble(CURVE_NUMBER);
//    checkRange(cu_number, CURVE_NUMBER, 30, 100);

    double ws_slope = parameter().getDouble(WATERSHED_SLOPE);
//    checkRange(ws_slope, WATERSHED_SLOPE, 0.5, 64);

    double ws_length = parameter().getDouble(WATERSHED_LENGTH);
//    checkRange(ws_length, WATERSHED_LENGTH, 200, 26000);

    double ra_depth = parameter().getDouble(RAIN_DEPTH);
//    checkRange(ra_depth, RAIN_DEPTH, 0, 26);

    String ra_dist = parameter().getString(RAIN_DIST);

    // populate the input file
    DecimalFormat f = new DecimalFormat("0.0####");
    Map<String, Object> m = new HashMap<>();
    m.put("wn", ws_name);
    m.put("da______", pad(f.format(dr_area), 10));
    m.put("cn______", pad(f.format(cu_number), 10));
    m.put("wl______", pad(f.format(ws_length), 10));
    m.put("ws______", pad(f.format(ws_slope), 10));
    m.put("rde_____", pad(f.format(ra_depth), 10));
    m.put("sn______", pad(st_name, 10));
    m.put("rdi_____", pad(ra_dist, 10));
    createTR20Input(m, new File(getWorkspaceDir(), "TR20.inp"));
  }


  @Override
  protected void doProcess() throws Exception {
    Executable tr20 = resources().getExe("tr20");
    int ret = tr20.exec();
    if (ret != 0) {
      File err = tr20.stderr();
      if (err.exists() && err.length() > 0) {
        throw new ServiceException(FileUtils.readFileToString(err));
      }
      throw new ServiceException("Error executing TR20");
    }

    File err = new File(getWorkspaceDir(), "TR20.err");
    if (err.exists() && err.length() > 0) {
      throw new ServiceException(FileUtils.readFileToString(err));
    }

    File out = new File(getWorkspaceDir(), "TR20.out");
    if (!out.exists()) {
      throw new ServiceException("Missing File : TR20.out");
    }
    File dbg = new File(getWorkspaceDir(), "TR20.dbg");
    if (!dbg.exists()) {
      throw new ServiceException("Missing File : TR20.dbg");
    }

    res = parseOutputFile(st_name, out);
    if (res == null || res.length != 3) {
      throw new ServiceException("Missing output.");
    }

    toc = parseDebugFile(dbg);
    if (toc == null) {
      throw new ServiceException("Missing tocs.");
    }
  }


  @Override
  protected void postProcess() throws Exception {
    results().put(RUNOFF_VOLUME, res[0], "Runoff depth output", "inch");
    results().put(PEAK_TIME, res[1], "Peak Time", "h");
    results().put(PEAK_DISCHARGE, res[2], "Unit Peak Discharge", "cfs/acre/inch");
    results().put(TOC, toc, "Time of Concentration", "h");
  }


  static private String parseDebugFile(File debugFile) throws IOException {
    List<String> out = FileUtils.readLines(debugFile);
    for (int i = 0; i < out.size(); i++) {
      String l = out.get(i);
      if (l.trim().toLowerCase().startsWith("time of concentration =")) {
        String[] vals = l.trim().split("\\s+");
        return (vals.length == 5) ? vals[4] : null;
      }
    }
    return null;
  }


  static private String[] parseOutputFile(String st_name, File outFile) throws IOException {
    List<String> out = FileUtils.readLines(outFile);
    int st_index = 0;
    for (int i = 0; i < out.size(); i++) {
      String l = out.get(i);
      if (l.trim().startsWith("STORM " + st_name)) {
        st_index = i;
        break;
      }
    }
    if (st_index > 0) {
      for (int i = st_index; i < out.size(); i++) {
        String l = out.get(i);
        if (l.trim().startsWith("sub 1")) {
          String[] vals = l.split("\\s+");
          if (vals.length > 5) {
//                        System.out.println("runoff " + vals[3]);
//                        System.out.println("time " + vals[4]);
//                        System.out.println("rate " + vals[5]);
            return new String[]{vals[3], vals[4], vals[5]};
          }
          break;
        }
      }
    }
    return null;
  }


  public static void createTR20Input(Map<String, Object> inp, File file) throws Exception {
    Properties props = new Properties();
    props.put("resource.loader", "class");
    props.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");

    VelocityContext context = new VelocityContext(inp);
    try (Writer writer = new FileWriter(file)) {
      VelocityEngine ve = new VelocityEngine();
      ve.init(props);
      ve.getTemplate("m/hydrotools/efh2/V2_0_input.vm").merge(context, writer);
    }
  }


  /**
   *
   * @param s
   * @param len
   * @return
   */
  private static String pad(String s, int len) {
    if (s.length() == len) {
      return s;
    }
    if (s.length() > len) {
      return s.substring(0, len);
    }
    return String.format("%1$-" + len + "s", s);
  }

//    public static void main(String[] args) throws IOException, Exception {
////        parseOutput("storm1", new File("/od/projects/csip-all/csip-hydrotools/src/java/bin/lin-amd64/TR20.out"));
//
//        DecimalFormat formatter = new java.text.DecimalFormat("0000.00");
//        float[] floats = new float[]{123.45f, 99.0f, 23.2f, 45.0f};
//        String format = "%-10.1f"; 
//
//        for (int i = 0; i < floats.length; i++) {
//            float value = floats[i];
//            System.out.println("'" + String.format(format, value) + "'");
//        }
//        
//        Map<String, Object> m = new HashMap<>();
//        m.put("wn", "ws name");
//        m.put("da______", String.format(format, (float)1));
//        m.put("cn______", String.format(format, (float)80));
//        m.put("ws______", String.format(format, (float)3));
//        m.put("wl______", String.format(format, (float)6000));
//        m.put("sn______", pad("storm1",10));
//        m.put("rde_____", String.format(format, (float)5));
//        m.put("rdi_____", pad("TYPE NO_D", 10));
//        createTR20Input(m, new File("/tmp", "TR20.inp"));
//        
//        System.out.println("'" + pad("storm", 10) + "'");
//        System.out.println("'" + pad("s", 10) + "'");
//        System.out.println("'" + pad("ssdddg", 10) + "'");
//        System.out.println("'" + pad("stormchaserolaf", 10) + "'");
//    }
}