<?php
// aqigraph.php
// russell young (airZquality (at) youngZ-0.com, remove the "Z"s)
// This code is open source, feel free to use it or adapt it. Please leave
// the attribution in it, and if anyone does use it please drop me a line to 
// let me know.
//
// This code gets pollution information from an existing database and 
// displays it in the form of bar graphs using http
// 
// put login information out of public area
 
//define('LOCAL', 1);
 
if (!defined('LOCAL'))
    define('LOCAL', 0);
 
if (!LOCAL) 
    include '../../airquality/sqlinfo.php';
 
define('WIDTH', 1100);
date_default_timezone_set('Asia/Chongqing');
 
// define levels and colors
 
$levels = array(
    array(0,    'no reading',    '#ffffff'),
    array(50,    'Good',        '#00e400'),
    array(100,    'Moderate',     '#ffff00'),
    array(150,    'Heavy',    '#ff7e00'),
    array(200,    'Unhealthy',     '#ff0000'),
    array(300,    'Very unhealthy','#7e0023'),
    array(500,    'Hazardous',     '#888888'),
//    array(500,    'Hazardous',     '#99004c'),
    array(9999,    '<a href="http://www.theguardian.com/environment/blog/2010/nov/19/crazy-bad-beijing-air-pollution">Crazy bad</a>',    '#000000')
);
 
$cities = array('Beijing', 'Chengdu', 'Guangzhou', 'Shanghai', 'Shenyang');
 
$china = array(0, 35, 75, 115, 150, 250);
 
// do a linear conversion to change to Chinese scale
function chinaAQI($conc) {
    global $levels;
    global $china;
    for ($i = 0; $conc >= $china[$i]; $i++);
    $i--;
    $ratio = ($conc - $china[$i])/($china[$i + 1] - $china[$i]);
    return floor(0.5 + ($levels[$i][0] + ($levels[$i + 1][0] - $levels[$i][0])*$ratio));
}        
 
function level($value) {
    global $levels;
    $end = sizeof($levels);
    for ($i = 0; $i < $end - 1; $i++) {
        if ($value < $levels[$i][0])
            return $i;
    }
    return $i;
}
 
// see if anyone but me ever uses this
if (empty($_GET['me'])) {
    $time = date('Y-m-d H:i');
    $hostname = gethostbyaddr($_SERVER['REMOTE_ADDR']);
    $args = array();
    foreach ($_GET as $key => $value) 
        $args[] = "$key=$value";
    $args = implode('&', $args);
    file_put_contents('airquality.log', sprintf("%-19s %-25s $args\n", $time, $hostname), FILE_APPEND);
}
 
// keep this around
$sql = '';
function query($query) {
    global $sql;
    if (!$sql) {
        $sql = new mysqli(DBHOST, DBUSER, DBPASSWORD, DB, DBSOCKET);
        if ($sql->connect_error)
            return 'SQL connection error ' . $sql->connect_errno . ': ' . $sql->connect_error;
    }
    return $sql->query($query);
}
 
// use this for development on the local machine
if (LOCAL) {
    function getData($city) {
        $data = array();
        $lines = file('/tmp/data');
        $dummy = $date = $hour = $avg = '';
        foreach ($lines as $line) {
            sscanf($line, "%d %s %d:00 %d %d", $dummy, $date, $hour, $avg, $conc);
            $data[] = array($date, $hour, $avg, $conc);
        }
        return $data;
    }
}
else {
 
// gets the data from sql
    function getData($city) {
        $unit = (isset($_GET['unit'])) ? $_GET['unit'] : 1;
        $number = (isset($_GET['number'])) ? $_GET['number'] : 1;
        
        list($year, $month, $day, $hour) = explode('-', date('Y-m-d-H'));
        if (!empty($_GET['enddate'])) {
            if (isset($_GET['year'])) $year = $_GET['year'];
            if (isset($_GET['month'])) $month = $_GET['month'];
            if (isset($_GET['day'])) $day = $_GET['day'];
            if (isset($_GET['hour'])) $hour = $_GET['hour'];
        }
        $fromTime = $toTime = mktime($hour, 0, 0, $month, $day, $year);
        $to = "$year-$month-$day";
        switch ($unit) {
        case 0: $fromTime -= $number*3600; break;
        case 1: $fromTime -= $number*3600*24; break;
        case 2: $fromTime -= $number*3600*24*7; break;
        case 3: $fromTime = mktime($hour, 0, 0, $month - $number, $day, $year); break;
        case 4: $fromTime = mktime($hour, 0, 0, $month, $day, $year - $number); break;
        default: return 'Request error';
        }
        
        list($from, $fromH) = explode(' ', date('Y-m-d H', $fromTime));
        $query = "SELECT * FROM airQuality WHERE city = $city AND ((day = '$from' AND hour >= $fromH) OR ('$from' < day AND day < '$to') OR ('$to' = day AND hour <= $hour)) order by day, hour";
        $result = query($query);
        if (!$result) return false;
        $data = array();
        while ($line = $result->fetch_array(MYSQLI_NUM))
            $data[] = $line;
        $result->free();
        if (!$data)
            return "$from $fromH:00+$to $hour:00";
        return $data;
    }
}
 
