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

Geos ccw analysis #229

Merged
merged 8 commits into from
Nov 18, 2020
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
2 changes: 2 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Add SphericalPolygonMethods#centroid #208 (allknowingfrog)
* Expand gemspec
* Drop Ruby 2.3 support
* Add a simple fallback for `Polygon#contains?` (Quiwin) #224
* Add `ccw?` method for linear rings, with geos support #229

### 2.1.1 / 2019-8-26

Expand Down
4 changes: 0 additions & 4 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ require "rake/testtask"
require "rake/extensiontask"
require "bundler/gem_tasks"

gemspec = eval(File.read(Dir.glob("*.gemspec").first))
release_gemspec = eval(File.read(Dir.glob("*.gemspec").first))
release_gemspec.version = gemspec.version.to_s.sub(/\.nonrelease$/, "")

# Build tasks

if RUBY_DESCRIPTION.match(/^jruby/)
Expand Down
78 changes: 78 additions & 0 deletions ext/geos_c_impl/analysis.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
Analysis methos for GEOS wrapper
*/

#include "preface.h"

#ifdef RGEO_GEOS_SUPPORTED

#include <ruby.h>
#include <geos_c.h>

#include "analysis.h"
#include "factory.h"
#include "errors.h"

RGEO_BEGIN_C

/*
* call-seq:
* RGeo::Geos::Analysis.ccw? -> true or false
*
* Checks direction for a ring, returns +true+ if counter-clockwise, +false+
* otherwise.
*/
#ifdef RGEO_GEOS_SUPPORTS_ISCCW
VALUE rgeo_geos_analysis_ccw_p(VALUE self, VALUE ring)
{

const RGeo_GeometryData* ring_data;
const GEOSCoordSequence* coord_seq;
char is_ccw;

rgeo_check_geos_object(ring);

ring_data = RGEO_GEOMETRY_DATA_PTR(ring);

coord_seq = GEOSGeom_getCoordSeq_r(ring_data->geos_context, ring_data->geom);
if (!coord_seq) { rb_raise(geos_error, "Could not retrieve CoordSeq from given ring."); }
if (!GEOSCoordSeq_isCCW_r(ring_data->geos_context, coord_seq, &is_ccw)) {
rb_raise(geos_error, "Could not determine if the CoordSeq is CCW.");
}

return is_ccw ? Qtrue : Qfalse;
};
#endif // RGEO_GEOS_SUPPORTS_ISCCW


/**
* call-seq:
* RGeo::Geos::Analysis.ccw_supported? -> true or false
*
* Checks if the RGEO_GEOS_SUPPORTS_ISCCW macro is defined, returns +true+
* if it is, +false+ otherwise
*/
VALUE rgeo_geos_analysis_supports_ccw(VALUE self)
{
#ifdef RGEO_GEOS_SUPPORTS_ISCCW
return Qtrue;
#else
return Qfalse;
#endif
}


void rgeo_init_geos_analysis(RGeo_Globals* globals)
{
VALUE geos_analysis_module;

geos_analysis_module = rb_define_module_under(globals->geos_module, "Analysis");
rb_define_singleton_method(geos_analysis_module, "ccw_supported?", rgeo_geos_analysis_supports_ccw, 0);
#ifdef RGEO_GEOS_SUPPORTS_ISCCW
rb_define_singleton_method(geos_analysis_module, "ccw?", rgeo_geos_analysis_ccw_p, 1);
#endif // RGEO_GEOS_SUPPORTS_ISCCW
}

RGEO_END_C

#endif
42 changes: 42 additions & 0 deletions ext/geos_c_impl/analysis.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Analysis methos for GEOS wrapper
*/

#ifndef RGEO_GEOS_ANALYSIS_INCLUDED
#define RGEO_GEOS_ANALYSIS_INCLUDED

#include <ruby.h>

#ifdef RGEO_GEOS_SUPPORTED

#include "factory.h"

RGEO_BEGIN_C

/*
* call-seq:
* RGeo::Geos::Analysis.ccw? -> true or false
*
* Checks direction for a ring, returns +true+ if counter-clockwise, +false+
* otherwise.
*/
#ifdef RGEO_GEOS_SUPPORTS_CCW
VALUE rgeo_geos_analysis_ccw_p(VALUE self, VALUE ring);
#endif // RGEO_GEOS_SUPPORTS_CCW

