00001
00007 #include <stdlib.h>
00008 #include <stdio.h>
00009 #include <errno.h>
00010 #include <math.h>
00011
00012 #include "climate.h"
00013 #include "timestamp.h"
00014
00015 #define RAD(x) (x*PI)/180
00016
00017 double std_meridians[] = {75,90,105,120,135,150};
00018
00019 double surface_angles[] = {
00020 360,
00021 180,
00022 135,
00023 90,
00024 45,
00025 0,
00026 -45,
00027 -90,
00028 -135,
00029 };
00030
00043 int tmy2_reader::open(const char *file){
00044 fp = fopen(file, "r");
00045
00046 if(fp == NULL){
00047 gl_error("tmy2_reader::open() -- fopen failed on \"%s\"", file);
00048 return 0;
00049 }
00050
00051
00052 if(fgets(buf,500,fp)){
00053 return sscanf(buf,"%*s %75s %3s %d %*s %d %d %*s %d %d",data_city,data_state,&tz_offset,&lat_degrees,&lat_minutes,&long_degrees,&long_minutes);
00054 } else {
00055 gl_error("tmy2_reader::open() -- first fgets read nothing");
00056 return 0;
00057 }
00058
00059 }
00060
00065 int tmy2_reader::next(){
00066
00067 char *val = fgets(buf,500,fp);
00068
00069 if(val != NULL)
00070 return 1;
00071 else
00072 return 0;
00073 }
00074
00085 int tmy2_reader::header_info(char* city, char* state, int* degrees, int* minutes, int* long_deg, int* long_min){
00086 if(city) strcpy(city,data_city);
00087 if(state) strcpy(state,data_state);
00088 if(degrees) *degrees = lat_degrees;
00089 if(minutes) *minutes = lat_minutes;
00090 if(long_deg) *long_deg = long_degrees;
00091 if(long_min) *long_min = long_minutes;
00092 return 1;
00093 }
00094
00107 int tmy2_reader::read_data(double *dnr, double *dhr, double *ghr, double *tdb, double *rh, int* month, int* day, int* hour, double *wind, double *precip, double *snowDepth){
00108 int tmp_dnr, tmp_dhr, tmp_tdb, tmp_rh, tmp_ws, tmp_precip, tmp_sf, tmp_ghr;
00109
00110 int tmh, tday, thr;
00111 if(month == NULL) month = &tmh;
00112 if(day == NULL) day = &tday;
00113 if(hour == NULL) hour = &thr;
00114 sscanf(buf, "%*2s%2d%2d%2d%*8s%4d%*2s%4d%*2s%4d%*34s%4d%8*s%3d%*13s%3d%*25s%3d%*7s%3d",month,day,hour,&tmp_ghr,&tmp_dnr,&tmp_dhr,&tmp_tdb,&tmp_rh, &tmp_ws,&tmp_precip,&tmp_sf);
00115
00116 if(dnr) *dnr = tmp_dnr;
00117 if(dhr) *dhr = tmp_dhr;
00118 if(ghr) *ghr = tmp_ghr;
00119 if(tdb)
00120 {
00121 *tdb = ((double)tmp_tdb)/10.0;
00122 if (*tdb<low_temp || low_temp==0) low_temp = *tdb;
00123 else if (*tdb>high_temp || high_temp==0) high_temp = *tdb;
00124 }
00125
00126 if(rh) *rh = ((double)tmp_rh)/100.0;
00127 if(wind) *wind = tmp_ws/10.0;
00128
00129
00130 if(precip) *precip = ((double)tmp_precip) * 0.03937;
00131
00132
00133 if(snowDepth) *snowDepth = ((double)tmp_sf) * 0.3937;
00134
00135 return 1;
00136 }
00137
00151 double tmy2_reader::calc_solar(COMPASS_PTS cpt, short doy, double lat, double sol_time, double dnr, double dhr, double ghr, double gnd_ref, double vert_angle = 90)
00152 {
00153 SolarAngles *sa = new SolarAngles();
00154 double surface_angle = surface_angles[cpt];
00155 double cos_incident = sa->cos_incident(lat,RAD(vert_angle),RAD(surface_angle),sol_time,doy);
00156
00157
00158 double solar = dnr * cos_incident + dhr;
00159
00160 if (peak_solar==0 || solar>peak_solar) peak_solar = solar;
00161 return solar;
00162 }
00166 void tmy2_reader::close(){
00167 fclose(fp);
00168 }
00169
00178 CLASS *climate::oclass = NULL;
00179 climate *climate::defaults = NULL;
00180
00181 climate::climate(MODULE *module)
00182 {
00183 memset(this, 0, sizeof(climate));
00184 if (oclass==NULL)
00185 {
00186 oclass = gl_register_class(module,"climate",sizeof(climate),PC_PRETOPDOWN);
00187 if (gl_publish_variable(oclass,
00188 PT_char32, "city", PADDR(city),
00189 PT_char1024,"tmyfile",PADDR(tmyfile),
00190 PT_double,"temperature[degF]",PADDR(temperature),
00191 PT_double,"humidity[%]",PADDR(humidity),
00192 PT_double,"solar_flux[W/sf]",PADDR(solar_flux), PT_SIZE, 9,
00193 PT_double,"solar_direct[W/sf]",PADDR(solar_direct),
00194 PT_double,"wind_speed[mph]", PADDR(wind_speed),
00195 PT_double,"wind_dir[deg]", PADDR(wind_dir),
00196 PT_double,"wind_gust[mph]", PADDR(wind_gust),
00197 PT_double,"record.low[degF]", PADDR(record.low),
00198 PT_int32,"record.low_day",PADDR(record.low_day),
00199 PT_double,"record.high[degF]", PADDR(record.high),
00200 PT_int32,"record.high_day",PADDR(record.high_day),
00201 PT_double,"record.solar[W/sf]", PADDR(record.solar),
00202 PT_double,"rainfall[in/h]",PADDR(rainfall),
00203 PT_double,"snowdepth[in]",PADDR(snowdepth),
00204 PT_enumeration,"interpolate",PADDR(interpolate),PT_DESCRIPTION,"the interpolation mode used on the climate data",
00205 PT_KEYWORD,"NONE",CI_NONE,
00206 PT_KEYWORD,"LINEAR",CI_LINEAR,
00207 PT_KEYWORD,"QUADRATIC",CI_QUADRATIC,
00208 PT_double,"solar_horiz",PADDR(solar_flux[CP_H]),
00209 PT_double,"solar_north",PADDR(solar_flux[CP_N]),
00210 PT_double,"solar_northeast",PADDR(solar_flux[CP_NE]),
00211 PT_double,"solar_east",PADDR(solar_flux[CP_E]),
00212 PT_double,"solar_southeast",PADDR(solar_flux[CP_SE]),
00213 PT_double,"solar_south",PADDR(solar_flux[CP_S]),
00214 PT_double,"solar_southwest",PADDR(solar_flux[CP_SW]),
00215 PT_double,"solar_west",PADDR(solar_flux[CP_W]),
00216 PT_double,"solar_northwest",PADDR(solar_flux[CP_NW]),
00217 PT_double,"solar_raw[W/sf]",PADDR(solar_raw),
00218 PT_double,"ground_reflectivity[%]",PADDR(ground_reflectivity),
00219 PT_object,"reader",PADDR(reader),
00220 NULL)<1) GL_THROW("unable to publish properties in %s",__FILE__);
00221 memset(this,0,sizeof(climate));
00222 strcpy(city,"");
00223 strcpy(tmyfile,"");
00224 temperature = 59.0;
00225 temperature_raw = 15.0;
00226 humidity = 0.75;
00227 rainfall = 0.0;
00228 snowdepth = 0.0;
00229 ground_reflectivity = 0.3;
00230
00231 solar_flux[0] = solar_flux[1] = solar_flux[2] = solar_flux[3] = solar_flux[4] = solar_flux[5] = solar_flux[6] = solar_flux[7] = solar_flux[8] = 0.0;
00232
00233 tmy = NULL;
00234 sa = new SolarAngles();
00235 defaults = this;
00236 }
00237 }
00238
00239 int climate::create(void)
00240 {
00241 memcpy(this,defaults,sizeof(climate));
00242 return 1;
00243 }
00244
00245 int climate::init(OBJECT *parent)
00246 {
00247 char *dot = 0;
00248 reader_type = RT_NONE;
00249
00250
00251 if (strcmp(tmyfile,"")==0)
00252 return 1;
00253
00254
00255 char *found_file = gl_findfile(tmyfile,NULL,FF_READ);
00256 if (found_file==NULL)
00257 {
00258 gl_error("weather file '%s' access failed", tmyfile);
00259 return 0;
00260 }
00261
00262
00263
00264
00265
00266
00267 if(strstr(tmyfile, ".tmy2") || strstr(tmyfile,".tmy")){
00268 reader_type = RT_TMY2;
00269 } else if(strstr(tmyfile, ".csv")){
00270 reader_type = RT_CSV;
00271 } else {
00272 gl_warning("climate: unrecognized filetype, assuming TMY2");
00273 }
00274
00275 if(reader_type == RT_CSV){
00276
00277
00278 if(reader == NULL){
00279 int rv = 0;
00280 csv_reader *creader = new csv_reader();
00281 reader_hndl = creader;
00282 rv = creader->open(found_file);
00283
00284 return rv;
00285 } else {
00286 int rv = 0;
00287 csv_reader *my = OBJECTDATA(reader,csv_reader);
00288 reader_hndl = my;
00289 rv = my->open(my->filename);
00290
00291 return rv;
00292 }
00293 }
00294
00295
00296 if( file.open(found_file) < 3 ){
00297 gl_error("climate::init() -- weather file header improperly formed");
00298 return 0;
00299 }
00300
00301
00302 int line=0;
00303 tmy = (TMYDATA*)malloc(sizeof(TMYDATA)*8760);
00304 if (tmy==NULL)
00305 {
00306 gl_error("TMY buffer allocation failed");
00307 return 0;
00308 }
00309
00310 int month, day, hour;
00311 double dnr,dhr,ghr,wspeed,precip,snowdepth;
00312
00313
00314 int lat_deg,lat_min,long_deg,long_min;
00315
00316
00317 file.header_info(NULL,NULL,&lat_deg,&lat_min,&long_deg,&long_min);
00318 double degrees = (double)lat_deg + ((double)lat_min) / 60;
00319 double long_degrees = (double)long_deg + ((double)long_min) / 60;
00320 double tz_meridian = 15 * abs(file.tz_offset);
00321 while (line<8760 && file.next())
00322 {
00323
00324 file.read_data(&dnr,&dhr,&ghr,&temperature,&humidity,&month,&day,&hour,&wspeed,&precip,&snowdepth);
00325
00326 int doy = sa->day_of_yr(month,day);
00327 int hoy = (doy - 1) * 24 + (hour-1);
00328 if (hoy>=0 && hoy<8760){
00329
00330 if(0 == gl_convert("W/m^2", "W/sf", &(dnr))){
00331 gl_error("climate::init unable to gl_convert() 'W/m^2' to 'W/sf'!");
00332 return 0;
00333 }
00334 if(0 == gl_convert("W/m^2", "W/sf", &(dhr))){
00335 gl_error("climate::init unable to gl_convert() 'W/m^2' to 'W/sf'!");
00336 return 0;
00337 }
00338 if(0 == gl_convert("W/m^2", "W/sf", &(ghr))){
00339 gl_error("climate::init unable to gl_convert() 'W/m^2' to 'W/sf'!");
00340 return 0;
00341 }
00342 tmy[hoy].temp_raw = temperature;
00343 tmy[hoy].temp = temperature;
00344
00345 if(0 == gl_convert("degC", "degF", &(tmy[hoy].temp))){
00346 gl_error("climate::init unable to gl_convert() 'degC' to 'degF'!");
00347 return 0;
00348 }
00349 tmy[hoy].windspeed=wspeed;
00350 tmy[hoy].rh = humidity;
00351 tmy[hoy].dnr = dnr;
00352 tmy[hoy].dhr = dhr;
00353 tmy[hoy].ghr = ghr;
00354 tmy[hoy].rainfall = precip;
00355 tmy[hoy].snowdepth = snowdepth;
00356 tmy[hoy].solar_raw = dnr;
00357
00358 double sol_time = sa->solar_time((double)hour,doy,RAD(tz_meridian),RAD(long_degrees));
00359 double sol_rad = 0.0;
00360
00361 for(COMPASS_PTS c_point = CP_H; c_point < CP_LAST;c_point=COMPASS_PTS(c_point+1)){
00362 if(c_point == CP_H)
00363 sol_rad = file.calc_solar(CP_E,doy,RAD(degrees),sol_time,dnr,dhr,ghr,ground_reflectivity,0.0);
00364 else
00365 sol_rad = file.calc_solar(c_point,doy,RAD(degrees),sol_time,dnr,dhr,ghr,ground_reflectivity);
00366
00367 tmy[hoy].solar[c_point] = sol_rad;
00368
00369
00370 if (sol_rad>record.solar || record.solar==0) record.solar = sol_rad;
00371 if (tmy[hoy].temp>record.high || record.high==0)
00372 {
00373 record.high = tmy[hoy].temp;
00374 record.high_day = doy;
00375 }
00376 if (tmy[hoy].temp<record.low || record.low==0)
00377 {
00378 record.low = tmy[hoy].temp;
00379 record.low_day = doy;
00380 }
00381 }
00382
00383 }
00384 else
00385 gl_error("%s(%d): day %d, hour %d is out of allowed range 0-8759 hours", tmyfile,line,day,hour);
00386
00387 line++;
00388 }
00389 file.close();
00390
00391 return 1;
00392 }
00393
00394 TIMESTAMP climate::sync(TIMESTAMP t0)
00395 {
00396 if(t0 > TS_ZERO && reader_type == RT_CSV){
00397 csv_reader *cr = OBJECTDATA(reader,csv_reader);
00398 return cr->get_data(t0, &temperature, &humidity, &solar_direct, &solar_diffuse, &solar_global, &wind_speed, &rainfall, &snowdepth);
00399 }
00400 if (t0>TS_ZERO && tmy!=NULL)
00401 {
00402 DATETIME ts;
00403 int localres = gl_localtime(t0,&ts);
00404 int hoy;
00405 double now, hoy0, hoy1, hoy2;
00406 if(localres == 0){
00407 GL_THROW("climate::sync -- unable to resolve localtime!");
00408 }
00409 int doy = sa->day_of_yr(ts.month,ts.day);
00410 hoy = (doy - 1) * 24 + (ts.hour);
00411 switch(interpolate){
00412 case CI_NONE:
00413 temperature = tmy[hoy].temp;
00414 temperature_raw = tmy[hoy].temp_raw;
00415 humidity = tmy[hoy].rh;
00416 solar_direct = tmy[hoy].dnr;
00417 solar_diffuse = tmy[hoy].dhr;
00418 solar_global = tmy[hoy].ghr;
00419 solar_raw = tmy[hoy].solar_raw;
00420 this->wind_speed = tmy[hoy].windspeed;
00421 this->rainfall = tmy[hoy].rainfall;
00422 this->snowdepth = tmy[hoy].snowdepth;
00423
00424 if(memcmp(solar_flux,tmy[hoy].solar,CP_LAST*sizeof(double)))
00425 memcpy(solar_flux,tmy[hoy].solar,CP_LAST*sizeof(double));
00426 break;
00427 case CI_LINEAR:
00428 now = hoy+ts.minute/60.0;
00429 hoy0 = hoy;
00430 hoy1 = hoy+1.0;
00431 temperature = gl_lerp(now, hoy0, tmy[hoy].temp, hoy1, tmy[hoy+1%8760].temp);
00432 temperature_raw = gl_lerp(now, hoy0, tmy[hoy].temp_raw, hoy1, tmy[hoy+1%8760].temp_raw);
00433 humidity = gl_lerp(now, hoy0, tmy[hoy].rh, hoy1, tmy[hoy+1%8760].rh);
00434 solar_direct = gl_lerp(now, hoy0, tmy[hoy].dnr, hoy1, tmy[hoy+1%8760].dnr);
00435 solar_diffuse = gl_lerp(now, hoy0, tmy[hoy].dhr, hoy1, tmy[hoy+1%8760].dhr);
00436 solar_global = gl_lerp(now, hoy0, tmy[hoy].ghr, hoy1, tmy[hoy+1%8760].ghr);
00437 wind_speed = gl_lerp(now, hoy0, tmy[hoy].windspeed, hoy1, tmy[hoy+1%8760].windspeed);
00438 rainfall = gl_lerp(now, hoy0, tmy[hoy].rainfall, hoy1, tmy[hoy+1%8760].rainfall);
00439 snowdepth = gl_lerp(now, hoy0, tmy[hoy].snowdepth, hoy1, tmy[hoy+1%8760].snowdepth);
00440 solar_raw = gl_lerp(now, hoy0, tmy[hoy].solar_raw, hoy1, tmy[hoy+1%8760].solar_raw);
00441 for(int pt = 0; pt < CP_LAST; ++pt){
00442 solar_flux[pt] = gl_lerp(now, hoy0, tmy[hoy].solar[pt], hoy1, tmy[hoy+1%8760].solar[pt]);
00443 }
00444 break;
00445 case CI_QUADRATIC:
00446 now = hoy+ts.minute/60.0;
00447 hoy0 = hoy;
00448 hoy1 = hoy+1.0;
00449 hoy2 = hoy+2.0;
00450 temperature = gl_qerp(now, hoy0, tmy[hoy].temp, hoy1, tmy[hoy+1%8760].temp, hoy2, tmy[hoy+2%8760].temp);
00451 temperature_raw = gl_qerp(now, hoy0, tmy[hoy].temp_raw, hoy1, tmy[hoy+1%8760].temp_raw, hoy2, tmy[hoy+2%8760].temp_raw);
00452 humidity = gl_qerp(now, hoy0, tmy[hoy].rh, hoy1, tmy[hoy+1%8760].rh, hoy2, tmy[hoy+2%8760].rh);
00453 solar_direct = gl_qerp(now, hoy0, tmy[hoy].dnr, hoy1, tmy[hoy+1%8760].dnr, hoy2, tmy[hoy+2%8760].dnr);
00454 solar_diffuse = gl_qerp(now, hoy0, tmy[hoy].dhr, hoy1, tmy[hoy+1%8760].dhr, hoy2, tmy[hoy+2%8760].dhr);
00455 solar_global = gl_qerp(now, hoy0, tmy[hoy].ghr, hoy1, tmy[hoy+1%8760].ghr, hoy2, tmy[hoy+2%8760].ghr);
00456 wind_speed = gl_qerp(now, hoy0, tmy[hoy].windspeed, hoy1, tmy[hoy+1%8760].windspeed, hoy2, tmy[hoy+2%8760].windspeed);
00457 rainfall = gl_qerp(now, hoy0, tmy[hoy].rainfall, hoy1, tmy[hoy+1%8760].rainfall, hoy2, tmy[hoy+2%8760].rainfall);
00458 snowdepth = gl_qerp(now, hoy0, tmy[hoy].snowdepth, hoy1, tmy[hoy+1%8760].snowdepth, hoy2, tmy[hoy+2%8760].snowdepth);
00459 solar_raw = gl_qerp(now, hoy0, tmy[hoy].solar_raw, hoy1, tmy[hoy+1%8760].solar_raw, hoy2, tmy[hoy+2%8760].solar_raw);
00460 for(int pt = 0; pt < CP_LAST; ++pt){
00461 if(tmy[hoy].solar[pt] == tmy[hoy+1].solar[pt]){
00462 solar_flux[pt] = tmy[hoy].solar[pt];
00463 } else {
00464 solar_flux[pt] = gl_qerp(now, hoy0, tmy[hoy].solar[pt], hoy1, tmy[hoy+1%8760].solar[pt], hoy2, tmy[hoy+2%8760].solar[pt]);
00465 if(solar_flux[pt] < 0.0)
00466 solar_flux[pt] = 0.0;
00467 }
00468 }
00469 break;
00470 default:
00471 GL_THROW("climate::sync -- unrecognize interpolation mode!");
00472 }
00473 return -(t0+(3600*TS_SECOND-t0%(3600 *TS_SECOND)));
00474 }
00475 return TS_NEVER;
00476 }
00477
00479
00481
00483 EXPORT int create_climate(OBJECT **obj,
00484 OBJECT *parent)
00485 {
00486 *obj = gl_create_object(climate::oclass);
00487 if (*obj!=NULL)
00488 {
00489 climate *my = OBJECTDATA(*obj,climate);
00490 gl_set_parent(*obj,parent);
00491 my->create();
00492 return 1;
00493 }
00494 return 0;
00495 }
00496
00498 EXPORT int init_climate(OBJECT *obj,
00499 OBJECT *parent)
00500 {
00501 if (obj!=NULL)
00502 {
00503 climate *my = OBJECTDATA(obj,climate);
00504 return my->init(parent);
00505 }
00506 return 0;
00507 }
00508
00510 EXPORT TIMESTAMP sync_climate(OBJECT *obj,
00511 TIMESTAMP t0)
00512 {
00513 TIMESTAMP t1 = OBJECTDATA(obj,climate)->sync(t0);
00514 obj->clock = t0;
00515 return t1;
00516 }
00517