aixm 0.1.3 → 0.2.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +91 -11
  4. data/lib/aixm.rb +10 -0
  5. data/lib/aixm/base.rb +10 -0
  6. data/lib/aixm/component/base.rb +1 -1
  7. data/lib/aixm/component/class_layer.rb +0 -3
  8. data/lib/aixm/component/geometry.rb +0 -3
  9. data/lib/aixm/component/geometry/arc.rb +4 -8
  10. data/lib/aixm/component/geometry/base.rb +8 -0
  11. data/lib/aixm/component/geometry/border.rb +3 -8
  12. data/lib/aixm/component/geometry/circle.rb +5 -9
  13. data/lib/aixm/component/geometry/point.rb +6 -13
  14. data/lib/aixm/component/schedule.rb +8 -10
  15. data/lib/aixm/component/vertical_limits.rb +1 -4
  16. data/lib/aixm/document.rb +9 -6
  17. data/lib/aixm/f.rb +41 -0
  18. data/lib/aixm/feature/airspace.rb +5 -9
  19. data/lib/aixm/feature/base.rb +6 -0
  20. data/lib/aixm/feature/navigational_aid/base.rb +46 -0
  21. data/lib/aixm/feature/navigational_aid/designated_point.rb +69 -0
  22. data/lib/aixm/feature/navigational_aid/dme.rb +54 -0
  23. data/lib/aixm/feature/navigational_aid/marker.rb +41 -0
  24. data/lib/aixm/feature/navigational_aid/ndb.rb +56 -0
  25. data/lib/aixm/feature/navigational_aid/tacan.rb +54 -0
  26. data/lib/aixm/feature/navigational_aid/vor.rb +92 -0
  27. data/lib/aixm/refinements.rb +23 -2
  28. data/lib/aixm/shortcuts.rb +12 -5
  29. data/lib/aixm/version.rb +1 -1
  30. data/lib/aixm/xy.rb +9 -6
  31. data/lib/aixm/z.rb +22 -11
  32. data/spec/factory.rb +87 -4
  33. data/spec/lib/aixm/component/class_layer_spec.rb +6 -6
  34. data/spec/lib/aixm/component/geometry/arc_spec.rb +16 -16
  35. data/spec/lib/aixm/component/geometry/border_spec.rb +4 -4
  36. data/spec/lib/aixm/component/geometry/circle_spec.rb +10 -10
  37. data/spec/lib/aixm/component/geometry/point_spec.rb +4 -4
  38. data/spec/lib/aixm/component/geometry_spec.rb +21 -21
  39. data/spec/lib/aixm/component/schedule_spec.rb +6 -6
  40. data/spec/lib/aixm/component/vertical_limits_spec.rb +18 -18
  41. data/spec/lib/aixm/document_spec.rb +220 -30
  42. data/spec/lib/aixm/f_spec.rb +58 -0
  43. data/spec/lib/aixm/feature/airspace_spec.rb +5 -5
  44. data/spec/lib/aixm/feature/navigational_aid/base_spec.rb +37 -0
  45. data/spec/lib/aixm/feature/navigational_aid/designated_point_spec.rb +43 -0
  46. data/spec/lib/aixm/feature/navigational_aid/dme_spec.rb +43 -0
  47. data/spec/lib/aixm/feature/navigational_aid/marker_spec.rb +44 -0
  48. data/spec/lib/aixm/feature/navigational_aid/ndb_spec.rb +54 -0
  49. data/spec/lib/aixm/feature/navigational_aid/tacan_spec.rb +43 -0
  50. data/spec/lib/aixm/feature/navigational_aid/vor_spec.rb +58 -0
  51. data/spec/lib/aixm/refinements_spec.rb +27 -0
  52. data/spec/lib/aixm/xy_spec.rb +29 -23
  53. data/spec/lib/aixm/z_spec.rb +33 -17
  54. metadata +29 -2
@@ -1,8 +1,9 @@
1
1
  module AIXM
2
- class Document
3
-
2
+ class Document < Base
4
3
  using AIXM::Refinements
5
4
 