function dump_data($city) {
        $query = "SELECT * FROM airQuality WHERE city = $city order by day, hour into outfile '$city.csv' fields terminated by ','enclosed by '\"\' lines terminated by '\\n'";
        $result = query($query);
        echo "RESULT IS " . $result;
}
 
// parses out the data from the raw stats
//  - fill in 0s for missing data
//  - collect statistics
function parseStats($readings) {
    global $levels;
    $cn = !empty($_GET['cn']);
    $total = $expected = $max = 0;
    $min = 1000;
    $data = array();
    $breakdown = array_fill(0, sizeof($levels), 0);
    foreach ($readings as $reading) {
        list($date, $hour, $avg, $conc) = $reading;
        if ($cn && ($conc < 150))
            $avg = chinaAQI($conc);
        list($year, $month, $day) = explode('-', $date);
        $time = mktime($hour, 0, 0, $month, $day, $year);
        if (!$expected) 
            $expected = $time;
        while ($expected < $time) {
            $d = date('Y-m-d', $expected);
            $h = date('H', $expected);
            $data[] = array($d, (int) $h, 0, $levels[0][2], '');
            $breakdown[0]++;
            $expected += 3600;
        }
        $level = level($avg);
        $data[] = array($date, $hour, $avg, $levels[$level][2], $conc);
        $breakdown[$level]++;
        if ($avg > $max) $max = $avg;
        if ($avg < $min) $min = $avg;
        $total += $avg;
        $expected += 3600;
    }
    if (!sizeof($data)) 
        return array('No readings in the given period', '');
    $stats = array('breakdown' => $breakdown,
                   'average' => floor((100*$total)/(sizeof($data) - $breakdown[0]))/100,
                   'max' => $max,
                   'min' => $min);
    return array($data, $stats);
}
 
