rgeo-ar 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,215 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Various Arel hacks to support spatial queries
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010-2012 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module RGeo
38
+
39
+ module ActiveRecord
40
+
41
+
42
+ # A set of common Arel visitor hacks for spatial ToSql visitors.
43
+ # Generally, a spatial ActiveRecord adapter should provide a custom
44
+ # ToSql Arel visitor that includes and customizes this module.
45
+ # See the existing spatial adapters (i.e. postgis, spatialite,
46
+ # mysqlspatial, and mysql2spatial) for usage examples.
47
+
48
+ module SpatialToSql
49
+
50
+
51
+ # Map a standard OGC SQL function name to the actual name used by
52
+ # a particular database. This method should take a name and
53
+ # return either the changed name or the original name.
54
+
55
+ def st_func(standard_name_)
56
+ standard_name_
57
+ end
58
+
59
+
60
+ # Visit the SpatialNamedFunction node. This operates similarly to
61
+ # the standard NamedFunction node, but it performs function name
62
+ # mapping for the database, and it also uses the type information
63
+ # in the node to determine when to cast string arguments to WKT,
64
+
65
+ def visit_RGeo_ActiveRecord_SpatialNamedFunction(node_, *args)
66
+ name_ = st_func(node_.name)
67
+ exprs_ = []
68
+ node_.expressions.each_with_index do |expr_, index_|
69
+ exprs_ << (node_.spatial_argument?(index_) ? visit_in_spatial_context(expr_, *args) : visit(expr_, *args))
70
+ end
71
+ "#{name_}(#{node_.distinct ? 'DISTINCT ' : ''}#{exprs_.join(', ')})#{node_.alias ? " AS #{visit(node_.alias, *args)}" : ''}"
72
+ end
73
+
74
+
75
+ # Generates SQL for a spatial node.
76
+ # The node must be a string (in which case it is treated as WKT),
77
+ # an RGeo feature, or a spatial attribute.
78
+
79
+ def visit_in_spatial_context(node_, *args)
80
+ case node_
81
+ when ::String
82
+ "#{st_func('ST_WKTToSQL')}(#{visit_String(node_, *args)})"
83
+ when ::RGeo::Feature::Instance
84
+ visit_RGeo_Feature_Instance(node_, *args)
85
+ when ::RGeo::Cartesian::BoundingBox
86
+ visit_RGeo_Cartesian_BoundingBox(node_, *args)
87
+ else
88
+ visit(node_, *args)
89
+ end
90
+ end
91
+
92
+
93
+ end
94
+
95
+
96
+ # This node wraps an RGeo feature and gives it spatial expression
97
+ # constructors.
98
+
99
+ class SpatialConstantNode
100
+
101
+ include ::RGeo::ActiveRecord::SpatialExpressions
102
+
103
+
104
+ # The delegate should be the RGeo feature.
105
+
106
+ def initialize(delegate_)
107
+ @delegate = delegate_
108
+ end
109
+
110
+
111
+ # Return the RGeo feature
112
+
113
+ attr_reader :delegate
114
+
115
+
116
+ end
117
+
118
+
119
+ # :stopdoc:
120
+
121
+
122
+ # Hack Arel Attributes dispatcher to recognize geometry columns.
123
+ # This is deprecated but necessary to support legacy Arel versions.
124
+
125
+ if ::Arel::Attributes.method_defined?(:for)
126
+ module ArelAttributesLegacyClassMethods
127
+ def for(column_)
128
+ column_.type == :spatial ? Attribute : super
129
+ end
130
+ end
131
+ ::Arel::Attributes.extend(ArelAttributesLegacyClassMethods)
132
+ end
133
+
134
+
135
+ # Make sure the standard Arel visitors can handle RGeo feature objects
136
+ # by default.
137
+
138
+ ::Arel::Visitors::Visitor.class_eval do
139
+ def visit_RGeo_ActiveRecord_SpatialConstantNode(node_, *args)
140
+ if respond_to?(:visit_in_spatial_context)
141
+ visit_in_spatial_context(node_.delegate, *args)
142
+ else
143
+ visit(node_.delegate, *args)
144
+ end
145
+ end
146
+ end
147
+ ::Arel::Visitors::Dot.class_eval do
148
+ alias :visit_RGeo_Feature_Instance :visit_String
149
+ alias :visit_RGeo_Cartesian_BoundingBox :visit_String
150
+ end
151
+ ::Arel::Visitors::DepthFirst.class_eval do
152
+ alias :visit_RGeo_Feature_Instance :terminal
153
+ alias :visit_RGeo_Cartesian_BoundingBox :terminal
154
+ end
155
+ ::Arel::Visitors::ToSql.class_eval do
156
+ alias :visit_RGeo_Feature_Instance :visit_String
157
+ alias :visit_RGeo_Cartesian_BoundingBox :visit_String
158
+ end
159
+
160
+
161
+ # Add tools to build spatial structures in the AST.
162
+ # This stuff requires Arel 2.1 or later.
163
+
164
+ if defined?(::Arel::Nodes::NamedFunction)
165
+
166
+ # Allow chaining of predications from named functions
167
+ # (Some older versions of Arel didn't do this.)
168
+ ::Arel::Nodes::NamedFunction.class_eval do
169
+ include ::Arel::Predications unless include?(::Arel::Predications)
170
+ end
171
+
172
+ # Allow chaining of spatial expressions from attributes
173
+ ::Arel::Attribute.class_eval do
174
+ include ::RGeo::ActiveRecord::SpatialExpressions
175
+ end
176
+
177
+
178
+ # A NamedFunction subclass that keeps track of the spatial-ness of
179
+ # the arguments and return values, so that it can provide context to
180
+ # visitors that want to interpret syntax differently when dealing with
181
+ # spatial elements.
182
+
183
+ class SpatialNamedFunction < ::Arel::Nodes::NamedFunction
184
+
185
+ include ::RGeo::ActiveRecord::SpatialExpressions
186
+
187
+ def initialize(name_, expr_, spatial_flags_=[], aliaz_=nil)
188
+ super(name_, expr_, aliaz_)
189
+ @spatial_flags = spatial_flags_
190
+ end
191
+
192
+ def spatial_result?
193
+ @spatial_flags.first
194
+ end
195
+
196
+ def spatial_argument?(index_)
197
+ @spatial_flags[index_+1]
198
+ end
199
+
200
+ end
201
+
202
+ else
203
+
204
+ # A dummy SpatialNamedFunction for pre-2.1 versions of Arel.
205
+ class SpatialNamedFunction; end
206
+
207
+ end
208
+
209
+
210
+ # :startdoc:
211
+
212
+
213
+ end
214
+
215
+ end
@@ -0,0 +1,177 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Common tools for spatial adapters for ActiveRecord
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010-2012 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ require 'active_support/core_ext/class' # Workaround for a missing require in ActiveRecord 3.2.1
38
+ require 'active_record'
39
+
40
+ # Force AbstractAdapter to autoload
41
+ if ::ActiveRecord::ConnectionAdapters::AbstractAdapter
42
+ end
43
+
44
+
45
+ module RGeo
46
+
47
+ module ActiveRecord
48
+
49
+
50
+ # Some default column constructors specifications for most spatial
51
+ # databases. Individual adapters may add to or override this list.
52
+
53
+ DEFAULT_SPATIAL_COLUMN_CONSTRUCTORS = {
54
+ :spatial => {:type => 'geometry'}.freeze,
55
+ :geometry => {}.freeze,
56
+ :point => {}.freeze,
57
+ :line_string => {}.freeze,
58
+ :polygon => {}.freeze,
59
+ :geometry_collection => {}.freeze,
60
+ :multi_line_string => {}.freeze,
61
+ :multi_point => {}.freeze,
62
+ :multi_polygon => {}.freeze,
63
+ }.freeze
64
+
65
+
66
+ # Index definition struct with a spatial flag field.
67
+
68
+ class SpatialIndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :spatial)
69
+ end
70
+
71
+
72
+ # Returns a feature type module given a string type.
73
+
74
+ def self.geometric_type_from_name(name_)
75
+ case name_.to_s
76
+ when /^geometry/i then ::RGeo::Feature::Geometry
77
+ when /^point/i then ::RGeo::Feature::Point
78
+ when /^linestring/i then ::RGeo::Feature::LineString
79
+ when /^polygon/i then ::RGeo::Feature::Polygon
80
+ when /^geometrycollection/i then ::RGeo::Feature::GeometryCollection
81
+ when /^multipoint/i then ::RGeo::Feature::MultiPoint
82
+ when /^multilinestring/i then ::RGeo::Feature::MultiLineString
83
+ when /^multipolygon/i then ::RGeo::Feature::MultiPolygon
84
+ else nil
85
+ end
86
+ end
87
+
88
+
89
+ # :stopdoc:
90
+
91
+
92
+ # Provide methods for each geometric subtype during table definitions.
93
+
94
+ ::ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do
95
+
96
+ alias_method :method_missing_without_rgeo_modification, :method_missing
97
+
98
+ def method_missing(method_name_, *args_, &block_)
99
+ if @base.respond_to?(:spatial_column_constructor) && (info_ = @base.spatial_column_constructor(method_name_))
100
+ info_ = info_.dup
101
+ type_ = (info_.delete(:type) || method_name_).to_s
102
+ opts_ = args_.extract_options!.merge(info_)
103
+ args_.each do |name_|
104
+ column(name_, type_, opts_)
105
+ end
106
+ else
107
+ method_missing_without_rgeo_modification(method_name_, *args_, &block_)
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+
114
+ # Provide methods for each geometric subtype during table changes.
115
+
116
+ ::ActiveRecord::ConnectionAdapters::Table.class_eval do
117
+
118
+ alias_method :method_missing_without_rgeo_modification, :method_missing
119
+
120
+ def method_missing(method_name_, *args_, &block_)
121
+ if @base.respond_to?(:spatial_column_constructor) && (info_ = @base.spatial_column_constructor(method_name_))
122
+ info_ = info_.dup
123
+ type_ = (info_.delete(:type) || method_name_).to_s
124
+ opts_ = args_.extract_options!.merge(info_)
125
+ args_.each do |name_|
126
+ @base.add_column(@table_name, name_, type_, opts_)
127
+ end
128
+ else
129
+ method_missing_without_rgeo_modification(method_name_, *args_, &block_)
130
+ end
131
+ end
132
+
133
+ end
134
+
135
+
136
+ # Hack schema dumper to output spatial index flag
137
+
138
+ ::ActiveRecord::SchemaDumper.class_eval do
139
+
140
+ private
141
+
142
+ alias_method :_old_indexes_method, :indexes
143
+
144
+ def indexes(table_, stream_)
145
+ if (indexes_ = @connection.indexes(table_)).any?
146
+ add_index_statements_ = indexes_.map do |index_|
147
+ statement_parts_ = [
148
+ ('add_index ' + index_.table.inspect),
149
+ index_.columns.inspect,
150
+ (':name => ' + index_.name.inspect),
151
+ ]
152
+ statement_parts_ << ':unique => true' if index_.unique
153
+ statement_parts_ << ':spatial => true' if index_.respond_to?(:spatial) && index_.spatial
154
+ index_lengths_ = (index_.lengths || []).compact
155
+ statement_parts_ << (':length => ' + ::Hash[*index_.columns.zip(index_.lengths).flatten].inspect) unless index_lengths_.empty?
156
+ ' ' + statement_parts_.join(', ')
157
+ end
158
+ stream_.puts add_index_statements_.sort.join("\n")
159
+ stream_.puts
160
+ end
161
+ end
162
+
163
+ end
164
+
165
+
166
+ # Tell ActiveRecord to cache spatial attribute values so they don't get
167
+ # re-parsed on every access.
168
+
169
+ ::ActiveRecord::Base.attribute_types_cached_by_default << :spatial
170
+
171
+
172
+ # :startdoc:
173
+
174
+
175
+ end
176
+
177
+ end
@@ -0,0 +1,108 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Geometry mixin for JSON serialization
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright 2010-2012 Daniel Azuma
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Redistribution and use in source and binary forms, with or without
11
+ # modification, are permitted provided that the following conditions are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright notice,
14
+ # this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright notice,
16
+ # this list of conditions and the following disclaimer in the documentation
17
+ # and/or other materials provided with the distribution.
18
+ # * Neither the name of the copyright holder, nor the names of any other
19
+ # contributors to this software, may be used to endorse or promote products
20
+ # derived from this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ # POSSIBILITY OF SUCH DAMAGE.
33
+ # -----------------------------------------------------------------------------
34
+ ;
35
+
36
+
37
+ module RGeo
38
+
39
+ module ActiveRecord
40
+
41
+
42
+ # This module is mixed into all geometry objects. It provides an
43
+ # as_json method so that ActiveRecord knows how to generate JSON
44
+ # for a geometry-valued field.
45
+
46
+ module GeometryMixin
47
+
48
+
49
+ # The default JSON generator Proc. Renders geometry fields as WKT.
50
+ DEFAULT_JSON_GENERATOR = ::Proc.new{ |geom_| geom_.to_s }
51
+
52
+ @json_generator = DEFAULT_JSON_GENERATOR
53
+
54
+
55
+ # Set the style of JSON generation used for geometry fields in an
56
+ # ActiveRecord model by default. You may pass nil to use
57
+ # DEFAULT_JSON_GENERATOR, a proc that takes a geometry as the
58
+ # argument and returns an object that can be converted to JSON
59
+ # (i.e. usually a hash or string), or one of the following symbolic
60
+ # values:
61
+ #
62
+ # <tt>:wkt</tt>::
63
+ # Well-known text format. (Same as DEFAULT_JSON_GENERATOR.)
64
+ # <tt>:geojson</tt>::
65
+ # GeoJSON format. Requires the rgeo-geojson gem.
66
+
67
+ def self.set_json_generator(value_=nil, &block_)
68
+ if block_ && !value_
69
+ value_ = block_
70
+ elsif value_ == :geojson
71
+ require 'rgeo/geo_json'
72
+ value_ = ::Proc.new{ |geom_| ::RGeo::GeoJSON.encode(geom_) }
73
+ end
74
+ if value_.is_a?(::Proc)
75
+ @json_generator = value_
76
+ else
77
+ @json_generator = DEFAULT_JSON_GENERATOR
78
+ end
79
+ end
80
+
81
+
82
+ # Given a feature, returns an object that can be serialized as JSON
83
+ # (i.e. usually a hash or string), using the current json_generator.
84
+ # This is used to generate JSON for geometry-valued ActiveRecord
85
+ # fields by default.
86
+
87
+ def self.generate_json(geom_)
88
+ @json_generator.call(geom_)
89
+ end
90
+
91
+
92
+ # Serializes this object as JSON for ActiveRecord.
93
+
94
+ def as_json(opts_=nil)
95
+ GeometryMixin.generate_json(self)
96
+ end
97
+
98
+
99
+ end
100
+
101
+
102
+ end
103
+
104
+ end
105
+
106
+
107
+ ::RGeo::Feature::MixinCollection::GLOBAL.for_type(::RGeo::Feature::Geometry).
108
+ add(::RGeo::ActiveRecord::GeometryMixin)