aipp 1.0.0 → 2.0.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -2
  3. data/CHANGELOG.md +17 -1
  4. data/README.md +269 -150
  5. data/exe/aip2aixm +2 -8
  6. data/exe/aip2ofmx +2 -8
  7. data/exe/notam2aixm +5 -0
  8. data/exe/notam2ofmx +5 -0
  9. data/lib/aipp/aip/README.md +10 -0
  10. data/lib/aipp/aip/executable.rb +40 -0
  11. data/lib/aipp/aip/parser.rb +9 -0
  12. data/lib/aipp/aip/runner.rb +85 -0
  13. data/lib/aipp/border.rb +2 -2
  14. data/lib/aipp/debugger.rb +14 -19
  15. data/lib/aipp/downloader/file.rb +57 -0
  16. data/lib/aipp/downloader/graphql.rb +29 -0
  17. data/lib/aipp/downloader/http.rb +48 -0
  18. data/lib/aipp/downloader.rb +78 -29
  19. data/lib/aipp/environment.rb +88 -0
  20. data/lib/aipp/executable.rb +36 -53
  21. data/lib/aipp/notam/README.md +25 -0
  22. data/lib/aipp/notam/executable.rb +27 -0
  23. data/lib/aipp/notam/parser.rb +9 -0
  24. data/lib/aipp/notam/runner.rb +28 -0
  25. data/lib/aipp/parser.rb +133 -160
  26. data/lib/aipp/patcher.rb +4 -5
  27. data/lib/aipp/regions/LF/README.md +6 -2
  28. data/lib/aipp/regions/LF/aip/aerodromes.rb +220 -0
  29. data/lib/aipp/regions/LF/aip/d_p_r_airspaces.rb +53 -0
  30. data/lib/aipp/regions/LF/aip/dangerous_activities.rb +48 -0
  31. data/lib/aipp/regions/LF/aip/designated_points.rb +44 -0
  32. data/lib/aipp/regions/LF/aip/helipads.rb +119 -0
  33. data/lib/aipp/regions/LF/aip/navigational_aids.rb +82 -0
  34. data/lib/aipp/regions/LF/aip/obstacles.rb +150 -0
  35. data/lib/aipp/regions/LF/aip/serviced_airspaces.rb +67 -0
  36. data/lib/aipp/regions/LF/aip/services.rb +169 -0
  37. data/lib/aipp/regions/LF/fixtures/aerodromes.yml +2 -2
  38. data/lib/aipp/regions/LF/helpers/base.rb +32 -32
  39. data/lib/aipp/regions/LS/README.md +59 -0
  40. data/lib/aipp/regions/LS/helpers/base.rb +111 -0
  41. data/lib/aipp/regions/LS/notam/ENR.rb +173 -0
  42. data/lib/aipp/runner.rb +152 -0
  43. data/lib/aipp/version.rb +1 -1
  44. data/lib/aipp.rb +30 -11
  45. data/lib/core_ext/array.rb +13 -0
  46. data/lib/core_ext/nokogiri.rb +56 -8
  47. data/lib/core_ext/string.rb +63 -1
  48. data.tar.gz.sig +0 -0
  49. metadata +115 -64
  50. metadata.gz.sig +0 -0
  51. data/lib/aipp/aip.rb +0 -166
  52. data/lib/aipp/regions/LF/aerodromes.rb +0 -223
  53. data/lib/aipp/regions/LF/d_p_r_airspaces.rb +0 -56
  54. data/lib/aipp/regions/LF/dangerous_activities.rb +0 -49
  55. data/lib/aipp/regions/LF/designated_points.rb +0 -47
  56. data/lib/aipp/regions/LF/helipads.rb +0 -122
  57. data/lib/aipp/regions/LF/navigational_aids.rb +0 -85
  58. data/lib/aipp/regions/LF/obstacles.rb +0 -153
  59. data/lib/aipp/regions/LF/serviced_airspaces.rb +0 -70
  60. data/lib/aipp/regions/LF/services.rb +0 -172
