aipp 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +15 -0
  5. data/README.md +122 -37
  6. data/TODO.md +4 -0
  7. data/aipp.gemspec +8 -3
  8. data/lib/aipp.rb +14 -2
  9. data/lib/aipp/aip.rb +44 -29
  10. data/lib/aipp/downloader.rb +115 -0
  11. data/lib/aipp/executable.rb +6 -6
  12. data/lib/aipp/parser.rb +23 -23
  13. data/lib/aipp/patcher.rb +47 -0
  14. data/lib/aipp/pdf.rb +123 -0
  15. data/lib/aipp/regions/LF/AD-1.3.rb +162 -0
  16. data/lib/aipp/regions/LF/AD-1.3.yml +511 -0
  17. data/lib/aipp/regions/LF/AD-1.6.rb +31 -0
  18. data/lib/aipp/regions/LF/AD-2.rb +316 -0
  19. data/lib/aipp/regions/LF/AD-2.yml +185 -0
  20. data/lib/aipp/regions/LF/AD-3.1.rb-NEW +11 -0
  21. data/lib/aipp/regions/LF/ENR-2.1.rb +25 -24
  22. data/lib/aipp/regions/LF/ENR-4.1.rb +24 -23
  23. data/lib/aipp/regions/LF/ENR-4.3.rb +8 -6
  24. data/lib/aipp/regions/LF/ENR-5.1.rb +32 -22
  25. data/lib/aipp/regions/LF/ENR-5.5.rb-NEW +11 -0
  26. data/lib/aipp/regions/LF/helpers/AD_radio.rb +90 -0
  27. data/lib/aipp/regions/LF/helpers/URL.rb +26 -0
  28. data/lib/aipp/regions/LF/helpers/common.rb +186 -0
  29. data/lib/aipp/version.rb +1 -1
  30. data/lib/core_ext/enumerable.rb +52 -0
  31. data/lib/core_ext/nil_class.rb +10 -0
  32. data/lib/core_ext/object.rb +42 -0
  33. data/lib/core_ext/string.rb +105 -0
  34. data/spec/fixtures/archive.zip +0 -0
  35. data/spec/fixtures/document.pdf +0 -0
  36. data/spec/fixtures/document.pdf.json +1 -0
  37. data/spec/fixtures/new.html +6 -0
  38. data/spec/fixtures/new.pdf +0 -0
  39. data/spec/fixtures/new.txt +1 -0
  40. data/spec/lib/aipp/downloader_spec.rb +81 -0
  41. data/spec/lib/aipp/patcher_spec.rb +46 -0
  42. data/spec/lib/aipp/pdf_spec.rb +124 -0
  43. data/spec/lib/core_ext/enumberable_spec.rb +76 -0
  44. data/spec/lib/core_ext/nil_class_spec.rb +11 -0
  45. data/spec/lib/core_ext/string_spec.rb +88 -0
  46. data/spec/spec_helper.rb +1 -0
  47. metadata +123 -23
  48. data/lib/aipp/progress.rb +0 -40
  49. data/lib/aipp/refinements.rb +0 -114
  50. data/lib/aipp/regions/LF/helper.rb +0 -177
  51. data/spec/lib/aipp/refinements_spec.rb +0 -123
