rgeo 2.4.0 → 3.0.0.pre.rc.1

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/README.md +1 -0
  4. data/ext/geos_c_impl/analysis.c +4 -4
  5. data/ext/geos_c_impl/errors.c +8 -6
  6. data/ext/geos_c_impl/errors.h +7 -3
  7. data/ext/geos_c_impl/extconf.rb +2 -0
  8. data/ext/geos_c_impl/factory.c +80 -7
  9. data/ext/geos_c_impl/factory.h +28 -14
  10. data/ext/geos_c_impl/geometry.c +46 -14
  11. data/ext/geos_c_impl/geometry.h +7 -0
  12. data/ext/geos_c_impl/geometry_collection.c +2 -104
  13. data/ext/geos_c_impl/geometry_collection.h +0 -11
  14. data/ext/geos_c_impl/line_string.c +1 -1
  15. data/ext/geos_c_impl/point.c +1 -1
  16. data/ext/geos_c_impl/polygon.c +1 -37
  17. data/ext/geos_c_impl/preface.h +3 -0
  18. data/lib/rgeo/cartesian/calculations.rb +54 -17
  19. data/lib/rgeo/cartesian/factory.rb +0 -7
  20. data/lib/rgeo/cartesian/feature_classes.rb +66 -46
  21. data/lib/rgeo/cartesian/feature_methods.rb +51 -20
  22. data/lib/rgeo/cartesian/interface.rb +0 -6
  23. data/lib/rgeo/cartesian/planar_graph.rb +379 -0
  24. data/lib/rgeo/cartesian/sweepline_intersector.rb +149 -0
  25. data/lib/rgeo/cartesian/valid_op.rb +71 -0
  26. data/lib/rgeo/cartesian.rb +3 -0
  27. data/lib/rgeo/coord_sys/cs/wkt_parser.rb +6 -6
  28. data/lib/rgeo/error.rb +15 -0
  29. data/lib/rgeo/feature/geometry.rb +28 -28
  30. data/lib/rgeo/feature/geometry_collection.rb +13 -5
  31. data/lib/rgeo/feature/line_string.rb +3 -3
  32. data/lib/rgeo/feature/multi_surface.rb +3 -3
  33. data/lib/rgeo/feature/point.rb +4 -4
  34. data/lib/rgeo/feature/surface.rb +3 -3
  35. data/lib/rgeo/geographic/factory.rb +0 -7
  36. data/lib/rgeo/geographic/interface.rb +5 -20
  37. data/lib/rgeo/geographic/proj4_projector.rb +0 -2
  38. data/lib/rgeo/geographic/projected_feature_classes.rb +21 -9
  39. data/lib/rgeo/geographic/projected_feature_methods.rb +51 -28
  40. data/lib/rgeo/geographic/simple_mercator_projector.rb +0 -2
  41. data/lib/rgeo/geographic/spherical_feature_classes.rb +29 -9
  42. data/lib/rgeo/geographic/spherical_feature_methods.rb +62 -1
  43. data/lib/rgeo/geos/capi_factory.rb +21 -31
  44. data/lib/rgeo/geos/capi_feature_classes.rb +35 -11
  45. data/lib/rgeo/geos/ffi_factory.rb +0 -28
  46. data/lib/rgeo/geos/ffi_feature_classes.rb +34 -10
  47. data/lib/rgeo/geos/ffi_feature_methods.rb +23 -5
  48. data/lib/rgeo/geos/interface.rb +0 -7
  49. data/lib/rgeo/geos/zm_factory.rb +0 -12
  50. data/lib/rgeo/impl_helper/basic_geometry_collection_methods.rb +4 -4
  51. data/lib/rgeo/impl_helper/basic_geometry_methods.rb +1 -1
  52. data/lib/rgeo/impl_helper/basic_line_string_methods.rb +15 -19
  53. data/lib/rgeo/impl_helper/basic_point_methods.rb +1 -1
  54. data/lib/rgeo/impl_helper/basic_polygon_methods.rb +1 -1
  55. data/lib/rgeo/impl_helper/valid_op.rb +354 -0
  56. data/lib/rgeo/impl_helper/validity_check.rb +138 -0
  57. data/lib/rgeo/impl_helper.rb +1 -0
  58. data/lib/rgeo/version.rb +1 -1
  59. metadata +31 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abb42ec2fb37f915e49d13c7e802279560588d75f376d06d37f3a4e4f0e1ab61
4
- data.tar.gz: 6606d1fe12f30d645fb66edf95e694708ffb24ba81afa78ce03a961456f67686
3
+ metadata.gz: e26b01fb2fff6915756d98e206ee4bdabefeaceadae3cd0b85cb7be9ee6d8285
4
+ data.tar.gz: 9b94dd4e6c57e9fa5ab07c7b5fb9c622a52d843b67d146b52454933cb817a4a1
5
5
  SHA512:
