Save PIREP route to ACARS data table #102

This commit is contained in:
Nabeel Shahzad 2018-01-01 13:48:02 -06:00
parent 216d686cc7
commit b456dc1a71
16 changed files with 258 additions and 43 deletions

View File

@ -79,7 +79,7 @@ class AcarsReplay extends BaseCommand
'aircraft_id' => 1, 'aircraft_id' => 1,
'dpt_airport_id' => $flight->planned_depairport, 'dpt_airport_id' => $flight->planned_depairport,
'arr_airport_id' => $flight->planned_destairport, 'arr_airport_id' => $flight->planned_destairport,
'altitude' => $flight->planned_altitude, 'level' => $flight->planned_altitude,
'planned_flight_time' => $pft, 'planned_flight_time' => $pft,
'route' => $flight->planned_route, 'route' => $flight->planned_route,
] ]

View File

@ -0,0 +1,15 @@
<?php
use Faker\Generator as Faker;
use \App\Models\Enums\NavaidType;
$factory->define(App\Models\Navdata::class, function (Faker $faker) {
return [
'id' => substr($faker->unique()->word, 0, 5),
'name' => $faker->unique()->text(10),
'type' => $faker->randomElement([NavaidType::VOR, NavaidType::NDB]),
'lat' => $faker->latitude,
'lon' => $faker->longitude,
'freq' => $faker->randomFloat(2, 100, 1000),
];
});

View File

@ -1,15 +0,0 @@
<?php
use Faker\Generator as Faker;
$factory->define(App\Models\Navpoint::class, function (Faker $faker) {
return [
'id' => $faker->unique()->numberBetween(10, 100000),
'name' => $faker->unique()->text(10),
'title' => $faker->unique()->text(25),
'airway' => $faker->unique()->text(7),
'lat' => $faker->latitude,
'lon' => $faker->longitude,
'freq' => $faker->randomFloat(2, 100, 1000),
];
});

View File

@ -28,6 +28,7 @@ class CreatePirepTables extends Migration
$table->string('dpt_airport_id', 5); $table->string('dpt_airport_id', 5);
$table->string('arr_airport_id', 5); $table->string('arr_airport_id', 5);
$table->unsignedInteger('altitude')->nullable(); $table->unsignedInteger('altitude')->nullable();
$table->unsignedInteger('level')->nullable();
$table->unsignedDecimal('flight_time', 19)->nullable(); $table->unsignedDecimal('flight_time', 19)->nullable();
$table->unsignedDecimal('planned_flight_time', 19)->nullable(); $table->unsignedDecimal('planned_flight_time', 19)->nullable();
$table->unsignedDecimal('gross_weight', 19)->nullable(); $table->unsignedDecimal('gross_weight', 19)->nullable();

View File

