ffi-geos 2.3.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010-2021 J Smith <dark.panda@gmail.com>
1
+ Copyright (c) 2010-2022 J Smith <dark.panda@gmail.com>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
data/README.rdoc CHANGED
@@ -54,6 +54,8 @@ Ruby bindings along with the following enhancements and additions:
54
54
  GEOS library and perform other work or cancel GEOS calls outright. The
55
55
  interruption API was added in GEOS 3.4.0.
56
56
 
57
+ * Geos::GeoJSONReader and Geos::GeoJSONWriter support on GEOS 3.10+.
58
+
57
59
  == New Methods and Additions (not exhaustive)
58
60
 
59
61
  * SRIDs can be copied on many operations. GEOS doesn't usually copy SRIDs
data/ffi-geos.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.version = Geos::VERSION
8
8
 
9
9
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
10
- s.required_ruby_version = '>= 2.5'
10
+ s.required_ruby_version = '>= 2.6'
11
11
 
12
12
  s.authors = ['J Smith']
13
13
  s.description = 'An ffi wrapper for GEOS, a C++ port of the Java Topology Suite (JTS).'
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geos
4
+ class GeoJSONReader
5
+ include Geos::Tools
6
+
7
+ attr_reader :ptr
8
+
9
+ class ParseError < Geos::ParseError
10
+ end
11
+
12
+ def initialize(*args)
13
+ ptr = if args.first.is_a?(FFI::Pointer)
14
+ args.first
15
+ else
16
+ FFIGeos.GEOSGeoJSONReader_create_r(Geos.current_handle_pointer, *args)
17
+ end
18
+
19
+ @ptr = FFI::AutoPointer.new(
20
+ ptr,
21
+ self.class.method(:release)
22
+ )
23
+ end
24
+
25
+ def read(json, options = {})
26
+ cast_geometry_ptr(FFIGeos.GEOSGeoJSONReader_readGeometry_r(Geos.current_handle_pointer, ptr, json), srid: options[:srid])
27
+ rescue Geos::GEOSException => e
28
+ raise ParseError, e
29
+ end
30
+
31
+ def self.release(ptr) # :nodoc:
32
+ FFIGeos.GEOSGeoJSONReader_destroy_r(Geos.current_handle_pointer, ptr)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geos
4
+ class GeoJSONWriter
5
+ attr_accessor :indentation
6
+ attr_reader :ptr
7
+
8
+ def initialize(options = {})
9
+ options = {
10
+ indentation: -1
11
+ }.merge(options)
12
+
13
+ ptr = FFIGeos.GEOSGeoJSONWriter_create_r(Geos.current_handle_pointer)
14
+ @ptr = FFI::AutoPointer.new(
15
+ ptr,
16
+ self.class.method(:release)
17
+ )
18
+
19
+ set_options(options)
20
+ end
21
+
22
+ def self.release(ptr) # :nodoc:
23
+ FFIGeos.GEOSGeoJSONWriter_destroy_r(Geos.current_handle_pointer, ptr)
24
+ end
25
+
26
+ def set_options(options) # :nodoc:
27
+ [:indentation].each do |k|
28
+ send("#{k}=", options[k]) if respond_to?("#{k}=") && options.key?(k)
29
+ end
30
+ end
31
+ private :set_options
32
+
33
+ # Options can be set temporarily for individual writes using an options
34
+ # Hash. Options include :indentation.
35
+ def write(geom, options = nil)
36
+ unless options.nil?
37
+ old_options = {
38
+ indentation: indentation
39
+ }
40
+
41
+ set_options(options)
42
+ end
43
+
44
+ FFIGeos.GEOSGeoJSONWriter_writeGeometry_r(Geos.current_handle_pointer, ptr, geom.ptr, indentation)
45
+ ensure
46
+ set_options(old_options) unless options.nil?
47
+ end
48
+ end
49
+ end
@@ -637,6 +637,12 @@ module Geos
637
637
  end
638
638
  end
639
639
 
640
+ if FFIGeos.respond_to?(:GEOSConstrainedDelaunayTriangulation_r)
641
+ def constrained_delaunay_triangulation
642
+ cast_geometry_ptr(FFIGeos.GEOSConstrainedDelaunayTriangulation_r(Geos.current_handle_pointer, ptr))
643
+ end
644
+ end
645
+
640
646
  if FFIGeos.respond_to?(:GEOSVoronoiDiagram_r)
