rs_spatial_adapter 1.2.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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +197 -0
- data/VERSION +1 -0
- data/lib/spatial_adapter.rb +41 -0
- data/lib/spatial_adapter/common/raw_geom_info.rb +23 -0
- data/lib/spatial_adapter/common/schema_definitions.rb +11 -0
- data/lib/spatial_adapter/common/schema_dumper.rb +136 -0
- data/lib/spatial_adapter/common/spatial_column.rb +70 -0
- data/lib/spatial_adapter/common/table_definition.rb +14 -0
- data/lib/spatial_adapter/mysql.rb +98 -0
- data/lib/spatial_adapter/mysql2.rb +97 -0
- data/lib/spatial_adapter/postgresql.rb +388 -0
- data/lib/spatial_adapter/railtie.rb +13 -0
- data/rails/init.rb +11 -0
- data/spec/README.txt +22 -0
- data/spec/db/mysql2_raw.rb +70 -0
- data/spec/db/mysql_raw.rb +70 -0
- data/spec/db/postgis_raw.rb +190 -0
- data/spec/models/common.rb +65 -0
- data/spec/mysql/connection_adapter_spec.rb +106 -0
- data/spec/mysql/migration_spec.rb +64 -0
- data/spec/mysql/models_spec.rb +65 -0
- data/spec/mysql/schema_dumper_spec.rb +56 -0
- data/spec/mysql2/connection_adapter_spec.rb +106 -0
- data/spec/mysql2/migration_spec.rb +64 -0
- data/spec/mysql2/models_spec.rb +65 -0
- data/spec/mysql2/schema_dumper_spec.rb +56 -0
- data/spec/postgresql/connection_adapter_spec.rb +227 -0
- data/spec/postgresql/migration_spec.rb +365 -0
- data/spec/postgresql/models_spec.rb +221 -0
- data/spec/postgresql/schema_dumper_spec.rb +79 -0
- data/spec/shared_examples.rb +43 -0
- data/spec/spec_helper.rb +90 -0
- metadata +131 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
include SpatialAdapter
|
2
|
+
|
3
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do
|
4
|
+
SpatialAdapter.geometry_data_types.keys.each do |column_type|
|
5
|
+
class_eval <<-EOV
|
6
|
+
def #{column_type}(*args)
|
7
|
+
options = args.extract_options!
|
8
|
+
column_names = args
|
9
|
+
|
10
|
+
column_names.each { |name| column(name, '#{column_type}', options) }
|
11
|
+
end
|
12
|
+
EOV
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spatial_adapter'
|
2
|
+
require 'active_record/connection_adapters/mysql_adapter'
|
3
|
+
|
4
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
|
5
|
+
include SpatialAdapter
|
6
|
+
|
7
|
+
def supports_geographic?
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
alias :original_native_database_types :native_database_types
|
12
|
+
def native_database_types
|
13
|
+
original_native_database_types.merge!(geometry_data_types)
|
14
|
+
end
|
15
|
+
|
16
|
+
alias :original_quote :quote
|
17
|
+
#Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods
|
18
|
+
def quote(value, column = nil)
|
19
|
+
if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
|
20
|
+
"GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})"
|
21
|
+
else
|
22
|
+
original_quote(value,column)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#Redefinition of columns to add the information that a column is geometric
|
27
|
+
def columns(table_name, name = nil)#:nodoc:
|
28
|
+
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
29
|
+
columns = []
|
30
|
+
result = execute(sql, name)
|
31
|
+
result.each do |field|
|
32
|
+
klass = field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i ? ActiveRecord::ConnectionAdapters::SpatialMysqlColumn : ActiveRecord::ConnectionAdapters::MysqlColumn
|
33
|
+
columns << klass.new(field[0], field[4], field[1], field[2] == "YES")
|
34
|
+
end
|
35
|
+
result.free
|
36
|
+
columns
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
#operations relative to migrations
|
41
|
+
|
42
|
+
#Redefines add_index to support the case where the index is spatial
|
43
|
+
#If the :spatial key in the options table is true, then the sql string for a spatial index is created
|
44
|
+
def add_index(table_name,column_name,options = {})
|
45
|
+
index_name = options[:name] || index_name(table_name,:column => Array(column_name))
|
46
|
+
|
47
|
+
if options[:spatial]
|
48
|
+
execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#Check the nature of the index : If it is SPATIAL, it is indicated in the IndexDefinition object (redefined to add the spatial flag in spatial_adapter_common.rb)
|
55
|
+
def indexes(table_name, name = nil)#:nodoc:
|
56
|
+
indexes = []
|
57
|
+
current_index = nil
|
58
|
+
execute("SHOW KEYS FROM #{table_name}", name).each do |row|
|
59
|
+
if current_index != row[2]
|
60
|
+
next if row[2] == "PRIMARY" # skip the primary key
|
61
|
+
current_index = row[2]
|
62
|
+
indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL")
|
63
|
+
end
|
64
|
+
indexes.last.columns << row[4]
|
65
|
+
end
|
66
|
+
indexes
|
67
|
+
end
|
68
|
+
|
69
|
+
#Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here.
|
70
|
+
def options_for(table)
|
71
|
+
result = execute("show table status like '#{table}'")
|
72
|
+
engine = result.fetch_row[1]
|
73
|
+
if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration
|
74
|
+
"ENGINE=#{engine}"
|
75
|
+
else
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
module ActiveRecord
|
83
|
+
module ConnectionAdapters
|
84
|
+
class SpatialMysqlColumn < MysqlColumn
|
85
|
+
include SpatialAdapter::SpatialColumn
|
86
|
+
|
87
|
+
#MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions.
|
88
|
+
def self.string_to_geometry(string)
|
89
|
+
return string unless string.is_a?(String)
|
90
|
+
begin
|
91
|
+
GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1])
|
92
|
+
rescue Exception => exception
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spatial_adapter'
|
2
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
3
|
+
|
4
|
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
|
5
|
+
include SpatialAdapter
|
6
|
+
|
7
|
+
def supports_geographic?
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
alias :original_native_database_types :native_database_types
|
12
|
+
def native_database_types
|
13
|
+
original_native_database_types.merge!(geometry_data_types)
|
14
|
+
end
|
15
|
+
|
16
|
+
alias :original_quote :quote
|
17
|
+
#Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods
|
18
|
+
def quote(value, column = nil)
|
19
|
+
if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
|
20
|
+
"GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})"
|
21
|
+
else
|
22
|
+
original_quote(value,column)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#Redefinition of columns to add the information that a column is geometric
|
27
|
+
def columns(table_name, name = nil)#:nodoc:
|
28
|
+
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
29
|
+
columns = []
|
30
|
+
result = execute(sql, name)
|
31
|
+
result.each do |field|
|
32
|
+
klass = field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i ? ActiveRecord::ConnectionAdapters::SpatialMysql2Column : ActiveRecord::ConnectionAdapters::Mysql2Column
|
33
|
+
columns << klass.new(field[0], field[4], field[1], field[2] == "YES")
|
34
|
+
end
|
35
|
+
columns
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
#operations relative to migrations
|
40
|
+
|
41
|
+
#Redefines add_index to support the case where the index is spatial
|
42
|
+
#If the :spatial key in the options table is true, then the sql string for a spatial index is created
|
43
|
+
def add_index(table_name,column_name,options = {})
|
44
|
+
index_name = options[:name] || index_name(table_name,:column => Array(column_name))
|
45
|
+
|
46
|
+
if options[:spatial]
|
47
|
+
execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#Check the nature of the index : If it is SPATIAL, it is indicated in the IndexDefinition object (redefined to add the spatial flag in spatial_adapter_common.rb)
|
54
|
+
def indexes(table_name, name = nil)#:nodoc:
|
55
|
+
indexes = []
|
56
|
+
current_index = nil
|
57
|
+
(execute("SHOW KEYS FROM #{table_name}", name) || []).each do |row|
|
58
|
+
if current_index != row[2]
|
59
|
+
next if row[2] == "PRIMARY" # skip the primary key
|
60
|
+
current_index = row[2]
|
61
|
+
indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL")
|
62
|
+
end
|
63
|
+
indexes.last.columns << row[4]
|
64
|
+
end
|
65
|
+
indexes
|
66
|
+
end
|
67
|
+
|
68
|
+
#Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here.
|
69
|
+
def options_for(table)
|
70
|
+
result = execute("show table status like '#{table}'")
|
71
|
+
engine = result.first[1]
|
72
|
+
if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration
|
73
|
+
"ENGINE=#{engine}"
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
module ActiveRecord
|
82
|
+
module ConnectionAdapters
|
83
|
+
class SpatialMysql2Column < Mysql2Column
|
84
|
+
include SpatialAdapter::SpatialColumn
|
85
|
+
|
86
|
+
#MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions.
|
87
|
+
def self.string_to_geometry(string)
|
88
|
+
return string unless string.is_a?(String)
|
89
|
+
begin
|
90
|
+
GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1])
|
91
|
+
rescue Exception => exception
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,388 @@
|
|
1
|
+
require 'spatial_adapter'
|
2
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
3
|
+
|
4
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
5
|
+
include SpatialAdapter
|
6
|
+
|
7
|
+
def postgis_version
|
8
|
+
begin
|
9
|
+
select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0]
|
10
|
+
rescue ActiveRecord::StatementInvalid
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def postgis_major_version
|
16
|
+
version = postgis_version
|
17
|
+
version ? version.scan(/^(\d)\.\d\.\d$/)[0][0].to_i : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def postgis_minor_version
|
21
|
+
version = postgis_version
|
22
|
+
version ? version.scan(/^\d\.(\d)\.\d$/)[0][0].to_i : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def spatial?
|
26
|
+
!postgis_version.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def supports_geographic?
|
30
|
+
postgis_major_version > 1 || (postgis_major_version == 1 && postgis_minor_version >= 5)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias :original_native_database_types :native_database_types
|
34
|
+
def native_database_types
|
35
|
+
original_native_database_types.merge!(geometry_data_types)
|
36
|
+
end
|
37
|
+
|
38
|
+
alias :original_quote :quote
|
39
|
+
#Redefines the quote method to add behaviour for when a Geometry is encountered
|
40
|
+
def quote(value, column = nil)
|
41
|
+
if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
|
42
|
+
"'#{value.as_hex_ewkb}'"
|
43
|
+
else
|
44
|
+
original_quote(value,column)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def columns(table_name, name = nil) #:nodoc:
|
49
|
+
raw_geom_infos = column_spatial_info(table_name)
|
50
|
+
|
51
|
+
column_definitions(table_name).collect do |name, type, default, notnull|
|
52
|
+
case type
|
53
|
+
when /geography/i
|
54
|
+
ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_from_geography(name, default, type, notnull == 'f')
|
55
|
+
when /geometry/i
|
56
|
+
raw_geom_info = raw_geom_infos[name]
|
57
|
+
if raw_geom_info.nil?
|
58
|
+
# This column isn't in the geometry_columns table, so we don't know anything else about it
|
59
|
+
ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_simplified(name, default, notnull == "f")
|
60
|
+
else
|
61
|
+
ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.new(name, default, raw_geom_info.type, notnull == "f", raw_geom_info.srid, raw_geom_info.with_z, raw_geom_info.with_m)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(name, default, type, notnull == "f")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_table(table_name, options = {})
|
70
|
+
# Using the subclassed table definition
|
71
|
+
table_definition = ActiveRecord::ConnectionAdapters::PostgreSQLTableDefinition.new(self)
|
72
|
+
table_definition.primary_key(options[:primary_key] || ActiveRecord::Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
|
73
|
+
|
74
|
+
yield table_definition if block_given?
|
75
|
+
|
76
|
+
if options[:force] && table_exists?(table_name)
|
77
|
+
drop_table(table_name, options)
|
78
|
+
end
|
79
|
+
|
80
|
+
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
81
|
+
create_sql << "#{quote_table_name(table_name)} ("
|
82
|
+
create_sql << table_definition.to_sql
|
83
|
+
create_sql << ") #{options[:options]}"
|
84
|
+
|
85
|
+
# This is the additional portion for PostGIS
|
86
|
+
unless table_definition.geom_columns.nil?
|
87
|
+
table_definition.geom_columns.each do |geom_column|
|
88
|
+
geom_column.table_name = table_name
|
89
|
+
create_sql << "; " + geom_column.to_sql
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
execute create_sql
|
94
|
+
end
|
95
|
+
|
96
|
+
alias :original_remove_column :remove_column
|
97
|
+
def remove_column(table_name, *column_names)
|
98
|
+
column_names = column_names.flatten
|
99
|
+
columns(table_name).each do |col|
|
100
|
+
if column_names.include?(col.name.to_sym)
|
101
|
+
# Geometry columns have to be removed using DropGeometryColumn
|
102
|
+
if col.is_a?(SpatialColumn) && col.spatial? && !col.geographic?
|
103
|
+
execute "SELECT DropGeometryColumn('#{table_name}','#{col.name}')"
|
104
|
+
else
|
105
|
+
original_remove_column(table_name, col.name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
alias :original_add_column :add_column
|
112
|
+
def add_column(table_name, column_name, type, options = {})
|
113
|
+
unless geometry_data_types[type].nil?
|
114
|
+
geom_column = ActiveRecord::ConnectionAdapters::PostgreSQLColumnDefinition.new(self, column_name, type, nil, nil, options[:null], options[:srid] || -1 , options[:with_z] || false , options[:with_m] || false, options[:geographic] || false)
|
115
|
+
if geom_column.geographic
|
116
|
+
default = options[:default]
|
117
|
+
notnull = options[:null] == false
|
118
|
+
|
119
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{geom_column.to_sql}")
|
120
|
+
|
121
|
+
change_column_default(table_name, column_name, default) if options_include_default?(options)
|
122
|
+
change_column_null(table_name, column_name, false, default) if notnull
|
123
|
+
else
|
124
|
+
geom_column.table_name = table_name
|
125
|
+
execute geom_column.to_sql
|
126
|
+
end
|
127
|
+
else
|
128
|
+
original_add_column(table_name, column_name, type, options)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Adds an index to a column.
|
133
|
+
def add_index(table_name, column_name, options = {})
|
134
|
+
column_names = Array(column_name)
|
135
|
+
index_name = index_name(table_name, :column => column_names)
|
136
|
+
|
137
|
+
if Hash === options # legacy support, since this param was a string
|
138
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
139
|
+
index_name = options[:name] || index_name
|
140
|
+
index_method = options[:spatial] ? 'USING GIST' : ""
|
141
|
+
else
|
142
|
+
index_type = options
|
143
|
+
end
|
144
|
+
quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
|
145
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_method} (#{quoted_column_names})"
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the list of all indexes for a table.
|
149
|
+
#
|
150
|
+
# This is a full replacement for the ActiveRecord method and as a result
|
151
|
+
# has a higher probability of breaking in future releases.
|
152
|
+
def indexes(table_name, name = nil)
|
153
|
+
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
154
|
+
|
155
|
+
# Changed from upstread: link to pg_am to grab the index type (e.g. "gist")
|
156
|
+
result = query(<<-SQL, name)
|
157
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, t.oid, am.amname
|
158
|
+
FROM pg_class t, pg_class i, pg_index d, pg_attribute a, pg_am am
|
159
|
+
WHERE i.relkind = 'i'
|
160
|
+
AND d.indexrelid = i.oid
|
161
|
+
AND d.indisprimary = 'f'
|
162
|
+
AND t.oid = d.indrelid
|
163
|
+
AND t.relname = '#{table_name}'
|
164
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
|
165
|
+
AND i.relam = am.oid
|
166
|
+
AND a.attrelid = t.oid
|
167
|
+
ORDER BY i.relname
|
168
|
+
SQL
|
169
|
+
|
170
|
+
|
171
|
+
indexes = []
|
172
|
+
|
173
|
+
indexes = result.map do |row|
|
174
|
+
index_name = row[0]
|
175
|
+
unique = row[1] == 't'
|
176
|
+
indkey = row[2].split(" ")
|
177
|
+
oid = row[3]
|
178
|
+
indtype = row[4]
|
179
|
+
|
180
|
+
# Changed from upstream: need to get the column types to test for spatial indexes
|
181
|
+
columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = [r[0], r[2]]; attlist}
|
182
|
+
SELECT a.attname, a.attnum, t.typname
|
183
|
+
FROM pg_attribute a, pg_type t
|
184
|
+
WHERE a.attrelid = #{oid}
|
185
|
+
AND a.attnum IN (#{indkey.join(",")})
|
186
|
+
AND a.atttypid = t.oid
|
187
|
+
SQL
|
188
|
+
|
189
|
+
# Only GiST indexes on spatial columns denote a spatial index
|
190
|
+
spatial = indtype == 'gist' && columns.size == 1 && (columns.values.first[1] == 'geometry' || columns.values.first[1] == 'geography')
|
191
|
+
|
192
|
+
column_names = indkey.map {|attnum| columns[attnum] ? columns[attnum][0] : nil }
|
193
|
+
ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, spatial)
|
194
|
+
end
|
195
|
+
|
196
|
+
indexes
|
197
|
+
end
|
198
|
+
|
199
|
+
def disable_referential_integrity(&block) #:nodoc:
|
200
|
+
if supports_disable_referential_integrity?() then
|
201
|
+
execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
202
|
+
end
|
203
|
+
yield
|
204
|
+
ensure
|
205
|
+
if supports_disable_referential_integrity?() then
|
206
|
+
execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
|
212
|
+
def tables_without_postgis
|
213
|
+
tables - %w{ geometry_columns spatial_ref_sys }
|
214
|
+
end
|
215
|
+
|
216
|
+
def column_spatial_info(table_name)
|
217
|
+
constr = query("SELECT * FROM geometry_columns WHERE f_table_name = '#{table_name}'")
|
218
|
+
|
219
|
+
raw_geom_infos = {}
|
220
|
+
constr.each do |constr_def_a|
|
221
|
+
raw_geom_infos[constr_def_a[3]] ||= SpatialAdapter::RawGeomInfo.new
|
222
|
+
raw_geom_infos[constr_def_a[3]].type = constr_def_a[6]
|
223
|
+
raw_geom_infos[constr_def_a[3]].dimension = constr_def_a[4].to_i
|
224
|
+
raw_geom_infos[constr_def_a[3]].srid = constr_def_a[5].to_i
|
225
|
+
|
226
|
+
if raw_geom_infos[constr_def_a[3]].type[-1] == ?M
|
227
|
+
raw_geom_infos[constr_def_a[3]].with_m = true
|
228
|
+
raw_geom_infos[constr_def_a[3]].type.chop!
|
229
|
+
else
|
230
|
+
raw_geom_infos[constr_def_a[3]].with_m = false
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
raw_geom_infos.each_value do |raw_geom_info|
|
235
|
+
#check the presence of z and m
|
236
|
+
raw_geom_info.convert!
|
237
|
+
end
|
238
|
+
|
239
|
+
raw_geom_infos
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
module ActiveRecord
|
245
|
+
module ConnectionAdapters
|
246
|
+
class PostgreSQLTableDefinition < TableDefinition
|
247
|
+
attr_reader :geom_columns
|
248
|
+
|
249
|
+
def column(name, type, options = {})
|
250
|
+
unless (@base.geometry_data_types[type.to_sym].nil? or
|
251
|
+
(options[:create_using_addgeometrycolumn] == false))
|
252
|
+
|
253
|
+
column = self[name] || PostgreSQLColumnDefinition.new(@base, name, type)
|
254
|
+
column.null = options[:null]
|
255
|
+
column.srid = options[:srid] || -1
|
256
|
+
column.with_z = options[:with_z] || false
|
257
|
+
column.with_m = options[:with_m] || false
|
258
|
+
column.geographic = options[:geographic] || false
|
259
|
+
|
260
|
+
if column.geographic
|
261
|
+
@columns << column unless @columns.include? column
|
262
|
+
else
|
263
|
+
# Hold this column for later
|
264
|
+
@geom_columns ||= []
|
265
|
+
@geom_columns << column
|
266
|
+
end
|
267
|
+
self
|
268
|
+
else
|
269
|
+
super(name, type, options)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
class PostgreSQLColumnDefinition < ColumnDefinition
|
275
|
+
attr_accessor :table_name
|
276
|
+
attr_accessor :srid, :with_z, :with_m, :geographic
|
277
|
+
attr_reader :spatial
|
278
|
+
|
279
|
+
def initialize(base = nil, name = nil, type=nil, limit=nil, default=nil, null=nil, srid=-1, with_z=false, with_m=false, geographic=false)
|
280
|
+
super(base, name, type, limit, default, null)
|
281
|
+
@table_name = nil
|
282
|
+
@spatial = true
|
283
|
+
@srid = srid
|
284
|
+
@with_z = with_z
|
285
|
+
@with_m = with_m
|
286
|
+
@geographic = geographic
|
287
|
+
end
|
288
|
+
|
289
|
+
def sql_type
|
290
|
+
if geographic
|
291
|
+
type_sql = base.geometry_data_types[type.to_sym][:name]
|
292
|
+
type_sql += "Z" if with_z
|
293
|
+
type_sql += "M" if with_m
|
294
|
+
# SRID is not yet supported (defaults to 4326)
|
295
|
+
#type_sql += ", #{srid}" if (srid && srid != -1)
|
296
|
+
type_sql = "geography(#{type_sql})"
|
297
|
+
type_sql
|
298
|
+
else
|
299
|
+
super
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def to_sql
|
304
|
+
if spatial && !geographic
|
305
|
+
type_sql = base.geometry_data_types[type.to_sym][:name]
|
306
|
+
type_sql += "M" if with_m and !with_z
|
307
|
+
if with_m and with_z
|
308
|
+
dimension = 4
|
309
|
+
elsif with_m or with_z
|
310
|
+
dimension = 3
|
311
|
+
else
|
312
|
+
dimension = 2
|
313
|
+
end
|
314
|
+
|
315
|
+
column_sql = "SELECT AddGeometryColumn('#{table_name}','#{name}',#{srid},'#{type_sql}',#{dimension})"
|
316
|
+
column_sql += ";ALTER TABLE #{table_name} ALTER #{name} SET NOT NULL" if null == false
|
317
|
+
column_sql
|
318
|
+
else
|
319
|
+
super
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
module ActiveRecord
|
327
|
+
module ConnectionAdapters
|
328
|
+
class SpatialPostgreSQLColumn < PostgreSQLColumn
|
329
|
+
include SpatialAdapter::SpatialColumn
|
330
|
+
|
331
|
+
def initialize(name, default, sql_type = nil, null = true, srid=-1, with_z=false, with_m=false, geographic = false)
|
332
|
+
super(name, default, sql_type, null, srid, with_z, with_m)
|
333
|
+
@geographic = geographic
|
334
|
+
end
|
335
|
+
|
336
|
+
def geographic?
|
337
|
+
@geographic
|
338
|
+
end
|
339
|
+
|
340
|
+
#Transforms a string to a geometry. PostGIS returns a HewEWKB string.
|
341
|
+
def self.string_to_geometry(string)
|
342
|
+
return string unless string.is_a?(String)
|
343
|
+
GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb(string) rescue nil
|
344
|
+
end
|
345
|
+
|
346
|
+
def self.create_simplified(name, default, null = true)
|
347
|
+
new(name, default, "geometry", null)
|
348
|
+
end
|
349
|
+
|
350
|
+
def self.create_from_geography(name, default, sql_type, null = true)
|
351
|
+
params = extract_geography_params(sql_type)
|
352
|
+
new(name, default, sql_type, null, params[:srid], params[:with_z], params[:with_m], true)
|
353
|
+
end
|
354
|
+
|
355
|
+
private
|
356
|
+
|
357
|
+
# Add detection of PostGIS-specific geography columns
|
358
|
+
def geometry_simplified_type(sql_type)
|
359
|
+
case sql_type
|
360
|
+
when /geography\(point/i then :point
|
361
|
+
when /geography\(linestring/i then :line_string
|
362
|
+
when /geography\(polygon/i then :polygon
|
363
|
+
when /geography\(multipoint/i then :multi_point
|
364
|
+
when /geography\(multilinestring/i then :multi_line_string
|
365
|
+
when /geography\(multipolygon/i then :multi_polygon
|
366
|
+
when /geography\(geometrycollection/i then :geometry_collection
|
367
|
+
when /geography/i then :geometry
|
368
|
+
else
|
369
|
+
super
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def self.extract_geography_params(sql_type)
|
374
|
+
params = {
|
375
|
+
:srid => 0,
|
376
|
+
:with_z => false,
|
377
|
+
:with_m => false
|
378
|
+
}
|
379
|
+
if sql_type =~ /geography(?:\((?:\w+?)(Z)?(M)?(?:,(\d+))?\))?/i
|
380
|
+
params[:with_z] = $1 == 'Z'
|
381
|
+
params[:with_m] = $2 == 'M'
|
382
|
+
params[:srid] = $3.to_i
|
383
|
+
end
|
384
|
+
params
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|