5
+ IGNORE_ERROR_PATTERN = %r(OrgUid)
6
+
6
7
  attr_reader :created_at, :effective_at
7
8
  attr_accessor :features
8
9
 
@@ -20,7 +21,7 @@ module AIXM
20
21
  ##
21
22
  # Check whether the document is complete (extensions excluded)
22
23
  def complete?
23
- features.any? && features.none? { |f| !f.complete? }
24
+ features.any? && features.none? { |f| f.respond_to?(:complete?) && !f.complete? }
24
25
  end
25
26
 
26
27
  ##
@@ -33,14 +34,16 @@ module AIXM
33
34
  # Validate against the XSD and return an array of errors
34
35
  def errors
35
36
  xsd = Nokogiri::XML::Schema(File.open(AIXM::SCHEMA))
36
- xsd.validate(Nokogiri::XML(to_xml))
37
+ xsd.validate(Nokogiri::XML(to_xml)).reject do |error|
38
+ error.message =~ IGNORE_ERROR_PATTERN
39
+ end
37
40
  end
38
41
 
39
42
  ##
40
43
  # Render AIXM
41
44
  #
42
45
  # Extensions:
43
- # * +:OFM+ - Open Flightmaps
46
+ # * +:ofm+ - Open Flightmaps
44
47
  def to_xml(*extensions)
45
48
  now = Time.now.xmlschema
46
49
  meta = {
@@ -50,7 +53,7 @@ module AIXM
50
53
  created: @created_at&.xmlschema || now,
51
54
  effective: @effective_at&.xmlschema || now
52
55
  }
53
- meta[:version] += ' + OFM extensions of version 0.1' if extensions >> :OFM
56
+ meta[:version] += ' + OFM extensions of version 0.1' if extensions >> :ofm
54
57
  builder = Builder::XmlMarkup.new(indent: 2)
55
58
  builder.instruct!
56
59
  builder.tag!('AIXM-Snapshot', meta) do |aixm_snapshot|
@@ -0,0 +1,41 @@
1
+ module AIXM
2
+
3
+ ##
4
+ # Frequency
5
+ #
6
+ # The following units are recognized:
7
+ # * +:mhz+ - megahertz
8
+ # * +:khz+ - kilohertz
9
+ class F < Base
10
+ using AIXM::Refinements
11
+
12
+ UNITS = %i(mhz khz).freeze
13
+
14
+ attr_reader :freq, :unit
15
+
16
+ def initialize(freq, unit)
17
+ @freq, @unit = freq.to_f, unit&.to_sym&.downcase
18
+ fail(ArgumentError, "unrecognized unit `#{@unit}'") unless UNITS.include? @unit
19
+ end
20
+
21
+ ##
22
+ # Digest to identify the payload
23
+ def to_digest
24
+ [freq, unit].to_digest
25
+ end
26
+
27
+ ##
28
+ # Check whether two frequencies are identical
29
+ def ==(other)
30
+ other.is_a?(F) && freq == other.freq && unit == other.unit
31
+ end
32
+
33
+ ##
34
+ # Check whether this frequency is part of a frequency band
35
+ def between?(lower_freq, upper_freq, unit)
36
+ freq.between?(lower_freq, upper_freq) && self.unit == unit
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -1,7 +1,6 @@
1
1
  module AIXM
2
2
  module Feature
3
- class Airspace
4
-
3
+ class Airspace < Base
5
4
  using AIXM::Refinements
6
5
 
7
6
  attr_reader :name, :short_name, :type
@@ -45,15 +44,12 @@ module AIXM
45
44
 
46
45
  ##
47
46
  # Render AIXM
48
- #
49
- # Extensions:
50
- # * +:OFM+ - Open Flightmaps
51
47
  def to_xml(*extensions)
52
48
  mid = to_digest
53
49
  builder = Builder::XmlMarkup.new(indent: 2)
54
50
  builder.comment! "Airspace: [#{type}] #{name}"
