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,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostGIS
6
+ module Quoting
7
+ def quote(value)
8
+ if value.is_a?(RGeo::Feature::Instance)
9
+ # Convert spatial objects to EWKT format for PostgreSQL
10
+ if value.srid && value.srid != 0
11
+ "'SRID=#{value.srid};#{value.as_text}'"
12
+ else
13
+ "'#{value.as_text}'"
14
+ end
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def type_cast(value)
21
+ if value.is_a?(RGeo::Feature::Instance)
22
+ # Convert spatial objects to EWKT string for parameter binding
23
+ if value.srid && value.srid != 0
24
+ "SRID=#{value.srid};#{value.as_text}"
25
+ else
26
+ value.as_text
27
+ end
28
+ else
29
+ super
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostGIS
6
+ module SchemaDumper
7
+ private
8
+
9
+ # Tell Rails these are valid column types that should use method syntax
10
+ def valid_column_spec?(column)
11
+ return true if column.sql_type =~ /^(geometry|geography)/i
12
+ super
13
+ end
14
+
15
+ def column_spec_for_primary_key(column)
16
+ return super unless column.sql_type =~ /^(geometry|geography)/i
17
+ spec = { id: column.name }
18
+ spec[:type] = schema_type(column).to_sym
19
+ extract_spatial_options(column, spec)
20
+ spec
21
+ end
22
+
23
+ def prepare_column_options(column)
24
+ spec = super
25
+
26
+ if column.sql_type =~ /^(geometry|geography)/i
27
+ extract_spatial_options(column, spec)
28
+ end
29
+
30
+ spec
31
+ end
32
+
33
+ # Override to return our spatial types
34
+ def schema_type(column)
35
+ if column.sql_type =~ /^(geometry|geography)/i
36
+ case column.sql_type
37
+ when /geography\(Point/i, /geometry\(Point/i
38
+ :st_point
39
+ when /geography\(LineString/i, /geometry\(LineString/i
40
+ :st_line_string
41
+ when /geography\(Polygon/i, /geometry\(Polygon/i
42
+ :st_polygon
43
+ when /geography\(MultiPoint/i, /geometry\(MultiPoint/i
44
+ :st_multi_point
45
+ when /geography\(MultiLineString/i, /geometry\(MultiLineString/i
46
+ :st_multi_line_string
47
+ when /geography\(MultiPolygon/i, /geometry\(MultiPolygon/i
48
+ :st_multi_polygon
49
+ when /geography\(GeometryCollection/i, /geometry\(GeometryCollection/i
50
+ :st_geometry_collection
51
+ when /geography/i
52
+ :st_geography
53
+ when /geometry/i
54
+ :st_geometry
55
+ end
56
+ else
57
+ super
58
+ end
59
+ end
60
+
61
+ def extract_spatial_options(column, spec)
62
+ if column.sql_type =~ /^(geometry|geography)\(([^,\)]+)(?:,(\d+))?\)/i
63
+ spatial_type = $1
64
+ geom_type = $2
65
+ srid = $3&.to_i
66
+
67
+ # Add SRID if not default
68
+ if srid && srid != default_srid(spatial_type)
69
+ spec[:srid] = srid
70
+ end
71
+
72
+ # Check for dimension modifiers
73
+ if geom_type =~ /^(\w+?)(Z|M|ZM)$/i
74
+ dimensions = $2.upcase
75
+ spec[:has_z] = true if dimensions.include?("Z")
76
+ spec[:has_m] = true if dimensions.include?("M")
77
+ end
78
+ end
79
+ end
80
+
81
+ def default_srid(spatial_type)
82
+ spatial_type.downcase == "geography" ? 4326 : 0
83
+ end
84
+ end
85
+ end
86
+
87
+ # Prepend to PostgreSQL's SchemaDumper
88
+ module PostgreSQL
89
+ class SchemaDumper
90
+ prepend PostGIS::SchemaDumper
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostGIS
6
+ module SchemaStatements
7
+ # Override to handle PostGIS column types
8
+ def columns(table_name)
9
+ column_definitions(table_name).map do |field|
10
+ new_column_from_field(table_name, field, @type_map)
11
+ end
12
+ end
13
+
14
+ # Override column type lookup to handle PostGIS types
15
+ def column_type_for(column_name)
16
+ column_name = column_name.to_s
17
+
18
+ # Check if it's a known PostGIS type
19
+ if column_name =~ /^(geometry|geography)/
20
+ # Extract the base type name from definitions like "geometry(Point,4326)"
21
+ base_type = case column_name
22
+ when /\(Point/i then :point
23
+ when /\(LineString/i then :line_string
24
+ when /\(Polygon/i then :polygon
25
+ when /\(MultiPoint/i then :multi_point
26
+ when /\(MultiLineString/i then :multi_line_string
27
+ when /\(MultiPolygon/i then :multi_polygon
28
+ when /\(GeometryCollection/i then :geometry_collection
29
+ when /^geography/i then :geography
30
+ when /^geometry/i then :geometry
31
+ else
32
+ nil
33
+ end
34
+
35
+ return base_type if base_type
36
+ end
37
+
38
+ super
39
+ end
40
+
41
+ private
42
+
43
+ # Override to handle PostGIS types in column fetching
44
+ def column_definitions(table_name)
45
+ query = <<~SQL
46
+ SELECT#{' '}
47
+ a.attname AS column_name,
48
+ format_type(a.atttypid, a.atttypmod) AS sql_type,
49
+ pg_get_expr(d.adbin, d.adrelid) AS column_default,
50
+ a.attnotnull AS not_null,
51
+ a.atttypid AS type_id,
52
+ a.atttypmod AS type_mod,
53
+ c.collname AS collation,
54
+ col_description(pg_class.oid, a.attnum) AS comment,
55
+ #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
56
+ #{supports_virtual_columns? ? 'attgenerated' : quote('')} AS attgenerated
57
+ FROM pg_attribute a
58
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
59
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
60
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid
61
+ JOIN pg_class ON pg_class.oid = a.attrelid
62
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
63
+ AND a.attnum > 0
64
+ AND NOT a.attisdropped
65
+ ORDER BY a.attnum
66
+ SQL
67
+
68
+ execute_and_clear(query, "SCHEMA", allow_retry: true, uses_transaction: false) do |result|
69
+ result.map do |row|
70
+ {
71
+ "column_name" => row["column_name"],
72
+ "sql_type" => row["sql_type"],
73
+ "type_id" => row["type_id"],
74
+ "type_mod" => row["type_mod"],
75
+ "column_default" => row["column_default"],
76
+ "is_nullable" => row["not_null"] == "f" ? "YES" : "NO",
77
+ "collation" => row["collation"],
78
+ "comment" => row["comment"],
79
+ "identity" => row["identity"],
80
+ "attgenerated" => row["attgenerated"]
81
+ }
82
+ end
83
+ end
84
+ end
85
+
86
+ # Create column from field data, handling PostGIS types
87
+ def new_column_from_field(table_name, field, type_map)
88
+ type_metadata = fetch_type_metadata(field["column_name"], field["sql_type"], field, type_map)
89
+ default_value = extract_value_from_default(field["column_default"])
90
+ default_function = extract_default_function(field["column_default"], default_value)
91
+
92
+ PostgreSQL::Column.new(
93
+ field["column_name"],
94
+ default_value,
95
+ type_metadata,
96
+ field["is_nullable"] == "YES",
97
+ default_function,
98
+ comment: field["comment"].presence,
99
+ identity: field["identity"].presence
100
+ )
101
+ end
102
+
103
+ # Fetch type metadata, handling PostGIS types
104
+ def fetch_type_metadata(column_name, sql_type, field, type_map)
105
+ cast_type = if sql_type =~ /^(geometry|geography)/i
106
+ # Handle PostGIS types
107
+ type_name = case sql_type
108
+ when /\(Point/i then :point
109
+ when /\(LineString/i then :line_string
110
+ when /\(Polygon/i then :polygon
111
+ when /\(MultiPoint/i then :multi_point
112
+ when /\(MultiLineString/i then :multi_line_string
113
+ when /\(MultiPolygon/i then :multi_polygon
114
+ when /\(GeometryCollection/i then :geometry_collection
115
+ when /^geography/i then :geography
116
+ when /^geometry/i then :geometry
117
+ end
118
+
119
+ lookup_cast_type(type_name.to_s)
120
+ else
121
+ type_map.fetch(field["type_id"].to_i, field["type_mod"].to_i) do
122
+ lookup_cast_type(sql_type)
123
+ end
124
+ end
125
+
126
+ simple_type = SqlTypeMetadata.new(
127
+ sql_type: sql_type,
128
+ type: cast_type
129
+ )
130
+
131
+ PostgreSQL::TypeMetadata.new(simple_type)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostGIS
6
+ module SpatialColumnMethods
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ define_column_methods(*GEOMETRIC_TYPES)
11
+ end
12
+
13
+ private
14
+ # We reuse existing column definition options:
15
+ # - limit: SRID value
16
+ # - precision: has_z flag
17
+ # - scale: true for geography, false/nil for geometry
18
+ # + standard options like null, default, comment, etc.
19
+ def validate_column_definition(column_type, options)
20
+ super
21
+
22
+ return unless GEOMETRIC_TYPES.include?(column_type.to_sym)
23
+
24
+ if options[:limit] # SRID validation
25
+ srid = options[:limit]
26
+ if options[:scale] # geography type
27
+ unless srid == 4326
28
+ raise ArgumentError, "Geography columns only support SRID 4326. Got: #{srid}"
29
+ end
30
+ else # geometry type
31
+ unless srid.is_a?(Integer) && srid >= 0 && srid <= 999999
32
+ raise ArgumentError, "SRID must be between 0 and 999999. Got: #{srid}"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/postgresql_adapter"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module PostgreSQL
8
+ SchemaCreation.class_eval do
9
+ private
10
+
11
+ def visit_ColumnDefinition(o)
12
+ if o.type.to_s =~ /^st_/
13
+ # Parse options from limit if it's our custom format
14
+ srid = nil
15
+ has_z = false
16
+ has_m = false
17
+ geographic = false
18
+
19
+ # Check if we have spatial SQL string from table definition
20
+ if o.options[:spatial_sql] && o.options[:spatial_sql].include?(",")
21
+ geo_part, srid_part = o.options[:spatial_sql].split(",", 2)
22
+ srid = srid_part.to_i if srid_part && !srid_part.empty?
23
+ if geo_part
24
+ # Check for Z and M dimensions at the end of the geometry type
25
+ has_z = geo_part.end_with?("Z") || geo_part.end_with?("ZM")
26
+ has_m = geo_part.end_with?("M") || geo_part.end_with?("ZM")
27
+ end
28
+ elsif o.limit.is_a?(String) && o.limit.include?(",")
29
+ # Fallback to parsing limit string (for schema loading)
30
+ geo_part, srid_part = o.limit.split(",", 2)
31
+ srid = srid_part.to_i if srid_part && !srid_part.empty?
32
+ if geo_part
33
+ has_z = geo_part.end_with?("Z") || geo_part.end_with?("ZM")
34
+ has_m = geo_part.end_with?("M") || geo_part.end_with?("ZM")
35
+ end
36
+ else
37
+ # Use individual column options if available
38
+ srid = o.options[:srid] if o.options.key?(:srid)
39
+ has_z = o.options[:has_z] if o.options.key?(:has_z)
40
+ has_m = o.options[:has_m] if o.options.key?(:has_m)
41
+ geographic = o.options[:geographic] if o.options.key?(:geographic)
42
+ end
43
+
44
+ sql_type = type_to_sql(o.type.to_sym,
45
+ limit: o.options[:spatial_sql] || o.limit,
46
+ precision: o.precision,
47
+ scale: o.scale,
48
+ srid: srid,
49
+ geographic: geographic,
50
+ has_z: has_z,
51
+ has_m: has_m,
52
+ geo_part: geo_part)
53
+ column_sql = "#{quote_column_name(o.name)} #{sql_type}"
54
+ add_column_options!(column_sql, column_options(o))
55
+ column_sql
56
+ else
57
+ super
58
+ end
59
+ end
60
+
61
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, geographic: false, srid: nil, has_z: false, has_m: false, geo_part: nil, **options)
62
+ if type.to_s =~ /^st_/
63
+ # Use geo_part if available (from limit parsing), otherwise derive from type
64
+ if geo_part && geo_part != "GEOGRAPHY" && geo_part != "GEOMETRY"
65
+ # Convert from PostGIS SQL format (e.g., MULTIPOLYGON) to our internal format (e.g., multi_polygon)
66
+ # First strip any dimension suffixes (Z, M, ZM)
67
+ base_geo_part = geo_part.upcase.gsub(/[ZM]+$/, "")
68
+ geometric_type = case base_geo_part
69
+ when "MULTIPOINT" then "multi_point"
70
+ when "MULTILINESTRING" then "multi_line_string"
71
+ when "MULTIPOLYGON" then "multi_polygon"
72
+ when "GEOMETRYCOLLECTION" then "geometry_collection"
73
+ when "LINESTRING" then "line_string"
74
+ else
75
+ base_geo_part.downcase
76
+ end
77
+ else
78
+ geometric_type = type.to_s.sub(/^st_/, "")
79
+ end
80
+ # Only use geography if explicitly requested via type or option
81
+ is_geography = type.to_s == "st_geography" || geographic == true
82
+ result = PostGIS::SpatialColumnType.new(
83
+ geometric_type,
84
+ srid,
85
+ has_z: has_z,
86
+ has_m: has_m,
87
+ geography: is_geography
88
+ ).to_sql
89
+ result
90
+ else
91
+ super(type, limit: limit, precision: precision, scale: scale, **options)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ module PostGIS
98
+ class SpatialColumnType
99
+ VALID_TYPES = %w[
100
+ point line_string polygon multi_point
101
+ multi_line_string multi_polygon geometry_collection geography geometry
102
+ ].freeze
103
+
104
+ attr_reader :type, :srid, :has_z, :has_m, :geography
105
+
106
+ def initialize(type, srid = nil, has_z: false, has_m: false, geography: false)
107
+ @type = type.to_s.downcase
108
+ @srid = srid
109
+ @has_z = has_z
110
+ @has_m = has_m
111
+ @geography = geography || @type == "geography"
112
+
113
+ validate_type!
114
+ validate_srid!
115
+ validate_dimensions!
116
+ end
117
+
118
+ def to_sql
119
+ base_type = @geography ? "geography" : "geometry"
120
+ return base_type if @type == "geography"
121
+
122
+ type_with_dimensions = build_type_with_dimensions
123
+ # Include SRID if specified and not the default for the type
124
+ # Geography defaults to 4326, geometry defaults to 0
125
+ should_include_srid = @srid &&
126
+ ((@geography && @srid != 4326) || (!@geography && @srid != 0))
127
+
128
+ if should_include_srid
129
+ "#{base_type}(#{type_with_dimensions},#{@srid})"
130
+ else
131
+ "#{base_type}(#{type_with_dimensions})"
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def validate_type!
138
+ unless VALID_TYPES.include?(@type)
139
+ raise ArgumentError, "Invalid geometry type: #{@type}. Valid types are: #{VALID_TYPES.join(', ')}"
140
+ end
141
+ end
142
+
143
+ def validate_srid!
144
+ return unless @srid
145
+
146
+ if @geography && @srid != 4326
147
+ raise ArgumentError, "Invalid SRID for geography type: #{@srid}. The SRID must be 4326 or nil."
148
+ end
149
+
150
+ unless @srid.is_a?(Integer) && @srid >= 0 && @srid <= 999_999
151
+ raise ArgumentError, "Invalid SRID #{@srid}. The SRID must be within the range 0-999999."
152
+ end
153
+ end
154
+
155
+ def validate_dimensions!
156
+ # All geometry types can have Z, M, or ZM dimensions
157
+ end
158
+
159
+ def build_type_with_dimensions
160
+ type_name = @type.camelize
161
+ if @has_z && @has_m
162
+ type_name += "ZM"
163
+ elsif @has_z
164
+ type_name += "Z"
165
+ elsif @has_m
166
+ type_name += "M"
167
+ end
168
+ type_name
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostGIS
6
+ module TableDefinition
7
+ # Define spatial column methods
8
+ %i[st_point st_line_string st_polygon st_multi_point
9
+ st_multi_line_string st_multi_polygon st_geometry_collection
10
+ st_geometry st_geography].each do |spatial_type|
11
+ define_method(spatial_type) do |name, **options|
12
+ column(name, spatial_type, **options)
13
+ end
14
+ end
15
+
16
+ # Override new_column_definition to handle spatial column options
17
+ def new_column_definition(name, type, **options)
18
+ col_type = if type.to_sym == :virtual
19
+ options[:type]
20
+ else
21
+ type
22
+ end
23
+
24
+ if spatial_column_type?(col_type)
25
+ if (limit = options.delete(:limit)) && limit.is_a?(::Hash)
26
+ options.merge!(limit)
27
+ end
28
+
29
+ # Set geographic option for geography types
30
+ if col_type.to_sym == :st_geography || col_type.to_sym == :geography
31
+ options[:geographic] = true
32
+ end
33
+
34
+ geo_type = ColumnDefinitionUtils.geo_type(options[:type] || type)
35
+ base_type = determine_base_type(col_type, options)
36
+
37
+
38
+ # Create hash format limit for column metadata
39
+ spatial_limit = {}
40
+ spatial_limit[:type] = col_type.to_s
41
+ spatial_limit[:srid] = options[:srid] if options[:srid] && options[:srid] != (options[:geographic] ? 4326 : 0)
42
+ spatial_limit[:has_z] = options[:has_z] if options[:has_z]
43
+ spatial_limit[:has_m] = options[:has_m] if options[:has_m]
44
+ spatial_limit[:geographic] = options[:geographic] if options[:geographic]
45
+
46
+ # Use hash format as limit for spatial columns, string format for SQL generation
47
+ options[:limit] = spatial_limit.empty? ? nil : spatial_limit
48
+ options[:spatial_type] = geo_type
49
+ options[:spatial_sql] = ColumnDefinitionUtils.limit_from_options(geo_type, options)
50
+
51
+
52
+ column = super(name, base_type, **options)
53
+ else
54
+ column = super(name, type, **options)
55
+ end
56
+
57
+ column
58
+ end
59
+
60
+ private
61
+
62
+ # Allow spatial-specific options in column definitions
63
+ def valid_column_definition_options
64
+ super + [ :srid, :has_z, :has_m, :geographic, :spatial_type, :spatial_sql ]
65
+ end
66
+
67
+ def spatial_column_type?(type)
68
+ type.to_s.start_with?("st_") ||
69
+ [ :geography, :geometry, :geometry_collection, :line_string,
70
+ :multi_line_string, :multi_point, :multi_polygon, :polygon ].include?(type.to_sym)
71
+ end
72
+
73
+ def determine_base_type(col_type, options)
74
+ case col_type.to_sym
75
+ when :st_geography, :geography
76
+ :st_geography
77
+ else
78
+ # Only use geography if explicitly requested
79
+ if options[:geographic] == true
80
+ :st_geography
81
+ else
82
+ # Convert legacy types to st_ prefixed equivalents
83
+ convert_to_st_type(col_type)
84
+ end
85
+ end
86
+ end
87
+
88
+ def convert_to_st_type(col_type)
89
+ case col_type.to_sym
90
+ when :geometry then :st_geometry
91
+ when :geometry_collection then :st_geometry_collection
92
+ when :line_string then :st_line_string
93
+ when :multi_line_string then :st_multi_line_string
94
+ when :multi_point then :st_multi_point
95
+ when :multi_polygon then :st_multi_polygon
96
+ when :polygon then :st_polygon
97
+ else
98
+ # Already an st_ type or unknown type
99
+ col_type.to_sym
100
+ end
101
+ end
102
+ end
103
+
104
+ # Custom table definition class that includes spatial support
105
+ class SpatialTableDefinition < ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition
106
+ include TableDefinition
107
+ end
108
+
109
+ module ColumnDefinitionUtils
110
+ class << self
111
+ def geo_type(type = "GEOMETRY")
112
+ g_type = type.to_s.delete("_").upcase
113
+ case g_type
114
+ when "STPOINT" then "POINT"
115
+ when "STPOLYGON" then "POLYGON"
116
+ when "STLINESTRING" then "LINESTRING"
117
+ when "STMULTIPOINT" then "MULTIPOINT"
118
+ when "STMULTILINESTRING" then "MULTILINESTRING"
119
+ when "STMULTIPOLYGON" then "MULTIPOLYGON"
120
+ when "STGEOMETRYCOLLECTION" then "GEOMETRYCOLLECTION"
121
+ when "STGEOMETRY" then "GEOMETRY"
122
+ when "STGEOGRAPHY" then "GEOGRAPHY"
123
+ else
124
+ "GEOMETRY" # Default fallback
125
+ end
126
+ end
127
+
128
+ def limit_from_options(type, options = {})
129
+ has_z = options[:has_z] ? "Z" : ""
130
+ has_m = options[:has_m] ? "M" : ""
131
+ srid = options[:srid] || default_srid(options)
132
+ field_type = [ type, has_z, has_m ].compact.join
133
+ "#{field_type},#{srid}"
134
+ end
135
+
136
+ def default_srid(options)
137
+ # Geography columns default to SRID 4326, geometry columns to 0
138
+ options[:geographic] ? 4326 : 0
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ 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 Geography < Spatial
10
+ def initialize(srid: 4326, has_z: false, has_m: false)
11
+ super(geo_type: "geography", srid: srid, has_z: has_z, has_m: has_m, geographic: true)
12
+ end
13
+
14
+ def type
15
+ :st_geography
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 Geometry < Spatial
10
+ def initialize(srid: 0, has_z: false, has_m: false)
11
+ super(geo_type: "geometry", srid: srid, has_z: has_z, has_m: has_m)
12
+ end
13
+
14
+ def type
15
+ :st_geometry
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 GeometryCollection < Spatial
10
+ def initialize(srid: 0, has_z: false, has_m: false)
11
+ super(geo_type: "geometry_collection", srid: srid, has_z: has_z, has_m: has_m)
12
+ end
13
+
14
+ def type
15
+ :st_geometry_collection
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end