aixm 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +33 -3
  5. data/README.md +166 -56
  6. data/exe/ckmid +14 -0
  7. data/exe/mkmid +14 -0
  8. data/lib/aixm.rb +16 -6
  9. data/lib/aixm/association.rb +369 -0
  10. data/lib/aixm/classes.rb +43 -0
  11. data/lib/aixm/component/fato.rb +45 -53
  12. data/lib/aixm/component/frequency.rb +11 -12
  13. data/lib/aixm/component/geometry.rb +36 -38
  14. data/lib/aixm/component/geometry/arc.rb +2 -2
  15. data/lib/aixm/component/geometry/border.rb +6 -3
  16. data/lib/aixm/component/geometry/circle.rb +8 -2
  17. data/lib/aixm/component/geometry/point.rb +8 -2
  18. data/lib/aixm/component/helipad.rb +30 -38
  19. data/lib/aixm/component/layer.rb +28 -19
  20. data/lib/aixm/component/lighting.rb +12 -13
  21. data/lib/aixm/component/runway.rb +44 -48
  22. data/lib/aixm/{feature → component}/service.rb +37 -36
  23. data/lib/aixm/component/surface.rb +3 -3
  24. data/lib/aixm/component/timetable.rb +2 -2
  25. data/lib/aixm/component/{vertical_limits.rb → vertical_limit.rb} +12 -6
  26. data/lib/aixm/config.rb +2 -1
  27. data/lib/aixm/document.rb +27 -50
  28. data/lib/aixm/executables.rb +85 -0
  29. data/lib/aixm/feature.rb +13 -3
  30. data/lib/aixm/feature/address.rb +12 -13
  31. data/lib/aixm/feature/airport.rb +103 -128
  32. data/lib/aixm/feature/airspace.rb +44 -17
  33. data/lib/aixm/feature/navigational_aid.rb +7 -9
  34. data/lib/aixm/feature/navigational_aid/designated_point.rb +13 -15
  35. data/lib/aixm/feature/navigational_aid/dme.rb +11 -12
  36. data/lib/aixm/feature/navigational_aid/marker.rb +7 -3
  37. data/lib/aixm/feature/navigational_aid/ndb.rb +7 -3
  38. data/lib/aixm/feature/navigational_aid/tacan.rb +7 -3
  39. data/lib/aixm/feature/navigational_aid/vor.rb +23 -15
  40. data/lib/aixm/feature/obstacle.rb +29 -43
  41. data/lib/aixm/feature/obstacle_group.rb +37 -34
  42. data/lib/aixm/feature/organisation.rb +21 -5
  43. data/lib/aixm/feature/unit.rb +36 -46
  44. data/lib/aixm/memoize.rb +89 -0
  45. data/lib/aixm/object.rb +9 -0
  46. data/lib/aixm/payload_hash.rb +114 -0
  47. data/lib/aixm/refinements.rb +29 -76
  48. data/lib/aixm/shortcuts.rb +5 -42
  49. data/lib/aixm/version.rb +1 -1
  50. data/lib/aixm/xy.rb +1 -1
  51. data/schemas/ofmx/0/OFMX-Features.xsd +152 -20
  52. data/schemas/ofmx/0/OFMX-Snapshot.xsd +0 -5
  53. metadata +107 -156
  54. metadata.gz.sig +2 -0
  55. data/.github/workflows/test.yml +0 -26
  56. data/.gitignore +0 -6
  57. data/.ruby-version +0 -1
  58. data/.yardopts +0 -3
  59. data/Guardfile +0 -8
  60. data/aixm.gemspec +0 -35
  61. data/gems.rb +0 -3
  62. data/lib/aixm/component.rb +0 -6
  63. data/rakefile.rb +0 -36
  64. data/spec/factory.rb +0 -559
  65. data/spec/lib/aixm/a_spec.rb +0 -203
  66. data/spec/lib/aixm/component/fato_spec.rb +0 -267
  67. data/spec/lib/aixm/component/frequency_spec.rb +0 -74
  68. data/spec/lib/aixm/component/geometry/arc_spec.rb +0 -73
  69. data/spec/lib/aixm/component/geometry/border_spec.rb +0 -38
  70. data/spec/lib/aixm/component/geometry/circle_spec.rb +0 -68
  71. data/spec/lib/aixm/component/geometry/point_spec.rb +0 -37
  72. data/spec/lib/aixm/component/geometry_spec.rb +0 -316
  73. data/spec/lib/aixm/component/helipad_spec.rb +0 -193
  74. data/spec/lib/aixm/component/layer_spec.rb +0 -135
  75. data/spec/lib/aixm/component/lighting_spec.rb +0 -94
  76. data/spec/lib/aixm/component/runway_spec.rb +0 -479
  77. data/spec/lib/aixm/component/surface_spec.rb +0 -124
  78. data/spec/lib/aixm/component/timetable_spec.rb +0 -47
  79. data/spec/lib/aixm/component/vertical_limits_spec.rb +0 -94
  80. data/spec/lib/aixm/config_spec.rb +0 -41
  81. data/spec/lib/aixm/d_spec.rb +0 -150
  82. data/spec/lib/aixm/document_spec.rb +0 -1884
  83. data/spec/lib/aixm/errors_spec.rb +0 -14
  84. data/spec/lib/aixm/f_spec.rb +0 -85
  85. data/spec/lib/aixm/feature/address_spec.rb +0 -60
  86. data/spec/lib/aixm/feature/airport_spec.rb +0 -776
  87. data/spec/lib/aixm/feature/airspace_spec.rb +0 -394
  88. data/spec/lib/aixm/feature/navigational_aid/designated_point_spec.rb +0 -103
  89. data/spec/lib/aixm/feature/navigational_aid/dme_spec.rb +0 -98
  90. data/spec/lib/aixm/feature/navigational_aid/marker_spec.rb +0 -85
  91. data/spec/lib/aixm/feature/navigational_aid/ndb_spec.rb +0 -95
  92. data/spec/lib/aixm/feature/navigational_aid/tacan_spec.rb +0 -94
  93. data/spec/lib/aixm/feature/navigational_aid/vor_spec.rb +0 -251
  94. data/spec/lib/aixm/feature/navigational_aid_spec.rb +0 -52
  95. data/spec/lib/aixm/feature/obstacle_group_spec.rb +0 -330
  96. data/spec/lib/aixm/feature/obstacle_spec.rb +0 -284
  97. data/spec/lib/aixm/feature/organisation_spec.rb +0 -83
  98. data/spec/lib/aixm/feature/service_spec.rb +0 -59
  99. data/spec/lib/aixm/feature/unit_spec.rb +0 -238
  100. data/spec/lib/aixm/feature_spec.rb +0 -38
  101. data/spec/lib/aixm/p_spec.rb +0 -189
  102. data/spec/lib/aixm/refinements_spec.rb +0 -430
  103. data/spec/lib/aixm/version_spec.rb +0 -7
  104. data/spec/lib/aixm/w_spec.rb +0 -150
  105. data/spec/lib/aixm/xy_spec.rb +0 -180
  106. data/spec/lib/aixm/z_spec.rb +0 -94
  107. data/spec/macros/marking.rb +0 -12
  108. data/spec/macros/organisation.rb +0 -11
  109. data/spec/macros/remarks.rb +0 -12
  110. data/spec/macros/timetable.rb +0 -11
  111. data/spec/macros/xy.rb +0 -11
  112. data/spec/macros/z_qnh.rb +0 -11
  113. data/spec/sounds/failure.mp3 +0 -0
  114. data/spec/sounds/success.mp3 +0 -0
  115. data/spec/spec_helper.rb +0 -62
