tsuga 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +12 -0
  6. data/Gemfile +16 -0
  7. data/Gemfile.lock +146 -0
  8. data/Guardfile +8 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +161 -0
  11. data/Rakefile +1 -0
  12. data/lib/tsuga.rb +11 -0
  13. data/lib/tsuga/adapter.rb +4 -0
  14. data/lib/tsuga/adapter/active_record/base.rb +61 -0
  15. data/lib/tsuga/adapter/active_record/cluster.rb +52 -0
  16. data/lib/tsuga/adapter/active_record/migration.rb +50 -0
  17. data/lib/tsuga/adapter/active_record/record.rb +15 -0
  18. data/lib/tsuga/adapter/active_record/test.rb +73 -0
  19. data/lib/tsuga/adapter/memory/base.rb +146 -0
  20. data/lib/tsuga/adapter/memory/cluster.rb +32 -0
  21. data/lib/tsuga/adapter/memory/test.rb +27 -0
  22. data/lib/tsuga/adapter/mongoid/base.rb +41 -0
  23. data/lib/tsuga/adapter/mongoid/cluster.rb +29 -0
  24. data/lib/tsuga/adapter/mongoid/record.rb +16 -0
  25. data/lib/tsuga/adapter/mongoid/test.rb +77 -0
  26. data/lib/tsuga/adapter/sequel/base.rb +57 -0
  27. data/lib/tsuga/adapter/sequel/cluster.rb +43 -0
  28. data/lib/tsuga/adapter/sequel/record.rb +15 -0
  29. data/lib/tsuga/adapter/sequel/test.rb +73 -0
  30. data/lib/tsuga/adapter/shared.rb +4 -0
  31. data/lib/tsuga/adapter/shared/cluster.rb +19 -0
  32. data/lib/tsuga/errors.rb +3 -0
  33. data/lib/tsuga/model/cluster.rb +147 -0
  34. data/lib/tsuga/model/point.rb +206 -0
  35. data/lib/tsuga/model/record.rb +20 -0
  36. data/lib/tsuga/model/tile.rb +136 -0
  37. data/lib/tsuga/service/aggregator.rb +175 -0
  38. data/lib/tsuga/service/clusterer.rb +260 -0
  39. data/lib/tsuga/service/labeler.rb +20 -0
  40. data/lib/tsuga/version.rb +3 -0
  41. data/script/benchmark-aggregator.rb +72 -0
  42. data/script/benchmark-clusterer.rb +102 -0
  43. data/spec/adapter/memory/base_spec.rb +174 -0
  44. data/spec/adapter/memory/cluster_spec.rb +39 -0
  45. data/spec/adapter/shared/cluster_spec.rb +56 -0
  46. data/spec/integration/active_record_spec.rb +10 -0
  47. data/spec/integration/memory_spec.rb +10 -0
  48. data/spec/integration/mongoid_spec.rb +10 -0
  49. data/spec/integration/sequel_spec.rb +10 -0
  50. data/spec/integration/shared.rb +50 -0
  51. data/spec/model/point_spec.rb +102 -0
  52. data/spec/model/tile_spec.rb +116 -0
  53. data/spec/service/aggregator_spec.rb +143 -0
  54. data/spec/service/clusterer_spec.rb +84 -0
  55. data/spec/spec_helper.rb +26 -0
  56. data/spec/support/mongoid.yml +17 -0
  57. data/tsuga.gemspec +29 -0
  58. metadata +226 -0
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+ require 'tsuga/model/point'
3
+ require 'tsuga/model/tile'
4
+ require 'ostruct'
5
+
6
+ describe Tsuga::Model::Tile do
7
+
8
+ describe '.including' do
9
+ let(:depth) { OpenStruct.new :value => 1 }
10
+ let(:point) { Tsuga::Model::Point.new(lat:0, lng:0) }
11
+ let(:result) { described_class.including(point, :depth => depth.value) }
12
+
13
+ it 'creates a tile from a point' do
14
+ result.should be_a_kind_of(Tsuga::Model::Tile)
15
+ end
16
+
17
+ context "on equator/greenwidth" do
18
+ before do
19
+ point.lat = 0
20
+ point.lng = 0
21
+ depth.value = 18
22
+ end
23
+
24
+ it 'calculates southwest' do
25
+ result.southwest.should =~ point
26
+ end
27
+
28
+ it 'calculates northeast' do
29
+ result.northeast.lng.should be_within(1e-6).of(360.0 * (2 ** -18))
30
+ result.northeast.lat.should be_within(1e-6).of(180.0 * (2 ** -18))
31
+ end
32
+
33
+ it 'respects geohash ordering' do
34
+ result.southwest.geohash.should < result.northeast.geohash
35
+ end
36
+ end
37
+ end
38
+
39
+ describe '#contains?' do
40
+ let(:point) { Tsuga::Model::Point.new }
41
+
42
+ subject do
43
+ described_class.including(
44
+ Tsuga::Model::Point.new(lat:0, lng:0), :depth => 2)
45
+ end
46
+
47
+ let(:result) { subject.contains?(point) }
48
+
49
+ it 'includes the northwest corner' do
50
+ point.lat = 0
51
+ point.lng = 0
52
+ result.should be_true
53
+ end
54
+
55
+ it 'excludes the southeast corner' do
56
+ point.lat = 45
57
+ point.lng = 90
58
+ result.should be_false
59
+ end
60
+
61
+ it 'includes point close to the southeast corner' do
62
+ point.lat = 45 - 1e-6
63
+ point.lng = 90 - 1e-6
64
+ result.should be_true
65
+ end
66
+
67
+ it 'includes the center point' do
68
+ point.lat = 22.5
69
+ point.lng = 45
70
+ result.should be_true
71
+ end
72
+
73
+ it 'excludes points north of the border' do
74
+ point.lat = -1
75
+ point.lng = 45
76
+ result.should be_false
77
+ end
78
+
79
+ it 'excludes points south of the border' do
80
+ point.lat = 48
81
+ point.lng = 45
82
+ result.should be_false
83
+ end
84
+
85
+ it 'excludes points west of the border' do
86
+ point.lat = 22.5
87
+ point.lng = -1
88
+ result.should be_false
89
+ end
90
+
91
+ it 'excludes points east of the border' do
92
+ point.lat = 22.5
93
+ point.lng = 91
94
+ result.should be_false
95
+ end
96
+ end
97
+
98
+ describe '#neighbour' do
99
+ subject { described_class.new(prefix:'300') }
100
+ # . . . . . . . . 13
101
+ # . . . . . . . . 02
102
+ # . . . . - . . .
103
+ # . . . - X - . .
104
+ # . . . . - . . .
105
+ # . . . . . . . .
106
+ # . . . . . . . .
107
+ # . . . . . . . .
108
+
109
+
110
+ it('works to the east' ) { subject.neighbour(lat: 0, lng: 1).prefix.should == '302' }
111
+ it('works to the west' ) { subject.neighbour(lat: 0, lng:-1).prefix.should == '122' }
112
+ it('works to the north') { subject.neighbour(lat: 1, lng: 0).prefix.should == '301' }
113
+ it('works to the south') { subject.neighbour(lat:-1, lng: 0).prefix.should == '211' }
114
+ end
115
+
116
+ end
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+ require 'tsuga/service/aggregator'
3
+ require 'tsuga/adapter/memory/cluster'
4
+
5
+ describe Tsuga::Service::Aggregator do
6
+ let(:adapter) { Class.new { include Tsuga::Adapter::Memory::Cluster } }
7
+
8
+ def new_cluster(depth, lat, lng)
9
+ adapter.new.tap do |cluster|
10
+ cluster.depth = depth
11
+ cluster.lat = lat
12
+ cluster.lng = lng
13
+ cluster.weight = 1
14
+ cluster.sum_lat = lat
15
+ cluster.sum_lng = lng
16
+ cluster.ssq_lat = lat * lat
17
+ cluster.ssq_lng = lng * lng
18
+ cluster.children_ids = []
19
+ cluster.persist!
20
+ end
21
+ end
22
+
23
+ subject { described_class.new(clusters:clusters, ratio:0.2) }
24
+
25
+ describe '#min_distance' do
26
+ let(:clusters) { [new_cluster(2,0,0)] }
27
+
28
+ it 'is about 20% of a tile diagonal' do
29
+ subject.min_distance.should == be_within(1e-6).of(Math.sqrt(45*45 + 90*90) / 5)
30
+ end
31
+ end
32
+
33
+ describe '#run' do
34
+ context 'when the list of clusters is empty' do
35
+ let(:clusters) { [] }
36
+ it('passes') { subject.run }
37
+ end
38
+
39
+ context 'with a single cluster' do
40
+ let(:clusters) { [new_cluster(2,0,0)] }
41
+ it('passes') { subject.run }
42
+ end
43
+
44
+ context 'with 4 distant clusters' do
45
+ let(:north) { 0 }
46
+ let(:south) { 45 - 1e-4 }
47
+ let(:west) { 0 }
48
+ let(:east) { 90 - 1e-4 }
49
+
50
+ let(:clusters) {[
51
+ new_cluster(2, north, west),
52
+ new_cluster(2, south, west),
53
+ new_cluster(2, south, east),
54
+ new_cluster(2, north, east)
55
+ ]}
56
+
57
+ it('passes') { subject.run }
58
+
59
+ it('preserves clusers') do
60
+ subject.run
61
+ clusters.length.should == 4
62
+ end
63
+ end
64
+
65
+
66
+ context 'with 3 close clusters' do
67
+ let(:lat) { 22.5 }
68
+ let(:lng) { 45 }
69
+
70
+ let(:clusters) {[
71
+ new_cluster(2, lat + 0, lng),
72
+ new_cluster(2, lat + 1, lng),
73
+ new_cluster(2, lat - 1, lng)
74
+ ]}
75
+
76
+ it('passes') { subject.run }
77
+
78
+ it 'groups clusters' do
79
+ subject.run
80
+ clusters.length.should == 1
81
+ end
82
+
83
+ it 'computes centroid' do
84
+ subject.run
85
+ clusters.first.tap do |cluster|
86
+ cluster.lat.should == lat
87
+ cluster.lng.should == lng
88
+ end
89
+ end
90
+ end
91
+
92
+
93
+ context 'with superimposed points' do
94
+ let(:lat) { 22.5 }
95
+ let(:lng) { 45 }
96
+
97
+ let(:clusters) {[
98
+ new_cluster(2, lat, lng),
99
+ new_cluster(2, lat, lng),
100
+ new_cluster(2, lat, lng)
101
+ ]}
102
+
103
+ it('passes') { subject.run }
104
+
105
+ it 'groups clusters' do
106
+ subject.run
107
+ clusters.length.should == 1
108
+ end
109
+ end
110
+
111
+ context 'with 2 close and 2 distant clusters' do
112
+ let(:lat) { 22.5 }
113
+ let(:lng) { 45 }
114
+
115
+ let(:clusters) {[
116
+ new_cluster(2, 22, 45),
117
+ new_cluster(2, 23, 46),
118
+ new_cluster(2, 0, 0),
119
+ new_cluster(2, 1, 1)
120
+ ]}
121
+
122
+ it('passes') { subject.run }
123
+
124
+ it 'groups clusters' do
125
+ subject.run
126
+ clusters.length.should == 2
127
+ end
128
+ end
129
+
130
+ context 'with 100 random clusters' do
131
+ let(:lat_max) { 45 - 1e-4 }
132
+ let(:lng_max) { 90 - 1e-4 }
133
+
134
+ let(:clusters) {
135
+ (1...100).map { new_cluster(2, rand*lat_max, rand*lng_max) }
136
+ }
137
+
138
+ it('passes') { subject.run }
139
+ end
140
+
141
+ end
142
+
143
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ require 'tsuga/service/clusterer'
3
+ require 'tsuga/adapter/memory/cluster'
4
+ require 'ostruct'
5
+
6
+ describe Tsuga::Service::Clusterer do
7
+ subject { described_class.new(source: records, adapter: adapter) }
8
+ let(:adapter) { Class.new { include Tsuga::Adapter::Memory::Cluster } }
9
+ let(:records) { ArrayWithFindEach.new }
10
+
11
+ def make_record(lat, lng)
12
+ records << OpenStruct.new(lat: lat, lng: lng)
13
+ end
14
+
15
+ before { adapter.delete_all }
16
+
17
+ describe '#run' do
18
+ context 'with no records' do
19
+ it('passes') { subject.run }
20
+ end
21
+
22
+ context 'with a single record' do
23
+ before do
24
+ make_record(0,0)
25
+ end
26
+
27
+ it('passes') { subject.run }
28
+
29
+ it 'creates 1 cluster per depth' do
30
+ subject.run
31
+ adapter.collect_ids.length.should == 17
32
+ end
33
+
34
+ it 'creates clusters with the same position' do
35
+ subject.run
36
+ hashes = Set.new
37
+ adapter.find_each { |c| hashes << c.geohash }
38
+ hashes.size.should == 1
39
+ end
40
+ end
41
+
42
+ context 'with two distant records' do
43
+ before do
44
+ make_record(0,0)
45
+ make_record(45,0)
46
+ end
47
+
48
+ it('passes') { subject.run }
49
+
50
+ it 'creates 2 clusters per depth' do
51
+ subject.run
52
+ adapter.collect_ids.length.should == 34
53
+ end
54
+ end
55
+
56
+
57
+ context 'with random records' do
58
+ before do
59
+ 10.times { make_record(rand, rand) }
60
+ subject.run
61
+ end
62
+
63
+ let :toplevel_cluster do
64
+ id = adapter.at_depth(3).collect_ids.first
65
+ adapter.find_by_id(id)
66
+ end
67
+
68
+ let :barycenter do
69
+ sum_lat = 0
70
+ sum_lng = 0
71
+ records.find_each { |r| sum_lat += r.lat ; sum_lng += r.lng }
72
+ Tsuga::Model::Point.new(lat: 0.1 * sum_lat, lng: 0.1 * sum_lng)
73
+ end
74
+
75
+ it 'toplevel cluster has correct weight' do
76
+ toplevel_cluster.weight.should == 10
77
+ end
78
+
79
+ it 'toplevel cluster is centered' do
80
+ toplevel_cluster.should =~ barycenter
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,26 @@
1
+ require 'bundler/setup'
2
+
3
+ # This file was generated by the `rspec --init` command. Conventionally, all
4
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
+ # Require this file using `require "spec_helper"` to ensure that it is only
6
+ # loaded once.
7
+ #
8
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
9
+ RSpec.configure do |config|
10
+ config.treat_symbols_as_metadata_keys_with_true_values = true
11
+ config.run_all_when_everything_filtered = true
12
+ config.filter_run :focus
13
+
14
+ # Run specs in random order to surface order dependencies. If you find an
15
+ # order dependency and want to debug it, you can fix the order by providing
16
+ # the seed, which is printed after each run.
17
+ # --seed 1234
18
+ config.order = 'random'
19
+ end
20
+
21
+
22
+ class ArrayWithFindEach < Array
23
+ def find_each(&block)
24
+ each(&block)
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ development:
2
+ sessions:
3
+ default:
4
+ database: tsuga_dev
5
+ hosts:
6
+ - localhost:27017
7
+ options:
8
+ safe: true
9
+ test:
10
+ sessions:
11
+ default:
12
+ database: tsuga_test
13
+ hosts:
14
+ - localhost:27017
15
+ options:
16
+ safe: true
17
+
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tsuga/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tsuga"
8
+ spec.version = Tsuga::VERSION
9
+ spec.authors = ["Julien Letessier"]
10
+ spec.email = ["julien.letessier@gmail.com"]
11
+ spec.description = %q{Hierarchical Geo Clusterer tuned for Google Maps usage}
12
+ spec.summary = %q{Hierarchical Geo Clusterer tuned for Google Maps usage}
13
+ spec.homepage = "http://github.com/mezis/tsuga"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_runtime_dependency "ruby-progressbar"
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "guard"
26
+ spec.add_development_dependency "guard-rspec"
27
+ spec.add_development_dependency "pry"
28
+ spec.add_development_dependency "pry-nav"
29
+ end
metadata ADDED
@@ -0,0 +1,226 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tsuga
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Julien Letessier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-progressbar
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry-nav
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Hierarchical Geo Clusterer tuned for Google Maps usage
126
+ email:
127
+ - julien.letessier@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - .gitignore
133
+ - .rspec
134
+ - .ruby-version
135
+ - .travis.yml
136
+ - Gemfile
137
+ - Gemfile.lock
138
+ - Guardfile
139
+ - LICENSE.txt
140
+ - README.md
141
+ - Rakefile
142
+ - lib/tsuga.rb
143
+ - lib/tsuga/adapter.rb
144
+ - lib/tsuga/adapter/active_record/base.rb
145
+ - lib/tsuga/adapter/active_record/cluster.rb
146
+ - lib/tsuga/adapter/active_record/migration.rb
147
+ - lib/tsuga/adapter/active_record/record.rb
148
+ - lib/tsuga/adapter/active_record/test.rb
149
+ - lib/tsuga/adapter/memory/base.rb
150
+ - lib/tsuga/adapter/memory/cluster.rb
151
+ - lib/tsuga/adapter/memory/test.rb
152
+ - lib/tsuga/adapter/mongoid/base.rb
153
+ - lib/tsuga/adapter/mongoid/cluster.rb
154
+ - lib/tsuga/adapter/mongoid/record.rb
155
+ - lib/tsuga/adapter/mongoid/test.rb
156
+ - lib/tsuga/adapter/sequel/base.rb
157
+ - lib/tsuga/adapter/sequel/cluster.rb
158
+ - lib/tsuga/adapter/sequel/record.rb
159
+ - lib/tsuga/adapter/sequel/test.rb
160
+ - lib/tsuga/adapter/shared.rb
161
+ - lib/tsuga/adapter/shared/cluster.rb
162
+ - lib/tsuga/errors.rb
163
+ - lib/tsuga/model/cluster.rb
164
+ - lib/tsuga/model/point.rb
165
+ - lib/tsuga/model/record.rb
166
+ - lib/tsuga/model/tile.rb
167
+ - lib/tsuga/service/aggregator.rb
168
+ - lib/tsuga/service/clusterer.rb
169
+ - lib/tsuga/service/labeler.rb
170
+ - lib/tsuga/version.rb
171
+ - script/benchmark-aggregator.rb
172
+ - script/benchmark-clusterer.rb
173
+ - spec/adapter/memory/base_spec.rb
174
+ - spec/adapter/memory/cluster_spec.rb
175
+ - spec/adapter/shared/cluster_spec.rb
176
+ - spec/integration/active_record_spec.rb
177
+ - spec/integration/memory_spec.rb
178
+ - spec/integration/mongoid_spec.rb
179
+ - spec/integration/sequel_spec.rb
180
+ - spec/integration/shared.rb
181
+ - spec/model/point_spec.rb
182
+ - spec/model/tile_spec.rb
183
+ - spec/service/aggregator_spec.rb
184
+ - spec/service/clusterer_spec.rb
185
+ - spec/spec_helper.rb
186
+ - spec/support/mongoid.yml
187
+ - tsuga.gemspec
188
+ homepage: http://github.com/mezis/tsuga
189
+ licenses:
190
+ - MIT
191
+ metadata: {}
192
+ post_install_message:
193
+ rdoc_options: []
194
+ require_paths:
195
+ - lib
196
+ required_ruby_version: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - '>='
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ required_rubygems_version: !ruby/object:Gem::Requirement
202
+ requirements:
203
+ - - '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ requirements: []
207
+ rubyforge_project:
208
+ rubygems_version: 2.0.3
209
+ signing_key:
210
+ specification_version: 4
211
+ summary: Hierarchical Geo Clusterer tuned for Google Maps usage
212
+ test_files:
213
+ - spec/adapter/memory/base_spec.rb
214
+ - spec/adapter/memory/cluster_spec.rb
215
+ - spec/adapter/shared/cluster_spec.rb
216
+ - spec/integration/active_record_spec.rb
217
+ - spec/integration/memory_spec.rb
218
+ - spec/integration/mongoid_spec.rb
219
+ - spec/integration/sequel_spec.rb
220
+ - spec/integration/shared.rb
221
+ - spec/model/point_spec.rb
222
+ - spec/model/tile_spec.rb
223
+ - spec/service/aggregator_spec.rb
224
+ - spec/service/clusterer_spec.rb
225
+ - spec/spec_helper.rb
226
+ - spec/support/mongoid.yml