activerecord-spatialite-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 +97 -44
- data/Version +1 -1
- data/lib/active_record/connection_adapters/spatialite_adapter.rb +23 -426
- data/lib/active_record/connection_adapters/spatialite_adapter/arel_tosql.rb +61 -0
- data/lib/{rgeo/active_record → active_record/connection_adapters}/spatialite_adapter/databases.rake +1 -0
- data/lib/active_record/connection_adapters/spatialite_adapter/main_adapter.rb +234 -0
- data/lib/active_record/connection_adapters/spatialite_adapter/native_format_parser.rb +163 -0
- data/lib/active_record/connection_adapters/spatialite_adapter/railtie.rb +64 -0
- data/lib/active_record/connection_adapters/spatialite_adapter/spatial_column.rb +135 -0
- data/lib/active_record/connection_adapters/spatialite_adapter/spatial_table_definition.rb +102 -0
- data/lib/active_record/connection_adapters/spatialite_adapter/version.rb +63 -0
- data/lib/rgeo/active_record/spatialite_adapter/railtie.rb +2 -21
- data/test/tc_basic.rb +55 -21
- data/test/tc_spatial_queries.rb +165 -0
- metadata +20 -11
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
|
+
* API CHANGE: The indexes method now returns all indexes including spatial indexes; removed the separate spatial_indexes method.
|
6
|
+
* The path to the Railtie is now different (see the README), though a compatibility wrapper has been left in the old location.
|
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 sqlite3 adapter, this adapter requires
|
8
8
|
the sqlite3-ruby 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
|
@@ -17,24 +19,29 @@ the <tt>:spatial</tt> option to true.
|
|
17
19
|
|
18
20
|
Examples:
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
create_table :my_spatial_table do |t|
|
23
|
+
t.column :latlon, :point # or t.point :latlon
|
24
|
+
t.line_string :path
|
25
|
+
t.geometry :shape
|
26
|
+
end
|
27
|
+
change_table :my_spatial_table do |t|
|
28
|
+
t.index :latlon, :spatial => true
|
29
|
+
end
|
30
|
+
|
31
|
+
=== Spatial Attributes
|
28
32
|
|
29
33
|
When this adapter is in use, spatial attributes in your \ActiveRecord
|
30
34
|
objects will have RGeo geometry values. You can set spatial attributes
|
31
35
|
either to RGeo geometry objects, or to strings in WKT (well-known text)
|
32
36
|
format, which the adapter will automatically convert to geometry objects.
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
+
Spatial objects in RGeo are tied to a factory that specifies the
|
39
|
+
coordinate system as well as other behaviors of the object. You must
|
40
|
+
therefore specify a factory for each spatial column (attribute) in your
|
41
|
+
ActiveRecord class. You can either set an explicit factory for a specific
|
42
|
+
column, or provide a factory generator that will yield the appropriate
|
43
|
+
factory for the table's spatial columns based on their types. For the
|
44
|
+
former, call the <tt>set_rgeo_factory_for_column</tt> class method on your
|
38
45
|
\ActiveRecord class. For the latter, set the rgeo_factory_generator class
|
39
46
|
attribute. This generator should understand at least the <tt>:srid</tt>
|
40
47
|
option, which will be provided based on the column's specified SRID. Note
|
@@ -45,39 +52,45 @@ actually implemented and documented in the "rgeo-activerecord" gem.
|
|
45
52
|
|
46
53
|
Examples, given the spatial table defined above:
|
47
54
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
class MySpatialTable < ActiveRecord::Base
|
56
|
+
|
57
|
+
# By default, use the GEOS implementation for spatial columns.
|
58
|
+
self.rgeo_factory_generator = RGeo::Geos.method(:factory)
|
59
|
+
|
60
|
+
# But use a geographic implementation for the :latlon column.
|
61
|
+
set_rgeo_factory_for_column(:latlon, RGeo::Geographic.spherical_factory)
|
62
|
+
|
63
|
+
end
|
57
64
|
|
58
65
|
Now you can interact with the data using the RGeo types:
|
59
66
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
rec = MySpatialTable.new
|
68
|
+
rec.latlon = 'POINT(-122 47)' # You can set by feature object or WKT.
|
69
|
+
loc = rec.latlon # Accessing always returns a feature object, in
|
70
|
+
# this case, a geographic that understands latitude.
|
71
|
+
loc.latitude # => 47
|
72
|
+
rec.shape = loc # the factory for the :shape column is GEOS, so the
|
73
|
+
# value will be cast from geographic to GEOS.
|
74
|
+
RGeo::Geos.is_geos?(rec.shape) # => true
|
75
|
+
|
76
|
+
=== Spatial Queries
|
68
77
|
|
69
78
|
You can create simple queries based on spatial equality in the same way
|
70
79
|
you would on a scalar column:
|
71
80
|
|
72
|
-
|
81
|
+
rec = MySpatialTable.where(:latlon => RGeo::Geos.factory.point(-122, 47)).first
|
73
82
|
|
74
83
|
You can also use WKT:
|
75
84
|
|
76
|
-
|
85
|
+
rec = MySpatialTable.where(:latlon => 'POINT(-122 47)').first
|
86
|
+
|
87
|
+
The adapter also provides experimental support for more complex queries
|
88
|
+
such as radius searches. However, these extensions require Arel 2.1
|
89
|
+
(which is scheduled for release with Rails 3.1). We do not have these
|
90
|
+
documented yet, and the syntax is subject to change. For now, you should
|
91
|
+
write more complex queries in SQL.
|
77
92
|
|
78
|
-
|
79
|
-
investigating writing a set of Arel extensions for constructing arbitrary
|
80
|
-
spatial queries.
|
93
|
+
== Installation And Configuration
|
81
94
|
|
82
95
|
=== Installing The Adapter Gem
|
83
96
|
|
@@ -87,12 +100,12 @@ This adapter has the following requirements:
|
|
87
100
|
* SpatiaLite 2.3 or later (2.4 recommended).
|
88
101
|
* \ActiveRecord 3.0.3 or later. Earlier versions will not work.
|
89
102
|
* rgeo gem 0.2.4 or later.
|
90
|
-
* rgeo-activerecord gem 0.
|
91
|
-
* sqlite3 gem 1.3 or later.
|
103
|
+
* rgeo-activerecord gem 0.3.0 or later.
|
104
|
+
* sqlite3 gem 1.3.3 or later.
|
92
105
|
|
93
106
|
Install this adapter as a gem:
|
94
107
|
|
95
|
-
|
108
|
+
gem install activerecord-spatialite-adapter
|
96
109
|
|
97
110
|
See the README for the "rgeo" gem, a required dependency, for further
|
98
111
|
installation information.
|
@@ -109,16 +122,55 @@ which should be set to the full path to the libspatialite shared library,
|
|
109
122
|
if it is not installed in a standard place (such as /usr/local or
|
110
123
|
/opt/local).
|
111
124
|
|
112
|
-
|
113
|
-
provides support for SpatiaLite databases in ActiveRecord's rake tasks.
|
114
|
-
This railtie is required in order to run, e.g., rake test. To install
|
115
|
-
this railtie, you should add this line to your config/application.rb:
|
125
|
+
Generally, you can create a new Rails application using:
|
116
126
|
|
117
|
-
|
127
|
+
rails new my_app --database=sqlite3
|
128
|
+
|
129
|
+
...and then change the adapter names to "<tt>spatialite</tt>" and add an
|
130
|
+
appropriate <tt>libspatialite</tt> setting.
|
131
|
+
|
132
|
+
Next, the SpatiaLite adapter includes a special railtie that provides
|
133
|
+
support for SpatiaLite databases in ActiveRecord's rake tasks. This
|
134
|
+
railtie is required in order to run, e.g., rake test. To install this
|
135
|
+
railtie, you should add this line to your config/application.rb:
|
136
|
+
|
137
|
+
require 'active_record/connection_adapters/spatialite_adapter/railtie'
|
118
138
|
|
119
139
|
Note that this railtie must load after the ActiveRecord railtie. That is,
|
120
140
|
the above require command should appear after <tt>require 'rails/all'</tt>.
|
121
141
|
|
142
|
+
=== Dealing with SpatiaLite Definitions
|
143
|
+
|
144
|
+
SpatiaLite adds many objects (meta-information tables, functions,
|
145
|
+
triggers, etc.) to a Sqlite3 database. These objects are required to
|
146
|
+
maintain the spatial elements of the database, but they can be a hassle
|
147
|
+
when managing the database with Rails. Following are some tips and
|
148
|
+
gotchas that you may encounter.
|
149
|
+
|
150
|
+
Make sure you include the correct <tt>libspatialite</tt> setting in your
|
151
|
+
database.yml config file, especially for your production environments.
|
152
|
+
|
153
|
+
SpatiaLite databases need to be initialized by executing the SpatiaLite
|
154
|
+
initialization script or by calling the InitSpatialMetaData() function.
|
155
|
+
The rake db:create task will do this for you when it creates a database.
|
156
|
+
Thus, when setting up a new application, you should make sure you call
|
157
|
+
rake db:create or otherwise cause the SpatiaLite initialization to
|
158
|
+
occur, before you attempt to run your first migration. Failure to do so
|
159
|
+
will result in errors during the migration.
|
160
|
+
|
161
|
+
Dumping a SpatiaLite database as SQL will cause a bunch of internal tables
|
162
|
+
and triggers to be included in your dump. These are the actual SpatiaLite
|
163
|
+
implementation objects used to enforce spatial constraints and implement
|
164
|
+
spatial indexes. Unfortunately, not only is this a bit unsightly, but not
|
165
|
+
everything is dumped here: for example, for each spatial column, there
|
166
|
+
should be a row in the geometry_columns table, and those will be missing
|
167
|
+
in the SQL structure dump. As a result, loading from the SQL structure
|
168
|
+
dump will not properly reproduce your database schema. Because of this, we
|
169
|
+
highly recommend that you leave config.active_record.schema_format set to
|
170
|
+
<tt>:ruby</tt> for now, so that schema dumps are done in the Ruby format.
|
171
|
+
|
172
|
+
== Additional Information
|
173
|
+
|
122
174
|
=== Known bugs and limitations
|
123
175
|
|
124
176
|
The spatialite adapter works in principle, but there are a few known holes
|
@@ -145,7 +197,8 @@ Contact the author at dazuma at gmail dot com.
|
|
145
197
|
|
146
198
|
=== Acknowledgments
|
147
199
|
|
148
|
-
|
200
|
+
The SpatiaLite Adapter and its supporting libraries (including RGeo) are
|
201
|
+
written by Daniel Azuma (http://www.daniel-azuma.com).
|
149
202
|
|
150
203
|
Development of RGeo is sponsored by GeoPage, Inc. (http://www.geopage.com).
|
151
204
|
|
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/sqlite3_adapter'
|
39
39
|
|
40
40
|
|
41
|
-
# :stopdoc:
|
42
|
-
|
43
|
-
module Arel
|
44
|
-
module Visitors
|
45
|
-
|
46
|
-
class SpatiaLite < SQLite
|
47
|
-
|
48
|
-
FUNC_MAP = {
|
49
|
-
'ST_WKTToSQL' => 'GeomFromText',
|
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['spatialite'] = ::Arel::Visitors::SpatiaLite
|
61
|
-
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# :startdoc:
|
66
|
-
|
67
|
-
|
68
41
|
# The activerecord-spatialite-adapter gem installs the *spatialite*
|
69
42
|
# connection adapter into ActiveRecord.
|
70
43
|
|
@@ -122,418 +95,42 @@ module ActiveRecord
|
|
122
95
|
db_.enable_load_extension(1)
|
123
96
|
db_.load_extension(path_)
|
124
97
|
|
125
|
-
ConnectionAdapters::SpatiaLiteAdapter.new(db_, logger, config_)
|
98
|
+
::ActiveRecord::ConnectionAdapters::SpatiaLiteAdapter::MainAdapter.new(db_, logger, config_)
|
126
99
|
end
|
127
100
|
|
128
101
|
|
129
102
|
end
|
130
103
|
|
131
104
|
|
132
|
-
|
133
|
-
|
105
|
+
# All ActiveRecord adapters go in this namespace.
|
106
|
+
module ConnectionAdapters
|
134
107
|
|
135
|
-
|
136
|
-
|
108
|
+
# The SpatiaLite Adapter
|
109
|
+
module SpatiaLiteAdapter
|
137
110
|
|
111
|
+
# The name returned by the adapter_name method of this adapter.
|
138
112
|
ADAPTER_NAME = 'SpatiaLite'.freeze
|
139
113
|
|
140
|
-
@@native_database_types = nil
|
141
|
-
|
142
|
-
|
143
|
-
def native_database_types
|
144
|
-
unless @@native_database_types
|
145
|
-
@@native_database_types = super.dup
|
146
|
-
@@native_database_types.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"})
|
147
|
-
end
|
148
|
-
@@native_database_types
|
149
|
-
end
|
150
|
-
|
151
|
-
|
152
|
-
def adapter_name
|
153
|
-
ADAPTER_NAME
|
154
|
-
end
|
155
|
-
|
156
|
-
|
157
|
-
def spatialite_version
|
158
|
-
@spatialite_version ||= SQLiteAdapter::Version.new(select_value('SELECT spatialite_version()'))
|
159
|
-
end
|
160
|
-
|
161
|
-
|
162
|
-
def srs_database_columns
|
163
|
-
{:name_column => 'ref_sys_name', :proj4text_column => 'proj4text', :auth_name_column => 'auth_name', :auth_srid_column => 'auth_srid'}
|
164
|
-
end
|
165
|
-
|
166
|
-
|
167
|
-
def quote(value_, column_=nil)
|
168
|
-
if ::RGeo::Feature::Geometry.check_type(value_)
|
169
|
-
"GeomFromWKB(X'#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true).generate(value_)}', #{value_.srid})"
|
170
|
-
else
|
171
|
-
super
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
|
176
|
-
def columns(table_name_, name_=nil) #:nodoc:
|
177
|
-
spatial_info_ = spatial_column_info(table_name_)
|
178
|
-
table_structure(table_name_).map do |field_|
|
179
|
-
col_ = SpatialColumn.new(field_['name'], field_['dflt_value'], field_['type'], field_['notnull'].to_i == 0)
|
180
|
-
info_ = spatial_info_[field_['name']]
|
181
|
-
if info_
|
182
|
-
col_.set_srid(info_[:srid])
|
183
|
-
end
|
184
|
-
col_
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
|
189
|
-
def spatial_indexes(table_name_, name_=nil)
|
190
|
-
table_name_ = table_name_.to_s
|
191
|
-
names_ = select_values("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'idx_#{quote_string(table_name_)}_%' AND rootpage=0") || []
|
192
|
-
names_.map do |name_|
|
193
|
-
col_name_ = name_.sub("idx_#{table_name_}_", '')
|
194
|
-
::RGeo::ActiveRecord::SpatialIndexDefinition.new(table_name_, name_, false, [col_name_], [], true)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
|
199
|
-
def create_table(table_name_, options_={})
|
200
|
-
table_name_ = table_name_.to_s
|
201
|
-
table_definition_ = SpatialTableDefinition.new(self)
|
202
|
-
table_definition_.primary_key(options_[:primary_key] || ::ActiveRecord::Base.get_primary_key(table_name_.singularize)) unless options_[:id] == false
|
203
|
-
yield table_definition_ if block_given?
|
204
|
-
if options_[:force] && table_exists?(table_name_)
|
205
|
-
drop_table(table_name_, options_)
|
206
|
-
end
|
207
|
-
|
208
|
-
create_sql_ = "CREATE#{' TEMPORARY' if options_[:temporary]} TABLE "
|
209
|
-
create_sql_ << "#{quote_table_name(table_name_)} ("
|
210
|
-
create_sql_ << table_definition_.to_sql
|
211
|
-
create_sql_ << ") #{options_[:options]}"
|
212
|
-
execute create_sql_
|
213
|
-
|
214
|
-
table_definition_.spatial_columns.each do |col_|
|
215
|
-
execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(col_.name.to_s)}', #{col_.srid}, '#{quote_string(col_.type.to_s.gsub('_','').upcase)}', 'XY', #{col_.null ? 0 : 1})")
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
|
220
|
-
def drop_table(table_name_, options_={})
|
221
|
-
spatial_indexes(table_name_).each do |index_|
|
222
|
-
remove_index(table_name_, :spatial => true, :column => index_.columns[0])
|
223
|
-
end
|
224
|
-
execute("DELETE from geometry_columns where f_table_name='#{quote_string(table_name_.to_s)}'")
|
225
|
-
super
|
226
|
-
end
|
227
|
-
|
228
|
-
|
229
|
-
def add_column(table_name_, column_name_, type_, options_={})
|
230
|
-
if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(type_.to_sym)
|
231
|
-
execute("SELECT AddGeometryColumn('#{quote_string(table_name_.to_s)}', '#{quote_string(column_name_.to_s)}', #{options_[:srid].to_i}, '#{quote_string(type_.to_s)}', 'XY', #{options_[:null] == false ? 0 : 1})")
|
232
|
-
else
|
233
|
-
super
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
|
238
|
-
def add_index(table_name_, column_name_, options_={})
|
239
|
-
if options_[:spatial]
|
240
|
-
column_name_ = column_name_.first if column_name_.kind_of?(::Array) && column_name_.size == 1
|
241
|
-
table_name_ = table_name_.to_s
|
242
|
-
column_name_ = column_name_.to_s
|
243
|
-
spatial_info_ = spatial_column_info(table_name_)
|
244
|
-
unless spatial_info_[column_name_]
|
245
|
-
raise ::ArgumentError, "Can't create spatial index because column '#{column_name_}' in table '#{table_name_}' is not a geometry column"
|
246
|
-
end
|
247
|
-
result_ = select_value("SELECT CreateSpatialIndex('#{quote_string(table_name_)}', '#{quote_string(column_name_)}')").to_i
|
248
|
-
if result_ == 0
|
249
|
-
raise ::ArgumentError, "Spatial index already exists on table '#{table_name_}', column '#{column_name_}'"
|
250
|
-
end
|
251
|
-
result_
|
252
|
-
else
|
253
|
-
super
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
|
258
|
-
def remove_index(table_name_, options_={})
|
259
|
-
if options_[:spatial]
|
260
|
-
table_name_ = table_name_.to_s
|
261
|
-
column_ = options_[:column]
|
262
|
-
if column_
|
263
|
-
column_ = column_[0] if column_.kind_of?(::Array)
|
264
|
-
column_ = column_.to_s
|
265
|
-
else
|
266
|
-
index_name_ = options_[:name]
|
267
|
-
unless index_name_
|
268
|
-
raise ::ArgumentError, "You need to specify a column or index name to remove a spatial index."
|
269
|
-
end
|
270
|
-
if index_name_ =~ /^idx_#{table_name_}_(\w+)$/
|
271
|
-
column_ = $1
|
272
|
-
else
|
273
|
-
raise ::ArgumentError, "Unknown spatial index name: #{index_name_.inspect}."
|
274
|
-
end
|
275
|
-
end
|
276
|
-
spatial_info_ = spatial_column_info(table_name_)
|
277
|
-
unless spatial_info_[column_]
|
278
|
-
raise ::ArgumentError, "Can't remove spatial index because column '#{column_}' in table '#{table_name_}' is not a geometry column"
|
279
|
-
end
|
280
|
-
index_name_ = "idx_#{table_name_}_#{column_}"
|
281
|
-
has_index_ = select_value("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='#{quote_string(index_name_)}'").to_i > 0
|
282
|
-
unless has_index_
|
283
|
-
raise ::ArgumentError, "Spatial index not present on table '#{table_name_}', column '#{column_}'"
|
284
|
-
end
|
285
|
-
execute("SELECT DisableSpatialIndex('#{quote_string(table_name_)}', '#{quote_string(column_)}')")
|
286
|
-
execute("DROP TABLE #{quote_table_name(index_name_)}")
|
287
|
-
else
|
288
|
-
super
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
|
293
|
-
def spatial_column_info(table_name_)
|
294
|
-
info_ = execute("SELECT * FROM geometry_columns WHERE f_table_name='#{quote_string(table_name_.to_s)}'")
|
295
|
-
result_ = {}
|
296
|
-
info_.each do |row_|
|
297
|
-
result_[row_['f_geometry_column']] = {
|
298
|
-
:name => row_['f_geometry_column'],
|
299
|
-
:type => row_['type'],
|
300
|
-
:dimension => row_['coord_dimension'],
|
301
|
-
:srid => row_['srid'],
|
302
|
-
:has_index => row_['spatial_index_enabled'],
|
303
|
-
}
|
304
|
-
end
|
305
|
-
result_
|
306
|
-
end
|
307
|
-
|
308
|
-
|
309
|
-
class SpatialTableDefinition < ConnectionAdapters::TableDefinition # :nodoc:
|
310
|
-
|
311
|
-
attr_reader :spatial_columns
|
312
|
-
|
313
|
-
def initialize(base_)
|
314
|
-
super
|
315
|
-
end
|
316
|
-
|
317
|
-
def column(name_, type_, options_={})
|
318
|
-
super
|
319
|
-
col_ = self[name_]
|
320
|
-
if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(col_.type.to_sym)
|
321
|
-
col_.extend(GeometricColumnDefinitionMethods) unless col_.respond_to?(:srid)
|
322
|
-
col_.set_srid(options_[:srid].to_i)
|
323
|
-
end
|
324
|
-
self
|
325
|
-
end
|
326
|
-
|
327
|
-
def to_sql
|
328
|
-
@columns.find_all{ |c_| !c_.respond_to?(:srid) }.map{ |c_| c_.to_sql } * ', '
|
329
|
-
end
|
330
|
-
|
331
|
-
def spatial_columns
|
332
|
-
@columns.find_all{ |c_| c_.respond_to?(:srid) }
|
333
|
-
end
|
334
|
-
|
335
|
-
end
|
336
|
-
|
337
|
-
|
338
|
-
module GeometricColumnDefinitionMethods # :nodoc:
|
339
|
-
|
340
|
-
def srid
|
341
|
-
defined?(@srid) ? @srid : 4326
|
342
|
-
end
|
343
|
-
|
344
|
-
def set_srid(value_)
|
345
|
-
@srid = value_
|
346
|
-
end
|
347
|
-
|
348
|
-
end
|
349
|
-
|
350
|
-
|
351
|
-
class SpatialColumn < ConnectionAdapters::SQLiteColumn # :nodoc:
|
352
|
-
|
353
|
-
|
354
|
-
def initialize(name_, default_, sql_type_=nil, null_=true)
|
355
|
-
super(name_, default_, sql_type_, null_)
|
356
|
-
@geometric_type = ::RGeo::ActiveRecord.geometric_type_from_name(sql_type_)
|
357
|
-
@ar_class = ::ActiveRecord::Base
|
358
|
-
@srid = 0
|
359
|
-
end
|
360
|
-
|
361
|
-
|
362
|
-
def set_ar_class(val_)
|
363
|
-
@ar_class = val_
|
364
|
-
end
|
365
|
-
|
366
|
-
def set_srid(val_)
|
367
|
-
@srid = val_
|
368
|
-
end
|
369
|
-
|
370
|
-
|
371
|
-
attr_reader :srid
|
372
|
-
attr_reader :geometric_type
|
373
|
-
|
374
|
-
|
375
|
-
def spatial?
|
376
|
-
type == :geometry
|
377
|
-
end
|
378
|
-
|
379
|
-
|
380
|
-
def klass
|
381
|
-
type == :geometry ? ::RGeo::Feature::Geometry : super
|
382
|
-
end
|
383
|
-
|
384
|
-
|
385
|
-
def type_cast(value_)
|
386
|
-
type == :geometry ? SpatialColumn.convert_to_geometry(value_, @ar_class, name, @srid) : super
|
387
|
-
end
|
388
|
-
|
389
|
-
|
390
|
-
def type_cast_code(var_name_)
|
391
|
-
type == :geometry ? "::ActiveRecord::ConnectionAdapters::SpatiaLiteAdapter::SpatialColumn.convert_to_geometry(#{var_name_}, self.class, #{name.inspect}, #{@srid})" : super
|
392
|
-
end
|
393
|
-
|
394
|
-
|
395
|
-
private
|
396
|
-
|
397
|
-
|
398
|
-
def simplified_type(sql_type_)
|
399
|
-
sql_type_ =~ /geometry|point|linestring|polygon/i ? :geometry : super
|
400
|
-
end
|
401
|
-
|
402
|
-
|
403
|
-
def self.convert_to_geometry(input_, ar_class_, column_name_, column_srid_)
|
404
|
-
case input_
|
405
|
-
when ::RGeo::Feature::Geometry
|
406
|
-
factory_ = ar_class_.rgeo_factory_for_column(column_name_, :srid => column_srid_)
|
407
|
-
::RGeo::Feature.cast(input_, factory_)
|
408
|
-
when ::String
|
409
|
-
if input_.length == 0
|
410
|
-
nil
|
411
|
-
else
|
412
|
-
factory_ = ar_class_.rgeo_factory_for_column(column_name_, :srid => column_srid_)
|
413
|
-
if input_[0,1] == "\x00"
|
414
|
-
NativeFormatParser.new(factory_).parse(input_) rescue nil
|
415
|
-
else
|
416
|
-
::RGeo::WKRep::WKTParser.new(factory_, :support_ewkt => true).parse(input_)
|
417
|
-
end
|
418
|
-
end
|
419
|
-
else
|
420
|
-
nil
|
421
|
-
end
|
422
|
-
end
|
423
|
-
|
424
|
-
|
425
|
-
end
|
426
|
-
|
427
|
-
|
428
|
-
class NativeFormatParser # :nodoc:
|
429
|
-
|
430
|
-
|
431
|
-
def initialize(factory_)
|
432
|
-
@factory = factory_
|
433
|
-
end
|
434
|
-
|
435
|
-
|
436
|
-
def parse(data_)
|
437
|
-
@little_endian = data_[1,1] == "\x01"
|
438
|
-
srid_ = data_[2,4].unpack(@little_endian ? 'V' : 'N').first
|
439
|
-
begin
|
440
|
-
_start_scanner(data_)
|
441
|
-
obj_ = _parse_object(false)
|
442
|
-
_get_byte(0xfe)
|
443
|
-
ensure
|
444
|
-
_clean_scanner
|
445
|
-
end
|
446
|
-
obj_
|
447
|
-
end
|
448
|
-
|
449
|
-
|
450
|
-
def _parse_object(contained_)
|
451
|
-
_get_byte(contained_ ? 0x69 : 0x7c)
|
452
|
-
type_code_ = _get_integer
|
453
|
-
case type_code_
|
454
|
-
when 1
|
455
|
-
coords_ = _get_doubles(2)
|
456
|
-
@factory.point(*coords_)
|
457
|
-
when 2
|
458
|
-
_parse_line_string
|
459
|
-
when 3
|
460
|
-
interior_rings_ = (1.._get_integer).map{ _parse_line_string }
|
461
|
-
exterior_ring_ = interior_rings_.shift || @factory.linear_ring([])
|
462
|
-
@factory.polygon(exterior_ring_, interior_rings_)
|
463
|
-
when 4
|
464
|
-
@factory.multi_point((1.._get_integer).map{ _parse_object(1) })
|
465
|
-
when 5
|
466
|
-
@factory.multi_line_string((1.._get_integer).map{ _parse_object(2) })
|
467
|
-
when 6
|
468
|
-
@factory.multi_polygon((1.._get_integer).map{ _parse_object(3) })
|
469
|
-
when 7
|
470
|
-
@factory.collection((1.._get_integer).map{ _parse_object(true) })
|
471
|
-
else
|
472
|
-
raise ::RGeo::Error::ParseError, "Unknown type value: #{type_code_}."
|
473
|
-
end
|
474
|
-
end
|
475
|
-
|
476
|
-
|
477
|
-
def _parse_line_string
|
478
|
-
count_ = _get_integer
|
479
|
-
coords_ = _get_doubles(2 * count_)
|
480
|
-
@factory.line_string((0...count_).map{ |i_| @factory.point(*coords_[2*i_,2]) })
|
481
|
-
end
|
482
|
-
|
483
|
-
|
484
|
-
def _start_scanner(data_)
|
485
|
-
@_data = data_
|
486
|
-
@_len = data_.length
|
487
|
-
@_pos = 38
|
488
|
-
end
|
489
|
-
|
490
|
-
|
491
|
-
def _clean_scanner
|
492
|
-
@_data = nil
|
493
|
-
end
|
494
|
-
|
495
|
-
|
496
|
-
def _get_byte(expect_=nil)
|
497
|
-
if @_pos + 1 > @_len
|
498
|
-
raise ::RGeo::Error::ParseError, "Not enough bytes left to fulfill 1 byte"
|
499
|
-
end
|
500
|
-
str_ = @_data[@_pos, 1]
|
501
|
-
@_pos += 1
|
502
|
-
val_ = str_.unpack("C").first
|
503
|
-
if expect_ && expect_ != val_
|
504
|
-
raise ::RGeo::Error::ParseError, "Expected byte 0x#{expect_.to_s(16)} but got 0x#{val_.to_s(16)}"
|
505
|
-
end
|
506
|
-
val_
|
507
|
-
end
|
508
|
-
|
509
|
-
|
510
|
-
def _get_integer
|
511
|
-
if @_pos + 4 > @_len
|
512
|
-
raise ::RGeo::Error::ParseError, "Not enough bytes left to fulfill 1 integer"
|
513
|
-
end
|
514
|
-
str_ = @_data[@_pos, 4]
|
515
|
-
@_pos += 4
|
516
|
-
str_.unpack("#{@little_endian ? 'V' : 'N'}").first
|
517
|
-
end
|
518
|
-
|
519
|
-
|
520
|
-
def _get_doubles(count_)
|
521
|
-
len_ = 8 * count_
|
522
|
-
if @_pos + len_ > @_len
|
523
|
-
raise ::RGeo::Error::ParseError, "Not enough bytes left to fulfill #{count_} doubles"
|
524
|
-
end
|
525
|
-
str_ = @_data[@_pos, len_]
|
526
|
-
@_pos += len_
|
527
|
-
str_.unpack("#{@little_endian ? 'E' : 'G'}*")
|
528
|
-
end
|
529
|
-
|
530
|
-
|
531
|
-
end
|
532
|
-
|
533
|
-
|
534
114
|
end
|
535
115
|
|
536
116
|
end
|
537
117
|
|
538
118
|
|
539
119
|
end
|
120
|
+
|
121
|
+
|
122
|
+
require 'active_record/connection_adapters/spatialite_adapter/version.rb'
|
123
|
+
require 'active_record/connection_adapters/spatialite_adapter/native_format_parser.rb'
|
124
|
+
require 'active_record/connection_adapters/spatialite_adapter/main_adapter.rb'
|
125
|
+
require 'active_record/connection_adapters/spatialite_adapter/spatial_table_definition.rb'
|
126
|
+
require 'active_record/connection_adapters/spatialite_adapter/spatial_column.rb'
|
127
|
+
require 'active_record/connection_adapters/spatialite_adapter/arel_tosql.rb'
|
128
|
+
|
129
|
+
|
130
|
+
ignore_tables_ = ::ActiveRecord::SchemaDumper.ignore_tables
|
131
|
+
ignore_tables_ << 'geometry_columns' unless ignore_tables_.include?('geometry_columns')
|
132
|
+
ignore_tables_ << 'geometry_columns_auth' unless ignore_tables_.include?('geometry_columns_auth')
|
133
|
+
ignore_tables_ << 'views_geometry_columns' unless ignore_tables_.include?('views_geometry_columns')
|
134
|
+
ignore_tables_ << 'virts_geometry_columns' unless ignore_tables_.include?('virts_geometry_columns')
|
135
|
+
ignore_tables_ << 'spatial_ref_sys' unless ignore_tables_.include?('spatial_ref_sys')
|
136
|
+
ignore_tables_ << /^idx_\w+_\w+$/ unless ignore_tables_.include?(/^idx_\w+_\w+$/)
|