geos-extensions 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +17 -0
  4. data/Guardfile +17 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.rdoc +19 -91
  7. data/Rakefile +1 -12
  8. data/geos-extensions.gemspec +1 -9
  9. data/lib/geos-extensions.rb +1 -9
  10. data/lib/geos/coordinate_sequence.rb +92 -0
  11. data/lib/geos/extensions/version.rb +1 -1
  12. data/lib/geos/geometry.rb +252 -0
  13. data/lib/geos/geometry_collection.rb +60 -0
  14. data/lib/geos/geos_helper.rb +86 -72
  15. data/lib/geos/google_maps.rb +1 -0
  16. data/lib/geos/google_maps/api_2.rb +9 -23
  17. data/lib/geos/google_maps/api_3.rb +10 -24
  18. data/lib/geos/google_maps/api_common.rb +41 -0
  19. data/lib/geos/line_string.rb +15 -0
  20. data/lib/geos/multi_line_string.rb +15 -0
  21. data/lib/geos/multi_point.rb +15 -0
  22. data/lib/geos/multi_polygon.rb +27 -0
  23. data/lib/geos/point.rb +120 -0
  24. data/lib/geos/polygon.rb +158 -0
  25. data/lib/geos/yaml.rb +30 -0
  26. data/lib/geos/yaml/psych.rb +18 -0
  27. data/lib/geos/yaml/syck.rb +41 -0
  28. data/lib/geos_extensions.rb +110 -711
  29. data/test/google_maps_api_2_tests.rb +54 -32
  30. data/test/google_maps_api_3_tests.rb +58 -36
  31. data/test/google_maps_polyline_encoder_tests.rb +1 -1
  32. data/test/helper_tests.rb +28 -0
  33. data/test/misc_tests.rb +130 -10
  34. data/test/reader_tests.rb +38 -1
  35. data/test/test_helper.rb +54 -146
  36. data/test/writer_tests.rb +329 -10
  37. data/test/yaml_tests.rb +203 -0
  38. metadata +26 -102
  39. data/app/models/geos/geometry_column.rb +0 -39
  40. data/app/models/geos/spatial_ref_sys.rb +0 -12
  41. data/lib/geos/active_record_extensions.rb +0 -12
  42. data/lib/geos/active_record_extensions/connection_adapters/postgresql_adapter.rb +0 -151
  43. data/lib/geos/active_record_extensions/spatial_columns.rb +0 -367
  44. data/lib/geos/active_record_extensions/spatial_scopes.rb +0 -493
  45. data/lib/geos/rails/engine.rb +0 -6
  46. data/lib/tasks/test.rake +0 -42
  47. data/test/adapter_tests.rb +0 -38
  48. data/test/database.yml +0 -17
  49. data/test/fixtures/foo3ds.yml +0 -16
  50. data/test/fixtures/foo_geographies.yml +0 -16
  51. data/test/fixtures/foos.yml +0 -16
  52. data/test/geography_columns_tests.rb +0 -176
  53. data/test/geometry_columns_tests.rb +0 -178
  54. data/test/spatial_scopes_geographies_tests.rb +0 -107
  55. data/test/spatial_scopes_tests.rb +0 -337
