aixm 0.3.3 → 0.3.4

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +20 -0
  5. data/README.md +7 -2
  6. data/aixm.gemspec +2 -3
  7. data/lib/aixm.rb +6 -2
  8. data/lib/aixm/a.rb +151 -0
  9. data/lib/aixm/component/frequency.rb +2 -2
  10. data/lib/aixm/component/geometry.rb +4 -0
  11. data/lib/aixm/component/geometry/point.rb +0 -4
  12. data/lib/aixm/component/helipad.rb +8 -22
  13. data/lib/aixm/component/runway.rb +36 -36
  14. data/lib/aixm/component/surface.rb +121 -0
  15. data/lib/aixm/component/timetable.rb +1 -0
  16. data/lib/aixm/constants.rb +40 -0
  17. data/lib/aixm/d.rb +5 -0
  18. data/lib/aixm/document.rb +2 -2
  19. data/lib/aixm/f.rb +6 -0
  20. data/lib/aixm/feature/address.rb +100 -0
  21. data/lib/aixm/feature/airport.rb +26 -7
  22. data/lib/aixm/feature/airspace.rb +10 -1
  23. data/lib/aixm/feature/navigational_aid.rb +1 -1
  24. data/lib/aixm/feature/navigational_aid/designated_point.rb +20 -5
  25. data/lib/aixm/feature/navigational_aid/dme.rb +2 -2
  26. data/lib/aixm/{component → feature}/service.rb +67 -16
  27. data/lib/aixm/feature/unit.rb +40 -6
  28. data/lib/aixm/refinements.rb +63 -6
  29. data/lib/aixm/shortcuts.rb +12 -4
  30. data/lib/aixm/version.rb +1 -1
  31. data/lib/aixm/xy.rb +6 -1
  32. data/lib/aixm/z.rb +6 -0
  33. data/schemas/ofmx/0/OFMX-DataTypes.xsd +5 -2
  34. data/schemas/ofmx/0/OFMX-Features.xsd +2 -0
  35. data/spec/factory.rb +32 -10
  36. data/spec/lib/aixm/a_spec.rb +203 -0
  37. data/spec/lib/aixm/component/helipad_spec.rb +11 -17
  38. data/spec/lib/aixm/component/runway_spec.rb +46 -32
  39. data/spec/lib/aixm/component/surface_spec.rb +88 -0
  40. data/spec/lib/aixm/d_spec.rb +10 -0
  41. data/spec/lib/aixm/document_spec.rb +104 -32
  42. data/spec/lib/aixm/f_spec.rb +10 -0
  43. data/spec/lib/aixm/feature/address_spec.rb +55 -0
  44. data/spec/lib/aixm/feature/airport_spec.rb +73 -3
  45. data/spec/lib/aixm/feature/navigational_aid/designated_point_spec.rb +43 -6
  46. data/spec/lib/aixm/feature/navigational_aid/dme_spec.rb +2 -2
  47. data/spec/lib/aixm/feature/navigational_aid/marker_spec.rb +2 -2
  48. data/spec/lib/aixm/feature/navigational_aid/ndb_spec.rb +2 -2
  49. data/spec/lib/aixm/feature/navigational_aid/tacan_spec.rb +2 -2
  50. data/spec/lib/aixm/feature/navigational_aid/vor_spec.rb +6 -6
  51. data/spec/lib/aixm/{component → feature}/service_spec.rb +12 -14
  52. data/spec/lib/aixm/feature/unit_spec.rb +7 -4
  53. data/spec/lib/aixm/refinements_spec.rb +100 -15
  54. data/spec/lib/aixm/z_spec.rb +10 -0
  55. metadata +17 -25
  56. data/lib/aixm/h.rb +0 -87
  57. data/spec/lib/aixm/h_spec.rb +0 -113
