From b456dc1a717775c2244ac6f6402cbf9b6112728c Mon Sep 17 00:00:00 2001 From: Nabeel Shahzad Date: Mon, 1 Jan 2018 13:48:02 -0600 Subject: [PATCH] Save PIREP route to ACARS data table #102 --- app/Console/Commands/AcarsReplay.php | 2 +- app/Database/factories/NavdataFactory.php | 15 ++++ app/Database/factories/NavpointFactory.php | 15 ---- .../2017_06_28_195426_create_pirep_tables.php | 1 + ...017_12_20_004147_create_navdata_tables.php | 2 +- .../2017_12_20_005147_create_acars_tables.php | 2 + app/Database/seeds/sample.yml | 3 + .../Controllers/Admin/PirepController.php | 12 ++- app/Http/Controllers/Api/PirepController.php | 4 +- app/Models/Acars.php | 3 + app/Models/Navdata.php | 16 +++- app/Models/Pirep.php | 10 ++- app/Models/Traits/HashId.php | 24 ++++-- app/Services/GeoService.php | 61 ++++++++++++++++ app/Services/PIREPService.php | 73 +++++++++++++++++-- tests/PIREPTest.php | 58 ++++++++++++++- 16 files changed, 258 insertions(+), 43 deletions(-) create mode 100644 app/Database/factories/NavdataFactory.php delete mode 100644 app/Database/factories/NavpointFactory.php diff --git a/app/Console/Commands/AcarsReplay.php b/app/Console/Commands/AcarsReplay.php index 04632f13..36638a17 100644 --- a/app/Console/Commands/AcarsReplay.php +++ b/app/Console/Commands/AcarsReplay.php @@ -79,7 +79,7 @@ class AcarsReplay extends BaseCommand 'aircraft_id' => 1, 'dpt_airport_id' => $flight->planned_depairport, 'arr_airport_id' => $flight->planned_destairport, - 'altitude' => $flight->planned_altitude, + 'level' => $flight->planned_altitude, 'planned_flight_time' => $pft, 'route' => $flight->planned_route, ] diff --git a/app/Database/factories/NavdataFactory.php b/app/Database/factories/NavdataFactory.php new file mode 100644 index 00000000..4d1a9ae7 --- /dev/null +++ b/app/Database/factories/NavdataFactory.php @@ -0,0 +1,15 @@ +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), + ]; +}); diff --git a/app/Database/factories/NavpointFactory.php b/app/Database/factories/NavpointFactory.php deleted file mode 100644 index 918105b4..00000000 --- a/app/Database/factories/NavpointFactory.php +++ /dev/null @@ -1,15 +0,0 @@ -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), - ]; -}); diff --git a/app/Database/migrations/2017_06_28_195426_create_pirep_tables.php b/app/Database/migrations/2017_06_28_195426_create_pirep_tables.php index 822f51ff..22f7a618 100644 --- a/app/Database/migrations/2017_06_28_195426_create_pirep_tables.php +++ b/app/Database/migrations/2017_06_28_195426_create_pirep_tables.php @@ -28,6 +28,7 @@ class CreatePirepTables extends Migration $table->string('dpt_airport_id', 5); $table->string('arr_airport_id', 5); $table->unsignedInteger('altitude')->nullable(); + $table->unsignedInteger('level')->nullable(); $table->unsignedDecimal('flight_time', 19)->nullable(); $table->unsignedDecimal('planned_flight_time', 19)->nullable(); $table->unsignedDecimal('gross_weight', 19)->nullable(); diff --git a/app/Database/migrations/2017_12_20_004147_create_navdata_tables.php b/app/Database/migrations/2017_12_20_004147_create_navdata_tables.php index 309d973c..96d00448 100644 --- a/app/Database/migrations/2017_12_20_004147_create_navdata_tables.php +++ b/app/Database/migrations/2017_12_20_004147_create_navdata_tables.php @@ -18,7 +18,7 @@ class CreateNavdataTables extends Migration * https://github.com/skiselkov/openfmc/blob/master/airac.h */ Schema::create('navdata', function (Blueprint $table) { - $table->string('id', 4); + $table->string('id', 5); $table->string('name', 24); $table->unsignedInteger('type'); $table->float('lat', 7, 4)->default(0.0); diff --git a/app/Database/migrations/2017_12_20_005147_create_acars_tables.php b/app/Database/migrations/2017_12_20_005147_create_acars_tables.php index 8dc04db2..608ccd40 100644 --- a/app/Database/migrations/2017_12_20_005147_create_acars_tables.php +++ b/app/Database/migrations/2017_12_20_005147_create_acars_tables.php @@ -17,6 +17,8 @@ class CreateAcarsTables extends Migration $table->string('id', 12); $table->string('pirep_id', 12); $table->unsignedTinyInteger('type'); + $table->unsignedInteger('nav_type')->nullable(); + $table->string('name')->nullable(); $table->string('log')->nullable(); $table->float('lat', 7, 4)->default(0.0); $table->float('lon', 7, 4)->default(0.0); diff --git a/app/Database/seeds/sample.yml b/app/Database/seeds/sample.yml index a1740c72..4882dc7d 100644 --- a/app/Database/seeds/sample.yml +++ b/app/Database/seeds/sample.yml @@ -280,6 +280,7 @@ pireps: user_id: 1 airline_id: 1 flight_id: flightid_1 + flight_number: 100 aircraft_id: 1 dpt_airport_id: KAUS arr_airport_id: KJFK @@ -293,6 +294,7 @@ pireps: user_id: 1 airline_id: 1 flight_id: flightid_2 + flight_number: 101 aircraft_id: 1 dpt_airport_id: KJFK arr_airport_id: KAUS @@ -306,6 +308,7 @@ pireps: user_id: 1 airline_id: 1 flight_id: flightid_2 + flight_number: 101 aircraft_id: 1 dpt_airport_id: KJFK arr_airport_id: KAUS diff --git a/app/Http/Controllers/Admin/PirepController.php b/app/Http/Controllers/Admin/PirepController.php index 7af6490a..2853ce6f 100644 --- a/app/Http/Controllers/Admin/PirepController.php +++ b/app/Http/Controllers/Admin/PirepController.php @@ -24,7 +24,7 @@ class PirepController extends BaseController $airlineRepo, $pirepRepo, $aircraftRepo, - $pirepSvc; + $pirepSvc; public function __construct( AirportRepository $airportRepo, @@ -179,7 +179,15 @@ class PirepController extends BaseController 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.'); return redirect(route('admin.pireps.index')); diff --git a/app/Http/Controllers/Api/PirepController.php b/app/Http/Controllers/Api/PirepController.php index d012f2d4..17926b35 100644 --- a/app/Http/Controllers/Api/PirepController.php +++ b/app/Http/Controllers/Api/PirepController.php @@ -69,7 +69,7 @@ class PirepController extends AppBaseController 'route_code', 'flight_time', 'planned_flight_time', - 'altitude', + 'level', 'route', 'notes', ]; @@ -123,7 +123,7 @@ class PirepController extends AppBaseController 'route_code', 'flight_time', 'planned_flight_time', - 'altitude', + 'level', 'route', 'notes', ]; diff --git a/app/Models/Acars.php b/app/Models/Acars.php index 51212ec3..4196b7e2 100644 --- a/app/Models/Acars.php +++ b/app/Models/Acars.php @@ -14,6 +14,8 @@ class Acars extends BaseModel public $fillable = [ 'pirep_id', 'type', + 'nav_type', + 'name', 'log', 'lat', 'lon', @@ -29,6 +31,7 @@ class Acars extends BaseModel public $casts = [ 'type' => 'integer', + 'nav_type' => 'integer', 'lat' => 'float', 'lon' => 'float', 'heading' => 'integer', diff --git a/app/Models/Navdata.php b/app/Models/Navdata.php index 8be961b4..0da7f485 100644 --- a/app/Models/Navdata.php +++ b/app/Models/Navdata.php @@ -18,9 +18,23 @@ class Navdata extends BaseModel ]; public $casts = [ - 'id' => 'string', 'type' => 'integer', 'lat' => '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); + } + }); + } } diff --git a/app/Models/Pirep.php b/app/Models/Pirep.php index b68968a6..5c437ada 100644 --- a/app/Models/Pirep.php +++ b/app/Models/Pirep.php @@ -61,6 +61,8 @@ class Pirep extends BaseModel 'flight_number' => 'required', 'dpt_airport_id' => 'required', 'arr_airport_id' => 'required', + 'notes' => 'nullable', + 'route' => 'nullable', ]; /** @@ -70,10 +72,12 @@ class Pirep extends BaseModel public function getIdentAttribute() { $flight_id = $this->airline->code; - if ($this->flight_id) { - $flight_id .= $this->flight->flight_number; - } else { + if(!empty($this->flight_number)) { $flight_id .= $this->flight_number; + } else { + if ($this->flight_id) { + $flight_id .= $this->flight->flight_number; + } } return $flight_id; diff --git a/app/Models/Traits/HashId.php b/app/Models/Traits/HashId.php index f9d791dc..bec914cd 100644 --- a/app/Models/Traits/HashId.php +++ b/app/Models/Traits/HashId.php @@ -7,19 +7,27 @@ use Hashids\Hashids; 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() { parent::boot(); - - static::creating(function ($model) - { + static::creating(function ($model) { $key = $model->getKeyName(); - if (empty($model->{$key})) { - $hashids = new Hashids('', 12); - $mt = str_replace('.', '', microtime(true)); - $id = $hashids->encode($mt); - + $id = static::createNewHashId(); $model->{$key} = $id; } }); diff --git a/app/Services/GeoService.php b/app/Services/GeoService.php index e8788a30..fdfc2a3a 100644 --- a/app/Services/GeoService.php +++ b/app/Services/GeoService.php @@ -3,6 +3,9 @@ namespace App\Services; use App\Models\Acars; +use App\Models\Airport; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Support\Collection; use Log; use \GeoJson\Geometry\Point; @@ -34,6 +37,64 @@ class GeoService extends BaseService $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 * @param array $coordStart diff --git a/app/Services/PIREPService.php b/app/Services/PIREPService.php index 3b06f910..5ad06666 100644 --- a/app/Services/PIREPService.php +++ b/app/Services/PIREPService.php @@ -2,37 +2,94 @@ namespace App\Services; -use App\Models\Enums\PirepSource; -use App\Models\Enums\PirepState; +use Log; + +use App\Models\Acars; +use App\Models\Navdata; use App\Models\Pirep; 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\PirepFiled; use App\Events\PirepRejected; use App\Events\UserStatsChanged; -use App\Models\User; +use App\Repositories\NavdataRepository; use App\Repositories\PirepRepository; -use Log; class PIREPService extends BaseService { - protected $pilotSvc, $pirepRepo; + protected $geoSvc, + $navRepo, + $pilotSvc, + $pirepRepo; /** * PIREPService constructor. * @param UserService $pilotSvc + * @param GeoService $geoSvc + * @param NavdataRepository $navRepo * @param PirepRepository $pirepRepo */ public function __construct( UserService $pilotSvc, + GeoService $geoSvc, + NavdataRepository $navRepo, PirepRepository $pirepRepo ) { + $this->geoSvc = $geoSvc; $this->pilotSvc = $pilotSvc; + $this->navRepo = $navRepo; $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 * @@ -43,7 +100,7 @@ class PIREPService extends BaseService */ public function create(Pirep $pirep, array $field_values=[]): Pirep { - if($field_values === null) { + if(empty($field_values)) { $field_values = []; } @@ -60,6 +117,9 @@ class PIREPService extends BaseService } } + # Save the PIREP route + $pirep = $this->saveRoute($pirep); + $pirep->save(); $pirep->refresh(); @@ -73,7 +133,6 @@ class PIREPService extends BaseService } Log::info('New PIREP filed', [$pirep]); - event(new PirepFiled($pirep)); # only update the pilot last state if they are accepted diff --git a/tests/PIREPTest.php b/tests/PIREPTest.php index de583d2c..a435b55e 100644 --- a/tests/PIREPTest.php +++ b/tests/PIREPTest.php @@ -1,8 +1,11 @@ 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() { - $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, []); /** @@ -54,6 +86,26 @@ class PIREPTest extends TestCase $this->assertEquals($new_pirep_count, $pirep->pilot->flights); $this->assertEquals($new_flight_time, $pirep->pilot->flight_time); $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); } /**