ppe-postgis-adapter 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .DS_Store
2
+ doc/*
3
+ rdoc/*
4
+ coverage/*
5
+ spec/debug.log
6
+ pkg/*
7
+ *flymake.rb
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2008-12-10
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Spatial Adapter Copyright (c) 2006 Guilhem Vellut <guilhem.vellut+georuby@gmail.com>
2
+ PostGis Adapter Functions (c) 2008 Marcos Piccinini <nofxx>
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,347 @@
1
+ = PostgisAdapter
2
+
3
+ A plugin for ActiveRecord which manages the PostGIS geometric columns
4
+ in a transparent way (that is like the other base data type columns).
5
+ It also provides a way to manage these columns in migrations.
6
+
7
+ This fork adds handy methods to make geometrical calculations on postgis.
8
+ Based on http://georuby.rubyforge.org Spatial Adapter
9
+
10
+ RDocs - http://docs.github.com/nofxx/postgis_adapter
11
+ Postgis Manual - http://postgis.refractions.net/documentation/manual-svn
12
+
13
+ *PostGIS and Rails 2+ only*.
14
+
15
+
16
+ == Install
17
+
18
+ If you are using Spatial Adapter, *remove it first*.
19
+
20
+
21
+ === Dependencies
22
+
23
+ - georuby
24
+ - postgres 8.3+
25
+ - postgis 1.3+
26
+
27
+
28
+ === As gem:
29
+
30
+ sudo gem install postgis-adapter
31
+
32
+ Rails:
33
+
34
+ config.gem "postgis_adapter"
35
+
36
+
37
+ Github version:
38
+
39
+
40
+ sudo gem install nofxx-postgis_adapter --source http://gems.github.com
41
+
42
+ Rails:
43
+
44
+ config.gem "nofxx-postgis_adapter", :lib => "postgis_adapter", :source => "http://gems.github.com"
45
+
46
+
47
+ === As plugin:
48
+
49
+ script/plugin install git://github.com/nofxx/postgis_adapter.git
50
+
51
+
52
+ == How to Use
53
+
54
+ Geometric columns in your ActiveRecord models now appear just like
55
+ any other column of other basic data types. They can also be dumped
56
+ in ruby schema mode and loaded in migrations the same way as columns
57
+ of basic types.
58
+
59
+
60
+ === Example App
61
+
62
+ Simple rails app to demonstrate, check it out:
63
+
64
+ http://github.com/nofxx/postgis_example
65
+
66
+
67
+ === Model
68
+
69
+ class TablePoint < ActiveRecord::Base
70
+ end
71
+
72
+ That was easy! As you see, there is no need to declare a column as geometric.
73
+ The plugin will get this information by itself.
74
+
75
+ Here is an example of PostGIS row creation and access, using the
76
+ model and the table defined above :
77
+
78
+ pt = TablePoint.new(:data => "Hello!",:geom => Point.from_x_y(1,2))
79
+ pt.save
80
+ pt = TablePoint.first
81
+ puts pt.geom.x
82
+ => 1
83
+
84
+
85
+ == PostGIS Functions
86
+
87
+ Here are this fork additions. To use it:
88
+
89
+ acts_as_geom [column_name] => [geom_type]
90
+
91
+
92
+ Examples:
93
+
94
+ class POI < ActiveRecord::Base
95
+ acts_as_geom :geom => :point
96
+ end
97
+
98
+ class Street < ActiveRecord::Base
99
+ acts_as_geom :line => :line_string
100
+ end
101
+
102
+ ...
103
+
104
+
105
+
106
+ == Play!
107
+
108
+ @place = Poi.new( :geom => Point.from_x_y(10,20) )
109
+ @park = Park.new( :area => **Polygon** )
110
+ @street = Street.new( :line => **LineString** )
111
+
112
+ @place.inside?(@park)
113
+ => true
114
+
115
+ @place.in_bounds?(@park, 0.5) # margin
116
+ => false
117
+
118
+ @place.outside?(@park)
119
+ @street.crosses?(@park)
120
+ @area.contains?(@place)
121
+ ...
122
+
123
+
124
+ === Polygons:
125
+
126
+ @park.area
127
+ => 1345
128
+
129
+ @park.contains?(@point)
130
+ => true
131
+
132
+ @park.overlaps?(@other_park)
133
+ => false
134
+
135
+ Supports transform (useful to transform SRID to UTM for area in Km^2)
136
+
137
+ @park.area(SRID)
138
+ => Area with new SRID
139
+
140
+
141
+ === LineStrings:
142
+
143
+ @street_east.intersects?(@street_west)
144
+ => false
145
+
146
+ @street_central.length
147
+ => 4508.53636
148
+
149
+ @street.length_spheroid
150
+ => 4.40853636
151
+
152
+
153
+ === Class Methods
154
+
155
+ City.close_to(@point)
156
+ => [Array of cities in order by distance...
157
+
158
+ Street.close_to(@point)
159
+ => [Array streets in order by distance...
160
+
161
+ Country.contain(@point)
162
+ => The Conutry that contains the point
163
+
164
+ Area.contains(@point)
165
+ => [Array of areas contains the point...
166
+
167
+
168
+ === BBox Support
169
+
170
+ @area.strictly_left_of? @point
171
+
172
+ @area.overlaps_or_above? @street
173
+
174
+ ...
175
+
176
+ completely_contained_by?
177
+ completely_contains?
178
+ overlaps_or_above?
179
+ overlaps_or_below?
180
+ overlaps_or_left_of?
181
+ overlaps_or_right_of?
182
+ strictly_above?
183
+ strictly_below?
184
+ strictly_left_of?
185
+ strictly_right_of?
186
+ interacts_with?
187
+ binary_equal?
188
+ same_as?
189
+
190
+
191
+ Or use a (almost) PostGIS like notation:
192
+
193
+ @area.bbox "<<", @point
194
+
195
+ @area.bbox "|>>", @point
196
+
197
+ @area.bbox "@", @park
198
+
199
+
200
+ === Warning
201
+
202
+ *To be fixed:*
203
+
204
+ This only supports one geom column per model. Still looking for the best way to
205
+ implement a multi geom.
206
+
207
+ http://nofxx.lighthouseapp.com/projects/20712/tickets/3-multiple-geoms-in-model
208
+
209
+
210
+ === Find_by
211
+
212
+ find_by_*column* has been redefined when column is of a geometric type.
213
+ Instead of using the Rails default '=' operator, for which I can't see
214
+ a definition for MySql spatial datatypes and which performs a bounding
215
+ box equality test in PostGIS, it uses a bounding box intersection:
216
+ && in PostGIS and MBRIntersects in MySQL, which can both make use
217
+ of a spatial index if one is present to speed up the queries.
218
+ You could use this query, for example, if you need to display data
219
+ from the database: You would want only the geometries which are in
220
+ the screen rectangle and you could use a bounding box query for that.
221
+ Since this is a common case, it is the default. You have 2 ways to use
222
+ the find_by_*geom_column*: Either by passing a geometric object directly,
223
+ or passing an array with the 2 opposite corners of a bounding box
224
+ (with 2 or 3 coordinates depending of the dimension of the data).
225
+
226
+ Park.find_by_geom(LineString.from_coordinates([[1.4,5.6],[2.7,8.9],[1.6,5.6]]))
227
+
228
+ or
229
+
230
+ Park.find_by_geom([[3,5.6],[19.98,5.9]])
231
+
232
+ In PostGIS, since you can only use operations with geometries with the same SRID, you can add a third element representing the SRID of the bounding box to the array. It is by default set to -1:
233
+
234
+ Park.find_by_geom([[3,5.6],[19.98,5.9],123])
235
+
236
+
237
+
238
+ == Database Tools
239
+
240
+ === Migrations
241
+
242
+ Here is an example of code for the creation of a table with a
243
+ geometric column in PostGIS, along with the addition of a spatial
244
+ index on the column :
245
+
246
+ ActiveRecord::Schema.define do
247
+ create_table :places do |t|
248
+ t.string :name
249
+ t.point :geom, :srid => 4326, :with_z => true, :null => false
250
+
251
+ t.timestamps
252
+ end
253
+
254
+ add_index :places, :geom, :spatial => true
255
+ end
256
+
257
+
258
+ Types:
259
+
260
+ point
261
+ polygon
262
+ line_string
263
+ multi_point
264
+ multi_polygon
265
+ multi_line_string
266
+ geometry
267
+ geometry_collection
268
+
269
+
270
+ === Fixtures
271
+
272
+ If you use fixtures for your unit tests, at some point,
273
+ you will want to input a geometry. You could transform your
274
+ geometries to a form suitable for YAML yourself everytime but
275
+ the spatial adapter provides a method to do it for you: +to_yaml+.
276
+ It works for both MySQL and PostGIS (although the string returned
277
+ is different for each database). You would use it like this, if
278
+ the geometric column is a point:
279
+
280
+ fixture:
281
+ id: 1
282
+ data: HELLO
283
+ geom: <%= Point.from_x_y(123.5,321.9).to_yaml %>
284
+
285
+
286
+ === Annotate
287
+
288
+ If you are using annotate_models, check out this fork which adds geometrical annotations for PostgisAdapter and SpatialAdapter:
289
+
290
+ http://github.com/nofxx/annotate_models
291
+
292
+
293
+ == Geometric data types
294
+
295
+ Ruby geometric datatypes are currently made available only through
296
+ the GeoRuby library (http://georuby.rubyforge.org): This is where the
297
+ *Point.from_x_y* in the example above comes from. It is a goal
298
+ of a future release of the Spatial Adapter to support additional
299
+ geometric datatype libraries, such as Ruby/GEOS, as long as they
300
+ can support reading and writing of EWKB.
301
+
302
+
303
+
304
+ === Warning
305
+
306
+ - Since ActiveRecord seems to keep only the string values directly
307
+ returned from the database, it translates from these to the correct
308
+ types everytime an attribute is read, which is probably ok for simple
309
+ types, but might be less than efficient for geometries, since the EWKB
310
+ string has to be parsed everytime. Also it means you cannot modify the
311
+ geometry object returned from an attribute directly :
312
+
313
+ place = Place.first
314
+ place.the_geom.y=123456.7
315
+
316
+ - Since the translation to a geometry is performed everytime the_geom
317
+ is read, the change to y will not be saved! You would have to do
318
+ something like this :
319
+
320
+ place = Place.first
321
+ the_geom = place.the_geom
322
+ the_geom.y=123456.7
323
+ place.the_geom = the_geom
324
+
325
+
326
+ == License
327
+
328
+ Spatial Adapter for Rails is released under the MIT license.
329
+ Postgis Adapter is released under the MIT license.
330
+
331
+
332
+ == Support
333
+
334
+ Tested using rails 2.2.2/2.3.3 / postgresql 8.3.7 / postgis 1.3.3 / linux / osx
335
+
336
+ Any questions, enhancement proposals, bug notifications or corrections:
337
+
338
+
339
+ === PostgisAdapter
340
+
341
+ http://github.com/nofxx/postgis_adapter/issues
342
+
343
+
344
+ === SpatialAdapter
345
+
346
+ http://georuby.rubyforge.org
347
+ guilhem.vellut+georuby@gmail.com.
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "postgis_adapter"
8
+ gem.summary = "PostGIS Adapter for Active Record"
9
+ gem.description = "Execute PostGIS functions on Active Record"
10
+ gem.email = "x@nofxx.com"
11
+ gem.homepage = "http://github.com/nofxx/postgis_adapter"
12
+ gem.authors = ["Marcos Piccinini"]
13
+ gem.rubyforge_project = "postgis_adapter"
14
+ # TODO: better way for this.....
15
+ # gem.add_dependency 'geo_ruby'
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+ task :default => :spec
34
+
35
+ require 'rake/rdoctask'
36
+ Rake::RDocTask.new do |rdoc|
37
+ version = File.exist?('VERSION') ? File.read('VERSION').chomp : ""
38
+ rdoc.rdoc_dir = 'rdoc'
39
+ rdoc.title = "postgis_adapter #{version}"
40
+ rdoc.rdoc_files.include('README*')
41
+ rdoc.rdoc_files.include('lib/**/*.rb')
42
+ end
43
+
44
+ #
45
+ # Reek & Roodi
46
+ #
47
+ begin
48
+ require 'reek/rake_task'
49
+ Reek::RakeTask.new do |t|
50
+ t.fail_on_error = true
51
+ t.verbose = false
52
+ t.source_files = 'lib/**/*.rb'
53
+ end
54
+ rescue LoadError
55
+ task :reek do
56
+ abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
57
+ end
58
+ end
59
+
60
+ begin
61
+ require 'roodi'
62
+ require 'roodi_task'
63
+ RoodiTask.new do |t|
64
+ t.verbose = false
65
+ end
66
+ rescue LoadError
67
+ task :roodi do
68
+ abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
69
+ end
70
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7.2
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,39 @@
1
+ #
2
+ # PostGIS Adapter
3
+ #
4
+ # http://github.com/nofxx/postgis_adapter
5
+ #
6
+ module PostgisFunctions
7
+ def self.included(base)
8
+ base.send :extend, ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ # acts_as_geom :db_field => :geom_type
14
+ # Examples:
15
+ #
16
+ # acts_as_geom :data => :point
17
+ # acts_as_geom :geom => :line_string
18
+ # acts_as_geom :geom => :polygon
19
+ #
20
+ def acts_as_geom(*geom)
21
+ cattr_accessor :postgis_geoms
22
+ self.postgis_geoms = geom[0] # {:columns => column
23
+ send :include, case geom[0].values[0]
24
+ when :point then PointFunctions
25
+ when :polygon then PolygonFunctions
26
+ when :line_string then LineStringFunctions
27
+ end unless geom[0].kind_of? Symbol
28
+ end
29
+
30
+ def get_geom_type(column)
31
+ self.postgis_geoms.values[0] rescue nil
32
+ # self.columns.select { |c| c.name == column.to_s }[0].geometry_type
33
+ # rescue ActiveRecord::StatementInvalid => e
34
+ # nil
35
+ end
36
+ end
37
+ end
38
+
39
+ ActiveRecord::Base.send :include, PostgisFunctions
@@ -0,0 +1,184 @@
1
+ #
2
+ # PostGIS Adapter
3
+ #
4
+ # Common Spatial Adapter for ActiveRecord
5
+ #
6
+ # Code from
7
+ # http://georuby.rubyforge.org Spatial Adapter
8
+ #
9
+
10
+ #Addition of a flag indicating if the index is spatial
11
+ ActiveRecord::ConnectionAdapters::IndexDefinition.class_eval do
12
+ attr_accessor :spatial
13
+
14
+ def initialize(table, name, unique, spatial,columns)
15
+ super(table,name,unique,columns)
16
+ @spatial = spatial
17
+ end
18
+
19
+ end
20
+
21
+ ActiveRecord::SchemaDumper.class_eval do
22
+ def table(table, stream)
23
+
24
+ columns = @connection.columns(table)
25
+ begin
26
+ tbl = StringIO.new
27
+
28
+ if @connection.respond_to?(:pk_and_sequence_for)
29
+ pk, pk_seq = @connection.pk_and_sequence_for(table)
30
+ end
31
+ pk ||= 'id'
32
+
33
+ tbl.print " create_table #{table.inspect}"
34
+ if columns.detect { |c| c.name == pk }
35
+ if pk != 'id'
36
+ tbl.print %Q(, :primary_key => "#{pk}")
37
+ end
38
+ else
39
+ tbl.print ", :id => false"
40
+ end
41
+
42
+ if @connection.respond_to?(:options_for)
43
+ res = @connection.options_for(table)
44
+ tbl.print ", :options=>'#{res}'" if res
45
+ end
46
+
47
+ tbl.print ", :force => true"
48
+ tbl.puts " do |t|"
49
+
50
+ columns.each do |column|
51
+
52
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
53
+ next if column.name == pk
54
+ #need to use less_simplified_type here or have each specific geometry type be simplified to a specific simplified type in Column and each one treated separately in the Column methods
55
+ if column.is_a?(SpatialColumn)
56
+ tbl.print " t.column #{column.name.inspect}, #{column.geometry_type.inspect}"
57
+ tbl.print ", :srid => #{column.srid.inspect}" if column.srid != -1
58
+ tbl.print ", :with_z => #{column.with_z.inspect}" if column.with_z
59
+ tbl.print ", :with_m => #{column.with_m.inspect}" if column.with_m
60
+ else
61
+ tbl.print " t.column #{column.name.inspect}, #{column.type.inspect}"
62
+ end
63
+ tbl.print ", :limit => #{column.limit.inspect}" if column.limit != @types[column.type][:limit] && column.precision.blank? && column.scale.blank?
64
+ tbl.print ", :precision => #{column.precision.inspect}" if column.precision != @types[column.type][:precision]
65
+ tbl.print ", :scale => #{column.scale.inspect}" if column.scale != @types[column.type][:scale]
66
+ tbl.print ", :default => #{default_string(column.default)}" if !column.default.nil?
67
+ tbl.print ", :null => false" if !column.null
68
+ tbl.puts
69
+ end
70
+
71
+ tbl.puts " end"
72
+ tbl.puts
73
+ indexes(table, tbl)
74
+ tbl.rewind
75
+ stream.print tbl.read
76
+ rescue => e
77
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
78
+ stream.puts "# #{e.message} #{e.backtrace}"
79
+ stream.puts
80
+ end
81
+
82
+ stream end
83
+
84
+ def indexes(table, stream)
85
+ indexes = @connection.indexes(table)
86
+ indexes.each do |index|
87
+ stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
88
+ stream.print ", :unique => true" if index.unique
89
+ stream.print ", :spatial=> true " if index.spatial
90
+ stream.puts
91
+ end
92
+
93
+ stream.puts unless indexes.empty?
94
+ end
95
+ end
96
+
97
+
98
+
99
+
100
+ module SpatialAdapter
101
+ #Translation of geometric data types
102
+ def geometry_data_types
103
+ {
104
+ :point => { :name => "POINT" },
105
+ :line_string => { :name => "LINESTRING" },
106
+ :polygon => { :name => "POLYGON" },
107
+ :geometry_collection => { :name => "GEOMETRYCOLLECTION" },
108
+ :multi_point => { :name => "MULTIPOINT" },
109
+ :multi_line_string => { :name => "MULTILINESTRING" },
110
+ :multi_polygon => { :name => "MULTIPOLYGON" },
111
+ :geometry => { :name => "GEOMETRY"}
112
+ }
113
+ end
114
+
115
+ end
116
+
117
+
118
+ #using a mixin instead of subclassing Column since each adapter can have a specific subclass of Column
119
+ module SpatialColumn
120
+ attr_reader :geometry_type, :srid, :with_z, :with_m
121
+
122
+ def initialize(name, default, sql_type = nil, null = true,srid=-1,with_z=false,with_m=false)
123
+ super(name,default,sql_type,null)
124
+ @geometry_type = geometry_simplified_type(@sql_type)
125
+ @srid = srid
126
+ @with_z = with_z
127
+ @with_m = with_m
128
+ end
129
+
130
+
131
+ #Redefines type_cast to add support for geometries
132
+ def type_cast(value)
133
+ return nil if value.nil?
134
+ case type
135
+ when :geometry then self.class.string_to_geometry(value)
136
+ else super
137
+ end
138
+ end
139
+
140
+ #Redefines type_cast_code to add support for geometries.
141
+ #
142
+ #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.
143
+ def type_cast_code(var_name)
144
+ case type
145
+ when :geometry then "#{self.class.name}.string_to_geometry(#{var_name})"
146
+ else super
147
+ end
148
+ end
149
+
150
+
151
+ #Redefines klass to add support for geometries
152
+ def klass
153
+ case type
154
+ when :geometry then GeoRuby::SimpleFeatures::Geometry
155
+ else super
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ #Redefines the simplified_type method to add behaviour for when a column is of type geometry
162
+ def simplified_type(field_type)
163
+ case field_type
164
+ when /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i then :geometry
165
+ else super
166
+ end
167
+ end
168
+
169
+ #less simlpified geometric type to be use in migrations
170
+ def geometry_simplified_type(field_type)
171
+ case field_type
172
+ when /^point$/i then :point
173
+ when /^linestring$/i then :line_string
174
+ when /^polygon$/i then :polygon
175
+ when /^geometry$/i then :geometry
176
+ when /multipoint/i then :multi_point
177
+ when /multilinestring/i then :multi_line_string
178
+ when /multipolygon/i then :multi_polygon
179
+ when /geometrycollection/i then :geometry_collection
180
+ end
181
+ end
182
+
183
+
184
+ end