rcap 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/README +151 -0
- data/lib/rcap.rb +21 -0
- data/lib/rcap/alert.rb +186 -0
- data/lib/rcap/area.rb +82 -0
- data/lib/rcap/circle.rb +62 -0
- data/lib/rcap/event_code.rb +20 -0
- data/lib/rcap/geocode.rb +20 -0
- data/lib/rcap/info.rb +239 -0
- data/lib/rcap/parameter.rb +54 -0
- data/lib/rcap/point.rb +39 -0
- data/lib/rcap/polygon.rb +50 -0
- data/lib/rcap/resource.rb +81 -0
- data/lib/rcap/utilities.rb +50 -0
- data/lib/rcap/validations.rb +116 -0
- data/spec/alert_spec.rb +132 -0
- data/spec/area_spec.rb +120 -0
- data/spec/circle_spec.rb +59 -0
- data/spec/geocode_spec.rb +26 -0
- data/spec/info_spec.rb +119 -0
- data/spec/point_spec.rb +34 -0
- data/spec/polygon_spec.rb +45 -0
- data/spec/resource_spec.rb +74 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/utilities_spec.rb +57 -0
- data/spec/validations_spec.rb +95 -0
- metadata +99 -0
data/CHANGELOG
ADDED
data/README
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
= RCAP - Common Alerting Protocol for Ruby
|
2
|
+
|
3
|
+
== Overview
|
4
|
+
|
5
|
+
The Common Alerting Protocol is a lightweight standard to facilitate the distribution of alerting data. RCAP is an implementation of the CAP in Ruby. It allows for the creation of RCAP messages from Ruby applications and the parsing of external messages.
|
6
|
+
|
7
|
+
RCAP supports CAP Version 1.1.
|
8
|
+
|
9
|
+
== Usage
|
10
|
+
|
11
|
+
To include RCAP into your application add the following require
|
12
|
+
|
13
|
+
require 'rcap/rcap'
|
14
|
+
|
15
|
+
=== Creating an Alert
|
16
|
+
|
17
|
+
==== From XML
|
18
|
+
|
19
|
+
RCAP allows for the parsing of a CAP XML string into a collection of RCAP objects
|
20
|
+
|
21
|
+
alert = RCAP::Alert.from_xml( xml_string )
|
22
|
+
|
23
|
+
Currently RCAP only supports version 1.1 of the CAP standard and the parser is as strict as possible when parsing data.
|
24
|
+
|
25
|
+
==== Programatically
|
26
|
+
|
27
|
+
Alerts can also be created programatically by initialising the various RCAP classes manually
|
28
|
+
|
29
|
+
include RCAP # Include RCAP module into namespace
|
30
|
+
|
31
|
+
alert = Alert.new( :sender => 'cape_town_disaster_relief@capetown.municipal.za',
|
32
|
+
:status => Alert::STATUS_ACTUAL,
|
33
|
+
:msg_type => Alert::MSG_TYPE_ALERT,
|
34
|
+
:scope => Alert::SCOPE_PUBLIC,
|
35
|
+
:infos => Info.new( :event => 'Liquid Petroleoum Tanker Fire',
|
36
|
+
:language => 'en-ZA',
|
37
|
+
:categories => [ Info::CATEGORY_TRANSPORT, Info::CATEGORY_FIRE ],
|
38
|
+
:urgency => Info::URGENCY_IMMEDIATE,
|
39
|
+
:severity => Info::SEVERITY_SEVERE,
|
40
|
+
:certainty => Info::CERTAINTY_OBSERVED,
|
41
|
+
:headline => 'LIQUID PETROLEOUM TANKER FIRE ON N2 INCOMING FREEWAY',
|
42
|
+
:description => 'A liquid petroleoum tanker has caught fire on the N2 incoming freeway 1km
|
43
|
+
after the R300 interchange. Municipal fire fighting crews have been dispatched.
|
44
|
+
Traffic control officers are on the scene and have diverted traffic onto
|
45
|
+
alternate routes.' ))
|
46
|
+
|
47
|
+
# Accessing attributes
|
48
|
+
puts alert.status # Print out "Actual"
|
49
|
+
puts alert.infos[0].language # Print out "en-ZA"
|
50
|
+
puts alert.infos[0].categories.join( ' ' ) # Print out "Transport Fire"
|
51
|
+
|
52
|
+
puts alert.to_xml # Print out CAP XML message
|
53
|
+
|
54
|
+
Will print the following CAP XML
|
55
|
+
|
56
|
+
<?xml version='1.0'?>
|
57
|
+
<alert xmlns='urn:oasis:names:tc:emergency:cap:1.1'>
|
58
|
+
<identifier>494207a7-f86b-4060-8318-a4b2a3ce565e</identifier>
|
59
|
+
<sender>cape_town_disaster_relief@capetown.municipal.za</sender>
|
60
|
+
<sent>2009-10-26T21:04:51+2:00</sent>
|
61
|
+
<status>Actual</status>
|
62
|
+
<msgType>Alert</msgType>
|
63
|
+
<scope>Public</scope>
|
64
|
+
<info>
|
65
|
+
<language>en-ZA</language>
|
66
|
+
<category>Transport</category>
|
67
|
+
<category>Fire</category>
|
68
|
+
<event>Liquid Petroleoum Tanker Fire</event>
|
69
|
+
<urgency>Immediate</urgency>
|
70
|
+
<severity>Severe</severity>
|
71
|
+
<certainty>Observed</certainty>
|
72
|
+
<headline>LIQUID PETROLEOUM TANKER FIRE ON N2 INCOMING FREEWAY</headline>
|
73
|
+
<description>
|
74
|
+
A liquid petroleoum tanker has caught fire on the N2 incoming freeway 1km
|
75
|
+
after the R300 interchange. Municipal fire fighting crews have been
|
76
|
+
dispatched. Traffic control officers are on the scene and have diverted
|
77
|
+
traffic onto alternate routes.
|
78
|
+
</description>
|
79
|
+
</info>
|
80
|
+
</alert>
|
81
|
+
|
82
|
+
|
83
|
+
You can also create the various message elements seperately and then combine them later:
|
84
|
+
|
85
|
+
# Setting attributes at initialisation
|
86
|
+
alert = Alert.new( :sender => 'cape_town_disaster_relief@capetown.municipal.za',
|
87
|
+
:status => Alert::STATUS_ACTUAL,
|
88
|
+
:msg_type => Alert::MSG_TYPE_ALERT,
|
89
|
+
:scope => Alert::SCOPE_PUBLIC )
|
90
|
+
|
91
|
+
# Setting attributes after initialisation
|
92
|
+
info = Info.new
|
93
|
+
info.event = 'Liquid Petroleoum Tanker Fire'
|
94
|
+
info.language = 'en-ZA'
|
95
|
+
info.categories = [ Info::CATEGORY_TRANSPORT, Info::CATEGORY_FIRE ]
|
96
|
+
info.urgency = Info::URGENCY_IMMEDIATE
|
97
|
+
info.severity = Info::SEVERITY_SEVERE
|
98
|
+
info.certainty = Info::CERTAINTY_OBSERVED
|
99
|
+
|
100
|
+
|
101
|
+
resource = Resource.new( :resource_desc => 'Cape Town Municipal Traffic Management Guidelines',
|
102
|
+
:uri => 'http://capetown.municipal.za/traffic/management_guidelines.pdf',
|
103
|
+
:mime_type => 'application/pdf')
|
104
|
+
|
105
|
+
# Combining all elements
|
106
|
+
info.resources << resource
|
107
|
+
alert.infos << info
|
108
|
+
|
109
|
+
=== Validating an alert
|
110
|
+
|
111
|
+
The RCAP API aims to codify as many of the rules of the CAP XML format into validation rules that can be checked using the Assistance API (http://assistance.rubyforge.org). The following Info object has two attributes ('severity' and 'certainty') set to incorrect values.
|
112
|
+
|
113
|
+
info = Info.new( :event => 'Liquid Petroleoum Tanker Fire',
|
114
|
+
:language => 'en-ZA',
|
115
|
+
:categories => [ Info::CATEGORY_TRANSPORT, Info::CATEGORY_FIRE ],
|
116
|
+
:urgency => Info::URGENCY_IMMEDIATE,
|
117
|
+
:severity => nil, # Severity is not assigned
|
118
|
+
:certainty => 'Unknown Certainty' ) # Certainty is assigned in incorrect value
|
119
|
+
puts info.valid?
|
120
|
+
puts info.errors.full_messages
|
121
|
+
|
122
|
+
Will produce the folling output:
|
123
|
+
|
124
|
+
false
|
125
|
+
severity is not present
|
126
|
+
certainty can only be assigned the following values: Observed, Likely, Possible, Unlikely, Unknown
|
127
|
+
|
128
|
+
A full spec suite using RSpec (http://www.rspec.info) was used to test the validations and currently numbers over 140 tests.
|
129
|
+
|
130
|
+
== Version
|
131
|
+
|
132
|
+
0.1
|
133
|
+
|
134
|
+
== Dependencies
|
135
|
+
|
136
|
+
RCAP depends on the following gems
|
137
|
+
|
138
|
+
* Assistance - http://assistance.rubyforge.org
|
139
|
+
* UUIDTools - http://uuidtools.rubyforge.org
|
140
|
+
|
141
|
+
== Authors
|
142
|
+
|
143
|
+
Farrel Lifson - farrel.lifson@aimred.com
|
144
|
+
|
145
|
+
== License
|
146
|
+
|
147
|
+
RCAP is released under the BSD License.
|
148
|
+
|
149
|
+
== Copyright
|
150
|
+
|
151
|
+
2009 Aimred CC
|
data/lib/rcap.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'assistance'
|
3
|
+
require 'uuidtools'
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'rcap/utilities'
|
6
|
+
require 'rcap/validations'
|
7
|
+
require 'rcap/alert'
|
8
|
+
require 'rcap/parameter'
|
9
|
+
require 'rcap/event_code'
|
10
|
+
require 'rcap/info'
|
11
|
+
require 'rcap/resource'
|
12
|
+
require 'rcap/point'
|
13
|
+
require 'rcap/circle'
|
14
|
+
require 'rcap/polygon'
|
15
|
+
require 'rcap/geocode'
|
16
|
+
require 'rcap/area'
|
17
|
+
|
18
|
+
module RCAP
|
19
|
+
XMLNS = "urn:oasis:names:tc:emergency:cap:1.1"
|
20
|
+
VERSION = "0.1"
|
21
|
+
end
|
data/lib/rcap/alert.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
module RCAP
|
2
|
+
# An Alert object is valid if
|
3
|
+
# * it has an identifier
|
4
|
+
# * it has a sender
|
5
|
+
# * it has a sent time
|
6
|
+
# * it has a valid status value
|
7
|
+
# * it has a valid messge type value
|
8
|
+
# * it has a valid scope value
|
9
|
+
# * all Info objects contained in infos are valid
|
10
|
+
class Alert
|
11
|
+
include Validation
|
12
|
+
|
13
|
+
STATUS_ACTUAL = "Actual"
|
14
|
+
STATUS_EXERCISE = "Exercise"
|
15
|
+
STATUS_SYSTEM = "System"
|
16
|
+
STATUS_TEST = "Test"
|
17
|
+
STATUS_DRAFT = "Draft"
|
18
|
+
ALL_STATUSES = [ STATUS_ACTUAL, STATUS_EXERCISE, STATUS_SYSTEM, STATUS_TEST, STATUS_DRAFT ] #:nodoc:
|
19
|
+
|
20
|
+
MSG_TYPE_ALERT = "Alert"
|
21
|
+
MSG_TYPE_UPDATE = "Update"
|
22
|
+
MSG_TYPE_CANCEL = "Cancel"
|
23
|
+
MSG_TYPE_ACK = "Ack"
|
24
|
+
MSG_TYPE_ERROR = "Error"
|
25
|
+
ALL_MSG_TYPES = [ MSG_TYPE_ALERT, MSG_TYPE_UPDATE, MSG_TYPE_CANCEL, MSG_TYPE_ACK, MSG_TYPE_ERROR ] #:nodoc:
|
26
|
+
|
27
|
+
SCOPE_PUBLIC = "Public"
|
28
|
+
SCOPE_RESTRICTED = "Restricted"
|
29
|
+
SCOPE_PRIVATE = "Private"
|
30
|
+
ALL_SCOPES = [ SCOPE_PUBLIC, SCOPE_PRIVATE, SCOPE_RESTRICTED ] #:nodoc:
|
31
|
+
|
32
|
+
XML_ELEMENT_NAME = 'alert' # :nodoc:
|
33
|
+
IDENTIFIER_ELEMENT_NAME = 'identifier' # :nodoc:
|
34
|
+
SENDER_ELEMENT_NAME = 'sender' # :nodoc:
|
35
|
+
SENT_ELEMENT_NAME = 'sent' # :nodoc:
|
36
|
+
STATUS_ELEMENT_NAME = 'status' # :nodoc:
|
37
|
+
MSG_TYPE_ELEMENT_NAME = 'msgType' # :nodoc:
|
38
|
+
SOURCE_ELEMENT_NAME = 'source' # :nodoc:
|
39
|
+
SCOPE_ELEMENT_NAME = 'scope' # :nodoc:
|
40
|
+
RESTRICTION_ELEMENT_NAME = 'restriction' # :nodoc:
|
41
|
+
ADDRESSES_ELEMENT_NAME = 'addresses' # :nodoc:
|
42
|
+
CODE_ELEMENT_NAME = 'code' # :nodoc:
|
43
|
+
NOTE_ELEMENT_NAME = 'note' # :nodoc:
|
44
|
+
REFERENCES_ELEMENT_NAME = 'references' # :nodoc:
|
45
|
+
INCIDENTS_ELEMENT_NAME = 'incidents' # :nodoc:
|
46
|
+
|
47
|
+
XPATH = 'cap:alert' # :nodoc:
|
48
|
+
IDENTIFIER_XPATH = "cap:#{ IDENTIFIER_ELEMENT_NAME }" # :nodoc:
|
49
|
+
SENDER_XPATH = "cap:#{ SENDER_ELEMENT_NAME }" # :nodoc:
|
50
|
+
SENT_XPATH = "cap:#{ SENT_ELEMENT_NAME }" # :nodoc:
|
51
|
+
STATUS_XPATH = "cap:#{ STATUS_ELEMENT_NAME }" # :nodoc:
|
52
|
+
MSG_TYPE_XPATH = "cap:#{ MSG_TYPE_ELEMENT_NAME }" # :nodoc:
|
53
|
+
SOURCE_XPATH = "cap:#{ SOURCE_ELEMENT_NAME }" # :nodoc:
|
54
|
+
SCOPE_XPATH = "cap:#{ SCOPE_ELEMENT_NAME }" # :nodoc:
|
55
|
+
RESTRICTION_XPATH = "cap:#{ RESTRICTION_ELEMENT_NAME }" # :nodoc:
|
56
|
+
ADDRESSES_XPATH = "cap:#{ ADDRESSES_ELEMENT_NAME }" # :nodoc:
|
57
|
+
CODE_XPATH = "cap:#{ CODE_ELEMENT_NAME }" # :nodoc:
|
58
|
+
NOTE_XPATH = "cap:#{ NOTE_ELEMENT_NAME }" # :nodoc:
|
59
|
+
REFERENCES_XPATH = "cap:#{ REFERENCES_ELEMENT_NAME }" # :nodoc:
|
60
|
+
INCIDENTS_XPATH = "cap:#{ INCIDENTS_ELEMENT_NAME }" # :nodoc:
|
61
|
+
|
62
|
+
# If not set a UUID will be set by default
|
63
|
+
attr_accessor( :identifier)
|
64
|
+
attr_accessor( :sender )
|
65
|
+
# Sent Time - If not set will value will be time of creation.
|
66
|
+
attr_accessor( :sent )
|
67
|
+
attr_accessor( :status )
|
68
|
+
# Message Type
|
69
|
+
attr_accessor( :msg_type )
|
70
|
+
attr_accessor( :scope )
|
71
|
+
attr_accessor( :source )
|
72
|
+
# Depends on scope being SCOPE_RESTRICTED.
|
73
|
+
attr_accessor( :restriction )
|
74
|
+
attr_accessor( :code )
|
75
|
+
attr_accessor( :note )
|
76
|
+
|
77
|
+
# Collection of address strings. Depends on scope being SCOPE_PRIVATE.
|
78
|
+
attr_reader( :addresses )
|
79
|
+
# Collection of reference strings - see Alert#to_reference
|
80
|
+
attr_reader( :references)
|
81
|
+
# Collection of incident strings
|
82
|
+
attr_reader( :incidents )
|
83
|
+
# Collection of Info objects
|
84
|
+
attr_reader( :infos )
|
85
|
+
|
86
|
+
validates_presence_of( :identifier, :sender, :sent, :status, :msg_type, :scope )
|
87
|
+
|
88
|
+
validates_inclusion_of( :status, :in => ALL_STATUSES )
|
89
|
+
validates_inclusion_of( :msg_type, :in => ALL_MSG_TYPES )
|
90
|
+
validates_inclusion_of( :scope, :in => ALL_SCOPES )
|
91
|
+
|
92
|
+
validates_format_of( :identifier, :with => ALLOWED_CHARACTERS )
|
93
|
+
validates_format_of( :sender , :with => ALLOWED_CHARACTERS )
|
94
|
+
|
95
|
+
validates_dependency_of( :addresses, :on => :scope, :with_value => SCOPE_PRIVATE )
|
96
|
+
validates_dependency_of( :restriction, :on => :scope, :with_value => SCOPE_RESTRICTED )
|
97
|
+
|
98
|
+
validates_collection_of( :infos )
|
99
|
+
|
100
|
+
def initialize( attributes = {})
|
101
|
+
@identifier = attributes[ :identifier ] || UUIDTools::UUID.random_create.to_s
|
102
|
+
@sender = attributes[ :sender ]
|
103
|
+
@sent = attributes[ :sent ] || DateTime.now
|
104
|
+
@status = attributes[ :status ]
|
105
|
+
@msg_type = attributes[ :msg_type ]
|
106
|
+
@scope = attributes[ :scope ]
|
107
|
+
@source = attributes[ :source ]
|
108
|
+
@restriction = attributes[ :restriction ]
|
109
|
+
@addresses = Array( attributes[ :addresses ])
|
110
|
+
@references = Array( attributes[ :references ])
|
111
|
+
@incidents = Array( attributes[ :incidents ])
|
112
|
+
@infos = Array( attributes[ :infos ])
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_xml_element #:nodoc:
|
116
|
+
xml_element = REXML::Element.new( XML_ELEMENT_NAME )
|
117
|
+
xml_element.add_namespace( RCAP::XMLNS )
|
118
|
+
xml_element.add_element( IDENTIFIER_ELEMENT_NAME ).add_text( self.identifier )
|
119
|
+
xml_element.add_element( SENDER_ELEMENT_NAME ).add_text( self.sender )
|
120
|
+
xml_element.add_element( SENT_ELEMENT_NAME ).add_text( self.sent.to_s )
|
121
|
+
xml_element.add_element( STATUS_ELEMENT_NAME ).add_text( self.status )
|
122
|
+
xml_element.add_element( MSG_TYPE_ELEMENT_NAME ).add_text( self.msg_type )
|
123
|
+
xml_element.add_element( SOURCE_ELEMENT_NAME ).add_text( self.source ) if self.source
|
124
|
+
xml_element.add_element( SCOPE_ELEMENT_NAME ).add_text( self.scope )
|
125
|
+
xml_element.add_element( RESTRICTION_ELEMENT_NAME ).add_text( self.restriction ) if self.restriction
|
126
|
+
unless self.addresses.empty?
|
127
|
+
xml_element.add_element( ADDRESSES_ELEMENT_NAME ).add_text( self.addresses.to_s_for_cap )
|
128
|
+
end
|
129
|
+
xml_element.add_element( CODE_ELEMENT_NAME ).add_text( self.code ) if self.code
|
130
|
+
xml_element.add_element( NOTE_ELEMENT_NAME ).add_text( self.note ) if self.note
|
131
|
+
unless self.references.empty?
|
132
|
+
xml_element.add_element( REFERENCES_ELEMENT_NAME ).add_text( self.references.join( ' ' ))
|
133
|
+
end
|
134
|
+
unless self.incidents.empty?
|
135
|
+
xml_element.add_element( INCIDENTS_ELEMENT_NAME ).add_text( self.incidents.join( ' ' ))
|
136
|
+
end
|
137
|
+
self.infos.each do |info|
|
138
|
+
xml_element.add_element( info.to_xml_element )
|
139
|
+
end
|
140
|
+
xml_element
|
141
|
+
end
|
142
|
+
|
143
|
+
def to_xml_document #:nodoc:
|
144
|
+
xml_document = REXML::Document.new
|
145
|
+
xml_document.add( REXML::XMLDecl.new )
|
146
|
+
xml_document.add( self.to_xml_element )
|
147
|
+
xml_document
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns a string containing the XML representation of the alert.
|
151
|
+
def to_xml
|
152
|
+
self.to_xml_document.to_s
|
153
|
+
end
|
154
|
+
|
155
|
+
# Returns a string of the format 'sender,identifier,sent' suitable for usage as a reference in a CAP message.
|
156
|
+
def to_reference
|
157
|
+
"#{ self.sender },#{ self.identifier },#{ self.sent }"
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.from_xml_element( alert_xml_element ) # :nodoc:
|
161
|
+
alert = RCAP::Alert.new( :identifier => RCAP.xpath_text( alert_xml_element, RCAP::Alert::IDENTIFIER_XPATH ),
|
162
|
+
:sender => RCAP.xpath_text( alert_xml_element, SENDER_XPATH ),
|
163
|
+
:sent => (( sent = RCAP.xpath_first( alert_xml_element, SENT_XPATH )) ? DateTime.parse( sent.text ) : nil ),
|
164
|
+
:status => RCAP.xpath_text( alert_xml_element, STATUS_XPATH ),
|
165
|
+
:msg_type => RCAP.xpath_text( alert_xml_element, MSG_TYPE_XPATH ),
|
166
|
+
:source => RCAP.xpath_text( alert_xml_element, SOURCE_XPATH ),
|
167
|
+
:scope => RCAP.xpath_text( alert_xml_element, SCOPE_XPATH ),
|
168
|
+
:restriction => RCAP.xpath_text( alert_xml_element, RESTRICTION_XPATH ),
|
169
|
+
:addresses => (( address = RCAP.xpath_text( alert_xml_element, ADDRESSES_XPATH )) ? address.unpack_cap_list : nil ),
|
170
|
+
:code => RCAP.xpath_text( alert_xml_element, CODE_XPATH ),
|
171
|
+
:note => RCAP.xpath_text( alert_xml_element, NOTE_XPATH ),
|
172
|
+
:references => (( references = RCAP.xpath_text( alert_xml_element, REFERENCES_XPATH )) ? references.split( ' ' ) : nil ),
|
173
|
+
:incidents => (( incidents = RCAP.xpath_text( alert_xml_element, INCIDENTS_XPATH )) ? incidents.split( ' ' ) : nil ),
|
174
|
+
:infos => RCAP.xpath_match( alert_xml_element, RCAP::Info::XPATH ).map{ |element| RCAP::Info.from_xml_element( element )})
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.from_xml_document( xml_document ) # :nodoc:
|
178
|
+
self.from_xml_element( xml_document.root )
|
179
|
+
end
|
180
|
+
|
181
|
+
# Initialised an Alert object from the XML string.
|
182
|
+
def self.from_xml( xml_string )
|
183
|
+
self.from_xml_document( REXML::Document.new( xml_string ))
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/lib/rcap/area.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
module RCAP
|
2
|
+
# An Area object is valid if
|
3
|
+
# * it has an area description
|
4
|
+
# * all Circle objects contained in circles are valid
|
5
|
+
# * all Geocode objects contained in geocodes are valid
|
6
|
+
# * all Polygon objects contained in polygons are valid
|
7
|
+
# * altitude has a value if ceiling is set
|
8
|
+
class Area
|
9
|
+
include Validation
|
10
|
+
|
11
|
+
# Area Description - Textual description of area.
|
12
|
+
attr_accessor( :area_desc )
|
13
|
+
# Expressed in feet above sea level
|
14
|
+
attr_accessor( :altitude )
|
15
|
+
# Expressed in feet above sea level.
|
16
|
+
attr_accessor( :ceiling )
|
17
|
+
# Collection of Circle objects
|
18
|
+
attr_reader( :circles )
|
19
|
+
# Collection of Geocode objects
|
20
|
+
attr_reader( :geocodes )
|
21
|
+
# Collection of Polygon objects
|
22
|
+
attr_reader( :polygons )
|
23
|
+
|
24
|
+
validates_presence_of( :area_desc )
|
25
|
+
validates_collection_of( :circles, :geocodes, :polygons )
|
26
|
+
validates_dependency_of( :ceiling, :on => :altitude )
|
27
|
+
|
28
|
+
XML_ELEMENT_NAME = 'area' # :nodoc:
|
29
|
+
AREA_DESC_ELEMENT_NAME = 'areaDesc' # :nodoc:
|
30
|
+
ALTITUDE_ELEMENT_NAME = 'altitude' # :nodoc:
|
31
|
+
CEILING_ELEMENT_NAME = 'ceiling' # :nodoc:
|
32
|
+
|
33
|
+
XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
|
34
|
+
AREA_DESC_XPATH = "cap:#{ AREA_DESC_ELEMENT_NAME }" # :nodoc:
|
35
|
+
ALTITUDE_XPATH = "cap:#{ ALTITUDE_ELEMENT_NAME }" # :nodoc:
|
36
|
+
CEILING_XPATH = "cap:#{ CEILING_ELEMENT_NAME }" # :nodoc:
|
37
|
+
|
38
|
+
def initialize( attributes = {})
|
39
|
+
@area_desc = attributes[ :area_desc ]
|
40
|
+
@altitude = attributes[ :altitude ]
|
41
|
+
@ceiling = attributes[ :ceiling ]
|
42
|
+
@circles = Array( attributes[ :circles ])
|
43
|
+
@geocodes = Array( attributes[ :geocodes ])
|
44
|
+
@polygons = Array( attributes[ :polygons ])
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_xml_element # :nodoc:
|
48
|
+
xml_element = REXML::Element.new( XML_ELEMENT_NAME )
|
49
|
+
xml_element.add_element( AREA_DESC_ELEMENT_NAME ).add_text( @area_desc.to_s )
|
50
|
+
add_to_xml_element = lambda do |element, object|
|
51
|
+
element.add_element( object.to_xml_element )
|
52
|
+
element
|
53
|
+
end
|
54
|
+
@polygons.inject( xml_element, &add_to_xml_element )
|
55
|
+
@circles.inject( xml_element, &add_to_xml_element )
|
56
|
+
@geocodes.inject( xml_element, &add_to_xml_element )
|
57
|
+
xml_element.add_element( ALTITUDE_ELEMENT_NAME ).add_text( @altitude.to_s ) unless self.altitude.blank?
|
58
|
+
xml_element.add_element( CEILING_ELEMENT_NAME ).add_text( @ceiling.to_s ) unless self.altitude.blank?
|
59
|
+
xml_element
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_xml # :nodoc:
|
63
|
+
self.to_xml_element.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
# Implements an equality operator for the Area object. Two Area objects are equal if all their attributes are equal.
|
67
|
+
def ==( other )
|
68
|
+
comparison_attributes = lambda{ |area| [ area.area_desc, area.altitude, area.ceiling, area.circles, area.geocodes, area.polygons ]}
|
69
|
+
comparison_attributes.call( self ) == comparison_attributes.call( other )
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.from_xml_element( area_xml_element ) # :nodoc:
|
73
|
+
area = RCAP::Area.new( :area_desc => RCAP.xpath_text( area_xml_element, AREA_DESC_XPATH ),
|
74
|
+
:altitude => (( alt = RCAP.xpath_text( area_xml_element, ALTITUDE_XPATH )) ? alt.to_f : nil ),
|
75
|
+
:ceiling => (( ceil = RCAP.xpath_text( area_xml_element, CEILING_XPATH )) ? ceil.to_f : nil ),
|
76
|
+
:circles => RCAP.xpath_match( area_xml_element, RCAP::Circle::XPATH ).map{ |circle_element| RCAP::Circle.from_xml_element( circle_element )},
|
77
|
+
:geocodes => RCAP.xpath_match( area_xml_element, RCAP::Geocode::XPATH ).map{ |geocode_element| RCAP::Geocode.from_xml_element( geocode_element )},
|
78
|
+
:polygons => RCAP.xpath_match( area_xml_element, RCAP::Polygon::XPATH ).map{ |polygon_element| RCAP::Polygon.from_xml_element( polygon_element )})
|
79
|
+
area
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/rcap/circle.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module RCAP
|
2
|
+
# A Circle object is valid if
|
3
|
+
# * it has a point which is a valid Point object
|
4
|
+
# * it has a radius with a value greater than zero
|
5
|
+
class Circle
|
6
|
+
include Validation
|
7
|
+
|
8
|
+
# Instance of Point class
|
9
|
+
attr_accessor( :point )
|
10
|
+
# Expresed in kilometers
|
11
|
+
attr_accessor( :radius )
|
12
|
+
|
13
|
+
validates_presence_of( :point, :radius )
|
14
|
+
validates_numericality_of( :radius , :greater_than => 0 )
|
15
|
+
validates_validity_of( :point )
|
16
|
+
|
17
|
+
XML_ELEMENT_NAME = 'circle' # :nodoc:
|
18
|
+
|
19
|
+
XPATH = 'cap:circle' # :nodoc:
|
20
|
+
|
21
|
+
def initialize( attributes = {} )
|
22
|
+
@point = attributes[ :point ]
|
23
|
+
@radius = attributes[ :radius ]
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s # :nodoc:
|
27
|
+
"#{ self.point.to_s } #{ self.radius }"
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect # :nodoc:
|
31
|
+
"(#{ self.point.lattitude},#{ self.point.longitude } #{ self.radius })"
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_xml_element # :nodoc:
|
35
|
+
xml_element = REXML::Element.new( XML_ELEMENT_NAME )
|
36
|
+
xml_element.add_text( self.to_s )
|
37
|
+
xml_element
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_xml # :nodoc:
|
41
|
+
self.to_xml_element.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.parse_circle_string( circle_string ) # :nodoc:
|
45
|
+
coordinates, radius = circle_string.split( ' ' )
|
46
|
+
lattitude, longitude = coordinates.split( ',' )
|
47
|
+
[ lattitude, longitude, radius ].map{ |e| e.to_f }
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.from_xml_element( circle_xml_element ) # :nodoc:
|
51
|
+
lattitude, longitude, radius = self.parse_circle_string( circle_xml_element.text )
|
52
|
+
point = RCAP::Point.new( :lattitude => lattitude, :longitude => longitude )
|
53
|
+
circle = self.new( :point => point,
|
54
|
+
:radius => radius )
|
55
|
+
end
|
56
|
+
|
57
|
+
# Two circles are equivalent if their point and radius are equal.
|
58
|
+
def ==( other )
|
59
|
+
self.point == other.point && self.radius == other.radius
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|