6
- metadata.gz: 807c3f239731755e4afe348d70b2ebf0c81f29754a5677a4123dce474c0a18e7a6e63df5ee36fb244a05b04a9ef70356a3e966fab336a0925365afc05e331d5a
7
- data.tar.gz: c940601cc0c30944b5bc4e5f8d9ed632704295b28cb8115973a1856c823f01a7a0b655bc2e277ebe09268a08cf4fe9e1b0abbb4c192a598d2f06c95baef58901
6
+ metadata.gz: 9adb8cc283c8663fb0a178eeb7a1629fe466265f7fa15c54d4e8dac4188646a2682f591a3e76d12271df5dc6c875961833600b88952d035f824933bd454211de
7
+ data.tar.gz: c1f7e26d5a5edcc060cac2efaa782a586c9ee95e1dda08f7af771648ecc1ea2373a1e4ba9053aae00288332e401370c7f61e254367231ceabb4fb36ad5406209
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --markup rdoc
2
+ --output-dir ./yardoc
3
+ lib/**/*.rb
4
+ ext/**/*.{c,h}
5
+ -
6
+ doc/*.md
data/README.md CHANGED
@@ -129,6 +129,7 @@ Here's the current list of available topics:
129
129
  - [Installing GEOS](https://github.com/rgeo/rgeo/blob/master/doc/Installing-GEOS.md)
130
130
  - [Factory Compatibility](https://github.com/rgeo/rgeo/blob/master/doc/Factory-Compatibility.md)
131
131
  - [Which factory should I use?](https://github.com/rgeo/rgeo/blob/master/doc/Which-factory-should-I-use.md)
132
+ - [Geometry validity handling](https://github.com/rgeo/rgeo/blob/master/doc/Geometry-Validity.md)
132
133
  - [Examples](https://github.com/rgeo/rgeo/blob/master/doc/Examples.md)
133
134
  - [Who uses `rgeo`?](https://github.com/rgeo/rgeo/blob/master/doc/Gallery.md)
134
135
 
@@ -37,9 +37,9 @@ VALUE rgeo_geos_analysis_ccw_p(VALUE self, VALUE ring)
37
37
  ring_data = RGEO_GEOMETRY_DATA_PTR(ring);
38
38
 
39
39
  coord_seq = GEOSGeom_getCoordSeq_r(ring_data->geos_context, ring_data->geom);
40
- if (!coord_seq) { rb_raise(geos_error, "Could not retrieve CoordSeq from given ring."); }
40
+ if (!coord_seq) { rb_raise(rb_eGeosError, "Could not retrieve CoordSeq from given ring."); }
41
41
  if (!GEOSCoordSeq_isCCW_r(ring_data->geos_context, coord_seq, &is_ccw)) {
42
- rb_raise(geos_error, "Could not determine if the CoordSeq is CCW.");
42
+ rb_raise(rb_eGeosError, "Could not determine if the CoordSeq is CCW.");
43
43
  }
44
44
 
45
45
  return is_ccw ? Qtrue : Qfalse;
@@ -49,8 +49,8 @@ VALUE rgeo_geos_analysis_ccw_p(VALUE self, VALUE ring)
49
49
 
50
50
  /**
51
51
  * call-seq:
52
- * RGeo::Geos::Analysis.ccw_supported? -> true or false
53
- *
52
+ * RGeo::Geos::Analysis.ccw_supported? -> true or false
53
+ *
54
54
  * Checks if the RGEO_GEOS_SUPPORTS_ISCCW macro is defined, returns +true+
55
55
  * if it is, +false+ otherwise
56
56
  */
@@ -14,18 +14,20 @@
14
14
 
15
15
  RGEO_BEGIN_C
16
16
 
17
- // Any error relative to RGeo.
18
- VALUE rgeo_error;
19
- // RGeo error specific to the GEOS implementation.
20
- VALUE geos_error;
17
+ VALUE rb_eRGeoError;
18
+ VALUE rb_eRGeoInvalidGeometry;
19
+ VALUE rb_eRGeoUnsupportedOperation;
20
+ VALUE rb_eGeosError;
21
21
 
22
22
 
23
23
  void rgeo_init_geos_errors() {
24
24
  VALUE error_module;
25
25
 
26
26
  error_module = rb_define_module_under(rgeo_module, "Error");
27
- rgeo_error = rb_define_class_under(error_module, "RGeoError", rb_eRuntimeError);
28
- geos_error = rb_define_class_under(error_module, "GeosError", rgeo_error);
27
+ rb_eRGeoError = rb_define_class_under(error_module, "RGeoError", rb_eRuntimeError);
28
+ rb_eRGeoInvalidGeometry = rb_define_class_under(error_module, "InvalidGeometry", rb_eRGeoError);
29
+ rb_eRGeoUnsupportedOperation = rb_define_class_under(error_module, "UnsupportedOperation", rb_eRGeoError);
30
+ rb_eGeosError = rb_define_class_under(error_module, "GeosError", rb_eRGeoError);
29
31
  }
30
32
 
31
33
  RGEO_END_C
@@ -8,10 +8,14 @@
8
8
 
9
9
  RGEO_BEGIN_C
10
10
 
