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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +194 -0
- data/activerecord-postgis.gemspec +28 -0
- data/lib/active_record/connection_adapters/postgis/adapter_extensions.rb +85 -0
- data/lib/active_record/connection_adapters/postgis/column_extensions.rb +90 -0
- data/lib/active_record/connection_adapters/postgis/column_methods.rb +61 -0
- data/lib/active_record/connection_adapters/postgis/constants.rb +36 -0
- data/lib/active_record/connection_adapters/postgis/oid/spatial.rb +122 -0
- data/lib/active_record/connection_adapters/postgis/oid/spatial_types.rb +26 -0
- data/lib/active_record/connection_adapters/postgis/quoting.rb +35 -0
- data/lib/active_record/connection_adapters/postgis/schema_dumper.rb +94 -0
- data/lib/active_record/connection_adapters/postgis/schema_statements.rb +136 -0
- data/lib/active_record/connection_adapters/postgis/spatial_column_methods.rb +40 -0
- data/lib/active_record/connection_adapters/postgis/spatial_column_type.rb +173 -0
- data/lib/active_record/connection_adapters/postgis/table_definition.rb +144 -0
- data/lib/active_record/connection_adapters/postgis/type/geography.rb +21 -0
- data/lib/active_record/connection_adapters/postgis/type/geometry.rb +21 -0
- data/lib/active_record/connection_adapters/postgis/type/geometry_collection.rb +21 -0
- data/lib/active_record/connection_adapters/postgis/type/line_string.rb +21 -0
- data/lib/active_record/connection_adapters/postgis/type/multi_line_string.rb +21 -0
- data/lib/active_record/connection_adapters/postgis/type/multi_point.rb +21 -0
- data/lib/active_record/connection_adapters/postgis/type/multi_polygon.rb +21 -0
- data/lib/active_record/connection_adapters/postgis/type/point.rb +21 -0
- data/lib/active_record/connection_adapters/postgis/type/polygon.rb +21 -0
- data/lib/active_record/connection_adapters/postgis/type/spatial.rb +86 -0
- data/lib/active_record/connection_adapters/postgis/version.rb +9 -0
- data/lib/active_record/connection_adapters/postgis.rb +196 -0
- data/lib/activerecord-postgis.rb +12 -0
- data/lib/arel/visitors/postgis.rb +147 -0
- 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,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
|