ffi-geos 2.3.1 → 2.4.0

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 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