argos-ruby 1.0.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|