aipp 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ce24174fda71fe6649f2d1f02eab03a2d9de0a59da6dbb581d581fe2dc2df4c
4
- data.tar.gz: 7643f1ec9c399e244ddbc13ef9a3a5df31eaf63e78c197a55c55838a0536d8ad
3
+ metadata.gz: 279a7722a7ee0070603236b477cdd96bb1babfea88e8e70cce7088069683e783
4
+ data.tar.gz: cb9453b4aca266eafc8aa7ae8d949d402c42f748d2193796b160fc17c9280068
5
5
  SHA512:
6
- metadata.gz: d4029172c77e70998f89c10eb19c6bb827d042fd7a9c9190538bb422e319c0387c85534de28e8d0357b0c676f99e7a222479419bbc34d00cc34ca3c918636987
7
- data.tar.gz: 3a294be41d8eb5067dc286b60ed304db554607694e094b7fa92a114ce9bac663261ba4246d45d4da133b753689535f2a708d09dd47dc06204dd5f86c5dd982f6
6
+ metadata.gz: 4102f1a2c77ea39f2b3200c61d49c7dd194768944688f42d4cb0740707d431b2dc290e35a81215de0c646284a59b8ed9fee4dff15a6cb65616e70e3cf979868a
7
+ data.tar.gz: c8087248c414c41e415c1361370f5ec84116e6a11d6aa23336eceb353f9af13f72de7b8459b7d4504a44ff4bb9206c042734e18da15a02010bd8f8074ff830d5
data/.gitignore CHANGED
@@ -3,4 +3,6 @@ Gemfile.lock
3
3
  pkg/*
4
4
  *.gem
5
5
  .bundle
6
+ .yardoc
6
7
  *.aixm
8
+ *.ofmx
@@ -1 +1 @@
1
- ruby-2.5.0
1
+ ruby-2.5.3
@@ -0,0 +1,3 @@
1
+ --no-private
2
+ lib/**/*.rb -
3
+ README.md CHANGELOG.md LICENSE.txt
@@ -1,22 +1,37 @@
1
+ ## 0.2.0
2
+
3
+ #### Changes
4
+ * Complete rewrite of the framework in order to allow cross-AIP parsing made necessary due to recent changes in LF AIP.
5
+
6
+ #### Additions
7
+ * LF/ENR-2.1
8
+ * Handling of errors and warnings optimized for parser development
9
+
10
+ #### Removals
11
+ * LF/AD-1.5
12
+
1
13
  ## 0.1.3
2
14
 
3
- * Additons:
4
- LF/AD-1.5
5
- * Minor changes:
6
- * Summary at end of run
15
+ #### Changes
16
+ * Summary at end of run
17
+
18
+ #### Additions
19
+ * LF/AD-1.5
20
+ * Source file line number evaluation
7
21
 
8
22
  ## 0.1.2
9
23
 
10
- * Additions:
11
- * LF/ENR-4.3
24
+ #### Additions:
25
+ * LF/ENR-4.3
12
26
 
13
27
  ## 0.1.1
14
28
 
15
- * Additions:
16
- * Helper modules
17
- * LF/ENR-4.1
29
+ #### Additions:
30
+ * LF/ENR-4.1
31
+ * Helper modules
18
32
 
19
33
  ## 0.1.0
20
34
 
21
- * Initial implementation of aip2aixm executable and infrastructure
35
+ #### Initial Implementation
36
+ * Framework and aip2aixm executable
22
37
  * LF/ENR-5.1
data/README.md CHANGED
@@ -8,9 +8,7 @@
8
8
 
9
9
  Parser for Aeronautical Information Publication (AIP) available online.
10
10
 
