aixm 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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