aipp 0.2.6 → 1.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 +0 -0
- data/CHANGELOG.md +21 -0
- data/README.md +147 -91
- data/exe/aip2aixm +2 -2
- data/exe/aip2ofmx +2 -2
- data/lib/aipp/aip.rb +96 -11
- data/lib/aipp/border.rb +77 -46
- data/lib/aipp/debugger.rb +101 -0
- data/lib/aipp/downloader.rb +18 -5
- data/lib/aipp/executable.rb +33 -20
- data/lib/aipp/parser.rb +42 -37
- data/lib/aipp/patcher.rb +5 -2
- data/lib/aipp/regions/LF/README.md +49 -0
- data/lib/aipp/regions/LF/aerodromes.rb +223 -0
- data/lib/aipp/regions/LF/d_p_r_airspaces.rb +56 -0
- data/lib/aipp/regions/LF/dangerous_activities.rb +49 -0
- data/lib/aipp/regions/LF/designated_points.rb +47 -0
- data/lib/aipp/regions/LF/fixtures/aerodromes.yml +608 -0
- data/lib/aipp/regions/LF/helipads.rb +122 -0
- data/lib/aipp/regions/LF/helpers/base.rb +167 -174
- data/lib/aipp/regions/LF/helpers/surface.rb +49 -0
- data/lib/aipp/regions/LF/helpers/usage_limitation.rb +20 -0
- data/lib/aipp/regions/LF/navigational_aids.rb +85 -0
- data/lib/aipp/regions/LF/obstacles.rb +153 -0
- data/lib/aipp/regions/LF/serviced_airspaces.rb +70 -0
- data/lib/aipp/regions/LF/services.rb +172 -0
- data/lib/aipp/t_hash.rb +3 -4
- data/lib/aipp/version.rb +1 -1
- data/lib/aipp.rb +7 -5
- data/lib/core_ext/enumerable.rb +2 -2
- data/lib/core_ext/hash.rb +21 -5
- data/lib/core_ext/nokogiri.rb +54 -0
- data/lib/core_ext/string.rb +32 -65
- data.tar.gz.sig +0 -0
- metadata +70 -81
- metadata.gz.sig +0 -0
- data/lib/aipp/airac.rb +0 -55
- data/lib/aipp/regions/LF/AD-1.3.rb +0 -177
- data/lib/aipp/regions/LF/AD-1.6.rb +0 -33
- data/lib/aipp/regions/LF/AD-2.rb +0 -344
- data/lib/aipp/regions/LF/AD-3.1.rb +0 -185
- data/lib/aipp/regions/LF/ENR-2.1.rb +0 -167
- data/lib/aipp/regions/LF/ENR-4.1.rb +0 -41
- data/lib/aipp/regions/LF/ENR-4.3.rb +0 -27
- data/lib/aipp/regions/LF/ENR-5.1.rb +0 -106
- data/lib/aipp/regions/LF/ENR-5.4.rb +0 -90
- data/lib/aipp/regions/LF/ENR-5.5.rb +0 -55
- data/lib/aipp/regions/LF/fixtures/AD-1.3.yml +0 -511
- data/lib/aipp/regions/LF/fixtures/AD-2.yml +0 -185
- data/lib/aipp/regions/LF/fixtures/AD-3.1.yml +0 -10
- data/lib/aipp/regions/LF/helpers/URL.rb +0 -26
- data/lib/aipp/regions/LF/helpers/navigational_aid.rb +0 -104
- data/lib/aipp/regions/LF/helpers/radio_AD.rb +0 -110
- data/lib/core_ext/object.rb +0 -43
@@ -0,0 +1,122 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
|
4
|
+
class Helipads < AIP
|
5
|
+
|
6
|
+
include AIPP::LF::Helpers::Base
|
7
|
+
include AIPP::LF::Helpers::UsageLimitation
|
8
|
+
include AIPP::LF::Helpers::Surface
|
9
|
+
|
10
|
+
DEPENDS = %w(aerodromes)
|
11
|
+
|
12
|
+
HOSTILITIES = {
|
13
|
+
'hostile habitée' => 'Zone hostile habitée / hostile populated area',
|
14
|
+
'hostile non habitée' => 'Zone hostile non habitée / hostile unpopulated area',
|
15
|
+
'non hostile' => 'Zone non hostile / non-hostile area'
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
ELEVATED = {
|
19
|
+
true => 'En terrasse / on deck',
|
20
|
+
false => 'En surface / on ground'
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
def parse
|
24
|
+
cache.helistation.css(%Q(Helistation[lk^="[LF]"])).each do |helistation_node|
|
25
|
+
# Build airport if necessary
|
26
|
+
next unless limitation_type = LIMITATION_TYPES.fetch(helistation_node.(:Statut))
|
27
|
+
name = helistation_node.(:Nom)
|
28
|
+
airport = find_by(:airport, name: name).first || add(
|
29
|
+
AIXM.airport(
|
30
|
+
source: source(section: 'AD', position: helistation_node.line),
|
31
|
+
organisation: organisation_lf,
|
32
|
+
id: options[:region],
|
33
|
+
name: name,
|
34
|
+
xy: xy_from(helistation_node.(:Geometrie))
|
35
|
+
).tap do |airport|
|
36
|
+
airport.z = AIXM.z(helistation_node.(:AltitudeFt).to_i, :qnh)
|
37
|
+
airport.add_usage_limitation(type: limitation_type.fetch(:limitation)) do |limitation|
|
38
|
+
limitation.remarks = limitation_type[:remarks]
|
39
|
+
[:private].each do |purpose| # TODO: check and simplify
|
40
|
+
limitation.add_condition do |condition|
|
41
|
+
condition.realm = limitation_type.fetch(:realm)
|
42
|
+
condition.origin = :any
|
43
|
+
condition.rule = case
|
44
|
+
when helistation_node.(:Ifr?) then :ifr_and_vfr
|
45
|
+
else :vfr
|
46
|
+
end
|
47
|
+
condition.purpose = purpose
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
)
|
53
|
+
# TODO: link to VAC once supported downstream
|
54
|
+
# # Link to VAC
|
55
|
+
# if helistation_node.(:Atlas?)
|
56
|
+
# vac = "VAC-#{airport.id}" if airport.id.match?(/^LF[A-Z]{2}$/)
|
57
|
+
# vac ||= "VACH-H#{airport.name[0, 3].upcase}"
|
58
|
+
# airport.remarks = [
|
59
|
+
# airport.remarks.to_s,
|
60
|
+
# link_to('VAC-HP', url_for(vac))
|
61
|
+
# ].join("\n")
|
62
|
+
# end
|
63
|
+
# Add helipad and FATO
|
64
|
+
airport.add_helipad(
|
65
|
+
AIXM.helipad(
|
66
|
+
name: 'TLOF',
|
67
|
+
xy: xy_from(helistation_node.(:Geometrie))
|
68
|
+
).tap do |helipad|
|
69
|
+
helipad.z = AIXM.z(helistation_node.(:AltitudeFt).to_i, :qnh)
|
70
|
+
helipad.dimensions = dimensions_from(helistation_node.(:DimTlof))
|
71
|
+
end.tap do |helipad|
|
72
|
+
airport.add_helipad(helipad)
|
73
|
+
helipad.performance_class = performance_class_from(helistation_node.(:ClassePerf))
|
74
|
+
helipad.surface = surface_from(helistation_node)
|
75
|
+
helipad.marking = helistation_node.(:Balisage) unless helistation_node.(:Balisage)&.match?(/^nil$/i)
|
76
|
+
helipad.add_lighting(AIXM.lighting(position: :other)) if helistation_node.(:Nuit?) || helistation_node.(:Balisage)&.match?(/feu/i)
|
77
|
+
helipad.remarks = {
|
78
|
+
'position/positioning' => [
|
79
|
+
(HOSTILITIES.fetch(helistation_node.(:ZoneHabitee)) if helistation_node.(:ZoneHabitee)),
|
80
|
+
(ELEVATED.fetch(helistation_node.(:EnTerrasse?)) if helistation_node.(:EnTerrasse)),
|
81
|
+
].compact.join("\n"),
|
82
|
+
'hauteur/height' => given(helistation_node.(:HauteurFt)) { "#{_1} ft" },
|
83
|
+
'exploitant/operator' => helistation_node.(:Exploitant)
|
84
|
+
}.to_remarks
|
85
|
+
if fato_dimensions = dimensions_from(helistation_node.(:DimFato))
|
86
|
+
AIXM.fato(name: 'FATO').tap do |fato|
|
87
|
+
fato.dimensions = fato_dimensions
|
88
|
+
airport.add_fato(fato)
|
89
|
+
helipad.fato = fato
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def dimensions_from(content)
|
100
|
+
if content
|
101
|
+
dims = content.remove(/[^x\d.,]/i).split(/x/i).map { _1.to_ff.floor }
|
102
|
+
case dims.size
|
103
|
+
when 1
|
104
|
+
AIXM.r(AIXM.d(dims[0], :m))
|
105
|
+
when 2
|
106
|
+
AIXM.r(AIXM.d(dims[0], :m), AIXM.d(dims[1], :m))
|
107
|
+
when 4
|
108
|
+
AIXM.r(AIXM.d(dims.min, :m))
|
109
|
+
else
|
110
|
+
warn("ignoring dimensions `#{content}'", severe: false)
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def performance_class_from(content)
|
117
|
+
content.remove(/\d{2,}/).scan(/\d/).map(&:to_i).min&.to_s if content
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -5,217 +5,210 @@ module AIPP
|
|
5
5
|
|
6
6
|
using AIXM::Refinements
|
7
7
|
|
8
|
-
#
|
9
|
-
|
10
|
-
'franco-allemande' => 'FRANCE_GERMANY',
|
11
|
-
'franco-espagnole' => 'FRANCE_SPAIN',
|
12
|
-
'franco-italienne' => 'FRANCE_ITALY',
|
13
|
-
'franco-suisse' => 'FRANCE_SWITZERLAND',
|
14
|
-
'franco-luxembourgeoise' => 'FRANCE_LUXEMBOURG',
|
15
|
-
'franco-belge' => 'BELGIUM_FRANCE',
|
16
|
-
'germano-suisse' => 'GERMANY_SWITZERLAND',
|
17
|
-
'hispano-andorrane' => 'ANDORRA_SPAIN',
|
18
|
-
'la côte atlantique française' => 'FRANCE_ATLANTIC_COAST',
|
19
|
-
'côte méditérrannéenne' => 'FRANCE_MEDITERRANEAN_COAST',
|
20
|
-
'limite des eaux territoriales atlantique françaises' => 'FRANCE_ATLANTIC_TERRITORIAL_SEA',
|
21
|
-
'parc national des écrins' => 'FRANCE_ECRINS_NATIONAL_PARK'
|
22
|
-
}.freeze
|
23
|
-
|
24
|
-
# Intersection points between three countries
|
25
|
-
INTERSECTIONS = {
|
26
|
-
'FRANCE_SPAIN|ANDORRA_SPAIN' => AIXM.xy(lat: 42.502720, long: 1.725965),
|
27
|
-
'ANDORRA_SPAIN|FRANCE_SPAIN' => AIXM.xy(lat: 42.603571, long: 1.442681),
|
28
|
-
'FRANCE_SWITZERLAND|FRANCE_ITALY' => AIXM.xy(lat: 45.922701, long: 7.044125),
|
29
|
-
'BELGIUM_FRANCE|FRANCE_LUXEMBOURG' => AIXM.xy(lat: 49.546428, long: 5.818415),
|
30
|
-
'FRANCE_LUXEMBOURG|FRANCE_GERMANY' => AIXM.xy(lat: 49.469438, long: 6.367516),
|
31
|
-
'FRANCE_GERMANY|FRANCE_SWITZERLAND' => AIXM.xy(lat: 47.589831, long: 7.589049),
|
32
|
-
'GERMANY_SWITZERLAND|FRANCE_GERMANY' => AIXM.xy(lat: 47.589831, long: 7.589049)
|
33
|
-
}.freeze
|
34
|
-
|
35
|
-
# Map surface to OFMX composition, preparation and remarks
|
36
|
-
SURFACES = {
|
37
|
-
/^revêtue?$/ => { preparation: :paved },
|
38
|
-
/^non revêtue?$/ => { preparation: :natural },
|
39
|
-
'macadam' => { composition: :macadam },
|
40
|
-
/^bitume ?(traité|psp)?$/ => { composition: :bitumen },
|
41
|
-
'ciment' => { composition: :concrete, preparation: :paved },
|
42
|
-
/^b[eéè]ton ?(armé|bitume|bitumineux)?$/ => { composition: :concrete, preparation: :paved },
|
43
|
-
/^béton( de)? ciment$/ => { composition: :concrete, preparation: :paved },
|
44
|
-
'béton herbe' => { composition: :concrete_and_grass },
|
45
|
-
'béton avec résine' => { composition: :concrete, preparation: :paved, remarks: 'Avec résine / with resin' },
|
46
|
-
"béton + asphalte d'étanchéité sablé" => { composition: :concrete_and_asphalt, preparation: :paved, remarks: 'Étanchéité sablé / sandblasted waterproofing' },
|
47
|
-
'béton armé + support bitumastic' => { composition: :concrete, preparation: :paved, remarks: 'Support bitumastic / bitumen support' },
|
48
|
-
/résine (époxy )?su[er] béton/ => { composition: :concrete, preparation: :paved, remarks: 'Avec couche résine / with resin seal coat' },
|
49
|
-
/^(asphalte|tarmac)$/ => { composition: :asphalt, preparation: :paved },
|
50
|
-
'enrobé' => { preparation: :other, remarks: 'Enrobé / coated' },
|
51
|
-
'enrobé anti-kérozène' => { preparation: :other, remarks: 'Enrobé anti-kérozène / anti-kerosene coating' },
|
52
|
-
/^enrobé bitum(e|iné|ineux)$/ => { composition: :bitumen, preparation: :paved, remarks: 'Enrobé / coated' },
|
53
|
-
'enrobé béton' => { composition: :concrete, preparation: :paved, remarks: 'Enrobé / coated' },
|
54
|
-
/^résine( époxy)?$/ => { composition: :other, remarks: 'Résine / resin' },
|
55
|
-
'tole acier larmé' => { composition: :metal, preparation: :grooved },
|
56
|
-
/^(structure métallique|aluminium)$/ => { composition: :metal },
|
57
|
-
'matériaux composites ignifugés' => { composition: :other, remarks: 'Matériaux composites ignifugés / fire resistant mixed materials' },
|
58
|
-
/^(gazon|herbe)$/ => { composition: :grass },
|
59
|
-
'neige' => { composition: :snow },
|
60
|
-
'neige damée' => { composition: :snow, preparation: :rolled }
|
61
|
-
}.freeze
|
62
|
-
|
63
|
-
# Transform French text fragments to English
|
64
|
-
ANGLICISE_MAP = {
|
65
|
-
/[^A-Z0-9 .\-]/ => '',
|
66
|
-
/0(\d)/ => '\1',
|
67
|
-
/(\d)-(\d)/ => '\1.\2',
|
68
|
-
/PARTIE/ => '',
|
69
|
-
/DELEG\./ => 'DELEG ',
|
70
|
-
/FRANCAISE?/ => 'FR',
|
71
|
-
/ANGLAISE?/ => 'UK',
|
72
|
-
/BELGE/ => 'BE',
|
73
|
-
/LUXEMBOURGEOISE?/ => 'LU',
|
74
|
-
/ALLEMANDE?/ => 'DE',
|
75
|
-
/SUISSE/ => 'CH',
|
76
|
-
/ITALIEN(?:NE)?/ => 'IT',
|
77
|
-
/ESPAGNOLE?/ => 'ES',
|
78
|
-
/ANDORRANE?/ => 'AD',
|
79
|
-
/NORD/ => 'N',
|
80
|
-
/EST/ => 'E',
|
81
|
-
/SUD/ => 'S',
|
82
|
-
/OEST/ => 'W',
|
83
|
-
/ANGLO NORMANDES/ => 'ANGLO-NORMANDES',
|
84
|
-
/ +/ => ' '
|
85
|
-
}.freeze
|
8
|
+
# Supported version of the XML_SIA database dump
|
9
|
+
VERSION = '5'.freeze
|
86
10
|
|
87
|
-
#
|
11
|
+
# Mandatory Interface
|
88
12
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
13
|
+
def setup
|
14
|
+
AIXM.config.voice_channel_separation = :any
|
15
|
+
unless cache.espace
|
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")
|
19
|
+
end
|
20
|
+
warn("XML_SIA database dump version mismatch") unless xml.at_css('SiaExport').attr(:Version) == VERSION
|
95
21
|
end
|
96
22
|
end
|
97
23
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
27
|
+
sia_url = "https://www.sia.aviation-civile.gouv.fr/dvd/eAIP_#{sia_date}"
|
28
|
+
case aip_file
|
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"
|
37
|
+
else # SIA XML database dump
|
38
|
+
"XML_SIA_#{xml_date}.xml"
|
103
39
|
end
|
104
40
|
end
|
105
41
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
42
|
+
# Templates
|
43
|
+
|
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"),
|
48
|
+
name: 'FRANCE',
|
49
|
+
type: 'S'
|
50
|
+
).tap do |organisation|
|
51
|
+
organisation.id = 'LF'
|
110
52
|
end
|
53
|
+
add cache.organisation_lf
|
111
54
|
end
|
55
|
+
cache.organisation_lf
|
112
56
|
end
|
113
57
|
|
114
|
-
#
|
115
|
-
|
116
|
-
|
117
|
-
|
58
|
+
# Parsersettes
|
59
|
+
|
60
|
+
# Build a source string
|
61
|
+
#
|
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
|
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
|
118
69
|
[
|
119
70
|
options[:region],
|
120
|
-
|
71
|
+
section,
|
121
72
|
aip_file,
|
122
73
|
options[:airac].date.xmlschema,
|
123
74
|
position
|
124
75
|
].join('|')
|
125
76
|
end
|
126
77
|
|
127
|
-
|
128
|
-
|
129
|
-
|
78
|
+
# Convert content to boolean
|
79
|
+
#
|
80
|
+
# @param content [String] either "oui" or "non"
|
81
|
+
# @return [Boolean]
|
82
|
+
def b_from(content)
|
83
|
+
case content
|
84
|
+
when 'oui' then true
|
85
|
+
when 'non' then false
|
86
|
+
else fail "`#{content}' is not boolean content"
|
87
|
+
end
|
130
88
|
end
|
131
89
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
when /FL(\d+)/ then AIXM.z($1.to_i, :qne)
|
140
|
-
else fail "z `#{limit}' not recognized"
|
141
|
-
end
|
90
|
+
# Build coordinates from content
|
91
|
+
#
|
92
|
+
# @param content [String] source content
|
93
|
+
# @return [AIXM::XY]
|
94
|
+
def xy_from(content)
|
95
|
+
parts = content.split(/[\s,]+/)
|
96
|
+
AIXM.xy(lat: parts[0].to_f, long: parts[1].to_f)
|
142
97
|
end
|
143
98
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
99
|
+
# Build altitude/elevation from value and unit
|
100
|
+
#
|
101
|
+
# @param value [String, Numeric, nil] numeric value
|
102
|
+
# @param unit [String] unit like "ft ASFC" or absolute like "SFC"
|
103
|
+
# @return [AIXM::Z]
|
104
|
+
def z_from(value: nil, unit: 'ft ASFC')
|
105
|
+
if value
|
106
|
+
case unit
|
107
|
+
when 'SFC' then AIXM::GROUND
|
108
|
+
when 'UNL' then AIXM::UNLIMITED
|
109
|
+
when 'ft ASFC' then AIXM.z(value.to_i, :qfe)
|
110
|
+
when 'ft AMSL' then AIXM.z(value.to_i, :qnh)
|
111
|
+
when 'FL' then AIXM.z(value.to_i, :qne)
|
112
|
+
else fail "z `#{[value, unit].join(' ')}' not recognized"
|
113
|
+
end
|
149
114
|
end
|
150
115
|
end
|
151
116
|
|
152
|
-
|
153
|
-
|
154
|
-
|
117
|
+
# Build distance from content
|
118
|
+
#
|
119
|
+
# @param content [String] source content
|
120
|
+
# @return [AIXM::D]
|
121
|
+
def d_from(content)
|
122
|
+
parts = content.split(/\s/)
|
123
|
+
AIXM.d(parts[0].to_f, parts[1])
|
155
124
|
end
|
156
125
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
upper_z: z_from(above[0]),
|
163
|
-
max_z: z_from(above[1]),
|
164
|
-
lower_z: z_from(below[0]),
|
165
|
-
min_z: z_from(below[1])
|
166
|
-
)
|
167
|
-
)
|
168
|
-
end
|
169
|
-
|
170
|
-
def geometry_from(text)
|
126
|
+
# Build geometry from content
|
127
|
+
#
|
128
|
+
# @param content [String] source content
|
129
|
+
# @return [AIXM::Component::Geometry]
|
130
|
+
def geometry_from(content)
|
171
131
|
AIXM.geometry.tap do |geometry|
|
172
132
|
buffer = {}
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
geometry.add_segment AIXM.border(
|
205
|
-
xy: buffer.delete(:xy),
|
206
|
-
name: border_name
|
133
|
+
content.split("\n").each do |element|
|
134
|
+
parts = element.split(',', 3).last.split(/[():,]/)
|
135
|
+
# Write explicit geometry from previous iteration
|
136
|
+
if (bordure_name, xy = buffer.delete(:fnt))
|
137
|
+
border = borders[bordure_name]
|
138
|
+
geometry.add_segments border.segment(
|
139
|
+
from_position: border.nearest(xy: xy),
|
140
|
+
to_position: border.nearest(xy: xy_from(parts[0]))
|
141
|
+
).map(&:to_point)
|
142
|
+
end
|
143
|
+
# Write current iteration
|
144
|
+
geometry.add_segment(
|
145
|
+
case parts[1]
|
146
|
+
when 'grc'
|
147
|
+
AIXM.point(
|
148
|
+
xy: xy_from(parts[0])
|
149
|
+
)
|
150
|
+
when 'rhl'
|
151
|
+
AIXM.rhumb_line(
|
152
|
+
xy: xy_from(parts[0])
|
153
|
+
)
|
154
|
+
when 'cwa', 'cca'
|
155
|
+
AIXM.arc(
|
156
|
+
xy: xy_from(parts[0]),
|
157
|
+
center_xy: xy_from(parts[5]),
|
158
|
+
clockwise: (parts[1] == 'cwa')
|
159
|
+
)
|
160
|
+
when 'cir'
|
161
|
+
AIXM.circle(
|
162
|
+
center_xy: xy_from(parts[0]),
|
163
|
+
radius: d_from(parts[3..4].join(' '))
|
207
164
|
)
|
165
|
+
when 'fnt'
|
166
|
+
bordure = cache.bordure.at_css(%Q(Bordure[pk="#{parts[3]}"]))
|
167
|
+
bordure_name = bordure.(:Code)
|
168
|
+
if bordure_name.match? /:/ # explicit geometry
|
169
|
+
borders[bordure_name] ||= AIPP::Border.from_array([bordure.(:Geometrie).split])
|
170
|
+
buffer[:fnt] = [bordure_name, xy_from(parts[2])]
|
171
|
+
AIXM.point(
|
172
|
+
xy: xy_from(parts[0])
|
173
|
+
)
|
174
|
+
else
|
175
|
+
AIXM.border( # named border
|
176
|
+
xy: xy_from(parts[0]),
|
177
|
+
name: bordure_name
|
178
|
+
)
|
179
|
+
end
|
180
|
+
else
|
181
|
+
fail "geometry `#{parts[1]}' not recognized"
|
208
182
|
end
|
209
|
-
|
210
|
-
fail "geometry `#{element}' not recognized"
|
211
|
-
end
|
183
|
+
)
|
212
184
|
end
|
213
185
|
end
|
214
186
|
end
|
215
187
|
|
216
|
-
|
217
|
-
|
218
|
-
|
188
|
+
# Build timetable from content
|
189
|
+
#
|
190
|
+
# @param content [String] source content
|
191
|
+
# @return [AIXM::Component::Timetable]
|
192
|
+
def timetable_from(content)
|
193
|
+
AIXM.timetable(code: content) if AIXM::H_RE.match? content
|
194
|
+
end
|
195
|
+
|
196
|
+
# Build layer from "volume" node
|
197
|
+
#
|
198
|
+
# @param volume_node [Nokogiri::XML::Element] source node
|
199
|
+
# @return [AIXM::Component::Layer]
|
200
|
+
def layer_from(volume_node)
|
201
|
+
AIXM.layer(
|
202
|
+
class: volume_node.(:Classe),
|
203
|
+
vertical_limit: AIXM.vertical_limit(
|
204
|
+
upper_z: z_from(value: volume_node.(:Plafond), unit: volume_node.(:PlafondRefUnite)),
|
205
|
+
max_z: z_from(value: volume_node.(:Plafond2)),
|
206
|
+
lower_z: z_from(value: volume_node.(:Plancher), unit: volume_node.(:PlancherRefUnite)),
|
207
|
+
min_z: z_from(value: volume_node.(:Plancher2))
|
208
|
+
)
|
209
|
+
).tap do |layer|
|
210
|
+
layer.timetable = timetable_from(volume_node.(:HorCode))
|
211
|
+
layer.remarks = volume_node.(:Remarque)
|
219
212
|
end
|
220
213
|
end
|
221
214
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
module Helpers
|
4
|
+
module Surface
|
5
|
+
|
6
|
+
# Map surface to OFMX composition, preparation and remarks
|
7
|
+
SURFACES = {
|
8
|
+
/^revêtue?$/ => { preparation: :paved },
|
9
|
+
/^non revêtue?$/ => { preparation: :natural },
|
10
|
+
'macadam' => { composition: :macadam },
|
11
|
+
/^bitume ?(traité|psp)?$/ => { composition: :bitumen },
|
12
|
+
'ciment' => { composition: :concrete, preparation: :paved },
|
13
|
+
/^b[eéè]ton ?(armé|bitume|bitumeux|bitumineux)?$/ => { composition: :concrete, preparation: :paved },
|
14
|
+
/^béton( de)? ciment$/ => { composition: :concrete, preparation: :paved },
|
15
|
+
'béton herbe' => { composition: :concrete_and_grass },
|
16
|
+
'béton avec résine' => { composition: :concrete, preparation: :paved, remarks: 'Avec résine / with resin' },
|
17
|
+
"béton + asphalte d'étanchéité sablé" => { composition: :concrete_and_asphalt, preparation: :paved, remarks: 'Étanchéité sablé / sandblasted waterproofing' },
|
18
|
+
'béton armé + support bitumastic' => { composition: :concrete, preparation: :paved, remarks: 'Support bitumastic / bitumen support' },
|
19
|
+
/résine (époxy )?su[er] béton/ => { composition: :concrete, preparation: :paved, remarks: 'Avec couche résine / with resin seal coat' },
|
20
|
+
/^(asphalte|tarmac)$/ => { composition: :asphalt, preparation: :paved },
|
21
|
+
'enrobé' => { preparation: :other, remarks: 'Enrobé / coated' },
|
22
|
+
'enrobé anti-kérozène' => { preparation: :other, remarks: 'Enrobé anti-kérozène / anti-kerosene coating' },
|
23
|
+
/^enrobé bitum(e|iné|ineux)$/ => { composition: :bitumen, preparation: :paved, remarks: 'Enrobé / coated' },
|
24
|
+
'enrobé béton' => { composition: :concrete, preparation: :paved, remarks: 'Enrobé / coated' },
|
25
|
+
/^résine( époxy)?$/ => { composition: :other, remarks: 'Résine / resin' },
|
26
|
+
'tole acier larmé' => { composition: :metal, preparation: :grooved },
|
27
|
+
/^(structure métallique|structure et caillebotis métallique|aluminium)$/ => { composition: :metal },
|
28
|
+
'matériaux composites ignifugés' => { composition: :other, remarks: 'Matériaux composites ignifugés / fire resistant mixed materials' },
|
29
|
+
/^(gazon|herbe)$/ => { composition: :grass },
|
30
|
+
'neige' => { composition: :snow },
|
31
|
+
'neige damée' => { composition: :snow, preparation: :rolled },
|
32
|
+
'surface en bois' => { composition: :wood }
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
def surface_from(node)
|
36
|
+
AIXM.surface.tap do |surface|
|
37
|
+
SURFACES.metch(node.(:Revetement), default: {}).tap do |surface_attributes|
|
38
|
+
surface.composition = surface_attributes[:composition]
|
39
|
+
surface.preparation = surface_attributes[:preparation]
|
40
|
+
surface.remarks = surface_attributes[:remarks]
|
41
|
+
end
|
42
|
+
surface.pcn = node.(:Resistance)&.first_match(AIXM::PCN_RE)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
module Helpers
|
4
|
+
module UsageLimitation
|
5
|
+
|
6
|
+
# Map limitation type descriptions to AIXM limitation, realm and remarks
|
7
|
+
LIMITATION_TYPES = {
|
8
|
+
'OFF' => nil, # skip decommissioned aerodromes/helistations
|
9
|
+
'CAP' => { limitation: :permitted, realm: :civilian },
|
10
|
+
'ADM' => { limitation: :permitted, realm: :other, remarks: "Goverment ACFT only / Réservé aux ACFT de l'État" },
|
11
|
+
'MIL' => { limitation: :permitted, realm: :military },
|
12
|
+
'PRV' => { limitation: :reservation_required, realm: :civilian },
|
13
|
+
'RST' => { limitation: :reservation_required, realm: :civilian },
|
14
|
+
'TPD' => { limitation: :reservation_required, realm: :civilian }
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
|
4
|
+
class NavigationalAids < AIP
|
5
|
+
|
6
|
+
include AIPP::LF::Helpers::Base
|
7
|
+
|
8
|
+
SOURCE_TYPES = {
|
9
|
+
'DME-ATT' => [:dme],
|
10
|
+
'TACAN' => [:tacan],
|
11
|
+
'VOR' => [:vor],
|
12
|
+
'VOR-DME' => [:vor, :dme],
|
13
|
+
'VORTAC' => [:vor, :tacan],
|
14
|
+
'NDB' => [:ndb]
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def parse
|
18
|
+
SOURCE_TYPES.each do |source_type, (primary_type, secondary_type)|
|
19
|
+
verbose_info("processing #{source_type}")
|
20
|
+
cache.navfix.css(%Q(NavFix[lk^="[LF][#{source_type} "])).each do |navfix_node|
|
21
|
+
attributes = {
|
22
|
+
source: source(section: 'ENR', position: navfix_node.line),
|
23
|
+
organisation: organisation_lf,
|
24
|
+
id: navfix_node.(:Ident),
|
25
|
+
xy: xy_from(navfix_node.(:Geometrie))
|
26
|
+
}
|
27
|
+
if radionav_node = cache.radionav.at_css(%Q(RadioNav:has(NavFix[pk="#{navfix_node.attr(:pk)}"])))
|
28
|
+
attributes.merge! send(primary_type, radionav_node)
|
29
|
+
add(
|
30
|
+
AIXM.send(primary_type, **attributes).tap do |navigational_aid|
|
31
|
+
navigational_aid.name = radionav_node.(:NomPhraseo) || radionav_node.(:Station)
|
32
|
+
navigational_aid.timetable = timetable_from(radionav_node.(:HorCode))
|
33
|
+
navigational_aid.remarks = {
|
34
|
+
"location/situation" => radionav_node.(:Situation),
|
35
|
+
"range/portée" => range_from(radionav_node)
|
36
|
+
}.to_remarks
|
37
|
+
navigational_aid.send("associate_#{secondary_type}") if secondary_type
|
38
|
+
end
|
39
|
+
)
|
40
|
+
else
|
41
|
+
verbose_info("skipping incomplete #{source_type} #{attributes[:id]}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def dme(radionav_node)
|
50
|
+
{
|
51
|
+
ghost_f: AIXM.f(radionav_node.(:Frequence).to_f, :mhz),
|
52
|
+
z: AIXM.z(radionav_node.(:AltitudeFt).to_i, :qnh)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
alias_method :tacan, :dme
|
56
|
+
|
57
|
+
def vor(radionav_node)
|
58
|
+
{
|
59
|
+
type: :conventional,
|
60
|
+
north: :magnetic,
|
61
|
+
name: radionav_node.(:Station),
|
62
|
+
f: AIXM.f(radionav_node.(:Frequence).to_f, :mhz),
|
63
|
+
z: AIXM.z(radionav_node.(:AltitudeFt).to_i, :qnh),
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def ndb(radionav_node)
|
68
|
+
{
|
69
|
+
type: :en_route,
|
70
|
+
f: AIXM.f(radionav_node.(:Frequence).to_f, :khz),
|
71
|
+
z: AIXM.z(radionav_node.(:AltitudeFt).to_i, :qnh)
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def range_from(radionav_node)
|
76
|
+
[
|
77
|
+
radionav_node.(:Portee).blank_to_nil&.concat('NM'),
|
78
|
+
radionav_node.(:FlPorteeVert).blank_to_nil&.prepend('FL'),
|
79
|
+
radionav_node.(:Couverture).blank_to_nil
|
80
|
+
].compact.join(' / ')
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|