@@ -0,0 +1,369 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+
5
+ # Associate features and components with a minimalistic implementation of
6
+ # +has_many+, +has_one+ and +belongs_to+ associations.
7
+ #
8
+ # When adding or assigning an object on the associator (where the +has_many+
9
+ # or +has_one+ declaration is made), the object is verified and must be an
10
+ # instance of the declared class or a superclass thereof.
11
+ #
12
+ # When assigning an object on the associated (where the +belongs_to+
13
+ # declaration is made), the object is not verified. However, since the actual
14
+ # assignment is always delegated to the associator, unacceptable objects will
15
+ # raise errors.
16
+ #
17
+ # @example Simple +has_many+ association
18
+ # class Blog
19
+ # has_many :posts # :post has to be a key in AIXM::CLASSES
20
+ # end
21
+ # class Post
22
+ # belongs_to :blog
23
+ # end
24
+ # blog, post = Blog.new, Post.new
25
+ # # --either--
26
+ # blog.add_post(post)
27
+ # blog.posts.count # => 1
28
+ # blog.posts.first == post # => true
29
+ # post.blog == blog # => true
30
+ # blog.remove_post(post)
31
+ # blog.posts.count # => 0
32
+ # # --or--
33
+ # post.blog = blog
34
+ # blog.posts.count # => 1
35
+ # blog.posts.first == post # => true
36
+ # post.blog == blog # => true
37
+ # post.blog = nil
38
+ # blog.posts.count # => 0
39
+ # # --or--
40
+ # post_2 = Post.new
41
+ # blog.add_posts([post, post_2])
42
+ # blog.posts.count # => 2
43
+ # blog.posts == [post, post_2] # => true
44
+ # blog.remove_posts([post_2, post])
45
+ # blog.posts.count # => 0
46
+ #
47
+ # @example Simple +has_one+ association
48
+ # class Blog
49
+ # has_one :posts # :post has to be a key in AIXM::CLASSES
50
+ # end
51
+ # class Post
52
+ # belongs_to :blog
53
+ # end
54
+ # blog, post = Blog.new, Post.new
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
62
+ # # --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
69
+ #
70
+ # @example Association with readonly +belongs_to+ (idem for +has_one+)
71
+ # class Blog
72
+ # has_many :posts # :post has to be a key in AIXM::CLASSES
73
+ # end
74
+ # class Post
75
+ # belongs_to :blog, readonly: true
76
+ # end
77
+ # blog, post = Blog.new, Post.new
78
+ # post.blog = blog # => NoMethodError
79
+ #
80
+ # @example Association with explicit class (idem for +has_one+)
81
+ # class Blog
82
+ # include AIXM::Association
83
+ # has_many :posts, accept: 'Picture'
84
+ # end
85
+ # class Picture
86
+ # include AIXM::Association
87
+ # belongs_to :blog
88
+ # end
89
+ # blog, picture = Blog.new, Picture.new
90
+ # blog.add_post(picture)
91
+ # blog.posts.first == picture # => true
92
+ #
93
+ # @example Polymorphic associator (idem for +has_one+)
94
+ # class Blog
95
+ # has_many :posts, as: :postable
96
+ # end
97
+ # class Feed
98
+ # has_many :posts, as: :postable
99
+ # end
100
+ # class Post
101
+ # belongs_to :postable
102
+ # end
103
+ # blog, feed, post_1, post_2, post_3 = Blog.new, Feed.new, Post.new, Post.new, Post.new
104
+ # blog.add_post(post_1)
105
+ # post_1.postable == blog # => true
106
+ # feed.add_post(post_2)
107
+ # post_2.postable == feed # => true
108
+ # post_3.postable = blog # => NoMethodError
109
+ #
110
+ # @example Polymorphic associated (idem for +has_one+)
111
+ # class Blog
112
+ # include AIXM::Association
113
+ # has_many :items, accept: ['Post', :picture]
114
+ # end
115
+ # class Post
116
+ # include AIXM::Association
117
+ # belongs_to :blog, as: :item
118
+ # end
119
+ # class Picture
120
+ # include AIXM::Association
121
+ # belongs_to :blog, as: :item
122
+ # end
123
+ # blog, post, picture = Blog.new, Post.new, Picture.new
124
+ # blog.add_item(post)
125
+ # blog.add_item(picture)
126
+ # blog.items.count # => 2
127
+ # blog.items.first == post # => true
128
+ # blog.items.last == picture # => true
129
+ # post.blog == blog # => true
130
+ # picture.blog == blog # => true
131
+ #
132
+ # @example Add method which enriches passed associated object (+has_many+ only)
133
+ # class Blog
134
+ # has_many :posts do |post, related_to: nil| # this defines the signature of add_post
135
+ # post.related_to = related_to || @posts.last # executes in the context of the current blog
136
+ # end
137
+ # end
138
+ # class Post
139
+ # belongs_to :blog
140
+ # attr_accessor :related_to
141
+ # end
142
+ # blog, post_1, post_2, post_3 = Blog.new, Post.new, Post.new, Post.new
143
+ # blog.add_post(post_1)
144
+ # post_1.related_to # => nil
145
+ # blog.add_post(post_2)
146
+ # post_2.related_to == post_1 # => true
147
+ # blog.add_post(post_3, related_to: post_1)
148
+ # post_3.related_to == post_1 # => true
149
+ #
150
+ # @example Add method which builds and yields new associated object (+has_many+ only)
151
+ # class Blog
152
+ # include AIXM::Association
153
+ # has_many :posts do |post, title:| end
154
+ # end
155
+ # class Post
156
+ # include AIXM::Association
157
+ # belongs_to :blog
158
+ # attr_accessor :title, :text
159
+ # def initialize(title:) # same signature as "has_many" block above
160
+ # @title = title
161
+ # end
162
+ # end
163
+ # blog = Blog.new
164
+ # blog.add_post(title: "title") do |post| # note that no post instance is passed
165
+ # post.text = "text"
166
+ # end
167
+ # blog.posts.first.title # => "title"
168
+ # blog.posts.first.text # => "text"
169
+ module Association
170
+ module ClassMethods
171
+ attr_reader :has_many_attributes, :has_one_attributes, :belongs_to_attributes
172
+
173
+ def has_many(attribute, as: nil, accept: nil, &association_block)
174
+ association = attribute.to_s.inflect(:singularize)
175
+ inversion = as || self.to_s.inflect(:demodulize, :tableize, :singularize)
176
+ class_names = [accept || association].flatten.map { AIXM::CLASSES[_1.to_sym] || _1 }
177
+ (@has_many_attributes ||= []) << attribute
178
+ # features
179
+ define_method(attribute) do
180
+ instance_eval("@#{attribute} ||= AIXM::Association::Array.new")
181
+ end
182
+ # add_feature
183
+ define_method(:"add_#{association}") do |object=nil, **options, &add_block|
184
+ unless object
185
+ fail(ArgumentError, "must pass object to add") if class_names.count > 1
186
+ object = class_names.first.to_class.new(**options)
187
+ add_block.call(object) if add_block
188
+ end
189
+ instance_exec(object, **options, &association_block) if association_block
190
+ fail(ArgumentError, "#{object.__class__} not allowed") unless class_names.reduce(false){ |m, c| m || object.is_a?(c.to_class) }
191
+ send(attribute).send(:push, object)
192
+ object.instance_variable_set(:"@#{inversion}", self)
193
+ self
194
+ end
195
+ # add_features
196
+ define_method(:"add_#{attribute}") do |objects=[], **options, &add_block|
197
+ objects.each { send(:"add_#{association}", _1, **options, &add_block) }
198
+ self
199
+ end
200
+ # remove_feature
201
+ define_method(:"remove_#{association}") do |object|
202
+ send(attribute).send(:delete, object)
203
+ object.instance_variable_set(:"@#{inversion}", nil)
204
+ self
205
+ end
206
+ # remove_features
207
+ define_method(:"remove_#{attribute}") do |objects=[]|
208
+ objects.each { send(:"remove_#{association}", _1) }
209
+ self
210
+ end
211
+ end
212
+
213
+ def has_one(attribute, as: nil, accept: nil, allow_nil: false)
214
+ association = attribute.to_s
215
+ inversion = (as || self.to_s.inflect(:demodulize, :tableize, :singularize)).to_s
216
+ class_names = [accept || association].flatten.map { AIXM::CLASSES[_1.to_sym] || _1 }
217
+ class_names << 'NilClass' if allow_nil
218
+ (@has_one_attributes ||= []) << attribute
219
+ # feature
220
+ attr_reader attribute
221
+ # feature= / add_feature
222
+ define_method(:"#{association}=") do |object|
223
+ fail(ArgumentError, "#{object.__class__} not allowed") unless class_names.reduce(false){ |m, c| m || object.is_a?(c.to_class) }
224
+ instance_variable_get(:"@#{attribute}")&.instance_variable_set(:"@#{inversion}", nil)
225
+ instance_variable_set(:"@#{attribute}", object)
226
+ object&.instance_variable_set(:"@#{inversion}", self)
227
+ end
228
+ alias_method(:"add_#{association}", :"#{association}=")
229
+ # remove_feature
230
+ define_method(:"remove_#{association}") do |_|
231
+ send(:"#{association}=", nil)
232
+ self
233
+ end
234
+ end
235
+
236
+ def belongs_to(attribute, as: nil, readonly: false)
237
+ association = self.to_s.inflect(:demodulize, :tableize, :singularize)
238
+ inversion = (as || association).to_s
239
+ (@belongs_to_attributes ||= []) << attribute
240
+ # feature
241
+ attr_reader attribute
242
+ # feature=
243
+ unless readonly
244
+ define_method(:"#{attribute}=") do |object|
245
+ instance_variable_get(:"@#{attribute}")&.send(:"remove_#{inversion}", self)
246
+ object&.send(:"add_#{inversion}", self)
247
+ end
248
+ end
249
+ end
250
+ end
251
+
252
+ def self.included(base)
253
+ base.extend(ClassMethods)
254
+ end
255
+
256
+ class Array < ::Array
257
+ private :<<, :push, :append, :unshift, :prepend
258
+ private :delete, :pop, :shift
259
+
260
+ # Find objects of the given class and optionally with the given
261
+ # attribute values on a has_many association.
262
+ #
263
+ # The class can either be declared by passing the class itself or by
264
+ # passing a shortcut symbol as listed in +AIXM::CLASSES+.
265
+ #
266
+ # @example
267
+ # class Blog
268
+ # include AIXM::Association
269
+ # has_many :items, accept: %i(post picture)
270
+ # end
271
+ # class Post
272
+ # include AIXM::Association
273
+ # belongs_to :blog, as: :item
274
+ # attr_accessor :title
275
+ # end
276
+ # class Picture
277
+ # include AIXM::Association
278
+ # belongs_to :blog, as: :item
279
+ # end
280
+ # blog, post, picture = Blog.new, Post.new, Picture.new
281
+ # post.title = "title"
282
+ # blog.add_item(post)
283
+ # blog.add_item(picture)
284
+ # blog.items.find_by(:post) == [post] # => true
285
+ # blog.items.find_by(Post) == [post] # => true
286
+ # blog.items.find_by(:post, title: "title") == [post] # => true
287
+ # blog.items.find_by(Object) == [post, picture] # => true
288
+ #
289
+ # @param klass [Class, Symbol] class (e.g. AIXM::Feature::Airport,
290
+ # AIXM::Feature::NavigationalAid::VOR) or shortcut symbol (e.g.
291
+ # :airport or :vor) as listed in AIXM::CLASSES
292
+ # @param attributes [Hash] search attributes by their values
293
+ # @return [AIXM::Association::Array]
294
+ def find_by(klass, attributes={})
295
+ if klass.is_a? Symbol
296
+ klass = AIXM::CLASSES[klass]&.to_class || fail(ArgumentError, "unknown class shortcut `#{klass}'")
297
+ end
298
+ self.class.new(
299
+ select do |element|
300
+ if element.kind_of? klass
301
+ attributes.reduce(true) do |memo, (attribute, value)|
302
+ memo && element.send(attribute) == value
303
+ end
304
+ end
305
+ end
306
+ )
307
+ end
308
+
309
+ # Find equal objects on a has_many association.
310
+ #
311
+ # This may seem redundant at first, but keep in mind that two instances
312
+ # of +AIXM::CLASSES+ which implement `#to_uid` are considered equal if
313
+ # they are instances of the same class and both their UIDs as calculated
314
+ # by `#to_uid` are equal. Attributes which are not part of the `#to_uid`
315
+ # calculation are irrelevant!
316
+ #
317
+ # @example
318
+ # class Blog
319
+ # include AIXM::Association
320
+ # has_many :items, accept: %i(post picture)
321
+ # end
322
+ # class Post
323
+ # include AIXM::Association
324
+ # belongs_to :blog, as: :item
325
+ # attr_accessor :title
326
+ # end
327
+ # blog, post = Blog.new, Post.new
328
+ # blog.add_item(post)
329
+ # blog.items.find(post) == [post] # => true
330
+ #
331
+ # @param object [Object] instance of class listed in AIXM::CLASSES
332
+ # @return [AIXM::Association::Array]
333
+ def find(object)
334
+ klass = object.__class__
335
+ self.class.new(
336
+ select do |element|
337
+ element.kind_of?(klass) && element == object
338
+ end
339
+ )
340
+ end
341
+
342
+ # Find equal or identical duplicates on a has_many association.
343
+ #
344
+ # @example
345
+ # class Blog
346
+ # include AIXM::Association
347
+ # has_many :posts
348
+ # end
349
+ # class Post
350
+ # include AIXM::Association
351
+ # belongs_to :blog
352
+ # end
353
+ # blog, post = Blog.new, Post.new
354
+ # blog.add_posts([post, post])
355
+ # blog.posts.duplicates # => [post]
356
+ #
357
+ # @return [AIXM::Association::Array]
358
+ def duplicates
359
+ AIXM::Memoize.method :to_uid do
360
+ self.class.new(
361
+ select.with_index do |element, index|
362
+ index != self.index(element)
363
+ end
364
+ )
365
+ end
366
+ end
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,43 @@
1
+ module AIXM
2
+
3
+ # Manifest of shorthand names and their corresponding AIXM class names
4
+ CLASSES = {
5
+ document: 'AIXM::Document',
6
+ xy: 'AIXM::XY',
7
+ z: 'AIXM::Z',
8
+ d: 'AIXM::D',
9
+ f: 'AIXM::F',
10
+ a: 'AIXM::A',
11
+ w: 'AIXM::W',
12
+ p: 'AIXM::P',
13
+ address: 'AIXM::Feature::Address',
14
+ organisation: 'AIXM::Feature::Organisation',
15
+ unit: 'AIXM::Feature::Unit',
16
+ service: 'AIXM::Component::Service',
17
+ frequency: 'AIXM::Component::Frequency',
18
+ airport: 'AIXM::Feature::Airport',
19
+ runway: 'AIXM::Component::Runway',
20
+ fato: 'AIXM::Component::FATO',
21
+ helipad: 'AIXM::Component::Helipad',
22
+ surface: 'AIXM::Component::Surface',
23
+ lighting: 'AIXM::Component::Lighting',
24
+ airspace: 'AIXM::Feature::Airspace',
25
+ layer: 'AIXM::Component::Layer',
26
+ geometry: 'AIXM::Component::Geometry',
27
+ vertical_limit: 'AIXM::Component::VerticalLimit',
28
+ arc: 'AIXM::Component::Geometry::Arc',
29
+ border: 'AIXM::Component::Geometry::Border',
30
+ circle: 'AIXM::Component::Geometry::Circle',
31
+ point: 'AIXM::Component::Geometry::Point',
32
+ dme: 'AIXM::Feature::NavigationalAid::DME',
33
+ designated_point: 'AIXM::Feature::NavigationalAid::DesignatedPoint',
34
+ marker: 'AIXM::Feature::NavigationalAid::Marker',
35
+ tacan: 'AIXM::Feature::NavigationalAid::TACAN',
36
+ ndb: 'AIXM::Feature::NavigationalAid::NDB',
37
+ vor: 'AIXM::Feature::NavigationalAid::VOR',
38
+ obstacle: 'AIXM::Feature::Obstacle',
39
+ obstacle_group: 'AIXM::Feature::ObstacleGroup',
40
+ timetable: 'AIXM::Component::Timetable'
41
+ }.freeze
42
+
43
+ end
@@ -1,7 +1,7 @@
1
1
  using AIXM::Refinements
