aixm 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +43 -13
  4. data/README.md +31 -20
  5. data/lib/aixm/a.rb +89 -71
  6. data/lib/aixm/association.rb +37 -27
  7. data/lib/aixm/classes.rb +5 -2
  8. data/lib/aixm/{feature → component}/address.rb +12 -9
  9. data/lib/aixm/component/approach_lighting.rb +136 -0
  10. data/lib/aixm/component/fato.rb +58 -42
  11. data/lib/aixm/component/frequency.rb +2 -2
  12. data/lib/aixm/component/geometry/arc.rb +1 -1
  13. data/lib/aixm/component/geometry/border.rb +1 -1
  14. data/lib/aixm/component/geometry/circle.rb +3 -3
  15. data/lib/aixm/component/geometry/point.rb +1 -1
  16. data/lib/aixm/component/geometry/rhumb_line.rb +1 -1
  17. data/lib/aixm/component/geometry.rb +3 -2
  18. data/lib/aixm/component/helipad.rb +26 -36
  19. data/lib/aixm/component/layer.rb +5 -3
  20. data/lib/aixm/component/lighting.rb +5 -5
  21. data/lib/aixm/component/runway.rb +81 -52
  22. data/lib/aixm/component/service.rb +12 -3
  23. data/lib/aixm/component/surface.rb +12 -12
  24. data/lib/aixm/component/timetable.rb +2 -2
  25. data/lib/aixm/component/vasis.rb +105 -0
  26. data/lib/aixm/component/vertical_limit.rb +3 -3
  27. data/lib/aixm/component.rb +10 -0
  28. data/lib/aixm/config.rb +2 -0
  29. data/lib/aixm/d.rb +16 -15
  30. data/lib/aixm/document.rb +10 -1
  31. data/lib/aixm/f.rb +1 -1
  32. data/lib/aixm/feature/airport.rb +34 -10
  33. data/lib/aixm/feature/airspace.rb +3 -0
  34. data/lib/aixm/feature/navigational_aid/dme.rb +29 -10
  35. data/lib/aixm/feature/navigational_aid/marker.rb +2 -2
  36. data/lib/aixm/feature/navigational_aid/tacan.rb +3 -2
  37. data/lib/aixm/feature/navigational_aid/vor.rb +32 -13
  38. data/lib/aixm/feature/navigational_aid.rb +1 -1
  39. data/lib/aixm/feature/obstacle.rb +6 -6
  40. data/lib/aixm/feature/obstacle_group.rb +6 -2
  41. data/lib/aixm/feature/organisation.rb +1 -0
  42. data/lib/aixm/feature/unit.rb +2 -1
  43. data/lib/aixm/feature.rb +3 -0
  44. data/lib/aixm/memoize.rb +27 -11
  45. data/lib/aixm/p.rb +3 -2
  46. data/lib/aixm/payload_hash.rb +1 -1
  47. data/lib/aixm/r.rb +62 -0
  48. data/lib/aixm/refinements.rb +4 -4
  49. data/lib/aixm/version.rb +1 -1
  50. data/lib/aixm/w.rb +2 -1
  51. data/lib/aixm/xy.rb +1 -1
  52. data/lib/aixm/z.rb +5 -4
  53. data/lib/aixm.rb +10 -5
  54. data/schemas/ofmx/0.1/OFMX-DataTypes.xsd +6 -0
  55. data/schemas/ofmx/0.1/OFMX-Snapshot.xsd +5 -0
  56. data.tar.gz.sig +0 -0
  57. metadata +8 -4
  58. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f4efdae60e160732c8f30262673f01b49d6dd53d5c5e9bc2d8c395cf696ccae
4
- data.tar.gz: 7b129a858e6c6d0eb7545d4a32910243e7a8ecd00a8895698f4ae69fcaed33f2
3
+ metadata.gz: 33661b6ab9022c54a9d41dad531b0f1c53f39eaf053a6c4b8361c50543b9ae01
4
+ data.tar.gz: add22919257222a6f06dae9de21f0f71c2b941a67e6bc52dba14bde5fbae6c9a
5
5
  SHA512:
6
- metadata.gz: 98115e7b0f93f82e976235045afdff88b3b6a99b256620de733b4b4741ffa7fb769e6bb92aae86657d6ae57b5744aef5df82a634b22adb5ae81b9db18a7c1531
7
- data.tar.gz: fd3bf40b77d4cfef51465b4e1806382465a4eee86abd8c74c3639763a627653a7832696395bab9673dd6433458bef8474871b7dcd121533891df3c911a429baf
6
+ metadata.gz: c1fb317e1791098f127c769e5f680bbf0c30ab823c73c48f392d6ea918f1ba4c6396d4e5cb43357ef1c867bad73e475d25be95b274bad7fd9ee17ce297dbbb6f
7
+ data.tar.gz: 904a2fa5c84b80bf14a67536fa2c0f2ab26b6d34dc3dc789e272116812994f86b9c3aff92b08d1cda9b3d3495abfe990eb922f0579fd779a1602963ba28ded59
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,10 +1,40 @@
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
+
1
31
  ## 1.0.0
2
32
 
3
33
  #### Breaking Changes
4
34
  * Move `Ase->txtLocalType` up into `AseUid` for OFMX
5
35
 
6
36
  #### Additions
7
- * Add `AIXM::Component::Geometry::RhumbLine`
37
+ * Add rhumb line geometry
8
38
 
9
39
  ## 0.3.11
10
40
 
@@ -19,21 +49,21 @@
19
49
  #### Additions
20
50
  * Add `f#voice?` and `AIXM.config.voice_channel_separation` to check whether a
21
51
  frequency belongs to the voice communication airband and use it to validate
22
- `AIXM::Frequency`
52
+ `Frequency`
23
53
 
24
54
  ## 0.3.10
25
55
 
26
56
  #### Additions
27
57
  * Proper `has_many` and `has_one` associations
28
- * `AIXM::Association:Array#find_by|find|duplicate` on `has_many` associations
58
+ * `AIXM::Association:Array#find_by|find|duplicates` on `has_many` associations
29
59
  * `AIXM.config.mid` now defines whether `mid` attributes are inserted or not
30
60
  provided the selected schema is OFMX
31
61
  * `AIXM::Memoize` module
32
62
  * `AIXM::PayloadHash` class
33
63
  * `mkmid` executable to insert `mid` attributes into valid OFMX file
34
64
  * `ckmid` executable to check `mid` attributes in an OFMX file
35
- * `AIXM::Component::Geometry#point?|circle?|polygon?`
36
- * `AIXM::Component::Layer#services`
65
+ * Geometries respond to `#point?`, `#circle?` and `#polygon?`
66
+ * `Layer#services`
37
67
 
38
68
  #### Breaking Changes
39
69
  * Require Ruby 2.7
@@ -63,20 +93,20 @@
63
93
  ## 0.3.7
64
94
 
65
95
  #### Additions
66
- * `AIXM::Document#select_features`
67
- * `AIXM::Document#group_obstacles!`
96
+ * `Document#select_features`
97
+ * `Document#group_obstacles!`
68
98
 
69
99
  ## 0.3.6
70
100
 
71
101
  #### Additions
72
- * `AIXM::Component::FATO`
73
- * `AIXM::Component::Helipad#helicopter_class` and `AIXM::Component::Helipad#marking`
102
+ * `FATO`
103
+ * `Helipad#helicopter_class` and `Helipad#marking`
74
104
  * `AIXM::XY#seconds?` to detect possibly rounded or estimated coordinates
75
- * `AIXM::Features::Airport#operator`
105
+ * `Airport#operator`
76
106
  * `AIXM::W` (weight)
77
107
  * `AIXM::P` (pressure)
78
- * `AIXM::Component::Lighting` for use with runways, helipads and FATOs
79
- * 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`
80
110
 
81
111
  #### Changes
82
112
  * Generate `Airport#id` from region and `Airport#name`
@@ -242,7 +272,7 @@
242
272
 
243
273
  #### Changes
244
274
  * `Document#created_at` and `#effective_at` accept Time, Date, String or *nil*
245
- * Separate `AIXM::Document#valid?` from `#complete?`
275
+ * Separate `Document#valid?` from `#complete?`
246
276
  * Write coordinates in DD if extension `:OFM` is set
