dr-postgis_adapter 0.8.1

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.
@@ -0,0 +1,176 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # PostGIS Adapter - http://github.com/nofxx/postgis_adapter
4
+ #
5
+ # Hope you enjoy this plugin.
6
+ #
7
+ #
8
+ # Post any bugs/suggestions to GitHub issues tracker:
9
+ # http://github.com/nofxx/postgis_adapter/issues
10
+ #
11
+ #
12
+ # Some links:
13
+ #
14
+ # PostGis Manual - http://postgis.refractions.net/documentation/manual-svn
15
+ # Earth Spheroid - http://en.wikipedia.org/wiki/Figure_of_the_Earth
16
+ #
17
+
18
+ module PostgisAdapter
19
+ module Functions
20
+ # WGS84 Spheroid
21
+ EARTH_SPHEROID = "'SPHEROID[\"GRS-80\",6378137,298.257222101]'" # SRID => 4326
22
+
23
+ def postgis_calculate(operation, subjects, options = {})
24
+ subjects = [subjects] unless subjects.respond_to?(:map)
25
+ execute_geometrical_calculation(operation, subjects, options)
26
+ end
27
+
28
+ def geo_columns
29
+ @geo_columns ||= postgis_geoms[:columns]
30
+ end
31
+
32
+ private
33
+
34
+ #
35
+ # Construct the PostGIS SQL query
36
+ #
37
+ # Returns:
38
+ # Area/Distance/DWithin/Length/Perimeter => projected units
39
+ # DistanceSphere/Spheroid => meters
40
+ #
41
+ def construct_geometric_sql(type,geoms,options)
42
+ not_db, on_db = geoms.partition { |g| g.is_a?(Geometry) || g.new_record? }
43
+ not_db.map! {|o| o.respond_to?(:new_record?) ? o.geom : o }
44
+
45
+ tables = on_db.map do |t| {
46
+ :name => t.class.table_name,
47
+ :column => t.class.default_geometry,
48
+ :uid => unique_identifier,
49
+ :primary_key => t.class.primary_key,
50
+ :id => t[:id] }
51
+ end
52
+
53
+ # Implement a better way for options?
54
+ if options.instance_of? Hash
55
+ transform = options.delete(:transform)
56
+ stcollect = options.delete(:stcollect)
57
+ options = nil
58
+ end
59
+
60
+ fields = tables.map { |f| "#{f[:uid]}.#{f[:column]}" } # W1.geom
61
+ fields << not_db.map { |g| "'#{g.as_hex_ewkb}'::geometry"} unless not_db.empty?
62
+ fields.map! { |f| "ST_Transform(#{f}, #{transform})" } if transform # ST_Transform(W1.geom,x)
63
+ fields.map! { |f| "ST_Union(#{f})" } if stcollect # ST_Transform(W1.geom,x)
64
+ conditions = tables.map {|f| "#{f[:uid]}.#{f[:primary_key]} = #{f[:id]}" } # W1.id = 5
65
+
66
+ tables.map! { |f| "#{f[:name]} #{f[:uid]}" } # streets W1
67
+
68
+ #
69
+ # Data => SELECT Func(A,B)
70
+ # BBox => SELECT (A <=> B)
71
+ # Func => SELECT Func(Func(A))
72
+ #
73
+ if type != :bbox
74
+ opcode = type.to_s
75
+ opcode = "ST_#{opcode}" unless opcode =~ /th3d|pesinter/
76
+ fields << options if options
77
+ fields = fields.join(",")
78
+ else
79
+ fields = fields.join(" #{options} ")
80
+ end
81
+
82
+
83
+ sql = "SELECT #{opcode}(#{fields}) "
84
+ sql << "FROM #{tables.join(",")} " unless tables.empty?
85
+ sql << "WHERE #{conditions.join(" AND ")}" unless conditions.empty?
86
+ sql
87
+ end
88
+
89
+ #
90
+ # Execute the query and parse the return.
91
+ # We may receive:
92
+ #
93
+ # "t" or "f" for boolean queries
94
+ # BIGHASH for geometries
95
+ # HASH for ST_Relate
96
+ # Rescue a float
97
+ #
98
+ def execute_geometrical_calculation(operation, subject, options) #:nodoc:
99
+ value = connection.select_value(construct_geometric_sql(operation, subject, options))
100
+ return nil unless value
101
+ # TODO: bench case vs if here
102
+ if value =~ /^[tf]$/
103
+ {"f" => false, "t" => true}[value]
104
+ elsif value =~ /^\{/
105
+ value
106
+ else
107
+ GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb(value) rescue value
108
+ end
109
+ rescue Exception => e
110
+ raise StandardError, e.to_s + e.backtrace.inspect
111
+ end
112
+
113
+ # Get a unique ID for tables
114
+ def unique_identifier
115
+ @u_id ||= "T1"
116
+ @u_id = @u_id.succ
117
+ end
118
+
119
+ end
120
+ end
121
+ #
122
+ # POINT(0 0)
123
+ # LINESTRING(0 0,1 1,1 2)
124
+ # POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))
125
+ # MULTIPOINT(0 0,1 2)
126
+ # MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4))
127
+ # MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ..)
128
+ # GEOMETRYCOLLECTION(POINT(2 3),LINESTRING((2 3,3 4)))
129
+ #
130
+ #Accessors
131
+ #
132
+ #ST_Dump
133
+ #ST_ExteriorRing
134
+ #ST_GeometryN
135
+ #ST_GeometryType
136
+ #ST_InteriorRingN
137
+ #ST_IsEmpty
138
+ #ST_IsRing
139
+ #ST_IsSimple
140
+ #ST_IsValid
141
+ #ST_mem_size
142
+ #ST_M
143
+ #ST_NumGeometries
144
+ #ST_NumInteriorRings
145
+ #ST_PointN
146
+ #ST_SetSRID
147
+ #ST_Summary1
148
+ #ST_X
149
+ #ST_XMin,ST_XMax
150
+ #ST_Y
151
+ #YMin,YMax
152
+ #ST_Z
153
+ #ZMin,ZMax
154
+
155
+ #OUTPUT
156
+
157
+ #ST_AsBinary
158
+ #ST_AsText
159
+ #ST_AsEWKB
160
+ #ST_AsEWKT
161
+ #ST_AsHEXEWKB
162
+ #ST_AsGML
163
+ #ST_AsKML
164
+ #ST_AsSVG
165
+ # #EARTH_SPHEROID = "'SPHEROID[\"IERS_2003\",6378136.6,298.25642]'" # SRID =>
166
+ # def distance_convert(value, unit, from = nil)
167
+ # factor = case unit
168
+ # when :km, :kilo then 1
169
+ # when :miles,:mile then 0.62137119
170
+ # when :cm, :cent then 0.1
171
+ # when :nmi, :nmile then 0.5399568
172
+ # end
173
+ # factor *= 1e3 if from
174
+ # value * factor
175
+ # end #use all commands in lowcase form
176
+ #opcode = opcode.camelize unless opcode =~ /spher|max|npoints/
@@ -0,0 +1,458 @@
1
+ require 'geo_ruby'
2
+ require 'postgis_adapter/common_spatial_adapter'
3
+ require 'postgis_adapter/functions'
4
+ require 'postgis_adapter/functions/common'
5
+ require 'postgis_adapter/functions/class'
6
+ require 'postgis_adapter/functions/bbox'
7
+ require 'postgis_adapter/acts_as_geom'
8
+
9
+ include GeoRuby::SimpleFeatures
10
+ include SpatialAdapter
11
+
12
+ module PostgisAdapter
13
+ IGNORE_TABLES = %w{ spatial_ref_sys geometry_columns geography_columns }
14
+ end
15
+ #tables to ignore in migration : relative to PostGIS management of geometric columns
16
+ ActiveRecord::SchemaDumper.ignore_tables.concat PostgisAdapter::IGNORE_TABLES
17
+
18
+ #add a method to_yaml 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)
19
+ GeoRuby::SimpleFeatures::Geometry.class_eval do
20
+ def to_fixture_format
21
+ as_hex_ewkb
22
+ end
23
+ end
24
+
25
+ ActiveRecord::Base.class_eval do
26
+
27
+ #Vit Ondruch & Tilmann Singer 's patch
28
+ def self.get_conditions(attrs)
29
+ attrs.map do |attr, value|
30
+ attr = attr.to_s
31
+ column_name = connection.quote_column_name(attr)
32
+ if columns_hash[attr].is_a?(SpatialColumn)
33
+ if value.is_a?(Array)
34
+ attrs[attr.to_sym]= "BOX3D(" + value[0].join(" ") + "," + value[1].join(" ") + ")"
35
+ "#{table_name}.#{column_name} && SetSRID(?::box3d, #{value[2] || @@default_srid || DEFAULT_SRID} ) "
36
+ elsif value.is_a?(Envelope)
37
+ attrs[attr.to_sym]= "BOX3D(" + value.lower_corner.text_representation + "," + value.upper_corner.text_representation + ")"
38
+ "#{table_name}.#{column_name} && SetSRID(?::box3d, #{value.srid} ) "
39
+ else
40
+ "#{table_name}.#{column_name} && ? "
41
+ end
42
+ else
43
+ attribute_condition("#{table_name}.#{column_name}", value)
44
+ end
45
+ end.join(' AND ')
46
+ end
47
+
48
+ #For Rails >= 2
49
+ if method(:sanitize_sql_hash_for_conditions).arity == 1
50
+ # Before Rails 2.3.3, the method had only one argument
51
+ def self.sanitize_sql_hash_for_conditions(attrs)
52
+ conditions = get_conditions(attrs)
53
+ replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
54
+ end
55
+ elsif method(:sanitize_sql_hash_for_conditions).arity == -2
56
+ # After Rails 2.3.3, the method had only two args, the last one optional
57
+ def self.sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
58
+ attrs = expand_hash_conditions_for_aggregates(attrs)
59
+
60
+ conditions = attrs.map do |attr, value|
61
+ unless value.is_a?(Hash)
62
+ attr = attr.to_s
63
+
64
+ # Extract table name from qualified attribute names.
65
+ if attr.include?('.')
66
+ table_name, attr = attr.split('.', 2)
67
+ table_name = connection.quote_table_name(table_name)
68
+ end
69
+
70
+ if columns_hash[attr].is_a?(SpatialColumn)
71
+ if value.is_a?(Array)
72
+ attrs[attr.to_sym]= "BOX3D(" + value[0].join(" ") + "," + value[1].join(" ") + ")"
73
+ "#{table_name}.#{connection.quote_column_name(attr)} && SetSRID(?::box3d, #{value[2] || DEFAULT_SRID} ) "
74
+ elsif value.is_a?(Envelope)
75
+ attrs[attr.to_sym]= "BOX3D(" + value.lower_corner.text_representation + "," + value.upper_corner.text_representation + ")"
76
+ "#{table_name}.#{connection.quote_column_name(attr)} && SetSRID(?::box3d, #{value.srid} ) "
77
+ else
78
+ "#{table_name}.#{connection.quote_column_name(attr)} && ? "
79
+ end
80
+ else
81
+ # DEPRECATED
82
+ #attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value)
83
+ quoted_column_name = "#{table_name}.#{connection.quote_column_name(attr)}"
84
+ case value
85
+ when nil then puts quoted_column_name; "#{quoted_column_name} IS ?"
86
+ when Array, ActiveRecord::Associations::CollectionProxy, ActiveRecord::NamedScope::Scope then "#{quoted_column_name} IN (?)"
87
+ when Range then if argument.exclude_end?
88
+ "#{quoted_column_name} >= ? AND #{quoted_column_name} < ?"
89
+ else
90
+ "#{quoted_column_name} BETWEEN ? AND ?"
91
+ end
92
+ else
93
+ "#{quoted_column_name} = ?"
94
+ end
95
+ end
96
+ else
97
+ sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
98
+ end
99
+ end.join(' AND ')
100
+
101
+ replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
102
+ end
103
+ else
104
+ raise "Spatial Adapter will not work with this version of Rails"
105
+ end
106
+
107
+ end
108
+
109
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
110
+
111
+ include SpatialAdapter
112
+
113
+ # SCHEMA STATEMENTS ========================================
114
+ #
115
+ # Use :template on database.yml seems a better practice.
116
+ #
117
+ # alias :original_recreate_database :recreate_database
118
+ # def recreate_database(configuration, enc_option)
119
+ # `dropdb -U "#{configuration["test"]["username"]}" #{configuration["test"]["database"]}`
120
+ # `createdb #{enc_option} -U "#{configuration["test"]["username"]}" #{configuration["test"]["database"]}`
121
+ # `createlang -U "#{configuration["test"]["username"]}" plpgsql #{configuration["test"]["database"]}`
122
+ # `psql -d #{configuration["test"]["database"]} -f db/spatial/postgis.sql`
123
+ # `psql -d #{configuration["test"]["database"]} -f db/spatial/spatial_ref_sys.sql`
124
+ # end
125
+
126
+ # alias :original_create_database :create_database
127
+ # def create_database(name, options = {})
128
+ # original_create_database(name, options = {})
129
+ # `createlang plpgsql #{name}`
130
+ # `psql -d #{name} -f db/spatial/postgis.sql`
131
+ # `psql -d #{name} -f db/spatial/spatial_ref_sys.sql`
132
+ # end
133
+
134
+ alias :original_native_database_types :native_database_types
135
+ def native_database_types
136
+ original_native_database_types.update(geometry_data_types)
137
+ end
138
+
139
+ # Hack to make it works with Rails 3.1
140
+ alias :original_type_cast :type_cast rescue nil
141
+ def type_cast(value, column)
142
+ return value.as_hex_ewkb if value.is_a?(GeoRuby::SimpleFeatures::Geometry)
143
+ original_type_cast(value,column)
144
+ end
145
+
146
+ alias :original_quote :quote
147
+ #Redefines the quote method to add behaviour for when a Geometry is encountered
148
+ def quote(value, column = nil)
149
+ if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
150
+ "'#{value.as_hex_ewkb}'"
151
+ else
152
+ original_quote(value,column)
153
+ end
154
+ end
155
+
156
+ alias :original_tables :tables
157
+ def tables(name = nil) #:nodoc:
158
+ original_tables(name) + views(name)
159
+ end
160
+
161
+ def views(name = nil) #:nodoc:
162
+ schemas = schema_search_path.split(/,/).map { |p| quote(p.strip) }.join(',')
163
+ query(<<-SQL, name).map { |row| row[0] }
164
+ SELECT viewname
165
+ FROM pg_views
166
+ WHERE schemaname IN (#{schemas})
167
+ SQL
168
+ end
169
+
170
+ def create_table(name, options = {})
171
+ table_definition = ActiveRecord::ConnectionAdapters::PostgreSQLTableDefinition.new(self)
172
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
173
+
174
+ yield table_definition
175
+
176
+ if options[:force]
177
+ drop_table(name) rescue nil
178
+ end
179
+
180
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
181
+ create_sql << "#{name} ("
182
+ create_sql << table_definition.to_sql
183
+ create_sql << ") #{options[:options]}"
184
+ execute create_sql
185
+
186
+ #added to create the geometric columns identified during the table definition
187
+ unless table_definition.geom_columns.nil?
188
+ table_definition.geom_columns.each do |geom_column|
189
+ execute geom_column.to_sql(name)
190
+ end
191
+ end
192
+ end
193
+
194
+ alias :original_remove_column :remove_column
195
+ def remove_column(table_name,column_name, options = {})
196
+ columns(table_name).each do |col|
197
+ if col.name == column_name.to_s
198
+ #check if the column is geometric
199
+ unless geometry_data_types[col.type].nil? or
200
+ (options[:remove_using_dropgeometrycolumn] == false)
201
+ execute "SELECT DropGeometryColumn('#{table_name}','#{column_name}')"
202
+ else
203
+ original_remove_column(table_name,column_name)
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ alias :original_add_column :add_column
210
+ def add_column(table_name, column_name, type, options = {})
211
+ unless geometry_data_types[type].nil? or (options[:create_using_addgeometrycolumn] == false)
212
+ geom_column = ActiveRecord::ConnectionAdapters::PostgreSQLColumnDefinition.
213
+ new(self,column_name, type, nil,nil,options[:null],options[:srid] || -1 ,
214
+ options[:with_z] || false , options[:with_m] || false)
215
+
216
+ execute geom_column.to_sql(table_name)
217
+ else
218
+ original_add_column(table_name,column_name,type,options)
219
+ end
220
+ end
221
+
222
+ # Adds a GIST spatial index to a column. Its name will be
223
+ # <table_name>_<column_name>_spatial_index unless
224
+ # the key :name is present in the options hash, in which case its
225
+ # value is taken as the name of the index.
226
+ def add_index(table_name, column_name, options = {})
227
+ index_name = options[:name] || index_name(table_name,:column => Array(column_name))
228
+ if options[:spatial]
229
+ execute "CREATE INDEX #{index_name} ON #{table_name} USING GIST (#{Array(column_name).join(", ")} GIST_GEOMETRY_OPS)"
230
+ else
231
+ super
232
+ end
233
+ end
234
+
235
+ def indexes(table_name, name = nil) #:nodoc:
236
+ result = query(<<-SQL, name)
237
+ SELECT i.relname, d.indisunique, a.attname , am.amname
238
+ FROM pg_class t, pg_class i, pg_index d, pg_attribute a, pg_am am
239
+ WHERE i.relkind = 'i'
240
+ AND d.indexrelid = i.oid
241
+ AND d.indisprimary = 'f'
242
+ AND t.oid = d.indrelid
243
+ AND i.relam = am.oid
244
+ AND t.relname = '#{table_name}'
245
+ AND a.attrelid = t.oid
246
+ AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
247
+ OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
248
+ OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
249
+ OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
250
+ OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
251
+ ORDER BY i.relname
252
+ SQL
253
+
254
+ current_index = nil
255
+ indexes = []
256
+
257
+ result.each do |row|
258
+ if current_index != row[0]
259
+ #index type gist indicates a spatial index (probably not totally true but let's simplify!)
260
+ indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.
261
+ new(table_name, row[0], row[1] == "t", row[3] == "gist" ,[])
262
+
263
+ current_index = row[0]
264
+ end
265
+ indexes.last.columns << row[2]
266
+ end
267
+ indexes
268
+ end
269
+
270
+ def columns(table_name, name = nil) #:nodoc:
271
+ raw_geom_infos = column_spatial_info(table_name)
272
+
273
+ column_definitions(table_name).collect do |name, type, default, notnull|
274
+ if type =~ /geometry/i
275
+ raw_geom_info = raw_geom_infos[name]
276
+ if raw_geom_info.nil?
277
+ ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_simplified(name, default, notnull == "f")
278
+ else
279
+ 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)
280
+ end
281
+ else
282
+ ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(name, default, type, notnull == "f")
283
+ end
284
+ end
285
+ end
286
+
287
+ # For version of Rails where exists disable_referential_integrity
288
+ if self.instance_methods.include? :disable_referential_integrity
289
+ #Pete Deffendol's patch
290
+ alias :original_disable_referential_integrity :disable_referential_integrity
291
+ def disable_referential_integrity(&block) #:nodoc:
292
+ execute(tables.reject { |name| PostgisAdapter::IGNORE_TABLES.include?(name) }.map { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
293
+ yield
294
+ ensure
295
+ execute(tables.reject { |name| PostgisAdapter::IGNORE_TABLES.include?(name) }.map { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
296
+ end
297
+ end
298
+
299
+ private
300
+
301
+ def column_spatial_info(table_name)
302
+ constr = query <<-end_sql
303
+ SELECT * FROM geometry_columns WHERE f_table_name = '#{table_name}'
304
+ end_sql
305
+
306
+ raw_geom_infos = {}
307
+ constr.each do |constr_def_a|
308
+ raw_geom_infos[constr_def_a[3]] ||= ActiveRecord::ConnectionAdapters::RawGeomInfo.new
309
+ raw_geom_infos[constr_def_a[3]].type = constr_def_a[6]
310
+ raw_geom_infos[constr_def_a[3]].dimension = constr_def_a[4].to_i
311
+ raw_geom_infos[constr_def_a[3]].srid = constr_def_a[5].to_i
312
+
313
+ if raw_geom_infos[constr_def_a[3]].type[-1] == ?M
314
+ raw_geom_infos[constr_def_a[3]].with_m = true
315
+ raw_geom_infos[constr_def_a[3]].type.chop!
316
+ else
317
+ raw_geom_infos[constr_def_a[3]].with_m = false
318
+ end
319
+ end
320
+
321
+ raw_geom_infos.each_value do |raw_geom_info|
322
+ #check the presence of z and m
323
+ raw_geom_info.convert!
324
+ end
325
+
326
+ raw_geom_infos
327
+ rescue => e
328
+ nil
329
+ end
330
+
331
+ end
332
+
333
+ module ActiveRecord
334
+ module ConnectionAdapters
335
+ class RawGeomInfo < Struct.new(:type,:srid,:dimension,:with_z,:with_m) #:nodoc:
336
+ def convert!
337
+ self.type = "geometry" if self.type.nil? #if geometry the geometrytype constraint is not present : need to set the type here then
338
+
339
+ if dimension == 4
340
+ self.with_m = true
341
+ self.with_z = true
342
+ elsif dimension == 3
343
+ if with_m
344
+ self.with_z = false
345
+ self.with_m = true
346
+ else
347
+ self.with_z = true
348
+ self.with_m = false
349
+ end
350
+ else
351
+ self.with_z = false
352
+ self.with_m = false
353
+ end
354
+ end
355
+ end
356
+ end
357
+ end
358
+
359
+
360
+ module ActiveRecord
361
+ module ConnectionAdapters
362
+ class PostgreSQLTableDefinition < TableDefinition
363
+ attr_reader :geom_columns
364
+
365
+ def column(name, type, options = {})
366
+ unless (@base.geometry_data_types[type.to_sym].nil? or
367
+ (options[:create_using_addgeometrycolumn] == false))
368
+
369
+ geom_column = PostgreSQLColumnDefinition.new(@base,name, type)
370
+ geom_column.null = options[:null]
371
+ geom_column.srid = options[:srid] || -1
372
+ geom_column.with_z = options[:with_z] || false
373
+ geom_column.with_m = options[:with_m] || false
374
+
375
+ @geom_columns = [] if @geom_columns.nil?
376
+ @geom_columns << geom_column
377
+ else
378
+ super(name,type,options)
379
+ end
380
+ end
381
+
382
+ SpatialAdapter.geometry_data_types.keys.each do |column_type|
383
+ class_eval <<-EOV
384
+ def #{column_type}(*args)
385
+ options = args.extract_options!
386
+ column_names = args
387
+
388
+ column_names.each { |name| column(name, '#{column_type}', options) }
389
+ end
390
+ EOV
391
+ end
392
+ end
393
+
394
+ class PostgreSQLColumnDefinition < ColumnDefinition
395
+ attr_accessor :srid, :with_z,:with_m
396
+ attr_reader :spatial
397
+
398
+ def initialize(base = nil, name = nil, type=nil, limit=nil, default=nil,null=nil,srid=-1,with_z=false,with_m=false)
399
+ super(base, name, type, limit, default,null)
400
+ @spatial=true
401
+ @srid=srid
402
+ @with_z=with_z
403
+ @with_m=with_m
404
+ end
405
+
406
+ def to_sql(table_name)
407
+ if @spatial
408
+ type_sql = type_to_sql(type.to_sym)
409
+ type_sql += "M" if with_m and !with_z
410
+ if with_m and with_z
411
+ dimension = 4
412
+ elsif with_m or with_z
413
+ dimension = 3
414
+ else
415
+ dimension = 2
416
+ end
417
+
418
+ column_sql = "SELECT AddGeometryColumn('#{table_name}','#{name}',#{srid},'#{type_sql}',#{dimension})"
419
+ column_sql += ";ALTER TABLE #{table_name} ALTER #{name} SET NOT NULL" if null == false
420
+ column_sql
421
+ else
422
+ super
423
+ end
424
+ end
425
+
426
+
427
+ private
428
+ def type_to_sql(name, limit=nil)
429
+ base.type_to_sql(name, limit) rescue name
430
+ end
431
+
432
+ end
433
+
434
+ end
435
+ end
436
+
437
+ # Would prefer creation of a PostgreSQLColumn type instead but I would
438
+ # need to reimplement methods where Column objects are instantiated so
439
+ # I leave it like this
440
+ module ActiveRecord
441
+ module ConnectionAdapters
442
+ class SpatialPostgreSQLColumn < Column
443
+
444
+ include SpatialColumn
445
+
446
+ #Transforms a string to a geometry. PostGIS returns a HexEWKB string.
447
+ def self.string_to_geometry(string)
448
+ return string unless string.is_a?(String)
449
+ GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb(string) rescue nil
450
+ end
451
+
452
+ def self.create_simplified(name,default,null = true)
453
+ new(name,default,"geometry",null,nil,nil,nil)
454
+ end
455
+
456
+ end
457
+ end
458
+ end
@@ -0,0 +1,7 @@
1
+ module PostgisAdapter
2
+ class Railtie < Rails::Railtie
3
+ initializer "postgis_adapter", :after => "active_record.initialize_database" do
4
+ require 'postgis_adapter/init'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ #
2
+ # PostGIS Adapter
3
+ #
4
+ #
5
+ # Code from
6
+ # http://georuby.rubyforge.org Spatial Adapter
7
+ #
8
+
9
+ if defined?(Rails)
10
+ require 'postgis_adapter/railtie'
11
+ else
12
+ require 'active_record'
13
+ require 'active_record/connection_adapters/postgresql_adapter'
14
+
15
+ require 'postgis_adapter/init'
16
+ end
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = 'dr-postgis_adapter'
3
+ spec.version = '0.8.1'
4
+ spec.authors = ['Marcos Piccinini']
5
+ spec.summary = 'PostGIS Adapter for Active Record'
6
+ spec.email = 'x@nofxx.com'
7
+ spec.homepage = 'http://github.com/nofxx/postgis_adapter'
8
+
9
+ spec.rdoc_options = ['--charset=UTF-8']
10
+ spec.rubyforge_project = 'postgis_adapter'
11
+
12
+ spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
13
+ spec.test_files = Dir['spec/**/*.rb']
14
+ spec.extra_rdoc_files = ['README.rdoc']
15
+
16
+ spec.add_dependency 'nofxx-georuby'
17
+ spec.add_dependency 'rake'
18
+
19
+ spec.description = 'Execute PostGIS functions on Active Record'
20
+ end