tg_geometry 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/README.md +107 -14
- data/benchmark/_support.rb +450 -5
- data/benchmark/batch_packed_vs_loop.rb +38 -6
- data/benchmark/ewkb_roundtrip.rb +38 -0
- data/benchmark/falcon_concurrency.rb +17 -10
- data/benchmark/feature_source.rb +49 -23
- data/benchmark/flat_vs_rtree.rb +53 -11
- data/benchmark/geom_query.rb +43 -0
- data/benchmark/gvl_threshold.rb +10 -11
- data/benchmark/nearest_segment.rb +24 -0
- data/benchmark/objectspace_memsize.rb +14 -1
- data/benchmark/parse_throughput.rb +13 -10
- data/benchmark/rss_stability.rb +35 -24
- data/docs/GEOMETRY_QUERIES.md +23 -0
- data/docs/LIMITATIONS.md +12 -10
- data/docs/NEAREST_SEGMENT.md +17 -0
- data/docs/SRID_AND_EWKB.md +23 -0
- data/ext/tg_geometry/tg_geometry_ext.c +1234 -27
- data/lib/tg/geometry/active_record_source.rb +16 -34
- data/lib/tg/geometry/active_record_type.rb +61 -0
- data/lib/tg/geometry/registry.rb +17 -68
- data/lib/tg/geometry/version.rb +1 -1
- data/lib/tg/geometry.rb +85 -0
- data/spec/active_record_type_spec.rb +45 -0
- data/spec/constructors_spec.rb +104 -0
- data/spec/fixtures/feature_source/invalid_geometry_middle.geojson +8 -0
- data/spec/fixtures/feature_source/malformed_json.geojson +1 -0
- data/spec/fixtures/feature_source/mixed_geometry_types.geojson +8 -0
- data/spec/fixtures/feature_source/osm_like_feature_collection.geojson +10 -0
- data/spec/fixtures/feature_source/properties_null_missing.geojson +7 -0
- data/spec/fixtures/feature_source/simple_feature_collection.geojson +15 -0
- data/spec/fixtures/postgis/README.md +16 -0
- data/spec/fixtures/postgis/boundary_point_cases.geojson +83 -0
- data/spec/fixtures/postgis/multipolygon_large.ewkb +0 -0
- data/spec/fixtures/postgis/point_4326.ewkb +0 -0
- data/spec/fixtures/postgis/polygon_3857.ewkb +0 -0
- data/spec/fixtures/postgis/polygon_4326_simple.ewkb +0 -0
- data/spec/fixtures/postgis/polygon_4326_with_hole.ewkb +0 -0
- data/spec/index_geom_query_spec.rb +68 -0
- data/spec/keyword_validation_spec.rb +31 -0
- data/spec/nearest_segment_spec.rb +62 -0
- data/spec/postgis_fixtures_spec.rb +68 -0
- data/spec/srid_spec.rb +43 -0
- data/spec/to_ewkb_spec.rb +37 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
@@ -1305,12 +1945,27 @@ static VALUE rb_tg_geometry_rect_expand_to_include_point(VALUE self, VALUE x_val
|
|
|
1305
1945
|
check_finite_double(x, "x");
|
|
1306
1946
|
check_finite_double(y, "y");
|
|
1307
1947
|
|
|
1308
|
-
min_x = rect_data->min_x < x ? rect_data->min_x : x;
|
|
1309
|
-
min_y = rect_data->min_y < y ? rect_data->min_y : y;
|
|
1310
|
-
max_x = rect_data->max_x > x ? rect_data->max_x : x;
|
|
1311
|
-
max_y = rect_data->max_y > y ? rect_data->max_y : y;
|
|
1948
|
+
min_x = rect_data->min_x < x ? rect_data->min_x : x;
|
|
1949
|
+
min_y = rect_data->min_y < y ? rect_data->min_y : y;
|
|
1950
|
+
max_x = rect_data->max_x > x ? rect_data->max_x : x;
|
|
1951
|
+
max_y = rect_data->max_y > y ? rect_data->max_y : y;
|
|
1952
|
+
|
|
1953
|
+
return rect_build(min_x, min_y, max_x, max_y);
|
|
1954
|
+
}
|
|
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
|
+
}
|
|
1312
1965
|
|
|
1313
|
-
|
|
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;
|
|
1314
1969
|
}
|
|
1315
1970
|
|
|
1316
1971
|
static VALUE rb_tg_geometry_geom_type(VALUE self) {
|
|
@@ -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
|
|
|
@@ -2178,6 +3127,43 @@ static bool rtree_mark_candidate_iter(const double *min, const double *max, cons
|
|
|
2178
3127
|
return true;
|
|
2179
3128
|
}
|
|
2180
3129
|
|
|
3130
|
+
typedef struct {
|
|
3131
|
+
const tg_index_t *idx;
|
|
3132
|
+
const struct tg_geom *point;
|
|
3133
|
+
const tg_index_entry_t *best;
|
|
3134
|
+
long best_ordinal;
|
|
3135
|
+
} tg_rtree_find_args_t;
|
|
3136
|
+
|
|
3137
|
+
static bool rtree_find_covering_iter(const double *min, const double *max, const void *data,
|
|
3138
|
+
void *udata) {
|
|
3139
|
+
const tg_index_entry_t *entry = (const tg_index_entry_t *)data;
|
|
3140
|
+
tg_rtree_find_args_t *args = (tg_rtree_find_args_t *)udata;
|
|
3141
|
+
|
|
3142
|
+
(void)min;
|
|
3143
|
+
(void)max;
|
|
3144
|
+
|
|
3145
|
+
if (!entry) {
|
|
3146
|
+
return true;
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
if (entry->ordinal < 0 || entry->ordinal >= args->idx->len) {
|
|
3150
|
+
return true;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
if (args->best && entry->ordinal >= args->best_ordinal) {
|
|
3154
|
+
return true;
|
|
3155
|
+
}
|
|
3156
|
+
if (index_entry_matches_point(args->idx, entry, args->point)) {
|
|
3157
|
+
args->best = entry;
|
|
3158
|
+
args->best_ordinal = entry->ordinal;
|
|
3159
|
+
|
|
3160
|
+
if (entry->ordinal == 0) {
|
|
3161
|
+
return false;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
return true;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
2181
3167
|
static unsigned char *rtree_candidate_marks(tg_index_t *idx, struct tg_rect query_rect) {
|
|
2182
3168
|
unsigned char *marks;
|
|
2183
3169
|
tg_rtree_mark_args_t args;
|
|
@@ -2246,33 +3232,31 @@ static VALUE index_find_covering_value(tg_index_t *idx, double lon, double lat)
|
|
|
2246
3232
|
|
|
2247
3233
|
if (idx->strategy == TG_GEOMETRY_INDEX_STRATEGY_RTREE) {
|
|
2248
3234
|
struct tg_rect point_rect = tg_rect_from_xyxy(lon, lat, lon, lat);
|
|
2249
|
-
|
|
3235
|
+
tg_rtree_find_args_t args;
|
|
3236
|
+
double min[2];
|
|
3237
|
+
double max[2];
|
|
2250
3238
|
|
|
2251
3239
|
point = tg_query_point_new(lon, lat);
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
}
|
|
2256
|
-
if (tg_geom_error(point)) {
|
|
3240
|
+
tg_query_point_raise_if_invalid(point);
|
|
3241
|
+
|
|
3242
|
+
if (!idx->rtree) {
|
|
2257
3243
|
tg_geom_free(point);
|
|
2258
|
-
|
|
2259
|
-
rb_raise(eTGGeometryError, "TG point geometry error");
|
|
3244
|
+
return Qnil;
|
|
2260
3245
|
}
|
|
2261
3246
|
|
|
2262
|
-
|
|
2263
|
-
|
|
3247
|
+
args.idx = idx;
|
|
3248
|
+
args.point = point;
|
|
3249
|
+
args.best = NULL;
|
|
3250
|
+
args.best_ordinal = 0;
|
|
2264
3251
|
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
break;
|
|
2271
|
-
}
|
|
3252
|
+
tg_rect_to_arrays(point_rect, min, max);
|
|
3253
|
+
rtree_search(idx->rtree, min, max, rtree_find_covering_iter, &args);
|
|
3254
|
+
|
|
3255
|
+
if (args.best) {
|
|
3256
|
+
result = args.best->id;
|
|
2272
3257
|
}
|
|
2273
3258
|
|
|
2274
3259
|
tg_geom_free(point);
|
|
2275
|
-
free(candidates);
|
|
2276
3260
|
return result;
|
|
2277
3261
|
}
|
|
2278
3262
|
|
|
@@ -2406,6 +3390,200 @@ static unsigned char *index_intersecting_rect_marks(tg_index_t *idx, struct tg_r
|
|
|
2406
3390
|
return marks;
|
|
2407
3391
|
}
|
|
2408
3392
|
|
|
3393
|
+
static bool index_geom_query_matches(const struct tg_geom *stored_geom,
|
|
3394
|
+
const struct tg_geom *query_geom,
|
|
3395
|
+
enum tg_geometry_geom_query_predicate predicate) {
|
|
3396
|
+
switch (predicate) {
|
|
3397
|
+
case TG_GEOMETRY_GEOM_QUERY_INTERSECTS:
|
|
3398
|
+
return tg_geom_intersects(stored_geom, query_geom);
|
|
3399
|
+
case TG_GEOMETRY_GEOM_QUERY_COVERS:
|
|
3400
|
+
return tg_geom_covers(stored_geom, query_geom);
|
|
3401
|
+
case TG_GEOMETRY_GEOM_QUERY_CONTAINS:
|
|
3402
|
+
return tg_geom_contains(stored_geom, query_geom);
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
return false;
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
enum tg_index_geom_query_status {
|
|
3409
|
+
TG_INDEX_GEOM_QUERY_OK = 0,
|
|
3410
|
+
TG_INDEX_GEOM_QUERY_MATCH_ALLOC_FAILED,
|
|
3411
|
+
TG_INDEX_GEOM_QUERY_RESULT_OVERFLOW,
|
|
3412
|
+
TG_INDEX_GEOM_QUERY_RESULT_ALLOC_FAILED,
|
|
3413
|
+
};
|
|
3414
|
+
|
|
3415
|
+
static const char *index_geom_query_status_message(enum tg_index_geom_query_status status) {
|
|
3416
|
+
switch (status) {
|
|
3417
|
+
case TG_INDEX_GEOM_QUERY_MATCH_ALLOC_FAILED:
|
|
3418
|
+
return "match buffer allocation failed";
|
|
3419
|
+
case TG_INDEX_GEOM_QUERY_RESULT_OVERFLOW:
|
|
3420
|
+
return "result buffer allocation size overflow";
|
|
3421
|
+
case TG_INDEX_GEOM_QUERY_RESULT_ALLOC_FAILED:
|
|
3422
|
+
return "result buffer allocation failed";
|
|
3423
|
+
case TG_INDEX_GEOM_QUERY_OK:
|
|
3424
|
+
return "ok";
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
return "geometry query failed";
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
static enum tg_index_geom_query_status rtree_candidate_marks_no_raise(tg_index_t *idx,
|
|
3431
|
+
struct tg_rect query_rect,
|
|
3432
|
+
unsigned char **marks_out) {
|
|
3433
|
+
unsigned char *marks;
|
|
3434
|
+
tg_rtree_mark_args_t args;
|
|
3435
|
+
double min[2];
|
|
3436
|
+
double max[2];
|
|
3437
|
+
|
|
3438
|
+
*marks_out = NULL;
|
|
3439
|
+
|
|
3440
|
+
if (idx->len <= 0) {
|
|
3441
|
+
return TG_INDEX_GEOM_QUERY_OK;
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
marks = alloc_match_marks_raw(idx->len);
|
|
3445
|
+
if (!marks) {
|
|
3446
|
+
return TG_INDEX_GEOM_QUERY_MATCH_ALLOC_FAILED;
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
if (idx->rtree) {
|
|
3450
|
+
args.marks = marks;
|
|
3451
|
+
args.len = idx->len;
|
|
3452
|
+
tg_rect_to_arrays(query_rect, min, max);
|
|
3453
|
+
rtree_search(idx->rtree, min, max, rtree_mark_candidate_iter, &args);
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
*marks_out = marks;
|
|
3457
|
+
return TG_INDEX_GEOM_QUERY_OK;
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
static enum tg_index_geom_query_status
|
|
3461
|
+
index_geom_query_collect(tg_index_t *idx, const struct tg_geom *query_geom,
|
|
3462
|
+
struct tg_rect query_rect, enum tg_geometry_geom_query_predicate predicate,
|
|
3463
|
+
long **indices_out, long *count_out) {
|
|
3464
|
+
unsigned char *marks = NULL;
|
|
3465
|
+
long *indices = NULL;
|
|
3466
|
+
long count = 0;
|
|
3467
|
+
enum tg_index_geom_query_status status;
|
|
3468
|
+
|
|
3469
|
+
*indices_out = NULL;
|
|
3470
|
+
*count_out = 0;
|
|
3471
|
+
|
|
3472
|
+
if (idx->strategy == TG_GEOMETRY_INDEX_STRATEGY_RTREE) {
|
|
3473
|
+
status = rtree_candidate_marks_no_raise(idx, query_rect, &marks);
|
|
3474
|
+
if (status != TG_INDEX_GEOM_QUERY_OK) {
|
|
3475
|
+
return status;
|
|
3476
|
+
}
|
|
3477
|
+
} else {
|
|
3478
|
+
marks = alloc_match_marks_raw(idx->len);
|
|
3479
|
+
if (!marks) {
|
|
3480
|
+
return TG_INDEX_GEOM_QUERY_MATCH_ALLOC_FAILED;
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
for (long i = 0; i < idx->len; i++) {
|
|
3484
|
+
if (tg_rect_intersects_rect(idx->entries[i].bbox, query_rect)) {
|
|
3485
|
+
marks[i] = 1;
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
if (idx->len < 0 || (size_t)idx->len > SIZE_MAX / sizeof(long)) {
|
|
3491
|
+
free(marks);
|
|
3492
|
+
return TG_INDEX_GEOM_QUERY_RESULT_OVERFLOW;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
indices = (long *)calloc((size_t)idx->len, sizeof(long));
|
|
3496
|
+
if (!indices) {
|
|
3497
|
+
free(marks);
|
|
3498
|
+
return TG_INDEX_GEOM_QUERY_RESULT_ALLOC_FAILED;
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
for (long i = 0; i < idx->len; i++) {
|
|
3502
|
+
if (!marks[i]) {
|
|
3503
|
+
continue;
|
|
3504
|
+
}
|
|
3505
|
+
if (index_geom_query_matches(idx->entries[i].geom, query_geom, predicate)) {
|
|
3506
|
+
indices[count++] = i;
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
free(marks);
|
|
3511
|
+
*indices_out = indices;
|
|
3512
|
+
*count_out = count;
|
|
3513
|
+
return TG_INDEX_GEOM_QUERY_OK;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
typedef struct {
|
|
3517
|
+
tg_index_t *idx;
|
|
3518
|
+
long *indices;
|
|
3519
|
+
long count;
|
|
3520
|
+
} tg_build_ids_from_indices_args_t;
|
|
3521
|
+
|
|
3522
|
+
static VALUE build_ids_from_indices_body(VALUE arg) {
|
|
3523
|
+
tg_build_ids_from_indices_args_t *args = (tg_build_ids_from_indices_args_t *)arg;
|
|
3524
|
+
VALUE result = rb_ary_new_capa(args->count);
|
|
3525
|
+
|
|
3526
|
+
for (long i = 0; i < args->count; i++) {
|
|
3527
|
+
rb_ary_push(result, args->idx->entries[args->indices[i]].id);
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
return result;
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
static VALUE build_ids_from_indices_protected(tg_index_t *idx, long *indices, long count) {
|
|
3534
|
+
tg_build_ids_from_indices_args_t args;
|
|
3535
|
+
VALUE result;
|
|
3536
|
+
int state = 0;
|
|
3537
|
+
|
|
3538
|
+
args.idx = idx;
|
|
3539
|
+
args.indices = indices;
|
|
3540
|
+
args.count = count;
|
|
3541
|
+
result = rb_protect(build_ids_from_indices_body, (VALUE)&args, &state);
|
|
3542
|
+
free(indices);
|
|
3543
|
+
|
|
3544
|
+
if (state) {
|
|
3545
|
+
rb_jump_tag(state);
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
return result;
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
static VALUE index_geom_query_ids(VALUE self, VALUE geom_value,
|
|
3552
|
+
enum tg_geometry_geom_query_predicate predicate) {
|
|
3553
|
+
tg_index_t *idx = get_index_wrapper(self);
|
|
3554
|
+
tg_geom_wrapper_t *query_wrapper = get_geom_wrapper(geom_value);
|
|
3555
|
+
struct tg_geom *query_geom = query_wrapper->geom;
|
|
3556
|
+
struct tg_rect query_rect = tg_geom_rect(query_geom);
|
|
3557
|
+
long *indices = NULL;
|
|
3558
|
+
long count = 0;
|
|
3559
|
+
enum tg_index_geom_query_status status;
|
|
3560
|
+
|
|
3561
|
+
if (idx->len == 0) {
|
|
3562
|
+
return rb_ary_new();
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
status = index_geom_query_collect(idx, query_geom, query_rect, predicate, &indices, &count);
|
|
3566
|
+
if (status != TG_INDEX_GEOM_QUERY_OK) {
|
|
3567
|
+
rb_raise(rb_eNoMemError, "%s", index_geom_query_status_message(status));
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
RB_GC_GUARD(self);
|
|
3571
|
+
RB_GC_GUARD(geom_value);
|
|
3572
|
+
return build_ids_from_indices_protected(idx, indices, count);
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
static VALUE rb_tg_geometry_index_intersecting_geom_ids(VALUE self, VALUE geom) {
|
|
3576
|
+
return index_geom_query_ids(self, geom, TG_GEOMETRY_GEOM_QUERY_INTERSECTS);
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
static VALUE rb_tg_geometry_index_covering_geom_ids(VALUE self, VALUE geom) {
|
|
3580
|
+
return index_geom_query_ids(self, geom, TG_GEOMETRY_GEOM_QUERY_COVERS);
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
static VALUE rb_tg_geometry_index_containing_geom_ids(VALUE self, VALUE geom) {
|
|
3584
|
+
return index_geom_query_ids(self, geom, TG_GEOMETRY_GEOM_QUERY_CONTAINS);
|
|
3585
|
+
}
|
|
3586
|
+
|
|
2409
3587
|
typedef struct {
|
|
2410
3588
|
tg_index_t *idx;
|
|
2411
3589
|
VALUE entries;
|
|
@@ -2605,6 +3783,10 @@ static VALUE rb_tg_geometry_index_build(int argc, VALUE *argv, VALUE klass) {
|
|
|
2605
3783
|
int state = 0;
|
|
2606
3784
|
|
|
2607
3785
|
rb_scan_args(argc, argv, "1:", &entries_value, &kwargs);
|
|
3786
|
+
{
|
|
3787
|
+
ID allowed[] = {id_via, id_strategy, id_predicate, id_geometry_index};
|
|
3788
|
+
validate_keywords(kwargs, allowed, sizeof(allowed) / sizeof(allowed[0]));
|
|
3789
|
+
}
|
|
2608
3790
|
|
|
2609
3791
|
if (!RB_TYPE_P(entries_value, T_ARRAY)) {
|
|
2610
3792
|
rb_raise(rb_eTypeError, "entries must be Array");
|
|
@@ -5125,6 +6307,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
5125
6307
|
|
|
5126
6308
|
id_format = rb_intern("format");
|
|
5127
6309
|
id_index = rb_intern("index");
|
|
6310
|
+
id_srid = rb_intern("srid");
|
|
5128
6311
|
id_auto = rb_intern("auto");
|
|
5129
6312
|
id_geojson = rb_intern("geojson");
|
|
5130
6313
|
id_wkt = rb_intern("wkt");
|
|
@@ -5161,6 +6344,8 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
5161
6344
|
id_multipoint = rb_intern("multipoint");
|
|
5162
6345
|
id_multilinestring = rb_intern("multilinestring");
|
|
5163
6346
|
id_geometrycollection = rb_intern("geometrycollection");
|
|
6347
|
+
id_exterior = rb_intern("exterior");
|
|
6348
|
+
id_holes = rb_intern("holes");
|
|
5164
6349
|
|
|
5165
6350
|
mTG = rb_define_module("TG");
|
|
5166
6351
|
mTGGeometry = rb_define_module_under(mTG, "Geometry");
|
|
@@ -5191,10 +6376,14 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
5191
6376
|
0);
|
|
5192
6377
|
rb_define_singleton_method(mTGGeometry, "empty_geometrycollection",
|
|
5193
6378
|
rb_tg_geometry_empty_geometrycollection, 0);
|
|
6379
|
+
rb_define_singleton_method(mTGGeometry, "line_string", rb_tg_geometry_line_string, -1);
|
|
6380
|
+
rb_define_singleton_method(mTGGeometry, "polygon", rb_tg_geometry_polygon, -1);
|
|
6381
|
+
rb_define_singleton_method(mTGGeometry, "multi_polygon", rb_tg_geometry_multi_polygon, -1);
|
|
5194
6382
|
|
|
5195
6383
|
cTGGeometryGeom = rb_define_class_under(mTGGeometry, "Geom", rb_cObject);
|
|
5196
6384
|
rb_undef_alloc_func(cTGGeometryGeom);
|
|
5197
6385
|
rb_define_method(cTGGeometryGeom, "type", rb_tg_geometry_geom_type, 0);
|
|
6386
|
+
rb_define_method(cTGGeometryGeom, "srid", rb_tg_geometry_geom_srid, 0);
|
|
5198
6387
|
rb_define_method(cTGGeometryGeom, "bbox", rb_tg_geometry_geom_bbox, 0);
|
|
5199
6388
|
rb_define_method(cTGGeometryGeom, "covers_xy?", rb_tg_geometry_geom_covers_xy_p, 2);
|
|
5200
6389
|
rb_define_method(cTGGeometryGeom, "intersects_xy?", rb_tg_geometry_geom_intersects_xy_p, 2);
|
|
@@ -5211,6 +6400,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
5211
6400
|
rb_define_method(cTGGeometryGeom, "to_geojson", rb_tg_geometry_geom_to_geojson, 0);
|
|
5212
6401
|
rb_define_method(cTGGeometryGeom, "to_wkt", rb_tg_geometry_geom_to_wkt, 0);
|
|
5213
6402
|
rb_define_method(cTGGeometryGeom, "to_wkb", rb_tg_geometry_geom_to_wkb, 0);
|
|
6403
|
+
rb_define_method(cTGGeometryGeom, "to_ewkb", rb_tg_geometry_geom_to_ewkb, -1);
|
|
5214
6404
|
rb_define_method(cTGGeometryGeom, "to_hex", rb_tg_geometry_geom_to_hex, 0);
|
|
5215
6405
|
rb_define_method(cTGGeometryGeom, "to_geobin", rb_tg_geometry_geom_to_geobin, 0);
|
|
5216
6406
|
rb_define_method(cTGGeometryGeom, "extra_json", rb_tg_geometry_geom_extra_json, 0);
|
|
@@ -5251,6 +6441,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
5251
6441
|
rb_define_method(cTGGeometryLine, "segments", rb_tg_geometry_line_segments, 0);
|
|
5252
6442
|
rb_define_method(cTGGeometryLine, "length", rb_tg_geometry_line_length, 0);
|
|
5253
6443
|
rb_define_method(cTGGeometryLine, "clockwise?", rb_tg_geometry_line_clockwise_p, 0);
|
|
6444
|
+
rb_define_method(cTGGeometryLine, "nearest_segment", rb_tg_geometry_line_nearest_segment, 2);
|
|
5254
6445
|
|
|
5255
6446
|
cTGGeometryRing = rb_define_class_under(mTGGeometry, "Ring", rb_cObject);
|
|
5256
6447
|
rb_undef_alloc_func(cTGGeometryRing);
|
|
@@ -5265,6 +6456,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
5265
6456
|
rb_define_method(cTGGeometryRing, "perimeter", rb_tg_geometry_ring_perimeter, 0);
|
|
5266
6457
|
rb_define_method(cTGGeometryRing, "clockwise?", rb_tg_geometry_ring_clockwise_p, 0);
|
|
5267
6458
|
rb_define_method(cTGGeometryRing, "convex?", rb_tg_geometry_ring_convex_p, 0);
|
|
6459
|
+
rb_define_method(cTGGeometryRing, "nearest_segment", rb_tg_geometry_ring_nearest_segment, 2);
|
|
5268
6460
|
|
|
5269
6461
|
cTGGeometryPolygon = rb_define_class_under(mTGGeometry, "Polygon", rb_cObject);
|
|
5270
6462
|
rb_undef_alloc_func(cTGGeometryPolygon);
|
|
@@ -5283,6 +6475,15 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
5283
6475
|
rb_define_method(cTGGeometrySegment, "bbox", rb_tg_geometry_segment_bbox, 0);
|
|
5284
6476
|
rb_define_method(cTGGeometrySegment, "intersects?", rb_tg_geometry_segment_intersects_p, 1);
|
|
5285
6477
|
|
|
6478
|
+
cTGGeometryNearestSegment = rb_define_class_under(mTGGeometry, "NearestSegment", rb_cObject);
|
|
6479
|
+
rb_undef_alloc_func(cTGGeometryNearestSegment);
|
|
6480
|
+
rb_define_method(cTGGeometryNearestSegment, "segment", rb_tg_geometry_nearest_segment_segment,
|
|
6481
|
+
0);
|
|
6482
|
+
rb_define_method(cTGGeometryNearestSegment, "index", rb_tg_geometry_nearest_segment_index, 0);
|
|
6483
|
+
rb_define_method(cTGGeometryNearestSegment, "distance", rb_tg_geometry_nearest_segment_distance,
|
|
6484
|
+
0);
|
|
6485
|
+
rb_define_method(cTGGeometryNearestSegment, "point", rb_tg_geometry_nearest_segment_point, 0);
|
|
6486
|
+
|
|
5286
6487
|
cTGGeometryRect = rb_define_class_under(mTGGeometry, "Rect", rb_cObject);
|
|
5287
6488
|
rb_define_alloc_func(cTGGeometryRect, rb_tg_geometry_rect_alloc);
|
|
5288
6489
|
rb_define_method(cTGGeometryRect, "initialize", rb_tg_geometry_rect_initialize, -1);
|
|
@@ -5323,6 +6524,12 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
5323
6524
|
rb_define_singleton_method(cTGGeometryIndex, "build", rb_tg_geometry_index_build, -1);
|
|
5324
6525
|
rb_define_method(cTGGeometryIndex, "find_covering", rb_tg_geometry_index_find_covering, 2);
|
|
5325
6526
|
rb_define_method(cTGGeometryIndex, "covering_ids", rb_tg_geometry_index_covering_ids, 2);
|
|
6527
|
+
rb_define_method(cTGGeometryIndex, "intersecting_geom_ids",
|
|
6528
|
+
rb_tg_geometry_index_intersecting_geom_ids, 1);
|
|
6529
|
+
rb_define_method(cTGGeometryIndex, "covering_geom_ids", rb_tg_geometry_index_covering_geom_ids,
|
|
6530
|
+
1);
|
|
6531
|
+
rb_define_method(cTGGeometryIndex, "containing_geom_ids",
|
|
6532
|
+
rb_tg_geometry_index_containing_geom_ids, 1);
|
|
5326
6533
|
rb_define_method(cTGGeometryIndex, "intersecting_rect", rb_tg_geometry_index_intersecting_rect,
|
|
5327
6534
|
4);
|
|
5328
6535
|
rb_define_method(cTGGeometryIndex, "covering_ids_batch_packed",
|