@@ -0,0 +1,150 @@
1
+ module AIPP::LF::AIP
2
+ class Obstacles < AIPP::AIP::Parser
3
+
4
+ include AIPP::LF::Helpers::Base
5
+
6
+ # Map type descriptions to AIXM types and remarks
7
+ TYPES = {
8
+ 'Antenne' => [:antenna],
9
+ 'Autre' => [:other],
10
+ 'Bâtiment' => [:building],
11
+ 'Câble' => [:other, 'Cable / Câble'],
12
+ 'Centrale thermique' => [:building, 'Thermal power plant / Centrale thermique'],
13
+ "Château d'eau" => [:tower, "Water tower / Château d'eau"],
14
+ 'Cheminée' => [:chimney],
15
+ 'Derrick' => [:tower, 'Derrick'],
16
+ 'Eglise' => [:tower, 'Church / Eglise'],
17
+ 'Eolienne' => [:wind_turbine],
18
+ 'Eolienne(s)' => [:wind_turbine],
19
+ 'Grue' => [:tower, 'Crane / Grue'],
20
+ 'Mât' => [:mast],
21
+ 'Phare marin' => [:tower, 'Lighthouse / Phare marin'],
22
+ 'Pile de pont' => [:other, 'Bridge piers / Pile de pont'],
23
+ 'Portique' => [:building, 'Arch / Portique'],
24
+ 'Pylône' => [:mast, 'Pylon / Pylône'],
25
+ 'Silo' => [:tower, 'Silo'],
26
+ 'Terril' => [:other, 'Spoil heap / Teril'],
27
+ 'Torchère' => [:chimney, 'Flare / Torchère'],
28
+ 'Tour' => [:tower],
29
+ 'Treillis métallique' => [:other, 'Metallic grid / Treillis métallique']
30
+ }.freeze
31
+
32
+ def parse
33
+ if AIPP.options.region_options.include? 'lf_obstacles_xlsx'
34
+ info("reading obstacles from XLSX")
35
+ @xlsx = read('Obstacles')
36
+ parse_from_xlsx
37
+ else
38
+ parse_from_xml
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def parse_from_xlsx
45
+ # Build obstacles
46
+ @xlsx.sheet(@xlsx.sheets.find(/^data/i).first).each(
47
+ name: 'IDENTIFICATEUR',
48
+ type: 'TYPE',
49
+ count: 'NOMBRE',
50
+ longitude: 'LONGITUDE DECIMALE',
51
+ latitude: 'LATITUDE DECIMALE',
52
+ elevation: 'ALTITUDE AU SOMMET',
53
+ height: 'HAUTEUR HORS SOL',
54
+ height_unit: 'UNITE',
55
+ horizontal_accuracy: 'PRECISION HORIZONTALE',
56
+ vertical_accuracy: 'PRECISION VERTICALE',
57
+ visibility: 'BALISAGE',
58
+ remarks: 'REMARK',
59
+ effective_on: 'DATE DE MISE EN VIGUEUR'
60
+ ).with_index(0) do |row, index|
61
+ next unless row[:effective_on].to_s.match? /\d{8}/
62
+ type, type_remarks = TYPES.fetch(row[:type])
63
+ count = row[:count].to_i
64
+ obstacle = AIXM.obstacle(
65
+ source: source(part: 'ENR', position: index),
66
+ name: row[:name],
67
+ type: type,
68
+ xy: AIXM.xy(lat: row[:latitude].to_f, long: row[:longitude].to_f),
69
+ z: AIXM.z(row[:elevation].to_i, :qnh)
70
+ ).tap do |obstacle|
71
+ obstacle.height = AIXM.d(row[:height].to_i, row[:height_unit])
72
+ if row[:horizontal_accuracy]
73
+ accuracy = row[:horizontal_accuracy].split
74
+ obstacle.xy_accuracy = AIXM.d(accuracy.first.to_i, accuracy.last)
75
+ end
76
+ if row[:vertical_accuracy]
77
+ accuracy = row[:horizontal_accuracy].split
78
+ obstacle.z_accuracy = AIXM.d(accuracy.first.to_i, accuracy.last)
79
+ end
80
+ obstacle.marking = row[:visibility].match?(/jour/i)
81
+ obstacle.lighting = row[:visibility].match?(/nuit/i)
82
+ obstacle.remarks = {
83
+ 'type' => type_remarks,
84
+ 'number/nombre' => (count if count > 1),
85
+ 'details' => row[:remarks],
86
+ 'effective/mise en vigueur' => (row[:effective_on].to_s.unpack("a4a2a2").join("-") if row[:updated_on])
87
+ }.to_remarks
88
+ # Group obstacles
89
+ if aixm.features.find_by(:obstacle, xy: obstacle.xy).any?
90
+ warn("duplicate obstacle #{obstacle.name}", severe: false)
91
+ else
92
+ if count > 1
93
+ obstacle_group = AIXM.obstacle_group(
94
+ source: obstacle.source,
95
+ name: obstacle.name
96
+ ).tap do |obstacle_group|
97
+ obstacle_group.remarks = "#{count} obstacles"
98
+ end
99
+ obstacle_group.add_obstacle obstacle
100
+ add obstacle_group
101
+ else
102
+ add obstacle
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def parse_from_xml
110
+ AIPP.cache.obstacle.css(%Q(Obstacle[lk^="[LF]"])).each do |node|
111
+ # Build obstacles
112
+ type, type_remarks = TYPES.fetch(node.(:TypeObst))
113
+ count = node.(:Combien).to_i
114
+ obstacle = AIXM.obstacle(
115
+ source: source(part: 'ENR', position: node.line),
116
+ name: node.(:NumeroNom),
117
+ type: type,
118
+ xy: xy_from(node.(:Geometrie)),
119
+ z: AIXM.z(node.(:AmslFt).to_i, :qnh)
120
+ ).tap do |obstacle|
121
+ obstacle.height = AIXM.d(node.(:AglFt).to_i, :ft)
122
+ obstacle.marking = node.(:Balisage).match?(/jour/i)
123
+ obstacle.lighting = node.(:Balisage).match?(/nuit/i)
124
+ obstacle.remarks = {
125
+ 'type' => type_remarks,
126
+ 'number/nombre' => (count if count > 1)
127
+ }.to_remarks
128
+ end
129
+ # Group obstacles
130
+ if aixm.features.find_by(:obstacle, xy: obstacle.xy).any?
131
+ warn("duplicate obstacle #{obstacle.name}", severe: false)
132
+ else
133
+ if count > 1
134
+ obstacle_group = AIXM.obstacle_group(
135
+ source: obstacle.source,
136
+ name: obstacle.name
137
+ ).tap do |obstacle_group|
138
+ obstacle_group.remarks = "#{count} obstacles"
139
+ end
140
+ obstacle_group.add_obstacle obstacle
141
+ add obstacle_group
142
+ else
143
+ add obstacle
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,67 @@
1
+ module AIPP::LF::AIP
2
+ class ServicedAirspaces < AIPP::AIP::Parser
3
+
4
+ include AIPP::LF::Helpers::Base
5
+
6
+ # Map source types to type and optional local type and skip regexp
7
+ SOURCE_TYPES = {
8
+ 'FIR' => { type: 'FIR' },
9
+ 'UIR' => { type: 'UIR' },
10
+ 'UTA' => { type: 'UTA' },
11
+ 'CTA' => { type: 'CTA' },
12
+ 'LTA' => { type: 'CTA', local_type: 'LTA' },
13
+ 'TMA' => { type: 'TMA', skip: /geneve/i }, # Geneva listed FYI only
14
+ 'SIV' => { type: 'SECTOR', local_type: 'FIZ/SIV' }, # providing FIS
15
+ 'CTR' => { type: 'CTR' },
16
+ 'RMZ' => { type: 'RAS', local_type: 'RMZ' },
17
+ 'TMZ' => { type: 'RAS', local_type: 'TMZ' },
18
+ 'RMZ-TMZ' => { type: 'RAS', local_type: 'RMZ-TMZ' }
19
+ }.freeze
20
+
21
+ # Map airspace "<type> <name>" to location indicator
22
+ FIR_LOCATION_INDICATORS = {
23
+ 'BORDEAUX' => 'LFBB',
24
+ 'BREST' => 'LFRR',
25
+ 'MARSEILLE' => 'LFMM',
26
+ 'PARIS' => 'LFFF',
27
+ 'REIMS' => 'LFRR'
28
+ }.freeze
29
+
30
+ def parse
31
+ SOURCE_TYPES.each do |source_type, target|
32
+ verbose_info("processing #{source_type}")
33
+ AIPP.cache.espace.css(%Q(Espace[lk^="[LF][#{source_type} "])).each do |espace_node|
34
+ # Skip all delegated airspaces
35
+ next if espace_node.(:Nom).match? /deleg/i
36
+ next if (re = target[:skip]) && espace_node.(:Nom).match?(re)
37
+ # Build airspaces and layers
38
+ AIPP.cache.partie.css(%Q(Partie:has(Espace[pk="#{espace_node['pk']}"]))).each do |partie_node|
39
+ add(
40
+ AIXM.airspace(
41
+ source: source(part: 'ENR', position: espace_node.line),
42
+ name: [
43
+ espace_node.(:Nom),
44
+ partie_node.(:NomPartie).remove(/^\.$/).blank_to_nil
45
+ ].compact.join(' '),
46
+ type: target[:type],
47
+ local_type: target[:local_type]
48
+ ).tap do |airspace|
49
+ airspace.meta = espace_node.attr('pk')
50
+ airspace.geometry = geometry_from(partie_node.(:Contour))
51
+ fail("geometry is not closed") unless airspace.geometry.closed?
52
+ AIPP.cache.volume.css(%Q(Volume:has(Partie[pk="#{partie_node['pk']}"]))).each do |volume_node|
53
+ airspace.add_layer(
54
+ layer_from(volume_node).tap do |layer|
55
+ layer.location_indicator = FIR_LOCATION_INDICATORS.fetch(airspace.name) if airspace.type == :flight_information_region
56
+ end
57
+ )
58
+ end
59
+ end
60
+ )
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,169 @@
1
+ module AIPP::LF::AIP
2
+ class Services < AIPP::AIP::Parser
3
+
4
+ include AIPP::LF::Helpers::Base
5
+
6
+ depends_on :Aerodromes, :ServicedAirspaces
7
+
8
+ # Service types and how to treat them
9
+ SOURCE_TYPES = {
10
+ 'A/A' => :address,
11
+ 'AFIS' => :service,
12
+ 'APP' => :service,
13
+ 'ATIS' => :service,
14
+ 'CCM' => :ignore, # centre de contrôle militaire
15
+ 'CEV' => :ignore, # centre d’essai en vol
16
+ 'D-ATIS' => :ignore, # data link ATIS
17
+ 'FIS' => :service,
18
+ 'PAR' => :service,
19
+ 'SRE' => :ignore, # surveillance radar element of PAR
20
+ 'TWR' => :service,
21
+ 'UAC' => :ignore, # upper area control
22
+ 'VDF' => :service,
23
+ 'OTHER' => :service # no <Service> specified at source
24
+ }.freeze
25
+
26
+ # Map French callsigns to English and service type
27
+ CALLSIGNS = {
28
+ 'Approche' => { en: 'Approach', service_type: 'APP' },
29
+ 'Contrôle' => { en: 'Control', service_type: 'ACS' },
30
+ 'Information' => { en: 'Information', service_type: 'FIS' },
31
+ 'GCA' => { en: 'GCA', service_type: 'GCA' },
32
+ 'Gonio' => { en: 'Gonio', service_type: 'VDF' },
33
+ 'Prévol' => { en: 'Delivery', service_type: 'SMC' },
34
+ 'Sol' => { en: 'Ground', service_type: 'SMC' },
35
+ 'Tour' => { en: 'Tower', service_type: 'TWR' },
36
+ 'Trafic' => { en: 'Apron', service_type: 'SMC' }
37
+ }.freeze
38
+
39
+ def parse
40
+ AIPP.cache.service.css(%Q(Service[lk^="[LF]"][pk])).each do |service_node|
41
+ # Ignore private services/frequencies
42
+ next if service_node.(:IndicLieu) == 'XX'
43
+ # Load referenced airport
44
+ airport = given(service_node.at_css('Ad')&.attr('pk')) do
45
+ find_by(:airport, meta: _1).first
46
+ end
47
+ # Build addresses and services
48
+ case SOURCE_TYPES.fetch(type_for(service_node))
49
+ when :address
50
+ fail "dangling address without airport" unless airport
51
+ addresses_from(service_node).each { airport.add_address(_1) }
52
+ when :service
53
+ given service_from(service_node) do |service|
54
+ AIPP.cache.frequence.css(%Q(Frequence:has(Service[pk="#{service_node['pk']}"]))).each do |frequence_node|
55
+ if frequency = frequency_from(frequence_node, service_node)
56
+ service.add_frequency frequency
57
+ end
58
+ end
59
+ if airport
60
+ airport.add_unit(service.unit) if airport.units.find(service.unit).none?
61
+ airport.add_service(service) if airport.services.find(service).none?
62
+ end
63
+ given service_node.at_css('Espace')&.attr('pk') do |espace_pk|
64
+ find_by(:airspace, meta: espace_pk).each do |airspace|
65
+ airspace.layers.each { _1.add_service(service) }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ # Assign fallback address (default A/A frequency) to all yet radioless airports
72
+ find_by(:airport).each do |airport|
73
+ unless airport.services.find_by(:service, type: :aerodrome_control_tower_service).any? || airport.addresses.any?
74
+ airport.add_address(fallback_address_for(airport.name))
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def type_for(service_node)
82
+ SOURCE_TYPES.include?(type = service_node.(:Service)) ? type : 'OTHER'
83
+ end
84
+
85
+ def addresses_from(service_node)
86
+ AIPP.cache.frequence.css(%Q(Frequence:has(Service[pk="#{service_node['pk']}"]))).map do |frequence_node|
87
+ if frequency = frequency_from(frequence_node, service_node)
88
+ AIXM.address(
89
+ type: :radio_frequency,
90
+ address: frequency.transmission_f
91
+ ).tap do |address|
92
+ address.remarks = {
93
+ 'type' => service_node.(:Service),
94
+ 'indicatif/callsign' => frequency.callsigns.map { "#{_2} (#{_1})" }.join("/n")
95
+ }.to_remarks
96
+ end
97
+ end
98
+ end.compact
99
+ end
100
+
101
+ def fallback_address_for(callsign)
102
+ AIXM.address(
103
+ type: :radio_frequency,
104
+ address: AIXM.f(123.5, :mhz)
105
+ ).tap do |address|
106
+ address.remarks = {
107
+ 'type' => 'A/A',
108
+ 'indicatif/callsign' => callsign
109
+ }.to_remarks
110
+ end
111
+ end
112
+
113
+ def service_from(service_node)
114
+ service_type = CALLSIGNS.dig(service_node.(:IndicService), :service_type) || type_for(service_node)
115
+ service = find_by(:service, type: service_type).first
116
+ unit = service&.unit
117
+ unless service
118
+ service = AIXM.service(type: service_type)
119
+ unit = find_by(:unit, name: service_node.(:IndicLieu), type: service.guessed_unit_type).first
120
+ unless unit
121
+ unit = AIXM.unit(
122
+ source: source(part: 'GEN', position: service_node.line),
123
+ organisation: organisation_lf,
124
+ name: service_node.(:IndicLieu),
125
+ type: service.guessed_unit_type,
126
+ class: :icao
127
+ )
128
+ add unit
129
+ end
130
+ unit.add_service service
131
+ service
132
+ end
133
+ end
134
+
135
+ def frequency_from(frequence_node, service_node)
136
+ frequency = frequence_node.(:Frequence).to_f
137
+ case
138
+ when frequency >= 137
139
+ nil
140
+ when frequency < 108
141
+ warn("ignoring too low frequency `#{frequency}'", severe: false)
142
+ nil
143
+ else
144
+ AIXM.frequency(
145
+ transmission_f: AIXM.f(frequency, :mhz),
146
+ callsigns: callsigns_from(service_node)
147
+ ).tap do |frequency|
148
+ frequency.timetable = timetable_from(frequence_node.(:HorCode))
149
+ frequency.remarks = frequence_node.(:Remarque)
150
+ end
151
+ end
152
+ end
153
+
154
+ def callsigns_from(service_node)
155
+ if service_node.(:IndicService) == '.' # auto-information
156
+ %i(fr en).to_h { [_1, '(auto)'] }
157
+ else
158
+ warn("language other than french") unless service_node.(:Langue) == 'fr'
159
+ english = CALLSIGNS.fetch(service_node.(:IndicService)).fetch(:en)
160
+ warn("no english translation for callsign `#{service_node.(:IndicService)}'") unless english
161
+ {
162
+ fr: "#{service_node.(:IndicLieu)} #{service_node.(:IndicService)}",
163
+ en: ("#{service_node.(:IndicLieu)} #{english}" if english)
164
+ }.compact
165
+ end
166
+ end
167
+
168
+ end
169
+ end
@@ -185,9 +185,9 @@ LFDS:
185
185
  "28R":
