geoptima 0.1.12 → 0.1.13
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/show_geoptima +51 -7
- data/examples/show_geoptima.rb +51 -7
- data/lib/geoptima/data.rb +193 -10
- data/lib/geoptima/version.rb +1 -1
- metadata +66 -71
data/bin/show_geoptima
CHANGED
@@ -8,13 +8,18 @@ require 'date'
|
|
8
8
|
require 'geoptima'
|
9
9
|
require 'geoptima/options'
|
10
10
|
|
11
|
-
Geoptima::assert_version(">=0.1.
|
11
|
+
Geoptima::assert_version(">=0.1.13")
|
12
12
|
|
13
13
|
$debug=false
|
14
14
|
|
15
15
|
$event_names=[]
|
16
16
|
$files = []
|
17
17
|
$print_limit = 10000
|
18
|
+
$gpx_options = {
|
19
|
+
'scale' => 195, 'padding' => 5,
|
20
|
+
'limit' => 2, 'png_limit' => 10,
|
21
|
+
'points' => true, 'point_size' => 2, 'point_color' => '0000aa'
|
22
|
+
}
|
18
23
|
|
19
24
|
$files = Geoptima::Options.process_args do |option|
|
20
25
|
option.p {$print = true}
|
@@ -26,11 +31,13 @@ $files = Geoptima::Options.process_args do |option|
|
|
26
31
|
option.l {$more_headers = true}
|
27
32
|
option.e {$show_error_stats = true}
|
28
33
|
option.g {$export_gpx = true}
|
34
|
+
option.t {$split_time = true}
|
29
35
|
option.P {$export_prefix = ARGV.shift}
|
30
36
|
option.E {$event_names += ARGV.shift.split(/[\,\;\:\.]+/)}
|
31
37
|
option.T {$time_range = Geoptima::DateRange.from ARGV.shift}
|
32
38
|
option.L {$print_limit = [1,ARGV.shift.to_i].max}
|
33
39
|
option.M {$mapfile = ARGV.shift}
|
40
|
+
option.G {$gpx_options.merge! ARGV.shift.split(/[\,\;]+/).inject({}){|a,v| k=v.split(/[\:\=]+/);a[k[0]]=k[1]||true;a}}
|
34
41
|
end.map do |file|
|
35
42
|
File.exist?(file) ? file : puts("No such file: #{file}")
|
36
43
|
end.compact
|
@@ -124,12 +131,25 @@ Usage: show_geoptima <-dwvpxomlsafegh> <-P export_prefix> <-L limit> <-E types>
|
|
124
131
|
-f flush stdout #{cw $flush_stdout}
|
125
132
|
-e show error statistics #{cw $show_error_stats}
|
126
133
|
-g export GPX traces #{cw $export_gpx}
|
134
|
+
-t split time colum to multiple columns #{cw $split_time}
|
127
135
|
-h show this help
|
128
136
|
-P prefix for exported files (default: ''; current: #{$export_prefix})
|
129
137
|
-E comma-seperated list of event types to show and export (default: all; current: #{$event_names.join(',')})
|
130
138
|
-T time range to limit results to (default: all; current: #{$time_range})
|
131
139
|
-L limit verbose output to specific number of lines #{cw $print_limit}
|
132
140
|
-M mapfile of normal->altered header names: #{$mapfile}
|
141
|
+
-G GPX export options as ';' separated list of key:value pairs
|
142
|
+
Current GPX options: #{$gpx_options.inspect}
|
143
|
+
Known supported GPX options (might be more, see data.rb code):
|
144
|
+
limit:#{$gpx_options['limit']}\tLimit GPX output to traces with at least this number of events
|
145
|
+
png_limit:#{$gpx_options['png_limit']}\tLimit PNG output to traces with at least this number of events
|
146
|
+
scale:#{$gpx_options['scale']}\tSize of print area in PNG output
|
147
|
+
padding:#{$gpx_options['padding']}\tSpace around print area
|
148
|
+
points:#{$gpx_options['points']}\tTurn on/off points
|
149
|
+
point_size:#{$gpx_options['point_size']}\tSet point size
|
150
|
+
point_color:#{$gpx_options['point_color']}\tSet point color: RRGGBBAA in hex
|
151
|
+
PNG images will be 'scale + 2 * padding' big. The scale will be used for
|
152
|
+
the widest dimension, and the other will be reduced to fit the trace.
|
133
153
|
EOHELP
|
134
154
|
show_header_maps
|
135
155
|
exit 0
|
@@ -172,7 +192,7 @@ class Export
|
|
172
192
|
if $header_maps
|
173
193
|
file.puts $header_maps.find{|hm| hm.event == key}.columns.map{|c| c[0]}.join("\t")
|
174
194
|
else
|
175
|
-
file.puts map_headers(base_headers+more_headers+header(key)).join("\t")
|
195
|
+
file.puts map_headers(time_headers+base_headers+more_headers+header(key)).join("\t")
|
176
196
|
end
|
177
197
|
end
|
178
198
|
if $debug || $verbose
|
@@ -184,6 +204,17 @@ class Export
|
|
184
204
|
def export_imei
|
185
205
|
($combine_all || $more_headers)
|
186
206
|
end
|
207
|
+
def time_headers
|
208
|
+
$split_time ? ['Year','Month','Day','Hour','Minute','Second','Millisecond'] : []
|
209
|
+
end
|
210
|
+
def time_fields(event)
|
211
|
+
if $split_time
|
212
|
+
t = event.utc
|
213
|
+
[t.year,t.month,t.day,t.hour,t.minute,t.second,(t.second_fraction.to_f * 1000).to_i]
|
214
|
+
else
|
215
|
+
[]
|
216
|
+
end
|
217
|
+
end
|
187
218
|
def base_headers
|
188
219
|
['Time','Event','Latitude','Longitude'] +
|
189
220
|
(export_imei ? ['IMEI'] : [])
|
@@ -223,9 +254,6 @@ class Export
|
|
223
254
|
end
|
224
255
|
end
|
225
256
|
end
|
226
|
-
def get_field(event,name)
|
227
|
-
h=(base_headers+more_headers).grep(/#{name}/)
|
228
|
-
end
|
229
257
|
def cap(array,sep="")
|
230
258
|
array.map do |v|
|
231
259
|
"#{v[0..0].upcase}#{v[1..-1]}"
|
@@ -264,6 +292,15 @@ class Export
|
|
264
292
|
out.puts trace.as_gpx
|
265
293
|
end
|
266
294
|
end
|
295
|
+
def export_png(trace)
|
296
|
+
puts "Exporting #{trace.length} GPS events to PNG: #{trace}"
|
297
|
+
if $verbose
|
298
|
+
puts "\tBounds: #{trace.bounds}"
|
299
|
+
puts "\tWidth: #{trace.width}"
|
300
|
+
puts "\tHeight: #{trace.height}"
|
301
|
+
end
|
302
|
+
trace.to_png "#{$export_prefix}#{trace}.png", $gpx_options
|
303
|
+
end
|
267
304
|
def header(name=nil)
|
268
305
|
@headers[name]
|
269
306
|
end
|
@@ -324,8 +361,15 @@ $datasets.keys.sort.each do |imei|
|
|
324
361
|
export = Export.new(imei,names,dataset)
|
325
362
|
export.export_stats(dataset.stats) if($export_stats)
|
326
363
|
if $export_gpx
|
364
|
+
merged_traces = Geoptima::MergedTrace.new(dataset)
|
327
365
|
dataset.each_trace do |trace|
|
328
|
-
export.export_gpx(trace)
|
366
|
+
export.export_gpx(trace) if(trace.length>=($gpx_options['limit'] || 1).to_i)
|
367
|
+
export.export_png(trace) if(trace.length>=($gpx_options['png_limit'] || 10).to_i)
|
368
|
+
merged_traces << trace if($gpx_options['merge'])
|
369
|
+
end
|
370
|
+
if($gpx_options['merge'])
|
371
|
+
export.export_gpx(merged_traces)
|
372
|
+
export.export_png(merged_traces)
|
329
373
|
end
|
330
374
|
end
|
331
375
|
if $header_maps && $header_maps.length > 0
|
@@ -347,7 +391,7 @@ $datasets.keys.sort.each do |imei|
|
|
347
391
|
names.each do |name|
|
348
392
|
if event.name === name
|
349
393
|
fields = export.header($seperate ? name : nil).map{|h| event[h]}
|
350
|
-
b_fields = export.base_fields(event) + export.more_fields(event,dataset)
|
394
|
+
b_fields = export.time_fields(event) + export.base_fields(event) + export.more_fields(event,dataset)
|
351
395
|
export.puts_to "#{b_fields.join("\t")}\t#{fields.join("\t")}", name
|
352
396
|
if_le{puts "#{b_fields.join("\t")}\t#{event.fields.inspect}"}
|
353
397
|
end
|
data/examples/show_geoptima.rb
CHANGED
@@ -8,13 +8,18 @@ require 'date'
|
|
8
8
|
require 'geoptima'
|
9
9
|
require 'geoptima/options'
|
10
10
|
|
11
|
-
Geoptima::assert_version(">=0.1.
|
11
|
+
Geoptima::assert_version(">=0.1.13")
|
12
12
|
|
13
13
|
$debug=false
|
14
14
|
|
15
15
|
$event_names=[]
|
16
16
|
$files = []
|
17
17
|
$print_limit = 10000
|
18
|
+
$gpx_options = {
|
19
|
+
'scale' => 195, 'padding' => 5,
|
20
|
+
'limit' => 2, 'png_limit' => 10,
|
21
|
+
'points' => true, 'point_size' => 2, 'point_color' => '0000aa'
|
22
|
+
}
|
18
23
|
|
19
24
|
$files = Geoptima::Options.process_args do |option|
|
20
25
|
option.p {$print = true}
|
@@ -26,11 +31,13 @@ $files = Geoptima::Options.process_args do |option|
|
|
26
31
|
option.l {$more_headers = true}
|
27
32
|
option.e {$show_error_stats = true}
|
28
33
|
option.g {$export_gpx = true}
|
34
|
+
option.t {$split_time = true}
|
29
35
|
option.P {$export_prefix = ARGV.shift}
|
30
36
|
option.E {$event_names += ARGV.shift.split(/[\,\;\:\.]+/)}
|
31
37
|
option.T {$time_range = Geoptima::DateRange.from ARGV.shift}
|
32
38
|
option.L {$print_limit = [1,ARGV.shift.to_i].max}
|
33
39
|
option.M {$mapfile = ARGV.shift}
|
40
|
+
option.G {$gpx_options.merge! ARGV.shift.split(/[\,\;]+/).inject({}){|a,v| k=v.split(/[\:\=]+/);a[k[0]]=k[1]||true;a}}
|
34
41
|
end.map do |file|
|
35
42
|
File.exist?(file) ? file : puts("No such file: #{file}")
|
36
43
|
end.compact
|
@@ -124,12 +131,25 @@ Usage: show_geoptima <-dwvpxomlsafegh> <-P export_prefix> <-L limit> <-E types>
|
|
124
131
|
-f flush stdout #{cw $flush_stdout}
|
125
132
|
-e show error statistics #{cw $show_error_stats}
|
126
133
|
-g export GPX traces #{cw $export_gpx}
|
134
|
+
-t split time colum to multiple columns #{cw $split_time}
|
127
135
|
-h show this help
|
128
136
|
-P prefix for exported files (default: ''; current: #{$export_prefix})
|
129
137
|
-E comma-seperated list of event types to show and export (default: all; current: #{$event_names.join(',')})
|
130
138
|
-T time range to limit results to (default: all; current: #{$time_range})
|
131
139
|
-L limit verbose output to specific number of lines #{cw $print_limit}
|
132
140
|
-M mapfile of normal->altered header names: #{$mapfile}
|
141
|
+
-G GPX export options as ';' separated list of key:value pairs
|
142
|
+
Current GPX options: #{$gpx_options.inspect}
|
143
|
+
Known supported GPX options (might be more, see data.rb code):
|
144
|
+
limit:#{$gpx_options['limit']}\tLimit GPX output to traces with at least this number of events
|
145
|
+
png_limit:#{$gpx_options['png_limit']}\tLimit PNG output to traces with at least this number of events
|
146
|
+
scale:#{$gpx_options['scale']}\tSize of print area in PNG output
|
147
|
+
padding:#{$gpx_options['padding']}\tSpace around print area
|
148
|
+
points:#{$gpx_options['points']}\tTurn on/off points
|
149
|
+
point_size:#{$gpx_options['point_size']}\tSet point size
|
150
|
+
point_color:#{$gpx_options['point_color']}\tSet point color: RRGGBBAA in hex
|
151
|
+
PNG images will be 'scale + 2 * padding' big. The scale will be used for
|
152
|
+
the widest dimension, and the other will be reduced to fit the trace.
|
133
153
|
EOHELP
|
134
154
|
show_header_maps
|
135
155
|
exit 0
|
@@ -172,7 +192,7 @@ class Export
|
|
172
192
|
if $header_maps
|
173
193
|
file.puts $header_maps.find{|hm| hm.event == key}.columns.map{|c| c[0]}.join("\t")
|
174
194
|
else
|
175
|
-
file.puts map_headers(base_headers+more_headers+header(key)).join("\t")
|
195
|
+
file.puts map_headers(time_headers+base_headers+more_headers+header(key)).join("\t")
|
176
196
|
end
|
177
197
|
end
|
178
198
|
if $debug || $verbose
|
@@ -184,6 +204,17 @@ class Export
|
|
184
204
|
def export_imei
|
185
205
|
($combine_all || $more_headers)
|
186
206
|
end
|
207
|
+
def time_headers
|
208
|
+
$split_time ? ['Year','Month','Day','Hour','Minute','Second','Millisecond'] : []
|
209
|
+
end
|
210
|
+
def time_fields(event)
|
211
|
+
if $split_time
|
212
|
+
t = event.utc
|
213
|
+
[t.year,t.month,t.day,t.hour,t.minute,t.second,(t.second_fraction.to_f * 1000).to_i]
|
214
|
+
else
|
215
|
+
[]
|
216
|
+
end
|
217
|
+
end
|
187
218
|
def base_headers
|
188
219
|
['Time','Event','Latitude','Longitude'] +
|
189
220
|
(export_imei ? ['IMEI'] : [])
|
@@ -223,9 +254,6 @@ class Export
|
|
223
254
|
end
|
224
255
|
end
|
225
256
|
end
|
226
|
-
def get_field(event,name)
|
227
|
-
h=(base_headers+more_headers).grep(/#{name}/)
|
228
|
-
end
|
229
257
|
def cap(array,sep="")
|
230
258
|
array.map do |v|
|
231
259
|
"#{v[0..0].upcase}#{v[1..-1]}"
|
@@ -264,6 +292,15 @@ class Export
|
|
264
292
|
out.puts trace.as_gpx
|
265
293
|
end
|
266
294
|
end
|
295
|
+
def export_png(trace)
|
296
|
+
puts "Exporting #{trace.length} GPS events to PNG: #{trace}"
|
297
|
+
if $verbose
|
298
|
+
puts "\tBounds: #{trace.bounds}"
|
299
|
+
puts "\tWidth: #{trace.width}"
|
300
|
+
puts "\tHeight: #{trace.height}"
|
301
|
+
end
|
302
|
+
trace.to_png "#{$export_prefix}#{trace}.png", $gpx_options
|
303
|
+
end
|
267
304
|
def header(name=nil)
|
268
305
|
@headers[name]
|
269
306
|
end
|
@@ -324,8 +361,15 @@ $datasets.keys.sort.each do |imei|
|
|
324
361
|
export = Export.new(imei,names,dataset)
|
325
362
|
export.export_stats(dataset.stats) if($export_stats)
|
326
363
|
if $export_gpx
|
364
|
+
merged_traces = Geoptima::MergedTrace.new(dataset)
|
327
365
|
dataset.each_trace do |trace|
|
328
|
-
export.export_gpx(trace)
|
366
|
+
export.export_gpx(trace) if(trace.length>=($gpx_options['limit'] || 1).to_i)
|
367
|
+
export.export_png(trace) if(trace.length>=($gpx_options['png_limit'] || 10).to_i)
|
368
|
+
merged_traces << trace if($gpx_options['merge'])
|
369
|
+
end
|
370
|
+
if($gpx_options['merge'])
|
371
|
+
export.export_gpx(merged_traces)
|
372
|
+
export.export_png(merged_traces)
|
329
373
|
end
|
330
374
|
end
|
331
375
|
if $header_maps && $header_maps.length > 0
|
@@ -347,7 +391,7 @@ $datasets.keys.sort.each do |imei|
|
|
347
391
|
names.each do |name|
|
348
392
|
if event.name === name
|
349
393
|
fields = export.header($seperate ? name : nil).map{|h| event[h]}
|
350
|
-
b_fields = export.base_fields(event) + export.more_fields(event,dataset)
|
394
|
+
b_fields = export.time_fields(event) + export.base_fields(event) + export.more_fields(event,dataset)
|
351
395
|
export.puts_to "#{b_fields.join("\t")}\t#{fields.join("\t")}", name
|
352
396
|
if_le{puts "#{b_fields.join("\t")}\t#{event.fields.inspect}"}
|
353
397
|
end
|
data/lib/geoptima/data.rb
CHANGED
@@ -3,6 +3,11 @@
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'multi_json'
|
5
5
|
require 'geoptima/daterange'
|
6
|
+
begin
|
7
|
+
require 'png'
|
8
|
+
rescue
|
9
|
+
puts "No PNG library installed, ignoring PNG output of GPX results"
|
10
|
+
end
|
6
11
|
|
7
12
|
#
|
8
13
|
# The Geoptima Module provides support for the Geoptima Client JSON file format
|
@@ -34,7 +39,7 @@ module Geoptima
|
|
34
39
|
end
|
35
40
|
|
36
41
|
class Trace
|
37
|
-
attr_reader :dataset, :name, :tracename, :bounds, :events
|
42
|
+
attr_reader :dataset, :name, :tracename, :bounds, :events, :scale, :padding
|
38
43
|
def initialize(dataset)
|
39
44
|
@dataset = dataset
|
40
45
|
@name = dataset.name
|
@@ -60,6 +65,8 @@ module Geoptima
|
|
60
65
|
check_bounds_min :minlon, e.longitude
|
61
66
|
check_bounds_max :maxlat, e.latitude
|
62
67
|
check_bounds_max :maxlon, e.longitude
|
68
|
+
@width = nil
|
69
|
+
@height = nil
|
63
70
|
end
|
64
71
|
def check_bounds_min(key,value)
|
65
72
|
@bounds[key] = value if(@bounds[key].nil? || @bounds[key] > value)
|
@@ -70,6 +77,46 @@ module Geoptima
|
|
70
77
|
def each
|
71
78
|
events.each{|e| yield e}
|
72
79
|
end
|
80
|
+
def scale=(a)
|
81
|
+
@rescaled = false
|
82
|
+
@scale = a && (a.respond_to?('max') ? a : [a.to_i,a.to_i]) || [100,100]
|
83
|
+
end
|
84
|
+
def padding=(a)
|
85
|
+
@padding = a && (a.respond_to?('max') ? a : [a.to_i,a.to_i]) || [0,0]
|
86
|
+
end
|
87
|
+
def size
|
88
|
+
@size = [scale[0]+2*padding[0],scale[1]+2*padding[1]]
|
89
|
+
end
|
90
|
+
def scale
|
91
|
+
@scale ||= [100,100]
|
92
|
+
unless @rescaled
|
93
|
+
major,minor = (width > height) ? [0,1] : [1,0]
|
94
|
+
puts "About to rescale scale=#{@scale.inspect} using major=#{major}, minor=#{minor}, height=#{height}, width=#{width}" if($debug)
|
95
|
+
@scale[minor] = (@scale[major].to_f * height / width).to_i
|
96
|
+
@rescaled = true
|
97
|
+
end
|
98
|
+
@scale
|
99
|
+
end
|
100
|
+
def width
|
101
|
+
@width ||= @bounds[:maxlon].to_f - @bounds[:minlon].to_f
|
102
|
+
end
|
103
|
+
def height
|
104
|
+
@height ||= @bounds[:maxlat].to_f - @bounds[:minlat].to_f
|
105
|
+
end
|
106
|
+
def left
|
107
|
+
@left ||= @bounds[:minlon].to_f
|
108
|
+
end
|
109
|
+
def bottom
|
110
|
+
@bottom ||= @bounds[:minlat].to_f
|
111
|
+
end
|
112
|
+
def scale_event(e)
|
113
|
+
p = [e.longitude.to_f, e.latitude.to_f]
|
114
|
+
if scale
|
115
|
+
p[0] = padding[0] + ((p[0] - left) * (scale[0].to_f - 1) / (width)).to_i
|
116
|
+
p[1] = padding[1] + ((p[1] - bottom) * (scale[1].to_f - 1) / (height)).to_i
|
117
|
+
end
|
118
|
+
p
|
119
|
+
end
|
73
120
|
def too_far(other)
|
74
121
|
events && events[-1] && (events[-1].days_from(other) > 0.5 || events[-1].distance_from(other) > 0.002)
|
75
122
|
end
|
@@ -90,16 +137,119 @@ module Geoptima
|
|
90
137
|
<name>#{tracename}</name>
|
91
138
|
<trkseg>
|
92
139
|
} +
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
140
|
+
traces.map do |trace|
|
141
|
+
trace.events.map do |e|
|
142
|
+
ei += 1
|
143
|
+
event_as_gpx(e,ei)
|
144
|
+
end.join("\n ")
|
145
|
+
end.join("\n </trkseg><trkseg>") +
|
97
146
|
"""
|
98
147
|
</trkseg>
|
99
148
|
</trk>
|
100
149
|
</gpx>
|
101
150
|
"""
|
102
151
|
end
|
152
|
+
def fix_options(options={})
|
153
|
+
options.keys.each do |key|
|
154
|
+
val = options[key]
|
155
|
+
if val.to_s =~ /false/i
|
156
|
+
val = false
|
157
|
+
elsif val != true && val.to_i.to_s == val
|
158
|
+
val = val.to_i
|
159
|
+
end
|
160
|
+
options[key] = val
|
161
|
+
end
|
162
|
+
end
|
163
|
+
def traces
|
164
|
+
[self]
|
165
|
+
end
|
166
|
+
def colors
|
167
|
+
@colors ||= PNG::Color.constants.reject{|c| (c.is_a?(PNG::Color)) || c.to_s =~ /max/i || c.to_s =~ /background/i}.map{|c| PNG::Color.const_get c}.reject{|c| c == PNG::Color::Background}
|
168
|
+
end
|
169
|
+
def color(index=1)
|
170
|
+
self.colors[index%(self.colors.length)]
|
171
|
+
end
|
172
|
+
def to_png(filename, options={})
|
173
|
+
fix_options options
|
174
|
+
puts "Exporting with options: #{options.inspect}"
|
175
|
+
self.scale = options['scale']
|
176
|
+
self.padding = options['padding']
|
177
|
+
['scale','padding','size','bounds','width','height'].each do |a|
|
178
|
+
puts "\t#{a}: #{self.send(a).inspect}"
|
179
|
+
end
|
180
|
+
canvas = PNG::Canvas.new size[0],size[1]
|
181
|
+
|
182
|
+
traces.each_with_index do |trace,index|
|
183
|
+
prev = nil
|
184
|
+
point_color = color(index)
|
185
|
+
line_color = PNG::Color.from "0x00006688"
|
186
|
+
if options['point_color'] && !(options['point_color'] =~ /auto/i)
|
187
|
+
pc = options['point_color'].gsub(/^\#/,'').gsub(/^0x/,'').upcase
|
188
|
+
pc = "0x#{pc}FFFFFFFF"[0...10]
|
189
|
+
begin
|
190
|
+
point_color = PNG::Color.from pc
|
191
|
+
rescue
|
192
|
+
puts "Failed to interpret color #{pc}, use format 0x00000000: #{$!}"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
puts "Got point color #{point_color} from index #{index}"
|
196
|
+
trace.events.each do |e|
|
197
|
+
p = scale_event(e)
|
198
|
+
begin
|
199
|
+
# draw an anti-aliased line
|
200
|
+
if prev && prev != p
|
201
|
+
canvas.line prev[0], prev[1], p[0], p[1], line_color
|
202
|
+
end
|
203
|
+
|
204
|
+
if options['points']
|
205
|
+
# Set a point to a color
|
206
|
+
n = options['point_size'].to_i / 2
|
207
|
+
r = (-n..n)
|
208
|
+
r.each do |x|
|
209
|
+
r.each do |y|
|
210
|
+
canvas[p[0]+x, p[1]-y] = point_color
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
rescue
|
215
|
+
puts "Error in writing PNG: #{$!}"
|
216
|
+
end
|
217
|
+
|
218
|
+
prev = p
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
png = PNG.new canvas
|
223
|
+
png.save filename
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class MergedTrace < Trace
|
228
|
+
attr_reader :traces
|
229
|
+
def initialize(dataset)
|
230
|
+
@dataset = dataset
|
231
|
+
@name = dataset.name
|
232
|
+
@traces = []
|
233
|
+
end
|
234
|
+
def tracename
|
235
|
+
@tracename ||= "Merged-#{traces.length}-traces-#{traces[0]}"
|
236
|
+
end
|
237
|
+
def <<(t)
|
238
|
+
check_bounds(t)
|
239
|
+
@traces << t
|
240
|
+
end
|
241
|
+
def length
|
242
|
+
@length ||= traces.inject(0){|a,t| a+=t.length;a}
|
243
|
+
end
|
244
|
+
def check_bounds(t)
|
245
|
+
@bounds ||= {}
|
246
|
+
check_bounds_min :minlat, t.bounds[:minlat]
|
247
|
+
check_bounds_min :minlon, t.bounds[:minlon]
|
248
|
+
check_bounds_max :maxlat, t.bounds[:maxlat]
|
249
|
+
check_bounds_max :maxlon, t.bounds[:maxlon]
|
250
|
+
@width = nil
|
251
|
+
@height = nil
|
252
|
+
end
|
103
253
|
end
|
104
254
|
|
105
255
|
module ErrorCounter
|
@@ -174,6 +324,7 @@ module Geoptima
|
|
174
324
|
a
|
175
325
|
end
|
176
326
|
@timeoffset = (@fields['timeoffset'].to_f / MSPERDAY.to_f)
|
327
|
+
@time = start + timeoffset # Note we set this again later after corrections (need it now for puts output)
|
177
328
|
if(@timeoffset<-0.0000001)
|
178
329
|
puts "Have negative time offset: #{@fields['timeoffset']}" if($debug)
|
179
330
|
incr_error "#4506 negative offsets"
|
@@ -229,6 +380,9 @@ module Geoptima
|
|
229
380
|
def locate_if_closer_than(gps,seconds=60)
|
230
381
|
locate(gps) if(closer_than(gps,seconds))
|
231
382
|
end
|
383
|
+
def puts line
|
384
|
+
Kernel.puts "#{name}[#{time}]: #{line}"
|
385
|
+
end
|
232
386
|
def to_s
|
233
387
|
"#{name}[#{time}]: #{@fields.inspect}"
|
234
388
|
end
|
@@ -237,9 +391,10 @@ module Geoptima
|
|
237
391
|
# The Geoptima::Data is an entire JSON file of events
|
238
392
|
class Data
|
239
393
|
include ErrorCounter
|
240
|
-
attr_reader :path, :json, :count
|
394
|
+
attr_reader :path, :name, :json, :count
|
241
395
|
def initialize(path)
|
242
396
|
@path = path
|
397
|
+
@name = File.basename(path)
|
243
398
|
# @json = JSON.parse(File.read(path))
|
244
399
|
@json = MultiJson.decode(File.read(path))
|
245
400
|
@fields = {}
|
@@ -261,6 +416,9 @@ module Geoptima
|
|
261
416
|
def to_s
|
262
417
|
json.to_json[0..100]
|
263
418
|
end
|
419
|
+
def puts line
|
420
|
+
Kernel.puts "#{name}: #{line}"
|
421
|
+
end
|
264
422
|
def geoptima
|
265
423
|
@geoptima ||= json['geoptima']
|
266
424
|
end
|
@@ -273,6 +431,9 @@ module Geoptima
|
|
273
431
|
def imei
|
274
432
|
@imei ||= self['imei']
|
275
433
|
end
|
434
|
+
def id
|
435
|
+
@id ||= self['id'] || self['udid'] || self['imei']
|
436
|
+
end
|
276
437
|
def [](key)
|
277
438
|
@fields[key] ||= subscriber[key] || subscriber[key.downcase]
|
278
439
|
end
|
@@ -628,18 +789,40 @@ module Geoptima
|
|
628
789
|
"Dataset:#{name}, IMEI:#{imeis.join(',')}, IMSI:#{imsis.join(',')}, Platform:#{platforms.join(',')}, Model:#{models.join(',')}, OS:#{oses.join(',')}, Files:#{file_count}, Events:#{sorted && sorted.length}"
|
629
790
|
end
|
630
791
|
|
631
|
-
def self.
|
632
|
-
|
633
|
-
|
792
|
+
def self.add_file_to_datasets(datasets,file,options={})
|
793
|
+
if File.directory?(file)
|
794
|
+
add_directory_to_datasets(datasets,file,options)
|
795
|
+
else
|
634
796
|
geoptima=Geoptima::Data.new(file)
|
635
797
|
unless geoptima.valid?
|
636
798
|
puts "INVALID: #{geoptima.start}\t#{file}\n\n"
|
637
799
|
else
|
638
|
-
key = options[:combine_all] ? 'all' : geoptima
|
800
|
+
key = options[:combine_all] ? 'all' : geoptima.id
|
639
801
|
datasets[key] ||= Geoptima::Dataset.new(key, options)
|
640
802
|
datasets[key] << geoptima
|
641
803
|
end
|
642
804
|
end
|
805
|
+
end
|
806
|
+
|
807
|
+
def self.add_directory_to_datasets(datasets,directory,options={})
|
808
|
+
Dir.open(directory).each do |file|
|
809
|
+
next if(file =~ /^\./)
|
810
|
+
path = "#{directory}/#{file}"
|
811
|
+
if File.directory? path
|
812
|
+
add_directory_to_datasets(datasets,path,options)
|
813
|
+
elsif file =~ /\.json/i
|
814
|
+
add_file_to_datasets(datasets,path,options)
|
815
|
+
else
|
816
|
+
puts "Ignoring files without JSON extension: #{path}"
|
817
|
+
end
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
821
|
+
def self.make_datasets(files, options={})
|
822
|
+
datasets = {}
|
823
|
+
files.each do |file|
|
824
|
+
add_file_to_datasets(datasets,file,options)
|
825
|
+
end
|
643
826
|
datasets
|
644
827
|
end
|
645
828
|
|
data/lib/geoptima/version.rb
CHANGED
metadata
CHANGED
@@ -1,75 +1,81 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: geoptima
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.13
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
- 12
|
10
|
-
version: 0.1.12
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Craig Taverner
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-07-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: multi_json
|
22
|
-
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
24
17
|
none: false
|
25
|
-
requirements:
|
26
|
-
- -
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
hash: 19
|
29
|
-
segments:
|
30
|
-
- 1
|
31
|
-
- 1
|
32
|
-
- 0
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
33
21
|
version: 1.1.0
|
34
22
|
type: :runtime
|
35
|
-
version_requirements: *id001
|
36
|
-
- !ruby/object:Gem::Dependency
|
37
|
-
name: json_pure
|
38
23
|
prerelease: false
|
39
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.1.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: json_pure
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
40
33
|
none: false
|
41
|
-
requirements:
|
42
|
-
- -
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
hash: 5
|
45
|
-
segments:
|
46
|
-
- 1
|
47
|
-
- 6
|
48
|
-
- 5
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
49
37
|
version: 1.6.5
|
50
38
|
type: :runtime
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.6.5
|
46
|
+
description: ! 'Geoptima is a suite of applications for measuring and locating mobile/cellular
|
47
|
+
subscriber experience on GPS enabled smartphones.
|
48
|
+
|
49
|
+
It is produced by AmanziTel AB in Helsingborg, Sweden, and supports many phone manufacturers,
|
50
|
+
with free downloads from the
|
60
51
|
|
52
|
+
various app stores, markets or marketplaces. This Ruby library is only capable of
|
53
|
+
reading the JSON format files priduced by these phones
|
54
|
+
|
55
|
+
and reformating them as CSV for further analysis in Excel. This is a simple and
|
56
|
+
independent way of analysing the data, when
|
57
|
+
|
58
|
+
compared to the full-featured analysis applications and servers available from AmanziTel.
|
59
|
+
If you want to analyse a limited amount
|
60
|
+
|
61
|
+
of data in excel, or with Ruby, then this GEM might be for you. If you want to analyse
|
62
|
+
large amounts of data, from many subscribers, or over long periods of time
|
63
|
+
|
64
|
+
then rather consider the NetView and Customer IQ applications from AmanziTel at
|
65
|
+
www.amanzitel.com.
|
66
|
+
|
67
|
+
'
|
61
68
|
email: craig@amanzi.com
|
62
|
-
executables:
|
69
|
+
executables:
|
63
70
|
- show_geoptima
|
64
71
|
- geoptima_file_time
|
65
72
|
- csv_chart
|
66
73
|
- csv_stats
|
67
74
|
- csv_merge
|
68
75
|
extensions: []
|
69
|
-
|
70
|
-
extra_rdoc_files:
|
76
|
+
extra_rdoc_files:
|
71
77
|
- README.rdoc
|
72
|
-
files:
|
78
|
+
files:
|
73
79
|
- bin/show_geoptima_sos
|
74
80
|
- bin/show_geoptima
|
75
81
|
- bin/csv_chart
|
@@ -98,9 +104,8 @@ files:
|
|
98
104
|
- geoptima.gemspec
|
99
105
|
homepage: http://github.com/craigtaverner/geoptima.rb
|
100
106
|
licenses: []
|
101
|
-
|
102
107
|
post_install_message:
|
103
|
-
rdoc_options:
|
108
|
+
rdoc_options:
|
104
109
|
- --quiet
|
105
110
|
- --title
|
106
111
|
- Geoptima.rb
|
@@ -108,34 +113,24 @@ rdoc_options:
|
|
108
113
|
- --main
|
109
114
|
- README.rdoc
|
110
115
|
- --inline-source
|
111
|
-
require_paths:
|
116
|
+
require_paths:
|
112
117
|
- lib
|
113
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
119
|
none: false
|
115
|
-
requirements:
|
116
|
-
- -
|
117
|
-
- !ruby/object:Gem::Version
|
118
|
-
hash: 59
|
119
|
-
segments:
|
120
|
-
- 1
|
121
|
-
- 8
|
122
|
-
- 6
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
123
|
version: 1.8.6
|
124
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
125
|
none: false
|
126
|
-
requirements:
|
127
|
-
- -
|
128
|
-
- !ruby/object:Gem::Version
|
129
|
-
|
130
|
-
segments:
|
131
|
-
- 0
|
132
|
-
version: "0"
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
133
130
|
requirements: []
|
134
|
-
|
135
131
|
rubyforge_project: geoptima
|
136
|
-
rubygems_version: 1.8.
|
132
|
+
rubygems_version: 1.8.24
|
137
133
|
signing_key:
|
138
134
|
specification_version: 3
|
139
135
|
summary: Ruby access to Geoptima JSON files
|
140
136
|
test_files: []
|
141
|
-
|