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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +146 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +161 -0
- data/Rakefile +1 -0
- data/lib/tsuga.rb +11 -0
- data/lib/tsuga/adapter.rb +4 -0
- data/lib/tsuga/adapter/active_record/base.rb +61 -0
- data/lib/tsuga/adapter/active_record/cluster.rb +52 -0
- data/lib/tsuga/adapter/active_record/migration.rb +50 -0
- data/lib/tsuga/adapter/active_record/record.rb +15 -0
- data/lib/tsuga/adapter/active_record/test.rb +73 -0
- data/lib/tsuga/adapter/memory/base.rb +146 -0
- data/lib/tsuga/adapter/memory/cluster.rb +32 -0
- data/lib/tsuga/adapter/memory/test.rb +27 -0
- data/lib/tsuga/adapter/mongoid/base.rb +41 -0
- data/lib/tsuga/adapter/mongoid/cluster.rb +29 -0
- data/lib/tsuga/adapter/mongoid/record.rb +16 -0
- data/lib/tsuga/adapter/mongoid/test.rb +77 -0
- data/lib/tsuga/adapter/sequel/base.rb +57 -0
- data/lib/tsuga/adapter/sequel/cluster.rb +43 -0
- data/lib/tsuga/adapter/sequel/record.rb +15 -0
- data/lib/tsuga/adapter/sequel/test.rb +73 -0
- data/lib/tsuga/adapter/shared.rb +4 -0
- data/lib/tsuga/adapter/shared/cluster.rb +19 -0
- data/lib/tsuga/errors.rb +3 -0
- data/lib/tsuga/model/cluster.rb +147 -0
- data/lib/tsuga/model/point.rb +206 -0
- data/lib/tsuga/model/record.rb +20 -0
- data/lib/tsuga/model/tile.rb +136 -0
- data/lib/tsuga/service/aggregator.rb +175 -0
- data/lib/tsuga/service/clusterer.rb +260 -0
- data/lib/tsuga/service/labeler.rb +20 -0
- data/lib/tsuga/version.rb +3 -0
- data/script/benchmark-aggregator.rb +72 -0
- data/script/benchmark-clusterer.rb +102 -0
- data/spec/adapter/memory/base_spec.rb +174 -0
- data/spec/adapter/memory/cluster_spec.rb +39 -0
- data/spec/adapter/shared/cluster_spec.rb +56 -0
- data/spec/integration/active_record_spec.rb +10 -0
- data/spec/integration/memory_spec.rb +10 -0
- data/spec/integration/mongoid_spec.rb +10 -0
- data/spec/integration/sequel_spec.rb +10 -0
- data/spec/integration/shared.rb +50 -0
- data/spec/model/point_spec.rb +102 -0
- data/spec/model/tile_spec.rb +116 -0
- data/spec/service/aggregator_spec.rb +143 -0
- data/spec/service/clusterer_spec.rb +84 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/mongoid.yml +17 -0
- data/tsuga.gemspec +29 -0
- 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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
data/tsuga.gemspec
ADDED
@@ -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
|