@@ -0,0 +1,121 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Component
5
+
6
+ # Surface of a runway, helipad etc
7
+ #
8
+ # ===Cheat Sheet in Pseudo Code:
9
+ # surface = AIXM.surfaceservice(
10
+ # composition: COMPOSITIONS or nil
11
+ # preparation: PREPARATIONS or nil
12
+ # condition: CONDITIONS or nil
13
+ # )
14
+ # surface.pcn = String
15
+ # surface.remarks = String or nil
16
+ #
17
+ # ===Constants:
18
+ # * +AIXM::PCN_RE+ - regular expression to match PCN notations
19
+ #
20
+ #
21
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airport#rwy-runway
22
+ class Surface
23
+ COMPOSITIONS = {
24
+ ASPH: :asphalt,
25
+ BITUM: :bitumen, # dug up, bound and rolled ground
26
+ CONC: :concrete,
27
+ GRADE: :graded_earth, # graded or rolled earth possibly with some grass
28
+ GRASS: :grass, # lawn
29
+ GRAVE: :gravel, # small and midsize rounded stones
30
+ MACADAM: :macadam, # small rounded stones
31
+ SAND: :sand,
32
+ WATER: :water,
33
+ OTHER: :other # specify in remarks
34
+ }
35
+
36
+ PREPARATIONS = {
37
+ AFSC: :aggregate_friction_seal_coat,
38
+ GROOVED: :grooved, # cut or plastic grooved
39
+ NATURAL: :natural, # no treatment
40
+ OILED: :oiled,
41
+ PAVED: :paved,
42
+ PFC: :porous_friction_course,
43
+ RFSC: :rubberized_friction_seal_coat,
44
+ ROLLED: :rolled,
45
+ OTHER: :other
46
+ }
47
+
48
+ CONDITIONS = {
49
+ GOOD: :good,
50
+ FAIR: :fair,
51
+ POOR: :poor,
52
+ OTHER: :other
53
+ }
54
+
55
+ # @return [Symbol, nil] composition of the surface (see {COMPOSITIONS})
56
+ attr_reader :composition
57
+
58
+ # @return [Symbol, nil] preparation of the surface (see {PREPARATIONS})
59
+ attr_reader :preparation
60
+
61
+ # @return [Symbol, nil] condition of the surface (see {CONDITIONS})
62
+ attr_reader :condition
63
+
64
+ # @return [String, nil] free text remarks
65
+ attr_reader :remarks
66
+
67
+ def initialize
68
+ @pcn = {}
69
+ end
70
+
71
+ # @return [String]
72
+ def inspect
73
+ %Q(#<#{self.class} composition=#{composition.inspect} preparation=#{preparation.inspect} condition=#{condition.inspect} pcn=#{pcn.inspect}>)
74
+ end
75
+
76
+ def composition=(value)
77
+ @composition = value.nil? ? nil : COMPOSITIONS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid composition")
78
+ end
79
+
80
+ def preparation=(value)
81
+ @preparation = value.nil? ? nil : PREPARATIONS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid preparation")
82
+ end
83
+
84
+ def condition=(value)
85
+ @condition = value.nil? ? nil : CONDITIONS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid condition")
86
+ end
87
+
88
+ # @return [String, nil] pavement classification number (e.g. "59/F/A/W/T")
89
+ def pcn
90
+ @pcn.none? ? nil : @pcn.values.join("/")
91
+ end
92
+
93
+ def pcn=(value)
94
+ return @pcn = {} if value.nil?
95
+ fail(ArgumentError, "invalid PCN") unless match = value.to_s.upcase.match(PCN_RE)
96
+ @pcn = match.named_captures.reject{ |k| k == 'pcn' }
97
+ end
98
+
99
+ def remarks=(value)
100
+ @remarks = value&.to_s
101
+ end
102
+
103
+ # @return [String] AIXM or OFMX markup
104
+ def to_xml
105
+ builder = Builder::XmlMarkup.new(indent: true)
106
+ builder.codeComposition(COMPOSITIONS.key(composition).to_s) if composition
107
+ builder.codePreparation(PREPARATIONS.key(preparation).to_s) if preparation
108
+ builder.codeCondSfc(CONDITIONS.key(condition).to_s) if condition
109
+ if pcn
110
+ builder.valPcnClass(@pcn['capacity'])
111
+ builder.codePcnPavementType(@pcn['type'])
112
+ builder.codePcnPavementSubgrade(@pcn['subgrade'])
113
+ builder.codePcnMaxTirePressure(@pcn['tire_pressure'])
114
+ builder.codePcnEvalMethod(@pcn['evaluation_method'])
115
+ end
116
+ builder.txtPcnNote(@remarks) if remarks
117
+ builder.target!
118
+ end
119
+ end
120
+ end
121
+ end
@@ -15,6 +15,7 @@ module AIXM
15
15
  #
16
16
  # ===Shortcuts:
17
17
  # * +AIXM::H24+ - continuous, all day and all night
18
+ # * +AIXM::H_RE+ - pattern matching working hour codes
18
19
  #
19
20
  # @see https://github.com/openflightmaps/ofmx/wiki/Timetable#predefined-timetable
20
21
  class Timetable
@@ -0,0 +1,40 @@
1
+ module AIXM
2
+
3
+ # Characters recognized as symbols for "minute" in DMS notations
4
+ MIN = %Q('\u2018\u2019\u00b4).freeze
5
+
6
+ # Characters recognized as symbols for "second" in DMS notations
7
+ SEC = %Q("\u201c\u201d\u201f).freeze
8
+
9
+ # Pattern matching geographical coordinates in various DMS notations
10
+ DMS_RE = %r(
11
+ (?<dms>
12
+ (?<sgn>-)?
13
+ (?<deg>\d{1,3})[° ]{1,2}
14
+ (?<min>\d{2})[#{MIN}#{SEC} ]{1,2}
15
+ (?<sec>\d{2}(?:[\.,]\d{0,2})?)[#{SEC}#{MIN} ]{0,2}
16
+ (?<hem_ne>[NE])?(?<hem_sw>[SW])?
17
+ |
18
+ (?<sgn>-)?
19
+ (?<deg>\d{1,3})
20
+ (?<min>\d{2})
21
+ (?<sec>\d{2}(?:[\.,]\d{0,2})?)
22
+ (?:(?<hem_ne>[NE])|(?<hem_sw>[SW]))
23
+ )
24
+ )xi.freeze
25
+
26
+ # Pattern matching PCN surface strength notations
27
+ PCN_RE = %r(
28
+ (?<pcn>
29
+ (?<capacity>\d+)\W+
30
+ (?<type>[RF])\W+
31
+ (?<subgrade>[A-D])\W+
32
+ (?<tire_pressure>[W-Z])\W+
33
+ (?<evaluation_method>[TU])
34
+ )
35
+ )x.freeze
36
+
37
+ # Pattern matching timetable working hour codes
38
+ H_RE = /H(?:24|J|N|X|O)/.freeze
39
+
40
+ end
@@ -8,6 +8,7 @@ module AIXM
8
8
  # AIXM.d(123, :m)
9
9
  class D
10
10
  include Comparable
11
+ extend Forwardable
11
12
 
12
13
  UNITS = {
13
14
  ft: { km: 0.0003048, m: 0.3048, nm: 0.000164578833554 },
@@ -16,6 +17,10 @@ module AIXM
16
17
  nm: { ft: 6076.11548554, km: 1.852, m: 1852 }
17
18
  }.freeze
18
19
 
20
+ # @!method zero?
21
+ # @return [Boolean] whether length is zero
22
+ def_delegator :@dist, :zero?
23
+
19
24
  # @return [Float] distance
20
25
  attr_reader :dist
21
26
 
@@ -15,7 +15,7 @@ module AIXM
15
15
  #
16
16
  # @see https://github.com/openflightmaps/ofmx/wiki/Snapshot
17
17
  class Document
18
- NAMESPACE_PATTERN = /\A[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}\z/.freeze
18
+ 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
19
19
 
20
20
  # @return [String] OFMX region all features in this document belong to
21
21
  attr_reader :region
@@ -48,7 +48,7 @@ module AIXM
48
48
  end
49
49
 
50
50
  def namespace=(value)
51
- fail(ArgumentError, "invalid namespace") unless value.nil? || value.match?(NAMESPACE_PATTERN)
51
+ fail(ArgumentError, "invalid namespace") unless value.nil? || value.match?(NAMESPACE_RE)
52
52
  @namespace = value || SecureRandom.uuid
53
53
  end
54
54
 
@@ -7,8 +7,14 @@ module AIXM
7
7
  # @example
8
8
  # AIXM.f(123.35, :mhz)
9
9
  class F
10
+ extend Forwardable
11
+
10
12
  UNITS = %i(ghz mhz khz).freeze
11
13
 
14
+ # @!method zero?
15
+ # @return [Boolean] whether frequency is zero
16
+ def_delegator :@freq, :zero?
17
+
12
18
  # @return [Float] frequency
13
19
  attr_reader :freq
14
20
 
@@ -0,0 +1,100 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Feature
5
+
6
+ # Address or similar means to contact an entity.
7
+ #
8
+ # ===Cheat Sheet in Pseudo Code:
9
+ # address = AIXM.address(
10
+ # source: String or nil
11
+ # type: TYPES
12
+ # address: String
13
+ # )
14
+ # service.remarks = String or nil
15
+ #
16
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airport#aha-airport-address
17
+ class Address < Feature
18
+ public_class_method :new
19
+
20
+ TYPES = {
21
+ POST: :postal_address,
22
+ PHONE: :phone,
23
+ 'PHONE-MET': :weather_phone,
24
+ FAX: :fax,
25
+ TLX: :telex,
26
+ SITA: :sita,
27
+ AFS: :aeronautical_fixed_service_address,
28
+ EMAIL: :email,
29
+ URL: :url,
30
+ 'URL-CAM': :webcam,
31
+ 'URL-MET': :weather_url,
32
+ RADIO: :radio_frequency,
33
+ OTHER: :other # specify in remarks
34
+ }
35
+
36
+ # @return [AIXM::Feature] addressable feature
37
+ attr_reader :addressable
38
+
39
+ # @return [Symbol] type of address (see {TYPES})
40
+ attr_reader :type
41
+
42
+ # @return [String] postal address, phone number, radio frequency etc
43
+ attr_reader :address
44
+
45
+ # @return [String, nil] free text remarks
46
+ attr_reader :remarks
47
+
48
+ def initialize(source: nil, type:, address:)
49
+ super(source: source)
50
+ self.type, self.address = type, address
51
+ end
52
+
53
+ # @return [String]
54
+ def inspect
55
+ %Q(#<#{self.class} type=#{type.inspect}>)
56
+ end
57
+
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
+ def type=(value)
65
+ @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
66
+ end
67
+
68
+ def address=(value)
69
+ fail(ArgumentError, "invalid address") unless value.is_a? String
70
+ @address = value&.to_s
71
+ end
72
+
73
+ def remarks=(value)
74
+ @remarks = value&.to_s
75
+ end
76
+
77
+ # @return [String] UID markup
78
+ def to_uid(as:, sequence:)
79
+ builder = Builder::XmlMarkup.new(indent: 2)
80
+ builder.tag!(as) do |tag|
81
+ tag << addressable.to_uid.indent(2) if addressable
82
+ tag.codeType(TYPES.key(type).to_s.then { |t| AIXM.aixm? ? t.sub(/-\w+$/, '') : t })
83
+ tag.noSeq(sequence)
84
+ end
85
+ end
86
+
87
+ # @return [String] AIXM or OFMX markup
88
+ def to_xml(as:, sequence:)
89
+ builder = Builder::XmlMarkup.new(indent: 2)
90
+ builder.comment! ["Address: #{TYPES.key(type)}", addressable&.id].compact.join(' for ')
91
+ builder.tag!(as, { source: (source if AIXM.ofmx?) }.compact) do |tag|
92
+ tag << to_uid(as: :"#{as}Uid", sequence: sequence).indent(2)
93
+ tag.txtAddress(address)
94
+ tag.txtRmk(remarks) if remarks
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -24,12 +24,13 @@ module AIXM
24
24
  # airport.add_runway(AIXM.runway)
25
25
  # airport.add_helipad(AIXM.helipad)
26
26
  # airport.add_usage_limitation(UsageLimitation::TYPES)
27
+ # airport.add_address(AIXM.address)
27
28
  #
28
29
  # @see https://github.com/openflightmaps/ofmx/wiki/Airport#ahp-airport
29
30
  class Airport < Feature
30
31
  public_class_method :new
31
32
 
32
- ID_PATTERN = /^([A-Z]{3,4}|[A-Z]{2}[A-Z\d]{4,})$/.freeze
33
+ ID_RE = /^([A-Z]{3,4}|[A-Z]{2}[A-Z\d]{4,})$/.freeze
33
34
 
34
35
  TYPES = {
35
36
  AD: :aerodrome,
@@ -90,10 +91,13 @@ module AIXM
90
91
  # @return [Array<AIXM::Feature::Airport::UsageLimitation>] usage limitations
91
92
  attr_accessor :usage_limitations
92
93
 
94
+ # @return [Array<AIXM::Feature::Address>] postal address, url, A/A or A/G frequency etc
95
+ attr_reader :addresses
96
+
93
97
  def initialize(source: nil, organisation:, id:, name:, xy:)
94
98
  super(source: source)
95
99
  self.organisation, self.id, self.name, self.xy = organisation, id, name, xy
96
- @runways, @helipads, @usage_limitations = [], [], []
100
+ @runways, @helipads, @usage_limitations, @addresses = [], [], [], []
97
101
  end
98
102
 
99
103
  # @return [String]
@@ -107,7 +111,7 @@ module AIXM
107
111
  end
108
112
 
109
113
  def id=(value)
110
- fail(ArgumentError, "invalid id `#{id}'") unless value&.upcase&.match? ID_PATTERN
114
+ fail(ArgumentError, "invalid id `#{id}'") unless value&.upcase&.match? ID_RE
111
115
  @id = value.upcase
112
116
  end
113
117
 
@@ -155,7 +159,7 @@ module AIXM
155
159
  def declination=(value)
156
160
  return @declination = value if value.nil?
157
161
  fail(ArgumentError, "invalid declination") unless value.is_a?(Numeric) && (-180..180).include?(value)
158
- @declination = value.to_f
162
+ @declination = value.to_f + 0 # adding zero prevents -0.0
159
163
  end
160
164
 
161
165
  def transition_z=(value)
@@ -229,11 +233,22 @@ module AIXM
229
233
  self
230
234
  end
231
235
 
236
+ # Add an address (postal address, url, A/A or A/G frequency etc) to the airport.
237
+ #
238
+ # @params address [AIXM::Feature::Address] address instance
239
+ # @return [self]
240
+ def add_address(address)
241
+ fail(ArgumentError, "invalid address") unless address.is_a? AIXM::Feature::Address
242
+ address.send(:addressable=, self)
243
+ @addresses << address
244
+ self
245
+ end
246
+
232
247
  # @return [String] UID markup
233
- def to_uid
248
+ def to_uid(as: :AhpUid)
234
249
  builder = Builder::XmlMarkup.new(indent: 2)
235
- builder.AhpUid do |ahp_uid|
236
- ahp_uid.codeId(id)
250
+ builder.tag!(as) do |tag|
251
+ tag.codeId(id)
237
252
  end
238
253
  end
239
254
 
@@ -280,6 +295,10 @@ module AIXM
280
295
  end
281
296
  end
282
297
  end
298
+ addresses.each.with_object({}) do |address, sequences|
299
+ sequences[address.type] = (sequences[address.type] || 0) + 1
300
+ builder << address.to_xml(as: :Aha, sequence: sequences[address.type])
301
+ end
283
302
  builder.target!
284
303
  end
285
304
 
@@ -16,6 +16,13 @@ module AIXM
16
16
  # airspace.geometry << AIXM.point or AIXM.arc or AIXM.border or AIXM.circle
17
17
  # airspace.layers << AIXM.layer
18
18
  #
19
+ # Some regions define additional airspace types. In LF (France) for
20
+ # intance, the types RMZ (radio mandatory zone) and TMZ (transponder
21
+ # mandatory zone) exist. Such airspaces are usually specified together
22
+ # with a generic type such as +:regulated_airspace+:
23
+ #
24
+ # airspace= AIXM.airspace(type: :regulated_airspace, local_type: "RMZ")
25
+ #
19
26
  # @see https://github.com/openflightmaps/ofmx/wiki/Airspace#ase-airspace
20
27
  class Airspace < Feature
21
28
  public_class_method :new
@@ -71,7 +78,9 @@ module AIXM
71
78
  # @return [Symbol] type of airspace (see {TYPES})
72
79
  attr_reader :type
73
80
 
74
- # @return [String, nil] short name (e.g. "LF P 81")
81
+ # Some regions define additional types. They are usually specified with
82
+ #
83
+ # @return [String, nil] local type (e.g. "RMZ" or "TMZ")
75
84
  attr_reader :local_type
76
85
 
77
86
  # @return [String, nil] full name (e.g. "LF P 81 CHERBOURG")
@@ -85,7 +85,7 @@ module AIXM
85
85
 
86
86
  def to_builder
87
87
  builder = Builder::XmlMarkup.new(indent: 2)
88
- builder.comment! "NavigationalAid: [#{kind}] #{name || :UNNAMED}"
88
+ builder.comment! "NavigationalAid: [#{kind}] #{[id, name].compact.join(' / ')}"
89
89
  builder
90
90
  end
91
91
  end
@@ -15,6 +15,7 @@ module AIXM
15
15
  # xy: AIXM.xy
16
16
  # type: TYPES
17
17
  # )
18
+ # designated_point.airport = AIXM.airport or nil
18
19
  # designated_point.remarks = String or nil
19
20
  #
20
21
  # @see https://github.com/openflightmaps/ofmx/wiki/Navigational-aid#dpn-designated-point
@@ -24,15 +25,23 @@ module AIXM
24
25
  private :organisation
25
26
 
26
27
  TYPES = {
27
- ICAO: :icao, # five-letter ICAO name
28
- ADHP: :adhp, # airport related name
29
- COORD: :coordinates, # derived from geographical coordinates
30
- OTHER: :other # specify in remarks
28
+ ICAO: :icao, # five-letter ICAO id
29
+ ADHP: :adhp, # airport related id
30
+ COORD: :coordinates, # derived from geographical coordinates
31
+ 'VFR-RP': :vfr_reporting_point, # usually one or two letter id
32
+ 'VFR-MRP': :vfr_mandatory_reporting_point, # usually one or two letter id
33
+ 'VFR-ENR': :vfr_en_route_point,
34
+ 'VFR-GLD': :vfr_glider_point,
35
+ OTHER: :other # specify in remarks
31
36
  }.freeze
32
37
 
33
38
  # @return [Symbol] type of designated point
34
39
  attr_reader :type
35
40
 
41
+ # @return [AIXM::Feature::Airport] airport this designated point is
42
+ # associated with
43
+ attr_reader :airport
44
+
36
45
  def initialize(type:, **arguments)
37
46
  super(organisation: false, z: nil, **arguments)
38
47
  self.type = type
@@ -42,6 +51,11 @@ module AIXM
42
51
  @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
43
52
  end
44
53
 
54
+ def airport=(value)
55
+ fail(ArgumentError, "invalid airport") unless value.nil? || value.is_a?(AIXM::Feature::Airport)
56
+ @airport = value
57
+ end
58
+
45
59
  # @return [String] UID markup
46
60
  def to_uid
47
61
  builder = Builder::XmlMarkup.new(indent: 2)
@@ -57,8 +71,9 @@ module AIXM
57
71
  builder = to_builder
58
72
  builder.Dpn({ source: (source if AIXM.ofmx?) }.compact) do |dpn|
59
73
  dpn << to_uid.indent(2)
74
+ dpn << airport.to_uid(as: :AhpUidAssoc).indent(2) if airport
60
75
  dpn.codeDatum('WGE')
61
- dpn.codeType(type_key.to_s)
76
+ dpn.codeType(AIXM.aixm? && type_key =~ /^VFR/ ? 'OTHER' : type_key.to_s)
62
77
  dpn.txtName(name) if name
63
78
  dpn.txtRmk(remarks) if remarks
64
79
  dpn.target!