V1_0.java [src/java/m/svap/svap08_svapweather] 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.svap.svap08_svapweather;

import csip.Config;
import csip.ModelDataService;
import csip.ServiceException;
import csip.annotations.Resource;
import gisobjects.GISObject;
import gisobjects.GISObjectException;
import gisobjects.GISObjectFactory;
import gisobjects.db.GISEngineFactory;
import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Iterator;
import javax.ws.rs.Path;
import csip.annotations.Description;
import csip.annotations.Name;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.geotools.referencing.CRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import svap.utils.ClimateStation;
import svap.utils.DBResources;
import static svap.utils.DBResources.CRDB;
import static svap.utils.DBResources.NRTDB;
import svap.utils.HUC12;
import svap.utils.NRT_DB;
import svap.utils.NRT_DB.County;
import svap.utils.SVAPUtils;
import static svap.utils.SVAPUtils.convertKMtoMiles;
import svap.utils.StationDataService;
import svap.utils.StationMetaDataService;

/**
 *
 * @author Robert Streetman
 * @author Shaun Case
 */
@Name("SVAP-08 Get Weather Conditions for Field Assessment.")
@Description("This service returns average daily values for temperature, minimum temperature, maximum temperature, and precipitation within a specified period from the date  of stream reach assessment, default set to 5 days:  the date of assessment and previous 4 days")
@Path("m/svap/svapweather/1.0")
@Resource(from = DBResources.class)

public class V1_0 extends ModelDataService {

  private static final String URL_ACIS_STNMETA = "http://data.rcc-acis.org/StnMeta";
  private static final String URL_ACIS_STNDATA = "http://data.rcc-acis.org/StnData";
  private static final String URL_ERAMS_STATIONS = "http://csip.engr.colostate.edu:8083/csip-climate/d/acis/1.0";
  private static final String URL_ERAMS_HUC_EXTENT = "https://csip.erams.com/csip-huc/d/huc/extent/1.0";
  private static final String URL_ERAMS_HUC_GEOMETRY = "https://csip.erams.com/csip-huc/d/huc/geometry/1.0";

  private static final String ACIS = "ACIS";
  private static final String ERAMS = "ERAMS";
  private static final int DEFAULT_PAST_DAYS = 5;

  private LocalDate eDate, sDate;
  private GISObject locationPoint;
  private ClimateStation finalStation;

  private static String stations_source;
  private static String url_acis_stnmeta;
  private static String url_acis_stndata;
  private static String url_erams_stations;
  private static String url_erams_huc;

  private boolean foundInHUC, foundInCounty, foundInState = false;
  private boolean printAll;
  private ArrayList<ClimateStation> allStations = new ArrayList<>();


  static {
    // Setting the system-wide default at startup time
//System.setProperty("org.geotools.referencing.forceXY", "true");
    stations_source = Config.getString("service.svap.svapweather.stations.source", ACIS).toUpperCase();

    switch (stations_source) {
      case ACIS:
        url_acis_stnmeta = Config.getString("service.svap.svapweather.acis.stnmeta", URL_ACIS_STNMETA);
        url_acis_stnmeta = Config.getString("service.svap.svapweather.acis.stndata", URL_ACIS_STNDATA);
        break;

      case ERAMS:
        url_erams_stations = Config.getString("service.svap.svapweather.erams.climate.stations", URL_ERAMS_STATIONS);
        url_erams_huc = Config.getString("service.svap.svapweather.erams.huc.extent", URL_ERAMS_HUC_EXTENT);
        url_erams_huc = Config.getString("service.svap.svapweather.erams.huc.geometry", URL_ERAMS_HUC_GEOMETRY);
        break;
    }
  }


  @Override
  protected void preProcess() throws ServiceException, JSONException, IOException, GISObjectException, SQLException {
    JSONObject location;

    sDate = SVAPUtils.getDate(parameter().getString("date"), DEFAULT_PAST_DAYS);
    eDate = SVAPUtils.getDate(parameter().getString("date"), 0);
    location = parameter().getJSON("location");
    locationPoint = GISObjectFactory.createGISObject(location,
        GISEngineFactory.createGISEngine(resources().getJDBC(CRDB)), 4326);

    printAll = parameter().getBoolean("show_all_stations", false);
  }


  @Override
  protected void doProcess() throws ServiceException, FactoryException, TransformException {
    finalStation = getStationData(findClosestStation(getHUC12Boundary()));
  }


  @Override
  protected void postProcess() throws ServiceException, JSONException, FactoryException {
    JSONArray tOutput = new JSONArray();
    finalStation.setNonOutputColumns(null);
    finalStation.setOutputColumnOrdering(new ArrayList<>(Arrays.asList(
        ClimateStation.STATION_ID, ClimateStation.STATION_NAME,
        ClimateStation.STATION_DISTANCE, ClimateStation.AVG_TEMP,
        ClimateStation.AVG_LOW_TEMP, ClimateStation.AVG_HI_TEMP,
        ClimateStation.AVG_PCPN)));
    finalStation.toJSON(tOutput);
    for (int i = 0; i < tOutput.length(); i++) {
      // changed result to name the station from 'putResult(tOutput.get(i));' to
      results().put("station_" + i, tOutput.get(i));
    }
    results().put("station_proximity_type", ((foundInHUC) ? "Within HUC-12 boundary" : ((foundInCounty) ? "Within county boundary" : "Within state boundary")), "Identifies the potential applicability of the station's data to the actual watershed represented by the input location point.  Possible locations are HUC-12, county, and state boundary, respectively representing lesser applicability.");

    if (printAll && (allStations.size() > 0)) {
      JSONArray stations = new JSONArray();

      SortedMap sortedStations = sortStations(allStations);
      Iterator itr = sortedStations.values().iterator();

      while (itr.hasNext()) {
        ClimateStation tStation = (ClimateStation) itr.next();
        tStation.setNonOutputColumns(null);
        tStation.setOutputColumnOrdering(new ArrayList<>(Arrays.asList(
            ClimateStation.STATION_ID, ClimateStation.STATION_NAME,
            ClimateStation.STATION_DISTANCE)));
        JSONArray stationArray = new JSONArray();
        tStation.toJSON(stationArray);
        stations.put(stationArray);
      }

      if (stations.length() > 0) {
        results().put("all_station_list", stations);
      }
    }
  }

////////////////////////////////    
//    
//  Private member functions
//    
//////////////////////////////// 

