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
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')
|