aipp 2.0.3 → 2.1.1
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 +3 -2
- data/CHANGELOG.md +14 -0
- data/README.md +21 -14
- data/exe/aip2aixm +1 -1
- data/exe/aip2ofmx +1 -1
- data/lib/aipp/debugger.rb +5 -1
- data/lib/aipp/downloader/file.rb +7 -5
- data/lib/aipp/downloader/http.rb +4 -4
- data/lib/aipp/downloader.rb +9 -2
- data/lib/aipp/executable.rb +68 -30
- data/lib/aipp/regions/LF/aip/services.rb +24 -4
- data/lib/aipp/regions/LF/helpers/base.rb +4 -6
- data/lib/aipp/regions/LS/README.md +58 -2
- data/lib/aipp/regions/LS/helpers/base.rb +10 -5
- data/lib/aipp/regions/LS/notam/ENR.rb +12 -10
- data/lib/aipp/regions/LS/shoot/shooting_grounds.rb +116 -0
- data/lib/aipp/runner.rb +13 -9
- data/lib/aipp/scopes/aip/executable.rb +38 -0
- data/lib/aipp/{aip → scopes/aip}/runner.rb +4 -2
- data/lib/aipp/scopes/notam/executable.rb +28 -0
- data/lib/aipp/{notam → scopes/notam}/runner.rb +3 -1
- data/lib/aipp/scopes/shoot/README.md +10 -0
- data/lib/aipp/scopes/shoot/executable.rb +28 -0
- data/lib/aipp/scopes/shoot/parser.rb +9 -0
- data/lib/aipp/scopes/shoot/runner.rb +30 -0
- data/lib/aipp/version.rb +1 -1
- data/lib/aipp.rb +10 -8
- data.tar.gz.sig +0 -0
- metadata +18 -17
- metadata.gz.sig +0 -0
- data/exe/notam2aixm +0 -5
- data/exe/notam2ofmx +0 -5
- data/lib/aipp/aip/executable.rb +0 -40
- data/lib/aipp/notam/executable.rb +0 -27
- /data/lib/aipp/{aip → scopes/aip}/README.md +0 -0
- /data/lib/aipp/{aip → scopes/aip}/parser.rb +0 -0
- /data/lib/aipp/{notam → scopes/notam}/README.md +0 -0
- /data/lib/aipp/{notam → scopes/notam}/parser.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a3f910ee20e31b9a2d0ee0644714c43898917cd9fda36bb5f721c17fcb758be
|
4
|
+
data.tar.gz: a996dd39757199cdadd5ceab2c7014b871785c857dc2054c4d9e5c264b16b5a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8cea4595823cf29656e25e252e205007de55a74240c1bca96f47ccd4e69194a112cb4665da0c784c24289f2d117d9b04e1a43625f86820763553a04bde03ab0
|
7
|
+
data.tar.gz: 7b3ae6c428f88231456a94a882c51ec8c5436bd2ff1398e61a0465b48939ccc3a443ff20ab98039a9ee9fc1ac2a5b70cbbc54ebdf29f9712a84a35460250b312
|
checksums.yaml.gz.sig
CHANGED
@@ -1,2 +1,3 @@
|
|
1
|
-
|
2
|
-
�
|
1
|
+
�(��F���S�> *���
|
2
|
+
ͻ��\��� k��1�<��>%���#cu�Q�L��Y+<��`%e��#�{k 2�'���M�����&a����*�ɝ7W��T��L��fX�Oki<i�����{��i�+�ޔ
|
3
|
+
r�}��Q==J����ÑeF��0*L2��!���?���r�L�������
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,20 @@
|
|
2
2
|
|
3
3
|
Nothing so far
|
4
4
|
|
5
|
+
## 2.1.1
|
6
|
+
|
7
|
+
#### Additions
|
8
|
+
* Improve help when no scope is given
|
9
|
+
* Add `-0` to write empty OFMX in case of no upstream data
|
10
|
+
|
11
|
+
## 2.1.0
|
12
|
+
|
13
|
+
#### Breaking Changes
|
14
|
+
* Unify all executables into `aip2aixm` and `aip2aixm` respectively
|
15
|
+
|
16
|
+
#### Additions
|
17
|
+
* Support for shooting grounds in LS
|
18
|
+
|
5
19
|
## 2.0.3
|
6
20
|
|
7
21
|
#### Breaking Changes
|
data/README.md
CHANGED
@@ -55,22 +55,29 @@ bundle install --trust-policy MediumSecurity
|
|
55
55
|
|
56
56
|
## Usage
|
57
57
|
|
58
|
-
AIPP parses different kind of information sources
|
58
|
+
AIPP parses different kind of information sources and converts them to different output formats depending on which executable you use:
|
59
|
+
|
60
|
+
Executable | Output Format
|
61
|
+
-----------|--------------
|
62
|
+
`aip2aixm` | AIXM
|
63
|
+
`aip2ofmx` | OFMX
|
64
|
+
|
65
|
+
The parsers are organized in three levels:
|
59
66
|
|
60
67
|
```
|
61
68
|
region ⬅︎ aeronautical region such as "LF" (France)
|
62
|
-
└──
|
63
|
-
└── section ⬅︎
|
69
|
+
└── scope ⬅︎ scope such as "AIP" or "NOTAM"
|
70
|
+
└── section ⬅︎ section of the scope such as "ENR-2.1" or "aerodromes"
|
64
71
|
```
|
65
72
|
|
66
|
-
The following
|
73
|
+
The following scopes are currently available:
|
67
74
|
|
68
|
-
|
69
|
-
|
70
|
-
[AIP](lib/aipp/aip/README.md) | aeronautical information publication |
|
71
|
-
[NOTAM](lib/aipp/notam/README.md) | notice to airmen |
|
75
|
+
Scope | Content | Cache
|
76
|
+
------|---------|------
|
77
|
+
[AIP](lib/aipp/aip/README.md) (default) | aeronautical information publication | by AIRAC cycle
|
78
|
+
[NOTAM](lib/aipp/notam/README.md) | notice to airmen | by effective date and hour
|
72
79
|
|
73
|
-
To list all available regions and sections
|
80
|
+
To list all available scopes, regions and sections:
|
74
81
|
|
75
82
|
```
|
76
83
|
aip2aixm --list
|
@@ -92,15 +99,15 @@ You'll find the OFMX file in the current directory if the binary exits successfu
|
|
92
99
|
|
93
100
|
## Regions
|
94
101
|
|
95
|
-
To implement a region, you have to create a directory <samp>lib/aipp/regions/{REGION}/</samp> off the gem root and then subdirectories for each
|
102
|
+
To implement a region, you have to create a directory <samp>lib/aipp/regions/{REGION}/</samp> off the gem root and then subdirectories for each scope as well as for support files. Here's a simplified overview for the region "LF" (France):
|
96
103
|
|
97
104
|
```
|
98
105
|
LF/ ⬅︎ region "LF"
|
99
106
|
├── README.md
|
100
|
-
├── aip ⬅︎
|
107
|
+
├── aip ⬅︎ scope "AIP"
|
101
108
|
│ ├── AD-2.rb ⬅︎ section "AD-2"
|
102
109
|
│ └── ENR-4.3.rb ⬅︎ section "ENR-4.3"
|
103
|
-
├── notam ⬅︎
|
110
|
+
├── notam ⬅︎ scope "NOTAM"
|
104
111
|
│ ├── AD.rb ⬅︎ section "AD"
|
105
112
|
│ └── ENR.rb ⬅︎ section "ENR"
|
106
113
|
├── borders
|
@@ -315,7 +322,7 @@ module MyAPI
|
|
315
322
|
end
|
316
323
|
```
|
317
324
|
|
318
|
-
For performance, all downloads are cached and subsequent runs will use the cached data rather than fetching the sources anew. Each
|
325
|
+
For performance, all downloads are cached and subsequent runs will use the cached data rather than fetching the sources anew. Each scope defines a cache time window, see the [table of scopes above](#label-Usage). You can discard existing and rebuild caches by use of the `--clean` command line argument.
|
319
326
|
|
320
327
|
#### Optional `setup` Method
|
321
328
|
|
@@ -499,7 +506,7 @@ debugger
|
|
499
506
|
|
500
507
|
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.
|
501
508
|
|
502
|
-
You'll find a directory for each region and
|
509
|
+
You'll find a directory for each region and scope which contains the following items:
|
503
510
|
|
504
511
|
* `sources/`<br>ZIP archives which cache all source files used to build.
|
505
512
|
* `builds/`<br>ZIP archives of successful builds containing:
|
data/exe/aip2aixm
CHANGED
data/exe/aip2ofmx
CHANGED
data/lib/aipp/debugger.rb
CHANGED
@@ -78,7 +78,11 @@ module AIPP
|
|
78
78
|
rescue => error
|
79
79
|
message = error.respond_to?(:original_message) ? error.original_message : error.message
|
80
80
|
puts "ERROR: #{message}".magenta
|
81
|
-
|
81
|
+
if AIPP.options.verbose
|
82
|
+
raise
|
83
|
+
else
|
84
|
+
exit 1
|
85
|
+
end
|
82
86
|
end
|
83
87
|
|
84
88
|
def call_without_rescue(&block)
|
data/lib/aipp/downloader/file.rb
CHANGED
@@ -3,6 +3,8 @@ module AIPP
|
|
3
3
|
|
4
4
|
# Local file
|
5
5
|
class File
|
6
|
+
attr_reader :file
|
7
|
+
|
6
8
|
def initialize(archive: nil, file:, type: nil)
|
7
9
|
@archive = Pathname(archive) if archive
|
8
10
|
@file, @type = Pathname(file), type&.to_s
|
@@ -12,10 +14,10 @@ module AIPP
|
|
12
14
|
path.join(fetched_file).tap do |target|
|
13
15
|
if @archive
|
14
16
|
fail NotFoundError unless @archive.exist?
|
15
|
-
extract(
|
17
|
+
extract(file, from: @archive, as: target)
|
16
18
|
else
|
17
|
-
fail NotFoundError unless
|
18
|
-
FileUtils.cp(
|
19
|
+
fail NotFoundError unless file.exist?
|
20
|
+
FileUtils.cp(file, target)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
self
|
@@ -28,11 +30,11 @@ module AIPP
|
|
28
30
|
private
|
29
31
|
|
30
32
|
def name
|
31
|
-
|
33
|
+
file.basename(file.extname).to_s
|
32
34
|
end
|
33
35
|
|
34
36
|
def type
|
35
|
-
@type ||
|
37
|
+
@type || file.extname[1..] || fail("type must be declared")
|
36
38
|
end
|
37
39
|
|
38
40
|
def extract(file, from:, as:)
|
data/lib/aipp/downloader/http.rb
CHANGED
@@ -16,14 +16,14 @@ module AIPP
|
|
16
16
|
# @param path [Pathname] directory where to write the fetched file
|
17
17
|
# @return [File] fetched file
|
18
18
|
def fetch_to(path)
|
19
|
-
response = Excon.get((@archive ||
|
19
|
+
response = Excon.get((@archive || file).to_s, headers: @headers)
|
20
20
|
fail NotFoundError if response.status == 404
|
21
21
|
mime_type = ARCHIVE_MIME_TYPES.fetch(response.headers['Content-Type'], :dat)
|
22
22
|
downloaded_file = path.join([@digest, mime_type].join('.'))
|
23
23
|
::File.write(downloaded_file, response.body)
|
24
24
|
path.join(fetched_file).tap do |target|
|
25
25
|
if @archive
|
26
|
-
extract(
|
26
|
+
extract(file, from: downloaded_file, as: target)
|
27
27
|
::File.delete(downloaded_file)
|
28
28
|
else
|
29
29
|
::File.rename(downloaded_file, target)
|
@@ -35,12 +35,12 @@ module AIPP
|
|
35
35
|
private
|
36
36
|
|
37
37
|
def name
|
38
|
-
path = Pathname(
|
38
|
+
path = Pathname(file.path)
|
39
39
|
path.basename(path.extname).to_s.blank_to_nil || @digest
|
40
40
|
end
|
41
41
|
|
42
42
|
def type
|
43
|
-
@type || Pathname(
|
43
|
+
@type || Pathname(file.path).extname[1..].blank_to_nil || fail("type must be declared")
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
data/lib/aipp/downloader.rb
CHANGED
@@ -165,13 +165,20 @@ module AIPP
|
|
165
165
|
case file.extname
|
166
166
|
when '.xml', '.ofmx' then Nokogiri.XML(::File.open(file), &:noblanks)
|
167
167
|
when '.html' then Nokogiri.HTML5(::File.open(file))
|
168
|
-
when '.json' then JSON.load_file(file)
|
168
|
+
when '.json' then JSON.load_file(file, symbolize_names: true)
|
169
169
|
when '.pdf' then AIPP::PDF.new(file)
|
170
|
-
when '.xlsx', '.ods'
|
170
|
+
when '.xlsx', '.ods' then Roo::Spreadsheet.open(file.to_s)
|
171
|
+
when '.csv' then Roo::Spreadsheet.open(file.to_s, csv_options: { col_sep: separator(file) })
|
171
172
|
when '.txt' then ::File.read(file)
|
172
173
|
else fail(ArgumentError, "unrecognized file type")
|
173
174
|
end
|
174
175
|
end
|
175
176
|
|
177
|
+
# @return [String] most likely separator character of CSV and similar files
|
178
|
+
def separator(file)
|
179
|
+
content = file.read
|
180
|
+
%W(, ; \t).map { [content.scan(_1).count, _1] }.sort.last.last
|
181
|
+
end
|
182
|
+
|
176
183
|
end
|
177
184
|
end
|
data/lib/aipp/executable.rb
CHANGED
@@ -5,24 +5,54 @@ module AIPP
|
|
5
5
|
include AIPP::Debugger
|
6
6
|
|
7
7
|
def initialize(exe_file)
|
8
|
+
@exe_file = exe_file
|
9
|
+
help if ARGV.none? || ARGV.first == '--help'
|
10
|
+
require_scope
|
8
11
|
AIPP.options.replace(
|
9
|
-
|
12
|
+
scope: scope,
|
13
|
+
schema: schema,
|
10
14
|
storage: Pathname(Dir.home).join('.aipp'),
|
15
|
+
check_links: false,
|
11
16
|
clean: false,
|
12
17
|
force: false,
|
13
18
|
mid: false,
|
19
|
+
write_empty: false,
|
14
20
|
quiet: false,
|
15
21
|
verbose: false,
|
16
22
|
debug_on_warning: false,
|
17
23
|
debug_on_error: false
|
18
24
|
)
|
25
|
+
options if respond_to? :options
|
26
|
+
OptionParser.new do |o|
|
27
|
+
o.on('-r', '--region STRING', String, 'region (e.g. "LF")') { AIPP.options.region = _1.upcase }
|
28
|
+
o.on('-s', '--section STRING', String, 'process this section only') { AIPP.options.section = _1.classify }
|
29
|
+
o.on('-d', '--storage DIR', String, 'storage directory (default: "~/.aipp")') { AIPP.options.storage = Pathname(_1) }
|
30
|
+
o.on('-o', '--output FILE', String, 'output file') { AIPP.options.output_file = _1 }
|
31
|
+
option_parser(o)
|
32
|
+
if schema == :ofmx
|
33
|
+
o.on('-m', '--[no-]mid', 'insert mid attributes into all Uid elements (default: false)') { AIPP.options.mid = _1 }
|
34
|
+
o.on('-0', '--[no-]empty', 'write empty OFMX files in case of no upstream data (default: false)') { AIPP.options.write_empty = _1 }
|
35
|
+
end
|
36
|
+
o.on('-h', '--[no-]check-links', 'check all links with HEAD requests (default: false)') { AIPP.options.check_links = _1 }
|
37
|
+
o.on('-c', '--[no-]clean', 'clean cache and download from sources anew (default: false)') { AIPP.options.clean = _1 }
|
38
|
+
o.on('-f', '--[no-]force', 'continue on non-fatal errors (default: false)') { AIPP.options.force = _1 }
|
39
|
+
o.on('-q', '--[no-]quiet', 'suppress all informational output (default: false)') { AIPP.options.quiet = _1 }
|
40
|
+
o.on('-v', '--[no-]verbose', 'verbose output including unsevere warnings (default: false)') { AIPP.options.verbose = _1 }
|
41
|
+
o.on('-w', '--debug-on-warning [ID]', Integer, 'open debug session on warning with ID (default: false)') { AIPP.options.debug_on_warning = _1 || true }
|
42
|
+
o.on('-e', '--[no-]debug-on-error', 'open debug session on error (default: false)') { AIPP.options.debug_on_error = _1 }
|
43
|
+
o.on('-A', '--about', 'show author/license information and exit') { about }
|
44
|
+
o.on('-R', '--readme', 'show README and exit') { readme }
|
45
|
+
o.on('-L', '--list', 'list implemented regions') { list }
|
46
|
+
o.on('-V', '--version', 'show version and exit') { version }
|
47
|
+
end.parse!
|
48
|
+
guard if respond_to? :guard
|
19
49
|
end
|
20
50
|
|
21
51
|
def run
|
22
52
|
with_debugger do
|
23
53
|
String.disable_colorization = !STDOUT.tty?
|
24
54
|
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
25
|
-
[:AIPP, AIPP.options.
|
55
|
+
[:AIPP, AIPP.options.scope, :Runner].constantize.new.run
|
26
56
|
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
27
57
|
info("finished after %s" % Time.at(ending - starting).utc.strftime("%H:%M:%S"))
|
28
58
|
end
|
@@ -30,28 +60,12 @@ module AIPP
|
|
30
60
|
|
31
61
|
private
|
32
62
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def developer_options(o)
|
41
|
-
if AIPP.options.schema == :ofmx
|
42
|
-
o.on('-m', '--[no-]mid', 'insert mid attributes into all Uid elements (default: false)') { AIPP.options.mid = _1 }
|
43
|
-
end
|
44
|
-
o.on('-h', '--[no-]check-links', 'check all links with HEAD requests') { AIPP.options.check_links = _1 }
|
45
|
-
o.on('-c', '--[no-]clean', 'clean cache and download from sources anew (default: false)') { AIPP.options.clean = _1 }
|
46
|
-
o.on('-f', '--[no-]force', 'continue on non-fatal errors (default: false)') { AIPP.options.force = _1 }
|
47
|
-
o.on('-q', '--[no-]quiet', 'suppress all informational output (default: false)') { AIPP.options.quiet = _1 }
|
48
|
-
o.on('-v', '--[no-]verbose', 'verbose output including unsevere warnings (default: false)') { AIPP.options.verbose = _1 }
|
49
|
-
o.on('-w', '--debug-on-warning [ID]', Integer, 'open debug session on warning with ID (default: false)') { AIPP.options.debug_on_warning = _1 || true }
|
50
|
-
o.on('-e', '--[no-]debug-on-error', 'open debug session on error (default: false)') { AIPP.options.debug_on_error = _1 }
|
51
|
-
o.on('-A', '--about', 'show author/license information and exit') { about }
|
52
|
-
o.on('-R', '--readme', 'show README and exit') { readme }
|
53
|
-
o.on('-L', '--list', 'list implemented regions') { list }
|
54
|
-
o.on('-V', '--version', 'show version and exit') { version }
|
63
|
+
def help
|
64
|
+
puts <<~END
|
65
|
+
Download online aeronautical data and convert it to #{schema.upcase}.
|
66
|
+
Usage: #{File.basename($0)} [aip|notam|shoot] --help
|
67
|
+
END
|
68
|
+
exit
|
55
69
|
end
|
56
70
|
|
57
71
|
def about
|
@@ -65,13 +79,18 @@ module AIPP
|
|
65
79
|
end
|
66
80
|
|
67
81
|
def list
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
82
|
+
puts "Available scopes -> regions -> sections:"
|
83
|
+
lib_dir.join('scopes').each_child do |scope_dir|
|
84
|
+
next unless scope_dir.directory?
|
85
|
+
puts "\n#{scope_dir.basename} ->".upcase
|
86
|
+
lib_dir.join('regions').each_child do |region_dir|
|
87
|
+
next unless region_dir.directory? && region_dir.join(scope_dir.basename).exist?
|
88
|
+
puts " #{region_dir.basename} ->"
|
89
|
+
region_dir.join(scope_dir.basename).glob('*.rb') do |section_file|
|
90
|
+
puts " #{section_file.basename('.rb')}"
|
91
|
+
end
|
92
|
+
end
|
73
93
|
end
|
74
|
-
puts hash.to_yaml.lines[1..]
|
75
94
|
exit
|
76
95
|
end
|
77
96
|
|
@@ -80,5 +99,24 @@ module AIPP
|
|
80
99
|
exit
|
81
100
|
end
|
82
101
|
|
102
|
+
def lib_dir
|
103
|
+
Pathname(__FILE__).dirname
|
104
|
+
end
|
105
|
+
|
106
|
+
def schema
|
107
|
+
@schema ||= @exe_file.split('2').last.to_sym
|
108
|
+
end
|
109
|
+
|
110
|
+
def scope
|
111
|
+
@scope ||= ARGV.first.match?(/^-/) ? 'AIP' : ARGV.shift.upcase
|
112
|
+
end
|
113
|
+
|
114
|
+
def require_scope
|
115
|
+
lib_dir.join('scopes', scope.downcase).glob('*.rb').each { require _1 }
|
116
|
+
extend [:AIPP, scope, :Executable].constantize
|
117
|
+
rescue NameError
|
118
|
+
puts "ERROR: unknown scope `#{scope}'".magenta
|
119
|
+
exit 1
|
120
|
+
end
|
83
121
|
end
|
84
122
|
end
|
@@ -68,10 +68,12 @@ module AIPP::LF::AIP
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
end
|
71
|
-
# Assign
|
71
|
+
# Assign A/A address to all yet radioless airports
|
72
72
|
find_by(:airport).each do |airport|
|
73
73
|
unless airport.services.find_by(:service, type: :aerodrome_control_tower_service).any? || airport.addresses.any?
|
74
|
-
airport.add_address(
|
74
|
+
airport.add_address(
|
75
|
+
address_from_vac_for(airport) || fallback_address_for(airport)
|
76
|
+
)
|
75
77
|
end
|
76
78
|
end
|
77
79
|
end
|
@@ -98,14 +100,32 @@ module AIPP::LF::AIP
|
|
98
100
|
end.compact
|
99
101
|
end
|
100
102
|
|
101
|
-
|
103
|
+
# TODO: A/A adresses are read unreliably from VAC due to data inconsistencies
|
104
|
+
# in XML. Once fixed, integrate this into `addresses_from` as per:
|
105
|
+
# https://gitlab.com/openflightmaps/region-issues/-/issues/68
|
106
|
+
def address_from_vac_for(airport)
|
107
|
+
if aa = read("VAC-#{airport.id}").text.first_match(%r(A/A\s+\(?(\d{3}\.\d{1,3})))
|
108
|
+
AIXM.address(
|
109
|
+
type: :radio_frequency,
|
110
|
+
address: AIXM.f(aa.to_f, :mhz)
|
111
|
+
).tap do |address|
|
112
|
+
address.remarks = {
|
113
|
+
'type' => 'A/A',
|
114
|
+
'indicatif/callsign' => airport.name
|
115
|
+
}.to_remarks
|
116
|
+
end
|
117
|
+
end
|
118
|
+
rescue AIPP::Downloader::NotFoundError
|
119
|
+
end
|
120
|
+
|
121
|
+
def fallback_address_for(airport)
|
102
122
|
AIXM.address(
|
103
123
|
type: :radio_frequency,
|
104
124
|
address: AIXM.f(123.5, :mhz)
|
105
125
|
).tap do |address|
|
106
126
|
address.remarks = {
|
107
127
|
'type' => 'A/A',
|
108
|
-
'indicatif/callsign' =>
|
128
|
+
'indicatif/callsign' => airport.name
|
109
129
|
}.to_remarks
|
110
130
|
end
|
111
131
|
end
|
@@ -28,12 +28,10 @@ module AIPP
|
|
28
28
|
case document
|
29
29
|
when /^Obstacles$/ # obstacles spreadsheet
|
30
30
|
AIPP::Downloader::HTTP.new(file: "#{sia_url}/FRANCE/ObstaclesDataZone1MFRANCE_#{xml_date.remove('-')}.xlsx")
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# when /^[A-Z]+-/ # eAIP HTML page (e.g. ENR-5.5)
|
36
|
-
# AIPP::Downloader::HTTP.new(file: "#{sia_url}/FRANCE/AIRAC-#{xml_date}/html/eAIP/FR-#{document}-fr-FR.html")
|
31
|
+
when /^VAC\-(\w+)/ # aerodrome VAC PDF
|
32
|
+
AIPP::Downloader::HTTP.new(file: "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VAC/AD/AD-2.#{$1}.pdf")
|
33
|
+
when /^VACH\-(\w+)/ # helipad VAC PDF
|
34
|
+
AIPP::Downloader::HTTP.new(file: "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VACH/AD/AD-3.#{$1}.pdf")
|
37
35
|
else # SIA XML database dump
|
38
36
|
AIPP::Downloader::File.new(file: "XML_SIA_#{xml_date}.xml")
|
39
37
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# LS – Switzerland
|
2
2
|
|
3
|
-
##
|
3
|
+
## NOTAM API
|
4
4
|
|
5
|
-
The NOTAM messages are fetched from the Neway API which is a non-public
|
5
|
+
The NOTAM messages are fetched from the Neway API which is a **non-public**
|
6
6
|
GraphQL API.
|
7
7
|
|
8
8
|
You have to set the following environment variables:
|
@@ -36,6 +36,62 @@ The following query object shows all parameters and columns:
|
|
36
36
|
}
|
37
37
|
```
|
38
38
|
|
39
|
+
## SHOOT API
|
40
|
+
|
41
|
+
Geometries and most shooting details are available on the [geoinformation portal](https://geo.admin.ch) managed by GKG/swisstopo. However, all details as well as a list of active shooting ranges is only available in the original `schiessanzeigen.csv` file compiled by the Swiss army and distributed on the portal as well:
|
42
|
+
|
43
|
+
* [geo.admin.ch JSON API](https://api.geo.admin.ch/services/sdiservices.html) [(example)](https://api3.geo.admin.ch/rest/services/api/MapServer/ch.vbs.schiessanzeigen/1201.050?sr=4326&geometryFormat=geojson)
|
44
|
+
* [schiessanzeigen.csv](https://data.geo.admin.ch/ch.vbs.schiessanzeigen/schiessanzeigen/schiessanzeigen.csv)
|
45
|
+
|
46
|
+
As of Feburary 2023, the structure of the CSV is as follows:
|
47
|
+
|
48
|
+
### Shooting Ground Description Records
|
49
|
+
|
50
|
+
Row | Col | Attribute | Content | Mand | Remarks
|
51
|
+
----|-----|------------|-----------|------|--------
|
52
|
+
SPL | 0 | Row | text(3) | yes | **"SPL" (aka: Schiessplatz Stammdaten)**
|
53
|
+
SPL | 1 | Belplan ID | text(8) | yes | **ID of the shooting ground from Belplan as "nnnn.nnn" (equal to module# and object#) e.g. "3104.010"**
|
54
|
+
SPL | 2 | arimmo ID | text(10) | yes | ID of the shooting ground from arimmo e.g. "04.203"
|
55
|
+
SPL | 3 | Name | text(50) | yes | **Name of the shooting ground, e.g. "DAMMASTOCK / SUSTENHORN"**
|
56
|
+
SPL | 4 | URL DE | text(100) | yes | Info URL in DE from CMS-VBS
|
57
|
+
SPL | 5 | URL FR | text(100) | yes | Info URL in FR from CMS-VBS
|
58
|
+
SPL | 6 | URL IT | text(100) | yes | Info URL in IT from CMS-VBS
|
59
|
+
SPL | 7 | URL EN | text(100) | yes | **Info URL in EN from CMS-VBS**
|
60
|
+
SPL | 8 | Info name | text(100) | yes | Name of the info point e.g. "Koordinationsstelle Terreg 3, Altdorf"
|
61
|
+
SPL | 9 | Info phone | text(20) | yes | **Phone of the info point**
|
62
|
+
SPL | 10 | Info email | text(100) | no | **Email of the info point**
|
63
|
+
|
64
|
+
### Shooting Ground Activity Records
|
65
|
+
|
66
|
+
Row | Col | Attribute | Content | Mand | Remarks
|
67
|
+
----|-----|----------------|----------------|------|--------
|
68
|
+
BSZ | 0 | Row | text(3) | yes | **"BSZ" (aka: Belegungszeiten eines SPL)**
|
69
|
+
BSZ | 1 | Belplan ID | text(8) | yes | **ID of the shooting ground from Belplan as "nnnn.nnn" (equal to module# and object#) e.g. "3104.010"**
|
70
|
+
BSZ | 2 | Act date | date(yyyymmdd) | yes | **Datum of activity**
|
71
|
+
BSZ | 3 | Act time from | time(hhmm) | no | **Time when activity begins**
|
72
|
+
BSZ | 4 | Act time until | time(hhmm) | no | **Time when activity ends**
|
73
|
+
BSZ | 5 | Locations | text(50) | no | Locations of shooting activity [R2, (max 50 char)]
|
74
|
+
BSZ | 6 | Remarks | text(100) | no | Remarks [R2]
|
75
|
+
BSZ | 7 | URL DE | text(200) | no | Announcement URL DE [R2]
|
76
|
+
BSZ | 8 | URL FR | text(200) | no | Announcement URL FR [R2]
|
77
|
+
BSZ | 9 | URL IT | text(200) | no | Announcement URL IT [R2]
|
78
|
+
BSZ | 10 | URL EN | text(200) | no | **Announcement URL EN [R2]**
|
79
|
+
BSZ | 11 | Unit | text(120) | no | Military unit involved [R2]
|
80
|
+
BSZ | 12 | Weapons | text(50) | no | Weapons and ammunition involved [R2]
|
81
|
+
BSZ | 13 | Positions | text(50) | no | Shooting positions [R2]
|
82
|
+
BSZ | 14 | Coordinates | text(25) | no | Shooting coordinates [R2]
|
83
|
+
BSZ | 15 | Vertex height | number | no | **Max height of activity [R2]**
|
84
|
+
BSZ | 16 | DABS | boolean | no | **Relevant for DABS (formerly KOSIF): 0=no, 1=yes [R2]**
|
85
|
+
BSZ | 17 | No shooting | boolean | no | **0=no, 1=yes [R2]**
|
86
|
+
|
87
|
+
### Total Records
|
88
|
+
|
89
|
+
Row | Col | Attribute | Content | Mand | Remarks
|
90
|
+
----|-----|------------|---------------------------|------|--------
|
91
|
+
TOT | 0 | Row | text(3) | yes | "TOT" (aka: Total)
|
92
|
+
TOT | 1 | Created at | datetime(yyyymmddhhmmss) | yes | Date and time of CSV creation
|
93
|
+
TOT | 2 | SPL count | number | yes | Number of SPL records
|
94
|
+
|
39
95
|
## Asynchronous Use
|
40
96
|
|
41
97
|
### Command Line Arguments
|
@@ -29,11 +29,6 @@ module AIPP
|
|
29
29
|
|
30
30
|
# Mandatory Interface
|
31
31
|
|
32
|
-
def setup
|
33
|
-
AIPP.cache.aip = read('AIP').css('Ase')
|
34
|
-
AIPP.cache.dabs = read('DABS')
|
35
|
-
end
|
36
|
-
|
37
32
|
def origin_for(document)
|
38
33
|
case document
|
39
34
|
when 'ENR'
|
@@ -63,6 +58,16 @@ module AIPP
|
|
63
58
|
type: :pdf
|
64
59
|
)
|
65
60
|
end
|
61
|
+
when 'shooting_grounds'
|
62
|
+
AIPP::Downloader::HTTP.new(
|
63
|
+
file: "https://data.geo.admin.ch/ch.vbs.schiessanzeigen/schiessanzeigen/schiessanzeigen.csv",
|
64
|
+
type: :csv
|
65
|
+
)
|
66
|
+
when /^shooting_grounds-(\d+\.\d+)/
|
67
|
+
AIPP::Downloader::HTTP.new(
|
68
|
+
file: "https://api3.geo.admin.ch/rest/services/api/MapServer/ch.vbs.schiessanzeigen/#{$1}?sr=4326&geometryFormat=geojson",
|
69
|
+
type: :json
|
70
|
+
)
|
66
71
|
else
|
67
72
|
fail "document not recognized"
|
68
73
|
end
|
@@ -6,30 +6,32 @@ module AIPP::LS::NOTAM
|
|
6
6
|
include AIPP::LS::Helpers::Base
|
7
7
|
|
8
8
|
def parse
|
9
|
+
AIPP.cache.aip ||= read('AIP').css('Ase')
|
10
|
+
AIPP.cache.dabs ||= read('DABS')
|
9
11
|
json = read
|
10
|
-
fail "malformed JSON received from API" unless json.has_key?(
|
12
|
+
fail "malformed JSON received from API" unless json.has_key?(:queryNOTAMs)
|
11
13
|
added_notam_ids = []
|
12
|
-
json[
|
13
|
-
next unless row[
|
14
|
+
json[:queryNOTAMs].each do |row|
|
15
|
+
next unless row[:notamRaw].match? /^Q\) LS/ # only parse national NOTAM
|
14
16
|
|
15
17
|
# HACK: try to add missing commas to D-item of A- and B-series NOTAM
|
16
|
-
if row[
|
17
|
-
if row[
|
18
|
-
row[
|
19
|
-
row[
|
18
|
+
if row[:notamRaw].match? /\A[AB]/
|
19
|
+
if row[:notamRaw].gsub!(/(#{NOTAM::Schedule::HOUR_RE.decapture}-#{NOTAM::Schedule::HOUR_RE.decapture})/, '\1,')
|
20
|
+
row[:notamRaw].gsub!(/,+/, ',')
|
21
|
+
row[:notamRaw].sub!(/,\n/, "\n")
|
20
22
|
warn("HACK: added missing commas to D item")
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
# HACK: remove braindead years from D-item of W-series NOTAM
|
25
|
-
if row[
|
27
|
+
if row[:notamRaw].match? /\AW/
|
26
28
|
year = Time.now.year
|
27
|
-
if row[
|
29
|
+
if row[:notamRaw].gsub!(/\s*(?:#{year}|#{year+1})\s*(#{NOTAM::Schedule::MONTH_RE})/, ' \1')
|
28
30
|
warn("HACK: removed braindead years from D item")
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
32
|
-
(notam = notam_for(row[
|
34
|
+
(notam = notam_for(row[:notamRaw])) or next
|
33
35
|
if respect? notam
|
34
36
|
next if notam.data[:five_day_schedules] == []
|
35
37
|
added_notam_ids << notam.data[:id]
|
@@ -0,0 +1,116 @@
|
|
1
|
+
using AIXM::Refinements
|
2
|
+
|
3
|
+
module AIPP::LS::SHOOT
|
4
|
+
class ShootingGrounds < AIPP::SHOOT::Parser
|
5
|
+
|
6
|
+
include AIPP::LS::Helpers::Base
|
7
|
+
|
8
|
+
DEFAULT_Z = AIXM.z(1000, :qfe) # height 300m unless present in publication
|
9
|
+
|
10
|
+
def parse
|
11
|
+
effective_date = aixm.effective_at.strftime('%Y%m%d')
|
12
|
+
airac_date = AIRAC::Cycle.new(aixm.effective_at).to_s('%Y-%m-%d')
|
13
|
+
shooting_grounds = {}
|
14
|
+
read.each_with_index do |row, line|
|
15
|
+
type, id, date, no_shooting = row[0], row[1], row[2], (row[17] == "1")
|
16
|
+
if type == 'BSZ' && !no_shooting && date == effective_date
|
17
|
+
shooting_grounds[id] ||= read("shooting_grounds-#{id}")
|
18
|
+
.fetch(:feature)
|
19
|
+
.merge(
|
20
|
+
csv_line: line,
|
21
|
+
location_codes: row[5].split(/ *, */), # TODO: currently ignored - not available as separate geometries
|
22
|
+
details: row[6].blank_to_nil,
|
23
|
+
url: row[10].blank_to_nil,
|
24
|
+
upper_z: (AIXM.z(AIXM.d(row[15].to_i, :m).to_ft.dim.round, :qfe) if row[15]),
|
25
|
+
dabs: (row[16] == '1'),
|
26
|
+
schedules: []
|
27
|
+
)
|
28
|
+
shooting_grounds[id][:schedules] << schedule_for(row)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
shooting_grounds.each do |id, data|
|
32
|
+
data in csv_line:, location_codes:, details:, url:, upper_z:, schedules:, properties: { bezeichnung: name, infotelefonnr: phone, infoemail: email }
|
33
|
+
if schedules.compact.any?
|
34
|
+
geometries = geometries_for data[:geometry]
|
35
|
+
indexed = geometries.count > 1
|
36
|
+
geometries.each_with_index do |geometry, index|
|
37
|
+
remarks = {
|
38
|
+
details: details,
|
39
|
+
phone: phone,
|
40
|
+
email: email,
|
41
|
+
bulletin: url
|
42
|
+
}.to_remarks
|
43
|
+
add(
|
44
|
+
AIXM.airspace(
|
45
|
+
source: "LS|OTHER|schiessgebiete.csv|#{airac_date}|#{csv_line}",
|
46
|
+
region: 'LS',
|
47
|
+
type: :dangerous_activities_area,
|
48
|
+
name: "LS-S#{id} #{name} #{index if indexed}".strip
|
49
|
+
).tap do |airspace|
|
50
|
+
airspace.add_layer layer_for(upper_z, schedules, remarks)
|
51
|
+
airspace.geometry = geometry
|
52
|
+
airspace.comment = "DABS: marked for publication"
|
53
|
+
end
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Returns +nil+ if neither beginning nor ending time is declared which
|
63
|
+
# has to be treated as "no shooting".
|
64
|
+
def schedule_for(row)
|
65
|
+
from = AIXM.time("#{row[3]} #{AIPP.options.time_zone}") if row[3]
|
66
|
+
to = AIXM.time("#{row[4]} #{AIPP.options.time_zone}") if row[4]
|
67
|
+
case
|
68
|
+
when from && to then (from..to)
|
69
|
+
when from then (from..AIXM::END_OF_DAY)
|
70
|
+
when to then (AIXM::BEGINNING_OF_DAY..to)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def geometries_for(polygons)
|
75
|
+
fail "only type MultiPolygon supported" unless polygons[:type] == 'MultiPolygon'
|
76
|
+
fail "polygon coordinates missing" unless polygons[:coordinates]
|
77
|
+
polygons[:coordinates].map do |(outer_polygon, inner_polygon)|
|
78
|
+
warn "hole in polygon is ignored" if inner_polygon
|
79
|
+
AIXM.geometry(
|
80
|
+
*outer_polygon.map { AIXM.point(xy: AIXM.xy(long: _1.first , lat: _1.last)) }
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def layer_for(upper_z, schedules, remarks)
|
86
|
+
AIXM.layer(
|
87
|
+
vertical_limit: AIXM.vertical_limit(
|
88
|
+
upper_z: (upper_z || DEFAULT_Z),
|
89
|
+
lower_z: AIXM::GROUND
|
90
|
+
)
|
91
|
+
).tap do |layer|
|
92
|
+
layer.activity = :shooting_from_ground
|
93
|
+
layer.timetable = timetable_for(schedules)
|
94
|
+
layer.remarks = remarks
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def timetable_for(schedules)
|
99
|
+
AIXM.timetable.tap do |timetable|
|
100
|
+
schedules.each do |schedule|
|
101
|
+
timetable.add_timesheet(
|
102
|
+
AIXM.timesheet(
|
103
|
+
adjust_to_dst: true,
|
104
|
+
dates: (AIXM.date(aixm.effective_at)..AIXM.date(aixm.effective_at))
|
105
|
+
# TODO: transform to...
|
106
|
+
# dates: AIXM.date(aixm.effective_at)
|
107
|
+
).tap do |timesheet|
|
108
|
+
timesheet.times = schedule
|
109
|
+
end
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
data/lib/aipp/runner.rb
CHANGED
@@ -9,7 +9,7 @@ module AIPP
|
|
9
9
|
attr_reader :aixm
|
10
10
|
|
11
11
|
def initialize
|
12
|
-
AIPP.options.storage = AIPP.options.storage.join(AIPP.options.region, AIPP.options.
|
12
|
+
AIPP.options.storage = AIPP.options.storage.join(AIPP.options.region, AIPP.options.scope.downcase)
|
13
13
|
AIPP.options.storage.mkpath
|
14
14
|
@dependencies = THash.new
|
15
15
|
@aixm = AIXM.document(effective_at: effective_at, expiration_at: expiration_at)
|
@@ -49,7 +49,7 @@ module AIPP
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def output_file
|
52
|
-
"#{AIPP.options.region}_#{AIPP.options.
|
52
|
+
"#{AIPP.options.region}_#{AIPP.options.scope}_#{effective_at.strftime('%F_%HZ')}.#{AIPP.options.schema}"
|
53
53
|
end
|
54
54
|
|
55
55
|
# @return [Pathname] directory containing the builds
|
@@ -86,7 +86,7 @@ module AIPP
|
|
86
86
|
# Read parser files.
|
87
87
|
def read_parsers
|
88
88
|
verbose_info("reading parsers")
|
89
|
-
region_dir.join(AIPP.options.
|
89
|
+
region_dir.join(AIPP.options.scope.downcase).glob('*.rb').each do |file|
|
90
90
|
verbose_info "requiring #{file.basename}"
|
91
91
|
require file
|
92
92
|
section = file.basename('.*').to_s.classify
|
@@ -123,19 +123,23 @@ module AIPP
|
|
123
123
|
details = aixm.errors.map(&:message).join("\n")
|
124
124
|
AIPP.options.force ? warn(message) : fail([message, details].join(":\n"))
|
125
125
|
end
|
126
|
-
info("counting #{aixm.features.count}
|
126
|
+
info("counting #{aixm.features.count} feature(s)")
|
127
127
|
end
|
128
128
|
|
129
129
|
# Write the AIXM document.
|
130
130
|
def write_aixm(file)
|
131
|
-
|
132
|
-
|
133
|
-
|
131
|
+
if aixm.features.any? || AIPP.options.write_empty
|
132
|
+
info("writing #{file}")
|
133
|
+
AIXM.config.mid = AIPP.options.mid
|
134
|
+
File.write(file, aixm.to_xml)
|
135
|
+
else
|
136
|
+
info("no features to write")
|
137
|
+
end
|
134
138
|
end
|
135
139
|
|
136
140
|
# Write build information.
|
137
141
|
def write_build
|
138
|
-
info
|
142
|
+
info("skipping build")
|
139
143
|
end
|
140
144
|
|
141
145
|
# Write the configuration to config.yml.
|
@@ -145,7 +149,7 @@ module AIPP
|
|
145
149
|
end
|
146
150
|
|
147
151
|
def class_for(section)
|
148
|
-
[:AIPP, AIPP.options.region, AIPP.options.
|
152
|
+
[:AIPP, AIPP.options.region, AIPP.options.scope, section.classify].constantize
|
149
153
|
end
|
150
154
|
end
|
151
155
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module AIPP
|
2
|
+
module AIP
|
3
|
+
|
4
|
+
module Executable
|
5
|
+
|
6
|
+
def options
|
7
|
+
AIPP.options.merge(
|
8
|
+
scope: 'AIP',
|
9
|
+
airac: AIRAC::Cycle.new,
|
10
|
+
region_options: []
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def option_parser(o)
|
15
|
+
o.banner = <<~END
|
16
|
+
Download online AIP and convert it to #{AIPP.options.schema.upcase}.
|
17
|
+
Usage: #{File.basename($0)} [aip] [options]
|
18
|
+
END
|
19
|
+
o.on('-a', '--airac (DATE|INTEGER)', String, %Q[AIRAC date or delta e.g. "+1" (default: "#{AIPP.options.airac.date.xmlschema}")]) { AIPP.options.airac = airac_for(_1) }
|
20
|
+
if AIPP.options.schema == :ofmx
|
21
|
+
o.on('-g', '--[no-]grouped-obstacles', 'group obstacles (default: false)') { AIPP.options.grouped_obstacles = _1 }
|
22
|
+
end
|
23
|
+
o.on('-O', '--region-options STRING', String, %Q[comma separated region specific options]) { AIPP.options.region_options = _1.split(',') }
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def airac_for(argument)
|
29
|
+
if argument.match?(/^[+-]\d+$/) # delta
|
30
|
+
AIRAC::Cycle.new + argument.to_i
|
31
|
+
else # date
|
32
|
+
AIRAC::Cycle.new(argument)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module AIPP
|
2
|
+
module NOTAM
|
3
|
+
|
4
|
+
module Executable
|
5
|
+
|
6
|
+
def options
|
7
|
+
AIPP.options.merge(
|
8
|
+
module: 'NOTAM',
|
9
|
+
effective_at: Time.now.change(min: 0, sec: 0)
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def option_parser(o)
|
14
|
+
o.banner = <<~END
|
15
|
+
Download online NOTAM and convert it to #{AIPP.options.schema.upcase}.
|
16
|
+
Usage: #{File.basename($0)} notam [options]
|
17
|
+
END
|
18
|
+
o.on('-t', '--effective (TIME)', String, %Q[effective at this time (default: "#{AIPP.options.effective_at}")]) { AIPP.options.effective_at = Time.parse(_1) }
|
19
|
+
o.on('-x', '--crossload DIR', String, 'crossload directory') { AIPP.options.crossload = Pathname(_1) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def guard
|
23
|
+
AIPP.options.effective_at = AIPP.options.effective_at.change(min: 0, sec: 0).utc
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# AIPP Shoot Module
|
2
|
+
|
3
|
+
## Cache Time Window
|
4
|
+
|
5
|
+
The default time window for SHOOT is the day. This means:
|
6
|
+
|
7
|
+
* Source data is downloaded and cached based on the day.
|
8
|
+
* The effective date and time is rounded down to the previous midnight.
|
9
|
+
|
10
|
+
To force a rebuild within this time window, you have to clean the cache using the `-c` command line argument.
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module AIPP
|
2
|
+
module SHOOT
|
3
|
+
|
4
|
+
module Executable
|
5
|
+
|
6
|
+
def options
|
7
|
+
AIPP.options.merge(
|
8
|
+
module: 'Shoot',
|
9
|
+
effective_at: Time.now.at_midnight
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def option_parser(o)
|
14
|
+
o.banner = <<~END
|
15
|
+
Download online shooting activities and convert them to #{AIPP.options.schema.upcase}.
|
16
|
+
Usage: #{File.basename($0)} shoot [options]
|
17
|
+
END
|
18
|
+
o.on('-t', '--effective (DATE)', String, %Q[effective on this date (default: "#{AIPP.options.effective_at.to_date}")]) { AIPP.options.effective_at = Time.parse("#{_1} CET") }
|
19
|
+
end
|
20
|
+
|
21
|
+
def guard
|
22
|
+
AIPP.options.time_zone = AIPP.options.effective_at.at_noon.strftime('%z')
|
23
|
+
AIPP.options.effective_at = AIPP.options.effective_at.at_midnight.utc
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module AIPP
|
2
|
+
module SHOOT
|
3
|
+
|
4
|
+
class Runner < AIPP::Runner
|
5
|
+
|
6
|
+
def effective_at
|
7
|
+
AIPP.options.effective_at
|
8
|
+
end
|
9
|
+
|
10
|
+
def expiration_at
|
11
|
+
effective_at.end_of_day.round - 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
info("SHOOT effective #{effective_at}", color: :green)
|
16
|
+
read_config
|
17
|
+
read_region
|
18
|
+
read_parsers
|
19
|
+
parse_sections
|
20
|
+
if aixm.features.any?
|
21
|
+
validate_aixm
|
22
|
+
end
|
23
|
+
write_aixm(AIPP.options.output_file || output_file)
|
24
|
+
write_config
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/aipp/version.rb
CHANGED
data/lib/aipp.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'debug/session'
|
2
2
|
require 'singleton'
|
3
|
-
require 'colorize'
|
4
3
|
require 'optparse'
|
5
4
|
require 'yaml'
|
5
|
+
require 'json'
|
6
6
|
require 'pathname'
|
7
7
|
require 'fileutils'
|
8
8
|
require 'tmpdir'
|
@@ -10,6 +10,8 @@ require 'securerandom'
|
|
10
10
|
require 'tsort'
|
11
11
|
require 'ostruct'
|
12
12
|
require 'date'
|
13
|
+
|
14
|
+
require 'colorize'
|
13
15
|
require 'excon'
|
14
16
|
require 'graphql/client'
|
15
17
|
require 'graphql/client/http'
|
@@ -17,7 +19,6 @@ require 'nokogiri'
|
|
17
19
|
require 'csv'
|
18
20
|
require 'roo'
|
19
21
|
require 'pdf-reader'
|
20
|
-
require 'json'
|
21
22
|
require 'zip'
|
22
23
|
require 'airac'
|
23
24
|
require 'aixm'
|
@@ -53,10 +54,11 @@ require_relative 'aipp/t_hash'
|
|
53
54
|
require_relative 'aipp/executable'
|
54
55
|
require_relative 'aipp/runner'
|
55
56
|
|
56
|
-
require_relative 'aipp/aip/executable'
|
57
|
-
require_relative 'aipp/aip/runner'
|
58
|
-
require_relative 'aipp/aip/parser'
|
57
|
+
require_relative 'aipp/scopes/aip/executable'
|
58
|
+
require_relative 'aipp/scopes/aip/runner'
|
59
|
+
require_relative 'aipp/scopes/aip/parser'
|
60
|
+
|
61
|
+
require_relative 'aipp/scopes/notam/executable'
|
62
|
+
require_relative 'aipp/scopes/notam/runner'
|
63
|
+
require_relative 'aipp/scopes/notam/parser'
|
59
64
|
|
60
|
-
require_relative 'aipp/notam/executable'
|
61
|
-
require_relative 'aipp/notam/runner'
|
62
|
-
require_relative 'aipp/notam/parser'
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aipp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sven Schwyn
|
@@ -29,7 +29,7 @@ cert_chain:
|
|
29
29
|
kAyiRqgxF4dJviwtqI7mZIomWL63+kXLgjOjMe1SHxfIPo/0ji6+r1p4KYa7o41v
|
30
30
|
fwIwU1MKlFBdsjkd
|
31
31
|
-----END CERTIFICATE-----
|
32
|
-
date: 2023-
|
32
|
+
date: 2023-02-27 00:00:00.000000000 Z
|
33
33
|
dependencies:
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: airac
|
@@ -60,7 +60,7 @@ dependencies:
|
|
60
60
|
version: '1'
|
61
61
|
- - ">="
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version: 1.4.
|
63
|
+
version: 1.4.1
|
64
64
|
type: :runtime
|
65
65
|
prerelease: false
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -70,7 +70,7 @@ dependencies:
|
|
70
70
|
version: '1'
|
71
71
|
- - ">="
|
72
72
|
- !ruby/object:Gem::Version
|
73
|
-
version: 1.4.
|
73
|
+
version: 1.4.1
|
74
74
|
- !ruby/object:Gem::Dependency
|
75
75
|
name: notam
|
76
76
|
requirement: !ruby/object:Gem::Requirement
|
@@ -344,8 +344,6 @@ email:
|
|
344
344
|
executables:
|
345
345
|
- aip2aixm
|
346
346
|
- aip2ofmx
|
347
|
-
- notam2aixm
|
348
|
-
- notam2ofmx
|
349
347
|
extensions: []
|
350
348
|
extra_rdoc_files:
|
351
349
|
- README.md
|
@@ -357,13 +355,7 @@ files:
|
|
357
355
|
- README.md
|
358
356
|
- exe/aip2aixm
|
359
357
|
- exe/aip2ofmx
|
360
|
-
- exe/notam2aixm
|
361
|
-
- exe/notam2ofmx
|
362
358
|
- lib/aipp.rb
|
363
|
-
- lib/aipp/aip/README.md
|
364
|
-
- lib/aipp/aip/executable.rb
|
365
|
-
- lib/aipp/aip/parser.rb
|
366
|
-
- lib/aipp/aip/runner.rb
|
367
359
|
- lib/aipp/border.rb
|
368
360
|
- lib/aipp/debugger.rb
|
369
361
|
- lib/aipp/downloader.rb
|
@@ -372,10 +364,6 @@ files:
|
|
372
364
|
- lib/aipp/downloader/http.rb
|
373
365
|
- lib/aipp/environment.rb
|
374
366
|
- lib/aipp/executable.rb
|
375
|
-
- lib/aipp/notam/README.md
|
376
|
-
- lib/aipp/notam/executable.rb
|
377
|
-
- lib/aipp/notam/parser.rb
|
378
|
-
- lib/aipp/notam/runner.rb
|
379
367
|
- lib/aipp/parser.rb
|
380
368
|
- lib/aipp/patcher.rb
|
381
369
|
- lib/aipp/pdf.rb
|
@@ -400,7 +388,20 @@ files:
|
|
400
388
|
- lib/aipp/regions/LS/README.md
|
401
389
|
- lib/aipp/regions/LS/helpers/base.rb
|
402
390
|
- lib/aipp/regions/LS/notam/ENR.rb
|
391
|
+
- lib/aipp/regions/LS/shoot/shooting_grounds.rb
|
403
392
|
- lib/aipp/runner.rb
|
393
|
+
- lib/aipp/scopes/aip/README.md
|
394
|
+
- lib/aipp/scopes/aip/executable.rb
|
395
|
+
- lib/aipp/scopes/aip/parser.rb
|
396
|
+
- lib/aipp/scopes/aip/runner.rb
|
397
|
+
- lib/aipp/scopes/notam/README.md
|
398
|
+
- lib/aipp/scopes/notam/executable.rb
|
399
|
+
- lib/aipp/scopes/notam/parser.rb
|
400
|
+
- lib/aipp/scopes/notam/runner.rb
|
401
|
+
- lib/aipp/scopes/shoot/README.md
|
402
|
+
- lib/aipp/scopes/shoot/executable.rb
|
403
|
+
- lib/aipp/scopes/shoot/parser.rb
|
404
|
+
- lib/aipp/scopes/shoot/runner.rb
|
404
405
|
- lib/aipp/t_hash.rb
|
405
406
|
- lib/aipp/version.rb
|
406
407
|
- lib/core_ext/array.rb
|
@@ -441,7 +442,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
441
442
|
- !ruby/object:Gem::Version
|
442
443
|
version: '0'
|
443
444
|
requirements: []
|
444
|
-
rubygems_version: 3.4.
|
445
|
+
rubygems_version: 3.4.7
|
445
446
|
signing_key:
|
446
447
|
specification_version: 4
|
447
448
|
summary: Parser for aeronautical information publications
|
metadata.gz.sig
CHANGED
Binary file
|
data/exe/notam2aixm
DELETED
data/exe/notam2ofmx
DELETED
data/lib/aipp/aip/executable.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module AIP
|
3
|
-
|
4
|
-
class Executable < AIPP::Executable
|
5
|
-
|
6
|
-
def initialize(exe_file)
|
7
|
-
super
|
8
|
-
AIPP.options.merge(
|
9
|
-
module: 'AIP',
|
10
|
-
airac: AIRAC::Cycle.new,
|
11
|
-
region_options: []
|
12
|
-
)
|
13
|
-
OptionParser.new do |o|
|
14
|
-
o.banner = <<~END
|
15
|
-
Download online AIP and convert it to #{AIPP.options.schema.upcase}.
|
16
|
-
Usage: #{File.basename($0)} [options]
|
17
|
-
END
|
18
|
-
common_options(o)
|
19
|
-
o.on('-a', '--airac (DATE|INTEGER)', String, %Q[AIRAC date or delta e.g. "+1" (default: "#{AIPP.options.airac.date.xmlschema}")]) { AIPP.options.airac = airac_for(_1) }
|
20
|
-
if AIPP.options.schema == :ofmx
|
21
|
-
o.on('-g', '--[no-]grouped-obstacles', 'group obstacles (default: false)') { AIPP.options.grouped_obstacles = _1 }
|
22
|
-
end
|
23
|
-
o.on('-O', '--region-options STRING', String, %Q[comma separated region specific options]) { AIPP.options.region_options = _1.split(',') }
|
24
|
-
developer_options(o)
|
25
|
-
end.parse!
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def airac_for(argument)
|
31
|
-
if argument.match?(/^[+-]\d+$/) # delta
|
32
|
-
AIRAC::Cycle.new + argument.to_i
|
33
|
-
else # date
|
34
|
-
AIRAC::Cycle.new(argument)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module NOTAM
|
3
|
-
|
4
|
-
class Executable < AIPP::Executable
|
5
|
-
|
6
|
-
def initialize(exe_file)
|
7
|
-
super
|
8
|
-
now = Time.now.utc.round
|
9
|
-
AIPP.options.merge(
|
10
|
-
module: 'NOTAM',
|
11
|
-
effective_at: now - now.sec - (now.min * 60) # previous full hour
|
12
|
-
)
|
13
|
-
OptionParser.new do |o|
|
14
|
-
o.banner = <<~END
|
15
|
-
Download online NOTAM and convert it to #{AIPP.options.schema.upcase}.
|
16
|
-
Usage: #{File.basename($0)} [options]
|
17
|
-
END
|
18
|
-
common_options(o)
|
19
|
-
o.on('-t', '--effective (TIME)', String, %Q[effective after this point in time (default: #{AIPP.options.effective_at})]) { AIPP.options.effective_at = Time.parse(_1) }
|
20
|
-
o.on('-x', '--crossload DIR', String, 'crossload directory') { AIPP.options.crossload = Pathname(_1) }
|
21
|
-
developer_options(o)
|
22
|
-
end.parse!
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|