aipp 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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)