11
- This gem incluces an executable to download and parse aeronautical data, then
12
- export is as [AIXM](https://github.com/svoop/aixm) which can be consumed by
13
- [Open Flightmaps](https://openflightmaps.org).
11
+ This gem incluces two executables to download and parse aeronautical data, then export is as [AIXM](https://github.com/svoop/aixm) or [OFMX](https://github.com/openflightmaps/ofmx/wiki).
14
12
 
15
13
  * [Homepage](https://github.com/svoop/aipp)
16
14
  * Author: [Sven Schwyn - Bitcetera](http://www.bitcetera.com)
@@ -27,38 +25,126 @@ gem aipp
27
25
 
28
26
  ```
29
27
  aip2aixm --help
28
+ aip2ofmx --help
30
29
  ```
31
30
 
32
- ## Parsers
31
+ ## Storage
33
32
 
34
- Parsers are defined as modules and named <tt>lib/aipp/parser/{FIR}/{AIP}.rb</tt>. For
35
- them to plug in, you have to define the following public methods:
33
+ AIPP uses a storage directory for configuration, caching and in order to keep the results of previous runs. The default location is `~/.aipp`, however, you can pass a different directory with the `--storage` argument.
36
34
 
37
- * `url`<br>Must return the download URL of the AIP HTML as a string.
38
- * `convert!`<br>Takes `html` ([Nokogiri document](https://github.com/sparklemotion/nokogiri)) to parse and populate `aixm` ([AIXM document](https://github.com/svoop/aixm))
35
+ ## Regions
39
36
 
40
- You should read and honor the following attributes passed in from `aip2aixm`
41
- arguments:
37
+ The reference implementation is region "LF" (France).
42
38
 
43
- * `@fir`
44
- * `@aip`
45
- * `@airac`
46
- * `@limit`
39
+ To implement a region, you have to create a new directory <tt>lib/aipp/regions/{REGION}</tt> and place the following files there:
47
40
 
48
- You should `fail` on fatal and `warn` on non-fatal problems. If `$DEBUG` is
49
- +true+ (e.g. by use of the `-D` option), a Pry session will open if you use
50
- `warn` as follows:
41
+ ### helper.rb
42
+
43
+ Create the file <tt>helper.rb</tt> which defines the module `AIPP::LF::Helper` and usually contains the URL builder method `url_for` used by all AIP parsers:
44
+
45
+ ```ruby
46
+ module AIPP
47
+ module LF
48
+ module Helper
49
+
50
+ def url_for(aip_file)
51
+ # build and return the download URL for the aip file
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ ```
58
+
59
+ ### AIP Parsers
60
+
61
+ Say, you want to parse ENR-4.3, you have to create the file <tt>ENR-4.3.rb</tt> which defines the class `AIPP::LF::ENR43` as follows:
62
+
63
+ ```ruby
64
+ module AIPP
65
+ module LF
66
+ class ENR43 < AIP
67
+
68
+ DEPENDS = %w(ENR-2.1 ENR-2.2) # declare dependencies to other AIPs
69
+
70
+ def parse
71
+ html = load_html
72
+ # read from "html" (Nokogiri::HTML::Document) and write to "aixm"
73
+ end
74
+
75
+ end
76
+ end
77
+ end
78
+ ```
79
+
80
+ Some AIP may be split over several files which require a little more code to load the individual HTML source files:
81
+
82
+ ```ruby
83
+ module AIPP
84
+ module LF
85
+ class AD2 < AIP
86
+
87
+ def parse
88
+ %i(one two three).each do |part|
89
+ html = load_html(aip_file: "#{aip}.#{part}")
90
+ # read from "html" (Nokogiri::HTML::Document) and write to "aixm"
91
+ end
92
+ end
93
+
94
+ end
95
+ end
96
+ end
97
+ ```
98
+
99
+ Inside the `parse` method, you have access to the following objects:
100
+
101
+ * `aixm` – target: instance of `AIXM::Document` (see [AIXM Rubygem](https://github.com/svoop/aixm))
102
+ * `options` – arguments read from <tt>aip2aixm</tt> or <tt>aip2ofmx</tt> respectively
103
+ * `config` – configuration read from <tt>config.yml</tt>
104
+ * `cache` – virgin `OStruct` instance to make objects available across AIPs
105
+
106
+ Furthermore, you have access to any method defined in <tt>helper.rb</tt> and you can overwrite any of them if need be (most notably `url_for`).
107
+
108
+ ### Source File Line Numbers
109
+
110
+ In order to reference the source of an AIXM/OFMX feature, it's necessary to know the line number where a particular node occurs in the HTML source file. You can ask any HTML element as follows:
111
+
112
+ ```ruby
113
+ tr.line
114
+ ```
115
+
116
+ :warning: Make sure you have build Nokogumbo `--with-libxml2`. Otherwise, all elements will report line number `0` and therefore render OFMX documents invalid. See the [Nokogumbo README](https://github.com/rubys/nokogumbo/blob/master/README.md#flavors-of-nokogumbo) for more on this.
117
+
118
+ ### Errors
119
+
120
+ You should `fail` on fatal problems. The `-E` command line argument will open a Pry session when such an error occurs. Issue errors as usual:
51
121
 
52
122
  ```ruby
53
- warn("my message", binding)
123
+ fail "my message"
54
124
  ```
55
125
 
56
- ## Helpers
126
+ ### Warnings
127
+
128
+ You should `warn` on non-fatal problems. The `-W ID` command line argument will open a Pry session when then warning with the given ID occurs. To issue a warning:
129
+
130
+ ```ruby
131
+ warn("my message", context: binding) # open Pry with binding context
132
+ warn("my message", context: error) # open Pry with error context
133
+ ```
134
+
135
+ ### Informational Messages
136
+
137
+ You may `info` any other useful information:
138
+
139
+ ```ruby
140
+ info("my message") # show info only in verbose mode (-V)
141
+ info("my message", force: true) # always show info
142
+ info("my message", color: :green) # show info with this color
143
+ ```
57
144
 
58
- Any modules in <tt>lib/aipp/parser/{FIR}/helpers</tt> are required automatically and
59
- can be included and used in the parsers.
145
+ ## AIRAC Date Calculations
60
146
 
61
- ## AIRAC date calculations
147
+ The `AIPP::AIRAC` class is used to calculate AIRAC cycles:
62
148
 
63
149
  ```ruby
64
150
  airac = AIPP::AIRAC.new(Date.parse('2017-12-24'))
@@ -70,9 +156,13 @@ airac.next_id # => 1801
70
156
 
71
157
  ## References
72
158
 
73
- * AIP authorities
74
- * [LF: SIA](https://www.sia.aviation-civile.gouv.fr)
75
- * [Open Flightmaps](https://openflightmaps.org)
159
+ * LF - France
160
+ * [SIA – AIP publisher](https://www.sia.aviation-civile.gouv.fr)
161
+ * [OpenData – public data files](https://www.data.gouv.fr)
162
+ * [Protected Planet – protected area data files](https://www.protectedplanet.net)
163
+ * [Geo Maps – programmatically generated GeoJSON maps](https://github.com/simonepri/geo-maps)
164
+ * [Open Flightmaps – open-source aeronautical maps](https://openflightmaps.org)
165
+ * [AIXM Rubygem – AIXM/OFMX generator for Ruby](https://github.com/svoop/aixm)
76
166
 
77
167
  ## Development
78
168
 
@@ -88,8 +178,7 @@ Please submit issues on:
88
178
 
89
179
  https://github.com/svoop/aipp/issues
90
180
 
91
- To contribute code, fork the project on Github, add your code and submit a
92
- pull request:
181
+ To contribute code, fork the project on Github, add your code and submit a pull request:
93
182
 
94
183
  https://help.github.com/articles/fork-a-repo
95
184
 
@@ -30,8 +30,10 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'guard'
31
31
  spec.add_development_dependency 'guard-minitest'
32
32
 
33
- spec.add_runtime_dependency 'aixm', '~> 0', '>= 0.2.3'
33
+ spec.add_runtime_dependency 'aixm', '~> 0', '>= 0.3.2'
34
34
  spec.add_runtime_dependency 'nokogiri', '~> 1'
35
- spec.add_runtime_dependency 'nokogumbo', '~> 1'
35
+ spec.add_runtime_dependency 'nokogumbo', '~> 2'
36
+ spec.add_runtime_dependency 'colorize', '~> 0'
36
37
  spec.add_runtime_dependency 'pry', '~> 0'
38
+ spec.add_runtime_dependency 'pry-rescue', '~> 1'
37
39
  end
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/inline'
4
- require 'optparse'
5
- require 'yaml'
6
4
 
7
5
  gemfile do
8
6
  source 'https://rubygems.org'
@@ -10,51 +8,4 @@ gemfile do
10
8
  gem 'aipp', '~> 0'
11
9
  end
12
10
 
13
- class Executable
14
- attr_reader :fir, :aip, :airac, :ofm, :limit, :verbose
15
-
16
- def initialize
17
- @ogn = false
18
- @airac = AIPP::AIRAC.new(Date.today).date
19
- @limit = Float::INFINITY
20
- @verbose = false
21
- OptionParser.new do |o|
22
- o.banner = <<~END
23
- Download online AIP and convert it to AIXM for VFR.
24
- Usage: #{File.basename($0)} [options]
25
- END
26
- o.on('-A', '--about', 'author and license information') { puts 'Written by Sven Schwyn (bitcetera.com) and distributed under MIT license.'; exit }
27
- o.on('-l', '--list', "list available FIR/AIP and exit") { puts AIPP::Loader.list.to_yaml; exit }
28
- o.on('-f', '--fir STRING', String, 'FIR (flight information region, e.g. "LF")') { |v| @fir = v }
29
- o.on('-a', '--aip STRING', String, 'AIP (aeronautical information publication, e.g. "ENR-5.1")') { |v| @aip = v }
30
- o.on('-d', '--airac DATE', String, 'AIRAC date (e.g. "2018-01-04", default: current)') { |v| @airac = v }
31
- o.on('-o', '--[no-]ofm', 'Use OFM extensions (default: false)') { |v| @ofm = v }
32
- o.on('-L', '--limit INTEGER', Integer, 'Stop conversion after this many features') { |v| @limit = v }
33
- o.on('-D', '--[no-]debug', 'Enable debug mode (default: false)') { |v| $DEBUG = v }
34
- o.on('-v', '--[no-]verbose', 'Verbose error reporting (default: false)') { |v| @verbose = v }
35
- end.parse!
36
- fail(ArgumentError, "FIR must be supplied") unless @fir
37
- fail(ArgumentError, "AIP must be supplied") unless @aip
38
- fail(ArgumentError, "AIRAC date must be supplied") unless @airac
39
- end
40
-
41
- def run
42
- extensions = [(:ofm if ofm)].compact
43
- filename = [fir, aip, airac].join('_') + '.aixm'
44
- loader = AIPP::Loader.new(fir: fir, aip: aip, airac: airac, limit: limit)
45
- File.write(Pathname.new(Dir.pwd).join(filename), loader.aixm.to_aixm(*extensions))
46
- puts "#{loader.aixm.features.count} feature(s) written to #{filename}"
47
- end
48
- end
49
-
50
- begin
51
- executable = Executable.new
52
- executable.run
53
- rescue => exception
54
- if executable&.verbose
55
- raise exception
56
- else
57
- puts "#{File.basename($0)}: #{exception.message}"
58
- exit 1
59
- end
60
- end
11
+ AIPP::Executable.new(schema: File.basename($0)[4..-1].to_sym).run
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ ruby '>= 2.5'
8
+ gem 'aipp', '~> 0'
9
+ end
10
+
11
+ AIPP::Executable.new(schema: File.basename($0)[4..-1].to_sym).run
@@ -1,12 +1,29 @@
1
- require 'tmpdir'
1
+ require 'forwardable'
2
+ require 'colorize'
3
+ require 'pry'
4
+ require 'pry-rescue'
5
+ require 'optparse'
6
+ require 'yaml'
2
7
  require 'pathname'
3
8
  require 'open-uri'
9
+ require 'securerandom'
10
+ require 'tsort'
11
+ require 'ostruct'
4
12
  require 'date'
5
13
  require 'nokogiri'
6
14
  require 'nokogumbo'
7
15
  require 'aixm'
8
16
 
9
17
  require_relative 'aipp/version'
10
- require_relative 'aipp/airac'
11
18
  require_relative 'aipp/refinements'
12
- require_relative 'aipp/loader'
19
+ require_relative 'aipp/t_hash'
20
+ require_relative 'aipp/executable'
21
+ require_relative 'aipp/progress'
22
+ require_relative 'aipp/airac'
23
+ require_relative 'aipp/aip'
24
+ require_relative 'aipp/parser'
25
+
26
+ # Disable "did you mean?" suggestions
27
+ module DidYouMean::Correctable
28
+ remove_method :to_s
29
+ end
@@ -0,0 +1,63 @@
1
+ module AIPP
2
+
3
+ # @abstract
4
+ class AIP
5
+ using AIPP::Refinements
6
+ include AIPP::Progress
7
+ extend Forwardable
8
+
9
+ DEPENDS = []
10
+
11
+ # @return [String] AIP name (e.g. "ENR-2.1")
12
+ attr_reader :aip
13
+
14
+ # @return [AIXM::Document] target document
15
+ attr_reader :aixm
16
+
17
+ # @!method aixm
18
+ # @return (see AIPP::Parser#aixm)
19
+ # @!method config
20
+ # @return (see AIPP::Parser#config)
21
+ # @!method options
22
+ # @return (see AIPP::Parser#options)
23
+ # @!method cache
24
+ # @return (see AIPP::Parser#cache)
25
+ def_delegators :@parser, :aixm, :config, :options, :cache
26
+
27
+ def initialize(aip:, parser:)
28
+ @aip, @parser = aip, parser
29
+ self.class.include [:AIPP, options[:region], :Helper].constantize
30
+ end
31
+
32
+ # Load an AIP source file
33
+ #
34
+ # Depending on whether a local copy of the file exists, either:
35
+ # * download from URL to local storage and read from local storage
36
+ # * read from local storage
37
+ #
38
+ # An URL builder method +url_for(aip_file)+ must be defined either in
39
+ # +helper.rb+ or in the AIP parser definition (e.g. +ENR-2.1.rb+).
40
+ #
41
+ # @param aip_file [String] e.g. "ENR-2.1" or "AD-2.LFMV" (default: +aip+)
42
+ # @return [Nokogiri::HTML5] HTML document
43
+ def load_html(aip_file: nil)
44
+ aip_file ||= aip
45
+ unless (storage_path = storage_path(aip_file)).exist?
46
+ info("Downloading #{storage_file}", force: true)
47
+ storage_path.mkpath
48
+ IO.copy_stream(open(url_for(aip_file)), storage_path)
49
+ end
50
+ Nokogiri::HTML5(storage_path)
51
+ end
52
+
53
+ private
54
+
55
+ def storage_path(aip_file=nil)
56
+ options[:storage].
57
+ join(options[:airac].date.xmlschema).
58
+ join(("#{aip_file}.html" if aip_file).to_s)
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -1,43 +1,51 @@
1
1
  module AIPP
2
2
 
3
- ##
4
- # Calculate the AIRAC date and AIRAC ID for the given +any_date+
3
+ # AIRAC cycle date calculations
4
+ #
5
+ # @example
6
+ # airac = AIPP::AIRAC.new('2018-01-01')
7
+ # airac.date # => #<Date: 2017-12-07 ((2458095j,0s,0n),+0s,2299161j)>
8
+ # airac.id # => 1713
9
+ # airac.next_date # => #<Date: 2018-01-04 ((2458123j,0s,0n),+0s,2299161j)>
10
+ # airac.next_id # => 1801
5
11
  class AIRAC
6
- ##
7
12
  # First AIRAC date following the last cycle length modification
8
13
  ROOT_DATE = Date.parse('2015-06-25').freeze
9
14
 
10
- ##
11
15
  # Length of one AIRAC cycle
12
16
  DAYS_PER_CYCLE = 28
13
17
 
14
- attr_reader :date, :id
18
+ # @return [Date] AIRAC effective on date
19
+ attr_reader :date
15
20
 
21
+ # @return [Integer] AIRAC cycle ID
22
+ attr_reader :id
23
+
24
+ # @param any_date [Date] any date within the AIRAC cycle (default: today)
16
25
  def initialize(any_date = nil)
17
- any_date ||= Date.today
18
- fail(ArgumentError, "argument must be of class Date") unless any_date.is_a? Date
26
+ any_date = any_date ? Date.parse(any_date.to_s) : Date.today
19
27
  fail(ArgumentError, "cannot calculate dates before #{ROOT_DATE}") if any_date < ROOT_DATE
20
28
  @date = date_for(any_date)
21
29
  @id = id_for(@date)
22
30
  end
23
31
 
32
+ # @return [Date] next AIRAC effective on date
24
33
  def next_date
25
34
  date + DAYS_PER_CYCLE
26
35
  end
27
36
 
37
+ # @return [Integer] next AIRAC cycle ID
28
38
  def next_id
29
39
  id_for next_date
30
40
  end
31
41
 
32
42
  private
33
43
 
34
- ##
35
44
  # Find the AIRAC date for +any_date+
36
45
  def date_for(any_date)
37
46
  ROOT_DATE + (any_date - ROOT_DATE).to_i / DAYS_PER_CYCLE * DAYS_PER_CYCLE
38
47
  end
39
48
 
40
- ##
41
49
  # Find the AIRAC ID for the AIRAC +date+
42
50
  def id_for(date)
43
51
  (date.year % 100) * 100 + ((date.yday - 1) / 28) + 1