Add airport overview page and links to it #225

This commit is contained in:
Nabeel Shahzad 2018-03-31 15:57:30 -05:00
parent 46f9b3d9b9
commit 0bed38c78b
29 changed files with 380 additions and 56 deletions

View File

@ -113,6 +113,15 @@ class CreateSettingsTable extends Migration
'description' => 'The units for fuel for display',
]);
$this->addSetting('units.temperature', [
'name' => 'Temperature Units',
'group' => 'units',
'value' => 'f',
'type' => 'select',
'options' => 'f=Fahrenheit,c=Celsius',
'description' => 'The units for temperature',
]);
/**
* BIDS
*/

32
app/Support/Http.php Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace App\Support;
use GuzzleHttp\Client;
/**
* Helper for HTTP stuff
* @package App\Support
*/
class Http
{
/**
* Download a URI. If a file is given, it will save the downloaded
* content into that file
* @param $uri
* @param array $opts
* @return string
*/
public static function get($uri, array $opts)
{
$client = new Client();
$response = $client->request('GET', $uri, $opts);
$body = $response->getBody()->getContents();
if ($response->getHeader('content-type') === 'application/json') {
$body = \GuzzleHttp\json_decode($body);
}
return $body;
}
}

52
app/Widgets/CheckWx.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace App\Widgets;
use App\Interfaces\Widget;
use App\Support\Http;
/**
* 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 {
$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);
#dd($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'),
]);
}
}

View File

@ -195,3 +195,29 @@ if (!function_exists('show_date')) {
return $date->timezone($timezone)->toFormattedDateString();
}
}
if (!function_exists('_fmt')) {
/**
* Replace strings
* @param $line "Hi, my name is :name"
* @param array $replace ['name' => 'Nabeel']
* @return mixed
*/
function _fmt($line, array $replace)
{
if (empty($replace)) {
return $line;
}
foreach ($replace as $key => $value) {
$key = strtolower($key);
$line = str_replace(
[':'.$key],
[$value],
$line
);
}
return $line;
}
}

10
config/checkwx.php Normal file
View File

@ -0,0 +1,10 @@
<?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,
];

16
config/map.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/**
* Configuration for mapping URLs and stuff
*/
return [
/**
* This can really be any METAR service, as long as it returns GeoJSON
*/
'metar_wms' => [
'url' => 'https://ogcie.iblsoft.com/observations?',
'params' => [
'layers' => 'metar'
],
],
];

View File

