geoptima 0.1.12 → 0.1.13
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/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
|
-
|