activerecord-spatial 0.0.1

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