tsuga 0.0.1

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 (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