V1_0.java [src/java/m/swatc] Revision: default  Date:
/*
 * $Id:$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API, and application suite.
 *
 * 2012-2023, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package m.swatc;

import csip.Config;
import csip.ModelDataService;
import csip.annotations.Description;
import csip.annotations.Name;
import csip.annotations.Resource;
import csip.annotations.ResourceType;
import static csip.annotations.ResourceType.OUTPUT;
import csip.api.client.ModelDataServiceCall;
import csip.api.server.Executable;
import csip.api.server.ServiceException;
import csip.utils.Parallel;
import csip.utils.PayloadCache;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.Path;
import static m.swatc.V1_0.SWATC_EXE;
import org.apache.commons.io.FileUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;

/**
 *
 * @author sidereus
 */
@Name("SWAT-C model edge of field")
@Description("Model run")
@Path("m/swatc/1.0")
@Resource(type = ResourceType.EXECUTABLE, file = "/bin/swatc_1.0", id = SWATC_EXE)
@Resource(type = OUTPUT, file = "*-std*.* output*.* *.txt *.out")
public class V1_0 extends ModelDataService {

  static final int connTimeout
      = Config.getInt("swat.conn.timeout", 4000);
  static final int readTimeout
      = Config.getInt("swat.read.timeout", 4000);
  static final boolean serialdataFetch
      = Config.getBoolean("swatc.serial.datafetch", false);
  static final String swatplus_cligen_URL
      = Config.getString("swatplus.cligen", "http://csip.engr.colostate.edu:8088/csip-climate/d/swatplus/1.0");
  static final String swatc_soil_URL
      = Config.getString("swatc.soil.service", "http://csip.engr.colostate.edu:8088/csip-soils/d/swatc/1.0");

  static final String SWATC_EXE = "swatc1.0";

  // cache must be static.
  static PayloadCache soilsCache = new PayloadCache().withSize(64);
  static PayloadCache climateCache = new PayloadCache().withSize(64);


  @Override
  public void doProcess() throws ServiceException, Exception {
    double latitude = parameter().getDouble("latitude");
    double longitude = parameter().getDouble("longitude");
    String cokey = parameter().getString("cokey");
    int startYear = parameter().getInt("start_year");
    int simulationYears = parameter().getInt("simulation_years");

    Parallel.run(serialdataFetch,
        () -> {
          fetchClimate(longitude, latitude, startYear, simulationYears);
        },
        () -> {
          fetchSoil(cokey);
        }
    );

    Executable p = resources().getExe(SWATC_EXE);
    int ret = p.exec();

    if (ret != 0) {
      File errf = p.stderr();
      String err = "";
      if (errf.exists() && errf.length() > 0)
        err = FileUtils.readFileToString(errf, "utf-8");

      throw new ServiceException("Error running SWATC: " + ret + "\n" + err);
    }
  }


  @Override
  protected void postProcess() throws Exception {
//    File zip = ZipFiles.zip(new File(workspace().getDir().getAbsolutePath()));
//    results().put(zip);
  }


  private void fetchClimate(double longitude, double latitude, int startYear, int simulationYears) throws Exception {
    ModelDataServiceCall res = new ModelDataServiceCall()
        .put("longitude", longitude)
        .put("latitude", latitude)
        .put("startyear", startYear)
        .put("duration", simulationYears)
        .url(swatplus_cligen_URL)
        .withCache(climateCache)
        .withDefaultLogger()
        .call();

    if (res.serviceFinished()) {
      List<WeatherData> wd = new ArrayList();
      double elevation = writeClimateData(res, wd);
      writeAll(wd, simulationYears, latitude, longitude, elevation);
    } else {
      throw new ServiceException(res.getError());
    }
  }


  private void fetchSoil(String cokey) throws Exception {
    ModelDataServiceCall res = new ModelDataServiceCall()
        .put("cokey", cokey)
        .url(swatc_soil_URL)
        .withCache(soilsCache)
        .withDefaultLogger()
        .call();

    if (res.serviceFinished()) {
      String fileName = "hru1.sol";
      res.download(fileName, workspace().getFile(fileName));
    } else {
      throw new ServiceException(res.getError());
    }
  }


  private double writeClimateData(ModelDataServiceCall map, List<WeatherData> wd) {
    try {
      double elevation = map.getDouble("elevation");
      // JSONArray data_template = map.getJSONArray("data_template"); to use for weatherdata
      JSONArray data_array = map.getJSONArray("data");
      for (int i = 0; i < data_array.length(); i++) {
        wd.add(new WeatherData(data_array.getJSONArray(i)));
      }
      return elevation;
    } catch (JSONException ex) {
      throw new RuntimeException(ex.getMessage());
    }
  }


