aipp 0.2.5 → 0.2.6
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.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)
|