aixm 0.3.10 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +3 -1
  3. data/CHANGELOG.md +67 -14
  4. data/README.md +45 -24
  5. data/exe/ckmid +1 -4
  6. data/exe/mkmid +1 -4
  7. data/lib/aixm/a.rb +89 -71
  8. data/lib/aixm/association.rb +40 -32
  9. data/lib/aixm/classes.rb +8 -4
  10. data/lib/aixm/{feature → component}/address.rb +21 -12
  11. data/lib/aixm/component/approach_lighting.rb +136 -0
  12. data/lib/aixm/component/fato.rb +58 -42
  13. data/lib/aixm/component/frequency.rb +5 -5
  14. data/lib/aixm/component/geometry/arc.rb +1 -1
  15. data/lib/aixm/component/geometry/border.rb +1 -1
  16. data/lib/aixm/component/geometry/circle.rb +4 -4
  17. data/lib/aixm/component/geometry/point.rb +4 -3
  18. data/lib/aixm/component/geometry/rhumb_line.rb +54 -0
  19. data/lib/aixm/component/geometry.rb +10 -7
  20. data/lib/aixm/component/helipad.rb +26 -36
  21. data/lib/aixm/component/layer.rb +5 -3
  22. data/lib/aixm/component/lighting.rb +5 -5
  23. data/lib/aixm/component/runway.rb +81 -52
  24. data/lib/aixm/component/service.rb +12 -3
  25. data/lib/aixm/component/surface.rb +12 -12
  26. data/lib/aixm/component/timetable.rb +5 -3
  27. data/lib/aixm/component/vasis.rb +105 -0
  28. data/lib/aixm/component/vertical_limit.rb +3 -3
  29. data/lib/aixm/component.rb +10 -0
  30. data/lib/aixm/config.rb +5 -2
  31. data/lib/aixm/d.rb +16 -15
  32. data/lib/aixm/document.rb +14 -1
  33. data/lib/aixm/f.rb +29 -1
  34. data/lib/aixm/feature/airport.rb +34 -10
  35. data/lib/aixm/feature/airspace.rb +5 -1
  36. data/lib/aixm/feature/navigational_aid/dme.rb +29 -10
  37. data/lib/aixm/feature/navigational_aid/marker.rb +2 -2
  38. data/lib/aixm/feature/navigational_aid/tacan.rb +3 -2
  39. data/lib/aixm/feature/navigational_aid/vor.rb +32 -13
  40. data/lib/aixm/feature/navigational_aid.rb +1 -1
  41. data/lib/aixm/feature/obstacle.rb +36 -20
  42. data/lib/aixm/feature/obstacle_group.rb +12 -11
  43. data/lib/aixm/feature/organisation.rb +1 -0
  44. data/lib/aixm/feature/unit.rb +2 -1
  45. data/lib/aixm/feature.rb +3 -0
  46. data/lib/aixm/memoize.rb +27 -11
  47. data/lib/aixm/p.rb +3 -2
  48. data/lib/aixm/payload_hash.rb +1 -1
  49. data/lib/aixm/r.rb +62 -0
  50. data/lib/aixm/refinements.rb +4 -4
  51. data/lib/aixm/shortcuts.rb +2 -2
  52. data/lib/aixm/version.rb +1 -1
  53. data/lib/aixm/w.rb +2 -1
  54. data/lib/aixm/xy.rb +9 -1
  55. data/lib/aixm/z.rb +5 -4
  56. data/lib/aixm.rb +12 -6
  57. data/schemas/ofmx/{0 → 0.1}/OFMX-CSV-Obstacle.json +0 -0
  58. data/schemas/ofmx/{0 → 0.1}/OFMX-CSV.json +0 -0
  59. data/schemas/ofmx/{0 → 0.1}/OFMX-DataTypes.xsd +58 -2
  60. data/schemas/ofmx/{0 → 0.1}/OFMX-Features.xsd +119 -40
  61. data/schemas/ofmx/{0 → 0.1}/OFMX-Snapshot.xsd +5 -0
  62. data.tar.gz.sig +0 -0
  63. metadata +34 -29
  64. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3f6bbccc417b5489c0406281f074c0e2ba763681f2ba93eecce13fc4512a5a3
