| 1 |
<?php
|
| 2 |
/**
|
| 3 |
* $Id: carbon_calculate.inc,v 1.42 2009/07/03 12:21:29 johnackers Exp $
|
| 4 |
*
|
| 5 |
* Drupal carbon module (calculations)
|
| 6 |
*
|
| 7 |
* Created on 18-Oct-2006
|
| 8 |
*
|
| 9 |
* @file
|
| 10 |
* The calculations used to build carbon footprint.
|
| 11 |
*
|
| 12 |
* Different carbon_stamps have a different scope, some declare
|
| 13 |
* emissions on one date, others declare emissions over a period
|
| 14 |
* of time.
|
| 15 |
*
|
| 16 |
* Some sources of emissions e.g. from air travel need special
|
| 17 |
* formulas for calculating resulting CO2 because fuel used in
|
| 18 |
* aircraft takeoff has to be taken into consideration.
|
| 19 |
*/
|
| 20 |
|
| 21 |
|
| 22 |
/*
|
| 23 |
* Describes the footprint results for a single account.
|
| 24 |
*
|
| 25 |
* used to pass results information between main functions
|
| 26 |
* including the reporting functions and the CRAG settlement functions.
|
| 27 |
*/
|
| 28 |
|
| 29 |
class GroupResult
|
| 30 |
{
|
| 31 |
var $account ; // account
|
| 32 |
var $group ; // group name
|
| 33 |
var $start ;
|
| 34 |
var $end ;
|
| 35 |
|
| 36 |
var $sector_kwh0 = array();
|
| 37 |
var $sector_kwh1 = array() ;
|
| 38 |
var $sector_co2 = array() ;
|
| 39 |
var $grand_kwh0 = 0 ;
|
| 40 |
var $grand_kwh1 = 0 ;
|
| 41 |
var $grand_co2 = 0 ;
|
| 42 |
var $C02_above_target = 0 ;
|
| 43 |
var $emissions = array();
|
| 44 |
}
|
| 45 |
|
| 46 |
/**
|
| 47 |
* Describes a single emission source e.g. a plane trip
|
| 48 |
*
|
| 49 |
*/
|
| 50 |
|
| 51 |
class Emission
|
| 52 |
{
|
| 53 |
var $start ; // stamp use for starting point
|
| 54 |
var $end ; // stamp used for end point
|
| 55 |
var $error ;
|
| 56 |
var $notes ; // additional information to present to user
|
| 57 |
|
| 58 |
var $select ; // array of stamps
|
| 59 |
var $co2 ; // co2 emission
|
| 60 |
var $kwh0 ; // energy 0
|
| 61 |
var $kwh1 ; // energy 1
|
| 62 |
|
| 63 |
function Emission($select)
|
| 64 |
{
|
| 65 |
$this->select = $select;
|
| 66 |
}
|
| 67 |
}
|
| 68 |
|
| 69 |
|
| 70 |
class CarbonStamp
|
| 71 |
{
|
| 72 |
|
| 73 |
}
|
| 74 |
|
| 75 |
/**
|
| 76 |
* work out where to place the boundary between years
|
| 77 |
* use months and days from any user preference
|
| 78 |
* otherwise use Jan 1 in year of first stamp
|
| 79 |
*
|
| 80 |
* @param $dateRange first and last enddate omn stamps
|
| 81 |
* @param $firstDate as specified in account settings, year ignored
|
| 82 |
* @param $period as specified in account settings
|
| 83 |
* @return unknown_type
|
| 84 |
*/
|
| 85 |
function carbon_calculate_reporting_dates(&$dateRange, $firstDate, $period)
|
| 86 |
{
|
| 87 |
$earliestStampDate = $dateRange['first'];
|
| 88 |
if (!empty($firstDate))
|
| 89 |
{
|
| 90 |
$y = date('Y', $firstDate);
|
| 91 |
$m = date('m', $firstDate);
|
| 92 |
$d = date('d', $firstDate);
|
| 93 |
}
|
| 94 |
else
|
| 95 |
{
|
| 96 |
$y = date('Y',$earliestStampDate);
|
| 97 |
$m = 1 ; // jan
|
| 98 |
$d = 1 ; // 1st
|
| 99 |
}
|
| 100 |
$earliestDate = mktime(0, 0, 0, $m, $d, $y);
|
| 101 |
|
| 102 |
// the purpose of the $maxReports integer is only to prevent
|
| 103 |
// any software bug from printing numerous reports
|
| 104 |
|
| 105 |
$dates = array();
|
| 106 |
|
| 107 |
for ($i = 0 ; $i < ($maxReports=20) ; $i++)
|
| 108 |
{
|
| 109 |
$dates[] = $firstdayinyear = _add_months($earliestDate, $i * $period);
|
| 110 |
$firstdayinyear_s = CarbonDate::asYYYYMMDD($firstdayinyear); // to help debug
|
| 111 |
|
| 112 |
|
| 113 |
// break out when last date reached
|
| 114 |
if ($firstdayinyear >= $dateRange['last'])
|
| 115 |
break ;
|
| 116 |
}
|
| 117 |
return $dates;
|
| 118 |
}
|
| 119 |
|
| 120 |
|
| 121 |
|
| 122 |
/**
|
| 123 |
* Create a set of results for a given period (usually a year)
|
| 124 |
* based on on all the available carbon_stamps. Maintain
|
| 125 |
* sector (e.g. home, tranport) and grand totals.
|
| 126 |
*
|
| 127 |
* @param $account passed to $results but only nid used
|
| 128 |
* @param $stamps array of all carbon_stamps (with associated carbon_source fields)
|
| 129 |
* @param $start first date the generated carbon footprint
|
| 130 |
* @param $end last date of the generated carbon footprint
|
| 131 |
*
|
| 132 |
* @return a complex structure that has the carbon footprint for this period
|
| 133 |
*/
|
| 134 |
|
| 135 |
|
| 136 |
function carbon_calculate_total(&$account, &$stamps, $start, $end)
|
| 137 |
{
|
| 138 |
$results = new GroupResult();
|
| 139 |
$results->account = $account ;
|
| 140 |
$results->start = $start ;
|
| 141 |
$results->end = $end ;
|
| 142 |
$results->emissions = _calculate($stamps, $start, $end);
|
| 143 |
|
| 144 |
foreach ($results->emissions as $emission)
|
| 145 |
{
|
| 146 |
$sector = $emission->select[0]->sector ;
|
| 147 |
if (isset($emission->kwh0))
|
| 148 |
{
|
| 149 |
//aacc
|
| 150 |
if (!isset($results->sector_kwh0[$sector]))
|
| 151 |
$results->sector_kwh0[$sector] = 0 ;
|
| 152 |
|
| 153 |
$results->sector_kwh0[$sector] += $emission->kwh0 ;
|
| 154 |
$results->grand_kwh0 += $emission->kwh0 ;
|
| 155 |
}
|
| 156 |
if (isset($emission->kwh1))
|
| 157 |
{
|
| 158 |
//aacc
|
| 159 |
if (!isset($results->sector_kwh1[$sector]))
|
| 160 |
$results->sector_kwh1[$sector] = 0 ;
|
| 161 |
|
| 162 |
$results->sector_kwh1[$sector] += $emission->kwh1 ;
|
| 163 |
$results->grand_kwh1 += $emission->kwh1 ;
|
| 164 |
}
|
| 165 |
if (isset($emission->co2))
|
| 166 |
{
|
| 167 |
if (!isset($results->sector_co2[$sector]))
|
| 168 |
$results->sector_co2[$sector] = 0 ;
|
| 169 |
|
| 170 |
$results->sector_co2[$sector] += $emission->co2 ;
|
| 171 |
$results->grand_co2 += $emission->co2 ;
|
| 172 |
}
|
| 173 |
// need to execute this once.
|
| 174 |
$results->enablekwh0 = $emission->select[0]->enablekwh0 && (0 < strlen(variable_get("carbon_energy_units_0", "")));
|
| 175 |
$results->enablekwh1 = $emission->select[0]->enablekwh1 && (0 < strlen(variable_get("carbon_energy_units_1", "")));
|
| 176 |
$results->enableco2 = $emission->select[0]->enableco2 ;
|
| 177 |
}
|
| 178 |
return $results ;
|
| 179 |
}
|
| 180 |
|
| 181 |
|
| 182 |
|
| 183 |
/**
|
| 184 |
* Aggregate all similar carbon stamps and build an array
|
| 185 |
* of results of CO2 calculations.
|
| 186 |
*
|
| 187 |
* @param $ar array of all carbon_stamps (with associated carbon_source fields)
|
| 188 |
* @param $start first date the generated carbon footprint
|
| 189 |
* @param $end last date of the generated carbon footprint
|
| 190 |
*
|
| 191 |
* @return an array of emissions. Each result contains text and/or value.
|
| 192 |
*/
|
| 193 |
|
| 194 |
|
| 195 |
function _calculate(&$ar, $start, $stop)
|
| 196 |
{
|
| 197 |
$dgroup = array();
|
| 198 |
foreach ($ar as $node)
|
| 199 |
{
|
| 200 |
$dgroup[] = _concat_same_value($node);
|
| 201 |
}
|
| 202 |
|
| 203 |
// each key represents a group of readings
|
| 204 |
// that can be considered identical.
|
| 205 |
|
| 206 |
$keys = array_unique($dgroup);
|
| 207 |
$emissions = array();
|
| 208 |
foreach ($keys as $subgroup)
|
| 209 |
{
|
| 210 |
// pull out the first group and
|
| 211 |
// select all the nodes that match that group
|
| 212 |
|
| 213 |
$select = array();
|
| 214 |
$column_notes = "" ;
|
| 215 |
foreach ($ar as $node2)
|
| 216 |
{
|
| 217 |
if (_concat_same_value($node2) != $subgroup)
|
| 218 |
continue ;
|
| 219 |
$select[] = $node2;
|
| 220 |
$node2->total = null ;
|
| 221 |
}
|
| 222 |
$fname = '_scope_'.$select[0]->scope;
|
| 223 |
|
| 224 |
if (function_exists($fname))
|
| 225 |
{
|
| 226 |
$fname($emissions, $select, $start, $stop);
|
| 227 |
}
|
| 228 |
else
|
| 229 |
{
|
| 230 |
drupal_set_message("Invalid or missing function ". $fname, "error");
|
| 231 |
}
|
| 232 |
}
|
| 233 |
return $emissions ;
|
| 234 |
}
|
| 235 |
|
| 236 |
/**
|
| 237 |
* identify stamps that are identical so that they can be
|
| 238 |
* group together.
|
| 239 |
*
|
| 240 |
* meter readings (scope == 1) are handled as a group, so stamps
|
| 241 |
* with a different value should be group together.
|
| 242 |
*
|
| 243 |
* ja 2-jan-2009 use _concat() function
|
| 244 |
*/
|
| 245 |
|
| 246 |
|
| 247 |
function _concat_same_value($node)
|
| 248 |
{
|
| 249 |
$hashcode = _concat($node);
|
| 250 |
switch ($node->scope)
|
| 251 |
{
|
| 252 |
case 0 :
|
| 253 |
case 2 :
|
| 254 |
return $hashcode.'.'.$node->reading.'.'.$node->enddate.'.'.$node->startdate;
|
| 255 |
case 1 : return $hashcode ;
|
| 256 |
}
|
| 257 |
}
|
| 258 |
|
| 259 |
/**
|
| 260 |
* identify stamps that are identical that can presented in a group on a form
|
| 261 |
* and the only the field that needs to be changed is enddate and reading.
|
| 262 |
*
|
| 263 |
* ja 2-jan-2009 ignore adjustment and code fields. The side effect is that
|
| 264 |
* the title alone of each meter reading determines which sequence of readings it
|
| 265 |
* belongs to.
|
| 266 |
*/
|
| 267 |
|
| 268 |
function _concat($node){
|
| 269 |
return $node->title.'.'.$node->scope.'.'.$node->sourceclass;
|
| 270 |
}
|
| 271 |
|
| 272 |
|
| 273 |
/**
|
| 274 |
* todo check that no ; or : present
|
| 275 |
*/
|
| 276 |
|
| 277 |
function _evaluate($expression)
|
| 278 |
{
|
| 279 |
$expression = trim($expression);
|
| 280 |
$exp1 = substr($expression,0,1);
|
| 281 |
if (strcmp($exp1,"=")==0)
|
| 282 |
$expression = substr($expression,1);
|
| 283 |
|
| 284 |
return eval('return '.$expression.';');
|
| 285 |
}
|
| 286 |
|
| 287 |
|
| 288 |
|
| 289 |
function _stamp_map_sorted_by_enddate($ar)
|
| 290 |
{
|
| 291 |
$ar1 = array();
|
| 292 |
foreach ($ar as $node)
|
| 293 |
{
|
| 294 |
$k = $node->enddate;
|
| 295 |
while (isset($ar1[$k]))
|
| 296 |
$k++ ;
|
| 297 |
$ar1[$node->enddate] = $node ;
|
| 298 |
}
|
| 299 |
ksort($ar1);
|
| 300 |
return $ar1;
|
| 301 |
}
|
| 302 |
|
| 303 |
|
| 304 |
function _sort_by_valuedate($ar)
|
| 305 |
{
|
| 306 |
$ar1 = array();
|
| 307 |
foreach ($ar as $node)
|
| 308 |
{
|
| 309 |
$k = $node->valuedate;
|
| 310 |
while (isset($ar1[$k]))
|
| 311 |
$k++ ;
|
| 312 |
$ar1[$node->valuedate] = $node ;
|
| 313 |
}
|
| 314 |
ksort($ar1);
|
| 315 |
return array_values($ar1);
|
| 316 |
}
|
| 317 |
|
| 318 |
|
| 319 |
|
| 320 |
|
| 321 |
/**
|
| 322 |
* There are two scenarios.
|
| 323 |
* 1. the emissions occur on a single date (e.g. air travel)
|
| 324 |
* 2. the emissions are spread over a month , year or more.
|
| 325 |
*
|
| 326 |
* @return false if no stamp falls outside date range
|
| 327 |
*/
|
| 328 |
|
| 329 |
function _scope_0(&$emissions, $select, $start, $stop)
|
| 330 |
{
|
| 331 |
$emission = new Emission($select);
|
| 332 |
_duplicate_check($emission->select);
|
| 333 |
|
| 334 |
$stamp = $emission->select[0] ;
|
| 335 |
if (empty($stamp->startdate) || $stamp->startdate == $stamp->enddate)
|
| 336 |
{
|
| 337 |
// this is the simple case where all the emissions
|
| 338 |
// were released on the same date
|
| 339 |
// (take care with < and <= etc to include all stamps in
|
| 340 |
// at least one accounting period (a year)
|
| 341 |
//
|
| 342 |
if ($stamp->enddate < $start) return false ;
|
| 343 |
if ($stamp->enddate >= $stop) return false ;
|
| 344 |
_invoke_calculate($emission, $stamp, 1);
|
| 345 |
}
|
| 346 |
else
|
| 347 |
{
|
| 348 |
// this is the case where the emissions spread across a long
|
| 349 |
// period of time e.g. the carbon stamp might refer to emissions
|
| 350 |
// from june 05 to june 06 and so would appear in two separate years.
|
| 351 |
|
| 352 |
// explanation of below:
|
| 353 |
// Let's say user inputs carbon stamp of 1000 Kg of CO2 over 10 years.
|
| 354 |
// We want to allocate the 1000Kg over the ten years as evenly as possible.
|
| 355 |
// So we calculate an adjustment which equals
|
| 356 |
// number of emitting days inside our period of interest (i.e between $start and $end) /
|
| 357 |
// total number of emitting days.
|
| 358 |
|
| 359 |
if ($stamp->startdate <= $end) return false ;
|
| 360 |
|
| 361 |
$begin = max($stamp->startdate, $start);
|
| 362 |
$end = min($stamp->enddate, $stop);
|
| 363 |
if ($end <= $begin) return false ;
|
| 364 |
|
| 365 |
$adjust_for_period = ($end - $begin) / ($stamp->enddate - $stamp->startdate);
|
| 366 |
_invoke_calculate($emission, $stamp, $adjust_for_period);
|
| 367 |
|
| 368 |
}
|
| 369 |
$emission->valuedate = $stamp->enddate ;
|
| 370 |
|
| 371 |
$emissions[] = $emission ;
|
| 372 |
}
|
| 373 |
|
| 374 |
/**
|
| 375 |
* meter readings
|
| 376 |
*
|
| 377 |
* Use linear interpolation to calculate meter readings for the
|
| 378 |
* start and end times below then calculate emissions by creating
|
| 379 |
* a scope==0 stamp and calculating emissions in the usual way.
|
| 380 |
*
|
| 381 |
* The resulting emission is added to the emissions array.
|
| 382 |
*
|
| 383 |
*
|
| 384 |
* @param $emissions array of results of each calculation
|
| 385 |
* @param $select one of more scope==1 stamps to process
|
| 386 |
* @param $start start and
|
| 387 |
* @param $end end time of the accounting period we are interested in
|
| 388 |
* @return unknown_type
|
| 389 |
*/
|
| 390 |
function _scope_1(&$emissions, &$select, $start, $end)
|
| 391 |
{
|
| 392 |
$meterStamps = _stamp_map_sorted_by_enddate($select);
|
| 393 |
|
| 394 |
$usefulStamps = array(); // the stamps that we will use to calculate emissions
|
| 395 |
|
| 396 |
// add the first real stamp or calculated estimate stamp
|
| 397 |
// at the start of the period that we are interested in.
|
| 398 |
|
| 399 |
$usefulStamps[] = $first = _estimate_meter_reading($meterStamps, $start) ;
|
| 400 |
|
| 401 |
// add all the intermediate stamps
|
| 402 |
foreach ($meterStamps as $stamp) // sorted by ascending date
|
| 403 |
{
|
| 404 |
if ($stamp->enddate <= $start)
|
| 405 |
continue ; // meter reading too early
|
| 406 |
|
| 407 |
if ($stamp->enddate >= $end)
|
| 408 |
continue ; // meter reading too late
|
| 409 |
|
| 410 |
$usefulStamps[] = $stamp ;
|
| 411 |
}
|
| 412 |
// add the last stamp of the period we are interested in
|
| 413 |
$usefulStamps[] = _estimate_meter_reading($meterStamps, $end) ;
|
| 414 |
|
| 415 |
// look for other stamps between $start and $end where the $sourcecode
|
| 416 |
// or the sharing adjustment has been changed. If the adjustment
|
| 417 |
// hasn't changed, remove them from the map. This doesn't make
|
| 418 |
// any difference to the calculation but it is confusing to the
|
| 419 |
// user to itemise emissions between each reading.
|
| 420 |
|
| 421 |
$first = $second = null ;
|
| 422 |
$usefulStampsMap = _stamp_map_sorted_by_enddate($usefulStamps);
|
| 423 |
foreach ($usefulStampsMap as $stamp)
|
| 424 |
{
|
| 425 |
$date_s = _format_date_only($stamp->enddate);
|
| 426 |
if (isset($first) && isset($second))
|
| 427 |
{
|
| 428 |
if ( ($stamp->adjustment == $second->adjustment)
|
| 429 |
&& ($stamp->sourcecode == $second->sourcecode))
|
| 430 |
{
|
| 431 |
unset($usefulStampsMap[$second->enddate]); // remove the middle stamp
|
| 432 |
}
|
| 433 |
}
|
| 434 |
$first = $second ; $second = $stamp ;
|
| 435 |
}
|
| 436 |
|
| 437 |
$usefulStamps = array_values($usefulStampsMap);
|
| 438 |
|
| 439 |
|
| 440 |
// there will only be two stamps here, unless sourcecode or
|
| 441 |
// adjustment changes have been uncovered.
|
| 442 |
|
| 443 |
foreach ($usefulStamps as $current)
|
| 444 |
{
|
| 445 |
if ($current == null)
|
| 446 |
continue ; // no estimate available
|
| 447 |
|
| 448 |
if (empty($previous)) // skip first meter reading
|
| 449 |
{
|
| 450 |
$previous = $current ; continue ;
|
| 451 |
}
|
| 452 |
|
| 453 |
// $emissions = _create_emissions_record($previousStamp, $stamp);
|
| 454 |
|
| 455 |
$stamp = clone($current); /// use adjust and sourcecode from last reading
|
| 456 |
$stamp->scope = 0 ;
|
| 457 |
$stamp->reading = $current->reading - $previous->reading ;
|
| 458 |
|
| 459 |
$e0 = new Emission($usefulStamps);
|
| 460 |
$e0->start = $previous ; $e0->end = $current ;
|
| 461 |
$e0->notes = "" ;
|
| 462 |
|
| 463 |
_invoke_calculate($e0, $stamp, 1) ;
|
| 464 |
|
| 465 |
if ($e0->start->estimate)
|
| 466 |
{
|
| 467 |
$e0->notes .= $e0->start->notes ;
|
| 468 |
}
|
| 469 |
|
| 470 |
if ($e0->end->estimate)
|
| 471 |
{
|
| 472 |
$e0->notes .= $e0->end->notes ;
|
| 473 |
}
|
| 474 |
|
| 475 |
if (isset($e0->end->adjustment))
|
| 476 |
if (_evaluate($e0->end->adjustment) != 1.0)
|
| 477 |
{
|
| 478 |
$e0->notes .= " Adjusted by ". $e0->end->adjustment. "." ;
|
| 479 |
}
|
| 480 |
|
| 481 |
$e0->valuedate = $e0->current->enddate;
|
| 482 |
|
| 483 |
$emissions[] = $e0;
|
| 484 |
$previous = $current ;
|
| 485 |
}
|
| 486 |
}
|
| 487 |
|
| 488 |
/**
|
| 489 |
* estimate a meter reading for a given date and return the value
|
| 490 |
* in a new temporary carbon stamp
|
| 491 |
*
|
| 492 |
* @param $meter_readings an array of carbon_stamps
|
| 493 |
* @param $date the date of the desired new fillin stamp
|
| 494 |
* @return calculated timestamp
|
| 495 |
*/
|
| 496 |
|
| 497 |
function _estimate_meter_reading($meter_readings, $date)
|
| 498 |
{
|
| 499 |
$date_s = _format_date_only($date);
|
| 500 |
$i = 0 ;
|
| 501 |
foreach ($meter_readings as $mr) // assume meter readings are sorted by date
|
| 502 |
{
|
| 503 |
if ($mr->enddate == $date)
|
| 504 |
return $mr ; // got an actual reading for that date
|
| 505 |
|
| 506 |
if ($mr->enddate > $date)
|
| 507 |
break ;
|
| 508 |
|
| 509 |
$i++; // push pointer above all the earlier dates.
|
| 510 |
}
|
| 511 |
$i-- ;
|
| 512 |
// now, $i is the index of the meter reading just below
|
| 513 |
// where we want to calculate our estimate (and if there isn't
|
| 514 |
// one -1.
|
| 515 |
|
| 516 |
// special case, we want an estimate before the first meter reading
|
| 517 |
// shift up and use the first two meter readings
|
| 518 |
|
| 519 |
|
| 520 |
if ($i <= 0)
|
| 521 |
$i++ ;
|
| 522 |
|
| 523 |
// special case, we want to estimate after the last meter reading
|
| 524 |
// shift down and use the last two meter readings
|
| 525 |
|
| 526 |
if ($i >= count($meter_readings) - 1)
|
| 527 |
$i-- ;
|
| 528 |
|
| 529 |
// now pick out the meter readings we are going to use
|
| 530 |
$meter_readings_array = array_values($meter_readings);
|
| 531 |
$below = $above = null ;
|
| 532 |
if ($i >= 0 && $i <= count($meter_readings) - 2)
|
| 533 |
{
|
| 534 |
$below = $meter_readings_array[$i];
|
| 535 |
$above = $meter_readings_array[$i+1];
|
| 536 |
}
|
| 537 |
else
|
| 538 |
return null ;
|
| 539 |
|
| 540 |
$stamp = clone($above); // assume source code and adjustment of later stamp
|
| 541 |
$stamp->estimate = true ;
|
| 542 |
$stamp->enddate = $date ;
|
| 543 |
|
| 544 |
// use linear interpolation to estimate value between the dates that we have
|
| 545 |
|
| 546 |
$adjust = ($date - $below->enddate) / ($above->enddate - $below->enddate);
|
| 547 |
|
| 548 |
// If the estimate required falls between 2 meter readings then 0.0 < $adjust < 1.0.
|
| 549 |
|
| 550 |
// We try to provide estimates before the first or after the last meter reading,
|
| 551 |
// however this is a big assumption as the person may have moved.
|
| 552 |
|
| 553 |
// the margin of 1/2 below is equivalent to creating estimates up to month before
|
| 554 |
// the first meter reading or 1 month after the last meter reading, if the
|
| 555 |
// meter readings are 2 months apart.
|
| 556 |
|
| 557 |
$margin = 1/2 ;
|
| 558 |
|
| 559 |
if ( $adjust < -$margin // we are estimating before the first meter reading
|
| 560 |
|| $adjust > 1+ $margin) // we are estimating after the last meter reading
|
| 561 |
{
|
| 562 |
return null ;
|
| 563 |
}
|
| 564 |
|
| 565 |
|
| 566 |
$stamp->reading = $below->reading + $adjust * ($above->reading - $below->reading) ;
|
| 567 |
|
| 568 |
$stamp->notes = "Using estimated reading of "
|
| 569 |
. _format_float($stamp->reading) . " on " . _format_date_only($stamp->enddate)
|
| 570 |
. " interpolated from "
|
| 571 |
. _format_float($below->reading) . " read on " . _format_date_only($below->enddate)
|
| 572 |
. " and "
|
| 573 |
. _format_float($above->reading) . " read on " . _format_date_only($above->enddate)
|
| 574 |
. ". ";
|
| 575 |
|
| 576 |
return $stamp ;
|
| 577 |
}
|
| 578 |
|
| 579 |
|
| 580 |
/**
|
| 581 |
* daily event
|
| 582 |
* The difference betweeen a scope_0 and scope_2 event is that
|
| 583 |
* the scope_2 event repeats the same level of emissions every
|
| 584 |
* year and the emissions are not dispersed between start and
|
| 585 |
* date.
|
| 586 |
*/
|
| 587 |
|
| 588 |
|
| 589 |
function _scope_2(&$emissions, &$select, $start, $stop)
|
| 590 |
{
|
| 591 |
$emission = new Emission($select);
|
| 592 |
|
| 593 |
_duplicate_check($emission->select);
|
| 594 |
|
| 595 |
$stamp = $emission->select[0] ;
|
| 596 |
|
| 597 |
if (empty($stamp->startdate))
|
| 598 |
{
|
| 599 |
if ($stamp->enddate > $start && $stamp->enddate <= $stop)
|
| 600 |
$begin = $start ;
|
| 601 |
else
|
| 602 |
return false;
|
| 603 |
}
|
| 604 |
else
|
| 605 |
{
|
| 606 |
// find out the range of dates when this event occurred
|
| 607 |
// and that fall inside the reporting period that we are
|
| 608 |
// interested in.
|
| 609 |
|
| 610 |
$begin = max($stamp->startdate, $start);
|
| 611 |
}
|
| 612 |
$end = min($stamp->enddate, $stop);
|
| 613 |
|
| 614 |
if ($end <= $begin)
|
| 615 |
return false ;
|
| 616 |
// assuming annual repetition
|
| 617 |
$adjust_for_repetition = ($end - $begin) / (60*60*24*365);
|
| 618 |
_invoke_calculate($emission, $stamp, $adjust_for_repetition);
|
| 619 |
$emission->valuedate = $end;
|
| 620 |
|
| 621 |
$emissions[] = $emission; // process only 1 stamp, ignore duplicates
|
| 622 |
}
|
| 623 |
|
| 624 |
/**
|
| 625 |
* We had expected to be passed an array of just 1 carbon_stamp.
|
| 626 |
* Log an error if there is more than one.
|
| 627 |
* @param $ar an array of carbon stamps
|
| 628 |
*/
|
| 629 |
|
| 630 |
|
| 631 |
function _duplicate_check($ar)
|
| 632 |
{
|
| 633 |
if (count($ar) > 1)
|
| 634 |
{
|
| 635 |
$text = "These identical carbon stamps treated as one:";
|
| 636 |
foreach ($ar as $stamp)
|
| 637 |
{
|
| 638 |
$text .= " ".l("node ".$stamp->nid,"/node/".$stamp->nid);
|
| 639 |
}
|
| 640 |
drupal_set_message($text, "warning");
|
| 641 |
}
|
| 642 |
}
|
| 643 |
|
| 644 |
/**
|
| 645 |
*
|
| 646 |
* At the moment the classname is simply mapped to a function name.
|
| 647 |
*
|
| 648 |
* @param $emission structure into which results are place
|
| 649 |
* @param $node a carbon stamp
|
| 650 |
*/
|
| 651 |
|
| 652 |
|
| 653 |
function _invoke_calculate(&$emission, $node, $adjust)
|
| 654 |
{
|
| 655 |
if (!isset($node->sourcecode))
|
| 656 |
{
|
| 657 |
$emission->sourceError = t("Unknown source code: %code", array('%code'=>$node->code)) ;
|
| 658 |
return ;
|
| 659 |
}
|
| 660 |
if ($node->sourcestatus == 0)
|
| 661 |
{
|
| 662 |
$emission->sourceError = t("Source not or no longer published: %code", array('%code'=>$node->code)) ;
|
| 663 |
return ;
|
| 664 |
}
|
| 665 |
|
| 666 |
if (empty($node->sourceclass))
|
| 667 |
{
|
| 668 |
$emission->sourceError = t("Source class not defined: %class", array('%class'=>$node->sourceclass)) ;
|
| 669 |
return ;
|
| 670 |
}
|
| 671 |
|
| 672 |
$fn = '_'.$node->sourceclass ;
|
| 673 |
if (!function_exists($fn))
|
| 674 |
{
|
| 675 |
$emission->sourceError = t("Calculator function not defined: ").$fn ;
|
| 676 |
return ;
|
| 677 |
}
|
| 678 |
$fn($emission, $node, $adjust);
|
| 679 |
}
|
| 680 |
|
| 681 |
/**
|
| 682 |
* The simple class calculates emissions with the formula
|
| 683 |
*
|
| 684 |
* emissions = r*m*a
|
| 685 |
*
|
| 686 |
* where r is the value spcified by the user
|
| 687 |
* m is the multiplier used to convert units of r
|
| 688 |
* a is the adjustment specified by the user
|
| 689 |
* which might be 48/52 to allow for holidays
|
| 690 |
* from a daily commute.
|
| 691 |
* (this can be a string and must be evaluated)
|
| 692 |
*
|
| 693 |
* to the corresponding CO2 value.
|
| 694 |
*
|
| 695 |
* @param $node a carbon stamp
|
| 696 |
*/
|
| 697 |
|
| 698 |
function _simple(&$emission, $node, $adjust)
|
| 699 |
{
|
| 700 |
$emission->units = $node->reading * _evaluate($node->adjustment) * $adjust;
|
| 701 |
$emission->co2 = $emission->units * $node->toco2 ;
|
| 702 |
$emission->kwh0 = $emission->units * $node->tokwh0 ;
|
| 703 |
$emission->kwh1 = $emission->units * $node->tokwh1 ;
|
| 704 |
}
|
| 705 |
|
| 706 |
/**
|
| 707 |
* This formula comes from http://www.chooseclimate.org
|
| 708 |
*
|
| 709 |
|
| 710 |
Fuel per passenger (Data for B-747).
|
| 711 |
|
| 712 |
Fuel = [7840 + 10.1 * (distance-250)] (*2 if return)
|
| 713 |
(7840 kg take off-climb-descent, 10.1 kg/km cruising.
|
| 714 |
Passengers, = 370 * [occupancy] (/1.5 if business)
|
| 715 |
Greenhouse warming:
|
| 716 |
CO2 = fuel * (44/12 * 156/184) (molecular masses)
|
| 717 |
|
| 718 |
Total warming effect of CO2,Ozone (made by NOx), water vapour and
|
| 719 |
contrails is about three times greater than effect of CO2 alone
|
| 720 |
*/
|
| 721 |
|
| 722 |
|
| 723 |
/**
|
| 724 |
* $multiplier should only be used to convert distance from some
|
| 725 |
* arbitrary unit to Km.
|
| 726 |
**/
|
| 727 |
|
| 728 |
function _chooseclimate(&$emission, $node, $adjust)
|
| 729 |
{
|
| 730 |
$model = array(
|
| 731 |
"aircraft" => "B747-400",
|
| 732 |
"fuel_for_takeoff" => 7840,
|
| 733 |
"fuel_cruising_per_km" => 10.1,
|
| 734 |
"climb_distance" => 250,
|
| 735 |
"capacity" => 370,
|
| 736 |
"occupancy" => .80,
|
| 737 |
"fuel_conversion" => (44/12 * 156/184),
|
| 738 |
"radiative_forcing_index" => 2.7
|
| 739 |
);
|
| 740 |
return _flight_calculate($emission, $node, $adjust, $model);
|
| 741 |
}
|
| 742 |
|
| 743 |
/**
|
| 744 |
Climate care model
|
| 745 |
|
| 746 |
This model is based on the information in a report written
|
| 747 |
by the Environmnetal Change Institute for climate care
|
| 748 |
and is provided on the climate care website.
|
| 749 |
http://www.climatecare.org/_media/documents/pdf/Aviation_Emissions_&_Offsets.pdf
|
| 750 |
|
| 751 |
the raw data refrred to on aircraft fuel consumption
|
| 752 |
http://reports.eea.europa.eu/EMEPCORINAIR3/en/B851vs2.4.pdf
|
| 753 |
|
| 754 |
my version of this with average fuel consumption added:
|
| 755 |
http://cvs.drupal.org/viewcvs/ checkout /drupal/contributions/modules/carbon/air_fuel_use.html
|
| 756 |
|
| 757 |
note the climate care assume 100% occupancy and that on long
|
| 758 |
haul flights the 10% of fuel can be be attributed to freight.
|
| 759 |
*/
|
| 760 |
|
| 761 |
function _climatecare(&$emission, $node, $adjust)
|
| 762 |
{
|
| 763 |
$distance_km = $node->reading * $node->toco2 ;
|
| 764 |
|
| 765 |
// switch planes depending on distance
|
| 766 |
|
| 767 |
$model = $distance_km < 3500 ? _climatecareB737() : _climatecareB747();
|
| 768 |
|
| 769 |
return _flight_calculate($emission, $node, $adjust, $model);
|
| 770 |
}
|
| 771 |
|
| 772 |
function _climatecareB747() {
|
| 773 |
return array(
|
| 774 |
"aircraft" => "B747-400",
|
| 775 |
"fuel_for_takeoff" => 3402,
|
| 776 |
"fuel_cruising_per_km" => 10.1, // from average kg/mile in EU table
|
| 777 |
"climb_distance" => 0,
|
| 778 |
"fuel_conversion" => 3.15, // http://www.climatecare.org/_media/documents/pdf/Aviation_Emissions_&_Offsets.pdf
|
| 779 |
"capacity" => 400, // ditto
|
| 780 |
"occupancy" => 1.00, // ditto
|
| 781 |
"radiative_forcing_index" => 2.0 // ditto & http://iacweb.ethz.ch/tradeoff/
|
| 782 |
);
|
| 783 |
}
|
| 784 |
|
| 785 |
function _climatecareB737() {
|
| 786 |
return array(
|
| 787 |
"aircraft" => "B737-400",
|
| 788 |
"fuel_for_takeoff" => 825.4,
|
| 789 |
"fuel_cruising_per_km" => 3.01, // from average kg/mile above
|
| 790 |
"climb_distance" => 0,
|
| 791 |
"fuel_conversion" => 3.15, // http://www.climatecare.org/_media/documents/pdf/Aviation_Emissions_&_Offsets.pdf
|
| 792 |
"capacity" => 180, // ditto
|
| 793 |
"occupancy" => 1.00, // ditto
|
| 794 |
"radiative_forcing_index" => 2.0 // diito & http://iacweb.ethz.ch/tradeoff/
|
| 795 |
);
|
| 796 |
}
|
| 797 |
|
| 798 |
function _climatecareA340() {
|
| 799 |
return array(
|
| 800 |
"aircraft" => "Airbus-A340",
|
| 801 |
"fuel_for_takeoff" => 2019,
|
| 802 |
"fuel_cruising_per_km" => 6.6, // from average kg/mile in EU table
|
| 803 |
"climb_distance" => 0,
|
| 804 |
"fuel_conversion" => 3.15, // http://www.climatecare.org/_media/documents/pdf/Aviation_Emissions_&_Offsets.pdf
|
| 805 |
"capacity" => 295, // ditto
|
| 806 |
"occupancy" => 1.00, // ditto
|
| 807 |
"radiative_forcing_index" => 2.0 // diito & http://iacweb.ethz.ch/tradeoff/
|
| 808 |
);
|
| 809 |
}
|
| 810 |
|
| 811 |
|
| 812 |
/**
|
| 813 |
* the choose climate model is basically a simple
|
| 814 |
* first order polynomial and I have used it for
|
| 815 |
* the climate care model as well.
|
| 816 |
*/
|
| 817 |
|
| 818 |
|
| 819 |
function _flight_calculate(&$emission, $node, $adjust, $model)
|
| 820 |
{
|
| 821 |
$distance = $node->reading * $node->toco2 ;
|
| 822 |
|
| 823 |
$flight_takeoff = $model["fuel_for_takeoff"] ;
|
| 824 |
|
| 825 |
if ($distance > $model["climb_distance"])
|
| 826 |
$flight_cruising += ($model["fuel_cruising_per_km"] * ($distance-$model["climb_distance"])); // Kg
|
| 827 |
|
| 828 |
$fuel = ($flight_takeoff + $flight_cruising)/($model["capacity"] * $model["occupancy"]) ; //.Kg
|
| 829 |
|
| 830 |
$co2 = $fuel * $model["fuel_conversion"]; // convert fuel to CO2
|
| 831 |
|
| 832 |
// the radiative_forcing_index below (expected to be between
|
| 833 |
// 1.8 and around 2.7) includes effect of the non CO2 gasses
|
| 834 |
|
| 835 |
$co2_and_other_gasses = $co2 * $model["radiative_forcing_index"] ;
|
| 836 |
|
| 837 |
$emission->notes = sprintf("Assumptions: aircraft is a %s" .
|
| 838 |
" which uses %s Kg of fuel on takeoff/landing, ".
|
| 839 |
" and %s Kg/Km while cruising; ".
|
| 840 |
" %s%% of %s seats occupied," .
|
| 841 |
" radiative forcing index is %s." .
|
| 842 |
" Calculation: CO2 per passenger is %2d Kg," .
|
| 843 |
" all gasses (expressed as CO2 equivalent) is %2d Kg.",
|
| 844 |
$model["aircraft"], $model["fuel_for_takeoff"], $model["fuel_cruising_per_km"],
|
| 845 |
100 * $model["occupancy"],$model["capacity"], $model["radiative_forcing_index"],
|
| 846 |
$co2, $co2_and_other_gasses);
|
| 847 |
|
| 848 |
$emission->units = $node->reading ; // this was previously set !
|
| 849 |
$emission->co2 = $co2_and_other_gasses * _evaluate($node->adjustment) * $adjust ; // Kg
|
| 850 |
}
|
| 851 |
|
| 852 |
|
| 853 |
/**
|
| 854 |
* Create a printable carbon footprint for a given period (usually a year)
|
| 855 |
* based on on all the available carbon_stamps.
|
| 856 |
*
|
| 857 |
* @param $results a structure containing all results of calculations
|
| 858 |
* @param $json set to true if want table as raw data
|
| 859 |
* @return a string containing a table that shows the carbon footprint.
|
| 860 |
*/
|
| 861 |
|
| 862 |
|
| 863 |
function carbon_calculate_present_results(&$group_result)
|
| 864 |
{
|
| 865 |
$rows = array();
|
| 866 |
$grand_total = 0 ;
|
| 867 |
$numupdates = 0 ;
|
| 868 |
$notes = array();
|
| 869 |
$show_model = false ;
|
| 870 |
|
| 871 |
|
| 872 |
// now go through the results and create a table
|
| 873 |
// containing the results and then add the notes
|
| 874 |
// to the end of the table.
|
| 875 |
|
| 876 |
foreach ($group_result->emissions as $emission)
|
| 877 |
{
|
| 878 |
$result_as_text = null ;
|
| 879 |
$column_notes = "" ;
|
| 880 |
$showRow = false ;
|
| 881 |
|
| 882 |
if (!empty($emission->notes))
|
| 883 |
{
|
| 884 |
$notes[] = $emission->notes;
|
| 885 |
$column_notes = " [".count($notes)."]" ;
|
| 886 |
}
|
| 887 |
|
| 888 |
if (isset($emission->sourceError) || isset($emission->error) || isset($emission->co2))
|
| 889 |
{
|
| 890 |
$showRow = true ;
|
| 891 |
}
|
| 892 |
if ($showRow)
|
| 893 |
{
|
| 894 |
$select = $emission->select ;
|
| 895 |
$daterange = "" ;
|
| 896 |
if ($select[0]->scope == 1)
|
| 897 |
{
|
| 898 |
if ($emission->start->enddate != $emission->end->enddate)
|
| 899 |
$daterange = _format_date_only($emission->start->enddate)." and " ;
|
| 900 |
$daterange .= _format_date_only($emission->end->enddate);
|
| 901 |
}
|
| 902 |
else
|
| 903 |
{
|
| 904 |
if (!empty($select[0]->startdate) && ($select[0]->startdate != $select[0]->enddate))
|
| 905 |
$daterange = _format_date($select[0]->startdate)." to ";
|
| 906 |
$daterange .= _format_date($select[0]->enddate);
|
| 907 |
$emission->end = $select[0] ;
|
| 908 |
}
|
| 909 |
if (isset($emission->sourceError))
|
| 910 |
{
|
| 911 |
$units_col = $emission->sourceError ;
|
| 912 |
}
|
| 913 |
else // ja 2-1-2008 replace select[0] with emission->end
|
| 914 |
{
|
| 915 |
if (isset($emission->units))
|
| 916 |
$units = is_numeric($emission->units) ? _format_float($emission->units) : $emission->units ;
|
| 917 |
else
|
| 918 |
$units = $emission->end->amount ; // is this line used?
|
| 919 |
|
| 920 |
$model = $show_model ? $emission->end->sourcemodel."/" : "" ;
|
| 921 |
$units_col = isset($units) ? $units." ".$emission->end->units." of " : "" ;
|
| 922 |
|
| 923 |
$units_col .= l($model.$emission->end->sourcetitle, 'node/'.$emission->end->sourceid);
|
| 924 |
}
|
| 925 |
$row = array(
|
| 926 |
$daterange,
|
| 927 |
carbon_link_from_title($emission->end,$group_result->account,"")." ".$column_notes,
|
| 928 |
$units_col);
|
| 929 |
if ($group_result->enablekwh0)
|
| 930 |
$row[] = _format_float($emission->kwh0);
|
| 931 |
if ($group_result->enablekwh1)
|
| 932 |
$row[] = _format_float($emission->kwh1);
|
| 933 |
if ($group_result->enableco2)
|
| 934 |
$row[] = empty($emission->error) ? _format_float($emission->co2) : $emission->error;
|
| 935 |
// really need to redirect to original page rather than hard coded page
|
| 936 |
$row[] = carbon_access("update", $group_result->account) ?
|
| 937 |
carbon_format_edit_ops($emission->end, "destination=node/".$group_result->account->nid) : "" ;
|
| 938 |
$rows[] = $row ;
|
| 939 |
$numupdates++ ;
|
| 940 |
}
|
| 941 |
}
|
| 942 |
if (!empty($numupdates))
|
| 943 |
{
|
| 944 |
$out = "";
|
| 945 |
$row = array("total", null, null);
|
| 946 |
|
| 947 |
if ($group_result->enablekwh0)
|
| 948 |
$row[] = _format_float($group_result->grand_kwh0);
|
| 949 |
|
| 950 |
if ($group_result->enablekwh1)
|
| 951 |
$row[] = _format_float($group_result->grand_kwh1);
|
| 952 |
|
| 953 |
if ($group_result->enableco2)
|
| 954 |
$row[] = _format_float($group_result->grand_co2);
|
| 955 |
|
| 956 |
// pass the footprint and see whether we are allowed to update
|
| 957 |
// it rather than asking if we can crate a new stamp as the former
|
| 958 |
// check makes sure we own the existing footprint.
|
| 959 |
$row[] = "" ; // carbon_format_add_new_op($group_result->account, "destination=node/".$group_result->account->nid);
|
| 960 |
$rows[] = $row ;
|
| 961 |
|
| 962 |
$header = array(t("date"),t("title"),t("source"));
|
| 963 |
if ($group_result->enablekwh0)
|
| 964 |
$header[] = variable_get("carbon_energy_units_0", "");
|
| 965 |
if ($group_result->enablekwh1)
|
| 966 |
$header[] = variable_get("carbon_energy_units_1", "");
|
| 967 |
if ($group_result->enableco2)
|
| 968 |
$header[] = variable_get("carbon_units", "Kg of C02") ;
|
| 969 |
$header[] = t("Operations");
|
| 970 |
|
| 971 |
$out .= theme('table', $header, $rows);
|
| 972 |
|
| 973 |
$i = 1 ; foreach ($notes as $note)
|
| 974 |
{
|
| 975 |
$out .= "<p class='link'>[".$i++."] ".$note."</p>" ;
|
| 976 |
}
|
| 977 |
}
|
| 978 |
return $out ;
|
| 979 |
}
|
| 980 |
|
| 981 |
|
| 982 |
function carbon_calculate_empty_table($footprint)
|
| 983 |
{
|
| 984 |
$row = array(t("There are no carbon stamps attached to this carbon account. <br/>" .
|
| 985 |
"Select <i>Meters</i> or <i>Travel</i> tabs and add new carbon stamps."), null);
|
| 986 |
$out .= "<h1></h1>".theme('table', array("","",""), array($row));
|
| 987 |
return $out ;
|
| 988 |
}
|
| 989 |
|
| 990 |
/**
|
| 991 |
* this routine borrowed from php.net
|
| 992 |
* @param $amount value to print
|
| 993 |
* @return unknown_type
|
| 994 |
*/
|
| 995 |
|
| 996 |
function _format_float($amount)
|
| 997 |
{
|
| 998 |
$precision = 2 ; // number of significant digits
|
| 999 |
$logval = log10(abs($amount));
|
| 1000 |
$decimals = min(0, max(0, $precision - 1 - intval($logval)));
|
| 1001 |
$format = "%." . $decimals . "f";
|
| 1002 |
return sprintf($format, $amount);
|
| 1003 |
}
|
| 1004 |
|
| 1005 |
|
| 1006 |
?>
|