// build the input form
function makeInput($city) {
    $us = $cn = $now = $at = '';
    $number = (isset($_GET['number'])) ? $_GET['number'] : 1;
    $unit = (isset($_GET['unit'])) ? $_GET['unit'] : 1;
    list($year, $month, $day, $hour) = explode('-', date('Y-m-d-H'));
    $selectedCity = array($city => 'selected="selected"');
    if (isset($_GET['year'])) $year = $_GET['year'];
    if (isset($_GET['month'])) $month = $_GET['month'];
    if (isset($_GET['day'])) $day = $_GET['day'];
    if (isset($_GET['hour'])) $hour = $_GET['hour'];
    if (empty($_GET['cn'])) $us = ' checked="checked"';
    else $cn = ' checked="checked"';
    if (empty($_GET['enddate'])) $now = ' checked="checked"';
    else $at = ' checked="checked"';
    $me = (empty($_GET['me'])) ? '' : '<input type="hidden" name="me" value="1">';
    $auto = (isset($_GET['auto'])) ? ' checked="checked"' : '';
    echo "<form action='index.php' method='GET'>
Get results for <select name='number'>\n";
    for ($i = 1; $i < 7; $i++) {
        $selected = ($i == $number) ? ' selected="selected"' : '';
        echo "<option value='$i'$selected>$i</option>\n";
    }
    $units = array('hours', 'days', 'weeks', 'months', 'years');
    $days = $weeks = $months = $hours = $years = '';
    ${$units[$unit]} = ' selected="selected"';
    
    echo "</select> <select name='unit'>
<option value='0' $hours>hours</option>
<option value='1' $days>days</option>
<option value='2' $weeks>weeks</option>
<option value='3' $months>months</option>
<option value='4' $years>years</option></select>
ending: <input type='radio' name='enddate' value=''$now onclick='enable(false);'>Now
<input type='radio' name='enddate' value='1'$at onclick='enable(true);'>At:<span id='enddate'>
<select name='year' class='date_fld'>\n";
    for ($i = 2008; $i <= date('Y'); $i++) {
        $selected = ($i == $year) ? ' selected="selected"' : '';
        echo "<option value='$i'$selected>$i</option>\n";
    }
    echo "</select>-<select name='month' class='date_fld'>\n";
    for ($i = 1; $i <= 12; $i++) {
        $selected = ($i == $month) ? ' selected="selected"' : '';
        echo "<option value='$i'$selected>$i</option>\n";
    }
    echo "</select>-<select name='day' class='date_fld'>\n";
    for ($i = 1; $i <= 31; $i++) {
        $selected = ($i == $day) ? ' selected="selected"' : '';
        echo "<option value='$i'$selected>$i</option>\n";
    }
    echo "</select> at <select name='hour' class='date_fld'>\n";
    for ($i = 0; $i < 24; $i++) {
        $selected = ($i == $hour) ? ' selected="selected"' : '';
        echo sprintf("<option value='$i'$selected>%02d</option>\n", $i);
    }
    echo "</select>:00</span></td></tr></table>
    <div style='position: absolute; left: 700; top: 10; z-index: 10'><b>New! Beijing smog forecast from <a href='http://banshirne.com'>banshirne.com</a></b><br>";
    readfile('next-good.html');
        //<iframe src='http://goo.gl/Bimr30' frameborder='0' height='300px' width='250px'></iframe>
    echo "<p>More complete forecast information can be found at <a href='http://aqicn.org/forecast/beijing/'>aqicn.org</a>
    <p><b>Android App!</b> My (un)employment situation has given me both time to program my own stuff and an impetus to work on new things to enhance my experience. To learn about Android programming I made an app for displaying the most recent readings on an Android phone. I am not going to release it officially, but will make the debug version available <a href='aqiapp.apk'>here</a> if anyone cares to download it. You can also <a href='aqiapp.html'>preview it online</a>.
    </div>
<br />City: <select name='city'>
<option value='0' ${selectedCity[0]}>Beijing</option>
<option value='1' ${selectedCity[1]}>Chengdu</option>
<option value='2' ${selectedCity[2]}>Guangzhou</option>
<option value='3' ${selectedCity[3]}>Shanghai</option>
<option value='4' ${selectedCity[4]}>Shenyang</option>
</select><br/>
Scale: US<input type='radio' name='cn' value='0' $us> 
             China<input type='radio' value='1' name='cn' $cn> <i><a href='page2.html'>What does this mean?</a></i>
<br />Automatic refresh: <input type='checkbox' name='auto' $auto>
<p /><button name='action' value='0' type='submit'>Hourly graph</button>
<button name='action' value='1' type='submit'>Daily summary graph</button>
<button name='action' value='2' type='submit'>Data only</button>
<br>
Data for previous years can be accessed in csv form from <a href='year.php'>the summary pages</a>
$me</form>
<script type='text/javascript'>enable($at);</script>";
}
 
