tg_geometry 0.1.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 +55 -79
- data/README.md +168 -184
- data/Rakefile +3 -3
- data/benchmark/ewkb_roundtrip.rb +29 -0
- data/benchmark/falcon_concurrency.rb +1 -1
- data/benchmark/feature_source.rb +92 -0
- data/benchmark/geom_query.rb +33 -0
- data/benchmark/nearest_segment.rb +20 -0
- data/docs/ARCHITECTURE.md +29 -107
- data/docs/BENCHMARKING.md +20 -1
- data/docs/CASUAL_EXAMPLE.md +71 -458
- data/docs/CONCURRENCY.md +13 -7
- data/docs/ERROR_HANDLING.md +30 -0
- data/docs/FEATURE_SOURCE.md +166 -0
- data/docs/GEOMETRY_QUERIES.md +23 -0
- data/docs/LIMITATIONS.md +17 -54
- data/docs/MEMORY_OWNERSHIP.md +20 -2
- data/docs/NEAREST_SEGMENT.md +17 -0
- data/docs/SRID_AND_EWKB.md +23 -0
- data/ext/tg_geometry/extconf.rb +46 -4
- data/ext/tg_geometry/tg_geometry_ext.c +3643 -168
- data/ext/tg_geometry/tg_geometry_vendor_json.c +17 -0
- data/ext/tg_geometry/tg_geometry_vendor_tg.c +3 -0
- data/ext/tg_geometry/vendor/.vendored +8 -2
- data/ext/tg_geometry/vendor/json/LICENSE +20 -0
- data/ext/tg_geometry/vendor/json/VERSION +3 -0
- data/ext/tg_geometry/vendor/json/json.c +1024 -0
- data/ext/tg_geometry/vendor/json/json.h +207 -0
- 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 +16 -67
- data/lib/tg/geometry/version.rb +1 -1
- data/lib/tg/geometry.rb +85 -0
- data/script/vendor_libs.rb +22 -6
- data/spec/active_record_type_spec.rb +45 -0
- data/spec/{expansion_a_auto_strategy_spec.rb → auto_strategy_spec.rb} +1 -1
- data/spec/{block_12_batch_packed_spec.rb → batch_packed_spec.rb} +1 -1
- data/spec/{block_20_concurrency_spec.rb → concurrency_spec.rb} +1 -1
- data/spec/constructors_spec.rb +104 -0
- data/spec/{block_13_error_hardening_spec.rb → error_hardening_spec.rb} +1 -1
- data/spec/feature_source_nogvl_spec.rb +51 -0
- data/spec/feature_source_spec.rb +268 -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/{expansion_d_format_coverage_spec.rb → format_coverage_spec.rb} +1 -1
- data/spec/{block_20_fuzz_spec.rb → fuzz_spec.rb} +1 -1
- data/spec/{block_4_geom_api_spec.rb → geom_api_spec.rb} +1 -1
- data/spec/{block_3_geom_parse_spec.rb → geom_parse_spec.rb} +1 -1
- data/spec/{block_8_index_borrowed_geometry_spec.rb → index_borrowed_geometry_spec.rb} +1 -1
- data/spec/{block_6_index_build_spec.rb → index_build_spec.rb} +2 -2
- data/spec/{block_9_flat_query_spec.rb → index_flat_query_spec.rb} +1 -1
- data/spec/index_geom_query_spec.rb +68 -0
- data/spec/{block_7_index_owned_geometry_spec.rb → index_owned_geometry_spec.rb} +1 -1
- data/spec/{block_10_rtree_strategy_spec.rb → index_rtree_accounting_spec.rb} +1 -1
- data/spec/{block_11_rtree_order_spec.rb → index_rtree_order_spec.rb} +1 -1
- data/spec/keyword_validation_spec.rb +31 -0
- data/spec/{block_1_skeleton_spec.rb → load_and_errors_spec.rb} +1 -1
- data/spec/{expansion_e_low_level_geometry_spec.rb → low_level_geometry_spec.rb} +1 -1
- data/spec/{block_14_memory_gc_hardening_spec.rb → memory_gc_spec.rb} +1 -1
- data/spec/nearest_segment_spec.rb +62 -0
- data/spec/postgis_fixtures_spec.rb +68 -0
- data/spec/{expansion_i_ractor_spec.rb → ractor_spec.rb} +1 -1
- data/spec/{block_5_rect_api_spec.rb → rect_api_spec.rb} +1 -1
- data/spec/{expansion_b_registry_spec.rb → registry_spec.rb} +1 -1
- data/spec/srid_spec.rb +43 -0
- data/spec/{expansion_j_full_tg_api_coverage_spec.rb → tg_api_coverage_spec.rb} +1 -1
- data/spec/to_ewkb_spec.rb +37 -0
- data/spec/{block_2_vendor_spec.rb → vendor_sources_spec.rb} +4 -4
- metadata +87 -38
- data/docs/ACTIVE_RECORD.md +0 -26
- data/docs/AUTO_STRATEGY.md +0 -15
- data/docs/EXPANSION_E_TO_H_STATUS.md +0 -51
- data/docs/FORMAT_COVERAGE.md +0 -23
- data/docs/FULL_TG_API_COVERAGE.md +0 -109
- data/docs/LOW_LEVEL_GEOMETRY.md +0 -121
- data/docs/RACTOR.md +0 -40
- data/docs/REGISTRY.md +0 -37
- data/docs/RELEASE_CHECKLIST.md +0 -39
- /data/spec/{expansion_c_active_record_source_spec.rb → active_record_source_spec.rb} +0 -0
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
#endif
|
|
12
12
|
#include "ruby.h"
|
|
13
13
|
#include "ruby/encoding.h"
|
|
14
|
+
#include "ruby/thread.h"
|
|
14
15
|
#if defined(__clang__)
|
|
15
16
|
#pragma clang diagnostic pop
|
|
16
17
|
#elif defined(__GNUC__)
|
|
@@ -19,23 +20,36 @@
|
|
|
19
20
|
|
|
20
21
|
#include "tg.h"
|
|
21
22
|
#include "rtree.h"
|
|
23
|
+
#include "json.h"
|
|
22
24
|
|
|
25
|
+
#include <errno.h>
|
|
26
|
+
#include <fcntl.h>
|
|
23
27
|
#include <limits.h>
|
|
24
28
|
#include <math.h>
|
|
25
29
|
#include <stdbool.h>
|
|
26
30
|
#include <stdint.h>
|
|
27
31
|
#include <stdlib.h>
|
|
28
32
|
#include <string.h>
|
|
33
|
+
#include <sys/stat.h>
|
|
34
|
+
#include <unistd.h>
|
|
35
|
+
|
|
36
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
37
|
+
#define TG_GEOMETRY_NORETURN __attribute__((noreturn))
|
|
38
|
+
#else
|
|
39
|
+
#define TG_GEOMETRY_NORETURN
|
|
40
|
+
#endif
|
|
29
41
|
|
|
30
42
|
static VALUE mTG;
|
|
31
43
|
static VALUE mTGGeometry;
|
|
32
44
|
static VALUE cTGGeometryGeom;
|
|
33
45
|
static VALUE cTGGeometryRect;
|
|
34
46
|
static VALUE cTGGeometryIndex;
|
|
47
|
+
static VALUE mTGGeometryFeatureSource;
|
|
35
48
|
static VALUE cTGGeometryLine;
|
|
36
49
|
static VALUE cTGGeometryRing;
|
|
37
50
|
static VALUE cTGGeometryPolygon;
|
|
38
51
|
static VALUE cTGGeometrySegment;
|
|
52
|
+
static VALUE cTGGeometryNearestSegment;
|
|
39
53
|
static VALUE eTGGeometryError;
|
|
40
54
|
static VALUE eTGGeometryParseError;
|
|
41
55
|
static VALUE eTGGeometryArgumentError;
|
|
@@ -46,6 +60,8 @@ typedef struct {
|
|
|
46
60
|
struct tg_geom *geom;
|
|
47
61
|
size_t geom_bytes;
|
|
48
62
|
bool owned;
|
|
63
|
+
bool has_srid;
|
|
64
|
+
int srid;
|
|
49
65
|
} tg_geom_wrapper_t;
|
|
50
66
|
|
|
51
67
|
typedef struct {
|
|
@@ -76,6 +92,14 @@ typedef struct {
|
|
|
76
92
|
bool initialized;
|
|
77
93
|
} tg_segment_wrapper_t;
|
|
78
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
|
+
|
|
79
103
|
enum tg_geometry_index_via {
|
|
80
104
|
TG_GEOMETRY_INDEX_VIA_GEOM,
|
|
81
105
|
TG_GEOMETRY_INDEX_VIA_GEOJSON,
|
|
@@ -92,6 +116,12 @@ enum tg_geometry_index_predicate {
|
|
|
92
116
|
TG_GEOMETRY_INDEX_PREDICATE_CONTAINS,
|
|
93
117
|
};
|
|
94
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
|
+
|
|
95
125
|
typedef struct {
|
|
96
126
|
VALUE id;
|
|
97
127
|
VALUE geom_owner;
|
|
@@ -130,6 +160,7 @@ static _Thread_local tg_index_t *tg_current_rtree_owner = NULL;
|
|
|
130
160
|
|
|
131
161
|
static ID id_format;
|
|
132
162
|
static ID id_index;
|
|
163
|
+
static ID id_srid;
|
|
133
164
|
static ID id_auto;
|
|
134
165
|
static ID id_geojson;
|
|
135
166
|
static ID id_wkt;
|
|
@@ -149,6 +180,25 @@ static ID id_flat;
|
|
|
149
180
|
static ID id_rtree;
|
|
150
181
|
static ID id_covers;
|
|
151
182
|
static ID id_contains;
|
|
183
|
+
static ID id_id;
|
|
184
|
+
static ID id_only;
|
|
185
|
+
static ID id_on_invalid;
|
|
186
|
+
static ID id_on_missing_id;
|
|
187
|
+
static ID id_report;
|
|
188
|
+
static ID id_max_errors;
|
|
189
|
+
static ID id_read;
|
|
190
|
+
static ID id_raise;
|
|
191
|
+
static ID id_skip;
|
|
192
|
+
static ID id_ordinal;
|
|
193
|
+
static ID id_polygon;
|
|
194
|
+
static ID id_multipolygon;
|
|
195
|
+
static ID id_point;
|
|
196
|
+
static ID id_linestring;
|
|
197
|
+
static ID id_multipoint;
|
|
198
|
+
static ID id_multilinestring;
|
|
199
|
+
static ID id_geometrycollection;
|
|
200
|
+
static ID id_exterior;
|
|
201
|
+
static ID id_holes;
|
|
152
202
|
|
|
153
203
|
#ifdef TG_DEBUG_TEST
|
|
154
204
|
static bool tg_debug_fail_next_entries_alloc = false;
|
|
@@ -197,6 +247,8 @@ static void geom_free(void *ptr) {
|
|
|
197
247
|
w->geom_owner = Qnil;
|
|
198
248
|
w->geom_bytes = 0;
|
|
199
249
|
w->owned = false;
|
|
250
|
+
w->has_srid = false;
|
|
251
|
+
w->srid = 0;
|
|
200
252
|
|
|
201
253
|
ruby_xfree(w);
|
|
202
254
|
}
|
|
@@ -380,6 +432,29 @@ static const rb_data_type_t tg_segment_type = {
|
|
|
380
432
|
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
381
433
|
};
|
|
382
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
|
+
|
|
383
458
|
static void index_dispose(tg_index_t *idx) {
|
|
384
459
|
if (!idx)
|
|
385
460
|
return;
|
|
@@ -491,6 +566,62 @@ static const rb_data_type_t tg_index_type = {
|
|
|
491
566
|
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
492
567
|
};
|
|
493
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
|
+
|
|
494
625
|
static VALUE kwargs_value(VALUE kwargs, ID key, VALUE fallback) {
|
|
495
626
|
VALUE value;
|
|
496
627
|
|
|
@@ -558,6 +689,120 @@ static enum tg_geometry_parse_format parse_format_symbol(VALUE value) {
|
|
|
558
689
|
"format: must be one of :auto, :geojson, :wkt, :wkb, :hex, :geobin");
|
|
559
690
|
}
|
|
560
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
|
+
|
|
561
806
|
static VALUE required_kwargs_value(VALUE kwargs, ID key, const char *name) {
|
|
562
807
|
VALUE value;
|
|
563
808
|
|
|
@@ -622,7 +867,7 @@ static enum tg_geometry_index_predicate parse_index_predicate_symbol(VALUE value
|
|
|
622
867
|
rb_raise(eTGGeometryArgumentError, "predicate: must be one of :covers, :contains");
|
|
623
868
|
}
|
|
624
869
|
|
|
625
|
-
static VALUE
|
|
870
|
+
static VALUE geom_wrap_owned_with_srid(struct tg_geom *geom, bool has_srid, int srid) {
|
|
626
871
|
tg_geom_wrapper_t *w;
|
|
627
872
|
VALUE wrapper;
|
|
628
873
|
|
|
@@ -636,6 +881,8 @@ static VALUE geom_wrap_owned(struct tg_geom *geom) {
|
|
|
636
881
|
w->geom = geom;
|
|
637
882
|
w->geom_bytes = tg_geom_memsize(geom);
|
|
638
883
|
w->owned = true;
|
|
884
|
+
w->has_srid = has_srid;
|
|
885
|
+
w->srid = has_srid ? srid : 0;
|
|
639
886
|
|
|
640
887
|
if (w->geom_bytes > 0) {
|
|
641
888
|
rb_gc_adjust_memory_usage((ssize_t)w->geom_bytes);
|
|
@@ -659,6 +906,8 @@ static VALUE geom_wrap_borrowed(VALUE geom_owner, const struct tg_geom *geom) {
|
|
|
659
906
|
w->geom = (struct tg_geom *)geom;
|
|
660
907
|
w->geom_bytes = 0;
|
|
661
908
|
w->owned = false;
|
|
909
|
+
w->has_srid = false;
|
|
910
|
+
w->srid = 0;
|
|
662
911
|
|
|
663
912
|
rb_obj_freeze(wrapper);
|
|
664
913
|
RB_GC_GUARD(geom_owner);
|
|
@@ -734,6 +983,8 @@ static VALUE parse_string_with_format(VALUE input, enum tg_geometry_parse_format
|
|
|
734
983
|
struct tg_geom *geom = NULL;
|
|
735
984
|
const char *data;
|
|
736
985
|
size_t len;
|
|
986
|
+
bool has_srid = false;
|
|
987
|
+
int srid = 0;
|
|
737
988
|
|
|
738
989
|
StringValue(input);
|
|
739
990
|
data = RSTRING_PTR(input);
|
|
@@ -750,9 +1001,11 @@ static VALUE parse_string_with_format(VALUE input, enum tg_geometry_parse_format
|
|
|
750
1001
|
geom = tg_parse_wktn_ix(data, len, index);
|
|
751
1002
|
break;
|
|
752
1003
|
case TG_GEOMETRY_FORMAT_WKB:
|
|
1004
|
+
extract_ewkb_srid((const uint8_t *)data, len, &has_srid, &srid);
|
|
753
1005
|
geom = tg_parse_wkb_ix((const uint8_t *)data, len, index);
|
|
754
1006
|
break;
|
|
755
1007
|
case TG_GEOMETRY_FORMAT_HEX:
|
|
1008
|
+
extract_hex_ewkb_srid(data, len, &has_srid, &srid);
|
|
756
1009
|
geom = tg_parse_hexn_ix(data, len, index);
|
|
757
1010
|
break;
|
|
758
1011
|
case TG_GEOMETRY_FORMAT_GEOBIN:
|
|
@@ -761,7 +1014,7 @@ static VALUE parse_string_with_format(VALUE input, enum tg_geometry_parse_format
|
|
|
761
1014
|
}
|
|
762
1015
|
|
|
763
1016
|
raise_parse_error_from_geom(geom);
|
|
764
|
-
return
|
|
1017
|
+
return geom_wrap_owned_with_srid(geom, has_srid, srid);
|
|
765
1018
|
}
|
|
766
1019
|
|
|
767
1020
|
static VALUE rb_tg_geometry_parse(int argc, VALUE *argv, VALUE self) {
|
|
@@ -774,6 +1027,10 @@ static VALUE rb_tg_geometry_parse(int argc, VALUE *argv, VALUE self) {
|
|
|
774
1027
|
|
|
775
1028
|
(void)self;
|
|
776
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
|
+
}
|
|
777
1034
|
|
|
778
1035
|
format_value = kwargs_value(kwargs, id_format, ID2SYM(id_auto));
|
|
779
1036
|
index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
|
|
@@ -791,6 +1048,10 @@ static VALUE rb_tg_geometry_parse_geojson(int argc, VALUE *argv, VALUE self) {
|
|
|
791
1048
|
|
|
792
1049
|
(void)self;
|
|
793
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
|
+
}
|
|
794
1055
|
|
|
795
1056
|
index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
|
|
796
1057
|
index = parse_index_symbol(index_value);
|
|
@@ -806,6 +1067,10 @@ static VALUE rb_tg_geometry_parse_wkt(int argc, VALUE *argv, VALUE self) {
|
|
|
806
1067
|
|
|
807
1068
|
(void)self;
|
|
808
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
|
+
}
|
|
809
1074
|
|
|
810
1075
|
index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
|
|
811
1076
|
index = parse_index_symbol(index_value);
|
|
@@ -821,6 +1086,10 @@ static VALUE rb_tg_geometry_parse_wkb(int argc, VALUE *argv, VALUE self) {
|
|
|
821
1086
|
|
|
822
1087
|
(void)self;
|
|
823
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
|
+
}
|
|
824
1093
|
|
|
825
1094
|
index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
|
|
826
1095
|
index = parse_index_symbol(index_value);
|
|
@@ -836,6 +1105,10 @@ static VALUE rb_tg_geometry_parse_hex(int argc, VALUE *argv, VALUE self) {
|
|
|
836
1105
|
|
|
837
1106
|
(void)self;
|
|
838
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
|
+
}
|
|
839
1112
|
|
|
840
1113
|
index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
|
|
841
1114
|
index = parse_index_symbol(index_value);
|
|
@@ -851,6 +1124,10 @@ static VALUE rb_tg_geometry_parse_geobin(int argc, VALUE *argv, VALUE self) {
|
|
|
851
1124
|
|
|
852
1125
|
(void)self;
|
|
853
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
|
+
}
|
|
854
1131
|
|
|
855
1132
|
index_value = kwargs_value(kwargs, id_index, ID2SYM(id_ystripes));
|
|
856
1133
|
index = parse_index_symbol(index_value);
|
|
@@ -886,11 +1163,15 @@ static void validate_rect_coordinates(double min_x, double min_y, double max_x,
|
|
|
886
1163
|
}
|
|
887
1164
|
}
|
|
888
1165
|
|
|
889
|
-
static VALUE
|
|
1166
|
+
static VALUE wrap_constructed_geom_with_srid(struct tg_geom *geom, bool has_srid, int srid) {
|
|
890
1167
|
raise_geom_error_and_free_as(geom, eTGGeometryError, "TG geometry allocation failed",
|
|
891
1168
|
"TG geometry error message is too large",
|
|
892
1169
|
"TG geometry error message allocation failed");
|
|
893
|
-
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);
|
|
894
1175
|
}
|
|
895
1176
|
|
|
896
1177
|
static VALUE rb_tg_geometry_point(VALUE self, VALUE x_value, VALUE y_value) {
|
|
@@ -993,163 +1274,552 @@ static VALUE rb_tg_geometry_empty_geometrycollection(VALUE self) {
|
|
|
993
1274
|
return wrap_constructed_geom(tg_geom_new_geometrycollection_empty());
|
|
994
1275
|
}
|
|
995
1276
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
rect = TypedData_Make_Struct(cTGGeometryRect, tg_rect_wrapper_t, &tg_rect_type, rect_data);
|
|
1003
|
-
rect_data->min_x = min_x;
|
|
1004
|
-
rect_data->min_y = min_y;
|
|
1005
|
-
rect_data->max_x = max_x;
|
|
1006
|
-
rect_data->max_y = max_y;
|
|
1007
|
-
rect_data->initialized = true;
|
|
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;
|
|
1008
1283
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
return rect;
|
|
1012
|
-
}
|
|
1284
|
+
static VALUE parse_points_body(VALUE arg) {
|
|
1285
|
+
tg_parse_points_args_t *args = (tg_parse_points_args_t *)arg;
|
|
1013
1286
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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;
|
|
1017
1293
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
}
|
|
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
|
+
}
|
|
1021
1297
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
+
}
|
|
1026
1308
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1309
|
+
args->points[i].x = x;
|
|
1310
|
+
args->points[i].y = y;
|
|
1311
|
+
}
|
|
1029
1312
|
|
|
1030
|
-
|
|
1031
|
-
RB_GC_GUARD(wrapper);
|
|
1032
|
-
return wrapper;
|
|
1313
|
+
return Qnil;
|
|
1033
1314
|
}
|
|
1034
1315
|
|
|
1035
|
-
static
|
|
1036
|
-
|
|
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;
|
|
1037
1319
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
rb_raise(eTGGeometryArgumentError, "invalid TG::Geometry::Segment");
|
|
1320
|
+
if (!RB_TYPE_P(points_value, T_ARRAY)) {
|
|
1321
|
+
rb_raise(rb_eTypeError, "%s must be an Array", label);
|
|
1041
1322
|
}
|
|
1042
1323
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1324
|
+
args.len = RARRAY_LEN(points_value);
|
|
1325
|
+
args.points_value = points_value;
|
|
1326
|
+
args.label = label;
|
|
1327
|
+
args.points = NULL;
|
|
1045
1328
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1329
|
+
if (args.len > INT_MAX) {
|
|
1330
|
+
rb_raise(eTGGeometryArgumentError, "%s has too many points", label);
|
|
1331
|
+
}
|
|
1048
1332
|
|
|
1049
|
-
if (
|
|
1050
|
-
|
|
1333
|
+
if (args.len > 0) {
|
|
1334
|
+
args.points = (struct tg_point *)ruby_xcalloc((size_t)args.len, sizeof(struct tg_point));
|
|
1051
1335
|
}
|
|
1052
1336
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1337
|
+
rb_protect(parse_points_body, (VALUE)&args, &state);
|
|
1338
|
+
if (state) {
|
|
1339
|
+
ruby_xfree(args.points);
|
|
1340
|
+
rb_jump_tag(state);
|
|
1055
1341
|
}
|
|
1056
1342
|
|
|
1057
|
-
|
|
1343
|
+
*len_out = args.len;
|
|
1344
|
+
return args.points;
|
|
1058
1345
|
}
|
|
1059
1346
|
|
|
1060
|
-
static
|
|
1061
|
-
|
|
1062
|
-
|
|
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;
|
|
1063
1353
|
|
|
1064
|
-
|
|
1065
|
-
|
|
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);
|
|
1066
1362
|
}
|
|
1067
1363
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
w->line = line;
|
|
1071
|
-
|
|
1072
|
-
rb_obj_freeze(wrapper);
|
|
1073
|
-
RB_GC_GUARD(geom_owner);
|
|
1074
|
-
RB_GC_GUARD(wrapper);
|
|
1075
|
-
return wrapper;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
static VALUE ring_wrap_borrowed(VALUE geom_owner, const struct tg_ring *ring) {
|
|
1079
|
-
tg_ring_wrapper_t *w;
|
|
1080
|
-
VALUE wrapper;
|
|
1081
|
-
|
|
1364
|
+
ring = tg_ring_new_ix(points, (int)len, index);
|
|
1365
|
+
ruby_xfree(points);
|
|
1082
1366
|
if (!ring) {
|
|
1083
|
-
|
|
1367
|
+
rb_raise(rb_eNoMemError, "TG ring allocation failed");
|
|
1084
1368
|
}
|
|
1085
1369
|
|
|
1086
|
-
|
|
1087
|
-
w->geom_owner = geom_owner;
|
|
1088
|
-
w->ring = ring;
|
|
1089
|
-
|
|
1090
|
-
rb_obj_freeze(wrapper);
|
|
1091
|
-
RB_GC_GUARD(geom_owner);
|
|
1092
|
-
RB_GC_GUARD(wrapper);
|
|
1093
|
-
return wrapper;
|
|
1370
|
+
return ring;
|
|
1094
1371
|
}
|
|
1095
1372
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
VALUE
|
|
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
|
+
}
|
|
1099
1387
|
|
|
1100
|
-
if (
|
|
1101
|
-
|
|
1388
|
+
if (args->exterior) {
|
|
1389
|
+
tg_ring_free(args->exterior);
|
|
1390
|
+
args->exterior = NULL;
|
|
1102
1391
|
}
|
|
1103
1392
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
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
|
+
}
|
|
1107
1403
|
|
|
1108
|
-
|
|
1109
|
-
RB_GC_GUARD(geom_owner);
|
|
1110
|
-
RB_GC_GUARD(wrapper);
|
|
1111
|
-
return wrapper;
|
|
1404
|
+
args->nholes = 0;
|
|
1112
1405
|
}
|
|
1113
1406
|
|
|
1114
|
-
static
|
|
1115
|
-
|
|
1407
|
+
static VALUE build_poly_body(VALUE arg) {
|
|
1408
|
+
tg_build_poly_args_t *args = (tg_build_poly_args_t *)arg;
|
|
1116
1409
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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");
|
|
1120
1416
|
}
|
|
1121
1417
|
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
+
}
|
|
1124
1422
|
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
+
}
|
|
1127
1439
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
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");
|
|
1131
1444
|
}
|
|
1132
1445
|
|
|
1133
|
-
return
|
|
1446
|
+
return Qnil;
|
|
1134
1447
|
}
|
|
1135
1448
|
|
|
1136
|
-
static
|
|
1137
|
-
|
|
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;
|
|
1138
1453
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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;
|
|
1143
1461
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1462
|
+
rb_protect(build_poly_body, (VALUE)&args, &state);
|
|
1463
|
+
build_poly_cleanup(&args);
|
|
1146
1464
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1465
|
+
if (state) {
|
|
1466
|
+
rb_jump_tag(state);
|
|
1467
|
+
}
|
|
1150
1468
|
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
+
|
|
1666
|
+
static VALUE rect_build(double min_x, double min_y, double max_x, double max_y) {
|
|
1667
|
+
tg_rect_wrapper_t *rect_data;
|
|
1668
|
+
VALUE rect;
|
|
1669
|
+
|
|
1670
|
+
validate_rect_coordinates(min_x, min_y, max_x, max_y);
|
|
1671
|
+
|
|
1672
|
+
rect = TypedData_Make_Struct(cTGGeometryRect, tg_rect_wrapper_t, &tg_rect_type, rect_data);
|
|
1673
|
+
rect_data->min_x = min_x;
|
|
1674
|
+
rect_data->min_y = min_y;
|
|
1675
|
+
rect_data->max_x = max_x;
|
|
1676
|
+
rect_data->max_y = max_y;
|
|
1677
|
+
rect_data->initialized = true;
|
|
1678
|
+
|
|
1679
|
+
rb_obj_freeze(rect);
|
|
1680
|
+
RB_GC_GUARD(rect);
|
|
1681
|
+
return rect;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
static VALUE rect_from_tg_rect(struct tg_rect rect) {
|
|
1685
|
+
return rect_build(rect.min.x, rect.min.y, rect.max.x, rect.max.y);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
static VALUE point_array_from_tg_point(struct tg_point point) {
|
|
1689
|
+
return rb_ary_new_from_args(2, rb_float_new(point.x), rb_float_new(point.y));
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
static VALUE segment_wrap_value(struct tg_segment segment) {
|
|
1693
|
+
tg_segment_wrapper_t *w;
|
|
1694
|
+
VALUE wrapper =
|
|
1695
|
+
TypedData_Make_Struct(cTGGeometrySegment, tg_segment_wrapper_t, &tg_segment_type, w);
|
|
1696
|
+
|
|
1697
|
+
w->segment = segment;
|
|
1698
|
+
w->initialized = true;
|
|
1699
|
+
|
|
1700
|
+
rb_obj_freeze(wrapper);
|
|
1701
|
+
RB_GC_GUARD(wrapper);
|
|
1702
|
+
return wrapper;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
static tg_segment_wrapper_t *get_segment_wrapper(VALUE value) {
|
|
1706
|
+
tg_segment_wrapper_t *w;
|
|
1707
|
+
|
|
1708
|
+
TypedData_Get_Struct(value, tg_segment_wrapper_t, &tg_segment_type, w);
|
|
1709
|
+
if (!w || !w->initialized) {
|
|
1710
|
+
rb_raise(eTGGeometryArgumentError, "invalid TG::Geometry::Segment");
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
return w;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
static int checked_child_index(VALUE index_value, int count, const char *name) {
|
|
1717
|
+
long index = NUM2LONG(index_value);
|
|
1718
|
+
|
|
1719
|
+
if (index < 0 || index >= count) {
|
|
1720
|
+
rb_raise(eTGGeometryArgumentError, "%s index out of range", name);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
if (index > INT_MAX) {
|
|
1724
|
+
rb_raise(eTGGeometryArgumentError, "%s index out of range", name);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
return (int)index;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
static VALUE line_wrap_borrowed(VALUE geom_owner, const struct tg_line *line) {
|
|
1731
|
+
tg_line_wrapper_t *w;
|
|
1732
|
+
VALUE wrapper;
|
|
1733
|
+
|
|
1734
|
+
if (!line) {
|
|
1735
|
+
return Qnil;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
wrapper = TypedData_Make_Struct(cTGGeometryLine, tg_line_wrapper_t, &tg_line_type, w);
|
|
1739
|
+
w->geom_owner = geom_owner;
|
|
1740
|
+
w->line = line;
|
|
1741
|
+
|
|
1742
|
+
rb_obj_freeze(wrapper);
|
|
1743
|
+
RB_GC_GUARD(geom_owner);
|
|
1744
|
+
RB_GC_GUARD(wrapper);
|
|
1745
|
+
return wrapper;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
static VALUE ring_wrap_borrowed(VALUE geom_owner, const struct tg_ring *ring) {
|
|
1749
|
+
tg_ring_wrapper_t *w;
|
|
1750
|
+
VALUE wrapper;
|
|
1751
|
+
|
|
1752
|
+
if (!ring) {
|
|
1753
|
+
return Qnil;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
wrapper = TypedData_Make_Struct(cTGGeometryRing, tg_ring_wrapper_t, &tg_ring_type, w);
|
|
1757
|
+
w->geom_owner = geom_owner;
|
|
1758
|
+
w->ring = ring;
|
|
1759
|
+
|
|
1760
|
+
rb_obj_freeze(wrapper);
|
|
1761
|
+
RB_GC_GUARD(geom_owner);
|
|
1762
|
+
RB_GC_GUARD(wrapper);
|
|
1763
|
+
return wrapper;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
static VALUE polygon_wrap_borrowed(VALUE geom_owner, const struct tg_poly *poly) {
|
|
1767
|
+
tg_polygon_wrapper_t *w;
|
|
1768
|
+
VALUE wrapper;
|
|
1769
|
+
|
|
1770
|
+
if (!poly) {
|
|
1771
|
+
return Qnil;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
wrapper = TypedData_Make_Struct(cTGGeometryPolygon, tg_polygon_wrapper_t, &tg_polygon_type, w);
|
|
1775
|
+
w->geom_owner = geom_owner;
|
|
1776
|
+
w->poly = poly;
|
|
1777
|
+
|
|
1778
|
+
rb_obj_freeze(wrapper);
|
|
1779
|
+
RB_GC_GUARD(geom_owner);
|
|
1780
|
+
RB_GC_GUARD(wrapper);
|
|
1781
|
+
return wrapper;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
static tg_line_wrapper_t *get_line_wrapper(VALUE value) {
|
|
1785
|
+
tg_line_wrapper_t *w;
|
|
1786
|
+
|
|
1787
|
+
TypedData_Get_Struct(value, tg_line_wrapper_t, &tg_line_type, w);
|
|
1788
|
+
if (!w || !w->line) {
|
|
1789
|
+
rb_raise(eTGGeometryArgumentError, "invalid TG::Geometry::Line");
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
return w;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
static tg_ring_wrapper_t *get_ring_wrapper(VALUE value) {
|
|
1796
|
+
tg_ring_wrapper_t *w;
|
|
1797
|
+
|
|
1798
|
+
TypedData_Get_Struct(value, tg_ring_wrapper_t, &tg_ring_type, w);
|
|
1799
|
+
if (!w || !w->ring) {
|
|
1800
|
+
rb_raise(eTGGeometryArgumentError, "invalid TG::Geometry::Ring");
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
return w;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
static tg_polygon_wrapper_t *get_polygon_wrapper(VALUE value) {
|
|
1807
|
+
tg_polygon_wrapper_t *w;
|
|
1808
|
+
|
|
1809
|
+
TypedData_Get_Struct(value, tg_polygon_wrapper_t, &tg_polygon_type, w);
|
|
1810
|
+
if (!w || !w->poly) {
|
|
1811
|
+
rb_raise(eTGGeometryArgumentError, "invalid TG::Geometry::Polygon");
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
return w;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
static VALUE rb_tg_geometry_rect_alloc(VALUE klass) {
|
|
1818
|
+
tg_rect_wrapper_t *rect_data;
|
|
1819
|
+
VALUE rect = TypedData_Make_Struct(klass, tg_rect_wrapper_t, &tg_rect_type, rect_data);
|
|
1820
|
+
|
|
1821
|
+
rect_data->initialized = false;
|
|
1822
|
+
return rect;
|
|
1153
1823
|
}
|
|
1154
1824
|
|
|
1155
1825
|
static tg_rect_wrapper_t *get_rect_wrapper(VALUE value) {
|
|
@@ -1283,6 +1953,21 @@ static VALUE rb_tg_geometry_rect_expand_to_include_point(VALUE self, VALUE x_val
|
|
|
1283
1953
|
return rect_build(min_x, min_y, max_x, max_y);
|
|
1284
1954
|
}
|
|
1285
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
|
+
|
|
1286
1971
|
static VALUE rb_tg_geometry_geom_type(VALUE self) {
|
|
1287
1972
|
tg_geom_wrapper_t *w = get_geom_wrapper(self);
|
|
1288
1973
|
|
|
@@ -1481,8 +2166,98 @@ static VALUE rb_tg_geometry_geom_to_wkb(VALUE self) {
|
|
|
1481
2166
|
return binary_writer_result(tg_geom_wkb, w->geom, "WKB");
|
|
1482
2167
|
}
|
|
1483
2168
|
|
|
1484
|
-
|
|
1485
|
-
|
|
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
|
+
|
|
2259
|
+
static VALUE rb_tg_geometry_geom_to_hex(VALUE self) {
|
|
2260
|
+
tg_geom_wrapper_t *w = get_geom_wrapper(self);
|
|
1486
2261
|
return text_writer_result(tg_geom_hex, w->geom);
|
|
1487
2262
|
}
|
|
1488
2263
|
|
|
@@ -1925,6 +2700,210 @@ static VALUE rb_tg_geometry_segment_intersects_p(VALUE self, VALUE other) {
|
|
|
1925
2700
|
return tg_segment_intersects_segment(w->segment, other_w->segment) ? Qtrue : Qfalse;
|
|
1926
2701
|
}
|
|
1927
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
|
+
|
|
1928
2907
|
static tg_index_t *get_index_wrapper(VALUE value) {
|
|
1929
2908
|
tg_index_t *idx;
|
|
1930
2909
|
|
|
@@ -2376,6 +3355,200 @@ static unsigned char *index_intersecting_rect_marks(tg_index_t *idx, struct tg_r
|
|
|
2376
3355
|
return marks;
|
|
2377
3356
|
}
|
|
2378
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
|
+
|
|
2379
3552
|
typedef struct {
|
|
2380
3553
|
tg_index_t *idx;
|
|
2381
3554
|
VALUE entries;
|
|
@@ -2575,6 +3748,10 @@ static VALUE rb_tg_geometry_index_build(int argc, VALUE *argv, VALUE klass) {
|
|
|
2575
3748
|
int state = 0;
|
|
2576
3749
|
|
|
2577
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
|
+
}
|
|
2578
3755
|
|
|
2579
3756
|
if (!RB_TYPE_P(entries_value, T_ARRAY)) {
|
|
2580
3757
|
rb_raise(rb_eTypeError, "entries must be Array");
|
|
@@ -2854,64 +4031,2324 @@ static VALUE rb_tg_geometry_index_force_dispose_for_test(VALUE self) {
|
|
|
2854
4031
|
}
|
|
2855
4032
|
#endif
|
|
2856
4033
|
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
4034
|
+
typedef enum {
|
|
4035
|
+
FS_MODE_READ_ENTRIES,
|
|
4036
|
+
FS_MODE_READ_FEATURES,
|
|
4037
|
+
FS_MODE_BUILD_INDEX,
|
|
4038
|
+
} fs_mode_t;
|
|
4039
|
+
|
|
4040
|
+
typedef enum {
|
|
4041
|
+
FS_ON_INVALID_RAISE,
|
|
4042
|
+
FS_ON_INVALID_SKIP,
|
|
4043
|
+
} fs_on_invalid_t;
|
|
4044
|
+
|
|
4045
|
+
typedef enum {
|
|
4046
|
+
FS_ON_MISSING_ID_RAISE,
|
|
4047
|
+
FS_ON_MISSING_ID_SKIP,
|
|
4048
|
+
FS_ON_MISSING_ID_ORDINAL,
|
|
4049
|
+
} fs_on_missing_id_t;
|
|
4050
|
+
|
|
4051
|
+
#define FS_GEOM_POINT (1u << 0)
|
|
4052
|
+
#define FS_GEOM_LINESTRING (1u << 1)
|
|
4053
|
+
#define FS_GEOM_POLYGON (1u << 2)
|
|
4054
|
+
#define FS_GEOM_MULTIPOINT (1u << 3)
|
|
4055
|
+
#define FS_GEOM_MULTILINESTRING (1u << 4)
|
|
4056
|
+
#define FS_GEOM_MULTIPOLYGON (1u << 5)
|
|
4057
|
+
#define FS_GEOM_GEOMETRYCOLLECTION (1u << 6)
|
|
2881
4058
|
|
|
2882
|
-
|
|
2883
|
-
|
|
4059
|
+
typedef struct {
|
|
4060
|
+
VALUE id_path;
|
|
4061
|
+
bool only_all;
|
|
4062
|
+
unsigned int only_mask;
|
|
4063
|
+
fs_on_invalid_t on_invalid;
|
|
4064
|
+
fs_on_missing_id_t on_missing_id;
|
|
4065
|
+
bool report;
|
|
4066
|
+
long max_errors;
|
|
4067
|
+
enum tg_index geometry_index;
|
|
4068
|
+
enum tg_geometry_index_strategy strategy;
|
|
4069
|
+
enum tg_geometry_index_predicate predicate;
|
|
4070
|
+
} fs_options_t;
|
|
2884
4071
|
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
rb_define_class_under(mTGGeometry, "FrozenIndexError", eTGGeometryError);
|
|
4072
|
+
typedef struct {
|
|
4073
|
+
char *data;
|
|
4074
|
+
size_t len;
|
|
4075
|
+
} fs_source_t;
|
|
2890
4076
|
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
4077
|
+
typedef struct {
|
|
4078
|
+
fs_source_t source;
|
|
4079
|
+
fs_options_t opts;
|
|
4080
|
+
fs_mode_t mode;
|
|
4081
|
+
} fs_args_t;
|
|
4082
|
+
|
|
4083
|
+
typedef struct {
|
|
4084
|
+
fs_args_t *fs;
|
|
4085
|
+
struct json features;
|
|
4086
|
+
VALUE wrapper;
|
|
4087
|
+
tg_index_t *idx;
|
|
4088
|
+
} fs_build_args_t;
|
|
4089
|
+
|
|
4090
|
+
typedef struct {
|
|
4091
|
+
bool accepted;
|
|
4092
|
+
bool filtered;
|
|
4093
|
+
bool missing_id_skip;
|
|
4094
|
+
VALUE id;
|
|
4095
|
+
struct json feature;
|
|
4096
|
+
struct json geometry;
|
|
4097
|
+
struct json properties;
|
|
4098
|
+
const char *reason;
|
|
4099
|
+
VALUE reason_value;
|
|
4100
|
+
} fs_feature_result_t;
|
|
4101
|
+
|
|
4102
|
+
static VALUE fs_sym(const char *name) {
|
|
4103
|
+
return ID2SYM(rb_intern(name));
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
static VALUE fs_kwargs_value(VALUE kwargs, ID key, VALUE fallback, bool *present) {
|
|
4107
|
+
VALUE sym = ID2SYM(key);
|
|
4108
|
+
if (NIL_P(kwargs)) {
|
|
4109
|
+
if (present)
|
|
4110
|
+
*present = false;
|
|
4111
|
+
return fallback;
|
|
4112
|
+
}
|
|
4113
|
+
if (RTEST(rb_funcall(kwargs, rb_intern("key?"), 1, sym))) {
|
|
4114
|
+
if (present)
|
|
4115
|
+
*present = true;
|
|
4116
|
+
return rb_hash_aref(kwargs, sym);
|
|
4117
|
+
}
|
|
4118
|
+
if (present)
|
|
4119
|
+
*present = false;
|
|
4120
|
+
return fallback;
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
static VALUE fs_utf8_string(const char *ptr, size_t len) {
|
|
4124
|
+
VALUE str;
|
|
4125
|
+
|
|
4126
|
+
if (len > LONG_MAX) {
|
|
4127
|
+
rb_raise(rb_eNoMemError, "FeatureSource JSON substring is too large");
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
str = rb_str_new(ptr, (long)len);
|
|
4131
|
+
rb_enc_associate(str, rb_utf8_encoding());
|
|
4132
|
+
return str;
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
static VALUE fs_cstr_utf8_string(const char *ptr) {
|
|
4136
|
+
VALUE str = rb_str_new_cstr(ptr);
|
|
4137
|
+
rb_enc_associate(str, rb_utf8_encoding());
|
|
4138
|
+
return str;
|
|
4139
|
+
}
|
|
4140
|
+
|
|
4141
|
+
static size_t fs_json_offset(fs_source_t *source, struct json value) {
|
|
4142
|
+
const char *raw = json_raw(value);
|
|
4143
|
+
|
|
4144
|
+
if (!raw || raw < source->data || raw > source->data + source->len) {
|
|
4145
|
+
return 0;
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
return (size_t)(raw - source->data);
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
static VALUE fs_error_hash(long feature_index, size_t byte_offset, VALUE reason) {
|
|
4152
|
+
VALUE h = rb_hash_new();
|
|
4153
|
+
rb_hash_aset(h, fs_sym("feature_index"), LONG2NUM(feature_index));
|
|
4154
|
+
rb_hash_aset(h, fs_sym("byte_offset"), ULL2NUM((unsigned long long)byte_offset));
|
|
4155
|
+
rb_hash_aset(h, fs_sym("reason"), reason);
|
|
4156
|
+
return h;
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
static void fs_report_error(VALUE errors, long max_errors, long feature_index, size_t byte_offset,
|
|
4160
|
+
VALUE reason) {
|
|
4161
|
+
if (RARRAY_LEN(errors) < max_errors) {
|
|
4162
|
+
rb_ary_push(errors, fs_error_hash(feature_index, byte_offset, reason));
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
static void TG_GEOMETRY_NORETURN fs_raise_parse_error_value(long feature_index, size_t byte_offset,
|
|
4167
|
+
VALUE reason) {
|
|
4168
|
+
VALUE prefix =
|
|
4169
|
+
rb_sprintf("feature %ld at byte %llu: ", feature_index, (unsigned long long)byte_offset);
|
|
4170
|
+
VALUE message = rb_str_plus(prefix, reason);
|
|
4171
|
+
rb_exc_raise(rb_exc_new_str(eTGGeometryParseError, message));
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4174
|
+
static void TG_GEOMETRY_NORETURN fs_raise_argument_error(long feature_index, size_t byte_offset,
|
|
4175
|
+
const char *reason) {
|
|
4176
|
+
rb_raise(eTGGeometryArgumentError, "feature %ld at byte %llu: %s", feature_index,
|
|
4177
|
+
(unsigned long long)byte_offset, reason);
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
static VALUE fs_copy_json_string_value(struct json value) {
|
|
4181
|
+
size_t len = json_string_copy(value, NULL, 0);
|
|
4182
|
+
VALUE str;
|
|
4183
|
+
|
|
4184
|
+
if (len > LONG_MAX) {
|
|
4185
|
+
rb_raise(rb_eNoMemError, "JSON string is too large");
|
|
4186
|
+
}
|
|
4187
|
+
|
|
4188
|
+
str = rb_str_new(NULL, (long)len + 1);
|
|
4189
|
+
json_string_copy(value, RSTRING_PTR(str), len + 1);
|
|
4190
|
+
rb_str_set_len(str, (long)len);
|
|
4191
|
+
rb_enc_associate(str, rb_utf8_encoding());
|
|
4192
|
+
return str;
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
static bool fs_json_number_is_integer(struct json value) {
|
|
4196
|
+
const char *raw = json_raw(value);
|
|
4197
|
+
size_t len = json_raw_length(value);
|
|
4198
|
+
|
|
4199
|
+
if (!raw || len == 0)
|
|
4200
|
+
return false;
|
|
4201
|
+
for (size_t i = 0; i < len; i++) {
|
|
4202
|
+
if (raw[i] == '.' || raw[i] == 'e' || raw[i] == 'E')
|
|
4203
|
+
return false;
|
|
4204
|
+
}
|
|
4205
|
+
return true;
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
static bool fs_integer_id_from_json(struct json value, VALUE *id_out, VALUE *error_message) {
|
|
4209
|
+
const char *raw = json_raw(value);
|
|
4210
|
+
size_t len = json_raw_length(value);
|
|
4211
|
+
char stack[64];
|
|
4212
|
+
char *buf = stack;
|
|
4213
|
+
char *endp = NULL;
|
|
4214
|
+
long long parsed;
|
|
4215
|
+
|
|
4216
|
+
if (!fs_json_number_is_integer(value)) {
|
|
4217
|
+
*error_message = rb_str_new_cstr("invalid id: numeric id must be an integer");
|
|
4218
|
+
return false;
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
if (len >= sizeof(stack)) {
|
|
4222
|
+
buf = (char *)malloc(len + 1);
|
|
4223
|
+
if (!buf)
|
|
4224
|
+
rb_raise(rb_eNoMemError, "id buffer allocation failed");
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4227
|
+
memcpy(buf, raw, len);
|
|
4228
|
+
buf[len] = '\0';
|
|
4229
|
+
errno = 0;
|
|
4230
|
+
parsed = strtoll(buf, &endp, 10);
|
|
4231
|
+
|
|
4232
|
+
if (errno != 0 || endp != buf + len) {
|
|
4233
|
+
if (buf != stack)
|
|
4234
|
+
free(buf);
|
|
4235
|
+
*error_message = rb_str_new_cstr("invalid id: integer is out of range");
|
|
4236
|
+
return false;
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
*id_out = LL2NUM(parsed);
|
|
4240
|
+
if (buf != stack)
|
|
4241
|
+
free(buf);
|
|
4242
|
+
return true;
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
static bool fs_validate_integer_id_json(struct json value, VALUE *error_message) {
|
|
4246
|
+
const char *raw = json_raw(value);
|
|
4247
|
+
size_t len = json_raw_length(value);
|
|
4248
|
+
char stack[64];
|
|
4249
|
+
char *buf = stack;
|
|
4250
|
+
char *endp = NULL;
|
|
4251
|
+
|
|
4252
|
+
if (!fs_json_number_is_integer(value)) {
|
|
4253
|
+
*error_message = rb_str_new_cstr("invalid id: numeric id must be an integer");
|
|
4254
|
+
return false;
|
|
4255
|
+
}
|
|
4256
|
+
|
|
4257
|
+
if (len >= sizeof(stack)) {
|
|
4258
|
+
buf = (char *)malloc(len + 1);
|
|
4259
|
+
if (!buf)
|
|
4260
|
+
rb_raise(rb_eNoMemError, "id buffer allocation failed");
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
memcpy(buf, raw, len);
|
|
4264
|
+
buf[len] = '\0';
|
|
4265
|
+
errno = 0;
|
|
4266
|
+
(void)strtoll(buf, &endp, 10);
|
|
4267
|
+
|
|
4268
|
+
if (errno != 0 || endp != buf + len) {
|
|
4269
|
+
if (buf != stack)
|
|
4270
|
+
free(buf);
|
|
4271
|
+
*error_message = rb_str_new_cstr("invalid id: integer is out of range");
|
|
4272
|
+
return false;
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
if (buf != stack)
|
|
4276
|
+
free(buf);
|
|
4277
|
+
return true;
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
static bool fs_id_from_json_value(struct json value, VALUE *id_out, VALUE *error_message,
|
|
4281
|
+
bool materialize_id) {
|
|
4282
|
+
switch (json_type(value)) {
|
|
4283
|
+
case JSON_STRING:
|
|
4284
|
+
*id_out = materialize_id ? fs_copy_json_string_value(value) : Qnil;
|
|
4285
|
+
return true;
|
|
4286
|
+
case JSON_NUMBER:
|
|
4287
|
+
if (!materialize_id) {
|
|
4288
|
+
*id_out = Qnil;
|
|
4289
|
+
return fs_validate_integer_id_json(value, error_message);
|
|
4290
|
+
}
|
|
4291
|
+
return fs_integer_id_from_json(value, id_out, error_message);
|
|
4292
|
+
case JSON_NULL:
|
|
4293
|
+
case JSON_TRUE:
|
|
4294
|
+
case JSON_FALSE:
|
|
4295
|
+
case JSON_ARRAY:
|
|
4296
|
+
case JSON_OBJECT:
|
|
4297
|
+
*error_message = rb_str_new_cstr("invalid id: expected JSON string or integer number");
|
|
4298
|
+
return false;
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
*error_message = rb_str_new_cstr("invalid id");
|
|
4302
|
+
return false;
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
static VALUE fs_default_id_path(void) {
|
|
4306
|
+
return rb_ary_new_from_args(2, fs_cstr_utf8_string("properties"), fs_cstr_utf8_string("@id"));
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4309
|
+
static VALUE fs_normalize_id_path(VALUE value) {
|
|
4310
|
+
VALUE path;
|
|
4311
|
+
|
|
4312
|
+
if (NIL_P(value)) {
|
|
4313
|
+
return fs_default_id_path();
|
|
4314
|
+
}
|
|
4315
|
+
|
|
4316
|
+
if (RB_TYPE_P(value, T_STRING)) {
|
|
4317
|
+
path = rb_funcall(value, rb_intern("split"), 1, rb_str_new_cstr("."));
|
|
4318
|
+
} else if (RB_TYPE_P(value, T_ARRAY)) {
|
|
4319
|
+
path = rb_ary_dup(value);
|
|
4320
|
+
} else {
|
|
4321
|
+
rb_raise(eTGGeometryArgumentError, "id: must be String or Array<String>");
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
if (RARRAY_LEN(path) == 0) {
|
|
4325
|
+
rb_raise(eTGGeometryArgumentError, "id: path cannot be empty");
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
for (long i = 0; i < RARRAY_LEN(path); i++) {
|
|
4329
|
+
VALUE part = rb_ary_entry(path, i);
|
|
4330
|
+
if (!RB_TYPE_P(part, T_STRING)) {
|
|
4331
|
+
rb_raise(eTGGeometryArgumentError, "id: every path component must be String");
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4335
|
+
return path;
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
static bool fs_json_get_path(struct json root, VALUE path, struct json *out) {
|
|
4339
|
+
struct json cur = root;
|
|
4340
|
+
|
|
4341
|
+
for (long i = 0; i < RARRAY_LEN(path); i++) {
|
|
4342
|
+
VALUE key = rb_ary_entry(path, i);
|
|
4343
|
+
StringValue(key);
|
|
4344
|
+
cur = json_object_get(cur, StringValueCStr(key));
|
|
4345
|
+
if (!json_exists(cur)) {
|
|
4346
|
+
*out = (struct json){0};
|
|
4347
|
+
return false;
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
*out = cur;
|
|
4352
|
+
return true;
|
|
4353
|
+
}
|
|
4354
|
+
|
|
4355
|
+
static unsigned int fs_geometry_type_bit(struct json type_value) {
|
|
4356
|
+
if (json_string_compare(type_value, "Point") == 0)
|
|
4357
|
+
return FS_GEOM_POINT;
|
|
4358
|
+
if (json_string_compare(type_value, "LineString") == 0)
|
|
4359
|
+
return FS_GEOM_LINESTRING;
|
|
4360
|
+
if (json_string_compare(type_value, "Polygon") == 0)
|
|
4361
|
+
return FS_GEOM_POLYGON;
|
|
4362
|
+
if (json_string_compare(type_value, "MultiPoint") == 0)
|
|
4363
|
+
return FS_GEOM_MULTIPOINT;
|
|
4364
|
+
if (json_string_compare(type_value, "MultiLineString") == 0)
|
|
4365
|
+
return FS_GEOM_MULTILINESTRING;
|
|
4366
|
+
if (json_string_compare(type_value, "MultiPolygon") == 0)
|
|
4367
|
+
return FS_GEOM_MULTIPOLYGON;
|
|
4368
|
+
if (json_string_compare(type_value, "GeometryCollection") == 0)
|
|
4369
|
+
return FS_GEOM_GEOMETRYCOLLECTION;
|
|
4370
|
+
return 0;
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
static unsigned int fs_symbol_geometry_type_bit(VALUE sym) {
|
|
4374
|
+
ID id;
|
|
4375
|
+
|
|
4376
|
+
if (!SYMBOL_P(sym)) {
|
|
4377
|
+
rb_raise(eTGGeometryArgumentError, "only: must contain geometry type symbols");
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
id = SYM2ID(sym);
|
|
4381
|
+
if (id == id_point)
|
|
4382
|
+
return FS_GEOM_POINT;
|
|
4383
|
+
if (id == id_linestring)
|
|
4384
|
+
return FS_GEOM_LINESTRING;
|
|
4385
|
+
if (id == id_polygon)
|
|
4386
|
+
return FS_GEOM_POLYGON;
|
|
4387
|
+
if (id == id_multipoint)
|
|
4388
|
+
return FS_GEOM_MULTIPOINT;
|
|
4389
|
+
if (id == id_multilinestring)
|
|
4390
|
+
return FS_GEOM_MULTILINESTRING;
|
|
4391
|
+
if (id == id_multipolygon)
|
|
4392
|
+
return FS_GEOM_MULTIPOLYGON;
|
|
4393
|
+
if (id == id_geometrycollection)
|
|
4394
|
+
return FS_GEOM_GEOMETRYCOLLECTION;
|
|
4395
|
+
|
|
4396
|
+
rb_raise(eTGGeometryArgumentError,
|
|
4397
|
+
"only: must contain one of :point, :linestring, :polygon, :multipoint, "
|
|
4398
|
+
":multilinestring, :multipolygon, :geometrycollection");
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
static void fs_parse_only_option(fs_options_t *opts, VALUE value, bool present) {
|
|
4402
|
+
opts->only_all = false;
|
|
4403
|
+
opts->only_mask = FS_GEOM_POLYGON | FS_GEOM_MULTIPOLYGON;
|
|
4404
|
+
|
|
4405
|
+
if (present && NIL_P(value)) {
|
|
4406
|
+
opts->only_all = true;
|
|
4407
|
+
opts->only_mask = 0;
|
|
4408
|
+
return;
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
if (!present) {
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
if (!RB_TYPE_P(value, T_ARRAY)) {
|
|
4416
|
+
rb_raise(eTGGeometryArgumentError, "only: must be Array<Symbol> or nil");
|
|
4417
|
+
}
|
|
4418
|
+
|
|
4419
|
+
opts->only_mask = 0;
|
|
4420
|
+
for (long i = 0; i < RARRAY_LEN(value); i++) {
|
|
4421
|
+
opts->only_mask |= fs_symbol_geometry_type_bit(rb_ary_entry(value, i));
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
static fs_on_invalid_t fs_parse_on_invalid(VALUE value) {
|
|
4426
|
+
ID id;
|
|
4427
|
+
if (!SYMBOL_P(value)) {
|
|
4428
|
+
rb_raise(eTGGeometryArgumentError, "on_invalid: must be one of :raise, :skip");
|
|
4429
|
+
}
|
|
4430
|
+
id = SYM2ID(value);
|
|
4431
|
+
if (id == id_raise)
|
|
4432
|
+
return FS_ON_INVALID_RAISE;
|
|
4433
|
+
if (id == id_skip)
|
|
4434
|
+
return FS_ON_INVALID_SKIP;
|
|
4435
|
+
rb_raise(eTGGeometryArgumentError, "on_invalid: must be one of :raise, :skip");
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
static fs_on_missing_id_t fs_parse_on_missing_id(VALUE value) {
|
|
4439
|
+
ID id;
|
|
4440
|
+
if (!SYMBOL_P(value)) {
|
|
4441
|
+
rb_raise(eTGGeometryArgumentError, "on_missing_id: must be one of :raise, :skip, :ordinal");
|
|
4442
|
+
}
|
|
4443
|
+
id = SYM2ID(value);
|
|
4444
|
+
if (id == id_raise)
|
|
4445
|
+
return FS_ON_MISSING_ID_RAISE;
|
|
4446
|
+
if (id == id_skip)
|
|
4447
|
+
return FS_ON_MISSING_ID_SKIP;
|
|
4448
|
+
if (id == id_ordinal)
|
|
4449
|
+
return FS_ON_MISSING_ID_ORDINAL;
|
|
4450
|
+
rb_raise(eTGGeometryArgumentError, "on_missing_id: must be one of :raise, :skip, :ordinal");
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4453
|
+
static bool fs_bool_option(VALUE value, const char *name) {
|
|
4454
|
+
if (value == Qtrue)
|
|
4455
|
+
return true;
|
|
4456
|
+
if (value == Qfalse)
|
|
4457
|
+
return false;
|
|
4458
|
+
rb_raise(eTGGeometryArgumentError, "%s must be true or false", name);
|
|
4459
|
+
}
|
|
4460
|
+
|
|
4461
|
+
static bool fs_keyword_allowed(ID key_id, fs_mode_t mode) {
|
|
4462
|
+
if (key_id == id_id || key_id == id_only || key_id == id_on_invalid ||
|
|
4463
|
+
key_id == id_on_missing_id || key_id == id_geometry_index) {
|
|
4464
|
+
return true;
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
if (mode == FS_MODE_BUILD_INDEX) {
|
|
4468
|
+
return key_id == id_strategy || key_id == id_predicate || key_id == id_report;
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
return key_id == id_report || key_id == id_max_errors;
|
|
4472
|
+
}
|
|
4473
|
+
|
|
4474
|
+
static int fs_validate_keyword_i(VALUE key, VALUE value, VALUE mode_value) {
|
|
4475
|
+
fs_mode_t mode = (fs_mode_t)NUM2INT(mode_value);
|
|
4476
|
+
(void)value;
|
|
4477
|
+
|
|
4478
|
+
if (!SYMBOL_P(key) || !fs_keyword_allowed(SYM2ID(key), mode)) {
|
|
4479
|
+
VALUE inspected = rb_inspect(key);
|
|
4480
|
+
rb_raise(eTGGeometryArgumentError, "unknown keyword: %s", StringValueCStr(inspected));
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4483
|
+
return ST_CONTINUE;
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4486
|
+
static void fs_validate_keywords(VALUE kwargs, fs_mode_t mode) {
|
|
4487
|
+
if (NIL_P(kwargs))
|
|
4488
|
+
return;
|
|
4489
|
+
if (!RB_TYPE_P(kwargs, T_HASH)) {
|
|
4490
|
+
rb_raise(rb_eTypeError, "keywords must be a Hash");
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4493
|
+
rb_hash_foreach(kwargs, fs_validate_keyword_i, INT2FIX((int)mode));
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4496
|
+
static fs_options_t fs_parse_options(VALUE kwargs, fs_mode_t mode) {
|
|
4497
|
+
fs_options_t opts;
|
|
4498
|
+
VALUE id_value;
|
|
4499
|
+
VALUE only_value;
|
|
4500
|
+
VALUE on_invalid_value;
|
|
4501
|
+
VALUE on_missing_id_value;
|
|
4502
|
+
VALUE report_value;
|
|
4503
|
+
VALUE max_errors_value;
|
|
4504
|
+
VALUE geometry_index_value;
|
|
4505
|
+
VALUE strategy_value;
|
|
4506
|
+
VALUE predicate_value;
|
|
4507
|
+
bool only_present = false;
|
|
4508
|
+
|
|
4509
|
+
memset(&opts, 0, sizeof(opts));
|
|
4510
|
+
fs_validate_keywords(kwargs, mode);
|
|
4511
|
+
|
|
4512
|
+
id_value = fs_kwargs_value(kwargs, id_id, Qnil, NULL);
|
|
4513
|
+
only_value = fs_kwargs_value(kwargs, id_only, Qnil, &only_present);
|
|
4514
|
+
on_invalid_value = fs_kwargs_value(kwargs, id_on_invalid, ID2SYM(id_raise), NULL);
|
|
4515
|
+
on_missing_id_value = fs_kwargs_value(kwargs, id_on_missing_id, ID2SYM(id_raise), NULL);
|
|
4516
|
+
report_value = fs_kwargs_value(kwargs, id_report, Qfalse, NULL);
|
|
4517
|
+
max_errors_value = fs_kwargs_value(kwargs, id_max_errors, INT2NUM(100), NULL);
|
|
4518
|
+
geometry_index_value = fs_kwargs_value(kwargs, id_geometry_index, ID2SYM(id_ystripes), NULL);
|
|
4519
|
+
|
|
4520
|
+
opts.id_path = fs_normalize_id_path(id_value);
|
|
4521
|
+
fs_parse_only_option(&opts, only_value, only_present);
|
|
4522
|
+
opts.on_invalid = fs_parse_on_invalid(on_invalid_value);
|
|
4523
|
+
opts.on_missing_id = fs_parse_on_missing_id(on_missing_id_value);
|
|
4524
|
+
opts.report = fs_bool_option(report_value, "report:");
|
|
4525
|
+
opts.max_errors = NUM2LONG(max_errors_value);
|
|
4526
|
+
if (opts.max_errors < 0) {
|
|
4527
|
+
rb_raise(eTGGeometryArgumentError, "max_errors: must be >= 0");
|
|
4528
|
+
}
|
|
4529
|
+
opts.geometry_index = parse_index_symbol(geometry_index_value);
|
|
4530
|
+
|
|
4531
|
+
if (opts.on_invalid == FS_ON_INVALID_SKIP && !opts.report) {
|
|
4532
|
+
rb_raise(eTGGeometryArgumentError, "on_invalid: :skip requires report: true");
|
|
4533
|
+
}
|
|
4534
|
+
if (opts.on_missing_id == FS_ON_MISSING_ID_SKIP && !opts.report) {
|
|
4535
|
+
rb_raise(eTGGeometryArgumentError, "on_missing_id: :skip requires report: true");
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
if (mode == FS_MODE_BUILD_INDEX) {
|
|
4539
|
+
if (opts.report) {
|
|
4540
|
+
rb_raise(eTGGeometryArgumentError, "build_index_* does not accept report: true");
|
|
4541
|
+
}
|
|
4542
|
+
strategy_value = fs_kwargs_value(kwargs, id_strategy, ID2SYM(id_rtree), NULL);
|
|
4543
|
+
predicate_value = fs_kwargs_value(kwargs, id_predicate, ID2SYM(id_covers), NULL);
|
|
4544
|
+
opts.strategy = parse_index_strategy_symbol(strategy_value);
|
|
4545
|
+
opts.predicate = parse_index_predicate_symbol(predicate_value);
|
|
4546
|
+
} else {
|
|
4547
|
+
opts.strategy = TG_GEOMETRY_INDEX_STRATEGY_RTREE;
|
|
4548
|
+
opts.predicate = TG_GEOMETRY_INDEX_PREDICATE_COVERS;
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4551
|
+
return opts;
|
|
4552
|
+
}
|
|
4553
|
+
|
|
4554
|
+
static VALUE fs_tg_parse_error_message(struct tg_geom *geom) {
|
|
4555
|
+
const char *err;
|
|
4556
|
+
VALUE message;
|
|
4557
|
+
|
|
4558
|
+
if (!geom) {
|
|
4559
|
+
rb_raise(rb_eNoMemError, "TG geometry allocation failed");
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4562
|
+
err = tg_geom_error(geom);
|
|
4563
|
+
if (!err) {
|
|
4564
|
+
return Qnil;
|
|
4565
|
+
}
|
|
4566
|
+
|
|
4567
|
+
message = rb_str_new_cstr(err);
|
|
4568
|
+
tg_geom_free(geom);
|
|
4569
|
+
return message;
|
|
4570
|
+
}
|
|
4571
|
+
|
|
4572
|
+
static bool fs_validate_geometry_or_error(struct json geometry, enum tg_index geometry_index,
|
|
4573
|
+
VALUE *error_message) {
|
|
4574
|
+
struct tg_geom *geom =
|
|
4575
|
+
tg_parse_geojsonn_ix(json_raw(geometry), json_raw_length(geometry), geometry_index);
|
|
4576
|
+
VALUE message = fs_tg_parse_error_message(geom);
|
|
4577
|
+
|
|
4578
|
+
if (!NIL_P(message)) {
|
|
4579
|
+
*error_message = message;
|
|
4580
|
+
return false;
|
|
4581
|
+
}
|
|
4582
|
+
|
|
4583
|
+
tg_geom_free(geom);
|
|
4584
|
+
return true;
|
|
4585
|
+
}
|
|
4586
|
+
|
|
4587
|
+
static VALUE fs_missing_id_ordinal(long feature_index) {
|
|
4588
|
+
return rb_sprintf("feature/%ld", feature_index);
|
|
4589
|
+
}
|
|
4590
|
+
|
|
4591
|
+
static bool fs_extract_id(fs_source_t *source, fs_options_t *opts, struct json feature,
|
|
4592
|
+
long feature_index, VALUE *id_out, VALUE *error_message,
|
|
4593
|
+
bool materialize_id) {
|
|
4594
|
+
struct json id_json;
|
|
4595
|
+
|
|
4596
|
+
if (!fs_json_get_path(feature, opts->id_path, &id_json) || json_type(id_json) == JSON_NULL) {
|
|
4597
|
+
switch (opts->on_missing_id) {
|
|
4598
|
+
case FS_ON_MISSING_ID_ORDINAL:
|
|
4599
|
+
*id_out = materialize_id ? fs_missing_id_ordinal(feature_index) : Qnil;
|
|
4600
|
+
return true;
|
|
4601
|
+
case FS_ON_MISSING_ID_SKIP:
|
|
4602
|
+
*error_message = rb_sprintf("missing id at configured path");
|
|
4603
|
+
return false;
|
|
4604
|
+
case FS_ON_MISSING_ID_RAISE:
|
|
4605
|
+
fs_raise_argument_error(feature_index, fs_json_offset(source, feature),
|
|
4606
|
+
"missing id at configured path");
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4610
|
+
if (!fs_id_from_json_value(id_json, id_out, error_message, materialize_id)) {
|
|
4611
|
+
if (opts->on_invalid == FS_ON_INVALID_RAISE) {
|
|
4612
|
+
fs_raise_argument_error(feature_index, fs_json_offset(source, id_json),
|
|
4613
|
+
StringValueCStr(*error_message));
|
|
4614
|
+
}
|
|
4615
|
+
return false;
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4618
|
+
return true;
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
static bool fs_feature_prepare(fs_source_t *source, fs_options_t *opts, fs_mode_t mode,
|
|
4622
|
+
struct json feature, long feature_index, bool materialize_id,
|
|
4623
|
+
fs_feature_result_t *result) {
|
|
4624
|
+
struct json geometry;
|
|
4625
|
+
struct json geometry_type;
|
|
4626
|
+
struct json properties;
|
|
4627
|
+
unsigned int geom_bit;
|
|
4628
|
+
VALUE id = Qnil;
|
|
4629
|
+
VALUE error_message = Qnil;
|
|
4630
|
+
|
|
4631
|
+
memset(result, 0, sizeof(*result));
|
|
4632
|
+
result->feature = feature;
|
|
4633
|
+
result->id = Qnil;
|
|
4634
|
+
result->reason_value = Qnil;
|
|
4635
|
+
|
|
4636
|
+
if (!json_exists(feature) || json_type(feature) != JSON_OBJECT) {
|
|
4637
|
+
result->reason = "feature is not an object";
|
|
4638
|
+
return false;
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4641
|
+
geometry = json_object_get(feature, "geometry");
|
|
4642
|
+
if (!json_exists(geometry) || json_type(geometry) == JSON_NULL) {
|
|
4643
|
+
result->reason = "missing geometry";
|
|
4644
|
+
return false;
|
|
4645
|
+
}
|
|
4646
|
+
if (json_type(geometry) != JSON_OBJECT) {
|
|
4647
|
+
result->reason = "geometry must be an object";
|
|
4648
|
+
return false;
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
geometry_type = json_object_get(geometry, "type");
|
|
4652
|
+
if (!json_exists(geometry_type) || json_type(geometry_type) != JSON_STRING) {
|
|
4653
|
+
result->reason = "geometry.type must be a string";
|
|
4654
|
+
return false;
|
|
4655
|
+
}
|
|
4656
|
+
|
|
4657
|
+
geom_bit = fs_geometry_type_bit(geometry_type);
|
|
4658
|
+
if (geom_bit == 0) {
|
|
4659
|
+
result->reason = "unsupported geometry.type";
|
|
4660
|
+
return false;
|
|
4661
|
+
}
|
|
4662
|
+
|
|
4663
|
+
if (!opts->only_all && (opts->only_mask & geom_bit) == 0) {
|
|
4664
|
+
result->filtered = true;
|
|
4665
|
+
return true;
|
|
4666
|
+
}
|
|
4667
|
+
|
|
4668
|
+
if (!fs_extract_id(source, opts, feature, feature_index, &id, &error_message, materialize_id)) {
|
|
4669
|
+
result->missing_id_skip = (opts->on_missing_id == FS_ON_MISSING_ID_SKIP);
|
|
4670
|
+
result->reason_value = error_message;
|
|
4671
|
+
return false;
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
if (mode == FS_MODE_READ_FEATURES) {
|
|
4675
|
+
properties = json_object_get(feature, "properties");
|
|
4676
|
+
if (!json_exists(properties) || json_type(properties) == JSON_NULL) {
|
|
4677
|
+
/* returned as "null" */
|
|
4678
|
+
} else if (json_type(properties) != JSON_OBJECT) {
|
|
4679
|
+
result->reason = "properties must be an object or null";
|
|
4680
|
+
return false;
|
|
4681
|
+
}
|
|
4682
|
+
result->properties = properties;
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
result->accepted = true;
|
|
4686
|
+
result->id = id;
|
|
4687
|
+
result->geometry = geometry;
|
|
4688
|
+
return true;
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
static void fs_parse_root(fs_source_t *source, struct json *features_out) {
|
|
4692
|
+
struct json_valid valid;
|
|
4693
|
+
struct json root;
|
|
4694
|
+
struct json type;
|
|
4695
|
+
struct json features;
|
|
4696
|
+
|
|
4697
|
+
valid = json_validn_ex(source->data, source->len, 0);
|
|
4698
|
+
if (!valid.valid) {
|
|
4699
|
+
rb_raise(eTGGeometryParseError, "malformed JSON at byte %llu",
|
|
4700
|
+
(unsigned long long)valid.pos);
|
|
4701
|
+
}
|
|
4702
|
+
|
|
4703
|
+
root = json_parsen(source->data, source->len);
|
|
4704
|
+
if (!json_exists(root) || json_type(root) != JSON_OBJECT) {
|
|
4705
|
+
rb_raise(eTGGeometryParseError, "GeoJSON root must be a FeatureCollection object");
|
|
4706
|
+
}
|
|
4707
|
+
|
|
4708
|
+
type = json_object_get(root, "type");
|
|
4709
|
+
if (!json_exists(type) || json_type(type) != JSON_STRING ||
|
|
4710
|
+
json_string_compare(type, "FeatureCollection") != 0) {
|
|
4711
|
+
rb_raise(eTGGeometryParseError, "GeoJSON root type must be FeatureCollection");
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4714
|
+
features = json_object_get(root, "features");
|
|
4715
|
+
if (!json_exists(features) || json_type(features) != JSON_ARRAY) {
|
|
4716
|
+
rb_raise(eTGGeometryParseError, "GeoJSON FeatureCollection features must be an Array");
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
*features_out = features;
|
|
4720
|
+
}
|
|
4721
|
+
|
|
4722
|
+
static void fs_handle_invalid_or_raise(fs_source_t *source, fs_options_t *opts,
|
|
4723
|
+
fs_feature_result_t *feature_result, long feature_index,
|
|
4724
|
+
VALUE errors, long *skipped) {
|
|
4725
|
+
size_t offset =
|
|
4726
|
+
fs_json_offset(source, json_exists(feature_result->geometry) ? feature_result->geometry
|
|
4727
|
+
: feature_result->feature);
|
|
4728
|
+
VALUE reason =
|
|
4729
|
+
!NIL_P(feature_result->reason_value)
|
|
4730
|
+
? feature_result->reason_value
|
|
4731
|
+
: rb_str_new_cstr(feature_result->reason ? feature_result->reason : "invalid feature");
|
|
4732
|
+
|
|
4733
|
+
if (feature_result->missing_id_skip || opts->on_invalid == FS_ON_INVALID_SKIP) {
|
|
4734
|
+
(*skipped)++;
|
|
4735
|
+
fs_report_error(errors, opts->max_errors, feature_index, offset, reason);
|
|
4736
|
+
return;
|
|
4737
|
+
}
|
|
4738
|
+
|
|
4739
|
+
fs_raise_parse_error_value(feature_index, offset, reason);
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4742
|
+
static VALUE fs_properties_json_string(struct json properties) {
|
|
4743
|
+
if (!json_exists(properties) || json_type(properties) == JSON_NULL) {
|
|
4744
|
+
return fs_cstr_utf8_string("null");
|
|
4745
|
+
}
|
|
4746
|
+
|
|
4747
|
+
return fs_utf8_string(json_raw(properties), json_raw_length(properties));
|
|
4748
|
+
}
|
|
4749
|
+
|
|
4750
|
+
/*
|
|
4751
|
+
* FeatureSource safe bulk executor.
|
|
4752
|
+
*
|
|
4753
|
+
* Heavy source traversal and TG geometry parsing run without GVL. The no-GVL phase stores only
|
|
4754
|
+
* C-owned data and json.c ranges backed by the owned source buffer. Ruby VALUE ids/strings/arrays
|
|
4755
|
+
* and rb_gc_adjust_memory_usage are only created/called again after the GVL is restored.
|
|
4756
|
+
*/
|
|
4757
|
+
typedef enum {
|
|
4758
|
+
FS_SAFE_ERROR_NONE,
|
|
4759
|
+
FS_SAFE_ERROR_NOMEM,
|
|
4760
|
+
FS_SAFE_ERROR_PARSE,
|
|
4761
|
+
FS_SAFE_ERROR_ARGUMENT,
|
|
4762
|
+
FS_SAFE_ERROR_SYSTEM,
|
|
4763
|
+
} fs_safe_error_type_t;
|
|
4764
|
+
|
|
4765
|
+
typedef enum {
|
|
4766
|
+
FS_SAFE_ID_STRING,
|
|
4767
|
+
FS_SAFE_ID_INTEGER,
|
|
4768
|
+
FS_SAFE_ID_ORDINAL,
|
|
4769
|
+
} fs_safe_id_kind_t;
|
|
4770
|
+
|
|
4771
|
+
typedef struct {
|
|
4772
|
+
fs_safe_id_kind_t kind;
|
|
4773
|
+
struct json json_value;
|
|
4774
|
+
long long integer_value;
|
|
4775
|
+
long feature_index;
|
|
4776
|
+
} fs_safe_id_t;
|
|
4777
|
+
|
|
4778
|
+
typedef struct {
|
|
4779
|
+
fs_safe_id_t id;
|
|
4780
|
+
struct json geometry;
|
|
4781
|
+
struct json properties;
|
|
4782
|
+
bool properties_null;
|
|
4783
|
+
} fs_safe_row_t;
|
|
4784
|
+
|
|
4785
|
+
typedef struct {
|
|
4786
|
+
fs_safe_id_t id;
|
|
4787
|
+
struct tg_geom *geom;
|
|
4788
|
+
struct tg_rect bbox;
|
|
4789
|
+
size_t geom_bytes;
|
|
4790
|
+
long ordinal;
|
|
4791
|
+
} fs_safe_native_entry_t;
|
|
4792
|
+
|
|
4793
|
+
typedef struct {
|
|
4794
|
+
long feature_index;
|
|
4795
|
+
size_t byte_offset;
|
|
4796
|
+
char *reason;
|
|
4797
|
+
} fs_safe_report_error_t;
|
|
4798
|
+
|
|
4799
|
+
typedef struct {
|
|
4800
|
+
fs_safe_error_type_t type;
|
|
4801
|
+
int sys_errno;
|
|
4802
|
+
long feature_index;
|
|
4803
|
+
size_t byte_offset;
|
|
4804
|
+
char *message;
|
|
4805
|
+
} fs_safe_error_t;
|
|
4806
|
+
|
|
4807
|
+
typedef struct {
|
|
4808
|
+
char **keys;
|
|
4809
|
+
size_t *lens;
|
|
4810
|
+
long len;
|
|
4811
|
+
bool only_all;
|
|
4812
|
+
unsigned int only_mask;
|
|
4813
|
+
fs_on_invalid_t on_invalid;
|
|
4814
|
+
fs_on_missing_id_t on_missing_id;
|
|
4815
|
+
bool report;
|
|
4816
|
+
long max_errors;
|
|
4817
|
+
enum tg_index geometry_index;
|
|
4818
|
+
enum tg_geometry_index_strategy strategy;
|
|
4819
|
+
enum tg_geometry_index_predicate predicate;
|
|
4820
|
+
} fs_safe_options_t;
|
|
4821
|
+
|
|
4822
|
+
typedef struct {
|
|
4823
|
+
fs_mode_t mode;
|
|
4824
|
+
fs_safe_options_t opts;
|
|
4825
|
+
|
|
4826
|
+
char *path;
|
|
4827
|
+
char *source_data;
|
|
4828
|
+
size_t source_len;
|
|
4829
|
+
bool source_ruby_alloc;
|
|
4830
|
+
|
|
4831
|
+
fs_safe_row_t *rows;
|
|
4832
|
+
long row_len;
|
|
4833
|
+
long row_cap;
|
|
4834
|
+
|
|
4835
|
+
fs_safe_native_entry_t *entries;
|
|
4836
|
+
long entry_len;
|
|
4837
|
+
|
|
4838
|
+
long skipped;
|
|
4839
|
+
long filtered;
|
|
4840
|
+
fs_safe_report_error_t *errors;
|
|
4841
|
+
long errors_len;
|
|
4842
|
+
long errors_cap;
|
|
4843
|
+
|
|
4844
|
+
fs_safe_error_t fatal;
|
|
4845
|
+
} fs_safe_job_t;
|
|
4846
|
+
|
|
4847
|
+
typedef struct {
|
|
4848
|
+
fs_safe_job_t *job;
|
|
4849
|
+
VALUE result;
|
|
4850
|
+
} fs_safe_materialize_args_t;
|
|
4851
|
+
|
|
4852
|
+
static char *fs_safe_strdup_len(const char *ptr, size_t len) {
|
|
4853
|
+
char *copy;
|
|
4854
|
+
|
|
4855
|
+
if (len > SIZE_MAX - 1)
|
|
4856
|
+
return NULL;
|
|
4857
|
+
copy = (char *)malloc(len + 1);
|
|
4858
|
+
if (!copy)
|
|
4859
|
+
return NULL;
|
|
4860
|
+
if (len > 0)
|
|
4861
|
+
memcpy(copy, ptr, len);
|
|
4862
|
+
copy[len] = '\0';
|
|
4863
|
+
return copy;
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
static char *fs_safe_strdup_cstr(const char *ptr) {
|
|
4867
|
+
return fs_safe_strdup_len(ptr ? ptr : "", strlen(ptr ? ptr : ""));
|
|
4868
|
+
}
|
|
4869
|
+
|
|
4870
|
+
static char *fs_safe_format_feature_message(long feature_index, size_t byte_offset,
|
|
4871
|
+
const char *reason) {
|
|
4872
|
+
const char *r = reason ? reason : "invalid feature";
|
|
4873
|
+
int n = snprintf(NULL, 0, "feature %ld at byte %llu: %s", feature_index,
|
|
4874
|
+
(unsigned long long)byte_offset, r);
|
|
4875
|
+
char *buf;
|
|
4876
|
+
|
|
4877
|
+
if (n < 0)
|
|
4878
|
+
return NULL;
|
|
4879
|
+
buf = (char *)malloc((size_t)n + 1);
|
|
4880
|
+
if (!buf)
|
|
4881
|
+
return NULL;
|
|
4882
|
+
snprintf(buf, (size_t)n + 1, "feature %ld at byte %llu: %s", feature_index,
|
|
4883
|
+
(unsigned long long)byte_offset, r);
|
|
4884
|
+
return buf;
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
static void fs_safe_set_error(fs_safe_job_t *job, fs_safe_error_type_t type, const char *message) {
|
|
4888
|
+
if (job->fatal.type != FS_SAFE_ERROR_NONE)
|
|
4889
|
+
return;
|
|
4890
|
+
job->fatal.type = type;
|
|
4891
|
+
job->fatal.message = fs_safe_strdup_cstr(message);
|
|
4892
|
+
if (!job->fatal.message && type != FS_SAFE_ERROR_NOMEM) {
|
|
4893
|
+
job->fatal.type = FS_SAFE_ERROR_NOMEM;
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
|
|
4897
|
+
static void fs_safe_set_system_error(fs_safe_job_t *job, int err) {
|
|
4898
|
+
if (job->fatal.type != FS_SAFE_ERROR_NONE)
|
|
4899
|
+
return;
|
|
4900
|
+
job->fatal.type = FS_SAFE_ERROR_SYSTEM;
|
|
4901
|
+
job->fatal.sys_errno = err;
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4904
|
+
static void fs_safe_set_feature_error(fs_safe_job_t *job, fs_safe_error_type_t type,
|
|
4905
|
+
long feature_index, size_t byte_offset, const char *reason) {
|
|
4906
|
+
if (job->fatal.type != FS_SAFE_ERROR_NONE)
|
|
4907
|
+
return;
|
|
4908
|
+
job->fatal.type = type;
|
|
4909
|
+
job->fatal.feature_index = feature_index;
|
|
4910
|
+
job->fatal.byte_offset = byte_offset;
|
|
4911
|
+
job->fatal.message = fs_safe_format_feature_message(feature_index, byte_offset, reason);
|
|
4912
|
+
if (!job->fatal.message && type != FS_SAFE_ERROR_NOMEM) {
|
|
4913
|
+
job->fatal.type = FS_SAFE_ERROR_NOMEM;
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
|
|
4917
|
+
static bool fs_safe_add_report_error(fs_safe_job_t *job, long feature_index, size_t byte_offset,
|
|
4918
|
+
const char *reason) {
|
|
4919
|
+
fs_safe_report_error_t *grown;
|
|
4920
|
+
|
|
4921
|
+
if (job->errors_len >= job->opts.max_errors)
|
|
4922
|
+
return true;
|
|
4923
|
+
|
|
4924
|
+
if (job->errors_len == job->errors_cap) {
|
|
4925
|
+
long new_cap = job->errors_cap == 0 ? 8 : job->errors_cap * 2;
|
|
4926
|
+
if (new_cap < job->errors_cap || (size_t)new_cap > SIZE_MAX / sizeof(*job->errors)) {
|
|
4927
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource report allocation overflow");
|
|
4928
|
+
return false;
|
|
4929
|
+
}
|
|
4930
|
+
grown =
|
|
4931
|
+
(fs_safe_report_error_t *)realloc(job->errors, (size_t)new_cap * sizeof(*job->errors));
|
|
4932
|
+
if (!grown) {
|
|
4933
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource report allocation failed");
|
|
4934
|
+
return false;
|
|
4935
|
+
}
|
|
4936
|
+
job->errors = grown;
|
|
4937
|
+
job->errors_cap = new_cap;
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
job->errors[job->errors_len].feature_index = feature_index;
|
|
4941
|
+
job->errors[job->errors_len].byte_offset = byte_offset;
|
|
4942
|
+
job->errors[job->errors_len].reason = fs_safe_strdup_cstr(reason ? reason : "invalid feature");
|
|
4943
|
+
if (!job->errors[job->errors_len].reason) {
|
|
4944
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM,
|
|
4945
|
+
"FeatureSource report reason allocation failed");
|
|
4946
|
+
return false;
|
|
4947
|
+
}
|
|
4948
|
+
job->errors_len++;
|
|
4949
|
+
return true;
|
|
4950
|
+
}
|
|
4951
|
+
|
|
4952
|
+
static size_t fs_safe_json_offset(fs_safe_job_t *job, struct json value) {
|
|
4953
|
+
const char *raw = json_raw(value);
|
|
4954
|
+
|
|
4955
|
+
if (!raw || !job->source_data || raw < job->source_data ||
|
|
4956
|
+
raw > job->source_data + job->source_len)
|
|
4957
|
+
return 0;
|
|
4958
|
+
return (size_t)(raw - job->source_data);
|
|
4959
|
+
}
|
|
4960
|
+
|
|
4961
|
+
static bool fs_safe_json_get_path(struct json root, fs_safe_options_t *opts, struct json *out) {
|
|
4962
|
+
struct json cur = root;
|
|
4963
|
+
|
|
4964
|
+
for (long i = 0; i < opts->len; i++) {
|
|
4965
|
+
if (!json_exists(cur) || json_type(cur) != JSON_OBJECT)
|
|
4966
|
+
return false;
|
|
4967
|
+
cur = json_object_getn(cur, opts->keys[i], opts->lens[i]);
|
|
4968
|
+
if (!json_exists(cur))
|
|
4969
|
+
return false;
|
|
4970
|
+
}
|
|
4971
|
+
|
|
4972
|
+
*out = cur;
|
|
4973
|
+
return true;
|
|
4974
|
+
}
|
|
4975
|
+
|
|
4976
|
+
static unsigned int fs_safe_geometry_type_bit(struct json type_value) {
|
|
4977
|
+
if (!json_exists(type_value) || json_type(type_value) != JSON_STRING)
|
|
4978
|
+
return 0;
|
|
4979
|
+
if (json_string_compare(type_value, "Point") == 0)
|
|
4980
|
+
return FS_GEOM_POINT;
|
|
4981
|
+
if (json_string_compare(type_value, "LineString") == 0)
|
|
4982
|
+
return FS_GEOM_LINESTRING;
|
|
4983
|
+
if (json_string_compare(type_value, "Polygon") == 0)
|
|
4984
|
+
return FS_GEOM_POLYGON;
|
|
4985
|
+
if (json_string_compare(type_value, "MultiPoint") == 0)
|
|
4986
|
+
return FS_GEOM_MULTIPOINT;
|
|
4987
|
+
if (json_string_compare(type_value, "MultiLineString") == 0)
|
|
4988
|
+
return FS_GEOM_MULTILINESTRING;
|
|
4989
|
+
if (json_string_compare(type_value, "MultiPolygon") == 0)
|
|
4990
|
+
return FS_GEOM_MULTIPOLYGON;
|
|
4991
|
+
if (json_string_compare(type_value, "GeometryCollection") == 0)
|
|
4992
|
+
return FS_GEOM_GEOMETRYCOLLECTION;
|
|
4993
|
+
return 0;
|
|
4994
|
+
}
|
|
4995
|
+
|
|
4996
|
+
static bool fs_safe_json_number_is_integer(struct json value) {
|
|
4997
|
+
const char *raw = json_raw(value);
|
|
4998
|
+
size_t len = json_raw_length(value);
|
|
4999
|
+
|
|
5000
|
+
if (!raw || len == 0)
|
|
5001
|
+
return false;
|
|
5002
|
+
for (size_t i = 0; i < len; i++) {
|
|
5003
|
+
if (raw[i] == '.' || raw[i] == 'e' || raw[i] == 'E')
|
|
5004
|
+
return false;
|
|
5005
|
+
}
|
|
5006
|
+
return true;
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
static bool fs_safe_parse_integer_id(struct json value, long long *out, const char **reason) {
|
|
5010
|
+
const char *raw = json_raw(value);
|
|
5011
|
+
size_t len = json_raw_length(value);
|
|
5012
|
+
char stack[64];
|
|
5013
|
+
char *buf = stack;
|
|
5014
|
+
char *endp = NULL;
|
|
5015
|
+
long long parsed;
|
|
5016
|
+
bool ok;
|
|
5017
|
+
|
|
5018
|
+
if (!fs_safe_json_number_is_integer(value)) {
|
|
5019
|
+
*reason = "invalid id: numeric id must be an integer";
|
|
5020
|
+
return false;
|
|
5021
|
+
}
|
|
5022
|
+
|
|
5023
|
+
if (len >= sizeof(stack)) {
|
|
5024
|
+
buf = fs_safe_strdup_len(raw, len);
|
|
5025
|
+
if (!buf) {
|
|
5026
|
+
*reason = "id buffer allocation failed";
|
|
5027
|
+
return false;
|
|
5028
|
+
}
|
|
5029
|
+
} else {
|
|
5030
|
+
memcpy(buf, raw, len);
|
|
5031
|
+
buf[len] = '\0';
|
|
5032
|
+
}
|
|
5033
|
+
|
|
5034
|
+
errno = 0;
|
|
5035
|
+
parsed = strtoll(buf, &endp, 10);
|
|
5036
|
+
ok = (errno == 0 && endp == buf + len);
|
|
5037
|
+
if (buf != stack)
|
|
5038
|
+
free(buf);
|
|
5039
|
+
|
|
5040
|
+
if (!ok) {
|
|
5041
|
+
*reason = "invalid id: integer is out of range";
|
|
5042
|
+
return false;
|
|
5043
|
+
}
|
|
5044
|
+
|
|
5045
|
+
*out = parsed;
|
|
5046
|
+
return true;
|
|
5047
|
+
}
|
|
5048
|
+
|
|
5049
|
+
static bool fs_safe_extract_id(fs_safe_job_t *job, struct json feature, long feature_index,
|
|
5050
|
+
fs_safe_id_t *id_out, const char **reason, bool *missing_id_skip,
|
|
5051
|
+
bool *argument_error) {
|
|
5052
|
+
struct json id_json;
|
|
5053
|
+
|
|
5054
|
+
*missing_id_skip = false;
|
|
5055
|
+
*argument_error = false;
|
|
5056
|
+
|
|
5057
|
+
if (!fs_safe_json_get_path(feature, &job->opts, &id_json) || json_type(id_json) == JSON_NULL) {
|
|
5058
|
+
switch (job->opts.on_missing_id) {
|
|
5059
|
+
case FS_ON_MISSING_ID_ORDINAL:
|
|
5060
|
+
id_out->kind = FS_SAFE_ID_ORDINAL;
|
|
5061
|
+
id_out->feature_index = feature_index;
|
|
5062
|
+
return true;
|
|
5063
|
+
case FS_ON_MISSING_ID_SKIP:
|
|
5064
|
+
*reason = "missing id at configured path";
|
|
5065
|
+
*missing_id_skip = true;
|
|
5066
|
+
return false;
|
|
5067
|
+
case FS_ON_MISSING_ID_RAISE:
|
|
5068
|
+
*reason = "missing id at configured path";
|
|
5069
|
+
*argument_error = true;
|
|
5070
|
+
return false;
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
|
|
5074
|
+
switch (json_type(id_json)) {
|
|
5075
|
+
case JSON_STRING:
|
|
5076
|
+
id_out->kind = FS_SAFE_ID_STRING;
|
|
5077
|
+
id_out->json_value = id_json;
|
|
5078
|
+
id_out->feature_index = feature_index;
|
|
5079
|
+
return true;
|
|
5080
|
+
case JSON_NUMBER: {
|
|
5081
|
+
long long parsed = 0;
|
|
5082
|
+
if (!fs_safe_parse_integer_id(id_json, &parsed, reason)) {
|
|
5083
|
+
*argument_error = (job->opts.on_invalid == FS_ON_INVALID_RAISE);
|
|
5084
|
+
return false;
|
|
5085
|
+
}
|
|
5086
|
+
id_out->kind = FS_SAFE_ID_INTEGER;
|
|
5087
|
+
id_out->integer_value = parsed;
|
|
5088
|
+
id_out->feature_index = feature_index;
|
|
5089
|
+
return true;
|
|
5090
|
+
}
|
|
5091
|
+
default:
|
|
5092
|
+
*reason = "invalid id: expected JSON string or integer number";
|
|
5093
|
+
*argument_error = (job->opts.on_invalid == FS_ON_INVALID_RAISE);
|
|
5094
|
+
return false;
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
|
|
5098
|
+
typedef struct {
|
|
5099
|
+
bool accepted;
|
|
5100
|
+
bool filtered;
|
|
5101
|
+
bool missing_id_skip;
|
|
5102
|
+
bool argument_error;
|
|
5103
|
+
fs_safe_id_t id;
|
|
5104
|
+
struct json feature;
|
|
5105
|
+
struct json geometry;
|
|
5106
|
+
struct json properties;
|
|
5107
|
+
bool properties_null;
|
|
5108
|
+
const char *reason;
|
|
5109
|
+
} fs_safe_feature_t;
|
|
5110
|
+
|
|
5111
|
+
static bool fs_safe_prepare_feature(fs_safe_job_t *job, struct json feature, long feature_index,
|
|
5112
|
+
fs_mode_t mode, fs_safe_feature_t *out) {
|
|
5113
|
+
struct json geometry;
|
|
5114
|
+
struct json geometry_type;
|
|
5115
|
+
struct json properties;
|
|
5116
|
+
unsigned int geom_bit;
|
|
5117
|
+
|
|
5118
|
+
memset(out, 0, sizeof(*out));
|
|
5119
|
+
out->feature = feature;
|
|
5120
|
+
|
|
5121
|
+
if (!json_exists(feature) || json_type(feature) != JSON_OBJECT) {
|
|
5122
|
+
out->reason = "feature is not an object";
|
|
5123
|
+
return false;
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
geometry = json_object_get(feature, "geometry");
|
|
5127
|
+
if (!json_exists(geometry) || json_type(geometry) == JSON_NULL) {
|
|
5128
|
+
out->reason = "missing geometry";
|
|
5129
|
+
return false;
|
|
5130
|
+
}
|
|
5131
|
+
if (json_type(geometry) != JSON_OBJECT) {
|
|
5132
|
+
out->reason = "geometry must be an object";
|
|
5133
|
+
return false;
|
|
5134
|
+
}
|
|
5135
|
+
|
|
5136
|
+
geometry_type = json_object_get(geometry, "type");
|
|
5137
|
+
if (!json_exists(geometry_type) || json_type(geometry_type) != JSON_STRING) {
|
|
5138
|
+
out->reason = "geometry.type must be a string";
|
|
5139
|
+
return false;
|
|
5140
|
+
}
|
|
5141
|
+
|
|
5142
|
+
geom_bit = fs_safe_geometry_type_bit(geometry_type);
|
|
5143
|
+
if (geom_bit == 0) {
|
|
5144
|
+
out->reason = "unsupported geometry.type";
|
|
5145
|
+
return false;
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5148
|
+
if (!job->opts.only_all && (job->opts.only_mask & geom_bit) == 0) {
|
|
5149
|
+
out->filtered = true;
|
|
5150
|
+
return true;
|
|
5151
|
+
}
|
|
5152
|
+
|
|
5153
|
+
if (!fs_safe_extract_id(job, feature, feature_index, &out->id, &out->reason,
|
|
5154
|
+
&out->missing_id_skip, &out->argument_error)) {
|
|
5155
|
+
return false;
|
|
5156
|
+
}
|
|
5157
|
+
|
|
5158
|
+
if (mode == FS_MODE_READ_FEATURES) {
|
|
5159
|
+
properties = json_object_get(feature, "properties");
|
|
5160
|
+
if (!json_exists(properties) || json_type(properties) == JSON_NULL) {
|
|
5161
|
+
out->properties_null = true;
|
|
5162
|
+
} else if (json_type(properties) != JSON_OBJECT) {
|
|
5163
|
+
out->reason = "properties must be an object or null";
|
|
5164
|
+
return false;
|
|
5165
|
+
} else {
|
|
5166
|
+
out->properties = properties;
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
|
|
5170
|
+
out->accepted = true;
|
|
5171
|
+
out->geometry = geometry;
|
|
5172
|
+
return true;
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5175
|
+
static bool fs_safe_handle_invalid(fs_safe_job_t *job, fs_safe_feature_t *feature,
|
|
5176
|
+
long feature_index) {
|
|
5177
|
+
size_t offset = fs_safe_json_offset(job, json_exists(feature->geometry) ? feature->geometry
|
|
5178
|
+
: feature->feature);
|
|
5179
|
+
const char *reason = feature->reason ? feature->reason : "invalid feature";
|
|
5180
|
+
|
|
5181
|
+
if (feature->missing_id_skip ||
|
|
5182
|
+
(!feature->argument_error && job->opts.on_invalid == FS_ON_INVALID_SKIP)) {
|
|
5183
|
+
job->skipped++;
|
|
5184
|
+
if (!fs_safe_add_report_error(job, feature_index, offset, reason))
|
|
5185
|
+
return false;
|
|
5186
|
+
return true;
|
|
5187
|
+
}
|
|
5188
|
+
|
|
5189
|
+
fs_safe_set_feature_error(
|
|
5190
|
+
job, feature->argument_error ? FS_SAFE_ERROR_ARGUMENT : FS_SAFE_ERROR_PARSE, feature_index,
|
|
5191
|
+
offset, reason);
|
|
5192
|
+
return false;
|
|
5193
|
+
}
|
|
5194
|
+
|
|
5195
|
+
static bool fs_safe_parse_root(fs_safe_job_t *job, struct json *features_out) {
|
|
5196
|
+
struct json_valid valid;
|
|
5197
|
+
struct json root;
|
|
5198
|
+
struct json type;
|
|
5199
|
+
struct json features;
|
|
5200
|
+
|
|
5201
|
+
valid = json_validn_ex(job->source_data, job->source_len, 0);
|
|
5202
|
+
if (!valid.valid) {
|
|
5203
|
+
char msg[128];
|
|
5204
|
+
snprintf(msg, sizeof(msg), "malformed JSON at byte %llu", (unsigned long long)valid.pos);
|
|
5205
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_PARSE, msg);
|
|
5206
|
+
return false;
|
|
5207
|
+
}
|
|
5208
|
+
|
|
5209
|
+
root = json_parsen(job->source_data, job->source_len);
|
|
5210
|
+
if (!json_exists(root) || json_type(root) != JSON_OBJECT) {
|
|
5211
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_PARSE,
|
|
5212
|
+
"GeoJSON root must be a FeatureCollection object");
|
|
5213
|
+
return false;
|
|
5214
|
+
}
|
|
5215
|
+
|
|
5216
|
+
type = json_object_get(root, "type");
|
|
5217
|
+
if (!json_exists(type) || json_type(type) != JSON_STRING ||
|
|
5218
|
+
json_string_compare(type, "FeatureCollection") != 0) {
|
|
5219
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_PARSE, "GeoJSON root type must be FeatureCollection");
|
|
5220
|
+
return false;
|
|
5221
|
+
}
|
|
5222
|
+
|
|
5223
|
+
features = json_object_get(root, "features");
|
|
5224
|
+
if (!json_exists(features) || json_type(features) != JSON_ARRAY) {
|
|
5225
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_PARSE,
|
|
5226
|
+
"GeoJSON FeatureCollection features must be an Array");
|
|
5227
|
+
return false;
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5230
|
+
*features_out = features;
|
|
5231
|
+
return true;
|
|
5232
|
+
}
|
|
5233
|
+
|
|
5234
|
+
static char *fs_safe_tg_error_message(struct tg_geom *geom) {
|
|
5235
|
+
const char *err;
|
|
5236
|
+
char *msg;
|
|
5237
|
+
int n;
|
|
5238
|
+
|
|
5239
|
+
if (!geom)
|
|
5240
|
+
return fs_safe_strdup_cstr("TG geometry allocation failed");
|
|
5241
|
+
err = tg_geom_error(geom);
|
|
5242
|
+
if (!err)
|
|
5243
|
+
return NULL;
|
|
5244
|
+
n = snprintf(NULL, 0, "invalid geometry: %s", err);
|
|
5245
|
+
if (n < 0)
|
|
5246
|
+
return NULL;
|
|
5247
|
+
msg = (char *)malloc((size_t)n + 1);
|
|
5248
|
+
if (!msg)
|
|
5249
|
+
return NULL;
|
|
5250
|
+
snprintf(msg, (size_t)n + 1, "invalid geometry: %s", err);
|
|
5251
|
+
return msg;
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5254
|
+
static bool fs_safe_validate_geometry(fs_safe_job_t *job, struct json geometry, long feature_index,
|
|
5255
|
+
bool *skipped_out) {
|
|
5256
|
+
*skipped_out = false;
|
|
5257
|
+
struct tg_geom *geom = tg_parse_geojsonn_ix(json_raw(geometry), json_raw_length(geometry),
|
|
5258
|
+
job->opts.geometry_index);
|
|
5259
|
+
char *message = fs_safe_tg_error_message(geom);
|
|
5260
|
+
|
|
5261
|
+
if (!geom) {
|
|
5262
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "TG geometry allocation failed");
|
|
5263
|
+
return false;
|
|
5264
|
+
}
|
|
5265
|
+
|
|
5266
|
+
if (message) {
|
|
5267
|
+
if (job->opts.on_invalid == FS_ON_INVALID_SKIP) {
|
|
5268
|
+
job->skipped++;
|
|
5269
|
+
bool ok = fs_safe_add_report_error(job, feature_index,
|
|
5270
|
+
fs_safe_json_offset(job, geometry), message);
|
|
5271
|
+
free(message);
|
|
5272
|
+
tg_geom_free(geom);
|
|
5273
|
+
*skipped_out = true;
|
|
5274
|
+
return ok;
|
|
5275
|
+
}
|
|
5276
|
+
fs_safe_set_feature_error(job, FS_SAFE_ERROR_PARSE, feature_index,
|
|
5277
|
+
fs_safe_json_offset(job, geometry), message);
|
|
5278
|
+
free(message);
|
|
5279
|
+
tg_geom_free(geom);
|
|
5280
|
+
return false;
|
|
5281
|
+
}
|
|
5282
|
+
|
|
5283
|
+
tg_geom_free(geom);
|
|
5284
|
+
return true;
|
|
5285
|
+
}
|
|
5286
|
+
|
|
5287
|
+
static bool fs_safe_append_row(fs_safe_job_t *job, fs_safe_feature_t *feature) {
|
|
5288
|
+
fs_safe_row_t *grown;
|
|
5289
|
+
long new_cap;
|
|
5290
|
+
|
|
5291
|
+
if (job->row_len == job->row_cap) {
|
|
5292
|
+
new_cap = job->row_cap == 0 ? 64 : job->row_cap * 2;
|
|
5293
|
+
if (new_cap < job->row_cap || (size_t)new_cap > SIZE_MAX / sizeof(*job->rows)) {
|
|
5294
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource rows allocation overflow");
|
|
5295
|
+
return false;
|
|
5296
|
+
}
|
|
5297
|
+
grown = (fs_safe_row_t *)realloc(job->rows, (size_t)new_cap * sizeof(*job->rows));
|
|
5298
|
+
if (!grown) {
|
|
5299
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource rows allocation failed");
|
|
5300
|
+
return false;
|
|
5301
|
+
}
|
|
5302
|
+
job->rows = grown;
|
|
5303
|
+
job->row_cap = new_cap;
|
|
5304
|
+
}
|
|
5305
|
+
|
|
5306
|
+
memset(&job->rows[job->row_len], 0, sizeof(job->rows[job->row_len]));
|
|
5307
|
+
job->rows[job->row_len].id = feature->id;
|
|
5308
|
+
job->rows[job->row_len].geometry = feature->geometry;
|
|
5309
|
+
job->rows[job->row_len].properties = feature->properties;
|
|
5310
|
+
job->rows[job->row_len].properties_null = feature->properties_null;
|
|
5311
|
+
job->row_len++;
|
|
5312
|
+
return true;
|
|
5313
|
+
}
|
|
5314
|
+
|
|
5315
|
+
static bool fs_safe_run_read(fs_safe_job_t *job, struct json features) {
|
|
5316
|
+
struct json feature;
|
|
5317
|
+
long feature_index = 0;
|
|
5318
|
+
|
|
5319
|
+
for (feature = json_first(features); json_exists(feature);
|
|
5320
|
+
feature = json_next(feature), feature_index++) {
|
|
5321
|
+
fs_safe_feature_t prepared;
|
|
5322
|
+
|
|
5323
|
+
if (!fs_safe_prepare_feature(job, feature, feature_index, job->mode, &prepared)) {
|
|
5324
|
+
if (!fs_safe_handle_invalid(job, &prepared, feature_index))
|
|
5325
|
+
return false;
|
|
5326
|
+
continue;
|
|
5327
|
+
}
|
|
5328
|
+
|
|
5329
|
+
if (prepared.filtered) {
|
|
5330
|
+
job->filtered++;
|
|
5331
|
+
continue;
|
|
5332
|
+
}
|
|
5333
|
+
|
|
5334
|
+
{
|
|
5335
|
+
bool geometry_skipped = false;
|
|
5336
|
+
if (!fs_safe_validate_geometry(job, prepared.geometry, feature_index,
|
|
5337
|
+
&geometry_skipped))
|
|
5338
|
+
return false;
|
|
5339
|
+
if (geometry_skipped)
|
|
5340
|
+
continue;
|
|
5341
|
+
}
|
|
5342
|
+
|
|
5343
|
+
if (!fs_safe_append_row(job, &prepared))
|
|
5344
|
+
return false;
|
|
5345
|
+
}
|
|
5346
|
+
|
|
5347
|
+
return true;
|
|
5348
|
+
}
|
|
5349
|
+
|
|
5350
|
+
static bool fs_safe_count_build(fs_safe_job_t *job, struct json features, long *accepted_out) {
|
|
5351
|
+
struct json feature;
|
|
5352
|
+
long feature_index = 0;
|
|
5353
|
+
long accepted = 0;
|
|
5354
|
+
|
|
5355
|
+
for (feature = json_first(features); json_exists(feature);
|
|
5356
|
+
feature = json_next(feature), feature_index++) {
|
|
5357
|
+
fs_safe_feature_t prepared;
|
|
5358
|
+
|
|
5359
|
+
if (!fs_safe_prepare_feature(job, feature, feature_index, FS_MODE_BUILD_INDEX, &prepared)) {
|
|
5360
|
+
if (!fs_safe_handle_invalid(job, &prepared, feature_index))
|
|
5361
|
+
return false;
|
|
5362
|
+
continue;
|
|
5363
|
+
}
|
|
5364
|
+
|
|
5365
|
+
if (prepared.filtered) {
|
|
5366
|
+
job->filtered++;
|
|
5367
|
+
continue;
|
|
5368
|
+
}
|
|
5369
|
+
if (prepared.accepted)
|
|
5370
|
+
accepted++;
|
|
5371
|
+
}
|
|
5372
|
+
|
|
5373
|
+
*accepted_out = accepted;
|
|
5374
|
+
return true;
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5377
|
+
static bool fs_safe_fill_build(fs_safe_job_t *job, struct json features) {
|
|
5378
|
+
struct json feature;
|
|
5379
|
+
long feature_index = 0;
|
|
5380
|
+
long ordinal = 0;
|
|
5381
|
+
|
|
5382
|
+
for (feature = json_first(features); json_exists(feature);
|
|
5383
|
+
feature = json_next(feature), feature_index++) {
|
|
5384
|
+
fs_safe_feature_t prepared;
|
|
5385
|
+
struct tg_geom *geom;
|
|
5386
|
+
char *message;
|
|
5387
|
+
|
|
5388
|
+
if (!fs_safe_prepare_feature(job, feature, feature_index, FS_MODE_BUILD_INDEX, &prepared)) {
|
|
5389
|
+
if (!fs_safe_handle_invalid(job, &prepared, feature_index))
|
|
5390
|
+
return false;
|
|
5391
|
+
continue;
|
|
5392
|
+
}
|
|
5393
|
+
if (prepared.filtered || !prepared.accepted)
|
|
5394
|
+
continue;
|
|
5395
|
+
|
|
5396
|
+
geom = tg_parse_geojsonn_ix(json_raw(prepared.geometry), json_raw_length(prepared.geometry),
|
|
5397
|
+
job->opts.geometry_index);
|
|
5398
|
+
message = fs_safe_tg_error_message(geom);
|
|
5399
|
+
if (!geom) {
|
|
5400
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "TG geometry allocation failed");
|
|
5401
|
+
return false;
|
|
5402
|
+
}
|
|
5403
|
+
if (message) {
|
|
5404
|
+
fs_safe_set_feature_error(job, FS_SAFE_ERROR_PARSE, feature_index,
|
|
5405
|
+
fs_safe_json_offset(job, prepared.geometry), message);
|
|
5406
|
+
free(message);
|
|
5407
|
+
tg_geom_free(geom);
|
|
5408
|
+
return false;
|
|
5409
|
+
}
|
|
5410
|
+
|
|
5411
|
+
job->entries[ordinal].id = prepared.id;
|
|
5412
|
+
job->entries[ordinal].geom = geom;
|
|
5413
|
+
job->entries[ordinal].bbox = tg_geom_rect(geom);
|
|
5414
|
+
job->entries[ordinal].geom_bytes = tg_geom_memsize(geom);
|
|
5415
|
+
job->entries[ordinal].ordinal = ordinal;
|
|
5416
|
+
ordinal++;
|
|
5417
|
+
}
|
|
5418
|
+
|
|
5419
|
+
job->entry_len = ordinal;
|
|
5420
|
+
return true;
|
|
5421
|
+
}
|
|
5422
|
+
|
|
5423
|
+
static bool fs_safe_read_file_no_gvl(fs_safe_job_t *job) {
|
|
5424
|
+
int fd;
|
|
5425
|
+
struct stat st;
|
|
5426
|
+
char *buf = NULL;
|
|
5427
|
+
size_t cap = 0;
|
|
5428
|
+
size_t len = 0;
|
|
5429
|
+
|
|
5430
|
+
fd = open(job->path, O_RDONLY);
|
|
5431
|
+
if (fd < 0) {
|
|
5432
|
+
fs_safe_set_system_error(job, errno);
|
|
5433
|
+
return false;
|
|
5434
|
+
}
|
|
5435
|
+
|
|
5436
|
+
if (fstat(fd, &st) == 0 && st.st_size >= 0) {
|
|
5437
|
+
cap = (size_t)st.st_size;
|
|
5438
|
+
if (cap > SIZE_MAX - 1) {
|
|
5439
|
+
close(fd);
|
|
5440
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource file is too large");
|
|
5441
|
+
return false;
|
|
5442
|
+
}
|
|
5443
|
+
buf = (char *)malloc(cap + 1);
|
|
5444
|
+
if (!buf) {
|
|
5445
|
+
close(fd);
|
|
5446
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource file allocation failed");
|
|
5447
|
+
return false;
|
|
5448
|
+
}
|
|
5449
|
+
} else {
|
|
5450
|
+
cap = 8192;
|
|
5451
|
+
buf = (char *)malloc(cap + 1);
|
|
5452
|
+
if (!buf) {
|
|
5453
|
+
close(fd);
|
|
5454
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource file allocation failed");
|
|
5455
|
+
return false;
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
|
|
5459
|
+
for (;;) {
|
|
5460
|
+
ssize_t n;
|
|
5461
|
+
if (len == cap) {
|
|
5462
|
+
size_t new_cap = cap < 8192 ? 8192 : cap * 2;
|
|
5463
|
+
char *grown;
|
|
5464
|
+
if (new_cap < cap || new_cap > SIZE_MAX - 1) {
|
|
5465
|
+
free(buf);
|
|
5466
|
+
close(fd);
|
|
5467
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM,
|
|
5468
|
+
"FeatureSource file allocation overflow");
|
|
5469
|
+
return false;
|
|
5470
|
+
}
|
|
5471
|
+
grown = (char *)realloc(buf, new_cap + 1);
|
|
5472
|
+
if (!grown) {
|
|
5473
|
+
free(buf);
|
|
5474
|
+
close(fd);
|
|
5475
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource file allocation failed");
|
|
5476
|
+
return false;
|
|
5477
|
+
}
|
|
5478
|
+
buf = grown;
|
|
5479
|
+
cap = new_cap;
|
|
5480
|
+
}
|
|
5481
|
+
|
|
5482
|
+
n = read(fd, buf + len, cap - len);
|
|
5483
|
+
if (n < 0) {
|
|
5484
|
+
if (errno == EINTR)
|
|
5485
|
+
continue;
|
|
5486
|
+
int err = errno;
|
|
5487
|
+
free(buf);
|
|
5488
|
+
close(fd);
|
|
5489
|
+
fs_safe_set_system_error(job, err);
|
|
5490
|
+
return false;
|
|
5491
|
+
}
|
|
5492
|
+
if (n == 0)
|
|
5493
|
+
break;
|
|
5494
|
+
len += (size_t)n;
|
|
5495
|
+
}
|
|
5496
|
+
|
|
5497
|
+
if (close(fd) != 0) {
|
|
5498
|
+
int err = errno;
|
|
5499
|
+
free(buf);
|
|
5500
|
+
fs_safe_set_system_error(job, err);
|
|
5501
|
+
return false;
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
buf[len] = '\0';
|
|
5505
|
+
job->source_data = buf;
|
|
5506
|
+
job->source_len = len;
|
|
5507
|
+
job->source_ruby_alloc = false;
|
|
5508
|
+
return true;
|
|
5509
|
+
}
|
|
5510
|
+
|
|
5511
|
+
static void *fs_safe_run_no_gvl(void *ptr) {
|
|
5512
|
+
fs_safe_job_t *job = (fs_safe_job_t *)ptr;
|
|
5513
|
+
struct json features;
|
|
5514
|
+
|
|
5515
|
+
if (job->path && !job->source_data) {
|
|
5516
|
+
if (!fs_safe_read_file_no_gvl(job))
|
|
5517
|
+
return NULL;
|
|
5518
|
+
}
|
|
5519
|
+
|
|
5520
|
+
if (!fs_safe_parse_root(job, &features))
|
|
5521
|
+
return NULL;
|
|
5522
|
+
|
|
5523
|
+
if (job->mode == FS_MODE_BUILD_INDEX) {
|
|
5524
|
+
long accepted = 0;
|
|
5525
|
+
if (!fs_safe_count_build(job, features, &accepted))
|
|
5526
|
+
return NULL;
|
|
5527
|
+
if (accepted > 0) {
|
|
5528
|
+
if ((size_t)accepted > SIZE_MAX / sizeof(*job->entries)) {
|
|
5529
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM,
|
|
5530
|
+
"FeatureSource entries allocation overflow");
|
|
5531
|
+
return NULL;
|
|
5532
|
+
}
|
|
5533
|
+
job->entries =
|
|
5534
|
+
(fs_safe_native_entry_t *)calloc((size_t)accepted, sizeof(*job->entries));
|
|
5535
|
+
if (!job->entries) {
|
|
5536
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM,
|
|
5537
|
+
"FeatureSource entries allocation failed");
|
|
5538
|
+
return NULL;
|
|
5539
|
+
}
|
|
5540
|
+
}
|
|
5541
|
+
if (!fs_safe_fill_build(job, features))
|
|
5542
|
+
return NULL;
|
|
5543
|
+
} else {
|
|
5544
|
+
if (!fs_safe_run_read(job, features))
|
|
5545
|
+
return NULL;
|
|
5546
|
+
}
|
|
5547
|
+
|
|
5548
|
+
return NULL;
|
|
5549
|
+
}
|
|
5550
|
+
|
|
5551
|
+
static void fs_safe_run_without_gvl(fs_safe_job_t *job) {
|
|
5552
|
+
#if defined(HAVE_RB_NOGVL_OFFLOAD_SAFE)
|
|
5553
|
+
rb_nogvl(fs_safe_run_no_gvl, job, RUBY_UBF_IO, NULL, RB_NOGVL_OFFLOAD_SAFE);
|
|
5554
|
+
#else
|
|
5555
|
+
rb_thread_call_without_gvl(fs_safe_run_no_gvl, job, RUBY_UBF_IO, NULL);
|
|
5556
|
+
#endif
|
|
5557
|
+
}
|
|
5558
|
+
|
|
5559
|
+
static void fs_safe_free_c_options(fs_safe_options_t *opts) {
|
|
5560
|
+
if (!opts)
|
|
5561
|
+
return;
|
|
5562
|
+
if (opts->keys) {
|
|
5563
|
+
for (long i = 0; i < opts->len; i++) {
|
|
5564
|
+
free(opts->keys[i]);
|
|
5565
|
+
}
|
|
5566
|
+
free(opts->keys);
|
|
5567
|
+
opts->keys = NULL;
|
|
5568
|
+
}
|
|
5569
|
+
free(opts->lens);
|
|
5570
|
+
opts->lens = NULL;
|
|
5571
|
+
opts->len = 0;
|
|
5572
|
+
}
|
|
5573
|
+
|
|
5574
|
+
static void fs_safe_cleanup(fs_safe_job_t *job) {
|
|
5575
|
+
if (!job)
|
|
5576
|
+
return;
|
|
5577
|
+
|
|
5578
|
+
if (job->entries) {
|
|
5579
|
+
for (long i = 0; i < job->entry_len; i++) {
|
|
5580
|
+
if (job->entries[i].geom) {
|
|
5581
|
+
tg_geom_free(job->entries[i].geom);
|
|
5582
|
+
job->entries[i].geom = NULL;
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5585
|
+
free(job->entries);
|
|
5586
|
+
job->entries = NULL;
|
|
5587
|
+
job->entry_len = 0;
|
|
5588
|
+
}
|
|
5589
|
+
|
|
5590
|
+
free(job->rows);
|
|
5591
|
+
job->rows = NULL;
|
|
5592
|
+
job->row_len = 0;
|
|
5593
|
+
job->row_cap = 0;
|
|
5594
|
+
|
|
5595
|
+
if (job->errors) {
|
|
5596
|
+
for (long i = 0; i < job->errors_len; i++) {
|
|
5597
|
+
free(job->errors[i].reason);
|
|
5598
|
+
}
|
|
5599
|
+
free(job->errors);
|
|
5600
|
+
job->errors = NULL;
|
|
5601
|
+
job->errors_len = 0;
|
|
5602
|
+
job->errors_cap = 0;
|
|
5603
|
+
}
|
|
5604
|
+
|
|
5605
|
+
if (job->source_data) {
|
|
5606
|
+
if (job->source_ruby_alloc)
|
|
5607
|
+
ruby_xfree(job->source_data);
|
|
5608
|
+
else
|
|
5609
|
+
free(job->source_data);
|
|
5610
|
+
job->source_data = NULL;
|
|
5611
|
+
job->source_len = 0;
|
|
5612
|
+
}
|
|
5613
|
+
|
|
5614
|
+
free(job->path);
|
|
5615
|
+
job->path = NULL;
|
|
5616
|
+
free(job->fatal.message);
|
|
5617
|
+
job->fatal.message = NULL;
|
|
5618
|
+
fs_safe_free_c_options(&job->opts);
|
|
5619
|
+
}
|
|
5620
|
+
|
|
5621
|
+
static VALUE fs_safe_job_ensure(VALUE arg) {
|
|
5622
|
+
fs_safe_job_t *job = (fs_safe_job_t *)arg;
|
|
5623
|
+
fs_safe_cleanup(job);
|
|
5624
|
+
return Qnil;
|
|
5625
|
+
}
|
|
5626
|
+
|
|
5627
|
+
static VALUE fs_safe_id_to_value(fs_safe_id_t *id) {
|
|
5628
|
+
switch (id->kind) {
|
|
5629
|
+
case FS_SAFE_ID_STRING:
|
|
5630
|
+
return fs_copy_json_string_value(id->json_value);
|
|
5631
|
+
case FS_SAFE_ID_INTEGER:
|
|
5632
|
+
return LL2NUM(id->integer_value);
|
|
5633
|
+
case FS_SAFE_ID_ORDINAL:
|
|
5634
|
+
return fs_missing_id_ordinal(id->feature_index);
|
|
5635
|
+
}
|
|
5636
|
+
return Qnil;
|
|
5637
|
+
}
|
|
5638
|
+
|
|
5639
|
+
static VALUE fs_safe_properties_to_value(fs_safe_row_t *row) {
|
|
5640
|
+
if (row->properties_null || !json_exists(row->properties))
|
|
5641
|
+
return fs_cstr_utf8_string("null");
|
|
5642
|
+
return fs_utf8_string(json_raw(row->properties), json_raw_length(row->properties));
|
|
5643
|
+
}
|
|
5644
|
+
|
|
5645
|
+
static void fs_safe_raise_fatal(fs_safe_job_t *job) {
|
|
5646
|
+
switch (job->fatal.type) {
|
|
5647
|
+
case FS_SAFE_ERROR_NONE:
|
|
5648
|
+
return;
|
|
5649
|
+
case FS_SAFE_ERROR_NOMEM:
|
|
5650
|
+
rb_raise(rb_eNoMemError, "%s",
|
|
5651
|
+
job->fatal.message ? job->fatal.message : "FeatureSource allocation failed");
|
|
5652
|
+
case FS_SAFE_ERROR_PARSE:
|
|
5653
|
+
rb_raise(eTGGeometryParseError, "%s",
|
|
5654
|
+
job->fatal.message ? job->fatal.message : "FeatureSource parse error");
|
|
5655
|
+
case FS_SAFE_ERROR_ARGUMENT:
|
|
5656
|
+
rb_raise(eTGGeometryArgumentError, "%s",
|
|
5657
|
+
job->fatal.message ? job->fatal.message : "FeatureSource argument error");
|
|
5658
|
+
case FS_SAFE_ERROR_SYSTEM:
|
|
5659
|
+
rb_syserr_fail(job->fatal.sys_errno, job->path ? job->path : "FeatureSource file read");
|
|
5660
|
+
}
|
|
5661
|
+
}
|
|
5662
|
+
|
|
5663
|
+
static VALUE fs_safe_materialize_rows(fs_safe_job_t *job) {
|
|
5664
|
+
VALUE rows = rb_ary_new_capa(job->row_len);
|
|
5665
|
+
|
|
5666
|
+
for (long i = 0; i < job->row_len; i++) {
|
|
5667
|
+
VALUE id = fs_safe_id_to_value(&job->rows[i].id);
|
|
5668
|
+
VALUE geom_json =
|
|
5669
|
+
fs_utf8_string(json_raw(job->rows[i].geometry), json_raw_length(job->rows[i].geometry));
|
|
5670
|
+
if (job->mode == FS_MODE_READ_ENTRIES) {
|
|
5671
|
+
rb_ary_push(rows, rb_ary_new_from_args(2, id, geom_json));
|
|
5672
|
+
} else {
|
|
5673
|
+
VALUE props_json = fs_safe_properties_to_value(&job->rows[i]);
|
|
5674
|
+
rb_ary_push(rows, rb_ary_new_from_args(3, id, geom_json, props_json));
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
|
|
5678
|
+
if (job->opts.report) {
|
|
5679
|
+
VALUE report = rb_hash_new();
|
|
5680
|
+
VALUE errors = rb_ary_new_capa(job->errors_len);
|
|
5681
|
+
for (long i = 0; i < job->errors_len; i++) {
|
|
5682
|
+
VALUE reason = fs_cstr_utf8_string(job->errors[i].reason);
|
|
5683
|
+
rb_ary_push(errors, fs_error_hash(job->errors[i].feature_index,
|
|
5684
|
+
job->errors[i].byte_offset, reason));
|
|
5685
|
+
}
|
|
5686
|
+
rb_hash_aset(report,
|
|
5687
|
+
job->mode == FS_MODE_READ_ENTRIES ? fs_sym("entries") : fs_sym("features"),
|
|
5688
|
+
rows);
|
|
5689
|
+
rb_hash_aset(report, fs_sym("skipped"), LONG2NUM(job->skipped));
|
|
5690
|
+
rb_hash_aset(report, fs_sym("filtered"), LONG2NUM(job->filtered));
|
|
5691
|
+
rb_hash_aset(report, fs_sym("errors"), errors);
|
|
5692
|
+
RB_GC_GUARD(rows);
|
|
5693
|
+
RB_GC_GUARD(errors);
|
|
5694
|
+
RB_GC_GUARD(report);
|
|
5695
|
+
return report;
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5698
|
+
RB_GC_GUARD(rows);
|
|
5699
|
+
return rows;
|
|
5700
|
+
}
|
|
5701
|
+
|
|
5702
|
+
typedef struct {
|
|
5703
|
+
fs_safe_job_t *job;
|
|
5704
|
+
tg_index_t *idx;
|
|
5705
|
+
} fs_safe_index_finalize_args_t;
|
|
5706
|
+
|
|
5707
|
+
static VALUE fs_safe_index_finalize_body(VALUE arg) {
|
|
5708
|
+
fs_safe_index_finalize_args_t *a = (fs_safe_index_finalize_args_t *)arg;
|
|
5709
|
+
fs_safe_job_t *job = a->job;
|
|
5710
|
+
tg_index_t *idx = a->idx;
|
|
5711
|
+
|
|
5712
|
+
if (job->entry_len > 0) {
|
|
5713
|
+
if ((size_t)job->entry_len > SIZE_MAX / sizeof(tg_index_entry_t)) {
|
|
5714
|
+
rb_raise(rb_eNoMemError, "entries allocation size overflow");
|
|
5715
|
+
}
|
|
5716
|
+
idx->entries = calloc((size_t)job->entry_len, sizeof(tg_index_entry_t));
|
|
5717
|
+
if (!idx->entries) {
|
|
5718
|
+
rb_raise(rb_eNoMemError, "entries allocation failed");
|
|
5719
|
+
}
|
|
5720
|
+
idx->entries_bytes = (size_t)job->entry_len * sizeof(tg_index_entry_t);
|
|
5721
|
+
rb_gc_adjust_memory_usage((ssize_t)idx->entries_bytes);
|
|
5722
|
+
}
|
|
5723
|
+
|
|
5724
|
+
for (long i = 0; i < job->entry_len; i++) {
|
|
5725
|
+
tg_index_entry_t entry;
|
|
5726
|
+
memset(&entry, 0, sizeof(entry));
|
|
5727
|
+
entry.id = fs_safe_id_to_value(&job->entries[i].id);
|
|
5728
|
+
entry.geom_owner = Qnil;
|
|
5729
|
+
entry.geom = job->entries[i].geom;
|
|
5730
|
+
entry.bbox = job->entries[i].bbox;
|
|
5731
|
+
entry.geom_bytes = job->entries[i].geom_bytes;
|
|
5732
|
+
entry.ordinal = i;
|
|
5733
|
+
entry.owned = true;
|
|
5734
|
+
|
|
5735
|
+
job->entries[i].geom = NULL; /* ownership moves to idx */
|
|
5736
|
+
idx->entries[i] = entry;
|
|
5737
|
+
idx->initialized++;
|
|
5738
|
+
idx->owned_geom_bytes_total += entry.geom_bytes;
|
|
5739
|
+
if (entry.geom_bytes > 0)
|
|
5740
|
+
rb_gc_adjust_memory_usage((ssize_t)entry.geom_bytes);
|
|
5741
|
+
index_expand_bbox(idx, entry.bbox);
|
|
5742
|
+
}
|
|
5743
|
+
|
|
5744
|
+
if (idx->strategy == TG_GEOMETRY_INDEX_STRATEGY_RTREE) {
|
|
5745
|
+
index_build_rtree(idx);
|
|
5746
|
+
}
|
|
5747
|
+
|
|
5748
|
+
return Qnil;
|
|
5749
|
+
}
|
|
5750
|
+
|
|
5751
|
+
static VALUE fs_safe_materialize_index(fs_safe_job_t *job) {
|
|
5752
|
+
tg_index_t *idx;
|
|
5753
|
+
VALUE wrapper;
|
|
5754
|
+
fs_safe_index_finalize_args_t args;
|
|
5755
|
+
int state = 0;
|
|
5756
|
+
|
|
5757
|
+
wrapper = TypedData_Make_Struct(cTGGeometryIndex, tg_index_t, &tg_index_type, idx);
|
|
5758
|
+
idx->len = job->entry_len;
|
|
5759
|
+
idx->capacity = job->entry_len;
|
|
5760
|
+
idx->initialized = 0;
|
|
5761
|
+
idx->strategy = job->opts.strategy;
|
|
5762
|
+
idx->predicate = job->opts.predicate;
|
|
5763
|
+
idx->rtree = NULL;
|
|
5764
|
+
idx->frozen = false;
|
|
5765
|
+
idx->has_bbox = false;
|
|
5766
|
+
|
|
5767
|
+
args.job = job;
|
|
5768
|
+
args.idx = idx;
|
|
5769
|
+
rb_protect(fs_safe_index_finalize_body, (VALUE)&args, &state);
|
|
5770
|
+
if (state) {
|
|
5771
|
+
index_dispose(idx);
|
|
5772
|
+
RB_GC_GUARD(wrapper);
|
|
5773
|
+
rb_jump_tag(state);
|
|
5774
|
+
}
|
|
5775
|
+
|
|
5776
|
+
idx->frozen = true;
|
|
5777
|
+
rb_obj_freeze(wrapper);
|
|
5778
|
+
RB_GC_GUARD(wrapper);
|
|
5779
|
+
return wrapper;
|
|
5780
|
+
}
|
|
5781
|
+
|
|
5782
|
+
static VALUE fs_safe_materialize(VALUE arg) {
|
|
5783
|
+
fs_safe_materialize_args_t *a = (fs_safe_materialize_args_t *)arg;
|
|
5784
|
+
fs_safe_job_t *job = a->job;
|
|
5785
|
+
|
|
5786
|
+
fs_safe_raise_fatal(job);
|
|
5787
|
+
if (job->mode == FS_MODE_BUILD_INDEX)
|
|
5788
|
+
a->result = fs_safe_materialize_index(job);
|
|
5789
|
+
else
|
|
5790
|
+
a->result = fs_safe_materialize_rows(job);
|
|
5791
|
+
return a->result;
|
|
5792
|
+
}
|
|
5793
|
+
|
|
5794
|
+
static void fs_safe_copy_options(fs_safe_job_t *job, fs_options_t *opts) {
|
|
5795
|
+
long len = RARRAY_LEN(opts->id_path);
|
|
5796
|
+
|
|
5797
|
+
memset(&job->opts, 0, sizeof(job->opts));
|
|
5798
|
+
job->opts.only_all = opts->only_all;
|
|
5799
|
+
job->opts.only_mask = opts->only_mask;
|
|
5800
|
+
job->opts.on_invalid = opts->on_invalid;
|
|
5801
|
+
job->opts.on_missing_id = opts->on_missing_id;
|
|
5802
|
+
job->opts.report = opts->report;
|
|
5803
|
+
job->opts.max_errors = opts->max_errors;
|
|
5804
|
+
job->opts.geometry_index = opts->geometry_index;
|
|
5805
|
+
job->opts.strategy = opts->strategy;
|
|
5806
|
+
job->opts.predicate = opts->predicate;
|
|
5807
|
+
job->opts.len = len;
|
|
5808
|
+
|
|
5809
|
+
if (len > 0) {
|
|
5810
|
+
job->opts.keys = calloc((size_t)len, sizeof(char *));
|
|
5811
|
+
job->opts.lens = calloc((size_t)len, sizeof(size_t));
|
|
5812
|
+
if (!job->opts.keys || !job->opts.lens) {
|
|
5813
|
+
fs_safe_free_c_options(&job->opts);
|
|
5814
|
+
rb_raise(rb_eNoMemError, "FeatureSource id path allocation failed");
|
|
5815
|
+
}
|
|
5816
|
+
for (long i = 0; i < len; i++) {
|
|
5817
|
+
VALUE key = rb_ary_entry(opts->id_path, i);
|
|
5818
|
+
StringValue(key);
|
|
5819
|
+
job->opts.lens[i] = (size_t)RSTRING_LEN(key);
|
|
5820
|
+
job->opts.keys[i] = fs_safe_strdup_len(RSTRING_PTR(key), job->opts.lens[i]);
|
|
5821
|
+
if (!job->opts.keys[i]) {
|
|
5822
|
+
fs_safe_free_c_options(&job->opts);
|
|
5823
|
+
rb_raise(rb_eNoMemError, "FeatureSource id path key allocation failed");
|
|
5824
|
+
}
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
}
|
|
5828
|
+
|
|
5829
|
+
typedef struct {
|
|
5830
|
+
fs_safe_job_t *job;
|
|
5831
|
+
fs_safe_materialize_args_t materialize;
|
|
5832
|
+
} fs_safe_dispatch_ctx_t;
|
|
5833
|
+
|
|
5834
|
+
static VALUE fs_safe_dispatch_body(VALUE arg) {
|
|
5835
|
+
fs_safe_dispatch_ctx_t *ctx = (fs_safe_dispatch_ctx_t *)arg;
|
|
5836
|
+
|
|
5837
|
+
fs_safe_run_without_gvl(ctx->job);
|
|
5838
|
+
ctx->materialize.job = ctx->job;
|
|
5839
|
+
return fs_safe_materialize((VALUE)&ctx->materialize);
|
|
5840
|
+
}
|
|
5841
|
+
|
|
5842
|
+
static VALUE fs_safe_dispatch_json(VALUE json_string, VALUE kwargs, fs_mode_t mode) {
|
|
5843
|
+
fs_args_t fs;
|
|
5844
|
+
fs_safe_job_t job;
|
|
5845
|
+
fs_safe_dispatch_ctx_t ctx;
|
|
5846
|
+
|
|
5847
|
+
memset(&fs, 0, sizeof(fs));
|
|
5848
|
+
memset(&job, 0, sizeof(job));
|
|
5849
|
+
memset(&ctx, 0, sizeof(ctx));
|
|
5850
|
+
|
|
5851
|
+
fs.mode = mode;
|
|
5852
|
+
fs.opts = fs_parse_options(kwargs, mode);
|
|
5853
|
+
job.mode = mode;
|
|
5854
|
+
{
|
|
5855
|
+
VALUE str = json_string;
|
|
5856
|
+
StringValue(str);
|
|
5857
|
+
if ((size_t)RSTRING_LEN(str) > SIZE_MAX - 1)
|
|
5858
|
+
rb_raise(rb_eNoMemError, "FeatureSource input is too large");
|
|
5859
|
+
|
|
5860
|
+
fs_safe_copy_options(&job, &fs.opts);
|
|
5861
|
+
job.source_len = (size_t)RSTRING_LEN(str);
|
|
5862
|
+
job.source_data = (char *)malloc(job.source_len + 1);
|
|
5863
|
+
if (!job.source_data) {
|
|
5864
|
+
fs_safe_job_ensure((VALUE)&job);
|
|
5865
|
+
rb_raise(rb_eNoMemError, "FeatureSource input allocation failed");
|
|
5866
|
+
}
|
|
5867
|
+
memcpy(job.source_data, RSTRING_PTR(str), job.source_len);
|
|
5868
|
+
job.source_data[job.source_len] = '\0';
|
|
5869
|
+
job.source_ruby_alloc = false;
|
|
5870
|
+
RB_GC_GUARD(str);
|
|
5871
|
+
}
|
|
5872
|
+
|
|
5873
|
+
ctx.job = &job;
|
|
5874
|
+
return rb_ensure(fs_safe_dispatch_body, (VALUE)&ctx, fs_safe_job_ensure, (VALUE)&job);
|
|
5875
|
+
}
|
|
5876
|
+
|
|
5877
|
+
static VALUE fs_safe_dispatch_file(VALUE path, VALUE kwargs, fs_mode_t mode) {
|
|
5878
|
+
fs_args_t fs;
|
|
5879
|
+
fs_safe_job_t job;
|
|
5880
|
+
fs_safe_dispatch_ctx_t ctx;
|
|
5881
|
+
VALUE path_str = path;
|
|
5882
|
+
|
|
5883
|
+
memset(&fs, 0, sizeof(fs));
|
|
5884
|
+
memset(&job, 0, sizeof(job));
|
|
5885
|
+
memset(&ctx, 0, sizeof(ctx));
|
|
5886
|
+
|
|
5887
|
+
StringValueCStr(path_str);
|
|
5888
|
+
fs.mode = mode;
|
|
5889
|
+
fs.opts = fs_parse_options(kwargs, mode);
|
|
5890
|
+
job.mode = mode;
|
|
5891
|
+
fs_safe_copy_options(&job, &fs.opts);
|
|
5892
|
+
job.path = fs_safe_strdup_cstr(StringValueCStr(path_str));
|
|
5893
|
+
if (!job.path) {
|
|
5894
|
+
fs_safe_job_ensure((VALUE)&job);
|
|
5895
|
+
rb_raise(rb_eNoMemError, "FeatureSource path allocation failed");
|
|
5896
|
+
}
|
|
5897
|
+
|
|
5898
|
+
ctx.job = &job;
|
|
5899
|
+
return rb_ensure(fs_safe_dispatch_body, (VALUE)&ctx, fs_safe_job_ensure, (VALUE)&job);
|
|
5900
|
+
}
|
|
5901
|
+
|
|
5902
|
+
static VALUE fs_read_body(VALUE arg) {
|
|
5903
|
+
fs_args_t *fs = (fs_args_t *)arg;
|
|
5904
|
+
struct json features;
|
|
5905
|
+
struct json feature;
|
|
5906
|
+
VALUE rows;
|
|
5907
|
+
VALUE errors;
|
|
5908
|
+
long feature_index = 0;
|
|
5909
|
+
long skipped = 0;
|
|
5910
|
+
long filtered = 0;
|
|
5911
|
+
|
|
5912
|
+
fs_parse_root(&fs->source, &features);
|
|
5913
|
+
rows = rb_ary_new();
|
|
5914
|
+
errors = rb_ary_new();
|
|
5915
|
+
|
|
5916
|
+
for (feature = json_first(features); json_exists(feature);
|
|
5917
|
+
feature = json_next(feature), feature_index++) {
|
|
5918
|
+
fs_feature_result_t prepared;
|
|
5919
|
+
VALUE tg_error = Qnil;
|
|
5920
|
+
|
|
5921
|
+
if (!fs_feature_prepare(&fs->source, &fs->opts, fs->mode, feature, feature_index, true,
|
|
5922
|
+
&prepared)) {
|
|
5923
|
+
fs_handle_invalid_or_raise(&fs->source, &fs->opts, &prepared, feature_index, errors,
|
|
5924
|
+
&skipped);
|
|
5925
|
+
continue;
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5928
|
+
if (prepared.filtered) {
|
|
5929
|
+
filtered++;
|
|
5930
|
+
continue;
|
|
5931
|
+
}
|
|
5932
|
+
|
|
5933
|
+
if (!fs_validate_geometry_or_error(prepared.geometry, fs->opts.geometry_index, &tg_error)) {
|
|
5934
|
+
prepared.reason_value = rb_str_plus(rb_str_new_cstr("invalid geometry: "), tg_error);
|
|
5935
|
+
fs_handle_invalid_or_raise(&fs->source, &fs->opts, &prepared, feature_index, errors,
|
|
5936
|
+
&skipped);
|
|
5937
|
+
continue;
|
|
5938
|
+
}
|
|
5939
|
+
|
|
5940
|
+
if (fs->mode == FS_MODE_READ_ENTRIES) {
|
|
5941
|
+
VALUE geom_json =
|
|
5942
|
+
fs_utf8_string(json_raw(prepared.geometry), json_raw_length(prepared.geometry));
|
|
5943
|
+
rb_ary_push(rows, rb_ary_new_from_args(2, prepared.id, geom_json));
|
|
5944
|
+
} else {
|
|
5945
|
+
VALUE geom_json =
|
|
5946
|
+
fs_utf8_string(json_raw(prepared.geometry), json_raw_length(prepared.geometry));
|
|
5947
|
+
VALUE props_json = fs_properties_json_string(prepared.properties);
|
|
5948
|
+
rb_ary_push(rows, rb_ary_new_from_args(3, prepared.id, geom_json, props_json));
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
|
|
5952
|
+
if (fs->opts.report) {
|
|
5953
|
+
VALUE report = rb_hash_new();
|
|
5954
|
+
rb_hash_aset(report,
|
|
5955
|
+
fs->mode == FS_MODE_READ_ENTRIES ? fs_sym("entries") : fs_sym("features"),
|
|
5956
|
+
rows);
|
|
5957
|
+
rb_hash_aset(report, fs_sym("skipped"), LONG2NUM(skipped));
|
|
5958
|
+
rb_hash_aset(report, fs_sym("filtered"), LONG2NUM(filtered));
|
|
5959
|
+
rb_hash_aset(report, fs_sym("errors"), errors);
|
|
5960
|
+
RB_GC_GUARD(rows);
|
|
5961
|
+
RB_GC_GUARD(errors);
|
|
5962
|
+
RB_GC_GUARD(report);
|
|
5963
|
+
RB_GC_GUARD(fs->opts.id_path);
|
|
5964
|
+
return report;
|
|
5965
|
+
}
|
|
5966
|
+
|
|
5967
|
+
RB_GC_GUARD(errors);
|
|
5968
|
+
RB_GC_GUARD(rows);
|
|
5969
|
+
RB_GC_GUARD(fs->opts.id_path);
|
|
5970
|
+
return rows;
|
|
5971
|
+
}
|
|
5972
|
+
|
|
5973
|
+
static long fs_count_accepted_features(fs_args_t *fs, struct json features) {
|
|
5974
|
+
struct json feature;
|
|
5975
|
+
long feature_index = 0;
|
|
5976
|
+
long accepted = 0;
|
|
5977
|
+
|
|
5978
|
+
for (feature = json_first(features); json_exists(feature);
|
|
5979
|
+
feature = json_next(feature), feature_index++) {
|
|
5980
|
+
fs_feature_result_t prepared;
|
|
5981
|
+
long skipped = 0;
|
|
5982
|
+
|
|
5983
|
+
if (!fs_feature_prepare(&fs->source, &fs->opts, FS_MODE_BUILD_INDEX, feature, feature_index,
|
|
5984
|
+
false, &prepared)) {
|
|
5985
|
+
fs_handle_invalid_or_raise(&fs->source, &fs->opts, &prepared, feature_index, Qnil,
|
|
5986
|
+
&skipped);
|
|
5987
|
+
}
|
|
5988
|
+
|
|
5989
|
+
if (prepared.filtered)
|
|
5990
|
+
continue;
|
|
5991
|
+
if (prepared.accepted)
|
|
5992
|
+
accepted++;
|
|
5993
|
+
}
|
|
5994
|
+
|
|
5995
|
+
return accepted;
|
|
5996
|
+
}
|
|
5997
|
+
|
|
5998
|
+
static void fs_fill_index_entry_from_geometry(tg_index_t *idx, long ordinal, VALUE id,
|
|
5999
|
+
struct json geometry, enum tg_index geometry_index,
|
|
6000
|
+
fs_source_t *source, long feature_index) {
|
|
6001
|
+
tg_index_entry_t entry;
|
|
6002
|
+
struct tg_geom *geom;
|
|
6003
|
+
const char *err;
|
|
6004
|
+
|
|
6005
|
+
geom = tg_parse_geojsonn_ix(json_raw(geometry), json_raw_length(geometry), geometry_index);
|
|
6006
|
+
if (!geom) {
|
|
6007
|
+
rb_raise(rb_eNoMemError, "TG geometry allocation failed");
|
|
6008
|
+
}
|
|
6009
|
+
|
|
6010
|
+
err = tg_geom_error(geom);
|
|
6011
|
+
if (err) {
|
|
6012
|
+
VALUE msg = rb_str_new_cstr(err);
|
|
6013
|
+
VALUE full;
|
|
6014
|
+
tg_geom_free(geom);
|
|
6015
|
+
full = rb_str_plus(rb_str_new_cstr("invalid geometry: "), msg);
|
|
6016
|
+
fs_raise_parse_error_value(feature_index, fs_json_offset(source, geometry), full);
|
|
6017
|
+
}
|
|
6018
|
+
|
|
6019
|
+
memset(&entry, 0, sizeof(entry));
|
|
6020
|
+
entry.id = id;
|
|
6021
|
+
entry.geom_owner = Qnil;
|
|
6022
|
+
entry.geom = geom;
|
|
6023
|
+
entry.bbox = tg_geom_rect(geom);
|
|
6024
|
+
entry.geom_bytes = tg_geom_memsize(geom);
|
|
6025
|
+
entry.ordinal = ordinal;
|
|
6026
|
+
entry.owned = true;
|
|
6027
|
+
|
|
6028
|
+
idx->entries[ordinal] = entry;
|
|
6029
|
+
idx->initialized++;
|
|
6030
|
+
idx->owned_geom_bytes_total += entry.geom_bytes;
|
|
6031
|
+
if (entry.geom_bytes > 0) {
|
|
6032
|
+
rb_gc_adjust_memory_usage((ssize_t)entry.geom_bytes);
|
|
6033
|
+
}
|
|
6034
|
+
index_expand_bbox(idx, entry.bbox);
|
|
6035
|
+
}
|
|
6036
|
+
|
|
6037
|
+
static VALUE fs_build_index_body(VALUE arg) {
|
|
6038
|
+
fs_build_args_t *build = (fs_build_args_t *)arg;
|
|
6039
|
+
fs_args_t *fs = build->fs;
|
|
6040
|
+
tg_index_t *idx = build->idx;
|
|
6041
|
+
struct json feature;
|
|
6042
|
+
long feature_index = 0;
|
|
6043
|
+
long ordinal = 0;
|
|
6044
|
+
|
|
6045
|
+
for (feature = json_first(build->features); json_exists(feature);
|
|
6046
|
+
feature = json_next(feature), feature_index++) {
|
|
6047
|
+
fs_feature_result_t prepared;
|
|
6048
|
+
long skipped = 0;
|
|
6049
|
+
|
|
6050
|
+
if (!fs_feature_prepare(&fs->source, &fs->opts, FS_MODE_BUILD_INDEX, feature, feature_index,
|
|
6051
|
+
true, &prepared)) {
|
|
6052
|
+
fs_handle_invalid_or_raise(&fs->source, &fs->opts, &prepared, feature_index, Qnil,
|
|
6053
|
+
&skipped);
|
|
6054
|
+
}
|
|
6055
|
+
|
|
6056
|
+
if (prepared.filtered)
|
|
6057
|
+
continue;
|
|
6058
|
+
if (!prepared.accepted)
|
|
6059
|
+
continue;
|
|
6060
|
+
|
|
6061
|
+
fs_fill_index_entry_from_geometry(idx, ordinal, prepared.id, prepared.geometry,
|
|
6062
|
+
fs->opts.geometry_index, &fs->source, feature_index);
|
|
6063
|
+
ordinal++;
|
|
6064
|
+
}
|
|
6065
|
+
|
|
6066
|
+
if (idx->initialized != idx->len) {
|
|
6067
|
+
rb_raise(eTGGeometryError, "internal FeatureSource index initialization mismatch");
|
|
6068
|
+
}
|
|
6069
|
+
|
|
6070
|
+
if (idx->strategy == TG_GEOMETRY_INDEX_STRATEGY_RTREE) {
|
|
6071
|
+
index_build_rtree(idx);
|
|
6072
|
+
}
|
|
6073
|
+
|
|
6074
|
+
return Qnil;
|
|
6075
|
+
}
|
|
6076
|
+
|
|
6077
|
+
static VALUE fs_build_index(VALUE arg) {
|
|
6078
|
+
fs_args_t *fs = (fs_args_t *)arg;
|
|
6079
|
+
struct json features;
|
|
6080
|
+
long accepted;
|
|
6081
|
+
tg_index_t *idx;
|
|
6082
|
+
VALUE wrapper;
|
|
6083
|
+
fs_build_args_t build;
|
|
6084
|
+
int state = 0;
|
|
6085
|
+
|
|
6086
|
+
fs_parse_root(&fs->source, &features);
|
|
6087
|
+
accepted = fs_count_accepted_features(fs, features);
|
|
6088
|
+
|
|
6089
|
+
wrapper = TypedData_Make_Struct(cTGGeometryIndex, tg_index_t, &tg_index_type, idx);
|
|
6090
|
+
idx->len = accepted;
|
|
6091
|
+
idx->capacity = accepted;
|
|
6092
|
+
idx->initialized = 0;
|
|
6093
|
+
idx->strategy = fs->opts.strategy;
|
|
6094
|
+
idx->predicate = fs->opts.predicate;
|
|
6095
|
+
idx->rtree = NULL;
|
|
6096
|
+
idx->frozen = false;
|
|
6097
|
+
idx->has_bbox = false;
|
|
6098
|
+
|
|
6099
|
+
if (accepted > 0) {
|
|
6100
|
+
if ((size_t)accepted > SIZE_MAX / sizeof(tg_index_entry_t)) {
|
|
6101
|
+
rb_raise(rb_eNoMemError, "entries allocation size overflow");
|
|
6102
|
+
}
|
|
6103
|
+
idx->entries = calloc((size_t)accepted, sizeof(tg_index_entry_t));
|
|
6104
|
+
if (!idx->entries) {
|
|
6105
|
+
rb_raise(rb_eNoMemError, "entries allocation failed");
|
|
6106
|
+
}
|
|
6107
|
+
idx->entries_bytes = (size_t)accepted * sizeof(tg_index_entry_t);
|
|
6108
|
+
rb_gc_adjust_memory_usage((ssize_t)idx->entries_bytes);
|
|
6109
|
+
}
|
|
6110
|
+
|
|
6111
|
+
build.fs = fs;
|
|
6112
|
+
build.features = features;
|
|
6113
|
+
build.wrapper = wrapper;
|
|
6114
|
+
build.idx = idx;
|
|
6115
|
+
|
|
6116
|
+
rb_protect(fs_build_index_body, (VALUE)&build, &state);
|
|
6117
|
+
if (state) {
|
|
6118
|
+
index_dispose(idx);
|
|
6119
|
+
RB_GC_GUARD(wrapper);
|
|
6120
|
+
RB_GC_GUARD(fs->opts.id_path);
|
|
6121
|
+
rb_jump_tag(state);
|
|
6122
|
+
}
|
|
6123
|
+
|
|
6124
|
+
idx->frozen = true;
|
|
6125
|
+
rb_obj_freeze(wrapper);
|
|
6126
|
+
|
|
6127
|
+
RB_GC_GUARD(wrapper);
|
|
6128
|
+
RB_GC_GUARD(fs->opts.id_path);
|
|
6129
|
+
return wrapper;
|
|
6130
|
+
}
|
|
6131
|
+
|
|
6132
|
+
static VALUE fs_source_ensure(VALUE arg) {
|
|
6133
|
+
fs_args_t *fs = (fs_args_t *)arg;
|
|
6134
|
+
|
|
6135
|
+
if (fs->source.data) {
|
|
6136
|
+
ruby_xfree(fs->source.data);
|
|
6137
|
+
fs->source.data = NULL;
|
|
6138
|
+
fs->source.len = 0;
|
|
6139
|
+
}
|
|
6140
|
+
|
|
6141
|
+
return Qnil;
|
|
6142
|
+
}
|
|
6143
|
+
|
|
6144
|
+
static void fs_copy_source_from_string(fs_args_t *fs, VALUE source) {
|
|
6145
|
+
VALUE str = source;
|
|
6146
|
+
StringValue(str);
|
|
6147
|
+
|
|
6148
|
+
if ((size_t)RSTRING_LEN(str) > SIZE_MAX - 1) {
|
|
6149
|
+
rb_raise(rb_eNoMemError, "FeatureSource input is too large");
|
|
6150
|
+
}
|
|
6151
|
+
|
|
6152
|
+
fs->source.len = (size_t)RSTRING_LEN(str);
|
|
6153
|
+
fs->source.data = ruby_xmalloc(fs->source.len + 1);
|
|
6154
|
+
memcpy(fs->source.data, RSTRING_PTR(str), fs->source.len);
|
|
6155
|
+
fs->source.data[fs->source.len] = '\0';
|
|
6156
|
+
RB_GC_GUARD(str);
|
|
6157
|
+
}
|
|
6158
|
+
|
|
6159
|
+
static VALUE __attribute__((unused)) fs_dispatch(VALUE source_string, VALUE kwargs,
|
|
6160
|
+
fs_mode_t mode) {
|
|
6161
|
+
fs_args_t fs;
|
|
6162
|
+
|
|
6163
|
+
memset(&fs, 0, sizeof(fs));
|
|
6164
|
+
fs.mode = mode;
|
|
6165
|
+
fs.opts = fs_parse_options(kwargs, mode);
|
|
6166
|
+
fs_copy_source_from_string(&fs, source_string);
|
|
6167
|
+
|
|
6168
|
+
if (mode == FS_MODE_BUILD_INDEX) {
|
|
6169
|
+
return rb_ensure(fs_build_index, (VALUE)&fs, fs_source_ensure, (VALUE)&fs);
|
|
6170
|
+
}
|
|
6171
|
+
|
|
6172
|
+
return rb_ensure(fs_read_body, (VALUE)&fs, fs_source_ensure, (VALUE)&fs);
|
|
6173
|
+
}
|
|
6174
|
+
|
|
6175
|
+
static VALUE __attribute__((unused)) fs_read_file_to_string(VALUE path) {
|
|
6176
|
+
return rb_funcall(rb_cFile, rb_intern("binread"), 1, path);
|
|
6177
|
+
}
|
|
6178
|
+
|
|
6179
|
+
static VALUE fs_read_io_to_string(VALUE io) {
|
|
6180
|
+
VALUE str;
|
|
6181
|
+
|
|
6182
|
+
if (!rb_respond_to(io, id_read)) {
|
|
6183
|
+
rb_raise(rb_eTypeError, "io must respond to read");
|
|
6184
|
+
}
|
|
6185
|
+
str = rb_funcall(io, id_read, 0);
|
|
6186
|
+
if (!RB_TYPE_P(str, T_STRING)) {
|
|
6187
|
+
rb_raise(rb_eTypeError, "io.read must return String");
|
|
6188
|
+
}
|
|
6189
|
+
return str;
|
|
6190
|
+
}
|
|
6191
|
+
|
|
6192
|
+
static VALUE rb_tg_feature_source_read_entries_json(int argc, VALUE *argv, VALUE self) {
|
|
6193
|
+
VALUE json_string;
|
|
6194
|
+
VALUE kwargs;
|
|
6195
|
+
(void)self;
|
|
6196
|
+
rb_scan_args(argc, argv, "1:", &json_string, &kwargs);
|
|
6197
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_READ_ENTRIES);
|
|
6198
|
+
}
|
|
6199
|
+
|
|
6200
|
+
static VALUE rb_tg_feature_source_read_features_json(int argc, VALUE *argv, VALUE self) {
|
|
6201
|
+
VALUE json_string;
|
|
6202
|
+
VALUE kwargs;
|
|
6203
|
+
(void)self;
|
|
6204
|
+
rb_scan_args(argc, argv, "1:", &json_string, &kwargs);
|
|
6205
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_READ_FEATURES);
|
|
6206
|
+
}
|
|
6207
|
+
|
|
6208
|
+
static VALUE rb_tg_feature_source_build_index_json(int argc, VALUE *argv, VALUE self) {
|
|
6209
|
+
VALUE json_string;
|
|
6210
|
+
VALUE kwargs;
|
|
6211
|
+
(void)self;
|
|
6212
|
+
rb_scan_args(argc, argv, "1:", &json_string, &kwargs);
|
|
6213
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_BUILD_INDEX);
|
|
6214
|
+
}
|
|
6215
|
+
|
|
6216
|
+
static VALUE rb_tg_feature_source_read_entries_file(int argc, VALUE *argv, VALUE self) {
|
|
6217
|
+
VALUE path;
|
|
6218
|
+
VALUE kwargs;
|
|
6219
|
+
(void)self;
|
|
6220
|
+
rb_scan_args(argc, argv, "1:", &path, &kwargs);
|
|
6221
|
+
return fs_safe_dispatch_file(path, kwargs, FS_MODE_READ_ENTRIES);
|
|
6222
|
+
}
|
|
6223
|
+
|
|
6224
|
+
static VALUE rb_tg_feature_source_read_features_file(int argc, VALUE *argv, VALUE self) {
|
|
6225
|
+
VALUE path;
|
|
6226
|
+
VALUE kwargs;
|
|
6227
|
+
(void)self;
|
|
6228
|
+
rb_scan_args(argc, argv, "1:", &path, &kwargs);
|
|
6229
|
+
return fs_safe_dispatch_file(path, kwargs, FS_MODE_READ_FEATURES);
|
|
6230
|
+
}
|
|
6231
|
+
|
|
6232
|
+
static VALUE rb_tg_feature_source_build_index_file(int argc, VALUE *argv, VALUE self) {
|
|
6233
|
+
VALUE path;
|
|
6234
|
+
VALUE kwargs;
|
|
6235
|
+
(void)self;
|
|
6236
|
+
rb_scan_args(argc, argv, "1:", &path, &kwargs);
|
|
6237
|
+
return fs_safe_dispatch_file(path, kwargs, FS_MODE_BUILD_INDEX);
|
|
6238
|
+
}
|
|
6239
|
+
|
|
6240
|
+
static VALUE rb_tg_feature_source_read_entries_io(int argc, VALUE *argv, VALUE self) {
|
|
6241
|
+
VALUE io;
|
|
6242
|
+
VALUE kwargs;
|
|
6243
|
+
VALUE json_string;
|
|
6244
|
+
(void)self;
|
|
6245
|
+
rb_scan_args(argc, argv, "1:", &io, &kwargs);
|
|
6246
|
+
json_string = fs_read_io_to_string(io);
|
|
6247
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_READ_ENTRIES);
|
|
6248
|
+
}
|
|
6249
|
+
|
|
6250
|
+
static VALUE rb_tg_feature_source_read_features_io(int argc, VALUE *argv, VALUE self) {
|
|
6251
|
+
VALUE io;
|
|
6252
|
+
VALUE kwargs;
|
|
6253
|
+
VALUE json_string;
|
|
6254
|
+
(void)self;
|
|
6255
|
+
rb_scan_args(argc, argv, "1:", &io, &kwargs);
|
|
6256
|
+
json_string = fs_read_io_to_string(io);
|
|
6257
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_READ_FEATURES);
|
|
6258
|
+
}
|
|
6259
|
+
|
|
6260
|
+
static VALUE rb_tg_feature_source_build_index_io(int argc, VALUE *argv, VALUE self) {
|
|
6261
|
+
VALUE io;
|
|
6262
|
+
VALUE kwargs;
|
|
6263
|
+
VALUE json_string;
|
|
6264
|
+
(void)self;
|
|
6265
|
+
rb_scan_args(argc, argv, "1:", &io, &kwargs);
|
|
6266
|
+
json_string = fs_read_io_to_string(io);
|
|
6267
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_BUILD_INDEX);
|
|
6268
|
+
}
|
|
6269
|
+
|
|
6270
|
+
RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
6271
|
+
tg_geometry_vendor_header_sanity();
|
|
6272
|
+
|
|
6273
|
+
id_format = rb_intern("format");
|
|
6274
|
+
id_index = rb_intern("index");
|
|
6275
|
+
id_srid = rb_intern("srid");
|
|
6276
|
+
id_auto = rb_intern("auto");
|
|
6277
|
+
id_geojson = rb_intern("geojson");
|
|
6278
|
+
id_wkt = rb_intern("wkt");
|
|
6279
|
+
id_wkb = rb_intern("wkb");
|
|
6280
|
+
id_hex = rb_intern("hex");
|
|
6281
|
+
id_geobin = rb_intern("geobin");
|
|
6282
|
+
id_default = rb_intern("default");
|
|
6283
|
+
id_none = rb_intern("none");
|
|
6284
|
+
id_natural = rb_intern("natural");
|
|
6285
|
+
id_ystripes = rb_intern("ystripes");
|
|
6286
|
+
id_via = rb_intern("via");
|
|
6287
|
+
id_strategy = rb_intern("strategy");
|
|
6288
|
+
id_predicate = rb_intern("predicate");
|
|
6289
|
+
id_geometry_index = rb_intern("geometry_index");
|
|
6290
|
+
id_geom = rb_intern("geom");
|
|
6291
|
+
id_flat = rb_intern("flat");
|
|
6292
|
+
id_rtree = rb_intern("rtree");
|
|
6293
|
+
id_covers = rb_intern("covers");
|
|
6294
|
+
id_contains = rb_intern("contains");
|
|
6295
|
+
id_id = rb_intern("id");
|
|
6296
|
+
id_only = rb_intern("only");
|
|
6297
|
+
id_on_invalid = rb_intern("on_invalid");
|
|
6298
|
+
id_on_missing_id = rb_intern("on_missing_id");
|
|
6299
|
+
id_report = rb_intern("report");
|
|
6300
|
+
id_max_errors = rb_intern("max_errors");
|
|
6301
|
+
id_read = rb_intern("read");
|
|
6302
|
+
id_raise = rb_intern("raise");
|
|
6303
|
+
id_skip = rb_intern("skip");
|
|
6304
|
+
id_ordinal = rb_intern("ordinal");
|
|
6305
|
+
id_polygon = rb_intern("polygon");
|
|
6306
|
+
id_multipolygon = rb_intern("multipolygon");
|
|
6307
|
+
id_point = rb_intern("point");
|
|
6308
|
+
id_linestring = rb_intern("linestring");
|
|
6309
|
+
id_multipoint = rb_intern("multipoint");
|
|
6310
|
+
id_multilinestring = rb_intern("multilinestring");
|
|
6311
|
+
id_geometrycollection = rb_intern("geometrycollection");
|
|
6312
|
+
id_exterior = rb_intern("exterior");
|
|
6313
|
+
id_holes = rb_intern("holes");
|
|
6314
|
+
|
|
6315
|
+
mTG = rb_define_module("TG");
|
|
6316
|
+
mTGGeometry = rb_define_module_under(mTG, "Geometry");
|
|
6317
|
+
|
|
6318
|
+
eTGGeometryError = rb_define_class_under(mTGGeometry, "Error", rb_eStandardError);
|
|
6319
|
+
eTGGeometryParseError = rb_define_class_under(mTGGeometry, "ParseError", eTGGeometryError);
|
|
6320
|
+
eTGGeometryArgumentError = rb_define_class_under(mTGGeometry, "ArgumentError", rb_eArgError);
|
|
6321
|
+
eTGGeometryFrozenIndexError =
|
|
6322
|
+
rb_define_class_under(mTGGeometry, "FrozenIndexError", eTGGeometryError);
|
|
6323
|
+
|
|
6324
|
+
rb_define_singleton_method(mTGGeometry, "parse", rb_tg_geometry_parse, -1);
|
|
6325
|
+
rb_define_singleton_method(mTGGeometry, "parse_geojson", rb_tg_geometry_parse_geojson, -1);
|
|
6326
|
+
rb_define_singleton_method(mTGGeometry, "parse_wkt", rb_tg_geometry_parse_wkt, -1);
|
|
6327
|
+
rb_define_singleton_method(mTGGeometry, "parse_wkb", rb_tg_geometry_parse_wkb, -1);
|
|
6328
|
+
rb_define_singleton_method(mTGGeometry, "parse_hex", rb_tg_geometry_parse_hex, -1);
|
|
6329
|
+
rb_define_singleton_method(mTGGeometry, "parse_geobin", rb_tg_geometry_parse_geobin, -1);
|
|
6330
|
+
rb_define_singleton_method(mTGGeometry, "point", rb_tg_geometry_point, 2);
|
|
6331
|
+
rb_define_singleton_method(mTGGeometry, "point_z", rb_tg_geometry_point_z, 3);
|
|
6332
|
+
rb_define_singleton_method(mTGGeometry, "point_m", rb_tg_geometry_point_m, 3);
|
|
6333
|
+
rb_define_singleton_method(mTGGeometry, "point_zm", rb_tg_geometry_point_zm, 4);
|
|
6334
|
+
rb_define_singleton_method(mTGGeometry, "empty_point", rb_tg_geometry_empty_point, 0);
|
|
6335
|
+
rb_define_singleton_method(mTGGeometry, "empty_linestring", rb_tg_geometry_empty_linestring, 0);
|
|
6336
|
+
rb_define_singleton_method(mTGGeometry, "empty_polygon", rb_tg_geometry_empty_polygon, 0);
|
|
6337
|
+
rb_define_singleton_method(mTGGeometry, "empty_multipoint", rb_tg_geometry_empty_multipoint, 0);
|
|
6338
|
+
rb_define_singleton_method(mTGGeometry, "empty_multilinestring",
|
|
6339
|
+
rb_tg_geometry_empty_multilinestring, 0);
|
|
6340
|
+
rb_define_singleton_method(mTGGeometry, "empty_multipolygon", rb_tg_geometry_empty_multipolygon,
|
|
6341
|
+
0);
|
|
6342
|
+
rb_define_singleton_method(mTGGeometry, "empty_geometrycollection",
|
|
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);
|
|
2911
6347
|
|
|
2912
6348
|
cTGGeometryGeom = rb_define_class_under(mTGGeometry, "Geom", rb_cObject);
|
|
2913
6349
|
rb_undef_alloc_func(cTGGeometryGeom);
|
|
2914
6350
|
rb_define_method(cTGGeometryGeom, "type", rb_tg_geometry_geom_type, 0);
|
|
6351
|
+
rb_define_method(cTGGeometryGeom, "srid", rb_tg_geometry_geom_srid, 0);
|
|
2915
6352
|
rb_define_method(cTGGeometryGeom, "bbox", rb_tg_geometry_geom_bbox, 0);
|
|
2916
6353
|
rb_define_method(cTGGeometryGeom, "covers_xy?", rb_tg_geometry_geom_covers_xy_p, 2);
|
|
2917
6354
|
rb_define_method(cTGGeometryGeom, "intersects_xy?", rb_tg_geometry_geom_intersects_xy_p, 2);
|
|
@@ -2928,6 +6365,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
2928
6365
|
rb_define_method(cTGGeometryGeom, "to_geojson", rb_tg_geometry_geom_to_geojson, 0);
|
|
2929
6366
|
rb_define_method(cTGGeometryGeom, "to_wkt", rb_tg_geometry_geom_to_wkt, 0);
|
|
2930
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);
|
|
2931
6369
|
rb_define_method(cTGGeometryGeom, "to_hex", rb_tg_geometry_geom_to_hex, 0);
|
|
2932
6370
|
rb_define_method(cTGGeometryGeom, "to_geobin", rb_tg_geometry_geom_to_geobin, 0);
|
|
2933
6371
|
rb_define_method(cTGGeometryGeom, "extra_json", rb_tg_geometry_geom_extra_json, 0);
|
|
@@ -2968,6 +6406,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
2968
6406
|
rb_define_method(cTGGeometryLine, "segments", rb_tg_geometry_line_segments, 0);
|
|
2969
6407
|
rb_define_method(cTGGeometryLine, "length", rb_tg_geometry_line_length, 0);
|
|
2970
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);
|
|
2971
6410
|
|
|
2972
6411
|
cTGGeometryRing = rb_define_class_under(mTGGeometry, "Ring", rb_cObject);
|
|
2973
6412
|
rb_undef_alloc_func(cTGGeometryRing);
|
|
@@ -2982,6 +6421,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
2982
6421
|
rb_define_method(cTGGeometryRing, "perimeter", rb_tg_geometry_ring_perimeter, 0);
|
|
2983
6422
|
rb_define_method(cTGGeometryRing, "clockwise?", rb_tg_geometry_ring_clockwise_p, 0);
|
|
2984
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);
|
|
2985
6425
|
|
|
2986
6426
|
cTGGeometryPolygon = rb_define_class_under(mTGGeometry, "Polygon", rb_cObject);
|
|
2987
6427
|
rb_undef_alloc_func(cTGGeometryPolygon);
|
|
@@ -3000,6 +6440,15 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
3000
6440
|
rb_define_method(cTGGeometrySegment, "bbox", rb_tg_geometry_segment_bbox, 0);
|
|
3001
6441
|
rb_define_method(cTGGeometrySegment, "intersects?", rb_tg_geometry_segment_intersects_p, 1);
|
|
3002
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
|
+
|
|
3003
6452
|
cTGGeometryRect = rb_define_class_under(mTGGeometry, "Rect", rb_cObject);
|
|
3004
6453
|
rb_define_alloc_func(cTGGeometryRect, rb_tg_geometry_rect_alloc);
|
|
3005
6454
|
rb_define_method(cTGGeometryRect, "initialize", rb_tg_geometry_rect_initialize, -1);
|
|
@@ -3015,11 +6464,37 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
|
3015
6464
|
rb_define_method(cTGGeometryRect, "expand_to_include_point",
|
|
3016
6465
|
rb_tg_geometry_rect_expand_to_include_point, 2);
|
|
3017
6466
|
|
|
6467
|
+
mTGGeometryFeatureSource = rb_define_module_under(mTGGeometry, "FeatureSource");
|
|
6468
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_entries_file",
|
|
6469
|
+
rb_tg_feature_source_read_entries_file, -1);
|
|
6470
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_entries_json",
|
|
6471
|
+
rb_tg_feature_source_read_entries_json, -1);
|
|
6472
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_entries_io",
|
|
6473
|
+
rb_tg_feature_source_read_entries_io, -1);
|
|
6474
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_features_file",
|
|
6475
|
+
rb_tg_feature_source_read_features_file, -1);
|
|
6476
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_features_json",
|
|
6477
|
+
rb_tg_feature_source_read_features_json, -1);
|
|
6478
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_features_io",
|
|
6479
|
+
rb_tg_feature_source_read_features_io, -1);
|
|
6480
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "build_index_file",
|
|
6481
|
+
rb_tg_feature_source_build_index_file, -1);
|
|
6482
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "build_index_json",
|
|
6483
|
+
rb_tg_feature_source_build_index_json, -1);
|
|
6484
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "build_index_io",
|
|
6485
|
+
rb_tg_feature_source_build_index_io, -1);
|
|
6486
|
+
|
|
3018
6487
|
cTGGeometryIndex = rb_define_class_under(mTGGeometry, "Index", rb_cObject);
|
|
3019
6488
|
rb_undef_alloc_func(cTGGeometryIndex);
|
|
3020
6489
|
rb_define_singleton_method(cTGGeometryIndex, "build", rb_tg_geometry_index_build, -1);
|
|
3021
6490
|
rb_define_method(cTGGeometryIndex, "find_covering", rb_tg_geometry_index_find_covering, 2);
|
|
3022
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);
|
|
3023
6498
|
rb_define_method(cTGGeometryIndex, "intersecting_rect", rb_tg_geometry_index_intersecting_rect,
|
|
3024
6499
|
4);
|
|
3025
6500
|
rb_define_method(cTGGeometryIndex, "covering_ids_batch_packed",
|