mongoid_geo 0.5.4.1 → 0.6.0

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.
Files changed (57) hide show
  1. data/.rspec +1 -0
  2. data/Changelog.textile +18 -0
  3. data/Gemfile +17 -0
  4. data/README.textile +45 -4
  5. data/Rakefile +41 -11
  6. data/VERSION +1 -0
  7. data/lib/mongoid/geo/index.rb +14 -2
  8. data/sandbox/haversine.rb +17 -0
  9. data/sandbox/location.rb +38 -0
  10. data/sandbox/test.rb +12 -0
  11. data/spec/dummy/Rakefile +7 -0
  12. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  13. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  14. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  15. data/spec/dummy/config.ru +4 -0
  16. data/spec/dummy/config/application.rb +44 -0
  17. data/spec/dummy/config/boot.rb +10 -0
  18. data/spec/dummy/config/environment.rb +5 -0
  19. data/spec/dummy/config/environments/development.rb +26 -0
  20. data/spec/dummy/config/environments/production.rb +49 -0
  21. data/spec/dummy/config/environments/test.rb +35 -0
  22. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  23. data/spec/dummy/config/initializers/inflections.rb +10 -0
  24. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  25. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  26. data/spec/dummy/config/initializers/session_store.rb +8 -0
  27. data/spec/dummy/config/locales/en.yml +5 -0
  28. data/spec/dummy/config/mongoid.yml +23 -0
  29. data/spec/dummy/config/routes.rb +58 -0
  30. data/spec/dummy/public/404.html +26 -0
  31. data/spec/dummy/public/422.html +26 -0
  32. data/spec/dummy/public/500.html +26 -0
  33. data/spec/dummy/public/favicon.ico +0 -0
  34. data/spec/dummy/public/javascripts/application.js +2 -0
  35. data/spec/dummy/public/javascripts/controls.js +965 -0
  36. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  37. data/spec/dummy/public/javascripts/effects.js +1123 -0
  38. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  39. data/spec/dummy/public/javascripts/rails.js +175 -0
  40. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  41. data/spec/dummy/script/rails +6 -0
  42. data/spec/integration/navigation_spec.rb +9 -0
  43. data/spec/models/address.rb +23 -0
  44. data/spec/models/person.rb +9 -0
  45. data/spec/mongoid/db_v1_8/geo_near_places_spec.rb +30 -0
  46. data/spec/mongoid/db_v1_8/geo_near_spec.rb +55 -0
  47. data/spec/mongoid/db_v1_8/geonear_benchmark_spec.rb +48 -0
  48. data/spec/mongoid/db_v1_8/spherical_calc_spec.rb +149 -0
  49. data/spec/mongoid/geo/geo_fields_spec.rb +140 -0
  50. data/spec/mongoid/geo/geo_inclusions_spec.rb +20 -0
  51. data/spec/mongoid/geo/geo_inflections_spec.rb +237 -0
  52. data/spec/mongoid/geo/geo_near_spec.rb +58 -0
  53. data/spec/mongoid/geo/geo_near_to_model_spec.rb +74 -0
  54. data/spec/mongoid/geo/geo_spherical_mode_spec.rb +137 -0
  55. data/spec/mongoid/spec_helper.rb +23 -0
  56. data/spec/spec_helper.rb +31 -0
  57. metadata +174 -29
