aixm 0.3.8 → 0.3.10

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -78,7 +78,8 @@ module AIXM
78
78
  def initialize_config
79
79
  @@config = OpenStruct.new(
80
80
  schema: :aixm,
81
- mid: false
81
+ mid: false,
82
+ inflector: Dry::Inflector.new
82
83
  )
83
84
  end
84
85
 
@@ -7,21 +7,25 @@ module AIXM
7
7
  #
8
8
  # ===Cheat Sheet in Pseudo Code:
9
9
  # document = AIXM.document(
10
- # region: String
11
10
  # namespace: String (UUID)
12
11
  # created_at: Time or Date or String
13
12
  # effective_at: Time or Date or String
14
13
  # )
15
- # document.features << AIXM::Feature
14
+ # document.add_feature(AIXM::Feature)
16
15
  #
17
- # @see https://github.com/openflightmaps/ofmx/wiki/Snapshot
16
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Snapshot
18
17
  class Document
19
- REGION_RE = /\A[A-Z]{2}\z/.freeze
18
+ include AIXM::Association
20
19
 
21
20
  NAMESPACE_RE = /\A[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}\z/.freeze
22
21
 
23
- # @return [String] OFMX region all features in this document belong to
24
- attr_reader :region
22
+ # @!method features
23
+ # @return [Array<AIXM::Feature>] features (e.g. airport or airspace) present
24
+ # in this document
25
+ # @!method add_feature(feature)
26
+ # @param feature [AIXM::Feature]
27
+ # @return [self]
28
+ has_many :features, accept: ['AIXM::Feature']
25
29
 
26
30
  # @return [String] UUID to namespace the data contained in this document
27
31
  attr_reader :namespace
@@ -32,12 +36,8 @@ module AIXM
32
36
  # @return [Time] effective after date and time (default: {#created_at} or now)
33
37
  attr_reader :effective_at
34
38
 
35
- # @return [Array<AIXM::Feature>] airspaces, airports and other features
36
- attr_accessor :features
37
-
38
- def initialize(region: nil, namespace: nil, created_at: nil, effective_at: nil)
39
- self.region, self.namespace, self.created_at, self.effective_at = region, namespace, created_at, effective_at
40
- @features = []
39
+ def initialize(namespace: nil, created_at: nil, effective_at: nil)
40
+ self.namespace, self.created_at, self.effective_at = namespace, created_at, effective_at
41
41
  end
42
42
 
43
43
  # @return [String]
