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
data/History.rdoc
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
=== 0.3.0 / 2011-01-26
|
2
|
+
|
3
|
+
* Reworked type and constraint handling, which should result in a large number of bug fixes, especially related to schema dumps.
|
4
|
+
* Experimental support for complex spatial queries. (Requires Arel 2.1, which is expected to be released with Rails 3.1.)
|
5
|
+
* The path to the Railtie is now different (see the README), though a compatibility wrapper has been left in the old location.
|
6
|
+
* Getting index information from the ActiveRecord class now properly recognizes spatial-ness.
|
7
|
+
* Reorganized the code a bit for better clarity.
|
8
|
+
|
1
9
|
=== 0.2.3 / 2011-01-06
|
2
10
|
|
3
11
|
* Many of ActiveRecord's rake tasks weren't working because they need to know about every adapter explicitly. I hesitate to call this "fixed" since I see it as a problem in ActiveRecord, but we now at least have a workaround so the rake tasks will run properly. (Reported by Tad Thorley.)
|
data/README.rdoc
CHANGED
@@ -7,7 +7,9 @@ the {RGeo}[http://github.com/dazuma/rgeo] library to represent spatial
|
|
7
7
|
data in Ruby. Like the standard postgresql adapter, this adapter requires
|
8
8
|
the pg gem.
|
9
9
|
|
10
|
-
|
10
|
+
== What This Adapter Provides
|
11
|
+
|
12
|
+
=== Spatial Migrations
|
11
13
|
|
12
14
|
First, this adapter extends the migration syntax to support creating
|
13
15
|
spatial columns and indexes. To create a spatial column, use the
|
@@ -19,24 +21,29 @@ creating the index.
|
|
19
21
|
|
20
22
|
Examples:
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
create_table :my_spatial_table do |t|
|
25
|
+
t.column :shape, :geometry # or t.geometry :shape
|
26
|
+
t.line_string :path, :srid => 3785
|
27
|
+
t.point :latlon, :geographic => true
|
28
|
+
end
|
29
|
+
change_table :my_spatial_table do |t|
|
30
|
+
t.index :latlon, :spatial => true
|
31
|
+
end
|
32
|
+
|
33
|
+
=== Spatial Attributes
|
30
34
|
|
31
35
|
When this adapter is in use, spatial attributes in your \ActiveRecord
|
32
36
|
objects will have RGeo geometry values. You can set spatial attributes
|
33
37
|
either to RGeo geometry objects, or to strings in WKT (well-known text)
|
34
38
|
format, which the adapter will automatically convert to geometry objects.
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
+
Spatial objects in RGeo are tied to a factory that specifies the
|
41
|
+
coordinate system as well as other behaviors of the object. You must
|
42
|
+
therefore specify a factory for each spatial column (attribute) in your
|
43
|
+
ActiveRecord class. You can either set an explicit factory for a specific
|
44
|
+
column, or provide a factory generator that will yield the appropriate
|
45
|
+
factory for the table's spatial columns based on their types. For the
|
46
|
+
former, call the <tt>set_rgeo_factory_for_column</tt> class method on your
|
40
47
|
\ActiveRecord class. For the latter, set the rgeo_factory_generator class
|
41
48
|
attribute. This generator should understand the usual <tt>:srid</tt>,
|
42
49
|
<tt>:has_z_coordinate</tt>, and <tt>:has_m_coordinate</tt> options. It
|
@@ -47,40 +54,45 @@ the "rgeo-activerecord" gem.
|
|
47
54
|
|
48
55
|
Examples, given the spatial table defined above:
|
49
56
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
57
|
+
class MySpatialTable < ActiveRecord::Base
|
58
|
+
|
59
|
+
# By default, use the GEOS implementation for spatial columns.
|
60
|
+
self.rgeo_factory_generator = RGeo::Geos.factory_generator
|
61
|
+
|
62
|
+
# But use a geographic implementation for the :latlon column.
|
63
|
+
set_rgeo_factory_for_column(:latlon, RGeo::Geographic.spherical_factory)
|
64
|
+
|
65
|
+
end
|
59
66
|
|
60
67
|
Now you can interact with the data using the RGeo types:
|
61
68
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
rec = MySpatialTable.new
|
70
|
+
rec.latlon = 'POINT(-122 47)' # You can set by feature object or WKT.
|
71
|
+
loc = rec.latlon # Accessing always returns a feature object, in
|
72
|
+
# this case, a geographic that understands latitude.
|
73
|
+
loc.latitude # => 47
|
74
|
+
rec.shape = loc # the factory for the :shape column is GEOS, so the
|
75
|
+
# value will be cast from geographic to GEOS.
|
76
|
+
RGeo::Geos.is_geos?(rec.shape) # => true
|
77
|
+
|
78
|
+
=== Spatial Queries
|
70
79
|
|
71
80
|
You can create simple queries based on spatial equality in the same way
|
72
81
|
you would on a scalar column:
|
73
82
|
|
74
|
-
|
83
|
+
rec = MySpatialTable.where(:latlon => RGeo::Geos.factory.point(-122, 47)).first
|
75
84
|
|
76
85
|
You can also use WKT:
|
77
86
|
|
78
|
-
|
87
|
+
rec = MySpatialTable.where(:latlon => 'POINT(-122 47)').first
|
79
88
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
89
|
+
The adapter also provides experimental support for more complex queries
|
90
|
+
such as radius searches. However, these extensions require Arel 2.1
|
91
|
+
(which is scheduled for release with Rails 3.1). We do not have these
|
92
|
+
documented yet, and the syntax is subject to change. For now, you should
|
93
|
+
write more complex queries in SQL.
|
94
|
+
|
95
|
+
== Installation And Configuration
|
84
96
|
|
85
97
|
=== Installing The Adapter Gem
|
86
98
|
|
@@ -90,73 +102,115 @@ This adapter has the following requirements:
|
|
90
102
|
* PostGIS 1.5 or later.
|
91
103
|
* \ActiveRecord 3.0.3 or later. Earlier versions will not work.
|
92
104
|
* rgeo gem 0.2.4 or later.
|
93
|
-
* rgeo-activerecord gem 0.
|
105
|
+
* rgeo-activerecord gem 0.3.0 or later.
|
94
106
|
* pg gem 0.10 or later.
|
95
107
|
|
96
108
|
Install this adapter as a gem:
|
97
109
|
|
98
|
-
|
110
|
+
gem install activerecord-postgis-adapter
|
99
111
|
|
100
112
|
See the README for the "rgeo" gem, a required dependency, for further
|
101
113
|
installation information.
|
102
114
|
|
103
|
-
===
|
115
|
+
=== Basic Setup
|
104
116
|
|
105
117
|
To use this adapter, add this gem, "activerecord-postgis-adapter", to
|
106
118
|
your Gemfile, and then request the adapter name "postgis" in your
|
107
119
|
database connection configuration (which, for a Rails application, is in
|
108
|
-
the config/database.yml file).
|
120
|
+
the config/database.yml file). Most of the rest of the configuration
|
121
|
+
parameters are identical to those used by the stock "postgresql" adapter,
|
122
|
+
so you can create a new Rails application using:
|
123
|
+
|
124
|
+
rails new my_app --database=postgresql
|
109
125
|
|
110
|
-
|
126
|
+
...and then just change the adapter name to "postgis".
|
127
|
+
|
128
|
+
Next, the PostGIS adapter includes a special railtie that provides
|
111
129
|
support for PostGIS databases in ActiveRecord's rake tasks. This railtie
|
112
130
|
is required in order to run, e.g., rake test. To install this railtie,
|
113
131
|
you should add this line to your config/application.rb:
|
114
132
|
|
115
|
-
|
133
|
+
require 'active_record/connection_adapters/postgis_adapter/railtie'
|
116
134
|
|
117
135
|
Note that this railtie must load after the ActiveRecord railtie. That is,
|
118
136
|
the above require command should appear after <tt>require 'rails/all'</tt>.
|
119
137
|
|
120
138
|
Besides the adapter name "postgis", most of the other database connection
|
121
139
|
configuration parameters are the same as for the stock postgresql adapter.
|
122
|
-
However, there are a
|
123
|
-
|
124
|
-
The <i>script_dir</i> parameter
|
125
|
-
containing the SQL scripts for PostGIS
|
126
|
-
|
127
|
-
|
128
|
-
It is used by the
|
129
|
-
|
130
|
-
|
140
|
+
However, there are a couple minor differences:
|
141
|
+
|
142
|
+
The <i>script_dir</i> parameter is specific to the PostGIS adapter, and
|
143
|
+
provides the path to the directory containing the SQL scripts for PostGIS
|
144
|
+
installation. This directory should contain the files <tt>postgis.sql</tt>
|
145
|
+
and <tt>spatial_ref_sys.sql</tt>. (A common setting for this directory
|
146
|
+
might be <tt>/usr/local/share/contrib/postgis-1.5</tt>.) It is used by the
|
147
|
+
db:create rake task to add the PostGIS types, functions, and tables to a
|
148
|
+
newly created database. If you do not provide this parameter, you will
|
149
|
+
need to add these objects to the database manually.
|
131
150
|
Generally, therefore, this parameter is required at least for the test
|
132
151
|
database, which is usually automatically created by the rake tasks.
|
133
152
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
* Dumping the structure as sql (rake db:structure:dump) may include all
|
138
|
-
the PostGIS definitions as well, cluttering your SQL dump.
|
139
|
-
* Rake tasks that automatically create the test database (e.g. rake test)
|
140
|
-
may emit a number of errors because PostGIS definitions are being added
|
141
|
-
twice to the database: once on creation, and again because they are
|
142
|
-
showing up in the SQL dump that ActiveRecord uses to copy the schema.
|
143
|
-
|
144
|
-
Because of these issues, it is recommended that you include the schema
|
145
|
-
name "postgis" in the <i>schema_search_path</i> parameter. The PostGIS
|
146
|
-
adapter treats this schema name as special, in that:
|
153
|
+
If the schema name "postgis" is included in the <i>schema_search_path</i>
|
154
|
+
parameter, the PostGIS adapter treats it as special, in that:
|
147
155
|
|
148
156
|
* The db:create rake task will automatically create the "postgis" schema,
|
149
|
-
and will create all the PostGIS objects
|
157
|
+
and, if <i>script_dir</i> is set, it will create all the PostGIS objects
|
158
|
+
within that schema.
|
150
159
|
* Dumping the structure as sql will omit objects in the "postgis" schema.
|
160
|
+
This is often useful if you don't want all the PostGIS definitions
|
161
|
+
cluttering up your SQL dump.
|
162
|
+
|
163
|
+
This can be useful in managing the PostGIS definitions in your database,
|
164
|
+
as described below.
|
165
|
+
|
166
|
+
=== Dealing With PostGIS Definitions
|
151
167
|
|
152
|
-
|
153
|
-
|
168
|
+
PostGIS adds many objects (types, functions, triggers, meta-information
|
169
|
+
tables, and other elements) to a PostgreSQL database. These objects are
|
170
|
+
required for PostGIS to do its magic, but they can be a hassle when you
|
171
|
+
are managing a database using Rails and \ActiveRecord. For example:
|
172
|
+
|
173
|
+
* Dumping the structure as sql (rake db:structure:dump) may include all
|
174
|
+
the PostGIS definitions as well, cluttering your SQL dump.
|
175
|
+
* Rake tasks that automatically create the test database (e.g. rake test)
|
176
|
+
may fail or emit a number of errors, because PostGIS definitions are
|
177
|
+
either missing from or being added twice to the test database.
|
178
|
+
|
179
|
+
To deal with these issues, we recommend the following technique:
|
180
|
+
|
181
|
+
* Set <i>script_dir</i> in both your development and test database
|
182
|
+
configurations. This will cause the PostGIS Adapter to automatically
|
183
|
+
add the PostGIS definitions and spatial references to your databases
|
184
|
+
when rake db:create is run.
|
185
|
+
* Include "postgis" in your <i>schema_search_path</i> for both your
|
186
|
+
development and test databases. It is recommended that you include it
|
187
|
+
as the <i>last</i> element, so that your application's tables don't get
|
188
|
+
added to it by default. For example:
|
189
|
+
schema_search_path: public,postgis
|
190
|
+
The PostGIS Adapter responds to this special name by sandboxing the
|
191
|
+
PostGIS definitions into it when rake db:create is run, and it omits
|
192
|
+
this schema when running SQL structure dumps.
|
193
|
+
|
194
|
+
Finally, you generally should _not_ set the \ActiveRecord schema format
|
195
|
+
to <tt>:sql</tt>. You should leave it set to <tt>:ruby</tt>. The reason
|
196
|
+
is that SQL structure dumps do not currently properly emit the correct
|
197
|
+
<tt>AddGeometryColumn</tt> calls to create geometry columns. As a result,
|
198
|
+
the <tt>geometry_columns</tt> table will not be properly populated, among
|
199
|
+
other issues. Instead, the schema.rb output by the Ruby schema format
|
200
|
+
should properly replicate the schema. This is a known issue that we are
|
201
|
+
investigating.
|
202
|
+
|
203
|
+
== Additional Information
|
154
204
|
|
155
205
|
=== Known bugs and limitations
|
156
206
|
|
157
|
-
|
158
|
-
|
159
|
-
|
207
|
+
* Dumping as SQL (i.e. rake db:structure:dump) does not properly emit
|
208
|
+
<tt>AddGeometryColumn</tt> calls, and so does not completely create the
|
209
|
+
spatial schema (e.g. it fails to add the proper row to the
|
210
|
+
<tt>geometry_columns</tt> table.) Because of this, you should not depend
|
211
|
+
on a SQL dump to be an accurate representation of the schema. (That is,
|
212
|
+
you should not set <tt>config.active_record.schema_format</tt> to
|
213
|
+
<tt>:sql</tt>.)
|
160
214
|
|
161
215
|
=== Development and support
|
162
216
|
|
@@ -172,7 +226,8 @@ Contact the author at dazuma at gmail dot com.
|
|
172
226
|
|
173
227
|
=== Acknowledgments
|
174
228
|
|
175
|
-
|
229
|
+
The PostGIS Adapter and its supporting libraries (including RGeo) are
|
230
|
+
written by Daniel Azuma (http://www.daniel-azuma.com).
|
176
231
|
|
177
232
|
Development of RGeo is sponsored by GeoPage, Inc. (http://www.geopage.com).
|
178
233
|
|
data/Version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
@@ -38,33 +38,6 @@ require 'rgeo/active_record'
|
|
38
38
|
require 'active_record/connection_adapters/postgresql_adapter'
|
39
39
|
|
40
40
|
|
41
|
-
# :stopdoc:
|
42
|
-
|
43
|
-
module Arel
|
44
|
-
module Visitors
|
45
|
-
|
46
|
-
class PostGIS < PostgreSQL
|
47
|
-
|
48
|
-
FUNC_MAP = {
|
49
|
-
'ST_WKTToSQL' => 'GeomFromEWKT',
|
50
|
-
}
|
51
|
-
|
52
|
-
include ::RGeo::ActiveRecord::SpatialToSql
|
53
|
-
|
54
|
-
def st_func(standard_name_)
|
55
|
-
FUNC_MAP[standard_name_] || standard_name_
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
VISITORS['postgis'] = ::Arel::Visitors::PostGIS
|
61
|
-
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# :startdoc:
|
66
|
-
|
67
|
-
|
68
41
|
# The activerecord-postgis-adapter gem installs the *postgis*
|
69
42
|
# connection adapter into ActiveRecord.
|
70
43
|
|
@@ -95,376 +68,37 @@ module ActiveRecord
|
|
95
68
|
|
96
69
|
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
97
70
|
# so just pass a nil connection object for the time being.
|
98
|
-
ConnectionAdapters::
|
71
|
+
::ActiveRecord::ConnectionAdapters::PostGISAdapter::MainAdapter.new(nil, logger, [host_, port_, nil, nil, database_, username_, password_], config_)
|
99
72
|
end
|
100
73
|
|
101
74
|
|
102
75
|
end
|
103
76
|
|
104
77
|
|
105
|
-
|
78
|
+
# All ActiveRecord adapters go in this namespace.
|
79
|
+
module ConnectionAdapters
|
106
80
|
|
107
|
-
|
108
|
-
|
109
|
-
|
81
|
+
# The PostGIS Adapter
|
82
|
+
module PostGISAdapter
|
110
83
|
|
84
|
+
# The name returned by the adapter_name method of this adapter.
|
111
85
|
ADAPTER_NAME = 'PostGIS'.freeze
|
112
86
|
|
113
|
-
@@native_database_types = nil
|
114
|
-
|
115
|
-
|
116
|
-
def native_database_types
|
117
|
-
@@native_database_types ||= super.merge(:geometry => {:name => "geometry"}, :point => {:name => "point"}, :line_string => {:name => "linestring"}, :polygon => {:name => "polygon"}, :geometry_collection => {:name => "geometrycollection"}, :multi_point => {:name => "multipoint"}, :multi_line_string => {:name => "multilinestring"}, :multi_polygon => {:name => "multipolygon"})
|
118
|
-
end
|
119
|
-
|
120
|
-
|
121
|
-
def adapter_name
|
122
|
-
ADAPTER_NAME
|
123
|
-
end
|
124
|
-
|
125
|
-
|
126
|
-
def postgis_lib_version
|
127
|
-
unless defined?(@postgis_lib_version)
|
128
|
-
@postgis_lib_version = select_value("SELECT PostGIS_Lib_Version()") rescue nil
|
129
|
-
end
|
130
|
-
@postgis_lib_version
|
131
|
-
end
|
132
|
-
|
133
|
-
|
134
|
-
def srs_database_columns
|
135
|
-
{:srtext_column => 'srtext', :proj4text_column => 'proj4text', :auth_name_column => 'auth_name', :auth_srid_column => 'auth_srid'}
|
136
|
-
end
|
137
|
-
|
138
|
-
|
139
|
-
def quote(value_, column_=nil)
|
140
|
-
if ::RGeo::Feature::Geometry.check_type(value_)
|
141
|
-
"'#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true, :type_format => :ewkb, :emit_ewkb_srid => true).generate(value_)}'"
|
142
|
-
else
|
143
|
-
super
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
|
148
|
-
def columns(table_name_, name_=nil) #:nodoc:
|
149
|
-
table_name_ = table_name_.to_s
|
150
|
-
spatial_info_ = spatial_column_info(table_name_)
|
151
|
-
column_definitions(table_name_).collect do |name_, type_, default_, notnull_|
|
152
|
-
SpatialColumn.new(name_, default_, type_, notnull_ == 'f', type_ =~ /geometry/i ? spatial_info_[name_] : nil)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
|
157
|
-
def create_table(table_name_, options_={})
|
158
|
-
table_name_ = table_name_.to_s
|
159
|
-
table_definition_ = SpatialTableDefinition.new(self)
|
160
|
-
table_definition_.primary_key(options_[:primary_key] || ::ActiveRecord::Base.get_primary_key(table_name_.singularize)) unless options_[:id] == false
|
161
|
-
yield table_definition_ if block_given?
|
162
|
-
if options_[:force] && table_exists?(table_name_)
|
163
|
-
drop_table(table_name_, options_)
|
164
|
-
end
|
165
|
-
|
166
|
-
create_sql_ = "CREATE#{' TEMPORARY' if options_[:temporary]} TABLE "
|
167
|
-
create_sql_ << "#{quote_table_name(table_name_)} ("
|
168
|
-
create_sql_ << table_definition_.to_sql
|
169
|
-
create_sql_ << ") #{options_[:options]}"
|
170
|
-
execute create_sql_
|
171
|
-
|
172
|
-
table_definition_.non_geographic_spatial_columns.each do |col_|
|
173
|
-
type_ = col_.type.to_s.gsub('_', '').upcase
|
174
|
-
has_z_ = col_.has_z?
|
175
|
-
has_m_ = col_.has_m?
|
176
|
-
type_ = "#{type_}M" if has_m_ && !has_z_
|
177
|
-
dimensions_ = 2
|
178
|
-
dimensions_ += 1 if has_z_
|
179
|
-
dimensions_ += 1 if has_m_
|
180
|
-
execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(col_.name.to_s)}', #{col_.srid}, '#{quote_string(type_)}', #{dimensions_})")
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
|
185
|
-
def drop_table(table_name_, options_={})
|
186
|
-
execute("DELETE from geometry_columns where f_table_name='#{quote_string(table_name_.to_s)}'")
|
187
|
-
super
|
188
|
-
end
|
189
|
-
|
190
|
-
|
191
|
-
def add_column(table_name_, column_name_, type_, options_={})
|
192
|
-
table_name_ = table_name_.to_s
|
193
|
-
if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(type_.to_sym)
|
194
|
-
type_ = type_.to_s.gsub('_', '').upcase
|
195
|
-
has_z_ = options_[:has_z]
|
196
|
-
has_m_ = options_[:has_m]
|
197
|
-
srid_ = (options_[:srid] || 4326).to_i
|
198
|
-
if options_[:geographic]
|
199
|
-
type_ << 'Z' if has_z_
|
200
|
-
type_ << 'M' if has_m_
|
201
|
-
execute("ALTER TABLE #{quote_table_name(table_name_)} ADD COLUMN #{quote_column_name(column_name_)} GEOGRAPHY(#{type_},#{srid_})")
|
202
|
-
change_column_default(table_name_, column_name_, options_[:default]) if options_include_default?(options_)
|
203
|
-
change_column_null(table_name_, column_name_, false, options_[:default]) if options_[:null] == false
|
204
|
-
else
|
205
|
-
type_ = "#{type_}M" if has_m_ && !has_z_
|
206
|
-
dimensions_ = 2
|
207
|
-
dimensions_ += 1 if has_z_
|
208
|
-
dimensions_ += 1 if has_m_
|
209
|
-
execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(column_name_.to_s)}', #{srid_}, '#{quote_string(type_)}', #{dimensions_})")
|
210
|
-
end
|
211
|
-
else
|
212
|
-
super
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
|
217
|
-
def remove_column(table_name_, *column_names_)
|
218
|
-
column_names_ = column_names_.flatten.map{ |n_| n_.to_s }
|
219
|
-
spatial_info_ = spatial_column_info(table_name_)
|
220
|
-
remaining_column_names_ = []
|
221
|
-
column_names_.each do |name_|
|
222
|
-
if spatial_info_.include?(name_)
|
223
|
-
execute("SELECT DropGeometryColumn('#{quote_string(table_name_.to_s)}','#{quote_string(name_)}')")
|
224
|
-
else
|
225
|
-
remaining_column_names_ << name_.to_sym
|
226
|
-
end
|
227
|
-
end
|
228
|
-
if remaining_column_names_.size > 0
|
229
|
-
super(table_name_, *remaining_column_names_)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
|
234
|
-
def add_index(table_name_, column_name_, options_={})
|
235
|
-
table_name_ = table_name_.to_s
|
236
|
-
column_names_ = ::Array.wrap(column_name_)
|
237
|
-
index_name_ = index_name(table_name_, :column => column_names_)
|
238
|
-
gist_clause_ = ''
|
239
|
-
index_type_ = ''
|
240
|
-
if ::Hash === options_ # legacy support, since this param was a string
|
241
|
-
index_type_ = 'UNIQUE' if options_[:unique]
|
242
|
-
index_name_ = options_[:name].to_s if options_.key?(:name)
|
243
|
-
gist_clause_ = 'USING GIST' if options_[:spatial]
|
244
|
-
else
|
245
|
-
index_type_ = options_
|
246
|
-
end
|
247
|
-
if index_name_.length > index_name_length
|
248
|
-
raise ::ArgumentError, "Index name '#{index_name_}' on table '#{table_name_}' is too long; the limit is #{index_name_length} characters"
|
249
|
-
end
|
250
|
-
if index_name_exists?(table_name_, index_name_, false)
|
251
|
-
raise ::ArgumentError, "Index name '#{index_name_}' on table '#{table_name_}' already exists"
|
252
|
-
end
|
253
|
-
quoted_column_names_ = quoted_columns_for_index(column_names_, options_).join(", ")
|
254
|
-
execute "CREATE #{index_type_} INDEX #{quote_column_name(index_name_)} ON #{quote_table_name(table_name_)} #{gist_clause_} (#{quoted_column_names_})"
|
255
|
-
end
|
256
|
-
|
257
|
-
|
258
|
-
def spatial_column_info(table_name_)
|
259
|
-
info_ = query("SELECT * FROM geometry_columns WHERE f_table_name='#{quote_string(table_name_.to_s)}'")
|
260
|
-
result_ = {}
|
261
|
-
info_.each do |row_|
|
262
|
-
name_ = row_[3]
|
263
|
-
type_ = row_[6]
|
264
|
-
dimension_ = row_[4].to_i
|
265
|
-
has_m_ = type_ =~ /m$/i ? true : false
|
266
|
-
type_.sub!(/m$/, '')
|
267
|
-
has_z_ = dimension_ > 3 || dimension_ == 3 && !has_m_
|
268
|
-
result_[name_] = {
|
269
|
-
:name => name_,
|
270
|
-
:type => type_,
|
271
|
-
:dimension => dimension_,
|
272
|
-
:srid => row_[5].to_i,
|
273
|
-
:has_z => has_z_,
|
274
|
-
:has_m => has_m_,
|
275
|
-
}
|
276
|
-
end
|
277
|
-
result_
|
278
|
-
end
|
279
|
-
|
280
|
-
|
281
|
-
class SpatialTableDefinition < ConnectionAdapters::TableDefinition # :nodoc:
|
282
|
-
|
283
|
-
attr_reader :spatial_columns
|
284
|
-
|
285
|
-
def initialize(base_)
|
286
|
-
super
|
287
|
-
end
|
288
|
-
|
289
|
-
def column(name_, type_, options_={})
|
290
|
-
super
|
291
|
-
col_ = self[name_]
|
292
|
-
if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(col_.type.to_sym)
|
293
|
-
col_.extend(GeometricColumnDefinitionMethods) unless col_.respond_to?(:geographic?)
|
294
|
-
col_.set_geographic(options_[:geographic])
|
295
|
-
col_.set_srid((options_[:srid] || 4326).to_i)
|
296
|
-
col_.set_has_z(options_[:has_z])
|
297
|
-
col_.set_has_m(options_[:has_m])
|
298
|
-
end
|
299
|
-
self
|
300
|
-
end
|
301
|
-
|
302
|
-
def to_sql
|
303
|
-
@columns.find_all{ |c_| !c_.respond_to?(:geographic?) || c_.geographic? }.map{ |c_| c_.to_sql } * ', '
|
304
|
-
end
|
305
|
-
|
306
|
-
def non_geographic_spatial_columns
|
307
|
-
@columns.find_all{ |c_| c_.respond_to?(:geographic?) && !c_.geographic? }
|
308
|
-
end
|
309
|
-
|
310
|
-
end
|
311
|
-
|
312
|
-
|
313
|
-
module GeometricColumnDefinitionMethods # :nodoc:
|
314
|
-
|
315
|
-
def geographic?
|
316
|
-
defined?(@geographic) && @geographic
|
317
|
-
end
|
318
|
-
|
319
|
-
def srid
|
320
|
-
defined?(@srid) ? @srid : 4326
|
321
|
-
end
|
322
|
-
|
323
|
-
def has_z?
|
324
|
-
defined?(@has_z) && @has_z
|
325
|
-
end
|
326
|
-
|
327
|
-
def has_m?
|
328
|
-
defined?(@has_m) && @has_m
|
329
|
-
end
|
330
|
-
|
331
|
-
def set_geographic(value_)
|
332
|
-
@geographic = value_ ? true : false
|
333
|
-
end
|
334
|
-
|
335
|
-
def set_srid(value_)
|
336
|
-
@srid = value_
|
337
|
-
end
|
338
|
-
|
339
|
-
def set_has_z(value_)
|
340
|
-
@has_z = value_ ? true : false
|
341
|
-
end
|
342
|
-
|
343
|
-
def set_has_m(value_)
|
344
|
-
@has_m = value_ ? true : false
|
345
|
-
end
|
346
|
-
|
347
|
-
def sql_type
|
348
|
-
type_ = type.to_s.upcase.gsub('_', '')
|
349
|
-
type_ << 'Z' if has_z?
|
350
|
-
type_ << 'M' if has_m?
|
351
|
-
"GEOGRAPHY(#{type_},#{srid})"
|
352
|
-
end
|
353
|
-
|
354
|
-
end
|
355
|
-
|
356
|
-
|
357
|
-
class SpatialColumn < ConnectionAdapters::PostgreSQLColumn # :nodoc:
|
358
|
-
|
359
|
-
|
360
|
-
def initialize(name_, default_, sql_type_=nil, null_=true, opts_=nil)
|
361
|
-
super(name_, default_, sql_type_, null_)
|
362
|
-
@geographic = sql_type_ =~ /^geography/ ? true : false
|
363
|
-
if opts_
|
364
|
-
@geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name(opts_[:type])
|
365
|
-
@srid = opts_[:srid].to_i
|
366
|
-
@has_z = opts_[:has_z]
|
367
|
-
@has_m = opts_[:has_m]
|
368
|
-
elsif @geographic
|
369
|
-
if sql_type_ =~ /geography\((\w+[^,zm])(z?)(m?),(\d+)\)/i
|
370
|
-
@has_z = $2.length > 0
|
371
|
-
@has_m = $3.length > 0
|
372
|
-
@srid = $4.to_i
|
373
|
-
@geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name($1)
|
374
|
-
else
|
375
|
-
@geometric_type = ::RGeo::Feature::Geometry
|
376
|
-
@srid = 4326
|
377
|
-
@has_z = @has_m = false
|
378
|
-
end
|
379
|
-
else
|
380
|
-
@geometric_type = @has_z = @has_m = nil
|
381
|
-
@srid = 0
|
382
|
-
end
|
383
|
-
@ar_class = ::ActiveRecord::Base
|
384
|
-
end
|
385
|
-
|
386
|
-
|
387
|
-
def set_ar_class(val_)
|
388
|
-
@ar_class = val_
|
389
|
-
end
|
390
|
-
|
391
|
-
|
392
|
-
attr_reader :srid
|
393
|
-
attr_reader :geometric_type
|
394
|
-
attr_reader :has_z
|
395
|
-
attr_reader :has_m
|
396
|
-
|
397
|
-
|
398
|
-
def spatial?
|
399
|
-
type == :geometry
|
400
|
-
end
|
401
|
-
|
402
|
-
|
403
|
-
def geographic?
|
404
|
-
@geographic
|
405
|
-
end
|
406
|
-
|
407
|
-
|
408
|
-
def klass
|
409
|
-
type == :geometry ? ::RGeo::Feature::Geometry : super
|
410
|
-
end
|
411
|
-
|
412
|
-
|
413
|
-
def type_cast(value_)
|
414
|
-
type == :geometry ? SpatialColumn.convert_to_geometry(value_, @ar_class, name, @geographic, @srid, @has_z, @has_m) : super
|
415
|
-
end
|
416
|
-
|
417
|
-
|
418
|
-
def type_cast_code(var_name_)
|
419
|
-
type == :geometry ? "::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
|
420
|
-
end
|
421
|
-
|
422
|
-
|
423
|
-
private
|
424
|
-
|
425
|
-
|
426
|
-
def simplified_type(sql_type_)
|
427
|
-
sql_type_ =~ /geography|geometry|point|linestring|polygon/i ? :geometry : super
|
428
|
-
end
|
429
|
-
|
430
|
-
|
431
|
-
def self.convert_to_geometry(input_, ar_class_, column_, geographic_, srid_, has_z_, has_m_)
|
432
|
-
case input_
|
433
|
-
when ::RGeo::Feature::Geometry
|
434
|
-
factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => srid_, :has_z_coordinate => has_z_, :has_m_coordinate => has_m_, :geographic => geographic_)
|
435
|
-
::RGeo::Feature.cast(input_, factory_)
|
436
|
-
when ::String
|
437
|
-
if input_.length == 0
|
438
|
-
nil
|
439
|
-
else
|
440
|
-
factory_ = ar_class_.rgeo_factory_for_column(column_, :srid => srid_, :has_z_coordinate => has_z_, :has_m_coordinate => has_m_, :geographic => geographic_)
|
441
|
-
marker_ = input_[0,1]
|
442
|
-
if marker_ == "\x00" || marker_ == "\x01"
|
443
|
-
::RGeo::WKRep::WKBParser.new(factory_, :support_ewkb => true).parse(input_) rescue nil
|
444
|
-
elsif input_[0,4] =~ /[0-9a-fA-F]{4}/
|
445
|
-
::RGeo::WKRep::WKBParser.new(factory_, :support_ewkb => true).parse_hex(input_) rescue nil
|
446
|
-
else
|
447
|
-
::RGeo::WKRep::WKTParser.new(factory_, :support_ewkt => true).parse(input_) rescue nil
|
448
|
-
end
|
449
|
-
end
|
450
|
-
else
|
451
|
-
nil
|
452
|
-
end
|
453
|
-
end
|
454
|
-
|
455
|
-
|
456
|
-
end
|
457
|
-
|
458
|
-
|
459
87
|
end
|
460
88
|
|
461
|
-
|
462
89
|
end
|
463
90
|
|
464
91
|
|
465
92
|
end
|
466
93
|
|
467
94
|
|
95
|
+
require 'active_record/connection_adapters/postgis_adapter/version.rb'
|
96
|
+
require 'active_record/connection_adapters/postgis_adapter/main_adapter.rb'
|
97
|
+
require 'active_record/connection_adapters/postgis_adapter/spatial_table_definition.rb'
|
98
|
+
require 'active_record/connection_adapters/postgis_adapter/spatial_column.rb'
|
99
|
+
require 'active_record/connection_adapters/postgis_adapter/arel_tosql.rb'
|
100
|
+
|
101
|
+
|
468
102
|
ignore_tables_ = ::ActiveRecord::SchemaDumper.ignore_tables
|
469
103
|
ignore_tables_ << 'geometry_columns' unless ignore_tables_.include?('geometry_columns')
|
470
104
|
ignore_tables_ << 'spatial_ref_sys' unless ignore_tables_.include?('spatial_ref_sys')
|