aixm 0.2.3 → 0.3.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.ruby-version +1 -1
  4. data/.yardopts +3 -0
  5. data/CHANGELOG.md +34 -14
  6. data/Guardfile +1 -0
  7. data/README.md +64 -257
  8. data/lib/aixm.rb +16 -7
  9. data/lib/aixm/component.rb +6 -0
  10. data/lib/aixm/component/frequency.rb +135 -0
  11. data/lib/aixm/component/geometry.rb +34 -23
  12. data/lib/aixm/component/geometry/arc.rb +37 -22
  13. data/lib/aixm/component/geometry/border.rb +29 -20
  14. data/lib/aixm/component/geometry/circle.rb +39 -22
  15. data/lib/aixm/component/geometry/point.rb +29 -13
  16. data/lib/aixm/component/helipad.rb +154 -0
  17. data/lib/aixm/component/layer.rb +91 -0
  18. data/lib/aixm/component/runway.rb +294 -0
  19. data/lib/aixm/component/service.rb +170 -0
  20. data/lib/aixm/component/timetable.rb +65 -0
  21. data/lib/aixm/component/vertical_limits.rb +65 -29
  22. data/lib/aixm/config.rb +87 -0
  23. data/lib/aixm/document.rb +66 -42
  24. data/lib/aixm/errors.rb +11 -0
  25. data/lib/aixm/f.rb +34 -20
  26. data/lib/aixm/feature.rb +38 -0
  27. data/lib/aixm/feature/airport.rb +473 -0
  28. data/lib/aixm/feature/airspace.rb +145 -92
  29. data/lib/aixm/feature/navigational_aid.rb +94 -0
  30. data/lib/aixm/feature/navigational_aid/designated_point.rb +50 -54
  31. data/lib/aixm/feature/navigational_aid/dme.rb +48 -40
  32. data/lib/aixm/feature/navigational_aid/marker.rb +55 -45
  33. data/lib/aixm/feature/navigational_aid/ndb.rb +54 -50
  34. data/lib/aixm/feature/navigational_aid/tacan.rb +38 -31
  35. data/lib/aixm/feature/navigational_aid/vor.rb +84 -76
  36. data/lib/aixm/feature/organisation.rb +97 -0
  37. data/lib/aixm/feature/unit.rb +152 -0
  38. data/lib/aixm/refinements.rb +132 -47
  39. data/lib/aixm/shortcuts.rb +11 -6
  40. data/lib/aixm/version.rb +1 -1
  41. data/lib/aixm/xy.rb +64 -20
  42. data/lib/aixm/z.rb +51 -22
  43. data/{lib/aixm/schemas → schemas/aixm}/4.5/AIXM-DataTypes.xsd +0 -0
  44. data/{lib/aixm/schemas → schemas/aixm}/4.5/AIXM-Features.xsd +0 -0
  45. data/{lib/aixm/schemas → schemas/aixm}/4.5/AIXM-Snapshot.xsd +0 -0
  46. data/schemas/ofmx/0/OFMX-DataTypes.xsd +5077 -0
  47. data/schemas/ofmx/0/OFMX-Features.xsd +9955 -0
  48. data/schemas/ofmx/0/OFMX-Snapshot.xsd +217 -0
  49. data/spec/factory.rb +209 -33
  50. data/spec/lib/aixm/component/frequency_spec.rb +75 -0
  51. data/spec/lib/aixm/component/geometry/arc_spec.rb +28 -22
  52. data/spec/lib/aixm/component/geometry/border_spec.rb +23 -20
  53. data/spec/lib/aixm/component/geometry/circle_spec.rb +31 -22
  54. data/spec/lib/aixm/component/geometry/point_spec.rb +11 -14
  55. data/spec/lib/aixm/component/geometry_spec.rb +150 -69
  56. data/spec/lib/aixm/component/helipad_spec.rb +136 -0
  57. data/spec/lib/aixm/component/layer_spec.rb +110 -0
  58. data/spec/lib/aixm/component/runway_spec.rb +402 -0
  59. data/spec/lib/aixm/component/service_spec.rb +61 -0
  60. data/spec/lib/aixm/component/timetable_spec.rb +49 -0
  61. data/spec/lib/aixm/component/vertical_limits_spec.rb +39 -20
  62. data/spec/lib/aixm/config_spec.rb +41 -0
  63. data/spec/lib/aixm/document_spec.rb +637 -147
  64. data/spec/lib/aixm/errors_spec.rb +14 -0
  65. data/spec/lib/aixm/f_spec.rb +17 -10
  66. data/spec/lib/aixm/feature/airport_spec.rb +546 -0
  67. data/spec/lib/aixm/feature/airspace_spec.rb +349 -226
  68. data/spec/lib/aixm/feature/navigational_aid/designated_point_spec.rb +47 -36
  69. data/spec/lib/aixm/feature/navigational_aid/dme_spec.rb +61 -36
  70. data/spec/lib/aixm/feature/navigational_aid/marker_spec.rb +61 -113
  71. data/spec/lib/aixm/feature/navigational_aid/ndb_spec.rb +65 -79
  72. data/spec/lib/aixm/feature/navigational_aid/tacan_spec.rb +57 -36
  73. data/spec/lib/aixm/feature/navigational_aid/vor_spec.rb +86 -112
  74. data/spec/lib/aixm/feature/navigational_aid_spec.rb +52 -0
  75. data/spec/lib/aixm/feature/organisation_spec.rb +77 -0
  76. data/spec/lib/aixm/feature/unit_spec.rb +227 -0
  77. data/spec/lib/aixm/feature_spec.rb +58 -0
  78. data/spec/lib/aixm/refinements_spec.rb +187 -178
  79. data/spec/lib/aixm/xy_spec.rb +45 -34
  80. data/spec/lib/aixm/z_spec.rb +19 -21
  81. data/spec/macros/organisation.rb +11 -0
  82. data/spec/macros/remarks.rb +12 -0
  83. data/spec/macros/timetable.rb +11 -0
  84. data/spec/macros/xy.rb +11 -0
  85. data/spec/macros/z_qnh.rb +11 -0
  86. data/spec/spec_helper.rb +26 -0
  87. metadata +60 -19
  88. data/lib/aixm/base.rb +0 -10
  89. data/lib/aixm/component/base.rb +0 -6
  90. data/lib/aixm/component/class_layer.rb +0 -46
  91. data/lib/aixm/component/geometry/base.rb +0 -8
  92. data/lib/aixm/component/schedule.rb +0 -43
  93. data/lib/aixm/feature/base.rb +0 -6
  94. data/lib/aixm/feature/navigational_aid/base.rb +0 -79
  95. data/spec/lib/aixm/component/class_layer_spec.rb +0 -74
  96. data/spec/lib/aixm/component/schedule_spec.rb +0 -33
  97. data/spec/lib/aixm/feature/navigational_aid/base_spec.rb +0 -41