@ -18,7 +18,7 @@ class CreateNavdataTables extends Migration
* https://github.com/skiselkov/openfmc/blob/master/airac.h * https://github.com/skiselkov/openfmc/blob/master/airac.h
*/ */
Schema::create('navdata', function (Blueprint $table) { Schema::create('navdata', function (Blueprint $table) {
$table->string('id', 4); $table->string('id', 5);
$table->string('name', 24); $table->string('name', 24);
$table->unsignedInteger('type'); $table->unsignedInteger('type');
$table->float('lat', 7, 4)->default(0.0); $table->float('lat', 7, 4)->default(0.0);

View File

@ -17,6 +17,8 @@ class CreateAcarsTables extends Migration
$table->string('id', 12); $table->string('id', 12);
$table->string('pirep_id', 12); $table->string('pirep_id', 12);
$table->unsignedTinyInteger('type'); $table->unsignedTinyInteger('type');
$table->unsignedInteger('nav_type')->nullable();
$table->string('name')->nullable();
$table->string('log')->nullable(); $table->string('log')->nullable();
$table->float('lat', 7, 4)->default(0.0); $table->float('lat', 7, 4)->default(0.0);
$table->float('lon', 7, 4)->default(0.0); $table->float('lon', 7, 4)->default(0.0);

View File

@ -280,6 +280,7 @@ pireps:
user_id: 1 user_id: 1
airline_id: 1 airline_id: 1
flight_id: flightid_1 flight_id: flightid_1
flight_number: 100
aircraft_id: 1 aircraft_id: 1
dpt_airport_id: KAUS dpt_airport_id: KAUS
arr_airport_id: KJFK arr_airport_id: KJFK
@ -293,6 +294,7 @@ pireps:
user_id: 1 user_id: 1
airline_id: 1 airline_id: 1
flight_id: flightid_2 flight_id: flightid_2
flight_number: 101
aircraft_id: 1 aircraft_id: 1
dpt_airport_id: KJFK dpt_airport_id: KJFK
arr_airport_id: KAUS arr_airport_id: KAUS
@ -306,6 +308,7 @@ pireps:
user_id: 1 user_id: 1
airline_id: 1 airline_id: 1
flight_id: flightid_2 flight_id: flightid_2
flight_number: 101
aircraft_id: 1 aircraft_id: 1
dpt_airport_id: KJFK dpt_airport_id: KJFK
arr_airport_id: KAUS arr_airport_id: KAUS

View File

@ -179,7 +179,15 @@ class PirepController extends BaseController
return redirect(route('admin.pireps.index')); return redirect(route('admin.pireps.index'));
} }
$pirep = $this->pirepRepo->update($request->all(), $id); $attrs = $request->all();
$orig_route = $pirep->route;
$pirep = $this->pirepRepo->update($attrs, $id);
// A route change in the PIREP, so update the saved points
// in the ACARS table
if($pirep->route !== $orig_route) {
$this->pirepSvc->saveRoute($pirep);
}
Flash::success('Pirep updated successfully.'); Flash::success('Pirep updated successfully.');
return redirect(route('admin.pireps.index')); return redirect(route('admin.pireps.index'));

View File

@ -69,7 +69,7 @@ class PirepController extends AppBaseController
'route_code', 'route_code',
'flight_time', 'flight_time',
'planned_flight_time', 'planned_flight_time',
'altitude', 'level',
'route', 'route',
'notes', 'notes',
]; ];
@ -123,7 +123,7 @@ class PirepController extends AppBaseController
'route_code', 'route_code',
'flight_time', 'flight_time',
'planned_flight_time', 'planned_flight_time',
'altitude', 'level',
'route', 'route',
'notes', 'notes',
]; ];

View File

@ -14,6 +14,8 @@ class Acars extends BaseModel
public $fillable = [ public $fillable = [
'pirep_id', 'pirep_id',
'type', 'type',
'nav_type',
'name',
'log', 'log',
'lat', 'lat',
'lon', 'lon',
@ -29,6 +31,7 @@ class Acars extends BaseModel
public $casts = [ public $casts = [
'type' => 'integer', 'type' => 'integer',
'nav_type' => 'integer',
'lat' => 'float', 'lat' => 'float',
'lon' => 'float', 'lon' => 'float',
'heading' => 'integer', 'heading' => 'integer',

View File

@ -18,9 +18,23 @@ class Navdata extends BaseModel
]; ];
public $casts = [ public $casts = [
'id' => 'string',
'type' => 'integer', 'type' => 'integer',
'lat' => 'float', 'lat' => 'float',
'lon' => 'float', 'lon' => 'float',
'freq' => 'float',
]; ];
protected static function boot()
{
parent::boot();
/**
* Make sure the ID is all caps
*/
static::creating(function (Navdata $model) {
if (!empty($model->id)) {
$model->id = strtoupper($model->id);
}
});
}
} }

View File