@@ -1,12 +0,0 @@
1
-
2
- require File.join(Geos::GEOS_EXTENSIONS_BASE, *%w{ geos active_record_extensions connection_adapters postgresql_adapter })
3
-
4
- module Geos
5
- module ActiveRecord
6
- autoload :SpatialColumns, File.join(Geos::GEOS_EXTENSIONS_BASE, %w{ geos active_record_extensions spatial_columns })
7
- autoload :GeometryColumns, File.join(Geos::GEOS_EXTENSIONS_BASE, %w{ geos active_record_extensions spatial_columns })
8
- autoload :SpatialScopes, File.join(Geos::GEOS_EXTENSIONS_BASE, %w{ geos active_record_extensions spatial_scopes })
9
- autoload :GeospatialScopes, File.join(Geos::GEOS_EXTENSIONS_BASE, %w{ geos active_record_extensions spatial_scopes })
10
- end
11
- end
12
-
@@ -1,151 +0,0 @@
1
-
2
- require 'active_record/connection_adapters/postgresql_adapter'
3
-
4
- module ActiveRecord
5
- module ConnectionAdapters
6
- # Allows access to the name, srid and coord_dimensions of a PostGIS
7
- # geometry column in PostgreSQL.
8
- class PostgreSQLSpatialColumn
9
- attr_accessor :name, :srid, :coord_dimension, :spatial_type
10
-
11
- def initialize(name, options = {})
12
- options = {
13
- :srid => nil,
14
- :coord_dimension => nil,
15
- :spatial_type => :geometry
16
- }.merge(options)
17
-
18
- @name = name
19
- @srid = options[:srid]
20
- @coord_dimension = options[:coord_dimension]
21
- @spatial_type = options[:spatial_type]
22
- end
23
- end
24
-
25
- class PostgreSQLColumn
26
- def simplified_type_with_spatial_type(field_type)
27
- if field_type =~ /^geometry(\(|$)/
28
- :geometry
29
- elsif field_type =~ /^geography(\(|$)/
30
- :geography
31
- else
32
- simplified_type_without_spatial_type(field_type)
33
- end
34
- end
35
- alias_method_chain :simplified_type, :spatial_type
36
- end
37
-
38
- class PostgreSQLAdapter
39
- def geometry_columns?
40
- true
41
- end
42
-
43
- def geography_columns?
44
- Geos::ActiveRecord.POSTGIS[:lib] >= '1.5'
45
- end
46
-
47
- # Returns the geometry columns for the table.
48
- def geometry_columns(table_name, name = nil)
49
- return [] if !geometry_columns? ||
50
- !table_exists?(table_name)
51
-
52
- columns(table_name, name).select { |c| c.type == :geometry }.collect do |c|
53
- res = select_rows(
54
- "SELECT srid, coord_dimension FROM geometry_columns WHERE f_table_name = #{quote(table_name)} AND f_geometry_column = #{quote(c.name)}",
55
- "Geometry column load for #{table_name}"
56
- )
57
-
58
- PostgreSQLSpatialColumn.new(c.name).tap do |g|
59
- # since we're too stupid at the moment to understand
60
- # PostgreSQL schemas, let's just go with this:
61
- if res.length == 1
62
- g.srid, g.coord_dimension = res.first.collect(&:to_i)
63
- end
64
- end
65
- end
66
- end
67
-
68
- # Returns the geography columns for the table.
69
- def geography_columns(table_name, name = nil)
70
- return [] if !geography_columns? ||
71
- !table_exists?(table_name)
72
-
73
- columns(table_name, name).select { |c| c.type == :geography }.collect do |c|
74
- res = select_rows(
75
- "SELECT srid, coord_dimension FROM geography_columns WHERE f_table_name = #{quote(table_name)} AND f_geography_column = #{quote(c.name)}",
76
- "Geography column load for #{table_name}"
77
- )
78
-
79
- PostgreSQLSpatialColumn.new(c.name, :spatial_type => :geography).tap do |g|
80
- # since we're too stupid at the moment to understand
81
- # PostgreSQL schemas, let's just go with this:
82
- if res.length == 1
83
- g.srid, g.coord_dimension = res.first.collect { |value|
84
- value.try(:to_i)
85
- }
86
- end
87
- end
88
- end
89
- end
90
-
91
- # Returns both the geometry and geography columns for the table.
92
- def spatial_columns(table_name, name = nil)
93
- geometry_columns(table_name, name) +
94
- geography_columns(table_name, name)
95
- end
96
- end
97
-
98
- # Alias for backwards compatibility:
99
- PostgreSQLGeometryColumn = PostgreSQLSpatialColumn
100
- end
101
- end
102
-
103
- module Geos
104
- module ActiveRecord
105
- def self.POSTGIS
106
- return @POSTGIS if defined?(@POSTGIS)
107
-
108
- @POSTGIS = if (version_string = ::ActiveRecord::Base.connection.select_rows("SELECT postgis_full_version()").flatten.first).present?
109
- hash = {
110
- :use_stats => version_string =~ /USE_STATS/
111
- }
112
-
113
- {
114
- :lib => /POSTGIS="([^"]+)"/,
115
- :geos => /GEOS="([^"]+)"/,
116
- :proj => /PROJ="([^"]+)"/,
117
- :libxml => /LIBXML="([^"]+)"/
118
- }.each do |k, v|
119
- hash[k] = version_string.scan(v).flatten.first
120
- end
121
-
122
- hash.freeze
123
- else
124
- {}.freeze
125
- end
126
- end
127
-
128
- def self.UNKNOWN_SRIDS
129
- return @UNKNOWN_SRIDS if defined?(@UNKNOWN_SRIDS)
130
-
131
- @UNKNOWN_SRIDS = if self.POSTGIS[:lib] >= '2.0'
132
- {
133
- :geography => 0,
134
- :geometry => 0
135
- }.freeze
136
- else
137
- {
138
- :geography => 0,
139
- :geometry => -1
140
- }.freeze
141
- end
142
- end
143
-
144
- def self.UNKNOWN_SRID
145
- return @UNKNOWN_SRID if defined?(@UNKNOWN_SRID)
146
-
147
- @UNKNOWN_SRID = self.UNKNOWN_SRIDS[:geometry]
148
- end
149
- end
150
- end
151
-
@@ -1,367 +0,0 @@
1
-
2
- module Geos
3
- module ActiveRecord #:nodoc:
4
- class << self
5
- def geometry_columns?
6
- ::ActiveRecord::Base.connection.geometry_columns?
7
- end
8
-
9
- def geography_columns?
10
- ::ActiveRecord::Base.connection.geography_columns?
11
- end
12
- end
13
-
14
- # This little module helps us out with geometry columns. At least, in
15
- # PostgreSQL it does.
16
- #
17
- # This module will add a method called geometry_columns to your model
18
- # which will contain information that can be gleaned from the
19
- # geometry_columns table that PostGIS creates.
20
- #
21
- # You can also have the module automagically create some accessor
22
- # methods for you to make your life easier. These accessor methods will
23
- # override the ActiveRecord defaults and allow you to set geometry
24
- # column values using Geos geometry objects directly or with
25
- # PostGIS-style extended WKT and such. See
26
- # create_geometry_column_accessors! for details.
27
- #
28
- # === Caveats:
29
- #
30
- # * This module currently only works with PostGIS.
31
- # * This module doesn't really "get" PostgreSQL catalogs and schemas
32
- # and such. That would be a little more involved but it would be
33
- # nice if Rails was aware of such things.
34
- module SpatialColumns
35
- SPATIAL_COLUMN_OUTPUT_FORMATS = %w{ geos wkt wkb ewkt ewkb wkb_bin ewkb_bin }.freeze
36
-
37
- class InvalidGeometry < ::ActiveRecord::ActiveRecordError
38
- def initialize(geom)
39
- super("Invalid geometry: #{geom}")
40
- end
41
- end
42
-
43
- class SRIDNotFound < ::ActiveRecord::ActiveRecordError
44
- def initialize(table_name, column)
45
- super("Couldn't find SRID for #{table_name}.#{column}")
46
- end
47
- end
48
-
49
- class CantConvertSRID < ::ActiveRecord::ActiveRecordError
50
- def initialize(table_name, column, from_srid, to_srid)
51
- super("Couldn't convert SRID for #{table_name}.#{column} from #{from_srid} to #{to_srid}")
52
- end
53
- end
54
-
55
- def self.included(base) #:nodoc:
56
- base.extend(ClassMethods)
57
- base.send(:include, Geos::ActiveRecord::SpatialScopes)
58
- end
59
-
60
- module ClassMethods
61
- protected
62
- @geometry_columns = nil
63
- @geography_columns = nil
64
-
65
- public
66
- # Stubs for documentation purposes:
67
-
68
- # Returns an Array of available geometry columns in the
69
- # table. These are PostgreSQLColumns with values set for
70
- # the srid and coord_dimensions properties.
71
- def geometry_columns; end
72
-
73
- # Returns an Array of available geography columns in the
74
- # table. These are PostgreSQLColumns with values set for
75
- # the srid and coord_dimensions properties.
76
- def geography_columns; end
77
-
78
- # Force a reload of available geometry columns.
79
- def geometry_columns!; end
80
-
81
- # Force a reload of available geography columns.
82
- def geography_columns!; end
83
-
84
- # Grabs a geometry column based on name.
85
- def geometry_column_by_name(name); end
86
-
87
- # Grabs a geography column based on name.
88
- def geography_column_by_name(name); end
89
-
90
- # Returns both the geometry and geography columns for a table.
91
- def spatial_columns
92
- self.geometry_columns + self.geography_columns
93
- end
94
-
95
- # Reloads both the geometry and geography columns for a table.
96
- def spatial_columns!
97
- self.geometry_columns! + self.geography_columns!
98
- end
99
-
100
- # Grabs a spatial column based on name.
101
- def spatial_column_by_name(name)
102
- self.geometry_column_by_name(name) || self.geography_column_by_name(name)
103
- end
104
-
105
- %w{ geometry geography }.each do |m|
106
- src, line = <<-EOF, __LINE__ + 1
107
- undef :#{m}_columns
108
- def #{m}_columns
109
- if !defined?(@#{m}_columns) || @#{m}_columns.nil?
110
- @#{m}_columns = connection.#{m}_columns(self.table_name)
111
- @#{m}_columns.freeze
112
- end
113
- @#{m}_columns
114
- end
115
-
116
- undef :#{m}_columns!
117
- def #{m}_columns!
118
- @#{m}_columns = nil
119
- #{m}_columns
120
- end
121
-
122
- undef :#{m}_column_by_name
123
- def #{m}_column_by_name(name)
124
- @#{m}_column_by_name ||= self.#{m}_columns.inject(HashWithIndifferentAccess.new) do |memo, obj|
125
- memo[obj.name] = obj
126
- memo
127
- end
128
- @#{m}_column_by_name[name]
129
- end
130
- EOF
131
- self.class_eval(src, __FILE__, line)
132
- end
133
-
134
- # Quickly grab the SRID for a geometry column.
135
- def srid_for(column_name)
136
- column = self.spatial_column_by_name(column_name)
137
- column.try(:srid) || Geos::ActiveRecord.UNKNOWN_SRID
138
- end
139
-
140
- # Quickly grab the number of dimensions for a geometry column.
141
- def coord_dimension_for(column_name)
142
- self.spatial_column_by_name(column_name).coord_dimension
143
- end
144
-
145
- protected
146
- # Sets up nifty setters and getters for spatial columns.
147
- # The methods created look like this:
148
- #
149
- # * spatial_column_name_geos
150
- # * spatial_column_name_wkb
151
- # * spatial_column_name_wkb_bin
152
- # * spatial_column_name_wkt
153
- # * spatial_column_name_ewkb
154
- # * spatial_column_name_ewkb_bin
155
- # * spatial_column_name_ewkt
156
- # * spatial_column_name=(geom)
157
- # * spatial_column_name(options = {})
158
- #
159
- # Where "spatial_column_name" is the name of the actual
160
- # column.
161
- #
162
- # You can specify which spatial columns you want to apply
163
- # these accessors using the :only and :except options.
164
- def create_spatial_column_accessors!(options = nil)
165
- create_these = []
166
-
167
- if options.nil?
168
- create_these.concat(self.spatial_columns)
169
- else
170
- if options[:geometry_columns]
171
- create_these.concat(self.geometry_columns)
172
- end
173
-
174
- if options[:geography_columns]
175
- create_these.concat(self.geography_columns)
176
- end
177
-
178
- if options[:except] && options[:only]
179
- raise ArgumentError, "You can only specify either :except or :only (#{options.keys.inspect})"
180
- elsif options[:except]
181
- except = Array(options[:except]).collect(&:to_s)
182
- create_these.reject! { |c| except.include?(c) }
183
- elsif options[:only]
184
- only = Array(options[:only]).collect(&:to_s)
185
- create_these.select! { |c| only.include?(c) }
186
- end
187
- end
188
-
189
- create_these.each do |k|
190
- src, line = <<-EOF, __LINE__ + 1
191
- def #{k.name}=(geom)
192
- if !geom
193
- self['#{k.name}'] = nil
194
- else
195
- column = self.class.spatial_column_by_name(#{k.name.inspect})
196
-
197
- if geom =~ /^SRID=default;/i
198
- geom = geom.sub(/default/i, column.srid.to_s)
199
- end
200
-
201
- geos = Geos.read(geom)
202
-
203
- if column.spatial_type != :geography
204
- geom_srid = if geos.srid == 0 || geos.srid == -1
205
- Geos::ActiveRecord.UNKNOWN_SRIDS[column.spatial_type]
206
- else
207
- geos.srid
208
- end
209
-
210
- if column.srid != geom_srid
211
- if column.srid == Geos::ActiveRecord.UNKNOWN_SRIDS[column.spatial_type] || geom_srid == Geos::ActiveRecord.UNKNOWN_SRIDS[column.spatial_type]
212
- geos.srid = column.srid
213
- else
214
- raise CantConvertSRID.new(self.class.table_name, #{k.name.inspect}, geom_srid, column.srid)
215
- end
216
- end
217
-
218
- self['#{k.name}'] = geos.to_ewkb
219
- else
220
- self['#{k.name}'] = geos.to_wkb
221
- end
222
- end
223
-
224
- SPATIAL_COLUMN_OUTPUT_FORMATS.each do |f|
225
- instance_variable_set("@#{k.name}_\#{f}", nil)
226
- end
227
- end
228
-
229
- def #{k.name}_geos
230
- @#{k.name}_geos ||= Geos.from_wkb(self['#{k.name}'])
231
- end
232
-
233
- def #{k.name}(options = {})
234
- format = case options
235
- when String, Symbol
236
- options
237
- when Hash
238
- options = options.stringify_keys
239
- options['format'] if options['format']
240
- end
241
-
242
- if format
243
- if SPATIAL_COLUMN_OUTPUT_FORMATS.include?(format)
244
- return self.send(:"#{k.name}_\#{format}")
245
- else
246
- raise ArgumentError, "Invalid option: \#{options[:format]}"
247
- end
248
- end
249
-
250
- self['#{k.name}']
251
- end
252
- EOF
253
- self.class_eval(src, __FILE__, line)
254
-
255
- SPATIAL_COLUMN_OUTPUT_FORMATS.reject { |f| f == 'geos' }.each do |f|
256
- src, line = <<-EOF, __LINE__ + 1
257
- def #{k.name}_#{f}(*args)
258
- @#{k.name}_#{f} ||= self.#{k.name}_geos.to_#{f}(*args) rescue nil
259
- end
260
- EOF
261
- self.class_eval(src, __FILE__, line)
262
- end
263
- end
264
- end
265
-
266
- # Creates column accessors for geometry columns only.
267
- def create_geometry_column_accessors!(options = {})
268
- options = {
269
- :geometry_columns => true
270
- }.merge(options)
271
-
272
- create_spatial_column_accessors!(options)
273
- end
274
-
275
- # Creates column accessors for geometry columns only.
276
- def create_geography_column_accessors!(options = {})
277
- options = {
278
- :geography_columns => true
279
- }.merge(options)
280
-
281
- create_spatial_column_accessors!(options)
282
- end
283
-
284
- # Stubs for documentation purposes:
285
-
286
- # Returns a Geos::Geometry object.
287
- def __spatial_column_name_geos; end
288
-
289
- # Returns a hex-encoded WKB String.
290
- def __spatial_column_name_wkb; end
291
-
292
- # Returns a WKB String in binary.
293
- def __spatial_column_name_wkb_bin; end
294
-
295
- # Returns a WKT String.
296
- def __spatial_column_name_wkt; end
297
-
298
- # Returns a hex-encoded EWKB String.
299
- def __spatial_column_name_ewkb; end
300
-
301
- # Returns an EWKB String in binary.
302
- def __spatial_column_name_ewkb_bin; end
303
-
304
- # Returns an EWKT String.
305
- def __spatial_column_name_ewkt; end
306
-
307
- # An enhanced setter that tries to deduce how you're
308
- # setting the value. The setter can handle Geos::Geometry
309
- # objects, WKT, EWKT and WKB and EWKB in both hex and
310
- # binary.
311
- #
312
- # When dealing with SRIDs, you can have the SRID set
313
- # automatically on WKT by setting the value as
314
- # "SRID=default;GEOMETRY(...)", i.e.:
315
- #
316
- # spatial_column_name = "SRID=default;POINT(1.0 1.0)"
317
- #
318
- # The SRID will be filled in automatically if available.
319
- # Note that we're only setting the SRID on the geometry,
320
- # but we're not doing any sort of re-projection or anything
321
- # of the sort. If you need to convert from one SRID to
322
- # another, you're stuck for the moment, but we'll be adding
323
- # support for reprojections/transoformations via proj4rb
324
- # soon.
325
- #
326
- # For WKB, you're better off manipulating the WKB directly
327
- # or using proper Geos geometry objects.
328
- def __spatial_column_name=(geom); end
329
-
330
- # An enhanced getter that accepts an options Hash or
331
- # String/Symbol that can be used to determine the output
332
- # format. In the options Hash, use :format, or set the
333
- # format directly as a String or Symbol.
334
- #
335
- # This basically allows you to do the following, which
336
- # are equivalent:
337
- #
338
- # spatial_column_name(:wkt)
339
- # spatial_column_name(:format => :wkt)
340
- # spatial_column_name_wkt
341
- def __spatial_column_name(options = {}); end
342
-
343
- undef __spatial_column_name_geos
344
- undef __spatial_column_name_wkb
345
- undef __spatial_column_name_wkb_bin
346
- undef __spatial_column_name_wkt
347
- undef __spatial_column_name_ewkb
348
- undef __spatial_column_name_ewkb_bin
349
- undef __spatial_column_name_ewkt
350
-
351
- #undef __spatial_column_name_geojson
352
- #undef __spatial_column_name_geohash
353
- #undef __spatial_column_name_hexweb
354
- #undef __spatial_column_name_kml
355
- #undef __spatial_column_name_svg
356
- #undef __spatial_column_name_x3d
357
- #undef __spatial_column_name_lat_lon_text
358
-
359
- undef __spatial_column_name=
360
- undef __spatial_column_name
361
- end
362
- end
363
-
364
- # Alias for backwards compatibility.
365
- GeometryColumns = SpatialColumns
366
- end
367
- end