@@ -0,0 +1,31 @@
1
+ module AIPP
2
+ module LF
3
+
4
+ # Aerodromes radiocommunication facilities (VFR only)
5
+ class AD16 < AIP
6
+
7
+ include AIPP::LF::Helpers::Common
8
+ include AIPP::LF::Helpers::ADRadio
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
+ prepare(html: read).css('tbody').first do |tbody|
19
+ tbody.css('tr').group_by_chunks { |e| e.attr(:id).match?(/-TXT_NAME-/) }.each do |tr, trs|
20
+ id = tr.css('span[id*="CODE_ICAO"]').text.cleanup
21
+ next unless id = ID_FIXES.fetch(id, id)
22
+ @airport = select(:airport, id: id).first
23
+ addresses_from(trs).each { |a| @airport.add_address(a) }
24
+ units_from(trs).each(&method(:write))
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,316 @@
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::Common
8
+ include AIPP::LF::Helpers::ADRadio
9
+ using AIXM::Refinements
10
+
11
+ # Map source types to type and optional local type
12
+ SOURCE_TYPES = {
13
+ 'CTR' => { type: 'CTR' },
14
+ 'RMZ' => { type: 'RAS', local_type: 'RMZ' },
15
+ 'TMZ' => { type: 'RAS', local_type: 'TMZ' },
16
+ 'RMZ-TMZ' => { type: 'RAS', local_type: 'RMZ-TMZ' }
17
+ }.freeze
18
+
19
+ # Airports without VAC (e.g. military installations)
20
+ NO_VAC = %w(LFOA LFBC LFQE LFOE LFSX LFBM LFSO LFMO LFQP LFSI LFKS LFPV).freeze
21
+
22
+ # Airports without VFR reporting points
23
+ # TODO: designated points on map but no list (LFLD LFSN LFBS) or no AD info (LFRL)
24
+ NO_DESIGNATED_POINTS = %w(LFAB LFAC LFAV LFAY LFBK LFBN LFBX LFCC LFCI LFCK LFCY LFDH LFDJ LFDN LFEC 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
25
+
26
+ # Map synonyms for +correlate+
27
+ SYNONYMS = [
28
+ 'nord', 'north',
29
+ 'est', 'east',
30
+ 'sud', 'south',
31
+ 'ouest', 'west',
32
+ 'inst', 'instruction',
33
+ 'junction', 'intersection',
34
+ 'harbour', 'port',
35
+ 'mouth', 'embouchure',
36
+ 'tower', 'chateau'
37
+ ].freeze
38
+
39
+ def parse
40
+ index_html = prepare(html: read("AD-0.6")) # index for AD-2.xxxx files
41
+ index_html.css('#AD-0\.6\.eAIP > .toc-block:nth-of-type(3) .toc-block a').each do |a|
42
+ @id = a.attribute('href').value[-4,4]
43
+ begin
44
+ aip_file = "AD-2.#{@id}"
45
+ html = prepare(html: read(aip_file))
46
+ # Airport
47
+ @remarks = []
48
+ @airport = AIXM.airport(
49
+ source: source(position: html.css('tr[id*="CODE_ICAO"]').first.line, aip_file: aip_file),
50
+ organisation: organisation_lf, # TODO: not yet implemented
51
+ id: @id,
52
+ name: html.css('tr[id*="CODE_ICAO"] td span:nth-of-type(2)').text.uptrans,
53
+ xy: xy_from(html.css('#AD-2\.2-Position_Geo_Arp td:nth-of-type(3)').text)
54
+ ).tap do |airport|
55
+ airport.z = elevation_from(html.css('#AD-2\.2-Altitude_Reference td:nth-of-type(3)').text)
56
+ airport.declination = declination_from(html.css('#AD-2\.2-Declinaison_Magnetique td:nth-of-type(3)').text)
57
+ airport.transition_z = AIXM.z(5000, :qnh) # TODO: default - exceptions may exist
58
+ airport.timetable = timetable_from(html.css('#AD-2\.3-Gestionnaire_AD td:nth-of-type(3)').text)
59
+ end
60
+ runways_from(html.css('div[id*="-AD-2\.12"] tbody')).each { |r| @airport.add_runway(r) if r }
61
+ helipads_from(html.css('div[id*="-AD-2\.16"] tbody')).each { |h| @airport.add_helipad(h) if h }
62
+ text = html.css('#AD-2\.2-Observations td:nth-of-type(3)').text
63
+ @airport.remarks = ([remarks_from(text)] + @remarks).compact.join("\n\n").blank_to_nil
64
+ write @airport
65
+ # Airspaces
66
+ airspaces_from(html.css('div[id*="-AD-2\.17"] tbody')).each(&method(:write))
67
+ # Radio
68
+ trs = html.css('div[id*="-AD-2\.18"] tbody tr')
69
+ addresses_from(trs).each { |a| @airport.add_address(a) }
70
+ units_from(trs).each(&method(:write))
71
+ # Landing aids
72
+ # TODO: LOC/GP/DME as of section 2.19
73
+ # Designated points
74
+ unless NO_VAC.include?(@id) || NO_DESIGNATED_POINTS.include?(@id)
75
+ pdf = read("VAC-#{@id}")
76
+ designated_points_from(pdf).tap do |designated_points|
77
+ fix_designated_point_remarks(designated_points)
78
+ # debug(designated_points)
79
+ designated_points.each(&method(:write))
80
+ end
81
+ end
82
+ rescue => error
83
+ warn("error parsing airport #{@id}: #{error.message}", pry: error)
84
+ end
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def elevation_from(text)
91
+ value, unit = text.strip.split
92
+ AIXM.z(AIXM.d(value.to_i, unit).to_ft.dist, :qnh)
93
+ end
94
+
95
+ def declination_from(text)
96
+ value, direction = text.strip.split('°')
97
+ value = value.to_f * (direction == 'W' ? -1 : 1)
98
+ end
99
+
100
+ def remarks_from(text)
101
+ text.sub(/NIL|\(\*\)\s+/, '').strip.gsub(/(\s)\s+/, '\1').blank_to_nil
102
+ end
103
+
104
+ def runways_from(tbody)
105
+ directions_map = tbody.css('tr[id*="TXT_DESIG"]').map do |tr|
106
+ [AIXM.a(tr.css('td:first-of-type').text.strip), tr]
107
+ end.to_h
108
+ remarks_map = tbody.css('tr[id*="TXT_RMK_NAT"]').map do |tr|
109
+ [tr.text.strip[/\A\((\d+)\)/, 1].to_i, tr.css('span')]
110
+ end.to_h
111
+ directions = directions_map.keys
112
+ grouped_directions = directions.map do |direction|
113
+ inverted_direction = direction.invert
114
+ if directions.include? inverted_direction
115
+ [direction, inverted_direction].map(&:to_s).sort.join('/')
116
+ else
117
+ direction.to_s
118
+ end
119
+ end.uniq
120
+ grouped_directions.map do |runway_name|
121
+ AIXM.runway(name: runway_name).tap do |runway|
122
+ %i(forth back).each do |direction_attr|
123
+ if direction = runway.send(direction_attr)
124
+ tr = directions_map[direction.name]
125
+ if direction_attr == :forth
126
+ length, width = tr.css('td:nth-of-type(3)').text.strip.split('x')
127
+ runway.length = AIXM.d(length.strip.to_i, :m)
128
+ runway.width = AIXM.d(width.strip.to_i, :m)
129
+ text = tr.css('td:nth-of-type(5)').text.strip.split(%r<\W+/\W+>).first
130
+ runway.surface.composition = COMPOSITIONS.fetch(text)[:composition]
131
+ runway.surface.preparation = COMPOSITIONS.fetch(text)[:preparation]
132
+ if (text = tr.css('td:nth-of-type(4)').text).match?(AIXM::PCN_RE)
133
+ runway.surface.pcn = text
134
+ end
135
+ end
136
+ text = tr.css('td:nth-of-type(6)').text.strip
137
+ direction.xy = (xy_from(text) unless text.match?(/\A(\(.*)?\z/m))
138
+ if (text = tr.css('td:nth-of-type(7)').text.strip[/thr:\s+(\d+\s+\w+)/i, 1]).present?
139
+ direction.z = elevation_from(text)
140
+ end
141
+ if (text = tr.css('td:nth-of-type(2)').text.strip.sub(/\A(\d+).*$/m, '\1')).present?
142
+ direction.geographic_orientation = AIXM.a(text.to_i)
143
+ end
144
+ if (text = tr.css('td:nth-of-type(6)').text[/\((.+)\)/m, 1]).present?
145
+ direction.displaced_threshold = xy_from(text)
146
+ end
147
+ if (text = tr.css('td:nth-of-type(10)').text.strip[/\A\((\d+)\)/, 1]).present?
148
+ direction.remarks = remarks_from(remarks_map.fetch(text.to_i).text)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ def helipads_from(tbody)
157
+ text_fr = tbody.css('td:nth-of-type(3)').text.compact
158
+ text_en = tbody.css('td:nth-of-type(4)').text.compact
159
+ case text_fr
160
+ when /NIL/, /\A\W*\z/
161
+ []
162
+ when /instructions?\s+twr/i
163
+ @remarks << "HELICOPTER:\nSur instructions TWR.\nOn TWR clearance."
164
+ []
165
+ when AIXM::DMS_RE
166
+ text_fr.scan(AIXM::DMS_RE).each_slice(2).with_index(1).map do |(lat, long), index|
167
+ AIXM.helipad(name: "H#{index}").tap do |helipad|
168
+ helipad.xy = AIXM.xy(lat: lat.first, long: long.first)
169
+ end
170
+ end
171
+ else
172
+ @remarks << ['HELICOPTER:', text_fr.blank_to_nil, text_en.blank_to_nil].compact.join("\n")
173
+ []
174
+ end
175
+ end
176
+
177
+ def airspaces_from(tbody)
178
+ return [] if tbody.text.blank?
179
+ airspace = nil
180
+ tbody.css('tr').to_enum.with_object([]) do |tr, array|
181
+ if tr.attr(:class) =~ /keep-with-next-row/
182
+ airspace = airspace_from tr
183
+ else
184
+ tds = tr.css('td')
185
+ airspace.geometry = geometry_from tds[0].text
186
+ fail("geometry is not closed") unless airspace.geometry.closed?
187
+ airspace.layers << layer_from(tds[2].text, tds[1].text.strip)
188
+ airspace.layers.first.timetable = timetable_from tds[4].text
189
+ airspace.layers.first.remarks = remarks_from(tds[4].text)
190
+ array << airspace
191
+ end
192
+ end
193
+ end
194
+
195
+ def airspace_from(tr)
196
+ spans = tr.css(:span)
197
+ source_type = spans[1].text.blank_to_nil
198
+ fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
199
+ AIXM.airspace(
200
+ name: [spans[2].text, anglicise(name: spans[3]&.text)].compact.join(' '),
201
+ type: SOURCE_TYPES.dig(source_type, :type),
202
+ local_type: SOURCE_TYPES.dig(source_type, :local_type)
203
+ ).tap do |airspace|
204
+ airspace.source = source(position: tr.line)
205
+ end
206
+ end
207
+
208
+ def designated_points_from(pdf, recursive=false)
209
+ from = (pdf.text =~ /^(.*?coordinates.*?names?)/i)
210
+ return [] if recursive && !from
211
+ warn("no designated points section begin found for #{@id}", pry: binding) unless from
212
+ from += $1.length
213
+ 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)
214
+ warn("no designated points section end found for #{@id}", pry: binding) unless to
215
+ from, to = from + pdf.range.min, to + pdf.range.min # offset when recursive
216
+ buffer = {}
217
+ pdf.from(from).to(to).each_line.with_object([]) do |(line, page, last), designated_points|
218
+ line.remove!(/\u2190/) # remove arrow symbols
219
+ has_id = $1 if line.sub!(/^\s{,20}([A-Z][A-Z\d ]{1,3})(?=\W)/, '')
220
+ has_xy = line.match?(AIXM::DMS_RE)
221
+ designated_points << designated_point_from(buffer, pdf) if has_id || has_xy
222
+ if has_xy
223
+ 2.times { (buffer[:xy] ||= []) << $1 if line.sub!(AIXM::DMS_RE, '') }
224
+ buffer[:xy]&.compact!
225
+ line.remove!(/\d{3,4}\D.+?MTG/) # remove extra columns (e.g. LFML)
226
+ line.remove!(/[\s#{AIXM::MIN}#{AIXM::SEC}]*[-\u2013]/) # remove dash between coordinates
227
+ end
228
+ buffer[:page] = page
229
+ buffer[:id] = has_id if has_id
230
+ buffer[:remarks] = [buffer[:remarks], line].join("\n")
231
+ designated_points << designated_point_from(buffer, pdf) if last
232
+ end.compact + designated_points_from(pdf.from(to).to(:end), true)
233
+ end
234
+
235
+ def designated_point_from(buffer, pdf)
236
+ if buffer[:id] && buffer[:xy]&.size == 2
237
+ buffer[:remarks].gsub!(/ {20}/, "\n") # recognize empty column space
238
+ buffer[:remarks].remove!(/\(\d+\)/) # remove footnotes
239
+ buffer[:remarks] = buffer[:remarks].unglue # separate glued words
240
+ AIXM.designated_point(
241
+ source: source(position: buffer[:page], aip_file: pdf.file.basename('.*').to_s),
242
+ type: :vfr_mandatory_reporting_point,
243
+ id: buffer[:id].remove(/\W/),
244
+ xy: AIXM.xy(lat: buffer[:xy].first, long: buffer[:xy].last)
245
+ ).tap do |designated_point|
246
+ designated_point.airport = @airport
247
+ designated_point.remarks = buffer[:remarks].compact.blank_to_nil
248
+ buffer.clear
249
+ end
250
+ end
251
+ end
252
+
253
+ # Assign scattered similar remarks to one and the same designated point
254
+ def fix_designated_point_remarks(designated_points)
255
+ one = nil
256
+ designated_points.map do |two|
257
+ if one
258
+ one_lines, two_lines = one.remarks&.lines, two.remarks&.lines
259
+ if one_lines && two_lines
260
+ if one_lines.count > 1 && (line = one_lines.last) !~ %r(\s/\s)
261
+ # Move up
262
+ if line.correlate(remainder = one_lines[0..-2].join, SYNONYMS) < line.correlate(two.remarks)
263
+ two.remarks = [line, two.remarks].join("\n").compact
264
+ one.remarks = remainder.compact
265
+ end
266
+ elsif two_lines.count > 1 && (line = two_lines.first) !~ %r(\s/\s)
267
+ # Move down
268
+ line = two_lines.first
269
+ if line.correlate(remainder = two_lines[1..-1].join, SYNONYMS) < line.correlate(one.remarks)
270
+ one.remarks = [one.remarks, line].join("\n").compact
271
+ two.remarks = remainder.compact
272
+ end
273
+ end
274
+ end
275
+ end
276
+ one = two
277
+ end.map do |designated_point|
278
+ designated_point.remarks = designated_point.remarks&.cleanup.blank_to_nil
279
+ end
280
+ end
281
+
282
+ # def debug(dp)
283
+ # f = "/Users/sschwyn/Desktop/okay/#{@id}.txt"
284
+ # result = "\n--- #{@id} ---\n\n".red
285
+ # dp.each do |d|
286
+ # result += d.id.red + "\t#{d.xy.lat} - #{d.xy.long}\n"
287
+ # result += "#{d.remarks}\n\n".blue
288
+ # end
289
+ # result += "#{dp.count} point(s) for #{@id}".red
290
+ # unless File.exist?(f) && result == File.read(f)
291
+ # puts result
292
+ # gets
293
+ # puts "\e[H\e[2J"
294
+ # end
295
+ # File.write(f, result)
296
+ # end
297
+
298
+ patch AIXM::Component::Runway::Direction, :xy do |parser, object, value|
299
+ throw :abort unless value.nil?
300
+ @fixtures ||= YAML.load_file(Pathname(__FILE__).dirname.join('AD-2.yml'))
301
+ airport_id = parser.instance_variable_get(:@airport).id
302
+ direction_name = object.name.to_s
303
+ throw :abort if (xy = @fixtures.dig('runways', airport_id, direction_name, 'xy')).nil?
304
+ lat, long = xy.split(/\s+/)
305
+ AIXM.xy(lat: lat, long: long)
306
+ end
307
+
308
+ patch AIXM::Feature::NavigationalAid, :remarks do |parser, object, value|
309
+ @fixtures ||= YAML.load_file(Pathname(__FILE__).dirname.join('AD-2.yml'))
310
+ airport_id, designated_point_id = object.airport.id, object.id
311
+ @fixtures.dig('designated_points', airport_id, designated_point_id, 'remarks') || throw(:abort)
312
+ end
313
+
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,185 @@
1
+ # On Google Maps, click in the middle of the runway beginning, then click on
2
+ # the coordinates in the little window displayed at the bottom. The DMS
3
+ # coordinates will then appear in the left column ready for copy/paste.
4
+ ---
5
+ runways:
6
+ LFLP:
7
+ "04R":
8
+ xy: 45°55'32.1"N 6°05'49.2"E
9
+ "22L":
10
+ xy: 45°55'53.4"N 6°06'13.4"E
11
+ LFOQ:
12
+ "02L":
13
+ xy: 47°40'11.2"N 1°12'14.1"E
14
+ "20R":
15
+ xy: 47°40'39.6"N 1°12'30.7"E
16
+ "02R":
17
+ xy: 47°40'10.4"N 1°12'17.4"E
18
+ "20L":
19
+ xy: 47°40'38.3"N 1°12'33.8"E
20
+ "12L":
21
+ xy: 47°40'49.9"N 1°12'02.3"E
22
+ "30R":
23
+ xy: 47°40'38.9"N 1°12'36.2"E
24
+ "12R":
25
+ xy: 47°40'49.9"N 1°12'02.3"E
26
+ "30L":
27
+ xy: 47°40'39.1"N 1°12'28.3"E
28
+ LFMK:
29
+ "10L":
30
+ xy: 43°13'01.3"N 2°18'19.7"E
31
+ "28R":
32
+ xy: 43°12'58.5"N 2°18'54.6"E
33
+ LFLH:
34
+ "17L":
35
+ xy: 46°49'57.5"N 4°49'14.4"E
36
+ "35R":
37
+ xy: 46°49'29.8"N 4°49'21.2"E
38
+ LFLB:
39
+ "18L":
40
+ xy: 45°38'16.9"N 5°52'52.2"E
41
+ "36R":
42
+ xy: 45°37'54.4"N 5°52'54.0"E
43
+ LFBG:
44
+ "05R":
45
+ xy: 45°38'56.8"N 0°19'07.6"W
46
+ "23L":
47
+ xy: 45°39'15.7"N 0°18'36.3"W
48
+ LFAB:
49
+ "13L":
50
+ xy: 49°53'12.1"N 1°04'40.1"E
51
+ "31R":
52
+ xy: 49°52'59.8"N 1°05'06.2"E
53
+ LFRL:
54
+ "13":
55
+ xy: 48°17'01.8"N 4°26'46.5"W
56
+ "31":
57
+ xy: 48°16'48.7"N 4°26'21.5"W
58
+ LFRM:
59
+ "02R":
60
+ xy: 47°56'40.1"N 0°12'01.6"E
61
+ "20L":
62
+ xy: 47°57'09.6"N 0°12'18.0"E
63
+ LFSM:
64
+ "08L":
65
+ xy: 47°29'10.8"N 6°47'08.0"E
66
+ "26R":
67
+ xy: 47°29'15.9"N 6°47'48.2"E
68
+ LFBR:
69
+ "12L":
70
+ xy: 43°27'08.9"N 1°15'45.3"E
71
+ "30R":
72
+ xy: 43°27'04.4"N 1°15'57.6"E
73
+ "12R":
74
+ xy: 43°27'02.1"N 1°15'31.3"E
75
+ "30L":
76
+ xy: 43°26'50.1"N 1°16'04.0"E
77
+ LFSN:
78
+ "03R":
79
+ xy: 48°41'15.5"N 6°13'44.5"E
80
+ "21L":
81
+ xy: 48°41'31.1"N 6°13'57.3"E
82
+ LFQG:
83
+ "12L":
84
+ xy: 47°00'19.6"N 3°06'30.2"E
85
+ "30R":
86
+ xy: 47°00'03.5"N 3°07'07.8"E
87
+ LFOJ:
88
+ "04":
89
+ xy: 47°59'24.5"N 1°45'29.9"E
90
+ "22":
91
+ xy: 47°59'49.3"N 1°45'58.2"E
92
+ LFBX:
93
+ "11L":
94
+ xy: 45°11'59.1"N 0°48'34.6"E
95
+ "29R":
96
+ xy: 45°11'48.2"N 0°49'10.1"E
97
+ LFQA:
98
+ "07R":
99
+ xy: 49°12'23.4"N 4°09'03.1"E
100
+ "25L":
101
+ xy: 49°12'36.9"N 4°09'50.2"E
102
+ LFRN:
103
+ "14L":
104
+ xy: 48°04'09.4"N 1°44'18.7"W
105
+ "32R":
106
+ xy: 48°03'55.4"N 1°44'02.4"W
107
+ LFOP:
108
+ "05":
109
+ xy: 49°23'05.4"N 1°11'11.4"E
110
+ "23":
111
+ xy: 49°23'26.9"N 1°11'44.1"E
112
+ LFSI:
113
+ "11L":
114
+ xy: 48°38'11.9"N 4°54'06.0"E
115
+ "29R":
116
+ xy: 48°37'57.2"N 4°54'58.1"E
117
+ LFMY:
118
+ "09":
119
+ xy: 43°36'32.8"N 5°06'33.4"E
120
+ "27":
121
+ xy: 43°36'32.3"N 5°07'08.8"E
122
+ "16L":
123
+ xy: 43°36'53.1"N 5°06'57.6"E
124
+ "34R":
125
+ xy: 43°36'22.9"N 5°07'09.5"E
126
+ "16R":
127
+ xy: 43°36'46.4"N 5°06'52.0"E
128
+ "34L":
129
+ xy: 43°36'12.1"N 5°07'05.4"E
130
+ LFQB:
131
+ "05":
132
+ xy: 48°19'01.1"N 4°00'28.2"E
133
+ "23":
134
+ xy: 48°19'15.8"N 4°00'56.2"E
135
+ "17R":
136
+ xy: 48°19'32.1"N 4°00'44.3"E
137
+ "35L":
138
+ xy: 48°19'03.1"N 4°00'49.1"E
139
+ LFLU:
140
+ "01L":
141
+ xy: 44°54'56.1"N 4°58'10.9"E
142
+ "19R":
143
+ xy: 44°55'34.4"N 4°58'18.1"E
144
+ "01R":
145
+ xy: 44°55'09.4"N 4°58'17.0"E
146
+ "19L":
147
+ xy: 44°55'22.3"N 4°58'19.4"E
148
+ LFAV:
149
+ "06":
150
+ xy: 50°19'40.0"N 3°27'19.8"E
151
+ "24":
152
+ xy: 50°19'49.6"N 3°27'47.5"E
153
+ LFRV:
154
+ "08":
155
+ xy: 47°43'26.6"N 2°43'51.6"W
156
+ "26":
157
+ xy: 47°43'31.0"N 2°43'04.4"W
158
+ designated_points:
159
+ LFQQ:
160
+ SA:
161
+ remarks: "Rond-point à l'Est de PONT A MARCQ, et au Nord du golf.\nTraffic circle East of PONT A MARCQ and North of the golf.\nPont de l’autoroute A1 et pont ligne TGV sur le canal de la\nDEULE."
162
+ SW:
163
+ remarks: "Bridge of A1 motorway and bridge of the high speed train\n(TGV) line over the DEULE canal."
164
+ LFML:
165
+ LP:
166
+ remarks: "St Cannat La Pile"
167
+ LFPG:
168
+ RH1:
169
+ remarks: "Péage autoroute A1.\nMotorway Toll A1."
170
+ RH2:
171
+ remarks: "Est du bourg (radial 181 PGS).\nEast Town (PGS 181 radial)."
172
+ LFLN:
173
+ S:
174
+ remarks: "Iguerande"
175
+ SA:
176
+ remarks: "ABM W ville d’Avrilly, ABM E château d’eau d’Avrilly\nABM W of town of Avrilly, ABM E water tower of Avrilly"
177
+ W:
178
+ remarks: "Le Donjon"
179
+ WA:
180
+ remarks: "Le Pin, château d’eau au SE de la localité\nLe Pin, water tower SE of the town"
181
+ LFBO:
182
+ WF:
183
+ remarks: "Lac de la Bordette.Forêt de Bouconne / La Bordette lake.\nBouconne forest"
184
+ WD:
185
+ remarks: "Echangeur N124.Zone commerciale En Jacca.A côté de\nLeroy Merlin.\nInterchange N124.Commercial zone En Jacca.Near Leroy\nMerlin."