@ -61,6 +61,8 @@ class Pirep extends BaseModel
'flight_number' => 'required', 'flight_number' => 'required',
'dpt_airport_id' => 'required', 'dpt_airport_id' => 'required',
'arr_airport_id' => 'required', 'arr_airport_id' => 'required',
'notes' => 'nullable',
'route' => 'nullable',
]; ];
/** /**
@ -70,10 +72,12 @@ class Pirep extends BaseModel
public function getIdentAttribute() public function getIdentAttribute()
{ {
$flight_id = $this->airline->code; $flight_id = $this->airline->code;
if(!empty($this->flight_number)) {
$flight_id .= $this->flight_number;
} else {
if ($this->flight_id) { if ($this->flight_id) {
$flight_id .= $this->flight->flight_number; $flight_id .= $this->flight->flight_number;
} else { }
$flight_id .= $this->flight_number;
} }
return $flight_id; return $flight_id;

View File

@ -7,19 +7,27 @@ use Hashids\Hashids;
trait HashId trait HashId
{ {
/**
* @return string
* @throws \Hashids\HashidsException
*/
protected static function createNewHashId()
{
$hashids = new Hashids('', 12);
$mt = str_replace('.', '', microtime(true));
return $hashids->encode($mt);
}
/**
* Register callbacks
*/
protected static function boot() protected static function boot()
{ {
parent::boot(); parent::boot();
static::creating(function ($model) {
static::creating(function ($model)
{
$key = $model->getKeyName(); $key = $model->getKeyName();
if (empty($model->{$key})) { if (empty($model->{$key})) {
$hashids = new Hashids('', 12); $id = static::createNewHashId();
$mt = str_replace('.', '', microtime(true));
$id = $hashids->encode($mt);
$model->{$key} = $id; $model->{$key} = $id;
} }
}); });

View File

@ -3,6 +3,9 @@
namespace App\Services; namespace App\Services;
use App\Models\Acars; use App\Models\Acars;
use App\Models\Airport;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Collection;
use Log; use Log;
use \GeoJson\Geometry\Point; use \GeoJson\Geometry\Point;
@ -34,6 +37,64 @@ class GeoService extends BaseService
$this->navRepo = $navRepo; $this->navRepo = $navRepo;
} }
/**
* Parse a route string into a collection of Navadata points
* TODO: Add the distance calculation in here
* @param $route
* @param Airport|null $dep
* @param Airport|null $arr
* @return array|Collection
*/
public function routeToNavPoints($route, Airport $dep=null, Airport $arr=null)
{
$route = trim($route);
if(empty($route)) {
return collect();
}
$skip_points = ['SID', 'STAR'];
if($dep !== null) {
$skip_points[] = $dep->icao;
}
if($arr !== null) {
$skip_points[] = $arr->icao;
}
# Iterate through the route
$route = collect(explode(' ', $route))
->transform(function($point) use ($skip_points) {
$point = trim($point);
if(empty($point)) {
return false;
}
if(\in_array($point, $skip_points, true)) {
return false;
}
try {
$navpoints = $this->navRepo->findWhere(['id'=>$point]);
} catch(ModelNotFoundException $e) {
return false;
}
if($navpoints->count() === 0) {
return false;
} elseif ($navpoints->count() === 1) {
return $navpoints[0];
}
# find the closest waypoint...
})
->filter(function($value, $key) {
return !empty($value);
});
return $route;
}
/** /**
* Determine the closest set of coordinates from the starting position * Determine the closest set of coordinates from the starting position
* @param array $coordStart * @param array $coordStart

View File

@ -2,37 +2,94 @@
namespace App\Services; namespace App\Services;
use App\Models\Enums\PirepSource; use Log;
use App\Models\Enums\PirepState;
use App\Models\Acars;
use App\Models\Navdata;
use App\Models\Pirep; use App\Models\Pirep;
use App\Models\PirepFieldValues; use App\Models\PirepFieldValues;
use App\Models\User;
use App\Models\Enums\AcarsType;
use App\Models\Enums\PirepSource;
use App\Models\Enums\PirepState;
use App\Events\PirepAccepted; use App\Events\PirepAccepted;
use App\Events\PirepFiled; use App\Events\PirepFiled;
use App\Events\PirepRejected; use App\Events\PirepRejected;
use App\Events\UserStatsChanged; use App\Events\UserStatsChanged;
use App\Models\User; use App\Repositories\NavdataRepository;
use App\Repositories\PirepRepository; use App\Repositories\PirepRepository;
use Log;
class PIREPService extends BaseService class PIREPService extends BaseService
{ {
protected $pilotSvc, $pirepRepo; protected $geoSvc,
$navRepo,
$pilotSvc,
$pirepRepo;
/** /**
* PIREPService constructor. * PIREPService constructor.
* @param UserService $pilotSvc * @param UserService $pilotSvc
* @param GeoService $geoSvc
* @param NavdataRepository $navRepo
* @param PirepRepository $pirepRepo * @param PirepRepository $pirepRepo
*/ */
public function __construct( public function __construct(
UserService $pilotSvc, UserService $pilotSvc,
GeoService $geoSvc,
NavdataRepository $navRepo,
PirepRepository $pirepRepo PirepRepository $pirepRepo
) { ) {
$this->geoSvc = $geoSvc;
$this->pilotSvc = $pilotSvc; $this->pilotSvc = $pilotSvc;
$this->navRepo = $navRepo;
$this->pirepRepo = $pirepRepo; $this->pirepRepo = $pirepRepo;
} }
/**
* Save the route into the ACARS table with AcarsType::ROUTE
* @param Pirep $pirep
* @return Pirep
*/
public function saveRoute(Pirep $pirep): Pirep
{
# Delete all the existing nav points
Acars::where([
'pirep_id' => $pirep->id,
'type' => AcarsType::ROUTE,
])->delete();
# Delete the route
if(empty($pirep->route)) {
return $pirep;
}
$route = $this->geoSvc->routeToNavPoints(
$pirep->route,
$pirep->dep_airport,
$pirep->arr_airport
);
/**
* @var $point Navdata
*/
foreach($route as $point) {
$acars = new Acars();
$acars->pirep_id = $pirep->id;
$acars->type = AcarsType::ROUTE;
$acars->nav_type = $point->type;
$acars->name = $point->id;
$acars->lat = $point->lat;
$acars->lon = $point->lon;
$acars->save();
}
return $pirep;
}
/** /**
* Create a new PIREP with some given fields * Create a new PIREP with some given fields
* *
@ -43,7 +100,7 @@ class PIREPService extends BaseService
*/ */
public function create(Pirep $pirep, array $field_values=[]): Pirep public function create(Pirep $pirep, array $field_values=[]): Pirep
{ {
if($field_values === null) { if(empty($field_values)) {
$field_values = []; $field_values = [];
} }
@ -60,6 +117,9 @@ class PIREPService extends BaseService
} }
} }
# Save the PIREP route
$pirep = $this->saveRoute($pirep);
$pirep->save(); $pirep->save();
$pirep->refresh(); $pirep->refresh();
@ -73,7 +133,6 @@ class PIREPService extends BaseService
} }
Log::info('New PIREP filed', [$pirep]); Log::info('New PIREP filed', [$pirep]);
event(new PirepFiled($pirep)); event(new PirepFiled($pirep));
# only update the pilot last state if they are accepted # only update the pilot last state if they are accepted

