aixm 0.3.1 → 0.3.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -6
- data/README.md +2 -0
- data/lib/aixm.rb +3 -0
- data/lib/aixm/component/geometry/circle.rb +6 -6
- data/lib/aixm/component/helipad.rb +19 -12
- data/lib/aixm/component/runway.rb +34 -22
- data/lib/aixm/d.rb +66 -0
- data/lib/aixm/document.rb +2 -14
- data/lib/aixm/feature/airport.rb +1 -0
- data/lib/aixm/feature/obstacle.rb +288 -0
- data/lib/aixm/feature/obstacle_group.rb +104 -0
- data/lib/aixm/refinements.rb +105 -102
- data/lib/aixm/shortcuts.rb +3 -0
- data/lib/aixm/version.rb +1 -1
- data/lib/aixm/xy.rb +5 -4
- data/lib/aixm/z.rb +3 -3
- data/spec/factory.rb +108 -5
- data/spec/lib/aixm/component/geometry/circle_spec.rb +4 -8
- data/spec/lib/aixm/component/geometry_spec.rb +2 -2
- data/spec/lib/aixm/component/helipad_spec.rb +2 -10
- data/spec/lib/aixm/component/runway_spec.rb +4 -16
- data/spec/lib/aixm/d_spec.rb +130 -0
- data/spec/lib/aixm/document_spec.rb +215 -2
- data/spec/lib/aixm/feature/obstacle_group_spec.rb +284 -0
- data/spec/lib/aixm/feature/obstacle_spec.rb +285 -0
- data/spec/lib/aixm/refinements_spec.rb +100 -76
- data/spec/lib/aixm/xy_spec.rb +3 -3
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3f7a1a1f47a40725a1f7444d97ca6f11acf3c1805d3d2521d9c79c72494abe5
|
4
|
+
data.tar.gz: a45d00f0667b808e4e44f5a065392c4d0f6a00fbcd7e085c8bab072288fcdf7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e58d749b2c1face5c6ec67c1e0f23e5fba2ebca6e3d111cc5075ce561c6c7d6b022585f2cdf0ace8e9320f7b1684385f7ab7bb7c1ca80ecf485afb19e7257cba
|
7
|
+
data.tar.gz: 5888395288986381ed501d7b7a488729e6be98dc65ed1e77c1bb9522022b95cc7d62f13031272edb86c86a3ce41c301917c30e5cce1e88756d88688b8ca2bd87
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 0.3.2
|
2
|
+
|
3
|
+
#### Additions
|
4
|
+
* Obstacle and obstacle group features
|
5
|
+
* `AIXM::D` (distance)
|
6
|
+
|
7
|
+
#### Breaking Changes
|
8
|
+
* All distances (circle geometry radius, helipad and runway length/width) must
|
9
|
+
be `AIXM::D`.
|
10
|
+
* `AIXM::XY#distance` now returns `AIXM::D`
|
11
|
+
* Removed obsolete refinement `Float#to_km` (use `AIXM::D#to_km` instead)
|
12
|
+
|
1
13
|
## 0.3.1
|
2
14
|
|
3
15
|
#### Additions
|
@@ -9,7 +21,7 @@
|
|
9
21
|
#### Breaking Changes
|
10
22
|
* Renamed `Airport#code` to `Airport#id`
|
11
23
|
* Renamed `Airspace#short_name` to `Airspace#local_type`
|
12
|
-
* Moved `region` attribute from features to Document
|
24
|
+
* Moved `region` attribute from features to Document
|
13
25
|
|
14
26
|
#### Changes
|
15
27
|
* Be more permissive on `Airport#id` in order to accomodate generated codes
|
@@ -27,7 +39,7 @@
|
|
27
39
|
* Removed `Array#to_digest`
|
28
40
|
* Removed `Document#complete?`
|
29
41
|
* Renamed Schedule to Timetable
|
30
|
-
* Timetable and remarks moved from Airspace to
|
42
|
+
* Timetable and remarks moved from Airspace to Layer (formerly known as class layer)
|
31
43
|
|
32
44
|
#### Additions
|
33
45
|
* Organization and Unit features
|
@@ -69,7 +81,7 @@
|
|
69
81
|
* Symbols such as `:qnh`, `:ofm` or `:mhz` are downcased now
|
70
82
|
|
71
83
|
#### Additions
|
72
|
-
*
|
84
|
+
* `AIXM::F` (frequency)
|
73
85
|
* Navigational aids features
|
74
86
|
* `AIXM::Z#qfe?` and friends
|
75
87
|
|
@@ -111,11 +123,11 @@
|
|
111
123
|
## 0.1.0
|
112
124
|
|
113
125
|
#### Initial Implementation
|
114
|
-
* XY
|
115
|
-
* Z
|
126
|
+
* `AIXM::XY` (coordinates)
|
127
|
+
* `AIXM::Z` (altitude or elevation)
|
116
128
|
* AIXM-Snapshot 4.5 Document
|
117
129
|
* Airspace feature
|
118
|
-
* Vertical
|
130
|
+
* Vertical limits
|
119
131
|
* Geometry
|
120
132
|
* Point
|
121
133
|
* Arc
|
data/README.md
CHANGED
@@ -74,6 +74,7 @@ AIXM.schema(:version) # => 0
|
|
74
74
|
* [Document](http://www.rubydoc.info/gems/aixm/AIXM/Document.html)
|
75
75
|
* [XY (longitude and latitude)](http://www.rubydoc.info/gems/aixm/AIXM/XY.html)
|
76
76
|
* [Z (height, elevation or altitude)](http://www.rubydoc.info/gems/aixm/AIXM/Z.html)
|
77
|
+
* [D (distance or length)](http://www.rubydoc.info/gems/aixm/AIXM/D.html)
|
77
78
|
* [F (frequency)](http://www.rubydoc.info/gems/aixm/AIXM/F.html)
|
78
79
|
|
79
80
|
### Features
|
@@ -88,6 +89,7 @@ AIXM.schema(:version) # => 0
|
|
88
89
|
* [NDB](http://www.rubydoc.info/gems/aixm/AIXM/Feature/NDB.html)
|
89
90
|
* [TACAN](http://www.rubydoc.info/gems/aixm/AIXM/Feature/TACAN.html)
|
90
91
|
* [VOR](http://www.rubydoc.info/gems/aixm/AIXM/Feature/VOR.html)
|
92
|
+
* [Obstacle and obstacle group](http://www.rubydoc.info/gems/aixm/AIXM/Feature/Obstacle.html)
|
91
93
|
|
92
94
|
### Components
|
93
95
|
* [Service](http://www.rubydoc.info/gems/aixm/AIXM/Component/Service.html)
|
data/lib/aixm.rb
CHANGED
@@ -14,6 +14,7 @@ require_relative 'aixm/errors'
|
|
14
14
|
require_relative 'aixm/document'
|
15
15
|
require_relative 'aixm/xy'
|
16
16
|
require_relative 'aixm/z'
|
17
|
+
require_relative 'aixm/d'
|
17
18
|
require_relative 'aixm/f'
|
18
19
|
|
19
20
|
require_relative 'aixm/component'
|
@@ -42,5 +43,7 @@ require_relative 'aixm/feature/navigational_aid/marker'
|
|
42
43
|
require_relative 'aixm/feature/navigational_aid/ndb'
|
43
44
|
require_relative 'aixm/feature/navigational_aid/tacan'
|
44
45
|
require_relative 'aixm/feature/navigational_aid/vor'
|
46
|
+
require_relative 'aixm/feature/obstacle'
|
47
|
+
require_relative 'aixm/feature/obstacle_group'
|
45
48
|
|
46
49
|
require_relative 'aixm/shortcuts'
|
@@ -4,12 +4,12 @@ module AIXM
|
|
4
4
|
class Component
|
5
5
|
class Geometry
|
6
6
|
|
7
|
-
# Circles are defined by a {#center_xy} and a {#radius}
|
7
|
+
# Circles are defined by a {#center_xy} and a {#radius}.
|
8
8
|
#
|
9
9
|
# ===Cheat Sheet in Pseudo Code:
|
10
10
|
# circle = AIXM.circle(
|
11
11
|
# center_xy: AIXM.xy
|
12
|
-
# radius:
|
12
|
+
# radius: AIXM.d
|
13
13
|
# )
|
14
14
|
#
|
15
15
|
# @see https://github.com/openflightmaps/ofmx/wiki/Airspace#circle
|
@@ -17,7 +17,7 @@ module AIXM
|
|
17
17
|
# @return [AIXM::XY] center point
|
18
18
|
attr_reader :center_xy
|
19
19
|
|
20
|
-
# @return [
|
20
|
+
# @return [AIXM::D] circle radius
|
21
21
|
attr_reader :radius
|
22
22
|
|
23
23
|
def initialize(center_xy:, radius:)
|
@@ -35,8 +35,8 @@ module AIXM
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def radius=(value)
|
38
|
-
fail(ArgumentError, "invalid radius") unless value.is_a?(
|
39
|
-
@radius = value
|
38
|
+
fail(ArgumentError, "invalid radius") unless value.is_a?(AIXM::D) && value.dist > 0
|
39
|
+
@radius = value
|
40
40
|
end
|
41
41
|
|
42
42
|
# @return [String] AIXM or OFMX markup
|
@@ -58,7 +58,7 @@ module AIXM
|
|
58
58
|
# and on the circumference of the circle.
|
59
59
|
def north_xy
|
60
60
|
AIXM.xy(
|
61
|
-
lat: center_xy.lat + radius.
|
61
|
+
lat: center_xy.lat + radius.to_km.dist / (AIXM::XY::EARTH_RADIUS / 1000) * 180 / Math::PI,
|
62
62
|
long: center_xy.long
|
63
63
|
)
|
64
64
|
end
|
@@ -11,8 +11,8 @@ module AIXM
|
|
11
11
|
# )
|
12
12
|
# helipad.xy = AIXM.xy
|
13
13
|
# helipad.z = AIXM.z or nil
|
14
|
-
# helipad.length =
|
15
|
-
# helipad.width =
|
14
|
+
# helipad.length = AIXM.d or nil # must use same unit as width
|
15
|
+
# helipad.width = AIXM.d or nil # must use same unit as length
|
16
16
|
# helipad.composition = COMPOSITIONS or nil
|
17
17
|
# helipad.status = STATUSES or nil
|
18
18
|
# helipad.remarks = String or nil
|
@@ -50,13 +50,13 @@ module AIXM
|
|
50
50
|
# @return [AIXM::XY] center point
|
51
51
|
attr_reader :xy
|
52
52
|
|
53
|
-
# @return [AIXM
|
53
|
+
# @return [AIXM::Z, nil] elevation in +:qnh+
|
54
54
|
attr_reader :z
|
55
55
|
|
56
|
-
# @return [
|
56
|
+
# @return [AIXM::D, nil] length
|
57
57
|
attr_reader :length
|
58
58
|
|
59
|
-
# @return [
|
59
|
+
# @return [AIXM::D, nil] width
|
60
60
|
attr_reader :width
|
61
61
|
|
62
62
|
# @return [Symbol, nil] composition of the surface (see {COMPOSITIONS})
|
@@ -99,13 +99,19 @@ module AIXM
|
|
99
99
|
end
|
100
100
|
|
101
101
|
def length=(value)
|
102
|
-
|
103
|
-
|
102
|
+
@length = if value
|
103
|
+
fail(ArgumentError, "invalid length") unless value.is_a?(AIXM::D) && value.dist > 0
|
104
|
+
fail(ArgumentError, "invalid length unit") if width && width.unit != value.unit
|
105
|
+
@length = value
|
106
|
+
end
|
104
107
|
end
|
105
108
|
|
106
109
|
def width=(value)
|
107
|
-
|
108
|
-
|
110
|
+
@width = if value
|
111
|
+
fail(ArgumentError, "invalid width") unless value.is_a?(AIXM::D) && value.dist > 0
|
112
|
+
fail(ArgumentError, "invalid width unit") if length && length.unit != value.unit
|
113
|
+
@width = value
|
114
|
+
end
|
109
115
|
end
|
110
116
|
|
111
117
|
def composition=(value)
|
@@ -141,9 +147,10 @@ module AIXM
|
|
141
147
|
tla.valElev(z.alt)
|
142
148
|
tla.uomDistVer(z.unit.upcase.to_s)
|
143
149
|
end
|
144
|
-
tla.valLen(length) if length
|
145
|
-
tla.valWid(width) if width
|
146
|
-
tla.uomDim(
|
150
|
+
tla.valLen(length.dist.trim) if length
|
151
|
+
tla.valWid(width.dist.trim) if width
|
152
|
+
tla.uomDim(length.unit.to_s.upcase) if length
|
153
|
+
tla.uomDim(width.unit.to_s.upcase) if width && !length
|
147
154
|
tla.codeComposition(COMPOSITIONS.key(composition).to_s) if composition
|
148
155
|
tla.codeSts(STATUSES.key(status).to_s) if status
|
149
156
|
tla.txtRmk(remarks) if remarks
|
@@ -16,8 +16,8 @@ module AIXM
|
|
16
16
|
# runway = AIXM.runway(
|
17
17
|
# name: String
|
18
18
|
# )
|
19
|
-
# runway.length =
|
20
|
-
# runway.width =
|
19
|
+
# runway.length = AIXM.d or nil # must use same unit as width
|
20
|
+
# runway.width = AIXM.d or nil # must use same unit as length
|
21
21
|
# runway.composition = COMPOSITIONS or nil
|
22
22
|
# runway.status = STATUSES or nil
|
23
23
|
# runway.remarks = String or nil
|
@@ -25,7 +25,7 @@ module AIXM
|
|
25
25
|
# runway.forth.geographic_orientation = Integer or nil # degrees
|
26
26
|
# runway.forth.xy = AIXM.xy
|
27
27
|
# runway.forth.z = AIXM.z or nil
|
28
|
-
# runway.forth.displaced_threshold =
|
28
|
+
# runway.forth.displaced_threshold = AIXM.xy or AIXM.d or nil
|
29
29
|
# runway.forth.remarks = String or nil
|
30
30
|
#
|
31
31
|
# @example Bidirectional runway
|
@@ -73,10 +73,10 @@ module AIXM
|
|
73
73
|
# @return [String] full name of runway (e.g. "12/30" or "16L/34R")
|
74
74
|
attr_reader :name
|
75
75
|
|
76
|
-
# @return [
|
76
|
+
# @return [AIXM::D, nil] length
|
77
77
|
attr_reader :length
|
78
78
|
|
79
|
-
# @return [
|
79
|
+
# @return [AIXM::D, nil] width
|
80
80
|
attr_reader :width
|
81
81
|
|
82
82
|
# @return [Symbol, nil] composition of the surface (see {COMPOSITIONS})
|
@@ -119,13 +119,19 @@ module AIXM
|
|
119
119
|
end
|
120
120
|
|
121
121
|
def length=(value)
|
122
|
-
|
123
|
-
|
122
|
+
@length = if value
|
123
|
+
fail(ArgumentError, "invalid length") unless value.is_a?(AIXM::D) && value.dist > 0
|
124
|
+
fail(ArgumentError, "invalid length unit") if width && width.unit != value.unit
|
125
|
+
@length = value
|
126
|
+
end
|
124
127
|
end
|
125
128
|
|
126
129
|
def width=(value)
|
127
|
-
|
128
|
-
|
130
|
+
@width = if value
|
131
|
+
fail(ArgumentError, "invalid width") unless value.is_a?(AIXM::D) && value.dist > 0
|
132
|
+
fail(ArgumentError, "invalid width unit") if length && length.unit != value.unit
|
133
|
+
@width = value
|
134
|
+
end
|
129
135
|
end
|
130
136
|
|
131
137
|
def composition=(value)
|
@@ -154,9 +160,10 @@ module AIXM
|
|
154
160
|
builder = Builder::XmlMarkup.new(indent: 2)
|
155
161
|
builder.Rwy do |rwy|
|
156
162
|
rwy << to_uid.indent(2)
|
157
|
-
rwy.valLen(length) if length
|
158
|
-
rwy.valWid(width) if width
|
159
|
-
rwy.uomDimRwy(
|
163
|
+
rwy.valLen(length.dist.trim) if length
|
164
|
+
rwy.valWid(width.dist.trim) if width
|
165
|
+
rwy.uomDimRwy(length.unit.to_s.upcase) if length
|
166
|
+
rwy.uomDimRwy(width.unit.to_s.upcase) if width && !length
|
160
167
|
rwy.codeComposition(COMPOSITIONS.key(composition).to_s) if composition
|
161
168
|
rwy.codeSts(STATUSES.key(status).to_s) if status
|
162
169
|
rwy.txtRmk(remarks) if remarks
|
@@ -188,9 +195,9 @@ module AIXM
|
|
188
195
|
# @return [AIXM::Z, nil] elevation of the touch down zone in +qnh+
|
189
196
|
attr_reader :z
|
190
197
|
|
191
|
-
# @return [AIXM::XY,
|
192
|
-
# coordinates (AIXM::XY) or distance (
|
193
|
-
#
|
198
|
+
# @return [AIXM::XY, AIXM::D, nil] displaced threshold point either as
|
199
|
+
# coordinates (AIXM::XY) or distance (AIXM::D) from the beginning
|
200
|
+
# point
|
194
201
|
attr_reader :displaced_threshold
|
195
202
|
|
196
203
|
# @return [String, nil] free text remarks
|
@@ -234,11 +241,16 @@ module AIXM
|
|
234
241
|
end
|
235
242
|
|
236
243
|
def displaced_threshold=(value)
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
244
|
+
case value
|
245
|
+
when AIXM::XY
|
246
|
+
@displaced_threshold = @xy.distance(value)
|
247
|
+
when AIXM::D
|
248
|
+
fail(ArgumentError, "invalid displaced threshold") unless value.dist > 0
|
249
|
+
@displaced_threshold = value
|
250
|
+
when NilClass
|
251
|
+
@displaced_threshold = nil
|
252
|
+
else
|
253
|
+
fail(ArgumentError, "invalid displaced threshold")
|
242
254
|
end
|
243
255
|
end
|
244
256
|
|
@@ -281,8 +293,8 @@ module AIXM
|
|
281
293
|
rdd_uid.codeType('DPLM')
|
282
294
|
rdd_uid.codeDayPeriod('A')
|
283
295
|
end
|
284
|
-
rdd.valDist(displaced_threshold)
|
285
|
-
rdd.uomDist(
|
296
|
+
rdd.valDist(displaced_threshold.dist.trim)
|
297
|
+
rdd.uomDist(displaced_threshold.unit.to_s.upcase)
|
286
298
|
rdd.txtRmk(remarks) if remarks
|
287
299
|
end
|
288
300
|
end
|
data/lib/aixm/d.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
using AIXM::Refinements
|
2
|
+
|
3
|
+
module AIXM
|
4
|
+
|
5
|
+
# Distance or length
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# AIXM.d(123, :m)
|
9
|
+
class D
|
10
|
+
include Comparable
|
11
|
+
|
12
|
+
UNITS = {
|
13
|
+
ft: { km: 0.0003048, m: 0.3048, nm: 0.000164578833554 },
|
14
|
+
km: { ft: 3280.839895, m: 1000, nm: 0.539956803 },
|
15
|
+
m: { ft: 3.280839895, km: 0.001, nm: 0.000539956803 },
|
16
|
+
nm: { ft: 6076.11548554, km: 1.852, m: 1852 }
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
# @return [Float] distance
|
20
|
+
attr_reader :dist
|
21
|
+
|
22
|
+
# @return [Symbol] unit (see {UNITS})
|
23
|
+
attr_reader :unit
|
24
|
+
|
25
|
+
def initialize(dist, unit)
|
26
|
+
self.dist, self.unit = dist, unit
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [String]
|
30
|
+
def inspect
|
31
|
+
%Q(#<#{self.class} #{to_s}>)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String] human readable representation (e.g. "123 m")
|
35
|
+
def to_s
|
36
|
+
[dist, unit].join(' ')
|
37
|
+
end
|
38
|
+
|
39
|
+
def dist=(value)
|
40
|
+
fail(ArgumentError, "invalid dist") unless value.is_a?(Numeric) && value >= 0
|
41
|
+
@dist = value.to_f
|
42
|
+
end
|
43
|
+
|
44
|
+
def unit=(value)
|
45
|
+
fail(ArgumentError, "invalid unit") unless value.respond_to? :to_sym
|
46
|
+
@unit = value.to_sym.downcase
|
47
|
+
fail(ArgumentError, "invalid unit") unless UNITS.has_key? @unit
|
48
|
+
end
|
49
|
+
|
50
|
+
def <=>(other)
|
51
|
+
to_m.dist <=> other.to_m.dist
|
52
|
+
end
|
53
|
+
|
54
|
+
# @!method to_ft
|
55
|
+
# @!method to_km
|
56
|
+
# @!method to_m
|
57
|
+
# @!method to_nm
|
58
|
+
# @return [AIXM::d] convert distance
|
59
|
+
UNITS.each_key do |target_unit|
|
60
|
+
define_method "to_#{target_unit}" do
|
61
|
+
return self if unit == target_unit
|
62
|
+
self.class.new((dist * UNITS[unit][target_unit]).round(8), target_unit)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/aixm/document.rb
CHANGED
@@ -53,11 +53,11 @@ module AIXM
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def created_at=(value)
|
56
|
-
@created_at =
|
56
|
+
@created_at = value&.to_time || effective_at || Time.now
|
57
57
|
end
|
58
58
|
|
59
59
|
def effective_at=(value)
|
60
|
-
@effective_at =
|
60
|
+
@effective_at = value&.to_time || created_at || Time.now
|
61
61
|
end
|
62
62
|
|
63
63
|
# Validate the generated AIXM or OFMX atainst it's XSD.
|
@@ -96,17 +96,5 @@ module AIXM
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
-
private
|
100
|
-
|
101
|
-
def parse_time(value)
|
102
|
-
case value
|
103
|
-
when String then Time.parse(value)
|
104
|
-
when Date then value.to_time
|
105
|
-
when Time then value
|
106
|
-
when nil then nil
|
107
|
-
else fail(ArgumentError, "invalid date or time")
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
99
|
end
|
112
100
|
end
|
data/lib/aixm/feature/airport.rb
CHANGED
@@ -0,0 +1,288 @@
|
|
1
|
+
using AIXM::Refinements
|
2
|
+
|
3
|
+
module AIXM
|
4
|
+
class Feature
|
5
|
+
|
6
|
+
# Obstacles are individual objects described as cylindrical volume with
|
7
|
+
# circular base and height.
|
8
|
+
#
|
9
|
+
# ===Cheat Sheet in Pseudo Code:
|
10
|
+
# obstacle = AIXM.obstacle(
|
11
|
+
# source: String or nil
|
12
|
+
# name: String or nil
|
13
|
+
# type: TYPES
|
14
|
+
# xy: AIXM.xy
|
15
|
+
# radius: AIXM.d
|
16
|
+
# z: AIXM.z
|
17
|
+
# )
|
18
|
+
# obstacle.lighting = true or false (default for AIXM) or nil (means: unknown, default for OFMX)
|
19
|
+
# obstacle.lighting_remarks = String or nil
|
20
|
+
# obstacle.marking = true or false or nil (means: unknown, default)
|
21
|
+
# obstacle.marking_remarks = String or nil
|
22
|
+
# obstacle.height = AIXM.d or nil
|
23
|
+
# obstacle.xy_accuracy = AIXM.d or nil
|
24
|
+
# obstacle.z_accuracy = AIXM.d or nil
|
25
|
+
# obstacle.height_accurate = true or false or nil (means: unknown, default)
|
26
|
+
# obstacle.valid_from = Time or Date or String or nil
|
27
|
+
# obstacle.valid_until = Time or Date or String or nil
|
28
|
+
# obstacle.remarks = String or nil
|
29
|
+
#
|
30
|
+
# @see https://github.com/openflightmaps/ofmx/wiki/Obstacle
|
31
|
+
class Obstacle < Feature
|
32
|
+
public_class_method :new
|
33
|
+
|
34
|
+
TYPES = {
|
35
|
+
ANTENNA: :antenna,
|
36
|
+
BUILDING: :building,
|
37
|
+
CHIMNEY: :chimney,
|
38
|
+
CRANE: :crane,
|
39
|
+
MAST: :mast,
|
40
|
+
TOWER: :tower,
|
41
|
+
WINDTURBINE: :wind_turbine,
|
42
|
+
OTHER: :other # specify in remarks
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
# @return [String] full name
|
46
|
+
attr_reader :name
|
47
|
+
|
48
|
+
# @return [Symbol] type of obstacle
|
49
|
+
attr_reader :type
|
50
|
+
|
51
|
+
# @return [AIXM::XY] circular base center point
|
52
|
+
attr_reader :xy
|
53
|
+
|
54
|
+
# @return [AIXM::D] circular base radius
|
55
|
+
attr_reader :radius
|
56
|
+
|
57
|
+
# @return [AIXM::Z] elevation of the top point in +:qnh+
|
58
|
+
attr_reader :z
|
59
|
+
|
60
|
+
# @return [Boolean, nil] lighting (e.g. strobes)
|
61
|
+
# true => lighting present, false => no lighting, nil => unknown
|
62
|
+
attr_reader :lighting
|
63
|
+
|
64
|
+
# @return [String, nil] detailed description of the lighting
|
65
|
+
attr_reader :lighting_remarks
|
66
|
+
|
67
|
+
# @return [Boolean, nil] marking (e.g. red/white paint)
|
68
|
+
# true => marking present, false => no marking, nil => unknown
|
69
|
+
attr_reader :marking
|
70
|
+
|
71
|
+
# @return [String, nil] detailed description of the marking
|
72
|
+
attr_reader :marking_remarks
|
73
|
+
|
74
|
+
# @return [AIXM::D, nil] height from ground to top point
|
75
|
+
attr_reader :height
|
76
|
+
|
77
|
+
# @return [AIXM::D, nil] margin of error for circular base center point
|
78
|
+
attr_reader :xy_accuracy
|
79
|
+
|
80
|
+
# @return [AIXM::D, nil] margin of error for top point
|
81
|
+
attr_reader :z_accuracy
|
82
|
+
|
83
|
+
# @return [Boolean, nil] height accuracy
|
84
|
+
# true => height measured, false => height estimated, nil => unknown
|
85
|
+
attr_reader :height_accurate
|
86
|
+
|
87
|
+
# @return [Time, Date, String, nil] effective after this point in time
|
88
|
+
attr_reader :valid_from
|
89
|
+
|
90
|
+
# @return [Time, Date, String, nil] effective until this point in time
|
91
|
+
attr_reader :valid_until
|
92
|
+
|
93
|
+
# @return [String, nil] free text remarks
|
94
|
+
attr_reader :remarks
|
95
|
+
|
96
|
+
def initialize(source: nil, name: nil, type:, xy:, radius:, z:)
|
97
|
+
super(source: source)
|
98
|
+
self.name, self.type, self.xy, self.radius, self.z = name, type, xy, radius, z
|
99
|
+
@lighting = @marking = @height_accurate = false
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [String]
|
103
|
+
def inspect
|
104
|
+
%Q(#<#{self.class} xy="#{xy.to_s}" type=#{type.inspect}>)
|
105
|
+
end
|
106
|
+
|
107
|
+
def name=(value)
|
108
|
+
fail(ArgumentError, "invalid name") unless value.nil? || value.is_a?(String)
|
109
|
+
@name = value&.uptrans
|
110
|
+
end
|
111
|
+
|
112
|
+
def type=(value)
|
113
|
+
@type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
|
114
|
+
end
|
115
|
+
|
116
|
+
def xy=(value)
|
117
|
+
fail(ArgumentError, "invalid xy") unless value.is_a? AIXM::XY
|
118
|
+
@xy = value
|
119
|
+
end
|
120
|
+
|
121
|
+
def radius=(value)
|
122
|
+
fail(ArgumentError, "invalid radius") unless value.is_a?(AIXM::D) && value.dist > 0
|
123
|
+
@radius = value
|
124
|
+
end
|
125
|
+
|
126
|
+
def z=(value)
|
127
|
+
fail(ArgumentError, "invalid z") unless value.is_a?(AIXM::Z) && value.qnh?
|
128
|
+
@z = value
|
129
|
+
end
|
130
|
+
|
131
|
+
def lighting=(value)
|
132
|
+
fail(ArgumentError, "invalid lighting") unless [true, false, nil].include? value
|
133
|
+
@lighting = value
|
134
|
+
end
|
135
|
+
|
136
|
+
def lighting_remarks=(value)
|
137
|
+
@lighting_remarks = value&.to_s
|
138
|
+
end
|
139
|
+
|
140
|
+
def marking=(value)
|
141
|
+
fail(ArgumentError, "invalid marking") unless [true, false, nil].include? value
|
142
|
+
@marking = value
|
143
|
+
end
|
144
|
+
|
145
|
+
def marking_remarks=(value)
|
146
|
+
@marking_remarks = value&.to_s
|
147
|
+
end
|
148
|
+
|
149
|
+
def height=(value)
|
150
|
+
fail(ArgumentError, "invalid height") unless value.nil? || (value.is_a?(AIXM::D) && value.dist > 0)
|
151
|
+
@height = value
|
152
|
+
end
|
153
|
+
|
154
|
+
def xy_accuracy=(value)
|
155
|
+
fail(ArgumentError, "invalid xy accuracy") unless value.nil? || value.is_a?(AIXM::D)
|
156
|
+
@xy_accuracy = value
|
157
|
+
end
|
158
|
+
|
159
|
+
def z_accuracy=(value)
|
160
|
+
fail(ArgumentError, "invalid z accuracy") unless value.nil? || value.is_a?(AIXM::D)
|
161
|
+
@z_accuracy = value
|
162
|
+
end
|
163
|
+
|
164
|
+
def height_accurate=(value)
|
165
|
+
fail(ArgumentError, "invalid height accurate") unless [true, false, nil].include? value
|
166
|
+
@height_accurate = value
|
167
|
+
end
|
168
|
+
|
169
|
+
def valid_from=(value)
|
170
|
+
@valid_from = value&.to_time
|
171
|
+
end
|
172
|
+
|
173
|
+
def valid_until=(value)
|
174
|
+
@valid_until = value&.to_time
|
175
|
+
end
|
176
|
+
|
177
|
+
def remarks=(value)
|
178
|
+
@remarks = value&.to_s
|
179
|
+
end
|
180
|
+
|
181
|
+
# @return [Boolean] estimation whether one obstacle (e.g. single tree) or
|
182
|
+
# a cluster of obstacles (e.g. a few trees)
|
183
|
+
def clustered?
|
184
|
+
height && radius > height
|
185
|
+
end
|
186
|
+
|
187
|
+
# @return [Boolean] whether part of an obstacle group
|
188
|
+
def grouped?
|
189
|
+
respond_to? :group
|
190
|
+
end
|
191
|
+
|
192
|
+
# @return [String] UID markup
|
193
|
+
def to_uid(as: :ObsUid)
|
194
|
+
builder = Builder::XmlMarkup.new(indent: 2)
|
195
|
+
builder.tag!(as) do |tag|
|
196
|
+
tag.geoLat((xy.lat(AIXM.schema)))
|
197
|
+
tag.geoLong((xy.long(AIXM.schema)))
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# @return [String] AIXM or OFMX markup
|
202
|
+
def to_xml
|
203
|
+
builder = Builder::XmlMarkup.new(indent: 2)
|
204
|
+
builder.comment! "Obstacle: [#{type}] #{xy.to_s} #{name}".strip
|
205
|
+
builder.Obs({ source: ((source || (group.source if grouped?)) if AIXM.ofmx?) }.compact) do |obs|
|
206
|
+
obs << to_uid.indent(2)
|
207
|
+
obs.txtName(name) if name
|
208
|
+
if AIXM.ofmx?
|
209
|
+
obs.codeType(TYPES.key(type).to_s)
|
210
|
+
else
|
211
|
+
obs.txtDescrType(TYPES.key(type).to_s)
|
212
|
+
obs.codeGroup(grouped? || clustered? ? 'Y' : 'N')
|
213
|
+
end
|
214
|
+
if AIXM.ofmx?
|
215
|
+
obs.codeLgt(lighting ? 'Y' : 'N') unless lighting.nil?
|
216
|
+
obs.codeMarking(marking ? 'Y' : 'N') unless marking.nil?
|
217
|
+
else
|
218
|
+
obs.codeLgt(lighting ? 'Y' : 'N')
|
219
|
+
end
|
220
|
+
obs.txtDescrLgt(lighting_remarks) if lighting_remarks
|
221
|
+
obs.txtDescrMarking(marking_remarks) if marking_remarks
|
222
|
+
obs.codeDatum('WGE')
|
223
|
+
if xy_accuracy
|
224
|
+
obs.valGeoAccuracy(xy_accuracy.dist.trim)
|
225
|
+
obs.uomGeoAccuracy(xy_accuracy.unit.upcase.to_s)
|
226
|
+
end
|
227
|
+
obs.valElev(z.alt)
|
228
|
+
obs.valElevAccuracy(z_accuracy.to_ft.dist.round) if z_accuracy
|
229
|
+
obs.valHgt(height.to_ft.dist.round) if height
|
230
|
+
if AIXM.ofmx? && !height_accurate.nil?
|
231
|
+
obs.codeHgtAccuracy(height_accurate ? 'Y' : 'N')
|
232
|
+
end
|
233
|
+
obs.uomDistVer('FT')
|
234
|
+
if AIXM.ofmx?
|
235
|
+
obs.valRadius(radius.dist.trim)
|
236
|
+
obs.uomRadius(radius.unit.upcase.to_s)
|
237
|
+
if grouped?
|
238
|
+
obs.codeGroupId(group.id)
|
239
|
+
obs.txtGroupName(group.name) if group.name
|
240
|
+
if linked?
|
241
|
+
obs << linked_to.to_uid(as: :ObsUidLink).indent(2)
|
242
|
+
obs.codeLinkType(AIXM::Feature::ObstacleGroup::LINK_TYPES.key(link_type).to_s)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
obs.datetimeValidWef(valid_from.xmlschema) if valid_from
|
246
|
+
obs.datetimeValidTil(valid_until.xmlschema) if valid_until
|
247
|
+
end
|
248
|
+
obs.txtRmk(remarks) if remarks
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Extensions used by ObstacleGroup feature
|
253
|
+
module Grouped
|
254
|
+
# @return [AIXM::Feature::ObstacleGroup] group this obstacle belongs to
|
255
|
+
attr_reader :group
|
256
|
+
|
257
|
+
# @return [AIXM::Feature::Obstacle] another obstacle this one is linked to
|
258
|
+
attr_reader :linked_to
|
259
|
+
|
260
|
+
# @return [Symbol] type of link between two obstacles
|
261
|
+
attr_reader :link_type
|
262
|
+
|
263
|
+
def group=(value)
|
264
|
+
fail(ArgumentError, "invalid group") unless value.is_a?(AIXM::Feature::ObstacleGroup)
|
265
|
+
@group = value
|
266
|
+
end
|
267
|
+
private :group=
|
268
|
+
|
269
|
+
# @return [Boolean] whether obstacle is linked to another one
|
270
|
+
def linked?
|
271
|
+
!!linked_to
|
272
|
+
end
|
273
|
+
|
274
|
+
def linked_to=(value)
|
275
|
+
fail(ArgumentError, "invalid linked to") unless value.is_a?(AIXM::Feature::Obstacle)
|
276
|
+
@linked_to = value
|
277
|
+
end
|
278
|
+
private :linked_to=
|
279
|
+
|
280
|
+
def link_type=(value)
|
281
|
+
@link_type = AIXM::Feature::ObstacleGroup::LINK_TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid link type")
|
282
|
+
end
|
283
|
+
private :link_type=
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|