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