ffi-geos 0.0.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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