activerecord-spatial 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +16 -0
  3. data/Gemfile +21 -0
  4. data/Guardfile +17 -0
  5. data/MIT-LICENSE +23 -0
  6. data/README.rdoc +169 -0
  7. data/Rakefile +28 -0
  8. data/activerecord-spatial.gemspec +26 -0
  9. data/lib/activerecord-spatial.rb +32 -0
  10. data/lib/activerecord-spatial/active_record.rb +14 -0
  11. data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/adapter_extensions.rb +36 -0
  12. data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/postgis.rb +24 -0
  13. data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/unknown_srid.rb +21 -0
  14. data/lib/activerecord-spatial/active_record/models/geography_column.rb +17 -0
  15. data/lib/activerecord-spatial/active_record/models/geometry_column.rb +17 -0
  16. data/lib/activerecord-spatial/active_record/models/spatial_column.rb +22 -0
  17. data/lib/activerecord-spatial/active_record/models/spatial_ref_sys.rb +20 -0
  18. data/lib/activerecord-spatial/associations.rb +292 -0
  19. data/lib/activerecord-spatial/spatial_columns.rb +345 -0
  20. data/lib/activerecord-spatial/spatial_function.rb +201 -0
  21. data/lib/activerecord-spatial/spatial_scope_constants.rb +114 -0
  22. data/lib/activerecord-spatial/spatial_scopes.rb +297 -0
  23. data/lib/activerecord-spatial/version.rb +5 -0
  24. data/lib/tasks/test.rake +45 -0
  25. data/test/accessors_geographies_tests.rb +149 -0
  26. data/test/accessors_geometries_tests.rb +151 -0
  27. data/test/adapter_tests.rb +44 -0
  28. data/test/associations_tests.rb +656 -0
  29. data/test/database.yml +17 -0
  30. data/test/fixtures/bars.yml +16 -0
  31. data/test/fixtures/blorts.yml +37 -0
  32. data/test/fixtures/foo3ds.yml +17 -0
  33. data/test/fixtures/foo_geographies.yml +16 -0
  34. data/test/fixtures/foos.yml +16 -0
  35. data/test/fixtures/zortables.yml +36 -0
  36. data/test/geography_column_tests.rb +40 -0
  37. data/test/geometry_column_tests.rb +40 -0
  38. data/test/models/bar.rb +17 -0
  39. data/test/models/blort.rb +12 -0
  40. data/test/models/foo.rb +17 -0
  41. data/test/models/foo3d.rb +17 -0
  42. data/test/models/foo_geography.rb +16 -0
  43. data/test/models/zortable.rb +17 -0
  44. data/test/spatial_scopes_geographies_tests.rb +106 -0
  45. data/test/spatial_scopes_tests.rb +444 -0
  46. data/test/test_helper.rb +272 -0
  47. metadata +138 -0