641
647
  # Available in GEOS 3.5.0+
642
648
  #
@@ -46,7 +46,7 @@ module Geos
46
46
 
47
47
  %w{ x y z }.each do |dimension|
48
48
  %w{ max min }.each do |op|
49
- native_method = "GEOSGeom_get#{dimension.upcase}#{op[0].upcase}#{op[1..-1]}_r"
49
+ native_method = "GEOSGeom_get#{dimension.upcase}#{op[0].upcase}#{op[1..]}_r"
50
50
 
51
51
  if FFIGeos.respond_to?(native_method)
52
52
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
@@ -146,7 +146,7 @@ module Geos
146
146
 
147
147
  %w{ max min }.each do |op|
148
148
  %w{ x y }.each do |dimension|
149
- native_method = "GEOSGeom_get#{dimension.upcase}#{op[0].upcase}#{op[1..-1]}_r"
149
+ native_method = "GEOSGeom_get#{dimension.upcase}#{op[0].upcase}#{op[1..]}_r"
150
150
 
151
151
  if FFIGeos.respond_to?(native_method)
152
152
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
@@ -78,7 +78,7 @@ module Geos
78
78
 
79
79
  %w{ max min }.each do |op|
80
80
  %w{ x y }.each do |dimension|
81
- native_method = "GEOSGeom_get#{dimension.upcase}#{op[0].upcase}#{op[1..-1]}_r"
81
+ native_method = "GEOSGeom_get#{dimension.upcase}#{op[0].upcase}#{op[1..]}_r"
82
82
 
83
83
  if FFIGeos.respond_to?(native_method)
84
84
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
@@ -77,5 +77,25 @@ module Geos
77
77
  check_geometry(geom)
78
78
  bool_result(FFIGeos.GEOSPreparedWithin_r(Geos.current_handle_pointer, ptr, geom.ptr))
79
79
  end
80
+
81
+ def distance(geom)
82
+ check_geometry(geom)
83
+ double_ptr = FFI::MemoryPointer.new(:double)
84
+ FFIGeos.GEOSPreparedDistance_r(Geos.current_handle_pointer, ptr, geom.ptr, double_ptr)
85
+ double_ptr.read_double
86
+ end
87
+
88
+ def distance_within?(geom, distance)
89
+ check_geometry(geom)
90
+ bool_result(FFIGeos.GEOSPreparedDistanceWithin_r(Geos.current_handle_pointer, ptr, geom.ptr, distance))
91
+ end
92
+
93
+ def nearest_points(geom)
94
+ check_geometry(geom)
95
+
96
+ coord_seq_ptr = FFIGeos.GEOSPreparedNearestPoints_r(Geos.current_handle_pointer, ptr, geom.ptr)
97
+
98
+ Geos::CoordinateSequence.new(coord_seq_ptr)
99
+ end
80
100
  end
81
101
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Geos
4
- VERSION = '2.3.1'
4
+ VERSION = '2.4.0'
5
5
  end
@@ -83,6 +83,18 @@ module Geos
83
83
  FFIGeos.GEOSWKBWriter_setByteOrder_r(Geos.current_handle_pointer, ptr, val)
84
84
  end
85
85
 
86
+ if FFIGeos.respond_to?(:GEOSWKBWriter_getFlavor_r)
87
+ def flavor
88
+ FFIGeos.GEOSWKBWriter_getFlavor_r(Geos.current_handle_pointer, ptr)
89
+ end
90
+ end
91
+
92
+ if FFIGeos.respond_to?(:GEOSWKBWriter_setFlavor_r)
93
+ def flavor=(val)
94
+ FFIGeos.GEOSWKBWriter_setFlavor_r(Geos.current_handle_pointer, ptr, val)
95
+ end
96
+ end
97
+
86
98
  private
87
99
 
88
100
  def set_options(options) # :nodoc:
data/lib/ffi-geos.rb CHANGED
@@ -15,6 +15,10 @@ module Geos
15
15
  File.join(GEOS_BASE, 'wkb_reader')
16
16
  autoload :WkbWriter,
17
17
  File.join(GEOS_BASE, 'wkb_writer')
18
+ autoload :GeoJSONReader,
19
+ File.join(GEOS_BASE, 'geojson_reader')
20
+ autoload :GeoJSONWriter,
21
+ File.join(GEOS_BASE, 'geojson_writer')
18
22
  autoload :CoordinateSequence,
