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/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, :data_ids, :name, :tracename
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
- @data_ids = []
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
- @tracename ||= "#{name}-#{e.time}"
54
- unless @data_id
55
- @data_id ||= e.file.id
56
- @data_ids << e.file.id
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,index)
178
- "<trkpt lat=\"#{e.latitude}\" lon=\"#{e.longitude}\"><ele>#{index}</ele><time>#{e.time}</time></trkpt>"
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 </trkseg><trkseg>") +
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 "Mapped data ID '#{did}' to color: #{color(data_idm[did])}"
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
- @data_ids = []
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
- @data_ids = (@data_ids+t.data_ids).sort.uniq.compact
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 ||= self['latitude'] && Point.new(self['latitude'],self['longitude'])
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($debug)
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
@@ -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,time=arg.to_s.split(/_/)
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
- @min = Point.new(f[0],f[2])
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
- @min = Point.new(f[0],f[1])
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
+
@@ -1,6 +1,6 @@
1
1
  module Geoptima
2
2
 
3
- VERSION = "0.1.15"
3
+ VERSION = "0.1.17"
4
4
 
5
5
  class Version
6
6
  attr_reader :comparator, :version, :major, :minor, :patch