/**
* call-seq:
* RGeo::Geos::Analysis.ccw_supported? -> true or false
*
* Checks if the RGEO_GEOS_SUPPORTS_ISCCW macro is defined, returns +true+
* if it is, +false+ otherwise
*/
VALUE rgeo_geos_analysis_supports_ccw(VALUE self);

void rgeo_init_geos_analysis(RGeo_Globals* globals);

RGEO_END_C

#endif // RGEO_GEOS_SUPPORTED

#endif // RGEO_GEOS_ANALYSIS_INCLUDED
35 changes: 35 additions & 0 deletions ext/geos_c_impl/errors.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

#ifndef RGEO_GEOS_ERROS_INCLUDED
#define RGEO_GEOS_ERROS_INCLUDED

#include <ruby.h>

#include "preface.h"

#ifdef RGEO_GEOS_SUPPORTED

#include "errors.h"

RGEO_BEGIN_C

// Any error relative to RGeo.
VALUE rgeo_error;
// RGeo error specific to the GEOS implementation.
VALUE geos_error;


void rgeo_init_geos_errors() {
VALUE rgeo_module;
VALUE error_module;

rgeo_module = rb_define_module("RGeo");
error_module = rb_define_module_under(rgeo_module, "Error");
rgeo_error = rb_define_class_under(error_module, "RGeoError", rb_eRuntimeError);
geos_error = rb_define_class_under(error_module, "GeosError", rgeo_error);
}

RGEO_END_C

#endif // RGEO_GEOS_SUPPORTED

#endif // RGEO_GEOS_ERROS_INCLUDED
22 changes: 22 additions & 0 deletions ext/geos_c_impl/errors.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

#ifndef RGEO_GEOS_ERROS_INCLUDED
#define RGEO_GEOS_ERROS_INCLUDED

#include <ruby.h>

#ifdef RGEO_GEOS_SUPPORTED

RGEO_BEGIN_C

// Any error relative to RGeo.
extern VALUE rgeo_error;
// RGeo error specific to the GEOS implementation.
extern VALUE geos_error;

void rgeo_init_geos_errors();

RGEO_END_C

#endif // RGEO_GEOS_SUPPORTED

#endif // RGEO_GEOS_ERROS_INCLUDED
1 change: 1 addition & 0 deletions ext/geos_c_impl/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def create_dummy_makefile
have_func("GEOSPreparedContains_r", "geos_c.h")
have_func("GEOSPreparedDisjoint_r", "geos_c.h")
have_func("GEOSUnaryUnion_r", "geos_c.h")
have_func("GEOSCoordSeq_isCCW_r", "geos_c.h")
have_func("rb_memhash", "ruby.h")
end

Expand Down
11 changes: 10 additions & 1 deletion ext/geos_c_impl/factory.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "line_string.h"
#include "polygon.h"
#include "geometry_collection.h"
#include "errors.h"

RGEO_BEGIN_C

Expand Down Expand Up @@ -576,10 +577,11 @@ RGeo_Globals* rgeo_init_geos_factory()
VALUE wrapped_globals;
VALUE feature_module;

rgeo_module = rb_define_module("RGeo");

globals = ALLOC(RGeo_Globals);

// Cache some modules so we don't have to look them up by name every time
rgeo_module = rb_define_module("RGeo");
feature_module = rb_define_module_under(rgeo_module, "Feature");
globals->feature_module = feature_module;
globals->geos_module = rb_define_module_under(rgeo_module, "Geos");
Expand Down Expand Up @@ -831,6 +833,13 @@ char rgeo_is_geos_object(VALUE obj)
return (TYPE(obj) == T_DATA && RDATA(obj)->dfree == (RUBY_DATA_FUNC)destroy_geometry_func) ? 1 : 0;
}

void rgeo_check_geos_object(VALUE obj)
{
if (!rgeo_is_geos_object(obj)) {
rb_raise(rgeo_error, "Not a GEOS Geometry object.");
}
}


