aipp 0.2.5 → 0.2.6
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.tar.gz.sig +0 -0
- data/CHANGELOG.md +8 -0
- data/README.md +40 -14
- data/exe/aip2aixm +1 -1
- data/exe/aip2ofmx +1 -1
- data/lib/aipp.rb +2 -0
- data/lib/aipp/aip.rb +17 -12
- data/lib/aipp/downloader.rb +1 -1
- data/lib/aipp/executable.rb +15 -12
- data/lib/aipp/parser.rb +58 -43
- data/lib/aipp/pdf.rb +1 -1
- data/lib/aipp/regions/LF/AD-1.3.rb +7 -6
- data/lib/aipp/regions/LF/AD-1.6.rb +7 -5
- data/lib/aipp/regions/LF/AD-2.rb +16 -9
- data/lib/aipp/regions/LF/AD-3.1.rb +6 -6
- data/lib/aipp/regions/LF/ENR-2.1.rb +81 -6
- data/lib/aipp/regions/LF/ENR-4.1.rb +3 -1
- data/lib/aipp/regions/LF/ENR-4.3.rb +2 -3
- data/lib/aipp/regions/LF/ENR-5.1.rb +15 -2
- data/lib/aipp/regions/LF/ENR-5.4.rb +90 -0
- data/lib/aipp/regions/LF/ENR-5.5.rb +12 -10
- data/lib/aipp/regions/LF/fixtures/AD-1.3.yml +2 -2
- data/lib/aipp/regions/LF/helpers/base.rb +17 -9
- data/lib/aipp/regions/LF/helpers/radio_AD.rb +21 -13
- data/lib/aipp/t_hash.rb +3 -3
- data/lib/aipp/version.rb +1 -1
- data/lib/core_ext/enumerable.rb +7 -7
- data/lib/core_ext/string.rb +9 -4
- metadata +156 -168
- metadata.gz.sig +1 -0
- data/.github/workflows/test.yml +0 -26
- data/.gitignore +0 -8
- data/.ruby-version +0 -1
- data/.yardopts +0 -3
- data/Guardfile +0 -7
- data/TODO.md +0 -6
- data/aipp.gemspec +0 -45
- data/gems.rb +0 -3
- data/rakefile.rb +0 -12
- 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/fixtures/source.zip +0 -0
- 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 -29
@@ -15,13 +15,15 @@ module AIPP
|
|
15
15
|
}
|
16
16
|
|
17
17
|
def parse
|
18
|
-
prepare(html: read)
|
19
|
-
|
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
|
20
22
|
id = tr.css('span[id*="CODE_ICAO"]').text.cleanup
|
21
23
|
next unless id = ID_FIXES.fetch(id, id)
|
22
|
-
@airport =
|
23
|
-
addresses_from(trs).each {
|
24
|
-
units_from(trs).each(&method(:add))
|
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))
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
data/lib/aipp/regions/LF/AD-2.rb
CHANGED
@@ -50,7 +50,7 @@ module AIPP
|
|
50
50
|
source: source(position: html.css('tr[id*="CODE_ICAO"]').first.line, aip_file: aip_file),
|
51
51
|
organisation: organisation_lf, # TODO: not yet implemented
|
52
52
|
id: @id,
|
53
|
-
name: html.css('tr[id*="CODE_ICAO"] td span:nth-of-type(2)').text.uptrans,
|
53
|
+
name: html.css('tr[id*="CODE_ICAO"] td span:nth-of-type(2)').text.strip.uptrans,
|
54
54
|
xy: xy_from(html.css('#AD-2\.2-Position_Geo_Arp td:nth-of-type(3)').text)
|
55
55
|
).tap do |airport|
|
56
56
|
airport.z = elevation_from(html.css('#AD-2\.2-Altitude_Reference td:nth-of-type(3)').text)
|
@@ -58,26 +58,33 @@ module AIPP
|
|
58
58
|
# airport.transition_z = AIXM.z(5000, :qnh) # TODO: default - exceptions may exist
|
59
59
|
airport.timetable = timetable_from!(html.css('#AD-2\.3-Gestionnaire_AD td:nth-of-type(3)').text)
|
60
60
|
end
|
61
|
-
runways_from(html.css('div[id*="-AD-2\.12"] tbody')).each {
|
62
|
-
helipads_from(html.css('div[id*="-AD-2\.16"] tbody')).each {
|
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
63
|
text = html.css('#AD-2\.2-Observations td:nth-of-type(3)').text
|
64
64
|
@airport.remarks = ([remarks_from(text)] + @remarks).compact.join("\n\n").blank_to_nil
|
65
65
|
add @airport
|
66
66
|
# Airspaces
|
67
|
-
airspaces_from(html.css('div[id*="-AD-2\.17"] tbody')).
|
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))
|
68
70
|
# Radio
|
69
71
|
trs = html.css('div[id*="-AD-2\.18"] tbody tr')
|
70
|
-
addresses_from(trs).each {
|
71
|
-
units_from(trs).each(&method(:add))
|
72
|
+
addresses_from(trs).each { @airport.add_address(_1) }
|
73
|
+
units_from(trs, airport: @airport).each(&method(:add))
|
72
74
|
# Navigational aids
|
73
|
-
navigational_aids_from(html.css('div[id*="-AD-2\.19"] tbody')).
|
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))
|
74
78
|
# Designated points
|
75
79
|
unless NO_VAC.include?(@id) || NO_DESIGNATED_POINTS.include?(@id)
|
76
80
|
pdf = read("VAC-#{@id}")
|
77
81
|
designated_points_from(pdf).tap do |designated_points|
|
78
82
|
fix_designated_point_remarks(designated_points)
|
79
83
|
# debug(designated_points)
|
80
|
-
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))
|
81
88
|
end
|
82
89
|
end
|
83
90
|
rescue => error
|
@@ -184,7 +191,7 @@ module AIPP
|
|
184
191
|
tds = tr.css('td')
|
185
192
|
airspace.geometry = geometry_from tds[0].text
|
186
193
|
fail("geometry is not closed") unless airspace.geometry.closed?
|
187
|
-
airspace.
|
194
|
+
airspace.add_layer layer_from(tds[2].text, tds[1].text.strip)
|
188
195
|
airspace.layers.first.timetable = timetable_from! tds[4].text
|
189
196
|
airspace.layers.first.remarks = remarks_from(tds[4].text)
|
190
197
|
array << airspace
|
@@ -26,7 +26,7 @@ module AIPP
|
|
26
26
|
prepare(html: read).css('tbody').each do |tbody|
|
27
27
|
tbody.css('tr').to_enum.each_slice(3).with_index(1) do |trs, index|
|
28
28
|
name = trs[0].css('span[id*="ADHP.TXT_NAME"]').text.cleanup.remove(/[^\w' ]/)
|
29
|
-
if
|
29
|
+
if find_by(:airport, name: name).any?
|
30
30
|
verbose_info "Skipping #{name} in favor of AD-2"
|
31
31
|
next
|
32
32
|
end
|
@@ -42,12 +42,12 @@ module AIPP
|
|
42
42
|
end
|
43
43
|
# Usage restrictions
|
44
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|
|
45
|
+
@airport.add_usage_limitation(type: :reservation_required) do |reservation_required|
|
46
46
|
reservation_required.remarks = "Usage restreint / restricted use"
|
47
47
|
end
|
48
48
|
end
|
49
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|
|
50
|
+
@airport.add_usage_limitation(type: :other) do |other|
|
51
51
|
other.remarks = "Réservé aux administrations de l'État / reserved for State administrations"
|
52
52
|
end
|
53
53
|
end
|
@@ -55,7 +55,7 @@ module AIPP
|
|
55
55
|
text = trs[2].css('span[id*="ADHP.REVETEMENT"]').text.remove(/tlof\s*|\s*\(.*?\)/i).downcase.compact
|
56
56
|
surface = text.blank? ? {} : SURFACES.metch(text)
|
57
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 {
|
58
|
+
fatos_from(trs[1].css('span[id*="ADHP.DIM_FATO"]').text).each { @airport.add_fato(_1) }
|
59
59
|
helipads_from(trs[1].css('span[id*="ADHP.DIM_TLOF"]').text).each do |helipad|
|
60
60
|
helipad.surface.composition = surface[:composition]
|
61
61
|
helipad.surface.preparation = surface[:preparation]
|
@@ -69,7 +69,7 @@ module AIPP
|
|
69
69
|
splitted = operator.text.split(/( (?<!\p{L})t[ée]l | fax | standard | [\d\s]{10,} | \.\s | \( )/ix, 2)
|
70
70
|
@airport.operator = splitted[0].full_strip.truncate(60, omission: '…').blank_to_nil
|
71
71
|
raw_addresses = splitted[1..].join.cleanup.full_strip
|
72
|
-
addresses_from(splitted[1..].join, source(position: operator.first.line)).each {
|
72
|
+
addresses_from(splitted[1..].join, source(position: operator.first.line)).each { @airport.add_address(_1) }
|
73
73
|
# Remarks
|
74
74
|
@airport.remarks = [].tap do |remarks|
|
75
75
|
hostility = trs[2].css('span[id*="ADHP.ZONE_HABITEE"]').text.cleanup.downcase.blank_to_nil
|
@@ -109,7 +109,7 @@ module AIPP
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def dimensions_from(text)
|
112
|
-
dims = text.remove(/[^x\d.,]/i).split(/x/i).map {
|
112
|
+
dims = text.remove(/[^x\d.,]/i).split(/x/i).map { _1.to_ff.floor }
|
113
113
|
case dims.size
|
114
114
|
when 1
|
115
115
|
[dim = AIXM.d(dims[0], :m), dim]
|
@@ -6,6 +6,9 @@ module AIPP
|
|
6
6
|
|
7
7
|
include AIPP::LF::Helpers::Base
|
8
8
|
|
9
|
+
# Airspaces to be ignored
|
10
|
+
NAME_BLACKLIST_RE = /deleg/i.freeze
|
11
|
+
|
9
12
|
# Map source types to type and optional local type
|
10
13
|
SOURCE_TYPES = {
|
11
14
|
'FIR' => { type: 'FIR' },
|
@@ -26,12 +29,24 @@ module AIPP
|
|
26
29
|
'FIR REIMS' => 'LFRR'
|
27
30
|
}.freeze
|
28
31
|
|
32
|
+
# Fix incomplete SIV service columns
|
33
|
+
SERVICE_FIXES = {
|
34
|
+
"IROISE INFO 135.825 / 119.575 (1)" => "APP IROISE\nIROISE INFO 135.825 / 119.575 (1)",
|
35
|
+
"APP TOULOUSE\nTOULOUSE INFO" => "APP TOULOUSE\nTOULOUSE INFO 121.250"
|
36
|
+
}.freeze
|
37
|
+
|
29
38
|
def parse
|
30
39
|
prepare(html: read).css('tbody').each do |tbody|
|
31
40
|
airspace = nil
|
32
41
|
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
33
42
|
if tr.attr(:id).match?(/--TXT_NAME/)
|
34
|
-
|
43
|
+
if airspace
|
44
|
+
if airspace.name.match? NAME_BLACKLIST_RE
|
45
|
+
verbose_info "Ignoring #{airspace.type} #{airspace.name}" unless airspace.type == :terminal_control_area
|
46
|
+
else
|
47
|
+
add airspace
|
48
|
+
end
|
49
|
+
end
|
35
50
|
airspace = airspace_from tr.css(:td).first
|
36
51
|
verbose_info "Parsing #{airspace.type} #{airspace.name}" unless airspace.type == :terminal_control_area
|
37
52
|
next
|
@@ -39,11 +54,18 @@ module AIPP
|
|
39
54
|
begin
|
40
55
|
tds = tr.css('td')
|
41
56
|
if airspace.type == :terminal_control_area && tds[0].text.blank_to_nil
|
42
|
-
|
57
|
+
if airspace.layers.any?
|
58
|
+
if airspace.name.match? NAME_BLACKLIST_RE
|
59
|
+
verbose_info "Ignoring #{airspace.type} #{airspace.name}"
|
60
|
+
else
|
61
|
+
add airspace
|
62
|
+
end
|
63
|
+
end
|
43
64
|
airspace = airspace_from tds[0]
|
44
65
|
verbose_info "Parsing #{airspace.type} #{airspace.name}"
|
45
66
|
end
|
46
67
|
if airspace
|
68
|
+
remarks = tds[-1].text
|
47
69
|
if tds[0].text.blank_to_nil
|
48
70
|
airspace.geometry = geometry_from tds[0].text
|
49
71
|
fail("geometry is not closed") unless airspace.geometry.closed?
|
@@ -51,11 +73,12 @@ module AIPP
|
|
51
73
|
layer = layer_from(tds[-3].text)
|
52
74
|
layer.class = class_from(tds[1].text) if tds.count == 5
|
53
75
|
layer.location_indicator = LOCATION_INDICATORS.fetch("#{airspace.type} #{airspace.name}", nil)
|
54
|
-
|
55
|
-
|
76
|
+
if airspace.local_type == 'SIV' # services parsed for SIV only
|
77
|
+
layer.add_services services_from(tds[-2], remarks)
|
78
|
+
end
|
56
79
|
layer.timetable = timetable_from! remarks
|
57
80
|
layer.remarks = remarks_from remarks
|
58
|
-
airspace.
|
81
|
+
airspace.add_layer layer
|
59
82
|
end
|
60
83
|
rescue => error
|
61
84
|
warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
@@ -68,7 +91,7 @@ module AIPP
|
|
68
91
|
private
|
69
92
|
|
70
93
|
def airspace_from(td)
|
71
|
-
spans = td.children.split {
|
94
|
+
spans = td.children.split { _1.name == 'br' }.first.css(:span).drop_while { _1.text.match? '\s' }
|
72
95
|
source_type = spans[0].text.blank_to_nil
|
73
96
|
fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
|
74
97
|
AIXM.airspace(
|
@@ -84,6 +107,58 @@ module AIPP
|
|
84
107
|
text.strip
|
85
108
|
end
|
86
109
|
|
110
|
+
def services_from(td, remarks)
|
111
|
+
text = td.text.cleanup
|
112
|
+
text = SERVICE_FIXES.fetch(text, text) # fix incomplete service columns
|
113
|
+
text.gsub!(/(info|app)\s+([\d.]{3,})/i, "\\1\n\\2") # put frequencies on separate line
|
114
|
+
text.gsub!(/(\d)\s*\/\s*(\d)/, "\\1\n\\2") # split frequencies onto separate lines
|
115
|
+
units, services = [], []
|
116
|
+
text.split("\n").each do |line|
|
117
|
+
case line
|
118
|
+
when /^(.+(?:info|app))$/i # service
|
119
|
+
callsign = $1
|
120
|
+
service = AIXM.service(
|
121
|
+
# TODO: add source as soon as it is supported by components
|
122
|
+
# source: source(position: td.line),
|
123
|
+
type: :flight_information_service
|
124
|
+
).tap do |service|
|
125
|
+
service.timetable = AIXM::H24 if remarks.match? /h\s?24/i
|
126
|
+
end
|
127
|
+
services << [service, callsign]
|
128
|
+
units.shift.add_service service
|
129
|
+
when /^(.*?)(\d{3}[.\d]*)(.*)$/ # frequency
|
130
|
+
label, freq, footnote = $1, $2, $3
|
131
|
+
service, callsign = services.last
|
132
|
+
frequency = AIXM.frequency(
|
133
|
+
transmission_f: AIXM.f(freq.to_f, :mhz),
|
134
|
+
callsigns: { en: callsign, fr: callsign }
|
135
|
+
).tap do |frequency|
|
136
|
+
frequency.type = :standard
|
137
|
+
frequency.timetable = AIXM::H24 if remarks.match? /h\s?24/i
|
138
|
+
frequency.remarks = [
|
139
|
+
(remarks.extract(/#{Regexp.escape(footnote.strip)}\s*([^\n]+)/).join(' / ') unless footnote.empty?),
|
140
|
+
label.strip
|
141
|
+
].map(&:blank_to_nil).compact.join(' / ').blank_to_nil
|
142
|
+
end
|
143
|
+
service.add_frequency frequency
|
144
|
+
when /.*(?<!info|app|\d{3}|\))$/i # unit
|
145
|
+
unit = AIXM.unit(
|
146
|
+
source: source(position: td.line),
|
147
|
+
organisation: organisation_lf, # TODO: not yet implemented
|
148
|
+
name: line,
|
149
|
+
type: :flight_information_centre,
|
150
|
+
class: :icao
|
151
|
+
)
|
152
|
+
units << ((u = find(unit).first) ? (unit = u) : (add unit))
|
153
|
+
else
|
154
|
+
fail("cannot parse `#{text}'")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
services = services.map(&:first)
|
158
|
+
fail("at least one service has no frequency") if services.any? { _1.frequencies.none? }
|
159
|
+
services
|
160
|
+
end
|
161
|
+
|
87
162
|
def remarks_from(text)
|
88
163
|
text.strip.gsub(/(\s)\s+/, '\1').blank_to_nil
|
89
164
|
end
|
@@ -28,7 +28,9 @@ module AIPP
|
|
28
28
|
observations: tds[9]
|
29
29
|
}
|
30
30
|
)
|
31
|
-
|
31
|
+
if navigational_aid && aixm.features.find_by(navigational_aid.class, id: navigational_aid.id, xy: navigational_aid.xy).none?
|
32
|
+
add navigational_aid
|
33
|
+
end
|
32
34
|
rescue => error
|
33
35
|
warn("error parsing navigational aid at ##{index}: #{error.message}", pry: error)
|
34
36
|
end
|
@@ -10,13 +10,12 @@ module AIPP
|
|
10
10
|
prepare(html: read).css('tbody').each do |tbody|
|
11
11
|
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
12
12
|
tds = tr.css('td')
|
13
|
-
|
13
|
+
add AIXM.designated_point(
|
14
|
+
source: source(position: tr.line),
|
14
15
|
type: :icao,
|
15
16
|
id: tds[0].text.strip,
|
16
17
|
xy: xy_from(tds[1].text)
|
17
18
|
)
|
18
|
-
designated_point.source = source(position: tr.line)
|
19
|
-
add designated_point
|
20
19
|
rescue => error
|
21
20
|
warn("error parsing designated point at ##{index}: #{error.message}", pry: error)
|
22
21
|
end
|
@@ -24,6 +24,9 @@ module AIPP
|
|
24
24
|
'ZIT' => { type: 'P', local_type: 'ZIT' }
|
25
25
|
}.freeze
|
26
26
|
|
27
|
+
# Radius to use for zones consisting of one point only
|
28
|
+
POINT_RADIUS = AIXM.d(1, :km).freeze
|
29
|
+
|
27
30
|
def parse
|
28
31
|
skip = false
|
29
32
|
prepare(html: read).css('h4, thead ~ tbody').each do |tag|
|
@@ -48,11 +51,21 @@ module AIPP
|
|
48
51
|
begin
|
49
52
|
tds = tr.css('td')
|
50
53
|
airspace.geometry = geometry_from tds[0].text
|
54
|
+
if airspace.geometry.point? # convert point to circle
|
55
|
+
airspace.geometry = AIXM.geometry(
|
56
|
+
AIXM.circle(
|
57
|
+
center_xy: airspace.geometry.segments.first.xy,
|
58
|
+
radius: POINT_RADIUS
|
59
|
+
)
|
60
|
+
)
|
61
|
+
end
|
51
62
|
fail("geometry is not closed") unless airspace.geometry.closed?
|
52
|
-
airspace.
|
63
|
+
airspace.add_layer layer_from(tds[1].text)
|
53
64
|
airspace.layers.first.timetable = timetable_from! tds[2].text
|
54
65
|
airspace.layers.first.remarks = remarks_from(tds[2], tds[3], tds[4])
|
55
|
-
|
66
|
+
if aixm.features.find_by(:airspace, type: airspace.type, id: airspace.id).none?
|
67
|
+
add airspace
|
68
|
+
end
|
56
69
|
rescue => error
|
57
70
|
warn("error parsing airspace `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
58
71
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
|
4
|
+
# Obstacles
|
5
|
+
class ENR54 < AIP
|
6
|
+
|
7
|
+
include AIPP::LF::Helpers::Base
|
8
|
+
|
9
|
+
# Obstacles to be ignored
|
10
|
+
NAME_BLACKLIST = %w(51076 52055 59000 72039).freeze # all duplicates
|
11
|
+
|
12
|
+
# Map type descriptions to AIXM types and remarks
|
13
|
+
TYPES = {
|
14
|
+
'Antenne' => [:antenna],
|
15
|
+
'Bâtiment' => [:building],
|
16
|
+
'Câble' => [:other, 'Cable / Câble'],
|
17
|
+
'Centrale thermique' => [:building, 'Thermal power plant / Centrale thermique'],
|
18
|
+
"Château d'eau" => [:tower, "Water tower / Château d'eau"],
|
19
|
+
'Cheminée' => [:chimney],
|
20
|
+
'Derrick' => [:tower, 'Derrick'],
|
21
|
+
'Eglise' => [:tower, 'Church / Eglise'],
|
22
|
+
'Eolienne(s)' => [:wind_turbine],
|
23
|
+
'Grue' => [:tower, 'Crane / Grue'],
|
24
|
+
'Mât' => [:mast],
|
25
|
+
'Phare marin' => [:tower, 'Lighthouse / Phare marin'],
|
26
|
+
'Pile de pont' => [:other, 'Bridge piers / Pile de pont'],
|
27
|
+
'Portique' => [:building, 'Arch / Portique'],
|
28
|
+
'Pylône' => [:mast, 'Pylon / Pylône'],
|
29
|
+
'Silo' => [:tower, 'Silo'],
|
30
|
+
'Terril' => [:other, 'Spoil heap / Teril'],
|
31
|
+
'Torchère' => [:chimney, 'Flare / Torchère'],
|
32
|
+
'Tour' => [:tower],
|
33
|
+
'Treillis métallique' => [:other, 'Metallic grid / Treillis métallique']
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
def parse
|
37
|
+
tbody = prepare(html: read).css('tbody').last
|
38
|
+
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
39
|
+
tds = tr.css('td')
|
40
|
+
name = tds[0].text.cleanup
|
41
|
+
next if NAME_BLACKLIST.include? name
|
42
|
+
elevation, height = tds[4].text.cleanup.split(/[()]/).map { _1.cleanup.remove("\n") }
|
43
|
+
type, type_remarks = TYPES.fetch(tds[2].text.cleanup)
|
44
|
+
count = tds[3].text.cleanup.to_i
|
45
|
+
visibility = tds[5].text.cleanup
|
46
|
+
obstacle = AIXM.obstacle(
|
47
|
+
source: source(position: tr.line),
|
48
|
+
name: name,
|
49
|
+
type: type,
|
50
|
+
xy: xy_from(tds[1].text),
|
51
|
+
z: z_from(elevation + 'AMSL')
|
52
|
+
).tap do |obstacle|
|
53
|
+
obstacle.height = d_from(height)
|
54
|
+
obstacle.marking = visibility.match?(/jour/i)
|
55
|
+
obstacle.lighting = visibility.match?(/nuit/i)
|
56
|
+
obstacle.remarks = remarks_from(type_remarks, (count if count > 1), tds[6].text)
|
57
|
+
end
|
58
|
+
if count > 1
|
59
|
+
obstacle_group = AIXM.obstacle_group(
|
60
|
+
source: obstacle.source,
|
61
|
+
name: obstacle.name
|
62
|
+
).tap do |obstacle_group|
|
63
|
+
obstacle_group.remarks = "#{count} obstacles"
|
64
|
+
end
|
65
|
+
obstacle_group.add_obstacle obstacle
|
66
|
+
add obstacle_group
|
67
|
+
else
|
68
|
+
add obstacle
|
69
|
+
end
|
70
|
+
rescue => error
|
71
|
+
warn("error parsing obstacle at ##{index}: #{error.message}", pry: error)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def remarks_from(*parts)
|
78
|
+
part_titles = ['TYPE', 'NUMBER/NOMBRE', 'DETAILS']
|
79
|
+
[].tap do |remarks|
|
80
|
+
parts.each.with_index do |part, index|
|
81
|
+
if part
|
82
|
+
part = part.to_s.cleanup.blank_to_nil
|
83
|
+
remarks << "**#{part_titles[index]}**\n#{part}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end.join("\n\n").blank_to_nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -19,15 +19,15 @@ module AIPP
|
|
19
19
|
prepare(html: read).css('tbody').each do |tbody|
|
20
20
|
tbody.css('tr').to_enum.each_slice(2).with_index(1) do |trs, index|
|
21
21
|
begin
|
22
|
-
id, activity_and_name,
|
22
|
+
id, activity_and_name, upper_limit, timetable = trs.first.css('td')
|
23
23
|
activity, name = activity_and_name.css('span')
|
24
|
-
|
25
|
-
|
26
|
-
geometry,
|
27
|
-
|
24
|
+
lateral_limit, lower_limit, remarks = trs.last.css('td')
|
25
|
+
lateral_limit.search('br').each { _1.replace("|||") }
|
26
|
+
geometry, lateral_limit = lateral_limit.text.split('|||', 2)
|
27
|
+
lateral_limit&.gsub!('|||', "\n")
|
28
28
|
remarks = [remarks&.text&.cleanup&.blank_to_nil]
|
29
29
|
s = timetable&.text&.cleanup and remarks.prepend('**SCHEDULE**', s, '')
|
30
|
-
s =
|
30
|
+
s = lateral_limit&.cleanup and remarks.prepend('**LATERAL LIMIT**', s, '')
|
31
31
|
airspace = AIXM.airspace(
|
32
32
|
source: source(position: trs.first.line),
|
33
33
|
id: id.text.strip,
|
@@ -35,10 +35,12 @@ module AIPP
|
|
35
35
|
name: [id.text.strip, name.text.cleanup].join(' ')
|
36
36
|
).tap do |airspace|
|
37
37
|
airspace.geometry = geometry_from(geometry)
|
38
|
-
airspace.
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
airspace.add_layer(
|
39
|
+
layer_from([upper_limit.text, lower_limit.text].join('---').cleanup).tap do |layer|
|
40
|
+
layer.activity = ACTIVITIES.fetch(activity.text.downcase).fetch(:activity)
|
41
|
+
layer.remarks = remarks.compact.join("\n")
|
42
|
+
end
|
43
|
+
)
|
42
44
|
end
|
43
45
|
rescue => error
|
44
46
|
warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|