activerecord-postgis 0.1.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.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +194 -0
  4. data/activerecord-postgis.gemspec +28 -0
  5. data/lib/active_record/connection_adapters/postgis/adapter_extensions.rb +85 -0
  6. data/lib/active_record/connection_adapters/postgis/column_extensions.rb +90 -0
  7. data/lib/active_record/connection_adapters/postgis/column_methods.rb +61 -0
  8. data/lib/active_record/connection_adapters/postgis/constants.rb +36 -0
  9. data/lib/active_record/connection_adapters/postgis/oid/spatial.rb +122 -0
  10. data/lib/active_record/connection_adapters/postgis/oid/spatial_types.rb +26 -0
  11. data/lib/active_record/connection_adapters/postgis/quoting.rb +35 -0
  12. data/lib/active_record/connection_adapters/postgis/schema_dumper.rb +94 -0
  13. data/lib/active_record/connection_adapters/postgis/schema_statements.rb +136 -0
  14. data/lib/active_record/connection_adapters/postgis/spatial_column_methods.rb +40 -0
  15. data/lib/active_record/connection_adapters/postgis/spatial_column_type.rb +173 -0
  16. data/lib/active_record/connection_adapters/postgis/table_definition.rb +144 -0
  17. data/lib/active_record/connection_adapters/postgis/type/geography.rb +21 -0
  18. data/lib/active_record/connection_adapters/postgis/type/geometry.rb +21 -0
  19. data/lib/active_record/connection_adapters/postgis/type/geometry_collection.rb +21 -0
  20. data/lib/active_record/connection_adapters/postgis/type/line_string.rb +21 -0
  21. data/lib/active_record/connection_adapters/postgis/type/multi_line_string.rb +21 -0
  22. data/lib/active_record/connection_adapters/postgis/type/multi_point.rb +21 -0
  23. data/lib/active_record/connection_adapters/postgis/type/multi_polygon.rb +21 -0
  24. data/lib/active_record/connection_adapters/postgis/type/point.rb +21 -0
  25. data/lib/active_record/connection_adapters/postgis/type/polygon.rb +21 -0
  26. data/lib/active_record/connection_adapters/postgis/type/spatial.rb +86 -0
  27. data/lib/active_record/connection_adapters/postgis/version.rb +9 -0
  28. data/lib/active_record/connection_adapters/postgis.rb +196 -0
  29. data/lib/activerecord-postgis.rb +12 -0
  30. data/lib/arel/visitors/postgis.rb +147 -0
  31. metadata +120 -0
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spatial"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostGIS
8
+ module Type
9
+ class LineString < Spatial
10
+ def initialize(srid: 0, has_z: false, has_m: false, geographic: false)
11
+ super(geo_type: "line_string", srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
12
+ end
13
+
14
+ def type
15
+ :st_line_string
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spatial"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostGIS
8
+ module Type
9
+ class MultiLineString < Spatial
10
+ def initialize(srid: 0, has_z: false, has_m: false)
11
+ super(geo_type: "multi_line_string", srid: srid, has_z: has_z, has_m: has_m)
12
+ end
13
+
14
+ def type
15
+ :st_multi_line_string
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spatial"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostGIS
8
+ module Type
9
+ class MultiPoint < Spatial
10
+ def initialize(srid: 0, has_z: false, has_m: false)
11
+ super(geo_type: "multi_point", srid: srid, has_z: has_z, has_m: has_m)
12
+ end
13
+
14
+ def type
15
+ :st_multi_point
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spatial"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostGIS
8
+ module Type
9
+ class MultiPolygon < Spatial
10
+ def initialize(srid: 0, has_z: false, has_m: false, geographic: false)
11
+ super(geo_type: "multi_polygon", srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
12
+ end
13
+
14
+ def type
15
+ :st_multi_polygon
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spatial"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostGIS
8
+ module Type
9
+ class Point < Spatial
10
+ def initialize(srid: 0, has_z: false, has_m: false, geographic: false)
11
+ super(geo_type: "point", srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
12
+ end
13
+
14
+ def type
15
+ :st_point
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spatial"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostGIS
8
+ module Type
9
+ class Polygon < Spatial
10
+ def initialize(srid: 0, has_z: false, has_m: false)
11
+ super(geo_type: "polygon", srid: srid, has_z: has_z, has_m: has_m)
12
+ end
13
+
14
+ def type
15
+ :st_polygon
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rgeo"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostGIS
8
+ module Type
9
+ class Spatial < ::ActiveRecord::Type::Value
10
+ attr_reader :geo_type, :srid, :has_z, :has_m, :geographic
11
+
12
+ def initialize(geo_type: "geometry", srid: 0, has_z: false, has_m: false, geographic: false)
13
+ @geo_type = geographic ? "geography" : geo_type
14
+ @srid = srid
15
+ @has_z = has_z
16
+ @has_m = has_m
17
+ @geographic = geographic
18
+ @factory = RGeo::ActiveRecord::SpatialFactoryStore.instance.factory(
19
+ geo_type: @geo_type.underscore,
20
+ srid: srid,
21
+ has_z: has_z,
22
+ has_m: has_m,
23
+ sql_type: (@geographic ? "geography" : "geometry")
24
+ )
25
+ end
26
+
27
+ def type
28
+ :geometry
29
+ end
30
+
31
+ def serialize(value)
32
+ if value.respond_to?(:as_text)
33
+ "SRID=#{value.srid};#{value.as_text}"
34
+ elsif value.is_a?(String)
35
+ value
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def cast_value(value)
44
+ case value
45
+ when ::RGeo::Feature::Instance
46
+ value
47
+ when String
48
+ parse_wkt(value)
49
+ when Hash
50
+ parse_hash(value)
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ def parse_wkt(string)
57
+ if binary_string?(string)
58
+ # Parse WKB (Well-Known Binary) format
59
+ wkb_parser = RGeo::WKRep::WKBParser.new(@factory, support_ewkb: true, default_srid: @srid)
60
+ wkb_parser.parse(string)
61
+ else
62
+ # Parse WKT (Well-Known Text) format
63
+ wkt_parser = RGeo::WKRep::WKTParser.new(@factory, support_ewkt: true, default_srid: @srid)
64
+ wkt_parser.parse(string)
65
+ end
66
+ rescue RGeo::Error::ParseError
67
+ nil
68
+ end
69
+
70
+ def binary_string?(string)
71
+ string[0] == "\x00" || string[0] == "\x01" || string[0, 4] =~ /[0-9a-fA-F]{4}/
72
+ end
73
+
74
+ def parse_hash(hash)
75
+ # Support GeoJSON-style hash
76
+ if hash["type"] && hash["coordinates"]
77
+ RGeo::GeoJSON.decode(hash.to_json, geo_factory: @factory)
78
+ else
79
+ nil
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostGIS
6
+ VERSION = "0.1.0"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "postgis/version"
4
+ require_relative "postgis/type/geography"
5
+ require_relative "postgis/type/geometry"
6
+ require_relative "postgis/type/geometry_collection"
7
+ require_relative "postgis/type/line_string"
8
+ require_relative "postgis/type/multi_line_string"
9
+ require_relative "postgis/type/multi_point"
10
+ require_relative "postgis/type/multi_polygon"
11
+ require_relative "postgis/type/point"
12
+ require_relative "postgis/type/polygon"
13
+ require_relative "postgis/schema_dumper"
14
+ require_relative "postgis/table_definition"
15
+ require_relative "postgis/column_methods"
16
+ require_relative "postgis/schema_statements"
17
+ require_relative "postgis/spatial_column_type"
18
+ require_relative "postgis/adapter_extensions"
19
+ require_relative "postgis/column_extensions"
20
+ require_relative "postgis/quoting"
21
+
22
+ module ActiveRecord
23
+ module ConnectionAdapters
24
+ module PostGIS
25
+ class Error < StandardError; end
26
+ @initialized = false
27
+
28
+ SPATIAL_TYPES_FOR_REGISTRATION = %i[
29
+ st_geography st_geometry st_geometry_collection st_line_string st_multi_line_string
30
+ st_multi_point st_multi_polygon st_point st_polygon line_string
31
+ ].freeze
32
+
33
+ SPATIAL_OPTIONS_FOR_REGISTRATION = %i[srid has_z has_m geographic].freeze
34
+
35
+ def self.initialize!
36
+ return if @initialized
37
+ @initialized = true
38
+
39
+ # Extend PostgreSQL Column class with spatial functionality
40
+ ActiveRecord::ConnectionAdapters::PostgreSQL::Column.include(ColumnExtensions)
41
+
42
+ # Add spatial object quoting support
43
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(Quoting)
44
+
45
+ # Allow PostGIS specific options in table definitions
46
+ # The `define_column_methods` call already makes these available on the `TableDefinition` instance.
47
+ # The issue might be deeper in how schema.rb is processed or how options are validated.
48
+ # Let's ensure that the column methods are defined *before* any schema loading that might trigger validation.
49
+
50
+ ActiveRecord::ConnectionAdapters::PostgreSQL::Table.include(
51
+ ActiveRecord::ConnectionAdapters::PostGIS::TableDefinition
52
+ )
53
+
54
+ # Using st_* prefix to avoid conflicts with PostgreSQL native geometric types
55
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:st_geography] = { name: "geography" }
56
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:st_geometry] = { name: "geometry" }
57
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:st_geometry_collection] = { name: "geometry(GeometryCollection)" }
58
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:st_line_string] = { name: "geometry(LineString)" }
59
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:st_multi_line_string] = { name: "geometry(MultiLineString)" }
60
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:st_multi_point] = { name: "geometry(MultiPoint)" }
61
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:st_multi_polygon] = { name: "geometry(MultiPolygon)" }
62
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:st_point] = { name: "geometry(Point)" }
63
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:st_polygon] = { name: "geometry(Polygon)" }
64
+
65
+ # Legacy aliases for compatibility with activerecord-postgis-adapter
66
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:geography] = { name: "geography" }
67
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:geometry] = { name: "geometry" }
68
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:geometry_collection] = { name: "geometry(GeometryCollection)" }
69
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:line_string] = { name: "geometry(LineString)" }
70
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:multi_line_string] = { name: "geometry(MultiLineString)" }
71
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:multi_point] = { name: "geometry(MultiPoint)" }
72
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:multi_polygon] = { name: "geometry(MultiPolygon)" }
73
+ PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:polygon] = { name: "geometry(Polygon)" }
74
+
75
+
76
+ # Tell Rails these are valid column methods for schema dumping - PostgreSQL only
77
+ ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition.send(:define_column_methods,
78
+ :st_geography, :st_geometry, :st_geometry_collection, :st_line_string,
79
+ :st_multi_line_string, :st_multi_point, :st_multi_polygon, :st_point, :st_polygon,
80
+ # Legacy column methods for compatibility with activerecord-postgis-adapter
81
+ :geography, :geometry, :geometry_collection, :line_string,
82
+ :multi_line_string, :multi_point, :multi_polygon, :polygon,
83
+ *SPATIAL_OPTIONS_FOR_REGISTRATION)
84
+
85
+ # prevent unknown OID warning and register PostGIS types
86
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.singleton_class.prepend(RegisterTypes)
87
+
88
+ # Prepend our extensions to handle spatial columns
89
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(AdapterExtensions)
90
+
91
+ # Register spatial types with ActiveRecord::Type - use st_* prefix to avoid conflicts
92
+ adapter_name = :postgresql
93
+
94
+ # Register specific spatial types with their corresponding classes
95
+ ActiveRecord::Type.register(:st_geography, Type::Geography, adapter: adapter_name)
96
+ ActiveRecord::Type.register(:st_geometry, Type::Geometry, adapter: adapter_name)
97
+ ActiveRecord::Type.register(:st_geometry_collection, Type::GeometryCollection, adapter: adapter_name)
98
+ ActiveRecord::Type.register(:st_line_string, Type::LineString, adapter: adapter_name)
99
+ ActiveRecord::Type.register(:st_multi_line_string, Type::MultiLineString, adapter: adapter_name)
100
+ ActiveRecord::Type.register(:st_multi_point, Type::MultiPoint, adapter: adapter_name)
101
+ ActiveRecord::Type.register(:st_multi_polygon, Type::MultiPolygon, adapter: adapter_name)
102
+ ActiveRecord::Type.register(:st_point, Type::Point, adapter: adapter_name)
103
+ ActiveRecord::Type.register(:st_polygon, Type::Polygon, adapter: adapter_name)
104
+
105
+ # Legacy type registrations for compatibility with activerecord-postgis-adapter
106
+ ActiveRecord::Type.register(:geography, Type::Geography, adapter: adapter_name)
107
+ ActiveRecord::Type.register(:geometry, Type::Geometry, adapter: adapter_name)
108
+ ActiveRecord::Type.register(:geometry_collection, Type::GeometryCollection, adapter: adapter_name)
109
+ ActiveRecord::Type.register(:line_string, Type::LineString, adapter: adapter_name)
110
+ ActiveRecord::Type.register(:multi_line_string, Type::MultiLineString, adapter: adapter_name)
111
+ ActiveRecord::Type.register(:multi_point, Type::MultiPoint, adapter: adapter_name)
112
+ ActiveRecord::Type.register(:multi_polygon, Type::MultiPolygon, adapter: adapter_name)
113
+ ActiveRecord::Type.register(:polygon, Type::Polygon, adapter: adapter_name)
114
+
115
+ # Ignore PostGIS system tables in schema dumps - PostgreSQL specific
116
+ ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.ignore_tables |= %w[
117
+ geography_columns
118
+ geometry_columns
119
+ layer
120
+ raster_columns
121
+ raster_overviews
122
+ spatial_ref_sys
123
+ topology
124
+ ]
125
+ end
126
+
127
+ module RegisterTypes
128
+ def initialize_type_map(m = type_map)
129
+ super
130
+
131
+ # Register by type name for schema loading
132
+ m.register_type "geography" do |_, _, sql_type|
133
+ create_spatial_type_from_sql(sql_type)
134
+ end
135
+
136
+ m.register_type "geometry" do |_, _, sql_type|
137
+ create_spatial_type_from_sql(sql_type)
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def create_spatial_type_from_sql(sql_type)
144
+ # Extract SRID and dimensions from SQL type
145
+ srid = extract_srid_from_sql(sql_type)
146
+ # Check for dimension suffixes (e.g., PointZ, PointM, PointZM)
147
+ has_z = sql_type.match?(/\b\w+Z\b|\b\w+ZM\b/)
148
+ has_m = sql_type.match?(/\b\w+M\b|\b\w+ZM\b/)
149
+ geographic = sql_type.start_with?("geography")
150
+ case sql_type
151
+ when /geography\(Point/i
152
+ Type::Point.new(srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
153
+ when /geometry\(Point/i
154
+ Type::Point.new(srid: srid, has_z: has_z, has_m: has_m)
155
+ when /geography\(LineString/i
156
+ Type::LineString.new(srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
157
+ when /geometry\(LineString/i
158
+ Type::LineString.new(srid: srid, has_z: has_z, has_m: has_m)
159
+ when /geography\(Polygon/i
160
+ Type::Polygon.new(srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
161
+ when /geometry\(Polygon/i
162
+ Type::Polygon.new(srid: srid, has_z: has_z, has_m: has_m)
163
+ when /geography\(MultiPoint/i
164
+ Type::MultiPoint.new(srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
165
+ when /geometry\(MultiPoint/i
166
+ Type::MultiPoint.new(srid: srid, has_z: has_z, has_m: has_m)
167
+ when /geography\(MultiLineString/i
168
+ Type::MultiLineString.new(srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
169
+ when /geometry\(MultiLineString/i
170
+ Type::MultiLineString.new(srid: srid, has_z: has_z, has_m: has_m)
171
+ when /geography\(MultiPolygon/i
172
+ Type::MultiPolygon.new(srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
173
+ when /geometry\(MultiPolygon/i
174
+ Type::MultiPolygon.new(srid: srid, has_z: has_z, has_m: has_m, geographic: false)
175
+ when /geography\(GeometryCollection/i
176
+ Type::GeometryCollection.new(srid: srid, has_z: has_z, has_m: has_m, geographic: geographic)
177
+ when /geometry\(GeometryCollection/i
178
+ Type::GeometryCollection.new(srid: srid, has_z: has_z, has_m: has_m)
179
+ when /geography/i
180
+ Type::Geography.new(srid: srid, has_z: has_z, has_m: has_m)
181
+ when /geometry/i
182
+ Type::Geometry.new(srid: srid, has_z: has_z, has_m: has_m)
183
+ else
184
+ Type::Geometry.new(srid: srid, has_z: has_z, has_m: has_m)
185
+ end
186
+ end
187
+
188
+ def extract_srid_from_sql(sql_type)
189
+ # Extract SRID from patterns like geometry(Point,3785) or geography(Point,4326)
190
+ match = sql_type.match(/,(\d+)\)/)
191
+ match ? match[1].to_i : 0
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # dependencies
4
+ require "active_support"
5
+ require "active_record"
6
+
7
+ # Initialize when PostgreSQL adapter is loaded
8
+ ActiveSupport.on_load(:active_record_postgresqladapter) do
9
+ require "rgeo-activerecord"
10
+ require_relative "active_record/connection_adapters/postgis"
11
+ ActiveRecord::ConnectionAdapters::PostGIS.initialize!
12
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rgeo"
4
+ require "rgeo/active_record/arel_spatial_queries"
5
+
6
+ module Arel
7
+ module Nodes
8
+ # Spatial node classes for different PostGIS functions
9
+ class SpatialNode < Binary
10
+ def initialize(left, right = nil)
11
+ super(left, right)
12
+ end
13
+ end
14
+
15
+ class SpatialDistance < SpatialNode; end
16
+ class SpatialLength < Unary; end
17
+ class SpatialContains < SpatialNode; end
18
+ class SpatialWithin < SpatialNode; end
19
+
20
+ # Wrapper for spatial values that need special handling
21
+ class SpatialValue < Node
22
+ attr_reader :value
23
+
24
+ def initialize(value)
25
+ @value = value
26
+ end
27
+
28
+ def st_distance(other)
29
+ SpatialDistance.new(self, other)
30
+ end
31
+
32
+ def st_contains(other)
33
+ SpatialContains.new(self, other)
34
+ end
35
+
36
+ def st_within(other)
37
+ SpatialWithin.new(self, other)
38
+ end
39
+ end
40
+ end
41
+
42
+ module Attributes
43
+ class Attribute
44
+ def st_distance(other)
45
+ Arel::Nodes::SpatialDistance.new(self, other)
46
+ end
47
+
48
+ def st_length
49
+ Arel::Nodes::SpatialLength.new(self)
50
+ end
51
+
52
+ def st_contains(other)
53
+ Arel::Nodes::SpatialContains.new(self, other)
54
+ end
55
+
56
+ def st_within(other)
57
+ Arel::Nodes::SpatialWithin.new(self, other)
58
+ end
59
+ end
60
+ end
61
+
62
+ # Add Arel.spatial() method
63
+ def self.spatial(value)
64
+ Arel::Nodes::SpatialValue.new(value)
65
+ end
66
+
67
+ module Visitors
68
+ class PostGIS < PostgreSQL
69
+ include RGeo::ActiveRecord::SpatialToSql
70
+
71
+ def visit_in_spatial_context(node, collector)
72
+ # Use ST_GeomFromEWKT for EWKT geometries
73
+ if node.is_a?(String) && node =~ /SRID=\d*;/
74
+ collector << "ST_GeomFromEWKT(#{quote(node)})"
75
+ else
76
+ super(node, collector) if defined?(super)
77
+ end
78
+ end
79
+
80
+ # Visitor methods for spatial nodes
81
+ def visit_Arel_Nodes_SpatialDistance(node, collector)
82
+ collector << "ST_Distance("
83
+ visit(node.left, collector)
84
+ collector << ", "
85
+ visit_spatial_operand(node.right, collector)
86
+ collector << ")"
87
+ end
88
+
89
+ def visit_Arel_Nodes_SpatialLength(node, collector)
90
+ collector << "ST_Length("
91
+ visit(node.expr, collector)
92
+ collector << ")"
93
+ end
94
+
95
+ def visit_Arel_Nodes_SpatialContains(node, collector)
96
+ collector << "ST_Contains("
97
+ visit(node.left, collector)
98
+ collector << ", "
99
+ visit_spatial_operand(node.right, collector)
100
+ collector << ")"
101
+ end
102
+
103
+ def visit_Arel_Nodes_SpatialWithin(node, collector)
104
+ collector << "ST_Within("
105
+ visit(node.left, collector)
106
+ collector << ", "
107
+ visit_spatial_operand(node.right, collector)
108
+ collector << ")"
109
+ end
110
+
111
+ def visit_Arel_Nodes_SpatialValue(node, collector)
112
+ visit_spatial_operand(node.value, collector)
113
+ end
114
+
115
+ private
116
+
117
+ def visit_spatial_operand(operand, collector)
118
+ case operand
119
+ when String
120
+ if operand =~ /SRID=\d*;/
121
+ collector << "ST_GeomFromEWKT(#{quote(operand)})"
122
+ else
123
+ collector << "ST_GeomFromText(#{quote(operand)})"
124
+ end
125
+ when RGeo::Feature::Instance
126
+ ewkt = if operand.srid && operand.srid != 0
127
+ "SRID=#{operand.srid};#{operand.as_text}"
128
+ else
129
+ operand.as_text
130
+ end
131
+ collector << "ST_GeomFromEWKT(#{quote(ewkt)})"
132
+ when RGeo::Cartesian::BoundingBox
133
+ # Convert bounding box to polygon WKT with SRID if available
134
+ wkt = "POLYGON((#{operand.min_x} #{operand.min_y}, #{operand.max_x} #{operand.min_y}, #{operand.max_x} #{operand.max_y}, #{operand.min_x} #{operand.max_y}, #{operand.min_x} #{operand.min_y}))"
135
+ if operand.respond_to?(:srid) && operand.srid && operand.srid != 0
136
+ ewkt = "SRID=#{operand.srid};#{wkt}"
137
+ collector << "ST_GeomFromEWKT(#{quote(ewkt)})"
138
+ else
139
+ collector << "ST_GeomFromText(#{quote(wkt)})"
140
+ end
141
+ else
142
+ visit(operand, collector)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end