const GEOSGeometry* rgeo_get_geos_geometry_safe(VALUE obj)
{
Expand Down
6 changes: 5 additions & 1 deletion ext/geos_c_impl/factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

RGEO_BEGIN_C


/*
Per-interpreter globals.
Most of these are cached references to commonly used classes, modules,
Expand Down Expand Up @@ -187,6 +186,11 @@ GEOSGeometry* rgeo_convert_to_detached_geos_geometry(VALUE obj, VALUE factory, V
*/
char rgeo_is_geos_object(VALUE obj);

/*
Raises a rgeo error if the object is not a GEOS Geometry implementation.
*/
void rgeo_check_geos_object(VALUE obj);

/*
Gets the underlying GEOS geometry for a given ruby object. Returns NULL
if the given ruby object is not a GEOS geometry wrapper.
Expand Down
7 changes: 5 additions & 2 deletions ext/geos_c_impl/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,27 @@
Main initializer for GEOS wrapper
*/


#include "preface.h"

#ifdef RGEO_GEOS_SUPPORTED

#include <ruby.h>
#include <geos_c.h>

#include "errors.h"

#include "factory.h"
#include "geometry.h"
#include "point.h"
#include "line_string.h"
#include "polygon.h"
#include "geometry_collection.h"
#include "analysis.h"

#endif

RGEO_BEGIN_C


void Init_geos_c_impl()
{
#ifdef RGEO_GEOS_SUPPORTED
Expand All @@ -33,6 +34,8 @@ void Init_geos_c_impl()
rgeo_init_geos_line_string(globals);
rgeo_init_geos_polygon(globals);
rgeo_init_geos_geometry_collection(globals);
rgeo_init_geos_analysis(globals);
rgeo_init_geos_errors();
#endif
}

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 @@ -21,6 +21,9 @@
#ifdef HAVE_GEOSUNARYUNION_R
#define RGEO_GEOS_SUPPORTS_UNARYUNION
#endif
#ifdef HAVE_GEOSCOORDSEQ_ISCCW_R
#define RGEO_GEOS_SUPPORTS_ISCCW
#endif
#ifdef HAVE_RB_MEMHASH
#define RGEO_SUPPORTS_NEW_HASHING
#endif
Expand Down
18 changes: 9 additions & 9 deletions lib/rgeo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@
# database, and based on the postgresql adapter. Available as the
# activerecord-postgis-adapter gem.

require "rgeo/version"
require "rgeo/error"
require "rgeo/feature"
require "rgeo/coord_sys"
require "rgeo/impl_helper"
require "rgeo/wkrep"
require "rgeo/geos"
require "rgeo/cartesian"
require "rgeo/geographic"
require_relative "rgeo/version"
require_relative "rgeo/error"
require_relative "rgeo/feature"
require_relative "rgeo/coord_sys"
require_relative "rgeo/impl_helper"
require_relative "rgeo/wkrep"
require_relative "rgeo/geos"
require_relative "rgeo/cartesian"
require_relative "rgeo/geographic"
14 changes: 7 additions & 7 deletions lib/rgeo/cartesian.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
# the simple Cartesian implementation. It also provides a namespace
# for Cartesian-specific analysis tools.

require "rgeo/cartesian/calculations"
require "rgeo/cartesian/feature_methods"
require "rgeo/cartesian/feature_classes"
require "rgeo/cartesian/factory"
require "rgeo/cartesian/interface"
require "rgeo/cartesian/bounding_box"
require "rgeo/cartesian/analysis"
require_relative "cartesian/calculations"
require_relative "cartesian/feature_methods"
require_relative "cartesian/feature_classes"
require_relative "cartesian/factory"
require_relative "cartesian/interface"
require_relative "cartesian/bounding_box"
require_relative "cartesian/analysis"
22 changes: 22 additions & 0 deletions lib/rgeo/cartesian/analysis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@ module Cartesian

module Analysis
class << self
# Check orientation of a ring, returns `true` if it is counter-clockwise
# and false otherwise.
#
# If the factory used is GEOS based, use the GEOS implementation to
# check that. Otherwise, this methods falls back to `ring_direction`.
#
# == Note
#
# This method does not ensure a correct result for an invalid geometry.
# You should make sure your ring is valid beforehand using `is_ring?`
# if you are using a LineString, or directly `valid?` for a
# `linear_ring?`.
# This will be subject to changes in v3.
def ccw?(ring)
if RGeo::Geos.is_capi_geos?(ring) && RGeo::Geos::Analysis.ccw_supported?
RGeo::Geos::Analysis.ccw?(ring)
else
RGeo::Cartesian::Analysis.ring_direction(ring) == 1
end
end
alias counter_clockwise? ccw?

# Given a LineString, which must be a ring, determine whether the
# ring proceeds clockwise or counterclockwise.
# Returns 1 for counterclockwise, or -1 for clockwise.
Expand Down