@@ -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' |
@@ -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) |
@@ -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 |
@@ -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 |