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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -0
- data/README.md +107 -14
- data/benchmark/ewkb_roundtrip.rb +29 -0
- data/benchmark/geom_query.rb +33 -0
- data/benchmark/nearest_segment.rb +20 -0
- 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 +1176 -4
- 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;
|
|
@@ -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",
|