aixm 0.3.0 → 0.3.1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +96 -72
  4. data/README.md +0 -1
  5. data/lib/aixm/component/geometry.rb +8 -2
  6. data/lib/aixm/component/layer.rb +83 -2
  7. data/lib/aixm/document.rb +11 -2
  8. data/lib/aixm/errors.rb +14 -2
  9. data/lib/aixm/feature.rb +2 -14
  10. data/lib/aixm/feature/airport.rb +19 -18
  11. data/lib/aixm/feature/airspace.rb +19 -21
  12. data/lib/aixm/feature/navigational_aid.rb +2 -2
  13. data/lib/aixm/feature/navigational_aid/designated_point.rb +1 -2
  14. data/lib/aixm/feature/navigational_aid/dme.rb +25 -4
  15. data/lib/aixm/feature/navigational_aid/marker.rb +1 -2
  16. data/lib/aixm/feature/navigational_aid/ndb.rb +1 -2
  17. data/lib/aixm/feature/navigational_aid/tacan.rb +5 -2
  18. data/lib/aixm/feature/navigational_aid/vor.rb +3 -4
  19. data/lib/aixm/feature/organisation.rb +3 -4
  20. data/lib/aixm/feature/unit.rb +3 -4
  21. data/lib/aixm/version.rb +1 -1
  22. data/schemas/ofmx/0/OFMX-CSV-Obstacle.json +209 -0
  23. data/schemas/ofmx/0/OFMX-CSV.json +12 -0
  24. data/schemas/ofmx/0/OFMX-DataTypes.xsd +64 -5
  25. data/schemas/ofmx/0/OFMX-Features.xsd +73 -23
  26. data/schemas/ofmx/0/OFMX-Snapshot.xsd +7 -2
  27. data/spec/factory.rb +8 -18
  28. data/spec/lib/aixm/component/geometry_spec.rb +27 -4
  29. data/spec/lib/aixm/component/helipad_spec.rb +2 -2
  30. data/spec/lib/aixm/component/layer_spec.rb +27 -0
  31. data/spec/lib/aixm/component/runway_spec.rb +13 -13
  32. data/spec/lib/aixm/document_spec.rb +56 -42
  33. data/spec/lib/aixm/feature/airport_spec.rb +15 -15
  34. data/spec/lib/aixm/feature/airspace_spec.rb +38 -30
  35. data/spec/lib/aixm/feature/navigational_aid/designated_point_spec.rb +2 -2
  36. data/spec/lib/aixm/feature/navigational_aid/dme_spec.rb +24 -7
  37. data/spec/lib/aixm/feature/navigational_aid/marker_spec.rb +4 -4
  38. data/spec/lib/aixm/feature/navigational_aid/ndb_spec.rb +4 -4
  39. data/spec/lib/aixm/feature/navigational_aid/tacan_spec.rb +24 -7
  40. data/spec/lib/aixm/feature/navigational_aid/vor_spec.rb +14 -14
  41. data/spec/lib/aixm/feature/organisation_spec.rb +2 -2
  42. data/spec/lib/aixm/feature/unit_spec.rb +11 -11
  43. data/spec/lib/aixm/feature_spec.rb +0 -20
  44. metadata +4 -2
@@ -7,8 +7,8 @@ module AIXM
7
7
  # @return [String] reference to source of the feature data
8
8
  attr_reader :source
9
9
 
10
- def initialize(source: nil, region: nil)
11
- self.source, self.region = source, region
10
+ def initialize(source: nil)
11
+ self.source = source
12
12
  end
13
13
 
14
14
  # @return [String] reference to source of the feature data
@@ -17,18 +17,6 @@ module AIXM
17
17
  @source = value
18
18
  end
19
19
 
20
- # @!attribute region
21
- # @note When assigning +nil+, the global default +AIXM.config.region+ is written instead.
22
- # @return [String] region the feature belongs to
23
- def region
24
- @region || AIXM.config.region&.upcase
25
- end
26
-
27
- def region=(value)
28
- fail(ArgumentError, "invalid region") unless value.nil? || value.is_a?(String)
29
- @region = value&.upcase
30
- end
31
-
32
20
  # @return [Boolean]
