aixm 0.3.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +72 -6
  4. data/README.md +191 -53
  5. data/exe/ckmid +11 -0
  6. data/exe/mkmid +11 -0
  7. data/lib/aixm/association.rb +367 -0
  8. data/lib/aixm/classes.rb +44 -0
  9. data/lib/aixm/component/fato.rb +44 -52
  10. data/lib/aixm/component/frequency.rb +13 -14
  11. data/lib/aixm/component/geometry/arc.rb +2 -2
  12. data/lib/aixm/component/geometry/border.rb +14 -5
  13. data/lib/aixm/component/geometry/circle.rb +8 -2
  14. data/lib/aixm/component/geometry/point.rb +10 -3
  15. data/lib/aixm/component/geometry/rhumb_line.rb +54 -0
  16. data/lib/aixm/component/geometry.rb +38 -38
  17. data/lib/aixm/component/helipad.rb +29 -37
  18. data/lib/aixm/component/layer.rb +28 -19
  19. data/lib/aixm/component/lighting.rb +11 -12
  20. data/lib/aixm/component/runway.rb +46 -53
  21. data/lib/aixm/{feature → component}/service.rb +36 -35
  22. data/lib/aixm/component/surface.rb +3 -3
  23. data/lib/aixm/component/timetable.rb +5 -3
  24. data/lib/aixm/component/{vertical_limits.rb → vertical_limit.rb} +12 -6
  25. data/lib/aixm/config.rb +6 -3
  26. data/lib/aixm/document.rb +31 -49
  27. data/lib/aixm/executables.rb +85 -0
  28. data/lib/aixm/f.rb +28 -0
  29. data/lib/aixm/feature/address.rb +20 -15
  30. data/lib/aixm/feature/airport.rb +113 -129
  31. data/lib/aixm/feature/airspace.rb +54 -23
  32. data/lib/aixm/feature/navigational_aid/designated_point.rb +12 -14
  33. data/lib/aixm/feature/navigational_aid/dme.rb +10 -11
  34. data/lib/aixm/feature/navigational_aid/marker.rb +6 -2
  35. data/lib/aixm/feature/navigational_aid/ndb.rb +6 -2
  36. data/lib/aixm/feature/navigational_aid/tacan.rb +6 -2
  37. data/lib/aixm/feature/navigational_aid/vor.rb +22 -14
  38. data/lib/aixm/feature/navigational_aid.rb +7 -9
  39. data/lib/aixm/feature/obstacle.rb +22 -20
  40. data/lib/aixm/feature/obstacle_group.rb +30 -30
  41. data/lib/aixm/feature/organisation.rb +20 -4
  42. data/lib/aixm/feature/unit.rb +35 -45
  43. data/lib/aixm/feature.rb +13 -3
  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 +34 -50
  48. data/lib/aixm/shortcuts.rb +6 -43
  49. data/lib/aixm/version.rb +1 -1
  50. data/lib/aixm/xy.rb +9 -1
  51. data/lib/aixm.rb +18 -7
  52. data/schemas/ofmx/{0 → 0.1}/OFMX-CSV-Obstacle.json +0 -0
  53. data/schemas/ofmx/{0 → 0.1}/OFMX-CSV.json +0 -0
  54. data/schemas/ofmx/{0 → 0.1}/OFMX-DataTypes.xsd +52 -2
  55. data/schemas/ofmx/{0 → 0.1}/OFMX-Features.xsd +225 -14
  56. data/schemas/ofmx/{0 → 0.1}/OFMX-Snapshot.xsd +0 -5
  57. data.tar.gz.sig +0 -0
  58. metadata +116 -164
  59. metadata.gz.sig +0 -0
  60. data/.gitignore +0 -6
  61. data/.ruby-version +0 -1
  62. data/.travis.yml +0 -8
  63. data/.yardopts +0 -3
  64. data/Guardfile +0 -8
  65. data/aixm.gemspec +0 -35
  66. data/gems.rb +0 -3
  67. data/lib/aixm/component.rb +0 -6
  68. data/rakefile.rb +0 -22
  69. data/spec/factory.rb +0 -559
  70. data/spec/lib/aixm/a_spec.rb +0 -203
  71. data/spec/lib/aixm/component/fato_spec.rb +0 -260
  72. data/spec/lib/aixm/component/frequency_spec.rb +0 -75
  73. data/spec/lib/aixm/component/geometry/arc_spec.rb +0 -75
  74. data/spec/lib/aixm/component/geometry/border_spec.rb +0 -33
  75. data/spec/lib/aixm/component/geometry/circle_spec.rb +0 -70
  76. data/spec/lib/aixm/component/geometry/point_spec.rb +0 -39
  77. data/spec/lib/aixm/component/geometry_spec.rb +0 -321
  78. data/spec/lib/aixm/component/helipad_spec.rb +0 -187
  79. data/spec/lib/aixm/component/layer_spec.rb +0 -137
  80. data/spec/lib/aixm/component/lighting_spec.rb +0 -88
  81. data/spec/lib/aixm/component/runway_spec.rb +0 -472
  82. data/spec/lib/aixm/component/surface_spec.rb +0 -124
  83. data/spec/lib/aixm/component/timetable_spec.rb +0 -49
  84. data/spec/lib/aixm/component/vertical_limits_spec.rb +0 -97
  85. data/spec/lib/aixm/config_spec.rb +0 -41
  86. data/spec/lib/aixm/d_spec.rb +0 -150
  87. data/spec/lib/aixm/document_spec.rb +0 -1875
  88. data/spec/lib/aixm/errors_spec.rb +0 -14
  89. data/spec/lib/aixm/f_spec.rb +0 -85
  90. data/spec/lib/aixm/feature/address_spec.rb +0 -55
  91. data/spec/lib/aixm/feature/airport_spec.rb +0 -770
  92. data/spec/lib/aixm/feature/airspace_spec.rb +0 -390
  93. data/spec/lib/aixm/feature/navigational_aid/designated_point_spec.rb +0 -98
  94. data/spec/lib/aixm/feature/navigational_aid/dme_spec.rb +0 -92
  95. data/spec/lib/aixm/feature/navigational_aid/marker_spec.rb +0 -79
  96. data/spec/lib/aixm/feature/navigational_aid/ndb_spec.rb +0 -89
  97. data/spec/lib/aixm/feature/navigational_aid/tacan_spec.rb +0 -88
  98. data/spec/lib/aixm/feature/navigational_aid/vor_spec.rb +0 -245
  99. data/spec/lib/aixm/feature/navigational_aid_spec.rb +0 -52
  100. data/spec/lib/aixm/feature/obstacle_group_spec.rb +0 -326
  101. data/spec/lib/aixm/feature/obstacle_spec.rb +0 -279
  102. data/spec/lib/aixm/feature/organisation_spec.rb +0 -77
  103. data/spec/lib/aixm/feature/service_spec.rb +0 -59
  104. data/spec/lib/aixm/feature/unit_spec.rb +0 -230
  105. data/spec/lib/aixm/feature_spec.rb +0 -38
  106. data/spec/lib/aixm/p_spec.rb +0 -189
  107. data/spec/lib/aixm/refinements_spec.rb +0 -381
  108. data/spec/lib/aixm/version_spec.rb +0 -7
  109. data/spec/lib/aixm/w_spec.rb +0 -150
  110. data/spec/lib/aixm/xy_spec.rb +0 -180
  111. data/spec/lib/aixm/z_spec.rb +0 -94
  112. data/spec/macros/marking.rb +0 -12
  113. data/spec/macros/organisation.rb +0 -11
  114. data/spec/macros/remarks.rb +0 -12
  115. data/spec/macros/timetable.rb +0 -11
  116. data/spec/macros/xy.rb +0 -11
  117. data/spec/macros/z_qnh.rb +0 -11
  118. data/spec/sounds/failure.mp3 +0 -0
  119. data/spec/sounds/success.mp3 +0 -0
  120. data/spec/spec_helper.rb +0 -55