// draws the bar graph for the chosen period
function showGraph($data, $stats) {
    $dw = floor(WIDTH/sizeof($data));
    $from = sprintf('%s %02d:00', $data[0][0], $data[0][1]);
    $total = sizeof($data);
    $to = sprintf('%s %02d:00', $data[$total - 1][0], $data[$total - 1][1]);
    echo "<div class='graph_bg'>\n";
    summary($data, $stats);
    $left = 0;
    for ($i = sizeof($data); $i--; ) {
        $entry = $data[$i];
//    foreach ($data as $entry) {
        $invisible = ($entry[1]) ? 'style="visibility: hidden;"' : '';
        $val = ($entry[2]) ? $entry[2] : '(no data)';
        $hr = sprintf("%02d", $entry[1]);
        echo "<span $invisible>$entry[0] </span>$hr:00 <div class='gr2' title='$entry[0] $entry[1]:00 $entry[2] ($entry[4])' style='border-left: ${entry[2]}px solid $entry[3]'>$val</div><br />\n";
        $left += $dw;
    }
    echo "</div>";
}
 
function oneDay($data, $city, $ptr, $left) {
    $today = $data[$ptr][0];
    $min = 10000;
    $x = explode('-', $today);
    $day = $x[2];
    for ($readings = $total = $max = $maxAt = $minAt = 0; ($ptr < sizeof($data)) && ($data[$ptr][0] == $today); $ptr++) {
        $days[] = $x[1];
        $level = $data[$ptr][2];
        if ($level > 0) {
            if ($level > $max) list($max, $maxAt) = array($level, $data[$ptr][1]);
            if ($level < $min) list($min, $minAt) = array($level, $data[$ptr][1]);
            $total += $level;
            $readings++;
        }
    }
    if (!$readings)
        $min = $avg = 0;
    else 
        $avg = $total/$readings;
    $d = $x[2] +1;
    $me = (empty($_GET['me'])) ? '' : '&me=1';
    echo "<a href='index.php?city=$city&number=1&unit=1&enddate=1&year=$x[0]&month=$x[1]&day=$d&hour=0$me'>";
    echo sprintf("<li class='l%s' title='%s maximum (%d:00) %s' style='height: %dpx; left: %dpx;'>%d</li>\n",
                 level($max), $today, $maxAt, $max, $max, $left, $max);
    echo sprintf("   <li class='l%s' title='%s average %.2f' style='height: %dpx; left: %dpx;'>%d</li>\n",
                 level($avg), $today, $avg, floor($avg), $left, $avg);
    echo sprintf("   <li class='l%s' title='%s minimum (%d:00) %s' style='height: %dpx; left: %dpx;'>%d</li>\n",
                 level($min), $today, $minAt, $min, floor($min), $left, $min);
    echo "   <li class='l0' style='height: 0px; left: ${left}px;'>$day</li></a>\n";
    return $ptr;
}
 
function dailyGraph($data, $city, $stats) {
    $dw = floor(WIDTH/sizeof($data));
    $from = sprintf('%s %02d:00', $data[0][0], $data[0][1]);
    $total = sizeof($data);
    $to = sprintf('%s %02d:00', $data[$total - 1][0], $data[$total - 1][1]);
    echo '<div class="graph_bg">';
    summary($data, $stats);
    echo "<ul class='verticalBarGraph'>\n";
    for ($left = $i = 0; $i < sizeof($data); $left += 37) 
        $i = oneDay($data, $city, $i, $left);
    echo '</ul></div><p />Click on the bar for any day to see its hourly breakdown';
}
 
// makes the summary graph
function summary($data, $stats) {
    $left = ($stats['max']< 450) ? 600 : $stats['max'] + 200;
    echo "<div class='summary' style='left: $left'>\nSummary<br>";
    global $levels;
    $i = 0;
    $readings = sizeof($data) - $stats['breakdown'][0];
    $max = 0;
    foreach ($stats['breakdown'] as $count) {
        if ($count > $max) $max = $count;
    }
    $scale = ($max > 50) ? 1 : 2;
    foreach ($levels as $level) {
        $s = $stats['breakdown'][$i];
        $pct = 100*$s/sizeof($data);
        $summary = sprintf("%.2f%%&nbsp;(%d)", $pct, $stats['breakdown'][$i]);
        $pixels = floor($scale*$pct);
        echo "<div class='sum_left'>&lt; $level[0] ($level[1])</div><div class='sum_right' style='border-left: ${pixels}px solid $level[2]'>$summary</div><br />\n";
        $i++;
    }
    echo "</div>";
 
}
 