@ -34,12 +34,6 @@ return [
*/
'vacentral_api_url' => 'https://api.vacentral.net',
/**
* For METAR features, register for an API key at
* https://www.checkwx.com
*/
'checkwx_api_key' => env('CHECKWX_API_KEY', false),
/**
* Misc Settings
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View File

@ -1,8 +1,8 @@
{
"/assets/frontend/js/app.js": "/assets/frontend/js/app.js?id=373465bdf8c91ab66596",
"/assets/frontend/js/app.js": "/assets/frontend/js/app.js?id=9fc76ac10f8d1c1906a0",
"/assets/frontend/css/now-ui-kit.css": "/assets/frontend/css/now-ui-kit.css?id=dae9f7e5d10b71e28486",
"/assets/admin/css/vendor.min.css": "/assets/admin/css/vendor.min.css?id=a11d81c6bf8d7a548c86",
"/assets/admin/js/app.js": "/assets/admin/js/app.js?id=7cc40ba818ed0f94c3fb",
"/assets/admin/js/app.js": "/assets/admin/js/app.js?id=aa2a19e307306470a073",
"/assets/installer/js/app.js": "/assets/installer/js/app.js?id=c65781eda730445d666e",
"/assets/fonts/glyphicons-halflings-regular.woff2": "/assets/fonts/glyphicons-halflings-regular.woff2?id=b5b5055c6d812c0f9f0d",
"/assets/admin/fonts/glyphicons-halflings-regular.woff2": "/assets/admin/fonts/glyphicons-halflings-regular.woff2?id=b5b5055c6d812c0f9f0d",

View File

@ -2,6 +2,7 @@
const leaflet = require('leaflet');
import draw_base_map from './base_map'
import { addWMSLayer } from './helpers';
/**
* Render a map with the airspace, etc around a given set of coords
@ -17,16 +18,27 @@ export default (opts) => {
zoom: 12,
layers: [],
set_marker: true,
marker_popup: '',
// Passed from the config/maps.php file
metar_wms: {
url: '',
params: {}
},
}, opts);
let map = draw_base_map(opts)
const coords = [opts.lat, opts.lon]
console.log('Applying coords', coords)
let map = draw_base_map(opts);
const coords = [opts.lat, opts.lon];
console.log('Applying coords', coords);
map.setView(coords, opts.zoom)
map.setView(coords, opts.zoom);
if (opts.set_marker === true) {
leaflet.marker(coords).addTo(map)
leaflet.marker(coords).addTo(map).bindPopup(opts.marker_popup);
}
return map
if(opts.metar_wms.url !== '') {
addWMSLayer(map, opts.metar_wms);
}
return map;
};

View File

@ -50,14 +50,14 @@ export default (opts) => {
feature_groups.push(openaip_cached_basemap);
*/
const openaip_basemap_phys_osm = leaflet.featureGroup(feature_groups)
const openaip_basemap_phys_osm = leaflet.featureGroup(feature_groups);
let map = leaflet.map('map', {
layers: [openaip_basemap_phys_osm],
center: opts.center,
zoom: opts.zoom,
scrollWheelZoom: false,
})
});
const attrib = leaflet.control.attribution({position: 'bottomleft'})
attrib.addAttribution('<a href="https://www.thunderforest.com" target="_blank" style="">Thunderforest</a>')

View File

@ -0,0 +1,47 @@
const leaflet = require('leaflet');
/**
* Add a WMS layer to a map. opts must be:
* {
* url: '',
* params: {}
* }
* @param map
* @param opts
*/
export function addWMSLayer(map, opts) {
if(opts.url === '') {
return;
}
opts.params = Object.assign({
format: 'image/png',
transparent: true,
maxZoom: 14,
minZoom: 4,
}, opts.params);
const mlayer = leaflet.tileLayer.wms(
opts.url, opts.params
);
mlayer.addTo(map);
return mlayer;
}
/**
* Show a popup
* @param feature
* @param layer
*/
export function showFeaturePopup(feature, layer) {
let popup_html = '';
if (feature.properties && feature.properties.popup) {
popup_html += feature.properties.popup
}
layer.bindPopup(popup_html)
}

View File

