clumpy 0.0.1 → 0.2.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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Rakefile +10 -13
- data/lib/clumpy.rb +3 -1
- data/lib/clumpy/builder.rb +14 -1
- data/lib/clumpy/cluster.rb +2 -33
- data/lib/clumpy/cluster_behavior.rb +54 -0
- data/lib/clumpy/extensions/sunspot_hit.rb +20 -0
- data/lib/clumpy/version.rb +1 -1
- data/spec/builder_spec.rb +16 -9
- data/spec/cluster_behavior_spec.rb +37 -0
- data/spec/extensions/sunspot_hit_spec.rb +36 -0
- metadata +19 -31
- data/spec/cluster_spec.rb +0 -34
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a098909784fa383eb4f35a22ca1047c8420b7f24
|
4
|
+
data.tar.gz: 20f94e3674bcde0a04a14690ca9e9fdaeef8a002
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 804979e09ec478f0db7c983bfb2734552abaf908bf1949378210d10b87f9a424916cd5c0b44d18a862c7e0a7656584181547876e4f8fb5068b08f6ad4badc0c5
|
7
|
+
data.tar.gz: 3173e9fd9ade10ce884149c502ab69a2e0bf033c9eae019f5cb52b8a832df3d784d3f008475b27e5902122ed2974ce7ae4b02cf9264caa79bf6ef82cd33d659e
|
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -9,25 +9,22 @@ task :console do
|
|
9
9
|
sh 'irb -rubygems -I lib -r clumpy.rb'
|
10
10
|
end
|
11
11
|
|
12
|
-
desc 'Run a simple benchmarking'
|
12
|
+
desc 'Run a simple benchmarking (cluster 100_000 random points)'
|
13
13
|
task :benchmark do
|
14
14
|
require 'benchmark'
|
15
|
-
require 'ostruct'
|
16
15
|
require 'clumpy'
|
17
16
|
|
18
|
-
number =
|
17
|
+
number = 100_000
|
19
18
|
points = []
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
number.times do
|
20
|
+
points << OpenStruct.new(
|
21
|
+
latitude: rand(Clumpy::Builder::MAX_LATITUDE_DISTANCE),
|
22
|
+
longitude: rand(Clumpy::Builder::MAX_LONGITUDE_DISTANCE)
|
23
|
+
)
|
24
|
+
end
|
23
25
|
|
24
26
|
Benchmark.bm do |x|
|
25
|
-
x.report(
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
x.report('high precision') do
|
30
|
-
clusters = Clumpy::Builder.new(points, precision: :high).cluster
|
31
|
-
end
|
27
|
+
x.report("normal precision") { Clumpy::Builder.new(points).cluster }
|
28
|
+
x.report(" high precision") { Clumpy::Builder.new(points, precision: :high).cluster }
|
32
29
|
end
|
33
30
|
end
|
data/lib/clumpy.rb
CHANGED
data/lib/clumpy/builder.rb
CHANGED
@@ -24,15 +24,20 @@ module Clumpy
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def add_to_cluster(point)
|
27
|
+
useable_point?(point) or return
|
27
28
|
parent_cluster = find_parent_cluster(point)
|
28
29
|
|
29
30
|
if parent_cluster
|
30
31
|
parent_cluster.points << point
|
31
32
|
else
|
32
|
-
clusters <<
|
33
|
+
clusters << cluster_class.new(point, cluster_distance, cluster_options)
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
37
|
+
def useable_point?(point)
|
38
|
+
point.respond_to?(:latitude) && point.respond_to?(:longitude)
|
39
|
+
end
|
40
|
+
|
36
41
|
def find_parent_cluster(point)
|
37
42
|
clusters.find { |c| c.contains?(point) }
|
38
43
|
end
|
@@ -41,6 +46,14 @@ module Clumpy
|
|
41
46
|
latitude_distance / DISTANCE_MODIFIER
|
42
47
|
end
|
43
48
|
|
49
|
+
def cluster_options
|
50
|
+
{ values_threshold: options[:values_threshold] }
|
51
|
+
end
|
52
|
+
|
53
|
+
def cluster_class
|
54
|
+
@cluster_class ||= (options[:cluster_class] || Clumpy::Cluster)
|
55
|
+
end
|
56
|
+
|
44
57
|
def latitude_distance
|
45
58
|
if options[:nelat] && options[:swlat]
|
46
59
|
(options[:nelat] - options[:swlat]).abs
|
data/lib/clumpy/cluster.rb
CHANGED
@@ -1,40 +1,9 @@
|
|
1
1
|
module Clumpy
|
2
2
|
class Cluster
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(point, distance)
|
6
|
-
@latitude, @longitude = point.lat, point.lng
|
7
|
-
@bb_latitudes = (latitude - distance)..(latitude + distance)
|
8
|
-
@bb_longitudes = (longitude - distance)..(longitude + distance)
|
9
|
-
@points = [point]
|
10
|
-
end
|
11
|
-
|
12
|
-
def contains?(point)
|
13
|
-
point.respond_to?(:lat) &&
|
14
|
-
point.respond_to?(:lng) &&
|
15
|
-
@bb_latitudes.include?(point.lat) &&
|
16
|
-
@bb_longitudes.include?(point.lng)
|
17
|
-
end
|
18
|
-
|
19
|
-
def reposition
|
20
|
-
@latitude = points.inject(0) { |m, p| m + p.lat } / points.size
|
21
|
-
@longitude = points.inject(0) { |m, p| m + p.lng } / points.size
|
22
|
-
end
|
23
|
-
|
24
|
-
def as_json(*)
|
25
|
-
{
|
26
|
-
swlat: @bb_latitudes.min,
|
27
|
-
swlng: @bb_longitudes.min,
|
28
|
-
nelat: @bb_latitudes.max,
|
29
|
-
nelng: @bb_longitudes.max,
|
30
|
-
lat: latitude,
|
31
|
-
lng: longitude,
|
32
|
-
points: points.length
|
33
|
-
}
|
34
|
-
end
|
3
|
+
include ClusterBehavior
|
35
4
|
|
36
5
|
def to_s
|
37
|
-
"Clumpy::Cluster(latitude: #{latitude}, longitude: #{longitude}, #
|
6
|
+
"Clumpy::Cluster(latitude: #{latitude}, longitude: #{longitude}, # size: #{points.size})"
|
38
7
|
end
|
39
8
|
end
|
40
9
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Clumpy
|
2
|
+
module ClusterBehavior
|
3
|
+
attr_accessor :latitude, :longitude, :points, :bounds
|
4
|
+
|
5
|
+
def initialize(point, distance, options = {})
|
6
|
+
@latitude = point.latitude
|
7
|
+
@longitude = point.longitude
|
8
|
+
@points = [point]
|
9
|
+
@options = options
|
10
|
+
@bounds = OpenStruct.new(
|
11
|
+
latitude: (latitude - distance)..(latitude + distance),
|
12
|
+
longitude: (longitude - distance)..(longitude + distance)
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def contains?(point)
|
17
|
+
bounds.latitude.include?(point.latitude) &&
|
18
|
+
bounds.longitude.include?(point.longitude)
|
19
|
+
end
|
20
|
+
|
21
|
+
def reposition
|
22
|
+
@latitude = points.inject(0.0) { |m, p| m + p.latitude } / points.size
|
23
|
+
@longitude = points.inject(0.0) { |m, p| m + p.longitude } / points.size
|
24
|
+
end
|
25
|
+
|
26
|
+
def as_json(*)
|
27
|
+
{
|
28
|
+
latitude: latitude,
|
29
|
+
longitude: longitude,
|
30
|
+
size: points.size,
|
31
|
+
bounds: bounds_as_json,
|
32
|
+
values: value_list
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def value_list
|
37
|
+
values_threshold = @options[:values_threshold] || 10
|
38
|
+
(points.size < values_threshold) ? points : []
|
39
|
+
end
|
40
|
+
|
41
|
+
def bounds_as_json
|
42
|
+
{
|
43
|
+
northeast: {
|
44
|
+
latitude: bounds.latitude.max,
|
45
|
+
longitude: bounds.longitude.max
|
46
|
+
},
|
47
|
+
southwest: {
|
48
|
+
latitude: bounds.latitude.min,
|
49
|
+
longitude: bounds.longitude.max
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/clumpy/version.rb
CHANGED
data/spec/builder_spec.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Clumpy::Builder do
|
4
|
-
let(:point1) { OpenStruct.new(
|
5
|
-
let(:point2) { OpenStruct.new(
|
6
|
-
let(:point3) { OpenStruct.new(
|
4
|
+
let(:point1) { OpenStruct.new(latitude: 15, longitude: 20) }
|
5
|
+
let(:point2) { OpenStruct.new(latitude: 15.5, longitude: 20.5) }
|
6
|
+
let(:point3) { OpenStruct.new(latitude: 50, longitude: 50) }
|
7
7
|
|
8
8
|
let(:points) { [point1, point2, point3] }
|
9
9
|
let(:builder) { Clumpy::Builder.new(points) }
|
@@ -23,6 +23,13 @@ describe Clumpy::Builder do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
it "passes the values threshold through to the cluster" do
|
27
|
+
builder = Clumpy::Builder.new([point1], values_threshold: 100)
|
28
|
+
builder.cluster_options.should eq({ values_threshold: 100 })
|
29
|
+
clusters = builder.cluster
|
30
|
+
clusters.first.instance_variable_get(:@options).should eq builder.cluster_options
|
31
|
+
end
|
32
|
+
|
26
33
|
context "#add_to_cluster" do
|
27
34
|
it "creates a new cluster if there is no matching" do
|
28
35
|
builder.clusters.should be_empty
|
@@ -30,6 +37,12 @@ describe Clumpy::Builder do
|
|
30
37
|
builder.clusters.size.should eq 1
|
31
38
|
end
|
32
39
|
|
40
|
+
it "will not create a cluster if the given value is not a true point" do
|
41
|
+
builder.clusters.should be_empty
|
42
|
+
builder.add_to_cluster(:foo)
|
43
|
+
builder.clusters.should be_empty
|
44
|
+
end
|
45
|
+
|
33
46
|
it "appends markers to a existing cluster if they match" do
|
34
47
|
builder.clusters << Clumpy::Cluster.new(point1, builder.cluster_distance)
|
35
48
|
builder.clusters.size.should eq 1
|
@@ -40,12 +53,6 @@ describe Clumpy::Builder do
|
|
40
53
|
end
|
41
54
|
end
|
42
55
|
|
43
|
-
context "#find_parent_cluster" do
|
44
|
-
it "returns the surrounding cluster if there is one" do
|
45
|
-
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
56
|
it "calculates the latitude distance" do
|
50
57
|
builder.latitude_distance.should eq Clumpy::Builder::MAX_LATITUDE_DISTANCE
|
51
58
|
builder.options = { nelat: -30, swlat: 40 }
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Clumpy::ClusterBehavior do
|
4
|
+
let(:point) { OpenStruct.new(latitude: 15, longitude: 20) }
|
5
|
+
let(:other_point) { OpenStruct.new(latitude: 40, longitude: 40) }
|
6
|
+
let(:cluster) { Clumpy::Cluster.new(point, 10) }
|
7
|
+
|
8
|
+
it "has a nicish to_s representation" do
|
9
|
+
cluster.to_s.should include '# size: 1'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "has a json represtation as well" do
|
13
|
+
cluster.as_json.should have_key :size
|
14
|
+
cluster.as_json.should have_key :latitude
|
15
|
+
cluster.as_json.should have_key :longitude
|
16
|
+
cluster.as_json.should have_key :bounds
|
17
|
+
end
|
18
|
+
|
19
|
+
context "#reposition" do
|
20
|
+
it "calculates new latitude and longitude values" do
|
21
|
+
cluster.points << other_point
|
22
|
+
cluster.reposition
|
23
|
+
cluster.latitude.should eq (15.0+40.0)/2.0
|
24
|
+
cluster.longitude.should eq (20.0+40.0)/2.0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "#contains" do
|
29
|
+
it "returns true if the given point is within the bounds" do
|
30
|
+
cluster.contains?(point).should be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns false for a points that isn't contained" do
|
34
|
+
cluster.contains?(other_point).should be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Hit
|
4
|
+
def primary_key
|
5
|
+
:foo
|
6
|
+
end
|
7
|
+
|
8
|
+
def class_name
|
9
|
+
'Foo'
|
10
|
+
end
|
11
|
+
|
12
|
+
def stored(key)
|
13
|
+
key
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Clumpy::Extensions::SunspotHit do
|
18
|
+
let(:hit) { Hit.new }
|
19
|
+
let(:extended_hit) { Hit.new.extend(Clumpy::Extensions::SunspotHit) }
|
20
|
+
|
21
|
+
it "adds #latitude method" do
|
22
|
+
hit.should_not respond_to :latitude
|
23
|
+
extended_hit.should respond_to :latitude
|
24
|
+
extended_hit.latitude.should eq :lat
|
25
|
+
end
|
26
|
+
|
27
|
+
it "adds #longitude method" do
|
28
|
+
hit.should_not respond_to :longitude
|
29
|
+
extended_hit.should respond_to :longitude
|
30
|
+
extended_hit.longitude.should eq :lng
|
31
|
+
end
|
32
|
+
|
33
|
+
it "overrides as_json to return minimal representation" do
|
34
|
+
extended_hit.as_json.should eq({ id: :foo, type: 'Foo' })
|
35
|
+
end
|
36
|
+
end
|
metadata
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clumpy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Johannes Opper
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
11
|
+
date: 2013-03-30 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: bundler
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - ~>
|
20
18
|
- !ruby/object:Gem::Version
|
@@ -22,7 +20,6 @@ dependencies:
|
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
24
|
- - ~>
|
28
25
|
- !ruby/object:Gem::Version
|
@@ -30,49 +27,43 @@ dependencies:
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rake
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - '>='
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - '>='
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rspec
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '>='
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '>='
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '0'
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: simplecov
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- -
|
59
|
+
- - '>='
|
68
60
|
- !ruby/object:Gem::Version
|
69
61
|
version: '0'
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- -
|
66
|
+
- - '>='
|
76
67
|
- !ruby/object:Gem::Version
|
77
68
|
version: '0'
|
78
69
|
description: Cluster points e.g. for a map
|
@@ -91,42 +82,39 @@ files:
|
|
91
82
|
- lib/clumpy.rb
|
92
83
|
- lib/clumpy/builder.rb
|
93
84
|
- lib/clumpy/cluster.rb
|
85
|
+
- lib/clumpy/cluster_behavior.rb
|
86
|
+
- lib/clumpy/extensions/sunspot_hit.rb
|
94
87
|
- lib/clumpy/version.rb
|
95
88
|
- spec/builder_spec.rb
|
96
|
-
- spec/
|
89
|
+
- spec/cluster_behavior_spec.rb
|
90
|
+
- spec/extensions/sunspot_hit_spec.rb
|
97
91
|
- spec/spec_helper.rb
|
98
92
|
homepage: http://github.com/xijo/clumpy
|
99
93
|
licenses:
|
100
94
|
- WTFPL
|
95
|
+
metadata: {}
|
101
96
|
post_install_message:
|
102
97
|
rdoc_options: []
|
103
98
|
require_paths:
|
104
99
|
- lib
|
105
100
|
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
101
|
requirements:
|
108
|
-
- -
|
102
|
+
- - '>='
|
109
103
|
- !ruby/object:Gem::Version
|
110
104
|
version: '0'
|
111
|
-
segments:
|
112
|
-
- 0
|
113
|
-
hash: -2869748522569163282
|
114
105
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
-
none: false
|
116
106
|
requirements:
|
117
|
-
- -
|
107
|
+
- - '>='
|
118
108
|
- !ruby/object:Gem::Version
|
119
109
|
version: '0'
|
120
|
-
segments:
|
121
|
-
- 0
|
122
|
-
hash: -2869748522569163282
|
123
110
|
requirements: []
|
124
111
|
rubyforge_project:
|
125
|
-
rubygems_version:
|
112
|
+
rubygems_version: 2.0.0
|
126
113
|
signing_key:
|
127
|
-
specification_version:
|
114
|
+
specification_version: 4
|
128
115
|
summary: Cluster a bunch of points
|
129
116
|
test_files:
|
130
117
|
- spec/builder_spec.rb
|
131
|
-
- spec/
|
118
|
+
- spec/cluster_behavior_spec.rb
|
119
|
+
- spec/extensions/sunspot_hit_spec.rb
|
132
120
|
- spec/spec_helper.rb
|
data/spec/cluster_spec.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Clumpy::Cluster do
|
4
|
-
let(:point) { OpenStruct.new(lat: 15, lng: 20) }
|
5
|
-
let(:other_point) { OpenStruct.new(lat: 40, lng: 40) }
|
6
|
-
let(:cluster) { Clumpy::Cluster.new(point, 10) }
|
7
|
-
|
8
|
-
it "has a nicish to_s representation" do
|
9
|
-
cluster.to_s.should include '# points: 1'
|
10
|
-
end
|
11
|
-
|
12
|
-
it "has a json represtation as well" do
|
13
|
-
cluster.as_json.should have_key :points
|
14
|
-
cluster.as_json.should have_key :lat
|
15
|
-
cluster.as_json.should have_key :lng
|
16
|
-
end
|
17
|
-
|
18
|
-
context "#contains" do
|
19
|
-
it "returns true if the given point is within the bounds" do
|
20
|
-
cluster.contains?(point).should be_true
|
21
|
-
end
|
22
|
-
|
23
|
-
it "returns false for strange values" do
|
24
|
-
cluster.contains?(nil).should be_false
|
25
|
-
cluster.contains?('').should be_false
|
26
|
-
cluster.contains?(42).should be_false
|
27
|
-
cluster.contains?([42, 42]).should be_false
|
28
|
-
end
|
29
|
-
|
30
|
-
it "returns false for a points that isn't contained" do
|
31
|
-
cluster.contains?(other_point).should be_false
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|