19
23
  File.join(GEOS_BASE, 'coordinate_sequence')
20
24
  autoload :Geometry,
@@ -104,6 +108,11 @@ module Geos
104
108
  :ndr, 1 # Little Endian
105
109
  ])
106
110
 
111
+ Geos::Flavors = enum(:flavor, [
112
+ :extended, 1,
113
+ :iso, 2
114
+ ])
115
+
107
116
  Geos::BufferCapStyles = enum(:buffer_cap_style, [
108
117
  :round, 1,
109
118
  :flat, 2,
@@ -587,6 +596,11 @@ module Geos
587
596
  :pointer, :pointer, :pointer, :double, :int
588
597
  ],
589
598
 
599
+ GEOSConstrainedDelaunayTriangulation_r: [
600
+ # *geom, *handle, *geom
601
+ :pointer, :pointer, :pointer
602
+ ],
603
+
590
604
  GEOSVoronoiDiagram_r: [
591
605
  # *geom, *handle, *geom, *envelope, tolerance, only_edges
592
606
  :pointer, :pointer, :pointer, :pointer, :double, :int
@@ -727,6 +741,11 @@ module Geos
727
741
  :int, :pointer, :pointer, :pointer, :pointer
728
742
  ],
729
743
 
744
+ GEOSDistanceWithin_r: [
745
+ # (0 on exception, 1 otherwise), *handle, *geom_a, *geom_b, double distance
746
+ :char, :pointer, :pointer, :pointer, :double
747
+ ],
748
+
730
749
  GEOSHausdorffDistance_r: [
731
750
  # (0 on exception, 1 otherwise), *handle, *geom_a, *geom_b, (double *) distance
732
751
  :int, :pointer, :pointer, :pointer, :pointer
@@ -955,6 +974,21 @@ module Geos
955
974
  # (2 on exception, 1 on true, 0 on false), *handle, *prepared, *geom
956
975
  :char, :pointer, :pointer, :pointer
957
976
  ],
977
+
978
+ GEOSPreparedDistance_r: [
979
+ # (1 on success, 0 on failure), *handle, *prepared, *geom, *distance
980
+ :int, :pointer, :pointer, :pointer, :pointer
981
+ ],
982
+
983
+ GEOSPreparedDistanceWithin_r: [
984
+ # (1 on true, 0 on false), *handle, *prepared, *geom, max_distance
985
+ :char, :pointer, :pointer, :pointer, :double
986
+ ],
987
+
988
+ GEOSPreparedNearestPoints_r: [
989
+ # *coord_seq, *handle, *prepared, *geom
990
+ :pointer, :pointer, :pointer, :pointer
991
+ ],
958
992
  #### /PreparedGeometry functions ####
959
993
 
960
994
  #### WktReader functions ####
@@ -1088,8 +1122,52 @@ module Geos
1088
1122
  # void, *handle, *geom, bool
1089
1123
  :void, :pointer, :pointer, :char
1090
1124
  ],
1125
+
1126
+ GEOSWKBWriter_getFlavor_r: [
1127
+ # flavor, *handle, *geom
1128
+ :flavor, :pointer, :pointer
1129
+ ],
1130
+
1131
+ GEOSWKBWriter_setFlavor_r: [
1132
+ # void, *handle, *geom, flavor
1133
+ :void, :pointer, :pointer, :flavor
1134
+ ],
1091
1135
  #### /WkbWriter functions ####
1092
1136
 
1137
+ #### GeoJSONReader functions ####
1138
+ GEOSGeoJSONReader_create_r: [
1139
+ # *geojson_reader, *handle
1140
+ :pointer, :pointer
1141
+ ],
1142
+
1143
+ GEOSGeoJSONReader_readGeometry_r: [
1144
+ # *geom, *handle, *geojson_reader, string
1145
+ :pointer, :pointer, :pointer, :string
1146
+ ],
1147
+
1148
+ GEOSGeoJSONReader_destroy_r: [
1149
+ # void, *handle, *geojson_reader
1150
+ :void, :pointer, :pointer
1151
+ ],
1152
+ #### /GeoJSONReader functions ###
1153
+
1154
+ #### GeoJSONWriter functions ####
1155
+ GEOSGeoJSONWriter_create_r: [
1156
+ # *geojson_writer, *handle
1157
+ :pointer, :pointer
1158
+ ],
1159
+
1160
+ GEOSGeoJSONWriter_destroy_r: [
1161
+ # void, *handle, *geojson_writer
1162
+ :void, :pointer, :pointer
1163
+ ],
1164
+
1165
+ GEOSGeoJSONWriter_writeGeometry_r: [
1166
+ # string, *handle, *geojson_writer, :geom, :indent
1167
+ :string, :pointer, :pointer, :pointer, :int
1168
+ ],
1169
+ #### /GeoJSONWriter functions ####
1170
+
1093
1171
  #### Linearref functions ####
