activerecord-postgis-adapter 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +8 -0
- data/README.rdoc +126 -71
- data/Version +1 -1
- data/lib/active_record/connection_adapters/postgis_adapter.rb +13 -379
- data/lib/active_record/connection_adapters/postgis_adapter/arel_tosql.rb +63 -0
- data/lib/{rgeo/active_record → active_record/connection_adapters}/postgis_adapter/databases.rake +1 -1
- data/lib/active_record/connection_adapters/postgis_adapter/main_adapter.rb +280 -0
- data/lib/active_record/connection_adapters/postgis_adapter/railtie.rb +64 -0
- data/lib/active_record/connection_adapters/postgis_adapter/spatial_column.rb +168 -0
- data/lib/active_record/connection_adapters/postgis_adapter/spatial_table_definition.rb +136 -0
- data/lib/active_record/connection_adapters/postgis_adapter/version.rb +62 -0
- data/lib/rgeo/active_record/postgis_adapter/railtie.rb +2 -21
- data/test/tc_basic.rb +8 -159
- data/test/tc_ddl.rb +270 -0
- data/test/tc_spatial_queries.rb +155 -0
- metadata +18 -8
@@ -0,0 +1,63 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# PostGIS adapter for ActiveRecord
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2010 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
# :stopdoc:
|
38
|
+
|
39
|
+
module Arel
|
40
|
+
module Visitors
|
41
|
+
|
42
|
+
class PostGIS < PostgreSQL
|
43
|
+
|
44
|
+
FUNC_MAP = {
|
45
|
+
'st_wkttosql' => 'ST_GeomFromEWKT',
|
46
|
+
}
|
47
|
+
|
48
|
+
include ::RGeo::ActiveRecord::SpatialToSql
|
49
|
+
|
50
|
+
def st_func(standard_name_)
|
51
|
+
FUNC_MAP[standard_name_.downcase] || standard_name_
|
52
|
+
end
|
53
|
+
|
54
|
+
alias_method :visit_in_spatial_context, :visit
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
VISITORS['postgis'] = ::Arel::Visitors::PostGIS
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# :startdoc:
|
data/lib/{rgeo/active_record → active_record/connection_adapters}/postgis_adapter/databases.rake
RENAMED
@@ -64,7 +64,7 @@ def create_database(config_)
|
|
64
64
|
::ActiveRecord::Base.establish_connection(config_)
|
65
65
|
rescue ::Exception => e_
|
66
66
|
$stderr.puts(e_, *(e_.backtrace))
|
67
|
-
$stderr.puts("Couldn't create database for #{
|
67
|
+
$stderr.puts("Couldn't create database for #{config_.inspect}")
|
68
68
|
end
|
69
69
|
else
|
70
70
|
create_database_without_postgis(config_)
|
@@ -0,0 +1,280 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# PostGIS adapter for ActiveRecord
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2010 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
# :stopdoc:
|
38
|
+
|
39
|
+
module ActiveRecord
|
40
|
+
|
41
|
+
module ConnectionAdapters
|
42
|
+
|
43
|
+
module PostGISAdapter
|
44
|
+
|
45
|
+
|
46
|
+
class MainAdapter < PostgreSQLAdapter
|
47
|
+
|
48
|
+
|
49
|
+
SPATIAL_COLUMN_CONSTRUCTORS = ::RGeo::ActiveRecord::DEFAULT_SPATIAL_COLUMN_CONSTRUCTORS.merge(
|
50
|
+
:geography => {:type => 'geometry', :geographic => true},
|
51
|
+
)
|
52
|
+
|
53
|
+
@@native_database_types = nil
|
54
|
+
|
55
|
+
|
56
|
+
def adapter_name
|
57
|
+
PostGISAdapter::ADAPTER_NAME
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def spatial_column_constructor(name_)
|
62
|
+
SPATIAL_COLUMN_CONSTRUCTORS[name_]
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def native_database_types
|
67
|
+
@@native_database_types ||= super.merge(:spatial => {:name => 'geometry'})
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def postgis_lib_version
|
72
|
+
unless defined?(@postgis_lib_version)
|
73
|
+
@postgis_lib_version = select_value("SELECT PostGIS_Lib_Version()") rescue nil
|
74
|
+
end
|
75
|
+
@postgis_lib_version
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def srs_database_columns
|
80
|
+
{:srtext_column => 'srtext', :proj4text_column => 'proj4text', :auth_name_column => 'auth_name', :auth_srid_column => 'auth_srid'}
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def quote(value_, column_=nil)
|
85
|
+
if ::RGeo::Feature::Geometry.check_type(value_)
|
86
|
+
"'#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true, :type_format => :ewkb, :emit_ewkb_srid => true).generate(value_)}'"
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def columns(table_name_, name_=nil)
|
94
|
+
table_name_ = table_name_.to_s
|
95
|
+
spatial_info_ = spatial_column_info(table_name_)
|
96
|
+
column_definitions(table_name_).collect do |name_, type_, default_, notnull_|
|
97
|
+
SpatialColumn.new(name_, default_, type_, notnull_ == 'f', type_ =~ /geometry/i ? spatial_info_[name_] : nil)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def indexes(table_name_, name_=nil)
|
103
|
+
|
104
|
+
# Remove postgis from schemas
|
105
|
+
schemas_ = schema_search_path.split(/,/)
|
106
|
+
schemas_.delete('postgis')
|
107
|
+
schemas_ = schemas_.map{ |p_| quote(p_) }.join(',')
|
108
|
+
|
109
|
+
# Get index type by joining with pg_am.
|
110
|
+
result_ = query(<<-SQL, name_)
|
111
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, t.oid, am.amname
|
112
|
+
FROM pg_class t, pg_class i, pg_index d, pg_am am
|
113
|
+
WHERE i.relkind = 'i'
|
114
|
+
AND d.indexrelid = i.oid
|
115
|
+
AND d.indisprimary = 'f'
|
116
|
+
AND t.oid = d.indrelid
|
117
|
+
AND t.relname = '#{table_name_}'
|
118
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas_}) )
|
119
|
+
AND i.relam = am.oid
|
120
|
+
ORDER BY i.relname
|
121
|
+
SQL
|
122
|
+
|
123
|
+
result_.map do |row_|
|
124
|
+
index_name_ = row_[0]
|
125
|
+
unique_ = row_[1] == 't'
|
126
|
+
indkey_ = row_[2].split(" ")
|
127
|
+
oid_ = row_[3]
|
128
|
+
indtype_ = row_[4]
|
129
|
+
|
130
|
+
columns_ = query(<<-SQL, "Columns for index #{row_[0]} on #{table_name_}").inject({}){ |h_, r_| h_[r_[0]] = [r_[1], r_[2]]; h_ }
|
131
|
+
SELECT a.attnum, a.attname, t.typname
|
132
|
+
FROM pg_attribute a, pg_type t
|
133
|
+
WHERE a.attrelid = #{oid_}
|
134
|
+
AND a.attnum IN (#{indkey_.join(",")})
|
135
|
+
AND a.atttypid = t.oid
|
136
|
+
SQL
|
137
|
+
|
138
|
+
spatial_ = indtype_ == 'gist' && columns_.size == 1 && (columns_.values.first[1] == 'geometry' || columns_.values.first[1] == 'geography')
|
139
|
+
column_names_ = columns_.values_at(*indkey_).compact.map{ |a_| a_[0] }
|
140
|
+
column_names_.empty? ? nil : ::RGeo::ActiveRecord::SpatialIndexDefinition.new(table_name_, index_name_, unique_, column_names_, nil, spatial_)
|
141
|
+
end.compact
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
def create_table(table_name_, options_={})
|
146
|
+
table_name_ = table_name_.to_s
|
147
|
+
table_definition_ = SpatialTableDefinition.new(self)
|
148
|
+
table_definition_.primary_key(options_[:primary_key] || ::ActiveRecord::Base.get_primary_key(table_name_.singularize)) unless options_[:id] == false
|
149
|
+
yield table_definition_ if block_given?
|
150
|
+
if options_[:force] && table_exists?(table_name_)
|
151
|
+
drop_table(table_name_, options_)
|
152
|
+
end
|
153
|
+
|
154
|
+
create_sql_ = "CREATE#{' TEMPORARY' if options_[:temporary]} TABLE "
|
155
|
+
create_sql_ << "#{quote_table_name(table_name_)} ("
|
156
|
+
create_sql_ << table_definition_.to_sql
|
157
|
+
create_sql_ << ") #{options_[:options]}"
|
158
|
+
execute create_sql_
|
159
|
+
|
160
|
+
table_definition_.non_geographic_spatial_columns.each do |col_|
|
161
|
+
type_ = col_.spatial_type.gsub('_', '').upcase
|
162
|
+
has_z_ = col_.has_z?
|
163
|
+
has_m_ = col_.has_m?
|
164
|
+
type_ = "#{type_}M" if has_m_ && !has_z_
|
165
|
+
dimensions_ = 2
|
166
|
+
dimensions_ += 1 if has_z_
|
167
|
+
dimensions_ += 1 if has_m_
|
168
|
+
execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(col_.name.to_s)}', #{col_.srid}, '#{quote_string(type_)}', #{dimensions_})")
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def drop_table(table_name_, options_={})
|
174
|
+
execute("DELETE from geometry_columns where f_table_name='#{quote_string(table_name_.to_s)}'")
|
175
|
+
super
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
def add_column(table_name_, column_name_, type_, options_={})
|
180
|
+
table_name_ = table_name_.to_s
|
181
|
+
if (info_ = spatial_column_constructor(type_.to_sym))
|
182
|
+
limit_ = options_[:limit]
|
183
|
+
options_.merge!(limit_) if limit_.is_a?(::Hash)
|
184
|
+
type_ = (options_[:type] || info_[:type] || type_).to_s.gsub('_', '').upcase
|
185
|
+
has_z_ = options_[:has_z]
|
186
|
+
has_m_ = options_[:has_m]
|
187
|
+
srid_ = (options_[:srid] || 4326).to_i
|
188
|
+
if options_[:geographic]
|
189
|
+
type_ << 'Z' if has_z_
|
190
|
+
type_ << 'M' if has_m_
|
191
|
+
execute("ALTER TABLE #{quote_table_name(table_name_)} ADD COLUMN #{quote_column_name(column_name_)} GEOGRAPHY(#{type_},#{srid_})")
|
192
|
+
change_column_default(table_name_, column_name_, options_[:default]) if options_include_default?(options_)
|
193
|
+
change_column_null(table_name_, column_name_, false, options_[:default]) if options_[:null] == false
|
194
|
+
else
|
195
|
+
type_ = "#{type_}M" if has_m_ && !has_z_
|
196
|
+
dimensions_ = 2
|
197
|
+
dimensions_ += 1 if has_z_
|
198
|
+
dimensions_ += 1 if has_m_
|
199
|
+
execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(column_name_.to_s)}', #{srid_}, '#{quote_string(type_)}', #{dimensions_})")
|
200
|
+
end
|
201
|
+
else
|
202
|
+
super
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
def remove_column(table_name_, *column_names_)
|
208
|
+
column_names_ = column_names_.flatten.map{ |n_| n_.to_s }
|
209
|
+
spatial_info_ = spatial_column_info(table_name_)
|
210
|
+
remaining_column_names_ = []
|
211
|
+
column_names_.each do |name_|
|
212
|
+
if spatial_info_.include?(name_)
|
213
|
+
execute("SELECT DropGeometryColumn('#{quote_string(table_name_.to_s)}','#{quote_string(name_)}')")
|
214
|
+
else
|
215
|
+
remaining_column_names_ << name_.to_sym
|
216
|
+
end
|
217
|
+
end
|
218
|
+
if remaining_column_names_.size > 0
|
219
|
+
super(table_name_, *remaining_column_names_)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
def add_index(table_name_, column_name_, options_={})
|
225
|
+
table_name_ = table_name_.to_s
|
226
|
+
column_names_ = ::Array.wrap(column_name_)
|
227
|
+
index_name_ = index_name(table_name_, :column => column_names_)
|
228
|
+
gist_clause_ = ''
|
229
|
+
index_type_ = ''
|
230
|
+
if ::Hash === options_ # legacy support, since this param was a string
|
231
|
+
index_type_ = 'UNIQUE' if options_[:unique]
|
232
|
+
index_name_ = options_[:name].to_s if options_.key?(:name)
|
233
|
+
gist_clause_ = 'USING GIST' if options_[:spatial]
|
234
|
+
else
|
235
|
+
index_type_ = options_
|
236
|
+
end
|
237
|
+
if index_name_.length > index_name_length
|
238
|
+
raise ::ArgumentError, "Index name '#{index_name_}' on table '#{table_name_}' is too long; the limit is #{index_name_length} characters"
|
239
|
+
end
|
240
|
+
if index_name_exists?(table_name_, index_name_, false)
|
241
|
+
raise ::ArgumentError, "Index name '#{index_name_}' on table '#{table_name_}' already exists"
|
242
|
+
end
|
243
|
+
quoted_column_names_ = quoted_columns_for_index(column_names_, options_).join(", ")
|
244
|
+
execute "CREATE #{index_type_} INDEX #{quote_column_name(index_name_)} ON #{quote_table_name(table_name_)} #{gist_clause_} (#{quoted_column_names_})"
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
def spatial_column_info(table_name_)
|
249
|
+
info_ = query("SELECT * FROM geometry_columns WHERE f_table_name='#{quote_string(table_name_.to_s)}'")
|
250
|
+
result_ = {}
|
251
|
+
info_.each do |row_|
|
252
|
+
name_ = row_[3]
|
253
|
+
type_ = row_[6]
|
254
|
+
dimension_ = row_[4].to_i
|
255
|
+
has_m_ = type_ =~ /m$/i ? true : false
|
256
|
+
type_.sub!(/m$/, '')
|
257
|
+
has_z_ = dimension_ > 3 || dimension_ == 3 && !has_m_
|
258
|
+
result_[name_] = {
|
259
|
+
:name => name_,
|
260
|
+
:type => type_,
|
261
|
+
:dimension => dimension_,
|
262
|
+
:srid => row_[5].to_i,
|
263
|
+
:has_z => has_z_,
|
264
|
+
:has_m => has_m_,
|
265
|
+
}
|
266
|
+
end
|
267
|
+
result_
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
|
280
|
+
# :startdoc:
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Railtie for PostGIS adapter
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2010 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
require 'rails/railtie'
|
38
|
+
|
39
|
+
|
40
|
+
# :stopdoc:
|
41
|
+
|
42
|
+
module ActiveRecord
|
43
|
+
|
44
|
+
module ConnectionAdapters
|
45
|
+
|
46
|
+
module PostGISAdapter
|
47
|
+
|
48
|
+
|
49
|
+
class Railtie < ::Rails::Railtie
|
50
|
+
|
51
|
+
rake_tasks do
|
52
|
+
load ::File.expand_path('databases.rake', ::File.dirname(__FILE__))
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# :startdoc:
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# PostGIS adapter for ActiveRecord
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2010 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
# :stopdoc:
|
38
|
+
|
39
|
+
module ActiveRecord
|
40
|
+
|
41
|
+
module ConnectionAdapters
|
42
|
+
|
43
|
+
module PostGISAdapter
|
44
|
+
|
45
|
+
|
46
|
+
class SpatialColumn < ConnectionAdapters::PostgreSQLColumn
|
47
|
+
|
48
|
+
|
49
|
+
def initialize(name_, default_, sql_type_=nil, null_=true, opts_=nil)
|
50
|
+
super(name_, default_, sql_type_, null_)
|
51
|
+
@geographic = sql_type_ =~ /geography/i ? true : false
|
52
|
+
if opts_
|
53
|
+
@geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name(opts_[:type])
|
54
|
+
@srid = opts_[:srid].to_i
|
55
|
+
@has_z = opts_[:has_z]
|
56
|
+
@has_m = opts_[:has_m]
|
57
|
+
elsif @geographic
|
58
|
+
@geometric_type = ::RGeo::Feature::Geometry
|
59
|
+
@srid = 4326
|
60
|
+
@has_z = @has_m = false
|
61
|
+
if sql_type_ =~ /geography\((.*)\)$/i
|
62
|
+
params_ = $1.split(',')
|
63
|
+
if params_.size >= 2
|
64
|
+
if params_.first =~ /([a-z]+[^zm])(z?)(m?)/i
|
65
|
+
@has_z = $2.length > 0
|
66
|
+
@has_m = $3.length > 0
|
67
|
+
@geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name($1)
|
68
|
+
end
|
69
|
+
if params_.last =~ /(\d+)/
|
70
|
+
@srid = $1.to_i
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
else
|
75
|
+
@geometric_type = @has_z = @has_m = nil
|
76
|
+
@srid = 0
|
77
|
+
end
|
78
|
+
if type == :spatial
|
79
|
+
@limit = {:srid => @srid, :type => @geometric_type.type_name.underscore}
|
80
|
+
@limit[:has_z] = true if @has_z
|
81
|
+
@limit[:has_m] = true if @has_m
|
82
|
+
@limit[:geographic] = true if @geographic
|
83
|
+
end
|
84
|
+
@ar_class = ::ActiveRecord::Base
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def set_ar_class(val_)
|
89
|
+
@ar_class = val_
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
attr_reader :geographic
|
94
|
+
attr_reader :srid
|
95
|
+
attr_reader :geometric_type
|
96
|
+
attr_reader :has_z
|
97
|
+
attr_reader :has_m
|
98
|
+
|
99
|
+
alias_method :geographic?, :geographic
|
100
|
+
alias_method :has_z?, :has_z
|
101
|
+
alias_method :has_m?, :has_m
|
102
|
+
|
103
|
+
|
104
|
+
def spatial?
|
105
|
+
type == :spatial
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def klass
|
110
|
+
type == :spatial ? ::RGeo::Feature::Geometry : super
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def type_cast(value_)
|
115
|
+
type == :spatial ? SpatialColumn.convert_to_geometry(value_, @ar_class, name, @geographic, @srid, @has_z, @has_m) : super
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def type_cast_code(var_name_)
|
120
|
+
type == :spatial ? "::ActiveRecord::ConnectionAdapters::PostGISAdapter::SpatialColumn.convert_to_geometry(#{var_name_}, self.class, #{name.inspect}, #{@geographic ? 'true' : 'false'}, #{@srid.inspect}, #{@has_z ? 'true' : 'false'}, #{@has_m ? 'true' : 'false'})" : super
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
|
127
|
+
def simplified_type(sql_type_)
|
128
|
+
sql_type_ =~ /geography|geometry|point|linestring|polygon/i ? :spatial : super
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
def self.convert_to_geometry(input_, ar_class_, column_, geographic_, srid_, has_z_, has_m_)
|
133
|
+
case input_
|
134
|
+
when ::RGeo::Feature::Geometry
|
135
|
+
factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => srid_, :has_z_coordinate => has_z_, :has_m_coordinate => has_m_, :geographic => geographic_)
|
136
|
+
::RGeo::Feature.cast(input_, factory_)
|
137
|
+
when ::String
|
138
|
+
if input_.length == 0
|
139
|
+
nil
|
140
|
+
else
|
141
|
+
factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => srid_, :has_z_coordinate => has_z_, :has_m_coordinate => has_m_, :geographic => geographic_)
|
142
|
+
marker_ = input_[0,1]
|
143
|
+
if marker_ == "\x00" || marker_ == "\x01"
|
144
|
+
::RGeo::WKRep::WKBParser.new(factory_, :support_ewkb => true).parse(input_) rescue nil
|
145
|
+
elsif input_[0,4] =~ /[0-9a-fA-F]{4}/
|
146
|
+
::RGeo::WKRep::WKBParser.new(factory_, :support_ewkb => true).parse_hex(input_) rescue nil
|
147
|
+
else
|
148
|
+
::RGeo::WKRep::WKTParser.new(factory_, :support_ewkt => true).parse(input_) rescue nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
else
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
# :startdoc:
|