beh_spatial_adapter 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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