33
21
  def ==(other)
34
22
  other.is_a?(self.class) && self.to_uid == other.to_uid
@@ -9,9 +9,8 @@ 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 (falls back to AIXM.config.region)
13
12
  # organisation: AIXM.organisation
14
- # code: String
13
+ # id: String
15
14
  # name: String
16
15
  # xy: AIXM.xy
17
16
  # )
@@ -30,7 +29,7 @@ module AIXM
30
29
  class Airport < Feature
31
30
  public_class_method :new
32
31
 
33
- CODE_PATTERN = /^[A-Z]{2}([A-Z]{1,2}|\d{4})$/.freeze
32
+ ID_PATTERN = /^([A-Z]{3,4}|[A-Z]{2}[A-Z\d]{4,})$/.freeze
34
33
 
35
34
  TYPES = {
36
35
  AD: :aerodrome,
@@ -42,14 +41,16 @@ module AIXM
42
41
  # @return [AIXM::Feature::Organisation] superior organisation
43
42
  attr_reader :organisation
44
43
 
45
- # ICAO indicator, IATA indicator or ICAO serial number
44
+ # ICAO indicator, IATA indicator or generated indicator
46
45
  #
47
46
  # * four letter ICAO indicator (e.g. "LFMV")
48
47
  # * three letter IATA indicator (e.g. "AVN")
49
48
  # * two letter ICAO country code + four digit number (e.g. "LF1234")
49
+ # * two letter ICAO country code + at least four letters/digits (e.g.
50
+ # "LFFOOBAR123" or "LF" + GPS code)
50
51
  #
51
- # @return [String] airport indicator code
52
- attr_reader :code
52
+ # @return [String] airport indicator
53
+ attr_reader :id
53
54
 
54
55
  # @return [String] full name
55
56
  attr_reader :name
@@ -89,15 +90,15 @@ module AIXM
89
90
  # @return [Array<AIXM::Feature::Airport::UsageLimitation>] usage limitations
90
91
  attr_accessor :usage_limitations
91
92
 
92
- def initialize(source: nil, region: nil, organisation:, code:, name:, xy:)
93
- super(source: source, region: region)
94
- self.organisation, self.code, self.name, self.xy = organisation, code, name, xy
93
+ def initialize(source: nil, organisation:, id:, name:, xy:)
94
+ super(source: source)
95
+ self.organisation, self.id, self.name, self.xy = organisation, id, name, xy
95
96
  @runways, @helipads, @usage_limitations = [], [], []
96
97
  end
97
98
 
98
99
  # @return [String]
99
100
  def inspect
100
- %Q(#<#{self.class} code=#{code.inspect}>)
101
+ %Q(#<#{self.class} id=#{id.inspect}>)
101
102
  end
102
103
 
103
104
  def organisation=(value)
@@ -105,9 +106,9 @@ module AIXM
105
106
  @organisation = value
106
107
  end
107
108
 
108
- def code=(value)
109
- fail(ArgumentError, "invalid code `#{code}'") unless value&.upcase&.match? CODE_PATTERN
110
- @code = value.upcase
109
+ def id=(value)
110
+ fail(ArgumentError, "invalid id `#{id}'") unless value&.upcase&.match? ID_PATTERN
111
+ @id = value.upcase
111
112
  end
112
113
 
113
114
  def name=(value)
@@ -230,21 +231,21 @@ module AIXM
230
231
  # @return [String] UID markup
231
232
  def to_uid
232
233
  builder = Builder::XmlMarkup.new(indent: 2)
233
- builder.AhpUid({ region: (region if AIXM.ofmx?) }.compact) do |ahp_uid|
234
- ahp_uid.codeId(code)
234
+ builder.AhpUid do |ahp_uid|
235
+ ahp_uid.codeId(id)
235
236
  end
236
237
  end
237
238
 
238
239
  # @return [String] AIXM or OFMX markup
239
240
  def to_xml
240
241
  builder = Builder::XmlMarkup.new(indent: 2)
241
- builder.comment! "Airport: #{code} #{name}"
242
+ builder.comment! "Airport: #{id} #{name}"
242
243
  builder.Ahp({ source: (source if AIXM.ofmx?) }.compact) do |ahp|
243
244
  ahp << to_uid.indent(2)
244
245
  ahp << organisation.to_uid.indent(2)
245
246
  ahp.txtName(name)
246
- ahp.codeIcao(code) if code.length == 4
247
- ahp.codeIata(code) if code.length == 3
247
+ ahp.codeIcao(id) if id.length == 4
248
+ ahp.codeIata(id) if id.length == 3
248
249
  ahp.codeGps(gps) if AIXM.ofmx? && gps
249
250
  ahp.codeType(TYPES.key(type).to_s) if type
250
251
  ahp.geoLat(xy.lat(AIXM.schema))
@@ -8,11 +8,10 @@ module AIXM
8
8
  # ===Cheat Sheet in Pseudo Code:
9
9
  # airspace = AIXM.airspace(
10
10
  # source: String or nil
11
- # region: String or nil (falls back to AIXM.config.region)
12
11
  # id: String
13
12
  # type: String or Symbol
13
+ # local_type: String or nil
14
14
  # name: String or nil
15
- # short_name: String or nil
16
15
  # )
17
16
  # airspace.geometry << AIXM.point or AIXM.arc or AIXM.border or AIXM.circle
18
17
  # airspace.layers << AIXM.layer
@@ -65,28 +64,28 @@ module AIXM
65
64
  }.freeze
66
65
 
67
66
  # @note When assigning +nil+, a 4 byte hex derived from {#type}, {#name}
68
- # and {#short_name} is written instead.
67
+ # and {#local_type} is written instead.
69
68
  # @return [String] published identifier (e.g. "LFP81")
70
69
  attr_reader :id
71
70
 
72
71
  # @return [Symbol] type of airspace (see {TYPES})
73
72
  attr_reader :type
74
73
 
74
+ # @return [String, nil] short name (e.g. "LF P 81")
75
+ attr_reader :local_type
76
+
75
77
  # @return [String, nil] full name (e.g. "LF P 81 CHERBOURG")
76
78
  attr_reader :name
77
79
 
78
- # @return [String, nil] short name (e.g. "LF P 81")
79
- attr_reader :short_name
80
-
81
80
  # @return [AIXM::Component::Geometry] horizontal geometrical shape
82
81
  attr_accessor :geometry
83
82
 
84
83
  # @return [Array<AIXM::Compoment::Layer>] vertical layers
85
84
  attr_accessor :layers
86
85
 
87
- def initialize(source: nil, region: nil, id: nil, type:, name: nil, short_name: nil)
88
- super(source: source, region: region)
89
- self.type, self.name, self.short_name = type, name, short_name
86
+ def initialize(source: nil, id: nil, type:, local_type: nil, name: nil)
87
+ super(source: source)
88
+ self.type, self.local_type, self.name = type, local_type, name
90
89
  self.id = id
91
90
  @geometry = AIXM.geometry
92
91
  @layers = []
@@ -99,27 +98,27 @@ module AIXM
99
98
 
100
99
  def id=(value)
101
100
  fail(ArgumentError, "invalid id") unless value.nil? || value.is_a?(String)
102
- @id = value&.uptrans || [type, name, short_name].to_digest.upcase
101
+ @id = value&.uptrans || [type, local_type, name].to_digest.upcase
103
102
  end
104
103
 
105
104
  def type=(value)
106
105
  @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
107
106
  end
108
107
 
108
+ def local_type=(value)
109
+ fail(ArgumentError, "invalid short name") unless value.nil? || value.is_a?(String)
110
+ @local_type = value&.uptrans
111
+ end
112
+
109
113
  def name=(value)
110
114
  fail(ArgumentError, "invalid name") unless value.nil? || value.is_a?(String)
111
115
  @name = value&.uptrans
112
116
  end
113
117
 
114
- def short_name=(value)
115
- fail(ArgumentError, "invalid short name") unless value.nil? || value.is_a?(String)
116
- @short_name = value&.uptrans
117
- end
118
-
119
118
  # @return [String] UID markup
120
119
  def to_uid(as: :AseUid)
121
120
  builder = Builder::XmlMarkup.new(indent: 2)
122
- builder.tag!(as, { region: (region if AIXM.ofmx?) }.compact) do |tag|
121
+ builder.tag!(as) do |tag|
123
122
  tag.codeType(TYPES.key(type).to_s)
124
123
  tag.codeId(id)
125
124
  end
@@ -129,15 +128,14 @@ module AIXM
129
128
  # @raise [AIXM::LayerError] if no layers are defined
130
129
  # @return [String] AIXM or OFMX markup
131
130
  def to_xml
132
- fail(LayerError, "no layers defined") unless layers.any?
131
+ fail(LayerError.new("no layers defined", self)) unless layers.any?
133
132
  builder = Builder::XmlMarkup.new(indent: 2)
134
133
  builder.comment! "Airspace: [#{TYPES.key(type)}] #{name || :UNNAMED}"
135
134
  builder.Ase({
136
- source: (source if AIXM.ofmx?),
137
- classLayers: (layers.count if AIXM.ofmx? && layered?)
135
+ source: (source if AIXM.ofmx?)
138
136
  }.compact) do |ase|
139
137
  ase << to_uid.indent(2)
140
- ase.txtLocalType(short_name) if short_name && short_name != name
138
+ ase.txtLocalType(local_type) if local_type && local_type != name
141
139
  ase.txtName(name) if name
142
140
  unless layered?
143
141
  ase << layers.first.to_xml.indent(2)
@@ -151,7 +149,7 @@ module AIXM
151
149
  end
152
150
  if layered?
153
151
  layers.each.with_index do |layer, index|
154
- layer_airspace = AIXM.airspace(region: region, type: 'CLASS', name: "#{name} LAYER #{index + 1}")
152
+ layer_airspace = AIXM.airspace(type: 'CLASS', name: "#{name} LAYER #{index + 1}")
155
153
  builder.Ase do |ase|
156
154
  ase << layer_airspace.to_uid.indent(2)
157
155
  ase.txtName(layer_airspace.name)
@@ -28,8 +28,8 @@ module AIXM
28
28
  # @return [String, nil] free text remarks
29
29
  attr_reader :remarks
30
30
 
31
- def initialize(source: nil, region: nil, organisation:, id:, name: nil, xy:, z: nil)
32
- super(source: source, region: region)
31
+ def initialize(source: nil, organisation:, id:, name: nil, xy:, z: nil)
32
+ super(source: source)
33
33
  self.organisation, self.id, self.name, self.xy, self.z = organisation, id, name, xy, z
34
34
  end
35
35
 
@@ -10,7 +10,6 @@ module AIXM
10
10
  # ===Cheat Sheet in Pseudo Code:
11
11
  # designated_point = AIXM.designated_point(
12
12
  # source: String or nil
13
- # region: String or nil (falls back to AIXM.config.region)
14
13
  # id: String
15
14
  # name: String or nil
16
15
  # xy: AIXM.xy
@@ -46,7 +45,7 @@ module AIXM
46
45
  # @return [String] UID markup
47
46
  def to_uid
48
47
  builder = Builder::XmlMarkup.new(indent: 2)
49
- builder.DpnUid({ region: (region if AIXM.ofmx?) }.compact) do |dpn_uid|
48
+ builder.DpnUid do |dpn_uid|
50
49
  dpn_uid.codeId(id)
51
50
  dpn_uid.geoLat(xy.lat(AIXM.schema))
52
51
  dpn_uid.geoLong(xy.long(AIXM.schema))
@@ -12,7 +12,6 @@ module AIXM
12
12
  # ===Cheat Sheet in Pseudo Code:
13
13
  # dme = AIXM.dme(
14
14
  # source: String or nil
15
- # region: String or nil (falls back to AIXM.config.region)
16
15
  # organisation: AIXM.organisation
17
16
  # id: String
18
17
  # name: String
@@ -27,6 +26,8 @@ module AIXM
27
26
  class DME < NavigationalAid
28
27
  public_class_method :new
29
28
 
29
+ CHANNEL_PATTERN = /\A([1-9]|[1-9]\d|1[0-1]\d|12[0-6])[XY]\z/.freeze
30
+
30
31
  # @return [String] radio channel
31
32
  attr_reader :channel
32
33
 
@@ -39,8 +40,24 @@ module AIXM
39
40
  end
40
41
 
41
42
  def channel=(value)
42
- fail(ArgumentError, "invalid channel") unless value.is_a? String
43
- @channel = value.upcase
43
+ fail(ArgumentError, "invalid channel") unless value.is_a?(String) && value.match?(CHANNEL_PATTERN)
44
+ @channel = value
45
+ end
46
+
47
+ # @return [AIXM::F] ghost frequency matching the channel
48
+ def ghost_f
49
+ if channel
50
+ number, letter = channel.split(/(?=[XY])/)
51
+ integer = case number.to_i
52
+ when (1..16) then 13430
53
+ when (17..59) then 10630
54
+ when (60..69) then 12730
55
+ when (70..126) then 10530
56
+ end
57
+ integer += number.to_i * 10
58
+ integer += 5 if letter == 'Y'
59
+ AIXM.f(integer.to_f / 100, :mhz)
60
+ end
44
61
  end
45
62
 
46
63
  def vor=(value)
@@ -52,7 +69,7 @@ module AIXM
52
69
  # @return [String] UID markup
53
70
  def to_uid
54
71
  builder = Builder::XmlMarkup.new(indent: 2)
55
- builder.DmeUid({ region: (region if AIXM.ofmx?) }.compact) do |dme_uid|
72
+ builder.DmeUid do |dme_uid|
56
73
  dme_uid.codeId(id)
57
74
  dme_uid.geoLat(xy.lat(AIXM.schema))
58
75
  dme_uid.geoLong(xy.long(AIXM.schema))
@@ -68,6 +85,10 @@ module AIXM
68
85
  dme << vor.to_uid.indent(2) if vor
69
86
  dme.txtName(name) if name
70
87
  dme.codeChannel(channel)
88
+ unless vor
89
+ dme.valGhostFreq(ghost_f.freq.trim)
90
+ dme.uomGhostFreq('MHZ')
91
+ end
71
92
  dme.codeDatum('WGE')
72
93
  if z
73
94
  dme.valElev(z.alt)
@@ -11,7 +11,6 @@ module AIXM
11
11
  # ===Cheat Sheet in Pseudo Code:
12
12
  # marker = AIXM.marker(
13
13
  # source: String or nil
14
- # region: String or nil (falls back to AIXM.config.region)
15
14
  # organisation: AIXM.organisation
16
15
  # id: String
17
16
  # name: String
@@ -54,7 +53,7 @@ module AIXM
54
53
  # @return [String] UID markup
55
54
  def to_uid
56
55
  builder = Builder::XmlMarkup.new(indent: 2)
57
- builder.MkrUid({ region: (region if AIXM.ofmx?) }.compact) do |mkr_uid|
56
+ builder.MkrUid do |mkr_uid|
58
57
  mkr_uid.codeId(id)
59
58
  mkr_uid.geoLat(xy.lat(AIXM.schema))
60
59
  mkr_uid.geoLong(xy.long(AIXM.schema))
@@ -10,7 +10,6 @@ module AIXM
10
10
  # ===Cheat Sheet in Pseudo Code:
11
11
  # ndb = AIXM.ndb(
12
12
  # source: String or nil
13
- # region: String or nil (falls back to AIXM.config.region)
14
13
  # organisation: AIXM.organisation
15
14
  # id: String
16
15
  # name: String
@@ -56,7 +55,7 @@ module AIXM
56
55
  # @return [String] UID markup
57
56
  def to_uid
58
57
  builder = Builder::XmlMarkup.new(indent: 2)
59
- builder.NdbUid({ region: (region if AIXM.ofmx?) }.compact) do |ndb_uid|
58
+ builder.NdbUid do |ndb_uid|
60
59
  ndb_uid.codeId(id)
61
60
  ndb_uid.geoLat(xy.lat(AIXM.schema))
62
61
  ndb_uid.geoLong(xy.long(AIXM.schema))
@@ -11,7 +11,6 @@ module AIXM
11
11
  # ===Cheat Sheet in Pseudo Code:
12
12
  # tacan = AIXM.tacan(
13
13
  # source: String or nil
14
- # region: String or nil (to use +AIXM.config.region+)
15
14
  # organisation: AIXM.organisation
16
15
  # id: String
17
16
  # name: String
@@ -29,7 +28,7 @@ module AIXM
29
28
  # @return [String] UID markup
30
29
  def to_uid
31
30
  builder = Builder::XmlMarkup.new(indent: 2)
32
- builder.TcnUid({ region: (region if AIXM.ofmx?) }.compact) do |tcn_uid|
31
+ builder.TcnUid do |tcn_uid|
33
32
  tcn_uid.codeId(id)
34
33
  tcn_uid.geoLat(xy.lat(AIXM.schema))
35
34
  tcn_uid.geoLong(xy.long(AIXM.schema))
@@ -45,6 +44,10 @@ module AIXM
45
44
  tcn << vor.to_uid.indent(2) if vor
46
45
  tcn.txtName(name) if name
47
46
  tcn.codeChannel(channel)
47
+ if !vor && AIXM.ofmx?
48
+ tcn.valGhostFreq(ghost_f.freq.trim)
49
+ tcn.uomGhostFreq('MHZ')
50
+ end
48
51
  tcn.codeDatum('WGE')
49
52
  if z
50
53
  tcn.valElev(z.alt)
@@ -11,7 +11,6 @@ module AIXM
11
11
  # ===Cheat Sheet in Pseudo Code:
12
12
  # vor = AIXM.vor(
13
13
  # source: String or nil
14
- # region: String or nil (falls back to AIXM.config.region)
15
14
  # organisation: AIXM.organisation
16
15
  # id: String
17
16
  # name: String
@@ -79,21 +78,21 @@ module AIXM
79
78
  # Associate a DME which turns the VOR into a VOR/DME
80
79
  def associate_dme(channel:)
81
80
  @dme = AIXM.dme(organisation: organisation, id: id, name: name, xy: xy, z: z, channel: channel)
82
- @dme.region, @dme.timetable, @dme.remarks = region, timetable, remarks
81
+ @dme.timetable, @dme.remarks = timetable, remarks
83
82
  @dme.send(:vor=, self)
84
83
  end
85
84
 
86
85
  # Associate a TACAN which turns the VOR into a VORTAC
87
86
  def associate_tacan(channel:)
88
87
  @tacan = AIXM.tacan(organisation: organisation, id: id, name: name, xy: xy, z: z, channel: channel)
89
- @tacan.region, @tacan.timetable, @tacan.remarks = region, timetable, remarks
88
+ @tacan.timetable, @tacan.remarks = timetable, remarks
90
89
  @tacan.send(:vor=, self)
91
90
  end
92
91
 
93
92
  # @return [String] UID markup
94
93
  def to_uid
95
94
  builder = Builder::XmlMarkup.new(indent: 2)
96
- builder.VorUid({ region: (region if AIXM.ofmx?) }.compact) do |vor_uid|
95
+ builder.VorUid do |vor_uid|
97
96
  vor_uid.codeId(id)
98
97
  vor_uid.geoLat(xy.lat(AIXM.schema))
99
98
  vor_uid.geoLong(xy.long(AIXM.schema))
@@ -9,7 +9,6 @@ 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 (falls back to +AIXM.config.region+)
13
12
  # name: String
14
13
  # type: TYPES
15
14
  # )
@@ -44,8 +43,8 @@ module AIXM
44
43
  # @return [String, nil] free text remarks
45
44
  attr_reader :remarks
46
45
 
47
- def initialize(source: nil, region: nil, name:, type:)
48
- super(source: source, region: region)
46
+ def initialize(source: nil, name:, type:)
47
+ super(source: source)
49
48
  self.name, self.type = name, type
50
49
  end
51
50
 
@@ -75,7 +74,7 @@ module AIXM
75
74
  # @return [String] UID markup
76
75
  def to_uid
77
76
  builder = Builder::XmlMarkup.new(indent: 2)
78
- builder.OrgUid({ region: (region if AIXM.ofmx?) }.compact) do |org_uid|
77
+ builder.OrgUid do |org_uid|
79
78
  org_uid.txtName(name)
80
79
  end
81
80
  end