All pastes #2074395 Raw Edit

Miscellany

public text v1 · immutable
#2074395 ·published 2011-06-04 01:43 UTC
rendered paste body
<?php

error_reporting(E_ALL);
libxml_use_internal_errors(true);

// ********************* //
// ***** FUNCTIONS ***** //
// ********************* //

class BDWeather {

	// Extract a time-layout structure from the overall document ($X) for a
	// given $Element.  Basically, $Element has a time-layout attribute.  The
	// value of that attribute specifies a time-layout element to use for
	// mapping the values in the element to a series of timestamps.
	public static function getLayout($X, $Element) {
		$layout = $Element->attributes()->{"time-layout"};
		$times = self::getPath($X, "data/time-layout[layout-key='{$layout}']");
		if (count($times) == 0) throw new Exception("Could not find time layout [{$layout}]");
		return current($times);
	}

	// Do the basic rest call to get the weather data we want.
	public static function restCall($zip) {
		$params = array(
			"whichClient" => "NDFDgenMultiZipCode",
			"zipCodeList" => $zip,
			"product" => "time-series",
			"temp" => "temp",
			"wspd" => "wspd",
			"sky" => "sky",
			"precipa_r" => "precipa_r",
		);

		$uri_base = "http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdXMLclient.php";
		$uri = "{$uri_base}?" . http_build_query($params);

		$result = file_get_contents($uri);
		try {
			$X = new SimpleXMLElement($result);
		} catch (Exception $E) {
			// Add the result into the exception and rethrow it, so we can see what the bad XML was later.
			$E->result = $result;
			throw $E;
		}

		return $X;
	}

	// Given a SimpleXMLElement object, and an XPath path, do the search, and deal with possible error conditions.
	public static function getPath($X, $path) {
		if (!($X instanceof SimpleXMLElement)) throw new Exception("X is not a SimpleXMLElement.  It's a [" . gettype($X) . "].");
		$elements = $X->xpath($path);
		if (!is_array($elements)) throw new Exception("xpath call [{$path}] did not return an array.  It returned a [" . gettype($elements) . "].");
		return $elements;
	}

	// Standardized short time format.
	public static function shortTime($time) {
		return date("g", $time) . substr(date("a", $time), 0, 1);
	}

	// Given a zip code, return an array of formatted weather data.
	public static function getWeather($zip) {

		$X = self::restCall($zip);

		$fp = fopen("~/logs/xml_weather", "a");
		fwrite($fp, date("c") . "\n\n");
		fwrite($fp, $X->asXML() . "\n\n");
		fclose($fp);

		// Get the 'temperature' element
		$temps = self::getPath($X, "data/parameters/temperature[@type='hourly']"); 
		if (count($temps) == 0) throw new Exception("Did not find an hourly 'temperature' element.");

		// There will only be one, so fetch it out of the array.
		$X_Temp = current($temps);

		// Now get the temperature values.
		$temp_values = self::getPath($X_Temp, 'value');
		if (count($temp_values) == 0) throw new Exception("Did not find any hourly temperature values.");


		// Get the 'wind-speed' element.
		$winds = self::getPath($X, "data/parameters/wind-speed");
		if (count($winds) == 0) throw new Exception("Did not find a 'wind-speed' element.");

		$X_Wind = current($winds);

		$wind_values = self::getPath($X_Wind, 'value');
		if (count($wind_values) == 0) throw new Exception("Did not find any wind speed values.");


		// Get the 'cloud-amount' element.
		$clouds = self::getPath($X, "data/parameters/cloud-amount");
		if (count($clouds) == 0) throw new Exception("Did not find a 'cloud-amount' element.");

		$X_Cloud = current($clouds);

		$cloud_values = self::getPath($X_Cloud, 'value');
		if (!is_array($temp_values)) throw new Exception("Did not find any cloud cover values.");


		$temps = array();
		$winds = array();
		$clouds = array();
		$hours = array();

		$start = null;

		$max_wind = 0;

		// What I want is to go through the array of dates, and for each one, get the next max/min temp.  They correspond.
		$Times = self::getLayout($X, $X_Temp);
		foreach ($Times->{"start-valid-time"} as $Time) {
			if ($start === null) $start = date("H:i:s", strtotime((string)$Time));
			$hours[] = self::shortTime(strtotime((string)$Time));
			$temps[] = current($temp_values);
			$max_wind = max($max_wind, current($wind_values));
			$winds[] = current($wind_values);
			$clouds[] = current($cloud_values);

			next($temp_values);
			next($wind_values);
			next($cloud_values);

			if (count($temps) == 4) break;
		}

		$out = array();
		$out["hours"]  = "{$zip}: " . implode(" ", $hours) . "\n";
		$out["temps"]  = "F* " . implode(",", $temps) . "\n";
		$out["clouds"] = "Cl: " . implode(",", $clouds) . "\n";
		$out["winds"]  = "Wn: " . implode(",", $winds) . "\n";
		if ($max_wind >= 20) $out["windsev"] = "Very windy\n";
		else if ($max_wind >= 10) $out["windsev"] = "Windy\n";
		else $out["windsev"] = "";

		return $out;
	}