function data($data, $stats) {
    echo '<table border="1"><tr><th>Reading</th><th>Time</th><th>PM2.5 level</th><th>Concentration</th></tr>';
    $i = 0;
    foreach ($data as $datum) {
        $d = ($datum[2]) ? $datum[2] : '(no data)';
        $i++;
        echo "<tr><td>$i</td><td>$datum[0] $datum[1]:00</td><td>$d</td><td>$datum[4]</td></tr>\n";
    }
    echo '</table>';
}
 
// execution starts here
$start = $finish = '???';
$city = (isset($_GET['city'])) ? $_GET['city'] : 0;
$lines = getData($city);
if (is_string($lines)) {
    list($start, $finish) = explode('+', $lines);
    $msg = "No data found from $start to $finish";
}
else if (!$lines)
    $msg = "ERROR GETTING DATA";
else {
    list($data, $stats) = parseStats($lines);
    if (is_string($data)) 
        $msg = $data;
    else {
        $msg = '';
        $i = sizeof($data) - 1;
        $start = sprintf('%s %02d:00', $data[0][0], $data[0][1]);
        $finish = sprintf('%s %02d:00', $data[$i][0], $data[$i][1]);
    }
}
$mainWidth = WIDTH;
if ($stats['max'] > 450)
    $mainWidth += ($stats['max'] - 450);
$lc_city = strtolower($cities[$city]);
echo "<html>
  <head>
    <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1' />
    <link rel='shortcut icon' href='favicon.ico' type='image/x-icon'>
        <link rel='shortcut icon' href='animated.gif' type='image/x-icon'>
    <title>China PM2.5 report: ${cities[$city]} from $start to $finish</title>
    <meta name='description' content='' />
    <style>
        .wrap {
            float: left;
            margin: 10px;
        }
    .verticalBarGraph {
        border-bottom: 1px solid #FFF;
        height: 600px;
        margin: 0;
        padding: 0;
        position: relative;
        }
 
    .verticalBarGraph li {
        border: 1px solid #555;
        border-bottom: none;
        bottom: 0;
        list-style:none;
        margin: 0;
        padding: 0;
        position: absolute;
        text-align: center;
        width: 36px;
    }
 
 
    .verticalBarGraph li.l0{ 
        background-color: #ffffff;
        }
    .verticalBarGraph li.l1{ 
        border-color: #ffffff; 
        background-color: #00e400;
        }
    .verticalBarGraph li.l2{ 
        border-color: #ffffff; 
        background-color: #ffff00;
        }
    .verticalBarGraph li.l3{ 
        border-color: #ffffff; 
        background-color: #ff7e00;
        }
    .verticalBarGraph li.l4{ 
        border-color: #ffffff; 
        background-color: #ff0000;
        }
    .verticalBarGraph li.l5{ 
        border-color: #ffffff; 
        color: #ffffff;
        background-color: #7e0023;
        }
    .verticalBarGraph li.l6{ 
        border-color: #ffffff; 
        color: #ffffff;
        background-color: #990042;
        }
    .verticalBarGraph li.l7{ 
        border-color: #4E536B; 
        color: #ffffff;
        background-color: #000000;
        }
    td,th {
      text-align: center;
    } 
    .graph_bg {
      margin-left: 16px; 
      width: ${mainWidth}px;
      background: #e7e7e7;
    }
    .gr2 {
      font: 12px/15px Arial; 
      margin-bottom: 1px; 
      padding-left: 8px; 
      display: inline; 
      position: absolute; 
      left: 200;
    }
 
    .sum_right {
      font: 12px/15px Arial;
      margin-bottom: 1px; 
      padding-left: 8px; 
      display: inline; 
      float: right; 
      width: 100px;
      left: 10
    }
    .sum_left {
      display: inline; 
      width: 200px
    }
    .summary {
      margin-left: 15px; 
      width: 450px; 
      background: #d7d7d7;
      float: right;
    }
    </style>
    <script type='text/javascript'>
      function enable(value) {
        value = !value;
        fields = document.getElementsByClassName('date_fld');
        for (var i = 0, len = fields.length; i < len; i++)
          fields[i].disabled = value;
      }
    </script>
 
  </head>
  <body class='bar'><h2>${cities[$city]} Air Quality: pm2.5</h2>
  <b> So how was 2016 for PM2.5 in Beijing?</b>