2
2
 
3
3
  module AIXM
4
- class Component
4
+ module Component
5
5
 
6
6
  # FATO (final approach and take-off area) for vertical take-off aircraft
7
7
  # such as helicopters.
@@ -24,8 +24,11 @@ module AIXM
24
24
  # direction.remarks = String or nil
25
25
  # end
26
26
  #
27
- # @see https://github.com/openflightmaps/ofmx/wiki/Airport#fto-fato
27
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Airport#fto-fato
28
28
  class FATO
29
+ include AIXM::Association
30
+ include AIXM::Memoize
31
+
29
32
  STATUSES = {
30
33
  CLSD: :closed,
31
34
  WIP: :work_in_progress, # e.g. construction work
@@ -35,8 +38,26 @@ module AIXM
35
38
  OTHER: :other # specify in remarks
36
39
  }.freeze
37
40
 
38
- # @return [AIXM::Feature::Airport] airport this FATO belongs to
39
- attr_reader :airport
41
+ # @!method surface
42
+ # @return [AIXM::Component::Surface] surface of the FATO
43
+ # @!method surface=(surface)
44
+ # @param surface [AIXM::Component::Surface]
45
+ has_one :surface
46
+
47
+ # @!method directions
48
+ # @return [Array<AIXM::Component::FATO::Direction>] maps added direction names to full FATO directions
49
+ # @!method add_direction(direction)
50
+ # @param direction [AIXM::A] name of the FATO direction (e.g. "12" or "16L")
51
+ # @return [self]
52
+ has_many :directions, accept: 'AIXM::Component::FATO::Direction' do |direction, name:| end
53
+
54
+ # @!method airport
55
+ # @return [AIXM::Feature::Airport] airport this FATO belongs to
56
+ belongs_to :airport
57
+
58
+ # @!method helipad
59
+ # @return [AIXM::Component::Helipad] helipad situated on this FATO
60
+ belongs_to :helipad
40
61
 