@@ -9,6 +9,7 @@ module AIXM
9
9
  # ===Cheat Sheet in Pseudo Code:
10
10
  # obstacle_group = AIXM.obstacle_group(
11
11
  # source: String or nil # see remarks below
12
+ # region: String or nil
12
13
  # name: String or nil
13
14
  # ).tap do |obstacle_group|
14
15
  # obstacle_group.xy_accuracy = AIXM.d or nil
@@ -31,12 +32,34 @@ module AIXM
31
32
  #
32
33
  # Please note: As soon as an obstacle is added to an obstacle group, the
33
34
  # +xy_accuracy+ and +z_accuracy+ of the obstacle group overwrite whatever
34
- # is set on the individual obstacles!
35
+ # is set on the individual obstacles. On the other hand, if the obstacle
36
+ # group has no +source+ set, it will inherit this value from the first
37
+ # obstacle in the group.
35
38
  #
36
- # @see https://github.com/openflightmaps/ofmx/wiki/Obstacle
39
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Obstacle
37
40
  class ObstacleGroup < Feature
41
+ include AIXM::Association
42
+ include AIXM::Memoize
43
+
38
44
  public_class_method :new
39
45
 
46
+ # @!method obstacles
47
+ # @return [Array<AIXM::Feature::Obstacle>] obstacles in this obstacle group
48
+ # @!method add_obstacle(obstacle, linked_to: nil, link_type: nil)
49
+ # @param obstacle [AIXM::Feature::Obstacle] obstacle instance
50
+ # @param linked_to [Symbol, AIXM::Feature::Obstacle, nil] Either:
51
+ # * :previous - link to the obstacle last added to the obstacle group
52
+ # * AIXM::Feature::Obstacle - link to this specific obstacle
53
+ # @param link_type [Symbol, nil] type of link (see
54
+ # {AIXM::Feature::Obstacle::LINK_TYPES})
55
+ # @return [self]
56
+ has_many :obstacles do |obstacle, linked_to: nil, link_type: nil|
57
+ if linked_to
58
+ obstacle.send(:linked_to=, linked_to == :previous ? @obstacles.last : linked_to)
59
+ obstacle.send(:link_type=, (link_type || :other))
60
+ end
61
+ end
62
+
40
63
  # @!method source
