geoptima 0.1.15 → 0.1.17
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/geoptima_file_time +2 -2
- data/bin/geoptima_file_time_stats +133 -0
- data/bin/show_geoptima +46 -7
- data/examples/geoptima_file_time.rb +2 -2
- data/examples/geoptima_file_time_stats.rb +133 -0
- data/examples/show_geoptima.rb +46 -7
- data/geoptima.gemspec +1 -1
- data/lib/geoptima/data.rb +265 -36
- data/lib/geoptima/file_time.rb +3 -2
- data/lib/geoptima/locationrange.rb +45 -6
- data/lib/geoptima/timer.rb +39 -0
- data/lib/geoptima/version.rb +1 -1
- metadata +75 -66
data/lib/geoptima/data.rb
CHANGED
@@ -4,6 +4,7 @@ require 'rubygems'
|
|
4
4
|
require 'multi_json'
|
5
5
|
require 'geoptima/daterange'
|
6
6
|
require 'geoptima/locationrange'
|
7
|
+
require 'geoptima/timer'
|
7
8
|
begin
|
8
9
|
require 'png'
|
9
10
|
rescue LoadError
|
@@ -40,24 +41,43 @@ module Geoptima
|
|
40
41
|
end
|
41
42
|
|
42
43
|
class Trace
|
43
|
-
attr_reader :dataset, :data_id, :
|
44
|
+
attr_reader :dataset, :data_id, :data_id_hashset, :name, :tracename
|
44
45
|
attr_reader :bounds, :events, :totals, :scale, :padding
|
45
|
-
def initialize(dataset)
|
46
|
+
def initialize(dataset, options={})
|
46
47
|
@dataset = dataset
|
48
|
+
@trace_type = options[:type]
|
47
49
|
@data_id = nil
|
48
|
-
@
|
49
|
-
@name = dataset.name
|
50
|
+
@data_id_hashset = {}
|
51
|
+
@name = options[:name] || dataset.name
|
50
52
|
@events = []
|
53
|
+
case @trace_type
|
54
|
+
when /ways/
|
55
|
+
@ptag = 'wpt'
|
56
|
+
when /route/
|
57
|
+
@ttag = 'rte'
|
58
|
+
@ptag = 'rtept'
|
59
|
+
else
|
60
|
+
@ttag = 'trk'
|
61
|
+
@stag = 'trkseg'
|
62
|
+
@ptag = 'trkpt'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
def data_ids
|
66
|
+
puts "About to sort data ids: #{@data_id_hashset.keys.join(',')}" if($debug)
|
67
|
+
@data_id_hashset.keys.compact.sort
|
51
68
|
end
|
52
69
|
def <<(e)
|
53
|
-
|
54
|
-
|
55
|
-
@data_id ||= e.
|
56
|
-
@
|
70
|
+
if gpx_id = e.gpx_id
|
71
|
+
@tracename ||= "#{name}-#{e.time}"
|
72
|
+
@data_id ||= e.gpx_id
|
73
|
+
@data_id_hashset[e.gpx_id] ||= 0
|
74
|
+
@data_id_hashset[e.gpx_id] += 1
|
75
|
+
check_bounds(e)
|
76
|
+
check_totals(e)
|
77
|
+
@events << e unless(co_located(e,@events[-1]))
|
78
|
+
elsif $debug
|
79
|
+
puts "Ignoring event with nil GPX id: #{e.inspect}"
|
57
80
|
end
|
58
|
-
check_bounds(e)
|
59
|
-
check_totals(e)
|
60
|
-
@events << e unless(co_located(e,@events[-1]))
|
61
81
|
end
|
62
82
|
def co_located(event,other)
|
63
83
|
event && other && event.latitude == other.latitude && event.longitude == other.longitude
|
@@ -88,13 +108,22 @@ module Geoptima
|
|
88
108
|
raise "Passed a string value: #{value.inspect}"
|
89
109
|
end
|
90
110
|
begin
|
91
|
-
@bounds[key] = value if(@bounds[key].nil? || @bounds[key] > value)
|
111
|
+
@bounds[key] = value if(!value.nil? &&(@bounds[key].nil? || @bounds[key] > value))
|
92
112
|
rescue
|
93
113
|
raise "Failed to set bounds using current:#{@bounds.inspect}, value=#{value.inspect}"
|
94
114
|
end
|
95
115
|
end
|
96
116
|
def check_bounds_max(key,value)
|
97
|
-
@bounds[key] = value if(@bounds[key].nil? || @bounds[key] < value)
|
117
|
+
@bounds[key] = value if(!value.nil? &&(@bounds[key].nil? || @bounds[key] < value))
|
118
|
+
end
|
119
|
+
def check_trace_bounds(t)
|
120
|
+
@bounds ||= {}
|
121
|
+
check_bounds_min :minlat, t.bounds[:minlat]
|
122
|
+
check_bounds_min :minlon, t.bounds[:minlon]
|
123
|
+
check_bounds_max :maxlat, t.bounds[:maxlat]
|
124
|
+
check_bounds_max :maxlon, t.bounds[:maxlon]
|
125
|
+
@width = nil
|
126
|
+
@height = nil
|
98
127
|
end
|
99
128
|
def each
|
100
129
|
events.each{|e| yield e}
|
@@ -174,10 +203,57 @@ module Geoptima
|
|
174
203
|
def bounds_as_gpx
|
175
204
|
"<bounds " + bounds.keys.map{|k| "#{k}=\"#{bounds[k]}\""}.join(' ') + "/>"
|
176
205
|
end
|
177
|
-
def event_as_gpx(e,
|
178
|
-
"
|
206
|
+
def event_as_gpx(e,elevation=0.0)
|
207
|
+
"<#{@ptag} lat=\"#{e.latitude}\" lon=\"#{e.longitude}\"><ele>#{elevation}</ele><time>#{e.time}</time><name>#{e.name}</name><desc>#{e.description}</desc></#{@ptag}>"
|
179
208
|
end
|
180
209
|
def as_gpx
|
210
|
+
return unless(length>0)
|
211
|
+
case @trace_type
|
212
|
+
when /way/
|
213
|
+
as_gpx_ways
|
214
|
+
when /route/
|
215
|
+
as_gpx_route
|
216
|
+
else
|
217
|
+
as_gpx_track
|
218
|
+
end
|
219
|
+
end
|
220
|
+
def as_gpx_ways
|
221
|
+
gpx = %{<?xml version="1.0" encoding="UTF-8"?>
|
222
|
+
<gpx version="1.1" creator="geoptima.rb - Craig Taverner">
|
223
|
+
<metadata>
|
224
|
+
#{bounds_as_gpx}
|
225
|
+
</metadata>
|
226
|
+
<name>#{tracename}</name>
|
227
|
+
} +
|
228
|
+
traces.map do |trace|
|
229
|
+
trace.events.map do |e|
|
230
|
+
event_as_gpx(e)
|
231
|
+
end.join("\n ")
|
232
|
+
end.join("\n ") +
|
233
|
+
"""
|
234
|
+
</gpx>
|
235
|
+
"""
|
236
|
+
end
|
237
|
+
def as_gpx_route
|
238
|
+
gpx = %{<?xml version="1.0" encoding="UTF-8"?>
|
239
|
+
<gpx version="1.1" creator="geoptima.rb - Craig Taverner">
|
240
|
+
<metadata>
|
241
|
+
#{bounds_as_gpx}
|
242
|
+
</metadata>
|
243
|
+
<rte>
|
244
|
+
<name>#{tracename}</name>
|
245
|
+
} +
|
246
|
+
traces.map do |trace|
|
247
|
+
trace.events.map do |e|
|
248
|
+
event_as_gpx(e,ei)
|
249
|
+
end.join("\n ")
|
250
|
+
end.join("\n ") +
|
251
|
+
"""
|
252
|
+
</rte>
|
253
|
+
</gpx>
|
254
|
+
"""
|
255
|
+
end
|
256
|
+
def as_gpx_track
|
181
257
|
ei = 0
|
182
258
|
gpx = %{<?xml version="1.0" encoding="UTF-8"?>
|
183
259
|
<gpx version="1.1" creator="geoptima.rb - Craig Taverner">
|
@@ -193,7 +269,7 @@ module Geoptima
|
|
193
269
|
ei += 1
|
194
270
|
event_as_gpx(e,ei)
|
195
271
|
end.join("\n ")
|
196
|
-
end.join("\n
|
272
|
+
end.join("\n </trkseg>\n <trkseg>\n ") +
|
197
273
|
"""
|
198
274
|
</trkseg>
|
199
275
|
</trk>
|
@@ -217,6 +293,13 @@ module Geoptima
|
|
217
293
|
end
|
218
294
|
options[key] = val
|
219
295
|
end
|
296
|
+
puts "Fixed options: #{options.inspect}"
|
297
|
+
options['show_line'].nil? && (options['show_line']=true)
|
298
|
+
if @trace_type =~ /ways/
|
299
|
+
options['show_line'] = false
|
300
|
+
options['point_size'] = (options['point_size'].to_i + 1) * 2
|
301
|
+
puts "Adjusted line/point settings for ways: show_line:#{options['show_line']}, point_size:#{options['point_size']}"
|
302
|
+
end
|
220
303
|
end
|
221
304
|
def traces
|
222
305
|
[self]
|
@@ -245,14 +328,15 @@ module Geoptima
|
|
245
328
|
end
|
246
329
|
|
247
330
|
data_idm = data_ids.inject({}){|a,v| a[v]=a.length;a}
|
331
|
+
puts "Created map of data ids from available set: #{data_ids.inspect}"
|
248
332
|
if data_ids.length > 1
|
249
333
|
data_ids.each do |did|
|
250
|
-
puts "
|
334
|
+
puts "\t#{color(data_idm[did])}\t#{data_id_hashset[did]}\t#{did}"
|
251
335
|
end
|
252
336
|
end
|
253
337
|
traces.each_with_index do |trace,index|
|
254
|
-
point_color = color(index)
|
255
338
|
line_color = PNG::Color.from "0x00006688"
|
339
|
+
point_color = color(index)
|
256
340
|
if options['point_color'] && !(options['point_color'] =~ /auto/i)
|
257
341
|
pc = options['point_color'].gsub(/^\#/,'').gsub(/^0x/,'').upcase
|
258
342
|
pc = "0x#{pc}FFFFFFFF"[0...10]
|
@@ -261,9 +345,6 @@ module Geoptima
|
|
261
345
|
rescue
|
262
346
|
puts "Failed to interpret color #{pc}, use format 0x00000000: #{$!}"
|
263
347
|
end
|
264
|
-
elsif data_ids.length > 1
|
265
|
-
point_color = color(data_idm[trace.data_id])
|
266
|
-
puts "Got point color #{point_color} from data ID '#{trace.data_id}' index #{data_idm[trace.data_id]}" if($debug)
|
267
348
|
else
|
268
349
|
point_color = color(index)
|
269
350
|
puts "Got point color #{point_color} from trace index #{index}" if($debug)
|
@@ -272,9 +353,13 @@ module Geoptima
|
|
272
353
|
prev = nil
|
273
354
|
trace.events.each do |e|
|
274
355
|
p = scale_event(e)
|
356
|
+
if data_idm.length > 1
|
357
|
+
point_color = color(data_idm[e.gpx_id])
|
358
|
+
puts "Got point color #{point_color} from data ID '#{e.gpx_id}' index #{data_idm[e.gpx_id]}" if($debug)
|
359
|
+
end
|
275
360
|
begin
|
276
361
|
# draw an anti-aliased line
|
277
|
-
if prev && prev != p
|
362
|
+
if options['show_line'] && prev && prev != p
|
278
363
|
canvas.line prev[0], prev[1], p[0], p[1], line_color
|
279
364
|
end
|
280
365
|
|
@@ -306,7 +391,7 @@ module Geoptima
|
|
306
391
|
def initialize(dataset)
|
307
392
|
@dataset = dataset
|
308
393
|
@name = dataset.name
|
309
|
-
@
|
394
|
+
@data_id_hashset = {}
|
310
395
|
@traces = []
|
311
396
|
end
|
312
397
|
def tracename
|
@@ -315,7 +400,7 @@ module Geoptima
|
|
315
400
|
def <<(t)
|
316
401
|
check_trace_totals(t)
|
317
402
|
check_trace_bounds(t)
|
318
|
-
@
|
403
|
+
@data_id_hashset = @data_id_hashset.merge(t.data_id_hashset)
|
319
404
|
@traces << t
|
320
405
|
end
|
321
406
|
def each
|
@@ -332,15 +417,6 @@ module Geoptima
|
|
332
417
|
@totals ||= [0.0,0.0,0]
|
333
418
|
[0,1,2].each{|i| @totals[i] += t.totals[i]}
|
334
419
|
end
|
335
|
-
def check_trace_bounds(t)
|
336
|
-
@bounds ||= {}
|
337
|
-
check_bounds_min :minlat, t.bounds[:minlat]
|
338
|
-
check_bounds_min :minlon, t.bounds[:minlon]
|
339
|
-
check_bounds_max :maxlat, t.bounds[:maxlat]
|
340
|
-
check_bounds_max :maxlon, t.bounds[:maxlon]
|
341
|
-
@width = nil
|
342
|
-
@height = nil
|
343
|
-
end
|
344
420
|
end
|
345
421
|
|
346
422
|
module ErrorCounter
|
@@ -464,8 +540,14 @@ module Geoptima
|
|
464
540
|
def closer_than(other,seconds=60)
|
465
541
|
(self - other).abs < seconds
|
466
542
|
end
|
543
|
+
def latitude
|
544
|
+
@latitude ||= self['latitude']
|
545
|
+
end
|
546
|
+
def longitude
|
547
|
+
@longitude ||= self['longitude']
|
548
|
+
end
|
467
549
|
def location
|
468
|
-
@location ||=
|
550
|
+
@location ||= latitude && Point.new(latitude,longitude)
|
469
551
|
end
|
470
552
|
def locate(gps)
|
471
553
|
incr_error "GPS String Data" if(gps['latitude'].is_a? String)
|
@@ -482,6 +564,110 @@ module Geoptima
|
|
482
564
|
def to_s
|
483
565
|
"#{name}[#{time}]: #{@fields.inspect}"
|
484
566
|
end
|
567
|
+
def description
|
568
|
+
"#{name}"
|
569
|
+
end
|
570
|
+
def valid_gpx?
|
571
|
+
location
|
572
|
+
end
|
573
|
+
def gpx_id
|
574
|
+
file.id
|
575
|
+
end
|
576
|
+
def to_type
|
577
|
+
case name
|
578
|
+
when 'runningApps'
|
579
|
+
RunningApps.new(self)
|
580
|
+
else
|
581
|
+
self
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
class AppCategory
|
587
|
+
attr_reader :key, :category, :app_class, :name
|
588
|
+
def initialize(key, category, app_class, name)
|
589
|
+
@key, @category, @app_class, @name = key, category, app_class, name
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
class AppCategories
|
594
|
+
attr_reader :app_categories
|
595
|
+
def initialize(path)
|
596
|
+
begin
|
597
|
+
@app_categories = File.open(path).inject({}) do |a,l|
|
598
|
+
f=l.chomp.split(/\,/).map{|v| v.gsub(/^\s+/,'').gsub(/\s+$/,'')}
|
599
|
+
a[f[0]] = AppCategory.new(*f) unless(f[0] =~ /appName.unique/i)
|
600
|
+
a
|
601
|
+
end
|
602
|
+
rescue
|
603
|
+
puts "Failed to load app categories from '#{path}': #{$!}"
|
604
|
+
@app_categories = {}
|
605
|
+
end
|
606
|
+
end
|
607
|
+
def length
|
608
|
+
@app_categories.length
|
609
|
+
end
|
610
|
+
def [](key)
|
611
|
+
@app_categories[key]
|
612
|
+
end
|
613
|
+
def apps
|
614
|
+
@apps ||= @app_categories.keys.compact.sort.uniq
|
615
|
+
end
|
616
|
+
def categories
|
617
|
+
@categories ||= @app_categories.values.map{|c| c.category}.compact.sort.uniq
|
618
|
+
end
|
619
|
+
def app_classes
|
620
|
+
@app_classes ||= @app_categories.values.map{|c| c.app_class}.compact.sort.uniq
|
621
|
+
end
|
622
|
+
def to_s
|
623
|
+
"#{length} apps, #{categories.length} categories, #{app_classes.length} classes"
|
624
|
+
end
|
625
|
+
def a_to_s(a)
|
626
|
+
(a.length > 3 ? a[0..2]+['...'] : a).join(',')
|
627
|
+
end
|
628
|
+
def describe
|
629
|
+
"#{length} apps, #{categories.length} categories (#{a_to_s(categories)}), #{app_classes.length} classes (#{a_to_s(app_classes)})"
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
# This class allows the 'runningApps' event to behave differently than
|
634
|
+
# normal events. It is used by the GPX exporter to color code these based
|
635
|
+
# on the app category
|
636
|
+
class RunningApps
|
637
|
+
attr_reader :inner_event
|
638
|
+
def initialize(event)
|
639
|
+
@inner_event = event
|
640
|
+
end
|
641
|
+
def method_missing(symbol,*args,&block)
|
642
|
+
@inner_event.send symbol, *args, &block
|
643
|
+
end
|
644
|
+
def description
|
645
|
+
@inner_event['appName']
|
646
|
+
end
|
647
|
+
def valid_gpx?
|
648
|
+
@inner_event.valid_gpx? && @inner_event['state'] == 'STARTED'
|
649
|
+
end
|
650
|
+
def gpx_id
|
651
|
+
app = @inner_event['appName']
|
652
|
+
if $app_categories && $app_categories.length > 0
|
653
|
+
puts "Choosing internal GPX ID based on specified categories: #{$app_categories.describe}" if($debug)
|
654
|
+
if $app_categories.categories.length == 1
|
655
|
+
puts "Filtering on only one category, using app name for GPX ID: #{app}" if($debug)
|
656
|
+
if (a=$app_categories[app]) && a.category =~ /\w/
|
657
|
+
a.key
|
658
|
+
else
|
659
|
+
nil
|
660
|
+
end
|
661
|
+
elsif (a=$app_categories[app]) && a.category =~ /\w/
|
662
|
+
puts "Found matching category app[#{app}] => #{a.category}" if($debug)
|
663
|
+
a.category
|
664
|
+
else
|
665
|
+
nil
|
666
|
+
end
|
667
|
+
else
|
668
|
+
app
|
669
|
+
end
|
670
|
+
end
|
485
671
|
end
|
486
672
|
|
487
673
|
# The Geoptima::Data is an entire JSON file of events
|
@@ -721,6 +907,8 @@ module Geoptima
|
|
721
907
|
|
722
908
|
def recent(event,key,seconds=60)
|
723
909
|
unless event[key]
|
910
|
+
timer("export.event.recent").start
|
911
|
+
timer("export.event.recent.#{key}").start
|
724
912
|
if imei = event.file.imei
|
725
913
|
puts "Searching for recent values for '#{key}' starting at event #{event}" if($debug)
|
726
914
|
ev,prop=key.split(/\./)
|
@@ -742,6 +930,8 @@ module Geoptima
|
|
742
930
|
else
|
743
931
|
puts "Not searching for correlated data without imei: #{event}"
|
744
932
|
end
|
933
|
+
timer("export.event.recent.#{key}").stop
|
934
|
+
timer("export.event.recent").stop
|
745
935
|
end
|
746
936
|
# @recent[key] ||= ''
|
747
937
|
event[key]
|
@@ -780,9 +970,11 @@ module Geoptima
|
|
780
970
|
def sorted(event_type=nil)
|
781
971
|
merge_events unless @sorted
|
782
972
|
unless @sorted[event_type] || event_type.nil?
|
973
|
+
timer("sorted.#{event_type}").start
|
783
974
|
@sorted[event_type] = @sorted[nil].reject do |event|
|
784
975
|
event.name != event_type
|
785
976
|
end
|
977
|
+
timer("sorted.#{event_type}").stop
|
786
978
|
end
|
787
979
|
@sorted[event_type]
|
788
980
|
end
|
@@ -797,6 +989,7 @@ module Geoptima
|
|
797
989
|
def stats
|
798
990
|
merge_events unless @sorted
|
799
991
|
unless @stats
|
992
|
+
timer('stats').start
|
800
993
|
@stats = {}
|
801
994
|
event_count = 0
|
802
995
|
sorted.each do |event|
|
@@ -809,6 +1002,7 @@ module Geoptima
|
|
809
1002
|
@stats[key][value] += 1
|
810
1003
|
end
|
811
1004
|
end
|
1005
|
+
timer('stats').stop
|
812
1006
|
end
|
813
1007
|
@stats.reject! do |k,v|
|
814
1008
|
v.length > 500 || v.length > 10 && v.length > event_count / 2
|
@@ -820,9 +1014,23 @@ module Geoptima
|
|
820
1014
|
@data.map{ |v| v.events_names }.flatten.uniq.sort
|
821
1015
|
end
|
822
1016
|
|
1017
|
+
def timer(name)
|
1018
|
+
@timers ||= {}
|
1019
|
+
@timers[name] ||= Geoptima::Timer.new(name)
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
def dump_timers(out=STDOUT)
|
1023
|
+
out.puts "Printing timer information for #{@timers.length} timers:"
|
1024
|
+
@timers.keys.sort.each do |key|
|
1025
|
+
t = @timers[key]
|
1026
|
+
out.puts "\t#{t.describe}"
|
1027
|
+
end
|
1028
|
+
end
|
1029
|
+
|
823
1030
|
def merge_events
|
824
1031
|
@sorted ||= {}
|
825
1032
|
unless @sorted[nil]
|
1033
|
+
timer('merge_events').start
|
826
1034
|
event_hash = {}
|
827
1035
|
puts "Creating sorted maps for #{self}" if($debug)
|
828
1036
|
events_names.each do |name|
|
@@ -845,16 +1053,34 @@ module Geoptima
|
|
845
1053
|
puts "After adding #{name} events, maps are #{event_hash.length} long" if($debug)
|
846
1054
|
end
|
847
1055
|
puts "Merging and sorting #{event_hash.keys.length} maps" if($debug)
|
1056
|
+
timer('merge_events.sort').start
|
848
1057
|
@sorted[nil] = event_hash.keys.sort.map{|k| event_hash[k]}
|
1058
|
+
timer('merge_events.sort').stop
|
849
1059
|
puts "Sorted #{@sorted[nil].length} events" if($debug)
|
1060
|
+
timer('merge_events.locate').start
|
850
1061
|
locate_events if(options[:locate])
|
1062
|
+
timer('merge_events.locate').stop
|
1063
|
+
timer('merge_events').stop
|
851
1064
|
end
|
852
1065
|
@sorted
|
853
1066
|
end
|
854
1067
|
|
1068
|
+
def waypoints(waypoints=nil)
|
1069
|
+
@waypoints ||= {}
|
1070
|
+
event_type = waypoints=='all' ? nil : waypoints
|
1071
|
+
unless @waypoints[event_type]
|
1072
|
+
@waypoints[event_type] = Trace.new(self, :type => 'ways', :name => "waypoints-#{self.name}")
|
1073
|
+
sorted(event_type).each do |e|
|
1074
|
+
e = e.to_type
|
1075
|
+
@waypoints[event_type] << e if(e.valid_gpx?)
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
@waypoints[event_type]
|
1079
|
+
end
|
1080
|
+
|
855
1081
|
def each_trace
|
856
|
-
puts "Exporting GPX traces"
|
857
1082
|
trace = nil
|
1083
|
+
timer('each_trace').start
|
858
1084
|
sorted('gps').each do |gps|
|
859
1085
|
trace ||= Trace.new(self)
|
860
1086
|
if trace.too_far(gps)
|
@@ -864,19 +1090,22 @@ module Geoptima
|
|
864
1090
|
trace << gps
|
865
1091
|
end
|
866
1092
|
yield trace if(trace)
|
1093
|
+
timer('each_trace').stop
|
867
1094
|
end
|
868
1095
|
|
869
1096
|
def locate_events
|
870
1097
|
prev_gps = nil
|
871
1098
|
count = 0
|
872
|
-
puts "Locating #{sorted.length} events" if(
|
1099
|
+
puts "Locating #{sorted.length} events" if(true||$debug)
|
873
1100
|
sorted.each do |event|
|
1101
|
+
timer('locate.each').start
|
874
1102
|
if event.name === 'gps'
|
875
1103
|
event.locate(event)
|
876
1104
|
prev_gps = event
|
877
1105
|
elsif prev_gps
|
878
1106
|
count += 1 if(event.locate_if_closer_than(prev_gps,60))
|
879
1107
|
end
|
1108
|
+
timer('locate.each').stop
|
880
1109
|
end
|
881
1110
|
puts "Located #{count} / #{sorted.length} events" if($debug)
|
882
1111
|
end
|
data/lib/geoptima/file_time.rb
CHANGED
@@ -8,8 +8,8 @@ module Geoptima
|
|
8
8
|
HUNDRED_YEARS_SECONDS = 100 * 365 * DAY_SECONDS
|
9
9
|
HUNDRED_YEARS_MILLIS = HUNDRED_YEARS_SECONDS * 1000
|
10
10
|
def self.from_file(arg)
|
11
|
-
base
|
12
|
-
self.from(time)
|
11
|
+
base,*times=arg.to_s.split(/_/)
|
12
|
+
times.map{|time| self.from(time)}
|
13
13
|
end
|
14
14
|
def self.from(time)
|
15
15
|
time = time.to_f
|
@@ -28,3 +28,4 @@ if $PROGRAM_NAME =~ /\/file_time.rb$/
|
|
28
28
|
puts "#{(filename.to_s+" "*40)[0..40]} --> #{Geoptima::FileTime.from_file filename}"
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
@@ -20,6 +20,19 @@ module Geoptima
|
|
20
20
|
def <=(other)
|
21
21
|
self.latitude - other.latitude <= 0 && self.longitude - other.longitude <= 0
|
22
22
|
end
|
23
|
+
def -(other)
|
24
|
+
other.respond_to?('latitude') ?
|
25
|
+
Point.new(self.latitude - other.latitude, self.longitude - other.longitude) :
|
26
|
+
Point.new(self.latitude - other.to_f, self.longitude - other.to_f)
|
27
|
+
end
|
28
|
+
def +(other)
|
29
|
+
other.respond_to?('latitude') ?
|
30
|
+
Point.new(self.latitude + other.latitude, self.longitude + other.longitude) :
|
31
|
+
Point.new(self.latitude + other.to_f, self.longitude + other.to_f)
|
32
|
+
end
|
33
|
+
def distance(other)
|
34
|
+
Math.sqrt( (self.latitude-other.latitude)**2 + (self.longitude-other.longitude)**2 )
|
35
|
+
end
|
23
36
|
def to_s
|
24
37
|
[@latitude,@longitude].inspect
|
25
38
|
end
|
@@ -28,14 +41,16 @@ module Geoptima
|
|
28
41
|
class LocationRange
|
29
42
|
attr_reader :min, :max
|
30
43
|
def initialize(spec)
|
31
|
-
f=spec.gsub(/\.\./,':').split(/[\,\;\:]/)
|
44
|
+
f=spec.gsub(/\.\./,':').gsub(/RANGE[\(\[]/i,'').split(/[\,\;\:]/)
|
32
45
|
if spec =~ /\.\./
|
33
|
-
|
34
|
-
@max = Point.new(f[1],f[3])
|
46
|
+
initialize_min_max(Point.new(f[0],f[2]), Point.new(f[1],f[3]))
|
35
47
|
else
|
36
|
-
|
37
|
-
@max = Point.new(f[2],f[3])
|
48
|
+
initialize_min_max(Point.new(f[0],f[1]), Point.new(f[2],f[3]))
|
38
49
|
end
|
50
|
+
end
|
51
|
+
def initialize_min_max(min,max)
|
52
|
+
@min = min
|
53
|
+
@max = max
|
39
54
|
if @min > @max
|
40
55
|
p = @min
|
41
56
|
@min = @max
|
@@ -52,6 +67,8 @@ module Geoptima
|
|
52
67
|
def self.from(spec)
|
53
68
|
if spec == '*' || spec =~ /everywhere/i
|
54
69
|
LocationEverywhere.new
|
70
|
+
elsif spec =~ /dist[\(\[]\s*([\d\.\-\+]+)\s*\,\s*([\d\.\-\+]+)\s*\,\s*([\d\.\-\+]+)\s*[\)\]]/i
|
71
|
+
LocationDistance.new($1.to_f,Point.new($2,$3))
|
55
72
|
else
|
56
73
|
LocationRange.new(spec)
|
57
74
|
end
|
@@ -63,8 +80,11 @@ module Geoptima
|
|
63
80
|
[
|
64
81
|
'56.1..57.0,12.0..15.8',
|
65
82
|
'56.1,12.0,57.0,15.8',
|
83
|
+
'range[56.1..57.0,12.0..15.8]',
|
84
|
+
'range[56.1,12.0,57.0,15.8]',
|
66
85
|
'everywhere',
|
67
|
-
'*'
|
86
|
+
'*',
|
87
|
+
'dist(70,56.5,12.0)'
|
68
88
|
].each do |test|
|
69
89
|
puts "Testing: #{test}"
|
70
90
|
range = Geoptima::LocationRange.from(test)
|
@@ -80,6 +100,25 @@ module Geoptima
|
|
80
100
|
end
|
81
101
|
end
|
82
102
|
end
|
103
|
+
# Note that this class does the distance calculation based on a direct translation
|
104
|
+
# of distance at the equator. This will be inaccurate far from the equator.
|
105
|
+
class LocationDistance <LocationRange
|
106
|
+
attr_reader :distance, :distance_in_km, :center
|
107
|
+
def initialize(distance_in_km,center)
|
108
|
+
@distance_in_km = distance_in_km.to_f
|
109
|
+
@center = center
|
110
|
+
initialize_min_max(@center - distance, @center + distance)
|
111
|
+
end
|
112
|
+
def distance
|
113
|
+
@distance ||= distance_in_km * 360.0 / 40000.0
|
114
|
+
end
|
115
|
+
def include?(point)
|
116
|
+
super(point) && center.distance(point) < distance
|
117
|
+
end
|
118
|
+
def to_s
|
119
|
+
super.to_s+",(#{distance},#{center})"
|
120
|
+
end
|
121
|
+
end
|
83
122
|
class LocationEverywhere <LocationRange
|
84
123
|
def initialize()
|
85
124
|
super("-90,90,-180,180")
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Geoptima
|
2
|
+
|
3
|
+
class Timer
|
4
|
+
attr_reader :name, :start_time, :end_time, :duration, :full_duration, :running
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
reset
|
8
|
+
end
|
9
|
+
def reset
|
10
|
+
@running = false
|
11
|
+
@duration = 0
|
12
|
+
@full_duration = 0
|
13
|
+
@start_time = nil
|
14
|
+
@end_time = nil
|
15
|
+
end
|
16
|
+
def start
|
17
|
+
@duration = 0
|
18
|
+
@running = true
|
19
|
+
@start_time = Time.new
|
20
|
+
end
|
21
|
+
def stop
|
22
|
+
if running
|
23
|
+
@running = false
|
24
|
+
@end_time = Time.new
|
25
|
+
@duration = @end_time - @start_time
|
26
|
+
@full_duration += @duration
|
27
|
+
end
|
28
|
+
@duration
|
29
|
+
end
|
30
|
+
def to_s
|
31
|
+
"#{name}(#{full_duration}s)"
|
32
|
+
end
|
33
|
+
def describe
|
34
|
+
"#{name}\t#{full_duration}s"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|