Merge pull request #685 from stvnrlly/far-enough

Far enough

Andrew Cantino 9 年之前
父節點
當前提交
8aa111f8ed
共有 4 個文件被更改,包括 58 次插入4 次删除
  1. 3 0
      Gemfile
  2. 2 0
      Gemfile.lock
  3. 23 3
      app/models/agents/user_location_agent.rb
  4. 30 1
      spec/models/agents/user_location_agent_spec.rb

+ 3 - 0
Gemfile

@@ -31,6 +31,9 @@ gem 'omniauth-tumblr'
31 31
 gem 'dropbox-api'
32 32
 gem 'omniauth-dropbox'
33 33
 
34
+# UserLocationAgent
35
+gem 'haversine'
36
+
34 37
 # Optional Services.
35 38
 gem 'omniauth-37signals'          # BasecampAgent
36 39
 # gem 'omniauth-github'

+ 2 - 0
Gemfile.lock

@@ -170,6 +170,7 @@ GEM
170 170
       guard (~> 2.1)
171 171
       rspec (>= 2.14, < 4.0)
172 172
     hashie (2.0.5)
173
+    haversine (0.3.0)
173 174
     hike (1.2.3)
174 175
     hipchat (1.2.0)
175 176
       httparty
@@ -476,6 +477,7 @@ DEPENDENCIES
476 477
   guard
477 478
   guard-livereload
478 479
   guard-rspec
480
+  haversine
479 481
   hipchat (~> 1.2.0)
480 482
   httparty (~> 0.13)
481 483
   hypdf (~> 1.0.7)

+ 23 - 3
app/models/agents/user_location_agent.rb

@@ -4,14 +4,19 @@ module Agents
4 4
   class UserLocationAgent < Agent
5 5
     cannot_be_scheduled!
6 6
 
7
+    gem_dependency_check { defined?(Haversine) }
8
+
7 9
     description do
8 10
       <<-MD
11
+        #{'## Include `haversine` in your Gemfile to use this Agent!' if dependencies_missing?}
9 12
         The UserLocationAgent creates events based on WebHook POSTS that contain a `latitude` and `longitude`.  You can use the [POSTLocation](https://github.com/cantino/post_location) or [PostGPS](https://github.com/chriseidhof/PostGPS) iOS app to post your location.
10 13
 
11 14
 
12 15
         Your POST path will be `https://#{ENV['DOMAIN']}/users/#{user.id}/update_location/:secret` where `:secret` is specified in your options.
13 16
 
14 17
         If you want to only keep more precise locations, set `max_accuracy` to the upper bound, in meters. The default name for this field is `accuracy`, but you can change this by setting a value for `accuracy_field`.
18
+
19
+        If you want to require a certain distance traveled, set `min_distance` to the minimum distance, in meters. Note that GPS readings and the measurement itself aren't exact, so don't rely on this for precision filtering.
15 20
       MD
16 21
     end
17 22
 
@@ -38,7 +43,8 @@ module Agents
38 43
     def default_options
39 44
       {
40 45
         'secret' => SecureRandom.hex(7),
41
-        'max_accuracy' => ''
46
+        'max_accuracy' => '',
47
+        'min_distance' => '',
42 48
       }
43 49
     end
44 50
 
@@ -73,13 +79,27 @@ module Agents
73 79
     def handle_payload(payload)
74 80
       location = Location.new(payload)
75 81
 
76
-      accuracy_field = interpolated[:accuracy_field].presence || 'accuracy'
82
+      accuracy_field = interpolated[:accuracy_field].presence || "accuracy"
83
+
84
+      def accurate_enough?(payload, accuracy_field)
85
+        !interpolated[:max_accuracy].present? || !payload[accuracy_field] || payload[accuracy_field].to_i < interpolated[:max_accuracy].to_i
86
+      end
87
+
88
+      def far_enough?(payload)
89
+        if memory['last_location'].present?
90
+          travel = Haversine.distance(memory['last_location']['latitude'].to_i, memory['last_location']['longitude'].to_i, payload['latitude'].to_i, payload['longitude'].to_i).to_meters
91
+          !interpolated[:min_distance].present? || travel > interpolated[:min_distance].to_i
92
+        else # for the first run, before "last_location" exists
93
+          true
94
+        end
95
+      end
77 96
 
78
-      if location.present? && (!interpolated[:max_accuracy].present? || !payload[accuracy_field] || payload[accuracy_field].to_i < interpolated[:max_accuracy].to_i)
97
+      if location.present? && accurate_enough?(payload, accuracy_field) && far_enough?(payload)
79 98
         if interpolated[:max_accuracy].present? && !payload[accuracy_field].present?
80 99
           log "Accuracy field missing; all locations will be kept"
81 100
         end
82 101
         create_event payload: payload, location: location
102
+        memory["last_location"] = payload
83 103
       end
84 104
     end
85 105
   end

+ 30 - 1
spec/models/agents/user_location_agent_spec.rb

@@ -5,7 +5,8 @@ describe Agents::UserLocationAgent do
5 5
     @agent = Agent.build_for_type('Agents::UserLocationAgent', users(:bob),
6 6
                                   :name => 'something',
7 7
                                   :options => { :secret => 'my_secret',
8
-                                    :max_accuracy => '50' })
8
+                                    :max_accuracy => '50',
9
+                                    :min_distance => '50' })
9 10
     @agent.save!
10 11
   end
11 12
 
@@ -90,4 +91,32 @@ describe Agents::UserLocationAgent do
90 91
     expect(@agent.events.last.lat).to eq(45)
91 92
     expect(@agent.events.last.lng).to eq(123)
92 93
   end
94
+
95
+  it 'does create an event when far enough' do
96
+    @agent.memory["last_location"] = { 'longitude' => 12, 'latitude' => 34, 'something' => 'else' }
97
+    event = Event.new
98
+    event.agent = agents(:bob_weather_agent)
99
+    event.created_at = Time.now
100
+    event.payload = { 'longitude' => 123, 'latitude' => 45, 'something' => 'else' }
101
+
102
+    expect {
103
+      @agent.receive([event])
104
+    }.to change { @agent.events.count }.by(1)
105
+
106
+    expect(@agent.events.last.payload).to eq({ 'longitude' => 123, 'latitude' => 45, 'something' => 'else' })
107
+    expect(@agent.events.last.lat).to eq(45)
108
+    expect(@agent.events.last.lng).to eq(123)
109
+  end
110
+
111
+  it 'does not create an event when too close' do
112
+    @agent.memory["last_location"] = { 'longitude' => 123, 'latitude' => 45, 'something' => 'else' }
113
+    event = Event.new
114
+    event.agent = agents(:bob_weather_agent)
115
+    event.created_at = Time.now
116
+    event.payload = { 'longitude' => 123, 'latitude' => 45, 'something' => 'else' }
117
+
118
+    expect {
119
+      @agent.receive([event])
120
+    }.to change { @agent.events.count }.by(0)
121
+  end
93 122
 end