Skip to content

Commit

Permalink
feat(geos): Add Geometry#voronoi_diagram
Browse files Browse the repository at this point in the history
  • Loading branch information
BuonOmo committed Oct 11, 2022
1 parent 0facc96 commit 1fc06b1
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 22 deletions.
8 changes: 4 additions & 4 deletions ext/geos_c_impl/factory.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ destroy_factory_func(void* data)
if (factory_data->marshal_wkb_writer) {
GEOSWKBWriter_destroy(factory_data->marshal_wkb_writer);
}
FREE(factory_data);
RB_FREE(factory_data);
}

// Destroy function for geometry data. We destroy the internal
Expand All @@ -79,7 +79,7 @@ destroy_geometry_func(void* data)
prep != (const GEOSPreparedGeometry*)3) {
GEOSPreparedGeom_destroy(prep);
}
FREE(geometry_data);
RB_FREE(geometry_data);
}

// Mark function for factory data. This marks the wkt and wkb generator
Expand Down Expand Up @@ -476,7 +476,7 @@ cmethod_factory_create(VALUE klass,
RGeo_FactoryData* data;

result = Qnil;
data = ALLOC(RGeo_FactoryData);
data = RB_ALLOC(RGeo_FactoryData);
if (data) {
data->flags = RB_NUM2INT(flags);
data->srid = RB_NUM2INT(srid);
Expand Down Expand Up @@ -782,7 +782,7 @@ rgeo_wrap_geos_geometry(VALUE factory, GEOSGeometry* geom, VALUE klass)
}
klass = inferred_klass;
}
data = ALLOC(RGeo_GeometryData);
data = RB_ALLOC(RGeo_GeometryData);
if (data) {
if (geom) {
GEOSSetSRID(geom, factory_data->srid);
Expand Down
41 changes: 41 additions & 0 deletions ext/geos_c_impl/geometry.c
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,43 @@ method_geometry_polygonize(VALUE self)
return result;
}

VALUE
primary_method_geometry_voronoi_diagram(VALUE _,
VALUE self,
VALUE env,
VALUE tolerance,
VALUE only_edges)
{
VALUE result;
const RGeo_GeometryData* self_data;
const GEOSGeometry* self_geom;
GEOSGeometry* diagram;
const GEOSGeometry* env_geom = NULL;

Check_Type(tolerance, T_FLOAT);

result = Qnil;
self_data = RGEO_GEOMETRY_DATA_PTR(self);
self_geom = self_data->geom;

if (self_geom) {
if (RB_TEST(env)) {
Check_TypedStruct(env, &rgeo_geometry_type);
env_geom = rgeo_convert_to_geos_geometry(self_data->factory, env, Qnil);
}
diagram = GEOSVoronoiDiagram(
self_geom, env_geom, rb_num2dbl(tolerance), RB_TEST(only_edges));

if (diagram == NULL) {
rb_raise(rb_eGeosError, "GEOS cannot create a voronoi diagram");
}

result = rgeo_wrap_geos_geometry(self_data->factory, diagram, Qnil);
}

return result;
}

VALUE
rgeo_geos_geometries_strict_eql(const GEOSGeometry* geom1,
const GEOSGeometry* geom2)
Expand Down Expand Up @@ -1277,6 +1314,10 @@ rgeo_init_geos_geometry()
geos_geometry_methods, "make_valid", method_geometry_make_valid, 0);
rb_define_method(
geos_geometry_methods, "polygonize", method_geometry_polygonize, 0);
rb_define_singleton_method(rgeo_geos_primary_module,
"voronoi_diagram",
primary_method_geometry_voronoi_diagram,
4);
}

RGEO_END_C
Expand Down
6 changes: 2 additions & 4 deletions ext/geos_c_impl/geometry_collection.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ create_geometry_collection(VALUE module, int type, VALUE factory, VALUE array)
VALUE result;
unsigned int len;
GEOSGeometry** geoms;
RGeo_FactoryData* factory_data;
VALUE klass;
unsigned int i;
unsigned int j;
Expand All @@ -44,12 +43,11 @@ create_geometry_collection(VALUE module, int type, VALUE factory, VALUE array)
result = Qnil;
Check_Type(array, T_ARRAY);
len = (unsigned int)RARRAY_LEN(array);
geoms = ALLOC_N(GEOSGeometry*, len == 0 ? 1 : len);
geoms = RB_ALLOC_N(GEOSGeometry*, len == 0 ? 1 : len);
if (!geoms) {
rb_raise(rb_eRGeoError, "not enough memory available");
}

