aipp 2.1.1 → 2.1.2

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: 5a3f910ee20e31b9a2d0ee0644714c43898917cd9fda36bb5f721c17fcb758be
4
- data.tar.gz: a996dd39757199cdadd5ceab2c7014b871785c857dc2054c4d9e5c264b16b5a9
3
+ metadata.gz: cb4a7ae75e5b180c62d60b63a28ef1e28de3d23e0fe008defc4cc7bc0cf61f14
4
+ data.tar.gz: 6705dbfab25adfb9c71b1781c8ded9106ab1adc6906ec67f3c1330beb73de3e7
5
5
  SHA512:
6
- metadata.gz: d8cea4595823cf29656e25e252e205007de55a74240c1bca96f47ccd4e69194a112cb4665da0c784c24289f2d117d9b04e1a43625f86820763553a04bde03ab0
7
- data.tar.gz: 7b3ae6c428f88231456a94a882c51ec8c5436bd2ff1398e61a0465b48939ccc3a443ff20ab98039a9ee9fc1ac2a5b70cbbc54ebdf29f9712a84a35460250b312
6
+ metadata.gz: f394783a21b6aaac134155d06c1e3f5c082dcfdd835d0cb3abf708bc1a417fcc2a4bebf382fa1cc70b82c8fe8fa440cacb6a2f73eb73a1efbd0d3b72c437d5e4
7
+ data.tar.gz: 6c9d845076a9c2f894c2d4e586f66a32616d1a1903beb54423844b26d12e930bf85089b72494abe51a47c2701450044ef35508b0221c994d9ed512e3a821b6be
checksums.yaml.gz.sig CHANGED
@@ -1,3 +1,2 @@
1
- (��F���S�> *���
2
- ͻ��\��� k��1�<��>%���#cu�Q�L��Y+<��`%e��#�{k 2�'� ��M�����&a����*�ɝ7W��T��L��fX�O ki<i�����{�� i�+�ޔ
3
- r�}��Q==J����ÑeF��0*L2��!���?���r�L�������
1
+ M{��T��8(��3;p =[PR��oS�WoE���ʦ�q�3���U~���
2
+ \�ܚ)��o�>�g���
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  Nothing so far
4
4
 
5
+ ## 2.1.2
6
+
7
+ #### Changes
8
+ * Improve timesheet calculation for shooting grounds in LS
9
+ * Improve safety margins for shooting grounds in LS
10
+ * Switch to unzipped OFMX for now due to issues upstream
11
+
5
12
  ## 2.1.1
6
13
 
7
14
  #### Additions
@@ -92,6 +92,14 @@ TOT | 0 | Row | text(3) | yes | "TOT" (aka: Total)
92
92
  TOT | 1 | Created at | datetime(yyyymmddhhmmss) | yes | Date and time of CSV creation
93
93
  TOT | 2 | SPL count | number | yes | Number of SPL records
94
94
 
95
+ ### Safety Margins
96
+
97
+ The max height (BSZ col 15) has to be treated as an advisory value, not a guarantee:
98
+
99
+ If no value is given, the ammunition *should* not exceed 250m above ground. However, regulations for certain ammunition types allow for higher peaks. As of March 2023, 6cm mortars *may* reach up to 500m and future weapon systems *may* even go beyond that.
100
+
101
+ To account for this, the two constants `DEFAULT_Z` and `SAFETY` should be revisited from time to time.
102
+
95
103
  ## Asynchronous Use
96
104
 
97
105
  ### Command Line Arguments
@@ -48,9 +48,12 @@ module AIPP
48
48
  fail "not yet implemented"
49
49
  when 'AIP'
50
50
  AIPP::Downloader::HTTP.new(
51
- archive: "https://snapshots.openflightmaps.org/live/#{AIRAC::Cycle.new.id}/ofmx/lsas/latest/ofmx_ls.zip",
52
- file: "ofmx_ls/isolated/ofmx_ls.ofmx"
51
+ file: "https://storage.googleapis.com/snapshots.openflightmaps.org/live/#{AIRAC::Cycle.new.id}/ofmx/lsas/latest/isolated/ofmx_ls.xml"
53
52
  )
53
+ # AIPP::Downloader::HTTP.new(
54
+ # archive: "https://snapshots.openflightmaps.org/live/#{AIRAC::Cycle.new.id}/ofmx/lsas/latest/ofmx_ls.zip",
55
+ # file: "ofmx_ls/isolated/ofmx_ls.ofmx"
56
+ # )
54
57
  when 'DABS'
55
58
  if aixm.effective_at.to_date == Date.today # DABS cross check works reliably for today only
56
59
  AIPP::Downloader::HTTP.new(
@@ -5,33 +5,40 @@ module AIPP::LS::SHOOT
5
5
 
6
6
  include AIPP::LS::Helpers::Base
7
7
 
8
- DEFAULT_Z = AIXM.z(1000, :qfe) # height 300m unless present in publication
8
+ DEFAULT_Z = AIXM.z(2000, :qfe) # fallback if no max height is defined
9
+ SAFETY = 100 # safety margin in meters added to max height
9
10
 
10
11
  def parse
11
- effective_date = aixm.effective_at.strftime('%Y%m%d')
12
+ effective_date = AIPP.options.local_effective_at.strftime('%Y%m%d')
12
13
  airac_date = AIRAC::Cycle.new(aixm.effective_at).to_s('%Y-%m-%d')
13
14
  shooting_grounds = {}
14
15
  read.each_with_index do |row, line|
15
16
  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)
17
+ next unless type == 'BSZ'
18
+ next if no_shooting || date != effective_date
19
+ next if AIPP.options.id && AIPP.options.id != id
20
+ # TODO: Several BSZ lines may exist for the same shooting area with
21
+ # different location codes (aka: partial activations). The geometries
22
+ # of those location codes are not currently available, we therefore
23
+ # have to merge the data into one record. The geometries should become
24
+ # available by the end of 2023 which will make it possible to map
25
+ # each line to one geometry and remove the merging logic.
26
+ upper_z = row[15] ? AIXM.z(AIXM.d(row[15].to_i + SAFETY, :m).to_ft.dim.round, :qfe) : DEFAULT_Z
27
+ (shooting_grounds[id] ||= { schedules: [], upper_z: AIXM::GROUND }).then do |s|
28
+ s[:feature] ||= read("shooting_grounds-#{id}").fetch(:feature)
29
+ s[:csv_line] ||= line
30
+ s[:url] ||= row[10].blank_to_nil
31
+ s[:details] = [s[:details], row[6].blank_to_nil].compact.join("\n")
32
+ s[:dabs] ||= (row[16] == '1')
33
+ s[:upper_z] = upper_z if upper_z.alt > s[:upper_z].alt
34
+ s[:schedules] += schedules_for(row)
29
35
  end
30
36
  end
31
37
  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]
38
+ data in csv_line:, details:, url:, upper_z:, schedules:, dabs:, feature: { geometry: polygons, properties: { bezeichnung: name, infotelefonnr: phone, infoemail: email } }
39
+ schedules = consolidate(schedules)
40
+ if schedules.any?
41
+ geometries = geometries_for polygons
35
42
  indexed = geometries.count > 1
36
43
  geometries.each_with_index do |geometry, index|
37
44
  remarks = {
@@ -49,7 +56,7 @@ module AIPP::LS::SHOOT
49
56
  ).tap do |airspace|
50
57
  airspace.add_layer layer_for(upper_z, schedules, remarks)
51
58
  airspace.geometry = geometry
52
- airspace.comment = "DABS: marked for publication"
59
+ airspace.comment = "DABS: marked for publication" if dabs
53
60
  end
54
61
  )
