beh_spatial_adapter 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +201 -0
- data/VERSION +1 -0
- data/lib/spatial_adapter.rb +41 -0
- data/lib/spatial_adapter/common/raw_geom_info.rb +23 -0
- data/lib/spatial_adapter/common/schema_definitions.rb +10 -0
- data/lib/spatial_adapter/common/schema_dumper.rb +144 -0
- data/lib/spatial_adapter/common/spatial_column.rb +70 -0
- data/lib/spatial_adapter/common/table_definition.rb +14 -0
- data/lib/spatial_adapter/mysql.rb +102 -0
- data/lib/spatial_adapter/oracle_enhanced.rb +651 -0
- data/lib/spatial_adapter/postgresql.rb +395 -0
- data/lib/spatial_adapter/railtie.rb +13 -0
- data/rails/init.rb +16 -0
- data/spec/README.txt +16 -0
- data/spec/db/mysql_raw.rb +70 -0
- data/spec/db/postgis_raw.rb +190 -0
- data/spec/models/common.rb +65 -0
- data/spec/mysql/connection_adapter_spec.rb +106 -0
- data/spec/mysql/migration_spec.rb +64 -0
- data/spec/mysql/models_spec.rb +66 -0
- data/spec/mysql/schema_dumper_spec.rb +56 -0
- data/spec/postgresql/connection_adapter_spec.rb +227 -0
- data/spec/postgresql/migration_spec.rb +365 -0
- data/spec/postgresql/models_spec.rb +222 -0
- data/spec/postgresql/schema_dumper_spec.rb +79 -0
- data/spec/spec_helper.rb +97 -0
- metadata +120 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006 Guilhem Vellut <guilhem.vellut+georuby@gmail.com>
|
2
|
+
Copyright (c) 2010 Pete Deffendol <pete@fragility.us>
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
12
|
+
copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
20
|
+
SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
= Spatial Adapter for ActiveRecord
|
2
|
+
|
3
|
+
This is the Spatial Adapter for ActiveRecord. It enhances ActiveRecord to
|
4
|
+
handle spatial datatypes in the following databases:
|
5
|
+
|
6
|
+
- PostgreSQL (using PostGIS)
|
7
|
+
- MySQL (using Spatial Extensions)
|
8
|
+
- Oracle (using Locator or Oracle Spatial)
|
9
|
+
|
10
|
+
== Dependencies
|
11
|
+
|
12
|
+
The following gems are required:
|
13
|
+
|
14
|
+
- GeoRuby
|
15
|
+
- ActiveRecord (version 2.2.2 and up)
|
16
|
+
|
17
|
+
For PostgreSQL:
|
18
|
+
|
19
|
+
- PostGIS version 1.4.0 or higher should be installed in your database
|
20
|
+
|
21
|
+
== Installation
|
22
|
+
|
23
|
+
Choose ONE of the following installation methods. You shouldn't have to do both.
|
24
|
+
|
25
|
+
=== From RubyGems
|
26
|
+
|
27
|
+
This is the preferred method of installation, and will pull in the required
|
28
|
+
dependencies as well.
|
29
|
+
|
30
|
+
gem install spatial_adapter
|
31
|
+
|
32
|
+
In a Rails 2.x app, you can add a gem dependency in environment.rb:
|
33
|
+
|
34
|
+
config.gem 'spatial_adapter'
|
35
|
+
|
36
|
+
In a Rails 3 app, add a gem dependency to Gemfile:
|
37
|
+
|
38
|
+
gem 'spatial_adapter'
|
39
|
+
|
40
|
+
=== As a Rails Plugin
|
41
|
+
|
42
|
+
In your Rails project, run the following:
|
43
|
+
|
44
|
+
script/plugin install git://github.com/fragility/spatial_adapter.git
|
45
|
+
|
46
|
+
You need to have Git installed first.
|
47
|
+
|
48
|
+
== Configuration
|
49
|
+
|
50
|
+
Choose the database type for which you would like to use spatial_adapter, and
|
51
|
+
load each with
|
52
|
+
|
53
|
+
require 'spatial_adapter/[database]'
|
54
|
+
|
55
|
+
where [database] should be replaced with one of the following:
|
56
|
+
|
57
|
+
- postgresql
|
58
|
+
- mysql
|
59
|
+
- oracle_enhanced
|
60
|
+
|
61
|
+
For example to use the PostgreSQL spatial adapter:
|
62
|
+
|
63
|
+
require 'spatial_adapter/postgresql'
|
64
|
+
|
65
|
+
In a Rails app, spatial_adapter will automatically load the adapter for the database
|
66
|
+
specified in your database.yml configuration.
|
67
|
+
|
68
|
+
== Operations
|
69
|
+
|
70
|
+
Geometric columns in your ActiveRecord models now appear just like any other
|
71
|
+
column of other basic data types. They can also be dumped in ruby schema mode
|
72
|
+
and loaded in migrations the same way as columns of basic types.
|
73
|
+
|
74
|
+
=== Migrations
|
75
|
+
|
76
|
+
Here is an example of code for the creation of a table with a geometric column
|
77
|
+
in PostGIS, along with the addition of a spatial index on the column:
|
78
|
+
|
79
|
+
ActiveRecord::Schema.define do
|
80
|
+
create_table :table_points, :force => true do |t|
|
81
|
+
t.string :data
|
82
|
+
t.point :geom, :null => false, :srid => 123, :with_z => true
|
83
|
+
end
|
84
|
+
|
85
|
+
add_index :table_points, :geom, :spatial => true
|
86
|
+
end
|
87
|
+
|
88
|
+
Here is a related statement valid for MySql version <= 5.0.16:
|
89
|
+
|
90
|
+
ActiveRecord::Schema.define do
|
91
|
+
create_table "table_points", ;options=>"ENGINE=MyISAM", :force => true do |t|
|
92
|
+
t.string :data
|
93
|
+
t.point :geom, :null => false
|
94
|
+
end
|
95
|
+
|
96
|
+
add_index :table_points, :geom, :spatial => true
|
97
|
+
end
|
98
|
+
|
99
|
+
=== Differences Between Databases
|
100
|
+
|
101
|
+
- On all versions of MySQL, the :srid, :with_z, and :with_m options are ignored, since
|
102
|
+
they are not supported.
|
103
|
+
|
104
|
+
- On MySQL versions <= 5.0.16, you have to add <tt>:options =>
|
105
|
+
"ENGINE=MyISAM"</tt> to the create_table statement, since only MyISAM tables
|
106
|
+
can have spatial columns. In addition, only MyISAM tables may have spatial
|
107
|
+
indexes.
|
108
|
+
|
109
|
+
- On Oracle, :with_m is not supported.
|
110
|
+
|
111
|
+
=== Models
|
112
|
+
|
113
|
+
Create your ActiveRecord models normally. Spatial Adapter will automatically
|
114
|
+
handle spatial columns, converting them to the appropriate GeoRuby type.
|
115
|
+
|
116
|
+
class TablePoint < ActiveRecord::Base
|
117
|
+
end
|
118
|
+
|
119
|
+
=== Access
|
120
|
+
|
121
|
+
Here is an example of row creation and access, using the model and the table
|
122
|
+
defined above:
|
123
|
+
|
124
|
+
pt = TablePoint.new(
|
125
|
+
:data => "Hello!",
|
126
|
+
:geom => Point.from_x_y_z(-1.6, 2.8, -3.4, 123))
|
127
|
+
pt.save
|
128
|
+
pt = TablePoint.find_first
|
129
|
+
puts pt.geom.x #access the geom column like any other
|
130
|
+
|
131
|
+
=== Fixtures
|
132
|
+
|
133
|
+
If you use fixtures for your unit tests, at some point, you will want to input
|
134
|
+
a geometry. You could transform your geometries to a form suitable for YAML
|
135
|
+
yourself every time but Spatial Adapter provides a method to do it for you:
|
136
|
+
+to_fixture_format+. You would use it like this, if the geometric column is a
|
137
|
+
point:
|
138
|
+
|
139
|
+
fixture:
|
140
|
+
id: 1
|
141
|
+
data: HELLO
|
142
|
+
geom: <%= Point.from_x_y(123.5,321.9).to_fixture_format %>
|
143
|
+
|
144
|
+
=== Finder Enhancements
|
145
|
+
|
146
|
+
Enhancements to find_by_* and friends has been removed from this version of
|
147
|
+
Spatial Adapter until a cleaner implementation can be made. (The previous
|
148
|
+
implementation made adapter-specific modifications to ActiveRecord::Base,
|
149
|
+
which prevented multiple adapters from being loaded at once.)
|
150
|
+
|
151
|
+
=== Geometric data types
|
152
|
+
|
153
|
+
Ruby geometric datatypes are currently made available only through the GeoRuby
|
154
|
+
library (http://georuby.rubyforge.org/): This is where the
|
155
|
+
<tt>Point.from_x_y</tt> in the example above comes from.
|
156
|
+
|
157
|
+
== Warning
|
158
|
+
|
159
|
+
- Since ActiveRecord seems to keep only the string values directly returned
|
160
|
+
from the database, it translates from these to the correct types everytime
|
161
|
+
an attribute is read, which is probably ok for simple types, but might be
|
162
|
+
less than efficient for geometries, since the EWKB string has to be parsed
|
163
|
+
everytime. Also it means you cannot modify the geometry object returned from
|
164
|
+
an attribute directly:
|
165
|
+
|
166
|
+
place = Place.find_first
|
167
|
+
place.the_geom.y=123456.7 # this doesn't work
|
168
|
+
|
169
|
+
Since the translation to a geometry is performed every time the_geom is read,
|
170
|
+
the change to y will not be saved! You would have to do something like this:
|
171
|
+
|
172
|
+
place = Place.find_first
|
173
|
+
the_geom = place.the_geom
|
174
|
+
the_geom.y=123456.7
|
175
|
+
place.the_geom = the_geom
|
176
|
+
|
177
|
+
== License
|
178
|
+
|
179
|
+
The Spatial Adapter for ActiveRecord is released under the MIT license.
|
180
|
+
|
181
|
+
== Latest Changes
|
182
|
+
|
183
|
+
Spatial Adapter has been refactored and is now available as a Ruby gem. The
|
184
|
+
dependency on Rails has been removed. Unfortunately, the current version is
|
185
|
+
without some of the previous functionality, until a cleaner implementation is
|
186
|
+
made.
|
187
|
+
|
188
|
+
The previous release is available on the "legacy" branch.
|
189
|
+
|
190
|
+
=== Removed Features in 0.2.0
|
191
|
+
|
192
|
+
- Compatibility with ActiveRecord/Rails older than version 2.2.2
|
193
|
+
- enhancements to find_by_* for spatial columns
|
194
|
+
- to_fixture_format extension to the GeoRuby types
|
195
|
+
|
196
|
+
These will hopefully be added back in the near future.
|
197
|
+
|
198
|
+
== Support
|
199
|
+
|
200
|
+
Any questions, enhancement proposals, bug notifications or corrections can be
|
201
|
+
made via the project page at http://github.com/fragility/spatial_adapter
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.1.2
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# This file should typically not be directly require'd into your project. You
|
2
|
+
# should require the database-specific adapter you desire, e.g.
|
3
|
+
#
|
4
|
+
# require 'spatial_adapter/postgresql'
|
5
|
+
#
|
6
|
+
# Why is this file here?
|
7
|
+
#
|
8
|
+
# Mostly to keep Rails happy when using config.gem to specify dependencies.
|
9
|
+
# The Rails init code (rails/init.rb) will then load the adapter that matches
|
10
|
+
# your database.yml configuration.
|
11
|
+
|
12
|
+
require 'geo_ruby'
|
13
|
+
require 'active_record'
|
14
|
+
|
15
|
+
include GeoRuby::SimpleFeatures
|
16
|
+
|
17
|
+
module SpatialAdapter
|
18
|
+
# Translation of geometric data types
|
19
|
+
def geometry_data_types
|
20
|
+
{
|
21
|
+
:point => { :name => "POINT" },
|
22
|
+
:line_string => { :name => "LINESTRING" },
|
23
|
+
:polygon => { :name => "POLYGON" },
|
24
|
+
:geometry_collection => { :name => "GEOMETRYCOLLECTION" },
|
25
|
+
:multi_point => { :name => "MULTIPOINT" },
|
26
|
+
:multi_line_string => { :name => "MULTILINESTRING" },
|
27
|
+
:multi_polygon => { :name => "MULTIPOLYGON" },
|
28
|
+
:geometry => { :name => "GEOMETRY"}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
class NotCompatibleError < ::StandardError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'spatial_adapter/common/raw_geom_info'
|
37
|
+
require 'spatial_adapter/common/spatial_column'
|
38
|
+
require 'spatial_adapter/common/schema_definitions'
|
39
|
+
require 'spatial_adapter/common/schema_dumper'
|
40
|
+
require 'spatial_adapter/common/table_definition'
|
41
|
+
require 'spatial_adapter/railtie' if defined?(Rails::Railtie)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SpatialAdapter
|
2
|
+
class RawGeomInfo < Struct.new(:type,:srid,:dimension,:with_z,:with_m) #:nodoc:
|
3
|
+
def convert!
|
4
|
+
self.type = "geometry" if self.type.nil? #if geometry the geometrytype constraint is not present : need to set the type here then
|
5
|
+
|
6
|
+
if dimension == 4
|
7
|
+
self.with_m = true
|
8
|
+
self.with_z = true
|
9
|
+
elsif dimension == 3
|
10
|
+
if with_m
|
11
|
+
self.with_z = false
|
12
|
+
self.with_m = true
|
13
|
+
else
|
14
|
+
self.with_z = true
|
15
|
+
self.with_m = false
|
16
|
+
end
|
17
|
+
else
|
18
|
+
self.with_z = false
|
19
|
+
self.with_m = false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
|
3
|
+
ActiveRecord::ConnectionAdapters::IndexDefinition.class_eval do
|
4
|
+
attr_accessor :spatial
|
5
|
+
alias_method :initialize_without_spatial, :initialize
|
6
|
+
def initialize(table, name, unique, columns, spatial = false)
|
7
|
+
initialize_without_spatial(table, name, unique, columns)
|
8
|
+
@spatial = spatial
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
ActiveRecord::SchemaDumper.ignore_tables << "spatial_ref_sys" << "geometry_columns"
|
2
|
+
|
3
|
+
ActiveRecord::SchemaDumper.class_eval do
|
4
|
+
# These are the valid options for a column specification (spatial options added)
|
5
|
+
VALID_COLUMN_SPEC_KEYS = [:name, :limit, :precision, :scale, :default, :null, :srid, :with_z, :with_m, :geographic]
|
6
|
+
|
7
|
+
def table(table, stream)
|
8
|
+
columns = @connection.columns(table)
|
9
|
+
begin
|
10
|
+
tbl = StringIO.new
|
11
|
+
|
12
|
+
# first dump primary key column
|
13
|
+
if @connection.respond_to?(:pk_and_sequence_for)
|
14
|
+
pk, pk_seq = @connection.pk_and_sequence_for(table)
|
15
|
+
elsif @connection.respond_to?(:primary_key)
|
16
|
+
pk = @connection.primary_key(table)
|
17
|
+
end
|
18
|
+
|
19
|
+
tbl.print " create_table #{table.inspect}"
|
20
|
+
if columns.detect { |c| c.name == pk }
|
21
|
+
if pk != 'id'
|
22
|
+
tbl.print %Q(, :primary_key => "#{pk}")
|
23
|
+
end
|
24
|
+
else
|
25
|
+
tbl.print ", :id => false"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Added by Spatial Adapter to ensure correct MySQL table engine
|
29
|
+
if @connection.respond_to?(:options_for)
|
30
|
+
res = @connection.options_for(table)
|
31
|
+
tbl.print ", :options=>'#{res}'" if res
|
32
|
+
end
|
33
|
+
|
34
|
+
tbl.print ", :force => true"
|
35
|
+
tbl.puts " do |t|"
|
36
|
+
|
37
|
+
# then dump all non-primary key columns
|
38
|
+
column_specs = columns.map do |column|
|
39
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
|
40
|
+
next if column.name == pk
|
41
|
+
spec = column_spec(column)
|
42
|
+
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
|
43
|
+
spec
|
44
|
+
end.compact
|
45
|
+
|
46
|
+
# find all migration keys used in this table
|
47
|
+
keys = VALID_COLUMN_SPEC_KEYS & column_specs.map(&:keys).flatten
|
48
|
+
|
49
|
+
# figure out the lengths for each column based on above keys
|
50
|
+
lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
|
51
|
+
|
52
|
+
# the string we're going to sprintf our values against, with standardized column widths
|
53
|
+
format_string = lengths.map{ |len| "%-#{len}s" }
|
54
|
+
|
55
|
+
# find the max length for the 'type' column, which is special
|
56
|
+
type_length = column_specs.map{ |column| column[:type].length }.max
|
57
|
+
|
58
|
+
# add column type definition to our format string
|
59
|
+
format_string.unshift " t.%-#{type_length}s "
|
60
|
+
|
61
|
+
format_string *= ''
|
62
|
+
|
63
|
+
column_specs.each do |colspec|
|
64
|
+
values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
|
65
|
+
values.unshift colspec[:type]
|
66
|
+
tbl.print((format_string % values).gsub(/,\s*$/, ''))
|
67
|
+
tbl.puts
|
68
|
+
end
|
69
|
+
|
70
|
+
tbl.puts " end"
|
71
|
+
tbl.puts
|
72
|
+
|
73
|
+
indexes(table, tbl)
|
74
|
+
|
75
|
+
tbl.rewind
|
76
|
+
stream.print tbl.read
|
77
|
+
rescue => e
|
78
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
79
|
+
stream.puts "# #{e.message} #{e.backtrace.join("\n")}"
|
80
|
+
stream.puts
|
81
|
+
puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
82
|
+
puts "# #{e.message} #{e.backtrace.join("\n")}"
|
83
|
+
puts
|
84
|
+
raise e
|
85
|
+
end
|
86
|
+
|
87
|
+
stream
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def indexes(table, stream)
|
92
|
+
if (indexes = @connection.indexes(table)).any?
|
93
|
+
add_index_statements = indexes.map do |index|
|
94
|
+
statment_parts = [ ('add_index ' + index.table.inspect) ]
|
95
|
+
statment_parts << index.columns.inspect
|
96
|
+
statment_parts << (':name => ' + index.name.inspect)
|
97
|
+
statment_parts << ':unique => true' if index.unique
|
98
|
+
# Add spatial option (this is the only change from the original method)
|
99
|
+
statment_parts << ':spatial => true' if index.spatial
|
100
|
+
|
101
|
+
' ' + statment_parts.join(', ')
|
102
|
+
end
|
103
|
+
|
104
|
+
stream.puts add_index_statements.sort.join("\n")
|
105
|
+
stream.puts
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Build specification for a table column
|
112
|
+
def column_spec(column)
|
113
|
+
spec = {}
|
114
|
+
spec[:name] = column.name.inspect
|
115
|
+
|
116
|
+
# AR has an optimisation which handles zero-scale decimals as integers. This
|
117
|
+
# code ensures that the dumper still dumps the column as a decimal.
|
118
|
+
spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
|
119
|
+
'decimal'
|
120
|
+
else
|
121
|
+
column.type.to_s
|
122
|
+
end
|
123
|
+
spec[:limit] = column.limit.inspect if column.limit && column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
|
124
|
+
spec[:precision] = column.precision.inspect if !column.precision.nil?
|
125
|
+
spec[:scale] = column.scale.inspect if !column.scale.nil?
|
126
|
+
spec[:null] = 'false' if !column.null
|
127
|
+
spec[:default] = default_string(column.default) if column.has_default?
|
128
|
+
|
129
|
+
# Additions for spatial columns
|
130
|
+
if column.is_a?(SpatialColumn)
|
131
|
+
# Override with specific geometry type
|
132
|
+
spec[:type] = column.geometry_type.to_s
|
133
|
+
if column.srid == @connection.default_srid
|
134
|
+
spec[:srid] = ":default_srid"
|
135
|
+
elsif column.srid != -1
|
136
|
+
spec[:srid] = column.srid.inspect
|
137
|
+
end
|
138
|
+
spec[:with_z] = 'true' if column.with_z
|
139
|
+
spec[:with_m] = 'true' if column.with_m
|
140
|
+
spec[:geographic] = 'true' if column.geographic?
|
141
|
+
end
|
142
|
+
spec
|
143
|
+
end
|
144
|
+
end
|