tg_geometry 0.2.0 → 0.3.0

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/README.md +107 -14
  4. data/benchmark/ewkb_roundtrip.rb +29 -0
  5. data/benchmark/geom_query.rb +33 -0
  6. data/benchmark/nearest_segment.rb +20 -0
  7. data/docs/GEOMETRY_QUERIES.md +23 -0
  8. data/docs/LIMITATIONS.md +12 -10
  9. data/docs/NEAREST_SEGMENT.md +17 -0
  10. data/docs/SRID_AND_EWKB.md +23 -0
  11. data/ext/tg_geometry/tg_geometry_ext.c +1176 -4
  12. data/lib/tg/geometry/active_record_source.rb +16 -34
  13. data/lib/tg/geometry/active_record_type.rb +61 -0
  14. data/lib/tg/geometry/registry.rb +17 -68
  15. data/lib/tg/geometry/version.rb +1 -1
  16. data/lib/tg/geometry.rb +85 -0
  17. data/spec/active_record_type_spec.rb +45 -0
  18. data/spec/constructors_spec.rb +104 -0
  19. data/spec/fixtures/feature_source/invalid_geometry_middle.geojson +8 -0
  20. data/spec/fixtures/feature_source/malformed_json.geojson +1 -0
  21. data/spec/fixtures/feature_source/mixed_geometry_types.geojson +8 -0
  22. data/spec/fixtures/feature_source/osm_like_feature_collection.geojson +10 -0
  23. data/spec/fixtures/feature_source/properties_null_missing.geojson +7 -0
  24. data/spec/fixtures/feature_source/simple_feature_collection.geojson +15 -0
  25. data/spec/fixtures/postgis/README.md +16 -0
  26. data/spec/fixtures/postgis/boundary_point_cases.geojson +83 -0
  27. data/spec/fixtures/postgis/multipolygon_large.ewkb +0 -0
  28. data/spec/fixtures/postgis/point_4326.ewkb +0 -0
  29. data/spec/fixtures/postgis/polygon_3857.ewkb +0 -0
  30. data/spec/fixtures/postgis/polygon_4326_simple.ewkb +0 -0
  31. data/spec/fixtures/postgis/polygon_4326_with_hole.ewkb +0 -0
  32. data/spec/index_geom_query_spec.rb +68 -0
  33. data/spec/keyword_validation_spec.rb +31 -0
  34. data/spec/nearest_segment_spec.rb +62 -0
  35. data/spec/postgis_fixtures_spec.rb +68 -0
  36. data/spec/srid_spec.rb +43 -0
  37. data/spec/to_ewkb_spec.rb +37 -0
  38. metadata +50 -2
@@ -49,6 +49,7 @@ static VALUE cTGGeometryLine;
49
49
  static VALUE cTGGeometryRing;
50
50
  static VALUE cTGGeometryPolygon;
51
51
  static VALUE cTGGeometrySegment;
52
+ static VALUE cTGGeometryNearestSegment;
52
53
  static VALUE eTGGeometryError;
53
54
  static VALUE eTGGeometryParseError;
54
55
  static VALUE eTGGeometryArgumentError;
@@ -59,6 +60,8 @@ typedef struct {
59
60
  struct tg_geom *geom;
60
61
  size_t geom_bytes;
61
62
  bool owned;
63
+ bool has_srid;
64
+ int srid;
62
65
  } tg_geom_wrapper_t;
63
66
 
