activerecord-postgis-adapter 0.2.3 → 0.3.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/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:
|