From a33a1b373197db33d6722c46e9bb7c44294ea283 Mon Sep 17 00:00:00 2001 From: J Smith Date: Mon, 29 Jul 2024 10:57:40 -0300 Subject: [PATCH] Add `Geometry#orient_polygons` and `#orient_polygons!`. --- lib/ffi-geos.rb | 5 ++ lib/ffi-geos/geometry.rb | 12 +++ test/geometry/orient_polygons_tests.rb | 101 +++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 test/geometry/orient_polygons_tests.rb diff --git a/lib/ffi-geos.rb b/lib/ffi-geos.rb index a69aa94..f7fadc2 100644 --- a/lib/ffi-geos.rb +++ b/lib/ffi-geos.rb @@ -821,6 +821,11 @@ def self.geos_library_paths :int, :pointer, :pointer ], + GEOSOrientPolygons_r: [ + # -1 on exception, *handle, *geom, exterior_cw + :int, :pointer, :pointer, :int + ], + GEOSGetInteriorRingN_r: [ # *geom, *handle, *geom, n :pointer, :pointer, :pointer, :int diff --git a/lib/ffi-geos/geometry.rb b/lib/ffi-geos/geometry.rb index c68d5b2..177105b 100644 --- a/lib/ffi-geos/geometry.rb +++ b/lib/ffi-geos/geometry.rb @@ -59,6 +59,18 @@ def normalize! end alias normalize normalize! + if FFIGeos.respond_to?(:GEOSOrientPolygons_r) + def orient_polygons!(exterior_cw = false) + raise Geos::GEOSException, self.class if FFIGeos.GEOSOrientPolygons_r(Geos.current_handle_pointer, ptr, bool_to_int(exterior_cw)) == -1 + + self + end + + def orient_polygons(exterior_cw = false) + dup.orient_polygons!(exterior_cw) + end + end + def srid FFIGeos.GEOSGetSRID_r(Geos.current_handle_pointer, ptr) end diff --git a/test/geometry/orient_polygons_tests.rb b/test/geometry/orient_polygons_tests.rb new file mode 100644 index 0000000..9a425da --- /dev/null +++ b/test/geometry/orient_polygons_tests.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'test_helper' + +describe '#orient_polygons' do + include TestHelper + + def setup + super + writer.trim = true + end + + it 'does not overwrite the original geometry' do + skip unless ENV['FORCE_TESTS'] || Geos::Geometry.method_defined?(:orient_polygons) + + geom = read('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))') + + result = geom.orient_polygons(true) + + assert_equal('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))', write(geom)) + assert_equal('POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))', write(result)) + refute_same(geom, result) + end + + it 'does overwrite the original geometry with bang method' do + skip unless ENV['FORCE_TESTS'] || Geos::Geometry.method_defined?(:orient_polygons!) + + geom = read('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))') + + result = geom.orient_polygons!(true) + + assert_equal('POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))', write(geom)) + assert_equal('POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))', write(result)) + assert_same(geom, result) + end + + it 'handles empty polygons' do + skip unless ENV['FORCE_TESTS'] || Geos::Geometry.method_defined?(:orient_polygons) + + simple_tester( + :orient_polygons, + 'POLYGON EMPTY', + 'POLYGON EMPTY' + ) + end + + it 'hole orientation is opposite to shell' do + skip unless ENV['FORCE_TESTS'] || Geos::Geometry.method_defined?(:orient_polygons) + + simple_tester( + :orient_polygons, + 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))', + 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))' + ) + + simple_tester( + :orient_polygons, + 'POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))', + 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))', + true + ) + end + + it 'ensures all polygons in collection are processed' do + skip unless ENV['FORCE_TESTS'] || Geos::Geometry.method_defined?(:orient_polygons) + + simple_tester( + :orient_polygons, + 'MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1)), ((100 100, 200 100, 200 200, 100 100)))', + 'MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1)), ((100 100, 200 100, 200 200, 100 100)))' + ) + + simple_tester( + :orient_polygons, + 'MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1)), ((100 100, 200 200, 200 100, 100 100)))', + 'MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1)), ((100 100, 200 100, 200 200, 100 100)))', + true + ) + end + + it 'polygons in collection are oriented, closed linestring unchanged' do + skip unless ENV['FORCE_TESTS'] || Geos::Geometry.method_defined?(:orient_polygons) + + simple_tester( + :orient_polygons, + 'GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1)), LINESTRING (100 100, 200 100, 200 200, 100 100))', + 'GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1)), LINESTRING (100 100, 200 100, 200 200, 100 100))', + true + ) + end + + it 'nested collection handled correctly' do + skip unless ENV['FORCE_TESTS'] || Geos::Geometry.method_defined?(:orient_polygons) + + simple_tester( + :orient_polygons, + 'GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)))))', + 'GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)))))' + ) + end +end