1094
1172
  GEOSProject_r: [
1095
1173
  # distance, *handle, *geom_a, *geom_b
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class GeoJSONReaderTests < Minitest::Test
6
+ include TestHelper
7
+
8
+ attr_reader :json_reader
9
+
10
+ def setup
11
+ super
12
+
13
+ skip unless ENV['FORCE_TESTS'] || Geos::FFIGeos.respond_to?(:GEOSGeoJSONReader_create_r)
14
+
15
+ @json_reader = Geos::GeoJSONReader.new
16
+
17
+ @writer.rounding_precision = 3
18
+ end
19
+
20
+ def json_read(*args, **options)
21
+ json_reader.read(*args, **options)
22
+ end
23
+
24
+ def geojson_tester(expected, json, **options)
25
+ assert_equal(expected, write(json_read(json, **options)))
26
+ end
27
+
28
+ def test_point
29
+ geojson_tester(
30
+ 'POINT (-117.000 33.000)',
31
+ '{"type":"Point","coordinates":[-117.0,33.0]}'
32
+ )
33
+ end
34
+
35
+ def test_line_string
36
+ geojson_tester(
37
+ 'LINESTRING (102.000 0.000, 103.000 1.000, 104.000 0.000, 105.000 1.000)',
38
+ '{"type":"LineString","coordinates":[[102.0,0.0],[103.0,1.0],[104.0,0.0],[105.0,1.0]]}'
39
+ )
40
+ end
41
+
42
+ def test_polygon
43
+ geojson_tester(
44
+ 'POLYGON ((30.000 10.000, 40.000 40.000, 20.000 40.000, 10.000 20.000, 30.000 10.000))',
45
+ '{"type":"Polygon","coordinates":[[[30,10],[40,40],[20,40],[10,20],[30,10]]]}'
46
+ )
47
+ end
48
+
49
+ def test_polygon_with_inner_ring
50
+ geojson_tester(
51
+ 'POLYGON ((35.000 10.000, 45.000 45.000, 15.000 40.000, 10.000 20.000, 35.000 10.000), (20.000 30.000, 35.000 35.000, 30.000 20.000, 20.000 30.000))',
52
+ '{"type":"Polygon","coordinates":[[[35,10],[45,45],[15,40],[10,20],[35,10]],[[20,30],[35,35],[30,20],[20,30]]]}'
53
+ )
54
+ end
55
+
56
+ def test_multi_point
57
+ geojson_tester(
58
+ 'MULTIPOINT (10.000 40.000, 40.000 30.000, 20.000 20.000, 30.000 10.000)',
59
+ '{"type":"MultiPoint","coordinates":[[10, 40], [40, 30], [20, 20], [30, 10]]}'
60
+ )
61
+ end
62
+
63
+ def test_multi_line_string
64
+ geojson_tester(
65
+ 'MULTILINESTRING ((10.000 10.000, 20.000 20.000, 10.000 40.000), (40.000 40.000, 30.000 30.000, 40.000 20.000, 30.000 10.000))',
66
+ '{"type":"MultiLineString","coordinates":[[[10, 10], [20, 20], [10, 40]],[[40, 40], [30, 30], [40, 20], [30, 10]]]}'
67
+ )
68
+ end
69
+
70
+ def test_multi_polygon
71
+ geojson_tester(
72
+ 'MULTIPOLYGON (((40.000 40.000, 20.000 45.000, 45.000 30.000, 40.000 40.000)), ((20.000 35.000, 10.000 30.000, 10.000 10.000, 30.000 5.000, 45.000 20.000, 20.000 35.000), (30.000 20.000, 20.000 15.000, 20.000 25.000, 30.000 20.000)))',
73
+ '{"type": "MultiPolygon", "coordinates": [[[[40, 40], [20, 45], [45, 30], [40, 40]]], [[[20, 35], [10, 30], [10, 10], [30, 5], [45, 20], [20, 35]], [[30, 20], [20, 15], [20, 25], [30, 20]]]]}'
74
+ )
75
+ end
76
+
77
+ def test_geometry_collection
78
+ geojson_tester(
79
+ 'GEOMETRYCOLLECTION (POINT (40.000 10.000), LINESTRING (10.000 10.000, 20.000 20.000, 10.000 40.000), POLYGON ((40.000 40.000, 20.000 45.000, 45.000 30.000, 40.000 40.000)))',
80
+ '{"type": "GeometryCollection","geometries": [{"type": "Point","coordinates": [40, 10]},{"type": "LineString","coordinates": [[10, 10], [20, 20], [10, 40]]},{"type": "Polygon","coordinates": [[[40, 40], [20, 45], [45, 30], [40, 40]]]}]}'
81
+ )
82
+ end
83
+
84
+ def test_feature_collection
85
+ geojson_tester(
86
+ 'GEOMETRYCOLLECTION (POINT (-117.000 33.000), POINT (-122.000 45.000))',
87
+ '{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[-117.0,33.0]}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.0,45.0]}}]}'
88
+ )
89
+ end
90
+
91
+ def test_empty_point
92
+ geojson_tester(
93
+ 'POINT EMPTY',
94
+ '{"type":"Point","coordinates":[]}'
95
+ )
96
+ end
97
+
98
+ def test_empty_line_string
99
+ geojson_tester(
100
+ 'LINESTRING EMPTY',
101
+ '{"type":"LineString","coordinates":[]}'
102
+ )
103
+ end
104
+
105
+ def test_empty_polygon
106
+ geojson_tester(
107
+ 'POLYGON EMPTY',
108
+ '{"type":"Polygon","coordinates":[]}'
109
+ )
110
+ end
111
+
112
+ def test_empty_multi_point
113
+ geojson_tester(
114
+ 'MULTIPOINT EMPTY',
115
+ '{"type":"MultiPoint","coordinates":[]}'
116
+ )
117
+ end
118
+
119
+ def test_empty_multi_line_string
120
+ geojson_tester(
121
+ 'MULTILINESTRING EMPTY',
122
+ '{"type":"MultiLineString","coordinates":[]}'
123
+ )
124
+ end
125
+
126
+ def test_empty_multi_polygon
127
+ geojson_tester(
128
+ 'MULTIPOLYGON EMPTY',
129
+ '{"type": "MultiPolygon", "coordinates": []}'
130
+ )
131
+ end
132
+
133
+ def test_empty_geometry_collection
134
+ geojson_tester(
135
+ 'GEOMETRYCOLLECTION EMPTY',
136
+ '{"type": "GeometryCollection","geometries": []}'
137
+ )
138
+ end
139
+
140
+ def test_incomplete_geojson
141
+ assert_raises(Geos::GeoJSONReader::ParseError) do
142
+ json_reader.read('{"type":"Point","coordinates":[-117.0]}')
143
+ end
144
+
145
+ assert_raises(Geos::GeoJSONReader::ParseError) do
146
+ json_reader.read('{"type":"LineString","coordinates":[[1,2],[2]]}')
147
+ end
148
+ end
149
+
150
+ def test_broken_geojson
151
+ assert_raises(Geos::GeoJSONReader::ParseError) do
152
+ json_reader.read('<gml>NOT_GEO_JSON</gml>')
153
+ end
154
+ end
155
+
156
+ def test_incompatible_type
157
+ assert_raises(Geos::GeoJSONReader::ParseError) do
158
+ json_reader.read('{"type":"Line","coordinates":[[1,2],[2,3]]}')
159
+ end
160
+ end
161
+
162
+ def test_srid_from_options
163
+ geom = json_reader.read(
164
+ '{"type":"Point","coordinates":[-117.0,33.0]}',
165
+ srid: 3857
166
+ )
167
+
168
+ assert_equal(geom.srid, 3857)
169
+ end
170
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class GeoJSONWriterTests < Minitest::Test
6
+ include TestHelper
7
+
8
+ attr_reader :json_writer
9
+
10
+ def setup
11
+ super
12
+
13
+ skip unless ENV['FORCE_TESTS'] || Geos::FFIGeos.respond_to?(:GEOSGeoJSONWriter_create_r)
14
+
15
+ @json_writer = Geos::GeoJSONWriter.new
16
+ end
17
+
18
+ def json_write(*args, **options)
19
+ json_writer.write(*args, **options)
20
+ end
21
+
22
+ def geojson_tester(expected, geom, **options)
23
+ assert_equal(expected, json_write(read(geom), **options))
24
+ end
25
+
26
+ def test_point
27
+ geojson_tester('{"type":"Point","coordinates":[-117.0,33.0]}', 'POINT(-117 33)')
28
+ end
29
+
30
+ def test_line_string
31
+ geojson_tester('{"type":"LineString","coordinates":[[102.0,0.0],[103.0,1.0],[104.0,0.0],[105.0,1.0]]}', 'LINESTRING(102.0 0.0, 103.0 1.0, 104.0 0.0, 105.0 1.0)')
32
+ end
33
+
34
+ def test_polygon
35
+ geojson_tester('{"type":"Polygon","coordinates":[[[30.0,10.0],[40.0,40.0],[20.0,40.0],[10.0,20.0],[30.0,10.0]]]}', 'POLYGON((30 10, 40 40, 20 40, 10 20, 30 10))')
36
+ end
37
+
38
+ def test_polygon_with_inner_ring
39
+ geojson_tester('{"type":"Polygon","coordinates":[[[35.0,10.0],[45.0,45.0],[15.0,40.0],[10.0,20.0],[35.0,10.0]],[[20.0,30.0],[35.0,35.0],[30.0,20.0],[20.0,30.0]]]}', 'POLYGON((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))')
40
+ end
41
+
42
+ def test_multi_point
43
+ geojson_tester('{"type":"MultiPoint","coordinates":[[10.0,40.0],[40.0,30.0],[20.0,20.0],[30.0,10.0]]}', 'MULTIPOINT ((10 40), (40 30), (20 20), (30 10))')
44
+ end
45
+
46
+ def test_multi_line_string
47
+ geojson_tester('{"type":"MultiLineString","coordinates":[[[10.0,10.0],[20.0,20.0],[10.0,40.0]],[[40.0,40.0],[30.0,30.0],[40.0,20.0],[30.0,10.0]]]}', 'MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))')
48
+ end
49
+
50
+ def test_multi_polygon
51
+ geojson_tester('{"type":"MultiPolygon","coordinates":[[[[30.0,20.0],[45.0,40.0],[10.0,40.0],[30.0,20.0]]],[[[15.0,5.0],[40.0,10.0],[10.0,20.0],[5.0,10.0],[15.0,5.0]]]]}', 'MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))')
52
+ end
53
+
54
+ def test_geometry_collection
55
+ geojson_tester('{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[1.0,1.0]},{"type":"Point","coordinates":[2.0,2.0]}]}', 'GEOMETRYCOLLECTION(POINT(1 1),POINT(2 2))')
56
+ end
57
+
58
+ def test_write_with_indentation
59
+ geojson_tester(<<~JSON.strip, 'LINESTRING(102.0 0.0, 103.0 1.0, 104.0 0.0, 105.0 1.0)', indentation: 2)
60
+ {
61
+ "type": "LineString",
62
+ "coordinates": [
63
+ [
64
+ 102.0,
65
+ 0.0
66
+ ],
67
+ [
68
+ 103.0,
69
+ 1.0
70
+ ],
71
+ [
72
+ 104.0,
73
+ 0.0
74
+ ],
75
+ [
76
+ 105.0,
77
+ 1.0
78
+ ]
79
+ ]
80
+ }
81
+ JSON
82
+ end
83
+
84
+ def test_empty_point
85
+ geojson_tester('{"type":"Point","coordinates":[]}', 'POINT EMPTY')
86
+ end
87
+
88
+ def test_empty_line_string
89
+ geojson_tester('{"type":"LineString","coordinates":[]}', 'LINESTRING EMPTY')
90
+ end
91
+
92
+ def test_empty_polygon
93
+ geojson_tester('{"type":"Polygon","coordinates":[[]]}', 'POLYGON EMPTY')
94
+ end
95
+
96
+ def test_empty_geometry_collection
97
+ geojson_tester('{"type":"GeometryCollection","geometries":[]}', 'GEOMETRYCOLLECTION EMPTY')
98
+ end
99
+
100
+ def test_linear_ring
101
+ geojson_tester('{"type":"LineString","coordinates":[[0.0,0.0],[1.0,1.0],[1.0,0.0],[0.0,0.0]]}', 'LINEARRING (0 0, 1 1, 1 0, 0 0)')
102
+ end
103
+ end
@@ -1845,6 +1845,24 @@ class GeometryTests < Minitest::Test
1845
1845
  tester['MULTILINESTRING ((10 0, 10 10), (0 0, 10 10), (0 0, 10 0))', 'MULTIPOINT(0 0, 10 0, 10 10, 11 10)', tolerance: 2.0, only_edges: true]
1846
1846
  end
1847
1847
 
1848
+ def test_constrained_delaunay_triangulation
1849
+ skip unless ENV['FORCE_TESTS'] || Geos::Geometry.method_defined?(:constrained_delaunay_triangulation)
1850
+
1851
+ tester = lambda { |expected, geom|
1852
+ geom = read(geom)
1853
+ geom_tri = geom.constrained_delaunay_triangulation
1854
+ geom_tri.normalize!
1855
+
1856
+ assert_equal(write(read(expected).normalize), write(geom_tri))
1857
+ }
1858
+
1859
+ writer.trim = true
1860
+
1861
+ tester['GEOMETRYCOLLECTION EMPTY', 'POLYGON EMPTY']
1862
+ tester['GEOMETRYCOLLECTION EMPTY', 'POINT(0 0)']
1863
+ tester['GEOMETRYCOLLECTION (POLYGON ((10 10, 20 40, 90 10, 10 10)), POLYGON ((90 90, 20 40, 90 10, 90 90)))', 'POLYGON ((10 10, 20 40, 90 90, 90 10, 10 10))']
1864
+ end
1865
+
1848
1866
  def test_voronoi_diagram
1849
1867
  skip unless ENV['FORCE_TESTS'] || Geos::Geometry.method_defined?(:voronoi_diagram)
1850
1868
 
@@ -70,6 +70,12 @@ class PreparedGeometryTests < Minitest::Test
70
70
  relationship_tester(:contains?, true, false, false, false, false, true, false, false)
71
71
  end
72
72
 
73
+ def test_contains_properly
74
+ skip unless ENV['FORCE_TESTS'] || defined?(Geos::PreparedGeometry)
75
+
76
+ relationship_tester(:contains_properly?, true, false, false, false, false, false, false, false)
77
+ end
78
+
73
79
  def test_overlaps
74
80
  skip unless ENV['FORCE_TESTS'] || defined?(Geos::PreparedGeometry)
75
81
 
@@ -111,4 +117,28 @@ class PreparedGeometryTests < Minitest::Test
111
117
  Geos::PreparedGeometry.new('hello world')
112
118
  end
113
119
  end
120
+
121
+ def test_distance
122
+ skip unless ENV['FORCE_TESTS'] || (defined?(Geos::PreparedGeometry) && Geos::FFIGeos.respond_to?(:GEOSPreparedDistance_r))
123
+
124
+ assert_equal(5.0, read(POINT_A).to_prepared.distance(read(POINT_B)))
125
+ end
126
+
127
+ def test_distance_within
128
+ skip unless ENV['FORCE_TESTS'] || (defined?(Geos::PreparedGeometry) && Geos::FFIGeos.respond_to?(:GEOSPreparedDistanceWithin_r))
129
+
130
+ assert(read(POINT_A).to_prepared.distance_within?(read(POINT_B), 30.0))
131
+ refute(read(POINT_A).to_prepared.distance_within?(read(POINT_B), 3.0))
132
+ end
133
+
134
+ def test_nearest_points
135
+ skip unless ENV['FORCE_TESTS'] || (defined?(Geos::PreparedGeometry) && Geos::FFIGeos.respond_to?(:GEOSPreparedNearestPoints_r))
136
+
137
+ coord_seq = read('POLYGON((1 1, 1 5, 5 5, 5 1, 1 1))').to_prepared.nearest_points(read('POLYGON((8 8, 9 9, 9 10, 8 8))'))
138
+
139
+ assert_equal(5.0, coord_seq.x[0])
140
+ assert_equal(5.0, coord_seq.y[0])
141
+ assert_equal(8.0, coord_seq.x[1])
142
+ assert_equal(8.0, coord_seq.y[1])
143
+ end
114
144
  end