spatial_adapter 0.2.0
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 +178 -0
- data/VERSION +1 -0
- data/lib/spatial_adapter.rb +26 -0
- data/lib/spatial_adapter/adapters/mysql.rb +101 -0
- data/lib/spatial_adapter/adapters/postgis.rb +302 -0
- data/lib/spatial_adapter/raw_geom_info.rb +23 -0
- data/lib/spatial_adapter/schema_definitions.rb +11 -0
- data/lib/spatial_adapter/schema_dumper.rb +135 -0
- data/lib/spatial_adapter/spatial_column.rb +68 -0
- data/lib/spatial_adapter/table_definition.rb +14 -0
- data/rails/init.rb +1 -0
- data/spec/README.txt +16 -0
- data/spec/db/mysql_raw.rb +69 -0
- data/spec/db/postgis_raw.rb +95 -0
- data/spec/models/common.rb +32 -0
- data/spec/mysql/connection_adapter_spec.rb +93 -0
- data/spec/mysql/migration_spec.rb +63 -0
- data/spec/mysql/models_spec.rb +103 -0
- data/spec/mysql/schema_dumper_spec.rb +55 -0
- data/spec/postgis/connection_adapter_spec.rb +152 -0
- data/spec/postgis/migration_spec.rb +218 -0
- data/spec/postgis/models_spec.rb +136 -0
- data/spec/postgis/schema_dumper_spec.rb +58 -0
- data/spec/spec_helper.rb +73 -0
- metadata +97 -0
@@ -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,11 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
|
3
|
+
ActiveRecord::ConnectionAdapters::IndexDefinition.class_eval do
|
4
|
+
attr_accessor :spatial
|
5
|
+
|
6
|
+
alias_method :initialize_without_spatial, :initialize
|
7
|
+
def initialize(table, name, unique, columns, spatial = false)
|
8
|
+
initialize_without_spatial(table, name, unique, columns)
|
9
|
+
@spatial = spatial
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,135 @@
|
|
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]
|
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}"
|
80
|
+
stream.puts
|
81
|
+
end
|
82
|
+
|
83
|
+
stream
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def indexes(table, stream)
|
88
|
+
if (indexes = @connection.indexes(table)).any?
|
89
|
+
add_index_statements = indexes.map do |index|
|
90
|
+
statment_parts = [ ('add_index ' + index.table.inspect) ]
|
91
|
+
statment_parts << index.columns.inspect
|
92
|
+
statment_parts << (':name => ' + index.name.inspect)
|
93
|
+
statment_parts << ':unique => true' if index.unique
|
94
|
+
# Add spatial option (this is the only change from the original method)
|
95
|
+
statment_parts << ':spatial => true' if index.spatial
|
96
|
+
|
97
|
+
' ' + statment_parts.join(', ')
|
98
|
+
end
|
99
|
+
|
100
|
+
stream.puts add_index_statements.sort.join("\n")
|
101
|
+
stream.puts
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# Build specification for a table column
|
108
|
+
def column_spec(column)
|
109
|
+
spec = {}
|
110
|
+
spec[:name] = column.name.inspect
|
111
|
+
|
112
|
+
# AR has an optimisation which handles zero-scale decimals as integers. This
|
113
|
+
# code ensures that the dumper still dumps the column as a decimal.
|
114
|
+
spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
|
115
|
+
'decimal'
|
116
|
+
else
|
117
|
+
column.type.to_s
|
118
|
+
end
|
119
|
+
spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
|
120
|
+
spec[:precision] = column.precision.inspect if !column.precision.nil?
|
121
|
+
spec[:scale] = column.scale.inspect if !column.scale.nil?
|
122
|
+
spec[:null] = 'false' if !column.null
|
123
|
+
spec[:default] = default_string(column.default) if column.has_default?
|
124
|
+
|
125
|
+
# Additions for spatial columns
|
126
|
+
if column.is_a?(SpatialColumn)
|
127
|
+
# Override with specific geometry type
|
128
|
+
spec[:type] = column.geometry_type.to_s
|
129
|
+
spec[:srid] = column.srid.inspect if column.srid != -1
|
130
|
+
spec[:with_z] = 'true' if column.with_z
|
131
|
+
spec[:with_m] = 'true' if column.with_m
|
132
|
+
end
|
133
|
+
spec
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module SpatialAdapter
|
2
|
+
module SpatialColumn
|
3
|
+
attr_reader :spatial, :geometry_type, :srid, :with_z, :with_m
|
4
|
+
|
5
|
+
def initialize(name, default, sql_type = nil, null = true,srid=-1,with_z=false,with_m=false)
|
6
|
+
super(name, default, sql_type, null)
|
7
|
+
@geometry_type = geometry_simplified_type(@sql_type)
|
8
|
+
@srid = srid
|
9
|
+
@with_z = with_z
|
10
|
+
@with_m = with_m
|
11
|
+
end
|
12
|
+
|
13
|
+
# Redefines type_cast to add support for geometries
|
14
|
+
# alias_method :type_cast_without_spatial, :type_cast
|
15
|
+
def type_cast(value)
|
16
|
+
return nil if value.nil?
|
17
|
+
case type
|
18
|
+
when :geometry then self.class.string_to_geometry(value)
|
19
|
+
else super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#Redefines type_cast_code to add support for geometries.
|
24
|
+
#
|
25
|
+
#WARNING : Since ActiveRecord keeps only the string values directly returned from the database, it translates from these to the correct types everytime an attribute is read (using the code returned by this method), which is probably ok for simple types, but might be less than efficient for geometries. Also you cannot modify the geometry object returned directly or your change will not be saved.
|
26
|
+
# alias_method :type_cast_code_without_spatial, :type_cast_code
|
27
|
+
def type_cast_code(var_name)
|
28
|
+
case type
|
29
|
+
when :geometry then "#{self.class.name}.string_to_geometry(#{var_name})"
|
30
|
+
else super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
#Redefines klass to add support for geometries
|
36
|
+
# alias_method :klass_without_spatial, :klass
|
37
|
+
def klass
|
38
|
+
case type
|
39
|
+
when :geometry then GeoRuby::SimpleFeatures::Geometry
|
40
|
+
else super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
#Redefines the simplified_type method to add behabiour for when a column is of type geometry
|
47
|
+
def simplified_type(field_type)
|
48
|
+
case field_type
|
49
|
+
when /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i then :geometry
|
50
|
+
else super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#less simlpified geometric type to be use in migrations
|
55
|
+
def geometry_simplified_type(field_type)
|
56
|
+
case field_type
|
57
|
+
when /^point$/i then :point
|
58
|
+
when /^linestring$/i then :line_string
|
59
|
+
when /^polygon$/i then :polygon
|
60
|
+
when /^geometry$/i then :geometry
|
61
|
+
when /multipoint/i then :multi_point
|
62
|
+
when /multilinestring/i then :multi_line_string
|
63
|
+
when /multipolygon/i then :multi_polygon
|
64
|
+
when /geometrycollection/i then :geometry_collection
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
include SpatialAdapter
|
2
|
+
|
3
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do
|
4
|
+
SpatialAdapter.geometry_data_types.keys.each do |column_type|
|
5
|
+
class_eval <<-EOV
|
6
|
+
def #{column_type}(*args)
|
7
|
+
options = args.extract_options!
|
8
|
+
column_names = args
|
9
|
+
|
10
|
+
column_names.each { |name| column(name, '#{column_type}', options) }
|
11
|
+
end
|
12
|
+
EOV
|
13
|
+
end
|
14
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'spatial_adapter'
|
data/spec/README.txt
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
= Running Tests
|
2
|
+
|
3
|
+
You will need to set up empty databases for each adapter you want to test.
|
4
|
+
|
5
|
+
== PostgreSQL
|
6
|
+
|
7
|
+
Create an empty database "spatial_adapter" and ensure that the PostGIS extensions are loaded.
|
8
|
+
|
9
|
+
run "rake spec:postgis" to run the specs
|
10
|
+
|
11
|
+
== MySQL
|
12
|
+
|
13
|
+
Create an empty database "spatial_adapter" - the spatial extensions are already available.
|
14
|
+
|
15
|
+
run "rake spec:mysql" to run the specs
|
16
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
mysql_connection
|
2
|
+
|
3
|
+
ActiveRecord::Schema.define() do
|
4
|
+
execute "drop table if exists point_models"
|
5
|
+
execute "create table point_models
|
6
|
+
(
|
7
|
+
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
8
|
+
extra varchar(255),
|
9
|
+
geom point not null
|
10
|
+
) ENGINE=MyISAM"
|
11
|
+
execute "create spatial index index_point_models_on_geom on point_models (geom)"
|
12
|
+
execute "create index index_point_models_on_extra on point_models (extra)"
|
13
|
+
|
14
|
+
execute "drop table if exists line_string_models"
|
15
|
+
execute "create table line_string_models
|
16
|
+
(
|
17
|
+
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
18
|
+
extra varchar(255),
|
19
|
+
geom linestring
|
20
|
+
) ENGINE=MyISAM"
|
21
|
+
|
22
|
+
execute "drop table if exists polygon_models"
|
23
|
+
execute "create table polygon_models
|
24
|
+
(
|
25
|
+
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
26
|
+
extra varchar(255),
|
27
|
+
geom polygon
|
28
|
+
) ENGINE=MyISAM"
|
29
|
+
|
30
|
+
execute "drop table if exists multi_point_models"
|
31
|
+
execute "create table multi_point_models
|
32
|
+
(
|
33
|
+
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
34
|
+
extra varchar(255),
|
35
|
+
geom multipoint
|
36
|
+
) ENGINE=MyISAM"
|
37
|
+
|
38
|
+
execute "drop table if exists multi_line_string_models"
|
39
|
+
execute "create table multi_line_string_models
|
40
|
+
(
|
41
|
+
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
42
|
+
extra varchar(255),
|
43
|
+
geom multilinestring
|
44
|
+
) ENGINE=MyISAM"
|
45
|
+
|
46
|
+
execute "drop table if exists multi_polygon_models"
|
47
|
+
execute "create table multi_polygon_models
|
48
|
+
(
|
49
|
+
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
50
|
+
extra varchar(255),
|
51
|
+
geom multipolygon
|
52
|
+
) ENGINE=MyISAM"
|
53
|
+
|
54
|
+
execute "drop table if exists geometry_collection_models"
|
55
|
+
execute "create table geometry_collection_models
|
56
|
+
(
|
57
|
+
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
58
|
+
extra varchar(255),
|
59
|
+
geom geometrycollection
|
60
|
+
) ENGINE=MyISAM"
|
61
|
+
|
62
|
+
execute "drop table if exists geometry_models"
|
63
|
+
execute "create table geometry_models
|
64
|
+
(
|
65
|
+
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
66
|
+
extra varchar(255),
|
67
|
+
geom geometry
|
68
|
+
) ENGINE=MyISAM"
|
69
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
postgis_connection
|
2
|
+
|
3
|
+
ActiveRecord::Schema.define() do
|
4
|
+
execute <<-SQL
|
5
|
+
drop table if exists point_models;
|
6
|
+
create table point_models
|
7
|
+
(
|
8
|
+
id serial primary key,
|
9
|
+
extra varchar(255)
|
10
|
+
);
|
11
|
+
select AddGeometryColumn('point_models', 'geom', 4326, 'POINT', 2);
|
12
|
+
create index index_point_models_on_geom on point_models using gist (geom);
|
13
|
+
create index index_point_models_on_extra on point_models (extra);
|
14
|
+
|
15
|
+
drop table if exists line_string_models;
|
16
|
+
create table line_string_models
|
17
|
+
(
|
18
|
+
id serial primary key,
|
19
|
+
extra varchar(255)
|
20
|
+
);
|
21
|
+
select AddGeometryColumn('line_string_models', 'geom', 4326, 'LINESTRING', 2);
|
22
|
+
|
23
|
+
drop table if exists polygon_models;
|
24
|
+
create table polygon_models
|
25
|
+
(
|
26
|
+
id serial primary key,
|
27
|
+
extra varchar(255)
|
28
|
+
);
|
29
|
+
select AddGeometryColumn('polygon_models', 'geom', 4326, 'POLYGON', 2);
|
30
|
+
|
31
|
+
drop table if exists multi_point_models;
|
32
|
+
create table multi_point_models
|
33
|
+
(
|
34
|
+
id serial primary key,
|
35
|
+
extra varchar(255)
|
36
|
+
);
|
37
|
+
select AddGeometryColumn('multi_point_models', 'geom', 4326, 'MULTIPOINT', 2);
|
38
|
+
|
39
|
+
drop table if exists multi_line_string_models;
|
40
|
+
create table multi_line_string_models
|
41
|
+
(
|
42
|
+
id serial primary key,
|
43
|
+
extra varchar(255)
|
44
|
+
);
|
45
|
+
select AddGeometryColumn('multi_line_string_models', 'geom', 4326, 'MULTILINESTRING', 2);
|
46
|
+
|
47
|
+
drop table if exists multi_polygon_models;
|
48
|
+
create table multi_polygon_models
|
49
|
+
(
|
50
|
+
id serial primary key,
|
51
|
+
extra varchar(255)
|
52
|
+
);
|
53
|
+
select AddGeometryColumn('multi_polygon_models', 'geom', 4326, 'MULTIPOLYGON', 2);
|
54
|
+
|
55
|
+
drop table if exists geometry_collection_models;
|
56
|
+
create table geometry_collection_models
|
57
|
+
(
|
58
|
+
id serial primary key,
|
59
|
+
extra varchar(255)
|
60
|
+
);
|
61
|
+
select AddGeometryColumn('geometry_collection_models', 'geom', 4326, 'GEOMETRYCOLLECTION', 2);
|
62
|
+
|
63
|
+
drop table if exists geometry_models;
|
64
|
+
create table geometry_models
|
65
|
+
(
|
66
|
+
id serial primary key,
|
67
|
+
extra varchar(255)
|
68
|
+
);
|
69
|
+
select AddGeometryColumn('geometry_models', 'geom', 4326, 'GEOMETRY', 2);
|
70
|
+
|
71
|
+
drop table if exists pointz_models;
|
72
|
+
create table pointz_models
|
73
|
+
(
|
74
|
+
id serial primary key,
|
75
|
+
extra varchar(255)
|
76
|
+
);
|
77
|
+
select AddGeometryColumn('pointz_models', 'geom', 4326, 'POINT', 3);
|
78
|
+
|
79
|
+
drop table if exists pointm_models;
|
80
|
+
create table pointm_models
|
81
|
+
(
|
82
|
+
id serial primary key,
|
83
|
+
extra varchar(255)
|
84
|
+
);
|
85
|
+
select AddGeometryColumn('pointm_models', 'geom', 4326, 'POINTM', 3);
|
86
|
+
|
87
|
+
drop table if exists point4_models;
|
88
|
+
create table point4_models
|
89
|
+
(
|
90
|
+
id serial primary key,
|
91
|
+
extra varchar(255)
|
92
|
+
);
|
93
|
+
select AddGeometryColumn('point4_models', 'geom', 4326, 'POINT', 4);
|
94
|
+
SQL
|
95
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class PointModel < ActiveRecord::Base
|
2
|
+
end
|
3
|
+
|
4
|
+
class LineStringModel < ActiveRecord::Base
|
5
|
+
end
|
6
|
+
|
7
|
+
class PolygonModel < ActiveRecord::Base
|
8
|
+
end
|
9
|
+
|
10
|
+
class MultiPointModel < ActiveRecord::Base
|
11
|
+
end
|
12
|
+
|
13
|
+
class MultiLineStringModel < ActiveRecord::Base
|
14
|
+
end
|
15
|
+
|
16
|
+
class MultiPolygonModel < ActiveRecord::Base
|
17
|
+
end
|
18
|
+
|
19
|
+
class GeometryCollectionModel < ActiveRecord::Base
|
20
|
+
end
|
21
|
+
|
22
|
+
class GeometryModel < ActiveRecord::Base
|
23
|
+
end
|
24
|
+
|
25
|
+
class PointzModel < ActiveRecord::Base
|
26
|
+
end
|
27
|
+
|
28
|
+
class PointmModel < ActiveRecord::Base
|
29
|
+
end
|
30
|
+
|
31
|
+
class Point4Model < ActiveRecord::Base
|
32
|
+
end
|