beh_spatial_adapter 1.1.2

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.
@@ -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