iata 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +24 -0
- data/README.adoc +142 -0
- data/lib/iata/coordinates.rb +45 -0
- data/lib/iata/data/airports.json +81467 -0
- data/lib/iata/data/fetcher.rb +140 -0
- data/lib/iata/data.rb +7 -0
- data/lib/iata/entry.rb +43 -0
- data/lib/iata/loader.rb +64 -0
- data/lib/iata/registry.rb +136 -0
- data/lib/iata/version.rb +5 -0
- data/lib/iata.rb +43 -0
- metadata +87 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
|
|
8
|
+
module Iata
|
|
9
|
+
module Data
|
|
10
|
+
# Downloads the IATA airport code list from Wikidata via SPARQL and
|
|
11
|
+
# writes it to `lib/iata/data/airports.json` as a single JSON object
|
|
12
|
+
# keyed by IATA code.
|
|
13
|
+
#
|
|
14
|
+
# Data source: Wikidata property P238 ("IATA airport code"). Each
|
|
15
|
+
# result is an airport with its English label, ISO 3166-1 alpha-2
|
|
16
|
+
# country code, and WGS-84 coordinates.
|
|
17
|
+
module Fetcher
|
|
18
|
+
SPARQL_ENDPOINT = 'https://query.wikidata.org/sparql'
|
|
19
|
+
USER_AGENT = 'metanorma-iata-gem/0.1 (https://github.com/metanorma/iata)'
|
|
20
|
+
DATA_DIR = File.expand_path(__dir__)
|
|
21
|
+
OUTPUT_PATH = File.join(DATA_DIR, 'airports.json')
|
|
22
|
+
|
|
23
|
+
QUERY = <<~SPARQL
|
|
24
|
+
SELECT ?iata ?name ?country ?countryIso2 ?coord ?wdId WHERE {
|
|
25
|
+
?airport wdt:P238 ?iata .
|
|
26
|
+
?airport wdt:P17 ?country .
|
|
27
|
+
?airport rdfs:label ?name . FILTER(LANG(?name) = "en")
|
|
28
|
+
OPTIONAL { ?country wdt:P297 ?countryIso2 . }
|
|
29
|
+
OPTIONAL { ?airport wdt:P625 ?coord . }
|
|
30
|
+
BIND(STRAFTER(STR(?airport), "/entity/") AS ?wdId)
|
|
31
|
+
}
|
|
32
|
+
ORDER BY ?iata
|
|
33
|
+
SPARQL
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
# @return [String] the path written
|
|
37
|
+
def call
|
|
38
|
+
results = query
|
|
39
|
+
data = transform(results)
|
|
40
|
+
write(data, fetched_at: Time.now.utc.iso8601, result_count: results.size)
|
|
41
|
+
warn "Fetched #{data.size} IATA airports from Wikidata (#{File.size(OUTPUT_PATH)} bytes)"
|
|
42
|
+
OUTPUT_PATH
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Array<Hash>] raw SPARQL bindings, each as a hash of {key: {value:, type:}}
|
|
46
|
+
def query
|
|
47
|
+
uri = URI(SPARQL_ENDPOINT)
|
|
48
|
+
uri.query = URI.encode_www_form(
|
|
49
|
+
query: QUERY,
|
|
50
|
+
format: 'json'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
54
|
+
http.use_ssl = (uri.scheme == 'https')
|
|
55
|
+
http.open_timeout = 30
|
|
56
|
+
http.read_timeout = 180
|
|
57
|
+
|
|
58
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
|
59
|
+
request['User-Agent'] = USER_AGENT
|
|
60
|
+
request['Accept'] = 'application/sparql-results+json'
|
|
61
|
+
|
|
62
|
+
response = http.request(request)
|
|
63
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
64
|
+
raise "Wikidata SPARQL query failed: HTTP #{response.code} #{response.message}\n#{response.body[0..500]}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
JSON.parse(response.body, symbolize_names: true)[:results][:bindings]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @param bindings [Array<Hash>] SPARQL result bindings
|
|
71
|
+
# @return [Hash<String, Hash>] keyed by IATA code
|
|
72
|
+
def transform(bindings)
|
|
73
|
+
data = {}
|
|
74
|
+
bindings.each do |row|
|
|
75
|
+
code = row[:iata]&.dig(:value)
|
|
76
|
+
next if code.nil?
|
|
77
|
+
|
|
78
|
+
data[code] = build_entry(row)
|
|
79
|
+
end
|
|
80
|
+
data
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def write(data, fetched_at:, result_count:)
|
|
84
|
+
FileUtils.mkdir_p(DATA_DIR)
|
|
85
|
+
payload = {
|
|
86
|
+
'_meta' => {
|
|
87
|
+
'fetched_at' => fetched_at,
|
|
88
|
+
'source' => 'Wikidata (property P238)',
|
|
89
|
+
'result_count' => result_count,
|
|
90
|
+
'entry_count' => data.size
|
|
91
|
+
}
|
|
92
|
+
}.merge(data)
|
|
93
|
+
File.write(OUTPUT_PATH, JSON.pretty_generate(payload))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def build_entry(row)
|
|
99
|
+
{
|
|
100
|
+
'code' => row[:iata]&.dig(:value),
|
|
101
|
+
'name' => row[:name]&.dig(:value),
|
|
102
|
+
'wikidata_id' => row[:wdId]&.dig(:value),
|
|
103
|
+
'country_iso2' => row[:countryIso2]&.dig(:value),
|
|
104
|
+
'country_name' => extract_country_label(row[:country]),
|
|
105
|
+
'latitude' => extract_lat(row[:coord]),
|
|
106
|
+
'longitude' => extract_lon(row[:coord])
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def extract_country_label(country_binding)
|
|
111
|
+
return nil unless country_binding
|
|
112
|
+
|
|
113
|
+
uri = country_binding[:value].to_s
|
|
114
|
+
# Use the entity's last URL segment as a placeholder; the human
|
|
115
|
+
# name comes from the rdfs:label via SERVICE wikibase:label
|
|
116
|
+
# which we don't include in the query. The dedicated
|
|
117
|
+
# `unlocode-iso3166` gem is the proper way to resolve a full
|
|
118
|
+
# country name; this is just a hint.
|
|
119
|
+
uri.split('/').last
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def extract_lat(coord_binding)
|
|
123
|
+
return nil unless coord_binding
|
|
124
|
+
|
|
125
|
+
point = coord_binding[:value].to_s
|
|
126
|
+
match = point.match(/Point\(([-\d.]+)\s+([-\d.]+)/)
|
|
127
|
+
match && match[2].to_f
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def extract_lon(coord_binding)
|
|
131
|
+
return nil unless coord_binding
|
|
132
|
+
|
|
133
|
+
point = coord_binding[:value].to_s
|
|
134
|
+
match = point.match(/Point\(([-\d.]+)\s+([-\d.]+)/)
|
|
135
|
+
match && match[1].to_f
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
data/lib/iata/data.rb
ADDED
data/lib/iata/entry.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
require_relative 'coordinates'
|
|
5
|
+
|
|
6
|
+
module Iata
|
|
7
|
+
# A single IATA airport entry.
|
|
8
|
+
#
|
|
9
|
+
# Stores wire-level fields as `lutaml-model` attributes (so the bundled
|
|
10
|
+
# JSON can be parsed round-trip) and exposes typed helpers (#coordinates,
|
|
11
|
+
# #country_name) for ergonomic queries.
|
|
12
|
+
class Entry < Lutaml::Model::Serializable
|
|
13
|
+
attribute :code, :string
|
|
14
|
+
attribute :name, :string
|
|
15
|
+
attribute :wikidata_id, :string
|
|
16
|
+
attribute :country_iso2, :string
|
|
17
|
+
attribute :country_name, :string
|
|
18
|
+
attribute :latitude, :float
|
|
19
|
+
attribute :longitude, :float
|
|
20
|
+
|
|
21
|
+
def coordinates
|
|
22
|
+
return Coordinates.new(latitude: nil, longitude: nil) if latitude.nil? && longitude.nil?
|
|
23
|
+
|
|
24
|
+
Coordinates.new(latitude: latitude, longitude: longitude)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def ==(other)
|
|
28
|
+
other.is_a?(Entry) && code == other.code
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def hash
|
|
32
|
+
code&.hash || super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def eql?(other)
|
|
36
|
+
self == other
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_s
|
|
40
|
+
"#{code} #{name}".strip
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/iata/loader.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative 'entry'
|
|
5
|
+
|
|
6
|
+
module Iata
|
|
7
|
+
# Parses the bundled IATA airport JSON file into {Entry} instances.
|
|
8
|
+
#
|
|
9
|
+
# File format:
|
|
10
|
+
#
|
|
11
|
+
# {
|
|
12
|
+
# "_meta": {
|
|
13
|
+
# "fetched_at": "2026-07-02T12:00:00Z",
|
|
14
|
+
# "source": "Wikidata (P238)",
|
|
15
|
+
# "count": 12345
|
|
16
|
+
# },
|
|
17
|
+
# "PVG": {
|
|
18
|
+
# "code": "PVG",
|
|
19
|
+
# "name": "Shanghai Pudong International Airport",
|
|
20
|
+
# "wikidata_id": "Q86792",
|
|
21
|
+
# "country_iso2": "CN",
|
|
22
|
+
# "country_name": "China",
|
|
23
|
+
# "latitude": 31.1434,
|
|
24
|
+
# "longitude": 121.8052
|
|
25
|
+
# },
|
|
26
|
+
# ...
|
|
27
|
+
# }
|
|
28
|
+
class Loader
|
|
29
|
+
class << self
|
|
30
|
+
# @param path [String]
|
|
31
|
+
# @return [Array<Iata::Entry>]
|
|
32
|
+
def load_file(path)
|
|
33
|
+
load_json(File.read(path))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param json [String]
|
|
37
|
+
# @return [Array<Iata::Entry>]
|
|
38
|
+
def load_json(json)
|
|
39
|
+
parse(JSON.parse(json, symbolize_names: false))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @param data [Hash]
|
|
43
|
+
# @return [Array<Iata::Entry>]
|
|
44
|
+
def parse(data)
|
|
45
|
+
entries = data.is_a?(Hash) ? data.except('_meta') : {}
|
|
46
|
+
entries.map { |_code, attrs| build_entry(attrs) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def build_entry(attrs)
|
|
52
|
+
Entry.new(
|
|
53
|
+
code: attrs['code'],
|
|
54
|
+
name: attrs['name'],
|
|
55
|
+
wikidata_id: attrs['wikidata_id'],
|
|
56
|
+
country_iso2: attrs['country_iso2'],
|
|
57
|
+
country_name: attrs['country_name'],
|
|
58
|
+
latitude: attrs['latitude'],
|
|
59
|
+
longitude: attrs['longitude']
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require_relative 'loader'
|
|
5
|
+
|
|
6
|
+
module Iata
|
|
7
|
+
# In-memory, lazily-indexed registry over a set of {Entry} instances.
|
|
8
|
+
#
|
|
9
|
+
# The default registry is loaded from the vendored dataset bundled with the
|
|
10
|
+
# gem (see {.load_default}). Callers can also construct a registry from any
|
|
11
|
+
# other source via {.from_entries} or {.load_file}.
|
|
12
|
+
class Registry
|
|
13
|
+
include Enumerable
|
|
14
|
+
extend Forwardable
|
|
15
|
+
|
|
16
|
+
attr_reader :entries
|
|
17
|
+
|
|
18
|
+
def_delegators :@entries, :size, :count, :to_a, :empty?
|
|
19
|
+
|
|
20
|
+
# Map of `#where` filter keys to the Entry attribute they read.
|
|
21
|
+
# `name` is intentionally NOT in this map — it gets routed through
|
|
22
|
+
# filter_name so Regexp / substring matching works (see apply_filter).
|
|
23
|
+
SCALAR_FILTERS = {
|
|
24
|
+
code: :code,
|
|
25
|
+
country: :country_iso2,
|
|
26
|
+
country_iso2: :country_iso2,
|
|
27
|
+
wikidata_id: :wikidata_id
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
def initialize(entries = [])
|
|
31
|
+
@entries = entries.freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def each(&)
|
|
35
|
+
@entries.each(&)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# Load the bundled dataset shipped inside the gem.
|
|
40
|
+
# @return [Registry]
|
|
41
|
+
def load_default
|
|
42
|
+
from_entries(Loader.load_file(default_data_path))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Load a specific JSON file from disk.
|
|
46
|
+
# @param path [String]
|
|
47
|
+
# @return [Registry]
|
|
48
|
+
def load_file(path)
|
|
49
|
+
from_entries(Loader.load_file(path))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Build a registry from an existing list of entries.
|
|
53
|
+
# @param entries [Array<Iata::Entry>]
|
|
54
|
+
# @return [Registry]
|
|
55
|
+
def from_entries(entries)
|
|
56
|
+
new(entries)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def default_data_path
|
|
62
|
+
File.expand_path('data/airports.json', __dir__)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Exact-code lookup.
|
|
67
|
+
# @param code [String] 3-letter IATA code (case-insensitive)
|
|
68
|
+
# @return [Iata::Entry, nil]
|
|
69
|
+
def find(code)
|
|
70
|
+
return nil if code.nil?
|
|
71
|
+
|
|
72
|
+
by_code[code.to_s.upcase]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
alias [] find
|
|
76
|
+
|
|
77
|
+
# Filter entries by one or more predicates. Scalar filters accept either
|
|
78
|
+
# a single value or an array (any-of). `name` accepts a String
|
|
79
|
+
# (case-insensitive equality) or a Regexp.
|
|
80
|
+
#
|
|
81
|
+
# @example
|
|
82
|
+
# registry.where(country: 'CN')
|
|
83
|
+
# registry.where(country: %w[CN HK], name: /international/i)
|
|
84
|
+
#
|
|
85
|
+
# @return [Array<Iata::Entry>]
|
|
86
|
+
def where(filters)
|
|
87
|
+
filters.reduce(entries) { |scope, (key, value)| apply_filter(scope, key, value) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# All distinct country codes present in the registry, sorted.
|
|
91
|
+
# @return [Array<String>]
|
|
92
|
+
def countries
|
|
93
|
+
entries.map(&:country_iso2).compact.uniq.sort
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Count of entries per country.
|
|
97
|
+
# @return [Hash{String=>Integer}]
|
|
98
|
+
def counts_by_country
|
|
99
|
+
entries.each_with_object(Hash.new(0)) { |e, h| h[e.country_iso2] += 1 if e.country_iso2 }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def by_code
|
|
105
|
+
@by_code ||= entries.each_with_object({}) do |e, h|
|
|
106
|
+
h[e.code.to_s.upcase] = e if e.code
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def apply_filter(scope, key, value)
|
|
111
|
+
if SCALAR_FILTERS.key?(key)
|
|
112
|
+
filter_scalar(scope, SCALAR_FILTERS.fetch(key), value)
|
|
113
|
+
elsif key == :name
|
|
114
|
+
filter_name(scope, value)
|
|
115
|
+
else
|
|
116
|
+
raise ArgumentError, "unknown filter: #{key.inspect}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def filter_scalar(scope, attr_name, value)
|
|
121
|
+
candidates = Array(value).map { |v| v.to_s.upcase }
|
|
122
|
+
scope.select do |e|
|
|
123
|
+
attr_val = e.public_send(attr_name)
|
|
124
|
+
attr_val && candidates.include?(attr_val.to_s.upcase)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def filter_name(scope, value)
|
|
129
|
+
scope.select { |e| e.name && name_matches?(e.name, value) }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def name_matches?(string, value)
|
|
133
|
+
value.is_a?(Regexp) ? string.match?(value) : string.casecmp?(value.to_s)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
data/lib/iata/version.rb
ADDED
data/lib/iata.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require 'lutaml/model'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
require_relative 'iata/version'
|
|
8
|
+
|
|
9
|
+
# Vendored IATA airport code list as a queryable Ruby registry.
|
|
10
|
+
#
|
|
11
|
+
# The data is sourced from Wikidata (property P238, "IATA airport code") and
|
|
12
|
+
# ships inside the gem as a small JSON file so the registry works offline.
|
|
13
|
+
# All entries are loaded lazily on first call to {.registry}.
|
|
14
|
+
module Iata
|
|
15
|
+
extend SingleForwardable
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
# @return [Iata::Registry] the process-wide registry, loaded lazily
|
|
19
|
+
def registry
|
|
20
|
+
@registry ||= Registry.load_default
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Reset the process-wide registry. Used by specs to swap fixtures.
|
|
24
|
+
def reset_registry!
|
|
25
|
+
@registry = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The Wikidata query timestamp bundled with this gem version
|
|
29
|
+
# (UTC ISO8601). nil if the data file lacks this metadata.
|
|
30
|
+
# @return [String, nil]
|
|
31
|
+
def source_timestamp
|
|
32
|
+
@source_timestamp ||= registry.entries.first&.source_timestamp
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def_delegators :registry, :find, :where, :each, :size, :count, :countries, :counts_by_country
|
|
37
|
+
|
|
38
|
+
autoload :Coordinates, 'iata/coordinates'
|
|
39
|
+
autoload :Entry, 'iata/entry'
|
|
40
|
+
autoload :Loader, 'iata/loader'
|
|
41
|
+
autoload :Registry, 'iata/registry'
|
|
42
|
+
autoload :Data, 'iata/data'
|
|
43
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: iata
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ribose Inc.
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: json
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.6'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.6'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: lutaml-model
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.8'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.8'
|
|
40
|
+
description: |
|
|
41
|
+
Vendored, offline access to the IATA (International Air Transport
|
|
42
|
+
Association) airport code list, sourced from Wikidata. Provides a
|
|
43
|
+
model-driven Ruby registry for looking up airports by IATA code,
|
|
44
|
+
country, or name.
|
|
45
|
+
email:
|
|
46
|
+
- open.source@ribose.com
|
|
47
|
+
executables: []
|
|
48
|
+
extensions: []
|
|
49
|
+
extra_rdoc_files: []
|
|
50
|
+
files:
|
|
51
|
+
- LICENSE
|
|
52
|
+
- README.adoc
|
|
53
|
+
- lib/iata.rb
|
|
54
|
+
- lib/iata/coordinates.rb
|
|
55
|
+
- lib/iata/data.rb
|
|
56
|
+
- lib/iata/data/airports.json
|
|
57
|
+
- lib/iata/data/fetcher.rb
|
|
58
|
+
- lib/iata/entry.rb
|
|
59
|
+
- lib/iata/loader.rb
|
|
60
|
+
- lib/iata/registry.rb
|
|
61
|
+
- lib/iata/version.rb
|
|
62
|
+
homepage: https://github.com/metanorma/iata
|
|
63
|
+
licenses:
|
|
64
|
+
- BSD-2-Clause
|
|
65
|
+
metadata:
|
|
66
|
+
homepage_uri: https://github.com/metanorma/iata
|
|
67
|
+
source_code_uri: https://github.com/metanorma/iata
|
|
68
|
+
bug_tracker_uri: https://github.com/metanorma/iata/issues
|
|
69
|
+
rubygems_mfa_required: 'true'
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 3.1.0
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 3.6.9
|
|
85
|
+
specification_version: 4
|
|
86
|
+
summary: IATA airport codes as a queryable Ruby registry
|
|
87
|
+
test_files: []
|