rgeo-ar 0.6.0

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