View File

@ -1,8 +1,11 @@
<?php <?php
use App\Models\Enums\PirepState; use App\Models\Acars;
use App\Models\User; use App\Models\Enums\AcarsType;
use App\Models\Navdata;
use App\Models\Pirep; use App\Models\Pirep;
use App\Models\User;
use App\Models\Enums\PirepState;
use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\WithoutMiddleware;
@ -20,11 +23,40 @@ class PIREPTest extends TestCase
$this->pirepSvc = app('App\Services\PIREPService'); $this->pirepSvc = app('App\Services\PIREPService');
} }
protected function createNewRoute()
{
$route = [];
$navpoints = factory(App\Models\Navdata::class, 5)->create();
foreach ($navpoints as $point) {
$route[] = $point->id;
}
return $route;
}
protected function getAcarsRoute($pirep)
{
$saved_route = [];
$route_points = Acars::where(
['pirep_id' => $pirep->id, 'type' => AcarsType::ROUTE]
)->orderBy('created_at', 'asc')->get();
foreach ($route_points as $point) {
$saved_route[] = $point->name;
}
return $saved_route;
}
/** /**
*/ */
public function testAddPirep() public function testAddPirep()
{ {
$pirep = factory(App\Models\Pirep::class)->create(); $route = $this->createNewRoute();
$pirep = factory(App\Models\Pirep::class)->create([
'route' => implode(' ', $route)
]);
$pirep = $this->pirepSvc->create($pirep, []); $pirep = $this->pirepSvc->create($pirep, []);
/** /**
@ -54,6 +86,26 @@ class PIREPTest extends TestCase
$this->assertEquals($new_pirep_count, $pirep->pilot->flights); $this->assertEquals($new_pirep_count, $pirep->pilot->flights);
$this->assertEquals($new_flight_time, $pirep->pilot->flight_time); $this->assertEquals($new_flight_time, $pirep->pilot->flight_time);
$this->assertEquals($pirep->arr_airport_id, $pirep->pilot->curr_airport_id); $this->assertEquals($pirep->arr_airport_id, $pirep->pilot->curr_airport_id);
/**
* Check the ACARS table
*/
$saved_route = $this->getAcarsRoute($pirep);
$this->assertEquals($route, $saved_route);
/**
* Recreate the route with new options points. Make sure that the
* old route is erased from the ACARS table and then recreated
*/
$route = $this->createNewRoute();
$pirep->route = implode(' ', $route);
$pirep->save();
# this should delete the old route from the acars table
$this->pirepSvc->saveRoute($pirep);
$saved_route = $this->getAcarsRoute($pirep);
$this->assertEquals($route, $saved_route);
} }
/** /**