rp_clustering-rgeo-activerecord 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +117 -23
- data/lib/rp_clustering-rgeo-activerecord.rb +13 -2
- data/lib/rp_clustering-rgeo-activerecord/active_record_base_spatial_expressions.rb +154 -0
- data/lib/rp_clustering-rgeo-activerecord/arel_attribute_spatial_expressions.rb +7 -8
- data/lib/rp_clustering-rgeo-activerecord/arel_table_spatial_expressions.rb +7 -8
- data/lib/rp_clustering-rgeo-activerecord/version.rb +1 -1
- data/test/test_clustering.rb +110 -0
- data/test/test_unit.rb +1 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MzliYTdiMDQyNjMxZmQ3NGM4NGQyNTZmMjgwYmY4NDUyMTk0NGFiYQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YTRmZWE4M2Q5NDhkZDE0NWYwOTEyYzRkYzk5MDU3ZmVmZDVlMDk2Mg==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ODEwOGZjNWVmZDg3ODFkNTU5YjZlODQ0OGM5NjlkZTU2NTNkZWU5Mjg3NGIy
|
10
|
+
YzcxOWY5OGJjMzg4ZjJmODhhZWNkNDM2MjIzMGU0NTk4ZmM1NDE0MDdjNWYz
|
11
|
+
NTAwMjllZTRjMTY1MzRjNjI3NWUyZDQ3ODUxNjFiMGU5MjBhYTk=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NzQ1OTU4MWE4MDM3OWJkNTJkYWI0ZWQ2ZTkxN2Q1NTBhNjU1NWJmYTFiNWRm
|
14
|
+
NWRlMjUyN2I1NjExZjJiOWVhZTY1MjllZmQ0ZmViN2E4MmZhMjViOWQzMzFh
|
15
|
+
ODAzMzZlNjA3OGQ0ZWQ1YzczOTg1OTMwZjFkZjU2ZmM3MTNhYzM=
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# RPClustering::RGeo::ActiveRecord
|
2
2
|
|
3
|
+
[![Code Climate](https://codeclimate.com/github/robertpyke/rp_clustering-rgeo-activerecord.png)](https://codeclimate.com/github/robertpyke/rp_clustering-rgeo-activerecord)
|
4
|
+
|
3
5
|
A RGeo PostGIS extension to provide Active Record (Model) clustering functionality.
|
4
6
|
|
5
7
|
The intention is that this Gem will eventually provide abstracted methods for
|
@@ -8,7 +10,7 @@ both "on the fly" clustering, as well as cached clustering (including associated
|
|
8
10
|
This Gem is currently in early development, so expect changes. On this note, if you'd like a specific clustering
|
9
11
|
algorithm or feature added, please ask.
|
10
12
|
|
11
|
-
If you find a problem with this Gem, please don't hesitate to raise an
|
13
|
+
If you find a problem with this Gem, please don't hesitate to [raise an issue](https://github.com/robertpyke/rp_clustering-rgeo-activerecord/issues).
|
12
14
|
|
13
15
|
## Installation
|
14
16
|
|
@@ -32,6 +34,98 @@ Or install it yourself as:
|
|
32
34
|
|
33
35
|
## Usage
|
34
36
|
|
37
|
+
### Added in Version 0.0.3
|
38
|
+
|
39
|
+
This version allows for the "on the fly" use of a ST_SnapToGrid clustering function.
|
40
|
+
The function is added to ActiveRecord::Base (Models). The function is:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
|
44
|
+
# Cluster using the PostGIS function ST_SnapToGrid
|
45
|
+
# -------------------------------------------------
|
46
|
+
#
|
47
|
+
# attr_to_cluster is the name of attribute to be clustered (a symbol).
|
48
|
+
# The attribute should be geometry attribute.
|
49
|
+
#
|
50
|
+
# Use the options Hash to define what cluster properties you would
|
51
|
+
# like returned.
|
52
|
+
#
|
53
|
+
# Options:
|
54
|
+
#
|
55
|
+
# [:grid_size] if set, will be used to create the cluster. The clustering
|
56
|
+
# works rougly like this; all geometries within 'grid_size'
|
57
|
+
# will be pulled together to form a single cluster. For a detailed
|
58
|
+
# explanation, please see the PostGIS docs for ST_SnapToGrid.
|
59
|
+
#
|
60
|
+
# If no +:grid_size+ is given, clusters will consist of all 'equal'
|
61
|
+
# geometries. E.g. all points at the same
|
62
|
+
# position (x,y) will be pulled together to form a single cluster.
|
63
|
+
# This is actually just a Group By of your +attr_to_cluster+.
|
64
|
+
#
|
65
|
+
# [:cluster_geometry_count] if set to true, the query will select, for
|
66
|
+
# each cluster, the number of geometries in the cluster.
|
67
|
+
#
|
68
|
+
# [:cluster_geometry_count_as] the name to select the
|
69
|
+
# cluster_geometry_count as, defaults to "cluster_geometry_count".
|
70
|
+
#
|
71
|
+
# [:cluster_centroid] if set to true, the query will select, for
|
72
|
+
# each cluster, the cluster centroid. The cluster_centroid returned
|
73
|
+
# will be a WKT string.
|
74
|
+
#
|
75
|
+
# [:cluster_centroid_as] the name to select the
|
76
|
+
# cluster_centroid as, defaults to "cluster_centroid".
|
77
|
+
#
|
78
|
+
# [:cluster_minimum_bounding_circle] if set to true, the query will select,
|
79
|
+
# for each cluster, the minimum bouding circle. The cluster_minimum_bounding_circle
|
80
|
+
# will be a WKT string.
|
81
|
+
#
|
82
|
+
# [:cluster_minimum_bounding_circle_as] the name to select the
|
83
|
+
# cluster_minimum_bounding_circle as, defaults to "cluster_minimum_bounding_circle"
|
84
|
+
#
|
85
|
+
# Note: Using the options hash, you must 'select' at least one attribute,
|
86
|
+
# else this method will rase an ArgumentError.
|
87
|
+
|
88
|
+
cluster_by_st_snap_to_grid(attr_to_cluster, options={})
|
89
|
+
|
90
|
+
```
|
91
|
+
|
92
|
+
e.g.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
|
96
|
+
|
97
|
+
@layer = Layer.find(params[:id])
|
98
|
+
|
99
|
+
cluster_result = @layer.cluster_by_st_snap_to_grid(
|
100
|
+
:geometry, # the column to cluster
|
101
|
+
grid_size: 0.01,
|
102
|
+
cluster_geometry_count: true,
|
103
|
+
cluster_centroid: true
|
104
|
+
)
|
105
|
+
|
106
|
+
features = []
|
107
|
+
cluster_result.each do |cluster|
|
108
|
+
geom_feature = Layer.rgeo_factory_for_column(:geometry).parse_wkt(cluster.cluster_centroid)
|
109
|
+
feature = RGeo::GeoJSON::Feature.new(geom_feature, nil, { cluster_size: cluster.cluster_geometry_count.to_i })
|
110
|
+
|
111
|
+
features << feature
|
112
|
+
end
|
113
|
+
|
114
|
+
feature_collection = RGeo::GeoJSON::FeatureCollection.new(features)
|
115
|
+
RGeo::GeoJSON.encode(feature_collection)
|
116
|
+
|
117
|
+
# BOOM! You just made some GeoJSON which is ready to be displayed on a map.
|
118
|
+
# You've also embedded the cluster_size in the GeoJSON, so you can do some
|
119
|
+
# fancy client side interaction based on cluster size. For example, you could
|
120
|
+
# make outliers (small clusters) bright red, or you could vary the size of the
|
121
|
+
# cluster centroid based on the size of cluster.
|
122
|
+
#
|
123
|
+
# Ideally, you could vary your +grid_size+ based on the user's view port.
|
124
|
+
# For example, you could set it to fixed values based on the user's zoom level.
|
125
|
+
# You could dynamically generate it based on some fraction of the user's view port bbox.
|
126
|
+
|
127
|
+
```
|
128
|
+
|
35
129
|
### Added in Version 0.0.1
|
36
130
|
|
37
131
|
This version allows for hand-coded low-level clustering via the Arel interface.
|
@@ -40,37 +134,37 @@ e.g.
|
|
40
134
|
|
41
135
|
```ruby
|
42
136
|
|
43
|
-
|
137
|
+
# Get the Arel handle for the model
|
44
138
|
|
45
|
-
|
139
|
+
arel_table = MyModel.arel_table
|
46
140
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
141
|
+
# Our cluster grid size.
|
142
|
+
# Smaller grid_size means more clusters.
|
143
|
+
# Larger grid_size means less clusters (cluster covers a larger area).
|
144
|
+
# See http://www.postgis.org/docs/ST_SnapToGrid.html for more info.
|
51
145
|
|
52
|
-
|
146
|
+
grid_size = 0.1
|
53
147
|
|
54
|
-
|
55
|
-
|
148
|
+
# Cluster against our model's :latlon attribute with a grid size of '0.1'.
|
149
|
+
# Return the centroid of each cluster as "cluster_centroid".
|
56
150
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
151
|
+
query = MyModel.select(
|
152
|
+
arel_table.st_astext(
|
153
|
+
arel_table.st_centroid(arel_table.st_collect(arel_table[:latlon]))
|
154
|
+
).as("cluster_centroid")
|
155
|
+
).group(arel_table[:latlon].st_snaptogrid(grid_size))
|
62
156
|
|
63
|
-
|
64
|
-
|
157
|
+
# Iterate over our clusters
|
158
|
+
query.all.each do |cluster|
|
65
159
|
|
66
|
-
|
67
|
-
|
160
|
+
# print the cluster_centroid (a point) as WKT
|
161
|
+
puts cluster["cluster_centroid"]
|
68
162
|
|
69
|
-
|
70
|
-
|
163
|
+
# convert the WKT into a RGeo Geometry (a point)
|
164
|
+
geographic_factory.parse_wkt(cluster["cluster_centroid")
|
71
165
|
|
72
|
-
|
73
|
-
|
166
|
+
# ...
|
167
|
+
end
|
74
168
|
|
75
169
|
```
|
76
170
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "rp_clustering-rgeo-activerecord/version"
|
2
|
+
require "rp_clustering-rgeo-activerecord/active_record_base_spatial_expressions"
|
2
3
|
require "rp_clustering-rgeo-activerecord/arel_attribute_spatial_expressions"
|
3
4
|
require "rp_clustering-rgeo-activerecord/arel_table_spatial_expressions"
|
4
5
|
|
@@ -8,12 +9,22 @@ module RPClustering
|
|
8
9
|
|
9
10
|
module ActiveRecord
|
10
11
|
|
12
|
+
# Spatial Expressions to be attached directly to ActiveRecord::Base
|
13
|
+
|
14
|
+
module BaseSpatialExpressions
|
15
|
+
end
|
16
|
+
|
17
|
+
# Attach our Spatial Expression methods onto the ActiveRecord::Base class (as class methods).
|
18
|
+
class ::ActiveRecord::Base
|
19
|
+
include ::RPClustering::RGeo::ActiveRecord::BaseSpatialExpressions
|
20
|
+
end
|
21
|
+
|
11
22
|
# Spatial Expressions to be attached directly to Arel Attributes (DB columns)
|
12
23
|
|
13
24
|
module ArelAttributeSpatialExpressions
|
14
25
|
end
|
15
26
|
|
16
|
-
# Attach our Spatial Expression methods onto the Arel::Attribute class.
|
27
|
+
# Attach our Spatial Expression methods onto the Arel::Attribute class (as instance methods).
|
17
28
|
#
|
18
29
|
# i.e. As stated in the RGeo::ActiveRecord docs.. Allow chaining of spatial expressions from attributes
|
19
30
|
|
@@ -26,7 +37,7 @@ module RPClustering
|
|
26
37
|
module ArelTableSpatialExpressions
|
27
38
|
end
|
28
39
|
|
29
|
-
# Attach our Spatial Expression methods onto the Arel::Table class.
|
40
|
+
# Attach our Spatial Expression methods onto the Arel::Table class (as instance methods).
|
30
41
|
|
31
42
|
::Arel::Table.class_eval do
|
32
43
|
include ::RPClustering::RGeo::ActiveRecord::ArelTableSpatialExpressions
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# Author: Robert Pyke
|
2
|
+
|
3
|
+
module RPClustering
|
4
|
+
|
5
|
+
module RGeo
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
|
9
|
+
module BaseSpatialExpressions
|
10
|
+
|
11
|
+
# Use ActiveSupport::Concern to seperate our
|
12
|
+
# class and instance methods for inclusion into ActiveRecord::Base
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
# Cluster using the PostGIS function ST_SnapToGrid
|
18
|
+
# -------------------------------------------------
|
19
|
+
#
|
20
|
+
# attr_to_cluster is the name of attribute to be clustered (a symbol).
|
21
|
+
# The attribute should be geometry attribute.
|
22
|
+
#
|
23
|
+
# Use the options Hash to define what cluster properties you would
|
24
|
+
# like returned.
|
25
|
+
#
|
26
|
+
# Options:
|
27
|
+
#
|
28
|
+
# [:grid_size] if set, will be used to create the cluster. The clustering
|
29
|
+
# works rougly like this; all geometries within 'grid_size'
|
30
|
+
# will be pulled together to form a single cluster. For a detailed
|
31
|
+
# explanation, please see the PostGIS docs for ST_SnapToGrid.
|
32
|
+
#
|
33
|
+
# If no +:grid_size+ is given, clusters will consist of all 'equal'
|
34
|
+
# geometries. E.g. all points at the same
|
35
|
+
# position (x,y) will be pulled together to form a single cluster.
|
36
|
+
# This is actually just a Group By of your +attr_to_cluster+.
|
37
|
+
#
|
38
|
+
# [:cluster_geometry_count] if set to true, the query will select, for
|
39
|
+
# each cluster, the number of geometries in the cluster.
|
40
|
+
#
|
41
|
+
# [:cluster_geometry_count_as] the name to select the
|
42
|
+
# cluster_geometry_count as, defaults to "cluster_geometry_count".
|
43
|
+
#
|
44
|
+
# [:cluster_centroid] if set to true, the query will select, for
|
45
|
+
# each cluster, the cluster centroid. The cluster_centroid returned
|
46
|
+
# will be a WKT string.
|
47
|
+
#
|
48
|
+
# [:cluster_centroid_as] the name to select the
|
49
|
+
# cluster_centroid as, defaults to "cluster_centroid".
|
50
|
+
#
|
51
|
+
# [:cluster_minimum_bounding_circle] if set to true, the query will select,
|
52
|
+
# for each cluster, the minimum bouding circle. The cluster_minimum_bounding_circle
|
53
|
+
# will be a WKT string.
|
54
|
+
#
|
55
|
+
# [:cluster_minimum_bounding_circle_as] the name to select the
|
56
|
+
# cluster_minimum_bounding_circle as, defaults to "cluster_minimum_bounding_circle"
|
57
|
+
#
|
58
|
+
# Note: Using the options hash, you must 'select' at least one attribute,
|
59
|
+
# else this method will raise an ArgumentError.
|
60
|
+
#
|
61
|
+
|
62
|
+
def cluster_by_st_snap_to_grid(attr_to_cluster, options={})
|
63
|
+
raise ArgumentError, "Invalid cluster_by_st_snap_to_grid options provided" unless _are_cluster_options_valid?(options)
|
64
|
+
|
65
|
+
grid_size = options[:grid_size]
|
66
|
+
|
67
|
+
arel_table = self.arel_table
|
68
|
+
arel_attr = arel_table[attr_to_cluster]
|
69
|
+
|
70
|
+
q = self
|
71
|
+
|
72
|
+
# Get the cluster geometry count (if asked to)
|
73
|
+
if options[:cluster_geometry_count]
|
74
|
+
cluster_geometry_count_as = options[:cluster_geometry_count_as] || "cluster_geometry_count"
|
75
|
+
q = q._select_cluster_geometry_count(attr_to_cluster, cluster_geometry_count_as)
|
76
|
+
end
|
77
|
+
|
78
|
+
if options[:cluster_centroid]
|
79
|
+
cluster_centroid_as = options[:cluster_centroid_as] || "cluster_centroid"
|
80
|
+
q = q._select_cluster_centroid_as_wkt(attr_to_cluster, cluster_centroid_as)
|
81
|
+
end
|
82
|
+
|
83
|
+
if options[:cluster_minimum_bounding_circle]
|
84
|
+
cluster_minimum_bounding_circle_as = options[:cluster_minimum_bounding_circle_as] || "cluster_minimum_bounding_circle"
|
85
|
+
q = q._select_cluster_minimum_bounding_circle_as_wkt(attr_to_cluster, cluster_minimum_bounding_circle_as)
|
86
|
+
end
|
87
|
+
|
88
|
+
if grid_size
|
89
|
+
q = q.group(arel_attr.st_snaptogrid(grid_size))
|
90
|
+
else
|
91
|
+
q = q.group(arel_attr)
|
92
|
+
end
|
93
|
+
|
94
|
+
q
|
95
|
+
end
|
96
|
+
|
97
|
+
# Ensure the user is selecting something, and that
|
98
|
+
# the grid size is either nil, or is a valid integer
|
99
|
+
|
100
|
+
def _are_cluster_options_valid?(options)
|
101
|
+
selecting_something = (
|
102
|
+
options[:cluster_geometry_count] or
|
103
|
+
options[:cluster_centroid] or
|
104
|
+
options[:cluster_minimum_bounding_circle]
|
105
|
+
)
|
106
|
+
|
107
|
+
grid_size_valid = (
|
108
|
+
options[:grid_size].nil? or
|
109
|
+
( options[:grid_size].is_a? Numeric and options[:grid_size] >= 0 )
|
110
|
+
)
|
111
|
+
|
112
|
+
valid = selecting_something and grid_size_valid
|
113
|
+
end
|
114
|
+
|
115
|
+
# Select the cluster geometry count
|
116
|
+
|
117
|
+
def _select_cluster_geometry_count(attr_to_cluster, as)
|
118
|
+
arel_table = self.arel_table
|
119
|
+
arel_attr = arel_table[attr_to_cluster]
|
120
|
+
|
121
|
+
select(arel_attr.count().as(as))
|
122
|
+
end
|
123
|
+
|
124
|
+
# Select the cluster centroid as WKT.
|
125
|
+
|
126
|
+
def _select_cluster_centroid_as_wkt(attr_to_cluster, as)
|
127
|
+
arel_table = self.arel_table
|
128
|
+
arel_attr = arel_table[attr_to_cluster]
|
129
|
+
|
130
|
+
select(
|
131
|
+
arel_table.st_astext(
|
132
|
+
arel_attr.st_collect
|
133
|
+
).as(as)
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Select the cluster minimum bounding circle as WKT.
|
138
|
+
|
139
|
+
def _select_cluster_minimum_bounding_circle_as_wkt(attr_to_cluster, as)
|
140
|
+
arel_table = self.arel_table
|
141
|
+
arel_attr = arel_table[attr_to_cluster]
|
142
|
+
|
143
|
+
select(
|
144
|
+
arel_table.st_astext(
|
145
|
+
arel_table.st_minimumboundingcircle(arel_attr.st_collect)
|
146
|
+
).as(as)
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -67,17 +67,16 @@ module RPClustering
|
|
67
67
|
|
68
68
|
def st_minimumboundingcircle(num_segs=nil)
|
69
69
|
args = [self]
|
70
|
+
spatial_flags = [true, true]
|
71
|
+
|
70
72
|
if num_segs
|
71
73
|
args << num_segs.to_s
|
72
|
-
|
73
|
-
::RGeo::ActiveRecord::SpatialNamedFunction.new(
|
74
|
-
'ST_MinimumBoundingCircle', args, [true, true, false]
|
75
|
-
)
|
76
|
-
else
|
77
|
-
::RGeo::ActiveRecord::SpatialNamedFunction.new(
|
78
|
-
'ST_MinimumBoundingCircle', args, [true, true]
|
79
|
-
)
|
74
|
+
spatial_flags << false
|
80
75
|
end
|
76
|
+
|
77
|
+
::RGeo::ActiveRecord::SpatialNamedFunction.new(
|
78
|
+
'ST_MinimumBoundingCircle', args, spatial_flags
|
79
|
+
)
|
81
80
|
end
|
82
81
|
|
83
82
|
end
|
@@ -83,17 +83,16 @@ module RPClustering
|
|
83
83
|
|
84
84
|
def st_minimumboundingcircle(g, num_segs=nil)
|
85
85
|
args = [g]
|
86
|
+
spatial_flags = [true, true]
|
87
|
+
|
86
88
|
if num_segs
|
87
89
|
args << num_segs.to_s
|
88
|
-
|
89
|
-
::RGeo::ActiveRecord::SpatialNamedFunction.new(
|
90
|
-
'ST_MinimumBoundingCircle', args, [true, true, false]
|
91
|
-
)
|
92
|
-
else
|
93
|
-
::RGeo::ActiveRecord::SpatialNamedFunction.new(
|
94
|
-
'ST_MinimumBoundingCircle', args, [true, true]
|
95
|
-
)
|
90
|
+
spatial_flags << false
|
96
91
|
end
|
92
|
+
|
93
|
+
::RGeo::ActiveRecord::SpatialNamedFunction.new(
|
94
|
+
'ST_MinimumBoundingCircle', args, spatial_flags
|
95
|
+
)
|
97
96
|
end
|
98
97
|
|
99
98
|
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rgeo/active_record/adapter_test_helper'
|
3
|
+
require 'rp_clustering-rgeo-activerecord'
|
4
|
+
require 'squeel'
|
5
|
+
|
6
|
+
module RPClustering
|
7
|
+
module RGeo
|
8
|
+
module ActiveRecord
|
9
|
+
module Tests
|
10
|
+
class MyUnitTest < Test::Unit::TestCase # :nodoc:
|
11
|
+
|
12
|
+
# Use the RGEO active record adapter test helper
|
13
|
+
|
14
|
+
DATABASE_CONFIG_PATH = ::File.dirname(__FILE__)+'/database.yml'
|
15
|
+
OVERRIDE_DATABASE_CONFIG_PATH = ::File.dirname(__FILE__)+'/database_local.yml'
|
16
|
+
include ::RGeo::ActiveRecord::AdapterTestHelper
|
17
|
+
|
18
|
+
|
19
|
+
define_test_methods do
|
20
|
+
|
21
|
+
def populate_ar_class(content_)
|
22
|
+
klass_ = create_ar_class
|
23
|
+
case content_
|
24
|
+
when :latlon_point
|
25
|
+
klass_.connection.create_table(:spatial_test) do |t_|
|
26
|
+
t_.column 'latlon', :point, :srid => 4326
|
27
|
+
end
|
28
|
+
end
|
29
|
+
klass_
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_cluster_by_st_snap_to_grid_exists
|
33
|
+
arel_klass = populate_ar_class(:latlon_point)
|
34
|
+
assert(
|
35
|
+
arel_klass.methods.include?(:cluster_by_st_snap_to_grid),
|
36
|
+
"ActiveRecord::Base should now have a cluster_by_st_snap_to_grid function. " +
|
37
|
+
"Found:\n#{arel_klass.methods.sort}"
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_cluster_by_st_snap_to_grid_should_exception_with_invalid_options
|
42
|
+
arel_klass = populate_ar_class(:latlon_point)
|
43
|
+
|
44
|
+
assert_raise(ArgumentError) do
|
45
|
+
res = arel_klass.cluster_by_st_snap_to_grid(:latlon, grid_size: 10)
|
46
|
+
end
|
47
|
+
|
48
|
+
assert_raise(ArgumentError) do
|
49
|
+
res = arel_klass.cluster_by_st_snap_to_grid(:latlon, grid_size: -12, cluster_geometry_count: true)
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_raise(ArgumentError) do
|
53
|
+
res = arel_klass.cluster_by_st_snap_to_grid(:latlon, grid_size: "2", cluster_geometry_count: true)
|
54
|
+
end
|
55
|
+
|
56
|
+
assert_nothing_thrown do
|
57
|
+
res = arel_klass.cluster_by_st_snap_to_grid(:latlon, grid_size: 2, cluster_geometry_count: true)
|
58
|
+
end
|
59
|
+
|
60
|
+
assert_nothing_thrown do
|
61
|
+
res = arel_klass.cluster_by_st_snap_to_grid(:latlon, grid_size: 2, cluster_centroid: true)
|
62
|
+
end
|
63
|
+
|
64
|
+
assert_nothing_thrown do
|
65
|
+
res = arel_klass.cluster_by_st_snap_to_grid(:latlon, grid_size: 2, cluster_minimum_bounding_circle: true)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_clustering_with_a_sufficiently_large_grid_size_reduces_count
|
71
|
+
arel_klass = populate_ar_class(:latlon_point)
|
72
|
+
|
73
|
+
points_generated = 0
|
74
|
+
(-5..5).each do |lng|
|
75
|
+
(-5..5).each do |lat|
|
76
|
+
obj = arel_klass.new
|
77
|
+
obj.latlon = @geographic_factory.point(lng, lat)
|
78
|
+
obj.save!
|
79
|
+
points_generated+=1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
res = arel_klass.cluster_by_st_snap_to_grid(:latlon, grid_size: 5, cluster_geometry_count: true)
|
84
|
+
clusters = res.all
|
85
|
+
total_clusters = clusters.count()
|
86
|
+
|
87
|
+
points_found_in_clusters = 0
|
88
|
+
|
89
|
+
res.all.each do |cluster|
|
90
|
+
points_found_in_clusters += cluster["cluster_geometry_count"].to_i
|
91
|
+
end
|
92
|
+
|
93
|
+
assert_equal(points_generated, points_found_in_clusters,
|
94
|
+
"The sum of the size of our clusters should equal the number of points in the table"
|
95
|
+
)
|
96
|
+
|
97
|
+
# We should have less clusters than we have points
|
98
|
+
assert(total_clusters < points_generated, "we should have less clusters than we have points")
|
99
|
+
|
100
|
+
# We should have more than 1 cluster with this size grid
|
101
|
+
assert(total_clusters > 1, "we should have more than 1 cluster with this grid size")
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/test/test_unit.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rp_clustering-rgeo-activerecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Pyke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -178,11 +178,13 @@ files:
|
|
178
178
|
- README.md
|
179
179
|
- Rakefile
|
180
180
|
- lib/rp_clustering-rgeo-activerecord.rb
|
181
|
+
- lib/rp_clustering-rgeo-activerecord/active_record_base_spatial_expressions.rb
|
181
182
|
- lib/rp_clustering-rgeo-activerecord/arel_attribute_spatial_expressions.rb
|
182
183
|
- lib/rp_clustering-rgeo-activerecord/arel_table_spatial_expressions.rb
|
183
184
|
- lib/rp_clustering-rgeo-activerecord/version.rb
|
184
185
|
- rp_clustering-rgeo-activerecord.gemspec
|
185
186
|
- test/database.yml
|
187
|
+
- test/test_clustering.rb
|
186
188
|
- test/test_unit.rb
|
187
189
|
homepage: https://github.com/robertpyke/rp_clustering-rgeo-activerecord
|
188
190
|
licenses:
|
@@ -210,4 +212,6 @@ specification_version: 4
|
|
210
212
|
summary: RGeo PostGIS extension to provide clustering functionality
|
211
213
|
test_files:
|
212
214
|
- test/database.yml
|
215
|
+
- test/test_clustering.rb
|
213
216
|
- test/test_unit.rb
|
217
|
+
has_rdoc:
|