Created
April 12, 2021 13:47
-
-
Save shawn-crigger/b02bc6d3a087dfa088f90c0d341a18d5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Carbon\Carbon; | |
define( 'OPENMAP_API_KEY', '0d86bc33034b99c11c73e7ee8ab0ffa6' ); | |
/** | |
* Attempts to fetch document at $requestAddress | |
* | |
* @param $requestAddress string | |
* @param $i int | |
* @return string | |
**/ | |
function getFeed($requestAddress, $i = 1) { | |
//AFTER 20 TRIES | |
if ($i >= 20) { | |
return "Sorry, our weather feed seems to be down at the moment.<br>Please check back at a later time."; | |
} | |
//GET FEED RECURSIVELY IF FAILS | |
try { | |
$ch = curl_init(); | |
curl_setopt($ch, CURLOPT_HEADER, 0); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
curl_setopt($ch, CURLOPT_URL, $requestAddress); | |
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); | |
curl_setopt($ch, CURLOPT_VERBOSE, 0); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); | |
curl_setopt($ch, CURLOPT_CAINFO, '/var/www/jb-virts/redbird.aero/v1/lib/cacert.pem'); | |
curl_setopt($ch, CURLOPT_USERAGENT,'weather/1.0.0 (Flight Cicle Web Application, flightcircle.com)'); | |
$response = curl_exec($ch); | |
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
curl_close($ch); | |
if ($status == 429) { | |
sleep(20); | |
} | |
if (!$response || $status != 200) { | |
throw new Exception("Sorry, our weather feed seems to be down at the moment.<br>Please check back at a later time."); | |
} | |
return $response; | |
} catch (Exception $e) { | |
$i++; | |
return getFeed($requestAddress, $i); | |
} | |
} | |
// ------------------------------------------------------------------------ | |
/** | |
* Returns text wind direction from | |
* @param integer $dir The dir | |
* @return array | |
*/ | |
function direction($dir) { | |
$directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']; | |
$key = round($dir / 45) % 8; | |
$value = $directions["{$key}"]; | |
return $value; | |
} | |
// ------------------------------------------------------------------------ | |
/** | |
* Takes the values from the API or cache table and formats it into a html table for display. | |
* @param $weather object | |
* @param $tz. string | |
* @return string | |
*/ | |
function format_weather($weather, $tz) { | |
$temp = $weather->temp; | |
$icon = $weather->icon; | |
$rain_type = $weather->rain_type; | |
$rain_percent = $weather->rain_percent; | |
$wind_speed = $weather->wind_speed; | |
$wind_bearing = ''; | |
$date = strtotime($weather->forecast_on); | |
$icon = 'https://cdn.flightcircle.com/images/weather/'.$icon.'-blue.png'; | |
$rain_icon = 'https://cdn.flightcircle.com/images/weather/Umbrella-blue.png'; | |
$wind_icon = 'https://cdn.flightcircle.com/images/weather/no-wind.png'; | |
if (isset($weather->wind_bearing) && strlen($weather->wind_bearing)>0) { | |
$wind_bearing = strtolower($weather->wind_bearing); | |
$wind_icon = 'https://cdn.flightcircle.com/images/weather/wind-'.$wind_bearing.'.png'; | |
} | |
if ($rain_percent>999) { | |
$rain_percent = $rain_percent / 100; | |
} | |
if ($rain_percent!='m') { | |
$rain_percent = $rain_percent.'%'; | |
} else { | |
$rain_percent = '0%'; | |
} | |
if ($wind_speed>0) { | |
$wind_speed = round((float)$wind_speed/1.1507794480235) . ' kts'; | |
$wind = ' | |
<table cellspacing="0" cellpadding="0" border="0" style="margin-top:10px" align="center"> | |
<tr> | |
<td valign="middle" align="right"> | |
<img src="'.$wind_icon.'" width="25px" style="margin-right: 6px;"> | |
</td> | |
<td valign="middle" style="color: #155ea8;font-size: 14px;font-weight:300;line-height: 14px; margin-right: 6px;" class="">'. | |
$wind_speed.' | |
</td> | |
</tr> | |
</table>'. PHP_EOL; | |
} elseif (0 == $wind_speed OR 'm' == $wind_speed) { | |
$wind_speed = 'Winds calm'; | |
$wind = ' | |
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 10px;" align="center"> | |
<tr> | |
<td valign="middle" align="right"> | |
<img src="https://cdn.flightcircle.com/images/weather/no-wind.png" width="25px" style="margin-right: 6px;"> | |
</td> | |
<td valign="middle" style="color: #155ea8;font-size: 14px;font-weight:300;line-height: 14px; margin-right: 6px;" class="">'. | |
$wind_speed.' | |
</td> | |
</tr> | |
</table>'. PHP_EOL; | |
} | |
$rain = ' | |
<table cellspacing="0" cellpadding="0" border="0" style="margin-top:10px" align="center"> | |
<tr> | |
<td valign="middle" align="right"> | |
<img src="'.$rain_icon.'" width="20px" style="margin-right: 5px;"> | |
</td> | |
<td valign="middle" style="color: #155ea8;font-size: 14px;font-weight:300;line-height: 14px; padding-left: 10px;" class="">'. | |
$rain_percent.' | |
</td> | |
</tr> | |
</table>'. PHP_EOL; | |
$date = Carbon::createFromTimestamp($date, $tz); | |
$date = $date->format('l, M j'); | |
$suffix = '°'; | |
$weather = '<a href="'.$weather->web_link.'"> | |
<table cellpadding="0" cellspacing="0" border="0" align="left" width="190" style="text-align: center"> | |
<tr> | |
<td style="padding:0px 15px;padding-top:0;"> | |
<table cellspacing="0" cellpadding="0" border="0" style="margin-top:10px" align="center"> | |
<tr> | |
<td valign="middle" align="right"> | |
<img src="'.$icon.'" width="35px"> | |
</td> | |
<td valign="middle" style="color: #155ea8;font-size: 20px;font-weight:700;line-height: 20px;padding-left:10px;">'. | |
$temp.$suffix.' | |
</td> | |
</tr> | |
</table> | |
'.$rain.' | |
'.$wind.' | |
<table cellspacing="0" cellpadding="0" border="0" align="center"> | |
<tr> | |
<td colspan="2" align="center" style="padding-top: 10px; font-weight: bold;"> | |
<p style="color:#3F4954;font-size:12px; line-height:14px;font-family: arial;margin-top:0px;margin-bottom:20px;padding:0px 15px; ">'. | |
$date . '<br> | |
</p> | |
</td> | |
</tr> | |
</table> | |
</td> | |
</tr> | |
</table></a>'; | |
return $weather; | |
} | |
// ------------------------------------------------------------------------ | |
function is_json($string) { | |
return is_string($string) && is_array(json_decode($string, true)) && (json_last_error() == JSON_ERROR_NONE) ? true : false; | |
} | |
// ------------------------------------------------------------------------ | |
/** | |
* Queries the database for cached results, if results are expired then queries Darksky API and returns HTML code to display weather | |
* @param $fbo_id int. Airport ID to query lat/lng from | |
* @param $tz [string] | |
* @param $time_format [int] | |
* @return string | |
*/ | |
function make_weather($fbo_id=0,$tz='America/New_York',$time_format=0) { | |
$weather = ''; | |
$airport = DB::queryFirstRow('SELECT airports.ID, airports.lat, airports.lon, airports.icao_id | |
FROM `fbos` | |
INNER JOIN `airports` ON fbos.airport_id = airports.ID | |
AND airports.lat IS NOT NULL | |
AND airports.lon IS NOT NULL | |
WHERE fbos.ID = %s',$fbo_id); | |
$lat = $airport['lat']; | |
$lng = $airport['lon']; | |
$aid = $airport['ID']; | |
$airport_code = $airport['icao_id']; | |
if(!isset($airport['lat'])) return ''; | |
$requestAddress = vsprintf('https://api.openweathermap.org/data/2.5/onecall?lat=%s&lon=%s&units=imperial&exclude=minutely,alerts&appid=%s', array($lat, $lng, OPENMAP_API_KEY)); | |
//$web_link = vsprintf('https://openweathermap.org/weathermap?zoom=12&lat=%s&lon=%s', array($lat, $lng)); | |
$web_link = 'https://www.windy.com/?' . $lat . ',' . $lng; | |
if ($airport_code != '') { | |
$web_link = 'https://www.windy.com/' . $airport_code; | |
} | |
$cached = false; | |
$count = 0; | |
$cached = DB::query('SELECT * FROM `weather_nonav` WHERE `airport_id`=%s AND `forecast_on` >= DATE_SUB(NOW(), INTERVAL 6 HOUR) LIMIT 3',$aid); | |
$count = count($cached); | |
if ($count>=3) { | |
foreach ($cached as $cache) { | |
$cache = (object) $cache; | |
$cache->web_link = $web_link; | |
$weather .= format_weather($cache, $tz); | |
} | |
} else { | |
$feed = getFeed($requestAddress); | |
if (!$feed OR !is_json($feed)) return ''; | |
if ('Sorry, our weather feed seems to be down at the moment.<br>Please check back at a later time.' == $feed) return ''; | |
$feed = json_decode($feed); | |
if (!isset($feed->daily)) return ''; | |
for ($i=0; $i < 3; $i++) { | |
unset($rain_time, $rain_percent); | |
if (isset($feed->timezone)) { | |
$tz = $feed->timezone; | |
} | |
$today = $feed->daily[$i]; | |
$date = $today->dt; | |
$icon = $today->weather[0]->id; | |
$high = $today->temp->max; | |
$low = $today->temp->min; | |
$temp = round($today->temp->day); | |
$alt = $today->weather[0]->id; | |
$updated_on = date('Y-m-d H:i:s', $date); | |
$server_tz = 'America/New_York'; // server timestamp for created at | |
$timestamp = time(); | |
$created_at = Carbon::createFromTimestamp($timestamp, $server_tz); | |
$created_at = $created_at->format('Y-m-d H:i:s'); | |
$dt = Carbon::createFromTimestamp($date, $server_tz); | |
$date = $dt->format('Y-m-d H:i:s'); | |
$rain_percent = 'm'; | |
$rain_type = 'm'; | |
$rain_percent = $today->pop; | |
$rain_percent = round((float) $rain_percent*100); //. '%'; | |
if ($i == 0 && isset($today->snow) OR isset($today->rain)) { | |
$rain_type = $today->weather[0]->description; | |
$hours = array(); | |
foreach ($feed->hourly as $key => $hourly) { | |
if ($i == 0 && $key < 24) { | |
$hours[] = $hourly; | |
} | |
} | |
foreach ($hours as $hourly) { | |
$match = isset($today->snow) ? 'snow' : 'rain'; | |
if (strpos($hourly->weather[0]->description, $match) === FALSE) continue; | |
$rain_ts = $hourly->dt; | |
$rain_time = Carbon::createFromTimestamp($rain_ts, $tz); | |
$rain_time = $rain_time->format('Y-m-d H:i:s'); | |
break; | |
} | |
unset($hours); | |
} | |
$wind_speed = 'm'; | |
if (isset($today->wind_speed)) { | |
$wind_speed = $today->wind_speed; | |
} | |
$wind_bearing = 'm'; | |
if (isset($today->wind_deg)) { | |
$wind_bearing = $today->wind_deg; | |
$wind_bearing = direction($wind_bearing); | |
} | |
if (isset($rain_percent) && isset($rain_time) && isset($rain_type)) { | |
if ($rain_percent > 30) { | |
$rain_text = "It looks like a {$rain_percent}% chance of {$rain_type} at {$rain_time}<br>"; | |
} | |
} | |
$icon = (int) $icon; | |
if ($icon >= 200 && $icon < 600) { | |
$icon = 'Cloud-Rain'; | |
} elseif ($icon >= 600 && $icon < 623) { | |
$icon = 'Cloud-Snow'; | |
if ($icon >= 611 && $icon <= 613) { | |
$icon = 'Cloud-Snow-Alt'; | |
} | |
} elseif ($icon >= 700 && $icon < 800) { | |
$icon = 'Cloud-Snow'; | |
if ($icon == 741) { | |
$icon = 'Cloud-Fog'; | |
} | |
} elseif ($icon >= 801 && $icon <= 802) { | |
$icon = 'Cloud'; | |
} elseif ($icon >= 803 && $icon <= 804) { | |
$icon = 'Cloud-Sun'; | |
if ($today->weather[0]->icon == '04n') { | |
$icon = 'Cloud-Moon'; | |
} | |
} elseif ($icon == 800) { | |
$icon = 'Sun'; | |
if ($today->weather[0]->icon == '01n') { | |
$icon = 'Moon'; | |
} | |
} | |
$db_update = array( | |
'airport_id' => $aid, | |
'temp' => $temp, | |
'icon' => $icon, | |
'rain_type' => $rain_type, | |
'rain_percent' => $rain_percent, | |
'wind_bearing' => $wind_bearing, | |
'wind_speed' => $wind_speed, | |
'forecast_on' => $updated_on, | |
'created_at' => $created_at, | |
); | |
DB::insert('weather_nonav',$db_update); | |
$db_update = (object) $db_update; | |
$db_update->icon = $icon; | |
$db_update->web_link = $web_link; | |
$weather .= format_weather($db_update, $tz); | |
} | |
} | |
if (''!=$weather) { | |
$weather = '<p style="color: #222;">Weather forecast near '.$airport_code.'...</p>' . $weather; | |
} | |
return $weather; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This file checks if the FBO is at an Airport that already has pulled the API information and either fetches the weather information or pulls new information, afterward it builds a string that is used in an HTML email to provide pilots with a 3-day weather forecast for the day before, the day of and the day after their scheduled flight time.
It was originally built to use DarkSky's weather API but they shut down which happens a lot with weather API's it seems, and I had to quickly modify the script so that it would work with the pre-existing icons and such. You'll notice it changes the wind direction from a number to a more human-readable N/E/S/W.
Another thing I thought was kinda neat was changing the Wind Speed from Miles per Hour to Knots Per Hour