55
62
  end
@@ -59,18 +66,34 @@ module AIPP::LS::SHOOT
59
66
 
60
67
  private
61
68
 
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)
69
+ def schedules_for(row)
70
+ from, to = time_for(row[3]), time_for(row[4], ending: true)
71
+ if from.to_date == to.to_date
72
+ [
73
+ [AIXM.date(from), (AIXM.time(from)..AIXM.time(to))]
74
+ ]
75
+ else
76
+ [
77
+ [AIXM.date(from), (AIXM.time(from)..AIXM::END_OF_DAY)],
78
+ [AIXM.date(to), (AIXM::BEGINNING_OF_DAY..AIXM.time(to))]
79
+ ]
71
80
  end
72
81
  end
73
82
 
83
+ def time_for(string, ending: false)
84
+ hour, min = case string.strip
85
+ when /(\d{2})(\d{2})/
86
+ [$1.to_i, $2.to_i]
87
+ when '', '0'
88
+ [0, 0]
89
+ else
90
+ warn("ignoring malformed time `#{string}'")
91
+ [0, 0]
92
+ end
93
+ hour = 24 if hour.zero? && min.zero? && ending
94
+ AIPP.options.local_effective_at.change(hour: hour, min: min).utc
95
+ end
96
+
74
97
  def geometries_for(polygons)
75
98
  fail "only type MultiPolygon supported" unless polygons[:type] == 'MultiPolygon'
