argos-ruby 1.0.5 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.gitignore +1 -0
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +40 -75
- data/argos-ruby.gemspec +2 -2
- data/bin/{argos-ruby → argos-ascii} +15 -6
- data/bin/argos-soap +13 -0
- data/lib/argos.rb +16 -138
- data/lib/argos/ascii.rb +134 -0
- data/lib/argos/diag.rb +4 -8
- data/lib/argos/ds.rb +5 -8
- data/lib/argos/exception.rb +4 -0
- data/lib/argos/soap.rb +372 -0
- data/lib/argos/soap_command.rb +201 -0
- data/spec/argos/_soap/getCsv.csv +39 -0
- data/spec/argos/_soap/getCsv.json +1 -0
- data/spec/argos/_soap/getKml.json +1 -0
- data/spec/argos/_soap/getKml.xml +1 -0
- data/spec/argos/_soap/getXml.json +1 -0
- data/spec/argos/_soap/getXml.xml +1 -0
- data/spec/{argos_spec.rb → argos/ascii_spec.rb} +6 -5
- data/spec/argos/diag_spec.rb +1 -1
- data/spec/argos/ds_spec.rb +3 -3
- data/spec/argos/soap_spec.rb +60 -0
- metadata +20 -7
data/lib/argos/diag.rb
CHANGED
@@ -5,15 +5,12 @@ module Argos
|
|
5
5
|
# Usage
|
6
6
|
#
|
7
7
|
# diag = Argos::Diag.new
|
8
|
-
# diag.log = Logger.new(STDERR)
|
9
8
|
# puts diag.parse(filename).to_json
|
10
9
|
#
|
11
|
-
# For information about Argos, see: http://www.argos-system.org
|
12
|
-
#
|
13
10
|
# @author Espen Egeland
|
14
11
|
# @author Conrad Helgeland
|
15
12
|
class Diag < Array
|
16
|
-
include Argos
|
13
|
+
include Argos::Ascii
|
17
14
|
|
18
15
|
LOCATION_CLASS = ["3", "2", "1", "0", "A", "B", "Z"]
|
19
16
|
|
@@ -56,6 +53,7 @@ module Argos
|
|
56
53
|
def initialize
|
57
54
|
@errors = []
|
58
55
|
@programs = []
|
56
|
+
@log = Logger.new(STDERR)
|
59
57
|
end
|
60
58
|
|
61
59
|
def filter?
|
@@ -235,12 +233,12 @@ module Argos
|
|
235
233
|
sensor_data: sensor_data,
|
236
234
|
technology: "argos",
|
237
235
|
type: type,
|
238
|
-
|
236
|
+
file: "file://"+filename,
|
239
237
|
source: "#{sha1}",
|
240
238
|
}
|
241
239
|
|
242
240
|
idbase = diag.clone
|
243
|
-
idbase.delete :
|
241
|
+
idbase.delete :file
|
244
242
|
id = Digest::SHA1.hexdigest(idbase.to_json)
|
245
243
|
|
246
244
|
diag[:parser] = Argos.library_version
|
@@ -328,8 +326,6 @@ end
|
|
328
326
|
# 168 64 30
|
329
327
|
|
330
328
|
|
331
|
-
|
332
|
-
|
333
329
|
#200910Oct.DIA
|
334
330
|
#: Line: 26132 Invalid format:
|
335
331
|
#52061 Date : 03.10.09 09:47:05 LC : Z IQ : 38 Lat1 : 76.998N Lon1 : 22.398W Lat2 : 74.370N Lon2 : 10.000 Nb mes : 003 Nb mes>-120dB : 000 Best level : -129 dB Pass duration : 100s NOPC : 0 Calcul freq : 401 677501.2 Hz Altitude : 0 m 40 35 00 00 00 00 00 240 00 00 00 00 64 00 197
|
data/lib/argos/ds.rb
CHANGED
@@ -5,17 +5,13 @@ module Argos
|
|
5
5
|
# Usage
|
6
6
|
#
|
7
7
|
# ds = Argos::Ds.new
|
8
|
-
# ds.log = Logger.new(STDERR)
|
9
8
|
# puts ds.parse(filename).to_json
|
10
9
|
#
|
11
|
-
#
|
12
|
-
# For information about Argos, see: http://www.argos-system.org
|
13
|
-
#
|
14
10
|
# @author Espen Egeland
|
15
11
|
# @author Conrad Helgeland
|
16
|
-
#
|
12
|
+
#
|
17
13
|
class Ds < Array
|
18
|
-
include Argos
|
14
|
+
include Argos::Ascii
|
19
15
|
|
20
16
|
attr_writer :log, :filename, :bundle
|
21
17
|
|
@@ -29,6 +25,7 @@ module Argos
|
|
29
25
|
|
30
26
|
def initialize
|
31
27
|
@errors = []
|
28
|
+
@log = Logger.new(STDERR)
|
32
29
|
end
|
33
30
|
|
34
31
|
def filter?
|
@@ -312,7 +309,7 @@ module Argos
|
|
312
309
|
m = m.merge(measurement)
|
313
310
|
m = m.merge ({ technology: "argos",
|
314
311
|
type: type,
|
315
|
-
|
312
|
+
file: "file://"+filename,
|
316
313
|
source: sha1
|
317
314
|
})
|
318
315
|
|
@@ -354,7 +351,7 @@ module Argos
|
|
354
351
|
|
355
352
|
idbase = m.clone
|
356
353
|
idbase.delete :errors
|
357
|
-
idbase.delete :
|
354
|
+
idbase.delete :file
|
358
355
|
idbase.delete :warn
|
359
356
|
|
360
357
|
id = Digest::SHA1.hexdigest(idbase.to_json)
|
data/lib/argos/exception.rb
CHANGED
data/lib/argos/soap.rb
ADDED
@@ -0,0 +1,372 @@
|
|
1
|
+
root = File.dirname(File.realpath(__FILE__))+"/../.."
|
2
|
+
|
3
|
+
Dir.chdir root do
|
4
|
+
begin
|
5
|
+
require "savon"
|
6
|
+
rescue LoadError
|
7
|
+
# Prepend Savon lib to path, bundler seems not work
|
8
|
+
unless defined? Savon
|
9
|
+
# Cannot use #find_name returns []
|
10
|
+
# raise Bundler.rubygems.find_name('savon').to_json #.first.full_gem_path
|
11
|
+
savonbundle = `bundle show savon`.chomp
|
12
|
+
$LOAD_PATH.unshift(savonbundle+"/lib")
|
13
|
+
require_relative "#{savonbundle}/lib/savon"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Argos
|
19
|
+
class Soap
|
20
|
+
|
21
|
+
# A "simple" Soap client for Argos-system satellite tracking webservice
|
22
|
+
# http://wanderingbarque.com/nonintersecting/2006/11/15/the-s-stands-for-simple/
|
23
|
+
|
24
|
+
# client [Savon]
|
25
|
+
# request [String] Soap:Envelope (XML request body)
|
26
|
+
# response [Savon::Response]
|
27
|
+
# operation [Savon::Operation]
|
28
|
+
# xml [String] (Extracted, inner) XML
|
29
|
+
attr_accessor :client, :request, :response, :operation, :xml, :filter,
|
30
|
+
:platformId, :programNumber, :nbDaysFromNow, :period
|
31
|
+
|
32
|
+
attr_writer :username, :password
|
33
|
+
|
34
|
+
URI = "http://ws-argos.cls.fr/argosDws/services/DixService"
|
35
|
+
# Alternative: "http://ws-argos.clsamerica.com/argosDws/services/DixService"
|
36
|
+
|
37
|
+
WSDL = "#{URI}?wsdl"
|
38
|
+
|
39
|
+
ARGOS_NS = "http://service.dataxmldistribution.argos.cls.fr/types"
|
40
|
+
SOAP_NS = "http://www.w3.org/2003/05/soap-envelope"
|
41
|
+
|
42
|
+
NAMESPACES = {
|
43
|
+
"soap" => SOAP_NS,
|
44
|
+
"argos" => ARGOS_NS
|
45
|
+
}
|
46
|
+
|
47
|
+
# Constructor
|
48
|
+
# soap = Argos::Soap.new({username: "argos-system-user", password: "argos-system-pw"})
|
49
|
+
def initialize(config={})
|
50
|
+
config.each do |k,v|
|
51
|
+
case k.to_sym
|
52
|
+
when :username
|
53
|
+
@username=v
|
54
|
+
when :password
|
55
|
+
@password=v
|
56
|
+
when :wsdl
|
57
|
+
@wsdl=v
|
58
|
+
when :programNumber
|
59
|
+
@programNumber = v
|
60
|
+
when :platformId
|
61
|
+
@platformId = v
|
62
|
+
when :nbDaysFromNow
|
63
|
+
@nbDaysFromNow = v.to_i
|
64
|
+
when :period
|
65
|
+
@period = v
|
66
|
+
when :filter
|
67
|
+
@filter = v
|
68
|
+
else
|
69
|
+
#raise ArgumentError, "Unkown config key: #{k}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Build baseRequest Hash
|
75
|
+
# The service requires programNumber or PlatformId, but if you do not provide any,
|
76
|
+
# this method will call the service (@see #programs) and get the current user's programs
|
77
|
+
# @return [Hash]
|
78
|
+
def baseRequest
|
79
|
+
# if override key is platformId... delete programNumber...
|
80
|
+
# if override key is period... delete nbDaysFromNow...
|
81
|
+
baseRequest = { username: _username, password: _password }
|
82
|
+
|
83
|
+
# First choice (program or platform)
|
84
|
+
if @programNumber.nil? and @platformId.nil?
|
85
|
+
# Fetch all programs if neither is provided
|
86
|
+
baseRequest[:programNumber] = programs.map {|p|p.to_s}.join(",")
|
87
|
+
elsif @programNumber =~ /\d+/ and @platformId =~ /\d+/
|
88
|
+
raise "Cannot provide both programNumber and platformId"
|
89
|
+
elsif @programNumber =~ /\d+/
|
90
|
+
baseRequest[:programNumber] = @programNumber
|
91
|
+
elsif @platformId =~ /\d+/
|
92
|
+
baseRequest[:platformId] = @platformId
|
93
|
+
end
|
94
|
+
|
95
|
+
# 2nd choice (time)
|
96
|
+
if @nbDaysFromNow.nil? and @period.nil?
|
97
|
+
# Default to 20 days of data (the maximum)
|
98
|
+
baseRequest[:nbDaysFromNow] = 20
|
99
|
+
elsif @nbDaysFromNow =~ /\d+/ and not @period.nil?
|
100
|
+
raise "Cannot provide both nbDaysFromNow and period"
|
101
|
+
elsif @nbDaysFromNow.to_s =~ /\d+/
|
102
|
+
baseRequest[:nbDaysFromNow] = @nbDaysFromNow.to_i
|
103
|
+
else
|
104
|
+
baseRequest[:period] = @period
|
105
|
+
end
|
106
|
+
|
107
|
+
baseRequest = baseRequest.merge({
|
108
|
+
#<xs:element minOccurs="0" name="referenceDate" type="tns:referenceDateType"/>
|
109
|
+
#<xs:element minOccurs="0" name="locClass" type="xs:string"/>
|
110
|
+
#<xs:element minOccurs="0" name="geographicArea" type="xs:string"/>
|
111
|
+
#<xs:element minOccurs="0" name="compression" type="xs:int"/>
|
112
|
+
#<xs:element minOccurs="0" name="mostRecentPassages" type="xs:boolean"/>
|
113
|
+
})
|
114
|
+
baseRequest
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [Savon]
|
119
|
+
def client
|
120
|
+
@client ||= begin
|
121
|
+
|
122
|
+
tmpwsdl = "/tmp/argos-soap.wsdl"
|
123
|
+
if File.exists?(tmpwsdl)
|
124
|
+
uri = tmpwsdl
|
125
|
+
else
|
126
|
+
uri = @wsdl||=WSDL
|
127
|
+
end
|
128
|
+
|
129
|
+
client = ::Savon.new(uri)
|
130
|
+
if not File.exists?(tmpwsdl)
|
131
|
+
response = ::Net::HTTP.get_response(::URI.parse(WSDL))
|
132
|
+
File.open(tmpwsdl, "wb") { |file| file.write(response.body)}
|
133
|
+
end
|
134
|
+
client
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def filter?
|
139
|
+
not @filter.nil? and filter.respond_to?(:call)
|
140
|
+
end
|
141
|
+
|
142
|
+
# @return [String]
|
143
|
+
def getCsv
|
144
|
+
o = _operation(:getCsv)
|
145
|
+
o.body = { csvRequest: baseRequest.merge(
|
146
|
+
showHeader: true).merge(xmlRequest)
|
147
|
+
}
|
148
|
+
@response = o.call
|
149
|
+
@request = o.build
|
150
|
+
@text = _extract_escaped_xml("csvResponse").call(response)
|
151
|
+
end
|
152
|
+
|
153
|
+
# @return [Hash]
|
154
|
+
def getKml
|
155
|
+
_call_xml_operation(:getKml, { kmlRequest: baseRequest.merge(xmlRequest)}, _extract_escaped_xml("kmlResponse"))
|
156
|
+
end
|
157
|
+
|
158
|
+
# @return [Hash]
|
159
|
+
# {"data":{"program":[{"programNumber":"9660","platform":[{ .. },{ .. }]}],"@version":"1.0"}}
|
160
|
+
# Each platform Hash (.. above): {"platformId":"129990","lastLocationClass":"3","lastCollectDate":"2013-10-03T08:32:24.000Z","lastLocationDate":"2013-05-22T04:55:15.000Z","lastLatitude":"47.67801","lastLongitude":"-122.13419"}
|
161
|
+
def getPlatformList
|
162
|
+
_call_xml_operation(:getPlatformList, { platformListRequest:
|
163
|
+
# Cannot use #baseRequest here because that methods calls #programs which also calls #getPlatformList...
|
164
|
+
{ username: _username, password: _password },
|
165
|
+
}, _extract_escaped_xml("platformListResponse"))
|
166
|
+
end
|
167
|
+
|
168
|
+
# @return [Hash]
|
169
|
+
def getXml
|
170
|
+
_call_xml_operation(:getXml,
|
171
|
+
{ xmlRequest: baseRequest.merge(xmlRequest)},
|
172
|
+
_extract_escaped_xml("xmlResponse"))
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [Hash]
|
176
|
+
def getStreamXml
|
177
|
+
_call_xml_operation(:getStreamXml,
|
178
|
+
{ streamXmlRequest: baseRequest.merge(xmlRequest)},
|
179
|
+
_extract_motm)
|
180
|
+
end
|
181
|
+
|
182
|
+
# @return [Hash]
|
183
|
+
def getXsd
|
184
|
+
_call_xml_operation(:getXsd, { xsdRequest: {} }, _extract_escaped_xml("xsdResponse"))
|
185
|
+
end
|
186
|
+
|
187
|
+
# @return [Hash]
|
188
|
+
# choice: programNumber | platformId | wmo*
|
189
|
+
# nbMaxObs
|
190
|
+
def getObsXml
|
191
|
+
_call_xml_operation(:getObsXml,
|
192
|
+
{ observationRequest: baseRequest.merge(xmlRequest)},
|
193
|
+
_extract_escaped_xml("observationResponse"))
|
194
|
+
end
|
195
|
+
|
196
|
+
# @return [Text]
|
197
|
+
# choice: programNumber | platformId | wmo*
|
198
|
+
# nbMaxObs
|
199
|
+
def getObsCsv
|
200
|
+
o = _operation(:getObsCsv)
|
201
|
+
o.body = { observationRequest: baseRequest.merge(xmlRequest)
|
202
|
+
}
|
203
|
+
@response = o.call
|
204
|
+
@request = o.build
|
205
|
+
@text = _extract_escaped_xml("observationResponse").call(response)
|
206
|
+
end
|
207
|
+
|
208
|
+
# @return [Array]
|
209
|
+
def platforms(programNumber=nil)
|
210
|
+
platforms = []
|
211
|
+
programs = getPlatformList["data"]["program"]
|
212
|
+
if programNumber.to_s =~ /\d+/
|
213
|
+
programs.select! {|p| p["programNumber"].to_i == programNumber.to_i }
|
214
|
+
end
|
215
|
+
programs.each do |program|
|
216
|
+
platforms += program["platform"].map {|p| p["platformId"].to_i}
|
217
|
+
end
|
218
|
+
platforms
|
219
|
+
end
|
220
|
+
|
221
|
+
def period(startDate, endDate)
|
222
|
+
{ startDate: startDate, endDate: endDate }
|
223
|
+
end
|
224
|
+
|
225
|
+
# @return [Array]
|
226
|
+
def programs
|
227
|
+
getPlatformList["data"]["program"].map {|p| p["programNumber"].to_i }
|
228
|
+
end
|
229
|
+
|
230
|
+
# @return [String]
|
231
|
+
def raw
|
232
|
+
response.raw
|
233
|
+
end
|
234
|
+
|
235
|
+
# @return [String]
|
236
|
+
def request
|
237
|
+
operation.build
|
238
|
+
end
|
239
|
+
|
240
|
+
# @return [Hash] {"DixService":{"ports":{"DixServicePort":{"type":"http://schemas.xmlsoap.org/wsdl/soap12/","location":"http://ws-argos.cls.fr/argosDws/services/DixService"}}}}
|
241
|
+
def services
|
242
|
+
client.services
|
243
|
+
end
|
244
|
+
|
245
|
+
# @return [String]
|
246
|
+
def text
|
247
|
+
@text||=""
|
248
|
+
end
|
249
|
+
|
250
|
+
# @return [Array] [:getCsv, :getStreamXml, :getKml, :getXml, :getXsd, :getPlatformList, :getObsCsv, :getObsXml]
|
251
|
+
def operations
|
252
|
+
@response = client.operations(:DixService, :DixServicePort)
|
253
|
+
end
|
254
|
+
|
255
|
+
def namespaces
|
256
|
+
NAMESPACES
|
257
|
+
end
|
258
|
+
|
259
|
+
def xmlRequest
|
260
|
+
{
|
261
|
+
displayLocation: true,
|
262
|
+
displayDiagnostic: true,
|
263
|
+
displayMessage: true,
|
264
|
+
displayCollect: true,
|
265
|
+
displayRawData: true,
|
266
|
+
displaySensor: true,
|
267
|
+
#argDistrib: "",
|
268
|
+
displayImageLocation: true,
|
269
|
+
displayHexId: true
|
270
|
+
}
|
271
|
+
end
|
272
|
+
|
273
|
+
protected
|
274
|
+
|
275
|
+
# Build and call @operation, set @response, @request, and @xml
|
276
|
+
# @raise on faults
|
277
|
+
# @return [Hash]
|
278
|
+
def _call_xml_operation(op_sym, body, extract=nil)
|
279
|
+
@operation = _operation(op_sym)
|
280
|
+
@operation.body = body
|
281
|
+
@response = operation.call
|
282
|
+
|
283
|
+
# Check for http errors?
|
284
|
+
|
285
|
+
# Handle faults (before extracting data)
|
286
|
+
_envelope.xpath("soap:Body/soap:Fault", namespaces).each do | fault |
|
287
|
+
raise fault.to_s
|
288
|
+
end
|
289
|
+
|
290
|
+
# Extract data
|
291
|
+
if extract.respond_to?(:call)
|
292
|
+
@xml = extract.call(response)
|
293
|
+
else
|
294
|
+
@xml = response.raw
|
295
|
+
end
|
296
|
+
|
297
|
+
# Handle errors
|
298
|
+
ng = Nokogiri.XML(xml)
|
299
|
+
ng.xpath("/data/errors/error").each do | error |
|
300
|
+
raise error.to_s
|
301
|
+
# FIXME Custom exception so that it mighht be trapped on raw responses...
|
302
|
+
end
|
303
|
+
|
304
|
+
# Validation
|
305
|
+
# Validation against XSD does not work: ["Element 'data': No matching global declaration available for the validation root."]
|
306
|
+
# schema = Nokogiri::XML::Schema(File.read("/tmp/argos-data.xsd"))
|
307
|
+
# v = schema.validate(ng)
|
308
|
+
|
309
|
+
# Convert XML to Hash
|
310
|
+
nori = Nori.new
|
311
|
+
nori.parse(xml)
|
312
|
+
end
|
313
|
+
|
314
|
+
# This is a shame, but who's to blame when there's multiple XML prologs (the second is escaped) and even mix of (declared) encodings (UTF-8 in soap envelope, ISO-8859-1 inside)
|
315
|
+
# Note: the inner data elements are non-namespaced (see da), so that recreating as proper XML would need to set xmlns=""
|
316
|
+
def _extract_escaped_xml(responseElement)
|
317
|
+
lambda {|response| CGI.unescapeHTML(response.raw.split("<#{responseElement} xmlns=\"http://service.dataxmldistribution.argos.cls.fr/types\"><return>")[1].split("</return>")[0])}
|
318
|
+
end
|
319
|
+
|
320
|
+
# This is proof-of-concept quality code.
|
321
|
+
# @todo Need to extract boundary and start markers from Content-Type header:
|
322
|
+
# Content-Type: multipart/related; type="application/xop+xml"; boundary="uuid:14b8db9f-a393-4786-be3f-f0f7b12e14a2"; start="<root.message@cxf.apache.org>"; start-info="application/soap+xml"
|
323
|
+
|
324
|
+
# @return [String]
|
325
|
+
|
326
|
+
def _extract_motm
|
327
|
+
lambda {|response|
|
328
|
+
# Scan for MOTM signature --uuid:*
|
329
|
+
if response.raw =~ (/^(--[\w:-]+)--$/)
|
330
|
+
# Get the last message, which is -2 because of the trailing --
|
331
|
+
xml = response.raw.split($1)[-2].strip
|
332
|
+
|
333
|
+
# Get rid of HTTP headers
|
334
|
+
if xml =~ /\r\n\r\n[<]/
|
335
|
+
xml = xml.split(/\r\n\r\n/)[-1]
|
336
|
+
end
|
337
|
+
|
338
|
+
else
|
339
|
+
raise "Cannot parse MOTM"
|
340
|
+
end
|
341
|
+
}
|
342
|
+
end
|
343
|
+
|
344
|
+
# @return [Nokogiri:*]
|
345
|
+
def _envelope
|
346
|
+
ng = Nokogiri.XML(response.raw).xpath("/soap:Envelope", namespaces)
|
347
|
+
if not ng.any?
|
348
|
+
# Again, this is a shame...
|
349
|
+
envstr = '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">'
|
350
|
+
extracted = envstr + response.raw.split(envstr)[1].split("</soap:Envelope>")[0] + "</soap:Envelope>"
|
351
|
+
ng = Nokogiri.XML(extracted).xpath("/soap:Envelope", namespaces)
|
352
|
+
end
|
353
|
+
ng
|
354
|
+
end
|
355
|
+
|
356
|
+
# @return [Savon::Operation]
|
357
|
+
def _operation(operation_name)
|
358
|
+
client.operation(:DixService, :DixServicePort, operation_name)
|
359
|
+
end
|
360
|
+
|
361
|
+
# @return [String]
|
362
|
+
def _username
|
363
|
+
@username||=ENV["ARGOS_SOAP_USERNAME"]
|
364
|
+
end
|
365
|
+
|
366
|
+
# @return [String]
|
367
|
+
def _password
|
368
|
+
@password||=ENV["ARGOS_SOAP_PASSWORD"]
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|
372
|
+
end
|