41
64
  # @return [String] reference to source of the feature data
42
65
  # @!method name
@@ -54,13 +77,9 @@ module AIXM
54
77
  # @return [String, nil] free text remarks
55
78
  attr_reader :remarks
56
79
 
57
- # @return [Array<AIXM::Feature::Obstacle>] obstacles in this obstacle group
58
- attr_reader :obstacles
59
-
60
- def initialize(source: nil, name: nil)
61
- super(source: source)
80
+ def initialize(source: nil, region: nil, name: nil)
81
+ super(source: source, region: region)
62
82
  self.name = name
63
- @obstacles = []
64
83
  end
65
84
 
66
85
  # @return [String]
@@ -87,35 +106,16 @@ module AIXM
87
106
  @remarks = value&.to_s
88
107
  end
89
108
 
90
- # Add an obstacle to the obstacle group and optionally link it to another
91
- # obstacle from the obstacle group.
92
- #
93
- # @param obstacle [AIXM::Feature::Obstacle] obstacle instance
94
- # @param linked_to [Symbol, AIXM::Feature::Obstacle, nil] Either:
95
- # * :previous - link to the obstacle last added to the obstacle group
96
- # * AIXM::Feature::Obstacle - link to this specific obstacle
97
- # @param link_type [Symbol, nil] type of link (see
98
- # {AIXM::Feature::Obstacle::LINK_TYPES})
99
- # @return [self]
100
- def add_obstacle(obstacle, linked_to: nil, link_type: :other)
101
- obstacle.send(:obstacle_group=, self)
102
- if linked_to && link_type
103
- obstacle.send(:linked_to=, linked_to == :previous ? @obstacles.last : linked_to)
104
- obstacle.send(:link_type=, link_type)
105
- end
106
- @obstacles << obstacle
107
- self
108
- end
109
-
110
109
  # @return [String] UID markup
111
110
  def to_uid
112
111
  builder = Builder::XmlMarkup.new(indent: 2)
113
- builder.OgrUid do |ogr_uid|
112
+ builder.OgrUid({ region: (region if AIXM.ofmx?) }.compact) do |ogr_uid|
114
113
  ogr_uid.txtName(name)
115
114
  ogr_uid.geoLat(obstacles.first.xy.lat(AIXM.schema))
116
115
  ogr_uid.geoLong(obstacles.first.xy.long(AIXM.schema))
117
116
  end
118
117
  end
118
+ memoize :to_uid
119
119
 
120
120
  # @return [String] AIXM or OFMX markup
121
121
  def to_xml
@@ -136,7 +136,7 @@ module AIXM
136
136
  ogr.txtRmk(remarks) if remarks
137
137
  end
138
138
  end
139
- obstacles.each { |o| builder << o.to_xml(delegate: false) }
139
+ obstacles.each { builder << _1.to_xml(delegate: false) }
140
140
  builder.target!