  private void writeAll(List<WeatherData> wd, int simulationYears, double latitude, double longitude, double elevation) throws IOException {

    String headPcp = "Swatc Daily Precipitation Data File" + "\n"
        + String.format("%-7s", "lat   :") + String.format("%5s", latitude) + "\n"
        + String.format("%-7s", "lon   :") + String.format("%5s", longitude) + "\n"
        + String.format("%-7s", "elev  :") + String.format("%5d", (int) elevation);

    String headTmp = "Swatc Daily Temperature Data File" + "\n"
        + String.format("%-7s", "lat   :") + String.format("%10s", latitude) + "\n"
        + String.format("%-7s", "lon   :") + String.format("%10s", longitude) + "\n"
        + String.format("%-7s", "elev  :") + String.format("%10s", (int) elevation);

    /*
     * String head = "nbyr" + String.format("%10s", "tstep") +
     * String.format("%10s", "lat") + String.format("%10s", "lon") +
     * String.format("%10s", "elev");
     *
     *
     * String head_vals = String.format("%4s", simulationYears) +
     * String.format("%10s", "0") + String.format("%10s", latitude) +
     * String.format("%10s", longitude) + String.format("%10s", elevation);
     */
    try (PrintWriter pw = new PrintWriter(
        Files.newBufferedWriter(workspace().getFile("tmp.cli").toPath()))) {
      pw.println(headTmp);
      //pw.println(head_vals);
      wd.stream().forEachOrdered(s -> s.writeTemp(pw));
    }

    try (PrintWriter pw = new PrintWriter(
        Files.newBufferedWriter(workspace().getFile("wnd.cli").toPath()))) {
      pw.println("Swatc Wind Velocity Data File");
      //pw.println(head_vals);
      wd.stream().forEachOrdered(s -> s.writeWindSpeed(pw));
    }

    try (PrintWriter pw = new PrintWriter(
        Files.newBufferedWriter(workspace().getFile("slr.cli").toPath()))) {
      pw.println("Swatc Solar Radiation Data File");
      //pw.println(head_vals);
      wd.stream().forEachOrdered(s -> s.writeSolar(pw));
    }

    try (PrintWriter pw = new PrintWriter(
        Files.newBufferedWriter(workspace().getFile("pcp.cli").toPath()))) {
      pw.println(headPcp);
      //pw.println(head_vals);
      wd.stream().forEachOrdered(s -> s.writePrecip(pw));
    }

    try (PrintWriter pw = new PrintWriter(
        Files.newBufferedWriter(workspace().getFile("hmd.cli").toPath()))) {
      pw.println("Swatc Relative Humidity Data File");
      //pw.println(head_vals);
      wd.stream().forEachOrdered(s -> s.writeRH(pw));
    }
  }

  static class WeatherData {

    float precp;
    float tmax;
    float tmin;
    float rh;
    float rad;
    float w_vl;
    String date;

    DecimalFormat df = new DecimalFormat("#.#####");


    WeatherData(JSONArray tmp) {
      int year;
      int day;
      try {
        year = Integer.parseInt(tmp.getString(0));
        day = Integer.parseInt(tmp.getString(1));
        this.precp = Float.parseFloat(tmp.getString(2));
        this.rad = Float.parseFloat(tmp.getString(3));
        this.tmax = Float.parseFloat(tmp.getString(4));
        this.tmin = Float.parseFloat(tmp.getString(5));
        this.w_vl = Float.parseFloat(tmp.getString(6));
        this.rh = Float.parseFloat(tmp.getString(7));
      } catch (JSONException ex) {
        throw new RuntimeException(ex.getMessage());
      }
      date = buildDate(year, day);
    }


    private String buildDate(int year, int day) {
      return year + String.format("%3s", df.format(day));
    }


    void writePrecip(PrintWriter pw) {
      pw.println(date + String.format("%5.1f", precp));
    }


    void writeTemp(PrintWriter pw) {
      pw.println(date
          + String.format("%5.1f", tmax)
          + String.format("%5.1f", tmin));
    }


    void writeRH(PrintWriter pw) {
      pw.println(date + String.format("%8.3f", rh));
    }


    void writeWindSpeed(PrintWriter pw) {
      pw.println(date + String.format("%8.3f", w_vl));
    }


    void writeSolar(PrintWriter pw) {
      pw.println(date + String.format("%8.3f", rad));
    }

  }

}