55
- builder.Ase({ xt_classLayersAvail: ((class_layers.count > 1) if extensions >> :OFM) }.compact) do |ase|
56
- ase.AseUid({ mid: mid, newEntity: (true if extensions >> :OFM) }.compact) do |aseuid|
51
+ builder.Ase({ xt_classLayersAvail: ((class_layers.count > 1) if extensions >> :ofm) }.compact) do |ase|
52
+ ase.AseUid({ mid: mid, newEntity: (true if extensions >> :ofm) }.compact) do |aseuid|
57
53
  aseuid.codeType(type.to_s)
58
54
  aseuid.codeId(mid)
59
55
  end
@@ -66,11 +62,11 @@ module AIXM
66
62
  end
67
63
  end
68
64
  ase.txtRmk(remarks.to_s) if remarks
69
- ase.xt_selAvail(false) if extensions >> :OFM
65
+ ase.xt_selAvail(false) if extensions >> :ofm
70
66
  end
71
67
  builder.Abd do |abd|
72
68
  abd.AbdUid do |abduid|
73
- abduid.AseUid({ mid: mid, newEntity: (true if extensions >> :OFM) }.compact) do |aseuid|
69
+ abduid.AseUid({ mid: mid, newEntity: (true if extensions >> :ofm) }.compact) do |aseuid|
74
70
  aseuid.codeType(type.to_s)
75
71
  aseuid.codeId(mid)
76
72
  end