141
141
  end
142
142
  end
@@ -9,14 +9,18 @@ module AIXM
9
9
  # ===Cheat Sheet in Pseudo Code:
10
10
  # organisation = AIXM.organisation(
11
11
  # source: String or nil
12
+ # region: String or nil
12
13
  # name: String
13
14
  # type: TYPES
14
15
  # )
15
16
  # organisation.id = String or nil
16
17
  # organisation.remarks = String or nil
17
18
  #
18
- # @see https://github.com/openflightmaps/ofmx/wiki/Organisation#org-organisation
19
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Organisation#org-organisation
19
20
  class Organisation < Feature
21
+ include AIXM::Association
22
+ include AIXM::Memoize
23
+
20
24
  public_class_method :new
21
25
 
22
26
  TYPES = {
@@ -31,6 +35,17 @@ module AIXM
31
35
  OTHER: :other # specify in remarks
32
36
  }.freeze
33
37
 
38
+ # @!method members
39
+ # @return [Array<AIXM::Feature::Airport,
40
+ # AIXM::Feature::Unit,
41
+ # AIXM::Feature::NavigationalAid>] aiports, units or navigational aids
42
+ # @!method add_member(member)
43
+ # @param member [AIXM::Feature::Airport,
44
+ # AIXM::Feature::Unit,
45
+ # AIXM::Feature::NavigationalAid]
46
+ # @return [self]
47
+ has_many :members, accept: [:airport, :unit, 'AIXM::Feature::NavigationalAid']
48
+
34
49
  # @return [String] name of organisation (e.g. "FRANCE")
35
50
  attr_reader :name
36
51
 
@@ -43,8 +58,8 @@ module AIXM
43
58
  # @return [String, nil] free text remarks
44
59
  attr_reader :remarks
45
60
 
46
- def initialize(source: nil, name:, type:)
47
- super(source: source)
61
+ def initialize(source: nil, region: nil, name:, type:)
62
+ super(source: source, region: region)
48
63
  self.name, self.type = name, type
49
64
  end
50
65
 
@@ -74,10 +89,11 @@ module AIXM
74
89
  # @return [String] UID markup
75
90
  def to_uid
76
91
  builder = Builder::XmlMarkup.new(indent: 2)
77
- builder.OrgUid do |org_uid|
92
+ builder.OrgUid({ region: (region if AIXM.ofmx?) }.compact) do |org_uid|
78
93
  org_uid.txtName(name)
79
94
  end
80
95
  end
96
+ memoize :to_uid
81
97
 
82
98
  # @return [String] AIXM or OFMX markup
83
99
  def to_xml
@@ -9,6 +9,7 @@ module AIXM
9
9
  # ===Cheat Sheet in Pseudo Code:
10
10
  # unit = AIXM.unit(
11
11
  # source: String or nil
12
+ # region: String or nil
12
13
  # organisation: AIXM.organisation
13
14
  # name: String
14
15
  # type: TYPES
@@ -18,8 +19,11 @@ module AIXM
18
19
  # unit.remarks = String or nil
19
20
  # unit.add_service(AIXM.service)
20
21
  #
21
- # @see https://github.com/openflightmaps/ofmx/wiki/Organisation#uni-unit
22
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Organisation#uni-unit
22
23
  class Unit < Feature
24
+ include AIXM::Association
25
+ include AIXM::Memoize
26
+
23
27
  public_class_method :new
24
28
 
25
29
  TYPES = {
@@ -76,8 +80,19 @@ module AIXM
76
80
  OTHER: :other # specify in remarks
77
81
  }.freeze
78
82
 
79
- # @return [AIXM::Feature::Organisation] superior organisation
80
- attr_reader :organisation
83
+ # @!method services
84
+ # @return [Array<AIXM::Component::Service>] services provided by this unit
85
+ # @!method add_service(service)
86
+ # @param service [AIXM::Component::Service]
87
+ has_many :services
88
+
89
+ # @!method organisation
90
+ # @return [AIXM::Feature::Organisation] superior organisation
91
+ belongs_to :organisation, as: :member
92
+
93
+ # @!method airport
94
+ # @return [AIXM::Feature::Airport, nil] airport
95
+ belongs_to :airport
81
96
 
82
97
  # @return [String] name of unit (e.g. "MARSEILLE ACS")
