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,493 +0,0 @@
1
-
2
- module Geos
3
- module ActiveRecord
4
-
5
- # Creates named scopes for geospatial relationships. The scopes created
6
- # follow the nine relationships established by the standard
7
- # Dimensionally Extended 9-Intersection Matrix functions plus a couple
8
- # of extra ones provided by PostGIS.
9
- #
10
- # Scopes provided are:
11
- #
12
- # * st_contains
13
- # * st_containsproperly
14
- # * st_covers
15
- # * st_coveredby
16
- # * st_crosses
17
- # * st_disjoint
18
- # * st_equals
19
- # * st_intersects
20
- # * st_orderingequals
21
- # * st_overlaps
22
- # * st_touches
23
- # * st_within
24
- # * st_dwithin
25
- # * st_dfullywithin
26
- #
27
- # The first argument to each of these methods can be a Geos::Geometry-based
28
- # object or anything readable by Geos.read along with an optional
29
- # options Hash.
30
- #
31
- # For ordering, we have the following:
32
- #
33
- # The following scopes take no arguments:
34
- #
35
- # * order_by_ndims
36
- # * order_by_npoints
37
- # * order_by_nrings
38
- # * order_by_numgeometries
39
- # * order_by_numinteriorring
40
- # * order_by_numinteriorrings
41
- # * order_by_numpoints
42
- # * order_by_length3d
43
- # * order_by_length
44
- # * order_by_length2d
45
- # * order_by_perimeter
46
- # * order_by_perimeter2d
47
- # * order_by_perimeter3d
48
- #
49
- # These next scopes allow you to specify a geometry argument for
50
- # measurement:
51
- #
52
- # * order_by_distance
53
- # * order_by_distance_sphere
54
- # * order_by_maxdistance
55
- # * order_by_hausdorffdistance (additionally allows you to set the
56
- # densify_frac argument)
57
- # * order_by_distance_spheroid (requires an additional SPHEROID
58
- # string to calculate against)
59
- #
60
- # These next scopes allow you to specify a SPHEROID string to calculate
61
- # against:
62
- #
63
- # * order_by_length2d_spheroid
64
- # * order_by_length3d_spheroid
65
- # * order_by_length_spheroid
66
- #
67
- # == Options
68
- #
69
- # * :column - the column to compare against. The default is 'the_geom'.
70
- # * :use_index - whether to use the "ST_" methods or the "\_ST_"
71
- # variants which don't use indexes. The default is true.
72
- # * :wkb_options - in order to facilitate some conversions, geometries
73
- # are converted to WKB. The default is `{:include_srid => true}` to
74
- # force the geometry to use PostGIS's Extended WKB.
75
- # * :allow_null - relationship scopes have the option of treating NULL
76
- # geometry values as TRUE, i.e.
77
- #
78
- # ST_within(the_geom, ...) OR the_geom IS NULL
79
- #
80
- # * :desc - the order_by scopes have an additional :desc option to alllow
81
- # for DESC ordering.
82
- # * :nulls - the order_by scopes also allow you to specify whether you
83
- # want NULL values to be sorted first or last.
84
- #
85
- # Because it's quite common to only want to flip the ordering to DESC,
86
- # you can also just pass :desc on its own rather than as an options Hash.
87
- #
88
- # == SRID Detection
89
- #
90
- # * the default SRID according to the SQL-MM standard is 0, but versions
91
- # of PostGIS prior to 2.0 would return -1. We do some detection here
92
- # and set the value of Geos::ActiveRecord.UNKNOWN_SRIDS[type]
93
- # accordingly.
94
- # * if the geometry itself has an SRID, we'll compare it to the
95
- # geometry of the column. If they differ, we'll use ST_Transform
96
- # to transform the geometry to the proper SRID for comparison. If
97
- # they're the same, no conversion is necessary.
98
- # * if no SRID is specified in the geometry, we'll use ST_SetSRID
99
- # to set the SRID to the column's SRID.
100
- # * in cases where the column has been defined with an SRID of
101
- # UNKNOWN_SRIDS[type], no transformation is done, but we'll set the SRID
102
- # of the geometry to UNKNOWN_SRIDS[type] to perform the query using
103
- # ST_SetSRID, as we'll assume the SRID of the column to be whatever
104
- # the SRID of the geometry is.
105
- # * when using geography types, the SRID is never transformed since
106
- # it's assumed that all of your geometries will be in 4326.
107
- module SpatialScopes
108
- SCOPE_METHOD = if ::ActiveRecord::VERSION::MAJOR >= 3
109
- 'scope'
110
- else
111
- 'named_scope'
112
- end
113
-
114
- RELATIONSHIPS = %w{
115
- contains
116
- containsproperly
117
- covers
118
- coveredby
119
- crosses
120
- disjoint
121
- equals
122
- intersects
123
- orderingequals
124
- overlaps
125
- touches
126
- within
127
- }
128
-
129
- ZERO_ARGUMENT_MEASUREMENTS = %w{
130
- area
131
- ndims
132
- npoints
133
- nrings
134
- numgeometries
135
- numinteriorring
136
- numinteriorrings
137
- numpoints
138
- length
139
- length2d
140
- perimeter
141
- perimeter2d
142
- }
143
-
144
- ONE_GEOMETRY_ARGUMENT_MEASUREMENTS = %w{
145
- distance
146
- distance_sphere
147
- maxdistance
148
- }
149
-
150
- ONE_GEOMETRY_ARGUMENT_AND_ONE_ARGUMENT_RELATIONSHIPS = %w{
151
- dwithin
152
- dfullywithin
153
- }
154
-
155
- ONE_ARGUMENT_MEASUREMENTS = %w{
156
- length2d_spheroid
157
- length_spheroid
158
- }
159
-
160
- # Some functions were renamed in PostGIS 2.0.
161
- if Geos::ActiveRecord.POSTGIS[:lib] >= '2.0'
162
- RELATIONSHIPS.concat(%w{
163
- 3dintersects
164
- })
165
-
166
- ZERO_ARGUMENT_MEASUREMENTS.concat(%w{
167
- 3dlength
168
- 3dperimeter
169
- })
170
-
171
- ONE_ARGUMENT_MEASUREMENTS.concat(%w{
172
- 3dlength_spheroid
173
- })
174
-
175
- ONE_GEOMETRY_ARGUMENT_MEASUREMENTS.concat(%w{
176
- 3ddistance
177
- 3dmaxdistance
178
- })
179
-
180
- ONE_GEOMETRY_ARGUMENT_AND_ONE_ARGUMENT_RELATIONSHIPS.concat(%w{
181
- 3ddwithin
182
- 3ddfullywithin
183
- })
184
- else
185
- ZERO_ARGUMENT_MEASUREMENTS.concat(%w{
186
- length3d
187
- perimeter3d
188
- })
189
-
190
- ONE_ARGUMENT_MEASUREMENTS.concat(%w{
191
- length3d_spheroid
192
- })
193
- end
194
-
195
- FUNCTION_ALIASES = {
196
- 'order_by_max_distance' => 'order_by_maxdistance',
197
- 'st_geometrytype' => 'st_geometry_type'
198
- }
199
-
200
- COMPATIBILITY_FUNCTION_ALIASES = if Geos::ActiveRecord.POSTGIS[:lib] >= '2.0'
201
- {
202
- 'order_by_length3d' => 'order_by_3dlength',
203
- 'order_by_perimeter3d' => 'order_by_3dperimeter',
204
- 'order_by_length3d_spheroid' => 'order_by_3dlength_spheroid'
205
- }
206
- else
207
- {
208
- 'order_by_3dlength' => 'order_by_length3d',
209
- 'order_by_3dperimeter' => 'order_by_perimeter3d',
210
- 'order_by_3dlength_spheroid' => 'order_by_length3d_spheroid'
211
- }
212
- end
213
-
214
- if Geos::ActiveRecord.POSTGIS[:lib] >= '2.0'
215
- FUNCTION_ALIASES.merge!({
216
- 'st_3d_dwithin' => 'st_3ddwithin',
217
- 'st_3d_dfully_within' => 'st_3ddfullywithin',
218
- 'order_by_3d_distance' => 'order_by_3ddistance',
219
- 'order_by_3d_max_distance' => 'order_by_3dmaxdistance'
220
- })
221
- end
222
-
223
- def self.included(base)
224
- base.class_eval do
225
- class << self
226
- protected
227
- def set_srid_or_transform(column_srid, geom_srid, geos, type)
228
- sql = if type != :geography && column_srid != geom_srid
229
- if column_srid == Geos::ActiveRecord.UNKNOWN_SRIDS[type] || geom_srid == Geos::ActiveRecord.UNKNOWN_SRIDS[type]
230
- %{ST_SetSRID(?::#{type}, #{column_srid})}
231
- else
232
- %{ST_Transform(?::#{type}, #{column_srid})}
233
- end
234
- else
235
- %{?::#{type}}
236
- end
237
-
238
- sanitize_sql([ sql, geos.to_ewkb ])
239
- end
240
-
241
- def read_geos(geom, column_srid)
242
- if geom.is_a?(String) && geom =~ /^SRID=default;/
243
- geom = geom.sub(/default/, column_srid.to_s)
244
- end
245
- Geos.read(geom)
246
- end
247
-
248
- def read_geom_srid(geos, column_type = :geometry)
249
- if geos.srid == 0 || geos.srid == -1
250
- Geos::ActiveRecord.UNKNOWN_SRIDS[column_type]
251
- else
252
- geos.srid
253
- end
254
- end
255
-
256
- def default_options(*args)
257
- options = args.extract_options!
258
-
259
- desc = if args.first == :desc
260
- true
261
- else
262
- options[:desc]
263
- end
264
-
265
- {
266
- :column => 'the_geom',
267
- :use_index => true,
268
- :desc => desc
269
- }.merge(options || {})
270
- end
271
-
272
- def function_name(function, use_index)
273
- if use_index
274
- "ST_#{function}"
275
- else
276
- "_ST_#{function}"
277
- end
278
- end
279
-
280
- def build_function_call(function, geom = nil, options = {}, function_options = {})
281
- options = default_options(options)
282
-
283
- function_options = {
284
- :additional_args => 0
285
- }.merge(function_options)
286
-
287
- ''.tap do |ret|
288
- column_name = self.connection.quote_table_name(options[:column])
289
- ret << "#{function_name(function, options[:use_index])}(#{self.quoted_table_name}.#{column_name}"
290
-
291
- if geom
292
- column_type = self.spatial_column_by_name(options[:column]).spatial_type
293
- column_srid = self.srid_for(options[:column])
294
-
295
- geos = read_geos(geom, column_srid)
296
- geom_srid = read_geom_srid(geos, column_type)
297
-
298
- ret << %{, #{self.set_srid_or_transform(column_srid, geom_srid, geos, column_type)}}
299
- end
300
-
301
- ret << ', ?' * function_options[:additional_args]
302
- ret << ')'
303
-
304
- if options[:allow_null]
305
- ret << " OR #{self.quoted_table_name}.#{column_name} IS NULL"
306
- end
307
- end
308
- end
309
-
310
- def additional_ordering(*args)
311
- options = args.extract_options!
312
-
313
- desc = if args.first == :desc
314
- true
315
- else
316
- options[:desc]
317
- end
318
-
319
- ''.tap do |ret|
320
- if desc
321
- ret << ' DESC'
322
- end
323
-
324
- if options[:nulls]
325
- ret << " NULLS #{options[:nulls].to_s.upcase}"
326
- end
327
- end
328
- end
329
-
330
- def assert_arguments_length(args, min, max = (1.0 / 0.0))
331
- raise ArgumentError.new("wrong number of arguments (#{args.length} for #{min}-#{max})") unless
332
- args.length.between?(min, max)
333
- end
334
- end
335
- end
336
-
337
- RELATIONSHIPS.each do |relationship|
338
- src, line = <<-EOF, __LINE__ + 1
339
- #{SCOPE_METHOD} :st_#{relationship}, lambda { |*args|
340
- assert_arguments_length(args, 1, 2)
341
-
342
- unless args.first.nil?
343
- {
344
- :conditions => build_function_call(
345
- '#{relationship}',
346
- *args
347
- )
348
- }
349
- end
350
- }
351
- EOF
352
- base.class_eval(src, __FILE__, line)
353
- end
354
-
355
- ONE_GEOMETRY_ARGUMENT_AND_ONE_ARGUMENT_RELATIONSHIPS.each do |relationship|
356
- src, line = <<-EOF, __LINE__ + 1
357
- #{SCOPE_METHOD} :st_#{relationship}, lambda { |*args|
358
- assert_arguments_length(args, 2, 3)
359
- geom, distance, options = args
360
-
361
- {
362
- :conditions => [
363
- build_function_call('#{relationship}', geom, options, :additional_args => 1),
364
- distance
365
- ]
366
- }
367
- }
368
- EOF
369
- base.class_eval(src, __FILE__, line)
370
- end
371
-
372
- base.class_eval do
373
- send(SCOPE_METHOD, :st_geometry_type, lambda { |*args|
374
- assert_arguments_length(args, 1)
375
- options = args.extract_options!
376
- types = args
377
-
378
- {
379
- :conditions => [
380
- "#{build_function_call('GeometryType', nil, options)} IN (?)",
381
- types
382
- ]
383
- }
384
- })
385
- end
386
-
387
- ZERO_ARGUMENT_MEASUREMENTS.each do |measurement|
388
- src, line = <<-EOF, __LINE__ + 1
389
- #{SCOPE_METHOD} :order_by_#{measurement}, lambda { |*args|
390
- assert_arguments_length(args, 0, 1)
391
- options = args[0]
392
-
393
- function_call = build_function_call('#{measurement}', nil, options)
394
- function_call << additional_ordering(options)
395
-
396
- {
397
- :order => function_call
398
- }
399
- }
400
- EOF
401
- base.class_eval(src, __FILE__, line)
402
- end
403
-
404
- ONE_GEOMETRY_ARGUMENT_MEASUREMENTS.each do |measurement|
405
- src, line = <<-EOF, __LINE__ + 1
406
- #{SCOPE_METHOD} :order_by_#{measurement}, lambda { |*args|
407
- assert_arguments_length(args, 1, 2)
408
- geom, options = args
409
-
410
- function_call = build_function_call('#{measurement}', geom, options)
411
- function_call << additional_ordering(options)
412
-
413
- {
414
- :order => function_call
415
- }
416
- }
417
- EOF
418
- base.class_eval(src, __FILE__, line)
419
- end
420
-
421
- ONE_ARGUMENT_MEASUREMENTS.each do |measurement|
422
- src, line = <<-EOF, __LINE__ + 1
423
- #{SCOPE_METHOD} :order_by_#{measurement}, lambda { |*args|
424
- assert_arguments_length(args, 1, 2)
425
- argument, options = args
426
-
427
- function_call = build_function_call('#{measurement}', nil, options, :additional_args => 1)
428
- function_call << additional_ordering(options)
429
-
430
- {
431
- :order => sanitize_sql([ function_call, argument ])
432
- }
433
- }
434
- EOF
435
- base.class_eval(src, __FILE__, line)
436
- end
437
-
438
- base.class_eval do
439
- send(SCOPE_METHOD, :order_by_hausdorffdistance, lambda { |*args|
440
- assert_arguments_length(args, 1, 3)
441
- options = args.extract_options!
442
- geom, densify_frac = args
443
-
444
- function_call = build_function_call(
445
- 'hausdorffdistance',
446
- geom,
447
- options,
448
- :additional_args => (densify_frac.present? ? 1 : 0)
449
- )
450
- function_call << additional_ordering(options)
451
-
452
- {
453
- :order => sanitize_sql([
454
- function_call,
455
- densify_frac
456
- ])
457
- }
458
- })
459
-
460
- send(SCOPE_METHOD, :order_by_distance_spheroid, lambda { |*args|
461
- assert_arguments_length(args, 2, 3)
462
- geom, spheroid, options = args
463
-
464
- function_call = build_function_call(
465
- 'distance_spheroid',
466
- geom,
467
- options,
468
- :additional_args => 1
469
- )
470
- function_call << additional_ordering(options)
471
-
472
- {
473
- :order => sanitize_sql([
474
- function_call,
475
- spheroid
476
- ])
477
- }
478
- })
479
-
480
- class << base
481
- aliases = COMPATIBILITY_FUNCTION_ALIASES.merge(FUNCTION_ALIASES)
482
-
483
- aliases.each do |k, v|
484
- alias_method(k, v)
485
- end
486
- end
487
- end
488
- end
489
- end
490
-
491
- GeospatialScopes = SpatialScopes
492
- end
493
- end