@@ -0,0 +1,170 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Component
5
+
6
+ # Service provided by a unit.
7
+ #
8
+ # ===Cheat Sheet in Pseudo Code:
9
+ # service = AIXM.service(
10
+ # name: String
11
+ # type: TYPES
12
+ # )
13
+ # service.timetable = AIXM.timetable or nil
14
+ # service.remarks = String or nil
15
+ # service.add_frequency(AIXM.frequency)
16
+ #
17
+ # @see https://github.com/openflightmaps/ofmx/wiki/Organisation#ser-service
18
+ class Service
19
+ TYPES = {
20
+ ACS: :area_control_service,
21
+ ADS: :automatic_dependent_surveillance_service,
22
+ ADVS: :advisory_service,
23
+ AFIS: :aerodrome_flight_information_service,
24
+ AFS: :aeronautical_fixed_service,
25
+ AIS: :aeronautical_information_service,
26
+ ALRS: :alerting_service,
27
+ AMS: :aeronautical_mobile_service,
28
+ AMSS: :aeronautical_mobile_satellite_service,
29
+ APP: :approach_control_service,
30
+ 'APP-ARR': :approach_control_service_for_arrival,
31
+ 'APP-DEP': :approach_control_service_for_departure,
32
+ ARTCC: :air_route_traffic_control_centre_service,
33
+ ATC: :air_traffic_control_service,
34
+ ATFM: :air_traffic_flow_management_service,
35
+ ATIS: :automated_terminal_information_service,
36
+ 'ATIS-ARR': :automated_terminal_information_service_for_arrival,
37
+ 'ATIS-DEP': :automated_terminal_information_service_for_departure,
38
+ ATM: :air_traffic_management_service,
39
+ ATS: :air_traffic_service,
40
+ BOF: :briefing_service,
41
+ BS: :commercial_broadcasting_service,
42
+ COM: :communications_service,
43
+ CTAF: :common_traffic_advisory_frequency_service,
44
+ DVDF: :doppler_vdf_service,
45
+ EFAS: :en_route_flight_advisory_service,
46
+ ENTRY: :entry_clearance_service,
47
+ EXIT: :exit_clearance_service,
48
+ FCST: :forecasting_service,
49
+ FIS: :flight_information_service,
50
+ FISA: :automated_flight_information_service,
51
+ FSS: :flight_service_station_service,
52
+ GCA: :ground_controlled_approach_service,
53
+ INFO: :information_provision_service,
54
+ MET: :meteorological_service,
55
+ NOF: :international_notam_service,
56
+ OAC: :oceanic_area_control_service,
57
+ OVERFLT: :overflight_clearance_service,
58
+ PAR: :precision_approach_radar_service,
59
+ RAC: :rules_of_the_air_and_air_traffic_services,
60
+ RADAR: :radar_service,
61
+ RAF: :regional_area_forecasting_service,
62
+ RCC: :rescue_coordination_service,
63
+ SAR: :search_and_rescue_service,
64
+ SIGMET: :sigmet_service,
65
+ SMC: :surface_movement_control_service,
66
+ SMR: :surface_movement_radar_service,
67
+ SRA: :surveillance_radar_approach_service,
68
+ SSR: :secondary_surveillance_radar_service,
69
+ TAR: :terminal_area_radar_service,
70
+ TWEB: :transcribed_weather_broadcast_service,
71
+ TWR: :aerodrome_control_tower_service,
72
+ UAC: :upper_area_control_service,
73
+ UDF: :uhf_direction_finding_service,
74
+ VDF: :vhf_direction_finding_service,
75
+ VOLMET: :volmet_service,
76
+ VOT: :vor_test_facility,
77
+ OTHER: :other # specify in remarks
78
+ }.freeze
79
+
80
+ # @return [AIXM::Feature::Unit] unit providing this service
81
+ attr_reader :unit
82
+
83
+ # @return [String] full name
84
+ attr_reader :name
85
+
86
+ # @return [Symbol] type of service (see {TYPES})
87
+ attr_reader :type
88
+
89
+ # @return [AIXM::Component::Timetable, nil] operating hours
90
+ attr_reader :timetable
91
+
92
+ # @return [String, nil] free text remarks
93
+ attr_reader :remarks
94
+
95
+ # @return [Array<AIXM::Component::Frequency>] frequencies used by this service
96
+ attr_reader :frequencies
97
+
98
+ def initialize(name:, type:)
99
+ self.name, self.type = name, type
100
+ @frequencies = []
101
+ end
102
+
103
+ # @return [String]
104
+ def inspect
105
+ %Q(#<#{self.class} type=#{type.inspect}>)
106
+ end
107
+
108
+ def unit=(value)
109
+ fail(ArgumentError, "invalid unit") unless value.is_a? AIXM::Feature::Unit
110
+ @unit = value
111
+ end
112
+ private :unit=
113
+
114
+ def name=(value)
115
+ fail(ArgumentError, "invalid name") unless value.is_a? String
116
+ @name = value.uptrans
117
+ end
118
+
119
+ def type=(value)
120
+ @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
121
+ end
122
+
123
+ def timetable=(value)
124
+ fail(ArgumentError, "invalid timetable") unless value.nil? || value.is_a?(AIXM::Component::Timetable)
125
+ @timetable = value
126
+ end
127
+
128
+ def remarks=(value)
129
+ @remarks = value&.to_s
130
+ end
131
+
132
+ # Add a frequency used by this service.
133
+ #
134
+ # @param frequency [AIXM::Component::Frequency] frequency instance
135
+ # @return [self]
136
+ def add_frequency(frequency)
137
+ fail(ArgumentError, "invalid frequency") unless frequency.is_a? AIXM::Component::Frequency
138
+ frequency.send(:service=, self)
139
+ @frequencies << frequency
140
+ self
141
+ end
142
+
143
+ # @return [String] UID markup
144
+ def to_uid
145
+ builder = Builder::XmlMarkup.new(indent: 2)
146
+ builder.SerUid do |ser_uid|
147
+ ser_uid << unit.to_uid.indent(2)
148
+ ser_uid.codeType(TYPES.key(type).to_s)
149
+ ser_uid.noSeq(@sequence)
150
+ end
151
+ end
152
+
153
+ # @return [String] AIXM or OFMX markup
154
+ def to_xml(sequence=1)
155
+ @sequence = sequence
156
+ builder = Builder::XmlMarkup.new(indent: 2)
157
+ builder.Ser do |ser|
158
+ ser << to_uid.indent(2)
159
+ ser << timetable.to_xml(as: :Stt).indent(2) if timetable
160
+ ser.txtRmk(remarks) if remarks
161
+ end
162
+ frequencies.each do |frequency|
163
+ builder << frequency.to_xml
164
+ end
165
+ builder.target!
166
+ end
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,65 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Component
5
+
6
+ # Timetables define activity time windows.
7
+ #
8
+ # @note As of now, only predefined timetables (see {CODES}) are imlemented.
9
+ #
10
+ # ===Cheat Sheat in Pseudo Code:
11
+ # timetable = AIXM.timetable(
12
+ # code: String or Symbol
13
+ # )
14
+ # timetable.remarks = String or nil
15
+ #
16
+ # ===Shortcuts:
17
+ # * +AIXM::H24+ - continuous, all day and all night
18
+ #
19
+ # @see https://github.com/openflightmaps/ofmx/wiki/Timetable#predefined-timetable
20
+ class Timetable
21
+ CODES = {
22
+ H24: :continuous, # all day and all night
23
+ HJ: :sunrise_to_sunset, # all day
24
+ HN: :sunset_to_sunrise, # all night
25
+ HX: :unspecified,
26
+ HO: :operational_request, # on request only
27
+ NOTAM: :notam, # see NOTAM
28
+ OTHER: :other # specify in remarks
29
+ }.freeze
30
+
31
+ # @return [Symbol] predefined timetable code (see {CODES})
32
+ attr_reader :code
33
+
34
+ # @return [String, nil] free text remarks
35
+ attr_reader :remarks
36
+
37
+ def initialize(code:)
38
+ self.code = code
39
+ end
40
+
41
+ # @return [String]
42
+ def inspect
43
+ %Q(#<#{self.class} code=#{code.inspect}>)
44
+ end
45
+
46
+ def code=(value)
47
+ @code = CODES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid code")
48
+ end
49
+
50
+ def remarks=(value)
51
+ @remarks = value&.to_s
52
+ end
53
+
54
+ # @return [String] AIXM or OFMX markup
55
+ def to_xml(as: :Timetable)
56
+ builder = Builder::XmlMarkup.new(indent: 2)
57
+ builder.tag!(as) do |tag|
58
+ tag.codeWorkHr(CODES.key(code).to_s)
59
+ tag.txtRmkWorkHr(remarks) if remarks
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -1,49 +1,85 @@
1
+ using AIXM::Refinements
2
+
1
3
  module AIXM
2
- module Component
4
+ class Component
3
5
 
4
- ##
5
- # Vertical limits define a 3D airspace vertically. They are normally noted
6
- # as follows:
6
+ # Vertical limits define a 3D airspace vertically. They are often noted in
7
+ # AIP as follows:
7
8
  #
8
- # +upper z+ (or +max_z+ whichever is higher)
9
- # ---------
10
- # +lower_z+ (or +min_z+ whichever is lower)
9
+ # upper_z (or max_z whichever is higher)
10
+ # -------
11
+ # lower_z (or min_z whichever is lower)
11
12
  #
12
- # Shortcuts:
13
- # * +AIXM::GROUND+ - surface (aka: 0ft QFE)
14
- # * +AIXM::UNLIMITED+ - no upper limit (aka: FL 999)
15
- class VerticalLimits < Base
16
- using AIXM::Refinements
17
-
13
+ # ===Cheat Sheet in Pseudo Code:
14
+ # vertical_limits = AIXM.vertical_limits(
15
+ # upper_z: AIXM.z
16
+ # lower_z: AIXM.z
17
+ # max_z: AIXM.z or nil
18
+ # min_z: AIXM.z or nil
19
+ # )
20
+ #
21
+ # ===Shortcuts:
22
+ # * +AIXM::GROUND+ - surface expressed as "0 ft QFE"
23
+ # * +AIXM::UNLIMITED+ - no upper limit expressed as "FL 999"
24
+ #
25
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airspace#ase-airspace
26
+ class VerticalLimits
27
+ # @api private
18
28
  TAGS = { upper: :Upper, lower: :Lower, max: :Max, min: :Mnm }.freeze
29
+
30
+ # @api private
19
31
  CODES = { qfe: :HEI, qnh: :ALT, qne: :STD }.freeze
20
32
 
21
- attr_reader :upper_z, :lower_z, :max_z, :min_z
33
+ # @return [AIXM::Z] upper limit
34
+ attr_reader :upper_z
35
+
36
+ # @return [AIXM::Z] lower limit
37
+ attr_reader :lower_z
38
+
39
+ # @return [AIXM::Z] alternative upper limit ("whichever is higher")
40
+ attr_reader :max_z
41
+
42
+ # @return [AIXM::Z] alternative lower limit ("whichever is lower")
43
+ attr_reader :min_z
44
+
45
+ def initialize(upper_z:, lower_z:, max_z: nil, min_z: nil)
46
+ self.upper_z, self.lower_z, self.max_z, self.min_z = upper_z, lower_z, max_z, min_z
47
+ end
48
+
49
+ # @return [String]
50
+ def inspect
51
+ %Q(#<#{self.class} upper_z="#{upper_z.to_s}" lower_z="#{lower_z.to_s}">)
52
+ end
53
+
54
+ def upper_z=(value)
55
+ fail(ArgumentError, "invalid upper_z") unless value.is_a? AIXM::Z
56
+ @upper_z = value
57
+ end
58
+
59
+ def lower_z=(value)
60
+ fail(ArgumentError, "invalid lower_z") unless value.is_a? AIXM::Z
61
+ @lower_z = value
62
+ end
22
63
 
23
- def initialize(max_z: nil, upper_z:, lower_z:, min_z: nil)
24
- fail(ArgumentError, "invalid upper_z") unless upper_z.is_a? AIXM::Z
25
- fail(ArgumentError, "invalid lower_z") unless lower_z.is_a? AIXM::Z
26
- fail(ArgumentError, "invalid max_z") unless max_z.nil? || max_z.is_a?(AIXM::Z)
27
- fail(ArgumentError, "invalid min_z") unless min_z.nil? || min_z.is_a?(AIXM::Z)
28
- @upper_z, @lower_z, @max_z, @min_z = upper_z, lower_z, max_z, min_z
64
+ def max_z=(value)
65
+ fail(ArgumentError, "invalid max_z") unless value.nil? || value.is_a?(AIXM::Z)
66
+ @max_z = value
29
67
  end
30
68
 
31
- ##
32
- # Digest to identify the payload
33
- def to_digest
34
- [upper_z.alt, upper_z.code, lower_z.alt, lower_z.code, max_z&.alt, max_z&.code, min_z&.alt, min_z&.code].to_digest
69
+ def min_z=(value)
70
+ fail(ArgumentError, "invalid min_z") unless value.nil? || value.is_a?(AIXM::Z)
71
+ @min_z = value
35
72
  end
36
73
 
37
- ##
38
- # Render AIXM markup
39
- def to_aixm(*extensions)
74
+ # @return [String] AIXM or OFMX markup
75
+ def to_xml
40
76
  %i(upper lower max min).each_with_object(Builder::XmlMarkup.new(indent: 2)) do |limit, builder|
41
77
  if z = send(:"#{limit}_z")
42
78
  builder.tag!(:"codeDistVer#{TAGS[limit]}", CODES[z.code].to_s)
43
79
  builder.tag!(:"valDistVer#{TAGS[limit]}", z.alt.to_s)
44
- builder.tag!(:"uomDistVer#{TAGS[limit]}", z.unit.to_s)
80
+ builder.tag!(:"uomDistVer#{TAGS[limit]}", z.unit.upcase.to_s)
45
81
  end
46
- end.target! # see https://github.com/jimweirich/builder/issues/42
82
+ end.target!
47
83
  end
48
84
  end
49
85
 
@@ -0,0 +1,87 @@
1
+ module AIXM
2
+
3
+ SCHEMAS = {
4
+ aixm: {
5
+ version: '4.5',
6
+ namespace: 'http://www.aixm.aero/schema/4.5/AIXM-Snapshot.xsd',
7
+ xsd: Pathname(__dir__).join('..', '..', 'schemas', 'aixm', '4.5', 'AIXM-Snapshot.xsd'),
8
+ root: 'AIXM-Snapshot'
9
+ },
10
+ ofmx: {
11
+ version: '0',
12
+ namespace: 'http://openflightmaps.org/schema/0/OFMX-Snapshot.xsd',
13
+ xsd: Pathname(__dir__).join('..', '..', 'schemas', 'ofmx', '0', 'OFMX-Snapshot.xsd'),
14
+ root: 'OFMX-Snapshot'
15
+ }
16
+ }.freeze
17
+
18
+ class << self
19
+
20
+ # Access the configuration (e.g. +AIXM.config.schema+)
21
+ # @return [OpenStruct] configuration struct
22
+ def config
23
+ @@config
24
+ end
25
+
26
+ # Currently active schema
27
+ #
28
+ # @example Get the schema identifyer
29
+ # AIXM.schema # => :aixm
30
+ #
31
+ # @example Get schema details
32
+ # AIXM.schema(:version) # => '4.5'
33
+ # AIXM.schema(:root) # => 'AIXM-Snapshot'
34
+ #
35
+ # @param key [Symbol, nil] schema detail key (see {SCHEMAS})
36
+ # @return [Object] schema detail value
37
+ def schema(key = nil)
38
+ key ? SCHEMAS.dig(@@config.schema, key) : @@config.schema
39
+ end
40
+
41
+ # Shortcuts to set the schema.
42
+ #
43
+ # @example
44
+ # AIXM.aixm! # => :aixm
45
+ # AIXM.ofmx? # => false
46
+ # AIXM.ofmx! # => :ofmx
47
+ # AIXM.ofmx? # => true
48
+ #
49
+ # @!method aixm!
50
+ # @!method ofmx!
51
+ # @return [Symbol] schema key
52
+ SCHEMAS.each_key do |schema|
53
+ define_method("#{schema}!") { @@config.schema = schema }
54
+ end
55
+
56
+ # Shortcuts to query the schema.
57
+ #
58
+ # @example
59
+ # AIXM.aixm! # => :aixm
60
+ # AIXM.ofmx? # => false
61
+ # AIXM.ofmx! # => :ofmx
62
+ # AIXM.ofmx? # => true
63
+ #
64
+ # @!method aixm?
65
+ # @!method ofmx?
66
+ # @return [Boolean]
67
+ SCHEMAS.each_key do |schema|
68
+ define_method("#{schema}?") { @@config.schema == schema }
69
+ end
70
+
71
+ private
72
+
73
+ # Configuration defaults (view source for more).
74
+ #
75
+ # @!visibility public
76
+ # @api private
77
+ # @return [OpenStruct]
78
+ def initialize_config
79
+ @@config = OpenStruct.new(
80
+ schema: :aixm
81
+ )
82
+ end
83
+
84
+ end
85
+ end
86
+
87
+ AIXM.send(:initialize_config)
@@ -1,63 +1,89 @@
1
+ using AIXM::Refinements
2
+
1
3
  module AIXM
2
- class Document < Base
3
- using AIXM::Refinements
4
4
 
5
- IGNORE_ERROR_PATTERN = %r(OrgUid)
5
+ # The AIXM-Snapshot or OFMX-Snapshot document is the root container for
6
+ # aeronautical information such as airports or airspaces.
7
+ #
8
+ # ===Cheat Sheet in Pseudo Code:
9
+ # document = AIXM.document(
10
+ # namespace: String (UUID)
11
+ # created_at: Time or Date or String
12
+ # effective_at: Time or Date or String
13
+ # )
14
+ # document.features << AIXM::Feature
15
+ #
16
+ # @see https://github.com/openflightmaps/ofmx/wiki/Snapshot
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
19
+
20
+ # @return [String] UUID to namespace the data contained in this document
21
+ attr_reader :namespace
22
+
23
+ # @return [Time] creation date and time (default: {#effective_at} or now)
24
+ attr_reader :created_at
25
+
26
+ # @return [Time] effective after date and time (default: {#created_at} or now)
27
+ attr_reader :effective_at
6
28
 
7
- attr_reader :created_at, :effective_at
29
+ # @return [Array<AIXM::Feature>] airspaces, airports and other features
8
30
  attr_accessor :features
9
31
 
10
- ##
11
- # Define a AIXM-Snapshot document
12
- #
13
- # Options:
14
- # * +created_at+ - creation date and time (default: now)
15
- # * +effective_at+ - snapshot effective after date and time (default: now)
16
- def initialize(created_at: nil, effective_at: nil)
17
- @created_at, @effective_at = parse_time(created_at), parse_time(effective_at)
32
+ def initialize(namespace: nil, created_at: nil, effective_at: nil)
33
+ self.namespace, self.created_at, self.effective_at = namespace, created_at, effective_at
18
34
  @features = []
19
35
  end
20
36
 
21
- ##
22
- # Check whether the document is complete (extensions excluded)
23
- def complete?
24
- features.any? && features.none? { |f| f.respond_to?(:complete?) && !f.complete? }
37
+ # @return [String]
38
+ def inspect
39
+ %Q(#<#{self.class} created_at=#{created_at.inspect}>)
40
+ end
41
+
42
+ def namespace=(value)
43
+ fail(ArgumentError, "invalid namespace") unless value.nil? || value.match?(NAMESPACE_PATTERN)
44
+ @namespace = value || SecureRandom.uuid
25
45
  end
26
46
 
27
- ##
28
- # Validate atainst the XSD and return +true+ if no errors were found
47
+ def created_at=(value)
48
+ @created_at = parse_time(value) || effective_at || Time.now
49
+ end
50
+
51
+ def effective_at=(value)
52
+ @effective_at = parse_time(value) || created_at || Time.now
53
+ end
54
+
55
+ # Validate the generated AIXM or OFMX atainst it's XSD.
56
+ #
57
+ # @return [Boolean] whether valid or not
29
58
  def valid?
30
59
  errors.none?
31
60
  end
32
61
 
33
- ##
34
- # Validate against the XSD and return an array of errors
62
+ # Validate the generated AIXM or OFMX atainst it's XSD and return the
63
+ # errors found.
64
+ #
65
+ # @return [Array<String>] validation errors
35
66
  def errors
36
- xsd = Nokogiri::XML::Schema(File.open(AIXM::SCHEMA))
37
- xsd.validate(Nokogiri::XML(to_aixm)).reject do |error|
38
- error.message =~ IGNORE_ERROR_PATTERN
67
+ xsd = Nokogiri::XML::Schema(File.open(AIXM.schema(:xsd)))
68
+ xsd.validate(Nokogiri::XML(to_xml)).reject do |error|
69
+ AIXM.config.ignored_errors && error.message.match?(AIXM.config.ignored_errors)
39
70
  end
40
71
  end
41
72
 
42
- ##
43
- # Render AIXM markup
44
- #
45
- # Extensions:
46
- # * +:ofm+ - Open Flightmaps
47
- def to_aixm(*extensions)
48
- now = Time.now.xmlschema
73
+ # @return [String] AIXM or OFMX markup
74
+ def to_xml
49
75
  meta = {
50
- 'xmlns:xsi': 'http://www.aixm.aero/schema/4.5/AIXM-Snapshot.xsd',
51
- version: '4.5',
52
- origin: "AIXM #{AIXM::VERSION} Ruby gem",
53
- created: @created_at&.xmlschema || now,
54
- effective: @effective_at&.xmlschema || now
55
- }
56
- meta[:version] += ' + OFM extensions of version 0.1' if extensions >> :ofm
76
+ 'xmlns:xsi': AIXM.schema(:namespace),
77
+ version: AIXM.schema(:version),
78
+ origin: "rubygem aixm-#{AIXM::VERSION}",
79
+ namespace: (namespace if AIXM.ofmx?),
80
+ created: @created_at.xmlschema,
81
+ effective: @effective_at.xmlschema
82
+ }.compact
57
83
  builder = Builder::XmlMarkup.new(indent: 2)
58
84
  builder.instruct!
59
- builder.tag!('AIXM-Snapshot', meta) do |aixm_snapshot|
60
- aixm_snapshot << features.map { |f| f.to_aixm(*extensions) }.join.indent(2)
85
+ builder.tag!(AIXM.schema(:root), meta) do |root|
86
+ root << features.map { |f| f.to_xml }.join.indent(2)
61
87
  end
62
88
  end
63
89
 
@@ -69,10 +95,8 @@ module AIXM
69
95
  when Date then value.to_time
70
96
  when Time then value
71
97
  when nil then nil
72
- else fail ArgumentError
98
+ else fail(ArgumentError, "invalid date or time")
73
99
  end
74
- rescue ArgumentError
75
- raise(ArgumentError, "`#{value}' is not a valid date and time")
76
100
  end
77
101
 
78
102
  end