76
99
  fail "polygon coordinates missing" unless polygons[:coordinates]
@@ -97,20 +120,30 @@ module AIPP::LS::SHOOT
97
120
 
98
121
  def timetable_for(schedules)
99
122
  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
- )
123
+ schedules.each do |date, times_array|
124
+ times_array.each do |times|
125
+ timetable.add_timesheet(
126
+ AIXM.timesheet(
127
+ adjust_to_dst: true,
128
+ dates: (date..date)
129
+ # TODO: transform to...
130
+ # dates: AIXM.date(date)
131
+ ).tap do |timesheet|
132
+ timesheet.times = times
133
+ end
134
+ )
135
+ end
111
136
  end
112
137
  end
113
138
  end
114
139
 
140
+ # TODO: Consolidate will become obsolete once location code geometries
141
+ # are available.
142
+ def consolidate(schedules)
143
+ schedules
144
+ .group_by(&:first)
145
+ .transform_values { _1.map(&:last).consolidate_ranges(:time) }
146
+ end
147
+
115
148
  end
116
149
  end
@@ -6,7 +6,8 @@ module AIPP
6
6
  def options
7
7
  AIPP.options.merge(
8
8
  module: 'Shoot',
9
- effective_at: Time.now.at_midnight
9
+ local_effective_at: Time.now.at_midnight,
10
+ id: nil
10
11
  )
11
12
  end
12
13
 
@@ -15,12 +16,13 @@ module AIPP
15
16
  Download online shooting activities and convert them to #{AIPP.options.schema.upcase}.
16
17
  Usage: #{File.basename($0)} shoot [options]
17
18
  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
+ o.on('-t', '--effective (DATE)', String, %Q[effective on this date (default: "#{AIPP.options.local_effective_at.to_date}")]) { AIPP.options.local_effective_at = Time.parse("#{_1} CET") }
20
+ o.on('-i', '--id ID', String, %Q[process shooting ground with this ID only]) { AIPP.options.id = _1 }
19
21
  end
20
22
 
21
23
  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
+ AIPP.options.time_zone = AIPP.options.local_effective_at.at_noon.strftime('%z')
25
+ AIPP.options.effective_at = AIPP.options.local_effective_at.at_midnight.utc
24
26
  end
25
27
 
26
28
  end
@@ -8,7 +8,7 @@ module AIPP
8
8
  end
9
9
 
10
10
  def expiration_at
11
- effective_at.end_of_day.round - 1
11
+ effective_at + 86399
12
12
  end
13
13
 
14
14
  def run
data/lib/aipp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module AIPP
2
- VERSION = "2.1.1".freeze
2
+ VERSION = "2.1.2".freeze
3
3
  end
@@ -10,4 +10,27 @@ class Array
10
10
  map(&:to_s).join('::').constantize
11
11
  end
12
12
 
13
+ # Consolidate array of possibly overlapping ranges.
14
+ #
15
+ # @example
16
+ # [15..17, 7..11, 12..13, 8..12, 12..13].consolidate_ranges
17
+ # # => [7..13, 15..17]
18
+ #
19
+ # @param [Symbol, nil] method to call on range members for comparison
20
+ # @return [Array] consolidated array
21
+ def consolidate_ranges(method=:itself)
22
+ uniq.sort_by { [_1.begin, _1.end] }.then do |ranges|
23
+ consolidated, a = [], ranges.first
24
+ Array(ranges[1..]).each do |b|
25
+ if a.end.send(method) >= b.begin.send(method) # overlapping
26
+ a = (a.begin..(a.end.send(method) > b.end.send(method) ? a.end : b.end))
27
+ else # not overlapping
28
+ consolidated << a
29
+ a = b
30
+ end
31
+ end
32
+ consolidated << a
33
+ end.compact
34
+ end
35
+
13
36
  end
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.1.1
4
+ version: 2.1.2
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-02-27 00:00:00.000000000 Z
32
+ date: 2023-03-23 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.1
63
+ version: 1.4.2
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.1
73
+ version: 1.4.2
74
74
  - !ruby/object:Gem::Dependency
75
75
  name: notam
76
76
  requirement: !ruby/object:Gem::Requirement
@@ -442,7 +442,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
442
442
  - !ruby/object:Gem::Version
443
443
  version: '0'
444
444
  requirements: []
445
- rubygems_version: 3.4.7
445
+ rubygems_version: 3.4.9
446
446
  signing_key:
447
447
  specification_version: 4
448
448
  summary: Parser for aeronautical information publications
metadata.gz.sig CHANGED
Binary file