factory_data = RGEO_FACTORY_DATA_PTR(factory);
klasses = Qnil;
cast_type = Qnil;
switch (type) {
Expand Down Expand Up @@ -95,7 +93,7 @@ create_geometry_collection(VALUE module, int type, VALUE factory, VALUE array)
// are not doing that ourselves. If that turns out not to be the
// case, this will be a memory leak.
}
FREE(geoms);
RB_FREE(geoms);
if (state) {
rb_exc_raise(rb_errinfo()); // raise $!
}
Expand Down
5 changes: 5 additions & 0 deletions ext/geos_c_impl/globals.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ VALUE rgeo_geos_multi_point_class;
VALUE rgeo_geos_multi_line_string_class;
VALUE rgeo_geos_multi_polygon_class;

VALUE rgeo_geos_primary_module;

// The notice handler is very rarely used by GEOS, only in
// GEOSIsValid_r (check for NOTICE_MESSAGE in GEOS codebase).
// We still set it to make sure we do not miss any implementation
Expand Down Expand Up @@ -162,6 +164,9 @@ rgeo_init_geos_globals()
rgeo_geos_multi_polygon_class =
rb_define_class_under(rgeo_geos_module, "CAPIMultiPolygonImpl", rb_cObject);
rb_gc_register_mark_object(rgeo_geos_multi_polygon_class);
rgeo_geos_primary_module =
rb_define_module_under(rgeo_geos_module, "Primary");
rb_gc_register_mark_object(rgeo_geos_primary_module);
}

RGEO_END_C
Expand Down
31 changes: 31 additions & 0 deletions ext/geos_c_impl/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,37 @@ extern VALUE rgeo_geos_multi_point_class;
extern VALUE rgeo_geos_multi_line_string_class;
extern VALUE rgeo_geos_multi_polygon_class;

/*
The `RGeo::Geos::Primary` namespace is used to ease argument parsing between
the ruby interface and the ruby C API. This lets us avoid the usage of
`rb_scan_args`, and give a more user friendly method source. Hence also less
burden of maintaining a correct documentation.
Example usage:
```
// ./geometry.c
VALUE
primary_method_geometry_simplify(VALUE _primary, VALUE self, VALUE tolerance)
{
...
}
void Init_geometry()
{
rb_define_singleton_method(rgeo_geos_primary_module,
"simplify",
primary_method_geometry_simplify,
2);
}
# ./geometry.rb
def simplify(tolerance: 0.2)
Primary.simplify(self, tolerance)
end
*/
extern VALUE rgeo_geos_primary_module;

void
rgeo_init_geos_globals();

Expand Down
6 changes: 3 additions & 3 deletions ext/geos_c_impl/line_string.c
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ coord_seq_from_array(VALUE factory, VALUE array, char close)
has_z = (char)(RGEO_FACTORY_DATA_PTR(factory)->flags &
RGEO_FACTORYFLAGS_SUPPORTS_Z_OR_M);
dims = has_z ? 3 : 2;
coords = ALLOC_N(double, len == 0 ? 1 : len * dims);
coords = RB_ALLOC_N(double, len == 0 ? 1 : len * dims);
if (!coords) {
return NULL;
}
Expand Down Expand Up @@ -440,7 +440,7 @@ coord_seq_from_array(VALUE factory, VALUE array, char close)
}
}
if (!good) {
FREE(coords);
RB_FREE(coords);
return NULL;
}
}
Expand All @@ -465,7 +465,7 @@ coord_seq_from_array(VALUE factory, VALUE array, char close)
GEOSCoordSeq_setZ(coord_seq, len, has_z ? coords[2] : 0);
}
}
FREE(coords);
RB_FREE(coords);
return coord_seq;
}

Expand Down
6 changes: 3 additions & 3 deletions ext/geos_c_impl/polygon.c
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ cmethod_create(VALUE module,
}

