clumpy 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -3,6 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
+ .rvmrc
6
7
  Gemfile.lock
7
8
  InstalledFiles
8
9
  _yardoc
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 = 20_000
17
+ number = 100_000
19
18
  points = []
20
- max_lat_range = -85..85.05115
21
- max_lng_range = -180..180
22
- number.times { points << OpenStruct.new(lat: rand(max_lat_range), lng: rand(max_lng_range)) }
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('low precision') do
26
- clusters = Clumpy::Builder.new(points).cluster
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
@@ -1,7 +1,9 @@
1
+ require "ostruct"
1
2
  require "clumpy/version"
3
+ require "clumpy/cluster_behavior"
2
4
  require "clumpy/cluster"
3
5
  require "clumpy/builder"
6
+ require "clumpy/extensions/sunspot_hit"
4
7
 
5
8
  module Clumpy
6
- # Your code goes here...
7
9
  end
@@ -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 << Cluster.new(point, cluster_distance)
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
@@ -1,40 +1,9 @@
1
1
  module Clumpy
2
2
  class Cluster
3
- attr_accessor :latitude, :longitude, :points
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}, # points: #{points.length})"
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
@@ -0,0 +1,20 @@
1
+ module Clumpy
2
+ module Extensions
3
+ module SunspotHit
4
+ def latitude
5
+ stored(:lat)
6
+ end
7
+
8
+ def longitude
9
+ stored(:lng)
10
+ end
11
+
12
+ def as_json(*)
13
+ {
14
+ id: primary_key,
15
+ type: class_name
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module Clumpy
2
- VERSION = "0.0.1"
2
+ VERSION = "0.2.0"
3
3
  end
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(lat: 15, lng: 20) }
5
- let(:point2) { OpenStruct.new(lat: 15.5, lng: 20.5) }
6
- let(:point3) { OpenStruct.new(lat: 50, lng: 50) }
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.1
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 00:00:00.000000000 Z
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/cluster_spec.rb
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: 1.8.25
112
+ rubygems_version: 2.0.0
126
113
  signing_key:
127
- specification_version: 3
114
+ specification_version: 4
128
115
  summary: Cluster a bunch of points
129
116
  test_files:
130
117
  - spec/builder_spec.rb
131
- - spec/cluster_spec.rb
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