41
62
  # @return [String] full name (e.g. "H1")
42
63
  attr_reader :name
@@ -47,9 +68,6 @@ module AIXM
47
68
  # @return [AIXM::D, nil] width
48
69
  attr_reader :width
49
70
 
50
- # @return [AIXM::Component::Surface] surface of the FATO
51
- attr_reader :surface
52
-
53
71
  # @return [String, nil] markings
54
72
  attr_reader :marking
55
73
 
@@ -62,13 +80,9 @@ module AIXM
62
80
  # @return [String, nil] free text remarks
63
81
  attr_reader :remarks
64
82
 
65
- # @return [Hash{String => AIXM::Component::FATO::Direction}] maps added direction names to full FATO directions
66
- attr_reader :directions
67
-
68
83
  def initialize(name:)
69
84
  self.name = name
70
- @surface = AIXM.surface
71
- @directions = {}
85
+ self.surface = AIXM.surface
72
86
  end
73
87
 
74
88
  # @return [String]
@@ -76,12 +90,6 @@ module AIXM
76
90
  %Q(#<#{self.class} airport=#{airport&.id.inspect} name=#{name.inspect}>)
77
91
  end
78
92
 
79
- def airport=(value)
80
- fail(ArgumentError, "invalid airport") unless value.is_a? AIXM::Feature::Airport
81
- @airport = value
82
- end
83
- private :airport=
84
-
85
93
  def name=(value)
86
94
  fail(ArgumentError, "invalid name") unless value.is_a? String
87
95
  @name = value.uptrans
@@ -119,20 +127,15 @@ module AIXM
119
127
  @remarks = value&.to_s
120
128
  end
121
129
 
122
- def add_direction(name:)
123
- direction = Direction.new(fato: self, name: name)
124
- yield direction
125
- @directions[name] = direction
126
- end
127
-
128
130
  # @return [String] UID markup
129
131
  def to_uid
130
132
  builder = Builder::XmlMarkup.new(indent: 2)
131
133
  builder.FtoUid do |fto_uid|
132
134
  fto_uid << airport.to_uid.indent(2)
133
135
  fto_uid.txtDesig(name)
134
- end.insert_payload_hash(region: AIXM.config.mid_region)
136
+ end
135
137
  end
138
+ memoize :to_uid
136
139
 
137
140
  # @return [String] AIXM or OFMX markup
138
141
  def to_xml
@@ -151,7 +154,7 @@ module AIXM
151
154
  fto.codeSts(STATUSES.key(status).to_s) if status
152
155
  fto.txtRmk(remarks) if remarks
153
156
  end
154
- directions.values.each do |direction|
157
+ directions.each do |direction|
155
158
  builder << direction.to_xml
156
159
  end
157
160
  builder.target!
@@ -159,11 +162,20 @@ module AIXM
159
162
 
160
163
  # FATO directions further describe each direction to and from the FATO.
161
164
  #
162
- # @see https://github.com/openflightmaps/ofmx/wiki/Airport#fdn-fato-direction
165
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Airport#fdn-fato-direction
163
166
  class Direction
167
+ include AIXM::Association
168
+ include AIXM::Memoize
169
+
170
+ # @!method lightings
171
+ # @return [Array<AIXM::Component::Lighting>] installed lighting systems
172
+ # @!method add_lighting(lighting)
173
+ # @param lighting [AIXM::Component::Lighting]
174
+ has_many :lightings, as: :lightable
164
175
 
165
- # @return [AIXM::Component::FATO] FATO the FATO direction is further describing
166
- attr_reader :fato
176
+ # @!method fato
177
+ # @return [AIXM::Component::FATO] FATO the FATO direction is further describing
178
+ belongs_to :fato
167
179
 
168
180
  # @return [AIXM::A] name of the FATO direction (e.g. "12" or "16L")
169
181
  attr_reader :name
@@ -174,12 +186,8 @@ module AIXM
174
186
  # @return [String, nil] free text remarks
175
187
  attr_reader :remarks
176
188
 
177
- # @return [Array<AIXM::Component::Lighting>] installed lighting systems
178
- attr_reader :lightings
179
-
180
- def initialize(fato:, name:)
181
- self.fato, self.name = fato, name
182
- @lightings = []
189
+ def initialize(name:)
190
+ self.name = name
183
191
  end
184
192
 
185
193
  # @return [String]
@@ -187,12 +195,6 @@ module AIXM
187
195
  %Q(#<#{self.class} airport=#{fato&.airport&.id.inspect} name=#{name.inspect}>)
188
196
  end
189
197
 
190
- def fato=(value)
191
- fail(ArgumentError, "invalid FATO") unless value.is_a? AIXM::Component::FATO
192
- @fato = value
193
- end
194
- private :fato
195
-
196
198
  def name=(value)
197
199
  fail(ArgumentError, "invalid name") unless value.is_a? String
198
200
  @name = AIXM.a(value)
@@ -208,17 +210,6 @@ module AIXM
208
210
  @remarks = value&.to_s
209
211
  end
210
212
 
211
- # Add a lighting system to the FATO direction.
212
- #
213
- # @param lighting [AIXM::Component::Lighting] lighting instance
214
- # @return [self]
215
- def add_lighting(lighting)
216
- fail(ArgumentError, "invalid lighting") unless lighting.is_a? AIXM::Component::Lighting
217
- lighting.send(:lightable=, self)
218
- @lightings << lighting
219
- self
220
- end
221
-
222
213
  # @return [AIXM::A] magnetic orientation (magnetic bearing) in degrees
223
214
  def magnetic_orientation
224
215
  if geographic_orientation && fato.airport.declination
@@ -232,8 +223,9 @@ module AIXM
232
223
  builder.FdnUid do |fdn_uid|
233
224
  fdn_uid << fato.to_uid.indent(2)
234
225
  fdn_uid.txtDesig(name)
235
- end.insert_payload_hash(region: AIXM.config.mid_region)
226
+ end
236
227
  end
228
+ memoize :to_uid
237
229
 
238
230
  # @return [String] AIXM or OFMX markup
239
231
  def to_xml