186
186
  xy: 44°47'29.9"N 1°14'56.5"E
187
187
  LFDU:
188
- "07":
188
+ "06":
189
189
  xy: 45°11'42.8"N 0°53'17.3"W
190
- "25":
190
+ "24":
191
191
  xy: 45°11'55.8"N 0°52'40.4"W
192
192
  LFDX:
193
193
  "17":
@@ -12,47 +12,47 @@ module AIPP
12
12
 
13
13
  def setup
14
14
  AIXM.config.voice_channel_separation = :any
15
- unless cache.espace
15
+ unless AIPP.cache.espace
16
16
  xml = read('XML_SIA')
17
- %i(Ad Bordure Espace Frequence Helistation NavFix Obstacle Partie RadioNav Rwy RwyLgt Service Volume).each do |section|
18
- cache[section.downcase] = xml.css("#{section}S")
17
+ %i(Ad Bordure Espace Frequence Helistation NavFix Obstacle Partie RadioNav Rwy RwyLgt Service Volume).each do |table|
18
+ AIPP.cache[table.downcase] = xml.css("#{table}S")
19
19
  end
20
20
  warn("XML_SIA database dump version mismatch") unless xml.at_css('SiaExport').attr(:Version) == VERSION
21
21
  end
22
22
  end
23
23
 