@@ -0,0 +1,201 @@
1
+
2
+ module ActiveRecordSpatial
3
+ class SpatialFunction
4
+ DEFAULT_OPTIONS = {
5
+ :column => ActiveRecordSpatial.default_column_name,
6
+ :use_index => true
7
+ }.freeze
8
+
9
+ def initialize(klass)
10
+ @klass = klass
11
+ end
12
+
13
+ def self.build!(klass, *args)
14
+ new(klass).build_function_call(*args)
15
+ end
16
+
17
+ def build_function_call(function, *args)
18
+ options = default_options(args.extract_options!)
19
+ geom = options[:geom_arg]
20
+ args = Array.wrap(options[:args])
21
+
22
+ column_name = self.column_name(options[:column])
23
+ first_geom_arg = self.wrap_column_or_geometry(
24
+ @klass.arel_table[column_name],
25
+ options[:column]
26
+ )
27
+ geom_args = [ first_geom_arg ]
28
+
29
+ if geom.present?
30
+ column_type = @klass.spatial_column_by_name(column_name).spatial_type
31
+ column_srid = @klass.srid_for(column_name)
32
+
33
+ unless geom.is_a?(Hash)
34
+ geom_arg = read_geos(geom, column_srid)
35
+ geom_srid = read_geom_srid(geom_arg, column_type)
36
+ geom_args << self.set_srid_or_transform(column_srid, geom_srid, geom_arg, column_type)
37
+ else
38
+ klass = if geom[:class]
39
+ geom[:class]
40
+ elsif geom[:class_name]
41
+ geom[:class_name].classify.constantize
42
+ else
43
+ raise ArgumentError.new("Need either a :class or :class_name option to determine the class.")
44
+ end
45
+
46
+ if geom[:value]
47
+ geom_arg = read_geos(geom[:value], column_srid)
48
+ geom_srid = read_geom_srid(geom_arg, column_type)
49
+ else
50
+ geom_arg = geom
51
+ geom_srid = klass.srid_for(self.column_name(geom[:column]))
52
+ end
53
+
54
+ transformed_geom = self.set_srid_or_transform(column_srid, geom_srid, geom_arg, column_type)
55
+ geom_args << self.wrap_column_or_geometry(transformed_geom, geom)
56
+ end
57
+ end
58
+
59
+ if options[:invert] && geom_args.length > 1
60
+ geom_args.reverse!
61
+ end
62
+
63
+ ret = Arel::Nodes::NamedFunction.new(
64
+ function_name(function, options[:use_index]),
65
+ geom_args + args
66
+ )
67
+
68
+ if options[:allow_null]
69
+ ret = ret.or(first_geom_arg.eq(nil))
70
+ end
71
+
72
+ ret
73
+ end
74
+
75
+ protected
76
+ def set_srid_or_transform(column_srid, geom_srid, geom, type)
77
+ geom_param = case geom
78
+ when Geos::Geometry
79
+ Arel.sql("#{@klass.connection.quote(geom.to_ewkb)}::#{type}")
80
+ when Hash
81
+ table_name = if geom[:table_alias]
82
+ @klass.connection.quote_table_name(geom[:table_alias])
83
+ elsif geom[:class]
84
+ geom[:class].quoted_table_name
85
+ elsif geom[:class_name]
86
+ geom[:class_name].classify.constantize.quoted_table_name
87
+ end
88
+
89
+ Arel.sql("#{table_name}.#{@klass.connection.quote_table_name(self.column_name(geom[:column]))}")
90
+ else
91
+ raise ArgumentError.new("Expected either a Geos::Geometry or a Hash.")
92
+ end
93
+
94
+ sql = if type != :geography && column_srid != geom_srid
95
+ if column_srid == ActiveRecordSpatial::UNKNOWN_SRIDS[type] || geom_srid == ActiveRecordSpatial::UNKNOWN_SRIDS[type]
96
+ Arel::Nodes::NamedFunction.new(
97
+ function_name('SetSRID'),
98
+ [ geom_param, column_srid ]
99
+ )
100
+
101
+ else
102
+ Arel::Nodes::NamedFunction.new(
103
+ function_name('Transform'),
104
+ [ geom_param, column_srid ]
105
+ )
106
+ end
107
+ else
108
+ geom_param
109
+ end
110
+ end
111
+
112
+ def read_geos(geom, column_srid)
113
+ if geom.is_a?(String) && geom =~ /^SRID=default;/
114
+ geom = geom.sub(/default/, column_srid.to_s)
115
+ end
116
+ Geos.read(geom)
117
+ end
118
+
119
+ def read_geom_srid(geos, column_type = :geometry)
120
+ if geos.srid == 0 || geos.srid == -1
121
+ ActiveRecordSpatial::UNKNOWN_SRIDS[column_type]
122
+ else
123
+ geos.srid
124
+ end
125
+ end
126
+
127
+ def default_options(*args)
128
+ options = args.extract_options!
129
+
130
+ if args.length > 0
131
+ desc = if args.first == :desc
132
+ true
133
+ else
134
+ options[:desc]
135
+ end
136
+
137
+ DEFAULT_OPTIONS.merge({
138
+ :desc => desc
139
+ }).merge(options || {})
140
+ else
141
+ DEFAULT_OPTIONS.merge(options || {})
142
+ end
143
+ end
144
+
145
+ def function_name(function, use_index = true)
146
+ if use_index
147
+ "ST_#{function}"
148
+ else
149
+ "_ST_#{function}"
150
+ end
151
+ end
152
+
153
+ def column_name(column_name_or_options)
154
+ column_name = if column_name_or_options.is_a?(Hash)
155
+ column_name_or_options[:name]
156
+ else
157
+ column_name_or_options
158
+ end || ActiveRecordSpatial.default_column_name
159
+ end
160
+
161
+ def wrap_column_or_geometry(column_name_or_geometry, options = nil)
162
+ if options.is_a?(Hash) && options[:wrapper]
163
+ wrapper, args = if options[:wrapper].is_a?(Hash)
164
+ [ options[:wrapper].keys.first, Array.wrap(options[:wrapper].values.first) ]
165
+ else
166
+ [ options[:wrapper], [] ]
167
+ end
168
+
169
+ Arel::Nodes::NamedFunction.new(
170
+ function_name(wrapper),
171
+ [ column_name_or_geometry, *args ]
172
+ )
173
+ else
174
+ column_name_or_geometry
175
+ end
176
+ end
177
+
178
+ class << self
179
+ def additional_ordering(*args)
180
+ options = args.extract_options!
181
+
182
+ desc = if args.first == :desc
183
+ true
184
+ else
185
+ options[:desc]
186
+ end
187
+
188
+ ''.tap do |ret|
189
+ if desc
190
+ ret << ' DESC'
191
+ end
192
+
193
+ if options[:nulls]
194
+ ret << " NULLS #{options[:nulls].to_s.upcase}"
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+
@@ -0,0 +1,114 @@
1
+
2
+ module ActiveRecordSpatial
3
+ module SpatialScopeConstants
4
+ RELATIONSHIPS = %w{
5
+ contains
6
+ containsproperly
7
+ covers
8
+ coveredby
9
+ crosses
10
+ disjoint
11
+ equals
12
+ intersects
13
+ orderingequals
14
+ overlaps
15
+ touches
16
+ within
17
+ }
18
+
19
+ ZERO_ARGUMENT_MEASUREMENTS = %w{
20
+ area
21
+ ndims
22
+ npoints
23
+ nrings
24
+ numgeometries
25
+ numinteriorring
26
+ numinteriorrings
27
+ numpoints
28
+ length
29
+ length2d
30
+ perimeter
31
+ perimeter2d
32
+ }
33
+
34
+ ONE_GEOMETRY_ARGUMENT_MEASUREMENTS = %w{
35
+ distance
36
+ distance_sphere
37
+ maxdistance
38
+ }
39
+
40
+ ONE_GEOMETRY_ARGUMENT_AND_ONE_ARGUMENT_RELATIONSHIPS = %w{
41
+ dwithin
42
+ dfullywithin
43
+ }
44
+
45
+ ONE_ARGUMENT_MEASUREMENTS = %w{
46
+ length2d_spheroid
47
+ length_spheroid
48
+ }
49
+
50
+ # Some functions were renamed in PostGIS 2.0.
51
+ if ActiveRecordSpatial::POSTGIS[:lib] >= '2.0'
52
+ RELATIONSHIPS.concat(%w{
53
+ 3dintersects
54
+ })
55
+
56
+ ZERO_ARGUMENT_MEASUREMENTS.concat(%w{
57
+ 3dlength
58
+ 3dperimeter
59
+ })
60
+
61
+ ONE_ARGUMENT_MEASUREMENTS.concat(%w{
62
+ 3dlength_spheroid
63
+ })
64
+
65
+ ONE_GEOMETRY_ARGUMENT_MEASUREMENTS.concat(%w{
66
+ 3ddistance
67
+ 3dmaxdistance
68
+ })
69
+
70
+ ONE_GEOMETRY_ARGUMENT_AND_ONE_ARGUMENT_RELATIONSHIPS.concat(%w{
71
+ 3ddwithin
72
+ 3ddfullywithin
73
+ })
74
+ else
75
+ ZERO_ARGUMENT_MEASUREMENTS.concat(%w{
76
+ length3d
77
+ perimeter3d
78
+ })
79
+
80
+ ONE_ARGUMENT_MEASUREMENTS.concat(%w{
81
+ length3d_spheroid
82
+ })
83
+ end
84
+
85
+ FUNCTION_ALIASES = {
86
+ 'order_by_st_max_distance' => 'order_by_st_maxdistance',
87
+ 'st_geometrytype' => 'st_geometry_type'
88
+ }
89
+
90
+ COMPATIBILITY_FUNCTION_ALIASES = if ActiveRecordSpatial::POSTGIS[:lib] >= '2.0'
91
+ {
92
+ 'order_by_st_length3d' => 'order_by_st_3dlength',
93
+ 'order_by_st_perimeter3d' => 'order_by_st_3dperimeter',
94
+ 'order_by_st_length3d_spheroid' => 'order_by_st_3dlength_spheroid'
95
+ }
96
+ else
97
+ {
98
+ 'order_by_st_3dlength' => 'order_by_st_length3d',
99
+ 'order_by_st_3dperimeter' => 'order_by_st_perimeter3d',
100
+ 'order_by_st_3dlength_spheroid' => 'order_by_st_length3d_spheroid'
101
+ }
102
+ end
103
+
104
+ if ActiveRecordSpatial::POSTGIS[:lib] >= '2.0'
105
+ FUNCTION_ALIASES.merge!({
106
+ 'st_3d_dwithin' => 'st_3ddwithin',
107
+ 'st_3d_dfully_within' => 'st_3ddfullywithin',
108
+ 'order_by_st_3d_distance' => 'order_by_st_3ddistance',
109
+ 'order_by_st_3d_max_distance' => 'order_by_st_3dmaxdistance'
110
+ })
111
+ end
112
+ end
113
+ end
114
+
@@ -0,0 +1,297 @@
1
+
2
+ module ActiveRecordSpatial
3
+ # Creates named scopes for spatial relationships. The scopes created
4
+ # follow the nine relationships established by the standard
5
+ # Dimensionally Extended 9-Intersection Matrix functions plus a couple
6
+ # of extra ones provided by PostGIS.
7
+ #
8
+ # Scopes provided are:
9
+ #
10
+ # * st_contains
11
+ # * st_containsproperly
12
+ # * st_covers
13
+ # * st_coveredby
14
+ # * st_crosses
15
+ # * st_disjoint
16
+ # * st_equals
17
+ # * st_intersects
18
+ # * st_orderingequals
19
+ # * st_overlaps
20
+ # * st_touches
21
+ # * st_within
22
+ # * st_dwithin
23
+ # * st_dfullywithin
24
+ #
25
+ # The first argument to each of these methods can be a Geos::Geometry-based
26
+ # object or anything readable by Geos.read along with an optional
27
+ # options Hash.
28
+ #
29
+ # For ordering, we have the following:
30
+ #
31
+ # The following scopes take no arguments:
32
+ #
33
+ # * order_by_st_area
34
+ # * order_by_st_ndims
35
+ # * order_by_st_npoints
36
+ # * order_by_st_nrings
37
+ # * order_by_st_numgeometries
38
+ # * order_by_st_numinteriorring
39
+ # * order_by_st_numinteriorrings
40
+ # * order_by_st_numpoints
41
+ # * order_by_st_length3d
42
+ # * order_by_st_length
43
+ # * order_by_st_length2d
44
+ # * order_by_st_perimeter
45
+ # * order_by_st_perimeter2d
46
+ # * order_by_st_perimeter3d
47
+ #
48
+ # These next scopes allow you to specify a geometry argument for
49
+ # measurement:
50
+ #
51
+ # * order_by_st_distance
52
+ # * order_by_st_distance_sphere
53
+ # * order_by_st_maxdistance
54
+ # * order_by_st_hausdorffdistance (additionally allows you to set the
55
+ # densify_frac argument)
56
+ # * order_by_st_distance_spheroid (requires an additional SPHEROID
57
+ # string to calculate against)
58
+ #
59
+ # These next scopes allow you to specify a SPHEROID string to calculate
60
+ # against:
61
+ #
62
+ # * order_by_st_length2d_spheroid
63
+ # * order_by_st_length3d_spheroid
64
+ # * order_by_st_length_spheroid
65
+ #
66
+ # == Options
67
+ #
68
+ # * :column - the column to compare against. This option can either be a
69
+ # straight-up column name or a Hash that contains a handful of options
70
+ # that can be used to wrap a geometry column in an ST_ function.
71
+ # When wrapping a geometry column in a function, you can set the name of
72
+ # the function and its methods like so:
73
+ #
74
+ # Foo.st_within(geom, :column => {
75
+ # :name => :the_geom,
76
+ # :wrapper => :centroid
77
+ # })
78
+ #
79
+ # Foo.st_within(geom, :column => {
80
+ # :wrapper => {
81
+ # :snap => [ 'POINT (0 0)', 1 ]
82
+ # }
83
+ # })
84
+ #
85
+ # In the first example, the name of the function is the value to the
86
+ # :wrapper+ option. In the second example, +:snap+ is the function name
87
+ # and the Array value is used as the arguments to the +ST_snap+ function.
88
+ # We can also see the column name being set in the first example.
89
+ #
90
+ # In all cases, the default column name is 'the_geom'. You can override
91
+ # the default column name for the ActiveRecordSpatial by setting it via
92
+ # ActiveRecordSpatial.default_column_name=, which is useful if you have
93
+ # a common geometry name you tend to use, such as +geom+, +wkb+,
94
+ # +feature+, etc.
95
+ # * :use_index - whether to use the "ST_" methods or the "\_ST_"
96
+ # variants which don't use indexes. The default is true.
97
+ # * :allow_null - relationship scopes have the option of treating NULL
98
+ # geometry values as TRUE, i.e.
99
+ #
100
+ # ST_within(the_geom, ...) OR the_geom IS NULL
101
+ #
102
+ # * :desc - the order_by scopes have an additional :desc option to alllow
103
+ # for DESC ordering.
104
+ # * :nulls - the order_by scopes also allow you to specify whether you
105
+ # want NULL values to be sorted first or last.
106
+ # * :invert - inverts the relationship query from ST_*(A, B) to ST_*(B, A).
107
+ #
108
+ # Because it's quite common to only want to flip the ordering to DESC,
109
+ # you can also just pass :desc on its own rather than as an options Hash.
110
+ #
111
+ # == SRID Detection
112
+ #
113
+ # * the default SRID according to the SQL-MM standard is 0, but versions
114
+ # of PostGIS prior to 2.0 would return -1. We do some detection here
115
+ # and set the value of ActiveRecordSpatial::UNKNOWN_SRIDS[type]
116
+ # accordingly.
117
+ # * if the geometry itself has an SRID, we'll compare it to the
118
+ # geometry of the column. If they differ, we'll use ST_Transform
119
+ # to transform the geometry to the proper SRID for comparison. If
120
+ # they're the same, no conversion is necessary.
121
+ # * if no SRID is specified in the geometry, we'll use ST_SetSRID
122
+ # to set the SRID to the column's SRID.
123
+ # * in cases where the column has been defined with an SRID of
124
+ # UNKNOWN_SRIDS[type], no transformation is done, but we'll set the SRID
125
+ # of the geometry to UNKNOWN_SRIDS[type] to perform the query using
126
+ # ST_SetSRID, as we'll assume the SRID of the column to be whatever
127
+ # the SRID of the geometry is.
128
+ # * when using geography types, the SRID is never transformed since
129
+ # it's assumed that all of your geometries will be in 4326.
130
+ module SpatialScopes
131
+ extend ActiveSupport::Concern
132
+
133
+ DEFAULT_OPTIONS = {
134
+ :column => ActiveRecordSpatial.default_column_name,
135
+ :use_index => true
136
+ }.freeze
137
+
138
+ included do
139
+ assert_arguments_length = proc { |args, min, max = (1.0 / 0.0)|
140
+ raise ArgumentError.new("wrong number of arguments (#{args.length} for #{min}-#{max})") unless
141
+ args.length.between?(min, max)
142
+ }
143
+
144
+ SpatialScopeConstants::RELATIONSHIPS.each do |relationship|
145
+ src, line = <<-EOF, __LINE__ + 1
146
+ scope :st_#{relationship}, lambda { |geom, options = {}|
147
+ options = {
148
+ :geom_arg => geom
149
+ }.merge(options)
150
+
151
+ unless geom.nil?
152
+ self.where(
153
+ ActiveRecordSpatial::SpatialFunction.build!(self, '#{relationship}', options).to_sql
154
+ )
155
+ end
156
+ }
157
+ EOF
158
+ self.class_eval(src, __FILE__, line)
159
+ end
160
+
161
+ SpatialScopeConstants::ONE_GEOMETRY_ARGUMENT_AND_ONE_ARGUMENT_RELATIONSHIPS.each do |relationship|
162
+ src, line = <<-EOF, __LINE__ + 1
163
+ scope :st_#{relationship}, lambda { |geom, distance, options = {}|
164
+ options = {
165
+ :geom_arg => geom,
166
+ :args => distance
167
+ }.merge(options)
168
+
169
+ self.where(
170
+ ActiveRecordSpatial::SpatialFunction.build!(self, '#{relationship}', options).to_sql
171
+ )
172
+ }
173
+ EOF
174
+ self.class_eval(src, __FILE__, line)
175
+ end
176
+
177
+ self.class_eval do
178
+ scope :st_geometry_type, lambda { |*args|
179
+ assert_arguments_length[args, 1]
180
+ options = args.extract_options!
181
+ types = args
182
+
183
+ self.where(
184
+ ActiveRecordSpatial::SpatialFunction.build!(self, 'GeometryType', options).in(types).to_sql
185
+ )
186
+ }
187
+ end
188
+
189
+ SpatialScopeConstants::ZERO_ARGUMENT_MEASUREMENTS.each do |measurement|
190
+ src, line = <<-EOF, __LINE__ + 1
191
+ scope :order_by_st_#{measurement}, lambda { |options = {}|
192
+ if options.is_a?(Symbol)
193
+ options = {
194
+ :desc => options
195
+ }
196
+ end
197
+
198
+ function_call = ActiveRecordSpatial::SpatialFunction.build!(self, '#{measurement}', options).to_sql
199
+ function_call << ActiveRecordSpatial::SpatialFunction.additional_ordering(options)
200
+
201
+ self.order(function_call)
202
+ }
203
+ EOF
204
+ self.class_eval(src, __FILE__, line)
205
+ end
206
+
207
+ SpatialScopeConstants::ONE_GEOMETRY_ARGUMENT_MEASUREMENTS.each do |measurement|
208
+ src, line = <<-EOF, __LINE__ + 1
209
+ scope :order_by_st_#{measurement}, lambda { |geom, options = {}|
210
+ if options.is_a?(Symbol)
211
+ options = {
212
+ :desc => options
213
+ }
214
+ end
215
+
216
+ options = {
217
+ :geom_arg => geom
218
+ }.merge(options)
219
+
220
+ function_call = ActiveRecordSpatial::SpatialFunction.build!(self, '#{measurement}', options).to_sql
221
+ function_call << ActiveRecordSpatial::SpatialFunction.additional_ordering(options)
222
+
223
+ self.order(function_call)
224
+ }
225
+ EOF
226
+ self.class_eval(src, __FILE__, line)
227
+ end
228
+
229
+ SpatialScopeConstants::ONE_ARGUMENT_MEASUREMENTS.each do |measurement|
230
+ src, line = <<-EOF, __LINE__ + 1
231
+ scope :order_by_st_#{measurement}, lambda { |argument, options = {}|
232
+ options = {
233
+ :args => argument
234
+ }.merge(options)
235
+
236
+ function_call = ActiveRecordSpatial::SpatialFunction.build!(self, '#{measurement}', options).to_sql
237
+ function_call << ActiveRecordSpatial::SpatialFunction.additional_ordering(options)
238
+
239
+ self.order(function_call)
240
+ }
241
+ EOF
242
+ self.class_eval(src, __FILE__, line)
243
+ end
244
+
245
+ self.class_eval do
246
+ scope :order_by_st_hausdorffdistance, lambda { |*args|
247
+ assert_arguments_length[args, 1, 3]
248
+ options = args.extract_options!
249
+ geom, densify_frac = args
250
+
251
+ options = {
252
+ :geom_arg => geom,
253
+ :args => densify_frac
254
+ }.merge(options)
255
+
256
+ function_call = ActiveRecordSpatial::SpatialFunction.build!(
257
+ self,
258
+ 'hausdorffdistance',
259
+ options
260
+ ).to_sql
261
+ function_call << ActiveRecordSpatial::SpatialFunction.additional_ordering(options)
262
+
263
+ self.order(function_call)
264
+ }
265
+
266
+ scope :order_by_st_distance_spheroid, lambda { |geom, spheroid, options = {}|
267
+ options = {
268
+ :geom_arg => geom,
269
+ :args => spheroid
270
+ }.merge(options)
271
+
272
+ function_call = ActiveRecordSpatial::SpatialFunction.build!(
273
+ self,
274
+ 'distance_spheroid',
275
+ options
276
+ ).to_sql
277
+ function_call << ActiveRecordSpatial::SpatialFunction.additional_ordering(options)
278
+
279
+ self.order(function_call)
280
+ }
281
+
282
+ class << self
283
+ aliases = SpatialScopeConstants::COMPATIBILITY_FUNCTION_ALIASES.merge(
284
+ SpatialScopeConstants::FUNCTION_ALIASES
285
+ )
286
+
287
+ aliases.each do |k, v|
288
+ alias_method(k, v)
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
294
+
295
+ # Alias for backwards compatibility.
296
+ GeospatialScopes = SpatialScopes
297
+ end