diff --git a/app/Models/Airport.php b/app/Models/Airport.php new file mode 100644 index 0000000..8417ab4 --- /dev/null +++ b/app/Models/Airport.php @@ -0,0 +1,33 @@ + 'float', + 'longitude_deg' => 'float', + 'elevation_ft' => 'integer', + ]; + + public function region(): BelongsTo + { + return $this->belongsTo(Region::class); + } +} diff --git a/database/migrations/2026_04_03_041232_create_airports_table.php b/database/migrations/2026_04_03_041232_create_airports_table.php new file mode 100644 index 0000000..25791c6 --- /dev/null +++ b/database/migrations/2026_04_03_041232_create_airports_table.php @@ -0,0 +1,128 @@ +id(); + $table->string('name'); + $table->decimal('latitude_deg', 10, 6); + $table->decimal('longitude_deg', 10, 6); + $table->integer('elevation_ft')->nullable(); + $table->foreignId('region_id')->constrained('regions'); + $table->string('municipality')->nullable(); + $table->string('icao_code')->nullable(); + $table->string('iata_code')->nullable(); + $table->string('local_code')->nullable(); + $table->string('type'); + $table->timestamps(); + }); + + $this->importCsv(); + } + + public function down(): void + { + Schema::dropIfExists('airports'); + } + + private function importCsv(): void + { + $path = storage_path('app/private/seed_data/airports.csv'); + + if (! file_exists($path)) { + throw new \RuntimeException("Airports CSV not found at: {$path}"); + } + + $handle = fopen($path, 'rb'); + + if ($handle === false) { + throw new \RuntimeException("Failed to open airports CSV at: {$path}"); + } + + // Skip header row + fgetcsv($handle); + + $regionMap = DB::table('regions')->pluck('id', 'code')->all(); + + $batch = []; + $batchSize = 500; + $now = now()->toDateTimeString(); + + while (($row = fgetcsv($handle)) !== false) { + if (count($row) < 19) { + continue; + } + + // id, ident, type, name, latitude_deg, longitude_deg, elevation_ft, + // continent, iso_country, iso_region, municipality, scheduled_service, + // icao_code, iata_code, gps_code, local_code, home_link, wikipedia_link, keywords + [ + , + , + $type, + $name, + $latitudeDeg, + $longitudeDeg, + $elevationFt, + , + , + $isoRegion, + $municipality, + , + $icaoCode, + $iataCode, + , + $localCode, + ] = $row; + + $icaoCode = trim(str_replace(["\r", "\n"], '', $icaoCode)); + $iataCode = trim(str_replace(["\r", "\n"], '', $iataCode)); + + // Skip rows without at least one of iata or icao + if ($icaoCode === '' && $iataCode === '') { + continue; + } + + $isoRegion = trim($isoRegion); + + if (! isset($regionMap[$isoRegion])) { + continue; + } + + $elevationFt = trim($elevationFt); + + $batch[] = [ + 'type' => trim($type), + 'name' => trim($name), + 'latitude_deg' => (float) trim($latitudeDeg), + 'longitude_deg' => (float) trim($longitudeDeg), + 'elevation_ft' => $elevationFt !== '' ? (int) $elevationFt : null, + 'region_id' => $regionMap[$isoRegion], + 'municipality' => trim($municipality) !== '' ? trim($municipality) : null, + 'icao_code' => $icaoCode !== '' ? $icaoCode : null, + 'iata_code' => $iataCode !== '' ? $iataCode : null, + 'local_code' => trim($localCode) !== '' ? trim($localCode) : null, + 'created_at' => $now, + 'updated_at' => $now, + ]; + + if (count($batch) >= $batchSize) { + DB::table('airports')->insert($batch); + $batch = []; + } + } + + fclose($handle); + + if (! empty($batch)) { + DB::table('airports')->insert($batch); + } + } +};