Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add simplify_polygon_hull method #366

Merged
merged 3 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### Unreleased

**Minor Changes**

* Add `simplify_polygon_hull` method to the CAPI factory (@oleksii-leonov) [#366](https://github.com/rgeo/rgeo/pull/366)

### 3.0.1 / 2023-11-15

**Minor Changes**
Expand Down
1 change: 1 addition & 0 deletions ext/geos_c_impl/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def create_dummy_makefile
have_func("GEOSUnaryUnion_r", "geos_c.h")
have_func("GEOSCoordSeq_isCCW_r", "geos_c.h")
have_func("GEOSDensify", "geos_c.h")
have_func("GEOSPolygonHullSimplify", "geos_c.h")
have_func("rb_memhash", "ruby.h")
have_func("rb_gc_mark_movable", "ruby.h")
end
Expand Down
35 changes: 35 additions & 0 deletions ext/geos_c_impl/polygon.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,34 @@ method_polygon_interior_rings(VALUE self)
return result;
}

#ifdef RGEO_GEOS_SUPPORTS_POLYGON_HULL_SIMPLIFY
static VALUE
method_polygon_simplify_polygon_hull(VALUE self,
VALUE vertex_fraction,
VALUE is_outer)
{
VALUE result;
RGeo_GeometryData* self_data;
const GEOSGeometry* self_geom;
VALUE factory;

unsigned int is_outer_uint = RTEST(is_outer) ? 1 : 0;

result = Qnil;
self_data = RGEO_GEOMETRY_DATA_PTR(self);
self_geom = self_data->geom;
if (self_geom) {
factory = self_data->factory;
result = rgeo_wrap_geos_geometry(
factory,
GEOSPolygonHullSimplify(
self_geom, is_outer_uint, rb_num2dbl(vertex_fraction)),
Qnil);
}
return result;
}
#endif

static VALUE
cmethod_create(VALUE module,
VALUE factory,
Expand Down Expand Up @@ -335,6 +363,13 @@ rgeo_init_geos_polygon()
geos_polygon_methods, "interior_rings", method_polygon_interior_rings, 0);
rb_define_method(
geos_polygon_methods, "coordinates", method_polygon_coordinates, 0);

#ifdef RGEO_GEOS_SUPPORTS_POLYGON_HULL_SIMPLIFY
rb_define_method(geos_polygon_methods,
"simplify_polygon_hull",
method_polygon_simplify_polygon_hull,
2);
#endif
}

st_index_t
Expand Down
3 changes: 3 additions & 0 deletions ext/geos_c_impl/preface.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
#ifdef HAVE_GEOSDENSIFY
#define RGEO_GEOS_SUPPORTS_DENSIFY
#endif
#ifdef HAVE_GEOSPOLYGONHULLSIMPLIFY
#define RGEO_GEOS_SUPPORTS_POLYGON_HULL_SIMPLIFY
#endif
#ifdef HAVE_RB_GC_MARK_MOVABLE
#define mark rb_gc_mark_movable
#else
Expand Down
154 changes: 154 additions & 0 deletions test/geos_capi/polygon_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,160 @@ def test_simplify_preserve_topology
end
end

def test_simplify_polygon_hull
skip_geos_version_less_then("3.11")

# Input polygon (8 vertices):
# +-----+
# | |
# +---+ |
# | |
# +---+ |
# | |
# +-----+
input_polygon = @factory.parse_wkt("POLYGON ((0 0, 0 2, 4 2, 4 4, 0 4, 0 6, 6 6, 6 0, 0 0))")

# Exected polygon with `is_outer` true and `vertex_fraction` 0.0 (minimum possible to cover the polygon):
# +-----+
# | |
# | |
# | |
# | |
# | |
# +-----+
expected_polygon_outer_true_vert0 = @factory.parse_wkt("POLYGON ((0 0, 0 6, 6 6, 6 0, 0 0))")