83
98
  attr_reader :name
@@ -85,27 +100,18 @@ module AIXM
85
100
  # @return [Symbol] type of unit (see {TYPES})
86
101
  attr_reader :type
87
102
 
88
- # @return [AIXM::Feature::Airport, nil] airport
89
- attr_reader :airport
90
-
91
103
  # @return [String, nil] free text remarks
92
104
  attr_reader :remarks
93
105
 
94
- def initialize(source: nil, organisation:, name:, type:, class:)
95
- super(source: source)
106
+ def initialize(source: nil, region: nil, organisation:, name:, type:, class:)
107
+ super(source: source, region: region)
96
108
  self.organisation, self.name, self.type = organisation, name, type
97
109
  self.class = binding.local_variable_get(:class)
98
- @services = []
99
110
  end
100
111
 
101
112
  # @return [String]
102
113
  def inspect
103
- %Q(#<#{original_class} name=#{name.inspect} type=#{type.inspect}>)
104
- end
105
-
106
- def organisation=(value)
107
- fail(ArgumentError, "invalid organisation") unless value.is_a? AIXM::Feature::Organisation
108
- @organisation = value
114
+ %Q(#<#{__class__} name=#{name.inspect} type=#{type.inspect}>)
109
115
  end
110
116
 
111
117
  def name=(value)
@@ -118,9 +124,8 @@ module AIXM
118
124
  end
119
125
 
120
126
  # @!attribute class
121
- # @note Use +original_class+ to query the Ruby object class.
127
+ # @note Use +Object#__class__+ alias to query the Ruby object class.
122
128
  # @return [Symbol] class of unit (see {CLASSES})
123
- alias_method :original_class, :class
124
129
  def class
125
130
  @klass
126
131
  end
@@ -129,58 +134,43 @@ module AIXM
129
134
  @klass = CLASSES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid class")
130
135
  end
131
136
 
132
- def airport=(value)
133
- fail(ArgumentError, "invalid airport") unless value.nil? || value.is_a?(AIXM::Feature::Airport)
134
- @airport = value
135
- end
136
-
137
137
  def remarks=(value)
138
138
  @remarks = value&.to_s
139
139
  end
140
140
 
141
- # Add a service provided by this unit.
142
- #
143
- # @param service [AIXM::Feature::Service] service instance
144
- # @return [self]
145
- def add_service(service)
146
- fail(ArgumentError, "invalid service") unless service.is_a? AIXM::Feature::Service
147
- service.send(:unit=, self)
148
- @services << service
149
- self
150
- end
151
-
152
- # @!attribute [r] services
153
- # @return [Array<AIXM::Feature::Service>] services provided by this unit
154
- def services
155
- @services.sort { |a, b| a.type <=> b.type }
156
- end
157
-
158
141
  # @return [String] UID markup
159
142
  def to_uid
160
143
  builder = Builder::XmlMarkup.new(indent: 2)
161
- builder.UniUid do |uni_uid|
144
+ builder.UniUid({ region: (region if AIXM.ofmx?) }.compact) do |uni_uid|
162
145
  uni_uid.txtName(name)
146
+ uni_uid.codeType(TYPES.key(type).to_s) if AIXM.ofmx?
163
147
  end
164
148
  end
149
+ memoize :to_uid
165
150
 
166
151
  # @return [String] AIXM or OFMX markup
167
152
  def to_xml
168
153
  builder = Builder::XmlMarkup.new(indent: 2)
169
- builder.comment! "Unit: #{name}"
154
+ builder.comment! "Unit: #{name_with_type}"
170
155
  builder.Uni({ source: (source if AIXM.ofmx?) }.compact) do |uni|
171
156
  uni << to_uid.indent(2)
172
157
  uni << organisation.to_uid.indent(2)
173
158
  uni << airport.to_uid.indent(2) if airport
174
- uni.codeType(TYPES.key(type).to_s)
159
+ uni.codeType(TYPES.key(type).to_s) unless AIXM.ofmx?
175
160
  uni.codeClass(CLASSES.key(self.class).to_s)
176
161
  uni.txtRmk(remarks) if remarks
177
162
  end
178
- services.each.with_object({}) do |service, sequences|
179
- sequences[service.type] = (sequences[service.type] || 0) + 1
180
- builder << service.to_xml(sequence: sequences[service.type])
163
+ services.sort { |a, b| a.type <=> b.type }.each do |service|
164
+ builder << service.to_xml
181
165
  end
182
166
  builder.target!
183
167
  end
168
+
169
+ private
170
+
171
+ def name_with_type
172
+ [name, TYPES.key(type)].join(' ')
173
+ end
184
174
  end
185
175
 
186
176
  end
data/lib/aixm/feature.rb CHANGED
@@ -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
 
@@ -0,0 +1,89 @@
1
+ module AIXM
2
+
3
+ # Memoize the return value of a specific method across multiple instances for
4
+ # the duration of a block.
5
+ #
6
+ # The method signature is taken into account, therefore calls of the same
7
+ # method with different positional and/or keyword arguments are cached
8
+ # independently. On the other hand, when calling the method with a block,
9
+ # no memoization is performed at all.
10
+ #
11
+ # @example
12
+ # class Either
13
+ # include AIXM::Memoize
14
+ #
15
+ # def either(argument=nil, keyword: nil, &block)
16
+ # $entropy || argument || keyword || (block.call if block)
17
+ # end
18
+ # memoize :either
19
+ # end
20
+ #
21
+ # a, b, c = Either.new, Either.new, Either.new
22
+ #
23
+ # # No memoization before the block
24
+ # $entropy = nil
25
+ # a.either(1) # => 1
26
+ # b.either(keyword: 2) # => 2
27
+ # c.either { 3 } # => 3
28
+ # $entropy = :not_nil
29
+ # a.either(1) # => :not_nil
30
+ # b.either(keyword: 2) # => :not_nil
31
+ # c.either { 3 } # => :not_nil
32
+ #
33
+ # # Memoization inside the block
34
+ # AIXM::Memoize.method :either do
35
+ # $entropy = nil
36
+ # a.either(1) # => 1
37
+ # b.either(keyword: 2) # => 2
38
+ # c.either { 3 } # => 3
39
+ # $entropy = :not_nil
40
+ # a.either(1) # => 1 (memoized)
41
+ # b.either(keyword: 2) # => 2 (memoized)
42
+ # c.either { 3 } # => :not_nil (cannot be memoized)
43
+ # end
44
+ #
45
+ # # No memoization after the block
46
+ # $entropy = nil
47
+ # a.either(1) # => 1
48
+ # $entropy = :not_nil
49
+ # a.either(1) # => :not_nil
50
+ module Memoize
51
+ module ClassMethods
52
+ def memoize(method)
53
+ unmemoized_method = :"unmemoized_#{method}"
54
+ alias_method unmemoized_method, method
55
+ define_method method do |*args, **kargs, &block|
56
+ if block || !AIXM::Memoize.cache
57
+ send(unmemoized_method, *args, **kargs, &block)
58
+ else
59
+ id = object_id.hash ^ args.hash ^ kargs.hash
60
+ if AIXM::Memoize.cache.has_key?(id)
61
+ AIXM::Memoize.cache[id]
62
+ else
63
+ AIXM::Memoize.cache[id] = send(unmemoized_method, *args, **kargs, &block)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ class << self
71
+ def included(base)
72
+ base.extend(ClassMethods)
73
+ @cache = {}
74
+ end
75
+
76
+ def method(method)
77
+ @method = method
78
+ @cache[@method] = {}
79
+ yield
80
+ ensure
81
+ @method = nil
82
+ end
83
+
84
+ def cache
85
+ (@cache[@method] ||= {}) if @method
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,9 @@
1
+ # This monkey patch is necessary because some classes have to introduce
2
+ # attributes named +class+ (e.g. for airspace classes as described by
3
+ # +AIXM::Component::Layer+) which clash with this core method. Other parts
4
+ # such as +AIXM::Association+ need the original implementation for introspection
5
+ # which is why this alias +Object#__class__+ makes it globally and consistently
6
+ # available again.
7
+ class Object
8
+ alias_method :__class__, :class
9
+ end
@@ -0,0 +1,114 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+
5
+ # Calculate OFMX-compliant payload hashes.
6
+ #
7
+ # @example with XML fragment string
8
+ # xml = '<xml><a></a></xml>'
9
+ # AIXM::PayloadHash.new(xml).to_uuid
10
+ #
11
+ # @example with Nokogiri fragment
12
+ # document = File.open("file.xml") { Nokogiri::XML(_1) }
13
+ # AIXM::PayloadHash.new(document).to_uuid
14
+ #
15
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Features#mid
16
+ class PayloadHash
17
+ IGNORED_ATTRIBUTES = %w(mid source).freeze
18
+
19
+ # @param fragment [Nokogiri::XML::DocumentFragment, Nokogiri::XML::Element, String] XML fragment
20
+ def initialize(fragment)
21
+ @fragment = case fragment
22
+ when Nokogiri::XML::DocumentFragment then fragment
23
+ when Nokogiri::XML::Element, String then Nokogiri::XML.fragment(fragment)
24
+ else fail ArgumentError
25
+ end
26
+ end
27
+
28
+ # @return [String] UUIDv3
29
+ def to_uuid
30
+ uuid_for payload_array
31
+ end
32
+
33
+ private
34
+
35
+ def payload_array
36
+ @fragment.css('*').each_with_object([]) do |element, array|
37
+ array << element.name.sub(/\A(\w+Uid)\w+/, '\1') # remove name extension
38
+ element.attributes.sort.each do |name, attribute|
39
+ array.push(name, attribute.value) unless IGNORED_ATTRIBUTES.include? name
40
+ end
41
+ array << element.child.text if element.children.one? && element.child.text?
42
+ array << '' if element.children.none?
43
+ end
44
+ end
45
+
46
+ def uuid_for(array)
47
+ ::Digest::MD5.hexdigest(array.flatten.map(&:to_s).join('|')).unpack("a8a4a4a4a12").join("-")
48
+ end
49
+
50
+ # Insert OFMX-compliant payload hashes as mid attributes into an XML
51
+ # document.
52
+ #
53
+ # Keep in mind: If you pass a Nokogiri::XML::Document, the mid attributes
54
+ # are added into this document. In order to leave the original document
55
+ # untouched, you have to `dup` it.
56
+ #
57
+ # @example with XML string
58
+ # string = '<OFMX-Snapshot><Ahp><AhpUid></AhpUid></Ahp></OFMX-Snapshot>'
59
+ # converter = AIXM::PayloadHash::Mid.new(string)
60
+ # converter.insert_mid.to_xml # returns XML as String
61
+ #
62
+ # @example with Nokogiri document
63
+ # document = File.open("file.ofmx") { Nokogiri::XML(_1) }
64
+ # converter = AIXM::PayloadHash::Mid.new(document)
65
+ # converter.insert_mid.to_xml # returns XML as String
66
+ # document.to_xml # returns XML as String as well
67
+ #
68
+ # @see https://gitlab.com/openflightmaps/ofmx/wikis/Features#mid
69
+ class Mid
70
+
71
+ # @param document [Nokogiri::XML::Document, String] XML document
72
+ def initialize(document)
73
+ @document = case document
74
+ when Nokogiri::XML::Document then document
75
+ when String then Nokogiri::XML(document)
76
+ else fail ArgumentError
77
+ end
78
+ end
79
+
80
+ # Insert or update mid attributes on all *Uid elements
81
+ #
82
+ # @return [self]
83
+ def insert_mid
84
+ uid_elements.each do |element|
85
+ element['mid'] = AIXM::PayloadHash.new(element).to_uuid
86
+ end
87
+ self
88
+ end
89
+
90
+ # Check mid attributes on all *Uid elements
91
+ #
92
+ # @return [Array<String>] array of errors found
93
+ def check_mid
94
+ uid_elements.each_with_object([]) do |element, errors|
95
+ unless element['mid'] == (uuid = AIXM::PayloadHash.new(element).to_uuid)
96
+ errors << "#{element.line}: ERROR: Element '#{element.name}': mid should be #{uuid}"
97
+ end
98
+ end
99
+ end
100
+
101
+ # @return [String] XML document as XML string
102
+ def to_xml
103
+ @document.to_xml
104
+ end
105
+
106
+ private
107
+
108
+ def uid_elements
109
+ @document.xpath('//*[contains(local-name(), "Uid")]')
110
+ end
111
+ end
112
+ end
113
+
114
+ end
@@ -30,22 +30,6 @@ module AIXM
30
30
  end
31
31
  end
32
32
 
33
- # @!method to_uuid
34
- # Builds a UUID version 3 digest from the Array payload.
35
- #
36
- # @example
37
- # ['foo', :bar, nil, [123]].to_uuid
38
- # # => "a68e9aae-81ef-c6f1-d954-2eefe2820c50"
39
- #
40
- # @note This is a refinement for +Array+
41
- # @return [String] UUID version 3
42
- refine Array do
43
- def to_uuid
44
- ::Digest::MD5.hexdigest(flatten.map(&:to_s).join('|')).unpack("a8a4a4a4a12").join("-")
45
- end
46
- end
47
-
48
-
49
33
  # @!method to_dms(padding=3)
50
34
  # Convert DD angle to DMS with the degrees zero padded to +padding+
51
35
  # length.
@@ -138,8 +122,8 @@ module AIXM
138
122
  # Same as +Object#then+ but only applied if the condition is true.
139
123
  #
140
124
  # @example
141
- # "foobar".then_if(false) { |s| s.gsub(/o/, 'i') } # => "foobar"
142
- # "foobar".then_if(true) { |s| s.gsub(/o/, 'i') } # => "fiibar"
125
+ # "foobar".then_if(false) { _1.gsub(/o/, 'i') } # => "foobar"
126
+ # "foobar".then_if(true) { _1.gsub(/o/, 'i') } # => "fiibar"
143
127
  #
144
128
  # @note This is a refinement for +Object+
145
129
  # @return [Object]
@@ -162,6 +146,38 @@ module AIXM
162
146
  end
163
147
  end
164
148
 
149
+ # @!method to_class
150
+ # Convert string to class
151
+ #
152
+ # @example
153
+ # "AIXM::Feature::NavigationalAid".to_class
154
+ # # => AIXM::Feature::NavigationalAid
155
+ #
156
+ # @note This is a refinement for +String+
157
+ refine String do
158
+ def to_class
159
+ Object.const_get(self)
160
+ end
161
+ end
162
+
163
+ # @!method inflect
164
+ # Apply inflections from the +dry-inflector+ gem
165
+ #
166
+ # @example
167
+ # s = "AIXM::Feature::NavigationalAid"
168
+ # s.inflect(:demodulize, :tableize, :pluralize)
169
+ # # => "navigational_aids"
170
+ #
171
+ # @see https://www.rubydoc.info/gems/dry-inflector
172
+ # @note This is a refinement for +String+
173
+ refine String do
174
+ def inflect(*inflections)
175
+ inflections.inject(self) do |memo, inflection|
176
+ AIXM.config.inflector.send(inflection, memo)
177
+ end
178
+ end
179
+ end
180
+
165
181
  # @!method indent(number)
166
182
  # Indent every line of a string with +number+ spaces.
167
183
  #
@@ -179,38 +195,6 @@ module AIXM
179
195
  end
180
196
  end
181
197
 
182
- # @!method payload_hash(region:, element:)
183
- # Calculate the UUIDv3 hash of an OFMX XML string.
184
- #
185
- # A word of warning: This is a minimalistic implementation for the AIXM
186
- # gem and won't work unless the following conditions are met:
187
- # * the XML string must be OFMX
188
- # * the XML string must be valid
189
- # * the XML string must be pretty-printed
190
- #
191
- # @example from https://github.com/openflightmaps/ofmx/wiki/Functions
192
- # xml.payload_hash(region: 'LF', element: 'Ser')
193
- # # => "269b1f18-cabe-3c9e-1d71-48a7414a4cb9"
194
- #
195
- # @note This is a refinement for +String+
196
- # @param region [String] OFMX region (e.g. "LF")
197
- # @param element [String] tag to calculate the payload hash for
198
- # @return [String] UUID version 3
199
- refine String do
200
- def payload_hash(region:, element:)
201
- gsub(%r(mid="[^"]*"), ''). # remove existing mid attributes
202
- sub(%r(\A.*?(?=<#{element}))m, ''). # remove everything before first <element>
203
- sub(%r(</#{element}>.*\z)m, ''). # remove everything after first </element>
204
- scan(%r(<([\w-]+)([^>]*)>([^<]*))).each_with_object([region]) do |(e, a, t), m|
205
- m << e << a.scan(%r(([\w-]+)="([^"]*)")).sort.flatten << t
206
- end.
207
- flatten.
208
- keep_if { |s| s.match?(/[^[:space:]]/m) }.
209
- compact.
210
- to_uuid
211
- end
212
- end
213
-
214
198
  # @!method to_dd
215
199
  # Convert DMS angle to DD or +nil+ if the notation is not recognized.
216
200
  #