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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +2 -2
- data/CHANGELOG.md +17 -1
- data/README.md +269 -150
- data/exe/aip2aixm +2 -8
- data/exe/aip2ofmx +2 -8
- data/exe/notam2aixm +5 -0
- data/exe/notam2ofmx +5 -0
- data/lib/aipp/aip/README.md +10 -0
- data/lib/aipp/aip/executable.rb +40 -0
- data/lib/aipp/aip/parser.rb +9 -0
- data/lib/aipp/aip/runner.rb +85 -0
- data/lib/aipp/border.rb +2 -2
- data/lib/aipp/debugger.rb +14 -19
- data/lib/aipp/downloader/file.rb +57 -0
- data/lib/aipp/downloader/graphql.rb +29 -0
- data/lib/aipp/downloader/http.rb +48 -0
- data/lib/aipp/downloader.rb +78 -29
- data/lib/aipp/environment.rb +88 -0
- data/lib/aipp/executable.rb +36 -53
- data/lib/aipp/notam/README.md +25 -0
- data/lib/aipp/notam/executable.rb +27 -0
- data/lib/aipp/notam/parser.rb +9 -0
- data/lib/aipp/notam/runner.rb +28 -0
- data/lib/aipp/parser.rb +133 -160
- data/lib/aipp/patcher.rb +4 -5
- data/lib/aipp/regions/LF/README.md +6 -2
- data/lib/aipp/regions/LF/aip/aerodromes.rb +220 -0
- data/lib/aipp/regions/LF/aip/d_p_r_airspaces.rb +53 -0
- data/lib/aipp/regions/LF/aip/dangerous_activities.rb +48 -0
- data/lib/aipp/regions/LF/aip/designated_points.rb +44 -0
- data/lib/aipp/regions/LF/aip/helipads.rb +119 -0
- data/lib/aipp/regions/LF/aip/navigational_aids.rb +82 -0
- data/lib/aipp/regions/LF/aip/obstacles.rb +150 -0
- data/lib/aipp/regions/LF/aip/serviced_airspaces.rb +67 -0
- data/lib/aipp/regions/LF/aip/services.rb +169 -0
- data/lib/aipp/regions/LF/fixtures/aerodromes.yml +2 -2
- data/lib/aipp/regions/LF/helpers/base.rb +32 -32
- data/lib/aipp/regions/LS/README.md +59 -0
- data/lib/aipp/regions/LS/helpers/base.rb +111 -0
- data/lib/aipp/regions/LS/notam/ENR.rb +173 -0
- data/lib/aipp/runner.rb +152 -0
- data/lib/aipp/version.rb +1 -1
- data/lib/aipp.rb +30 -11
- data/lib/core_ext/array.rb +13 -0
- data/lib/core_ext/nokogiri.rb +56 -8
- data/lib/core_ext/string.rb +63 -1
- data.tar.gz.sig +0 -0
- metadata +115 -64
- metadata.gz.sig +0 -0
- data/lib/aipp/aip.rb +0 -166
- data/lib/aipp/regions/LF/aerodromes.rb +0 -223
- data/lib/aipp/regions/LF/d_p_r_airspaces.rb +0 -56
- data/lib/aipp/regions/LF/dangerous_activities.rb +0 -49
- data/lib/aipp/regions/LF/designated_points.rb +0 -47
- data/lib/aipp/regions/LF/helipads.rb +0 -122
- data/lib/aipp/regions/LF/navigational_aids.rb +0 -85
- data/lib/aipp/regions/LF/obstacles.rb +0 -153
- data/lib/aipp/regions/LF/serviced_airspaces.rb +0 -70
- 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
|
@@ -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 |
|
18
|
-
cache[
|
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
|
25
|
-
sia_date = options
|
26
|
-
xml_date = options
|
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
|
28
|
+
case document
|
29
29
|
when /^Obstacles$/ # obstacles spreadsheet
|
30
|
-
"#{sia_url}/FRANCE/ObstaclesDataZone1MFRANCE_#{xml_date.remove('-')}.xlsx"
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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,
|
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
|
64
|
-
# @param
|
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:,
|
67
|
-
|
68
|
-
|
66
|
+
def source(position:, part: nil, document: nil)
|
67
|
+
document ||= 'XML_SIA'
|
68
|
+
part ||= document.split(/-(?=\d)/).first
|
69
69
|
[
|
70
|
-
options
|
71
|
-
|
72
|
-
|
73
|
-
options
|
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
|
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)
|