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