@@ -0,0 +1,175 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+ })();
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Navigation" do
4
+ include Capybara
5
+
6
+ it "should be a valid app" do
7
+ ::Rails.application.should be_a(Dummy::Application)
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ puts "Address"
2
+
3
+ class Address
4
+ include Mongoid::Document
5
+ extend Mongoid::Geo::Near
6
+
7
+ attr_accessor :mode
8
+
9
+ field :address_type
10
+ field :number, :type => Integer
11
+ field :street
12
+ field :city
13
+ field :state
14
+ field :post_code
15
+ field :location, :type => Array, :geo => true
16
+
17
+ field :pos, :type => Array, :geo => true, :lat => :latitude, :lng => :longitude
18
+
19
+ # key :street
20
+
21
+ puts "call geo_index :location"
22
+ geo_index :location
23
+ end
@@ -0,0 +1,9 @@
1
+ class Person
2
+ include Mongoid::Document
3
+
4
+ field :name
5
+ embeds_many :addresses
6
+
7
+ index :addresses
8
+ index :name
9
+ end
@@ -0,0 +1,30 @@
1
+ require "mongoid/spec_helper"
2
+
3
+ Mongoid::Geo.mongo_db_version = 1.8
4
+
5
+ # Address.collection.create_index([['location', Mongo::GEO2D]], :min => -180, :max => 180)
6
+
7
+ [Address].create_geo_indexes!
8
+
9
+ class Place
10
+ include Mongoid::Document
11
+ extend Mongoid::Geo::Near
12
+ field :loc, type: Array, :geo => true
13
+ geo_index :loc
14
+ end
15
+
16
+ Place.collection.create_index([['loc', Mongo::GEO2D]], :min => -180, :max => 180)
17
+
18
+ describe Mongoid::Geo::Near do
19
+ describe 'place' do
20
+ before(:each) do
21
+ Place.create(:loc => [45, 11])
22
+ Place.create(:loc => [46, 12])
23
+ end
24
+
25
+ it 'should return places near' do
26
+ @places = Place.geoNear([44, 11.5], :loc)
27
+ pp @places.to_a
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ require "mongoid/spec_helper"
2
+
3
+ Mongoid::Geo.mongo_db_version = 1.8
4
+ Address.collection.create_index([['location', Mongo::GEO2D]], :min => -180, :max => 180)
5
+
6
+ describe Mongoid::Geo::Near do
7
+ let(:address) do
8
+ Address.new
9
+ end
10
+
11
+ before(:each) do
12
+ Address.create(:location => [45, 11], :city => 'Munich')
13
+ Address.create(:location => [46, 12], :city => 'Berlin')
14
+ end
15
+ describe "geoNear" do
16
+ it "should work with specifying specific center and different location attribute on collction" do
17
+ address.location = "23.5, -47"
18
+ Address.geoNear(address.location, :location).first.location[0].should == 45
19
+ end
20
+
21
+ it "should assume same attribute searched on both center instance and collection" do
22
+ address.location = "23.5, -47"
23
+ Address.geoNear(address, :location).first.location[0].should == 45
24
+ end
25
+
26
+ describe 'option :num' do
27
+ it "should limit number of results to 1" do
28
+ address.location = "23.5, -47"
29
+ Address.geoNear(address, :location, :num => 1).size.should == 1
30
+ end
31
+ end
32
+
33
+ describe 'option :maxDistance' do
34
+ it "should limit on maximum distance" do
35
+ address.location = "45.1, 11.1"
36
+ Address.geoNear(address, :location, :maxDistance => 0.2).size.should == 1
37
+ end
38
+ end
39
+
40
+ describe 'option :distanceMultiplier' do
41
+ it "should multiply returned distance with multiplier" do
42
+ address.location = "45.1, 11.1"
43
+ distances = Address.geoNear(address, :location, :distanceMultiplier => 4).map(&:distance)
44
+ distances.first.should > 0
45
+ end
46
+ end
47
+
48
+ describe 'option :query' do
49
+ it "should filter using extra query option" do
50
+ address.location = "45.1, 11.1"
51
+ Address.geoNear(address, :location, :query => {:city => 'Munich'}).size.should == 1
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,48 @@
1
+ require "mongoid/spec_helper"
2
+ require 'benchmark'
3
+
4
+ Address.collection.create_index([['location', Mongo::GEO2D]], :min => -180, :max => 180)
5
+
6
+ def delta
7
+ (rand(10000) / 10000.0) - 0.5
8
+ end
9
+
10
+ describe 'Mongoid spherical geoNear distance calculations' do
11
+ context "Spherical mode distance" do
12
+ before do
13
+ Mongoid::Geo.spherical = true
14
+
15
+ @center = Address.create(:location => {:lat => 31.5, :lng => -121.5}, :city => 'center')
16
+ 5000.times do
17
+ Address.create(:location => {:lat => 31 + delta, :lng => -121 + delta})
18
+ end
19
+ end
20
+
21
+ context 'Mongo DB < 1.7' do
22
+ before do
23
+ Mongoid::Geo.mongo_db_version = 1.6
24
+ end
25
+
26
+ it "calculates distance using ruby Haversine code" do
27
+ Mongoid::Geo.mongo_db_version.should == 1.6
28
+ Mongoid::Geo.spherical.should be_true
29
+
30
+ puts Benchmark.measure { Address.geoNear @center.location, :location, :unit => :km }
31
+ end
32
+ end
33
+
34
+ context 'Mongo DB 1.8' do
35
+ before do
36
+ Mongoid::Geo.mongo_db_version = 1.8
37
+ Mongoid::Geo.spherical = true
38
+ end
39
+
40
+ it "calculate distance using Mongo 1.8 native distance calculation" do
41
+ Mongoid::Geo.mongo_db_version.should == 1.8
42
+ Mongoid::Geo.spherical.should be_true
43
+
44
+ puts Benchmark.measure { Address.geoNear @center.location, :location, :unit => :km, :mode => :sphere }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,149 @@
1
+ require "mongoid/spec_helper"
2
+
3
+ Address.collection.create_index([['location', Mongo::GEO2D]], :min => -180, :max => 180)
4
+
5
+ describe 'Mongoid Spherical geonear distance calculations' do
6
+ context "Normal mode distance" do
7
+ before do
8
+ Mongoid::Geo.spherical = false
9
+ end
10
+
11
+ after do
12
+ Mongoid.database.collections.each do |coll|
13
+ coll.remove
14
+ end
15
+ end
16
+
17
+ context 'Mongo DB < 1.7' do
18
+ before do
19
+ Mongoid::Geo.mongo_db_version = 1.6
20
+ end
21
+
22
+ before :each do
23
+ @center = Address.new(:location => {:lat => 31.2010839, :lng => -121.583509}, :city => 'center')
24
+ @center.save!
25
+ @icc = Address.new(:location => {:lat => 31.2026708, :lng => -121.6024088}, :city => 'icc')
26
+ @icc.save!
27
+ end
28
+
29
+ it "calculate distance" do
30
+ # d1 = Geokit::LatLng.distance_between(@center.location,@icc.location,{:units=> :kms}) # 1.8078417965905265
31
+ # d2 = Geokit::LatLng.distance_between(@center.location,@icc.location,{:units=> :kms,:formula => :flat}) # 1.5037404243943175
32
+
33
+ Mongoid::Geo.mongo_db_version.should == 1.6
34
+ Mongoid::Geo.spherical.should be_false
35
+
36
+ # results = Address.geoNear @center.location, :location, :distanceMultiplier => 111.17
37
+ results = Address.geoNear @center.location, :location, :unit => :km
38
+
39
+ distances = results.asc(:distance).map(&:distance)
40
+ from = results.first.fromPoint
41
+
42
+ puts "distances: #{distances}, calculated from: #{from}"
43
+ # # 1.8062052078680642
44
+ hd = Mongoid::Geo::Haversine.distance(@center.lat, @center.lng, @icc.lat, @icc.lng) * 6371
45
+ distances.last.should be_within(0.005).of(hd)
46
+ end
47
+ end
48
+
49
+ context 'Mongo DB 1.8' do
50
+ before do
51
+ Mongoid::Geo.mongo_db_version = 1.8
52
+ end
53
+
54
+ after do
55
+ Mongoid::Geo.mongo_db_version = 1.6
56
+ end
57
+
58
+ before :each do
59
+ @center = Address.create(:location => {:lat => 31.2010839, :lng => -121.583509}, :city => 'center')
60
+ @icc = Address.create(:location => {:lat => 31.2026708, :lng => -121.6024088}, :city => 'icc')
61
+ end
62
+
63
+ it "calculate distance and sort them in descending distance order" do
64
+ # d1 = Geokit::LatLng.distance_between(@center.location,@icc.location,{:units=> :kms}) # 1.8078417965905265
65
+ # d2 = Geokit::LatLng.distance_between(@center.location,@icc.location,{:units=> :kms,:formula => :flat}) # 1.5037404243943175
66
+
67
+ Mongoid::Geo.mongo_db_version.should == 1.8
68
+ Mongoid::Geo.spherical.should be_false
69
+
70
+ # results = Address.geoNear @center.location, :location, :distanceMultiplier => 111.17
71
+ results = Address.geoNear @center.location, :location, :unit => :km, :dist_order => :desc
72
+
73
+ distances = results.map(&:distance)
74
+ puts "distances: #{distances}"
75
+ # # 1.8062052078680642
76
+ hd = Mongoid::Geo::Haversine.distance(@center.lat, @center.lng, @icc.lat, @icc.lng) * 6371
77
+ # distances.first.should be_within(0.5).of(hd)
78
+ end
79
+ end
80
+ end
81
+
82
+ context "Spherical mode distance" do
83
+ before do
84
+ Mongoid::Geo.spherical = true
85
+ end
86
+
87
+ after do
88
+ Mongoid::Geo.spherical = false
89
+ end
90
+
91
+ context 'Mongo DB < 1.7' do
92
+ before do
93
+ Mongoid::Geo.mongo_db_version = 1.6
94
+ end
95
+
96
+ before :each do
97
+ @center = Address.create(:location => {:lat => 31.2010839, :lng => -121.583509}, :city => 'center')
98
+ @icc = Address.create(:location => {:lat => 31.2026708, :lng => -121.6024088}, :city => 'icc')
99
+ end
100
+
101
+ it "calculate distance" do
102
+ # d1 = Geokit::LatLng.distance_between(@center.location,@icc.location,{:units=> :kms}) # 1.8078417965905265
103
+ # d2 = Geokit::LatLng.distance_between(@center.location,@icc.location,{:units=> :kms,:formula => :flat}) # 1.5037404243943175
104
+
105
+ Mongoid::Geo.mongo_db_version.should == 1.6
106
+ Mongoid::Geo.spherical.should be_true
107
+
108
+ results = Address.geoNear @center.location, :location, :unit => :km
109
+
110
+ distances = results.desc(:distance).map(&:distance)
111
+ puts "distances: #{distances}"
112
+ # # 1.8062052078680642
113
+ hd = Mongoid::Geo::Haversine.distance(@center.lat, @center.lng, @icc.lat, @icc.lng) * 6371
114
+ distances.first.should be_within(0.5).of(hd)
115
+ end
116
+ end
117
+
118
+ context 'Mongo DB 1.8' do
119
+ before do
120
+ Mongoid::Geo.mongo_db_version = 1.8
121
+ Mongoid::Geo.spherical = true
122
+ end
123
+
124
+ after do
125
+ Mongoid::Geo.mongo_db_version = 1.6
126
+ Mongoid::Geo.spherical = false
127
+ end
128
+
129
+ before :each do
130
+ @center = Address.create(:location => {:lat => 31.2010839, :lng => -121.583509}, :city => 'center')
131
+ @icc = Address.create(:location => {:lat => 31.2026708, :lng => -121.6024088}, :city => 'icc')
132
+ end
133
+
134
+ it "calculates distance" do
135
+ Mongoid::Geo.mongo_db_version.should == 1.8
136
+ Mongoid::Geo.spherical.should be_true
137
+
138
+ results = Address.geoNear @center.location, :location, :unit => :km, :mode => :sphere
139
+ distances = results.map(&:distance)
140
+ puts "distances: #{distances}"
141
+ hd = Mongoid::Geo::Haversine.distance(@center.lat, @center.lng, @icc.lat, @icc.lng) * 6371
142
+
143
+ # c is 1.8061253521165859
144
+ # hd is 1.8062052078680642
145
+ distances.last.should be_within(0.5).of(hd)
146
+ end
147
+ end
148
+ end
149
+ end