@ -3,6 +3,7 @@ const leaflet = require('leaflet');
import draw_base_map from './base_map'
import { ACTUAL_ROUTE_COLOR, PLAN_ROUTE_COLOR } from './config'
import {addWMSLayer} from './helpers';
/**
* Show some popup text when a feature is clicked on
@ -48,11 +49,19 @@ export default (opts) => {
actual_route_points: null,
actual_route_line: null,
render_elem: 'map',
metar_wms: {
url: '',
params: {}
},
}, opts);
console.log(opts)
console.log(opts);
let map = draw_base_map(opts)
let map = draw_base_map(opts);
if (opts.metar_wms.url !== '') {
addWMSLayer(map, opts.metar_wms);
}
let geodesicLayer = leaflet.geodesic([], {
weight: 7,

View File

@ -37,6 +37,13 @@ return [
'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
#

View File

@ -3,19 +3,28 @@
@section('content')
<div class="row">
<div class="col-md-6">
<div class="col-12">
<h2 class="description">{{ $airport->full_name }}</h2>
</div>
<div class="col-md-6">
<div class="col-5">
{{ Widget::checkWx([
'icao' => $airport->icao,
]) }}
</div>
<div class="col-7">
{{ Widget::airspaceMap([
'width' => '100%',
'height' => '250px',
'height' => '400px',
'lat' => $airport->lat,
'lon' => $airport->lon,
]) }}
</div>
</div>
<div class="row" style="height: 30px;">
</div>
<div class="row">
<div class="col-md-12">
<h3 class="description">Inbound Flights</h3>
@ -26,7 +35,6 @@
@else
@each('airports.table', $inbound_flights, 'flight')
@endif
<h3 class="description">Outbound Flights</h3>
@each('airports.table', $outbound_flights, 'flight')
</div>

View File

@ -14,6 +14,7 @@
phpvms.map.render_route_map({
route_points: {!! json_encode($map_features['route_points']) !!},
planned_route_line: {!! json_encode($map_features['planned_route_line']) !!},
metar_wms: {!! json_encode(config('map.metar_wms')) !!},
});
</script>
@endsection

View File

@ -4,7 +4,7 @@
@section('content')
<div class="row">
<div class="col-md-12">
<h2 class="description">{{ $flight->ident }} - {{ $flight->dpt_airport->full_name }} to {{ $flight->arr_airport->full_name }}</h2>
<h2 class="description">{{ $flight->ident }}</h2>
</div>
</div>
@ -13,29 +13,26 @@
<table class="table">
<tr>
<td>Departure</td>
<td>{{ $flight->dpt_airport->icao }} @ {{ $flight->dpt_time }}</td>
<td>
<a href="{{route('frontend.airports.show', ['id'=>$flight->dpt_airport_id])}}">
{{ $flight->dpt_airport->full_name }}</a>
@ {{ $flight->dpt_time }}</td>
</tr>
<tr>
<td>Arrival</td>
<td>{{ $flight->arr_airport->icao }} @ {{ $flight->arr_time }}</td>
<td>
<a href="{{route('frontend.airports.show', ['id'=>$flight->arr_airport_id])}}">
{{ $flight->arr_airport->full_name }}</a> @ {{ $flight->arr_time }}</td>
</tr>
<tr>
<td>Route Code/Leg:</td>
<td>{{ $flight->route_code ?: '-' }}/{{ $flight->route_leg ?: '-' }}</td>
</tr>
@if($flight->alt_airport_id)
<tr>
<td>Alternate Airport</td>
<td>
@if($flight->alt_airport_id)
{{ $flight->alt_airport->full_name }}
@else
-
@endif
{{ $flight->alt_airport->full_name }}
</td>
</tr>
@endif
<tr>
<td>Route</td>
@ -49,5 +46,20 @@
</table>
</div>
</div>
@include("flights.map")
<div style="padding: 10px;"></div>
<div class="row">
<div class="col-6">
<h5>{{$flight->dpt_airport_id}} METAR</h5>
{{ Widget::checkWx([
'icao' => $flight->dpt_airport_id,
]) }}
</div>
<div class="col-6">
<h5>{{$flight->arr_airport_id}} METAR</h5>
{{ Widget::checkWx([
'icao' => $flight->arr_airport_id,
]) }}
</div>
</div>
@include('flights.map')
@endsection

View File

@ -32,10 +32,12 @@
<div class="row">
<div class="col-sm-5">
<span class="title">DEP&nbsp;</span>
{{ $flight->dpt_airport->icao }}@if($flight->dpt_time), {{ $flight->dpt_time }}@endif
<a href="{{route('frontend.airports.show', ['id'=>$flight->dpt_airport_id])}}">
{{ $flight->dpt_airport->icao }}</a>@if($flight->dpt_time), {{ $flight->dpt_time }}@endif
<br />
<span class="title">ARR&nbsp;</span>
{{ $flight->arr_airport->icao }}@if($flight->arr_time), {{ $flight->arr_time }}@endif
<a href="{{route('frontend.airports.show', ['id'=>$flight->arr_airport_id])}}">
{{ $flight->arr_airport->icao }}</a>@if($flight->arr_time), {{ $flight->arr_time }}@endif
<br />
@if($flight->distance)
<span class="title">DISTANCE&nbsp;</span>

View File

@ -1,12 +1,22 @@
<div class="card border-blue-bottom">
<div class="card-block" style="min-height: 0px">
<div class="row">
<div class="col-sm-2 text-center">
<div class="col-12">
<h5>
<a class="text-c" href="{{ route('frontend.pireps.show', [$pirep->id]) }}">
{{ $pirep->ident }}
</a>
-
<a href="{{route('frontend.airports.show', ['id' => $pirep->dpt_airport_id])}}">
{{ $pirep->dpt_airport_id }}</a>
<span class="description"> to </span>
<a href="{{route('frontend.airports.show', ['id' => $pirep->arr_airport_id])}}">
{{ $pirep->arr_airport_id }}</a>
</h5>
</div>
<div class="col-sm-2 text-center">
<div>
@if($pirep->state === PirepState::PENDING)
<div class="badge badge-warning">
@ -24,14 +34,6 @@
<div class="row">
<div class="col-sm-6">
<table width="100%">
<tr>
<td width="20%" nowrap><span class="title">DEP&nbsp;</span></td>
<td>{{ $pirep->dpt_airport_id }}</td>
</tr>
<tr>
<td nowrap><span class="title">ARR&nbsp;</span></td>
<td>{{ $pirep->arr_airport_id }}&nbsp;</td>
</tr>
<tr>
<td nowrap><span class="title">Flight Time&nbsp;</span></td>
<td>{{ Utils::minutesToTimeString($pirep->flight_time) }}</td>

View File

@ -35,9 +35,11 @@
<tr>
<td>Departure/Arrival</td>
<td>
{{ $pirep->dpt_airport->icao }} - {{ $pirep->dpt_airport->name }}
<a href="{{route('frontend.airports.show', ['id' => $pirep->dpt_airport_id])}}">
{{ $pirep->dpt_airport->full_name }}</a>
<span class="description">to</span>
{{ $pirep->arr_airport->icao }} - {{ $pirep->arr_airport->name }}
<a href="{{route('frontend.airports.show', ['id' => $pirep->arr_airport_id])}}">
{{ $pirep->arr_airport->full_name }}</a>
</td>
</tr>

View File

@ -5,6 +5,7 @@
phpvms.map.render_airspace_map({
lat: "{{$config['lat']}}",
lon: "{{$config['lon']}}",
metar_wms: {!! json_encode(config('map.metar_wms')) !!},
});
</script>
@endsection

View File

@ -0,0 +1,64 @@
@if(!$data)
<p>METAR/TAF data could not be retrieved</p>
@else
<table class="table">
<tr>
<td colspan="2">
<div style="line-height:1.5em;min-height: 3em;">
{{$data->raw_text}}
</div>
</td>
</tr>
<tr>
<td colspan="2">Updated: {{$data->observed}}</td>
</tr>
<tr>
<td>Conditions</td>
<td>
{{$data->flight_category}},
@if($unit_temp === 'f')
{{$data->temperature->fahrenheit}}°F
@else
{{$data->temperature->celsius}}°C
@endif
, visibility
@if($unit_dist === 'km')
{{intval(str_replace(',', '', $data->visibility->meters)) / 1000}}
@else
{{$data->visibility->miles}}
@endif
&nbsp;
{{$data->humidity_percent}}% humidity
</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>
</table>
@endif

View File

@ -12,6 +12,7 @@ mix.copy('node_modules/bootstrap3/fonts/*.woff2', 'public/assets/admin/fonts/');
mix.copy('node_modules/x-editable/dist/bootstrap3-editable/img/*', 'public/assets/admin/img/');
mix.copy('node_modules/jquery/dist/jquery.js', 'public/assets/global/js/');
mix.copy('node_modules/flag-icon-css/flags/', 'public/assets/global/flags/');
mix.copy('node_modules/leaflet/dist/images/', 'public/assets/global/css/images/');
/**
* DEFAULT SKIN FRONTEND FILES
@ -89,6 +90,7 @@ mix.styles([
'node_modules/flag-icon-css/css/flag-icon.css',
], 'public/assets/global/css/vendor.css')
.options({
//processCssUrls: true,
compressed: true
})
.sourceMaps();