11
- // Any error relative to RGeo.
12
- extern VALUE rgeo_error;
11
+ // Main rgeo error type
12
+ extern VALUE rb_eRGeoError;
13
+ // RGeo::Error::InvalidGeometry
14
+ extern VALUE rb_eRGeoInvalidGeometry;
15
+ // RGeo::Error::UnsupportedOperation
16
+ extern VALUE rb_eRGeoUnsupportedOperation;
13
17
  // RGeo error specific to the GEOS implementation.
14
- extern VALUE geos_error;
18
+ extern VALUE rb_eGeosError;
15
19
 
16
20
  void rgeo_init_geos_errors();
17
21
 
@@ -14,6 +14,8 @@ if RUBY_DESCRIPTION =~ /^jruby\s/
14
14
  else
15
15
  require "mkmf"
16
16
 
17
+ $CFLAGS << " -DRGEO_GEOS_DEBUG" if ENV.key?("DEBUG") || ENV.key?("RGEO_GEOS_DEBUG")
18
+
17
19
  geosconfig = with_config("geos-config") || find_executable("geos-config")
18
20
 
19
21
  if geosconfig
@@ -9,6 +9,9 @@
9
9
 
10
10
  #include <ruby.h>
11
11
  #include <geos_c.h>
12
+ #include <ctype.h>
13
+ #include <stdarg.h>
14
+ #include <stdio.h>
12
15
 
13
16
  #include "globals.h"
14
17
 
@@ -26,13 +29,50 @@ RGEO_BEGIN_C
26
29
  /**** RUBY AND GEOS CALLBACKS ****/
27
30
 
28
31
 
29
- // NOP message handler. GEOS requires that a message handler be set
30
- // for every context handle.
31
-
32
- static void message_handler(const char* fmt, ...)
32
+ // The notice handler is very rarely used by GEOS, only in
33
+ // GEOSIsValid_r (check for NOTICE_MESSAGE in GEOS codebase).
34
+ // We still set it to make sure we do not miss any implementation
35
+ // change. Use `DEBUG=1 rake` to show notice.
36
+ #ifdef RGEO_GEOS_DEBUG
37
+ static void notice_handler(const char* fmt, ...)
33
38
  {
39
+ va_list args;
40
+ va_start(args, fmt);
41
+ fprintf(stderr, "GEOS Notice -- ");
42
+ vfprintf(stderr, fmt, args);
43
+ fprintf(stderr, "\n");
44
+ va_end(args);
34
45
  }
46
+ #endif
35
47
 
48
+ static void error_handler(const char* fmt, ...)
49
+ {
50
+ // See https://en.cppreference.com/w/c/io/vfprintf
51
+ va_list args1;
52
+ va_start(args1, fmt);
53
+ va_list args2;
54
+ va_copy(args2, args1);
55
+ int size = 1+vsnprintf(NULL, 0, fmt, args1);
56
+ va_end(args1);
57
+ char geos_full_error[size];
58
+ vsnprintf(geos_full_error, sizeof geos_full_error, fmt, args2);
59
+ va_end(args2);
60
+
61
+ // NOTE: strtok is destructive, geos_full_error is not to be used afterwards.
62
+ char *geos_error = strtok(geos_full_error, ":");
63
+ char *geos_message = strtok(NULL, ":");
64
+ while(isspace(*geos_message)) geos_message++;
65
+
66
+ if (streq(geos_error, "UnsupportedOperationException")) {
67
+ rb_raise(rb_eRGeoUnsupportedOperation, "%s", geos_message);
68
+ } else if (streq(geos_error, "IllegalArgumentException")) {
69
+ rb_raise(rb_eRGeoInvalidGeometry, "%s", geos_message);
70
+ } else if (geos_message) {
71
+ rb_raise(rb_eGeosError, "%s: %s", geos_error, geos_message);
72
+ } else {
73
+ rb_raise(rb_eGeosError, "%s", geos_error);
74
+ }
75
+ }
36
76
 
37
77
  // Destroy function for factory data. We destroy any serialization
38
78
  // objects that have been created for the factory, and then destroy
@@ -227,6 +267,25 @@ static VALUE method_factory_flags(VALUE self)
227
267
  return INT2NUM(RGEO_FACTORY_DATA_PTR(self)->flags);
228
268
  }
229
269
 
270
+ VALUE method_factory_supports_z_p(VALUE self)
271
+ {
272
+ return RGEO_FACTORY_DATA_PTR(self)->flags & RGEO_FACTORYFLAGS_SUPPORTS_Z ? Qtrue : Qfalse;
273
+ }
274
+
275
+ VALUE method_factory_supports_m_p(VALUE self)
276
+ {
277
+ return RGEO_FACTORY_DATA_PTR(self)->flags & RGEO_FACTORYFLAGS_SUPPORTS_M ? Qtrue : Qfalse;
278
+ }
279
+
280
+ VALUE method_factory_supports_z_or_m_p(VALUE self)
281
+ {
282
+ return RGEO_FACTORY_DATA_PTR(self)->flags & RGEO_FACTORYFLAGS_SUPPORTS_Z_OR_M ? Qtrue : Qfalse;
283
+ }
284
+
285
+ VALUE method_factory_prepare_heuristic_p(VALUE self)
286
+ {
287
+ return RGEO_FACTORY_DATA_PTR(self)->flags & RGEO_FACTORYFLAGS_PREPARE_HEURISTIC ? Qtrue : Qfalse;
288
+ }
230
289
 
