Add METAR interface and use local library for decoding

This commit is contained in:
Nabeel Shahzad 2018-04-02 22:35:25 -05:00
parent 3dba586f83
commit 717118cfb4
16 changed files with 425 additions and 161 deletions

13
app/Interfaces/Metar.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace App\Interfaces;
abstract class Metar
{
/**
* Implement the METAR- Return the string
* @param $icao
* @return mixed
*/
abstract public function get($icao);
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Services\Metar;
use App\Interfaces\Metar;
use App\Support\Http;
/**
* Return the raw METAR string from the NOAA Aviation Weather Service
* @package App\Services\Metar
*/
class AviationWeather extends Metar
{
private const URL =
'https://www.aviationweather.gov/adds/dataserver_current/httpparam?'
.'dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3'
.'&mostRecent=true&fields=raw_text&stationString=';
/**
* Implement the METAR- Return the string
* @param $icao
* @return mixed
*/
public function get($icao)
{
$url = static::URL.$icao;
$res = Http::get($url, []);
$xml = simplexml_load_string($res);
return $xml->data->METAR->raw_text;
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Support\Units;
use Illuminate\Contracts\Support\Arrayable;
/**
* Wrap the converter class
* @package App\Support\Units
*/
class Temperature extends \PhpUnitsOfMeasure\PhysicalQuantity\Temperature implements Arrayable
{
/**
* @return string
*/
public function __toString()
{
$unit = strtoupper(setting('units.temperature'));
$value = $this->toUnit($unit);
return (string) round($value, 2);
}
/**
* Return value in native unit as integer
* @return array
*/
public function toNumber()
{
return $this->toArray();
}
/**
* For the HTTP Resource call
*/
public function toObject()
{
return [
'f' => round($this->toUnit('F'), 2),
'c' => round($this->toUnit('C') / 1000, 2),
];
}
/**
* Get the instance as an array.
*/
public function toArray()
{
return $this->__toString();
}
}

View File

@ -1,59 +0,0 @@
<?php
namespace App\Widgets;
use App\Interfaces\Widget;
use App\Support\Http;
use Illuminate\Support\Facades\Cache;
/**
* This is a widget for the 3rd party CheckWX service
* @package App\Widgets
*/
class CheckWx extends Widget
{
protected $config = [
'icao' => null,
];
/**
* Attempt to get the data from the CheckWX API
*/
public function run()
{
if (!config('checkwx.api_key')) {
$data = null;
} else {
// Cache the request so we don't need to repeatedly call out
$cache = config('cache.keys.WEATHER_LOOKUP');
$key = $cache['key'].$this->config['icao'];
$data = Cache::remember($key, $cache['time'], function () {
$url = config('checkwx.url').'/metar/'.$this->config['icao'].'/decoded';
$data = Http::get($url, [
'headers' => [
'X-API-Key' => config('checkwx.api_key'),
'content-type' => 'application/json',
]
]);
$data = json_decode($data);
return $data;
});
if($data->results === 1) {
$data = $data->data[0];
} else {
$data = null;
}
}
return view('widgets.check_wx', [
'config' => $this->config,
'data' => $data,
'unit_alt' => setting('units.altitude'),
'unit_dist' => setting('units.distance'),
'unit_temp' => setting('units.temperature'),
]);
}
}

84
app/Widgets/Weather.php Normal file
View File

@ -0,0 +1,84 @@
<?php
namespace App\Widgets;
use App\Interfaces\Widget;
use App\Support\Http;
use App\Support\Units\Distance;
use App\Support\Units\Temperature;
use Illuminate\Support\Facades\Cache;
use MetarDecoder\MetarDecoder;
use SimpleXMLElement;
/**
* This is a widget for the 3rd party CheckWX service
* @package App\Widgets
*/
class Weather extends Widget
{
protected $config = [
'icao' => null,
];
public const URL = 'https://avwx.rest/api/metar/';
/**
* Determine the category depending on the rules for visibility/ceiling
* https://www.aviationweather.gov/cva/help
* @param $metar
* @return string
*/
protected function determineCategory($metar): string
{
$category = 'VFR';
$visibility = $metar->getVisibility()->getVisibility()->getValue();
$ceiling = $metar->getClouds();
if ($ceiling) {
$ceiling = $ceiling[0]->getBaseHeight()->getValue();
} else {
$ceiling = 1000;
}
if ($visibility < 3 || $ceiling < 1000) {
$category = 'IFR';
}
return $category;
}
/**
* Attempt to get the data from the CheckWX API
*/
public function run()
{
// Cache the request so we don't need to repeatedly call out
$cache = config('cache.keys.WEATHER_LOOKUP');
$key = $cache['key'].$this->config['icao'];
$raw_metar = Cache::remember($key, $cache['time'], function () {
/**
* @var \App\Interfaces\Metar $klass
*/
$klass = config('phpvms.metar');
$metar_class = new $klass;
return $metar_class->get($this->config['icao']);
});
// Run through this parser
$decoder = new MetarDecoder();
$metar = $decoder->parse($raw_metar);
// Determine the flight category that's allowed
// Just check if we need to be under IFR conditions
$category = $this->determineCategory($metar);
return view('widgets.weather', [
'config' => $this->config,
'category' => $category,
'metar' => $metar,
'unit_alt' => setting('units.altitude'),
'unit_dist' => setting('units.distance'),
'unit_temp' => setting('units.temperature'),
]);
}
}

View File

@ -39,7 +39,8 @@
"igaster/laravel-theme": "^2.0", "igaster/laravel-theme": "^2.0",
"anhskohbo/no-captcha": "^3.0", "anhskohbo/no-captcha": "^3.0",
"league/csv": "^9.1", "league/csv": "^9.1",
"spatie/laravel-medialibrary": "^7.0.0" "spatie/laravel-medialibrary": "^7.0.0",
"safran-cassiopee/php-metar-decoder": "^0.7.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~7.0", "phpunit/phpunit": "~7.0",

154
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "6b643310b61c381c247802d56e9c7348", "content-hash": "687d7eb7d6572fdb553c60d001880a91",
"packages": [ "packages": [
{ {
"name": "akaunting/money", "name": "akaunting/money",
@ -1652,6 +1652,57 @@
"homepage": "https://laravelcollective.com", "homepage": "https://laravelcollective.com",
"time": "2018-03-16T16:57:31+00:00" "time": "2018-03-16T16:57:31+00:00"
}, },
{
"name": "laravie/parser",
"version": "v1.2.2",
"source": {
"type": "git",
"url": "https://github.com/laravie/parser.git",
"reference": "e759ba3e5a82151a320ad6e005da0937149bfe35"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravie/parser/zipball/e759ba3e5a82151a320ad6e005da0937149bfe35",
"reference": "e759ba3e5a82151a320ad6e005da0937149bfe35",
"shasum": ""
},
"require": {
"illuminate/support": "^5.1",
"php": ">=5.5.0"
},
"require-dev": {
"mockery/mockery": "~0.9.4 || ~1.0",
"phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
},
"autoload": {
"psr-4": {
"Laravie\\Parser\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mior Muhammad Zaki",
"email": "crynobone@gmail.com",
"homepage": "https://github.com/crynobone"
}
],
"description": "XML Document Parser for PHP",
"keywords": [
"parser",
"xml"
],
"time": "2017-12-25T12:36:44+00:00"
},
{ {
"name": "league/csv", "name": "league/csv",
"version": "9.1.3", "version": "9.1.3",
@ -3187,6 +3238,46 @@
], ],
"time": "2017-03-25T12:08:31+00:00" "time": "2017-03-25T12:08:31+00:00"
}, },
{
"name": "safran-cassiopee/php-metar-decoder",
"version": "v0.7",
"source": {
"type": "git",
"url": "https://github.com/SafranCassiopee/php-metar-decoder.git",
"reference": "ed920c93a6025af9bf6787af7683246c37b1d648"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SafranCassiopee/php-metar-decoder/zipball/ed920c93a6025af9bf6787af7683246c37b1d648",
"reference": "ed920c93a6025af9bf6787af7683246c37b1d648",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "4.3.*"
},
"type": "library",
"autoload": {
"psr-4": {
"MetarDecoder\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0+"
],
"authors": [
{
"name": "Edouard de Labareyre",
"email": "edouard@melix.net",
"role": "Developer"
}
],
"description": "METAR weather observation decoder",
"time": "2016-03-18T21:37:22+00:00"
},
{ {
"name": "santigarcor/laratrust", "name": "santigarcor/laratrust",
"version": "5.0.9", "version": "5.0.9",
@ -5993,6 +6084,67 @@
], ],
"time": "2018-03-21T20:11:24+00:00" "time": "2018-03-21T20:11:24+00:00"
}, },
{
"name": "orchestra/parser",
"version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/orchestral/parser.git",
"reference": "b647cc182a633fe2cca46e2c495f4c6d9766bd80"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/orchestral/parser/zipball/b647cc182a633fe2cca46e2c495f4c6d9766bd80",
"reference": "b647cc182a633fe2cca46e2c495f4c6d9766bd80",
"shasum": ""
},
"require": {
"illuminate/container": "~5.6.0",
"laravie/parser": "~1.2",
"php": ">=7.1"
},
"require-dev": {
"illuminate/support": "~5.6.0",
"mockery/mockery": "^1.0",
"phpunit/phpunit": "~7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.6-dev"
},
"laravel": {
"providers": [
"Orchestra\\Parser\\XmlServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Orchestra\\Parser\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mior Muhammad Zaki",
"email": "crynobone@gmail.com",
"homepage": "https://github.com/crynobone"
}
],
"description": "XML Document Parser for Laravel and PHP",
"keywords": [
"laravel",
"orchestra-platform",
"orchestral",
"parser",
"xml"
],
"time": "2018-02-08T00:56:36+00:00"
},
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
"version": "1.0.1", "version": "1.0.1",

View File

@ -79,6 +79,7 @@ return [
Igaster\LaravelTheme\themeServiceProvider::class, Igaster\LaravelTheme\themeServiceProvider::class,
Nwidart\Modules\LaravelModulesServiceProvider::class, Nwidart\Modules\LaravelModulesServiceProvider::class,
Anhskohbo\NoCaptcha\NoCaptchaServiceProvider::class, Anhskohbo\NoCaptcha\NoCaptchaServiceProvider::class,
Orchestra\Parser\XmlServiceProvider::class,
/* /*
* Application Service Providers... * Application Service Providers...

View File

@ -1,10 +0,0 @@
<?php
/**
* For METAR and TAF features, register for an API key at
* https://www.checkwx.com
*/
return [
'url' => 'https://api.checkwx.com',
'api_key' => false,
];

View File

@ -24,6 +24,12 @@ return [
*/ */
'currency' => 'USD', 'currency' => 'USD',
/**
* Point to the class to use to retrieve the METAR string. If this
* goes inactive at some date, it can be replaced
*/
'metar' => App\Services\Metar\AviationWeather::class,
/** /**
* Your vaCentral API key * Your vaCentral API key
*/ */

View File

@ -37,13 +37,6 @@ return [
'api_key' => '', 'api_key' => '',
], ],
// For METAR features, register for an API key at
// https://www.checkwx.com
'checkwx' => [
'url' => 'https://api.checkwx.com',
'api_key' => false,
],
# #
# Other settings and configuration you might not need to modify # Other settings and configuration you might not need to modify
# #

View File

@ -9,14 +9,14 @@
{{-- Show the weather widget in one column --}} {{-- Show the weather widget in one column --}}
<div class="col-5"> <div class="col-5">
{{ Widget::checkWx([ {{ Widget::Weather([
'icao' => $airport->icao, 'icao' => $airport->icao,
]) }} ]) }}
</div> </div>
{{-- Show the airspace map in the other column --}} {{-- Show the airspace map in the other column --}}
<div class="col-7"> <div class="col-7">
{{ Widget::airspaceMap([ {{ Widget::AirspaceMap([
'width' => '100%', 'width' => '100%',
'height' => '400px', 'height' => '400px',
'lat' => $airport->lat, 'lat' => $airport->lat,
@ -29,7 +29,7 @@
@if($airport->files && Auth::check()) @if($airport->files && Auth::check())
<div class="col-12"> <div class="col-12">
<h3 class="description">Downloads</h3> <h3 class="description">Downloads</h3>
@include('files.table', ['files' => $airport->files]) @include('downloads.table', ['files' => $airport->files])
</div> </div>
@endif @endif
</div> </div>

View File

@ -74,7 +74,7 @@
<div class="card-block"> <div class="card-block">
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
{{ Widget::checkWx(['icao' => $current_airport]) }} {{ Widget::Weather(['icao' => $current_airport]) }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -50,13 +50,13 @@
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<h5>{{$flight->dpt_airport_id}} METAR</h5> <h5>{{$flight->dpt_airport_id}} METAR</h5>
{{ Widget::checkWx([ {{ Widget::Weather([
'icao' => $flight->dpt_airport_id, 'icao' => $flight->dpt_airport_id,
]) }} ]) }}
</div> </div>
<div class="col-6"> <div class="col-6">
<h5>{{$flight->arr_airport_id}} METAR</h5> <h5>{{$flight->arr_airport_id}} METAR</h5>
{{ Widget::checkWx([ {{ Widget::Weather([
'icao' => $flight->arr_airport_id, 'icao' => $flight->arr_airport_id,
]) }} ]) }}
</div> </div>

View File

@ -1,77 +0,0 @@
{{--
If you want to edit this, you can reference the CheckWX API docs:
https://api.checkwx.com/#metar-decoded
--}}
@if(!$data)
<p>METAR/TAF data could not be retrieved</p>
@else
<table class="table">
<tr>
<td>Conditions</td>
<td>
{{$data->flight_category}},
@if($unit_temp === 'f')
{{$data->temperature->fahrenheit}}°F
@else
{{$data->temperature->celsius}}°C
@endif
@if($data->visibility->miles)
, visibility
@if($unit_dist === 'km')
{{intval(str_replace(',', '', $data->visibility->meters)) / 1000}}
@else
{{$data->visibility->miles}}
@endif
@endif
@if($data->humidity_percent)
, {{$data->humidity_percent}}% humidity
@endif
</td>
</tr>
<tr>
<td>Barometer</td>
<td>{{ $data->barometer->hg }}hg/{{ $data->barometer->mb }}mb</td>
</tr>
<tr>
<td>Clouds</td>
<td>
@foreach($data->clouds as $cloud)
<p>
{{$cloud->text}} @
@if($unit_alt === 'ft')
{{$cloud->base_feet_agl}}
@else
{{$cloud->base_meters_agl}}
@endif
{{$unit_alt}}
</p>
@endforeach
</td>
</tr>
<tr>
<td>Wind</td>
<td>
{{$data->wind->speed_kts}}kts @ {{$data->wind->degrees}},
@if(property_exists($data->wind, 'gusts_kts'))
gusts to {{$data->wind->gusts_kts}}
@endif
</td>
</tr>
<tr>
<td>Metar</td>
<td>
<div style="line-height:1.5em;min-height: 3em;">
{{$data->raw_text}}
</div>
</td>
</tr>
<tr>
<td>Updated</td>
<td>{{$data->observed}}</td>
</tr>
</table>
@endif

View File

@ -0,0 +1,78 @@
{{--
If you want to edit this, you can reference the CheckWX API docs:
https://api.checkwx.com/#metar-decoded
--}}
@if(!$metar)
<p>METAR/TAF data could not be retrieved</p>
@else
<table class="table">
<tr>
<td>Conditions</td>
<td>
{{ $category }},&nbsp;
@if($unit_temp === 'c')
{{ $metar->getAirTemperature()->getValue() }}
@else
{{ round(($metar->getAirTemperature()->getValue() * 9/5) + 32, 2) }}
@endif
°{{strtoupper($unit_temp)}}
@if($metar->getVisibility()->getVisibility())
, visibility
@if($unit_dist === 'km')
{{ $metar->getVisibility()->getVisibility()->getConvertedValue('m') / 1000 }}
@else
{{ $metar->getVisibility()->getVisibility()->getValue() }}
@endif
{{$unit_dist}}
@endif
</td>
</tr>
<tr>
<td>Barometer</td>
<td>{{ $metar->getPressure()->getValue() }} Hg
/ {{ round($metar->getPressure()->getValue() * 33.86) }} MB
</td>
</tr>
<tr>
<td>Clouds</td>
<td>
@foreach($metar->getClouds() as $cloud)
<p>
{{$cloud->getAmount()}} @
@if($unit_alt === 'ft')
{{$cloud->getBaseHeight()->getValue()}}
@else
{{$cloud->getBaseHeight()->getConvertedValue('m')}}
@endif
{{ $unit_alt }}
</p>
@endforeach
</td>
</tr>
<tr>
<td>Wind</td>
<td>
{{$metar->getSurfaceWind()->getMeanSpeed()->getConvertedValue('kt')}} kts
@ {{$metar->getSurfaceWind()->getMeanDirection()->getValue()}}°
@if($metar->getSurfaceWind()->getSpeedVariations())
gusts to {{$metar->getSurfaceWind()->getSpeedVariations()->getConvertedValue('kt')}}
@endif
</td>
</tr>
<tr>
<td>METAR</td>
<td>
<div style="line-height:1.5em;min-height: 3em;">
{{ $metar->getRawMetar() }}
</div>
</td>
</tr>
<tr>
<td>Updated</td>
<td>{{$metar->getTime()}}</td>
</tr>
</table>
@endif