  private HUC12 getHUC12Boundary() throws ServiceException {
    HUC12 huc12 = null;
    try {
      huc12 = new HUC12();
      huc12.setOutFields("HUC12, NAME");
      huc12.setFormat("geojson");
      //  Assumes that the input geometry was a point.  However, if not, that is okay also.
      //    The embedded function getLongitude() and getLatitude() in GISObject will
      //  return the centroid of all other geometry shapes, as well, by default.
      huc12.fillHUC12Data(locationPoint.getLongitude() + ", " + locationPoint.getLatitude());
    } catch (GISObjectException | JSONException | URISyntaxException | IOException | ServiceException ex) {
      throw new ServiceException("Cannot get HUC12 boundary: " + ex.getMessage(), ex);
    }

    return huc12;
  }


  private ClimateStation findClosestStation(HUC12 huc12) throws ServiceException, FactoryException, TransformException {
    ArrayList<ClimateStation> stationList;

    ClimateStation ret_val = null;

    if (null != huc12) {
      String coord = huc12.getEnvelopeList();
      StationMetaDataService stationService = new StationMetaDataService();

      stationList = stationService.searchBBox(coord, sDate.toString(), eDate.toString());
      if (stationList.size() > 0) {
        ret_val = traverseFindNearest(stationList);
        foundInHUC = true;
      } else {
        //  We want to keep trying to find stations, but now outside of the associated HUC-12 boundary.
        // For some states, there can be very few climate stations, and they may be almost 100 miles away.
        //  1.  Check county
        //  2.  If no county stations, check State.
        try (Connection conn = resources().getJDBC(NRTDB);) {
          NRT_DB nrtDb = new NRT_DB(conn);
          County countyData = nrtDb.findCounty(locationPoint);

          stationList = stationService.searchCounty(countyData.st_cnty_code, sDate.toString(), eDate.toString());

          if (stationList.size() > 0) {
            foundInCounty = true;
            ret_val = traverseFindNearest(stationList);
          } else {
            stationList = stationService.searchState(countyData.st_abbr, sDate.toString(), eDate.toString());

            if (stationList.size() > 0) {
              foundInState = true;
              ret_val = traverseFindNearest(stationList);
            } else {
              throw new ServiceException("Could not find  climate stations within the HUC-12, County, nor State boundaries, for the specified location, and date range.");
            }
          }
        } catch (SQLException ex) {
          throw new ServiceException("Did not find any climate stations within the bounding box containing the HUC-12 boundary which contained the location specified.  Failed in attempt to locate state and county information to search for more possible stations: " + ex.getMessage(), ex);
        }
      }
      if (printAll) {
        allStations.addAll(stationList);
      }
    } else {
      throw new ServiceException("No HUC-12 boundary was passed to the findClosestStation() function.");
    }

    return ret_val;

  }


  private SortedMap sortStations(ArrayList<ClimateStation> stationList) throws FactoryException, ServiceException {
    SortedMap distanceMap = new TreeMap();

    CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326");
    for (ClimateStation station : stationList) {
      //double distance = convertKMtoMiles(JTS.orthodromicDistance(locationPoint.getGeometry().getCoordinate(), station.coordinates().getGeometry().getCoordinate(), sourceCRS)/1000.0);
      double distance = Double.NaN;
      try {
        distance = convertKMtoMiles(locationPoint.distanceTo(station.coordinates()) / 1000.0);
      } catch (GISObjectException ex) {
        throw new ServiceException("Cannot calculate distance to climate station, " + station.name() + ", for the specified location point: " + ex.getMessage(), ex);
      }
      station.distance(distance);
      distanceMap.put(distance, station);
    }

    return distanceMap;
  }


  private ClimateStation traverseFindNearest(ArrayList<ClimateStation> stationList) throws ServiceException, FactoryException, TransformException {
    ClimateStation ret_val = null;

    if (null != stationList) {
      if (stationList.size() > 1) {
        SortedMap distanceMap = sortStations(stationList);
        if (distanceMap.size() > 0) {
          //  Get closest station
          ret_val = (ClimateStation) distanceMap.get(distanceMap.firstKey());
        } else {
          throw new ServiceException("Could not sort the station list data.  Sorting function failed to return any data.");
        }
      } else {
        ret_val = stationList.get(0);
      }
    }
    if (null == ret_val) {
      throw new ServiceException("No nearby climate station could be found for this HUC-12 boundary.");
    }
    return ret_val;
  }


  private ClimateStation getStationData(ClimateStation station) throws ServiceException {
    if (null != station) {
      StationDataService stationData = new StationDataService(station, sDate.toString(), 5);
      stationData.getStationData();
    } else {
      throw new ServiceException("No climate station was passed to the getStationData() function.");
    }
    return station;
  }
}