ffi-geos 0.0.1.beta1

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.
@@ -0,0 +1,504 @@
1
+
2
+ module Geos
3
+ class Geometry
4
+ include Geos::Tools
5
+
6
+ attr_reader :ptr
7
+
8
+ # For internal use. Geometry objects should be created via WkbReader,
9
+ # WktReader and the various Geos.create_* methods.
10
+ def initialize(ptr, auto_free = true)
11
+ @ptr = FFI::AutoPointer.new(
12
+ ptr,
13
+ auto_free ? self.class.method(:release) : self.class.method(:no_release)
14
+ )
15
+ end
16
+
17
+ def self.no_release(ptr) #:nodoc:
18
+ end
19
+
20
+ def self.release(ptr) #:nodoc:
21
+ FFIGeos.GEOSGeom_destroy_r(Geos.current_handle, ptr)
22
+ end
23
+
24
+ def clone
25
+ cast_geometry_ptr(FFIGeos.GEOSGeom_clone_r(Geos.current_handle, ptr))
26
+ end
27
+
28
+ # Returns the name of the Geometry type, i.e. "Point", "Polygon", etc.
29
+ def geom_type
30
+ FFIGeos.GEOSGeomType_r(Geos.current_handle, self.ptr)
31
+ end
32
+
33
+ # Returns one of the values from Geos::GeomTypes.
34
+ def type_id
35
+ FFIGeos.GEOSGeomTypeId_r(Geos.current_handle, self.ptr)
36
+ end
37
+
38
+ def normalize
39
+ FFIGeos.GEOSNormalize_r(Geos.current_handle, self.ptr)
40
+ end
41
+
42
+ def srid
43
+ FFIGeos.GEOSGetSRID_r(Geos.current_handle, self.ptr)
44
+ end
45
+
46
+ def srid=(s)
47
+ FFIGeos.GEOSSetSRID_r(Geos.current_handle, self.ptr, s)
48
+ end
49
+
50
+ def dimensions
51
+ FFIGeos.GEOSGeom_getDimensions_r(Geos.current_handle, self.ptr)
52
+ end
53
+
54
+ def num_geometries
55
+ FFIGeos.GEOSGetNumGeometries_r(Geos.current_handle, self.ptr)
56
+ end
57
+
58
+ def num_coordinates
59
+ FFIGeos.GEOSGetNumCoordinates_r(Geos.current_handle, self.ptr)
60
+ end
61
+
62
+ def coord_seq
63
+ CoordinateSequence.new(FFIGeos.GEOSGeom_getCoordSeq_r(Geos.current_handle, self.ptr), false)
64
+ end
65
+
66
+ def intersection(geom)
67
+ check_geometry(geom)
68
+ cast_geometry_ptr(FFIGeos.GEOSIntersection_r(Geos.current_handle, self.ptr, geom.ptr))
69
+ end
70
+
71
+ # :call-seq:
72
+ # buffer(width)
73
+ # buffer(width, options)
74
+ # buffer(width, quad_segs)
75
+ #
76
+ # Calls buffer on the Geometry. Calling with an options Hash is equivalent
77
+ # to calling buffer_with_style.
78
+ #
79
+ # By default, quad_segs is set to 8.
80
+ def buffer(width, *args)
81
+ quad_segs, options = if args.length <= 0
82
+ [ 8, nil ]
83
+ else
84
+ if args.first.is_a?(Hash)
85
+ [ args.first[:quad_segs] || 8, args.first ]
86
+ else
87
+ [ args.first, args[1] ]
88
+ end
89
+ end
90
+
91
+ if options
92
+ if options.is_a?(Hash)
93
+ self.buffer_with_style(width, options)
94
+ else
95
+ raise RuntimeError.new("Expected an options Hash")
96
+ end
97
+ else
98
+ cast_geometry_ptr(FFIGeos.GEOSBuffer_r(Geos.current_handle, self.ptr, width, quad_segs))
99
+ end
100
+ end
101
+
102
+ # Options:
103
+ #
104
+ # * :quad_segs - defaults to 8.
105
+ # * :endcap - defaults :round.
106
+ # * :join - defaults to :round.
107
+ # * :mitre_limit - defaults to 5.0.
108
+ def buffer_with_style(width, options = {})
109
+ options = {
110
+ :quad_segs => 8,
111
+ :endcap => :round,
112
+ :join => :round,
113
+ :mitre_limit => 5.0
114
+ }.merge(options)
115
+
116
+ check_enum_value(Geos::BufferCapStyles, options[:endcap]) if options[:endcap]
117
+ check_enum_value(Geos::BufferJoinStyles, options[:join]) if options[:join]
118
+
119
+ cast_geometry_ptr(FFIGeos.GEOSBufferWithStyle_r(
120
+ Geos.current_handle,
121
+ self.ptr,
122
+ width,
123
+ options[:quad_segs],
124
+ options[:endcap],
125
+ options[:join],
126
+ options[:mitre_limit]
127
+ ))
128
+ end
129
+
130
+ def convex_hull
131
+ cast_geometry_ptr(FFIGeos.GEOSConvexHull_r(Geos.current_handle, self.ptr))
132
+ end
133
+
134
+ def difference(geom)
135
+ check_geometry(geom)
136
+ cast_geometry_ptr(FFIGeos.GEOSDifference_r(Geos.current_handle, self.ptr, geom.ptr))
137
+ end
138
+
139
+ def sym_difference(geom)
140
+ check_geometry(geom)
141
+ cast_geometry_ptr(FFIGeos.GEOSSymDifference_r(Geos.current_handle, self.ptr, geom.ptr))
142
+ end
143
+
144
+ def boundary
145
+ cast_geometry_ptr(FFIGeos.GEOSBoundary_r(Geos.current_handle, self.ptr))
146
+ end
147
+
148
+ # Calling without a geom argument is equivalent to calling unary_union when
149
+ # using GEOS 3.3+ and is equivalent to calling union_cascaded in older
150
+ # versions.
151
+ def union(geom = nil)
152
+ if geom
153
+ check_geometry(geom)
154
+ cast_geometry_ptr(FFIGeos.GEOSUnion_r(Geos.current_handle, self.ptr, geom.ptr))
155
+ else
156
+ if self.respond_to?(:unary_union)
157
+ self.unary_union
158
+ else
159
+ self.union_cascaded
160
+ end
161
+ end
162
+ end
163
+
164
+ def union_cascaded
165
+ cast_geometry_ptr(FFIGeos.GEOSUnionCascaded_r(Geos.current_handle, self.ptr))
166
+ end
167
+
168
+ if FFIGeos.respond_to?(:GEOSUnaryUnion_r)
169
+ # Available in GEOS 3.3+
170
+ def unary_union
171
+ cast_geometry_ptr(FFIGeos.GEOSUnaryUnion_r(Geos.current_handle, self.ptr))
172
+ end
173
+ end
174
+
175
+ def point_on_surface
176
+ cast_geometry_ptr(FFIGeos.GEOSPointOnSurface_r(Geos.current_handle, self.ptr))
177
+ end
178
+
179
+ def centroid
180
+ cast_geometry_ptr(FFIGeos.GEOSGetCentroid_r(Geos.current_handle, self.ptr))
181
+ end
182
+ alias :center :centroid
183
+
184
+ def envelope
185
+ cast_geometry_ptr(FFIGeos.GEOSEnvelope_r(Geos.current_handle, self.ptr))
186
+ end
187
+
188
+ # Returns the Dimensionally Extended Nine-Intersection Model (DE-9IM)
189
+ # matrix of the geometries as a String.
190
+ def relate(geom)
191
+ check_geometry(geom)
192
+ FFIGeos.GEOSRelate_r(Geos.current_handle, self.ptr, geom.ptr)
193
+ end
194
+
195
+ # Checks the DE-9IM pattern against the geoms.
196
+ def relate_pattern(geom, pattern)
197
+ check_geometry(geom)
198
+ bool_result(FFIGeos.GEOSRelatePattern_r(Geos.current_handle, self.ptr, geom.ptr, pattern))
199
+ end
200
+
201
+ if FFIGeos.respond_to?(:GEOSRelateBoundaryNodeRule_r)
202
+ # Available in GEOS 3.3+.
203
+ def relate_boundary_node_rule(geom, bnr = :mod2)
204
+ check_geometry(geom)
205
+ check_enum_value(Geos::RelateBoundaryNodeRules, bnr)
206
+ FFIGeos.GEOSRelateBoundaryNodeRule_r(Geos.current_handle, self.ptr, geom.ptr, bnr)
207
+ end
208
+ end
209
+
210
+ def line_merge
211
+ cast_geometry_ptr(FFIGeos.GEOSLineMerge_r(Geos.current_handle, self.ptr))
212
+ end
213
+
214
+ def simplify(tolerance)
215
+ cast_geometry_ptr(FFIGeos.GEOSSimplify_r(Geos.current_handle, self.ptr, tolerance))
216
+ end
217
+
218
+ def topology_preserve_simplify(tolerance)
219
+ cast_geometry_ptr(FFIGeos.GEOSTopologyPreserveSimplify_r(Geos.current_handle, self.ptr, tolerance))
220
+ end
221
+
222
+ def extract_unique_points
223
+ cast_geometry_ptr(FFIGeos.GEOSGeom_extractUniquePoints_r(Geos.current_handle, self.ptr))
224
+ end
225
+ alias :unique_points :extract_unique_points
226
+
227
+ def disjoint?(geom)
228
+ check_geometry(geom)
229
+ bool_result(FFIGeos.GEOSDisjoint_r(Geos.current_handle, self.ptr, geom.ptr))
230
+ end
231
+
232
+ def touches?(geom)
233
+ check_geometry(geom)
234
+ bool_result(FFIGeos.GEOSTouches_r(Geos.current_handle, self.ptr, geom.ptr))
235
+ end
236
+
237
+ def intersects?(geom)
238
+ check_geometry(geom)
239
+ bool_result(FFIGeos.GEOSIntersects_r(Geos.current_handle, self.ptr, geom.ptr))
240
+ end
241
+
242
+ def crosses?(geom)
243
+ check_geometry(geom)
244
+ bool_result(FFIGeos.GEOSCrosses_r(Geos.current_handle, self.ptr, geom.ptr))
245
+ end
246
+
247
+ def within?(geom)
248
+ check_geometry(geom)
249
+ bool_result(FFIGeos.GEOSWithin_r(Geos.current_handle, self.ptr, geom.ptr))
250
+ end
251
+
252
+ def contains?(geom)
253
+ check_geometry(geom)
254
+ bool_result(FFIGeos.GEOSContains_r(Geos.current_handle, self.ptr, geom.ptr))
255
+ end
256
+
257
+ def overlaps?(geom)
258
+ check_geometry(geom)
259
+ bool_result(FFIGeos.GEOSOverlaps_r(Geos.current_handle, self.ptr, geom.ptr))
260
+ end
261
+
262
+ if FFIGeos.respond_to?(:GEOSCovers_r)
263
+ # In GEOS versions 3.3+, the native GEOSCoveredBy method will be used,
264
+ # while in older GEOS versions we'll use a relate_pattern-based
265
+ # implementation.
266
+ def covers?(geom)
267
+ check_geometry(geom)
268
+ bool_result(FFIGeos.GEOSCovers_r(Geos.current_handle, self.ptr, geom.ptr))
269
+ end
270
+ else
271
+ def covers?(geom) #:nodoc:
272
+ check_geometry(geom)
273
+ !!%w{
274
+ T*****FF*
275
+ *T****FF*
276
+ ***T**FF*
277
+ ****T*FF*
278
+ }.detect do |pattern|
279
+ self.relate_pattern(geom, pattern)
280
+ end
281
+ end
282
+ end
283
+
284
+ if FFIGeos.respond_to?(:GEOSCoveredBy_r)
285
+ # In GEOS versions 3.3+, the native GEOSCovers method will be used,
286
+ # while in older GEOS versions we'll use a relate_pattern-based
287
+ # implementation.
288
+ def covered_by?(geom)
289
+ check_geometry(geom)
290
+ bool_result(FFIGeos.GEOSCoveredBy_r(Geos.current_handle, self.ptr, geom.ptr))
291
+ end
292
+ else
293
+ def covered_by?(geom) #:nodoc:
294
+ check_geometry(geom)
295
+ !!%w{
296
+ T*F**F***
297
+ *TF**F***
298
+ **FT*F***
299
+ **F*TF***
300
+ }.detect do |pattern|
301
+ self.relate_pattern(geom, pattern)
302
+ end
303
+ end
304
+ end
305
+
306
+ def disjoint?(geom)
307
+ check_geometry(geom)
308
+ bool_result(FFIGeos.GEOSDisjoint_r(Geos.current_handle, self.ptr, geom.ptr))
309
+ end
310
+
311
+ def eql?(geom)
312
+ check_geometry(geom)
313
+ bool_result(FFIGeos.GEOSEquals_r(Geos.current_handle, self.ptr, geom.ptr))
314
+ end
315
+ alias :== :eql?
316
+
317
+ def eql_exact?(geom, tolerance)
318
+ check_geometry(geom)
319
+ bool_result(FFIGeos.GEOSEqualsExact_r(Geos.current_handle, self.ptr, geom.ptr, tolerance))
320
+ end
321
+
322
+ def empty?
323
+ bool_result(FFIGeos.GEOSisEmpty_r(Geos.current_handle, self.ptr))
324
+ end
325
+
326
+ def valid?
327
+ bool_result(FFIGeos.GEOSisValid_r(Geos.current_handle, self.ptr))
328
+ end
329
+
330
+ # Returns a String describing whether or not the Geometry is valid.
331
+ def valid_reason
332
+ FFIGeos.GEOSisValidReason_r(Geos.current_handle, self.ptr)
333
+ end
334
+
335
+ # Returns a Hash containing the following structure on invalid geometries:
336
+ #
337
+ # {
338
+ # :detail => "String explaining the problem",
339
+ # :location => Geos::Point # centered on the problem
340
+ # }
341
+ #
342
+ # If the Geometry is valid, returns nil.
343
+ def valid_detail(flags = 0)
344
+ detail = FFI::MemoryPointer.new(:pointer)
345
+ location = FFI::MemoryPointer.new(:pointer)
346
+ valid = bool_result(
347
+ FFIGeos.GEOSisValidDetail_r(Geos.current_handle, self.ptr, flags, detail, location)
348
+ )
349
+
350
+ if !valid
351
+ {
352
+ :detail => detail.read_pointer.read_string,
353
+ :location => cast_geometry_ptr(location.read_pointer)
354
+ }
355
+ end
356
+ end
357
+
358
+ def simple?
359
+ bool_result(FFIGeos.GEOSisSimple_r(Geos.current_handle, self.ptr))
360
+ end
361
+
362
+ def ring?
363
+ bool_result(FFIGeos.GEOSisRing_r(Geos.current_handle, self.ptr))
364
+ end
365
+
366
+ def has_z?
367
+ bool_result(FFIGeos.GEOSHasZ_r(Geos.current_handle, self.ptr))
368
+ end
369
+
370
+ # GEOS versions prior to 3.3.0 didn't handle exceptions and can crash on
371
+ # bad input.
372
+ if FFIGeos.respond_to?(:GEOSProject_r) && Geos::GEOS_VERSION >= '3.3.0'
373
+ def project(geom, normalized = false)
374
+ raise TypeError.new("Expected Geos::Point type") if !geom.is_a?(Geos::Point)
375
+
376
+ if normalized
377
+ FFIGeos.GEOSProjectNormalized_r(Geos.current_handle, self.ptr, geom.ptr)
378
+ else
379
+ FFIGeos.GEOSProject_r(Geos.current_handle, self.ptr, geom.ptr)
380
+ end
381
+ end
382
+
383
+ def project_normalized(geom)
384
+ self.project(geom, true)
385
+ end
386
+ end
387
+
388
+ def interpolate(d, normalized = false)
389
+ ret = if normalized
390
+ FFIGeos.GEOSInterpolateNormalized_r(Geos.current_handle, self.ptr, d)
391
+ else
392
+ FFIGeos.GEOSInterpolate_r(Geos.current_handle, self.ptr, d)
393
+ end
394
+
395
+ cast_geometry_ptr(ret)
396
+ end
397
+
398
+ def interpolate_normalized(d)
399
+ self.interpolate(d, true)
400
+ end
401
+
402
+ def start_point
403
+ cast_geometry_ptr(FFIGeos.GEOSGeomGetStartPoint_r(Geos.current_handle, self.ptr))
404
+ end
405
+
406
+ def end_point
407
+ cast_geometry_ptr(FFIGeos.GEOSGeomGetEndPoint_r(Geos.current_handle, self.ptr))
408
+ end
409
+
410
+ def area
411
+ FFI::MemoryPointer.new(:double).tap { |ret|
412
+ FFIGeos.GEOSArea_r(Geos.current_handle, self.ptr, ret)
413
+ }.read_double
414
+ end
415
+
416
+ def length
417
+ FFI::MemoryPointer.new(:double).tap { |ret|
418
+ FFIGeos.GEOSLength_r(Geos.current_handle, self.ptr, ret)
419
+ }.read_double
420
+ end
421
+
422
+ def distance(geom)
423
+ check_geometry(geom)
424
+ FFI::MemoryPointer.new(:double).tap { |ret|
425
+ FFIGeos.GEOSDistance_r(Geos.current_handle, self.ptr, geom.ptr, ret)
426
+ }.read_double
427
+ end
428
+
429
+ def hausdorff_distance(geom)
430
+ check_geometry(geom)
431
+ FFI::MemoryPointer.new(:double).tap { |ret|
432
+ FFIGeos.GEOSHausdorffDistance_r(Geos.current_handle, self.ptr, geom.ptr, ret)
433
+ }.read_double
434
+ end
435
+
436
+ def snap(geom, tolerance)
437
+ check_geometry(geom)
438
+ cast_geometry_ptr(FFIGeos.GEOSSnap_r(Geos.current_handle, self.ptr, geom.ptr, tolerance))
439
+ end
440
+ alias :snap_to :snap
441
+
442
+ def shared_paths(geom)
443
+ check_geometry(geom)
444
+ cast_geometry_ptr(FFIGeos.GEOSSharedPaths_r(Geos.current_handle, self.ptr, geom.ptr)).to_a
445
+ end
446
+
447
+ # Returns a Hash with the following structure:
448
+ #
449
+ # {
450
+ # :rings => [ ... ],
451
+ # :cuts => [ ... ],
452
+ # :dangles => [ ... ],
453
+ # :invalid_rings => [ ... ]
454
+ # }
455
+ def polygonize_full
456
+ cuts = FFI::MemoryPointer.new(:pointer)
457
+ dangles = FFI::MemoryPointer.new(:pointer)
458
+ invalid_rings = FFI::MemoryPointer.new(:pointer)
459
+
460
+ rings = cast_geometry_ptr(
461
+ FFIGeos.GEOSPolygonize_full_r(Geos.current_handle, self.ptr, cuts, dangles, invalid_rings)
462
+ )
463
+
464
+ cuts = cast_geometry_ptr(cuts.read_pointer)
465
+ dangles = cast_geometry_ptr(dangles.read_pointer)
466
+ invalid_rings = cast_geometry_ptr(invalid_rings.read_pointer)
467
+
468
+ {
469
+ :rings => rings.to_a,
470
+ :cuts => cuts.to_a,
471
+ :dangles => dangles.to_a,
472
+ :invalid_rings => invalid_rings.to_a
473
+ }
474
+ end
475
+
476
+ def polygonize
477
+ ary = FFI::MemoryPointer.new(:pointer)
478
+ ary.write_array_of_pointer([ self.ptr ])
479
+
480
+ cast_geometry_ptr(FFIGeos.GEOSPolygonize_r(Geos.current_handle, ary, 1)).to_a
481
+ end
482
+
483
+ def polygonize_cut_edges
484
+ ary = FFI::MemoryPointer.new(:pointer)
485
+ ary.write_array_of_pointer([ self.ptr ])
486
+
487
+ cast_geometry_ptr(FFIGeos.GEOSPolygonizer_getCutEdges_r(Geos.current_handle, ary, 1)).to_a
488
+ end
489
+
490
+ def to_prepared
491
+ Geos::PreparedGeometry.new(FFIGeos.GEOSPrepare_r(Geos.current_handle, self.ptr))
492
+ end
493
+
494
+ def to_s
495
+ writer = WktWriter.new
496
+ wkt = writer.write(self)
497
+ if wkt.length > 120
498
+ wkt = "#{wkt[0...120]} ... "
499
+ end
500
+
501
+ "#<Geos::#{self.geom_type}: #{wkt}>"
502
+ end
503
+ end
504
+ end