aipp 0.2.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -1,33 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# Aerodromes radiocommunication facilities (VFR only)
|
5
|
-
class AD16 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Base
|
8
|
-
include AIPP::LF::Helpers::RadioAD
|
9
|
-
|
10
|
-
DEPENDS = %w(AD-1.3)
|
11
|
-
|
12
|
-
ID_FIXES = {
|
13
|
-
'LF04' => 'LF9004', # illegal ID as per AIXM
|
14
|
-
'LFPY' => nil # decommissioned - see https://fr.wikipedia.org/wiki/Base_a%C3%A9rienne_217_Br%C3%A9tigny-sur-Orge
|
15
|
-
}
|
16
|
-
|
17
|
-
def parse
|
18
|
-
document = prepare(html: read)
|
19
|
-
document.css('tbody').each do |tbody|
|
20
|
-
tbody.css('tr').group_by_chunks { _1.attr(:id).match?(/-TXT_NAME-/) }.each do |tr, trs|
|
21
|
-
trs = Nokogiri::XML::NodeSet.new(document, trs) # convert array to node set
|
22
|
-
id = tr.css('span[id*="CODE_ICAO"]').text.cleanup
|
23
|
-
next unless id = ID_FIXES.fetch(id, id)
|
24
|
-
@airport = find_by(:airport, id: id).first
|
25
|
-
addresses_from(trs).each { @airport.add_address(_1) }
|
26
|
-
units_from(trs, airport: @airport).each(&method(:add))
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
data/lib/aipp/regions/LF/AD-2.rb
DELETED
@@ -1,344 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# Airports (IFR capable) and their CTR, AD navigational aids etc
|
5
|
-
class AD2 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Base
|
8
|
-
include AIPP::LF::Helpers::NavigationalAid
|
9
|
-
include AIPP::LF::Helpers::RadioAD
|
10
|
-
using AIXM::Refinements
|
11
|
-
|
12
|
-
# Map source types to type and optional local type
|
13
|
-
SOURCE_TYPES = {
|
14
|
-
'CTR' => { type: 'CTR' },
|
15
|
-
'RMZ' => { type: 'RAS', local_type: 'RMZ' },
|
16
|
-
'TMZ' => { type: 'RAS', local_type: 'TMZ' },
|
17
|
-
'RMZ-TMZ' => { type: 'RAS', local_type: 'RMZ-TMZ' }
|
18
|
-
}.freeze
|
19
|
-
|
20
|
-
# Airports without VAC (e.g. military installations)
|
21
|
-
NO_VAC = %w(LFOA LFBC LFQE LFOE LFSX LFBM LFSO LFMO LFQP LFSI LFKS LFPV).freeze
|
22
|
-
|
23
|
-
# Airports without VFR reporting points
|
24
|
-
# TODO: designated points on map but no list (LFLD LFSN LFBS) or no AD info (LFRL)
|
25
|
-
NO_DESIGNATED_POINTS = %w(LFAB LFAC LFAV LFAY LFBK LFBN LFBX LFCC LFCI LFCK LFCY LFDH LFDJ LFDN LFEC LFFK LFEV LFEY LFGA LFHP LFHV LFHY LFJR LFJY LFLA LFLH LFLO LFLV LFLW LFMQ LFMQ LFNB LFOH LFOQ LFOU LFOV LFOZ LFPO LFQA LFQB LFQG LFQM LFRC LFRI LFRM LFRT LFRU LFSD LFSG LFSM LFLD LFSN LFBS LFRL).freeze
|
26
|
-
|
27
|
-
# Map synonyms for +correlate+
|
28
|
-
SYNONYMS = [
|
29
|
-
'nord', 'north',
|
30
|
-
'est', 'east',
|
31
|
-
'sud', 'south',
|
32
|
-
'ouest', 'west',
|
33
|
-
'inst', 'instruction',
|
34
|
-
'junction', 'intersection',
|
35
|
-
'harbour', 'port',
|
36
|
-
'mouth', 'embouchure',
|
37
|
-
'tower', 'chateau'
|
38
|
-
].freeze
|
39
|
-
|
40
|
-
def parse
|
41
|
-
index_html = prepare(html: read("AD-0.6")) # index for AD-2.xxxx files
|
42
|
-
index_html.css('#AD-0\.6\.eAIP > .toc-block:nth-of-type(3) .toc-block a').each do |a|
|
43
|
-
@id = a.attribute('href').value[-4,4]
|
44
|
-
begin
|
45
|
-
aip_file = "AD-2.#{@id}"
|
46
|
-
html = prepare(html: read(aip_file))
|
47
|
-
# Airport
|
48
|
-
@remarks = []
|
49
|
-
@airport = AIXM.airport(
|
50
|
-
source: source(position: html.css('tr[id*="CODE_ICAO"]').first.line, aip_file: aip_file),
|
51
|
-
organisation: organisation_lf, # TODO: not yet implemented
|
52
|
-
id: @id,
|
53
|
-
name: html.css('tr[id*="CODE_ICAO"] td span:nth-of-type(2)').text.strip.uptrans,
|
54
|
-
xy: xy_from(html.css('#AD-2\.2-Position_Geo_Arp td:nth-of-type(3)').text)
|
55
|
-
).tap do |airport|
|
56
|
-
airport.z = elevation_from(html.css('#AD-2\.2-Altitude_Reference td:nth-of-type(3)').text)
|
57
|
-
airport.declination = declination_from(html.css('#AD-2\.2-Declinaison_Magnetique td:nth-of-type(3)').text)
|
58
|
-
# airport.transition_z = AIXM.z(5000, :qnh) # TODO: default - exceptions may exist
|
59
|
-
airport.timetable = timetable_from!(html.css('#AD-2\.3-Gestionnaire_AD td:nth-of-type(3)').text)
|
60
|
-
end
|
61
|
-
runways_from(html.css('div[id*="-AD-2\.12"] tbody')).each { @airport.add_runway(_1) if _1 }
|
62
|
-
helipads_from(html.css('div[id*="-AD-2\.16"] tbody')).each { @airport.add_helipad(_1) if _1 }
|
63
|
-
text = html.css('#AD-2\.2-Observations td:nth-of-type(3)').text
|
64
|
-
@airport.remarks = ([remarks_from(text)] + @remarks).compact.join("\n\n").blank_to_nil
|
65
|
-
add @airport
|
66
|
-
# Airspaces
|
67
|
-
airspaces_from(html.css('div[id*="-AD-2\.17"] tbody')).
|
68
|
-
reject { aixm.features.find_by(_1.class, type: _1.type, id: _1.id).any? }.
|
69
|
-
each(&method(:add))
|
70
|
-
# Radio
|
71
|
-
trs = html.css('div[id*="-AD-2\.18"] tbody tr')
|
72
|
-
addresses_from(trs).each { @airport.add_address(_1) }
|
73
|
-
units_from(trs, airport: @airport).each(&method(:add))
|
74
|
-
# Navigational aids
|
75
|
-
navigational_aids_from(html.css('div[id*="-AD-2\.19"] tbody')).
|
76
|
-
reject { aixm.features.find_by(_1.class, id: _1.id, xy: _1.xy).any? }.
|
77
|
-
each(&method(:add))
|
78
|
-
# Designated points
|
79
|
-
unless NO_VAC.include?(@id) || NO_DESIGNATED_POINTS.include?(@id)
|
80
|
-
pdf = read("VAC-#{@id}")
|
81
|
-
designated_points_from(pdf).tap do |designated_points|
|
82
|
-
fix_designated_point_remarks(designated_points)
|
83
|
-
# debug(designated_points)
|
84
|
-
designated_points.
|
85
|
-
uniq(&:to_uid).
|
86
|
-
reject { aixm.features.find_by(_1.class, id: _1.id, xy: _1.xy).any? }.
|
87
|
-
each(&method(:add))
|
88
|
-
end
|
89
|
-
end
|
90
|
-
rescue => error
|
91
|
-
warn("error parsing airport #{@id}: #{error.message}", pry: error)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
private
|
97
|
-
|
98
|
-
def declination_from(text)
|
99
|
-
value, direction = text.strip.split('°')
|
100
|
-
value = value.to_f * (direction == 'W' ? -1 : 1)
|
101
|
-
end
|
102
|
-
|
103
|
-
def remarks_from(text)
|
104
|
-
text.sub(/NIL|\(\*\)\s+/, '').strip.gsub(/(\s)\s+/, '\1').blank_to_nil
|
105
|
-
end
|
106
|
-
|
107
|
-
def runways_from(tbody)
|
108
|
-
directions_map = tbody.css('tr[id*="TXT_DESIG"]').map do |tr|
|
109
|
-
[AIXM.a(tr.css('td:first-of-type').text.strip), tr]
|
110
|
-
end.to_h
|
111
|
-
remarks_map = tbody.css('tr[id*="TXT_RMK_NAT"]').map do |tr|
|
112
|
-
[tr.text.strip[/\A\((\d+)\)/, 1].to_i, tr.css('span')]
|
113
|
-
end.to_h
|
114
|
-
directions = directions_map.keys
|
115
|
-
grouped_directions = directions.map do |direction|
|
116
|
-
inverted_direction = direction.invert
|
117
|
-
if directions.include? inverted_direction
|
118
|
-
[direction, inverted_direction].map(&:to_s).sort.join('/')
|
119
|
-
else
|
120
|
-
direction.to_s
|
121
|
-
end
|
122
|
-
end.uniq
|
123
|
-
grouped_directions.map do |runway_name|
|
124
|
-
AIXM.runway(name: runway_name).tap do |runway|
|
125
|
-
%i(forth back).each do |direction_attr|
|
126
|
-
if direction = runway.send(direction_attr)
|
127
|
-
tr = directions_map[direction.name]
|
128
|
-
if direction_attr == :forth
|
129
|
-
length, width = tr.css('td:nth-of-type(3)').text.strip.split('x')
|
130
|
-
runway.length = AIXM.d(length.strip.to_i, :m)
|
131
|
-
runway.width = AIXM.d(width.strip.to_i, :m)
|
132
|
-
unless (text = tr.css('td:nth-of-type(5)').text.strip.split(%r<\W+/\W+>).first.compact).blank?
|
133
|
-
surface = SURFACES.metch(text)
|
134
|
-
runway.surface.composition = surface[:composition]
|
135
|
-
runway.surface.preparation = surface[:preparation]
|
136
|
-
runway.surface.remarks = surface[:remarks]
|
137
|
-
end
|
138
|
-
if (text = tr.css('td:nth-of-type(4)').text).match?(AIXM::PCN_RE)
|
139
|
-
runway.surface.pcn = text
|
140
|
-
end
|
141
|
-
end
|
142
|
-
text = tr.css('td:nth-of-type(6)').text.strip
|
143
|
-
direction.xy = (xy_from(text) unless text.match?(/\A(\(.*)?\z/m))
|
144
|
-
if (text = tr.css('td:nth-of-type(7)').text.strip[/thr:\s+(\d+\s+\w+)/i, 1]).present?
|
145
|
-
direction.z = elevation_from(text)
|
146
|
-
end
|
147
|
-
if (text = tr.css('td:nth-of-type(2)').text.strip.sub(/\A(\d+).*$/m, '\1')).present?
|
148
|
-
direction.geographic_orientation = AIXM.a(text.to_i)
|
149
|
-
end
|
150
|
-
if (text = tr.css('td:nth-of-type(6)').text[/\((.+)\)/m, 1]).present?
|
151
|
-
direction.displaced_threshold = xy_from(text)
|
152
|
-
end
|
153
|
-
if (text = tr.css('td:nth-of-type(10)').text.strip[/\A\((\d+)\)/, 1]).present?
|
154
|
-
direction.remarks = remarks_from(remarks_map.fetch(text.to_i).text)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def helipads_from(tbody)
|
163
|
-
text_fr = tbody.css('td:nth-of-type(3)').text.compact
|
164
|
-
text_en = tbody.css('td:nth-of-type(4)').text.compact
|
165
|
-
case text_fr
|
166
|
-
when /NIL/, /\A\W*\z/
|
167
|
-
[]
|
168
|
-
when /instructions?\s+twr/i
|
169
|
-
@remarks << "HELICOPTER:\nSur instructions TWR.\nOn TWR clearance."
|
170
|
-
[]
|
171
|
-
when AIXM::DMS_RE
|
172
|
-
text_fr.scan(AIXM::DMS_RE).each_slice(2).with_index(1).map do |(lat, long), index|
|
173
|
-
AIXM.helipad(
|
174
|
-
name: "H#{index}",
|
175
|
-
xy: AIXM.xy(lat: lat.first, long: long.first)
|
176
|
-
)
|
177
|
-
end
|
178
|
-
else
|
179
|
-
@remarks << ['HELICOPTER:', text_fr.blank_to_nil, text_en.blank_to_nil].compact.join("\n")
|
180
|
-
[]
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def airspaces_from(tbody)
|
185
|
-
return [] if tbody.text.blank?
|
186
|
-
airspace = nil
|
187
|
-
tbody.css('tr').to_enum.with_object([]) do |tr, array|
|
188
|
-
if tr.attr(:class) =~ /keep-with-next-row/
|
189
|
-
airspace = airspace_from tr
|
190
|
-
else
|
191
|
-
tds = tr.css('td')
|
192
|
-
airspace.geometry = geometry_from tds[0].text
|
193
|
-
fail("geometry is not closed") unless airspace.geometry.closed?
|
194
|
-
airspace.add_layer layer_from(tds[2].text, tds[1].text.strip)
|
195
|
-
airspace.layers.first.timetable = timetable_from! tds[4].text
|
196
|
-
airspace.layers.first.remarks = remarks_from(tds[4].text)
|
197
|
-
array << airspace
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def airspace_from(tr)
|
203
|
-
spans = tr.css(:span)
|
204
|
-
source_type = spans[1].text.blank_to_nil
|
205
|
-
fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
|
206
|
-
AIXM.airspace(
|
207
|
-
name: [spans[2].text, anglicise(name: spans[3]&.text)].compact.join(' '),
|
208
|
-
type: SOURCE_TYPES.dig(source_type, :type),
|
209
|
-
local_type: SOURCE_TYPES.dig(source_type, :local_type)
|
210
|
-
).tap do |airspace|
|
211
|
-
airspace.source = source(position: tr.line)
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def navigational_aids_from(tbody)
|
216
|
-
tbody.css('tr').to_enum.with_object([]) do |tr, array|
|
217
|
-
tds = tr.css('td')
|
218
|
-
array << navigational_aid_from(
|
219
|
-
{
|
220
|
-
name: OpenStruct.new(text: @airport.name), # simulate td
|
221
|
-
type: tds[0],
|
222
|
-
id: tds[1],
|
223
|
-
f: tds[2],
|
224
|
-
schedule: tds[3],
|
225
|
-
xy: tds[4],
|
226
|
-
z: tds[5]
|
227
|
-
},
|
228
|
-
source: source(position: tr.line),
|
229
|
-
sections: {
|
230
|
-
range: tds[6],
|
231
|
-
situation: tds[8]
|
232
|
-
}
|
233
|
-
)
|
234
|
-
end.compact
|
235
|
-
end
|
236
|
-
|
237
|
-
def designated_points_from(pdf, recursive=false)
|
238
|
-
from = (pdf.text =~ /^(.*?coordinates.*?names?)/i)
|
239
|
-
return [] if recursive && !from
|
240
|
-
warn("no designated points section begin found for #{@id}", pry: binding) unless from
|
241
|
-
from += $1.length
|
242
|
-
to = from + (pdf.text.from(from) =~ /\n\s*\n\s*\n|^.*(?:ifr|vfr|ad\s*equipment|special\s*activities|training\s*flights|mto\s*minima)/i)
|
243
|
-
warn("no designated points section end found for #{@id}", pry: binding) unless to
|
244
|
-
from, to = from + pdf.range.min, to + pdf.range.min # offset when recursive
|
245
|
-
buffer = {}
|
246
|
-
pdf.from(from).to(to).each_line.with_object([]) do |(line, page, last), designated_points|
|
247
|
-
line.remove!(/\u2190/) # remove arrow symbols
|
248
|
-
has_id = $1 if line.sub!(/^\s{,20}([A-Z][A-Z\d ]{1,3})(?=\W)/, '')
|
249
|
-
has_xy = line.match?(AIXM::DMS_RE)
|
250
|
-
designated_points << designated_point_from(buffer, pdf) if has_id || has_xy
|
251
|
-
if has_xy
|
252
|
-
2.times { (buffer[:xy] ||= []) << $1 if line.sub!(AIXM::DMS_RE, '') }
|
253
|
-
buffer[:xy]&.compact!
|
254
|
-
line.remove!(/\d{3,4}\D.+?MTG/) # remove extra columns (e.g. LFML)
|
255
|
-
line.remove!(/[\s#{AIXM::MIN}#{AIXM::SEC}]*[-\u2013]/) # remove dash between coordinates
|
256
|
-
end
|
257
|
-
buffer[:page] = page
|
258
|
-
buffer[:id] = has_id if has_id
|
259
|
-
buffer[:remarks] = [buffer[:remarks], line].join("\n")
|
260
|
-
designated_points << designated_point_from(buffer, pdf) if last
|
261
|
-
end.compact + designated_points_from(pdf.from(to).to(:end), true)
|
262
|
-
end
|
263
|
-
|
264
|
-
def designated_point_from(buffer, pdf)
|
265
|
-
if buffer[:id] && buffer[:xy]&.size == 2
|
266
|
-
buffer[:remarks].gsub!(/ {20}/, "\n") # recognize empty column space
|
267
|
-
buffer[:remarks].remove!(/\(\d+\)/) # remove footnotes
|
268
|
-
buffer[:remarks] = buffer[:remarks].unglue # separate glued words
|
269
|
-
AIXM.designated_point(
|
270
|
-
source: source(position: buffer[:page], aip_file: pdf.file.basename('.*').to_s),
|
271
|
-
type: :vfr_mandatory_reporting_point,
|
272
|
-
id: buffer[:id].remove(/\W/),
|
273
|
-
xy: AIXM.xy(lat: buffer[:xy].first, long: buffer[:xy].last)
|
274
|
-
).tap do |designated_point|
|
275
|
-
designated_point.airport = @airport
|
276
|
-
designated_point.remarks = buffer[:remarks].compact.blank_to_nil
|
277
|
-
buffer.clear
|
278
|
-
end
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
# Assign scattered similar remarks to one and the same designated point
|
283
|
-
def fix_designated_point_remarks(designated_points)
|
284
|
-
one = nil
|
285
|
-
designated_points.map do |two|
|
286
|
-
if one
|
287
|
-
one_lines, two_lines = one.remarks&.lines, two.remarks&.lines
|
288
|
-
if one_lines && two_lines
|
289
|
-
if one_lines.count > 1 && (line = one_lines.last) !~ %r(\s/\s)
|
290
|
-
# Move up
|
291
|
-
if line.correlate(remainder = one_lines[0..-2].join, SYNONYMS) < line.correlate(two.remarks)
|
292
|
-
two.remarks = [line, two.remarks].join("\n").compact
|
293
|
-
one.remarks = remainder.compact
|
294
|
-
end
|
295
|
-
elsif two_lines.count > 1 && (line = two_lines.first) !~ %r(\s/\s)
|
296
|
-
# Move down
|
297
|
-
line = two_lines.first
|
298
|
-
if line.correlate(remainder = two_lines[1..-1].join, SYNONYMS) < line.correlate(one.remarks)
|
299
|
-
one.remarks = [one.remarks, line].join("\n").compact
|
300
|
-
two.remarks = remainder.compact
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|
304
|
-
end
|
305
|
-
one = two
|
306
|
-
end.map do |designated_point|
|
307
|
-
designated_point.remarks = designated_point.remarks&.cleanup.blank_to_nil
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
# def debug(dp)
|
312
|
-
# f = "/Users/sschwyn/Desktop/okay/#{@id}.txt"
|
313
|
-
# result = "\n--- #{@id} ---\n\n".red
|
314
|
-
# dp.each do |d|
|
315
|
-
# result += d.id.red + "\t#{d.xy.lat} - #{d.xy.long}\n"
|
316
|
-
# result += "#{d.remarks}\n\n".blue
|
317
|
-
# end
|
318
|
-
# result += "#{dp.count} point(s) for #{@id}".red
|
319
|
-
# unless File.exist?(f) && result == File.read(f)
|
320
|
-
# puts result
|
321
|
-
# gets
|
322
|
-
# puts "\e[H\e[2J"
|
323
|
-
# end
|
324
|
-
# File.write(f, result)
|
325
|
-
# end
|
326
|
-
|
327
|
-
patch AIXM::Component::Runway::Direction, :xy do |parser, object, value|
|
328
|
-
throw :abort unless value.nil?
|
329
|
-
airport_id = parser.instance_variable_get(:@airport).id
|
330
|
-
direction_name = object.name.to_s
|
331
|
-
throw :abort if (xy = parser.fixture.dig('runways', airport_id, direction_name, 'xy')).nil?
|
332
|
-
lat, long = xy.split(/\s+/)
|
333
|
-
AIXM.xy(lat: lat, long: long)
|
334
|
-
end
|
335
|
-
|
336
|
-
patch AIXM::Feature::NavigationalAid, :remarks do |parser, object, value|
|
337
|
-
throw :abort unless object.is_a? AIXM::Feature::NavigationalAid::DesignatedPoint
|
338
|
-
airport_id, designated_point_id = object.airport.id, object.id
|
339
|
-
parser.fixture.dig('designated_points', airport_id, designated_point_id, 'remarks') || throw(:abort)
|
340
|
-
end
|
341
|
-
|
342
|
-
end
|
343
|
-
end
|
344
|
-
end
|
@@ -1,185 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# Helipads
|
5
|
-
class AD31 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Base
|
8
|
-
using AIXM::Refinements
|
9
|
-
|
10
|
-
DEPENDS = %w(AD-2)
|
11
|
-
|
12
|
-
HOSTILITIES = {
|
13
|
-
'zone hostile habitée' => 'Zone hostile habitée / hostile populated area',
|
14
|
-
'zone hostile non habitée' => 'Zone hostile non habitée / hostile unpopulated area',
|
15
|
-
'zone non hostile' => 'Zone non hostile / non-hostile area'
|
16
|
-
}.freeze
|
17
|
-
|
18
|
-
POSITIONINGS = {
|
19
|
-
'en terrasse' => 'En terrasse / on deck',
|
20
|
-
'en surface' => 'En surface / on ground'
|
21
|
-
}.freeze
|
22
|
-
|
23
|
-
DIMENSIONS_RE = /( diam.tre\s+\d+ | (?:\d[\s\d,.m]*x\s*){1,}[\s\d,.m]+ )/ix.freeze
|
24
|
-
|
25
|
-
def parse
|
26
|
-
prepare(html: read).css('tbody').each do |tbody|
|
27
|
-
tbody.css('tr').to_enum.each_slice(3).with_index(1) do |trs, index|
|
28
|
-
name = trs[0].css('span[id*="ADHP.TXT_NAME"]').text.cleanup.remove(/[^\w' ]/)
|
29
|
-
if find_by(:airport, name: name).any?
|
30
|
-
verbose_info "Skipping #{name} in favor of AD-2"
|
31
|
-
next
|
32
|
-
end
|
33
|
-
# Airport
|
34
|
-
@airport = AIXM.airport(
|
35
|
-
source: source(position: trs[0].line),
|
36
|
-
organisation: organisation_lf, # TODO: not yet implemented
|
37
|
-
id: options[:region],
|
38
|
-
name: name,
|
39
|
-
xy: xy_from(trs[1].css('td:nth-of-type(1)').text.cleanup)
|
40
|
-
).tap do |airport|
|
41
|
-
airport.z = elevation_from(trs[1].css('td:nth-of-type(2)').text)
|
42
|
-
end
|
43
|
-
# Usage restrictions
|
44
|
-
if trs[0].css('span[id*="ADHP.STATUT"]').text.match?(/usage\s+restreint/i)
|
45
|
-
@airport.add_usage_limitation(type: :reservation_required) do |reservation_required|
|
46
|
-
reservation_required.remarks = "Usage restreint / restricted use"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
if trs[0].css('span[id*="ADHP.STATUT"]').text.match?(/r.serv.\s+aux\s+administrations/i)
|
50
|
-
@airport.add_usage_limitation(type: :other) do |other|
|
51
|
-
other.remarks = "Réservé aux administrations de l'État / reserved for State administrations"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
# FATOs and helipads
|
55
|
-
text = trs[2].css('span[id*="ADHP.REVETEMENT"]').text.remove(/tlof\s*|\s*\(.*?\)/i).downcase.compact
|
56
|
-
surface = text.blank? ? {} : SURFACES.metch(text)
|
57
|
-
lighting = lighting_from(trs[1].css('span[id*="ADHP.BALISAGE"]').text.cleanup)
|
58
|
-
fatos_from(trs[1].css('span[id*="ADHP.DIM_FATO"]').text).each { @airport.add_fato(_1) }
|
59
|
-
helipads_from(trs[1].css('span[id*="ADHP.DIM_TLOF"]').text).each do |helipad|
|
60
|
-
helipad.surface.composition = surface[:composition]
|
61
|
-
helipad.surface.preparation = surface[:preparation]
|
62
|
-
helipad.surface.remarks = surface[:remarks]
|
63
|
-
helipad.surface.auw_weight = auw_weight_from(trs[2].css('span[id*="ADHP.RESISTANCE"]').text)
|
64
|
-
helipad.add_lighting(lighting) if lighting
|
65
|
-
@airport.add_helipad helipad
|
66
|
-
end
|
67
|
-
# Operator and addresses
|
68
|
-
operator = trs[0].css('span[id*="ADHP.EXPLOITANT"]')
|
69
|
-
splitted = operator.text.split(/( (?<!\p{L})t[ée]l | fax | standard | [\d\s]{10,} | \.\s | \( )/ix, 2)
|
70
|
-
@airport.operator = splitted[0].full_strip.truncate(60, omission: '…').blank_to_nil
|
71
|
-
raw_addresses = splitted[1..].join.cleanup.full_strip
|
72
|
-
addresses_from(splitted[1..].join, source(position: operator.first.line)).each { @airport.add_address(_1) }
|
73
|
-
# Remarks
|
74
|
-
@airport.remarks = [].tap do |remarks|
|
75
|
-
hostility = trs[2].css('span[id*="ADHP.ZONE_HABITEE"]').text.cleanup.downcase.blank_to_nil
|
76
|
-
hostility = HOSTILITIES.fetch(hostility) if hostility
|
77
|
-
positioning = trs[2].css('span[id*="ADHP.EN_TERRASSE"]').text.cleanup.downcase.blank_to_nil
|
78
|
-
positioning = POSITIONINGS.fetch(positioning) if positioning
|
79
|
-
remarks << ('**SITUATION**' if hostility || positioning) << hostility << positioning << ''
|
80
|
-
remarks << trs[2].css('td:nth-of-type(5)').text.cleanup
|
81
|
-
remarks << raw_addresses unless raw_addresses.blank?
|
82
|
-
end.compact.join("\n").strip
|
83
|
-
add(@airport) if @airport.fatos.any? || @airport.helipads.any?
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def fatos_from(text)
|
91
|
-
[
|
92
|
-
if text.cleanup.match DIMENSIONS_RE
|
93
|
-
AIXM.fato(name: 'FATO').tap do |fato|
|
94
|
-
fato.length, fato.width = dimensions_from($1)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
].compact
|
98
|
-
end
|
99
|
-
|
100
|
-
def helipads_from(text)
|
101
|
-
[
|
102
|
-
if text.cleanup.match DIMENSIONS_RE
|
103
|
-
AIXM.helipad(name: 'TLOF', xy: @airport.xy).tap do |helipad|
|
104
|
-
helipad.z = @airport.z
|
105
|
-
helipad.length, helipad.width = dimensions_from($1)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
].compact
|
109
|
-
end
|
110
|
-
|
111
|
-
def dimensions_from(text)
|
112
|
-
dims = text.remove(/[^x\d.,]/i).split(/x/i).map { _1.to_ff.floor }
|
113
|
-
case dims.size
|
114
|
-
when 1
|
115
|
-
[dim = AIXM.d(dims[0], :m), dim]
|
116
|
-
when 2
|
117
|
-
[AIXM.d(dims[0], :m), AIXM.d(dims[1], :m)]
|
118
|
-
when 4
|
119
|
-
[dim = AIXM.d(dims.min, :m), dim]
|
120
|
-
else
|
121
|
-
warn("bad dimensions for #{@airport.name}", pry: binding)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def auw_weight_from(text)
|
126
|
-
if wgt = text.match(/(\d+(?:[,.]\d+)?)\s*t/i)&.captures&.first
|
127
|
-
AIXM.w(wgt.to_ff, :t)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def lighting_from(text)
|
132
|
-
return if text.blank? || text.match?(/nil|balisage\s*:\s*non/i)
|
133
|
-
description = text.remove(/balisage\s*:|oui\.?\s*:?/i).compact.full_strip
|
134
|
-
AIXM.lighting(position: :edge).tap do |lighting|
|
135
|
-
lighting.description = description unless description.blank?
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def addresses_from(text, source)
|
140
|
-
[].tap do |addresses|
|
141
|
-
text.sub! /(?<!\p{L})t[ée]l\D*([\d\s.]{10,}(?:poste[\d\s.]{2,})?)[-\/]?/i do |m|
|
142
|
-
addresses << AIXM.address(
|
143
|
-
source: source,
|
144
|
-
type: :phone,
|
145
|
-
address: m.strip.sub(/poste/i, '-').remove(/[^\d-]|-$/)
|
146
|
-
)
|
147
|
-
end
|
148
|
-
text.sub! /fax\D*([\d\s.]{10,}(?:poste[\d\s.]{2,})?)[-\/]?/i do |m|
|
149
|
-
addresses << AIXM.address(
|
150
|
-
source: source,
|
151
|
-
type: :fax,
|
152
|
-
address: m.strip.sub(/poste/i, '-').remove(/[^\d-]|-$/)
|
153
|
-
)
|
154
|
-
end
|
155
|
-
text.sub! /e-mail\W*(\S+)[-\/]?/i do |m|
|
156
|
-
addresses << AIXM.address(
|
157
|
-
source: source,
|
158
|
-
type: :email,
|
159
|
-
address: m.strip
|
160
|
-
)
|
161
|
-
end
|
162
|
-
text.sub! /(\d[\d\s]{9,}(?:poste[\d\s.]{2,})?)[-\/]?/i do |m|
|
163
|
-
addresses << AIXM.address(
|
164
|
-
source: source,
|
165
|
-
type: :phone,
|
166
|
-
address: m.strip.sub(/poste/i, '-').remove(/[^\d-]|-$/)
|
167
|
-
)
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
patch AIXM::Feature::Airport, :xy do |parser, object, value|
|
173
|
-
throw :abort if value.seconds?
|
174
|
-
if xy = parser.fixture.dig(object.name, 'xy')
|
175
|
-
lat, long = xy.split(/\s+/)
|
176
|
-
AIXM.xy(lat: lat, long: long)
|
177
|
-
else
|
178
|
-
warn("coordinates for #{object.name} appear not to be exact", pry: binding)
|
179
|
-
throw :abort
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|