CligenV1_0.java [src/java/d/weather] 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 d.weather;
import csip.Config;
import csip.ModelDataService;
import csip.api.client.ModelDataServiceCall;
import csip.api.server.ServiceException;
import csip.annotations.Description;
import csip.annotations.Name;
import csip.annotations.Resource;
import csip.utils.Parallel;
import data.interpretors.CligenData;
import static data.interpretors.CligenData.*;
import data.interpretors.WindGenData;
import gisobjects.GISObject;
import gisobjects.GISObjectFactory;
import gisobjects.db.GISEngineFactory;
import java.sql.Connection;
import java.time.LocalDate;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import javax.ws.rs.Path;
import static m.ghg.ApplicationResources.CLIGEN_URL_KEY;
import static m.ghg.ApplicationResources.GISDB_SQLSVR;
import static m.ghg.ApplicationResources.WINDGEN_URL_KEY;
import org.codehaus.jettison.json.JSONObject;
/**
*
* @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
*/
/*
WEATHER FILE Specifications:
==============================================================================
Daily Weather Data File:
1 1 1990 1 7.040 -10.300 0.000 186.425 55.42 10.939
2 1 1990 2 9.200 -10.530 0.000 158.115 57.42 5.552
3 1 1990 3 11.840 -7.330 0.000 222.946 42.13 9.165
4 1 1990 4 1.297 -10.310 0.000 182.844 40.97 12.543
5 1 1990 5 1.239 -16.010 0.000 213.159 52.25 5.214
6 1 1990 6 3.745 -9.380 0.000 230.346 41.89 10.603
...
27 12 1992 362 11.320 -12.880 0.000 217.456 35.13 4.429
28 12 1992 363 7.050 -10.180 0.000 216.501 28.25 5.132
29 12 1992 364 -1.095 -8.370 0.000 93.833 53.59 5.264
30 12 1992 365 7.330 -11.490 0.000 152.243 35.88 3.098
31 12 1992 366 7.330 -11.490 0.000 152.243 35.88 3.098
NOTES:
Column 1 - Day of month, 1-31
Column 2 - Month of year, 1-12
Column 3 - Year
Column 4 - Day of the year, 1-366
Column 5 - Maximum temperature for day, degrees C
Column 6 - Minimum temperature for day, degrees C
Column 7 - Precipitation for day, centimeters
Column 8 - Solar radiation, in langleys/day
Column 9 - Relative humidity, percentage, 1-100
Column 10 - Wind speed, miles per hour
Missing weather data values for precipitation, minimum temperature and maximum
temperature are represented by the value -99.9.
The last three columns in the weather data file, solar radiation, relative
humidity, and wind speed, are optional. When these values are not included in
a weather file PET is computed using the FWLOSS(4) input variable from the
FIX.100 file and the flag for the extra weather drivers in the SITEPAR.IN file
must be set to 0.
==============================================================================
Schedule file notes for running DayCent:
When creating your schedule file for DayCent keep in mind that it operates
using daily weather data and must be able to find and read your daily weather
data file. The "F" weather option choice is the only valid weather option for
the initial block in a DayCent schedule file. The weather filename of the
daily weather data file must also appear in your schedule file following the
"F". If you use a weather option value other than "F" for the initial weather
option in your schedule file the simulation will not run. Subsequent blocks
may use either the "F", to start reading from the start of a daily weather
file, or "C", to continue reading the current weather file, weather options.
*/
@Name("DayCent Weather")
@Description("Provide Daycent weather input.")
@Path("d/weather/cligen/1.0")
@Resource(from = m.ghg.ApplicationResources.class)
@Resource(from = soils.db.DBResources.class)
public class CligenV1_0 extends ModelDataService {
// Request JSON name defines
static final String STREAM_FILE_KEY = "stream_output_file";
static final String USE_CLIMATE_WIND_KEY = "use_cligen_wind";
static final String DEFAULT_DAYCENT_CLIMATE_FILE = "dayCentWeather.IN";
static final String WEATHER_FILE_KEY = "weather_file";
static final String WEATHER_DURATION_KEY = "weather_duration";
static final String STARTING_YEAR_KEY = "starting_year";
boolean fileAsJSON;
String dayCentClimateFileData;
int weatherDuration;
int startingYear;
JSONObject aoa_geometry;
boolean useClimateWind;
WindGenData windData;
CligenData climateData;
static final String windgenURL = Config.getString(WINDGEN_URL_KEY);
static final String cligenURL = Config.getString(CLIGEN_URL_KEY);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
protected void preProcess() throws Exception {
aoa_geometry = parameter().getJSON(soils.AoA.AOA_GEOMETRY);
useClimateWind = parameter().getBoolean(USE_CLIMATE_WIND_KEY, false);
fileAsJSON = parameter().getBoolean(STREAM_FILE_KEY, false);
// weatherDuration = parameter().getInt(WEATHER_DURATION_KEY, 50);
// startingYear = parameter().getInt(STARTING_YEAR_KEY, 2000);
LocalDate s = LocalDate.parse(parameter().getString("start_date"), formatter);
LocalDate e = LocalDate.parse(parameter().getString("end_date"), formatter);
startingYear = s.getYear();
weatherDuration = (int) s.until(e, ChronoUnit.YEARS);
}
@Override
protected void doProcess() throws Exception {
double[] latlon = {Double.NaN, Double.NaN};
// Get centroid of shape, if it is not a point already. (If it's a point, the centroid is the point)
try (Connection conn = resources().getJDBC(GISDB_SQLSVR)) {
GISObject shape = GISObjectFactory.createGISObject(aoa_geometry, GISEngineFactory.createGISEngine(conn));
// getLat/Lon functions get point value or the centroid of the shape.
latlon[0] = shape.getLatitude();
latlon[1] = shape.getLongitude();
}
if (Double.isNaN(latlon[0]) || Double.isNaN(latlon[1])) {
throw new ServiceException("Could not get the latitude and longitude of the AoI");
}
Parallel.run(
() -> {
climateData = getClimateData(cligenURL, latlon[0], latlon[1], weatherDuration);
},
() -> {
if (!useClimateWind) {
windData = getWindData(windgenURL, latlon[0], latlon[1], weatherDuration);
}
});
// Use combined Climate/Wind output files class in csip-common(to be written), to merge climate and wind data
// Generate combined Climate/Wind file using DayCent specifications above.
dayCentClimateFileData = toClimate(windData, climateData, useClimateWind, startingYear);
}
@Override
protected void postProcess() throws Exception {
if (!fileAsJSON) {
workspace().writeString(DEFAULT_DAYCENT_CLIMATE_FILE, dayCentClimateFileData);
results().put(workspace().getFile(DEFAULT_DAYCENT_CLIMATE_FILE), "DayCent weather file");
} else {
results().put(WEATHER_FILE_KEY, dayCentClimateFileData, "DayCent weather file data.");
}
}
static WindGenData getWindData(String url, double lat, double lon, int years) throws Exception {
ModelDataServiceCall res = new ModelDataServiceCall()
.put("latitude", lat)
.put("longitude", lon)
.put("duration", years)
.url(url)
.withDefaultLogger()
.call();
if (res.serviceFinished()) {
String windString = res.download("win_gen_d.win");
return new WindGenData(windString, null);
} else {
throw new ServiceException("Wind service error: " + res.getError());
}
}
static CligenData getClimateData(String url, double lat, double lon, int years) throws Exception {
ModelDataServiceCall r = new ModelDataServiceCall()
.put("outputFile", "cligen.cli")
.put("returnParFiles", "true")
//.put("adjustPWW_PWD", adjustPWW_PWD)
.put("usePRISM", true)
.put("dataVersion", 2015)
.put("duration", years)
.put("input_zone_features", new JSONObject(
"{ 'type': 'FeatureCollection',"
+ " 'features': [{"
+ " 'type': 'Feature',"
+ " 'properties': {"
+ " 'name': 'pt one',"
+ " 'gid': 1"
+ " },"
+ " 'geometry': {"
+ " 'type': 'Point',"
+ " 'coordinates': ["
+ " " + lon + ","
+ " " + lat
+ " ]"
+ " }"
+ " }]"
+ "}"))
.withDefaultLogger()
.url(url)
.call();
if (r.serviceFinished()) {
String climateString = r.download("cligen.cli");
return new CligenData(climateString, null);
} else {
throw new ServiceException("Climate service error: " + r.getError());
}
}
static String toClimate(WindGenData windData,
CligenData climateData, boolean useCligenWind, int startingYear)
throws ServiceException {
if (!((windData != null) || useCligenWind) && (climateData != null)) {
throw new ServiceException("requires a valid WindGenData and CligenData input.");
}
int climateYears = climateData.yearsInFile();
int windYears = useCligenWind ? climateYears : windData.yearsInFile();
int combinedYears = (windYears < climateYears) ? windYears : climateYears;
if (combinedYears < 1) {
throw new ServiceException("combined climate years < 1");
}
StringBuilder fs = new StringBuilder("");
for (int year = 0; year < combinedYears; year++) {
int currentYear = year + startingYear;
boolean leapYear = Year.of(currentYear).isLeap();
int yearDay = 0;
double[][] windEnergy = useCligenWind ? null : windData.dailyData(year);
double[][][] climateValues = climateData.dailyData(year);
for (int month = 0; month < 12; month++) {
//Use WindGenData months...skip leap years. (windgen and cligen don't count Feb the same)
for (int day = 0; day < WindGenData.monthDays[month]; day++) {
double tMax = climateValues[month][day][TMAX_INDEX];
double tMin = climateValues[month][day][TMIN_INDEX];
double dewPoint = climateValues[month][day][TDEW_INDEX];
//cligen stores precip in mm, we need cm
double precip = climateValues[month][day][PRCP_INDEX] / 10.0;
double rad = climateValues[month][day][RAD_INDEX];
//Wind speeds are stored in cligen and windgen as meters per second, we need mph.
double windSpeed = (useCligenWind ? climateValues[month][day][W_VL_INDEX] : windEnergy[month][day]) * 2.236936;
double avgT = (tMax + tMin) / 2.0;
//Standard RH calc based on temps and dew point in Celcius.
double rHumidity = 100 * (Math.exp((17.625 * dewPoint) / (243.04 + dewPoint)) / Math.exp((17.625 * avgT) / (243.04 + avgT)));
fs.append(day + 1).append('\t').append(month + 1).append('\t').append(currentYear).append('\t')
.append(yearDay + 1).append('\t').append(tMax).append('\t').append(tMin).append('\t').append(precip).append('\t')
.append(rad).append('\t').append(rHumidity).append('\t').append(windSpeed).append('\n');
yearDay++;
if (leapYear && (month == 1) && (day == 27)) {
// If this is a leapyear and Feb 28th, duplicate Feb 28th as also Feb 29th.
fs.append(day + 2).append('\t').append(month + 1).append('\t').append(currentYear).append('\t')
.append(yearDay + 1).append('\t').append(tMax).append('\t').append(tMin).append('\t').append(precip).append('\t')
.append(rad).append('\t').append(rHumidity).append('\t').append(windSpeed).append('\n');
yearDay++;
}
}
}
}
return fs.toString();
}
}