64
67
  typedef struct {
@@ -89,6 +92,14 @@ typedef struct {
89
92
  bool initialized;
90
93
  } tg_segment_wrapper_t;
91
94
 
95
+ typedef struct {
96
+ struct tg_segment segment;
97
+ long index;
98
+ double distance;
99
+ struct tg_point point;
100
+ bool initialized;
101
+ } tg_nearest_segment_wrapper_t;
102
+
92
103
  enum tg_geometry_index_via {
93
104
  TG_GEOMETRY_INDEX_VIA_GEOM,
94
105
  TG_GEOMETRY_INDEX_VIA_GEOJSON,
@@ -105,6 +116,12 @@ enum tg_geometry_index_predicate {
105
116
  TG_GEOMETRY_INDEX_PREDICATE_CONTAINS,
106
117
  };
107
118
 
119
+ enum tg_geometry_geom_query_predicate {
120
+ TG_GEOMETRY_GEOM_QUERY_INTERSECTS,
121
+ TG_GEOMETRY_GEOM_QUERY_COVERS,
122
+ TG_GEOMETRY_GEOM_QUERY_CONTAINS,
123
+ };
124
+
108
125
  typedef struct {
109
126
  VALUE id;
110
127
  VALUE geom_owner;
@@ -143,6 +160,7 @@ static _Thread_local tg_index_t *tg_current_rtree_owner = NULL;
143
160
 
144
161
  static ID id_format;
145
162
  static ID id_index;
163
+ static ID id_srid;
146
164
  static ID id_auto;
147
165
  static ID id_geojson;
148
166
  static ID id_wkt;
@@ -179,6 +197,8 @@ static ID id_linestring;
179
197
  static ID id_multipoint;
180
198
  static ID id_multilinestring;
181
199
  static ID id_geometrycollection;
200
+ static ID id_exterior;
201
+ static ID id_holes;
182
202
 
183
203
  #ifdef TG_DEBUG_TEST
184
204
  static bool tg_debug_fail_next_entries_alloc = false;
@@ -227,6 +247,8 @@ static void geom_free(void *ptr) {
227
247
  w->geom_owner = Qnil;
228
248
  w->geom_bytes = 0;
229
249
  w->owned = false;
250
+ w->has_srid = false;
251
+ w->srid = 0;
230
252
 
231
253
  ruby_xfree(w);
232
254
  }
@@ -410,6 +432,29 @@ static const rb_data_type_t tg_segment_type = {
410
432
  RUBY_TYPED_FREE_IMMEDIATELY,
411
433
  };
412
434
 
435
+ static void nearest_segment_free(void *ptr) {
436
+ ruby_xfree(ptr);
437
+ }
438
+
439
+ static size_t nearest_segment_memsize(const void *ptr) {
440
+ (void)ptr;
441
+ return sizeof(tg_nearest_segment_wrapper_t);
442
+ }
443
+
444
+ static const rb_data_type_t tg_nearest_segment_type = {
445
+ "TG::Geometry::NearestSegment",
446
+ {
447
+ NULL,
448
+ nearest_segment_free,
449
+ nearest_segment_memsize,
450
+ NULL,
451
+ {0},
452
+ },
453
+ 0,
454
+ 0,
455
+ RUBY_TYPED_FREE_IMMEDIATELY,
456
+ };
457
+
413
458
  static void index_dispose(tg_index_t *idx) {
414
459
  if (!idx)
415
460
  return;
@@ -521,6 +566,62 @@ static const rb_data_type_t tg_index_type = {
521
566
  RUBY_TYPED_FREE_IMMEDIATELY,
522
567
  };
523
568
 
569
+ typedef struct {
570
+ const ID *allowed;
571
+ size_t count;
572
+ } tg_keyword_validation_args_t;
573
+
574
+ static bool kwargs_has_key(VALUE kwargs, ID key) {
575
+ if (NIL_P(kwargs)) {
576
+ return false;
577
+ }
578
+
579
+ return RTEST(rb_funcall(kwargs, rb_intern("key?"), 1, ID2SYM(key)));
580
+ }
581
+
582
+ static bool keyword_allowed_p(ID key, const ID *allowed, size_t count) {
583
+ for (size_t i = 0; i < count; i++) {
584
+ if (key == allowed[i]) {
585
+ return true;
586
+ }
587
+ }
588
+
589
+ return false;
590
+ }
591
+
592
+ static int validate_keyword_i(VALUE key, VALUE value, VALUE arg) {
593
+ tg_keyword_validation_args_t *args = (tg_keyword_validation_args_t *)arg;
594
+
595
+ (void)value;
596
+
597
+ if (!SYMBOL_P(key)) {
598
+ rb_raise(eTGGeometryArgumentError, "keyword must be a Symbol");
599
+ }
600
+
601
+ if (!keyword_allowed_p(SYM2ID(key), args->allowed, args->count)) {
602
+ const char *name = rb_id2name(SYM2ID(key));
603
+ rb_raise(eTGGeometryArgumentError, "unknown keyword: :%s", name ? name : "?");
604
+ }
605
+
606
+ return ST_CONTINUE;
607
+ }
608
+
609
+ static void validate_keywords(VALUE kwargs, const ID *allowed, size_t count) {
610
+ tg_keyword_validation_args_t args;
611
+
612
+ if (NIL_P(kwargs)) {
613
+ return;
614
+ }
615
+
616
+ if (!RB_TYPE_P(kwargs, T_HASH)) {
617
+ rb_raise(eTGGeometryArgumentError, "keywords must be a Hash");
618
+ }
619
+
620
+ args.allowed = allowed;
621
+ args.count = count;
622
+ rb_hash_foreach(kwargs, validate_keyword_i, (VALUE)&args);
623
+ }
624
+
524
625
  static VALUE kwargs_value(VALUE kwargs, ID key, VALUE fallback) {
525
626
  VALUE value;
526
627
 
@@ -588,6 +689,120 @@ static enum tg_geometry_parse_format parse_format_symbol(VALUE value) {
588
689
  "format: must be one of :auto, :geojson, :wkt, :wkb, :hex, :geobin");
589
690
  }
590
691
 
692
+ static uint32_t tg_geometry_read_u32(const uint8_t *bytes, uint8_t byte_order) {
693
+ if (byte_order == 0) {
694
+ return ((uint32_t)bytes[0] << 24) | ((uint32_t)bytes[1] << 16) | ((uint32_t)bytes[2] << 8) |
695
+ (uint32_t)bytes[3];
696
+ }
697
+
698
+ return (uint32_t)bytes[0] | ((uint32_t)bytes[1] << 8) | ((uint32_t)bytes[2] << 16) |
699
+ ((uint32_t)bytes[3] << 24);
700
+ }
701
+
702
+ static void tg_geometry_write_u32(uint8_t *bytes, uint32_t value, uint8_t byte_order) {
703
+ if (byte_order == 0) {
704
+ bytes[0] = (uint8_t)((value >> 24) & 0xff);
705
+ bytes[1] = (uint8_t)((value >> 16) & 0xff);
706
+ bytes[2] = (uint8_t)((value >> 8) & 0xff);
707
+ bytes[3] = (uint8_t)(value & 0xff);
708
+ return;
709
+ }
710
+
711
+ bytes[0] = (uint8_t)(value & 0xff);
712
+ bytes[1] = (uint8_t)((value >> 8) & 0xff);
713
+ bytes[2] = (uint8_t)((value >> 16) & 0xff);
714
+ bytes[3] = (uint8_t)((value >> 24) & 0xff);
715
+ }
716
+
717
+ static bool extract_ewkb_srid(const uint8_t *bytes, size_t len, bool *has_srid_out, int *srid_out) {
718
+ uint8_t byte_order;
719
+ uint32_t type;
720
+ uint32_t srid;
721
+
722
+ *has_srid_out = false;
723
+ *srid_out = 0;
724
+
725
+ if (len < 5) {
726
+ return false;
727
+ }
728
+
729
+ byte_order = bytes[0];
730
+ if (byte_order > 1) {
731
+ return false;
732
+ }
733
+
734
+ type = tg_geometry_read_u32(bytes + 1, byte_order);
735
+ if ((type & 0x20000000u) == 0) {
736
+ return true;
737
+ }
738
+
739
+ if (len < 9) {
740
+ return false;
741
+ }
742
+
743
+ srid = tg_geometry_read_u32(bytes + 5, byte_order);
744
+ if (srid > (uint32_t)INT_MAX) {
745
+ rb_raise(eTGGeometryParseError, "EWKB SRID is out of supported range");
746
+ }
747
+
748
+ *has_srid_out = true;
749
+ *srid_out = (int)srid;
750
+ return true;
751
+ }
752
+
753
+ static int tg_geometry_hex_digit(unsigned char ch) {
754
+ if (ch >= '0' && ch <= '9')
755
+ return (int)(ch - '0');
756
+ if (ch >= 'a' && ch <= 'f')
757
+ return (int)(ch - 'a' + 10);
758
+ if (ch >= 'A' && ch <= 'F')
759
+ return (int)(ch - 'A' + 10);
760
+ return -1;
761
+ }
762
+
763
+ static bool extract_hex_ewkb_srid(const char *hex, size_t len, bool *has_srid_out, int *srid_out) {
764
+ uint8_t header[9];
765
+ size_t bytes_to_decode;
766
+
767
+ *has_srid_out = false;
768
+ *srid_out = 0;
769
+
770
+ if (len < 10) {
771
+ return false;
772
+ }
773
+
774
+ bytes_to_decode = len >= 18 ? 9 : 5;
775
+ for (size_t i = 0; i < bytes_to_decode; i++) {
776
+ int hi = tg_geometry_hex_digit((unsigned char)hex[i * 2]);
777
+ int lo = tg_geometry_hex_digit((unsigned char)hex[i * 2 + 1]);
778
+ if (hi < 0 || lo < 0) {
779
+ return false;
780
+ }
781
+ header[i] = (uint8_t)((hi << 4) | lo);
782
+ }
783
+
784
+ return extract_ewkb_srid(header, bytes_to_decode, has_srid_out, srid_out);
785
+ }
786
+
787
+ static bool parse_srid_option(VALUE srid_value, int *srid_out) {
788
+ if (NIL_P(srid_value)) {
789
+ *srid_out = 0;
790
+ return false;
791
+ }
792
+
793
+ if (!RB_INTEGER_TYPE_P(srid_value)) {
794
+ rb_raise(eTGGeometryArgumentError, "srid: must be an Integer in range [0, 2^31 - 1]");
795
+ }
796
+
797
+ if (RTEST(rb_funcall(srid_value, rb_intern("<"), 1, INT2NUM(0))) ||
798
+ RTEST(rb_funcall(srid_value, rb_intern(">"), 1, INT2NUM(INT_MAX)))) {
799
+ rb_raise(eTGGeometryArgumentError, "srid: must be an Integer in range [0, 2^31 - 1]");
800
+ }
801
+
802
+ *srid_out = NUM2INT(srid_value);
803
+ return true;
804
+ }
805
+
591
806
  static VALUE required_kwargs_value(VALUE kwargs, ID key, const char *name) {
592
807
  VALUE value;
593
808
 
@@ -652,7 +867,7 @@ static enum tg_geometry_index_predicate parse_index_predicate_symbol(VALUE value
652
867
  rb_raise(eTGGeometryArgumentError, "predicate: must be one of :covers, :contains");
653
868
  }
654
869
 
655
- static VALUE geom_wrap_owned(struct tg_geom *geom) {
870
+ static VALUE geom_wrap_owned_with_srid(struct tg_geom *geom, bool has_srid, int srid) {
656
871
  tg_geom_wrapper_t *w;
657
872
  VALUE wrapper;
658
873
 
@@ -666,6 +881,8 @@ static VALUE geom_wrap_owned(struct tg_geom *geom) {
666
881
  w->geom = geom;
667
882
  w->geom_bytes = tg_geom_memsize(geom);
668
883
  w->owned = true;
884
+ w->has_srid = has_srid;
885
+ w->srid = has_srid ? srid : 0;
669
886
 
670
887
  if (w->geom_bytes > 0) {
671
888
  rb_gc_adjust_memory_usage((ssize_t)w->geom_bytes);
@@ -689,6 +906,8 @@ static VALUE geom_wrap_borrowed(VALUE geom_owner, const struct tg_geom *geom) {
689
906
  w->geom = (struct tg_geom *)geom;
690
907
  w->geom_bytes = 0;
691
908
  w->owned = false;
909
+ w->has_srid = false;
910
+ w->srid = 0;
692
911
 
693
912
  rb_obj_freeze(wrapper);
694
913
  RB_GC_GUARD(geom_owner);
@@ -764,6 +983,8 @@ static VALUE parse_string_with_format(VALUE input, enum tg_geometry_parse_format
764
983
  struct tg_geom *geom = NULL;
765
984
  const char *data;
766
985
  size_t len;
986
+ bool has_srid = false;
987
+ int srid = 0;
767
988
 
768
989
  StringValue(input);
769
990
  data = RSTRING_PTR(input);
@@ -780,9 +1001,11 @@ static VALUE parse_string_with_format(VALUE input, enum tg_geometry_parse_format
780
1001
  geom = tg_parse_wktn_ix(data, len, index);
781
1002
  break;
782
1003
  case TG_GEOMETRY_FORMAT_WKB:
1004
+ extract_ewkb_srid((const uint8_t *)data, len, &has_srid, &srid);
783
1005
  geom = tg_parse_wkb_ix((const uint8_t *)data, len, index);
784
1006
  break;
785
1007
  case TG_GEOMETRY_FORMAT_HEX:
1008
+ extract_hex_ewkb_srid(data, len, &has_srid, &srid);
786
1009
  geom = tg_parse_hexn_ix(data, len, index);
787
1010
  break;
788
1011
  case TG_GEOMETRY_FORMAT_GEOBIN:
@@ -791,7 +1014,7 @@ static VALUE parse_string_with_format(VALUE input, enum tg_geometry_parse_format
791
1014
  }
792
1015
 
793
1016
  raise_parse_error_from_geom(geom);
794
- return geom_wrap_owned(geom);
1017
+ return geom_wrap_owned_with_srid(geom, has_srid, srid);
795
1018
  }
796
1019
 
797
1020
  static VALUE rb_tg_geometry_parse(int argc, VALUE *argv, VALUE self) {
@@ -804,6 +1027,10 @@ static VALUE rb_tg_geometry_parse(int argc, VALUE *argv, VALUE self) {
804
1027
 
805
1028
  (void)self;
806
1029
  rb_scan_args(argc, argv, "1:", &input, &kwargs);
1030
+ {
1031
+ ID allowed[] = {id_format, id_index};
1032
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
1033
+ }
807
1034
 
808
1035
  format_value = kwargs_value(kwargs, id_format, ID2SYM(id_auto));
809
1036
  index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
@@ -821,6 +1048,10 @@ static VALUE rb_tg_geometry_parse_geojson(int argc, VALUE *argv, VALUE self) {
821
1048
 
822
1049
  (void)self;
823
1050
  rb_scan_args(argc, argv, "1:", &input, &kwargs);
1051
+ {
1052
+ ID allowed[] = {id_index};
1053
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
1054
+ }
824
1055
 
825
1056
  index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
826
1057
  index = parse_index_symbol(index_value);
@@ -836,6 +1067,10 @@ static VALUE rb_tg_geometry_parse_wkt(int argc, VALUE *argv, VALUE self) {
836
1067
 
837
1068
  (void)self;
838
1069
  rb_scan_args(argc, argv, "1:", &input, &kwargs);
1070
+ {
1071
+ ID allowed[] = {id_index};
1072
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
1073
+ }
839
1074
 
840
1075
  index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
841
1076
  index = parse_index_symbol(index_value);
@@ -851,6 +1086,10 @@ static VALUE rb_tg_geometry_parse_wkb(int argc, VALUE *argv, VALUE self) {
851
1086
 
852
1087
  (void)self;
853
1088
  rb_scan_args(argc, argv, "1:", &input, &kwargs);
1089
+ {
1090
+ ID allowed[] = {id_index};
1091
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
1092
+ }
854
1093
 
855
1094
  index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
856
1095
  index = parse_index_symbol(index_value);
@@ -866,6 +1105,10 @@ static VALUE rb_tg_geometry_parse_hex(int argc, VALUE *argv, VALUE self) {
866
1105
 
867
1106
  (void)self;
868
1107
  rb_scan_args(argc, argv, "1:", &input, &kwargs);
1108
+ {
1109
+ ID allowed[] = {id_index};
1110
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
1111
+ }
869
1112
 
870
1113
  index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
871
1114
  index = parse_index_symbol(index_value);
@@ -881,6 +1124,10 @@ static VALUE rb_tg_geometry_parse_geobin(int argc, VALUE *argv, VALUE self) {
881
1124
 
882
1125
  (void)self;
883
1126
  rb_scan_args(argc, argv, "1:", &input, &kwargs);
1127
+ {
1128
+ ID allowed[] = {id_index};
1129
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
1130
+ }
884
1131
 
885
1132
  index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
886
1133
  index = parse_index_symbol(index_value);
@@ -916,11 +1163,15 @@ static void validate_rect_coordinates(double min_x, double min_y, double max_x,
916
1163
  }
917
1164
  }
918
1165
 
919
- static VALUE wrap_constructed_geom(struct tg_geom *geom) {
1166
+ static VALUE wrap_constructed_geom_with_srid(struct tg_geom *geom, bool has_srid, int srid) {
920
1167
  raise_geom_error_and_free_as(geom, eTGGeometryError, "TG geometry allocation failed",
921
1168
  "TG geometry error message is too large",
922
1169
  "TG geometry error message allocation failed");
923
- return geom_wrap_owned(geom);
1170
+ return geom_wrap_owned_with_srid(geom, has_srid, srid);
1171
+ }
1172
+
1173
+ static VALUE wrap_constructed_geom(struct tg_geom *geom) {
1174
+ return wrap_constructed_geom_with_srid(geom, false, 0);
924
1175
  }
925
1176
 
926
1177
  static VALUE rb_tg_geometry_point(VALUE self, VALUE x_value, VALUE y_value) {
@@ -1023,6 +1274,395 @@ static VALUE rb_tg_geometry_empty_geometrycollection(VALUE self) {
1023
1274
  return wrap_constructed_geom(tg_geom_new_geometrycollection_empty());
1024
1275
  }
1025
1276
 
1277
+ typedef struct {
1278
+ VALUE points_value;
1279
+ struct tg_point *points;
1280
+ long len;
1281
+ const char *label;
1282
+ } tg_parse_points_args_t;
1283
+
1284
+ static VALUE parse_points_body(VALUE arg) {
1285
+ tg_parse_points_args_t *args = (tg_parse_points_args_t *)arg;
1286
+
1287
+ for (long i = 0; i < args->len; i++) {
1288
+ VALUE pair = rb_ary_entry(args->points_value, i);
1289
+ VALUE x_value;
1290
+ VALUE y_value;
1291
+ double x;
1292
+ double y;
1293
+
1294
+ if (!RB_TYPE_P(pair, T_ARRAY) || RARRAY_LEN(pair) != 2) {
1295
+ rb_raise(eTGGeometryArgumentError, "%s point %ld must be [x, y]", args->label, i);
1296
+ }
1297
+
1298
+ x_value = rb_ary_entry(pair, 0);
1299
+ y_value = rb_ary_entry(pair, 1);
1300
+ x = NUM2DBL(x_value);
1301
+ y = NUM2DBL(y_value);
1302
+ if (!isfinite(x)) {
1303
+ rb_raise(eTGGeometryArgumentError, "%s point %ld x must be finite", args->label, i);
1304
+ }
1305
+ if (!isfinite(y)) {
1306
+ rb_raise(eTGGeometryArgumentError, "%s point %ld y must be finite", args->label, i);
1307
+ }
1308
+
1309
+ args->points[i].x = x;
1310
+ args->points[i].y = y;
1311
+ }
1312
+
1313
+ return Qnil;
1314
+ }
1315
+
1316
+ static struct tg_point *parse_points_array(VALUE points_value, long *len_out, const char *label) {
1317
+ tg_parse_points_args_t args;
1318
+ int state = 0;
1319
+
1320
+ if (!RB_TYPE_P(points_value, T_ARRAY)) {
1321
+ rb_raise(rb_eTypeError, "%s must be an Array", label);
1322
+ }
1323
+
1324
+ args.len = RARRAY_LEN(points_value);
1325
+ args.points_value = points_value;
1326
+ args.label = label;
1327
+ args.points = NULL;
1328
+
1329
+ if (args.len > INT_MAX) {
1330
+ rb_raise(eTGGeometryArgumentError, "%s has too many points", label);
1331
+ }
1332
+
1333
+ if (args.len > 0) {
1334
+ args.points = (struct tg_point *)ruby_xcalloc((size_t)args.len, sizeof(struct tg_point));
1335
+ }
1336
+
1337
+ rb_protect(parse_points_body, (VALUE)&args, &state);
1338
+ if (state) {
1339
+ ruby_xfree(args.points);
1340
+ rb_jump_tag(state);
1341
+ }
1342
+
1343
+ *len_out = args.len;
1344
+ return args.points;
1345
+ }
1346
+
1347
+ static struct tg_ring *build_ring_from_ruby(VALUE ring_value, enum tg_index index,
1348
+ const char *label, long min_points,
1349
+ const char *min_message, const char *closed_message) {
1350
+ struct tg_point *points;
1351
+ struct tg_ring *ring;
1352
+ long len;
1353
+
1354
+ points = parse_points_array(ring_value, &len, label);
1355
+ if (len < min_points) {
1356
+ ruby_xfree(points);
1357
+ rb_raise(eTGGeometryArgumentError, "%s", min_message);
1358
+ }
1359
+ if (len <= 0 || points[0].x != points[len - 1].x || points[0].y != points[len - 1].y) {
1360
+ ruby_xfree(points);
1361
+ rb_raise(eTGGeometryArgumentError, "%s", closed_message);
1362
+ }
1363
+
1364
+ ring = tg_ring_new_ix(points, (int)len, index);
1365
+ ruby_xfree(points);
1366
+ if (!ring) {
1367
+ rb_raise(rb_eNoMemError, "TG ring allocation failed");
1368
+ }
1369
+
1370
+ return ring;
1371
+ }
1372
+
1373
+ typedef struct {
1374
+ VALUE exterior_value;
1375
+ VALUE holes_value;
1376
+ enum tg_index index;
1377
+ struct tg_ring *exterior;
1378
+ struct tg_ring **holes;
1379
+ long nholes;
1380
+ struct tg_poly *poly;
1381
+ } tg_build_poly_args_t;
1382
+
1383
+ static void build_poly_cleanup(tg_build_poly_args_t *args) {
1384
+ if (!args) {
1385
+ return;
1386
+ }
1387
+
1388
+ if (args->exterior) {
1389
+ tg_ring_free(args->exterior);
1390
+ args->exterior = NULL;
1391
+ }
1392
+
1393
+ if (args->holes) {
1394
+ for (long i = 0; i < args->nholes; i++) {
1395
+ if (args->holes[i]) {
1396
+ tg_ring_free(args->holes[i]);
1397
+ args->holes[i] = NULL;
1398
+ }
1399
+ }
1400
+ ruby_xfree(args->holes);
1401
+ args->holes = NULL;
1402
+ }
1403
+
1404
+ args->nholes = 0;
1405
+ }
1406
+
1407
+ static VALUE build_poly_body(VALUE arg) {
1408
+ tg_build_poly_args_t *args = (tg_build_poly_args_t *)arg;
1409
+
1410
+ args->exterior = build_ring_from_ruby(args->exterior_value, args->index, "polygon exterior", 4,
1411
+ "polygon exterior ring requires at least 4 points",
1412
+ "polygon exterior ring is not closed");
1413
+
1414
+ if (!NIL_P(args->holes_value) && !RB_TYPE_P(args->holes_value, T_ARRAY)) {
1415
+ rb_raise(rb_eTypeError, "holes: must be an Array");
1416
+ }
1417
+
1418
+ args->nholes = NIL_P(args->holes_value) ? 0 : RARRAY_LEN(args->holes_value);
1419
+ if (args->nholes > INT_MAX) {
1420
+ rb_raise(eTGGeometryArgumentError, "polygon has too many holes");
1421
+ }
1422
+
1423
+ if (args->nholes > 0) {
1424
+ args->holes =
1425
+ (struct tg_ring **)ruby_xcalloc((size_t)args->nholes, sizeof(struct tg_ring *));
1426
+ for (long i = 0; i < args->nholes; i++) {
1427
+ char label[64];
1428
+ char min_message[96];
1429
+ char closed_message[96];
1430
+ VALUE hole_value = rb_ary_entry(args->holes_value, i);
1431
+ snprintf(label, sizeof(label), "polygon hole %ld", i);
1432
+ snprintf(min_message, sizeof(min_message),
1433
+ "polygon hole %ld requires at least 4 points", i);
1434
+ snprintf(closed_message, sizeof(closed_message), "polygon hole %ld is not closed", i);
1435
+ args->holes[i] = build_ring_from_ruby(hole_value, args->index, label, 4, min_message,
1436
+ closed_message);
1437
+ }
1438
+ }
1439
+
1440
+ args->poly =
1441
+ tg_poly_new(args->exterior, (const struct tg_ring *const *)args->holes, (int)args->nholes);
1442
+ if (!args->poly) {
1443
+ rb_raise(rb_eNoMemError, "TG polygon allocation failed");
1444
+ }
1445
+
1446
+ return Qnil;
1447
+ }
1448
+
1449
+ static struct tg_poly *build_poly_from_ruby(VALUE exterior_value, VALUE holes_value,
1450
+ enum tg_index index) {
1451
+ tg_build_poly_args_t args;
1452
+ int state = 0;
1453
+
1454
+ args.exterior_value = exterior_value;
1455
+ args.holes_value = holes_value;
1456
+ args.index = index;
1457
+ args.exterior = NULL;
1458
+ args.holes = NULL;
1459
+ args.nholes = 0;
1460
+ args.poly = NULL;
1461
+
1462
+ rb_protect(build_poly_body, (VALUE)&args, &state);
1463
+ build_poly_cleanup(&args);
1464
+
1465
+ if (state) {
1466
+ rb_jump_tag(state);
1467
+ }
1468
+
1469
+ return args.poly;
1470
+ }
1471
+
1472
+ static VALUE rb_tg_geometry_line_string(int argc, VALUE *argv, VALUE self) {
1473
+ VALUE points_value;
1474
+ VALUE kwargs;
1475
+ VALUE index_value;
1476
+ VALUE srid_value;
1477
+ enum tg_index index;
1478
+ bool has_srid;
1479
+ int srid;
1480
+ struct tg_point *points;
1481
+ long len;
1482
+ struct tg_line *line;
1483
+ struct tg_geom *geom;
1484
+
1485
+ (void)self;
1486
+ rb_scan_args(argc, argv, "1:", &points_value, &kwargs);
1487
+ {
1488
+ ID allowed[] = {id_index, id_srid};
1489
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
1490
+ }
1491
+
1492
+ index_value = kwargs_value(kwargs, id_index, ID2SYM(id_natural));
1493
+ srid_value = kwargs_value(kwargs, id_srid, Qnil);
1494
+ index = parse_index_symbol(index_value);
1495
+ has_srid = parse_srid_option(srid_value, &srid);
1496
+
1497
+ points = parse_points_array(points_value, &len, "line_string");
1498
+ if (len < 2) {
1499
+ ruby_xfree(points);
1500
+ rb_raise(eTGGeometryArgumentError, "line_string requires at least 2 points, got %ld", len);
1501
+ }
1502
+
1503
+ line = tg_line_new_ix(points, (int)len, index);
1504
+ ruby_xfree(points);
1505
+ if (!line) {
1506
+ rb_raise(rb_eNoMemError, "TG line allocation failed");
1507
+ }
1508
+
1509
+ geom = tg_geom_new_linestring(line);
1510
+ tg_line_free(line);
1511
+ return wrap_constructed_geom_with_srid(geom, has_srid, srid);
1512
+ }
1513
+
1514
+ static VALUE rb_tg_geometry_polygon(int argc, VALUE *argv, VALUE self) {
1515
+ VALUE exterior_value;
1516
+ VALUE kwargs;
1517
+ VALUE holes_value;
1518
+ VALUE index_value;
1519
+ VALUE srid_value;
1520
+ enum tg_index index;
1521
+ bool has_srid;
1522
+ int srid;
1523
+ struct tg_poly *poly;
1524
+ struct tg_geom *geom;
1525
+
1526
+ (void)self;
1527
+ rb_scan_args(argc, argv, "1:", &exterior_value, &kwargs);
1528
+ {
1529
+ ID allowed[] = {id_holes, id_index, id_srid};
1530
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
1531
+ }
1532
+
1533
+ holes_value = kwargs_value(kwargs, id_holes, Qnil);
1534
+ index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
1535
+ srid_value = kwargs_value(kwargs, id_srid, Qnil);
1536
+ index = parse_index_symbol(index_value);
1537
+ has_srid = parse_srid_option(srid_value, &srid);
1538
+
1539
+ poly = build_poly_from_ruby(exterior_value, holes_value, index);
1540
+ geom = tg_geom_new_polygon(poly);
1541
+ tg_poly_free(poly);
1542
+
1543
+ return wrap_constructed_geom_with_srid(geom, has_srid, srid);
1544
+ }
1545
+
1546
+ typedef struct {
1547
+ VALUE polygons_value;
1548
+ enum tg_index index;
1549
+ long npolys;
1550
+ struct tg_poly **polys;
1551
+ long built;
1552
+ struct tg_geom *geom;
1553
+ } tg_build_multipolygon_args_t;
1554
+
1555
+ static void build_multipolygon_cleanup(tg_build_multipolygon_args_t *args) {
1556
+ if (!args || !args->polys) {
1557
+ return;
1558
+ }
1559
+
1560
+ for (long i = 0; i < args->built; i++) {
1561
+ if (args->polys[i]) {
1562
+ tg_poly_free(args->polys[i]);
1563
+ args->polys[i] = NULL;
1564
+ }
1565
+ }
1566
+
1567
+ ruby_xfree(args->polys);
1568
+ args->polys = NULL;
1569
+ args->built = 0;
1570
+ }
1571
+
1572
+ static VALUE build_multipolygon_body(VALUE arg) {
1573
+ tg_build_multipolygon_args_t *args = (tg_build_multipolygon_args_t *)arg;
1574
+
1575
+ args->polys = (struct tg_poly **)ruby_xcalloc((size_t)args->npolys, sizeof(struct tg_poly *));
1576
+
1577
+ for (long i = 0; i < args->npolys; i++) {
1578
+ VALUE item = rb_ary_entry(args->polygons_value, i);
1579
+ VALUE exterior_value;
1580
+ VALUE holes_value = Qnil;
1581
+
1582
+ if (RB_TYPE_P(item, T_HASH)) {
1583
+ ID allowed[] = {id_exterior, id_holes};
1584
+ validate_keywords(item, allowed, sizeof(allowed) / sizeof(allowed[0]));
1585
+
1586
+ exterior_value = rb_hash_aref(item, ID2SYM(id_exterior));
1587
+ holes_value = kwargs_value(item, id_holes, Qnil);
1588
+ if (NIL_P(exterior_value)) {
1589
+ rb_raise(eTGGeometryArgumentError, "multi_polygon polygon %ld requires :exterior",
1590
+ i);
1591
+ }
1592
+ } else if (RB_TYPE_P(item, T_ARRAY)) {
1593
+ exterior_value = item;
1594
+ } else {
1595
+ rb_raise(rb_eTypeError, "multi_polygon polygon %ld must be a Hash or Array", i);
1596
+ }
1597
+
1598
+ args->polys[i] = build_poly_from_ruby(exterior_value, holes_value, args->index);
1599
+ args->built = i + 1;
1600
+ }
1601
+
1602
+ args->geom =
1603
+ tg_geom_new_multipolygon((const struct tg_poly *const *)args->polys, (int)args->npolys);
1604
+ if (!args->geom) {
1605
+ rb_raise(rb_eNoMemError, "TG multipolygon allocation failed");
1606
+ }
1607
+
1608
+ return Qnil;
1609
+ }
1610
+
1611
+ static VALUE rb_tg_geometry_multi_polygon(int argc, VALUE *argv, VALUE self) {
1612
+ VALUE polygons_value;
1613
+ VALUE kwargs;
1614
+ VALUE index_value;
1615
+ VALUE srid_value;
1616
+ enum tg_index index;
1617
+ bool has_srid;
1618
+ int srid;
1619
+ long npolys;
1620
+ tg_build_multipolygon_args_t args;
1621
+ int state = 0;
1622
+
1623
+ (void)self;
1624
+ rb_scan_args(argc, argv, "1:", &polygons_value, &kwargs);
1625
+ {
1626
+ ID allowed[] = {id_index, id_srid};
1627
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
1628
+ }
1629
+
1630
+ if (!RB_TYPE_P(polygons_value, T_ARRAY)) {
1631
+ rb_raise(rb_eTypeError, "polygons must be an Array");
1632
+ }
1633
+
1634
+ index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
1635
+ srid_value = kwargs_value(kwargs, id_srid, Qnil);
1636
+ index = parse_index_symbol(index_value);
1637
+ has_srid = parse_srid_option(srid_value, &srid);
1638
+
1639
+ npolys = RARRAY_LEN(polygons_value);
1640
+ if (npolys > INT_MAX) {
1641
+ rb_raise(eTGGeometryArgumentError, "multi_polygon has too many polygons");
1642
+ }
1643
+
1644
+ if (npolys == 0) {
1645
+ return wrap_constructed_geom_with_srid(tg_geom_new_multipolygon_empty(), has_srid, srid);
1646
+ }
1647
+
1648
+ args.polygons_value = polygons_value;
1649
+ args.index = index;
1650
+ args.npolys = npolys;
1651
+ args.polys = NULL;
1652
+ args.built = 0;
1653
+ args.geom = NULL;
1654
+
1655
+ rb_protect(build_multipolygon_body, (VALUE)&args, &state);
1656
+ build_multipolygon_cleanup(&args);
1657
+
1658
+ if (state) {
1659
+ rb_jump_tag(state);
1660
+ }
1661
+
1662
+ RB_GC_GUARD(polygons_value);
1663
+ return wrap_constructed_geom_with_srid(args.geom, has_srid, srid);
1664
+ }
1665
+
1026
1666
  static VALUE rect_build(double min_x, double min_y, double max_x, double max_y) {
1027
1667
  tg_rect_wrapper_t *rect_data;
1028
1668
  VALUE rect;
@@ -1313,6 +1953,21 @@ static VALUE rb_tg_geometry_rect_expand_to_include_point(VALUE self, VALUE x_val
1313
1953
  return rect_build(min_x, min_y, max_x, max_y);
1314
1954
  }
1315
1955
 
1956
+ static int parse_required_srid_value(VALUE srid_value) {
1957
+ int srid;
1958
+
1959
+ if (NIL_P(srid_value) || !parse_srid_option(srid_value, &srid)) {
1960
+ rb_raise(eTGGeometryArgumentError, "srid: must be an Integer in range [0, 2^31 - 1]");
1961
+ }
1962
+
1963
+ return srid;
1964
+ }
1965
+
1966
+ static VALUE rb_tg_geometry_geom_srid(VALUE self) {
1967
+ tg_geom_wrapper_t *w = get_geom_wrapper(self);
1968
+ return w->has_srid ? INT2NUM(w->srid) : Qnil;
1969
+ }
1970
+
1316
1971
  static VALUE rb_tg_geometry_geom_type(VALUE self) {
1317
1972
  tg_geom_wrapper_t *w = get_geom_wrapper(self);
1318
1973
 
@@ -1511,6 +2166,96 @@ static VALUE rb_tg_geometry_geom_to_wkb(VALUE self) {
1511
2166
  return binary_writer_result(tg_geom_wkb, w->geom, "WKB");
1512
2167
  }
1513
2168
 
2169
+ typedef struct {
2170
+ long len;
2171
+ } tg_str_alloc_args_t;
2172
+
2173
+ static VALUE tg_str_new_binary_body(VALUE arg) {
2174
+ tg_str_alloc_args_t *args = (tg_str_alloc_args_t *)arg;
2175
+ VALUE str = rb_str_new(NULL, args->len);
2176
+ rb_enc_associate(str, rb_ascii8bit_encoding());
2177
+ return str;
2178
+ }
2179
+
2180
+ static VALUE rb_tg_geometry_geom_to_ewkb(int argc, VALUE *argv, VALUE self) {
2181
+ tg_geom_wrapper_t *w = get_geom_wrapper(self);
2182
+ VALUE kwargs;
2183
+ VALUE srid_value;
2184
+ VALUE out;
2185
+ tg_str_alloc_args_t str_args;
2186
+ uint8_t *plain_buf;
2187
+ uint8_t *out_buf;
2188
+ size_t required;
2189
+ size_t written;
2190
+ uint8_t byte_order;
2191
+ uint32_t type;
2192
+ int effective_srid;
2193
+ bool explicit_srid;
2194
+ int state = 0;
2195
+
2196
+ rb_scan_args(argc, argv, "0:", &kwargs);
2197
+ {
2198
+ ID allowed[] = {id_srid};
2199
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
2200
+ }
2201
+ explicit_srid = kwargs_has_key(kwargs, id_srid);
2202
+
2203
+ if (explicit_srid) {
2204
+ srid_value = rb_hash_aref(kwargs, ID2SYM(id_srid));
2205
+ effective_srid = parse_required_srid_value(srid_value);
2206
+ } else if (w->has_srid) {
2207
+ effective_srid = w->srid;
2208
+ } else {
2209
+ rb_raise(eTGGeometryArgumentError, "to_ewkb requires srid (geom has no srid metadata)");
2210
+ }
2211
+
2212
+ required = tg_geom_wkb(w->geom, NULL, 0);
2213
+ if (required < 5) {
2214
+ rb_raise(eTGGeometryError, "TG WKB writer produced an invalid header");
2215
+ }
2216
+ if (required > (size_t)LONG_MAX - 4) {
2217
+ rb_raise(rb_eNoMemError, "serialized EWKB output is too large");
2218
+ }
2219
+
2220
+ plain_buf = (uint8_t *)ruby_xmalloc(required);
2221
+ written = tg_geom_wkb(w->geom, plain_buf, required);
2222
+ if (written != required) {
2223
+ ruby_xfree(plain_buf);
2224
+ rb_raise(eTGGeometryError, "TG WKB writer size changed during serialization");
2225
+ }
2226
+
2227
+ byte_order = plain_buf[0];
2228
+ if (byte_order > 1) {
2229
+ ruby_xfree(plain_buf);
2230
+ rb_raise(eTGGeometryError, "TG WKB writer produced invalid byte order");
2231
+ }
2232
+
2233
+ type = tg_geometry_read_u32(plain_buf + 1, byte_order);
2234
+ if ((type & 0x20000000u) != 0) {
2235
+ ruby_xfree(plain_buf);
2236
+ rb_raise(eTGGeometryError, "TG WKB writer unexpectedly produced EWKB SRID flag");
2237
+ }
2238
+ type |= 0x20000000u;
2239
+
2240
+ str_args.len = (long)(required + 4);
2241
+ out = rb_protect(tg_str_new_binary_body, (VALUE)&str_args, &state);
2242
+ if (state) {
2243
+ ruby_xfree(plain_buf);
2244
+ rb_jump_tag(state);
2245
+ }
2246
+
2247
+ out_buf = (uint8_t *)RSTRING_PTR(out);
2248
+ out_buf[0] = byte_order;
2249
+ tg_geometry_write_u32(out_buf + 1, type, byte_order);
2250
+ tg_geometry_write_u32(out_buf + 5, (uint32_t)effective_srid, byte_order);
2251
+ memcpy(out_buf + 9, plain_buf + 5, required - 5);
2252
+ ruby_xfree(plain_buf);
2253
+
2254
+ rb_obj_freeze(out);
2255
+ RB_GC_GUARD(out);
2256
+ return out;
2257
+ }
2258
+
1514
2259
  static VALUE rb_tg_geometry_geom_to_hex(VALUE self) {
1515
2260
  tg_geom_wrapper_t *w = get_geom_wrapper(self);
1516
2261
  return text_writer_result(tg_geom_hex, w->geom);
@@ -1955,6 +2700,210 @@ static VALUE rb_tg_geometry_segment_intersects_p(VALUE self, VALUE other) {
1955
2700
  return tg_segment_intersects_segment(w->segment, other_w->segment) ? Qtrue : Qfalse;
1956
2701
  }
1957
2702
 
2703
+ typedef struct {
2704
+ struct tg_point query;
2705
+ struct tg_segment best_segment;
2706
+ long best_index;
2707
+ double best_distance;
2708
+ bool found;
2709
+ } tg_nearest_segment_ctx_t;
2710
+
2711
+ static tg_nearest_segment_wrapper_t *get_nearest_segment_wrapper(VALUE value) {
2712
+ tg_nearest_segment_wrapper_t *w;
2713
+
2714
+ TypedData_Get_Struct(value, tg_nearest_segment_wrapper_t, &tg_nearest_segment_type, w);
2715
+ if (!w || !w->initialized) {
2716
+ rb_raise(eTGGeometryArgumentError, "invalid TG::Geometry::NearestSegment");
2717
+ }
2718
+
2719
+ return w;
2720
+ }
2721
+
2722
+ static double point_to_segment_distance(struct tg_point p, struct tg_segment seg) {
2723
+ double ax = seg.a.x;
2724
+ double ay = seg.a.y;
2725
+ double bx = seg.b.x;
2726
+ double by = seg.b.y;
2727
+ double dx = bx - ax;
2728
+ double dy = by - ay;
2729
+ double len_sq = dx * dx + dy * dy;
2730
+ double t;
2731
+ double proj_x;
2732
+ double proj_y;
2733
+
2734
+ if (len_sq == 0.0) {
2735
+ return hypot(p.x - ax, p.y - ay);
2736
+ }
2737
+
2738
+ t = ((p.x - ax) * dx + (p.y - ay) * dy) / len_sq;
2739
+ if (t < 0.0)
2740
+ t = 0.0;
2741
+ if (t > 1.0)
2742
+ t = 1.0;
2743
+
2744
+ proj_x = ax + t * dx;
2745
+ proj_y = ay + t * dy;
2746
+ return hypot(p.x - proj_x, p.y - proj_y);
2747
+ }
2748
+
2749
+ static struct tg_point project_point_onto_segment(struct tg_point p, struct tg_segment seg) {
2750
+ double ax = seg.a.x;
2751
+ double ay = seg.a.y;
2752
+ double bx = seg.b.x;
2753
+ double by = seg.b.y;
2754
+ double dx = bx - ax;
2755
+ double dy = by - ay;
2756
+ double len_sq = dx * dx + dy * dy;
2757
+ double t;
2758
+ struct tg_point projected;
2759
+
2760
+ if (len_sq == 0.0) {
2761
+ projected.x = ax;
2762
+ projected.y = ay;
2763
+ return projected;
2764
+ }
2765
+
2766
+ t = ((p.x - ax) * dx + (p.y - ay) * dy) / len_sq;
2767
+ if (t < 0.0)
2768
+ t = 0.0;
2769
+ if (t > 1.0)
2770
+ t = 1.0;
2771
+
2772
+ projected.x = ax + t * dx;
2773
+ projected.y = ay + t * dy;
2774
+ return projected;
2775
+ }
2776
+
2777
+ static double nearest_rect_distance(struct tg_rect rect, int *more, void *udata) {
2778
+ tg_nearest_segment_ctx_t *ctx = (tg_nearest_segment_ctx_t *)udata;
2779
+ double dx = 0.0;
2780
+ double dy = 0.0;
2781
+
2782
+ *more = 0;
2783
+
2784
+ if (ctx->query.x < rect.min.x) {
2785
+ dx = rect.min.x - ctx->query.x;
2786
+ } else if (ctx->query.x > rect.max.x) {
2787
+ dx = ctx->query.x - rect.max.x;
2788
+ }
2789
+
2790
+ if (ctx->query.y < rect.min.y) {
2791
+ dy = rect.min.y - ctx->query.y;
2792
+ } else if (ctx->query.y > rect.max.y) {
2793
+ dy = ctx->query.y - rect.max.y;
2794
+ }
2795
+
2796
+ return hypot(dx, dy);
2797
+ }
2798
+
2799
+ static double nearest_segment_distance(struct tg_segment seg, int *more, void *udata) {
2800
+ tg_nearest_segment_ctx_t *ctx = (tg_nearest_segment_ctx_t *)udata;
2801
+ *more = 0;
2802
+ return point_to_segment_distance(ctx->query, seg);
2803
+ }
2804
+
2805
+ static bool nearest_segment_iter(struct tg_segment seg, double dist, int index, void *udata) {
2806
+ tg_nearest_segment_ctx_t *ctx = (tg_nearest_segment_ctx_t *)udata;
2807
+
2808
+ if (!ctx->found) {
2809
+ ctx->best_segment = seg;
2810
+ ctx->best_index = (long)index;
2811
+ ctx->best_distance = dist;
2812
+ ctx->found = true;
2813
+ return false;
2814
+ }
2815
+
2816
+ return true;
2817
+ }
2818
+
2819
+ static VALUE nearest_segment_wrap_value(tg_nearest_segment_ctx_t *ctx) {
2820
+ tg_nearest_segment_wrapper_t *w;
2821
+ VALUE wrapper = TypedData_Make_Struct(cTGGeometryNearestSegment, tg_nearest_segment_wrapper_t,
2822
+ &tg_nearest_segment_type, w);
2823
+
2824
+ w->segment = ctx->best_segment;
2825
+ w->index = ctx->best_index;
2826
+ w->distance = ctx->best_distance;
2827
+ w->point = project_point_onto_segment(ctx->query, ctx->best_segment);
2828
+ w->initialized = true;
2829
+
2830
+ rb_obj_freeze(wrapper);
2831
+ RB_GC_GUARD(wrapper);
2832
+ return wrapper;
2833
+ }
2834
+
2835
+ static VALUE rb_tg_geometry_line_nearest_segment(VALUE self, VALUE x_value, VALUE y_value) {
2836
+ tg_line_wrapper_t *w = get_line_wrapper(self);
2837
+ tg_nearest_segment_ctx_t ctx;
2838
+ bool ok;
2839
+
2840
+ ctx.query.x = NUM2DBL(x_value);
2841
+ ctx.query.y = NUM2DBL(y_value);
2842
+ check_finite_double(ctx.query.x, "x");
2843
+ check_finite_double(ctx.query.y, "y");
2844
+ ctx.best_index = -1;
2845
+ ctx.best_distance = INFINITY;
2846
+ ctx.found = false;
2847
+
2848
+ ok = tg_line_nearest_segment(w->line, nearest_rect_distance, nearest_segment_distance,
2849
+ nearest_segment_iter, &ctx);
2850
+ if (!ok) {
2851
+ rb_raise(rb_eNoMemError, "nearest segment search failed");
2852
+ }
2853
+ if (!ctx.found) {
2854
+ return Qnil;
2855
+ }
2856
+
2857
+ RB_GC_GUARD(self);
2858
+ return nearest_segment_wrap_value(&ctx);
2859
+ }
2860
+
2861
+ static VALUE rb_tg_geometry_ring_nearest_segment(VALUE self, VALUE x_value, VALUE y_value) {
2862
+ tg_ring_wrapper_t *w = get_ring_wrapper(self);
2863
+ tg_nearest_segment_ctx_t ctx;
2864
+ bool ok;
2865
+
2866
+ ctx.query.x = NUM2DBL(x_value);
2867
+ ctx.query.y = NUM2DBL(y_value);
2868
+ check_finite_double(ctx.query.x, "x");
2869
+ check_finite_double(ctx.query.y, "y");
2870
+ ctx.best_index = -1;
2871
+ ctx.best_distance = INFINITY;
2872
+ ctx.found = false;
2873
+
2874
+ ok = tg_ring_nearest_segment(w->ring, nearest_rect_distance, nearest_segment_distance,
2875
+ nearest_segment_iter, &ctx);
2876
+ if (!ok) {
2877
+ rb_raise(rb_eNoMemError, "nearest segment search failed");
2878
+ }
2879
+ if (!ctx.found) {
2880
+ return Qnil;
2881
+ }
2882
+
2883
+ RB_GC_GUARD(self);
2884
+ return nearest_segment_wrap_value(&ctx);
2885
+ }
2886
+
2887
+ static VALUE rb_tg_geometry_nearest_segment_segment(VALUE self) {
2888
+ tg_nearest_segment_wrapper_t *w = get_nearest_segment_wrapper(self);
2889
+ return segment_wrap_value(w->segment);
2890
+ }
2891
+
2892
+ static VALUE rb_tg_geometry_nearest_segment_index(VALUE self) {
2893
+ tg_nearest_segment_wrapper_t *w = get_nearest_segment_wrapper(self);
2894
+ return LONG2NUM(w->index);
2895
+ }
2896
+
2897
+ static VALUE rb_tg_geometry_nearest_segment_distance(VALUE self) {
2898
+ tg_nearest_segment_wrapper_t *w = get_nearest_segment_wrapper(self);
2899
+ return rb_float_new(w->distance);
2900
+ }
2901
+
2902
+ static VALUE rb_tg_geometry_nearest_segment_point(VALUE self) {
2903
+ tg_nearest_segment_wrapper_t *w = get_nearest_segment_wrapper(self);
2904
+ return point_array_from_tg_point(w->point);
2905
+ }
2906
+
1958
2907
  static tg_index_t *get_index_wrapper(VALUE value) {
1959
2908
  tg_index_t *idx;
1960
2909
 
@@ -2406,6 +3355,200 @@ static unsigned char *index_intersecting_rect_marks(tg_index_t *idx, struct tg_r
2406
3355
  return marks;
2407
3356
  }
2408
3357
 
3358
+ static bool index_geom_query_matches(const struct tg_geom *stored_geom,
3359
+ const struct tg_geom *query_geom,
3360
+ enum tg_geometry_geom_query_predicate predicate) {
3361
+ switch (predicate) {
3362
+ case TG_GEOMETRY_GEOM_QUERY_INTERSECTS:
3363
+ return tg_geom_intersects(stored_geom, query_geom);
3364
+ case TG_GEOMETRY_GEOM_QUERY_COVERS:
3365
+ return tg_geom_covers(stored_geom, query_geom);
3366
+ case TG_GEOMETRY_GEOM_QUERY_CONTAINS:
3367
+ return tg_geom_contains(stored_geom, query_geom);
3368
+ }
3369
+
3370
+ return false;
3371
+ }
3372
+
3373
+ enum tg_index_geom_query_status {
3374
+ TG_INDEX_GEOM_QUERY_OK = 0,
3375
+ TG_INDEX_GEOM_QUERY_MATCH_ALLOC_FAILED,
3376
+ TG_INDEX_GEOM_QUERY_RESULT_OVERFLOW,
3377
+ TG_INDEX_GEOM_QUERY_RESULT_ALLOC_FAILED,
3378
+ };
3379
+
3380
+ static const char *index_geom_query_status_message(enum tg_index_geom_query_status status) {
3381
+ switch (status) {
3382
+ case TG_INDEX_GEOM_QUERY_MATCH_ALLOC_FAILED:
3383
+ return "match buffer allocation failed";
3384
+ case TG_INDEX_GEOM_QUERY_RESULT_OVERFLOW:
3385
+ return "result buffer allocation size overflow";
3386
+ case TG_INDEX_GEOM_QUERY_RESULT_ALLOC_FAILED:
3387
+ return "result buffer allocation failed";
3388
+ case TG_INDEX_GEOM_QUERY_OK:
3389
+ return "ok";
3390
+ }
3391
+
3392
+ return "geometry query failed";
3393
+ }
3394
+
3395
+ static enum tg_index_geom_query_status rtree_candidate_marks_no_raise(tg_index_t *idx,
3396
+ struct tg_rect query_rect,
3397
+ unsigned char **marks_out) {
3398
+ unsigned char *marks;
3399
+ tg_rtree_mark_args_t args;
3400
+ double min[2];
3401
+ double max[2];
3402
+
3403
+ *marks_out = NULL;
3404
+
3405
+ if (idx->len <= 0) {
3406
+ return TG_INDEX_GEOM_QUERY_OK;
3407
+ }
3408
+
3409
+ marks = alloc_match_marks_raw(idx->len);
3410
+ if (!marks) {
3411
+ return TG_INDEX_GEOM_QUERY_MATCH_ALLOC_FAILED;
3412
+ }
3413
+
3414
+ if (idx->rtree) {
3415
+ args.marks = marks;
3416
+ args.len = idx->len;
3417
+ tg_rect_to_arrays(query_rect, min, max);
3418
+ rtree_search(idx->rtree, min, max, rtree_mark_candidate_iter, &args);
3419
+ }
3420
+
3421
+ *marks_out = marks;
3422
+ return TG_INDEX_GEOM_QUERY_OK;
3423
+ }
3424
+
3425
+ static enum tg_index_geom_query_status
3426
+ index_geom_query_collect(tg_index_t *idx, const struct tg_geom *query_geom,
3427
+ struct tg_rect query_rect, enum tg_geometry_geom_query_predicate predicate,
3428
+ long **indices_out, long *count_out) {
3429
+ unsigned char *marks = NULL;
3430
+ long *indices = NULL;
3431
+ long count = 0;
3432
+ enum tg_index_geom_query_status status;
3433
+
3434
+ *indices_out = NULL;
3435
+ *count_out = 0;
3436
+
3437
+ if (idx->strategy == TG_GEOMETRY_INDEX_STRATEGY_RTREE) {
3438
+ status = rtree_candidate_marks_no_raise(idx, query_rect, &marks);
3439
+ if (status != TG_INDEX_GEOM_QUERY_OK) {
3440
+ return status;
3441
+ }
3442
+ } else {
3443
+ marks = alloc_match_marks_raw(idx->len);
3444
+ if (!marks) {
3445
+ return TG_INDEX_GEOM_QUERY_MATCH_ALLOC_FAILED;
3446
+ }
3447
+
3448
+ for (long i = 0; i < idx->len; i++) {
3449
+ if (tg_rect_intersects_rect(idx->entries[i].bbox, query_rect)) {
3450
+ marks[i] = 1;
3451
+ }
3452
+ }
3453
+ }
3454
+
3455
+ if (idx->len < 0 || (size_t)idx->len > SIZE_MAX / sizeof(long)) {
3456
+ free(marks);
3457
+ return TG_INDEX_GEOM_QUERY_RESULT_OVERFLOW;
3458
+ }
3459
+
3460
+ indices = (long *)calloc((size_t)idx->len, sizeof(long));
3461
+ if (!indices) {
3462
+ free(marks);
3463
+ return TG_INDEX_GEOM_QUERY_RESULT_ALLOC_FAILED;
3464
+ }
3465
+
3466
+ for (long i = 0; i < idx->len; i++) {
3467
+ if (!marks[i]) {
3468
+ continue;
3469
+ }
3470
+ if (index_geom_query_matches(idx->entries[i].geom, query_geom, predicate)) {
3471
+ indices[count++] = i;
3472
+ }
3473
+ }
3474
+
3475
+ free(marks);
3476
+ *indices_out = indices;
3477
+ *count_out = count;
3478
+ return TG_INDEX_GEOM_QUERY_OK;
3479
+ }
3480
+
3481
+ typedef struct {
3482
+ tg_index_t *idx;
3483
+ long *indices;
3484
+ long count;
3485
+ } tg_build_ids_from_indices_args_t;
3486
+
3487
+ static VALUE build_ids_from_indices_body(VALUE arg) {
3488
+ tg_build_ids_from_indices_args_t *args = (tg_build_ids_from_indices_args_t *)arg;
3489
+ VALUE result = rb_ary_new_capa(args->count);
3490
+
3491
+ for (long i = 0; i < args->count; i++) {
3492
+ rb_ary_push(result, args->idx->entries[args->indices[i]].id);
3493
+ }
3494
+
3495
+ return result;
3496
+ }
3497
+
3498
+ static VALUE build_ids_from_indices_protected(tg_index_t *idx, long *indices, long count) {
3499
+ tg_build_ids_from_indices_args_t args;
3500
+ VALUE result;
3501
+ int state = 0;
3502
+
3503
+ args.idx = idx;
3504
+ args.indices = indices;
3505
+ args.count = count;
3506
+ result = rb_protect(build_ids_from_indices_body, (VALUE)&args, &state);
3507
+ free(indices);
3508
+
3509
+ if (state) {
3510
+ rb_jump_tag(state);
3511
+ }
3512
+
3513
+ return result;
3514
+ }
3515
+
3516
+ static VALUE index_geom_query_ids(VALUE self, VALUE geom_value,
3517
+ enum tg_geometry_geom_query_predicate predicate) {
3518
+ tg_index_t *idx = get_index_wrapper(self);
3519
+ tg_geom_wrapper_t *query_wrapper = get_geom_wrapper(geom_value);
3520
+ struct tg_geom *query_geom = query_wrapper->geom;
3521
+ struct tg_rect query_rect = tg_geom_rect(query_geom);
3522
+ long *indices = NULL;
3523
+ long count = 0;
3524
+ enum tg_index_geom_query_status status;
3525
+
3526
+ if (idx->len == 0) {
3527
+ return rb_ary_new();
3528
+ }
3529
+
3530
+ status = index_geom_query_collect(idx, query_geom, query_rect, predicate, &indices, &count);
3531
+ if (status != TG_INDEX_GEOM_QUERY_OK) {
3532
+ rb_raise(rb_eNoMemError, "%s", index_geom_query_status_message(status));
3533
+ }
3534
+
3535
+ RB_GC_GUARD(self);
3536
+ RB_GC_GUARD(geom_value);
3537
+ return build_ids_from_indices_protected(idx, indices, count);
3538
+ }
3539
+
3540
+ static VALUE rb_tg_geometry_index_intersecting_geom_ids(VALUE self, VALUE geom) {
3541
+ return index_geom_query_ids(self, geom, TG_GEOMETRY_GEOM_QUERY_INTERSECTS);
3542
+ }
3543
+
3544
+ static VALUE rb_tg_geometry_index_covering_geom_ids(VALUE self, VALUE geom) {
3545
+ return index_geom_query_ids(self, geom, TG_GEOMETRY_GEOM_QUERY_COVERS);
3546
+ }
3547
+
3548
+ static VALUE rb_tg_geometry_index_containing_geom_ids(VALUE self, VALUE geom) {
3549
+ return index_geom_query_ids(self, geom, TG_GEOMETRY_GEOM_QUERY_CONTAINS);
3550
+ }
3551
+
2409
3552
  typedef struct {
2410
3553
  tg_index_t *idx;
2411
3554
  VALUE entries;
@@ -2605,6 +3748,10 @@ static VALUE rb_tg_geometry_index_build(int argc, VALUE *argv, VALUE klass) {
2605
3748
  int state = 0;
2606
3749
 
2607
3750
  rb_scan_args(argc, argv, "1:", &entries_value, &kwargs);
3751
+ {
3752
+ ID allowed[] = {id_via, id_strategy, id_predicate, id_geometry_index};
3753
+ validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
3754
+ }
2608
3755
 
2609
3756
  if (!RB_TYPE_P(entries_value, T_ARRAY)) {
2610
3757
  rb_raise(rb_eTypeError, "entries must be Array");
@@ -5125,6 +6272,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
5125
6272
 
5126
6273
  id_format = rb_intern("format");
5127
6274
  id_index = rb_intern("index");
6275
+ id_srid = rb_intern("srid");
5128
6276
  id_auto = rb_intern("auto");
5129
6277
  id_geojson = rb_intern("geojson");
5130
6278
  id_wkt = rb_intern("wkt");
@@ -5161,6 +6309,8 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
5161
6309
  id_multipoint = rb_intern("multipoint");
5162
6310
  id_multilinestring = rb_intern("multilinestring");
5163
6311
  id_geometrycollection = rb_intern("geometrycollection");
6312
+ id_exterior = rb_intern("exterior");
6313
+ id_holes = rb_intern("holes");
5164
6314
 
5165
6315
  mTG = rb_define_module("TG");
5166
6316
  mTGGeometry = rb_define_module_under(mTG, "Geometry");
@@ -5191,10 +6341,14 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
5191
6341
  0);
5192
6342
  rb_define_singleton_method(mTGGeometry, "empty_geometrycollection",
5193
6343
  rb_tg_geometry_empty_geometrycollection, 0);
6344
+ rb_define_singleton_method(mTGGeometry, "line_string", rb_tg_geometry_line_string, -1);
6345
+ rb_define_singleton_method(mTGGeometry, "polygon", rb_tg_geometry_polygon, -1);
6346
+ rb_define_singleton_method(mTGGeometry, "multi_polygon", rb_tg_geometry_multi_polygon, -1);
5194
6347
 
5195
6348
  cTGGeometryGeom = rb_define_class_under(mTGGeometry, "Geom", rb_cObject);
5196
6349
  rb_undef_alloc_func(cTGGeometryGeom);
5197
6350
  rb_define_method(cTGGeometryGeom, "type", rb_tg_geometry_geom_type, 0);
6351
+ rb_define_method(cTGGeometryGeom, "srid", rb_tg_geometry_geom_srid, 0);
5198
6352
  rb_define_method(cTGGeometryGeom, "bbox", rb_tg_geometry_geom_bbox, 0);
5199
6353
  rb_define_method(cTGGeometryGeom, "covers_xy?", rb_tg_geometry_geom_covers_xy_p, 2);
5200
6354
  rb_define_method(cTGGeometryGeom, "intersects_xy?", rb_tg_geometry_geom_intersects_xy_p, 2);
@@ -5211,6 +6365,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
5211
6365
  rb_define_method(cTGGeometryGeom, "to_geojson", rb_tg_geometry_geom_to_geojson, 0);
5212
6366
  rb_define_method(cTGGeometryGeom, "to_wkt", rb_tg_geometry_geom_to_wkt, 0);
5213
6367
  rb_define_method(cTGGeometryGeom, "to_wkb", rb_tg_geometry_geom_to_wkb, 0);
6368
+ rb_define_method(cTGGeometryGeom, "to_ewkb", rb_tg_geometry_geom_to_ewkb, -1);
5214
6369
  rb_define_method(cTGGeometryGeom, "to_hex", rb_tg_geometry_geom_to_hex, 0);
5215
6370
  rb_define_method(cTGGeometryGeom, "to_geobin", rb_tg_geometry_geom_to_geobin, 0);
5216
6371
  rb_define_method(cTGGeometryGeom, "extra_json", rb_tg_geometry_geom_extra_json, 0);
@@ -5251,6 +6406,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
5251
6406
  rb_define_method(cTGGeometryLine, "segments", rb_tg_geometry_line_segments, 0);
5252
6407
  rb_define_method(cTGGeometryLine, "length", rb_tg_geometry_line_length, 0);
5253
6408
  rb_define_method(cTGGeometryLine, "clockwise?", rb_tg_geometry_line_clockwise_p, 0);
6409
+ rb_define_method(cTGGeometryLine, "nearest_segment", rb_tg_geometry_line_nearest_segment, 2);
5254
6410
 
5255
6411
  cTGGeometryRing = rb_define_class_under(mTGGeometry, "Ring", rb_cObject);
5256
6412
  rb_undef_alloc_func(cTGGeometryRing);
@@ -5265,6 +6421,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
5265
6421
  rb_define_method(cTGGeometryRing, "perimeter", rb_tg_geometry_ring_perimeter, 0);
5266
6422
  rb_define_method(cTGGeometryRing, "clockwise?", rb_tg_geometry_ring_clockwise_p, 0);
5267
6423
  rb_define_method(cTGGeometryRing, "convex?", rb_tg_geometry_ring_convex_p, 0);
6424
+ rb_define_method(cTGGeometryRing, "nearest_segment", rb_tg_geometry_ring_nearest_segment, 2);
5268
6425
 
5269
6426
  cTGGeometryPolygon = rb_define_class_under(mTGGeometry, "Polygon", rb_cObject);
5270
6427
  rb_undef_alloc_func(cTGGeometryPolygon);
@@ -5283,6 +6440,15 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
5283
6440
  rb_define_method(cTGGeometrySegment, "bbox", rb_tg_geometry_segment_bbox, 0);
5284
6441
  rb_define_method(cTGGeometrySegment, "intersects?", rb_tg_geometry_segment_intersects_p, 1);
5285
6442
 
6443
+ cTGGeometryNearestSegment = rb_define_class_under(mTGGeometry, "NearestSegment", rb_cObject);
6444
+ rb_undef_alloc_func(cTGGeometryNearestSegment);
6445
+ rb_define_method(cTGGeometryNearestSegment, "segment", rb_tg_geometry_nearest_segment_segment,
6446
+ 0);
6447
+ rb_define_method(cTGGeometryNearestSegment, "index", rb_tg_geometry_nearest_segment_index, 0);
6448
+ rb_define_method(cTGGeometryNearestSegment, "distance", rb_tg_geometry_nearest_segment_distance,
6449
+ 0);
6450
+ rb_define_method(cTGGeometryNearestSegment, "point", rb_tg_geometry_nearest_segment_point, 0);
6451
+
5286
6452
  cTGGeometryRect = rb_define_class_under(mTGGeometry, "Rect", rb_cObject);
5287
6453
  rb_define_alloc_func(cTGGeometryRect, rb_tg_geometry_rect_alloc);
5288
6454
  rb_define_method(cTGGeometryRect, "initialize", rb_tg_geometry_rect_initialize, -1);
@@ -5323,6 +6489,12 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
5323
6489
  rb_define_singleton_method(cTGGeometryIndex, "build", rb_tg_geometry_index_build, -1);
5324
6490
  rb_define_method(cTGGeometryIndex, "find_covering", rb_tg_geometry_index_find_covering, 2);
5325
6491
  rb_define_method(cTGGeometryIndex, "covering_ids", rb_tg_geometry_index_covering_ids, 2);
6492
+ rb_define_method(cTGGeometryIndex, "intersecting_geom_ids",
6493
+ rb_tg_geometry_index_intersecting_geom_ids, 1);
6494
+ rb_define_method(cTGGeometryIndex, "covering_geom_ids", rb_tg_geometry_index_covering_geom_ids,
6495
+ 1);
6496
+ rb_define_method(cTGGeometryIndex, "containing_geom_ids",
6497
+ rb_tg_geometry_index_containing_geom_ids, 1);
5326
6498
  rb_define_method(cTGGeometryIndex, "intersecting_rect", rb_tg_geometry_index_intersecting_rect,
5327
6499
  4);
5328
6500
  rb_define_method(cTGGeometryIndex, "covering_ids_batch_packed",