@@ -0,0 +1,6 @@
1
+ module AIXM
2
+ module Feature
3
+ class Base < AIXM::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,46 @@
1
+ module AIXM
2
+ module Feature
3
+ module NavigationalAid
4
+
5
+ ##
6
+ # Implements common attributes of all navigational aids
7
+ #
8
+ # Please note that the optional elevation +z+ must be in +:qnh+.
9
+ class Base < AIXM::Feature::Base
10
+ using AIXM::Refinements
11
+
12
+ attr_reader :id, :name, :xy, :z
13
+ attr_accessor :remarks
14
+
15
+ private_class_method :new
16
+
17
+ def initialize(id:, name:, xy:, z: nil)
18
+ @id, @name, @xy, @z = id&.upcase, name&.upcase, xy, z
19
+ fail(ArgumentError, "invalid xy") unless xy.is_a? AIXM::XY
20
+ fail(ArgumentError, "invalid z") unless z.nil? || (z.is_a?(AIXM::Z) && z.qnh?)
21
+ end
22
+
23
+ ##
24
+ # Return either the +type_key+ or +class+
25
+ def kind
26
+ respond_to?(:type_key) ? type_key : self.class.name.split('::').last.to_sym
27
+ end
28
+
29
+ ##
30
+ # Digest to identify the payload
31
+ def to_digest
32
+ [kind, id, name, xy.to_digest, z&.to_digest, remarks].to_digest
33
+ end
34
+
35
+ ##
36
+ # Create builder to render AIXM in subclasses
37
+ def to_builder(*extensions)
38
+ builder = Builder::XmlMarkup.new(indent: 2)
39
+ builder.comment! "Navigational aid: [#{kind}] #{name}"
40
+ builder
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,69 @@
1
+ module AIXM
2
+ module Feature
3
+ module NavigationalAid
4
+
5
+ ##
6
+ # Designated points are named map coordinates
7
+ #
8
+ # Types:
9
+ # * +:icao+ (+:ICAO+) - ICAO 5 letter name code designator
10
+ # * +:adhp+ (+:ADHP+) - aerodrome/heliport related name code designator
11
+ # * +:coordinates+ (+:COORD+) - point with identifier derived from its
12
+ # geographical coordinates
13
+ # * +:other+ (+:OTHER+) - other type
14
+ class DesignatedPoint < Base
15
+ using AIXM::Refinements
16
+
17
+ TYPES = {
18
+ ICAO: :icao,
19
+ ADHP: :adhp,
20
+ COORD: :coordinates,
21
+ OTHER: :other
22
+ }.freeze
23
+
24
+ attr_reader :type
25
+
26
+ public_class_method :new
27
+
28
+ def initialize(id:, name:, xy:, z: nil, type:)
29
+ super(id: id, name: name, xy: xy, z: z)
30
+ @type = TYPES.lookup(type&.to_sym, nil) || fail(ArgumentError, "invalid type")
31
+ end
32
+
33
+ ##
34
+ # Digest to identify the payload
35
+ def to_digest
36
+ [super, type].to_digest
37
+ end
38
+
39
+ ##
40
+ # Render AIXM
41
+ def to_xml(*extensions)
42
+ builder = to_builder(*extensions)
43
+ builder.Dpn do |dpn|
44
+ dpn.DpnUid({ newEntity: (true if extensions >> :ofm) }.compact) do |dpnuid|
45
+ dpnuid.codeId(id)
46
+ dpnuid.geoLat(xy.lat(format_for(*extensions)))
47
+ dpnuid.geoLong(xy.long(format_for(*extensions)))
48
+ end
49
+ dpn.OrgUid
50
+ dpn.txtName(name)
51
+ dpn.codeDatum('WGE')
52
+ dpn.codeType(type_key.to_s)
53
+ if z
54
+ dpn.valElev(z.alt)
55
+ dpn.uomDistVer(z.unit.to_s)
56
+ end
57
+ dpn.txtRmk(remarks) if remarks
58
+ dpn.target! # see https://github.com/jimweirich/builder/issues/42
59
+ end
60
+ end
61
+
62
+ def type_key
63
+ TYPES.key(type)
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,54 @@
1
+ module AIXM
2
+ module Feature
3
+ module NavigationalAid
4
+
5
+ ##
6
+ # DME (distance measuring equipment) operate in the frequency band
7
+ # between 962 MHz and 1213 MHz.
8
+ #
9
+ # https://en.wikipedia.org/wiki/Distance_measuring_equipment
10
+ class DME < Base
11
+ using AIXM::Refinements
12
+
13
+ attr_reader :channel
14
+
15
+ public_class_method :new
16
+
17
+ def initialize(id:, name:, xy:, z: nil, channel:)
18
+ super(id: id, name: name, xy: xy, z: z)
19
+ @channel = channel&.upcase
20
+ end
21
+
22
+ ##
23
+ # Digest to identify the payload
24
+ def to_digest
25
+ [super, channel].to_digest
26
+ end
27
+
28
+ ##
29
+ # Render AIXM
30
+ def to_xml(*extensions)
31
+ builder = to_builder(*extensions)
32
+ builder.Dme do |dme|
33
+ dme.DmeUid({ newEntity: (true if extensions >> :ofm) }.compact) do |dmeuid|
34
+ dmeuid.codeId(id)
35
+ dmeuid.geoLat(xy.lat(format_for(*extensions)))
36
+ dmeuid.geoLong(xy.long(format_for(*extensions)))
37
+ end
38
+ dme.OrgUid
39
+ dme.txtName(name)
40
+ dme.codeChannel(channel)
41
+ dme.codeDatum('WGE')
42
+ if z
43
+ dme.valElev(z.alt)
44
+ dme.uomDistVer(z.unit.to_s)
45
+ end
46
+ dme.txtRmk(remarks) if remarks
47
+ dme.target! # see https://github.com/jimweirich/builder/issues/42
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,41 @@
1
+ module AIXM
2
+ module Feature
3
+ module NavigationalAid
4
+
5
+ ##
6
+ # Marker (marker beacons) operate on 75 MHz.
7
+ #
8
+ # https://en.wikipedia.org/wiki/Marker_beacon
9
+ class Marker < Base
10
+ using AIXM::Refinements
11
+
12
+ public_class_method :new
13
+
14
+ ##
15
+ # Render AIXM
16
+ def to_xml(*extensions)
17
+ builder = to_builder(*extensions)
18
+ builder.Mkr do |mkr|
19
+ mkr.MkrUid({ newEntity: (true if extensions >> :ofm) }.compact) do |mkruid|
20
+ mkruid.codeId(id)
21
+ mkruid.geoLat(xy.lat(format_for(*extensions)))
22
+ mkruid.geoLong(xy.long(format_for(*extensions)))
23
+ end
24
+ mkr.OrgUid
25
+ mkr.valFreq(75)
26
+ mkr.uomFreq('MHZ')
27
+ mkr.txtName(name)
28
+ mkr.codeDatum('WGE')
29
+ if z
30
+ mkr.valElev(z.alt)
31
+ mkr.uomDistVer(z.unit.to_s)
32
+ end
33
+ mkr.txtRmk(remarks) if remarks
34
+ mkr.target! # see https://github.com/jimweirich/builder/issues/42
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,56 @@
1
+ module AIXM
2
+ module Feature
3
+ module NavigationalAid
4
+
5
+ ##
6
+ # NDB (non-directional beacon) operate in the frequency band between
7
+ # 190 kHz and 1750 kHz.
8
+ #
9
+ # https://en.wikipedia.org/wiki/Non-directional_beacon
10
+ class NDB < Base
11
+ using AIXM::Refinements
12
+
13
+ attr_reader :f
14
+
15
+ public_class_method :new
16
+
17
+ def initialize(id:, name:, xy:, z: nil, f:)
18
+ super(id: id, name: name, xy: xy, z: z)
19
+ @f = f
20
+ fail(ArgumentError, "invalid frequency") unless f.is_a?(F) && f.between?(190, 1750, :khz)
21
+ end
22
+
23
+ ##
24
+ # Digest to identify the payload
25
+ def to_digest
26
+ [super, f.to_digest].to_digest
27
+ end
28
+
29
+ ##
30
+ # Render AIXM
31
+ def to_xml(*extensions)
32
+ builder = to_builder(*extensions)
33
+ builder.Ndb do |ndb|
34
+ ndb.NdbUid({ newEntity: (true if extensions >> :ofm) }.compact) do |ndbuid|
35
+ ndbuid.codeId(id)
36
+ ndbuid.geoLat(xy.lat(format_for(*extensions)))
37
+ ndbuid.geoLong(xy.long(format_for(*extensions)))
38
+ end
39
+ ndb.OrgUid
40
+ ndb.txtName(name)
41
+ ndb.valFreq(f.freq.trim)
42
+ ndb.uomFreq(f.unit.upcase.to_s)
43
+ ndb.codeDatum('WGE')
44
+ if z
45
+ ndb.valElev(z.alt)
46
+ ndb.uomDistVer(z.unit.to_s)
47
+ end
48
+ ndb.txtRmk(remarks) if remarks
49
+ ndb.target! # see https://github.com/jimweirich/builder/issues/42
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,54 @@
1
+ module AIXM
2
+ module Feature
3
+ module NavigationalAid
4
+
5
+ ##
6
+ # TACAN (tactical air navigation system) operate in the frequency band
7
+ # between 960 MHz and 1215 MHz.
8
+ #
9
+ # https://en.wikipedia.org/wiki/Tactical_air_navigation_system
10
+ class TACAN < Base
11
+ using AIXM::Refinements
12
+
13
+ attr_reader :channel
14
+
15
+ public_class_method :new
16
+
17
+ def initialize(id:, name:, xy:, z: nil, channel:)
18
+ super(id: id, name: name, xy: xy, z: z)
19
+ @channel = channel&.upcase
20
+ end
21
+
22
+ ##
23
+ # Digest to identify the payload
24
+ def to_digest
25
+ [super, channel].to_digest
26
+ end
27
+
28
+ ##
29
+ # Render AIXM
30
+ def to_xml(*extensions)
31
+ builder = to_builder(*extensions)
32
+ builder.Tcn do |tcn|
33
+ tcn.TcnUid({ newEntity: (true if extensions >> :ofm) }.compact) do |tcnuid|
34
+ tcnuid.codeId(id)
35
+ tcnuid.geoLat(xy.lat(format_for(*extensions)))
36
+ tcnuid.geoLong(xy.long(format_for(*extensions)))
37
+ end
38
+ tcn.OrgUid
39
+ tcn.txtName(name)
40
+ tcn.codeChannel(channel)
41
+ tcn.codeDatum('WGE')
42
+ if z
43
+ tcn.valElev(z.alt)
44
+ tcn.uomDistVer(z.unit.to_s)
45
+ end
46
+ tcn.txtRmk(remarks) if remarks
47
+ tcn.target! # see https://github.com/jimweirich/builder/issues/42
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end