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