# Exected polygon with `is_outer` true and `vertex_fraction` 0.500001 (4 vertices):
# +-----+
# | |
# | |
# | |
# | |
# | |
# +-----+
expected_polygon_outer_true_vert0500001 = @factory.parse_wkt("POLYGON ((0 0, 0 6, 6 6, 6 0, 0 0))")

# Exected polygon with `is_outer` true and `vertex_fraction` 0.750001 (6 vertices):
# +-----+
# | |
# + |
# | |
# + |
# | |
# +-----+
expected_polygon_outer_true_vert0750001 = @factory.parse_wkt("POLYGON ((0 0, 0 2, 0 4, 0 6, 6 6, 6 0, 0 0))")

# Exected polygon with `is_outer` true and `vertex_fraction` 1.0 (all vertices):
# +-----+
# | |
# +---+ |
# | |
# +---+ |
# | |
# +-----+
expected_polygon_outer_true_vert1 = input_polygon

# Exected polygon with `is_outer` false and `vertex_fraction` 0 (minimum possible, triangle):
# Version 1:
# +-----+
# \ /
# +
#
#
#
#
# Version 2:
# +
# /|
# + |
# | |
# \|
# ||
# +
# NOTE: We could receve 2 different results here, depending on the GEOS version and OS.
# Both are valid results, so we check for any.
expected_polygons_outer_false_vert0 = [
@factory.parse_wkt("POLYGON ((6 6, 0 6, 4 4, 6 6))"),
@factory.parse_wkt("POLYGON ((6 0, 6 6, 4 2, 6 0))")
]

# Exected polygon with `is_outer` false and `vertex_fraction` 0.5 (3 vertices):
# NOTE: `vertex_fraction` 0.5 shoud give us 4 vertices (8 * 0.5). But we have only 3 vertices in the result.
# To get 4 vertices in the result we need to use `vertex_fraction` 0.500001.
# Documenting this behavior of GEOSPolygonHullSimplify as is.
expected_polygons_outer_false_vert05 = expected_polygons_outer_false_vert0

# Exected polygon with `is_outer` false and `vertex_fraction` 0.500001 (4 vertices):
# Version 1:
# +-----+
# \ |
# + |
# | |
# \|
# ||
# +
# Version 2:
# +
# ||
# /|
# | |
# + |
# / |
# +-----+
# NOTE: We could receve 2 different results here, depending on the GEOS version and OS.
# Both are valid results, so we check for any.
expected_polygons_outer_false_vert0500001 = [
@factory.parse_wkt("POLYGON ((6 0, 6 6, 0 6, 4 4, 6 0))"),
@factory.parse_wkt("POLYGON ((0 0, 6 0, 6 6, 4 2, 0 0))")
]

# Exected polygon with `is_outer` false and `vertex_fraction` 1.0 (all vertices):
# +-----+
# | |
# +---+ |
# | |
# +---+ |
# | |
# +-----+
expected_polygon_outer_false_vert1 = input_polygon

# With `is_outer` true:
assert_equal(
expected_polygon_outer_true_vert0,
input_polygon.simplify_polygon_hull(0.0, true)
)
assert_equal(
expected_polygon_outer_true_vert0500001,
input_polygon.simplify_polygon_hull(0.500001, true)
)
assert_equal(
expected_polygon_outer_true_vert0750001,
input_polygon.simplify_polygon_hull(0.750001, true)
)
assert_equal(
expected_polygon_outer_true_vert1,
input_polygon.simplify_polygon_hull(1.0, true)
)

# With `is_outer` false:
assert_includes(
expected_polygons_outer_false_vert0,
input_polygon.simplify_polygon_hull(0.0, false)
)
assert_includes(
expected_polygons_outer_false_vert05,
input_polygon.simplify_polygon_hull(0.5, false)
)
assert_includes(
expected_polygons_outer_false_vert0500001,
input_polygon.simplify_polygon_hull(0.500001, false)
)
assert_equal(
expected_polygon_outer_false_vert1,
input_polygon.simplify_polygon_hull(1.0, false)
)
end

def test_buffer_with_style
polygon_coordinates = [[0.514589803375032, 4.299999999999999],
[6.0, 4.3],
Expand Down