geos-extensions 0.2.2 → 0.3.0

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