24
- def url_for(aip_file)
25
- sia_date = options[:airac].date.strftime('%d_%^b_%Y') # 04_JAN_2018
26
- xml_date = options[:airac].date.xmlschema # 2018-01-04
24
+ def origin_for(document)
25
+ sia_date = AIPP.options.airac.date.strftime('%d_%^b_%Y') # 04_JAN_2018
26
+ xml_date = AIPP.options.airac.date.xmlschema # 2018-01-04
27
27
  sia_url = "https://www.sia.aviation-civile.gouv.fr/dvd/eAIP_#{sia_date}"
28
- case aip_file
28
+ case document
29
29
  when /^Obstacles$/ # obstacles spreadsheet
30
- "#{sia_url}/FRANCE/ObstaclesDataZone1MFRANCE_#{xml_date.remove('-')}.xlsx"
31
- when /^VAC\-(\w+)/ # aerodrome VAC PDF
32
- "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VAC/AD/AD-2.#{$1}.pdf"
33
- when /^VACH\-(\w+)/ # helipad VAC PDF
34
- "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VACH/AD/AD-3.#{$1}.pdf"
35
- when /^[A-Z]+-/ # eAIP HTML page (e.g. ENR-5.5)
36
- "#{sia_url}/FRANCE/AIRAC-#{xml_date}/html/eAIP/FR-#{aip_file}-fr-FR.html"
30
+ AIPP::Downloader::HTTP.new(file: "#{sia_url}/FRANCE/ObstaclesDataZone1MFRANCE_#{xml_date.remove('-')}.xlsx")
31
+ # when /^VAC\-(\w+)/ # aerodrome VAC PDF
32
+ # AIPP::Downloader::HTTP.new(file: "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VAC/AD/AD-2.#{$1}.pdf")
33
+ # when /^VACH\-(\w+)/ # helipad VAC PDF
34
+ # AIPP::Downloader::HTTP.new(file: "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VACH/AD/AD-3.#{$1}.pdf")
35
+ # when /^[A-Z]+-/ # eAIP HTML page (e.g. ENR-5.5)
36
+ # AIPP::Downloader::HTTP.new(file: "#{sia_url}/FRANCE/AIRAC-#{xml_date}/html/eAIP/FR-#{document}-fr-FR.html")
37
37
  else # SIA XML database dump
