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.
- data/MIT-LICENSE +22 -0
- data/README.rdoc +135 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/lib/coordinate_sequence.rb +126 -0
- data/lib/ffi-geos.rb +810 -0
- data/lib/geometry.rb +504 -0
- data/lib/geometry_collection.rb +28 -0
- data/lib/line_string.rb +61 -0
- data/lib/linear_ring.rb +5 -0
- data/lib/multi_line_string.rb +5 -0
- data/lib/multi_point.rb +5 -0
- data/lib/multi_polygon.rb +5 -0
- data/lib/point.rb +22 -0
- data/lib/polygon.rb +20 -0
- data/lib/prepared_geometry.rb +42 -0
- data/lib/strtree.rb +111 -0
- data/lib/tools.rb +54 -0
- data/lib/utils.rb +145 -0
- data/lib/wkb_reader.rb +33 -0
- data/lib/wkb_writer.rb +95 -0
- data/lib/wkt_reader.rb +29 -0
- data/lib/wkt_writer.rb +108 -0
- data/test/coordinate_sequence_tests.rb +70 -0
- data/test/geometry_tests.rb +1499 -0
- data/test/misc_tests.rb +53 -0
- data/test/point_tests.rb +31 -0
- data/test/prepared_geometry_tests.rb +28 -0
- data/test/strtree_tests.rb +54 -0
- data/test/test_helper.rb +40 -0
- data/test/utils_tests.rb +333 -0
- data/test/wkb_reader_tests.rb +188 -0
- data/test/wkb_writer_tests.rb +435 -0
- data/test/wkt_reader_tests.rb +120 -0
- data/test/wkt_writer_tests.rb +149 -0
- metadata +114 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
class GeometryCollection < Geometry
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
# Yields each Geometry in the GeometryCollection.
|
7
|
+
def each
|
8
|
+
self.num_geometries.times do |n|
|
9
|
+
yield self.get_geometry_n(n)
|
10
|
+
end
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_geometry_n(n)
|
15
|
+
if n < 0 || n >= self.num_geometries
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
cast_geometry_ptr(FFIGeos.GEOSGetGeometryN_r(Geos.current_handle, self.ptr, n), false)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](*args)
|
23
|
+
self.to_a[*args]
|
24
|
+
end
|
25
|
+
alias :slice :[]
|
26
|
+
alias :at :[]
|
27
|
+
end
|
28
|
+
end
|
data/lib/line_string.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
class LineString < Geometry
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def each
|
7
|
+
self.num_points.times do |n|
|
8
|
+
yield self.point_n(n)
|
9
|
+
end
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
if FFIGeos.respond_to?(:GEOSGeomGetNumPoints_r)
|
14
|
+
def num_points
|
15
|
+
FFIGeos.GEOSGeomGetNumPoints_r(Geos.current_handle, self.ptr)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
def num_points
|
19
|
+
self.coord_seq.length
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def point_n(n)
|
24
|
+
if n < 0 || n >= self.num_points
|
25
|
+
raise RuntimeError.new("Index out of bounds")
|
26
|
+
else
|
27
|
+
cast_geometry_ptr(FFIGeos.GEOSGeomGetPointN_r(Geos.current_handle, self.ptr, n))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](*args)
|
32
|
+
self.to_a[*args]
|
33
|
+
end
|
34
|
+
alias :slice :[]
|
35
|
+
|
36
|
+
def buffer_single_sided(width, options = {})
|
37
|
+
options = {
|
38
|
+
:quad_segs => 8,
|
39
|
+
:join => :round,
|
40
|
+
:mitre_limit => 5.0,
|
41
|
+
:left_side => false
|
42
|
+
}.merge(options)
|
43
|
+
|
44
|
+
cast_geometry_ptr(FFIGeos.GEOSSingleSidedBuffer_r(
|
45
|
+
Geos.current_handle,
|
46
|
+
self.ptr,
|
47
|
+
width,
|
48
|
+
options[:quad_segs],
|
49
|
+
options[:join],
|
50
|
+
options[:mitre_limit],
|
51
|
+
options[:left_side] ? 1 : 0
|
52
|
+
))
|
53
|
+
end
|
54
|
+
|
55
|
+
if FFIGeos.respond_to?(:GEOSisClosed_r)
|
56
|
+
def closed?
|
57
|
+
bool_result(FFIGeos.GEOSisClosed_r(Geos.current_handle, self.ptr))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/linear_ring.rb
ADDED
data/lib/multi_point.rb
ADDED
data/lib/point.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
class Point < Geometry
|
4
|
+
if FFIGeos.respond_to?(:GEOSGeomGetX_r)
|
5
|
+
def get_x
|
6
|
+
FFI::MemoryPointer.new(:double).tap { |ret|
|
7
|
+
FFIGeos.GEOSGeomGetX_r(Geos.current_handle, self.ptr, ret)
|
8
|
+
}.read_double
|
9
|
+
end
|
10
|
+
alias :x :get_x
|
11
|
+
end
|
12
|
+
|
13
|
+
if FFIGeos.respond_to?(:GEOSGeomGetY_r)
|
14
|
+
def get_y
|
15
|
+
FFI::MemoryPointer.new(:double).tap { |ret|
|
16
|
+
FFIGeos.GEOSGeomGetY_r(Geos.current_handle, self.ptr, ret)
|
17
|
+
}.read_double
|
18
|
+
end
|
19
|
+
alias :y :get_y
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/polygon.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
class Polygon < Geometry
|
4
|
+
def num_interior_rings
|
5
|
+
FFIGeos.GEOSGetNumInteriorRings_r(Geos.current_handle, self.ptr)
|
6
|
+
end
|
7
|
+
|
8
|
+
def interior_ring_n(n)
|
9
|
+
if n < 0 || n >= self.num_interior_rings
|
10
|
+
raise RuntimeError.new("Index out of bounds")
|
11
|
+
else
|
12
|
+
cast_geometry_ptr(FFIGeos.GEOSGetInteriorRingN_r(Geos.current_handle, self.ptr, n), false)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def exterior_ring
|
17
|
+
cast_geometry_ptr(FFIGeos.GEOSGetExteriorRing_r(Geos.current_handle, self.ptr), false)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
class PreparedGeometry
|
4
|
+
include Geos::Tools
|
5
|
+
|
6
|
+
attr_reader :ptr
|
7
|
+
|
8
|
+
def initialize(ptr, auto_free = true)
|
9
|
+
@ptr = FFI::AutoPointer.new(
|
10
|
+
ptr,
|
11
|
+
auto_free ? self.class.method(:release) : self.class.method(:no_release)
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.no_release(ptr) #:nodoc:
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.release(ptr) #:nodoc:
|
19
|
+
FFIGeos.GEOSPreparedGeom_destroy_r(Geos.current_handle, ptr)
|
20
|
+
end
|
21
|
+
|
22
|
+
def contains?(geom)
|
23
|
+
check_geometry(geom)
|
24
|
+
bool_result(FFIGeos.GEOSPreparedContains_r(Geos.current_handle, self.ptr, geom.ptr))
|
25
|
+
end
|
26
|
+
|
27
|
+
def contains_properly?(geom)
|
28
|
+
check_geometry(geom)
|
29
|
+
bool_result(FFIGeos.GEOSPreparedContainsProperly_r(Geos.current_handle, self.ptr, geom.ptr))
|
30
|
+
end
|
31
|
+
|
32
|
+
def covers?(geom)
|
33
|
+
check_geometry(geom)
|
34
|
+
bool_result(FFIGeos.GEOSPreparedCovers_r(Geos.current_handle, self.ptr, geom.ptr))
|
35
|
+
end
|
36
|
+
|
37
|
+
def intersects?(geom)
|
38
|
+
check_geometry(geom)
|
39
|
+
bool_result(FFIGeos.GEOSPreparedIntersects_r(Geos.current_handle, self.ptr, geom.ptr))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/strtree.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
class STRtree
|
4
|
+
include Geos::Tools
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :ptr
|
8
|
+
|
9
|
+
def initialize(capacity)
|
10
|
+
ptr = FFIGeos.GEOSSTRtree_create_r(Geos.current_handle, capacity)
|
11
|
+
|
12
|
+
@ptr = FFI::AutoPointer.new(
|
13
|
+
ptr,
|
14
|
+
self.class.method(:release)
|
15
|
+
)
|
16
|
+
|
17
|
+
@storage = {}
|
18
|
+
@storage_pointers = {}
|
19
|
+
@storage_key = 0
|
20
|
+
@built = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.release(ptr) #:nodoc:
|
24
|
+
FFIGeos.GEOSSTRtree_destroy_r(Geos.current_handle, ptr)
|
25
|
+
end
|
26
|
+
|
27
|
+
def built?
|
28
|
+
@built
|
29
|
+
end
|
30
|
+
|
31
|
+
def next_key
|
32
|
+
@storage_key += 1
|
33
|
+
end
|
34
|
+
private :next_key
|
35
|
+
|
36
|
+
def insert(geom, item)
|
37
|
+
check_geometry(geom)
|
38
|
+
|
39
|
+
if self.built?
|
40
|
+
raise RuntimeError.new("STRtree has already been built")
|
41
|
+
else
|
42
|
+
key = next_key
|
43
|
+
key_ptr = FFI::MemoryPointer.new(:pointer)
|
44
|
+
key_ptr.write_int(key)
|
45
|
+
|
46
|
+
@storage[key] = item
|
47
|
+
@storage_pointers[key] = key_ptr
|
48
|
+
FFIGeos.GEOSSTRtree_insert_r(Geos.current_handle, self.ptr, geom.ptr, key_ptr)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def remove(geom, item)
|
53
|
+
check_geometry(geom)
|
54
|
+
|
55
|
+
key = if @storage.respond_to?(:key)
|
56
|
+
@storage.key(item)
|
57
|
+
else
|
58
|
+
@storage.index(item)
|
59
|
+
end
|
60
|
+
|
61
|
+
if key
|
62
|
+
key_ptr = @storage_pointers[key]
|
63
|
+
result = FFIGeos.GEOSSTRtree_remove_r(Geos.current_handle, self.ptr, geom.ptr, key_ptr)
|
64
|
+
@built = true
|
65
|
+
|
66
|
+
if result == 1
|
67
|
+
@storage[key] = nil
|
68
|
+
@storage_pointers[key] = nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def query(geom)
|
74
|
+
check_geometry(geom)
|
75
|
+
|
76
|
+
@built = true
|
77
|
+
retval = nil
|
78
|
+
|
79
|
+
callback = if block_given?
|
80
|
+
proc { |*args|
|
81
|
+
key = args.first.read_int
|
82
|
+
yield(@storage[key])
|
83
|
+
}
|
84
|
+
else
|
85
|
+
retval = []
|
86
|
+
proc { |*args|
|
87
|
+
retval << @storage[args.first.read_int]
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
FFIGeos.GEOSSTRtree_query_r(
|
92
|
+
Geos.current_handle,
|
93
|
+
self.ptr,
|
94
|
+
geom.ptr,
|
95
|
+
callback,
|
96
|
+
nil
|
97
|
+
)
|
98
|
+
|
99
|
+
if retval
|
100
|
+
retval.compact
|
101
|
+
end
|
102
|
+
retval
|
103
|
+
end
|
104
|
+
|
105
|
+
def iterate
|
106
|
+
@storage.values.each do |v|
|
107
|
+
yield(v)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/tools.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
module Tools
|
4
|
+
include GeomTypes
|
5
|
+
|
6
|
+
def cast_geometry_ptr(geom_ptr, auto_free = true)
|
7
|
+
if geom_ptr.null?
|
8
|
+
raise RuntimeError.new("Tried to create a Geometry from a NULL pointer!")
|
9
|
+
end
|
10
|
+
|
11
|
+
klass = case FFIGeos.GEOSGeomTypeId_r(Geos.current_handle, geom_ptr)
|
12
|
+
when GEOS_POINT
|
13
|
+
Point
|
14
|
+
when GEOS_LINESTRING
|
15
|
+
LineString
|
16
|
+
when GEOS_LINEARRING
|
17
|
+
LinearRing
|
18
|
+
when GEOS_POLYGON
|
19
|
+
Polygon
|
20
|
+
when GEOS_MULTIPOINT
|
21
|
+
MultiPoint
|
22
|
+
when GEOS_MULTILINESTRING
|
23
|
+
MultiLineString
|
24
|
+
when GEOS_MULTIPOLYGON
|
25
|
+
MultiPolygon
|
26
|
+
when GEOS_GEOMETRYCOLLECTION
|
27
|
+
GeometryCollection
|
28
|
+
else
|
29
|
+
raise RuntimeError.new("Invalid geometry type")
|
30
|
+
end
|
31
|
+
|
32
|
+
klass.new(geom_ptr, auto_free)
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_geometry(geom)
|
36
|
+
raise TypeError.new("Expected Geos::Geometry") unless geom.is_a?(Geos::Geometry)
|
37
|
+
end
|
38
|
+
|
39
|
+
def bool_result(r)
|
40
|
+
case r
|
41
|
+
when 1
|
42
|
+
true
|
43
|
+
when 0
|
44
|
+
false
|
45
|
+
else
|
46
|
+
raise RuntimeError.new("Unexpected boolean result: #{r}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_enum_value(enum, value)
|
51
|
+
raise TypeError.new("Couldn't find valid #{enum.tag} value: #{value}") unless enum[value]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/utils.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
|
2
|
+
module Geos
|
3
|
+
module Utils
|
4
|
+
class << self
|
5
|
+
include Geos::Tools
|
6
|
+
|
7
|
+
if FFIGeos.respond_to?(:GEOSOrientationIndex_r)
|
8
|
+
# Available in GEOS 3.3+.
|
9
|
+
def orientation_index(ax, ay, bx, by, px, py)
|
10
|
+
FFIGeos.GEOSOrientationIndex_r(
|
11
|
+
Geos.current_handle,
|
12
|
+
ax, ay, bx, by, px, py
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
if FFIGeos.respond_to?(:GEOSRelatePatternMatch_r)
|
18
|
+
# Available in GEOS 3.3+.
|
19
|
+
def relate_match(mat, pat)
|
20
|
+
bool_result(FFIGeos.GEOSRelatePatternMatch_r(Geos.current_handle, mat, pat))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_point(cs)
|
25
|
+
if cs.length != 1
|
26
|
+
raise RuntimeError.new("IllegalArgumentException: Point coordinate list must contain a single element")
|
27
|
+
end
|
28
|
+
|
29
|
+
cast_geometry_ptr(FFIGeos.GEOSGeom_createPoint_r(Geos.current_handle, cs.ptr)).tap {
|
30
|
+
cs.ptr.autorelease = false
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_line_string(cs)
|
35
|
+
if cs.length <= 1 && cs.length != 0
|
36
|
+
raise RuntimeError.new("IllegalArgumentException: point array must contain 0 or >1 elements")
|
37
|
+
end
|
38
|
+
|
39
|
+
cast_geometry_ptr(FFIGeos.GEOSGeom_createLineString_r(Geos.current_handle, cs.ptr)).tap {
|
40
|
+
cs.ptr.autorelease = false
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_linear_ring(cs)
|
45
|
+
if cs.length <= 1 && cs.length != 0
|
46
|
+
raise RuntimeError.new("IllegalArgumentException: point array must contain 0 or >1 elements")
|
47
|
+
end
|
48
|
+
|
49
|
+
cast_geometry_ptr(FFIGeos.GEOSGeom_createLinearRing_r(Geos.current_handle, cs.ptr)).tap {
|
50
|
+
cs.ptr.autorelease = false
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_polygon(outer, *inner)
|
55
|
+
inner = Array(inner).flatten.tap { |i|
|
56
|
+
if i.detect { |g| !g.is_a?(Geos::LinearRing) }
|
57
|
+
raise TypeError.new("Expected inner Array to contain Geometry::LinearRing objects")
|
58
|
+
end
|
59
|
+
}
|
60
|
+
|
61
|
+
ary = FFI::MemoryPointer.new(:pointer, inner.length)
|
62
|
+
ary.write_array_of_pointer(inner.map(&:ptr))
|
63
|
+
|
64
|
+
cast_geometry_ptr(FFIGeos.GEOSGeom_createPolygon_r(Geos.current_handle, outer.ptr, ary, inner.length)).tap {
|
65
|
+
outer.ptr.autorelease = false
|
66
|
+
inner.each { |i| i.ptr.autorelease = false }
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_empty_point
|
71
|
+
cast_geometry_ptr(FFIGeos.GEOSGeom_createEmptyPoint_r(Geos.current_handle))
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_empty_line_string
|
75
|
+
cast_geometry_ptr(FFIGeos.GEOSGeom_createEmptyLineString_r(Geos.current_handle))
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_empty_polygon
|
79
|
+
cast_geometry_ptr(FFIGeos.GEOSGeom_createEmptyPolygon_r(Geos.current_handle))
|
80
|
+
end
|
81
|
+
|
82
|
+
def create_empty_collection(t)
|
83
|
+
cast_geometry_ptr(FFIGeos.GEOSGeom_createEmptyCollection_r(Geos.current_handle, t))
|
84
|
+
end
|
85
|
+
|
86
|
+
def create_empty_multi_point
|
87
|
+
create_empty_collection(Geos::GeomTypes::GEOS_MULTIPOINT)
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_empty_multi_line_string
|
91
|
+
create_empty_collection(Geos::GeomTypes::GEOS_MULTILINESTRING)
|
92
|
+
end
|
93
|
+
|
94
|
+
def create_empty_multi_polygon
|
95
|
+
create_empty_collection(Geos::GeomTypes::GEOS_MULTIPOLYGON)
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_empty_geometry_collection
|
99
|
+
create_empty_collection(Geos::GeomTypes::GEOS_GEOMETRYCOLLECTION)
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_collection(t, *geoms)
|
103
|
+
klass = case t
|
104
|
+
when Geos::GeomTypes::GEOS_MULTIPOINT
|
105
|
+
Geos::Point
|
106
|
+
when Geos::GeomTypes::GEOS_MULTILINESTRING
|
107
|
+
Geos::LineString
|
108
|
+
when Geos::GeomTypes::GEOS_MULTIPOLYGON
|
109
|
+
Geos::Polygon
|
110
|
+
when Geos::GeomTypes::GEOS_GEOMETRYCOLLECTION
|
111
|
+
Geos::Geometry
|
112
|
+
end
|
113
|
+
|
114
|
+
geoms = Array(geoms).flatten.tap { |i|
|
115
|
+
if i.detect { |g| !g.is_a?(klass) }
|
116
|
+
raise TypeError.new("Expected geoms Array to contain #{klass} objects")
|
117
|
+
end
|
118
|
+
}
|
119
|
+
|
120
|
+
ary = FFI::MemoryPointer.new(:pointer, geoms.length)
|
121
|
+
ary.write_array_of_pointer(geoms.map(&:ptr))
|
122
|
+
|
123
|
+
cast_geometry_ptr(FFIGeos.GEOSGeom_createCollection_r(Geos.current_handle, t, ary, geoms.length)).tap {
|
124
|
+
geoms.each { |i| i.ptr.autorelease = false }
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def create_multi_point(*geoms)
|
129
|
+
create_collection(Geos::GeomTypes::GEOS_MULTIPOINT, *geoms)
|
130
|
+
end
|
131
|
+
|
132
|
+
def create_multi_line_string(*geoms)
|
133
|
+
create_collection(Geos::GeomTypes::GEOS_MULTILINESTRING, *geoms)
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_multi_polygon(*geoms)
|
137
|
+
create_collection(Geos::GeomTypes::GEOS_MULTIPOLYGON, *geoms)
|
138
|
+
end
|
139
|
+
|
140
|
+
def create_geometry_collection(*geoms)
|
141
|
+
create_collection(Geos::GeomTypes::GEOS_GEOMETRYCOLLECTION, *geoms)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|