@@ -45,11 +45,6 @@ module AIXM
45
45
  %Q(#<#{self.class} created_at=#{created_at.inspect}>)
46
46
  end
47
47
 
48
- def region=(value)
49
- fail(ArgumentError, "invalid region") unless value.nil? || value&.upcase&.match?(REGION_RE)
50
- @region = value&.upcase
51
- end
52
-
53
48
  def namespace=(value)
54
49
  fail(ArgumentError, "invalid namespace") unless value.nil? || value.match?(NAMESPACE_RE)
55
50
  @namespace = value || SecureRandom.uuid
@@ -63,31 +58,6 @@ module AIXM
63
58
  @effective_at = value&.to_time || created_at || Time.now
64
59
  end
65
60
 
66
- # Search features and return those matching the given class and attribute
67
- # values
68
- #
69
- # @example
70
- # select_features(:airport, id: "LFNT")
71
- #
72
- # @param klass [Class, Symbol] feature class like AIXM::Feature::Airport or
73
- # AIXM::Feature::NavigationalAid::VOR, shorthand notations as symbols
74
- # e.g. :airport or :vor as listed in AIXM::CLASSES are recognized as well
75
- # @param attributes [Hash] search attributes by their values
76
- # @return [Array<AIXM::Feature>]
77
- def select_features(klass, attributes={})
78
- if klass.is_a? Symbol
79
- klass = AIXM::CLASSES.fetch(klass, nil)
80
- fail(ArgumentError, "unknown feature shortcut") unless klass
81
- end
82
- features.select do |feature|
83
- if feature.is_a? klass
84
- attributes.reduce(true) do |memo, (attribute, value)|
85
- memo && feature.send(attribute) == value
86
- end
87
- end
88
- end
89
- end
90
-
91
61
  # Compare all ungrouped obstacles and create new obstacle groups whose
92
62
  # members are located within +max_distance+ pairwise.
93
63
  #
@@ -95,18 +65,19 @@ module AIXM
95
65
  # pairs (default: 1 NM)
96
66
  # @return [Integer] number of obstacle groups added
97
67
  def group_obstacles!(max_distance: AIXM.d(1, :nm))
98
- obstacles, list = select_features(:obstacle), {}
99
- while subject = obstacles.shift
68
+ obstacles, list = features.find_by(:obstacle), {}
69
+ while subject = obstacles.send(:shift)
100
70
  obstacles.each do |obstacle|
101
71
  if subject.xy.distance(obstacle.xy) <= max_distance
102
- [subject, obstacle].each { |o| list[o] = list[subject] || SecureRandom.uuid }
72
+ [subject, obstacle].each { list[_1] = list[subject] || SecureRandom.uuid }
103
73
  end
104
74
  end
105
75
  end
106
76
  list.group_by(&:last).each do |_, grouped_list|
107
- obstacle_group = AIXM.obstacle_group(source: grouped_list.first.first.source)
108
- grouped_list.each { |o, _| obstacle_group.obstacles << features.delete(o) }
109
- features << obstacle_group
77
+ first_obstacle = grouped_list.first.first
78
+ obstacle_group = AIXM.obstacle_group(source: first_obstacle.source, region: first_obstacle.region)
79
+ grouped_list.each { |o, _| obstacle_group.add_obstacle features.send(:delete, o) }
80
+ add_feature obstacle_group
110
81
  end.count
111
82
  end
112
83
 
@@ -134,7 +105,6 @@ module AIXM
134
105
  'xmlns:xsi': AIXM.schema(:namespace),
135
106
  version: AIXM.schema(:version),
136
107
  origin: "rubygem aixm-#{AIXM::VERSION}",
137
- region: (region if AIXM.ofmx?),
138
108
  namespace: (namespace if AIXM.ofmx?),
139
109
  created: @created_at.xmlschema,
140
110
  effective: @effective_at.xmlschema
@@ -142,7 +112,14 @@ module AIXM
142
112
  builder = Builder::XmlMarkup.new(indent: 2)
143
113
  builder.instruct!
144
114
  builder.tag!(AIXM.schema(:root), meta) do |root|
145
- root << features.map { |f| f.to_xml }.join.indent(2)
115
+ AIXM::Memoize.method :to_uid do
116
+ root << features.map { _1.to_xml }.join.indent(2)
117
+ end
118
+ end
119
+ if AIXM.ofmx? && AIXM.config.mid
120
+ AIXM::PayloadHash::Mid.new(builder.target!).insert_mid.to_xml
121
+ else
122
+ builder.target!
146
123
  end
147
124
  end
148
125
 
@@ -0,0 +1,85 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ module Executables
5
+
6
+ class Mkmid
7
+ def initialize(**options)
8
+ @options = options
9
+ OptionParser.new do |o|
10
+ o.banner = <<~END
11
+ Add mid attributes to a schema valid OFMX file.
12
+ Usage: #{File.basename($0)} infile.ofmx
13
+ END
14
+ o.on('-i', '--[no-]in-place', 'overwrite file instead of dumping to STDOUT (default: false)') { @options[:in_place] = _1 }
15
+ o.on('-f', '--[no-]force', 'ignore XML schema validation errors (default: false)') { @options[:force] = _1 }
16
+ o.on('-A', '--about', 'show author/license information and exit') { AIXM::Executables.about }
17
+ o.on('-V', '--version', 'show version and exit') { AIXM::Executables.version }
18
+ end.parse!
19
+ @infile = ARGV.shift
20
+ end
21
+
22
+ def run
23
+ fail 'cannot read file' unless @infile && File.readable?(@infile)
24
+ fail 'file ist not OFMX' unless @infile.match?(/\.ofmx$/)
25
+ AIXM.ofmx!
26
+ document = File.open(@infile) { Nokogiri::XML(_1) }
27
+ AIXM::PayloadHash::Mid.new(document).insert_mid
28
+ errors = Nokogiri::XML::Schema(File.open(AIXM.schema(:xsd))).validate(document)
29
+ case
30
+ when errors.any? && !@options[:force]
31
+ puts errors
32
+ fail "OFMX file is not schema valid"
33
+ when @options[:in_place]
34
+ File.write(@infile, document.to_xml)
35
+ else
36
+ puts document.to_xml
37
+ end
38
+ rescue => error
39
+ puts "ERROR: #{error.message}"
40
+ exit 1
41
+ end
42
+ end
43
+
44
+ class Ckmid
45
+ def initialize(**options)
46
+ OptionParser.new do |o|
47
+ o.banner = <<~END
48
+ Check mid attributes of an OFMX file.
49
+ Usage: #{File.basename($0)} infile.ofmx
50
+ END
51
+ o.on('-A', '--about', 'show author/license information and exit') { AIXM::Executables.about }
52
+ o.on('-V', '--version', 'show version and exit') { AIXM::Executables.version }
53
+ end.parse!
54
+ @infile = ARGV.shift
55
+ end
56
+
57
+ def run
58
+ fail 'cannot read file' unless @infile && File.readable?(@infile)
59
+ fail 'file ist not OFMX' unless @infile.match?(/\.ofmx$/)
60
+ AIXM.ofmx!
61
+ document = File.open(@infile) { Nokogiri::XML(_1) }
62
+ errors = Nokogiri::XML::Schema(File.open(AIXM.schema(:xsd))).validate(document)
63
+ errors += AIXM::PayloadHash::Mid.new(document).check_mid
64
+ if errors.any?
65
+ puts errors
66
+ fail "OFMX file has errors"
67
+ end
68
+ rescue => error
69
+ puts "ERROR: #{error.message}"
70
+ exit 1
71
+ end
72
+ end
73
+
74
+ def self.about
75
+ puts 'Written by Sven Schwyn (bitcetera.com) and distributed under MIT license.'
76
+ exit
77
+ end
78
+
79
+ def self.version
80
+ puts AIXM::VERSION
81
+ exit
82
+ end
83
+
84
+ end
85
+ end
@@ -2,24 +2,34 @@ module AIXM
2
2
 
3
3
  # @abstract
4
4
  class Feature
5
+ REGION_RE = /\A[A-Z]{2}\z/.freeze
6
+
5
7
  private_class_method :new
6
8
 
7
9
  # @return [String] reference to source of the feature data
8
10
  attr_reader :source
9
11
 
10
- def initialize(source: nil)
12
+ # @return [String] OFMX region all features in this document belong to
13
+ attr_reader :region
14
+
15
+ def initialize(source: nil, region: nil)
11
16
  self.source = source
17
+ self.region = region || AIXM.config.region
12
18
  end
13
19
 
14
- # @return [String] reference to source of the feature data
15
20
  def source=(value)
16
21
  fail(ArgumentError, "invalid source") unless value.nil? || value.is_a?(String)
17
22
  @source = value
18
23
  end
19
24
 
25
+ def region=(value)
26
+ fail(ArgumentError, "invalid region") unless value.nil? || (value.is_a?(String) && value.upcase.match?(REGION_RE))
27
+ @region = value&.upcase
28
+ end
29
+
20
30
  # @return [Boolean]
21
31
  def ==(other)
22
- self.class === other && self.to_uid == other.to_uid
32
+ self.__class__ === other && self.to_uid == other.to_uid
23
33
  end
24
34
  end
25
35
 
@@ -13,8 +13,11 @@ module AIXM
13
13
  # )
14
14
  # service.remarks = String or nil
15
15
  #
16
- # @see https://github.com/openflightmaps/ofmx/wiki/Airport#aha-airport-address
16
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Airport#aha-airport-address
17
17
  class Address < Feature
18
+ include AIXM::Association
19
+ include AIXM::Memoize
20
+
18
21
  public_class_method :new
19
22
 
20
23
  TYPES = {
@@ -33,8 +36,9 @@ module AIXM
33
36
  OTHER: :other # specify in remarks
34
37
  }
35
38
 
36
- # @return [AIXM::Feature] addressable feature
37
- attr_reader :addressable
39
+ # @!method addressable
40
+ # @return [AIXM::Feature] addressable feature
41
+ belongs_to :addressable
38
42
 
39
43
  # @return [Symbol] type of address (see {TYPES})
40
44
  attr_reader :type
@@ -45,8 +49,8 @@ module AIXM
45
49
  # @return [String, nil] free text remarks
46
50
  attr_reader :remarks
47
51
 
48
- def initialize(source: nil, type:, address:)
49
- super(source: source)
52
+ def initialize(source: nil, region: nil, type:, address:)
53
+ super(source: source, region: region)
50
54
  self.type, self.address = type, address
51
55
  end
52
56
 
@@ -55,12 +59,6 @@ module AIXM
55
59
  %Q(#<#{self.class} type=#{type.inspect}>)
56
60
  end
57
61
 
58
- def addressable=(value)
59
- fail(ArgumentError, "invalid addressable") unless value.is_a? AIXM::Feature
60
- @addressable = value
61
- end
62
- private :addressable=
63
-
64
62
  def type=(value)
65
63
  @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
66
64
  end
@@ -79,10 +77,11 @@ module AIXM
79
77
  builder = Builder::XmlMarkup.new(indent: 2)
80
78
  builder.tag!(as) do |tag|
81
79
  tag << addressable.to_uid.indent(2) if addressable
82
- tag.codeType(TYPES.key(type).to_s.then_if(AIXM.aixm?) { |t| t.sub(/-\w+$/, '') })
80
+ tag.codeType(TYPES.key(type).to_s.then_if(AIXM.aixm?) { _1.sub(/-\w+$/, '') })
83
81
  tag.noSeq(sequence)
84
- end.insert_payload_hash(region: AIXM.config.mid_region)
82
+ end
85
83
  end
84
+ memoize :to_uid
86
85
 
87
86
  # @return [String] AIXM or OFMX markup
88
87
  def to_xml(as:, sequence:)
@@ -9,6 +9,7 @@ module AIXM
9
9
  # ===Cheat Sheet in Pseudo Code:
10
10
  # airport = AIXM.airport(
11
11
  # source: String or nil
12
+ # region: String or nil
12
13
  # organisation: AIXM.organisation
13
14
  # id: String
14
15
  # name: String
@@ -31,8 +32,11 @@ module AIXM
31
32
  # For airports without an +id+, you may assign the two character region
32
33
  # (e.g. "LF") which will be combined with an 8 character digest of +name+.
33
34
  #
34
- # @see https://github.com/openflightmaps/ofmx/wiki/Airport#ahp-airport
35
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Airport#ahp-airport
35
36
  class Airport < Feature
37
+ include AIXM::Association
38
+ include AIXM::Memoize
39
+
36
40
  public_class_method :new
37
41
 
38
42
  ID_RE = /^([A-Z]{3,4}|[A-Z]{2}[A-Z\d]{4,})$/.freeze
@@ -44,8 +48,53 @@ module AIXM
44
48
  LS: :landing_site
45
49
  }.freeze
46
50
 
47
- # @return [AIXM::Feature::Organisation] superior organisation
48
- attr_reader :organisation
51
+ # @!method addresses
52
+ # @return [Array<AIXM::Feature::Address>] postal address, url, A/A or A/G frequency etc
53
+ # @!method add_address(address)
54
+ # @param address [AIXM::Feature::Address]
55
+ # @return [self]
56
+ has_many :addresses, as: :addressable
57
+
58
+ # @!method fatos
59
+ # @return [Array<AIXM::Component::FATO>] FATOs present at this airport
60
+ # @!method add_fato(fato)
61
+ # @param fato [AIXM::Component::FATO]
62
+ has_many :fatos
63
+
64
+ # @!method helipads
65
+ # @return [Array<AIXM::Component::Helipad>] helipads present at this airport
66
+ # @!method add_helipad(helipad)
67
+ # @param helipad [AIXM::Component::Helipad]
68
+ has_many :helipads
69
+
70
+ # @!method runways
71
+ # @return [Array<AIXM::Component::Runway>] runways present at this airport
72
+ # @!method add_runway(runway)
73
+ # @param runway [AIXM::Component::Runway]
74
+ has_many :runways
75
+
76
+ # @!method usage_limitations
77
+ # @return [Array<AIXM::Feature::Airport::UsageLimitation>] usage limitations
78
+ # @!method add_usage_limitation
79
+ # @yield [AIXM::Feature::Airport::UsageLimitation]
80
+ # @return [self]
81
+ has_many :usage_limitations, accept: 'AIXM::Feature::Airport::UsageLimitation' do |usage_limitation, type:| end
82
+
83
+ # @!method designated_points
84
+ # @return [Array<AIXM::Feature::NavigationalAid::DesignatedPoint>] designated points
85
+ # @!method add_designated_point(designated_point)
86
+ # @param designated_point [AIXM::Feature::NavigationalAid::DesignatedPoint]
87
+ has_many :designated_points
88
+
89
+ # @!method units
90
+ # @return [Array<AIXM::Feature::Unit>] units
91
+ # @!method add_unit(unit)
92
+ # @param unit [AIXM::Feature::Unit]
93
+ has_many :units
94
+
95
+ # @!method organisation
96
+ # @return [AIXM::Feature::Organisation] superior organisation
97
+ belongs_to :organisation, as: :member
49
98
 
50
99
  # ICAO indicator, IATA indicator or generated indicator
51
100
  #
@@ -93,26 +142,10 @@ module AIXM
93
142
  # @return [String, nil] free text remarks
94
143
  attr_reader :remarks
95
144
 
96
- # @return [Array<AIXM::Component::Runway>] runways present at this airport
97
- attr_reader :runways
98
-
99
- # @return [Array<AIXM::Component::FATO>] FATOs present at this airport
100
- attr_reader :fatos
101
-
102
- # @return [Array<AIXM::Component::Helipad>] helipads present at this airport
103
- attr_reader :helipads
104
-
105
- # @return [Array<AIXM::Feature::Airport::UsageLimitation>] usage limitations
106
- attr_accessor :usage_limitations
107
-
108
- # @return [Array<AIXM::Feature::Address>] postal address, url, A/A or A/G frequency etc
109
- attr_reader :addresses
110
-
111
- def initialize(source: nil, organisation:, id: nil, name:, xy:)
112
- super(source: source)
145
+ def initialize(source: nil, region: nil, organisation:, id: nil, name:, xy:)
146
+ super(source: source, region: region)
113
147
  self.organisation, self.name, self.xy = organisation, name, xy
114
148
  self.id = id # name must already be set
115
- @runways, @fatos, @helipads, @usage_limitations, @addresses = [], [], [], [], []
116
149
  end
117
150
 
118
151
  # @return [String]
@@ -120,15 +153,10 @@ module AIXM
120
153
  %Q(#<#{self.class} id=#{id.inspect}>)
121
154
  end
122
155
 
123
- def organisation=(value)
124
- fail(ArgumentError, "invalid organisation") unless value.is_a? AIXM::Feature::Organisation
125
- @organisation = value
126
- end
127
-
128
156
  # For airports without an +id+, you may assign the two character region
129
157
  # (e.g. "LF") which will be combined with an 8 character digest of +name+.
130
158
  def id=(value)
131
- value = [value, [name].to_digest].join.upcase if value&.upcase&.match? AIXM::Document::REGION_RE
159
+ value = [value, [name].to_digest].join.upcase if value&.upcase&.match? AIXM::Feature::REGION_RE
132
160
  fail(ArgumentError, "invalid id") unless value&.upcase&.match? ID_RE
133
161
  @id = value.upcase
134
162
  end
@@ -199,99 +227,21 @@ module AIXM
199
227
  @remarks = value&.to_s
200
228
  end
201
229
 
202
- # Add a runway to the airport.
203
- #
204
- # @param runway [AIXM::Component::Runway] runway instance
205
- # @return [self]
206
- def add_runway(runway)
207
- fail(ArgumentError, "invalid runway") unless runway.is_a? AIXM::Component::Runway
208
- runway.send(:airport=, self)
209
- @runways << runway
210
- self
211
- end
212
-
213
- # Add a FATO to the airport.
214
- #
215
- # @param FATO [AIXM::Component::FATO] FATO instance
216
- # @return [self]
217
- def add_fato(fato)
218
- fail(ArgumentError, "invalid FATO") unless fato.is_a? AIXM::Component::FATO
219
- fato.send(:airport=, self)
220
- @fatos << fato
221
- self
222
- end
223
-
224
- # Add a helipad to the airport.
225
- #
226
- # @param helipad [AIXM::Component::Helipad] helipad instance
227
- # @return [self]
228
- def add_helipad(helipad)
229
- fail(ArgumentError, "invalid helipad") unless helipad.is_a? AIXM::Component::Helipad
230
- helipad.send(:airport=, self)
231
- @helipads << helipad
232
- self
233
- end
234
-
235
- # Add an airport usage limitation.
236
- #
237
- # See {AIXM::Feature::Airport::UsageLimitation::TYPES UsageLimitation::TYPES}
238
- # for recognized limitations and {AIXM::Feature::Airport::UsageLimitation#add_condition UsageLimitation#add_condition}
239
- # for recognized conditions.
240
- #
241
- # Multiple conditions are joined with an implicit *or* whereas the
242
- # specifics of a condition (aircraft, rule etc) are joined with an
243
- # implicit *and*.
244
- #
245
- # @example Limitation applying to any traffic
246
- # airport.add_usage_limitation(:permitted)
247
- #
248
- # @example Limitation applying to specific traffic
249
- # airport.add_usage_limitation(:reservation_required) do |reservation_required|
250
- # reservation_required.add_condition do |condition|
251
- # condition.aircraft = :glider
252
- # end
253
- # reservation_required.add_condition do |condition|
254
- # condition.rule = :ifr
255
- # condition.origin = :international
256
- # end
257
- # reservation_required.timetable = AIXM::H24
258
- # reservation_required.remarks = "Reservation 24 HRS prior to arrival"
259
- # end
260
- #
261
- # @yieldparam usage_limitation [AIXM::Feature::Airport::UsageLimitation]
262
- # @return [self]
263
- def add_usage_limitation(type)
264
- usage_limitation = UsageLimitation.new(type: type)
265
- yield(usage_limitation) if block_given?
266
- @usage_limitations << usage_limitation
267
- self
268
- end
269
-
270
- # Add an address (postal address, url, A/A or A/G frequency etc) to the airport.
271
- #
272
- # @params address [AIXM::Feature::Address] address instance
273
- # @return [self]
274
- def add_address(address)
275
- fail(ArgumentError, "invalid address") unless address.is_a? AIXM::Feature::Address
276
- address.send(:addressable=, self)
277
- @addresses << address
278
- self
279
- end
280
-
281
230
  # @return [String] UID markup
282
231
  def to_uid(as: :AhpUid)
283
232
  builder = Builder::XmlMarkup.new(indent: 2)
284
- builder.tag!(as) do |tag|
233
+ builder.tag!(as, ({ region: (region if AIXM.ofmx?) }.compact)) do |tag|
285
234
  tag.codeId(id)
286
- end.insert_payload_hash(region: AIXM.config.mid_region)
235
+ end
287
236
  end
237
+ memoize :to_uid
288
238
 
289
239
  # @return [String] UID markup
290
240
  def to_wrapped_uid(as: :AhpUid, with:)
291
241
  builder = Builder::XmlMarkup.new(indent: 2)
292
242
  builder.tag!(with) do |tag|
293
243
  tag << to_uid(as: as).indent(2)
294
- end.insert_payload_hash(region: AIXM.config.mid_region)
244
+ end
295
245
  end
296
246
 
297
247
  # @return [String] AIXM or OFMX markup
@@ -349,9 +299,35 @@ module AIXM
349
299
  # Limitations concerning the availability of an airport for certain flight
350
300
  # types, aircraft types etc during specific hours.
351
301
  #
302
+ # See {AIXM::Feature::Airport::UsageLimitation::TYPES UsageLimitation::TYPES}
303
+ # for recognized limitations and {AIXM::Feature::Airport::UsageLimitation#add_condition UsageLimitation#add_condition}
304
+ # for recognized conditions.
305
+ #
306
+ # Multiple conditions are joined with an implicit *or* whereas the
307
+ # specifics of a condition (aircraft, rule etc) are joined with an
308
+ # implicit *and*.
309
+ #
310
+ # @example Limitation applying to any traffic
311
+ # airport.add_usage_limitation(type: :permitted)
312
+ #
313
+ # @example Limitation applying to specific traffic
314
+ # airport.add_usage_limitation(type: :reservation_required) do |reservation_required|
315
+ # reservation_required.add_condition do |condition|
316
+ # condition.aircraft = :glider
317
+ # end
318
+ # reservation_required.add_condition do |condition|
319
+ # condition.rule = :ifr
320
+ # condition.origin = :international
321
+ # end
322
+ # reservation_required.timetable = AIXM::H24
323
+ # reservation_required.remarks = "Reservation 24 HRS prior to arrival"
324
+ # end
325
+ #
352
326
  # @see AIXM::Feature::Airport#add_usage_limitation
353
- # @see https://github.com/openflightmaps/ofmx/wiki/Airport#ahu-airport-usage
327
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Airport#ahu-airport-usage
354
328
  class UsageLimitation
329
+ include AIXM::Association
330
+
355
331
  TYPES = {
356
332
  PERMIT: :permitted,
357
333
  FORBID: :forbidden,
@@ -359,15 +335,20 @@ module AIXM
359
335
  OTHER: :other # specify in remarks
360
336
  }.freeze
361
337
 
362
- # @return [AIXM::Feature::Airport] airport this usage limitation is assigned to
363
- attr_reader :airport
338
+ # @!method conditions
339
+ # @return [Array<AIXM::Feature::Airport::UsageLimitation::Condition>] conditions for this limitation to apply
340
+ # @!method add_condition
341
+ # @yield [AIXM::Feature::Airport::UsageLimitation::Condition]
342
+ # @return [self]
343
+ has_many :conditions, accept: 'AIXM::Feature::Airport::UsageLimitation::Condition' do |condition| end
344
+
345
+ # @!method airport
346
+ # @return [AIXM::Feature::Airport] airport this usage limitation is assigned to
347
+ belongs_to :airport
364
348
 
365
349
  # @return [Symbol] type of limitation
366
350
  attr_reader :type
367
351
 
368
- # @return [Array<AIXM::Feature::Airport::UsageLimitation::Condition>] conditions for this limitation to apply
369
- attr_reader :conditions
370
-
371
352
  # @return [AIXM::Component::Timetable, nil] limitation application hours
372
353
  attr_reader :timetable
373
354
 
@@ -376,7 +357,6 @@ module AIXM
376
357
 
377
358
  def initialize(type:)
378
359
  self.type = type
379
- @conditions = []
380
360
  end
381
361
 
382
362
  # @return [String]
@@ -388,17 +368,6 @@ module AIXM
388
368
  @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
389
369
  end
390
370
 
391
- # Add a condition to the usage limitation.
392
- #
393
- # @yieldparam condition [AIXM::Feature::Airport::UsageLimitation::Condition]
394
- # @return [self]
395
- def add_condition
396
- condition = Condition.new
397
- yield(condition)
398
- @conditions << condition
399
- self
400
- end
401
-
402
371
  def timetable=(value)
403
372
  fail(ArgumentError, "invalid timetable") unless value.nil? || value.is_a?(AIXM::Component::Timetable)
404
373
  @timetable = value
@@ -425,8 +394,10 @@ module AIXM
425
394
  # limitation.
426
395
  #
427
396
  # @see AIXM::Feature::Airport#add_usage_limitation
428
- # @see https://github.com/openflightmaps/ofmx/wiki/Airport#ahu-airport-usage
397
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Airport#ahu-airport-usage
429
398
  class Condition
399
+ include AIXM::Association
400
+
430
401
  AIRCRAFT = {
431
402
  L: :landplane,
432
403
  S: :seaplane,
@@ -472,6 +443,10 @@ module AIXM
472
443
  OTHER: :other # specify in remarks
473
444
  }.freeze
474
445
 
446
+ # @!method usage_limitation
447
+ # @return [AIXM::Feature::Airport::UsageLimitation] usage limitation the condition belongs to
448
+ belongs_to :usage_limitation
449
+
475
450
  # @return [Symbol, nil] kind of aircraft (see {AIRCRAFT})
476
451
  attr_reader :aircraft
477
452