/* ---------- LATEST CODE ---------- */ 'Date', 1 => 'Time', 2 => 'Temperature (°C)', 5 => 'Humidity (%)', 6 => 'Dew Point (°C)', 7 => 'Wind Speed', 8 => 'Wind Direction', 13 => 'Heat Index (°C)', 16 => 'Pressure (hPa)', 17 => 'Rain Today (mm)', 18 => 'Rain Rate (mm/hr)', 27 => 'Indoor Temp (°C)', 28 => 'Indoor Humidity (%)', ]; function fetch_to_cache(string $url, string $name, int $ttl): ?string { $cacheFile = sys_get_temp_dir() . '/uob_weather_' . $name . '.cache'; if (is_file($cacheFile) && (time() - filemtime($cacheFile)) < $ttl) { return $cacheFile; } $tmpFile = $cacheFile . '.' . getmypid() . '.tmp'; $ok = false; if (function_exists('curl_init')) { $fp = fopen($tmpFile, 'w'); if ($fp) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_FILE => $fp, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 120, CURLOPT_FOLLOWLOCATION => true, CURLOPT_USERAGENT => 'UoB-SpaceWeather-Site', ]); $ok = curl_exec($ch) === true && curl_getinfo($ch, CURLINFO_RESPONSE_CODE) === 200; fclose($fp); } } else { $ctx = stream_context_create(['http' => ['timeout' => 120]]); $data = @file_get_contents($url, false, $ctx); $ok = $data !== false && @file_put_contents($tmpFile, $data) !== false; } if ($ok && filesize($tmpFile) > 0) { rename($tmpFile, $cacheFile); touch($cacheFile); } else { @unlink($tmpFile); if (is_file($cacheFile)) { touch($cacheFile); } } return is_file($cacheFile) ? $cacheFile : null; } function row_timestamp(string $date, string $time): int { $d = explode('/', trim($date)); if (count($d) !== 3 || !ctype_digit($d[0]) || !ctype_digit($d[1])) { return 0; } $y = (int) $d[2]; if ($y < 100) { $y += 2000; } // Hours may be single-digit in the source files ("0:30" as well as "00:30"). if (!preg_match('/^(\d{1,2}):(\d{2})\b/', trim($time), $m) || (int) $m[1] > 23 || (int) $m[2] > 59) { return 0; } return ((((int) sprintf('%04d%02d%02d', $y, (int) $d[1], (int) $d[0])) * 100) + (int) $m[1]) * 100 + (int) $m[2]; } function normalize_time(string $time): string { if (preg_match('/^(\d{1,2}):(\d{2})\b/', trim($time), $m)) { return sprintf('%02d:%02d', (int) $m[1], (int) $m[2]); } return trim($time); } function normalize_date(string $date): string { $d = explode('/', trim($date)); if (count($d) === 3 && strlen($d[2]) === 2) { $d[2] = '20' . $d[2]; } return implode('/', $d); } function parse_live_rows(?string $cacheFile): array { if ($cacheFile === null) { return []; } $rows = []; foreach (preg_split('/\r\n|\r|\n/', (string) file_get_contents($cacheFile)) as $line) { $line = trim($line); if ($line === '') { continue; } $rows[] = str_getcsv($line, ',', '"', '\\'); } return $rows; } function dedupe_rows(array $rows): array { $byTs = []; foreach ($rows as $r) { $ts = row_timestamp($r[0] ?? '', $r[1] ?? ''); if ($ts === 0) { continue; } $byTs[$ts] = $r; } ksort($byTs, SORT_NUMERIC); return array_values($byTs); } function csv_field(string $v): string { return (strpbrk($v, ",\"\r\n") !== false) ? '"' . str_replace('"', '""', $v) . '"' : $v; } function project_row(array $fields, array $columns): array { $out = []; foreach (array_keys($columns) as $idx) { $val = trim($fields[$idx] ?? ''); $out[] = ($val === '' || $val === '---') ? '—' : $val; } return $out; } /* ---------- CSV download ---------- */ if (isset($_GET['download']) && $_GET['download'] === 'csv') { set_time_limit(300); $validDate = function (string $s): bool { return preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $s, $m) && checkdate((int) $m[2], (int) $m[3], (int) $m[1]); }; $fromParam = (isset($_GET['from']) && $validDate($_GET['from'])) ? $_GET['from'] : ''; $toParam = (isset($_GET['to']) && $validDate($_GET['to'])) ? $_GET['to'] : ''; $fromTs = $fromParam !== '' ? ((int) str_replace('-', '', $fromParam)) * 10000 : 0; $toTs = $toParam !== '' ? ((int) str_replace('-', '', $toParam)) * 10000 + 2359 : PHP_INT_MAX; $suffix = ($fromParam !== '' || $toParam !== '') ? '_' . ($fromParam !== '' ? $fromParam : DATA_START) . '_to_' . ($toParam !== '' ? $toParam : date('Y-m-d')) : '_full'; $map = []; $lastArchiveTs = 0; $archiveFile = fetch_to_cache(ARCHIVE_URL, 'archive', ARCHIVE_TTL); if ($archiveFile !== null && ($fh = fopen($archiveFile, 'r'))) { while (($line = fgets($fh)) !== false) { $fields = explode("\t", rtrim($line, "\r\n")); $ts = row_timestamp($fields[0] ?? '', $fields[1] ?? ''); if ($ts === 0) { continue; } $lastArchiveTs = max($lastArchiveTs, $ts); if ($ts < $fromTs || $ts > $toTs || isset($map[$ts])) { continue; } $fields[0] = normalize_date($fields[0]); $fields[1] = normalize_time($fields[1]); $fields = array_map('trim', array_pad(array_slice($fields, 0, 38), 38, '')); $map[$ts] = implode(',', array_map('csv_field', $fields)); } fclose($fh); } foreach (parse_live_rows(fetch_to_cache(LIVE_URL, 'live', LIVE_TTL)) as $fields) { $ts = row_timestamp($fields[0] ?? '', $fields[1] ?? ''); if ($ts === 0 || $ts <= $lastArchiveTs || $ts < $fromTs || $ts > $toTs) { continue; } $fields[0] = normalize_date($fields[0]); $fields[1] = normalize_time($fields[1]); $fields = array_map('trim', array_pad(array_slice($fields, 0, 38), 38, '')); $map[$ts] = implode(',', array_map('csv_field', $fields)); } ksort($map, SORT_NUMERIC); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="bradford_weatherstation' . $suffix . '.csv"'); echo implode(',', array_map('csv_field', $CSV_HEADERS)), "\r\n"; foreach ($map as $line) { echo $line, "\r\n"; } exit; } $rows = dedupe_rows(parse_live_rows(fetch_to_cache(LIVE_URL, 'live', LIVE_TTL))); if (isset($_GET['format']) && $_GET['format'] === 'json') { $recent = array_reverse(array_slice($rows, -TABLE_ROWS)); // newest first header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-cache'); echo json_encode([ 'generated' => date('c'), 'headers' => array_values($COLUMNS), 'rows' => array_map(fn ($r) => project_row($r, $COLUMNS), $recent), ]); exit; } /* ---------- HTML page ---------- */ $recent = array_reverse(array_slice($rows, -TABLE_ROWS)); // newest first $latest = $recent[0] ?? []; $headers = array_values($COLUMNS); $today = date('Y-m-d'); function h(?string $s): string { return htmlspecialchars($s ?? '', ENT_QUOTES, 'UTF-8'); } function cell(array $fields, int $idx): string { $v = trim($fields[$idx] ?? ''); return ($v === '' || $v === '---') ? '—' : $v; } ?> Live Weather Station — University of Bradford

University of Bradford — Data

Live measurements from the University of Bradford weather station. The page refreshes automatically every 60 seconds.

Last reading:
Temperature
°C
Heat Index
°C
Humidity
%
Dew Point
°C
Pressure
hPa
Rain Today
mm
"From" date must not be after "To" date.

Leave both dates empty to download the complete dataset (since ).

Showing the latest