38
- "XML_SIA_#{xml_date}.xml"
38
+ AIPP::Downloader::File.new(file: "XML_SIA_#{xml_date}.xml")
39
39
  end
40
40
  end
41
41
 
42
42
  # Templates
43
43
 
44
44
  def organisation_lf
45
- unless cache.organisation_lf
46
- cache.organisation_lf = AIXM.organisation(
47
- source: source(position: 1, aip_file: "GEN-3.1"),
45
+ unless AIPP.cache.organisation_lf
46
+ AIPP.cache.organisation_lf = AIXM.organisation(
47
+ source: source(position: 1, document: "GEN-3.1"),
48
48
  name: 'FRANCE',
49
49
  type: 'S'
50
50
  ).tap do |organisation|
51
51
  organisation.id = 'LF'
52
52
  end
53
- add cache.organisation_lf
53
+ add AIPP.cache.organisation_lf
54
54
  end
55
- cache.organisation_lf
55
+ AIPP.cache.organisation_lf
56
56
  end
57
57
 
58
58
  # Parsersettes
@@ -60,17 +60,17 @@ module AIPP
60
60
  # Build a source string
61
61
  #
62
62
  # @param position [Integer] line on which to find the information
63
- # @param section [String] override autodetected section (e.g. "ENR")
64
- # @param aip_file [String] override autodetected aip_file
63
+ # @param part [String] override autodetected part (e.g. "ENR")
64
+ # @param document [String] override autodetected document (e.g. "ENR-2.1")
65
65
  # @return [String] source string
66
- def source(position:, section: nil, aip_file: nil)
67
- aip_file ||= 'XML_SIA'
68
- section ||= aip_file.split(/-(?=\d)/).first
66
+ def source(position:, part: nil, document: nil)
67
+ document ||= 'XML_SIA'
68
+ part ||= document.split(/-(?=\d)/).first
69
69
  [
70
- options[:region],
71
- section,
72
- aip_file,
73
- options[:airac].date.xmlschema,
70
+ AIPP.options.region,
71
+ part,
72
+ document,
73
+ AIPP.options.airac.date.xmlschema,
74
74
  position
75
75
  ].join('|')
76
76
  end
@@ -134,7 +134,7 @@ module AIPP
134
134
  parts = element.split(',', 3).last.split(/[():,]/)
135
135
  # Write explicit geometry from previous iteration
136
136
  if (bordure_name, xy = buffer.delete(:fnt))
137
- border = borders[bordure_name]
137
+ border = AIPP.borders.send(bordure_name)
138
138
  geometry.add_segments border.segment(
139
139
  from_position: border.nearest(xy: xy),
140
140
  to_position: border.nearest(xy: xy_from(parts[0]))
@@ -163,10 +163,10 @@ module AIPP
163
163
  radius: d_from(parts[3..4].join(' '))
164
164
  )
165
165
  when 'fnt'
166
- bordure = cache.bordure.at_css(%Q(Bordure[pk="#{parts[3]}"]))
166
+ bordure = AIPP.cache.bordure.at_css(%Q(Bordure[pk="#{parts[3]}"]))
167
167
  bordure_name = bordure.(:Code)
168
168
  if bordure_name.match? /:/ # explicit geometry
169
- borders[bordure_name] ||= AIPP::Border.from_array([bordure.(:Geometrie).split])
169
+ AIPP.borders[bordure_name] ||= AIPP::Border.from_array([bordure.(:Geometrie).split])
170
170
  buffer[:fnt] = [bordure_name, xy_from(parts[2])]
171
171
  AIXM.point(
172
172
  xy: xy_from(parts[0])
@@ -0,0 +1,59 @@
1
+ # LS – Switzerland
2
+
3
+ ## Neway API
4
+
5
+ The NOTAM messages are fetched from the Neway API which is a non-public
6
+ GraphQL API.
7
+
8
+ You have to set the following environment variables:
9
+
10
+ * `NEWAY_API_URL` – API endpoint
11
+ * `NEWAY_API_AUTHORIZATION` – the bearer authentication token
12
+
13
+ The following query object shows all parameters and columns:
14
+
15
+ ```
16
+ {
17
+ queryNOTAMs(
18
+ filter: {
19
+ country: "CHE", # country as detected by ICAO
20
+ series: ["W", "B"], # NOTAM series (first letter of name)
21
+ region: "LS", # FIR region (extracted from name)
22
+ start: 1651449600, # time window begins (UTC timestamp)
23
+ end: 1651535999 # time window ends (UTC timestamp)
24
+ }
25
+ ) {
26
+ id # internal ID
27
+ name # NOTAM name
28
+ notamRaw # raw NOTAM message
29
+ series # NOTAM series (first letter of name)
30
+ region # FIR region (extracted from name)
31
+ country # country as detected by ICAO
32
+ area # NOTAM topic area
33
+ effectiveFrom # validity begins (UTC timestamp)
34
+ validUntil # validity ends (UTC timestamp)
35
+ }
36
+ }
37
+ ```
38
+
39
+ ## Asynchronous Use
40
+
41
+ ### Command Line Arguments
42
+
43
+ When used asynchronously, the following command line arguments should be considered:
44
+
45
+ * `-c` – clear cache (mandatory to create builds more often than once per hour)
46
+ * `-f` – continue on non-fatal errors such as if the validation fails
47
+ * `-q` – suppress all informational output
48
+ * `-x` – crossload fixed versions of malformed NOTAM
49
+
50
+ ### Monitoring
51
+
52
+ Any output on STDERR should trigger an alert.
53
+
54
+ ## References
55
+
56
+ * [skybriefing](https://www.skybriefing.com)
57
+ * [AIM Data Catalogue](https://www.aerodatacat.ch)
58
+ * [DABS](https://www.skybriefing.com/de/dabs)
59
+ * [NOTAM Info](https://notaminfo.com/switzerlandmap)