geoptima 0.0.1
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/CHANGELOG +2 -0
- data/CONTRIBUTORS +5 -0
- data/Gemfile +15 -0
- data/README.rdoc +68 -0
- data/bin/show_geoptima +161 -0
- data/examples/export_layer.rb +59 -0
- data/examples/sample_geoptima.json +1173 -0
- data/examples/show_geoptima.rb +161 -0
- data/examples/show_geoptima_sos.rb +50 -0
- data/examples/stats.rb +41 -0
- data/geoptima.gemspec +31 -0
- data/lib/geoptima/data.rb +311 -0
- data/lib/geoptima/version.rb +3 -0
- data/lib/geoptima.rb +3 -0
- metadata +96 -0
@@ -0,0 +1,161 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# useful if being run inside a source code checkout
|
4
|
+
$: << 'lib'
|
5
|
+
$: << '../lib'
|
6
|
+
|
7
|
+
require 'date'
|
8
|
+
require 'geoptima'
|
9
|
+
|
10
|
+
$debug=false
|
11
|
+
|
12
|
+
$event_names=[]
|
13
|
+
$files = []
|
14
|
+
$print_limit = 10000
|
15
|
+
|
16
|
+
def cw(val)
|
17
|
+
val.nil? ? '' : "(#{val})"
|
18
|
+
end
|
19
|
+
|
20
|
+
while arg=ARGV.shift:
|
21
|
+
if arg =~ /^\-(\w+)/
|
22
|
+
$1.split(//).each do |aa|
|
23
|
+
case aa
|
24
|
+
when 'h'
|
25
|
+
$help=true
|
26
|
+
when 'd'
|
27
|
+
$debug=true
|
28
|
+
when 'p'
|
29
|
+
$print=true
|
30
|
+
when 'v'
|
31
|
+
$verbose=true
|
32
|
+
when 'x'
|
33
|
+
$export=true
|
34
|
+
when 's'
|
35
|
+
$seperate=true
|
36
|
+
when 'E'
|
37
|
+
$event_names += ARGV.shift.split(/[\,\;\:\.]+/)
|
38
|
+
when 'T'
|
39
|
+
$time_range = Range.new(*(ARGV.shift.split(/[\,]+/).map do |t|
|
40
|
+
DateTime.parse t
|
41
|
+
end))
|
42
|
+
when 'L'
|
43
|
+
$print_limit = ARGV.shift.to_i
|
44
|
+
$print_limit = 10 if($print_limit<1)
|
45
|
+
else
|
46
|
+
puts "Unrecognized option: -#{aa}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
else
|
50
|
+
if File.exist? arg
|
51
|
+
$files << arg
|
52
|
+
else
|
53
|
+
puts "No such file: #{arg}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
$help = true if($files.length < 1)
|
59
|
+
if $help
|
60
|
+
puts <<EOHELP
|
61
|
+
Usage: ./showGeoptimaEvents.rb <-dvxEh> <-L limit> <-E types> <-T min,max> file <files>
|
62
|
+
-d debug mode (output more context during processing) #{cw $debug}
|
63
|
+
-p print mode (print out final results to console) #{cw $print}
|
64
|
+
-v verbose mode (output extra information to console) #{cw $verbose}
|
65
|
+
-x export IMEI specific CSV files for further processing #{cw $export}
|
66
|
+
-s seperate the export files by event type #{cw $seperate}
|
67
|
+
-h show this help
|
68
|
+
-L limit verbose output to specific number of lines #{cw $print_limit}
|
69
|
+
-E comma-seperated list of event types to show and export (default: all; current: #{$event_names.join(',')})
|
70
|
+
-T time range to limit results to (default: all; current: #{$time_range})
|
71
|
+
EOHELP
|
72
|
+
exit 0
|
73
|
+
end
|
74
|
+
|
75
|
+
$verbose = $verbose || $debug
|
76
|
+
$datasets = Geoptima::Dataset.make_datasets($files, :locate => true, :time_range => $time_range)
|
77
|
+
|
78
|
+
class Export
|
79
|
+
attr_reader :files, :names, :headers
|
80
|
+
def initialize(imei,names,dataset)
|
81
|
+
@names = names
|
82
|
+
if $export
|
83
|
+
if $seperate
|
84
|
+
@files = names.inject({}) do |a,name|
|
85
|
+
a[name] = File.open("#{imei}_#{name}.csv",'w')
|
86
|
+
a
|
87
|
+
end
|
88
|
+
else
|
89
|
+
@files={nil => File.open("#{imei}.csv",'w')}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
@headers = names.inject({}) do |a,name|
|
93
|
+
a[name] = dataset.header([name]).reject{|h| h === 'timeoffset'}
|
94
|
+
puts "Created header for name #{name}: #{a[name].join(',')}" if($debug)
|
95
|
+
a
|
96
|
+
end
|
97
|
+
@headers[nil] = @headers.values.flatten
|
98
|
+
files && files.each do |key,file|
|
99
|
+
file.puts "Time\tEvent\tLatitude\tLongitude\t#{header(key).join("\t")}\n"
|
100
|
+
end
|
101
|
+
if $debug || $verbose
|
102
|
+
@headers.each do |name,head|
|
103
|
+
puts "Header[#{name}]: #{head.join(',')}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
def header(name=nil)
|
108
|
+
@headers[name]
|
109
|
+
end
|
110
|
+
def puts_to(line,name)
|
111
|
+
name = nil unless($seperate)
|
112
|
+
files[name].puts(line) if($export && files[name])
|
113
|
+
end
|
114
|
+
def puts_to_all(line)
|
115
|
+
files && files.each do |key,file|
|
116
|
+
file.puts line
|
117
|
+
end
|
118
|
+
end
|
119
|
+
def close
|
120
|
+
files && files.each do |key,file|
|
121
|
+
file.close
|
122
|
+
@files[key] = nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def if_le
|
128
|
+
$count ||= 0
|
129
|
+
if $print
|
130
|
+
if $count < $print_limit
|
131
|
+
yield
|
132
|
+
elsif $count == $print_limit
|
133
|
+
puts " ... "
|
134
|
+
end
|
135
|
+
end
|
136
|
+
$count += 1
|
137
|
+
end
|
138
|
+
|
139
|
+
puts "Found #{$datasets.length} IMEIs"
|
140
|
+
$datasets.keys.sort.each do |imei|
|
141
|
+
dataset = $datasets[imei]
|
142
|
+
events = dataset.sorted
|
143
|
+
puts if($print)
|
144
|
+
puts "Found #{dataset}"
|
145
|
+
if events && ($print || $export)
|
146
|
+
names = $event_names
|
147
|
+
names = dataset.events_names if(names.length<1)
|
148
|
+
export = Export.new(imei,names,dataset)
|
149
|
+
events.each do |event|
|
150
|
+
names.each do |name|
|
151
|
+
if event.name === name
|
152
|
+
fields = export.header($seperate ? name : nil).map{|h| event[h]}
|
153
|
+
export.puts_to "#{event.time_key}\t#{event.name}\t#{event.latitude}\t#{event.longitude}\t#{fields.join("\t")}", name
|
154
|
+
if_le{puts "#{event.time_key}\t#{event.name}\t#{event.latitude}\t#{event.longitude}\t#{event.fields.inspect}"}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
export.close
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# useful if being run inside a source code checkout
|
4
|
+
$: << 'lib'
|
5
|
+
$: << '../lib'
|
6
|
+
|
7
|
+
require 'date'
|
8
|
+
require 'geoptima'
|
9
|
+
|
10
|
+
$debug=false
|
11
|
+
|
12
|
+
$events={}
|
13
|
+
$files = []
|
14
|
+
|
15
|
+
$events={}
|
16
|
+
|
17
|
+
while arg=ARGV.shift:
|
18
|
+
if arg =~ /^\-(\w+)/
|
19
|
+
$1.split(//).each do |aa|
|
20
|
+
case aa
|
21
|
+
when 'd'
|
22
|
+
$debug=true
|
23
|
+
else
|
24
|
+
puts "Unrecognized option: -#{aa}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
else
|
28
|
+
if File.exist? arg
|
29
|
+
$files << arg
|
30
|
+
else
|
31
|
+
puts "No such file: #{arg}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
$datasets = Geoptima::Dataset.make_datasets($files, :locate => true)
|
37
|
+
|
38
|
+
puts "Found #{$datasets.length} IMEIs"
|
39
|
+
$datasets.keys.sort.each do |imei|
|
40
|
+
dataset = $datasets[imei]
|
41
|
+
events = dataset.sorted('mode')
|
42
|
+
puts "\nFor #{imei} found #{dataset.length} events:\n"
|
43
|
+
if events && events.length>0
|
44
|
+
puts "Time\tLat\tLon\t#{events.first.header[1..-1].join("\t")}"
|
45
|
+
events.each do |event|
|
46
|
+
puts "#{event.time}\t#{event.latitude}\t#{event.longitude}\t#{event.data[1..-1].join("\t")}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
data/examples/stats.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$debug = false
|
4
|
+
$lines = 0
|
5
|
+
|
6
|
+
ARGF.each do |line|
|
7
|
+
$lines += 1
|
8
|
+
fields=line.chomp.split(/\t/)
|
9
|
+
if $headers
|
10
|
+
puts "Processing line: #{line}" if($debug)
|
11
|
+
fields.each_with_index do |field,index|
|
12
|
+
puts "\tField[#{index}]: #{field}" if($debug)
|
13
|
+
stats = $stats[index]
|
14
|
+
stats[field] ||= 0
|
15
|
+
stats[field] += 1
|
16
|
+
puts "\tField[#{index}]: #{field} => #{stats[field]}" if($debug)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
$headers = fields
|
20
|
+
$stats = fields.map{|h| {}}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
$headers.each_with_index do |header,index|
|
25
|
+
stats = $stats[index]
|
26
|
+
values = stats.keys
|
27
|
+
perc = 100.0 * values.length.to_f / $lines.to_f
|
28
|
+
puts "\nFound #{values.length} unique values for field '#{header}'"
|
29
|
+
if values.length > 500
|
30
|
+
puts "\tNot printing more values more diverse than 500"
|
31
|
+
elsif (perc > 75)
|
32
|
+
puts "\tNot printing more values more diverse than #{perc}%"
|
33
|
+
else
|
34
|
+
puts header
|
35
|
+
values.sort.each do |value|
|
36
|
+
value_text = (value.to_s.length < 1) ? '<empty>' : value
|
37
|
+
puts "\t#{value_text}\t#{stats[value]}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/geoptima.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
require 'geoptima/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "geoptima"
|
8
|
+
s.version = Geoptima::VERSION
|
9
|
+
s.authors = "Craig Taverner"
|
10
|
+
s.email = 'craig@amanzi.com'
|
11
|
+
s.homepage = "http://github.com/craigtaverner/geoptima.rb"
|
12
|
+
s.rubyforge_project = 'geoptima'
|
13
|
+
s.summary = "Ruby access to Geoptima JSON files"
|
14
|
+
s.description = <<-EOF
|
15
|
+
Geoptima is a suite of applications for measuring and locating mobile/cellular subscriber experience on GPS enabled smartphones.
|
16
|
+
It is produced by AmanziTel AB in Helsingborg, Sweden, and supports many phone manufacturers, with free downloads from the
|
17
|
+
various app stores, markets or marketplaces. This Ruby library is only capable of reading the JSON format files priduced by these phones
|
18
|
+
and reformating them as CSV for further analysis in Excel. This is a simple and independent way of analysing the data, when
|
19
|
+
compared to the full-featured analysis applications and servers available from AmanziTel. If you want to analyse a limited amount
|
20
|
+
of data in excel, or with Ruby, then this GEM might be for you. If you want to analyse large amounts of data, from many subscribers, or over long periods of time
|
21
|
+
then rather consider the NetView and Customer IQ applications from AmanziTel at www.amanzitel.com.
|
22
|
+
EOF
|
23
|
+
|
24
|
+
s.require_path = 'lib'
|
25
|
+
s.files = Dir.glob("{bin,lib,examples}/**/*").reject{|x| x=~/(tmp|target|test-data)/} + %w(README.rdoc CHANGELOG CONTRIBUTORS Gemfile geoptima.gemspec)
|
26
|
+
s.executables = ['show_geoptima']
|
27
|
+
s.has_rdoc = true
|
28
|
+
s.extra_rdoc_files = %w( README.rdoc )
|
29
|
+
s.rdoc_options = ["--quiet", "--title", "geoptima.rb", "--opname", "index.html", "--line-numbers", "--main", "README.rdoc", "--inline-source"]
|
30
|
+
s.required_ruby_version = ">= 1.8.7"
|
31
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'json'
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
#
|
8
|
+
# The Geoptima Module provides support for the Geoptima Client JSON file format
|
9
|
+
#
|
10
|
+
module Geoptima
|
11
|
+
|
12
|
+
SPERDAY = 60*60*24
|
13
|
+
MSPERDAY = 1000*60*60*24
|
14
|
+
SHORT = 256*256
|
15
|
+
|
16
|
+
class Config
|
17
|
+
DEFAULT={:min_datetime => DateTime.parse("2000-01-01"), :max_datetime => DateTime.parse("2030-01-01")}
|
18
|
+
def self.config(options={})
|
19
|
+
@@options = (@@options || DEFAULT).merge(options)
|
20
|
+
end
|
21
|
+
def self.options
|
22
|
+
@@options ||= DEFAULT
|
23
|
+
end
|
24
|
+
def self.[](key)
|
25
|
+
options[key]
|
26
|
+
end
|
27
|
+
def self.[]=(key,value)
|
28
|
+
options[key] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# The Geoptima::Event class represents and individual record or event
|
33
|
+
class Event
|
34
|
+
attr_reader :header, :name, :data, :fields, :time, :latitude, :longitude
|
35
|
+
def initialize(start,name,header,data)
|
36
|
+
@name = name
|
37
|
+
@header = header
|
38
|
+
@data = data
|
39
|
+
@fields = @header.inject({}) do |a,v|
|
40
|
+
a[v] = @data[a.length]
|
41
|
+
a
|
42
|
+
end
|
43
|
+
@time = start + (@fields['timeoffset'].to_f / MSPERDAY.to_f)
|
44
|
+
@fields.reject!{|k,v| k=~/timeoffset/}
|
45
|
+
if @fields['cell_id'].to_i > SHORT
|
46
|
+
@fields['cell_id'] = @fields['cell_id'].to_i % SHORT
|
47
|
+
end
|
48
|
+
puts "Created Event: #{self}" if($debug)
|
49
|
+
end
|
50
|
+
def utc
|
51
|
+
time.new_offset(0)
|
52
|
+
end
|
53
|
+
def time_key
|
54
|
+
utc.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
55
|
+
end
|
56
|
+
def [](key)
|
57
|
+
@fields[key]
|
58
|
+
end
|
59
|
+
def -(other)
|
60
|
+
(self.time - other.time) * SPERDAY
|
61
|
+
end
|
62
|
+
def closer_than(other,seconds=60)
|
63
|
+
(self - other).abs < seconds
|
64
|
+
end
|
65
|
+
def locate(gps)
|
66
|
+
@latitude = gps['latitude']
|
67
|
+
@longitude = gps['longitude']
|
68
|
+
end
|
69
|
+
def locate_if_closer_than(gps,seconds=60)
|
70
|
+
locate(gps) if(closer_than(gps,seconds))
|
71
|
+
end
|
72
|
+
def to_s
|
73
|
+
"#{name}[#{time}]: #{@fields.inspect}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# The Geoptima::Data is an entire JSON file of events
|
78
|
+
class Data
|
79
|
+
attr_reader :path, :json, :count
|
80
|
+
def initialize(path)
|
81
|
+
@path = path
|
82
|
+
@json = JSON.parse(File.read(path))
|
83
|
+
if $debug
|
84
|
+
puts "Read Geoptima: #{geoptima}"
|
85
|
+
puts "\tSubscriber: #{subscriber.to_json}"
|
86
|
+
puts "\tIMSI: #{imsi}"
|
87
|
+
puts "\tIMEI: #{imei}"
|
88
|
+
puts "\tStart: #{start}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
def to_s
|
92
|
+
json.to_json[0..100]
|
93
|
+
end
|
94
|
+
def geoptima
|
95
|
+
@geoptima ||= json['geoptima']
|
96
|
+
end
|
97
|
+
def subscriber
|
98
|
+
@subscriber ||= geoptima['subscriber']
|
99
|
+
end
|
100
|
+
def imsi
|
101
|
+
@imsi ||= subscriber['imsi']
|
102
|
+
end
|
103
|
+
def imei
|
104
|
+
@imei ||= subscriber['imei']
|
105
|
+
end
|
106
|
+
def platform
|
107
|
+
@platform ||= subscriber['Platform'] || subscriber['platform']
|
108
|
+
end
|
109
|
+
def model
|
110
|
+
@model ||= subscriber['Model'] || subscriber['model']
|
111
|
+
end
|
112
|
+
def os
|
113
|
+
@os ||= subscriber['OS']
|
114
|
+
end
|
115
|
+
def start
|
116
|
+
@start ||= subscriber['start'] && DateTime.parse(subscriber['start'].gsub(/Asia\/Bangkok/,'GMT+7').gsub(/Mar 17 2044/,'Feb 14 2012'))
|
117
|
+
end
|
118
|
+
def valid?
|
119
|
+
start && start > Data.min_start && start < Data.max_start
|
120
|
+
end
|
121
|
+
def self.min_start
|
122
|
+
@@min_start ||= DateTime.parse("2010-01-01 00:00:00")
|
123
|
+
end
|
124
|
+
def self.max_start
|
125
|
+
@@max_start ||= DateTime.parse("2020-01-01 00:00:00")
|
126
|
+
end
|
127
|
+
def events
|
128
|
+
@events ||= make_events
|
129
|
+
end
|
130
|
+
def events_names
|
131
|
+
events.keys.sort
|
132
|
+
end
|
133
|
+
def make_hash(name)
|
134
|
+
geoptima[name].inject({}) do |a,md|
|
135
|
+
key = md.keys[0]
|
136
|
+
a[key]=md[key]
|
137
|
+
a
|
138
|
+
end
|
139
|
+
end
|
140
|
+
def make_events
|
141
|
+
@count = 0
|
142
|
+
@events_metadata = make_hash('events-metadata')
|
143
|
+
events_data = {}
|
144
|
+
geoptima['events'].each do |data|
|
145
|
+
events = data['values']
|
146
|
+
event_type = data.keys.reject{|k| k=~/values/}[0]
|
147
|
+
header = @events_metadata[event_type]
|
148
|
+
if header
|
149
|
+
events_data[event_type] = (0...data[event_type].to_i).inject([]) do |a,block|
|
150
|
+
index = header.length * block
|
151
|
+
data = events[index...(index+header.length)]
|
152
|
+
if data && data.length == header.length
|
153
|
+
@count += 1
|
154
|
+
a << Event.new(start,event_type,header,data)
|
155
|
+
else
|
156
|
+
puts "Invalid '#{event_type}' data block #{block}: #{data.inspect}"
|
157
|
+
break a
|
158
|
+
end
|
159
|
+
end
|
160
|
+
if $debug
|
161
|
+
puts "Have '#{event_type}' event data:"
|
162
|
+
puts "\t#{header.join("\t")}"
|
163
|
+
events_data[event_type].each do |d|
|
164
|
+
puts "\t#{d.data.join("\t")}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
else
|
168
|
+
puts "No header found for event type: #{event_type}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
events_data
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class Dataset
|
176
|
+
|
177
|
+
attr_reader :imei, :options
|
178
|
+
|
179
|
+
def initialize(imei,options={})
|
180
|
+
@imei = imei
|
181
|
+
@data = []
|
182
|
+
@options = options
|
183
|
+
@time_range = options[:time_range] || Range.new(Config[:min_datetime],Config[:max_datetime])
|
184
|
+
end
|
185
|
+
|
186
|
+
def <<(data)
|
187
|
+
@sorted = nil
|
188
|
+
@data << data
|
189
|
+
end
|
190
|
+
|
191
|
+
def file_count
|
192
|
+
@data.length
|
193
|
+
end
|
194
|
+
|
195
|
+
def imsi
|
196
|
+
imsis[0]
|
197
|
+
end
|
198
|
+
|
199
|
+
def imsis
|
200
|
+
@imsis ||= @data.inject({}) do |a,d|
|
201
|
+
a[d.imsi] ||= 0
|
202
|
+
a[d.imsi] += d.count.to_i
|
203
|
+
a
|
204
|
+
end.to_a.sort do |a,b|
|
205
|
+
b[1]<=>a[1]
|
206
|
+
end.map do |x|
|
207
|
+
#puts "Have IMSI: #{x.join('=')}"
|
208
|
+
x[0]
|
209
|
+
end.compact.uniq
|
210
|
+
end
|
211
|
+
|
212
|
+
def platform
|
213
|
+
@platform ||= @data.map{|d| d.platform}.compact.uniq[0]
|
214
|
+
end
|
215
|
+
|
216
|
+
def model
|
217
|
+
@model ||= @data.map{|d| d.model}.compact.uniq[0]
|
218
|
+
end
|
219
|
+
|
220
|
+
def os
|
221
|
+
@os ||= @data.map{|d| d.os}.compact.uniq[0]
|
222
|
+
end
|
223
|
+
|
224
|
+
def first
|
225
|
+
merge_events unless @sorted
|
226
|
+
@sorted[nil][0]
|
227
|
+
end
|
228
|
+
|
229
|
+
def last
|
230
|
+
merge_events unless @sorted
|
231
|
+
@sorted[nil][-1]
|
232
|
+
end
|
233
|
+
|
234
|
+
def length
|
235
|
+
sorted.length
|
236
|
+
end
|
237
|
+
|
238
|
+
def sorted(event_type=nil)
|
239
|
+
merge_events unless @sorted
|
240
|
+
unless @sorted[event_type] || event_type.nil?
|
241
|
+
@sorted[event_type] = @sorted[nil].reject do |event|
|
242
|
+
event.name != event_type
|
243
|
+
end
|
244
|
+
end
|
245
|
+
@sorted[event_type]
|
246
|
+
end
|
247
|
+
|
248
|
+
def header(names=nil)
|
249
|
+
merge_events unless @sorted
|
250
|
+
(names || events_names).map do |name|
|
251
|
+
[(s=sorted(name)[0]) && s.header]
|
252
|
+
end.flatten
|
253
|
+
end
|
254
|
+
|
255
|
+
def events_names
|
256
|
+
@data.map{ |v| v.events_names }.flatten.uniq.sort
|
257
|
+
end
|
258
|
+
|
259
|
+
def merge_events
|
260
|
+
@sorted ||= {}
|
261
|
+
unless @sorted[nil]
|
262
|
+
event_hash = {}
|
263
|
+
events_names.each do |name|
|
264
|
+
@data.each do |data|
|
265
|
+
(events = data.events[name]) && events.each do |event|
|
266
|
+
if @time_range.include?(event.time)
|
267
|
+
key = "#{event.time_key} #{name}"
|
268
|
+
event_hash[key] = event
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
@sorted[nil] = event_hash.keys.sort.map{|k| event_hash[k]}
|
274
|
+
locate_events if(options[:locate])
|
275
|
+
end
|
276
|
+
@sorted
|
277
|
+
end
|
278
|
+
|
279
|
+
def locate_events
|
280
|
+
prev_gps = nil
|
281
|
+
sorted.each do |event|
|
282
|
+
if event.name === 'gps'
|
283
|
+
prev_gps = event
|
284
|
+
elsif prev_gps
|
285
|
+
event.locate_if_closer_than(prev_gps,60)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def to_s
|
291
|
+
"IMEI:#{imei}, IMSI:#{imsis.join(',')}, Platform:#{platform}, Model:#{model}, OS:#{os}, Files:#{file_count}, Events:#{sorted.length}"
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.make_datasets(files, options={})
|
295
|
+
datasets = {}
|
296
|
+
files.each do |file|
|
297
|
+
geoptima=Geoptima::Data.new(file)
|
298
|
+
unless geoptima.valid?
|
299
|
+
puts "INVALID: #{geoptima.start}\t#{file}\n\n"
|
300
|
+
else
|
301
|
+
datasets[geoptima.imei] ||= Geoptima::Dataset.new(geoptima.imei, options)
|
302
|
+
datasets[geoptima.imei] << geoptima
|
303
|
+
end
|
304
|
+
end
|
305
|
+
datasets
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
311
|
+
|
data/lib/geoptima.rb
ADDED
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: geoptima
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Craig Taverner
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-03-01 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: |
|
22
|
+
Geoptima is a suite of applications for measuring and locating mobile/cellular subscriber experience on GPS enabled smartphones.
|
23
|
+
It is produced by AmanziTel AB in Helsingborg, Sweden, and supports many phone manufacturers, with free downloads from the
|
24
|
+
various app stores, markets or marketplaces. This Ruby library is only capable of reading the JSON format files priduced by these phones
|
25
|
+
and reformating them as CSV for further analysis in Excel. This is a simple and independent way of analysing the data, when
|
26
|
+
compared to the full-featured analysis applications and servers available from AmanziTel. If you want to analyse a limited amount
|
27
|
+
of data in excel, or with Ruby, then this GEM might be for you. If you want to analyse large amounts of data, from many subscribers, or over long periods of time
|
28
|
+
then rather consider the NetView and Customer IQ applications from AmanziTel at www.amanzitel.com.
|
29
|
+
|
30
|
+
email: craig@amanzi.com
|
31
|
+
executables:
|
32
|
+
- show_geoptima
|
33
|
+
extensions: []
|
34
|
+
|
35
|
+
extra_rdoc_files:
|
36
|
+
- README.rdoc
|
37
|
+
files:
|
38
|
+
- bin/show_geoptima
|
39
|
+
- lib/geoptima/version.rb
|
40
|
+
- lib/geoptima/data.rb
|
41
|
+
- lib/geoptima.rb
|
42
|
+
- examples/show_geoptima_sos.rb
|
43
|
+
- examples/export_layer.rb
|
44
|
+
- examples/stats.rb
|
45
|
+
- examples/show_geoptima.rb
|
46
|
+
- examples/sample_geoptima.json
|
47
|
+
- README.rdoc
|
48
|
+
- CHANGELOG
|
49
|
+
- CONTRIBUTORS
|
50
|
+
- Gemfile
|
51
|
+
- geoptima.gemspec
|
52
|
+
homepage: http://github.com/craigtaverner/geoptima.rb
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --quiet
|
58
|
+
- --title
|
59
|
+
- geoptima.rb
|
60
|
+
- --opname
|
61
|
+
- index.html
|
62
|
+
- --line-numbers
|
63
|
+
- --main
|
64
|
+
- README.rdoc
|
65
|
+
- --inline-source
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 57
|
74
|
+
segments:
|
75
|
+
- 1
|
76
|
+
- 8
|
77
|
+
- 7
|
78
|
+
version: 1.8.7
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
requirements: []
|
89
|
+
|
90
|
+
rubyforge_project: geoptima
|
91
|
+
rubygems_version: 1.7.2
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Ruby access to Geoptima JSON files
|
95
|
+
test_files: []
|
96
|
+
|