4
- data.tar.gz: 2ee3415fd70d683ed60911ddb78f3cd2fa2277aaeea5546cb3e22ab1c22b2d73
3
+ metadata.gz: 33661b6ab9022c54a9d41dad531b0f1c53f39eaf053a6c4b8361c50543b9ae01
4
+ data.tar.gz: add22919257222a6f06dae9de21f0f71c2b941a67e6bc52dba14bde5fbae6c9a
5
5
  SHA512:
6
- metadata.gz: c8105e111e3ee60f5ad28b99a2beccd05ce719297c9bb836cca7742dc4b6dec6187fd3cc32b2f09d3924deea35e863329350ca02db869079a2a0f0855a60a6d5
7
- data.tar.gz: 5d620f1fb1d47d8a8e6f542029bba5e29d31db796baa6dba3c418d8aef0a6dd75e8996c95d68fa5f45187aae66119bf765af9ebc9d304c8179287c1bb395927c
6
+ metadata.gz: c1fb317e1791098f127c769e5f680bbf0c30ab823c73c48f392d6ea918f1ba4c6396d4e5cb43357ef1c867bad73e475d25be95b274bad7fd9ee17ce297dbbb6f
7
+ data.tar.gz: 904a2fa5c84b80bf14a67536fa2c0f2ab26b6d34dc3dc789e272116812994f86b9c3aff92b08d1cda9b3d3495abfe990eb922f0579fd779a1602963ba28ded59
checksums.yaml.gz.sig CHANGED
@@ -1 +1,3 @@
1
- ��*; ^ь�G�`��ِ .�%o+�����ZP[~fRs��O7�ߘn(8�.Yg����9EV��&Et���ט�97��0�`�ɨ���lxtl����_;���ٮa�EO��@V��g���|H�n�lb7�J���'����Ui4%@J"�(_�ذ���#
1
+ y)ጕ;_���E��-�X�ʸ\=����3{U`�����yX}���yM�a! ��s c��8����m� ?r���%�&c�w��X.�,�}�~���]X6M�$��
2
+ >�1D―b1`R����
3
+ g�ee屒���ڶl��X������ԧ
data/CHANGELOG.md CHANGED
@@ -1,18 +1,71 @@
1
+ ## 1.1.0
2
+
3
+ #### Breaking Changes
4
+ * `AIXM::Association:Array#duplicates` now returns an array of arrays which
5
+ group all duplicates together.
6
+ * `VOR#associate_dme` and `VOR#associate_tacan` no longer take the channel
7
+ as argument but calculate it from the (ghost) frequency of the VOR.
8
+ * Replaced `#length`/`#width` with `#dimensions` on `Runway`, `Helipad` and `FATO`
9
+ * Renamed `AIXM::D#dist` to `AIXM::D#dim`
10
+ * Renamed `TLOF#helicopter_class` to `TLOF#performance_class`
11
+ * Renamed `#geographic_orientation` and `#magnetic_orientation` to more familiar
12
+ `#geographic_bearing` and `#magnetic_bearing` on `Runway` and `FATO`
13
+ * Re-implementation of `AIXM::A` without precision
14
+ * Demoted `Address` to component
15
+ * Fixed typo in `Service` type `:vdf_direction_finding_service`
16
+
17
+ #### Additions
18
+ * Associations from `Service` to `Airport` and `Airspace`
19
+ * `AIXM::R` (rectangle)
20
+ * `Runway#marking`
21
+ * `ApproachLighting` on `Runway::Direction` and `FATO::Direction`
22
+ * `VASIS` on `Runway::Direction` and `FATO::Direction`
23
+ * `#meta` on every feature and component
24
+ * `Document#regions` which is added to the root element for OFMX
25
+
26
+ #### Changes
27
+ * Nested memoization of the same method is now allowed and won't reset the
28
+ memoization cache anymore.
29
+ * Remove unit "mhz" from `Address` of type `:radio_frequency`.
30
+
31
+ ## 1.0.0
32
+
33
+ #### Breaking Changes
34
+ * Move `Ase->txtLocalType` up into `AseUid` for OFMX
35
+
36
+ #### Additions
37
+ * Add rhumb line geometry
38
+
39
+ ## 0.3.11
40
+
41
+ #### Breaking Changes
42
+ * Renamed default git branch to `main`
43
+ * Require Ruby 3.0
44
+ * `Address#address` requires and returns `AIXM::F` for type `:radio_frequency`
45
+
46
+ #### Changes
47
+ * Fix `Obstacle#source` for OFMX
48
+
49
+ #### Additions
50
+ * Add `f#voice?` and `AIXM.config.voice_channel_separation` to check whether a
51
+ frequency belongs to the voice communication airband and use it to validate
52
+ `Frequency`
53
+
1
54
  ## 0.3.10
2
55
 
3
- ### Additions
56
+ #### Additions
4
57
  * Proper `has_many` and `has_one` associations
5
- * `AIXM::Association:Array#find_by|find|duplicate` on `has_many` associations
58
+ * `AIXM::Association:Array#find_by|find|duplicates` on `has_many` associations
6
59
  * `AIXM.config.mid` now defines whether `mid` attributes are inserted or not
7
60
  provided the selected schema is OFMX
8
61
  * `AIXM::Memoize` module
9
62
  * `AIXM::PayloadHash` class
10
63
  * `mkmid` executable to insert `mid` attributes into valid OFMX file
11
64
  * `ckmid` executable to check `mid` attributes in an OFMX file
12
- * `AIXM::Component::Geometry#point?|circle?|polygon?`
13
- * `AIXM::Component::Layer#services`
65
+ * Geometries respond to `#point?`, `#circle?` and `#polygon?`
66
+ * `Layer#services`
14
67
 
15
- ### Breaking Changes
68
+ #### Breaking Changes
16
69
  * Require Ruby 2.7
17
70
  * Moved `region` attribute from `Document` back to features again
18
71
  * Use `Document#add_feature` instead of `Document@features#<<`
@@ -25,7 +78,7 @@
25
78
  * Refinement `String#payload_hash` removed in favor of `AIXM::PayloadHash` class
26
79
  * Refinements `Array#find|duplicates` removed
27
80
 
28
- ### Changes
81
+ #### Changes
29
82
  * Renamed `AIXM.config.mid_region` to `AIXM.config.region`
30
83
 
31
84
  ## 0.3.8
@@ -40,20 +93,20 @@
40
93
  ## 0.3.7
41
94
 
42
95
  #### Additions
43
- * `AIXM::Document#select_features`
44
- * `AIXM::Document#group_obstacles!`
96
+ * `Document#select_features`
97
+ * `Document#group_obstacles!`
45
98
 
46
99
  ## 0.3.6
47
100
 
48
101
  #### Additions
49
- * `AIXM::Component::FATO`
50
- * `AIXM::Component::Helipad#helicopter_class` and `AIXM::Component::Helipad#marking`
102
+ * `FATO`
103
+ * `Helipad#helicopter_class` and `Helipad#marking`
51
104
  * `AIXM::XY#seconds?` to detect possibly rounded or estimated coordinates
52
- * `AIXM::Features::Airport#operator`
105
+ * `Airport#operator`
53
106
  * `AIXM::W` (weight)
54
107
  * `AIXM::P` (pressure)
55
- * `AIXM::Component::Lighting` for use with runways, helipads and FATOs
56
- * Surface details `siwl_weight`, `siwl_tire_pressure` and `auw_weight`
108
+ * `Lighting` for use with runways, helipads and FATOs
109
+ * Surface details `#siwl_weight`, `#siwl_tire_pressure` and `#auw_weight`
57
110
 
58
111
  #### Changes
59
112
  * Generate `Airport#id` from region and `Airport#name`
@@ -219,7 +272,7 @@
219
272
 
220
273
  #### Changes
221
274
  * `Document#created_at` and `#effective_at` accept Time, Date, String or *nil*
222
- * Separate `AIXM::Document#valid?` from `#complete?`
275
+ * Separate `Document#valid?` from `#complete?`
223
276
  * Write coordinates in DD if extension `:OFM` is set
224
277
  * `Array#to_digest` returns Integer which fits in signed 32bit
225
278
 
data/README.md CHANGED
@@ -20,7 +20,7 @@ For now, only the parts needed to automize the AIP import of [open flightmaps](h
20
20
  This gem is [cryptographically signed](https://guides.rubygems.org/security/#using-gems) in order to assure it hasn't been tampered with. Unless already done, please add the author's public key as a trusted certificate now:
21
21
 
22
22
  ```
23
- gem cert --add <(curl -Ls https://raw.github.com/svoop/aixm/master/certs/svoop.pem)
23
+ gem cert --add <(curl -Ls https://raw.github.com/svoop/aixm/main/certs/svoop.pem)
24
24
  ```
25
25
 
26
26
  ### Bundler
@@ -47,7 +47,7 @@ gem install aixm --trust-policy MediumSecurity
47
47
 
48
48
  ## Usage
49
49
 
50
- Here's how to build a document object, populate it with a simple feature and then render it as AIXM:
50
+ Here's how to build a document object, populate it with a simple feature and then render it as AIXM or OFMX:
51
51
 
52
52
  ```ruby
53
53
  document = AIXM.document(
@@ -100,19 +100,29 @@ The `:ofmx` schema requires the [region to be set on all core features](https://
100
100
 
101
101
  ```ruby
102
102
  AIXM.ofmx!
103
- AIXM.region = 'LF'
103
+ AIXM.config.region = 'LF'
104
104
  ```
105
105
 
106
106
  :warning: This setting has no effect when using the `:aixm` schema.
107
107
 
108
+ ### AIXM.voice_channel_separation
109
+
110
+ Define which voice channel separation should be used to validate voice communication frequencies.
111
+
112
+ ```ruby
113
+ AIXM.voice_channel_separation = :any # both 25 and 8.33 kHz (default)
114
+ AIXM.voice_channel_separation = 25 # 25 kHz only
115
+ AIXM.voice_channel_separation = 833 # 8.33 kHz only
116
+ ```
117
+
108
118
  ### AIXM.config.mid
109
119
 
110
120
  In order to insert [OFMX-compliant `mid` attributes](https://gitlab.com/openflightmaps/ofmx/wikis/Features#mid) into all `*Uid` elements, you have set the mid configuration option to `true`.
111
121
 
112
122
  ```ruby
113
123
  AIXM.ofmx!
114
- AIXM.config.mid # => false - don't insert mid attributes by default
115
- AIXM.config.mid = true # => true - insert mid attributes
124
+ AIXM.config.mid = false # don't insert mid attributes (default)
125
+ AIXM.config.mid = true # insert mid attributes
116
126
  ```
117
127
 
118
128
  :warning: This setting has no effect when using the `:aixm` schema.
@@ -129,17 +139,16 @@ AIXM.config.ignored_errors = /invalid date/i
129
139
 
130
140
  ### Fundamentals
131
141
  * [Document](https://www.rubydoc.info/gems/aixm/AIXM/Document.html)
142
+ * [A (angle)](https://www.rubydoc.info/gems/aixm/AIXM/A.html)
143
+ * [D (dimension, distance or length)](https://www.rubydoc.info/gems/aixm/AIXM/D.html)
144
+ * [F (frequency)](https://www.rubydoc.info/gems/aixm/AIXM/F.html)
145
+ * [P (pressure)](https://www.rubydoc.info/gems/aixm/AIXM/P.html)
146
+ * [R (rectangle)](https://www.rubydoc.info/gems/aixm/AIXM/R.html)
132
147
  * [XY (longitude and latitude)](https://www.rubydoc.info/gems/aixm/AIXM/XY.html)
133
148
  * [Z (height, elevation or altitude)](https://www.rubydoc.info/gems/aixm/AIXM/Z.html)
134
- * [D (distance or length)](https://www.rubydoc.info/gems/aixm/AIXM/D.html)
135
- * [F (frequency)](https://www.rubydoc.info/gems/aixm/AIXM/F.html)
136
- * [A (angle)](https://www.rubydoc.info/gems/aixm/AIXM/A.html)
137
149
 
138
150
  ### Features
139
151
  * [Address](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Address.html)
140
- * [Organisation](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Organisation.html)
141
- * [Unit](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Unit.html)
142
- * [Service](https://www.rubydoc.info/gems/aixm/AIXM/Component/Service.html)
143
152
  * [Airport](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Airport.html)
144
153
  * [Airspace](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Airspace.html)
145
154
  * [Navigational aid](https://www.rubydoc.info/gems/aixm/AIXM/NavigationalAid.html)
@@ -149,22 +158,32 @@ AIXM.config.ignored_errors = /invalid date/i
149
158
  * [NDB](https://www.rubydoc.info/gems/aixm/AIXM/Feature/NDB.html)
150
159
  * [TACAN](https://www.rubydoc.info/gems/aixm/AIXM/Feature/TACAN.html)
151
160
  * [VOR](https://www.rubydoc.info/gems/aixm/AIXM/Feature/VOR.html)
152
- * [Obstacle and obstacle group](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Obstacle.html)
161
+ * [Obstacle](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Obstacle.html)
162
+ * [Obstacle group](https://www.rubydoc.info/gems/aixm/AIXM/Feature/ObstacleGroup.html)
163
+ * [Organisation](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Organisation.html)
164
+ * [Service](https://www.rubydoc.info/gems/aixm/AIXM/Component/Service.html)
165
+ * [Unit](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Unit.html)
153
166
 
154
167
  ### Components
168
+
169
+ * [ApproachLighting](https://www.rubydoc.info/gems/aixm/AIXM/Component/ApproachLighting.html)
170
+ * [FATO](https://www.rubydoc.info/gems/aixm/AIXM/Component/FATO.html)
155
171
  * [Frequency](https://www.rubydoc.info/gems/aixm/AIXM/Component/Frequency.html)
156
172
  * [Geometry](https://www.rubydoc.info/gems/aixm/AIXM/Component/Geometry.html)
157
- * [Point](https://www.rubydoc.info/gems/aixm/AIXM/Component/Point.html)
158
173
  * [Arc](https://www.rubydoc.info/gems/aixm/AIXM/Component/Arc.html)
159
174
  * [Border](https://www.rubydoc.info/gems/aixm/AIXM/Component/Border.html)
160
175
  * [Circle](https://www.rubydoc.info/gems/aixm/AIXM/Component/Circle.html)
161
- * [Runway](https://www.rubydoc.info/gems/aixm/AIXM/Component/Runway.html)
176
+ * [Point](https://www.rubydoc.info/gems/aixm/AIXM/Component/Point.html)
177
+ * [RhumbLine](https://www.rubydoc.info/gems/aixm/AIXM/Component/RhumbLine.html)
162
178
  * [Helipad](https://www.rubydoc.info/gems/aixm/AIXM/Component/Helipad.html)
163
- * [FATO](https://www.rubydoc.info/gems/aixm/AIXM/Component/FATO.html)
164
- * [Surface](https://www.rubydoc.info/gems/aixm/AIXM/Component/Surface.html)
165
179
  * [Layer](https://www.rubydoc.info/gems/aixm/AIXM/Component/Layer.html)
166
- * [Vertical limit](https://www.rubydoc.info/gems/aixm/AIXM/Component/VerticalLimit.html)
180
+ * [Lighting](https://www.rubydoc.info/gems/aixm/AIXM/Component/Lighting.html)
181
+ * [Runway](https://www.rubydoc.info/gems/aixm/AIXM/Component/Runway.html)
182
+ * [Service](https://www.rubydoc.info/gems/aixm/AIXM/Component/Service.html)
183
+ * [Surface](https://www.rubydoc.info/gems/aixm/AIXM/Component/Surface.html)
167
184
  * [Timetable](https://www.rubydoc.info/gems/aixm/AIXM/Component/Timetable.html)
185
+ * [VASIS](https://www.rubydoc.info/gems/aixm/AIXM/Component/VASIS.html)
186
+ * [Vertical limit](https://www.rubydoc.info/gems/aixm/AIXM/Component/VerticalLimit.html)
168
187
 
169
188
  ## Associations
170
189
 
@@ -194,6 +213,14 @@ document.features.find(airport) # => [#<AIXM::Feature::Airport>]
194
213
 
195
214
  This may seem redundant at first, but keep in mind that two instances of +AIXM::CLASSES+ which implement `#to_uid` are considered equal if they are instances of the same class and both their UIDs as calculated by `#to_uid` are equal. Attributes which are not part of the `#to_uid` calculation are irrelevant!
196
215
 
216
+ ### meta
217
+
218
+ You can write arbitrary meta information to any feature or component. It won't be used when building the AIXM or OFMX document, in fact, it is not used by this gem at all. But you can store e.g. foreign keys and then later use them to find a feature or component like so:
219
+
220
+ ```ruby
221
+ document.features.find_by(:airport, meta: 1234) # 1234 is the foreign key
222
+ ```
223
+
197
224
  ### duplicates
198
225
 
199
226
  Equally on `has_many` associations, use `duplicates` to find identical or equal associations:
@@ -272,13 +299,7 @@ bundle exec rake # run tests once
272
299
  bundle exec guard # run tests whenever files are modified
273
300
  ```
274
301
 
275
- Please submit issues on:
276
-
277
- https://github.com/svoop/aixm/issues
278
-
279
- To contribute code, fork the project on Github, add your code and submit a pull request:
280
-
281
- https://help.github.com/articles/fork-a-repo
302
+ You're welcome to [submit issues](https://github.com/svoop/aixm/issues) and contribute code by [forking the project and submitting pull requests](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
282
303
 
283
304
  ## License
284
305
 
data/exe/ckmid CHANGED
@@ -1,13 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # TODO: hide deprecations unless in gem root until fully complying with Ruby 2.7
4
- Warning[:deprecated] = Dir.pwd.match? /aixm$/
5
-
6
3
  require 'bundler/inline'
7
4
 
8
5
  gemfile do
9
6
  source 'https://rubygems.org'
10
- ruby '>= 2.7'
7
+ ruby '>= 3.0'
11
8
  gem 'aixm', '~> 0'
12
9
  end
13
10
 
data/exe/mkmid CHANGED
@@ -1,13 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # TODO: hide deprecations unless in gem root until fully complying with Ruby 2.7
4
- Warning[:deprecated] = Dir.pwd.match? /aixm$/
5
-
6
3
  require 'bundler/inline'
7
4
 
8
5
  gemfile do
9
6
  source 'https://rubygems.org'
10
- ruby '>= 2.7'
7
+ ruby '>= 3.0'
11
8
  gem 'aixm', '~> 0'
12
9
  end
13
10
 
data/lib/aixm/a.rb CHANGED
@@ -2,53 +2,48 @@ using AIXM::Refinements
2
2
 
3
3
  module AIXM
4
4
 
5
- # Angle from 0 to 359 degrees with an optional suffix used for azimuths,
6
- # bearings, headings, courses etc.
5
+ # Angle in the range of -360 < angle < 360 degrees (used for azimuths or
6
+ # courses) and with an optional one-letter suffix (used for runways).
7
7
  #
8
- # @example Initialized with Numeric
9
- # a = AIXM.a(12) # 12 degrees, 1 degree precision, no suffix
10
- # a.precision # => 3 (three digits = steps of 1 degree)
11
- # a.to_s # => "012"
12
- # a.suffix # => nil
13
- # a.deg # => 12
14
- # a.deg += 7 # => 19
15
- # a.deg += 341 # => 0 - deg is always within (0..359)
16
- # a.to_s # => "000" - to_s is always within ("000".."359")
8
+ # @example Initialization
9
+ # AIXM.a(-36.9) # => #<AIXM::A -36.9° "32">
10
+ # AIXM.a(12) # => #<AIXM::A 12° "01">
11
+ # AIXM.a("12L") # => #<AIXM::A 120° "12L">
12
+ # AIXM.a(360) # => #<AIXM::A 0° "36">
13
+ # AIXM.a(-400) # => #<AIXM::A -40° "32">
17
14
  #
18
- # @example Initialized with String
19
- # a = AIXM.a('06L') # 60 degrees, 10 degree precision, suffix :L
20
- # a.precision # => 2 (two digits = steps of 10 degrees)
21
- # a.to_s # => "06L"
22
- # a.suffix # => :L
23
- # a.deg # => 60
24
- # a.deg += 7 # => 70
25
- # a.deg += 190 # => 0 - deg is always within (0..359)
26
- # a.to_s # => "36L" - to_s converts to ("01".."36")
15
+ # @example Calculations
16
+ # a = AIXM.a("02L")
17
+ # a += 5 # => #<AIXM::A 25° "03L">
18
+ # a -= AIXM.a(342.8) # => #<AIXM::A -317.8° "04L">
19
+ # a.to_s # => "-317.8°"
20
+ # a.to_s(:runway) # => "04L"
21
+ # a.to_s(:bearing) # => "042.2000"
22
+ # a.to_f # => 42.2
23
+ # a.to_i # => 42
24
+ # a.invert # => #<AIXM::A -137.8° "22R">
25
+ # a.to_s(:runway) # => "22R"
27
26
  class A
28
27
  SUFFIX_INVERSIONS = {
29
28
  R: :L,
30
29
  L: :R
31
30
  }.freeze
32
31
 
33
- # @return [Integer] angle
34
- attr_reader :deg
32
+ RUNWAY_RE = /\A(0[1-9]|[12]\d|3[0-6])([A-Z])?\z/
35
33
 
36
- # @return [Integer] precision: +2+ (10 degree steps) or +3+ (1 degree steps)
37
- attr_reader :precision
34
+ # @return [Integer] angle in the range of -360 < angle < 360
35
+ attr_reader :deg
38
36
 
39
- # @return [Symbol, nil] suffix
37
+ # @return [Symbol, nil] one-letter suffix
40
38
  attr_reader :suffix
41
39
 
42
- def initialize(deg_and_suffix)
43
- case deg_and_suffix
44
- when Numeric
45
- self.deg, @precision = deg_and_suffix, 3
40
+ def initialize(value)
41
+ case value
46
42
  when String
47
- fail(ArgumentError, "invalid angle") unless deg_and_suffix.to_s =~ /\A(\d+)([A-Z]+)?\z/
48
- self.deg, @precision, self.suffix = $1.to_i * 10, 2, $2
49
- when Symbol # used only by private build method
50
- fail(ArgumentError, "invalid precision") unless %i(2 3).include? deg_and_suffix
51
- @deg, @precision = 0, deg_and_suffix.to_s.to_i
43
+ fail(ArgumentError, "invalid angle") unless value =~ RUNWAY_RE
44
+ self.deg, self.suffix = $1.to_i * 10, $2
45
+ when Numeric
46
+ self.deg = value
52
47
  else
53
48
  fail(ArgumentError, "invalid angle")
54
49
  end
@@ -56,39 +51,62 @@ module AIXM
56
51
 
57
52
  # @return [String]
58
53
  def inspect
59
- %Q(#<#{self.class}[precision=#{precision}] #{to_s}>)
54
+ %Q(#<#{self.class} #{to_s} #{to_s(:runway).inspect}>)
60
55
  end
61
56
 
62
- # @return [String] human readable representation according to precision
63
- def to_s
64
- if precision == 2
65
- [('%02d' % ((deg / 10 + 35) % 36 + 1)), suffix].map(&:to_s).join
66
- else
67
- ('%03d' % deg)
57
+ # @return [Integer] within 0..359
58
+ def to_i
59
+ (deg.round + 360) % 360
60
+ end
61
+
62
+ # @return [Float] within 0.0..359.9~
63
+ def to_f
64
+ ((deg + 360) % 360).to_f
65
+ end
66
+
67
+ # Degrees as formatted string
68
+ #
69
+ # Types are:
70
+ # * :human - degrees within -359.9~..359.9~ as D.D° (default)
71
+ # * :bearing - degrees within 0.0..359.9~ as DDD.DDDD
72
+ # * :runway - degrees within "01".."36" plus optional suffix
73
+ #
74
+ # @param type [Symbol, nil] either :runway, :bearing or nil
75
+ # @param unit [String] unit to postfix
76
+ # @return [String]
77
+ def to_s(type=:human)
78
+ return '' unless deg
79
+ case type
80
+ when :runway then [('%02d' % (((deg / 10).round + 35) % 36 + 1)), suffix].join
81
+ when :bearing then '%08.4f' % to_f.round(4)
82
+ when :human then [deg.to_s('F').sub(/\.0$/, ''), '°'].join
83
+ else fail ArgumentError
68
84
  end
69
85
  end
70
86
 
71
87
  def deg=(value)
72
- fail(ArgumentError, "invalid deg `#{value}'") unless value.is_a?(Numeric) && value.round.between?(0, 360)
73
- @deg = (precision == 2 ? (value.to_f / 10).round * 10 : value.round) % 360
88
+ fail(ArgumentError, "invalid deg `#{value}'") unless value.is_a? Numeric
89
+ normalized_value = value.abs % 360
90
+ sign = '-' if value.negative? && normalized_value.nonzero?
91
+ @deg = BigDecimal("#{sign}#{normalized_value}")
74
92
  end
75
93
 
76
94
  def suffix=(value)
77
- fail(RuntimeError, "suffix only allowed when precision is 2") unless value.nil? || precision == 2
78
- fail(ArgumentError, "invalid suffix") unless value.nil? || value.to_s =~ /\A[A-Z]+\z/
95
+ fail(ArgumentError, "invalid suffix") unless value.nil? || value.to_s =~ /\A[A-Z]\z/
79
96
  @suffix = value&.to_s&.to_sym
80
97
  end
81
98
 
82
99
  # Invert an angle by 180 degrees
83
100
  #
84
101
  # @example
85
- # AIXM.a(120).invert # => AIXM.a(300)
86
- # AIXM.a("34L").invert # => AIXM.a("16R")
87
- # AIXM.a("33X").invert # => AIXM.a("33X")
102
+ # AIXM.a(120).invert # (300°)
103
+ # AIXM.a("34L").invert # (160° suffix "R")
88
104
  #
89
105
  # @return [AIXM::A] inverted angle
90
106
  def invert
91
- build(precision: precision, deg: (deg + 180) % 360, suffix: SUFFIX_INVERSIONS.fetch(suffix, suffix))
107
+ self.class.new(deg.negative? ? deg - 180 : deg + 180).tap do |angle|
108
+ angle.suffix = SUFFIX_INVERSIONS.fetch(suffix, suffix)
109
+ end
92
110
  end
93
111
 
94
112
  # Check whether +other+ angle is the inverse
@@ -104,47 +122,47 @@ module AIXM
104
122
  invert == other
105
123
  end
106
124
 
107
- # Add degrees
125
+ # Negate degrees
108
126
  #
109
127
  # @return [AIXM::A]
110
- def +(numeric_or_angle)
111
- fail ArgumentError unless numeric_or_angle.respond_to? :round
112
- build(precision: precision, deg: (deg + numeric_or_angle.round) % 360, suffix: suffix)
128
+ def -@
129
+ deg.zero? ? self : self.class.new(-deg).tap { _1.suffix = suffix }
113
130
  end
114
131
 
115
- # Subtract degrees
132
+ # Add degrees
116
133
  #
134
+ # @param value [Numeric, AIXM::A]
117
135
  # @return [AIXM::A]
118
- def -(numeric_or_angle)
119
- fail ArgumentError unless numeric_or_angle.respond_to? :round
120
- build(precision: precision, deg: (deg - numeric_or_angle.round + 360) % 360, suffix: suffix)
136
+ def +(value)
137
+ case value
138
+ when Numeric
139
+ value.zero? ? self : self.class.new(deg + value).tap { _1.suffix = suffix }
140
+ when AIXM::A
141
+ value.deg.zero? ? self : self.class.new(deg + value.deg).tap { _1.suffix = suffix }
142
+ else
143
+ fail ArgumentError
144
+ end
121
145
  end
122
146
 
123
- # @private
124
- def round
125
- deg
147
+ # Subtract degrees
148
+ #
149
+ # @param value [Numeric, AIXM::A]
150
+ # @return [AIXM::A]
151
+ def -(value)
152
+ self + -value
126
153
  end
127
154
 
128
155
  # @see Object#==
129
156
  # @return [Boolean]
130
157
  def ==(other)
131
- self.class === other && deg == other.deg && precision == other.precision && suffix == other.suffix
158
+ self.class === other && deg == other.deg && suffix == other.suffix
132
159
  end
133
160
  alias_method :eql?, :==
134
161
 
135
162
  # @see Object#hash
136
163
  # @return [Integer]
137
164
  def hash
138
- to_s.hash
139
- end
140
-
141
- private
142
-
143
- def build(precision:, deg:, suffix: nil)
144
- self.class.new(precision.to_s.to_sym).tap do |a|
145
- a.deg = deg
146
- a.suffix = suffix
147
- end
165
+ [deg, suffix].join.hash
148
166
  end
149
167
  end
150
168