Add compositional interface for METAR parser class
This commit is contained in:
@@ -0,0 +1,214 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
use MetarDecoder\MetarDecoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around the METAR decoder. Compensate for
|
||||||
|
* errors and have tests around this functionality
|
||||||
|
* @package App\Support
|
||||||
|
*/
|
||||||
|
class Metar
|
||||||
|
{
|
||||||
|
private $metar,
|
||||||
|
$metar_str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metar constructor.
|
||||||
|
* @param $metar_str
|
||||||
|
*/
|
||||||
|
public function __construct($metar_str)
|
||||||
|
{
|
||||||
|
$decoder = new MetarDecoder();
|
||||||
|
$this->metar = $decoder->parse($metar_str);
|
||||||
|
$this->metar_str = $metar_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if this is VFR or IFR conditions
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCategory(): string
|
||||||
|
{
|
||||||
|
$category = 'VFR';
|
||||||
|
|
||||||
|
$visibility = $this->getVisibility(false);
|
||||||
|
$ceiling = $this->getCeiling(false);
|
||||||
|
|
||||||
|
if ($visibility < 3 || $ceiling < 1000) {
|
||||||
|
$category = 'IFR';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the ceiling
|
||||||
|
* @param bool $convert
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getCeiling($convert = true): int
|
||||||
|
{
|
||||||
|
$ceiling = 1000;
|
||||||
|
$clouds = $this->metar->getClouds();
|
||||||
|
if ($clouds && \count($clouds) > 0) {
|
||||||
|
$ceiling = $clouds[0]->getBaseHeight()->getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$convert) {
|
||||||
|
return $ceiling;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ceiling;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all of the cloud layers
|
||||||
|
*/
|
||||||
|
public function getClouds(): array
|
||||||
|
{
|
||||||
|
if (!$this->metar->getClouds()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$layers = [];
|
||||||
|
$unit = setting('units.altitude');
|
||||||
|
|
||||||
|
foreach($this->metar->getClouds() as $cloud) {
|
||||||
|
if($unit === 'ft') {
|
||||||
|
$base_height = $cloud->getBaseHeight()->getValue();
|
||||||
|
} else {
|
||||||
|
$base_height = $cloud->getBaseHeight()->getConvertedValue('m');
|
||||||
|
}
|
||||||
|
|
||||||
|
$layers[] = [
|
||||||
|
'amount' => $cloud->getAmount(),
|
||||||
|
'base_height' => $base_height,
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last update time
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLastUpdate(): string
|
||||||
|
{
|
||||||
|
return $this->metar->getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the pressure, pass in the unit type
|
||||||
|
* @param string $unit Pass mb for millibars, hg for hg
|
||||||
|
* @return float|null
|
||||||
|
*/
|
||||||
|
public function getPressure($unit = 'mb')
|
||||||
|
{
|
||||||
|
if (!$this->metar->getPressure()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pressure = $this->metar->getPressure()->getValue();
|
||||||
|
if (strtolower($unit) === 'mb') {
|
||||||
|
return $pressure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($pressure * 33.86, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the raw metar string
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getRawMetar()
|
||||||
|
{
|
||||||
|
return $this->metar_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the temperature, if it exists in the METAR
|
||||||
|
* Convert to the units that are set in the VA
|
||||||
|
* @return float|null
|
||||||
|
*/
|
||||||
|
public function getTemperature()
|
||||||
|
{
|
||||||
|
if (!$this->metar->getAirTemperature()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(setting('units.temperature') === 'c') {
|
||||||
|
return $this->metar->getAirTemperature()->getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to F
|
||||||
|
round(($this->metar->getAirTemperature()->getValue() * 9 / 5) + 32, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the visibility
|
||||||
|
* @param bool $convert
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getVisibility($convert=true): int
|
||||||
|
{
|
||||||
|
// initially in miles
|
||||||
|
$visibility = 10; // assume it's ok and VFR
|
||||||
|
$vis = $this->metar->getVisibility();
|
||||||
|
if ($vis) {
|
||||||
|
$vis = $vis->getVisibility();
|
||||||
|
if ($vis) {
|
||||||
|
$visibility = (int) $vis->getValue();
|
||||||
|
|
||||||
|
if ($convert && setting('units.distance') === 'km') {
|
||||||
|
return $vis->getConvertedValue('m') / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $visibility;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($convert && setting('units.distance') === 'km') {
|
||||||
|
return round($visibility * 1.60934, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $visibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return wind information
|
||||||
|
*/
|
||||||
|
public function getWinds()
|
||||||
|
{
|
||||||
|
$sw = $this->metar->getSurfaceWind();
|
||||||
|
if (!$sw) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = [
|
||||||
|
'speed' => null,
|
||||||
|
'direction' => null,
|
||||||
|
'gusts' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$mean_speed = $sw->getMeanSpeed();
|
||||||
|
if($mean_speed) {
|
||||||
|
$ret['speed'] = $mean_speed->getConvertedValue('kt');
|
||||||
|
}
|
||||||
|
|
||||||
|
$dir = $sw->getMeanDirection();
|
||||||
|
if($dir) {
|
||||||
|
$ret['direction'] = $dir->getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
$gusts = $sw->getSpeedVariations();
|
||||||
|
if($gusts) {
|
||||||
|
$ret['gusts'] = $gusts->getConvertedValue('kt');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-35
@@ -4,6 +4,7 @@ namespace App\Widgets;
|
|||||||
|
|
||||||
use App\Interfaces\Widget;
|
use App\Interfaces\Widget;
|
||||||
use App\Support\Http;
|
use App\Support\Http;
|
||||||
|
use App\Support\Metar;
|
||||||
use App\Support\Units\Distance;
|
use App\Support\Units\Distance;
|
||||||
use App\Support\Units\Temperature;
|
use App\Support\Units\Temperature;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
@@ -30,29 +31,7 @@ class Weather extends Widget
|
|||||||
*/
|
*/
|
||||||
protected function determineCategory($metar): string
|
protected function determineCategory($metar): string
|
||||||
{
|
{
|
||||||
$category = 'VFR';
|
|
||||||
|
|
||||||
$visibility = 10; // assume it's ok and VFR
|
|
||||||
$vis = $metar->getVisibility();
|
|
||||||
if($vis) {
|
|
||||||
$vis = $vis->getVisibility();
|
|
||||||
if($vis) {
|
|
||||||
$visibility = $vis->getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$ceiling = $metar->getClouds();
|
|
||||||
if ($ceiling && count($ceiling) > 0) {
|
|
||||||
$ceiling = $ceiling[0]->getBaseHeight()->getValue();
|
|
||||||
} else {
|
|
||||||
$ceiling = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($visibility < 3 || $ceiling < 1000) {
|
|
||||||
$category = 'IFR';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $category;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,26 +44,20 @@ class Weather extends Widget
|
|||||||
*/
|
*/
|
||||||
$klass = config('phpvms.metar');
|
$klass = config('phpvms.metar');
|
||||||
$metar_class = new $klass;
|
$metar_class = new $klass;
|
||||||
|
|
||||||
|
$metar = null;
|
||||||
|
$wind = null;
|
||||||
$raw_metar = $metar_class->get_metar($this->config['icao']);
|
$raw_metar = $metar_class->get_metar($this->config['icao']);
|
||||||
|
|
||||||
if(!$raw_metar || $raw_metar === '') {
|
if ($raw_metar && $raw_metar !== '') {
|
||||||
$category = null;
|
$metar = new Metar($raw_metar);
|
||||||
$metar = null;
|
$wind = $metar->getWinds();
|
||||||
} else {
|
|
||||||
// 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', [
|
return view('widgets.weather', [
|
||||||
'config' => $this->config,
|
'config' => $this->config,
|
||||||
'category' => $category,
|
|
||||||
'metar' => $metar,
|
'metar' => $metar,
|
||||||
|
'wind' => $wind,
|
||||||
'unit_alt' => setting('units.altitude'),
|
'unit_alt' => setting('units.altitude'),
|
||||||
'unit_dist' => setting('units.distance'),
|
'unit_dist' => setting('units.distance'),
|
||||||
'unit_temp' => setting('units.temperature'),
|
'unit_temp' => setting('units.temperature'),
|
||||||
|
|||||||
@@ -11,29 +11,20 @@ https://api.checkwx.com/#metar-decoded
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Conditions</td>
|
<td>Conditions</td>
|
||||||
<td>
|
<td>
|
||||||
{{ $category }},
|
{{ $metar->getCategory() }}
|
||||||
@if($unit_temp === 'c')
|
@if($metar->getTemperature())
|
||||||
{{ $metar->getAirTemperature()->getValue() }}
|
|
||||||
@else
|
{{$metar->getTemperature()}}°{{strtoupper($unit_temp)}}
|
||||||
{{ 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
|
@endif
|
||||||
|
visibility
|
||||||
|
{{$metar->getVisibility()}}{{$unit_dist}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Barometer</td>
|
<td>Barometer</td>
|
||||||
<td>{{ $metar->getPressure()->getValue() }} Hg
|
<td>{{ $metar->getPressure('hg') }} Hg
|
||||||
/ {{ round($metar->getPressure()->getValue() * 33.86) }} MB
|
/ {{ $metar->getPressure('mb') }} MB
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -41,13 +32,7 @@ https://api.checkwx.com/#metar-decoded
|
|||||||
<td>
|
<td>
|
||||||
@foreach($metar->getClouds() as $cloud)
|
@foreach($metar->getClouds() as $cloud)
|
||||||
<p>
|
<p>
|
||||||
{{$cloud->getAmount()}} @
|
{{$cloud['amount']}} @ {{$cloud['base_height']}} {{ $unit_alt }}
|
||||||
@if($unit_alt === 'ft')
|
|
||||||
{{$cloud->getBaseHeight()->getValue()}}
|
|
||||||
@else
|
|
||||||
{{$cloud->getBaseHeight()->getConvertedValue('m')}}
|
|
||||||
@endif
|
|
||||||
{{ $unit_alt }}
|
|
||||||
</p>
|
</p>
|
||||||
@endforeach
|
@endforeach
|
||||||
</td>
|
</td>
|
||||||
@@ -55,10 +40,10 @@ https://api.checkwx.com/#metar-decoded
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Wind</td>
|
<td>Wind</td>
|
||||||
<td>
|
<td>
|
||||||
{{$metar->getSurfaceWind()->getMeanSpeed()->getConvertedValue('kt')}} kts
|
{{$wind['speed']}} kts
|
||||||
@ {{$metar->getSurfaceWind()->getMeanDirection()->getValue()}}°
|
@ {{$wind['direction']}}°
|
||||||
@if($metar->getSurfaceWind()->getSpeedVariations())
|
@if($wind['gusts'])
|
||||||
gusts to {{$metar->getSurfaceWind()->getSpeedVariations()->getConvertedValue('kt')}}
|
gusts to {{$wind['gusts']}}
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -72,7 +57,7 @@ https://api.checkwx.com/#metar-decoded
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Updated</td>
|
<td>Updated</td>
|
||||||
<td>{{$metar->getTime()}}</td>
|
<td>{{$metar->getLastUpdate()}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use \App\Support\Metar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the parsing/support class of the metar
|
||||||
|
*/
|
||||||
|
class MetarTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test adding/subtracting a percentage
|
||||||
|
*/
|
||||||
|
public function testMetar1()
|
||||||
|
{
|
||||||
|
$metar =
|
||||||
|
'KJFK 042151Z 28026G39KT 10SM '
|
||||||
|
.'FEW055 SCT095 BKN110 BKN230 12/M04 '
|
||||||
|
.'A2958 RMK AO2 PK WND 27045/2128 PRESRR '
|
||||||
|
.'SLP018 T01221044';
|
||||||
|
|
||||||
|
$parsed = new Metar($metar);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Conditions VFR visibility 10NM
|
||||||
|
Barometer 1001.58 Hg / 29.58 MB
|
||||||
|
Clouds FEW @ 5500 ft
|
||||||
|
SCT @ 9500 ft
|
||||||
|
BKN @ 11000 ft
|
||||||
|
BKN @ 23000 ft
|
||||||
|
Wind 26 kts @ 280° gusts to 39
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMetar2()
|
||||||
|
{
|
||||||
|
$metar =
|
||||||
|
'CYWG 172000Z 30015G25KT 3/4SM R36/4000FT/D -SN '
|
||||||
|
.'BLSN BKN008 OVC040 M05/M08 A2992 REFZRA WS RWY36 '
|
||||||
|
.'RMK SF5NS3 SLP134';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user