<br /> Short answer: BIG improvement. The average has dropped from 158 in 2014 to 142
<br /> in 2015 to 133 in 2016! The best 2 months in the last 10 years were February 
<br />and August 2016, and February, with an average of 93.6, was the first month 
<br />to average under 100 since my records started in 2008. 4 of the top 10 months 
<br />in that time came in 2016, and 7 of the top 10 have been in 2015 and 2016. 
<br /><a href='http://young-0.com/airquality/charts.php?years=90&months=0&city=0&month=1&year=2008&charts%5B%5D=4&dir=1&threshold=500&action=Go'>See monthly statistics here</a>  
  <br />
  <b><a href='charts.php'>Generate your own charts with the new chart page</a></b>
  <p />Want to contact me? <a href='mail.php'>Use the new mail form</a><p />";
 
$action = (isset($_GET['action'])) ? $_GET['action'] : 0;
makeInput($city);
if ($msg)
    echo $msg;
else {
    $from = sprintf('%s %02d:00', $data[0][0], $data[0][1]);
    $total = sizeof($data);
    $to = sprintf('%s %02d:00', $data[$total - 1][0], $data[$total - 1][1]);
    echo "<p>&nbsp;<p><h3>${cities[$city]} Results from <b>$from</b> to <b>$to</b></h3>" .
        sprintf('Hours: <b>%d</b> Readings: <b>%d</b> Average: <b>%.2f</b> High: <b>%d</b> Low: <b>%d</b>', 
                sizeof($data), sizeof($data) - $stats['breakdown'][0], $stats['average'], $stats['max'], $stats['min']);
    echo '<p />';
    if ($action == 2)
        data($data, $stats);
    else if ($action == 1)
        dailyGraph($data, $city, $stats);
    else
        showGraph($data, $stats);
}
?>
<hr />Wired had an article about <a href="http://www.wired.com/2015/03/opinion-us-embassy-beijing-tweeted-clear-air/">how the US Embassy AQI tweets helped speed up China's monitoring and addressing its pollution problem</a>. I'd like to think my page also helped. 
<hr />The original purpose of this site was to save historical data, which at the time the US Embassy did not supply. A while ago they started making it available too. The site is <a href='http://www.stateair.net/'>stateair.net</a>. They have some <a href="conditions.html">explicit restrictions and limitations</a> on use of their data.
<p>
<hr />
More information on my site can be found <a href='page2.html'>here</a>, or visit <a href='http://young-0.com'>my main site</a>. That page also has links where you can get history updates or link to a graphics label that can be put on web pages. And, for ubuntu users, there is an app that shows the current API in the top bar.
<p />PM2.5 measurements are commonly given in 2 different units, ug/m3 and AQI. <a href="http://airnow.gov/index.cfm?action=aqibasics.aqi">The difference between them is explained here</a>, and <a href="http://airnow.gov/index.cfm?action=resources.conc_aqi_calc">here is a calculator that will let you convert one to the other</a>. AQI only goes up to 500, <a href="http://www.theguardian.com/environment/blog/2010/nov/19/crazy-bad-beijing-air-pollution">what do those "crazy bad" readings mean?</a><p>
<a href="http://online.thatsmags.com/post/photos-this-is-what-one-years-worth-of-beijing-pollution-looks-like">Here</a> is a story about a guy who has another, more qualitative way to show the data: every day he takes a picture of the same scene outside his window. His blog (in Chinese) is at <a href="http://weibo.com/u/1000481815">http://weibo.com/u/1000481815</a>.<br>
<div><div class="wrap"><h4>Daily 2014 images</h4><image src="oneview.jpg"/></div>
<div class="wrap"><h4>Daily 2015 images</h4><image src="aqi-2015.jpeg" /></div>
</div>
 
 
<p style="clear:left; background:yellow;"/>Does not work well with IE, but does with Chrome, Firefox, and Opera - and I don't care about IE.
</body></html>