argos-ruby 1.0.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.
- data/.gitignore +18 -0
- data/LICENSE +674 -0
- data/README.md +35 -0
- data/argos-ruby.gemspec +21 -0
- data/bin/argos-ruby +137 -0
- data/lib/argos/command.rb +58 -0
- data/lib/argos/diag.rb +316 -0
- data/lib/argos/ds.rb +438 -0
- data/lib/argos/exception.rb +4 -0
- data/lib/argos.rb +142 -0
- data/spec/argos/_diag/990660_A.DIA +2708 -0
- data/spec/argos/_diag/valid_2009-03-02_DB_DIAG.txt +25 -0
- data/spec/argos/_ds/990660_A.DAT +1312 -0
- data/spec/argos/_ds/sensor_mismatch_ds.txt +27 -0
- data/spec/argos/diag_spec.rb +75 -0
- data/spec/argos/ds_spec.rb +117 -0
- data/spec/spec_helper.rb +14 -0
- metadata +82 -0
data/lib/argos/ds.rb
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
module Argos
|
|
2
|
+
|
|
3
|
+
# Argos DS|DAT file parser
|
|
4
|
+
#
|
|
5
|
+
# Usage
|
|
6
|
+
#
|
|
7
|
+
# ds = Argos::Ds.new
|
|
8
|
+
# ds.log = Logger.new(STDERR)
|
|
9
|
+
# puts ds.parse(filename).to_json
|
|
10
|
+
#
|
|
11
|
+
#
|
|
12
|
+
# For information about Argos, see: http://www.argos-system.org
|
|
13
|
+
#
|
|
14
|
+
# @author Espen Egeland
|
|
15
|
+
# @author Conrad Helgeland
|
|
16
|
+
# @todo errors => warn or remove unless asked for? (debug)
|
|
17
|
+
class Ds < Array
|
|
18
|
+
include Argos
|
|
19
|
+
|
|
20
|
+
attr_writer :log, :filename
|
|
21
|
+
|
|
22
|
+
attr_reader :filename, :filter, :filtername, :valid, :filesize, :sha1, :messages
|
|
23
|
+
|
|
24
|
+
START_REGEX = /^\d{5} \d{5,6} +\d+ +\d+/
|
|
25
|
+
|
|
26
|
+
START_REGEX_LEGACY = /\s+\d\.\d{3}\s\d{9}\s+\w{4}$/
|
|
27
|
+
|
|
28
|
+
LOCATION_CLASS = [nil, "0","1","2","3","A","B","G","Z"]
|
|
29
|
+
|
|
30
|
+
def self.ds? filename
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def filter?
|
|
34
|
+
not @filter.nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def filter=filter
|
|
38
|
+
if filter.respond_to? :call
|
|
39
|
+
@filter = filter
|
|
40
|
+
elsif filter =~ /lambda|Proc/
|
|
41
|
+
@filtername = filter
|
|
42
|
+
@filter = eval(filter)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def log
|
|
47
|
+
if @log.nil?
|
|
48
|
+
@log = Logger.new(STDERR)
|
|
49
|
+
end
|
|
50
|
+
@log
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Parses Argos DS file and returns Argos::Ds -> Array
|
|
54
|
+
#
|
|
55
|
+
# The parser loops all messages (stored in @messages), before #unfold
|
|
56
|
+
# creates a sorted Array of measurements
|
|
57
|
+
#
|
|
58
|
+
#@param filename [String] Filename of Argos DS file
|
|
59
|
+
#@return [Argos::Ds]
|
|
60
|
+
def parse(filename=nil)
|
|
61
|
+
|
|
62
|
+
self.clear # Needed if you parse multiple times
|
|
63
|
+
@messages = []
|
|
64
|
+
@valid = false
|
|
65
|
+
|
|
66
|
+
if filename.nil?
|
|
67
|
+
filename = @filename
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
filename = File.realpath(filename)
|
|
72
|
+
@filename = filename
|
|
73
|
+
if filename.nil? or not File.exists? filename
|
|
74
|
+
raise ArgumentError, "Missing ARGOS DS file: \"#{filename}\""
|
|
75
|
+
end
|
|
76
|
+
@sha1 = Digest::SHA1.file(filename).hexdigest
|
|
77
|
+
|
|
78
|
+
contact = []
|
|
79
|
+
file = File.open(filename)
|
|
80
|
+
@filesize = file.size
|
|
81
|
+
|
|
82
|
+
log.debug "Parsing ARGOS DS file #{filename} source:#{sha1} (#{filesize} bytes)"
|
|
83
|
+
if filter?
|
|
84
|
+
log.debug "Using filter: #{@filtername.nil? ? filter : @filtername }"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
firstline = file.readline
|
|
88
|
+
file.rewind
|
|
89
|
+
|
|
90
|
+
if firstline =~ START_REGEX_LEGACY
|
|
91
|
+
return parse_legacy(file)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
file.each_with_index do |line, c|
|
|
95
|
+
line = line.strip
|
|
96
|
+
|
|
97
|
+
#if (c+1) % 1000 == 0
|
|
98
|
+
# log.debug "Line: #{c+1}"
|
|
99
|
+
#end
|
|
100
|
+
|
|
101
|
+
if line =~ START_REGEX
|
|
102
|
+
|
|
103
|
+
@valid = true
|
|
104
|
+
|
|
105
|
+
if contact.any?
|
|
106
|
+
item = parse_message(contact)
|
|
107
|
+
|
|
108
|
+
if self.class.valid_item? item
|
|
109
|
+
|
|
110
|
+
if not filter? or filter.call(item)
|
|
111
|
+
@messages << item
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
else
|
|
115
|
+
raise "Argos DS message #{filename}:#{c} lacks required program and/or platform"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
contact = [line]
|
|
120
|
+
|
|
121
|
+
else
|
|
122
|
+
# 2010-12-14 15:11:34 1 00 37 01 52
|
|
123
|
+
if contact.any? and line != ""
|
|
124
|
+
contact << line
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
if false == @valid
|
|
130
|
+
#log.debug file.read
|
|
131
|
+
message = "Cannot parse file: #{filename}"
|
|
132
|
+
raise ArgumentError, message
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
last = parse_message(contact)
|
|
136
|
+
|
|
137
|
+
# The last message
|
|
138
|
+
if last
|
|
139
|
+
if not filter? or filter.call(last)
|
|
140
|
+
@messages << last
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
log.debug "Parsed #{@messages.size} Argos DS messages into #{self.class.name} Array"
|
|
145
|
+
@segments = @messages.size
|
|
146
|
+
unfold.each do |d|
|
|
147
|
+
self << d
|
|
148
|
+
end
|
|
149
|
+
self
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Pare one DS segment
|
|
153
|
+
def parse_message(contact)
|
|
154
|
+
header = contact[0]
|
|
155
|
+
body = contact[1,contact.count]
|
|
156
|
+
items = process_item_body(body)
|
|
157
|
+
combine_header_with_transmission(items, header)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def type
|
|
161
|
+
"ds"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @param [String] header
|
|
165
|
+
# Header is is a space-separated string containing
|
|
166
|
+
# [0] Program number
|
|
167
|
+
# [1] Platform number
|
|
168
|
+
# [2] Number of lines of data per satellite pass
|
|
169
|
+
# [3] Number of sensors
|
|
170
|
+
# [4] Satellite identifier
|
|
171
|
+
# [5] Location class (lc)
|
|
172
|
+
# [6] Location date 2007-03-02
|
|
173
|
+
# [7] Location UTC time
|
|
174
|
+
# [8] Latitude (decimal degrees)
|
|
175
|
+
# [9] Longitude, may be > 180 like 255.452°, equivalent to 255.452 - 360 = -104.548 (°E)
|
|
176
|
+
# [10] Altitude (km)
|
|
177
|
+
# [11] Frequency (calculated)
|
|
178
|
+
#
|
|
179
|
+
# The header varies in information elemenet, often either 0..4|5 or 0..11.
|
|
180
|
+
# Header examples (plit on " "):
|
|
181
|
+
# ["09660", "10788", "4", "3", "D", "0"]
|
|
182
|
+
# ["09660", "10788", "5", "3", "H", "2", "1992-04-06", "22:12:16", "78.248", "15.505", "0.000", "401649604"]
|
|
183
|
+
# ["09660", "10788", "2", "3", "D"]
|
|
184
|
+
# http://www.argos-system.org/files/pmedia/public/r363_9_argos_users_manual-v1.5.pdf page 42
|
|
185
|
+
#
|
|
186
|
+
# Warning, the parser does not support this header format from 1989 [AUO89.DAT]
|
|
187
|
+
# 19890800-19891000: ["09660", "14653", "10", "41", "14", "1", "-.42155E+1", "00", "112", "17DD"]
|
|
188
|
+
def combine_header_with_transmission(measurements, header)
|
|
189
|
+
unless header.is_a? Array
|
|
190
|
+
header = header.split(" ")
|
|
191
|
+
end
|
|
192
|
+
latitude = longitude = positioned = nil
|
|
193
|
+
warn = []
|
|
194
|
+
errors = []
|
|
195
|
+
|
|
196
|
+
lc = header[5]
|
|
197
|
+
|
|
198
|
+
if not header[6].nil? and not header[7].nil?
|
|
199
|
+
positioned = convert_datetime(header[6]+" "+header[7])
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
if header[8] != nil && valid_float?(header[8])
|
|
203
|
+
latitude = header[8].to_f
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
if header[9] != nil && valid_float?(header[9])
|
|
207
|
+
longitude = header[9].to_f
|
|
208
|
+
if (180..360).include? longitude
|
|
209
|
+
longitude = (longitude - 360)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
altitude = header[10]
|
|
214
|
+
if not altitude.nil?
|
|
215
|
+
altitude = altitude.to_f*1000
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
if positioned.nil? and measurements.nil?
|
|
219
|
+
warn << "missing-time"
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
if latitude.nil? or longitude.nil?
|
|
223
|
+
warn << "missing-position"
|
|
224
|
+
else
|
|
225
|
+
|
|
226
|
+
unless latitude.between?(-90, 90) and longitude.between?(-180, 180)
|
|
227
|
+
errors << "invalid-position"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
unless LOCATION_CLASS.include? lc
|
|
232
|
+
errors << "invalid-lc"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Satellites
|
|
236
|
+
# ["A", "B", "K", "L", "M", "N", "P", "R"]
|
|
237
|
+
|
|
238
|
+
document = { program: header[0].to_i,
|
|
239
|
+
platform: header[1].to_i,
|
|
240
|
+
lines: header[2].to_i,
|
|
241
|
+
sensors: header[3].to_i,
|
|
242
|
+
satellite: header[4],
|
|
243
|
+
lc: lc,
|
|
244
|
+
positioned: positioned,
|
|
245
|
+
latitude: latitude,
|
|
246
|
+
longitude: longitude,
|
|
247
|
+
altitude: altitude,
|
|
248
|
+
measurements: measurements,
|
|
249
|
+
headers: header.size
|
|
250
|
+
}
|
|
251
|
+
if warn.any?
|
|
252
|
+
document[:warn]=warn
|
|
253
|
+
end
|
|
254
|
+
if errors.any?
|
|
255
|
+
document[:errors]=errors
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
document
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Merge position and all other top-level DS fields with each measurement line
|
|
262
|
+
# (containing sensor data)
|
|
263
|
+
# The 3 lines below will unfold to *2* documents, each with
|
|
264
|
+
# "positioned":2010-03-05T14:19:06Z, "platform": "23695", "latitude":"79.989", etc.
|
|
265
|
+
# 23695 074772 3 4 M B 2010-03-05 14:19:06 79.989 12.644 0.036 401639707
|
|
266
|
+
# 2010-03-05 14:17:35 1 01 25 37630 36
|
|
267
|
+
# 2010-03-05 14:20:38 1 00 28 00 65
|
|
268
|
+
def unfold
|
|
269
|
+
|
|
270
|
+
# First, grab all segments *without* measurements (if any)
|
|
271
|
+
unfolded = messages.reject {|ds| ds.key?(:measurements) or ds[:measurements].nil? }
|
|
272
|
+
log.debug "#{messages.size - unfolded.size} / #{messages.size} messages contained measurements"
|
|
273
|
+
|
|
274
|
+
messages.select {|ds|
|
|
275
|
+
ds.key?(:measurements) and not ds[:measurements].nil?
|
|
276
|
+
}.each do |ds|
|
|
277
|
+
|
|
278
|
+
ds[:measurements].each do |measurement|
|
|
279
|
+
unfolded << merge(ds,measurement)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
unfolded = unfolded.sort_by {|ds|
|
|
284
|
+
if not ds[:measured].nil?
|
|
285
|
+
DateTime.parse(ds[:measured])
|
|
286
|
+
elsif not ds[:positioned].nil?
|
|
287
|
+
DateTime.parse(ds[:positioned])
|
|
288
|
+
else
|
|
289
|
+
ds[:program]
|
|
290
|
+
end
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
log.info "Unfolded #{messages.size} ARGOS DS position and sensor messages into #{unfolded.size} new documents source:#{sha1} #{filename}"
|
|
294
|
+
|
|
295
|
+
unfolded
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def merge(ds, measurement)
|
|
299
|
+
m = ds.select {|k,v| k != :measurements and k != :errors and k != :warn }
|
|
300
|
+
m = m.merge(measurement)
|
|
301
|
+
m = m.merge({ technology: "argos",
|
|
302
|
+
type: type, filename: filename, source: sha1
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
if not ds[:errors].nil? and ds[:errors].any?
|
|
306
|
+
m[:errors] = ds[:errors].clone
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
if not ds[:warn].nil? and ds[:warn].any?
|
|
310
|
+
m[:warn] = ds[:warn].clone
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
if not m[:sensor_data].nil? and m[:sensor_data].size != ds[:sensors]
|
|
314
|
+
if m[:warn].nil?
|
|
315
|
+
m[:warn] = []
|
|
316
|
+
end
|
|
317
|
+
m[:warn] << "sensors-count-mismatch"
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
idbase = m.clone
|
|
321
|
+
idbase.delete :errors
|
|
322
|
+
idbase.delete :filename
|
|
323
|
+
idbase.delete :warn
|
|
324
|
+
|
|
325
|
+
id = Digest::SHA1.hexdigest(idbase.to_json)
|
|
326
|
+
|
|
327
|
+
m[:parser] = Argos.library_version
|
|
328
|
+
m[:id] = id
|
|
329
|
+
m
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def process_item_body(body_arr)
|
|
333
|
+
@buf =""
|
|
334
|
+
@transmission_arr = []
|
|
335
|
+
@transmission_arr = recursive_transmission_parse(body_arr)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# @param [Array] body_arr
|
|
340
|
+
# @return [Aray]
|
|
341
|
+
def recursive_transmission_parse(body_arr)
|
|
342
|
+
if body_arr.nil? or body_arr.empty?
|
|
343
|
+
return
|
|
344
|
+
end
|
|
345
|
+
@buf =@buf + " " + body_arr[0]
|
|
346
|
+
|
|
347
|
+
if body_arr[1] =~ /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/ or body_arr[1]==nil
|
|
348
|
+
@transmission_arr << transmission_package(@buf)
|
|
349
|
+
@buf=""
|
|
350
|
+
end
|
|
351
|
+
recursive_transmission_parse(body_arr[1,body_arr.length])
|
|
352
|
+
@transmission_arr
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def transmission_package(data)
|
|
356
|
+
transmission_time = data[/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/,1]
|
|
357
|
+
transmission_time = convert_datetime(transmission_time)
|
|
358
|
+
|
|
359
|
+
identical = data.split(" ")[2].to_i
|
|
360
|
+
data = data.strip[23,data.length]
|
|
361
|
+
|
|
362
|
+
if not data.nil?
|
|
363
|
+
sensor_data = data.split(" ")
|
|
364
|
+
end
|
|
365
|
+
{ measured: transmission_time,
|
|
366
|
+
identical: identical,
|
|
367
|
+
sensor_data: sensor_data
|
|
368
|
+
}
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def start
|
|
372
|
+
positioned.map {|ds| ds [:positioned] }.first
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def stop
|
|
376
|
+
positioned.map {|ds| ds [:positioned] }.last
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def source
|
|
380
|
+
@sha1
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
protected
|
|
384
|
+
|
|
385
|
+
# "1999-04-02 01:28:54"
|
|
386
|
+
def convert_datetime(datetime)
|
|
387
|
+
|
|
388
|
+
#AUO89.DAT/home/ch/github.com/argos-ruby/lib/ds.rb:143:in `parse': can't convert nil into String (TypeError)
|
|
389
|
+
#/home/ch/github.com/api.npolar.no/seed/tracking/argos/19890800-19891000
|
|
390
|
+
#AUO89.DAT/home/ch/github.com/argos-ruby/lib/ds.rb:149:in `parse': invalid date (ArgumentError)
|
|
391
|
+
begin
|
|
392
|
+
datetime = ::DateTime.parse(datetime).iso8601.to_s
|
|
393
|
+
datetime['+00:00'] = "Z"
|
|
394
|
+
datetime
|
|
395
|
+
rescue
|
|
396
|
+
log.error "Invalid date #{datetime}"
|
|
397
|
+
DateTime.new(0).xmlschema.gsub(/\+00:00/, "Z")
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def positioned
|
|
402
|
+
select {|ds|
|
|
403
|
+
ds.key? :positioned and not ds[:positioned].nil?
|
|
404
|
+
}
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Argos format until 1991
|
|
408
|
+
#
|
|
409
|
+
#09660 6 09691 2 14286 89 042 17 18 05 1 3 G 0.000 401650000 0VDI
|
|
410
|
+
#09660 2 59.891 10.629 401649651 0VDJ
|
|
411
|
+
#09660 14286 17 14 26 1 -.72543E+1 00 0VDK
|
|
412
|
+
#09660 14286 17 16 52 1 -.72410E+1 00 0VDL
|
|
413
|
+
#09660 14286 17 19 18 2 -.72376E+1 00 0VDM
|
|
414
|
+
#09660 14286 17 21 44 3 -.72376E+1 00 0VDN
|
|
415
|
+
|
|
416
|
+
# Header: 09660[program] 6[lines] ????? 2 ????? 89[year] 042[day?] 17 18 05[time?] 1 3[?] G[?] 0.000 \d{9}[f] \d\w{3}[ident]
|
|
417
|
+
def parse_legacy(file)
|
|
418
|
+
raise "Legacy DS file parser: not implemented"
|
|
419
|
+
#file.each_with_index do |line, c|
|
|
420
|
+
# line = line.strip
|
|
421
|
+
# log.debug line
|
|
422
|
+
#end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def valid_float?(str)
|
|
427
|
+
!!Float(str) rescue false
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def self.valid_item?(item)
|
|
431
|
+
unless item.respond_to?(:key)
|
|
432
|
+
return false
|
|
433
|
+
end
|
|
434
|
+
item.key?(:program) and item.key?(:platform)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
end
|
|
438
|
+
end
|
data/lib/argos.rb
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
require "bigdecimal"
|
|
2
|
+
require "date"
|
|
3
|
+
require "digest/sha1"
|
|
4
|
+
require "json"
|
|
5
|
+
require "logger"
|
|
6
|
+
|
|
7
|
+
require_relative "argos/exception"
|
|
8
|
+
require_relative "argos/ds"
|
|
9
|
+
require_relative "argos/diag"
|
|
10
|
+
|
|
11
|
+
# Argos module
|
|
12
|
+
# Contains parsers for Argos DS/DAT and DIAG files.
|
|
13
|
+
#
|
|
14
|
+
# Code written by staff at the Norwegian Polar Data Centre
|
|
15
|
+
# http://data.npolar.no - a service run by the [Norwegian Polar Institute](http://npolar.no)
|
|
16
|
+
#
|
|
17
|
+
# For information about Argos, see: http://www.argos-system.org
|
|
18
|
+
module Argos
|
|
19
|
+
VERSION = "1.0.0"
|
|
20
|
+
# Detect Argos type ("ds" or "diag" or nil)
|
|
21
|
+
#
|
|
22
|
+
# @param filename [String] Argos (DS or DIAG) file
|
|
23
|
+
# @return [String]
|
|
24
|
+
#"ds"|"diag"
|
|
25
|
+
def self.type filename
|
|
26
|
+
|
|
27
|
+
if File.file? filename
|
|
28
|
+
# Avoid invalid byte sequence in UTF-8 (ArgumentError)
|
|
29
|
+
firstline = File.open(filename, :encoding => "iso-8859-1") {|f| f.readline}
|
|
30
|
+
else
|
|
31
|
+
firstline = filename
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
case firstline
|
|
35
|
+
when Argos::Ds::START_REGEX, Argos::Ds::START_REGEX_LEGACY
|
|
36
|
+
"ds"
|
|
37
|
+
when Argos::Diag::START_REGEX
|
|
38
|
+
"diag"
|
|
39
|
+
when "", nil
|
|
40
|
+
raise ArgumentError, "Not a file or empty string: #{filename}"
|
|
41
|
+
else nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Argos file?
|
|
46
|
+
#
|
|
47
|
+
# @param filename [String] Argos (DS or DIAG) file
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def self.argos?(filename)
|
|
50
|
+
case type(filename)
|
|
51
|
+
when "ds", "diag"
|
|
52
|
+
true
|
|
53
|
+
else
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Factory for Argos::Ds / Argos::Diag
|
|
59
|
+
#
|
|
60
|
+
# @param type [String]: Argos (DS or DIAG) file type (or filename)
|
|
61
|
+
# @return [Argos::Ds Argos::Diag]
|
|
62
|
+
# @throws ArgumentError
|
|
63
|
+
def self.factory(type)
|
|
64
|
+
|
|
65
|
+
# Auto-detect file format if not "ds" or "diag"
|
|
66
|
+
if not ["ds","diag"].include? type
|
|
67
|
+
if argos? type
|
|
68
|
+
type = self.type(type)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
case type
|
|
73
|
+
when "ds"
|
|
74
|
+
Ds.new
|
|
75
|
+
when "diag"
|
|
76
|
+
Diag.new
|
|
77
|
+
else
|
|
78
|
+
raise ArgumentError, "Unknown Argos type: #{type}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def self.library_version
|
|
84
|
+
"argos-ruby-#{VERSION}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Source fingerprint of Argos file (sha1 hash, segment and document counts, etc.)
|
|
88
|
+
#
|
|
89
|
+
# @param [Argos::Ds Argos::Diag] argos
|
|
90
|
+
# @return [Hash]
|
|
91
|
+
def self.source(argos)
|
|
92
|
+
|
|
93
|
+
argos.parse(argos.filename)
|
|
94
|
+
|
|
95
|
+
latitude_mean = longitude_mean = nil
|
|
96
|
+
if argos.latitudes.any?
|
|
97
|
+
latitude_mean = (argos.latitudes.inject{ |sum, latitude| sum + latitude } / argos.latitudes.size).round(3)
|
|
98
|
+
end
|
|
99
|
+
if argos.longitudes.any?
|
|
100
|
+
longitude_mean = (argos.longitudes.inject{ |sum, longitude| sum + longitude } / argos.latitudes.size).round(3)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
{ id: argos.source,
|
|
105
|
+
type: argos.type,
|
|
106
|
+
programs: argos.programs,
|
|
107
|
+
platforms: argos.platforms,
|
|
108
|
+
start: argos.start,
|
|
109
|
+
stop: argos.stop,
|
|
110
|
+
north: argos.latitudes.max,
|
|
111
|
+
east: argos.longitudes.max,
|
|
112
|
+
south: argos.latitudes.min,
|
|
113
|
+
west: argos.longitudes.min,
|
|
114
|
+
latitude_mean: latitude_mean,
|
|
115
|
+
longitude_mean: longitude_mean,
|
|
116
|
+
filename: argos.filename,
|
|
117
|
+
filesize: argos.filesize,
|
|
118
|
+
messages: argos.messages.size,
|
|
119
|
+
filter: argos.filtername.nil? ? argos.filter : argos.filtername,
|
|
120
|
+
size: argos.size,
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def latitudes
|
|
126
|
+
select {|a| a.key? :latitude and a[:latitude].is_a? Float }.map {|a| a[:latitude]}.sort
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def longitudes
|
|
130
|
+
select {|a| a.key? :longitude and a[:longitude].is_a? Float }.map {|a| a[:longitude]}.sort
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def platforms
|
|
134
|
+
map {|a| a[:platform]}.uniq.sort
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def programs
|
|
138
|
+
map {|a| a[:program]}.uniq.sort
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
end
|