247
277
  * `Array#to_digest` returns Integer which fits in signed 32bit
248
278
 
data/README.md CHANGED
@@ -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(
@@ -139,17 +139,16 @@ AIXM.config.ignored_errors = /invalid date/i
139
139
 
140
140
  ### Fundamentals
141
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)
142
147
  * [XY (longitude and latitude)](https://www.rubydoc.info/gems/aixm/AIXM/XY.html)
143
148
  * [Z (height, elevation or altitude)](https://www.rubydoc.info/gems/aixm/AIXM/Z.html)
144
- * [D (distance or length)](https://www.rubydoc.info/gems/aixm/AIXM/D.html)
145
- * [F (frequency)](https://www.rubydoc.info/gems/aixm/AIXM/F.html)
146
- * [A (angle)](https://www.rubydoc.info/gems/aixm/AIXM/A.html)
147
149
 
148
150
  ### Features
149
151
  * [Address](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Address.html)
150
- * [Organisation](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Organisation.html)
151
- * [Unit](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Unit.html)
152
- * [Service](https://www.rubydoc.info/gems/aixm/AIXM/Component/Service.html)
153
152
  * [Airport](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Airport.html)
154
153
  * [Airspace](https://www.rubydoc.info/gems/aixm/AIXM/Feature/Airspace.html)
155
154
  * [Navigational aid](https://www.rubydoc.info/gems/aixm/AIXM/NavigationalAid.html)
@@ -159,22 +158,32 @@ AIXM.config.ignored_errors = /invalid date/i
159
158
  * [NDB](https://www.rubydoc.info/gems/aixm/AIXM/Feature/NDB.html)
160
159
  * [TACAN](https://www.rubydoc.info/gems/aixm/AIXM/Feature/TACAN.html)
161
160
  * [VOR](https://www.rubydoc.info/gems/aixm/AIXM/Feature/VOR.html)
162
- * [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)
163
166
 
164
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)
165
171
  * [Frequency](https://www.rubydoc.info/gems/aixm/AIXM/Component/Frequency.html)
166
172
  * [Geometry](https://www.rubydoc.info/gems/aixm/AIXM/Component/Geometry.html)
167
- * [Point](https://www.rubydoc.info/gems/aixm/AIXM/Component/Point.html)
168
173
  * [Arc](https://www.rubydoc.info/gems/aixm/AIXM/Component/Arc.html)
169
174
  * [Border](https://www.rubydoc.info/gems/aixm/AIXM/Component/Border.html)
170
175
  * [Circle](https://www.rubydoc.info/gems/aixm/AIXM/Component/Circle.html)
171
- * [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)
172
178
  * [Helipad](https://www.rubydoc.info/gems/aixm/AIXM/Component/Helipad.html)
173
- * [FATO](https://www.rubydoc.info/gems/aixm/AIXM/Component/FATO.html)
174
- * [Surface](https://www.rubydoc.info/gems/aixm/AIXM/Component/Surface.html)
175
179
  * [Layer](https://www.rubydoc.info/gems/aixm/AIXM/Component/Layer.html)
176
- * [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)
177
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)
178
187
 
179
188
  ## Associations
180
189
 
@@ -204,6 +213,14 @@ document.features.find(airport) # => [#<AIXM::Feature::Airport>]
204
213
 
205
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!
206
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
+
207
224
  ### duplicates
208
225
 
209
226
  Equally on `has_many` associations, use `duplicates` to find identical or equal associations:
@@ -282,13 +299,7 @@ bundle exec rake # run tests once
282
299
  bundle exec guard # run tests whenever files are modified
283
300
  ```
284
301
 
285
- Please submit issues on:
286
-
287
- https://github.com/svoop/aixm/issues
288
-
289
- To contribute code, fork the project on Github, add your code and submit a pull request:
290
-
291
- 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).
292
303
 
293
304
  ## License
294
305
 
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
 
@@ -23,18 +23,18 @@ module AIXM
23
23
  # end
24
24
  # blog, post = Blog.new, Post.new
25
25
  # # --either--
26
- # blog.add_post(post)
26
+ # blog.add_post(post) # => Blog
27
27
  # blog.posts.count # => 1
28
28
  # blog.posts.first == post # => true
29
29
  # post.blog == blog # => true
30
- # blog.remove_post(post)
30
+ # blog.remove_post(post) # => Blog
31
31
  # blog.posts.count # => 0
32
32
  # # --or--
33
- # post.blog = blog
33
+ # post.blog = blog # => Blog
34
34
  # blog.posts.count # => 1
35
35
  # blog.posts.first == post # => true
36
36
  # post.blog == blog # => true
37
- # post.blog = nil
37
+ # post.blog = nil # => nil
38
38
  # blog.posts.count # => 0
39
39
  # # --or--
40
40
  # post_2 = Post.new
@@ -53,19 +53,21 @@ module AIXM
53
53
  # end
54
54
  # blog, post = Blog.new, Post.new
55
55
  # # --either--
56
- # blog.post = post
57
- # blog.post == post # => true
58
- # post.blog == blog # => true
59
- # blog.post = nil
60
- # blog.post # => nil
61
- # post.blog # => nil
56
+ # blog.post = post # => Post (standard assignment)
57
+ # blog.add_post(post) # => Blog (alternative for chaining)
58
+ # blog.post == post # => true
59
+ # post.blog == blog # => true
60
+ # blog.post = nil # => nil
61
+ # blog.post # => nil
62
+ # post.blog # => nil
62
63
  # # --or--
63
- # post.blog = blog
64
- # post.blog == blog # => true
65
- # blog.post == post # => true
66
- # post.blog = nil
67
- # post.blog # => nil
68
- # blog.post # => nil
64
+ # post.blog = blog # => Blog (standard assignment)
65
+ # post.add_blog(blog) # => Post (alternative for chaining)
66
+ # post.blog == blog # => true
67
+ # blog.post == post # => true
68
+ # post.blog = nil # => nil
69
+ # post.blog # => nil
70
+ # blog.post # => nil
69
71
  #
70
72
  # @example Association with readonly +belongs_to+ (idem for +has_one+)
71
73
  # class Blog
@@ -218,14 +220,19 @@ module AIXM
218
220
  (@has_one_attributes ||= []) << attribute
219
221
  # feature
220
222
  attr_reader attribute
221
- # feature= / add_feature
223
+ # feature=
222
224
  define_method(:"#{association}=") do |object|
223
225
  fail(ArgumentError, "#{object.__class__} not allowed") unless class_names.any? { |c| object.is_a?(c.to_class) }
224
226
  instance_variable_get(:"@#{attribute}")&.instance_variable_set(:"@#{inversion}", nil)
225
227
  instance_variable_set(:"@#{attribute}", object)
226
228
  object&.instance_variable_set(:"@#{inversion}", self)
229
+ object
230
+ end
231
+ # add_feature
232
+ define_method(:"add_#{association}") do |object|
233
+ send("#{association}=", object)
234
+ self
227
235
  end
228
- alias_method(:"add_#{association}", :"#{association}=")
229
236
  # remove_feature
230
237
  define_method(:"remove_#{association}") do |_|
231
238
  send(:"#{association}=", nil)
@@ -239,11 +246,17 @@ module AIXM
239
246
  (@belongs_to_attributes ||= []) << attribute
240
247
  # feature
241
248
  attr_reader attribute
242
- # feature=
243
249
  unless readonly
250
+ # feature=
244
251
  define_method(:"#{attribute}=") do |object|
245
252
  instance_variable_get(:"@#{attribute}")&.send(:"remove_#{inversion}", self)
246
253
  object&.send(:"add_#{inversion}", self)
254
+ object
255
+ end
256
+ # add_feature
257
+ define_method(:"add_#{attribute}") do |object|
258
+ send("#{attribute}=", object)
259
+ self
247
260
  end
248
261
  end
249
262
  end
@@ -349,17 +362,14 @@ module AIXM
349
362
  # belongs_to :blog
350
363
  # end
351
364
  # blog, post = Blog.new, Post.new
352
- # blog.add_posts([post, post])
353
- # blog.posts.duplicates # => [post]
365
+ # duplicate_post = post.dup
366
+ # blog.add_posts([post, duplicate_post])
367
+ # blog.posts.duplicates # => [[post, duplicate_post]]
354
368
  #
355
- # @return [AIXM::Association::Array]
369
+ # @return [Array<Array<AIXM::Feature>>]
356
370
  def duplicates
357
371
  AIXM::Memoize.method :to_uid do
358
- self.class.new(
359
- select.with_index do |element, index|
360
- index != self.index(element)
361
- end
362
- )
372
+ group_by(&:to_uid).select { |_, a| a.count > 1 }.map(&:last)
363
373
  end
364
374
  end
365
375
  end
data/lib/aixm/classes.rb CHANGED
@@ -6,11 +6,13 @@ module AIXM
6
6
  xy: 'AIXM::XY',
7
7
  z: 'AIXM::Z',
8
8
  d: 'AIXM::D',
9
+ r: 'AIXM::R',
9
10
  f: 'AIXM::F',
10
11
  a: 'AIXM::A',
11
12
  w: 'AIXM::W',
12
13
  p: 'AIXM::P',
13
- address: 'AIXM::Feature::Address',
14
+ address: 'AIXM::Component::Address',
15
+ approach_lighting: 'AIXM::Component::ApproachLighting',
14
16
  organisation: 'AIXM::Feature::Organisation',
15
17
  unit: 'AIXM::Feature::Unit',
16
18
  service: 'AIXM::Component::Service',
@@ -38,7 +40,8 @@ module AIXM
38
40
  vor: 'AIXM::Feature::NavigationalAid::VOR',
39
41
  obstacle: 'AIXM::Feature::Obstacle',
40
42
  obstacle_group: 'AIXM::Feature::ObstacleGroup',
41
- timetable: 'AIXM::Component::Timetable'
43
+ timetable: 'AIXM::Component::Timetable',
44
+ vasis: 'AIXM::Component::VASIS'
42
45
  }.freeze
43
46
 
44
47
  end
@@ -1,20 +1,19 @@
1
1
  using AIXM::Refinements
2
2
 
3
3
  module AIXM
4
- class Feature
4
+ class Component
5
5
 
6
6
  # Address or similar means to contact an entity.
7
7
  #
8
8
  # ===Cheat Sheet in Pseudo Code:
9
9
  # address = AIXM.address(
10
- # source: String or nil
11
10
  # type: TYPES
12
11
  # address: AIXM.f (type :radio_frequency) or String (other types)
13
12
  # )
14
- # service.remarks = String or nil
13
+ # address.remarks = String or nil
15
14
  #
16
15
  # @see https://gitlab.com/openflightmaps/ofmx/wikis/Airport#aha-airport-address
17
- class Address < Feature
16
+ class Address < Component
18
17
  include AIXM::Association
19
18
  include AIXM::Memoize
20
19
 
@@ -34,7 +33,7 @@ module AIXM
34
33
  'URL-MET': :weather_url,
35
34
  RADIO: :radio_frequency,
36
35
  OTHER: :other # specify in remarks
37
- }
36
+ }.freeze
38
37
 
39
38
  # @!method addressable
40
39
  # @return [AIXM::Feature] addressable feature
@@ -49,8 +48,7 @@ module AIXM
49
48
  # @return [String, nil] free text remarks
50
49
  attr_reader :remarks
51
50
 
52
- def initialize(source: nil, region: nil, type:, address:)
53
- super(source: source, region: region)
51
+ def initialize(type:, address:)
54
52
  self.type, self.address = type, address
55
53
  end
56
54
 
@@ -93,9 +91,14 @@ module AIXM
93
91
  def to_xml(as:, sequence:)
94
92
  builder = Builder::XmlMarkup.new(indent: 2)
95
93
  builder.comment! ["Address: #{TYPES.key(type)}", addressable&.id].compact.join(' for ')
96
- builder.tag!(as, { source: (source if AIXM.ofmx?) }.compact) do |tag|
94
+ builder.tag!(as) do |tag|
97
95
  tag << to_uid(as: :"#{as}Uid", sequence: sequence).indent(2)
98
- tag.txtAddress(address)
96
+ case type
97
+ when :radio_frequency
98
+ tag.txtAddress(address.freq.to_s)
99
+ else
100
+ tag.txtAddress(address)
101
+ end
99
102
  tag.txtRmk(remarks) if remarks
100
103
  end
101
104
  end