len = (unsigned int)RARRAY_LEN(interior_array);
interior_geoms = ALLOC_N(GEOSGeometry*, len == 0 ? 1 : len);
interior_geoms = RB_ALLOC_N(GEOSGeometry*, len == 0 ? 1 : len);
if (interior_geoms) {
actual_len = 0;
for (i = 0; i < len; ++i) {
Expand All @@ -282,7 +282,7 @@ cmethod_create(VALUE module,
polygon =
GEOSGeom_createPolygon(exterior_geom, interior_geoms, actual_len);
if (polygon) {
FREE(interior_geoms);
RB_FREE(interior_geoms);
// NOTE: we can return safely here, state cannot be other than 0.
return rgeo_wrap_geos_geometry(
factory, polygon, rgeo_geos_polygon_class);
Expand All @@ -291,7 +291,7 @@ cmethod_create(VALUE module,
for (i = 0; i < actual_len; ++i) {
GEOSGeom_destroy(interior_geoms[i]);
}
FREE(interior_geoms);
RB_FREE(interior_geoms);
}
GEOSGeom_destroy(exterior_geom);
if (state) {
Expand Down
7 changes: 4 additions & 3 deletions ext/geos_c_impl/preface.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
// https://ozlabs.org/~rusty/index.cgi/tech/2008-04-01.html
#define streq(a, b) (!strcmp((a), (b)))

// When using ruby ALLOC* macros, we are using ruby_xmalloc, which counterpart
// is ruby_xfree. This macro helps enforcing that by showing us the way.
#define FREE ruby_xfree
// When using ruby RB_ALLOC* macros, we are using ruby_xmalloc, which
// counterpart is ruby_xfree. This macro helps enforcing that by showing us the
// way.
#define RB_FREE ruby_xfree
2 changes: 1 addition & 1 deletion ext/geos_c_impl/ruby_more.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ rb_protect_funcall(VALUE recv, ID mid, int* state, int n, ...)
if (n > 0) {
long i;
va_start(ar, n);
argv = ALLOCA_N(VALUE, n);
argv = RB_ALLOC_N(VALUE, n);
for (i = 0; i < n; i++) {
argv[i] = va_arg(ar, VALUE);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/rgeo/cartesian/bounding_box.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ def initialize(factory, opts = {})
if (values = opts[:raw])
@has_z, @has_m, @min_x, @max_x, @min_y, @max_y, @min_z, @max_z, @min_m, @max_m = values
else
@has_z = !opts[:ignore_z] && factory.property(:has_z_coordinate) ? true : false
@has_m = !opts[:ignore_m] && factory.property(:has_m_coordinate) ? true : false
@has_z = !opts[:ignore_z] && factory.property(:has_z_coordinate)
@has_m = !opts[:ignore_m] && factory.property(:has_m_coordinate)
@min_x = @max_x = @min_y = @max_y = @min_z = @max_z = @min_m = @max_m = nil
end
end
Expand Down
33 changes: 33 additions & 0 deletions lib/rgeo/geos/capi_feature_classes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,39 @@ def as_text
str
end
alias to_s as_text

# TODO: test it (see shapely tests/test_voronoi_diagram.py)
# TODO: add it in geos-ffi

# Constructs a [Voronoi diagram][1] from the vertices of the input geometry.
#
# The source may be any geometry type. All vertices of the geometry will be
# used as the input points to the diagram.
#
# @param enevelope [RGeo::Feature::Geometry | nil]
# The `envelope` keyword argument provides an envelope to use to clip the
# resulting diagram. If `nil`, it will be calculated automatically.
# The diagram will be clipped to the *larger* of the provided envelope
# or an envelope surrounding the sites.
#
# @param tolerance [Float]
# The `tolerance` keyword argument sets the snapping tolerance used to improve
# the robustness of the computation. A tolerance of 0.0 specifies
# that no snapping will take place. The `tolerance` argument can be
# finicky and is known to cause the algorithm to fail in several cases.
# If you're using `tolerance` and getting a failure, try removing it.
# The test cases in [`tests/test_voronoi_diagram.py`][2] show more details.
#
# @param only_edges [true|false]
# If the `only_edges` keyword argument is `false` a collection of `Polygon`s
# will be returned. Otherwise a collection of `LineString` edges is returned.
#
# [1]: https://en.wikipedia.org/wiki/Voronoi_diagram
# [2]: TODO
# [3]: https://libgeos.org/doxygen/geos__c_8h.html#ace0b2fabc92d8457a295c385ea128aa5
def voronoi_diagram(envelope: nil, tolerance: 0.0, only_edges: false)
Primary.voronoi_diagram(self, envelope, tolerance || 0.0, only_edges)
end
end

module CAPIGeometryCollectionMethods # :nodoc:
Expand Down
4 changes: 2 additions & 2 deletions lib/rgeo/wkrep/wkt_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ def parse(str)
str = str.downcase
@cur_factory = @exact_factory
if @cur_factory
@cur_factory_support_z = @cur_factory.property(:has_z_coordinate) ? true : false
@cur_factory_support_m = @cur_factory.property(:has_m_coordinate) ? true : false
@cur_factory_support_z = @cur_factory.property(:has_z_coordinate)
@cur_factory_support_m = @cur_factory.property(:has_m_coordinate)
end
@cur_expect_z = nil
@cur_expect_m = nil
Expand Down

0 comments on commit 1fc06b1

Please sign in to comment.