GeoRuby 1.1.2 → 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/README +16 -13
- data/lib/geo_ruby/shp4r/dbf.rb +234 -0
- data/lib/geo_ruby/shp4r/shp.rb +301 -0
- data/lib/geo_ruby/simple_features/geometry.rb +1 -0
- data/lib/geo_ruby.rb +1 -1
- data/rakefile.rb +4 -4
- data/test/data/point.dbf +0 -0
- data/test/data/point.shp +0 -0
- data/test/data/point.shx +0 -0
- data/test/data/polygon.dbf +0 -0
- data/test/data/polygon.shp +0 -0
- data/test/data/polygon.shx +0 -0
- data/test/data/polyline.dbf +0 -0
- data/test/data/polyline.shp +0 -0
- data/test/data/polyline.shx +0 -0
- data/test/test_shp.rb +77 -0
- data/tools/db.yml +6 -0
- data/tools/lib/spatial_adapter/MIT-LICENSE +7 -0
- data/tools/lib/spatial_adapter/README +116 -0
- data/tools/lib/spatial_adapter/init.rb +10 -0
- data/tools/lib/spatial_adapter/lib/common_spatial_adapter.rb +175 -0
- data/tools/lib/spatial_adapter/lib/mysql_spatial_adapter.rb +143 -0
- data/tools/lib/spatial_adapter/lib/post_gis_adapter.rb +333 -0
- data/tools/lib/spatial_adapter/rakefile.rb +35 -0
- data/tools/lib/spatial_adapter/test/access_mysql_test.rb +87 -0
- data/tools/lib/spatial_adapter/test/access_postgis_test.rb +151 -0
- data/tools/lib/spatial_adapter/test/common/common_mysql.rb +18 -0
- data/tools/lib/spatial_adapter/test/common/common_postgis.rb +19 -0
- data/tools/lib/spatial_adapter/test/db/database_mysql.yml +5 -0
- data/tools/lib/spatial_adapter/test/db/database_postgis.yml +4 -0
- data/tools/lib/spatial_adapter/test/find_mysql_test.rb +64 -0
- data/tools/lib/spatial_adapter/test/find_postgis_test.rb +65 -0
- data/tools/lib/spatial_adapter/test/migration_mysql_test.rb +136 -0
- data/tools/lib/spatial_adapter/test/migration_postgis_test.rb +170 -0
- data/tools/lib/spatial_adapter/test/models/models_mysql.rb +25 -0
- data/tools/lib/spatial_adapter/test/models/models_postgis.rb +41 -0
- data/tools/lib/spatial_adapter/test/schema/schema_mysql.rb +40 -0
- data/tools/lib/spatial_adapter/test/schema/schema_postgis.rb +69 -0
- data/tools/shp2sql.rb +91 -0
- metadata +47 -4
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'geo_ruby'
|
3
|
+
require 'common_spatial_adapter'
|
4
|
+
|
5
|
+
include GeoRuby::SimpleFeatures
|
6
|
+
|
7
|
+
|
8
|
+
#add a method to_fixture_format to the Geometry class which will transform a geometry in a form suitable to be used in a YAML file (such as in a fixture)
|
9
|
+
GeoRuby::SimpleFeatures::Geometry.class_eval do
|
10
|
+
def to_fixture_format
|
11
|
+
"!binary | #{[(255.chr * 4) + as_wkb].pack('m')}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
ActiveRecord::Base.class_eval do
|
17
|
+
#Redefinition of the method to do something special when a geometric column is encountered
|
18
|
+
def self.construct_conditions_from_arguments(attribute_names, arguments)
|
19
|
+
conditions = []
|
20
|
+
attribute_names.each_with_index do |name, idx|
|
21
|
+
if columns_hash[name].is_a?(SpatialColumn)
|
22
|
+
#when the discriminating column is spatial, always use the MBRIntersects (bounding box intersection check) operator : the user can pass either a geometric object (which will be transformed to a string using the quote method of the database adapter) or an array with the corner points of a bounding box
|
23
|
+
if arguments[idx].is_a?(Array)
|
24
|
+
conditions << "MBRIntersects(?, #{table_name}.#{connection.quote_column_name(name)}) "
|
25
|
+
#using some georuby utility : The multipoint has a bbox whose corners are the 2 points passed as parameters : [ pt1, pt2]
|
26
|
+
arguments[idx]= MultiPoint.from_coordinates(arguments[idx])
|
27
|
+
else
|
28
|
+
conditions << "MBRIntersects(?, #{table_name}.#{connection.quote_column_name(name)}) "
|
29
|
+
end
|
30
|
+
else
|
31
|
+
conditions << "#{table_name}.#{connection.quote_column_name(name)} #{attribute_condition(arguments[idx])} "
|
32
|
+
end
|
33
|
+
end
|
34
|
+
[ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
|
40
|
+
|
41
|
+
include SpatialAdapter
|
42
|
+
|
43
|
+
alias :original_native_database_types :native_database_types
|
44
|
+
def native_database_types
|
45
|
+
original_native_database_types.merge!(geometry_data_types)
|
46
|
+
end
|
47
|
+
|
48
|
+
alias :original_quote :quote
|
49
|
+
#Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods
|
50
|
+
def quote(value, column = nil)
|
51
|
+
if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
|
52
|
+
"GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})"
|
53
|
+
else
|
54
|
+
original_quote(value,column)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
#Redefinition of columns to add the information that a column is geometric
|
59
|
+
def columns(table_name, name = nil)#:nodoc:
|
60
|
+
sql = "SHOW FIELDS FROM #{table_name}"
|
61
|
+
columns = []
|
62
|
+
execute(sql, name).each do |field|
|
63
|
+
if field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i
|
64
|
+
#to note that the column is spatial
|
65
|
+
columns << ActiveRecord::ConnectionAdapters::SpatialMysqlColumn.new(field[0], field[4], field[1], field[2] == "YES")
|
66
|
+
else
|
67
|
+
columns << ActiveRecord::ConnectionAdapters::MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
columns
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
#operations relative to migrations
|
75
|
+
|
76
|
+
#Redefines add_index to support the case where the index is spatial
|
77
|
+
#If the :spatial key in the options table is true, then the sql string for a spatial index is created
|
78
|
+
def add_index(table_name,column_name,options = {})
|
79
|
+
index_name = options[:name] || "#{table_name}_#{Array(column_name).first}_index"
|
80
|
+
|
81
|
+
if options[:spatial]
|
82
|
+
if column_name.is_a?(Array) and column_name.length > 1
|
83
|
+
#one by one or error : Should raise exception instead? ; use default name even if name passed as argument
|
84
|
+
Array(column_name).each do |col|
|
85
|
+
execute "CREATE SPATIAL INDEX #{table_name}_#{col}_index ON #{table_name} (#{col})"
|
86
|
+
end
|
87
|
+
else
|
88
|
+
col = Array(column_name)[0]
|
89
|
+
execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{col})"
|
90
|
+
end
|
91
|
+
else
|
92
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
93
|
+
#all together
|
94
|
+
execute "CREATE #{index_type} INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#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)
|
99
|
+
def indexes(table_name, name = nil)#:nodoc:
|
100
|
+
indexes = []
|
101
|
+
current_index = nil
|
102
|
+
execute("SHOW KEYS FROM #{table_name}", name).each do |row|
|
103
|
+
if current_index != row[2]
|
104
|
+
next if row[2] == "PRIMARY" # skip the primary key
|
105
|
+
current_index = row[2]
|
106
|
+
indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", row[10] == "SPATIAL",[])
|
107
|
+
end
|
108
|
+
indexes.last.columns << row[4]
|
109
|
+
end
|
110
|
+
indexes
|
111
|
+
end
|
112
|
+
|
113
|
+
#Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here.
|
114
|
+
def options_for(table)
|
115
|
+
result = execute("show table status like '#{table}'")
|
116
|
+
engine = result.fetch_row[1]
|
117
|
+
if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration
|
118
|
+
"ENGINE=#{engine}"
|
119
|
+
else
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
module ActiveRecord
|
127
|
+
module ConnectionAdapters
|
128
|
+
class SpatialMysqlColumn < MysqlColumn
|
129
|
+
|
130
|
+
include SpatialColumn
|
131
|
+
|
132
|
+
#MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions.
|
133
|
+
def self.string_to_geometry(string)
|
134
|
+
return string unless string.is_a?(String)
|
135
|
+
begin
|
136
|
+
GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1])
|
137
|
+
rescue Exception => exception
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,333 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'geo_ruby'
|
3
|
+
require 'common_spatial_adapter'
|
4
|
+
|
5
|
+
include GeoRuby::SimpleFeatures
|
6
|
+
|
7
|
+
#tables to ignore in migration : relative to PostGIS management of geometric columns
|
8
|
+
ActiveRecord::SchemaDumper.ignore_tables << "spatial_ref_sys" << "geometry_columns"
|
9
|
+
|
10
|
+
|
11
|
+
#add a method to_fixture_format to the Geometry class which will transform a geometry in a form suitable to be used in a YAML file (such as in a fixture)
|
12
|
+
GeoRuby::SimpleFeatures::Geometry.class_eval do
|
13
|
+
def to_fixture_format
|
14
|
+
as_hex_ewkb
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
ActiveRecord::Base.class_eval do
|
20
|
+
def self.construct_conditions_from_arguments(attribute_names, arguments)
|
21
|
+
conditions = []
|
22
|
+
attribute_names.each_with_index do |name, idx|
|
23
|
+
if columns_hash[name].is_a?(SpatialColumn)
|
24
|
+
#when the discriminating column is spatial, always use the && (bounding box intersection check) operator : the user can pass either a geometric object (which will be transformed to a string using the quote method of the database adapter) or an array representing 2 opposite corners of a bounding box
|
25
|
+
if arguments[idx].is_a?(Array)
|
26
|
+
bbox = arguments[idx]
|
27
|
+
conditions << "#{table_name}.#{connection.quote_column_name(name)} && SetSRID(?::box3d, #{bbox[2] || -1} ) "
|
28
|
+
#Could do without the ? and replace directly with the quoted BBOX3D but like this, the flow is the same everytime
|
29
|
+
arguments[idx]= "BOX3D(" + bbox[0].join(" ") + "," + bbox[1].join(" ") + ")"
|
30
|
+
else
|
31
|
+
conditions << "#{table_name}.#{connection.quote_column_name(name)} && ? "
|
32
|
+
end
|
33
|
+
else
|
34
|
+
conditions << "#{table_name}.#{connection.quote_column_name(name)} #{attribute_condition(arguments[idx])} "
|
35
|
+
end
|
36
|
+
end
|
37
|
+
[ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
42
|
+
|
43
|
+
include SpatialAdapter
|
44
|
+
|
45
|
+
alias :original_native_database_types :native_database_types
|
46
|
+
def native_database_types
|
47
|
+
original_native_database_types.merge!(geometry_data_types)
|
48
|
+
end
|
49
|
+
|
50
|
+
alias :original_quote :quote
|
51
|
+
#Redefines the quote method to add behaviour for when a Geometry is encountered
|
52
|
+
def quote(value, column = nil)
|
53
|
+
if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
|
54
|
+
"'#{value.as_hex_ewkb}'"
|
55
|
+
else
|
56
|
+
original_quote(value,column)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_table(name, options = {})
|
61
|
+
table_definition = ActiveRecord::ConnectionAdapters::PostgreSQLTableDefinition.new(self)
|
62
|
+
table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
|
63
|
+
|
64
|
+
yield table_definition
|
65
|
+
|
66
|
+
if options[:force]
|
67
|
+
drop_table(name) rescue nil
|
68
|
+
end
|
69
|
+
|
70
|
+
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
71
|
+
create_sql << "#{name} ("
|
72
|
+
create_sql << table_definition.to_sql
|
73
|
+
create_sql << ") #{options[:options]}"
|
74
|
+
execute create_sql
|
75
|
+
|
76
|
+
#added to create the geometric columns identified during the table definition
|
77
|
+
unless table_definition.geom_columns.nil?
|
78
|
+
table_definition.geom_columns.each do |geom_column|
|
79
|
+
execute geom_column.to_sql(name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
alias :original_remove_column :remove_column
|
85
|
+
def remove_column(table_name,column_name)
|
86
|
+
columns(table_name).each do |col|
|
87
|
+
if col.name.to_s == column_name.to_s
|
88
|
+
#check if the column is geometric
|
89
|
+
unless geometry_data_types[col.type].nil?
|
90
|
+
execute "SELECT DropGeometryColumn('#{table_name}','#{column_name}')"
|
91
|
+
else
|
92
|
+
original_remove_column(table_name,column_name)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
alias :original_add_column :add_column
|
99
|
+
def add_column(table_name, column_name, type, options = {})
|
100
|
+
unless geometry_data_types[type].nil?
|
101
|
+
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)
|
102
|
+
execute geom_column.to_sql(table_name)
|
103
|
+
else
|
104
|
+
original_add_column(table_name,column_name,type,options)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
#Adds a GIST spatial index to a column. Its name will be <table_name>_<column_name>_spatial_index unless the key :name is present in the options hash, in which case its value is taken as the name of the index.
|
111
|
+
def add_index(table_name,column_name,options = {})
|
112
|
+
index_name = options[:name] ||"#{table_name}_#{Array(column_name).first}_index"
|
113
|
+
if options[:spatial]
|
114
|
+
if column_name.is_a?(Array) and column_name.length > 1
|
115
|
+
#one by one or error : Should raise exception instead? ; use default name even if name passed as argument
|
116
|
+
Array(column_name).each do |col|
|
117
|
+
execute "CREATE INDEX #{table_name}_#{col}_index ON #{table_name} USING GIST (#{col} GIST_GEOMETRY_OPS)"
|
118
|
+
end
|
119
|
+
else
|
120
|
+
col = Array(column_name)[0]
|
121
|
+
execute "CREATE INDEX #{index_name} ON #{table_name} USING GIST (#{col} GIST_GEOMETRY_OPS)"
|
122
|
+
end
|
123
|
+
else
|
124
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
125
|
+
#all together
|
126
|
+
execute "CREATE #{index_type} INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def indexes(table_name, name = nil) #:nodoc:
|
132
|
+
result = query(<<-SQL, name)
|
133
|
+
SELECT i.relname, d.indisunique, a.attname , am.amname
|
134
|
+
FROM pg_class t, pg_class i, pg_index d, pg_attribute a, pg_am am
|
135
|
+
WHERE i.relkind = 'i'
|
136
|
+
AND d.indexrelid = i.oid
|
137
|
+
AND d.indisprimary = 'f'
|
138
|
+
AND t.oid = d.indrelid
|
139
|
+
AND i.relam = am.oid
|
140
|
+
AND t.relname = '#{table_name}'
|
141
|
+
AND a.attrelid = t.oid
|
142
|
+
AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
|
143
|
+
OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
|
144
|
+
OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
|
145
|
+
OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
|
146
|
+
OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
|
147
|
+
ORDER BY i.relname
|
148
|
+
SQL
|
149
|
+
|
150
|
+
current_index = nil
|
151
|
+
indexes = []
|
152
|
+
|
153
|
+
result.each do |row|
|
154
|
+
if current_index != row[0]
|
155
|
+
indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", row[3] == "gist" ,[]) #index type gist indicates a spatial index (probably not totally true but let's simplify!)
|
156
|
+
current_index = row[0]
|
157
|
+
end
|
158
|
+
|
159
|
+
indexes.last.columns << row[2]
|
160
|
+
end
|
161
|
+
|
162
|
+
indexes
|
163
|
+
end
|
164
|
+
|
165
|
+
def columns(table_name, name = nil) #:nodoc:
|
166
|
+
raw_geom_infos = column_spatial_info(table_name)
|
167
|
+
|
168
|
+
column_definitions(table_name).collect do |name, type, default, notnull|
|
169
|
+
if type =~ /geometry/i and raw_geom_infos[name]
|
170
|
+
raw_geom_info = raw_geom_infos[name]
|
171
|
+
|
172
|
+
ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.new(name,default_value(default),raw_geom_info.type,notnull == "f",raw_geom_info.srid,raw_geom_info.with_z,raw_geom_info.with_m)
|
173
|
+
else
|
174
|
+
ActiveRecord::ConnectionAdapters::Column.new(name, default_value(default), translate_field_type(type),notnull == "f")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def column_spatial_info(table_name)
|
182
|
+
constr = query <<-end_sql
|
183
|
+
SELECT pg_get_constraintdef(oid)
|
184
|
+
FROM pg_constraint
|
185
|
+
WHERE conrelid = '#{table_name}'::regclass
|
186
|
+
AND contype = 'c'
|
187
|
+
end_sql
|
188
|
+
|
189
|
+
raw_geom_infos = {}
|
190
|
+
constr.each do |constr_def_a|
|
191
|
+
constr_def = constr_def_a[0] #only 1 column in the result
|
192
|
+
if constr_def =~ /geometrytype\(["']?([^"')]+)["']?\)\s*=\s*'([^']+)'/i
|
193
|
+
column_name,type = $1,$2
|
194
|
+
if type[-1] == ?M
|
195
|
+
with_m = true
|
196
|
+
type.chop!
|
197
|
+
else
|
198
|
+
with_m = false
|
199
|
+
end
|
200
|
+
raw_geom_info = raw_geom_infos[column_name] || ActiveRecord::ConnectionAdapters::RawGeomInfo.new
|
201
|
+
raw_geom_info.type = type
|
202
|
+
raw_geom_info.with_m = with_m
|
203
|
+
raw_geom_infos[column_name] = raw_geom_info
|
204
|
+
elsif constr_def =~ /ndims\(["']?([^"')]+)["']?\)\s*=\s*(\d+)/i
|
205
|
+
column_name,dimension = $1,$2
|
206
|
+
raw_geom_info = raw_geom_infos[column_name] || ActiveRecord::ConnectionAdapters::RawGeomInfo.new
|
207
|
+
raw_geom_info.dimension = dimension.to_i
|
208
|
+
raw_geom_infos[column_name] = raw_geom_info
|
209
|
+
elsif constr_def =~ /srid\(["']?([^"')]+)["']?\)\s*=\s*(-?\d+)/i
|
210
|
+
column_name,srid = $1,$2
|
211
|
+
raw_geom_info = raw_geom_infos[column_name] || ActiveRecord::ConnectionAdapters::RawGeomInfo.new
|
212
|
+
raw_geom_info.srid = srid.to_i
|
213
|
+
raw_geom_infos[column_name] = raw_geom_info
|
214
|
+
end #if constr_def
|
215
|
+
end #constr.each
|
216
|
+
|
217
|
+
raw_geom_infos.each_value do |raw_geom_info|
|
218
|
+
#check the presence of z and m
|
219
|
+
raw_geom_info.convert!
|
220
|
+
end
|
221
|
+
|
222
|
+
raw_geom_infos
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
module ActiveRecord
|
229
|
+
module ConnectionAdapters
|
230
|
+
class RawGeomInfo < Struct.new(:type,:srid,:dimension,:with_z,:with_m) #:nodoc:
|
231
|
+
def convert!
|
232
|
+
self.type = "geometry" if self.type.nil? #if geometry the geometrytype constraint is not present : need to set the type here then
|
233
|
+
|
234
|
+
if dimension == 4
|
235
|
+
self.with_m = true
|
236
|
+
self.with_z = true
|
237
|
+
elsif dimension == 3
|
238
|
+
if with_m
|
239
|
+
self.with_z = false
|
240
|
+
self.with_m = true
|
241
|
+
else
|
242
|
+
self.with_z = true
|
243
|
+
self.with_m = false
|
244
|
+
end
|
245
|
+
else
|
246
|
+
self.with_z = false
|
247
|
+
self.with_m = false
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
module ActiveRecord
|
256
|
+
module ConnectionAdapters
|
257
|
+
class PostgreSQLTableDefinition < TableDefinition
|
258
|
+
attr_reader :geom_columns
|
259
|
+
|
260
|
+
def column(name, type, options = {})
|
261
|
+
unless @base.geometry_data_types[type].nil?
|
262
|
+
geom_column = PostgreSQLColumnDefinition.new(@base,name, type)
|
263
|
+
geom_column.null = options[:null]
|
264
|
+
geom_column.srid = options[:srid] || -1
|
265
|
+
geom_column.with_z = options[:with_z] || false
|
266
|
+
geom_column.with_m = options[:with_m] || false
|
267
|
+
|
268
|
+
@geom_columns = [] if @geom_columns.nil?
|
269
|
+
@geom_columns << geom_column
|
270
|
+
else
|
271
|
+
super(name,type,options)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class PostgreSQLColumnDefinition < ColumnDefinition
|
277
|
+
attr_accessor :srid, :with_z,:with_m
|
278
|
+
attr_reader :spatial
|
279
|
+
|
280
|
+
def initialize(base = nil, name = nil, type=nil, limit=nil, default=nil,null=nil,srid=-1,with_z=false,with_m=false)
|
281
|
+
super(base, name, type, limit, default,null)
|
282
|
+
@spatial=true
|
283
|
+
@srid=srid
|
284
|
+
@with_z=with_z
|
285
|
+
@with_m=with_m
|
286
|
+
end
|
287
|
+
|
288
|
+
def to_sql(table_name)
|
289
|
+
if @spatial
|
290
|
+
type_sql = type_to_sql(type.to_sym)
|
291
|
+
type_sql += "M" if with_m and !with_z
|
292
|
+
if with_m and with_z
|
293
|
+
dimension = 4
|
294
|
+
elsif with_m or with_z
|
295
|
+
dimension = 3
|
296
|
+
else
|
297
|
+
dimension = 2
|
298
|
+
end
|
299
|
+
|
300
|
+
column_sql = "SELECT AddGeometryColumn('#{table_name}','#{name}',#{srid},'#{type_sql}',#{dimension})"
|
301
|
+
column_sql += ";ALTER TABLE #{table_name} ALTER #{name} SET NOT NULL" if null == false
|
302
|
+
column_sql
|
303
|
+
else
|
304
|
+
super
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
|
309
|
+
private
|
310
|
+
def type_to_sql(name, limit=nil)
|
311
|
+
base.type_to_sql(name, limit) rescue name
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
#Would prefer creation of a PostgreSQLColumn type instead but I would need to reimplement methods where Column objects are instantiated so I leave it like this
|
320
|
+
module ActiveRecord
|
321
|
+
module ConnectionAdapters
|
322
|
+
class SpatialPostgreSQLColumn < Column
|
323
|
+
|
324
|
+
include SpatialColumn
|
325
|
+
|
326
|
+
#Transforms a string to a geometry. PostGIS returns a HewEWKB string.
|
327
|
+
def self.string_to_geometry(string)
|
328
|
+
return string unless string.is_a?(String)
|
329
|
+
GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb(string) rescue nil
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|