231
290
  static VALUE method_factory_parse_wkt(VALUE self, VALUE str)
232
291
  {
@@ -459,7 +518,12 @@ static VALUE cmethod_factory_create(VALUE klass, VALUE flags, VALUE srid, VALUE
459
518
  result = Qnil;
460
519
  data = ALLOC(RGeo_FactoryData);
461
520
  if (data) {
462
- context = initGEOS_r(message_handler, message_handler);
521
+ context = GEOS_init_r();
522
+ #ifdef RGEO_GEOS_DEBUG
523
+ GEOSContext_setNoticeHandler_r(context, notice_handler);
524
+ #endif
525
+ GEOSContext_setErrorHandler_r(context, error_handler);
526
+
463
527
  if (context) {
464
528
  data->geos_context = context;
465
529
  data->flags = NUM2INT(flags);
@@ -629,15 +693,24 @@ void rgeo_init_geos_factory()
629
693
  rb_gc_register_address(&marshal_wkb_generator);
630
694
  #endif
631
695
 
632
- // Add C methods to the factory.
633
696
  geos_factory_class = rb_define_class_under(rgeo_geos_module, "CAPIFactory", rb_cObject);
634
697
  rb_define_alloc_func(geos_factory_class, alloc_factory);
698
+ // Add C constants to the factory.
699
+ rb_define_const(geos_factory_class, "FLAG_SUPPORTS_Z", INT2FIX(RGEO_FACTORYFLAGS_SUPPORTS_Z));
700
+ rb_define_const(geos_factory_class, "FLAG_SUPPORTS_M", INT2FIX(RGEO_FACTORYFLAGS_SUPPORTS_M));
701
+ rb_define_const(geos_factory_class, "FLAG_SUPPORTS_Z_OR_M", INT2FIX(RGEO_FACTORYFLAGS_SUPPORTS_Z_OR_M));
702
+ rb_define_const(geos_factory_class, "FLAG_PREPARE_HEURISTIC", INT2FIX(RGEO_FACTORYFLAGS_PREPARE_HEURISTIC));
703
+ // Add C methods to the factory.
635
704
  rb_define_method(geos_factory_class, "initialize_copy", method_factory_initialize_copy, 1);
636
705
  rb_define_method(geos_factory_class, "_parse_wkt_impl", method_factory_parse_wkt, 1);
637
706
  rb_define_method(geos_factory_class, "_parse_wkb_impl", method_factory_parse_wkb, 1);
638
707
  rb_define_method(geos_factory_class, "_srid", method_factory_srid, 0);
639
708
  rb_define_method(geos_factory_class, "_buffer_resolution", method_factory_buffer_resolution, 0);
640
709
  rb_define_method(geos_factory_class, "_flags", method_factory_flags, 0);
710
+ rb_define_method(geos_factory_class, "supports_z?", method_factory_supports_z_p, 0);
711
+ rb_define_method(geos_factory_class, "supports_m?", method_factory_supports_m_p, 0);
712
+ rb_define_method(geos_factory_class, "supports_z_or_m?", method_factory_supports_z_or_m_p, 0);
713
+ rb_define_method(geos_factory_class, "prepare_heuristic?", method_factory_prepare_heuristic_p, 0);
641
714
  rb_define_method(geos_factory_class, "_set_wkrep_parsers", method_set_wkrep_parsers, 2);
642
715
  rb_define_method(geos_factory_class, "_proj4", method_get_proj4, 0);
643
716
  rb_define_method(geos_factory_class, "_coord_sys", method_get_coord_sys, 0);
@@ -831,7 +904,7 @@ char rgeo_is_geos_object(VALUE obj)
831
904
  void rgeo_check_geos_object(VALUE obj)
832
905
  {
833
906
  if (!rgeo_is_geos_object(obj)) {
834
- rb_raise(rgeo_error, "Not a GEOS Geometry object.");
907
+ rb_raise(rb_eRGeoError, "Not a GEOS Geometry object.");
835
908
  }
836
909
  }
837
910
 
@@ -37,12 +37,35 @@ typedef struct {
37
37
  int buffer_resolution;
38
38
  } RGeo_FactoryData;
39
39
 
40
- #define RGEO_FACTORYFLAGS_LENIENT_MULTIPOLYGON 1
41
- #define RGEO_FACTORYFLAGS_SUPPORTS_Z 2
42
- #define RGEO_FACTORYFLAGS_SUPPORTS_M 4
43
- #define RGEO_FACTORYFLAGS_SUPPORTS_Z_OR_M 6
44
- #define RGEO_FACTORYFLAGS_PREPARE_HEURISTIC 8
40
+ /*
41
+ Flags that are used to pass options when creating a factory.
42
+ They are available in ruby under RGeo::Geos::CAPIFactory::FLAG_name
43
+ where name is the name below without the RGEO_FACTORYFLAGS_ prefix.
44
+ */
45
+ #define RGEO_FACTORYFLAGS_SUPPORTS_Z 0b0010
46
+ #define RGEO_FACTORYFLAGS_SUPPORTS_M 0b0100
47
+ #define RGEO_FACTORYFLAGS_SUPPORTS_Z_OR_M (RGEO_FACTORYFLAGS_SUPPORTS_Z | RGEO_FACTORYFLAGS_SUPPORTS_M)
48
+ #define RGEO_FACTORYFLAGS_PREPARE_HEURISTIC 0b1000
49
+
50
+ /* call-seq:
51
+ * RGeo::Geos::CAPIFactory.supports_z? -> true or false
52
+ */
53
+ VALUE method_factory_supports_z_p(VALUE self);
54
+
55
+ /* call-seq:
56
+ * RGeo::Geos::CAPIFactory.supports_m? -> true or false
57
+ */
58
+ VALUE method_factory_supports_m_p(VALUE self);
59
+
60
+ /* call-seq:
61
+ * RGeo::Geos::CAPIFactory.supports_z_or_m? -> true or false
62
+ */
63
+ VALUE method_factory_supports_z_or_m_p(VALUE self);
45
64
 
65
+ /* call-seq:
66
+ * RGeo::Geos::CAPIFactory.prepare_heuristic? -> true or false
67
+ */
68
+ VALUE method_factory_prepare_heuristic_p(VALUE self);
46
69
 
47
70
  /*
48
71
  Wrapped structure for Geometry objects.
@@ -164,15 +187,6 @@ void rgeo_check_geos_object(VALUE obj);
164
187
  */
165
188
  const GEOSGeometry* rgeo_get_geos_geometry_safe(VALUE obj);
166
189
 
167
- /*
168
- Compares the coordinate sequences for two given GEOS geometries.
169
- The two given geometries MUST be of types backed directly by
170
- coordinate sequences-- i.e. points or line strings.
171
- Returns Qtrue if the two coordinate sequences are equal, Qfalse
172
- if they are inequal, or Qnil if an error occurs.
173
- */
174
- VALUE rgeo_geos_coordseqs_eql(GEOSContextHandle_t context, const GEOSGeometry* geom1, const GEOSGeometry* geom2, char check_z);
175
-
176
190
  /*
177
191
  Compares the ruby classes and geometry factories of the two given ruby
178
192
  objects. Returns Qtrue if everything is equal (that is, the two objects
@@ -13,6 +13,7 @@
13
13
 
14
14
  #include "globals.h"
15
15
 
16
+ #include "errors.h"
16
17
  #include "factory.h"
17
18
  #include "geometry.h"
18
19
 
@@ -863,24 +864,21 @@ static VALUE method_geometry_union(VALUE self, VALUE rhs)
863
864
 
864
865
  static VALUE method_geometry_unary_union(VALUE self)
865
866
  {
866
- VALUE result;
867
+ #ifdef RGEO_GEOS_SUPPORTS_UNARYUNION
867
868
  RGeo_GeometryData* self_data;
868
869
  const GEOSGeometry* self_geom;
869
870
 
870
- result = Qnil;
871
-
872
- #ifdef RGEO_GEOS_SUPPORTS_UNARYUNION
873
871
  self_data = RGEO_GEOMETRY_DATA_PTR(self);
874
872
  self_geom = self_data->geom;
875
873
  if (self_geom) {
876
874
  GEOSContextHandle_t self_context = self_data->geos_context;
877
- result = rgeo_wrap_geos_geometry(self_data->factory,
875
+ return rgeo_wrap_geos_geometry(self_data->factory,
878
876
  GEOSUnaryUnion_r(self_context, self_geom),
879
877
  Qnil);
880
878
  }
881
879
  #endif
882
880
 
883
- return result;
881
+ return Qnil;
884
882
  }
885
883
 
886
884
 
@@ -1044,18 +1042,39 @@ static VALUE method_geometry_invalid_reason(VALUE self)
1044
1042
  self_data = RGEO_GEOMETRY_DATA_PTR(self);
1045
1043
  self_geom = self_data->geom;
1046
1044
  if (self_geom) {
1047
- str = GEOSisValidReason_r(self_data->geos_context, self_geom);
1048
- // Per documentation, a valid geometry should give an empty string.
1049
- // However it seems not to be the case. Hence the comparison against
1050
- // the string that is really given: `"Valid Geometry"`.
1051
- // See https://github.com/libgeos/geos/issues/431.
1052
- if (str) result = (str[0] == '\0' || !strcmp(str, "Valid Geometry")) ? Qnil : rb_str_new2(str);
1053
- else result = rb_str_new2("Exception");
1054
- GEOSFree_r(self_data->geos_context, str);
1045
+ // We use NULL there to tell GEOS that we don't care about the position.
1046
+ switch(GEOSisValidDetail_r(self_data->geos_context, self_geom, 0, &str, NULL)) {
1047
+ case 0: // invalid
1048
+ result = rb_utf8_str_new_cstr(str);
1049
+ case 1: // valid
1050
+ break;
1051
+ case 2: // exception
1052
+ default:
1053
+ result = rb_utf8_str_new_cstr("Exception");
1054
+ break;
1055
+ };
1056
+ if (str) GEOSFree_r(self_data->geos_context, str);
1055
1057
  }
1056
1058
  return result;
1057
1059
  }
1058
1060
 
1061
+ static VALUE method_geometry_make_valid(VALUE self)
1062
+ {
1063
+ RGeo_GeometryData* self_data;
1064
+ const GEOSGeometry* self_geom;
1065
+ GEOSGeometry* valid_geom;
1066
+ self_data = RGEO_GEOMETRY_DATA_PTR(self);
1067
+ self_geom = self_data->geom;
1068
+ if (!self_geom) return Qnil;
1069
+
1070
+ // According to GEOS implementation, MakeValid always returns.
1071
+ valid_geom = GEOSMakeValid_r(self_data->geos_context, self_geom);
1072
+ if (!valid_geom) {
1073
+ rb_raise(rb_eRGeoInvalidGeometry, "%"PRIsVALUE, method_geometry_invalid_reason(self));
1074
+ }
1075
+ return rgeo_wrap_geos_geometry(self_data->factory, valid_geom, Qnil);
1076
+ }
1077
+
1059
1078
  static VALUE method_geometry_point_on_surface(VALUE self)
1060
1079
  {
1061
1080
  VALUE result;
@@ -1071,6 +1090,18 @@ static VALUE method_geometry_point_on_surface(VALUE self)
1071
1090
  return result;
1072
1091
  }
1073
1092
 
1093
+ VALUE rgeo_geos_geometries_strict_eql(GEOSContextHandle_t context, const GEOSGeometry* geom1, const GEOSGeometry* geom2)
1094
+ {
1095
+ switch (GEOSEqualsExact_r(context, geom1, geom2, 0.0)) {
1096
+ case 0:
1097
+ return Qfalse;
1098
+ case 1:
1099
+ return Qtrue;
1100
+ case 2:
1101
+ default:
1102
+ rb_raise(rb_eGeosError, "Cannot test equality.");
1103
+ }
1104
+ }
1074
1105
 
1075
1106
  /**** INITIALIZATION FUNCTION ****/
1076
1107
 
@@ -1126,6 +1157,7 @@ void rgeo_init_geos_geometry()
1126
1157
  rb_define_method(geos_geometry_methods, "valid?", method_geometry_is_valid, 0);
1127
1158
  rb_define_method(geos_geometry_methods, "invalid_reason", method_geometry_invalid_reason, 0);
1128
1159
  rb_define_method(geos_geometry_methods, "point_on_surface", method_geometry_point_on_surface, 0);
1160
+ rb_define_method(geos_geometry_methods, "make_valid", method_geometry_make_valid, 0);
1129
1161
  }
1130
1162
 
1131
1163
 
@@ -16,6 +16,13 @@ RGEO_BEGIN_C
16
16
  void rgeo_init_geos_geometry();
17
17
 
18
18
 
19
+ /*
20
+ Compares two geometries using strict GEOS comparison. return Qtrue
21
+ if they are equal, Qfalse otherwise.
22
+ May raise a `RGeo::Error::GeosError`.
23
+ */
24
+ VALUE rgeo_geos_geometries_strict_eql(GEOSContextHandle_t context, const GEOSGeometry* geom1, const GEOSGeometry* geom2);
25
+
19
26
  RGEO_END_C
20
27
 
21
28
  #endif
@@ -16,6 +16,7 @@
16
16
  #include "geometry.h"
17
17
  #include "line_string.h"
18
18
  #include "polygon.h"
19
+ #include "geometry.h"
19
20
  #include "geometry_collection.h"
20
21
 
21
22
  #include "coordinates.h"
@@ -43,9 +44,6 @@ static VALUE create_geometry_collection(VALUE module, int type, VALUE factory, V
43
44
  VALUE cast_type;
44
45
  GEOSGeometry* geom;
45
46
  GEOSGeometry* collection;
46
- char problem;
47
- GEOSGeometry* igeom;
48
- GEOSGeometry* jgeom;
49
47
 
50
48
  result = Qnil;
51
49
  Check_Type(array, T_ARRAY);
@@ -90,32 +88,6 @@ static VALUE create_geometry_collection(VALUE module, int type, VALUE factory, V
90
88
  }
91
89
  else {
92
90
  collection = GEOSGeom_createCollection_r(geos_context, type, geoms, len);
93
- // Due to a limitation of GEOS, the MultiPolygon assertions are not checked.
94
- // We do that manually here.
95
- if (collection && type == GEOS_MULTIPOLYGON && (factory_data->flags & 1) == 0) {
96
- problem = 0;
97
- for (i=1; i<len; ++i) {
98
- for (j=0; j<i; ++j) {
99
- igeom = geoms[i];
100
- jgeom = geoms[j];
101
- problem = GEOSRelatePattern_r(geos_context, igeom, jgeom, "2********");
102
- if (problem) {
103
- break;
104
- }
105
- problem = GEOSRelatePattern_r(geos_context, igeom, jgeom, "****1****");
106
- if (problem) {
107
- break;
108
- }
109
- }
110
- if (problem) {
111
- break;
112
- }
113
- }
114
- if (problem) {
115
- GEOSGeom_destroy_r(geos_context, collection);
116
- collection = NULL;
117
- }
118
- }
119
91
  if (collection) {
120
92
  result = rgeo_wrap_geos_geometry(factory, collection, module);
121
93
  RGEO_GEOMETRY_DATA_PTR(result)->klasses = klasses;
@@ -143,7 +115,7 @@ static VALUE method_geometry_collection_eql(VALUE self, VALUE rhs)
143
115
  result = rgeo_geos_klasses_and_factories_eql(self, rhs);
144
116
  if (RTEST(result)) {
145
117
  self_data = RGEO_GEOMETRY_DATA_PTR(self);
146
- result = rgeo_geos_geometry_collections_eql(self_data->geos_context, self_data->geom, RGEO_GEOMETRY_DATA_PTR(rhs)->geom, RGEO_FACTORY_DATA_PTR(self_data->factory)->flags & RGEO_FACTORYFLAGS_SUPPORTS_Z_OR_M);
118
+ result = rgeo_geos_geometries_strict_eql(self_data->geos_context, self_data->geom, RGEO_GEOMETRY_DATA_PTR(rhs)->geom);
147
119
  }
148
120
  return result;
149
121
  }
@@ -635,80 +607,6 @@ void rgeo_init_geos_geometry_collection()
635
607
  /**** OTHER PUBLIC FUNCTIONS ****/
636
608
 
637
609
 
638
- VALUE rgeo_geos_geometry_collections_eql(GEOSContextHandle_t context, const GEOSGeometry* geom1, const GEOSGeometry* geom2, char check_z)
639
- {
640
- VALUE result;
641
- int len1;
642
- int len2;
643
- int i;
644
- const GEOSGeometry* sub_geom1;
645
- const GEOSGeometry* sub_geom2;
646
- int type1;
647
- int type2;
648
-
649
- result = Qnil;
650
- if (geom1 && geom2) {
651
- len1 = GEOSGetNumGeometries_r(context, geom1);
652
- len2 = GEOSGetNumGeometries_r(context, geom2);
653
- if (len1 >= 0 && len2 >= 0) {
654
- if (len1 == len2) {
655
- result = Qtrue;
656
- for (i=0; i<len1; ++i) {
657
- sub_geom1 = GEOSGetGeometryN_r(context, geom1, i);
658
- sub_geom2 = GEOSGetGeometryN_r(context, geom2, i);
659
- if (sub_geom1 && sub_geom2) {
660
- type1 = GEOSGeomTypeId_r(context, sub_geom1);
661
- type2 = GEOSGeomTypeId_r(context, sub_geom2);
662
- if (type1 >= 0 && type2 >= 0) {
663
- if (type1 == type2) {
664
- switch (type1) {
665
- case GEOS_POINT:
666
- case GEOS_LINESTRING:
667
- case GEOS_LINEARRING:
668
- result = rgeo_geos_coordseqs_eql(context, sub_geom1, sub_geom2, check_z);
669
- break;
670
- case GEOS_POLYGON:
671
- result = rgeo_geos_polygons_eql(context, sub_geom1, sub_geom2, check_z);
672
- break;
673
- case GEOS_GEOMETRYCOLLECTION:
674
- case GEOS_MULTIPOINT:
675
- case GEOS_MULTILINESTRING:
676
- case GEOS_MULTIPOLYGON:
677
- result = rgeo_geos_geometry_collections_eql(context, sub_geom1, sub_geom2, check_z);
678
- break;
679
- default:
680
- result = Qnil;
681
- break;
682
- }
683
- if (!RTEST(result)) {
684
- break;
685
- }
686
- }
687
- else {
688
- result = Qfalse;
689
- break;
690
- }
691
- }
692
- else {
693
- result = Qnil;
694
- break;
695
- }
696
- }
697
- else {
698
- result = Qnil;
699
- break;
700
- }
701
- }
702
- }
703
- else {
704
- result = Qfalse;
705
- }
706
- }
707
- }
708
- return result;
709
- }
710
-
711
-
712
610
  st_index_t rgeo_geos_geometry_collection_hash(GEOSContextHandle_t context, const GEOSGeometry* geom, st_index_t hash)
713
611
  {
714
612
  const GEOSGeometry* sub_geom;
@@ -18,17 +18,6 @@ RGEO_BEGIN_C
18
18
  */
19
19
  void rgeo_init_geos_geometry_collection();
20
20
 
21
- /*
22
- Comopares the contents of two geometry collections. Does not test the
23
- types of the collections themselves, but tests the types, values, and
24
- contents of all the contents. The two given geometries MUST be
25
- collection types-- i.e. GeometryCollection, MultiPoint, MultiLineString,
26
- or MultiPolygon.
27
- Returns Qtrue if the contents of the two geometry collections are equal,
28
- Qfalse if they are inequal, or Qnil if an error occurs.
29
- */
30
- VALUE rgeo_geos_geometry_collections_eql(GEOSContextHandle_t context, const GEOSGeometry* geom1, const GEOSGeometry* geom2, char check_z);
31
-
32
21
  /*
33
22
  A tool for building up hash values.
34
23
  You must pass in the context, a geos geometry, and a seed hash.
@@ -345,7 +345,7 @@ static VALUE method_line_string_eql(VALUE self, VALUE rhs)
345
345
  result = rgeo_geos_klasses_and_factories_eql(self, rhs);
346
346
  if (RTEST(result)) {
347
347
  self_data = RGEO_GEOMETRY_DATA_PTR(self);
348
- result = rgeo_geos_coordseqs_eql(self_data->geos_context, self_data->geom, RGEO_GEOMETRY_DATA_PTR(rhs)->geom, RGEO_FACTORY_DATA_PTR(self_data->factory)->flags & RGEO_FACTORYFLAGS_SUPPORTS_Z_OR_M);
348
+ result = rgeo_geos_geometries_strict_eql(self_data->geos_context, self_data->geom, RGEO_GEOMETRY_DATA_PTR(rhs)->geom);
349
349
  }
350
350
  return result;
351
351
  }
@@ -155,7 +155,7 @@ static VALUE method_point_eql(VALUE self, VALUE rhs)
155
155
  result = rgeo_geos_klasses_and_factories_eql(self, rhs);
156
156
  if (RTEST(result)) {
157
157
  self_data = RGEO_GEOMETRY_DATA_PTR(self);
158
- result = rgeo_geos_coordseqs_eql(self_data->geos_context, self_data->geom, RGEO_GEOMETRY_DATA_PTR(rhs)->geom, RGEO_FACTORY_DATA_PTR(self_data->factory)->flags & RGEO_FACTORYFLAGS_SUPPORTS_Z_OR_M);
158
+ result = rgeo_geos_geometries_strict_eql(self_data->geos_context, self_data->geom, RGEO_GEOMETRY_DATA_PTR(rhs)->geom);
159
159
  }
160
160
  return result;
161
161
  }
@@ -30,7 +30,7 @@ static VALUE method_polygon_eql(VALUE self, VALUE rhs)
30
30
  result = rgeo_geos_klasses_and_factories_eql(self, rhs);
31
31
  if (RTEST(result)) {
32
32
  self_data = RGEO_GEOMETRY_DATA_PTR(self);
33
- result = rgeo_geos_polygons_eql(self_data->geos_context, self_data->geom, RGEO_GEOMETRY_DATA_PTR(rhs)->geom, RGEO_FACTORY_DATA_PTR(self_data->factory)->flags & RGEO_FACTORYFLAGS_SUPPORTS_Z_OR_M);
33
+ result = rgeo_geos_geometries_strict_eql(self_data->geos_context, self_data->geom, RGEO_GEOMETRY_DATA_PTR(rhs)->geom);
34
34
  }
35
35
  return result;
36
36
  }
@@ -299,42 +299,6 @@ void rgeo_init_geos_polygon()
299
299
  rb_define_method(geos_polygon_methods, "coordinates", method_polygon_coordinates, 0);
300
300
  }
301
301
 
302
-
303
- VALUE rgeo_geos_polygons_eql(GEOSContextHandle_t context, const GEOSGeometry* geom1, const GEOSGeometry* geom2, char check_z)
304
- {
305
- VALUE result;
306
- int len1;
307
- int len2;
308
- int i;
309
-
310
- result = Qnil;
311
- if (geom1 && geom2) {
312
- result = rgeo_geos_coordseqs_eql(context, GEOSGetExteriorRing_r(context, geom1), GEOSGetExteriorRing_r(context, geom2), check_z);
313
- if (RTEST(result)) {
314
- len1 = GEOSGetNumInteriorRings_r(context, geom1);
315
- len2 = GEOSGetNumInteriorRings_r(context, geom2);
316
- if (len1 >= 0 && len2 >= 0) {
317
- if (len1 == len2) {
318
- for (i=0; i<len1; ++i) {
319
- result = rgeo_geos_coordseqs_eql(context, GEOSGetInteriorRingN_r(context, geom1, i), GEOSGetInteriorRingN_r(context, geom2, i), check_z);
320
- if (!RTEST(result)) {
321
- break;
322
- }
323
- }
324
- }
325
- else {
326
- result = Qfalse;
327
- }
328
- }
329
- else {
330
- result = Qnil;
331
- }
332
- }
333
- }
334
- return result;
335
- }
336
-
337
-
338
302
  st_index_t rgeo_geos_polygon_hash(GEOSContextHandle_t context, const GEOSGeometry* geom, st_index_t hash)
339
303
  {
340
304
  unsigned int len;
@@ -47,3 +47,6 @@
47
47
  #define RGEO_BEGIN_C
48
48
  #define RGEO_END_C
49
49
  #endif
50
+
51
+ // https://ozlabs.org/~rusty/index.cgi/tech/2008-04-01.html
52
+ #define streq(a, b) (!strcmp((a),(b)))