aipp 0.2.4 → 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 +2 -0
- data/CHANGELOG.md +38 -0
- data/README.md +222 -88
- data/exe/aip2aixm +2 -2
- data/exe/aip2ofmx +2 -2
- data/lib/aipp/aip.rb +113 -31
- data/lib/aipp/border.rb +77 -46
- data/lib/aipp/debugger.rb +101 -0
- data/lib/aipp/downloader.rb +39 -26
- data/lib/aipp/executable.rb +41 -22
- data/lib/aipp/parser.rb +94 -21
- data/lib/aipp/patcher.rb +5 -2
- data/lib/aipp/pdf.rb +1 -1
- 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 +218 -0
- 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 +4 -5
- data/lib/aipp/version.rb +1 -1
- data/lib/aipp.rb +11 -5
- data/lib/core_ext/enumerable.rb +9 -9
- data/lib/core_ext/hash.rb +21 -5
- data/lib/core_ext/nokogiri.rb +54 -0
- data/lib/core_ext/string.rb +38 -66
- data.tar.gz.sig +2 -0
- metadata +180 -188
- metadata.gz.sig +0 -0
- data/.gitignore +0 -8
- data/.ruby-version +0 -1
- data/.travis.yml +0 -8
- data/.yardopts +0 -3
- data/Guardfile +0 -7
- data/TODO.md +0 -6
- data/aipp.gemspec +0 -44
- data/gems.rb +0 -3
- data/lib/aipp/airac.rb +0 -55
- data/lib/aipp/regions/LF/AD-1.3.rb +0 -162
- data/lib/aipp/regions/LF/AD-1.6.rb +0 -31
- data/lib/aipp/regions/LF/AD-2.rb +0 -313
- data/lib/aipp/regions/LF/AD-3.1.rb +0 -185
- data/lib/aipp/regions/LF/ENR-2.1.rb +0 -92
- data/lib/aipp/regions/LF/ENR-4.1.rb +0 -97
- data/lib/aipp/regions/LF/ENR-4.3.rb +0 -28
- data/lib/aipp/regions/LF/ENR-5.1.rb +0 -75
- data/lib/aipp/regions/LF/ENR-5.5.rb +0 -53
- 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/AD_radio.rb +0 -90
- data/lib/aipp/regions/LF/helpers/URL.rb +0 -26
- data/lib/aipp/regions/LF/helpers/common.rb +0 -217
- data/lib/core_ext/object.rb +0 -43
- data/rakefile.rb +0 -12
- data/spec/fixtures/archive.zip +0 -0
- data/spec/fixtures/border.geojson +0 -201
- data/spec/fixtures/document.pdf +0 -0
- data/spec/fixtures/document.pdf.json +0 -1
- data/spec/fixtures/new.html +0 -6
- data/spec/fixtures/new.pdf +0 -0
- data/spec/fixtures/new.txt +0 -1
- data/spec/lib/aipp/airac_spec.rb +0 -98
- data/spec/lib/aipp/border_spec.rb +0 -135
- data/spec/lib/aipp/downloader_spec.rb +0 -81
- data/spec/lib/aipp/patcher_spec.rb +0 -46
- data/spec/lib/aipp/pdf_spec.rb +0 -124
- data/spec/lib/aipp/t_hash_spec.rb +0 -44
- data/spec/lib/aipp/version_spec.rb +0 -7
- data/spec/lib/core_ext/enumberable_spec.rb +0 -76
- data/spec/lib/core_ext/hash_spec.rb +0 -27
- data/spec/lib/core_ext/integer_spec.rb +0 -15
- data/spec/lib/core_ext/nil_class_spec.rb +0 -11
- data/spec/lib/core_ext/string_spec.rb +0 -112
- data/spec/sounds/failure.mp3 +0 -0
- data/spec/sounds/success.mp3 +0 -0
- data/spec/spec_helper.rb +0 -28
@@ -1,185 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# Helipads
|
5
|
-
class AD31 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Common
|
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 select(: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(: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(: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
|
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 { |f| @airport.add_fato f }
|
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 { |a| @airport.add_address(a) }
|
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 { |s| s.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
|
@@ -1,92 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# FIR, TMA etc
|
5
|
-
class ENR21 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Common
|
8
|
-
|
9
|
-
# Map source types to type and optional local type
|
10
|
-
SOURCE_TYPES = {
|
11
|
-
'FIR' => { type: 'FIR' },
|
12
|
-
'UIR' => { type: 'UIR' },
|
13
|
-
'UTA' => { type: 'UTA' },
|
14
|
-
'CTA' => { type: 'CTA' },
|
15
|
-
'LTA' => { type: 'CTA', local_type: 'LTA' },
|
16
|
-
'TMA' => { type: 'TMA' },
|
17
|
-
'SIV' => { type: 'SECTOR', local_type: 'SIV' } # providing FIS
|
18
|
-
}.freeze
|
19
|
-
|
20
|
-
# Map airspace "<type> <name>" to location indicator
|
21
|
-
LOCATION_INDICATORS = {
|
22
|
-
'FIR BORDEAUX' => 'LFBB',
|
23
|
-
'FIR BREST' => 'LFRR',
|
24
|
-
'FIR MARSEILLE' => 'LFMM',
|
25
|
-
'FIR PARIS' => 'LFFF',
|
26
|
-
'FIR REIMS' => 'LFRR'
|
27
|
-
}.freeze
|
28
|
-
|
29
|
-
def parse
|
30
|
-
prepare(html: read).css('tbody').each do |tbody|
|
31
|
-
airspace = nil
|
32
|
-
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
33
|
-
if tr.attr(:id).match?(/--TXT_NAME/)
|
34
|
-
add airspace if airspace
|
35
|
-
airspace = airspace_from tr.css(:td).first
|
36
|
-
verbose_info "Parsing #{airspace.type} #{airspace.name}" unless airspace.type == :terminal_control_area
|
37
|
-
next
|
38
|
-
end
|
39
|
-
begin
|
40
|
-
tds = tr.css('td')
|
41
|
-
if airspace.type == :terminal_control_area && tds[0].text.blank_to_nil
|
42
|
-
add airspace if airspace.layers.any?
|
43
|
-
airspace = airspace_from tds[0]
|
44
|
-
verbose_info "Parsing #{airspace.type} #{airspace.name}"
|
45
|
-
end
|
46
|
-
if airspace
|
47
|
-
if tds[0].text.blank_to_nil
|
48
|
-
airspace.geometry = geometry_from tds[0].text
|
49
|
-
fail("geometry is not closed") unless airspace.geometry.closed?
|
50
|
-
end
|
51
|
-
layer = layer_from(tds[-3].text)
|
52
|
-
layer.class = class_from(tds[1].text) if tds.count == 5
|
53
|
-
layer.location_indicator = LOCATION_INDICATORS.fetch("#{airspace.type} #{airspace.name}", nil)
|
54
|
-
# TODO: unit, call sign and frequency from tds[-2] and with extracted remarks such as (1), (2) etc
|
55
|
-
remarks = tds[-1].text
|
56
|
-
layer.timetable = timetable_from! remarks
|
57
|
-
layer.remarks = remarks_from remarks
|
58
|
-
airspace.layers << layer
|
59
|
-
end
|
60
|
-
rescue => error
|
61
|
-
warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
add airspace if airspace
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def airspace_from(td)
|
71
|
-
spans = td.children.split { |e| e.name == 'br' }.first.css(:span).drop_while { |e| e.text.match? '\s' }
|
72
|
-
source_type = spans[0].text.blank_to_nil
|
73
|
-
fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
|
74
|
-
AIXM.airspace(
|
75
|
-
name: anglicise(name: spans[1..-1].join(' ')),
|
76
|
-
type: SOURCE_TYPES.dig(source_type, :type),
|
77
|
-
local_type: SOURCE_TYPES.dig(source_type, :local_type)
|
78
|
-
).tap do |airspace|
|
79
|
-
airspace.source = source(position: td.line)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def class_from(text)
|
84
|
-
text.strip
|
85
|
-
end
|
86
|
-
|
87
|
-
def remarks_from(text)
|
88
|
-
text.strip.gsub(/(\s)\s+/, '\1').blank_to_nil
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
@@ -1,97 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# ENR Navaids
|
5
|
-
class ENR41 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Common
|
8
|
-
|
9
|
-
def parse
|
10
|
-
prepare(html: read).css('tbody').each do |tbody|
|
11
|
-
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
12
|
-
tds = tr.css('td')
|
13
|
-
master, slave = tds[1].text.strip.gsub(/[^\w-]/, '').downcase.split('-')
|
14
|
-
navaid = AIXM.send(master, base_from(tds).merge(send("#{master}_from", tds)))
|
15
|
-
navaid.source = source(position: tr.line)
|
16
|
-
navaid.timetable = timetable_from! tds[4].text
|
17
|
-
navaid.remarks = remarks_from(tds[5], tds[7], tds[9])
|
18
|
-
navaid.send("associate_#{slave}", channel: channel_from(tds[3].text)) if slave
|
19
|
-
add navaid
|
20
|
-
rescue => error
|
21
|
-
warn("error parsing navigational aid at ##{index}: #{error.message}", pry: error)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def base_from(tds)
|
29
|
-
{
|
30
|
-
organisation: organisation_lf,
|
31
|
-
id: tds[2].text.strip,
|
32
|
-
name: tds[0].text.strip,
|
33
|
-
xy: xy_from(tds[5].text),
|
34
|
-
z: z_from(tds[6].text)
|
35
|
-
}
|
36
|
-
end
|
37
|
-
|
38
|
-
def vor_from(tds)
|
39
|
-
{
|
40
|
-
type: :conventional,
|
41
|
-
f: f_from(tds[3].text),
|
42
|
-
north: :magnetic,
|
43
|
-
}
|
44
|
-
end
|
45
|
-
|
46
|
-
def dme_from(tds)
|
47
|
-
{
|
48
|
-
channel: channel_from(tds[3].text)
|
49
|
-
}
|
50
|
-
end
|
51
|
-
|
52
|
-
def ndb_from(tds)
|
53
|
-
{
|
54
|
-
type: :en_route,
|
55
|
-
f: f_from(tds[3].text)
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
def tacan_from(tds)
|
60
|
-
{
|
61
|
-
channel: channel_from(tds[3].text)
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
def z_from(text)
|
66
|
-
parts = text.strip.split(/\s+/)
|
67
|
-
AIXM.z(parts[0].to_i, :qnh) if parts[1] == 'ft'
|
68
|
-
end
|
69
|
-
|
70
|
-
def f_from(text)
|
71
|
-
parts = text.strip.split(/\s+/)
|
72
|
-
AIXM.f(parts[0].to_f, parts[1]) if parts[1] =~ /hz$/i
|
73
|
-
end
|
74
|
-
|
75
|
-
def channel_from(text)
|
76
|
-
parts = text.strip.split(/\s+/)
|
77
|
-
parts.last if parts[-2].downcase == 'ch'
|
78
|
-
end
|
79
|
-
|
80
|
-
def remarks_from(*parts)
|
81
|
-
part_titles = ['RANGE', 'SITUATION', 'OBSERVATIONS']
|
82
|
-
[].tap do |remarks|
|
83
|
-
parts.each.with_index do |part, index|
|
84
|
-
text = if index == 0
|
85
|
-
part = part.text.strip.split(/\s+/)
|
86
|
-
part.shift(2)
|
87
|
-
part.join(' ').blank_to_nil
|
88
|
-
else
|
89
|
-
part.text.strip.blank_to_nil
|
90
|
-
end
|
91
|
-
remarks << "**#{part_titles[index]}**\n#{text}" if text
|
92
|
-
end
|
93
|
-
end.join("\n\n").blank_to_nil
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# Designated Points
|
5
|
-
class ENR43 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Common
|
8
|
-
|
9
|
-
def parse
|
10
|
-
prepare(html: read).css('tbody').each do |tbody|
|
11
|
-
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
12
|
-
tds = tr.css('td')
|
13
|
-
designated_point = AIXM.designated_point(
|
14
|
-
type: :icao,
|
15
|
-
id: tds[0].text.strip,
|
16
|
-
xy: xy_from(tds[1].text)
|
17
|
-
)
|
18
|
-
designated_point.source = source(position: tr.line)
|
19
|
-
add designated_point
|
20
|
-
rescue => error
|
21
|
-
warn("error parsing designated point at ##{index}: #{error.message}", pry: error)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,75 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# D/P/R Zones
|
5
|
-
class ENR51 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Common
|
8
|
-
|
9
|
-
# Map source types to type and optional local type
|
10
|
-
SOURCE_TYPES = {
|
11
|
-
'D' => { type: 'D' },
|
12
|
-
'P' => { type: 'P' },
|
13
|
-
'R' => { type: 'R' },
|
14
|
-
'ZIT' => { type: 'P', local_type: 'ZIT' }
|
15
|
-
}.freeze
|
16
|
-
|
17
|
-
def parse
|
18
|
-
prepare(html: read).css('tbody:has(tr[id^=mid])').each do |tbody|
|
19
|
-
airspace = nil
|
20
|
-
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
21
|
-
tds = tr.css('td')
|
22
|
-
case
|
23
|
-
when tr.attr(:id).match?(/TXT_NAME/) # airspace
|
24
|
-
airspace = airspace_from tr
|
25
|
-
when tds.count == 1 # big comment on separate row
|
26
|
-
airspace.layers.first.remarks.
|
27
|
-
concat("\n", tds.text.cleanup).
|
28
|
-
remove!(/\((\d)\)\s*\(\1\)\W*/)
|
29
|
-
else # layer
|
30
|
-
begin
|
31
|
-
tds = tr.css('td')
|
32
|
-
airspace.geometry = geometry_from tds[0].text
|
33
|
-
fail("geometry is not closed") unless airspace.geometry.closed?
|
34
|
-
airspace.layers << layer_from(tds[1].text)
|
35
|
-
airspace.layers.first.timetable = timetable_from! tds[2].text
|
36
|
-
airspace.layers.first.remarks = remarks_from(tds[2], tds[3], tds[4])
|
37
|
-
add airspace
|
38
|
-
rescue => error
|
39
|
-
warn("error parsing airspace `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def airspace_from(tr)
|
49
|
-
spans = tr.css('span:not([class*=strong])')
|
50
|
-
source_type = spans[1].text.blank_to_nil
|
51
|
-
fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
|
52
|
-
AIXM.airspace(
|
53
|
-
name: spans.map { |s| s.text.strip.blank_to_nil }.compact.join(' '),
|
54
|
-
type: SOURCE_TYPES.dig(source_type, :type),
|
55
|
-
local_type: SOURCE_TYPES.dig(source_type, :local_type)
|
56
|
-
).tap do |airspace|
|
57
|
-
airspace.source = source(position: tr.line)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def remarks_from(*parts)
|
62
|
-
part_titles = ['TIMETABLE', 'RESTRICTION', 'AUTHORITY/CONDITIONS']
|
63
|
-
[].tap do |remarks|
|
64
|
-
parts.each.with_index do |part, index|
|
65
|
-
if part = part.text.gsub(/ +/, ' ').gsub(/(\n ?)+/, "\n").strip.blank_to_nil
|
66
|
-
unless index.zero? && part == 'H24'
|
67
|
-
remarks << "**#{part_titles[index]}**\n#{part}"
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end.join("\n\n").blank_to_nil
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# Sporting and Recreational Activities
|
5
|
-
class ENR55 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Common
|
8
|
-
|
9
|
-
# Map raw activities to activity and airspace type
|
10
|
-
ACTIVITIES = {
|
11
|
-
'activité particulière' => { activity: :other, airspace_type: :dangerous_activities_area },
|
12
|
-
'aéromodélisme' => { activity: :aeromodelling, airspace_type: :dangerous_activities_area },
|
13
|
-
'parachutage' => { activity: :parachuting, airspace_type: :dangerous_activities_area },
|
14
|
-
'treuillage' => { activity: :glider_winch, airspace_type: :dangerous_activities_area },
|
15
|
-
'voltige' => { activity: :acrobatics, airspace_type: :dangerous_activities_area }
|
16
|
-
}.freeze
|
17
|
-
|
18
|
-
def parse
|
19
|
-
prepare(html: read).css('tbody').each do |tbody|
|
20
|
-
tbody.css('tr').to_enum.each_slice(2).with_index(1) do |trs, index|
|
21
|
-
begin
|
22
|
-
id, activity_and_name, upper_limits, timetable = trs.first.css('td')
|
23
|
-
activity, name = activity_and_name.css('span')
|
24
|
-
lateral_limits, lower_limits, remarks = trs.last.css('td')
|
25
|
-
lateral_limits.search('br').each { |br| br.replace("|||") }
|
26
|
-
geometry, lateral_limits = lateral_limits.text.split('|||', 2)
|
27
|
-
lateral_limits&.gsub!('|||', "\n")
|
28
|
-
remarks = [remarks&.text&.cleanup&.blank_to_nil]
|
29
|
-
s = timetable&.text&.cleanup and remarks.prepend('**SCHEDULE**', s, '')
|
30
|
-
s = lateral_limits&.cleanup and remarks.prepend('**LATERAL LIMITS**', s, '')
|
31
|
-
airspace = AIXM.airspace(
|
32
|
-
source: source(position: trs.first.line),
|
33
|
-
id: id.text.strip,
|
34
|
-
type: ACTIVITIES.fetch(activity.text.downcase).fetch(:airspace_type),
|
35
|
-
name: [id.text.strip, name.text.cleanup].join(' ')
|
36
|
-
).tap do |airspace|
|
37
|
-
airspace.geometry = geometry_from(geometry)
|
38
|
-
airspace.layers << layer_from([upper_limits.text, lower_limits.text].join('---').cleanup).tap do |layer|
|
39
|
-
layer.activity = ACTIVITIES.fetch(activity.text.downcase).fetch(:activity)
|
40
|
-
layer.remarks = remarks.compact.join("\n")
|
41
|
-
end
|
42
|
-
end
|
43
|
-
rescue => error
|
44
|
-
warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
45
|
-
end
|
46
|
-
add airspace
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|