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.
- checksums.yaml +15 -0
- data/.gitignore +16 -0
- data/Gemfile +21 -0
- data/Guardfile +17 -0
- data/MIT-LICENSE +23 -0
- data/README.rdoc +169 -0
- data/Rakefile +28 -0
- data/activerecord-spatial.gemspec +26 -0
- data/lib/activerecord-spatial.rb +32 -0
- data/lib/activerecord-spatial/active_record.rb +14 -0
- data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/adapter_extensions.rb +36 -0
- data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/postgis.rb +24 -0
- data/lib/activerecord-spatial/active_record/connection_adapters/postgresql/unknown_srid.rb +21 -0
- data/lib/activerecord-spatial/active_record/models/geography_column.rb +17 -0
- data/lib/activerecord-spatial/active_record/models/geometry_column.rb +17 -0
- data/lib/activerecord-spatial/active_record/models/spatial_column.rb +22 -0
- data/lib/activerecord-spatial/active_record/models/spatial_ref_sys.rb +20 -0
- data/lib/activerecord-spatial/associations.rb +292 -0
- data/lib/activerecord-spatial/spatial_columns.rb +345 -0
- data/lib/activerecord-spatial/spatial_function.rb +201 -0
- data/lib/activerecord-spatial/spatial_scope_constants.rb +114 -0
- data/lib/activerecord-spatial/spatial_scopes.rb +297 -0
- data/lib/activerecord-spatial/version.rb +5 -0
- data/lib/tasks/test.rake +45 -0
- data/test/accessors_geographies_tests.rb +149 -0
- data/test/accessors_geometries_tests.rb +151 -0
- data/test/adapter_tests.rb +44 -0
- data/test/associations_tests.rb +656 -0
- data/test/database.yml +17 -0
- data/test/fixtures/bars.yml +16 -0
- data/test/fixtures/blorts.yml +37 -0
- data/test/fixtures/foo3ds.yml +17 -0
- data/test/fixtures/foo_geographies.yml +16 -0
- data/test/fixtures/foos.yml +16 -0
- data/test/fixtures/zortables.yml +36 -0
- data/test/geography_column_tests.rb +40 -0
- data/test/geometry_column_tests.rb +40 -0
- data/test/models/bar.rb +17 -0
- data/test/models/blort.rb +12 -0
- data/test/models/foo.rb +17 -0
- data/test/models/foo3d.rb +17 -0
- data/test/models/foo_geography.rb +16 -0
- data/test/models/zortable.rb +17 -0
- data/test/spatial_scopes_geographies_tests.rb +106 -0
- data/test/spatial_scopes_tests.rb +444 -0
- data/test/test_helper.rb +272 -0
- 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
|