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