	// Format getWeather parts in the way Alpha likes.
	public static function formatAlpha($parts) {
		$msg = $parts["hours"];
		$msg .= $parts["temps"];
		$msg .= $parts["clouds"];
		$msg .= $parts["winds"];
		if (array_key_exists('weathers', $parts)) $msg .= $parts["weathers"];
		return $msg;
	}

	// Format getWeather parts in the way Bravo likes.
	public static function formatBravo($parts) {
		$msg = $parts["hours"];
		$msg .= $parts["temps"];
		$msg .= $parts["clouds"];
		$msg .= $parts["windsev"];
		if (array_key_exists('weathers', $parts)) $msg .= $parts["weathers"];
		return $msg;
	}

	// Send a text message to Alpha.
	public static function textAlpha($msg) {
		echo("Texting Alpha...\n");
		$gateway_address = "foo@bar.net";
		self::sendSMS($gateway_address, $msg);
	}

	// Send a text message to Bravo.
	public static function textBravo($msg) {
		echo("Texting Bravo...\n");
		$gateway_address = "foo@bar.net";
		self::sendSMS($gateway_address, $msg);
	}

	public static function sendSMS($addr, $msg) {
		$msg = date("c") . "\n" . substr($msg, 0, 140 - 37);
		mail($addr, "", $msg, "From: a@b.c");
	}

}

// ***************** //
// ***** LOGIC ***** //
// ***************** //

date_default_timezone_set('America/New_York');

$dump = false;
$text = false;

if (php_sapi_name() == "cli") {
	if ($argc == 2 && $argv[1] == "dump") $dump = true;
	else if ($argc == 2 && array_search('text', $argv)) $text = true;
} else if (php_sapi_name() == "cgi") {
	if (array_key_exists('dump', $_GET) && $_GET['dump'] == '1') $dump = true;
	else if (array_key_exists('text', $_GET) && $_GET['text'] == '1') $text = true;
} else {
	echo("Only CLI and CGI are allowed.  Exiting.\n");
	exit;
}

if ($dump) {
	try {
		$X = BDWeather::restCall(10101);
	} catch (Exception $E) {
		$X = new SimpleXMLElement("<error>There was an error: [" . $E->getMessage() . "], [" . htmlspecialchars($E->result) . "]</error>");
	}
	header("Content-Type: text/xml");
	echo($X->asXML());
	exit;
} else {

	$sent = false;

	echo("<pre>\n");

	$count = 0;
	$max = 20;
	$sec = 15;
	while ($count < $max) {
		$count++;

		echo(date("c") . "\n\n");

		try {

			$msgs = array();

			if (date("N") >= 6) {
				$parts = BDWeather::getWeather(10101);

				$msg = BDWeather::formatAlpha($parts);
				$msgs["alpha"] = $msg;
				echo($msg);
				var_dump(strlen($msg));
				echo("\n");

				$msg = BDWeather::formatBravo($parts);
				$msgs["bravo"] = $msg;
				echo($msg);
				var_dump(strlen($msg));
				echo("\n");
			} else {
				$parts = BDWeather::getWeather(10102);
				$msg = BDWeather::formatAlpha($parts);
				$msgs["alpha"] = $msg;
				echo($msg . "\n\n");
				var_dump(strlen($msg));
				echo("\n");

				$parts = BDWeather::getWeather(10101);
				$msg = BDWeather::formatBravo($parts);
				$msgs["bravo"] = $msg;
				echo($msg . "\n\n");
				var_dump(strlen($msg));
				echo("\n");
			}

			if ($text) {
				foreach ($msgs as $target => $msg) {
					if ($target == 'alpha') BDWeather::textAlpha($msg);
					if ($target == 'bravo') BDWeather::textBravo($msg);
				}
			}

			$count = $max;
			$sent = true;
		} catch (Exception $E) {
			echo("Something went wrong: [" . $E->getMessage() . "].\nSleeping for {$sec} seconds and trying again (this was attempt {$count} of {$max}).\n");
			echo($E->getTraceAsString());
			flush();
			sleep($sec);
		}

	}

	if (!$sent) {
		echo("Could not get weather report.<br>");
		if ($text) {
			echo("Sending failure text messages.<br>");
			BDWeather::textAlpha("Could not get weather report.  Check the log.");
			BDWeather::textBravo("Sorry, I couldn't generate the weather report today.  Tell Alpha!");
		}
	}
}