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