Skip to content

Instantly share code, notes, and snippets.

@shawn-crigger
Created April 12, 2021 13:47
Show Gist options
  • Save shawn-crigger/b02bc6d3a087dfa088f90c0d341a18d5 to your computer and use it in GitHub Desktop.
Save shawn-crigger/b02bc6d3a087dfa088f90c0d341a18d5 to your computer and use it in GitHub Desktop.
<?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 = '&deg;';
$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;
}
@shawn-crigger
Copy link
Author

shawn-crigger commented Apr 12, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment