trackberry-airports
Every airport with an IATA code (about 9,000), with its name, city, position and ISO 3166-2 subdivision, as a table shipped inside the gem. No network calls.
require "trackberry/airports"
Trackberry::Airports.find("FRA") # => #<data Trackberry::Airports::Airport iata="FRA", icao="EDDF", ...>
Trackberry::Airports.city("FRA") # => "Frankfurt"
Trackberry::Airports.short_name("LIM") # => "Lima Jorge Chávez"
Trackberry::Airports.country("FRA") # => "DE"
Trackberry::Airports.coordinates("FRA") # => { "lat" => 50.0267, "lng" => 8.5584 }
Trackberry::Airports.subdivision_path("SXB") # => ["FR-67", "FR-6AE", "FR-GES"]An unknown code answers nil (or [] for a path): carriers send city codes ("LON") and codes of airports that have closed, and that is ordinary, not an error.
Why not the airports gem
airports is the established gem for this, and it does the plain lookup well. This one exists for what it does not do:
-
Fresh data.
airportsis built from OpenFlights, which stopped updating in 2019, and adds the airports opened since by hand (BER,HYD). This one is built from OurAirports, which is still maintained, and rebuilt every month behind checks that stop a bad download from shipping. -
Where the airport is, inside its country.
airportsgives the country as a name ("United Kingdom"). This one gives the ISO 3166-2 country code and subdivision path, which is what a regional holiday calendar or a customs rule keys on. - Numbers as numbers. Coordinates are floats, not strings.
airports has time zones; this one does not. If that is all you need, use it.
Subdivisions
subdivision_path is the ISO 3166-2 code of the area the airport is in and every code above it, most specific first, the same shape seaports gives for a port. Hand it to a holiday calendar that matches a holiday at any level of the path, such as Trackberry's own (not published):
Trackberry::Holidays.holiday?(Date.new(2027, 5, 27), Trackberry::Airports.subdivision_path("MUC")) # => true, Corpus Christi in Bavaria
Trackberry::Holidays.holiday?(Date.new(2027, 5, 27), Trackberry::Airports.subdivision_path("HAM")) # => false, not in HamburgWhere the code comes from, best first (the subdivision_source column says which):
| Source | Airports | Example |
|---|---|---|
override: a reviewed list, data/sources/subdivision_overrides.csv
|
0 | |
boundaries: the airport's position on Natural Earth's states-and-provinces map |
831 | Strasbourg FR-67, Beijing CN-BJ
|
ourairports: OurAirports' iso_region
|
7,903 | Frankfurt DE-HE, Hamburg DE-HH
|
The map is used two ways:
-
To go below OurAirports. OurAirports stops at the top level: Grand Est for Strasbourg. When the map finds an area inside that code, the airport gets the longer path: Bas-Rhin, inside the European Collectivity of Alsace, inside Grand Est. Some rules are kept at those levels. Alsace and Moselle have two public holidays the rest of France does not, which a calendar would key on
FR-6AEandFR-57; Nager.Date does not list them yet. When the map disagrees with OurAirports, OurAirports wins. The map answers with the area containing the airport, and only for an airport outside every area (built on reclaimed land, past the drawn coastline) with the nearest one within 10 km. -
To fill a gap. OurAirports still writes some codes ISO has replaced, such as China's pre-2017 numeric ones, and
XX-U-Afor "unassigned".
8,734 of 9,051 airports are placed. The 317 left have no subdivision because both sources still use codes ISO has retired (Poland, Norway, South Africa, Czechia, Kazakhstan) or the territory has none (French Polynesia, Puerto Rico). None of them is in a country whose public holidays differ by region, so a holiday lookup by country is complete for them.
Every code is checked against ISO 3166-2 when the table is built. The ISO hierarchy and the map lookup are seaports' Seaports::Subdivisions and Seaports::Boundaries, used by the build only: the gem itself depends on neither.
Cities
city is the city the airport serves, the one a carrier, a courier or a person names: "Brussels", "Athens", "Leipzig". municipality is the town it stands in, as OurAirports gives it: "Zaventem", "Spata-Artemida", "Schkeuditz". They are the same for most airports.
The served city comes from OpenFlights for the 6,000 airports it lists. OpenFlights stopped updating in 2019, so its cities were extracted once into data/sources/served_cities.csv by script/extract_served_cities.rb, from a pinned commit. A city carries over only when OpenFlights puts the airport within 50 km of OurAirports' position, so a code IATA has since given to another airport does not bring the old city with it. Every other airport takes its municipality, without a trailing note in brackets: "Helsinki", not "Helsinki (Vantaa)".
The subdivision always follows where the airport is, not the city it serves: Cincinnati's airport is in Kentucky (US-KY), not Ohio.
Short names
short_name is the label to show beside a code when the city alone is not enough ("London" is five airports): "London Heathrow", "Lima Jorge Chávez", "Dubai". It is the airport's name without "Airport", "International" and their translations, with the city put in front when what is left does not already say it (compared without accents or case).
Where that reads badly (an official name that runs to eight words, a repeated word, a dotted İ), a reviewed label wins: data/sources/short_name_overrides.csv, as iata,short_name,note. Like the subdivision overrides it is a build input, not shipped; the label is built into the short_name column of the table, so a monthly refresh keeps it. Each refresh lists the rows whose airport has gone or that the rule now labels the same, so they can be removed. An unknown code answers nil.
Data
OurAirports (public domain), filtered to airports with an IATA code that are not closed. When two rows share a code, the one with scheduled flights wins, then the larger airport. Coordinates are rounded to four decimal places (about ten metres).
Served cities: OpenFlights, © OpenFlights contributors, available under the Open Database License.
License
The code is MIT. The table (data/airports.csv) is a derivative of OpenFlights' database, so it is under the ODbL 1.0: a database you build from it and use publicly has to credit OpenFlights and be offered under the ODbL too. Using the gem to look airports up does not. See LICENSE.txt.
Refresh
.github/workflows/refresh.yml runs on the 3rd of every month:
-
rake airports:refreshdownloads OurAirports, the ISO 3166-2 list and the Natural Earth map, and rebuildsdata/airports.csv. -
Trackberry::Airports::Diffchecks the result before anything ships:- at least 8,500 airports;
- the table shrinks by no more than 2%;
- airports with a subdivision fall by no more than 2%;
- no more than 2% move to another principal subdivision;
- two dozen major airports (FRA, LHR, JFK, LIM…) are all present;
- fourteen of them, in countries whose holidays differ by region, keep their principal subdivision (FRA in
DE-HE, MUC inDE-BY…), because one country's codes breaking stays under the percentage caps.
- If it passes, the job opens a pull request listing every added, removed, renamed, moved, re-citied and resubdivided airport, and starts CI on it. If it fails, or a download fails, it opens an issue instead.
Merging a refresh bumps the minor version, and .github/workflows/release.yml publishes it to rubygems.org.
To rebuild from files already on disk: bundle exec rake "airports:refresh[airports.csv,iso_3166-2.json,admin1.geojson]".
Install
gem "trackberry-airports", "~> 0.1", require: "trackberry/airports"