tg_geometry 0.1.0 → 0.2.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 +18 -79
- data/README.md +82 -191
- data/Rakefile +3 -3
- data/benchmark/falcon_concurrency.rb +1 -1
- data/benchmark/feature_source.rb +92 -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/LIMITATIONS.md +11 -50
- data/docs/MEMORY_OWNERSHIP.md +20 -2
- data/ext/tg_geometry/extconf.rb +46 -4
- data/ext/tg_geometry/tg_geometry_ext.c +2453 -150
- 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/registry.rb +3 -3
- data/lib/tg/geometry/version.rb +1 -1
- data/script/vendor_libs.rb +22 -6
- 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/{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/{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/{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/{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/{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/{expansion_j_full_tg_api_coverage_spec.rb → tg_api_coverage_spec.rb} +1 -1
- data/spec/{block_2_vendor_spec.rb → vendor_sources_spec.rb} +4 -4
- metadata +39 -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,19 +20,31 @@
|
|
|
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;
|
|
@@ -149,6 +162,23 @@ static ID id_flat;
|
|
|
149
162
|
static ID id_rtree;
|
|
150
163
|
static ID id_covers;
|
|
151
164
|
static ID id_contains;
|
|
165
|
+
static ID id_id;
|
|
166
|
+
static ID id_only;
|
|
167
|
+
static ID id_on_invalid;
|
|
168
|
+
static ID id_on_missing_id;
|
|
169
|
+
static ID id_report;
|
|
170
|
+
static ID id_max_errors;
|
|
171
|
+
static ID id_read;
|
|
172
|
+
static ID id_raise;
|
|
173
|
+
static ID id_skip;
|
|
174
|
+
static ID id_ordinal;
|
|
175
|
+
static ID id_polygon;
|
|
176
|
+
static ID id_multipolygon;
|
|
177
|
+
static ID id_point;
|
|
178
|
+
static ID id_linestring;
|
|
179
|
+
static ID id_multipoint;
|
|
180
|
+
static ID id_multilinestring;
|
|
181
|
+
static ID id_geometrycollection;
|
|
152
182
|
|
|
153
183
|
#ifdef TG_DEBUG_TEST
|
|
154
184
|
static bool tg_debug_fail_next_entries_alloc = false;
|
|
@@ -2854,166 +2884,2439 @@ static VALUE rb_tg_geometry_index_force_dispose_for_test(VALUE self) {
|
|
|
2854
2884
|
}
|
|
2855
2885
|
#endif
|
|
2856
2886
|
|
|
2857
|
-
|
|
2858
|
-
|
|
2887
|
+
typedef enum {
|
|
2888
|
+
FS_MODE_READ_ENTRIES,
|
|
2889
|
+
FS_MODE_READ_FEATURES,
|
|
2890
|
+
FS_MODE_BUILD_INDEX,
|
|
2891
|
+
} fs_mode_t;
|
|
2892
|
+
|
|
2893
|
+
typedef enum {
|
|
2894
|
+
FS_ON_INVALID_RAISE,
|
|
2895
|
+
FS_ON_INVALID_SKIP,
|
|
2896
|
+
} fs_on_invalid_t;
|
|
2897
|
+
|
|
2898
|
+
typedef enum {
|
|
2899
|
+
FS_ON_MISSING_ID_RAISE,
|
|
2900
|
+
FS_ON_MISSING_ID_SKIP,
|
|
2901
|
+
FS_ON_MISSING_ID_ORDINAL,
|
|
2902
|
+
} fs_on_missing_id_t;
|
|
2903
|
+
|
|
2904
|
+
#define FS_GEOM_POINT (1u << 0)
|
|
2905
|
+
#define FS_GEOM_LINESTRING (1u << 1)
|
|
2906
|
+
#define FS_GEOM_POLYGON (1u << 2)
|
|
2907
|
+
#define FS_GEOM_MULTIPOINT (1u << 3)
|
|
2908
|
+
#define FS_GEOM_MULTILINESTRING (1u << 4)
|
|
2909
|
+
#define FS_GEOM_MULTIPOLYGON (1u << 5)
|
|
2910
|
+
#define FS_GEOM_GEOMETRYCOLLECTION (1u << 6)
|
|
2859
2911
|
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
id_via = rb_intern("via");
|
|
2873
|
-
id_strategy = rb_intern("strategy");
|
|
2874
|
-
id_predicate = rb_intern("predicate");
|
|
2875
|
-
id_geometry_index = rb_intern("geometry_index");
|
|
2876
|
-
id_geom = rb_intern("geom");
|
|
2877
|
-
id_flat = rb_intern("flat");
|
|
2878
|
-
id_rtree = rb_intern("rtree");
|
|
2879
|
-
id_covers = rb_intern("covers");
|
|
2880
|
-
id_contains = rb_intern("contains");
|
|
2912
|
+
typedef struct {
|
|
2913
|
+
VALUE id_path;
|
|
2914
|
+
bool only_all;
|
|
2915
|
+
unsigned int only_mask;
|
|
2916
|
+
fs_on_invalid_t on_invalid;
|
|
2917
|
+
fs_on_missing_id_t on_missing_id;
|
|
2918
|
+
bool report;
|
|
2919
|
+
long max_errors;
|
|
2920
|
+
enum tg_index geometry_index;
|
|
2921
|
+
enum tg_geometry_index_strategy strategy;
|
|
2922
|
+
enum tg_geometry_index_predicate predicate;
|
|
2923
|
+
} fs_options_t;
|
|
2881
2924
|
|
|
2882
|
-
|
|
2883
|
-
|
|
2925
|
+
typedef struct {
|
|
2926
|
+
char *data;
|
|
2927
|
+
size_t len;
|
|
2928
|
+
} fs_source_t;
|
|
2884
2929
|
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2930
|
+
typedef struct {
|
|
2931
|
+
fs_source_t source;
|
|
2932
|
+
fs_options_t opts;
|
|
2933
|
+
fs_mode_t mode;
|
|
2934
|
+
} fs_args_t;
|
|
2890
2935
|
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
rb_define_singleton_method(mTGGeometry, "point", rb_tg_geometry_point, 2);
|
|
2898
|
-
rb_define_singleton_method(mTGGeometry, "point_z", rb_tg_geometry_point_z, 3);
|
|
2899
|
-
rb_define_singleton_method(mTGGeometry, "point_m", rb_tg_geometry_point_m, 3);
|
|
2900
|
-
rb_define_singleton_method(mTGGeometry, "point_zm", rb_tg_geometry_point_zm, 4);
|
|
2901
|
-
rb_define_singleton_method(mTGGeometry, "empty_point", rb_tg_geometry_empty_point, 0);
|
|
2902
|
-
rb_define_singleton_method(mTGGeometry, "empty_linestring", rb_tg_geometry_empty_linestring, 0);
|
|
2903
|
-
rb_define_singleton_method(mTGGeometry, "empty_polygon", rb_tg_geometry_empty_polygon, 0);
|
|
2904
|
-
rb_define_singleton_method(mTGGeometry, "empty_multipoint", rb_tg_geometry_empty_multipoint, 0);
|
|
2905
|
-
rb_define_singleton_method(mTGGeometry, "empty_multilinestring",
|
|
2906
|
-
rb_tg_geometry_empty_multilinestring, 0);
|
|
2907
|
-
rb_define_singleton_method(mTGGeometry, "empty_multipolygon", rb_tg_geometry_empty_multipolygon,
|
|
2908
|
-
0);
|
|
2909
|
-
rb_define_singleton_method(mTGGeometry, "empty_geometrycollection",
|
|
2910
|
-
rb_tg_geometry_empty_geometrycollection, 0);
|
|
2936
|
+
typedef struct {
|
|
2937
|
+
fs_args_t *fs;
|
|
2938
|
+
struct json features;
|
|
2939
|
+
VALUE wrapper;
|
|
2940
|
+
tg_index_t *idx;
|
|
2941
|
+
} fs_build_args_t;
|
|
2911
2942
|
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
rb_define_method(cTGGeometryGeom, "covers?", rb_tg_geometry_geom_covers_p, 1);
|
|
2924
|
-
rb_define_method(cTGGeometryGeom, "covered_by?", rb_tg_geometry_geom_covered_by_p, 1);
|
|
2925
|
-
rb_define_method(cTGGeometryGeom, "touches?", rb_tg_geometry_geom_touches_p, 1);
|
|
2926
|
-
rb_define_method(cTGGeometryGeom, "intersects_rect?", rb_tg_geometry_geom_intersects_rect_p,
|
|
2927
|
-
-1);
|
|
2928
|
-
rb_define_method(cTGGeometryGeom, "to_geojson", rb_tg_geometry_geom_to_geojson, 0);
|
|
2929
|
-
rb_define_method(cTGGeometryGeom, "to_wkt", rb_tg_geometry_geom_to_wkt, 0);
|
|
2930
|
-
rb_define_method(cTGGeometryGeom, "to_wkb", rb_tg_geometry_geom_to_wkb, 0);
|
|
2931
|
-
rb_define_method(cTGGeometryGeom, "to_hex", rb_tg_geometry_geom_to_hex, 0);
|
|
2932
|
-
rb_define_method(cTGGeometryGeom, "to_geobin", rb_tg_geometry_geom_to_geobin, 0);
|
|
2933
|
-
rb_define_method(cTGGeometryGeom, "extra_json", rb_tg_geometry_geom_extra_json, 0);
|
|
2934
|
-
rb_define_method(cTGGeometryGeom, "feature?", rb_tg_geometry_geom_feature_p, 0);
|
|
2935
|
-
rb_define_method(cTGGeometryGeom, "feature_collection?",
|
|
2936
|
-
rb_tg_geometry_geom_feature_collection_p, 0);
|
|
2937
|
-
rb_define_method(cTGGeometryGeom, "empty?", rb_tg_geometry_geom_empty_p, 0);
|
|
2938
|
-
rb_define_method(cTGGeometryGeom, "dims", rb_tg_geometry_geom_dims, 0);
|
|
2939
|
-
rb_define_method(cTGGeometryGeom, "has_z?", rb_tg_geometry_geom_has_z_p, 0);
|
|
2940
|
-
rb_define_method(cTGGeometryGeom, "has_m?", rb_tg_geometry_geom_has_m_p, 0);
|
|
2941
|
-
rb_define_method(cTGGeometryGeom, "z", rb_tg_geometry_geom_z, 0);
|
|
2942
|
-
rb_define_method(cTGGeometryGeom, "m", rb_tg_geometry_geom_m, 0);
|
|
2943
|
-
rb_define_method(cTGGeometryGeom, "extra_coords", rb_tg_geometry_geom_extra_coords, 0);
|
|
2944
|
-
rb_define_method(cTGGeometryGeom, "point", rb_tg_geometry_geom_point, 0);
|
|
2945
|
-
rb_define_method(cTGGeometryGeom, "num_points", rb_tg_geometry_geom_num_points, 0);
|
|
2946
|
-
rb_define_method(cTGGeometryGeom, "point_at", rb_tg_geometry_geom_point_at, 1);
|
|
2947
|
-
rb_define_method(cTGGeometryGeom, "points", rb_tg_geometry_geom_points, 0);
|
|
2948
|
-
rb_define_method(cTGGeometryGeom, "line", rb_tg_geometry_geom_line, 0);
|
|
2949
|
-
rb_define_method(cTGGeometryGeom, "num_lines", rb_tg_geometry_geom_num_lines, 0);
|
|
2950
|
-
rb_define_method(cTGGeometryGeom, "line_at", rb_tg_geometry_geom_line_at, 1);
|
|
2951
|
-
rb_define_method(cTGGeometryGeom, "lines", rb_tg_geometry_geom_lines, 0);
|
|
2952
|
-
rb_define_method(cTGGeometryGeom, "polygon", rb_tg_geometry_geom_polygon, 0);
|
|
2953
|
-
rb_define_method(cTGGeometryGeom, "num_polygons", rb_tg_geometry_geom_num_polygons, 0);
|
|
2954
|
-
rb_define_method(cTGGeometryGeom, "polygon_at", rb_tg_geometry_geom_polygon_at, 1);
|
|
2955
|
-
rb_define_method(cTGGeometryGeom, "polygons", rb_tg_geometry_geom_polygons, 0);
|
|
2956
|
-
rb_define_method(cTGGeometryGeom, "num_geometries", rb_tg_geometry_geom_num_geometries, 0);
|
|
2957
|
-
rb_define_method(cTGGeometryGeom, "geometry_at", rb_tg_geometry_geom_geometry_at, 1);
|
|
2958
|
-
rb_define_method(cTGGeometryGeom, "geometries", rb_tg_geometry_geom_geometries, 0);
|
|
2943
|
+
typedef struct {
|
|
2944
|
+
bool accepted;
|
|
2945
|
+
bool filtered;
|
|
2946
|
+
bool missing_id_skip;
|
|
2947
|
+
VALUE id;
|
|
2948
|
+
struct json feature;
|
|
2949
|
+
struct json geometry;
|
|
2950
|
+
struct json properties;
|
|
2951
|
+
const char *reason;
|
|
2952
|
+
VALUE reason_value;
|
|
2953
|
+
} fs_feature_result_t;
|
|
2959
2954
|
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
rb_define_method(cTGGeometryLine, "num_points", rb_tg_geometry_line_num_points, 0);
|
|
2964
|
-
rb_define_method(cTGGeometryLine, "point_at", rb_tg_geometry_line_point_at, 1);
|
|
2965
|
-
rb_define_method(cTGGeometryLine, "points", rb_tg_geometry_line_points, 0);
|
|
2966
|
-
rb_define_method(cTGGeometryLine, "num_segments", rb_tg_geometry_line_num_segments, 0);
|
|
2967
|
-
rb_define_method(cTGGeometryLine, "segment_at", rb_tg_geometry_line_segment_at, 1);
|
|
2968
|
-
rb_define_method(cTGGeometryLine, "segments", rb_tg_geometry_line_segments, 0);
|
|
2969
|
-
rb_define_method(cTGGeometryLine, "length", rb_tg_geometry_line_length, 0);
|
|
2970
|
-
rb_define_method(cTGGeometryLine, "clockwise?", rb_tg_geometry_line_clockwise_p, 0);
|
|
2955
|
+
static VALUE fs_sym(const char *name) {
|
|
2956
|
+
return ID2SYM(rb_intern(name));
|
|
2957
|
+
}
|
|
2971
2958
|
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2959
|
+
static VALUE fs_kwargs_value(VALUE kwargs, ID key, VALUE fallback, bool *present) {
|
|
2960
|
+
VALUE sym = ID2SYM(key);
|
|
2961
|
+
if (NIL_P(kwargs)) {
|
|
2962
|
+
if (present)
|
|
2963
|
+
*present = false;
|
|
2964
|
+
return fallback;
|
|
2965
|
+
}
|
|
2966
|
+
if (RTEST(rb_funcall(kwargs, rb_intern("key?"), 1, sym))) {
|
|
2967
|
+
if (present)
|
|
2968
|
+
*present = true;
|
|
2969
|
+
return rb_hash_aref(kwargs, sym);
|
|
2970
|
+
}
|
|
2971
|
+
if (present)
|
|
2972
|
+
*present = false;
|
|
2973
|
+
return fallback;
|
|
2974
|
+
}
|
|
2985
2975
|
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
rb_define_method(cTGGeometryPolygon, "bbox", rb_tg_geometry_polygon_bbox, 0);
|
|
2989
|
-
rb_define_method(cTGGeometryPolygon, "exterior_ring", rb_tg_geometry_polygon_exterior_ring, 0);
|
|
2990
|
-
rb_define_method(cTGGeometryPolygon, "num_holes", rb_tg_geometry_polygon_num_holes, 0);
|
|
2991
|
-
rb_define_method(cTGGeometryPolygon, "hole_at", rb_tg_geometry_polygon_hole_at, 1);
|
|
2992
|
-
rb_define_method(cTGGeometryPolygon, "holes", rb_tg_geometry_polygon_holes, 0);
|
|
2993
|
-
rb_define_method(cTGGeometryPolygon, "clockwise?", rb_tg_geometry_polygon_clockwise_p, 0);
|
|
2976
|
+
static VALUE fs_utf8_string(const char *ptr, size_t len) {
|
|
2977
|
+
VALUE str;
|
|
2994
2978
|
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
rb_define_method(cTGGeometrySegment, "b", rb_tg_geometry_segment_b, 0);
|
|
2999
|
-
rb_define_method(cTGGeometrySegment, "points", rb_tg_geometry_segment_points, 0);
|
|
3000
|
-
rb_define_method(cTGGeometrySegment, "bbox", rb_tg_geometry_segment_bbox, 0);
|
|
3001
|
-
rb_define_method(cTGGeometrySegment, "intersects?", rb_tg_geometry_segment_intersects_p, 1);
|
|
2979
|
+
if (len > LONG_MAX) {
|
|
2980
|
+
rb_raise(rb_eNoMemError, "FeatureSource JSON substring is too large");
|
|
2981
|
+
}
|
|
3002
2982
|
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
2983
|
+
str = rb_str_new(ptr, (long)len);
|
|
2984
|
+
rb_enc_associate(str, rb_utf8_encoding());
|
|
2985
|
+
return str;
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
static VALUE fs_cstr_utf8_string(const char *ptr) {
|
|
2989
|
+
VALUE str = rb_str_new_cstr(ptr);
|
|
2990
|
+
rb_enc_associate(str, rb_utf8_encoding());
|
|
2991
|
+
return str;
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
static size_t fs_json_offset(fs_source_t *source, struct json value) {
|
|
2995
|
+
const char *raw = json_raw(value);
|
|
2996
|
+
|
|
2997
|
+
if (!raw || raw < source->data || raw > source->data + source->len) {
|
|
2998
|
+
return 0;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
return (size_t)(raw - source->data);
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
static VALUE fs_error_hash(long feature_index, size_t byte_offset, VALUE reason) {
|
|
3005
|
+
VALUE h = rb_hash_new();
|
|
3006
|
+
rb_hash_aset(h, fs_sym("feature_index"), LONG2NUM(feature_index));
|
|
3007
|
+
rb_hash_aset(h, fs_sym("byte_offset"), ULL2NUM((unsigned long long)byte_offset));
|
|
3008
|
+
rb_hash_aset(h, fs_sym("reason"), reason);
|
|
3009
|
+
return h;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
static void fs_report_error(VALUE errors, long max_errors, long feature_index, size_t byte_offset,
|
|
3013
|
+
VALUE reason) {
|
|
3014
|
+
if (RARRAY_LEN(errors) < max_errors) {
|
|
3015
|
+
rb_ary_push(errors, fs_error_hash(feature_index, byte_offset, reason));
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
static void TG_GEOMETRY_NORETURN fs_raise_parse_error_value(long feature_index, size_t byte_offset,
|
|
3020
|
+
VALUE reason) {
|
|
3021
|
+
VALUE prefix =
|
|
3022
|
+
rb_sprintf("feature %ld at byte %llu: ", feature_index, (unsigned long long)byte_offset);
|
|
3023
|
+
VALUE message = rb_str_plus(prefix, reason);
|
|
3024
|
+
rb_exc_raise(rb_exc_new_str(eTGGeometryParseError, message));
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
static void TG_GEOMETRY_NORETURN fs_raise_argument_error(long feature_index, size_t byte_offset,
|
|
3028
|
+
const char *reason) {
|
|
3029
|
+
rb_raise(eTGGeometryArgumentError, "feature %ld at byte %llu: %s", feature_index,
|
|
3030
|
+
(unsigned long long)byte_offset, reason);
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
static VALUE fs_copy_json_string_value(struct json value) {
|
|
3034
|
+
size_t len = json_string_copy(value, NULL, 0);
|
|
3035
|
+
VALUE str;
|
|
3036
|
+
|
|
3037
|
+
if (len > LONG_MAX) {
|
|
3038
|
+
rb_raise(rb_eNoMemError, "JSON string is too large");
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
str = rb_str_new(NULL, (long)len + 1);
|
|
3042
|
+
json_string_copy(value, RSTRING_PTR(str), len + 1);
|
|
3043
|
+
rb_str_set_len(str, (long)len);
|
|
3044
|
+
rb_enc_associate(str, rb_utf8_encoding());
|
|
3045
|
+
return str;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
static bool fs_json_number_is_integer(struct json value) {
|
|
3049
|
+
const char *raw = json_raw(value);
|
|
3050
|
+
size_t len = json_raw_length(value);
|
|
3051
|
+
|
|
3052
|
+
if (!raw || len == 0)
|
|
3053
|
+
return false;
|
|
3054
|
+
for (size_t i = 0; i < len; i++) {
|
|
3055
|
+
if (raw[i] == '.' || raw[i] == 'e' || raw[i] == 'E')
|
|
3056
|
+
return false;
|
|
3057
|
+
}
|
|
3058
|
+
return true;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
static bool fs_integer_id_from_json(struct json value, VALUE *id_out, VALUE *error_message) {
|
|
3062
|
+
const char *raw = json_raw(value);
|
|
3063
|
+
size_t len = json_raw_length(value);
|
|
3064
|
+
char stack[64];
|
|
3065
|
+
char *buf = stack;
|
|
3066
|
+
char *endp = NULL;
|
|
3067
|
+
long long parsed;
|
|
3068
|
+
|
|
3069
|
+
if (!fs_json_number_is_integer(value)) {
|
|
3070
|
+
*error_message = rb_str_new_cstr("invalid id: numeric id must be an integer");
|
|
3071
|
+
return false;
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
if (len >= sizeof(stack)) {
|
|
3075
|
+
buf = (char *)malloc(len + 1);
|
|
3076
|
+
if (!buf)
|
|
3077
|
+
rb_raise(rb_eNoMemError, "id buffer allocation failed");
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
memcpy(buf, raw, len);
|
|
3081
|
+
buf[len] = '\0';
|
|
3082
|
+
errno = 0;
|
|
3083
|
+
parsed = strtoll(buf, &endp, 10);
|
|
3084
|
+
|
|
3085
|
+
if (errno != 0 || endp != buf + len) {
|
|
3086
|
+
if (buf != stack)
|
|
3087
|
+
free(buf);
|
|
3088
|
+
*error_message = rb_str_new_cstr("invalid id: integer is out of range");
|
|
3089
|
+
return false;
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
*id_out = LL2NUM(parsed);
|
|
3093
|
+
if (buf != stack)
|
|
3094
|
+
free(buf);
|
|
3095
|
+
return true;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
static bool fs_validate_integer_id_json(struct json value, VALUE *error_message) {
|
|
3099
|
+
const char *raw = json_raw(value);
|
|
3100
|
+
size_t len = json_raw_length(value);
|
|
3101
|
+
char stack[64];
|
|
3102
|
+
char *buf = stack;
|
|
3103
|
+
char *endp = NULL;
|
|
3104
|
+
|
|
3105
|
+
if (!fs_json_number_is_integer(value)) {
|
|
3106
|
+
*error_message = rb_str_new_cstr("invalid id: numeric id must be an integer");
|
|
3107
|
+
return false;
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
if (len >= sizeof(stack)) {
|
|
3111
|
+
buf = (char *)malloc(len + 1);
|
|
3112
|
+
if (!buf)
|
|
3113
|
+
rb_raise(rb_eNoMemError, "id buffer allocation failed");
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
memcpy(buf, raw, len);
|
|
3117
|
+
buf[len] = '\0';
|
|
3118
|
+
errno = 0;
|
|
3119
|
+
(void)strtoll(buf, &endp, 10);
|
|
3120
|
+
|
|
3121
|
+
if (errno != 0 || endp != buf + len) {
|
|
3122
|
+
if (buf != stack)
|
|
3123
|
+
free(buf);
|
|
3124
|
+
*error_message = rb_str_new_cstr("invalid id: integer is out of range");
|
|
3125
|
+
return false;
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
if (buf != stack)
|
|
3129
|
+
free(buf);
|
|
3130
|
+
return true;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
static bool fs_id_from_json_value(struct json value, VALUE *id_out, VALUE *error_message,
|
|
3134
|
+
bool materialize_id) {
|
|
3135
|
+
switch (json_type(value)) {
|
|
3136
|
+
case JSON_STRING:
|
|
3137
|
+
*id_out = materialize_id ? fs_copy_json_string_value(value) : Qnil;
|
|
3138
|
+
return true;
|
|
3139
|
+
case JSON_NUMBER:
|
|
3140
|
+
if (!materialize_id) {
|
|
3141
|
+
*id_out = Qnil;
|
|
3142
|
+
return fs_validate_integer_id_json(value, error_message);
|
|
3143
|
+
}
|
|
3144
|
+
return fs_integer_id_from_json(value, id_out, error_message);
|
|
3145
|
+
case JSON_NULL:
|
|
3146
|
+
case JSON_TRUE:
|
|
3147
|
+
case JSON_FALSE:
|
|
3148
|
+
case JSON_ARRAY:
|
|
3149
|
+
case JSON_OBJECT:
|
|
3150
|
+
*error_message = rb_str_new_cstr("invalid id: expected JSON string or integer number");
|
|
3151
|
+
return false;
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
*error_message = rb_str_new_cstr("invalid id");
|
|
3155
|
+
return false;
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
static VALUE fs_default_id_path(void) {
|
|
3159
|
+
return rb_ary_new_from_args(2, fs_cstr_utf8_string("properties"), fs_cstr_utf8_string("@id"));
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
static VALUE fs_normalize_id_path(VALUE value) {
|
|
3163
|
+
VALUE path;
|
|
3164
|
+
|
|
3165
|
+
if (NIL_P(value)) {
|
|
3166
|
+
return fs_default_id_path();
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
if (RB_TYPE_P(value, T_STRING)) {
|
|
3170
|
+
path = rb_funcall(value, rb_intern("split"), 1, rb_str_new_cstr("."));
|
|
3171
|
+
} else if (RB_TYPE_P(value, T_ARRAY)) {
|
|
3172
|
+
path = rb_ary_dup(value);
|
|
3173
|
+
} else {
|
|
3174
|
+
rb_raise(eTGGeometryArgumentError, "id: must be String or Array<String>");
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
if (RARRAY_LEN(path) == 0) {
|
|
3178
|
+
rb_raise(eTGGeometryArgumentError, "id: path cannot be empty");
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
for (long i = 0; i < RARRAY_LEN(path); i++) {
|
|
3182
|
+
VALUE part = rb_ary_entry(path, i);
|
|
3183
|
+
if (!RB_TYPE_P(part, T_STRING)) {
|
|
3184
|
+
rb_raise(eTGGeometryArgumentError, "id: every path component must be String");
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
return path;
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
static bool fs_json_get_path(struct json root, VALUE path, struct json *out) {
|
|
3192
|
+
struct json cur = root;
|
|
3193
|
+
|
|
3194
|
+
for (long i = 0; i < RARRAY_LEN(path); i++) {
|
|
3195
|
+
VALUE key = rb_ary_entry(path, i);
|
|
3196
|
+
StringValue(key);
|
|
3197
|
+
cur = json_object_get(cur, StringValueCStr(key));
|
|
3198
|
+
if (!json_exists(cur)) {
|
|
3199
|
+
*out = (struct json){0};
|
|
3200
|
+
return false;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
*out = cur;
|
|
3205
|
+
return true;
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
static unsigned int fs_geometry_type_bit(struct json type_value) {
|
|
3209
|
+
if (json_string_compare(type_value, "Point") == 0)
|
|
3210
|
+
return FS_GEOM_POINT;
|
|
3211
|
+
if (json_string_compare(type_value, "LineString") == 0)
|
|
3212
|
+
return FS_GEOM_LINESTRING;
|
|
3213
|
+
if (json_string_compare(type_value, "Polygon") == 0)
|
|
3214
|
+
return FS_GEOM_POLYGON;
|
|
3215
|
+
if (json_string_compare(type_value, "MultiPoint") == 0)
|
|
3216
|
+
return FS_GEOM_MULTIPOINT;
|
|
3217
|
+
if (json_string_compare(type_value, "MultiLineString") == 0)
|
|
3218
|
+
return FS_GEOM_MULTILINESTRING;
|
|
3219
|
+
if (json_string_compare(type_value, "MultiPolygon") == 0)
|
|
3220
|
+
return FS_GEOM_MULTIPOLYGON;
|
|
3221
|
+
if (json_string_compare(type_value, "GeometryCollection") == 0)
|
|
3222
|
+
return FS_GEOM_GEOMETRYCOLLECTION;
|
|
3223
|
+
return 0;
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
static unsigned int fs_symbol_geometry_type_bit(VALUE sym) {
|
|
3227
|
+
ID id;
|
|
3228
|
+
|
|
3229
|
+
if (!SYMBOL_P(sym)) {
|
|
3230
|
+
rb_raise(eTGGeometryArgumentError, "only: must contain geometry type symbols");
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
id = SYM2ID(sym);
|
|
3234
|
+
if (id == id_point)
|
|
3235
|
+
return FS_GEOM_POINT;
|
|
3236
|
+
if (id == id_linestring)
|
|
3237
|
+
return FS_GEOM_LINESTRING;
|
|
3238
|
+
if (id == id_polygon)
|
|
3239
|
+
return FS_GEOM_POLYGON;
|
|
3240
|
+
if (id == id_multipoint)
|
|
3241
|
+
return FS_GEOM_MULTIPOINT;
|
|
3242
|
+
if (id == id_multilinestring)
|
|
3243
|
+
return FS_GEOM_MULTILINESTRING;
|
|
3244
|
+
if (id == id_multipolygon)
|
|
3245
|
+
return FS_GEOM_MULTIPOLYGON;
|
|
3246
|
+
if (id == id_geometrycollection)
|
|
3247
|
+
return FS_GEOM_GEOMETRYCOLLECTION;
|
|
3248
|
+
|
|
3249
|
+
rb_raise(eTGGeometryArgumentError,
|
|
3250
|
+
"only: must contain one of :point, :linestring, :polygon, :multipoint, "
|
|
3251
|
+
":multilinestring, :multipolygon, :geometrycollection");
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
static void fs_parse_only_option(fs_options_t *opts, VALUE value, bool present) {
|
|
3255
|
+
opts->only_all = false;
|
|
3256
|
+
opts->only_mask = FS_GEOM_POLYGON | FS_GEOM_MULTIPOLYGON;
|
|
3257
|
+
|
|
3258
|
+
if (present && NIL_P(value)) {
|
|
3259
|
+
opts->only_all = true;
|
|
3260
|
+
opts->only_mask = 0;
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
if (!present) {
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
if (!RB_TYPE_P(value, T_ARRAY)) {
|
|
3269
|
+
rb_raise(eTGGeometryArgumentError, "only: must be Array<Symbol> or nil");
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
opts->only_mask = 0;
|
|
3273
|
+
for (long i = 0; i < RARRAY_LEN(value); i++) {
|
|
3274
|
+
opts->only_mask |= fs_symbol_geometry_type_bit(rb_ary_entry(value, i));
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
static fs_on_invalid_t fs_parse_on_invalid(VALUE value) {
|
|
3279
|
+
ID id;
|
|
3280
|
+
if (!SYMBOL_P(value)) {
|
|
3281
|
+
rb_raise(eTGGeometryArgumentError, "on_invalid: must be one of :raise, :skip");
|
|
3282
|
+
}
|
|
3283
|
+
id = SYM2ID(value);
|
|
3284
|
+
if (id == id_raise)
|
|
3285
|
+
return FS_ON_INVALID_RAISE;
|
|
3286
|
+
if (id == id_skip)
|
|
3287
|
+
return FS_ON_INVALID_SKIP;
|
|
3288
|
+
rb_raise(eTGGeometryArgumentError, "on_invalid: must be one of :raise, :skip");
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
static fs_on_missing_id_t fs_parse_on_missing_id(VALUE value) {
|
|
3292
|
+
ID id;
|
|
3293
|
+
if (!SYMBOL_P(value)) {
|
|
3294
|
+
rb_raise(eTGGeometryArgumentError, "on_missing_id: must be one of :raise, :skip, :ordinal");
|
|
3295
|
+
}
|
|
3296
|
+
id = SYM2ID(value);
|
|
3297
|
+
if (id == id_raise)
|
|
3298
|
+
return FS_ON_MISSING_ID_RAISE;
|
|
3299
|
+
if (id == id_skip)
|
|
3300
|
+
return FS_ON_MISSING_ID_SKIP;
|
|
3301
|
+
if (id == id_ordinal)
|
|
3302
|
+
return FS_ON_MISSING_ID_ORDINAL;
|
|
3303
|
+
rb_raise(eTGGeometryArgumentError, "on_missing_id: must be one of :raise, :skip, :ordinal");
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
static bool fs_bool_option(VALUE value, const char *name) {
|
|
3307
|
+
if (value == Qtrue)
|
|
3308
|
+
return true;
|
|
3309
|
+
if (value == Qfalse)
|
|
3310
|
+
return false;
|
|
3311
|
+
rb_raise(eTGGeometryArgumentError, "%s must be true or false", name);
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
static bool fs_keyword_allowed(ID key_id, fs_mode_t mode) {
|
|
3315
|
+
if (key_id == id_id || key_id == id_only || key_id == id_on_invalid ||
|
|
3316
|
+
key_id == id_on_missing_id || key_id == id_geometry_index) {
|
|
3317
|
+
return true;
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
if (mode == FS_MODE_BUILD_INDEX) {
|
|
3321
|
+
return key_id == id_strategy || key_id == id_predicate || key_id == id_report;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
return key_id == id_report || key_id == id_max_errors;
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
static int fs_validate_keyword_i(VALUE key, VALUE value, VALUE mode_value) {
|
|
3328
|
+
fs_mode_t mode = (fs_mode_t)NUM2INT(mode_value);
|
|
3329
|
+
(void)value;
|
|
3330
|
+
|
|
3331
|
+
if (!SYMBOL_P(key) || !fs_keyword_allowed(SYM2ID(key), mode)) {
|
|
3332
|
+
VALUE inspected = rb_inspect(key);
|
|
3333
|
+
rb_raise(eTGGeometryArgumentError, "unknown keyword: %s", StringValueCStr(inspected));
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
return ST_CONTINUE;
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
static void fs_validate_keywords(VALUE kwargs, fs_mode_t mode) {
|
|
3340
|
+
if (NIL_P(kwargs))
|
|
3341
|
+
return;
|
|
3342
|
+
if (!RB_TYPE_P(kwargs, T_HASH)) {
|
|
3343
|
+
rb_raise(rb_eTypeError, "keywords must be a Hash");
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
rb_hash_foreach(kwargs, fs_validate_keyword_i, INT2FIX((int)mode));
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
static fs_options_t fs_parse_options(VALUE kwargs, fs_mode_t mode) {
|
|
3350
|
+
fs_options_t opts;
|
|
3351
|
+
VALUE id_value;
|
|
3352
|
+
VALUE only_value;
|
|
3353
|
+
VALUE on_invalid_value;
|
|
3354
|
+
VALUE on_missing_id_value;
|
|
3355
|
+
VALUE report_value;
|
|
3356
|
+
VALUE max_errors_value;
|
|
3357
|
+
VALUE geometry_index_value;
|
|
3358
|
+
VALUE strategy_value;
|
|
3359
|
+
VALUE predicate_value;
|
|
3360
|
+
bool only_present = false;
|
|
3361
|
+
|
|
3362
|
+
memset(&opts, 0, sizeof(opts));
|
|
3363
|
+
fs_validate_keywords(kwargs, mode);
|
|
3364
|
+
|
|
3365
|
+
id_value = fs_kwargs_value(kwargs, id_id, Qnil, NULL);
|
|
3366
|
+
only_value = fs_kwargs_value(kwargs, id_only, Qnil, &only_present);
|
|
3367
|
+
on_invalid_value = fs_kwargs_value(kwargs, id_on_invalid, ID2SYM(id_raise), NULL);
|
|
3368
|
+
on_missing_id_value = fs_kwargs_value(kwargs, id_on_missing_id, ID2SYM(id_raise), NULL);
|
|
3369
|
+
report_value = fs_kwargs_value(kwargs, id_report, Qfalse, NULL);
|
|
3370
|
+
max_errors_value = fs_kwargs_value(kwargs, id_max_errors, INT2NUM(100), NULL);
|
|
3371
|
+
geometry_index_value = fs_kwargs_value(kwargs, id_geometry_index, ID2SYM(id_ystripes), NULL);
|
|
3372
|
+
|
|
3373
|
+
opts.id_path = fs_normalize_id_path(id_value);
|
|
3374
|
+
fs_parse_only_option(&opts, only_value, only_present);
|
|
3375
|
+
opts.on_invalid = fs_parse_on_invalid(on_invalid_value);
|
|
3376
|
+
opts.on_missing_id = fs_parse_on_missing_id(on_missing_id_value);
|
|
3377
|
+
opts.report = fs_bool_option(report_value, "report:");
|
|
3378
|
+
opts.max_errors = NUM2LONG(max_errors_value);
|
|
3379
|
+
if (opts.max_errors < 0) {
|
|
3380
|
+
rb_raise(eTGGeometryArgumentError, "max_errors: must be >= 0");
|
|
3381
|
+
}
|
|
3382
|
+
opts.geometry_index = parse_index_symbol(geometry_index_value);
|
|
3383
|
+
|
|
3384
|
+
if (opts.on_invalid == FS_ON_INVALID_SKIP && !opts.report) {
|
|
3385
|
+
rb_raise(eTGGeometryArgumentError, "on_invalid: :skip requires report: true");
|
|
3386
|
+
}
|
|
3387
|
+
if (opts.on_missing_id == FS_ON_MISSING_ID_SKIP && !opts.report) {
|
|
3388
|
+
rb_raise(eTGGeometryArgumentError, "on_missing_id: :skip requires report: true");
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
if (mode == FS_MODE_BUILD_INDEX) {
|
|
3392
|
+
if (opts.report) {
|
|
3393
|
+
rb_raise(eTGGeometryArgumentError, "build_index_* does not accept report: true");
|
|
3394
|
+
}
|
|
3395
|
+
strategy_value = fs_kwargs_value(kwargs, id_strategy, ID2SYM(id_rtree), NULL);
|
|
3396
|
+
predicate_value = fs_kwargs_value(kwargs, id_predicate, ID2SYM(id_covers), NULL);
|
|
3397
|
+
opts.strategy = parse_index_strategy_symbol(strategy_value);
|
|
3398
|
+
opts.predicate = parse_index_predicate_symbol(predicate_value);
|
|
3399
|
+
} else {
|
|
3400
|
+
opts.strategy = TG_GEOMETRY_INDEX_STRATEGY_RTREE;
|
|
3401
|
+
opts.predicate = TG_GEOMETRY_INDEX_PREDICATE_COVERS;
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
return opts;
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
static VALUE fs_tg_parse_error_message(struct tg_geom *geom) {
|
|
3408
|
+
const char *err;
|
|
3409
|
+
VALUE message;
|
|
3410
|
+
|
|
3411
|
+
if (!geom) {
|
|
3412
|
+
rb_raise(rb_eNoMemError, "TG geometry allocation failed");
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
err = tg_geom_error(geom);
|
|
3416
|
+
if (!err) {
|
|
3417
|
+
return Qnil;
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
message = rb_str_new_cstr(err);
|
|
3421
|
+
tg_geom_free(geom);
|
|
3422
|
+
return message;
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
static bool fs_validate_geometry_or_error(struct json geometry, enum tg_index geometry_index,
|
|
3426
|
+
VALUE *error_message) {
|
|
3427
|
+
struct tg_geom *geom =
|
|
3428
|
+
tg_parse_geojsonn_ix(json_raw(geometry), json_raw_length(geometry), geometry_index);
|
|
3429
|
+
VALUE message = fs_tg_parse_error_message(geom);
|
|
3430
|
+
|
|
3431
|
+
if (!NIL_P(message)) {
|
|
3432
|
+
*error_message = message;
|
|
3433
|
+
return false;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
tg_geom_free(geom);
|
|
3437
|
+
return true;
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
static VALUE fs_missing_id_ordinal(long feature_index) {
|
|
3441
|
+
return rb_sprintf("feature/%ld", feature_index);
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
static bool fs_extract_id(fs_source_t *source, fs_options_t *opts, struct json feature,
|
|
3445
|
+
long feature_index, VALUE *id_out, VALUE *error_message,
|
|
3446
|
+
bool materialize_id) {
|
|
3447
|
+
struct json id_json;
|
|
3448
|
+
|
|
3449
|
+
if (!fs_json_get_path(feature, opts->id_path, &id_json) || json_type(id_json) == JSON_NULL) {
|
|
3450
|
+
switch (opts->on_missing_id) {
|
|
3451
|
+
case FS_ON_MISSING_ID_ORDINAL:
|
|
3452
|
+
*id_out = materialize_id ? fs_missing_id_ordinal(feature_index) : Qnil;
|
|
3453
|
+
return true;
|
|
3454
|
+
case FS_ON_MISSING_ID_SKIP:
|
|
3455
|
+
*error_message = rb_sprintf("missing id at configured path");
|
|
3456
|
+
return false;
|
|
3457
|
+
case FS_ON_MISSING_ID_RAISE:
|
|
3458
|
+
fs_raise_argument_error(feature_index, fs_json_offset(source, feature),
|
|
3459
|
+
"missing id at configured path");
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
if (!fs_id_from_json_value(id_json, id_out, error_message, materialize_id)) {
|
|
3464
|
+
if (opts->on_invalid == FS_ON_INVALID_RAISE) {
|
|
3465
|
+
fs_raise_argument_error(feature_index, fs_json_offset(source, id_json),
|
|
3466
|
+
StringValueCStr(*error_message));
|
|
3467
|
+
}
|
|
3468
|
+
return false;
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
return true;
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
static bool fs_feature_prepare(fs_source_t *source, fs_options_t *opts, fs_mode_t mode,
|
|
3475
|
+
struct json feature, long feature_index, bool materialize_id,
|
|
3476
|
+
fs_feature_result_t *result) {
|
|
3477
|
+
struct json geometry;
|
|
3478
|
+
struct json geometry_type;
|
|
3479
|
+
struct json properties;
|
|
3480
|
+
unsigned int geom_bit;
|
|
3481
|
+
VALUE id = Qnil;
|
|
3482
|
+
VALUE error_message = Qnil;
|
|
3483
|
+
|
|
3484
|
+
memset(result, 0, sizeof(*result));
|
|
3485
|
+
result->feature = feature;
|
|
3486
|
+
result->id = Qnil;
|
|
3487
|
+
result->reason_value = Qnil;
|
|
3488
|
+
|
|
3489
|
+
if (!json_exists(feature) || json_type(feature) != JSON_OBJECT) {
|
|
3490
|
+
result->reason = "feature is not an object";
|
|
3491
|
+
return false;
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
geometry = json_object_get(feature, "geometry");
|
|
3495
|
+
if (!json_exists(geometry) || json_type(geometry) == JSON_NULL) {
|
|
3496
|
+
result->reason = "missing geometry";
|
|
3497
|
+
return false;
|
|
3498
|
+
}
|
|
3499
|
+
if (json_type(geometry) != JSON_OBJECT) {
|
|
3500
|
+
result->reason = "geometry must be an object";
|
|
3501
|
+
return false;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
geometry_type = json_object_get(geometry, "type");
|
|
3505
|
+
if (!json_exists(geometry_type) || json_type(geometry_type) != JSON_STRING) {
|
|
3506
|
+
result->reason = "geometry.type must be a string";
|
|
3507
|
+
return false;
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
geom_bit = fs_geometry_type_bit(geometry_type);
|
|
3511
|
+
if (geom_bit == 0) {
|
|
3512
|
+
result->reason = "unsupported geometry.type";
|
|
3513
|
+
return false;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
if (!opts->only_all && (opts->only_mask & geom_bit) == 0) {
|
|
3517
|
+
result->filtered = true;
|
|
3518
|
+
return true;
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
if (!fs_extract_id(source, opts, feature, feature_index, &id, &error_message, materialize_id)) {
|
|
3522
|
+
result->missing_id_skip = (opts->on_missing_id == FS_ON_MISSING_ID_SKIP);
|
|
3523
|
+
result->reason_value = error_message;
|
|
3524
|
+
return false;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
if (mode == FS_MODE_READ_FEATURES) {
|
|
3528
|
+
properties = json_object_get(feature, "properties");
|
|
3529
|
+
if (!json_exists(properties) || json_type(properties) == JSON_NULL) {
|
|
3530
|
+
/* returned as "null" */
|
|
3531
|
+
} else if (json_type(properties) != JSON_OBJECT) {
|
|
3532
|
+
result->reason = "properties must be an object or null";
|
|
3533
|
+
return false;
|
|
3534
|
+
}
|
|
3535
|
+
result->properties = properties;
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
result->accepted = true;
|
|
3539
|
+
result->id = id;
|
|
3540
|
+
result->geometry = geometry;
|
|
3541
|
+
return true;
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
static void fs_parse_root(fs_source_t *source, struct json *features_out) {
|
|
3545
|
+
struct json_valid valid;
|
|
3546
|
+
struct json root;
|
|
3547
|
+
struct json type;
|
|
3548
|
+
struct json features;
|
|
3549
|
+
|
|
3550
|
+
valid = json_validn_ex(source->data, source->len, 0);
|
|
3551
|
+
if (!valid.valid) {
|
|
3552
|
+
rb_raise(eTGGeometryParseError, "malformed JSON at byte %llu",
|
|
3553
|
+
(unsigned long long)valid.pos);
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
root = json_parsen(source->data, source->len);
|
|
3557
|
+
if (!json_exists(root) || json_type(root) != JSON_OBJECT) {
|
|
3558
|
+
rb_raise(eTGGeometryParseError, "GeoJSON root must be a FeatureCollection object");
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
type = json_object_get(root, "type");
|
|
3562
|
+
if (!json_exists(type) || json_type(type) != JSON_STRING ||
|
|
3563
|
+
json_string_compare(type, "FeatureCollection") != 0) {
|
|
3564
|
+
rb_raise(eTGGeometryParseError, "GeoJSON root type must be FeatureCollection");
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
features = json_object_get(root, "features");
|
|
3568
|
+
if (!json_exists(features) || json_type(features) != JSON_ARRAY) {
|
|
3569
|
+
rb_raise(eTGGeometryParseError, "GeoJSON FeatureCollection features must be an Array");
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
*features_out = features;
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
static void fs_handle_invalid_or_raise(fs_source_t *source, fs_options_t *opts,
|
|
3576
|
+
fs_feature_result_t *feature_result, long feature_index,
|
|
3577
|
+
VALUE errors, long *skipped) {
|
|
3578
|
+
size_t offset =
|
|
3579
|
+
fs_json_offset(source, json_exists(feature_result->geometry) ? feature_result->geometry
|
|
3580
|
+
: feature_result->feature);
|
|
3581
|
+
VALUE reason =
|
|
3582
|
+
!NIL_P(feature_result->reason_value)
|
|
3583
|
+
? feature_result->reason_value
|
|
3584
|
+
: rb_str_new_cstr(feature_result->reason ? feature_result->reason : "invalid feature");
|
|
3585
|
+
|
|
3586
|
+
if (feature_result->missing_id_skip || opts->on_invalid == FS_ON_INVALID_SKIP) {
|
|
3587
|
+
(*skipped)++;
|
|
3588
|
+
fs_report_error(errors, opts->max_errors, feature_index, offset, reason);
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
fs_raise_parse_error_value(feature_index, offset, reason);
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
static VALUE fs_properties_json_string(struct json properties) {
|
|
3596
|
+
if (!json_exists(properties) || json_type(properties) == JSON_NULL) {
|
|
3597
|
+
return fs_cstr_utf8_string("null");
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
return fs_utf8_string(json_raw(properties), json_raw_length(properties));
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
/*
|
|
3604
|
+
* FeatureSource safe bulk executor.
|
|
3605
|
+
*
|
|
3606
|
+
* Heavy source traversal and TG geometry parsing run without GVL. The no-GVL phase stores only
|
|
3607
|
+
* C-owned data and json.c ranges backed by the owned source buffer. Ruby VALUE ids/strings/arrays
|
|
3608
|
+
* and rb_gc_adjust_memory_usage are only created/called again after the GVL is restored.
|
|
3609
|
+
*/
|
|
3610
|
+
typedef enum {
|
|
3611
|
+
FS_SAFE_ERROR_NONE,
|
|
3612
|
+
FS_SAFE_ERROR_NOMEM,
|
|
3613
|
+
FS_SAFE_ERROR_PARSE,
|
|
3614
|
+
FS_SAFE_ERROR_ARGUMENT,
|
|
3615
|
+
FS_SAFE_ERROR_SYSTEM,
|
|
3616
|
+
} fs_safe_error_type_t;
|
|
3617
|
+
|
|
3618
|
+
typedef enum {
|
|
3619
|
+
FS_SAFE_ID_STRING,
|
|
3620
|
+
FS_SAFE_ID_INTEGER,
|
|
3621
|
+
FS_SAFE_ID_ORDINAL,
|
|
3622
|
+
} fs_safe_id_kind_t;
|
|
3623
|
+
|
|
3624
|
+
typedef struct {
|
|
3625
|
+
fs_safe_id_kind_t kind;
|
|
3626
|
+
struct json json_value;
|
|
3627
|
+
long long integer_value;
|
|
3628
|
+
long feature_index;
|
|
3629
|
+
} fs_safe_id_t;
|
|
3630
|
+
|
|
3631
|
+
typedef struct {
|
|
3632
|
+
fs_safe_id_t id;
|
|
3633
|
+
struct json geometry;
|
|
3634
|
+
struct json properties;
|
|
3635
|
+
bool properties_null;
|
|
3636
|
+
} fs_safe_row_t;
|
|
3637
|
+
|
|
3638
|
+
typedef struct {
|
|
3639
|
+
fs_safe_id_t id;
|
|
3640
|
+
struct tg_geom *geom;
|
|
3641
|
+
struct tg_rect bbox;
|
|
3642
|
+
size_t geom_bytes;
|
|
3643
|
+
long ordinal;
|
|
3644
|
+
} fs_safe_native_entry_t;
|
|
3645
|
+
|
|
3646
|
+
typedef struct {
|
|
3647
|
+
long feature_index;
|
|
3648
|
+
size_t byte_offset;
|
|
3649
|
+
char *reason;
|
|
3650
|
+
} fs_safe_report_error_t;
|
|
3651
|
+
|
|
3652
|
+
typedef struct {
|
|
3653
|
+
fs_safe_error_type_t type;
|
|
3654
|
+
int sys_errno;
|
|
3655
|
+
long feature_index;
|
|
3656
|
+
size_t byte_offset;
|
|
3657
|
+
char *message;
|
|
3658
|
+
} fs_safe_error_t;
|
|
3659
|
+
|
|
3660
|
+
typedef struct {
|
|
3661
|
+
char **keys;
|
|
3662
|
+
size_t *lens;
|
|
3663
|
+
long len;
|
|
3664
|
+
bool only_all;
|
|
3665
|
+
unsigned int only_mask;
|
|
3666
|
+
fs_on_invalid_t on_invalid;
|
|
3667
|
+
fs_on_missing_id_t on_missing_id;
|
|
3668
|
+
bool report;
|
|
3669
|
+
long max_errors;
|
|
3670
|
+
enum tg_index geometry_index;
|
|
3671
|
+
enum tg_geometry_index_strategy strategy;
|
|
3672
|
+
enum tg_geometry_index_predicate predicate;
|
|
3673
|
+
} fs_safe_options_t;
|
|
3674
|
+
|
|
3675
|
+
typedef struct {
|
|
3676
|
+
fs_mode_t mode;
|
|
3677
|
+
fs_safe_options_t opts;
|
|
3678
|
+
|
|
3679
|
+
char *path;
|
|
3680
|
+
char *source_data;
|
|
3681
|
+
size_t source_len;
|
|
3682
|
+
bool source_ruby_alloc;
|
|
3683
|
+
|
|
3684
|
+
fs_safe_row_t *rows;
|
|
3685
|
+
long row_len;
|
|
3686
|
+
long row_cap;
|
|
3687
|
+
|
|
3688
|
+
fs_safe_native_entry_t *entries;
|
|
3689
|
+
long entry_len;
|
|
3690
|
+
|
|
3691
|
+
long skipped;
|
|
3692
|
+
long filtered;
|
|
3693
|
+
fs_safe_report_error_t *errors;
|
|
3694
|
+
long errors_len;
|
|
3695
|
+
long errors_cap;
|
|
3696
|
+
|
|
3697
|
+
fs_safe_error_t fatal;
|
|
3698
|
+
} fs_safe_job_t;
|
|
3699
|
+
|
|
3700
|
+
typedef struct {
|
|
3701
|
+
fs_safe_job_t *job;
|
|
3702
|
+
VALUE result;
|
|
3703
|
+
} fs_safe_materialize_args_t;
|
|
3704
|
+
|
|
3705
|
+
static char *fs_safe_strdup_len(const char *ptr, size_t len) {
|
|
3706
|
+
char *copy;
|
|
3707
|
+
|
|
3708
|
+
if (len > SIZE_MAX - 1)
|
|
3709
|
+
return NULL;
|
|
3710
|
+
copy = (char *)malloc(len + 1);
|
|
3711
|
+
if (!copy)
|
|
3712
|
+
return NULL;
|
|
3713
|
+
if (len > 0)
|
|
3714
|
+
memcpy(copy, ptr, len);
|
|
3715
|
+
copy[len] = '\0';
|
|
3716
|
+
return copy;
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
static char *fs_safe_strdup_cstr(const char *ptr) {
|
|
3720
|
+
return fs_safe_strdup_len(ptr ? ptr : "", strlen(ptr ? ptr : ""));
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
static char *fs_safe_format_feature_message(long feature_index, size_t byte_offset,
|
|
3724
|
+
const char *reason) {
|
|
3725
|
+
const char *r = reason ? reason : "invalid feature";
|
|
3726
|
+
int n = snprintf(NULL, 0, "feature %ld at byte %llu: %s", feature_index,
|
|
3727
|
+
(unsigned long long)byte_offset, r);
|
|
3728
|
+
char *buf;
|
|
3729
|
+
|
|
3730
|
+
if (n < 0)
|
|
3731
|
+
return NULL;
|
|
3732
|
+
buf = (char *)malloc((size_t)n + 1);
|
|
3733
|
+
if (!buf)
|
|
3734
|
+
return NULL;
|
|
3735
|
+
snprintf(buf, (size_t)n + 1, "feature %ld at byte %llu: %s", feature_index,
|
|
3736
|
+
(unsigned long long)byte_offset, r);
|
|
3737
|
+
return buf;
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
static void fs_safe_set_error(fs_safe_job_t *job, fs_safe_error_type_t type, const char *message) {
|
|
3741
|
+
if (job->fatal.type != FS_SAFE_ERROR_NONE)
|
|
3742
|
+
return;
|
|
3743
|
+
job->fatal.type = type;
|
|
3744
|
+
job->fatal.message = fs_safe_strdup_cstr(message);
|
|
3745
|
+
if (!job->fatal.message && type != FS_SAFE_ERROR_NOMEM) {
|
|
3746
|
+
job->fatal.type = FS_SAFE_ERROR_NOMEM;
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
static void fs_safe_set_system_error(fs_safe_job_t *job, int err) {
|
|
3751
|
+
if (job->fatal.type != FS_SAFE_ERROR_NONE)
|
|
3752
|
+
return;
|
|
3753
|
+
job->fatal.type = FS_SAFE_ERROR_SYSTEM;
|
|
3754
|
+
job->fatal.sys_errno = err;
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3757
|
+
static void fs_safe_set_feature_error(fs_safe_job_t *job, fs_safe_error_type_t type,
|
|
3758
|
+
long feature_index, size_t byte_offset, const char *reason) {
|
|
3759
|
+
if (job->fatal.type != FS_SAFE_ERROR_NONE)
|
|
3760
|
+
return;
|
|
3761
|
+
job->fatal.type = type;
|
|
3762
|
+
job->fatal.feature_index = feature_index;
|
|
3763
|
+
job->fatal.byte_offset = byte_offset;
|
|
3764
|
+
job->fatal.message = fs_safe_format_feature_message(feature_index, byte_offset, reason);
|
|
3765
|
+
if (!job->fatal.message && type != FS_SAFE_ERROR_NOMEM) {
|
|
3766
|
+
job->fatal.type = FS_SAFE_ERROR_NOMEM;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
static bool fs_safe_add_report_error(fs_safe_job_t *job, long feature_index, size_t byte_offset,
|
|
3771
|
+
const char *reason) {
|
|
3772
|
+
fs_safe_report_error_t *grown;
|
|
3773
|
+
|
|
3774
|
+
if (job->errors_len >= job->opts.max_errors)
|
|
3775
|
+
return true;
|
|
3776
|
+
|
|
3777
|
+
if (job->errors_len == job->errors_cap) {
|
|
3778
|
+
long new_cap = job->errors_cap == 0 ? 8 : job->errors_cap * 2;
|
|
3779
|
+
if (new_cap < job->errors_cap || (size_t)new_cap > SIZE_MAX / sizeof(*job->errors)) {
|
|
3780
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource report allocation overflow");
|
|
3781
|
+
return false;
|
|
3782
|
+
}
|
|
3783
|
+
grown =
|
|
3784
|
+
(fs_safe_report_error_t *)realloc(job->errors, (size_t)new_cap * sizeof(*job->errors));
|
|
3785
|
+
if (!grown) {
|
|
3786
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource report allocation failed");
|
|
3787
|
+
return false;
|
|
3788
|
+
}
|
|
3789
|
+
job->errors = grown;
|
|
3790
|
+
job->errors_cap = new_cap;
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
job->errors[job->errors_len].feature_index = feature_index;
|
|
3794
|
+
job->errors[job->errors_len].byte_offset = byte_offset;
|
|
3795
|
+
job->errors[job->errors_len].reason = fs_safe_strdup_cstr(reason ? reason : "invalid feature");
|
|
3796
|
+
if (!job->errors[job->errors_len].reason) {
|
|
3797
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM,
|
|
3798
|
+
"FeatureSource report reason allocation failed");
|
|
3799
|
+
return false;
|
|
3800
|
+
}
|
|
3801
|
+
job->errors_len++;
|
|
3802
|
+
return true;
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
static size_t fs_safe_json_offset(fs_safe_job_t *job, struct json value) {
|
|
3806
|
+
const char *raw = json_raw(value);
|
|
3807
|
+
|
|
3808
|
+
if (!raw || !job->source_data || raw < job->source_data ||
|
|
3809
|
+
raw > job->source_data + job->source_len)
|
|
3810
|
+
return 0;
|
|
3811
|
+
return (size_t)(raw - job->source_data);
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
static bool fs_safe_json_get_path(struct json root, fs_safe_options_t *opts, struct json *out) {
|
|
3815
|
+
struct json cur = root;
|
|
3816
|
+
|
|
3817
|
+
for (long i = 0; i < opts->len; i++) {
|
|
3818
|
+
if (!json_exists(cur) || json_type(cur) != JSON_OBJECT)
|
|
3819
|
+
return false;
|
|
3820
|
+
cur = json_object_getn(cur, opts->keys[i], opts->lens[i]);
|
|
3821
|
+
if (!json_exists(cur))
|
|
3822
|
+
return false;
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
*out = cur;
|
|
3826
|
+
return true;
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
static unsigned int fs_safe_geometry_type_bit(struct json type_value) {
|
|
3830
|
+
if (!json_exists(type_value) || json_type(type_value) != JSON_STRING)
|
|
3831
|
+
return 0;
|
|
3832
|
+
if (json_string_compare(type_value, "Point") == 0)
|
|
3833
|
+
return FS_GEOM_POINT;
|
|
3834
|
+
if (json_string_compare(type_value, "LineString") == 0)
|
|
3835
|
+
return FS_GEOM_LINESTRING;
|
|
3836
|
+
if (json_string_compare(type_value, "Polygon") == 0)
|
|
3837
|
+
return FS_GEOM_POLYGON;
|
|
3838
|
+
if (json_string_compare(type_value, "MultiPoint") == 0)
|
|
3839
|
+
return FS_GEOM_MULTIPOINT;
|
|
3840
|
+
if (json_string_compare(type_value, "MultiLineString") == 0)
|
|
3841
|
+
return FS_GEOM_MULTILINESTRING;
|
|
3842
|
+
if (json_string_compare(type_value, "MultiPolygon") == 0)
|
|
3843
|
+
return FS_GEOM_MULTIPOLYGON;
|
|
3844
|
+
if (json_string_compare(type_value, "GeometryCollection") == 0)
|
|
3845
|
+
return FS_GEOM_GEOMETRYCOLLECTION;
|
|
3846
|
+
return 0;
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
static bool fs_safe_json_number_is_integer(struct json value) {
|
|
3850
|
+
const char *raw = json_raw(value);
|
|
3851
|
+
size_t len = json_raw_length(value);
|
|
3852
|
+
|
|
3853
|
+
if (!raw || len == 0)
|
|
3854
|
+
return false;
|
|
3855
|
+
for (size_t i = 0; i < len; i++) {
|
|
3856
|
+
if (raw[i] == '.' || raw[i] == 'e' || raw[i] == 'E')
|
|
3857
|
+
return false;
|
|
3858
|
+
}
|
|
3859
|
+
return true;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
static bool fs_safe_parse_integer_id(struct json value, long long *out, const char **reason) {
|
|
3863
|
+
const char *raw = json_raw(value);
|
|
3864
|
+
size_t len = json_raw_length(value);
|
|
3865
|
+
char stack[64];
|
|
3866
|
+
char *buf = stack;
|
|
3867
|
+
char *endp = NULL;
|
|
3868
|
+
long long parsed;
|
|
3869
|
+
bool ok;
|
|
3870
|
+
|
|
3871
|
+
if (!fs_safe_json_number_is_integer(value)) {
|
|
3872
|
+
*reason = "invalid id: numeric id must be an integer";
|
|
3873
|
+
return false;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
if (len >= sizeof(stack)) {
|
|
3877
|
+
buf = fs_safe_strdup_len(raw, len);
|
|
3878
|
+
if (!buf) {
|
|
3879
|
+
*reason = "id buffer allocation failed";
|
|
3880
|
+
return false;
|
|
3881
|
+
}
|
|
3882
|
+
} else {
|
|
3883
|
+
memcpy(buf, raw, len);
|
|
3884
|
+
buf[len] = '\0';
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
errno = 0;
|
|
3888
|
+
parsed = strtoll(buf, &endp, 10);
|
|
3889
|
+
ok = (errno == 0 && endp == buf + len);
|
|
3890
|
+
if (buf != stack)
|
|
3891
|
+
free(buf);
|
|
3892
|
+
|
|
3893
|
+
if (!ok) {
|
|
3894
|
+
*reason = "invalid id: integer is out of range";
|
|
3895
|
+
return false;
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3898
|
+
*out = parsed;
|
|
3899
|
+
return true;
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
static bool fs_safe_extract_id(fs_safe_job_t *job, struct json feature, long feature_index,
|
|
3903
|
+
fs_safe_id_t *id_out, const char **reason, bool *missing_id_skip,
|
|
3904
|
+
bool *argument_error) {
|
|
3905
|
+
struct json id_json;
|
|
3906
|
+
|
|
3907
|
+
*missing_id_skip = false;
|
|
3908
|
+
*argument_error = false;
|
|
3909
|
+
|
|
3910
|
+
if (!fs_safe_json_get_path(feature, &job->opts, &id_json) || json_type(id_json) == JSON_NULL) {
|
|
3911
|
+
switch (job->opts.on_missing_id) {
|
|
3912
|
+
case FS_ON_MISSING_ID_ORDINAL:
|
|
3913
|
+
id_out->kind = FS_SAFE_ID_ORDINAL;
|
|
3914
|
+
id_out->feature_index = feature_index;
|
|
3915
|
+
return true;
|
|
3916
|
+
case FS_ON_MISSING_ID_SKIP:
|
|
3917
|
+
*reason = "missing id at configured path";
|
|
3918
|
+
*missing_id_skip = true;
|
|
3919
|
+
return false;
|
|
3920
|
+
case FS_ON_MISSING_ID_RAISE:
|
|
3921
|
+
*reason = "missing id at configured path";
|
|
3922
|
+
*argument_error = true;
|
|
3923
|
+
return false;
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3927
|
+
switch (json_type(id_json)) {
|
|
3928
|
+
case JSON_STRING:
|
|
3929
|
+
id_out->kind = FS_SAFE_ID_STRING;
|
|
3930
|
+
id_out->json_value = id_json;
|
|
3931
|
+
id_out->feature_index = feature_index;
|
|
3932
|
+
return true;
|
|
3933
|
+
case JSON_NUMBER: {
|
|
3934
|
+
long long parsed = 0;
|
|
3935
|
+
if (!fs_safe_parse_integer_id(id_json, &parsed, reason)) {
|
|
3936
|
+
*argument_error = (job->opts.on_invalid == FS_ON_INVALID_RAISE);
|
|
3937
|
+
return false;
|
|
3938
|
+
}
|
|
3939
|
+
id_out->kind = FS_SAFE_ID_INTEGER;
|
|
3940
|
+
id_out->integer_value = parsed;
|
|
3941
|
+
id_out->feature_index = feature_index;
|
|
3942
|
+
return true;
|
|
3943
|
+
}
|
|
3944
|
+
default:
|
|
3945
|
+
*reason = "invalid id: expected JSON string or integer number";
|
|
3946
|
+
*argument_error = (job->opts.on_invalid == FS_ON_INVALID_RAISE);
|
|
3947
|
+
return false;
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
typedef struct {
|
|
3952
|
+
bool accepted;
|
|
3953
|
+
bool filtered;
|
|
3954
|
+
bool missing_id_skip;
|
|
3955
|
+
bool argument_error;
|
|
3956
|
+
fs_safe_id_t id;
|
|
3957
|
+
struct json feature;
|
|
3958
|
+
struct json geometry;
|
|
3959
|
+
struct json properties;
|
|
3960
|
+
bool properties_null;
|
|
3961
|
+
const char *reason;
|
|
3962
|
+
} fs_safe_feature_t;
|
|
3963
|
+
|
|
3964
|
+
static bool fs_safe_prepare_feature(fs_safe_job_t *job, struct json feature, long feature_index,
|
|
3965
|
+
fs_mode_t mode, fs_safe_feature_t *out) {
|
|
3966
|
+
struct json geometry;
|
|
3967
|
+
struct json geometry_type;
|
|
3968
|
+
struct json properties;
|
|
3969
|
+
unsigned int geom_bit;
|
|
3970
|
+
|
|
3971
|
+
memset(out, 0, sizeof(*out));
|
|
3972
|
+
out->feature = feature;
|
|
3973
|
+
|
|
3974
|
+
if (!json_exists(feature) || json_type(feature) != JSON_OBJECT) {
|
|
3975
|
+
out->reason = "feature is not an object";
|
|
3976
|
+
return false;
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
geometry = json_object_get(feature, "geometry");
|
|
3980
|
+
if (!json_exists(geometry) || json_type(geometry) == JSON_NULL) {
|
|
3981
|
+
out->reason = "missing geometry";
|
|
3982
|
+
return false;
|
|
3983
|
+
}
|
|
3984
|
+
if (json_type(geometry) != JSON_OBJECT) {
|
|
3985
|
+
out->reason = "geometry must be an object";
|
|
3986
|
+
return false;
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
geometry_type = json_object_get(geometry, "type");
|
|
3990
|
+
if (!json_exists(geometry_type) || json_type(geometry_type) != JSON_STRING) {
|
|
3991
|
+
out->reason = "geometry.type must be a string";
|
|
3992
|
+
return false;
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
geom_bit = fs_safe_geometry_type_bit(geometry_type);
|
|
3996
|
+
if (geom_bit == 0) {
|
|
3997
|
+
out->reason = "unsupported geometry.type";
|
|
3998
|
+
return false;
|
|
3999
|
+
}
|
|
4000
|
+
|
|
4001
|
+
if (!job->opts.only_all && (job->opts.only_mask & geom_bit) == 0) {
|
|
4002
|
+
out->filtered = true;
|
|
4003
|
+
return true;
|
|
4004
|
+
}
|
|
4005
|
+
|
|
4006
|
+
if (!fs_safe_extract_id(job, feature, feature_index, &out->id, &out->reason,
|
|
4007
|
+
&out->missing_id_skip, &out->argument_error)) {
|
|
4008
|
+
return false;
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
if (mode == FS_MODE_READ_FEATURES) {
|
|
4012
|
+
properties = json_object_get(feature, "properties");
|
|
4013
|
+
if (!json_exists(properties) || json_type(properties) == JSON_NULL) {
|
|
4014
|
+
out->properties_null = true;
|
|
4015
|
+
} else if (json_type(properties) != JSON_OBJECT) {
|
|
4016
|
+
out->reason = "properties must be an object or null";
|
|
4017
|
+
return false;
|
|
4018
|
+
} else {
|
|
4019
|
+
out->properties = properties;
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
out->accepted = true;
|
|
4024
|
+
out->geometry = geometry;
|
|
4025
|
+
return true;
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
static bool fs_safe_handle_invalid(fs_safe_job_t *job, fs_safe_feature_t *feature,
|
|
4029
|
+
long feature_index) {
|
|
4030
|
+
size_t offset = fs_safe_json_offset(job, json_exists(feature->geometry) ? feature->geometry
|
|
4031
|
+
: feature->feature);
|
|
4032
|
+
const char *reason = feature->reason ? feature->reason : "invalid feature";
|
|
4033
|
+
|
|
4034
|
+
if (feature->missing_id_skip ||
|
|
4035
|
+
(!feature->argument_error && job->opts.on_invalid == FS_ON_INVALID_SKIP)) {
|
|
4036
|
+
job->skipped++;
|
|
4037
|
+
if (!fs_safe_add_report_error(job, feature_index, offset, reason))
|
|
4038
|
+
return false;
|
|
4039
|
+
return true;
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
fs_safe_set_feature_error(
|
|
4043
|
+
job, feature->argument_error ? FS_SAFE_ERROR_ARGUMENT : FS_SAFE_ERROR_PARSE, feature_index,
|
|
4044
|
+
offset, reason);
|
|
4045
|
+
return false;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
static bool fs_safe_parse_root(fs_safe_job_t *job, struct json *features_out) {
|
|
4049
|
+
struct json_valid valid;
|
|
4050
|
+
struct json root;
|
|
4051
|
+
struct json type;
|
|
4052
|
+
struct json features;
|
|
4053
|
+
|
|
4054
|
+
valid = json_validn_ex(job->source_data, job->source_len, 0);
|
|
4055
|
+
if (!valid.valid) {
|
|
4056
|
+
char msg[128];
|
|
4057
|
+
snprintf(msg, sizeof(msg), "malformed JSON at byte %llu", (unsigned long long)valid.pos);
|
|
4058
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_PARSE, msg);
|
|
4059
|
+
return false;
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
root = json_parsen(job->source_data, job->source_len);
|
|
4063
|
+
if (!json_exists(root) || json_type(root) != JSON_OBJECT) {
|
|
4064
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_PARSE,
|
|
4065
|
+
"GeoJSON root must be a FeatureCollection object");
|
|
4066
|
+
return false;
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
type = json_object_get(root, "type");
|
|
4070
|
+
if (!json_exists(type) || json_type(type) != JSON_STRING ||
|
|
4071
|
+
json_string_compare(type, "FeatureCollection") != 0) {
|
|
4072
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_PARSE, "GeoJSON root type must be FeatureCollection");
|
|
4073
|
+
return false;
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
features = json_object_get(root, "features");
|
|
4077
|
+
if (!json_exists(features) || json_type(features) != JSON_ARRAY) {
|
|
4078
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_PARSE,
|
|
4079
|
+
"GeoJSON FeatureCollection features must be an Array");
|
|
4080
|
+
return false;
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
*features_out = features;
|
|
4084
|
+
return true;
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
static char *fs_safe_tg_error_message(struct tg_geom *geom) {
|
|
4088
|
+
const char *err;
|
|
4089
|
+
char *msg;
|
|
4090
|
+
int n;
|
|
4091
|
+
|
|
4092
|
+
if (!geom)
|
|
4093
|
+
return fs_safe_strdup_cstr("TG geometry allocation failed");
|
|
4094
|
+
err = tg_geom_error(geom);
|
|
4095
|
+
if (!err)
|
|
4096
|
+
return NULL;
|
|
4097
|
+
n = snprintf(NULL, 0, "invalid geometry: %s", err);
|
|
4098
|
+
if (n < 0)
|
|
4099
|
+
return NULL;
|
|
4100
|
+
msg = (char *)malloc((size_t)n + 1);
|
|
4101
|
+
if (!msg)
|
|
4102
|
+
return NULL;
|
|
4103
|
+
snprintf(msg, (size_t)n + 1, "invalid geometry: %s", err);
|
|
4104
|
+
return msg;
|
|
4105
|
+
}
|
|
4106
|
+
|
|
4107
|
+
static bool fs_safe_validate_geometry(fs_safe_job_t *job, struct json geometry, long feature_index,
|
|
4108
|
+
bool *skipped_out) {
|
|
4109
|
+
*skipped_out = false;
|
|
4110
|
+
struct tg_geom *geom = tg_parse_geojsonn_ix(json_raw(geometry), json_raw_length(geometry),
|
|
4111
|
+
job->opts.geometry_index);
|
|
4112
|
+
char *message = fs_safe_tg_error_message(geom);
|
|
4113
|
+
|
|
4114
|
+
if (!geom) {
|
|
4115
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "TG geometry allocation failed");
|
|
4116
|
+
return false;
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
if (message) {
|
|
4120
|
+
if (job->opts.on_invalid == FS_ON_INVALID_SKIP) {
|
|
4121
|
+
job->skipped++;
|
|
4122
|
+
bool ok = fs_safe_add_report_error(job, feature_index,
|
|
4123
|
+
fs_safe_json_offset(job, geometry), message);
|
|
4124
|
+
free(message);
|
|
4125
|
+
tg_geom_free(geom);
|
|
4126
|
+
*skipped_out = true;
|
|
4127
|
+
return ok;
|
|
4128
|
+
}
|
|
4129
|
+
fs_safe_set_feature_error(job, FS_SAFE_ERROR_PARSE, feature_index,
|
|
4130
|
+
fs_safe_json_offset(job, geometry), message);
|
|
4131
|
+
free(message);
|
|
4132
|
+
tg_geom_free(geom);
|
|
4133
|
+
return false;
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
tg_geom_free(geom);
|
|
4137
|
+
return true;
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
static bool fs_safe_append_row(fs_safe_job_t *job, fs_safe_feature_t *feature) {
|
|
4141
|
+
fs_safe_row_t *grown;
|
|
4142
|
+
long new_cap;
|
|
4143
|
+
|
|
4144
|
+
if (job->row_len == job->row_cap) {
|
|
4145
|
+
new_cap = job->row_cap == 0 ? 64 : job->row_cap * 2;
|
|
4146
|
+
if (new_cap < job->row_cap || (size_t)new_cap > SIZE_MAX / sizeof(*job->rows)) {
|
|
4147
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource rows allocation overflow");
|
|
4148
|
+
return false;
|
|
4149
|
+
}
|
|
4150
|
+
grown = (fs_safe_row_t *)realloc(job->rows, (size_t)new_cap * sizeof(*job->rows));
|
|
4151
|
+
if (!grown) {
|
|
4152
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource rows allocation failed");
|
|
4153
|
+
return false;
|
|
4154
|
+
}
|
|
4155
|
+
job->rows = grown;
|
|
4156
|
+
job->row_cap = new_cap;
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
memset(&job->rows[job->row_len], 0, sizeof(job->rows[job->row_len]));
|
|
4160
|
+
job->rows[job->row_len].id = feature->id;
|
|
4161
|
+
job->rows[job->row_len].geometry = feature->geometry;
|
|
4162
|
+
job->rows[job->row_len].properties = feature->properties;
|
|
4163
|
+
job->rows[job->row_len].properties_null = feature->properties_null;
|
|
4164
|
+
job->row_len++;
|
|
4165
|
+
return true;
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
static bool fs_safe_run_read(fs_safe_job_t *job, struct json features) {
|
|
4169
|
+
struct json feature;
|
|
4170
|
+
long feature_index = 0;
|
|
4171
|
+
|
|
4172
|
+
for (feature = json_first(features); json_exists(feature);
|
|
4173
|
+
feature = json_next(feature), feature_index++) {
|
|
4174
|
+
fs_safe_feature_t prepared;
|
|
4175
|
+
|
|
4176
|
+
if (!fs_safe_prepare_feature(job, feature, feature_index, job->mode, &prepared)) {
|
|
4177
|
+
if (!fs_safe_handle_invalid(job, &prepared, feature_index))
|
|
4178
|
+
return false;
|
|
4179
|
+
continue;
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
if (prepared.filtered) {
|
|
4183
|
+
job->filtered++;
|
|
4184
|
+
continue;
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
{
|
|
4188
|
+
bool geometry_skipped = false;
|
|
4189
|
+
if (!fs_safe_validate_geometry(job, prepared.geometry, feature_index,
|
|
4190
|
+
&geometry_skipped))
|
|
4191
|
+
return false;
|
|
4192
|
+
if (geometry_skipped)
|
|
4193
|
+
continue;
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
if (!fs_safe_append_row(job, &prepared))
|
|
4197
|
+
return false;
|
|
4198
|
+
}
|
|
4199
|
+
|
|
4200
|
+
return true;
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
static bool fs_safe_count_build(fs_safe_job_t *job, struct json features, long *accepted_out) {
|
|
4204
|
+
struct json feature;
|
|
4205
|
+
long feature_index = 0;
|
|
4206
|
+
long accepted = 0;
|
|
4207
|
+
|
|
4208
|
+
for (feature = json_first(features); json_exists(feature);
|
|
4209
|
+
feature = json_next(feature), feature_index++) {
|
|
4210
|
+
fs_safe_feature_t prepared;
|
|
4211
|
+
|
|
4212
|
+
if (!fs_safe_prepare_feature(job, feature, feature_index, FS_MODE_BUILD_INDEX, &prepared)) {
|
|
4213
|
+
if (!fs_safe_handle_invalid(job, &prepared, feature_index))
|
|
4214
|
+
return false;
|
|
4215
|
+
continue;
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
if (prepared.filtered) {
|
|
4219
|
+
job->filtered++;
|
|
4220
|
+
continue;
|
|
4221
|
+
}
|
|
4222
|
+
if (prepared.accepted)
|
|
4223
|
+
accepted++;
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
*accepted_out = accepted;
|
|
4227
|
+
return true;
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
static bool fs_safe_fill_build(fs_safe_job_t *job, struct json features) {
|
|
4231
|
+
struct json feature;
|
|
4232
|
+
long feature_index = 0;
|
|
4233
|
+
long ordinal = 0;
|
|
4234
|
+
|
|
4235
|
+
for (feature = json_first(features); json_exists(feature);
|
|
4236
|
+
feature = json_next(feature), feature_index++) {
|
|
4237
|
+
fs_safe_feature_t prepared;
|
|
4238
|
+
struct tg_geom *geom;
|
|
4239
|
+
char *message;
|
|
4240
|
+
|
|
4241
|
+
if (!fs_safe_prepare_feature(job, feature, feature_index, FS_MODE_BUILD_INDEX, &prepared)) {
|
|
4242
|
+
if (!fs_safe_handle_invalid(job, &prepared, feature_index))
|
|
4243
|
+
return false;
|
|
4244
|
+
continue;
|
|
4245
|
+
}
|
|
4246
|
+
if (prepared.filtered || !prepared.accepted)
|
|
4247
|
+
continue;
|
|
4248
|
+
|
|
4249
|
+
geom = tg_parse_geojsonn_ix(json_raw(prepared.geometry), json_raw_length(prepared.geometry),
|
|
4250
|
+
job->opts.geometry_index);
|
|
4251
|
+
message = fs_safe_tg_error_message(geom);
|
|
4252
|
+
if (!geom) {
|
|
4253
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "TG geometry allocation failed");
|
|
4254
|
+
return false;
|
|
4255
|
+
}
|
|
4256
|
+
if (message) {
|
|
4257
|
+
fs_safe_set_feature_error(job, FS_SAFE_ERROR_PARSE, feature_index,
|
|
4258
|
+
fs_safe_json_offset(job, prepared.geometry), message);
|
|
4259
|
+
free(message);
|
|
4260
|
+
tg_geom_free(geom);
|
|
4261
|
+
return false;
|
|
4262
|
+
}
|
|
4263
|
+
|
|
4264
|
+
job->entries[ordinal].id = prepared.id;
|
|
4265
|
+
job->entries[ordinal].geom = geom;
|
|
4266
|
+
job->entries[ordinal].bbox = tg_geom_rect(geom);
|
|
4267
|
+
job->entries[ordinal].geom_bytes = tg_geom_memsize(geom);
|
|
4268
|
+
job->entries[ordinal].ordinal = ordinal;
|
|
4269
|
+
ordinal++;
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
job->entry_len = ordinal;
|
|
4273
|
+
return true;
|
|
4274
|
+
}
|
|
4275
|
+
|
|
4276
|
+
static bool fs_safe_read_file_no_gvl(fs_safe_job_t *job) {
|
|
4277
|
+
int fd;
|
|
4278
|
+
struct stat st;
|
|
4279
|
+
char *buf = NULL;
|
|
4280
|
+
size_t cap = 0;
|
|
4281
|
+
size_t len = 0;
|
|
4282
|
+
|
|
4283
|
+
fd = open(job->path, O_RDONLY);
|
|
4284
|
+
if (fd < 0) {
|
|
4285
|
+
fs_safe_set_system_error(job, errno);
|
|
4286
|
+
return false;
|
|
4287
|
+
}
|
|
4288
|
+
|
|
4289
|
+
if (fstat(fd, &st) == 0 && st.st_size >= 0) {
|
|
4290
|
+
cap = (size_t)st.st_size;
|
|
4291
|
+
if (cap > SIZE_MAX - 1) {
|
|
4292
|
+
close(fd);
|
|
4293
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource file is too large");
|
|
4294
|
+
return false;
|
|
4295
|
+
}
|
|
4296
|
+
buf = (char *)malloc(cap + 1);
|
|
4297
|
+
if (!buf) {
|
|
4298
|
+
close(fd);
|
|
4299
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource file allocation failed");
|
|
4300
|
+
return false;
|
|
4301
|
+
}
|
|
4302
|
+
} else {
|
|
4303
|
+
cap = 8192;
|
|
4304
|
+
buf = (char *)malloc(cap + 1);
|
|
4305
|
+
if (!buf) {
|
|
4306
|
+
close(fd);
|
|
4307
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource file allocation failed");
|
|
4308
|
+
return false;
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
for (;;) {
|
|
4313
|
+
ssize_t n;
|
|
4314
|
+
if (len == cap) {
|
|
4315
|
+
size_t new_cap = cap < 8192 ? 8192 : cap * 2;
|
|
4316
|
+
char *grown;
|
|
4317
|
+
if (new_cap < cap || new_cap > SIZE_MAX - 1) {
|
|
4318
|
+
free(buf);
|
|
4319
|
+
close(fd);
|
|
4320
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM,
|
|
4321
|
+
"FeatureSource file allocation overflow");
|
|
4322
|
+
return false;
|
|
4323
|
+
}
|
|
4324
|
+
grown = (char *)realloc(buf, new_cap + 1);
|
|
4325
|
+
if (!grown) {
|
|
4326
|
+
free(buf);
|
|
4327
|
+
close(fd);
|
|
4328
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "FeatureSource file allocation failed");
|
|
4329
|
+
return false;
|
|
4330
|
+
}
|
|
4331
|
+
buf = grown;
|
|
4332
|
+
cap = new_cap;
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4335
|
+
n = read(fd, buf + len, cap - len);
|
|
4336
|
+
if (n < 0) {
|
|
4337
|
+
if (errno == EINTR)
|
|
4338
|
+
continue;
|
|
4339
|
+
int err = errno;
|
|
4340
|
+
free(buf);
|
|
4341
|
+
close(fd);
|
|
4342
|
+
fs_safe_set_system_error(job, err);
|
|
4343
|
+
return false;
|
|
4344
|
+
}
|
|
4345
|
+
if (n == 0)
|
|
4346
|
+
break;
|
|
4347
|
+
len += (size_t)n;
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
if (close(fd) != 0) {
|
|
4351
|
+
int err = errno;
|
|
4352
|
+
free(buf);
|
|
4353
|
+
fs_safe_set_system_error(job, err);
|
|
4354
|
+
return false;
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
buf[len] = '\0';
|
|
4358
|
+
job->source_data = buf;
|
|
4359
|
+
job->source_len = len;
|
|
4360
|
+
job->source_ruby_alloc = false;
|
|
4361
|
+
return true;
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4364
|
+
static void *fs_safe_run_no_gvl(void *ptr) {
|
|
4365
|
+
fs_safe_job_t *job = (fs_safe_job_t *)ptr;
|
|
4366
|
+
struct json features;
|
|
4367
|
+
|
|
4368
|
+
if (job->path && !job->source_data) {
|
|
4369
|
+
if (!fs_safe_read_file_no_gvl(job))
|
|
4370
|
+
return NULL;
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
if (!fs_safe_parse_root(job, &features))
|
|
4374
|
+
return NULL;
|
|
4375
|
+
|
|
4376
|
+
if (job->mode == FS_MODE_BUILD_INDEX) {
|
|
4377
|
+
long accepted = 0;
|
|
4378
|
+
if (!fs_safe_count_build(job, features, &accepted))
|
|
4379
|
+
return NULL;
|
|
4380
|
+
if (accepted > 0) {
|
|
4381
|
+
if ((size_t)accepted > SIZE_MAX / sizeof(*job->entries)) {
|
|
4382
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM,
|
|
4383
|
+
"FeatureSource entries allocation overflow");
|
|
4384
|
+
return NULL;
|
|
4385
|
+
}
|
|
4386
|
+
job->entries =
|
|
4387
|
+
(fs_safe_native_entry_t *)calloc((size_t)accepted, sizeof(*job->entries));
|
|
4388
|
+
if (!job->entries) {
|
|
4389
|
+
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM,
|
|
4390
|
+
"FeatureSource entries allocation failed");
|
|
4391
|
+
return NULL;
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
if (!fs_safe_fill_build(job, features))
|
|
4395
|
+
return NULL;
|
|
4396
|
+
} else {
|
|
4397
|
+
if (!fs_safe_run_read(job, features))
|
|
4398
|
+
return NULL;
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
return NULL;
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
static void fs_safe_run_without_gvl(fs_safe_job_t *job) {
|
|
4405
|
+
#if defined(HAVE_RB_NOGVL_OFFLOAD_SAFE)
|
|
4406
|
+
rb_nogvl(fs_safe_run_no_gvl, job, RUBY_UBF_IO, NULL, RB_NOGVL_OFFLOAD_SAFE);
|
|
4407
|
+
#else
|
|
4408
|
+
rb_thread_call_without_gvl(fs_safe_run_no_gvl, job, RUBY_UBF_IO, NULL);
|
|
4409
|
+
#endif
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
static void fs_safe_free_c_options(fs_safe_options_t *opts) {
|
|
4413
|
+
if (!opts)
|
|
4414
|
+
return;
|
|
4415
|
+
if (opts->keys) {
|
|
4416
|
+
for (long i = 0; i < opts->len; i++) {
|
|
4417
|
+
free(opts->keys[i]);
|
|
4418
|
+
}
|
|
4419
|
+
free(opts->keys);
|
|
4420
|
+
opts->keys = NULL;
|
|
4421
|
+
}
|
|
4422
|
+
free(opts->lens);
|
|
4423
|
+
opts->lens = NULL;
|
|
4424
|
+
opts->len = 0;
|
|
4425
|
+
}
|
|
4426
|
+
|
|
4427
|
+
static void fs_safe_cleanup(fs_safe_job_t *job) {
|
|
4428
|
+
if (!job)
|
|
4429
|
+
return;
|
|
4430
|
+
|
|
4431
|
+
if (job->entries) {
|
|
4432
|
+
for (long i = 0; i < job->entry_len; i++) {
|
|
4433
|
+
if (job->entries[i].geom) {
|
|
4434
|
+
tg_geom_free(job->entries[i].geom);
|
|
4435
|
+
job->entries[i].geom = NULL;
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
free(job->entries);
|
|
4439
|
+
job->entries = NULL;
|
|
4440
|
+
job->entry_len = 0;
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
free(job->rows);
|
|
4444
|
+
job->rows = NULL;
|
|
4445
|
+
job->row_len = 0;
|
|
4446
|
+
job->row_cap = 0;
|
|
4447
|
+
|
|
4448
|
+
if (job->errors) {
|
|
4449
|
+
for (long i = 0; i < job->errors_len; i++) {
|
|
4450
|
+
free(job->errors[i].reason);
|
|
4451
|
+
}
|
|
4452
|
+
free(job->errors);
|
|
4453
|
+
job->errors = NULL;
|
|
4454
|
+
job->errors_len = 0;
|
|
4455
|
+
job->errors_cap = 0;
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4458
|
+
if (job->source_data) {
|
|
4459
|
+
if (job->source_ruby_alloc)
|
|
4460
|
+
ruby_xfree(job->source_data);
|
|
4461
|
+
else
|
|
4462
|
+
free(job->source_data);
|
|
4463
|
+
job->source_data = NULL;
|
|
4464
|
+
job->source_len = 0;
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
free(job->path);
|
|
4468
|
+
job->path = NULL;
|
|
4469
|
+
free(job->fatal.message);
|
|
4470
|
+
job->fatal.message = NULL;
|
|
4471
|
+
fs_safe_free_c_options(&job->opts);
|
|
4472
|
+
}
|
|
4473
|
+
|
|
4474
|
+
static VALUE fs_safe_job_ensure(VALUE arg) {
|
|
4475
|
+
fs_safe_job_t *job = (fs_safe_job_t *)arg;
|
|
4476
|
+
fs_safe_cleanup(job);
|
|
4477
|
+
return Qnil;
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
static VALUE fs_safe_id_to_value(fs_safe_id_t *id) {
|
|
4481
|
+
switch (id->kind) {
|
|
4482
|
+
case FS_SAFE_ID_STRING:
|
|
4483
|
+
return fs_copy_json_string_value(id->json_value);
|
|
4484
|
+
case FS_SAFE_ID_INTEGER:
|
|
4485
|
+
return LL2NUM(id->integer_value);
|
|
4486
|
+
case FS_SAFE_ID_ORDINAL:
|
|
4487
|
+
return fs_missing_id_ordinal(id->feature_index);
|
|
4488
|
+
}
|
|
4489
|
+
return Qnil;
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
static VALUE fs_safe_properties_to_value(fs_safe_row_t *row) {
|
|
4493
|
+
if (row->properties_null || !json_exists(row->properties))
|
|
4494
|
+
return fs_cstr_utf8_string("null");
|
|
4495
|
+
return fs_utf8_string(json_raw(row->properties), json_raw_length(row->properties));
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
static void fs_safe_raise_fatal(fs_safe_job_t *job) {
|
|
4499
|
+
switch (job->fatal.type) {
|
|
4500
|
+
case FS_SAFE_ERROR_NONE:
|
|
4501
|
+
return;
|
|
4502
|
+
case FS_SAFE_ERROR_NOMEM:
|
|
4503
|
+
rb_raise(rb_eNoMemError, "%s",
|
|
4504
|
+
job->fatal.message ? job->fatal.message : "FeatureSource allocation failed");
|
|
4505
|
+
case FS_SAFE_ERROR_PARSE:
|
|
4506
|
+
rb_raise(eTGGeometryParseError, "%s",
|
|
4507
|
+
job->fatal.message ? job->fatal.message : "FeatureSource parse error");
|
|
4508
|
+
case FS_SAFE_ERROR_ARGUMENT:
|
|
4509
|
+
rb_raise(eTGGeometryArgumentError, "%s",
|
|
4510
|
+
job->fatal.message ? job->fatal.message : "FeatureSource argument error");
|
|
4511
|
+
case FS_SAFE_ERROR_SYSTEM:
|
|
4512
|
+
rb_syserr_fail(job->fatal.sys_errno, job->path ? job->path : "FeatureSource file read");
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
static VALUE fs_safe_materialize_rows(fs_safe_job_t *job) {
|
|
4517
|
+
VALUE rows = rb_ary_new_capa(job->row_len);
|
|
4518
|
+
|
|
4519
|
+
for (long i = 0; i < job->row_len; i++) {
|
|
4520
|
+
VALUE id = fs_safe_id_to_value(&job->rows[i].id);
|
|
4521
|
+
VALUE geom_json =
|
|
4522
|
+
fs_utf8_string(json_raw(job->rows[i].geometry), json_raw_length(job->rows[i].geometry));
|
|
4523
|
+
if (job->mode == FS_MODE_READ_ENTRIES) {
|
|
4524
|
+
rb_ary_push(rows, rb_ary_new_from_args(2, id, geom_json));
|
|
4525
|
+
} else {
|
|
4526
|
+
VALUE props_json = fs_safe_properties_to_value(&job->rows[i]);
|
|
4527
|
+
rb_ary_push(rows, rb_ary_new_from_args(3, id, geom_json, props_json));
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
|
|
4531
|
+
if (job->opts.report) {
|
|
4532
|
+
VALUE report = rb_hash_new();
|
|
4533
|
+
VALUE errors = rb_ary_new_capa(job->errors_len);
|
|
4534
|
+
for (long i = 0; i < job->errors_len; i++) {
|
|
4535
|
+
VALUE reason = fs_cstr_utf8_string(job->errors[i].reason);
|
|
4536
|
+
rb_ary_push(errors, fs_error_hash(job->errors[i].feature_index,
|
|
4537
|
+
job->errors[i].byte_offset, reason));
|
|
4538
|
+
}
|
|
4539
|
+
rb_hash_aset(report,
|
|
4540
|
+
job->mode == FS_MODE_READ_ENTRIES ? fs_sym("entries") : fs_sym("features"),
|
|
4541
|
+
rows);
|
|
4542
|
+
rb_hash_aset(report, fs_sym("skipped"), LONG2NUM(job->skipped));
|
|
4543
|
+
rb_hash_aset(report, fs_sym("filtered"), LONG2NUM(job->filtered));
|
|
4544
|
+
rb_hash_aset(report, fs_sym("errors"), errors);
|
|
4545
|
+
RB_GC_GUARD(rows);
|
|
4546
|
+
RB_GC_GUARD(errors);
|
|
4547
|
+
RB_GC_GUARD(report);
|
|
4548
|
+
return report;
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4551
|
+
RB_GC_GUARD(rows);
|
|
4552
|
+
return rows;
|
|
4553
|
+
}
|
|
4554
|
+
|
|
4555
|
+
typedef struct {
|
|
4556
|
+
fs_safe_job_t *job;
|
|
4557
|
+
tg_index_t *idx;
|
|
4558
|
+
} fs_safe_index_finalize_args_t;
|
|
4559
|
+
|
|
4560
|
+
static VALUE fs_safe_index_finalize_body(VALUE arg) {
|
|
4561
|
+
fs_safe_index_finalize_args_t *a = (fs_safe_index_finalize_args_t *)arg;
|
|
4562
|
+
fs_safe_job_t *job = a->job;
|
|
4563
|
+
tg_index_t *idx = a->idx;
|
|
4564
|
+
|
|
4565
|
+
if (job->entry_len > 0) {
|
|
4566
|
+
if ((size_t)job->entry_len > SIZE_MAX / sizeof(tg_index_entry_t)) {
|
|
4567
|
+
rb_raise(rb_eNoMemError, "entries allocation size overflow");
|
|
4568
|
+
}
|
|
4569
|
+
idx->entries = calloc((size_t)job->entry_len, sizeof(tg_index_entry_t));
|
|
4570
|
+
if (!idx->entries) {
|
|
4571
|
+
rb_raise(rb_eNoMemError, "entries allocation failed");
|
|
4572
|
+
}
|
|
4573
|
+
idx->entries_bytes = (size_t)job->entry_len * sizeof(tg_index_entry_t);
|
|
4574
|
+
rb_gc_adjust_memory_usage((ssize_t)idx->entries_bytes);
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
for (long i = 0; i < job->entry_len; i++) {
|
|
4578
|
+
tg_index_entry_t entry;
|
|
4579
|
+
memset(&entry, 0, sizeof(entry));
|
|
4580
|
+
entry.id = fs_safe_id_to_value(&job->entries[i].id);
|
|
4581
|
+
entry.geom_owner = Qnil;
|
|
4582
|
+
entry.geom = job->entries[i].geom;
|
|
4583
|
+
entry.bbox = job->entries[i].bbox;
|
|
4584
|
+
entry.geom_bytes = job->entries[i].geom_bytes;
|
|
4585
|
+
entry.ordinal = i;
|
|
4586
|
+
entry.owned = true;
|
|
4587
|
+
|
|
4588
|
+
job->entries[i].geom = NULL; /* ownership moves to idx */
|
|
4589
|
+
idx->entries[i] = entry;
|
|
4590
|
+
idx->initialized++;
|
|
4591
|
+
idx->owned_geom_bytes_total += entry.geom_bytes;
|
|
4592
|
+
if (entry.geom_bytes > 0)
|
|
4593
|
+
rb_gc_adjust_memory_usage((ssize_t)entry.geom_bytes);
|
|
4594
|
+
index_expand_bbox(idx, entry.bbox);
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
if (idx->strategy == TG_GEOMETRY_INDEX_STRATEGY_RTREE) {
|
|
4598
|
+
index_build_rtree(idx);
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
return Qnil;
|
|
4602
|
+
}
|
|
4603
|
+
|
|
4604
|
+
static VALUE fs_safe_materialize_index(fs_safe_job_t *job) {
|
|
4605
|
+
tg_index_t *idx;
|
|
4606
|
+
VALUE wrapper;
|
|
4607
|
+
fs_safe_index_finalize_args_t args;
|
|
4608
|
+
int state = 0;
|
|
4609
|
+
|
|
4610
|
+
wrapper = TypedData_Make_Struct(cTGGeometryIndex, tg_index_t, &tg_index_type, idx);
|
|
4611
|
+
idx->len = job->entry_len;
|
|
4612
|
+
idx->capacity = job->entry_len;
|
|
4613
|
+
idx->initialized = 0;
|
|
4614
|
+
idx->strategy = job->opts.strategy;
|
|
4615
|
+
idx->predicate = job->opts.predicate;
|
|
4616
|
+
idx->rtree = NULL;
|
|
4617
|
+
idx->frozen = false;
|
|
4618
|
+
idx->has_bbox = false;
|
|
4619
|
+
|
|
4620
|
+
args.job = job;
|
|
4621
|
+
args.idx = idx;
|
|
4622
|
+
rb_protect(fs_safe_index_finalize_body, (VALUE)&args, &state);
|
|
4623
|
+
if (state) {
|
|
4624
|
+
index_dispose(idx);
|
|
4625
|
+
RB_GC_GUARD(wrapper);
|
|
4626
|
+
rb_jump_tag(state);
|
|
4627
|
+
}
|
|
4628
|
+
|
|
4629
|
+
idx->frozen = true;
|
|
4630
|
+
rb_obj_freeze(wrapper);
|
|
4631
|
+
RB_GC_GUARD(wrapper);
|
|
4632
|
+
return wrapper;
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4635
|
+
static VALUE fs_safe_materialize(VALUE arg) {
|
|
4636
|
+
fs_safe_materialize_args_t *a = (fs_safe_materialize_args_t *)arg;
|
|
4637
|
+
fs_safe_job_t *job = a->job;
|
|
4638
|
+
|
|
4639
|
+
fs_safe_raise_fatal(job);
|
|
4640
|
+
if (job->mode == FS_MODE_BUILD_INDEX)
|
|
4641
|
+
a->result = fs_safe_materialize_index(job);
|
|
4642
|
+
else
|
|
4643
|
+
a->result = fs_safe_materialize_rows(job);
|
|
4644
|
+
return a->result;
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
static void fs_safe_copy_options(fs_safe_job_t *job, fs_options_t *opts) {
|
|
4648
|
+
long len = RARRAY_LEN(opts->id_path);
|
|
4649
|
+
|
|
4650
|
+
memset(&job->opts, 0, sizeof(job->opts));
|
|
4651
|
+
job->opts.only_all = opts->only_all;
|
|
4652
|
+
job->opts.only_mask = opts->only_mask;
|
|
4653
|
+
job->opts.on_invalid = opts->on_invalid;
|
|
4654
|
+
job->opts.on_missing_id = opts->on_missing_id;
|
|
4655
|
+
job->opts.report = opts->report;
|
|
4656
|
+
job->opts.max_errors = opts->max_errors;
|
|
4657
|
+
job->opts.geometry_index = opts->geometry_index;
|
|
4658
|
+
job->opts.strategy = opts->strategy;
|
|
4659
|
+
job->opts.predicate = opts->predicate;
|
|
4660
|
+
job->opts.len = len;
|
|
4661
|
+
|
|
4662
|
+
if (len > 0) {
|
|
4663
|
+
job->opts.keys = calloc((size_t)len, sizeof(char *));
|
|
4664
|
+
job->opts.lens = calloc((size_t)len, sizeof(size_t));
|
|
4665
|
+
if (!job->opts.keys || !job->opts.lens) {
|
|
4666
|
+
fs_safe_free_c_options(&job->opts);
|
|
4667
|
+
rb_raise(rb_eNoMemError, "FeatureSource id path allocation failed");
|
|
4668
|
+
}
|
|
4669
|
+
for (long i = 0; i < len; i++) {
|
|
4670
|
+
VALUE key = rb_ary_entry(opts->id_path, i);
|
|
4671
|
+
StringValue(key);
|
|
4672
|
+
job->opts.lens[i] = (size_t)RSTRING_LEN(key);
|
|
4673
|
+
job->opts.keys[i] = fs_safe_strdup_len(RSTRING_PTR(key), job->opts.lens[i]);
|
|
4674
|
+
if (!job->opts.keys[i]) {
|
|
4675
|
+
fs_safe_free_c_options(&job->opts);
|
|
4676
|
+
rb_raise(rb_eNoMemError, "FeatureSource id path key allocation failed");
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
|
|
4682
|
+
typedef struct {
|
|
4683
|
+
fs_safe_job_t *job;
|
|
4684
|
+
fs_safe_materialize_args_t materialize;
|
|
4685
|
+
} fs_safe_dispatch_ctx_t;
|
|
4686
|
+
|
|
4687
|
+
static VALUE fs_safe_dispatch_body(VALUE arg) {
|
|
4688
|
+
fs_safe_dispatch_ctx_t *ctx = (fs_safe_dispatch_ctx_t *)arg;
|
|
4689
|
+
|
|
4690
|
+
fs_safe_run_without_gvl(ctx->job);
|
|
4691
|
+
ctx->materialize.job = ctx->job;
|
|
4692
|
+
return fs_safe_materialize((VALUE)&ctx->materialize);
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
static VALUE fs_safe_dispatch_json(VALUE json_string, VALUE kwargs, fs_mode_t mode) {
|
|
4696
|
+
fs_args_t fs;
|
|
4697
|
+
fs_safe_job_t job;
|
|
4698
|
+
fs_safe_dispatch_ctx_t ctx;
|
|
4699
|
+
|
|
4700
|
+
memset(&fs, 0, sizeof(fs));
|
|
4701
|
+
memset(&job, 0, sizeof(job));
|
|
4702
|
+
memset(&ctx, 0, sizeof(ctx));
|
|
4703
|
+
|
|
4704
|
+
fs.mode = mode;
|
|
4705
|
+
fs.opts = fs_parse_options(kwargs, mode);
|
|
4706
|
+
job.mode = mode;
|
|
4707
|
+
{
|
|
4708
|
+
VALUE str = json_string;
|
|
4709
|
+
StringValue(str);
|
|
4710
|
+
if ((size_t)RSTRING_LEN(str) > SIZE_MAX - 1)
|
|
4711
|
+
rb_raise(rb_eNoMemError, "FeatureSource input is too large");
|
|
4712
|
+
|
|
4713
|
+
fs_safe_copy_options(&job, &fs.opts);
|
|
4714
|
+
job.source_len = (size_t)RSTRING_LEN(str);
|
|
4715
|
+
job.source_data = (char *)malloc(job.source_len + 1);
|
|
4716
|
+
if (!job.source_data) {
|
|
4717
|
+
fs_safe_job_ensure((VALUE)&job);
|
|
4718
|
+
rb_raise(rb_eNoMemError, "FeatureSource input allocation failed");
|
|
4719
|
+
}
|
|
4720
|
+
memcpy(job.source_data, RSTRING_PTR(str), job.source_len);
|
|
4721
|
+
job.source_data[job.source_len] = '\0';
|
|
4722
|
+
job.source_ruby_alloc = false;
|
|
4723
|
+
RB_GC_GUARD(str);
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
ctx.job = &job;
|
|
4727
|
+
return rb_ensure(fs_safe_dispatch_body, (VALUE)&ctx, fs_safe_job_ensure, (VALUE)&job);
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
static VALUE fs_safe_dispatch_file(VALUE path, VALUE kwargs, fs_mode_t mode) {
|
|
4731
|
+
fs_args_t fs;
|
|
4732
|
+
fs_safe_job_t job;
|
|
4733
|
+
fs_safe_dispatch_ctx_t ctx;
|
|
4734
|
+
VALUE path_str = path;
|
|
4735
|
+
|
|
4736
|
+
memset(&fs, 0, sizeof(fs));
|
|
4737
|
+
memset(&job, 0, sizeof(job));
|
|
4738
|
+
memset(&ctx, 0, sizeof(ctx));
|
|
4739
|
+
|
|
4740
|
+
StringValueCStr(path_str);
|
|
4741
|
+
fs.mode = mode;
|
|
4742
|
+
fs.opts = fs_parse_options(kwargs, mode);
|
|
4743
|
+
job.mode = mode;
|
|
4744
|
+
fs_safe_copy_options(&job, &fs.opts);
|
|
4745
|
+
job.path = fs_safe_strdup_cstr(StringValueCStr(path_str));
|
|
4746
|
+
if (!job.path) {
|
|
4747
|
+
fs_safe_job_ensure((VALUE)&job);
|
|
4748
|
+
rb_raise(rb_eNoMemError, "FeatureSource path allocation failed");
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4751
|
+
ctx.job = &job;
|
|
4752
|
+
return rb_ensure(fs_safe_dispatch_body, (VALUE)&ctx, fs_safe_job_ensure, (VALUE)&job);
|
|
4753
|
+
}
|
|
4754
|
+
|
|
4755
|
+
static VALUE fs_read_body(VALUE arg) {
|
|
4756
|
+
fs_args_t *fs = (fs_args_t *)arg;
|
|
4757
|
+
struct json features;
|
|
4758
|
+
struct json feature;
|
|
4759
|
+
VALUE rows;
|
|
4760
|
+
VALUE errors;
|
|
4761
|
+
long feature_index = 0;
|
|
4762
|
+
long skipped = 0;
|
|
4763
|
+
long filtered = 0;
|
|
4764
|
+
|
|
4765
|
+
fs_parse_root(&fs->source, &features);
|
|
4766
|
+
rows = rb_ary_new();
|
|
4767
|
+
errors = rb_ary_new();
|
|
4768
|
+
|
|
4769
|
+
for (feature = json_first(features); json_exists(feature);
|
|
4770
|
+
feature = json_next(feature), feature_index++) {
|
|
4771
|
+
fs_feature_result_t prepared;
|
|
4772
|
+
VALUE tg_error = Qnil;
|
|
4773
|
+
|
|
4774
|
+
if (!fs_feature_prepare(&fs->source, &fs->opts, fs->mode, feature, feature_index, true,
|
|
4775
|
+
&prepared)) {
|
|
4776
|
+
fs_handle_invalid_or_raise(&fs->source, &fs->opts, &prepared, feature_index, errors,
|
|
4777
|
+
&skipped);
|
|
4778
|
+
continue;
|
|
4779
|
+
}
|
|
4780
|
+
|
|
4781
|
+
if (prepared.filtered) {
|
|
4782
|
+
filtered++;
|
|
4783
|
+
continue;
|
|
4784
|
+
}
|
|
4785
|
+
|
|
4786
|
+
if (!fs_validate_geometry_or_error(prepared.geometry, fs->opts.geometry_index, &tg_error)) {
|
|
4787
|
+
prepared.reason_value = rb_str_plus(rb_str_new_cstr("invalid geometry: "), tg_error);
|
|
4788
|
+
fs_handle_invalid_or_raise(&fs->source, &fs->opts, &prepared, feature_index, errors,
|
|
4789
|
+
&skipped);
|
|
4790
|
+
continue;
|
|
4791
|
+
}
|
|
4792
|
+
|
|
4793
|
+
if (fs->mode == FS_MODE_READ_ENTRIES) {
|
|
4794
|
+
VALUE geom_json =
|
|
4795
|
+
fs_utf8_string(json_raw(prepared.geometry), json_raw_length(prepared.geometry));
|
|
4796
|
+
rb_ary_push(rows, rb_ary_new_from_args(2, prepared.id, geom_json));
|
|
4797
|
+
} else {
|
|
4798
|
+
VALUE geom_json =
|
|
4799
|
+
fs_utf8_string(json_raw(prepared.geometry), json_raw_length(prepared.geometry));
|
|
4800
|
+
VALUE props_json = fs_properties_json_string(prepared.properties);
|
|
4801
|
+
rb_ary_push(rows, rb_ary_new_from_args(3, prepared.id, geom_json, props_json));
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4805
|
+
if (fs->opts.report) {
|
|
4806
|
+
VALUE report = rb_hash_new();
|
|
4807
|
+
rb_hash_aset(report,
|
|
4808
|
+
fs->mode == FS_MODE_READ_ENTRIES ? fs_sym("entries") : fs_sym("features"),
|
|
4809
|
+
rows);
|
|
4810
|
+
rb_hash_aset(report, fs_sym("skipped"), LONG2NUM(skipped));
|
|
4811
|
+
rb_hash_aset(report, fs_sym("filtered"), LONG2NUM(filtered));
|
|
4812
|
+
rb_hash_aset(report, fs_sym("errors"), errors);
|
|
4813
|
+
RB_GC_GUARD(rows);
|
|
4814
|
+
RB_GC_GUARD(errors);
|
|
4815
|
+
RB_GC_GUARD(report);
|
|
4816
|
+
RB_GC_GUARD(fs->opts.id_path);
|
|
4817
|
+
return report;
|
|
4818
|
+
}
|
|
4819
|
+
|
|
4820
|
+
RB_GC_GUARD(errors);
|
|
4821
|
+
RB_GC_GUARD(rows);
|
|
4822
|
+
RB_GC_GUARD(fs->opts.id_path);
|
|
4823
|
+
return rows;
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
static long fs_count_accepted_features(fs_args_t *fs, struct json features) {
|
|
4827
|
+
struct json feature;
|
|
4828
|
+
long feature_index = 0;
|
|
4829
|
+
long accepted = 0;
|
|
4830
|
+
|
|
4831
|
+
for (feature = json_first(features); json_exists(feature);
|
|
4832
|
+
feature = json_next(feature), feature_index++) {
|
|
4833
|
+
fs_feature_result_t prepared;
|
|
4834
|
+
long skipped = 0;
|
|
4835
|
+
|
|
4836
|
+
if (!fs_feature_prepare(&fs->source, &fs->opts, FS_MODE_BUILD_INDEX, feature, feature_index,
|
|
4837
|
+
false, &prepared)) {
|
|
4838
|
+
fs_handle_invalid_or_raise(&fs->source, &fs->opts, &prepared, feature_index, Qnil,
|
|
4839
|
+
&skipped);
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
if (prepared.filtered)
|
|
4843
|
+
continue;
|
|
4844
|
+
if (prepared.accepted)
|
|
4845
|
+
accepted++;
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
return accepted;
|
|
4849
|
+
}
|
|
4850
|
+
|
|
4851
|
+
static void fs_fill_index_entry_from_geometry(tg_index_t *idx, long ordinal, VALUE id,
|
|
4852
|
+
struct json geometry, enum tg_index geometry_index,
|
|
4853
|
+
fs_source_t *source, long feature_index) {
|
|
4854
|
+
tg_index_entry_t entry;
|
|
4855
|
+
struct tg_geom *geom;
|
|
4856
|
+
const char *err;
|
|
4857
|
+
|
|
4858
|
+
geom = tg_parse_geojsonn_ix(json_raw(geometry), json_raw_length(geometry), geometry_index);
|
|
4859
|
+
if (!geom) {
|
|
4860
|
+
rb_raise(rb_eNoMemError, "TG geometry allocation failed");
|
|
4861
|
+
}
|
|
4862
|
+
|
|
4863
|
+
err = tg_geom_error(geom);
|
|
4864
|
+
if (err) {
|
|
4865
|
+
VALUE msg = rb_str_new_cstr(err);
|
|
4866
|
+
VALUE full;
|
|
4867
|
+
tg_geom_free(geom);
|
|
4868
|
+
full = rb_str_plus(rb_str_new_cstr("invalid geometry: "), msg);
|
|
4869
|
+
fs_raise_parse_error_value(feature_index, fs_json_offset(source, geometry), full);
|
|
4870
|
+
}
|
|
4871
|
+
|
|
4872
|
+
memset(&entry, 0, sizeof(entry));
|
|
4873
|
+
entry.id = id;
|
|
4874
|
+
entry.geom_owner = Qnil;
|
|
4875
|
+
entry.geom = geom;
|
|
4876
|
+
entry.bbox = tg_geom_rect(geom);
|
|
4877
|
+
entry.geom_bytes = tg_geom_memsize(geom);
|
|
4878
|
+
entry.ordinal = ordinal;
|
|
4879
|
+
entry.owned = true;
|
|
4880
|
+
|
|
4881
|
+
idx->entries[ordinal] = entry;
|
|
4882
|
+
idx->initialized++;
|
|
4883
|
+
idx->owned_geom_bytes_total += entry.geom_bytes;
|
|
4884
|
+
if (entry.geom_bytes > 0) {
|
|
4885
|
+
rb_gc_adjust_memory_usage((ssize_t)entry.geom_bytes);
|
|
4886
|
+
}
|
|
4887
|
+
index_expand_bbox(idx, entry.bbox);
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4890
|
+
static VALUE fs_build_index_body(VALUE arg) {
|
|
4891
|
+
fs_build_args_t *build = (fs_build_args_t *)arg;
|
|
4892
|
+
fs_args_t *fs = build->fs;
|
|
4893
|
+
tg_index_t *idx = build->idx;
|
|
4894
|
+
struct json feature;
|
|
4895
|
+
long feature_index = 0;
|
|
4896
|
+
long ordinal = 0;
|
|
4897
|
+
|
|
4898
|
+
for (feature = json_first(build->features); json_exists(feature);
|
|
4899
|
+
feature = json_next(feature), feature_index++) {
|
|
4900
|
+
fs_feature_result_t prepared;
|
|
4901
|
+
long skipped = 0;
|
|
4902
|
+
|
|
4903
|
+
if (!fs_feature_prepare(&fs->source, &fs->opts, FS_MODE_BUILD_INDEX, feature, feature_index,
|
|
4904
|
+
true, &prepared)) {
|
|
4905
|
+
fs_handle_invalid_or_raise(&fs->source, &fs->opts, &prepared, feature_index, Qnil,
|
|
4906
|
+
&skipped);
|
|
4907
|
+
}
|
|
4908
|
+
|
|
4909
|
+
if (prepared.filtered)
|
|
4910
|
+
continue;
|
|
4911
|
+
if (!prepared.accepted)
|
|
4912
|
+
continue;
|
|
4913
|
+
|
|
4914
|
+
fs_fill_index_entry_from_geometry(idx, ordinal, prepared.id, prepared.geometry,
|
|
4915
|
+
fs->opts.geometry_index, &fs->source, feature_index);
|
|
4916
|
+
ordinal++;
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
if (idx->initialized != idx->len) {
|
|
4920
|
+
rb_raise(eTGGeometryError, "internal FeatureSource index initialization mismatch");
|
|
4921
|
+
}
|
|
4922
|
+
|
|
4923
|
+
if (idx->strategy == TG_GEOMETRY_INDEX_STRATEGY_RTREE) {
|
|
4924
|
+
index_build_rtree(idx);
|
|
4925
|
+
}
|
|
4926
|
+
|
|
4927
|
+
return Qnil;
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
static VALUE fs_build_index(VALUE arg) {
|
|
4931
|
+
fs_args_t *fs = (fs_args_t *)arg;
|
|
4932
|
+
struct json features;
|
|
4933
|
+
long accepted;
|
|
4934
|
+
tg_index_t *idx;
|
|
4935
|
+
VALUE wrapper;
|
|
4936
|
+
fs_build_args_t build;
|
|
4937
|
+
int state = 0;
|
|
4938
|
+
|
|
4939
|
+
fs_parse_root(&fs->source, &features);
|
|
4940
|
+
accepted = fs_count_accepted_features(fs, features);
|
|
4941
|
+
|
|
4942
|
+
wrapper = TypedData_Make_Struct(cTGGeometryIndex, tg_index_t, &tg_index_type, idx);
|
|
4943
|
+
idx->len = accepted;
|
|
4944
|
+
idx->capacity = accepted;
|
|
4945
|
+
idx->initialized = 0;
|
|
4946
|
+
idx->strategy = fs->opts.strategy;
|
|
4947
|
+
idx->predicate = fs->opts.predicate;
|
|
4948
|
+
idx->rtree = NULL;
|
|
4949
|
+
idx->frozen = false;
|
|
4950
|
+
idx->has_bbox = false;
|
|
4951
|
+
|
|
4952
|
+
if (accepted > 0) {
|
|
4953
|
+
if ((size_t)accepted > SIZE_MAX / sizeof(tg_index_entry_t)) {
|
|
4954
|
+
rb_raise(rb_eNoMemError, "entries allocation size overflow");
|
|
4955
|
+
}
|
|
4956
|
+
idx->entries = calloc((size_t)accepted, sizeof(tg_index_entry_t));
|
|
4957
|
+
if (!idx->entries) {
|
|
4958
|
+
rb_raise(rb_eNoMemError, "entries allocation failed");
|
|
4959
|
+
}
|
|
4960
|
+
idx->entries_bytes = (size_t)accepted * sizeof(tg_index_entry_t);
|
|
4961
|
+
rb_gc_adjust_memory_usage((ssize_t)idx->entries_bytes);
|
|
4962
|
+
}
|
|
4963
|
+
|
|
4964
|
+
build.fs = fs;
|
|
4965
|
+
build.features = features;
|
|
4966
|
+
build.wrapper = wrapper;
|
|
4967
|
+
build.idx = idx;
|
|
4968
|
+
|
|
4969
|
+
rb_protect(fs_build_index_body, (VALUE)&build, &state);
|
|
4970
|
+
if (state) {
|
|
4971
|
+
index_dispose(idx);
|
|
4972
|
+
RB_GC_GUARD(wrapper);
|
|
4973
|
+
RB_GC_GUARD(fs->opts.id_path);
|
|
4974
|
+
rb_jump_tag(state);
|
|
4975
|
+
}
|
|
4976
|
+
|
|
4977
|
+
idx->frozen = true;
|
|
4978
|
+
rb_obj_freeze(wrapper);
|
|
4979
|
+
|
|
4980
|
+
RB_GC_GUARD(wrapper);
|
|
4981
|
+
RB_GC_GUARD(fs->opts.id_path);
|
|
4982
|
+
return wrapper;
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4985
|
+
static VALUE fs_source_ensure(VALUE arg) {
|
|
4986
|
+
fs_args_t *fs = (fs_args_t *)arg;
|
|
4987
|
+
|
|
4988
|
+
if (fs->source.data) {
|
|
4989
|
+
ruby_xfree(fs->source.data);
|
|
4990
|
+
fs->source.data = NULL;
|
|
4991
|
+
fs->source.len = 0;
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
return Qnil;
|
|
4995
|
+
}
|
|
4996
|
+
|
|
4997
|
+
static void fs_copy_source_from_string(fs_args_t *fs, VALUE source) {
|
|
4998
|
+
VALUE str = source;
|
|
4999
|
+
StringValue(str);
|
|
5000
|
+
|
|
5001
|
+
if ((size_t)RSTRING_LEN(str) > SIZE_MAX - 1) {
|
|
5002
|
+
rb_raise(rb_eNoMemError, "FeatureSource input is too large");
|
|
5003
|
+
}
|
|
5004
|
+
|
|
5005
|
+
fs->source.len = (size_t)RSTRING_LEN(str);
|
|
5006
|
+
fs->source.data = ruby_xmalloc(fs->source.len + 1);
|
|
5007
|
+
memcpy(fs->source.data, RSTRING_PTR(str), fs->source.len);
|
|
5008
|
+
fs->source.data[fs->source.len] = '\0';
|
|
5009
|
+
RB_GC_GUARD(str);
|
|
5010
|
+
}
|
|
5011
|
+
|
|
5012
|
+
static VALUE __attribute__((unused)) fs_dispatch(VALUE source_string, VALUE kwargs,
|
|
5013
|
+
fs_mode_t mode) {
|
|
5014
|
+
fs_args_t fs;
|
|
5015
|
+
|
|
5016
|
+
memset(&fs, 0, sizeof(fs));
|
|
5017
|
+
fs.mode = mode;
|
|
5018
|
+
fs.opts = fs_parse_options(kwargs, mode);
|
|
5019
|
+
fs_copy_source_from_string(&fs, source_string);
|
|
5020
|
+
|
|
5021
|
+
if (mode == FS_MODE_BUILD_INDEX) {
|
|
5022
|
+
return rb_ensure(fs_build_index, (VALUE)&fs, fs_source_ensure, (VALUE)&fs);
|
|
5023
|
+
}
|
|
5024
|
+
|
|
5025
|
+
return rb_ensure(fs_read_body, (VALUE)&fs, fs_source_ensure, (VALUE)&fs);
|
|
5026
|
+
}
|
|
5027
|
+
|
|
5028
|
+
static VALUE __attribute__((unused)) fs_read_file_to_string(VALUE path) {
|
|
5029
|
+
return rb_funcall(rb_cFile, rb_intern("binread"), 1, path);
|
|
5030
|
+
}
|
|
5031
|
+
|
|
5032
|
+
static VALUE fs_read_io_to_string(VALUE io) {
|
|
5033
|
+
VALUE str;
|
|
5034
|
+
|
|
5035
|
+
if (!rb_respond_to(io, id_read)) {
|
|
5036
|
+
rb_raise(rb_eTypeError, "io must respond to read");
|
|
5037
|
+
}
|
|
5038
|
+
str = rb_funcall(io, id_read, 0);
|
|
5039
|
+
if (!RB_TYPE_P(str, T_STRING)) {
|
|
5040
|
+
rb_raise(rb_eTypeError, "io.read must return String");
|
|
5041
|
+
}
|
|
5042
|
+
return str;
|
|
5043
|
+
}
|
|
5044
|
+
|
|
5045
|
+
static VALUE rb_tg_feature_source_read_entries_json(int argc, VALUE *argv, VALUE self) {
|
|
5046
|
+
VALUE json_string;
|
|
5047
|
+
VALUE kwargs;
|
|
5048
|
+
(void)self;
|
|
5049
|
+
rb_scan_args(argc, argv, "1:", &json_string, &kwargs);
|
|
5050
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_READ_ENTRIES);
|
|
5051
|
+
}
|
|
5052
|
+
|
|
5053
|
+
static VALUE rb_tg_feature_source_read_features_json(int argc, VALUE *argv, VALUE self) {
|
|
5054
|
+
VALUE json_string;
|
|
5055
|
+
VALUE kwargs;
|
|
5056
|
+
(void)self;
|
|
5057
|
+
rb_scan_args(argc, argv, "1:", &json_string, &kwargs);
|
|
5058
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_READ_FEATURES);
|
|
5059
|
+
}
|
|
5060
|
+
|
|
5061
|
+
static VALUE rb_tg_feature_source_build_index_json(int argc, VALUE *argv, VALUE self) {
|
|
5062
|
+
VALUE json_string;
|
|
5063
|
+
VALUE kwargs;
|
|
5064
|
+
(void)self;
|
|
5065
|
+
rb_scan_args(argc, argv, "1:", &json_string, &kwargs);
|
|
5066
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_BUILD_INDEX);
|
|
5067
|
+
}
|
|
5068
|
+
|
|
5069
|
+
static VALUE rb_tg_feature_source_read_entries_file(int argc, VALUE *argv, VALUE self) {
|
|
5070
|
+
VALUE path;
|
|
5071
|
+
VALUE kwargs;
|
|
5072
|
+
(void)self;
|
|
5073
|
+
rb_scan_args(argc, argv, "1:", &path, &kwargs);
|
|
5074
|
+
return fs_safe_dispatch_file(path, kwargs, FS_MODE_READ_ENTRIES);
|
|
5075
|
+
}
|
|
5076
|
+
|
|
5077
|
+
static VALUE rb_tg_feature_source_read_features_file(int argc, VALUE *argv, VALUE self) {
|
|
5078
|
+
VALUE path;
|
|
5079
|
+
VALUE kwargs;
|
|
5080
|
+
(void)self;
|
|
5081
|
+
rb_scan_args(argc, argv, "1:", &path, &kwargs);
|
|
5082
|
+
return fs_safe_dispatch_file(path, kwargs, FS_MODE_READ_FEATURES);
|
|
5083
|
+
}
|
|
5084
|
+
|
|
5085
|
+
static VALUE rb_tg_feature_source_build_index_file(int argc, VALUE *argv, VALUE self) {
|
|
5086
|
+
VALUE path;
|
|
5087
|
+
VALUE kwargs;
|
|
5088
|
+
(void)self;
|
|
5089
|
+
rb_scan_args(argc, argv, "1:", &path, &kwargs);
|
|
5090
|
+
return fs_safe_dispatch_file(path, kwargs, FS_MODE_BUILD_INDEX);
|
|
5091
|
+
}
|
|
5092
|
+
|
|
5093
|
+
static VALUE rb_tg_feature_source_read_entries_io(int argc, VALUE *argv, VALUE self) {
|
|
5094
|
+
VALUE io;
|
|
5095
|
+
VALUE kwargs;
|
|
5096
|
+
VALUE json_string;
|
|
5097
|
+
(void)self;
|
|
5098
|
+
rb_scan_args(argc, argv, "1:", &io, &kwargs);
|
|
5099
|
+
json_string = fs_read_io_to_string(io);
|
|
5100
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_READ_ENTRIES);
|
|
5101
|
+
}
|
|
5102
|
+
|
|
5103
|
+
static VALUE rb_tg_feature_source_read_features_io(int argc, VALUE *argv, VALUE self) {
|
|
5104
|
+
VALUE io;
|
|
5105
|
+
VALUE kwargs;
|
|
5106
|
+
VALUE json_string;
|
|
5107
|
+
(void)self;
|
|
5108
|
+
rb_scan_args(argc, argv, "1:", &io, &kwargs);
|
|
5109
|
+
json_string = fs_read_io_to_string(io);
|
|
5110
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_READ_FEATURES);
|
|
5111
|
+
}
|
|
5112
|
+
|
|
5113
|
+
static VALUE rb_tg_feature_source_build_index_io(int argc, VALUE *argv, VALUE self) {
|
|
5114
|
+
VALUE io;
|
|
5115
|
+
VALUE kwargs;
|
|
5116
|
+
VALUE json_string;
|
|
5117
|
+
(void)self;
|
|
5118
|
+
rb_scan_args(argc, argv, "1:", &io, &kwargs);
|
|
5119
|
+
json_string = fs_read_io_to_string(io);
|
|
5120
|
+
return fs_safe_dispatch_json(json_string, kwargs, FS_MODE_BUILD_INDEX);
|
|
5121
|
+
}
|
|
5122
|
+
|
|
5123
|
+
RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
|
|
5124
|
+
tg_geometry_vendor_header_sanity();
|
|
5125
|
+
|
|
5126
|
+
id_format = rb_intern("format");
|
|
5127
|
+
id_index = rb_intern("index");
|
|
5128
|
+
id_auto = rb_intern("auto");
|
|
5129
|
+
id_geojson = rb_intern("geojson");
|
|
5130
|
+
id_wkt = rb_intern("wkt");
|
|
5131
|
+
id_wkb = rb_intern("wkb");
|
|
5132
|
+
id_hex = rb_intern("hex");
|
|
5133
|
+
id_geobin = rb_intern("geobin");
|
|
5134
|
+
id_default = rb_intern("default");
|
|
5135
|
+
id_none = rb_intern("none");
|
|
5136
|
+
id_natural = rb_intern("natural");
|
|
5137
|
+
id_ystripes = rb_intern("ystripes");
|
|
5138
|
+
id_via = rb_intern("via");
|
|
5139
|
+
id_strategy = rb_intern("strategy");
|
|
5140
|
+
id_predicate = rb_intern("predicate");
|
|
5141
|
+
id_geometry_index = rb_intern("geometry_index");
|
|
5142
|
+
id_geom = rb_intern("geom");
|
|
5143
|
+
id_flat = rb_intern("flat");
|
|
5144
|
+
id_rtree = rb_intern("rtree");
|
|
5145
|
+
id_covers = rb_intern("covers");
|
|
5146
|
+
id_contains = rb_intern("contains");
|
|
5147
|
+
id_id = rb_intern("id");
|
|
5148
|
+
id_only = rb_intern("only");
|
|
5149
|
+
id_on_invalid = rb_intern("on_invalid");
|
|
5150
|
+
id_on_missing_id = rb_intern("on_missing_id");
|
|
5151
|
+
id_report = rb_intern("report");
|
|
5152
|
+
id_max_errors = rb_intern("max_errors");
|
|
5153
|
+
id_read = rb_intern("read");
|
|
5154
|
+
id_raise = rb_intern("raise");
|
|
5155
|
+
id_skip = rb_intern("skip");
|
|
5156
|
+
id_ordinal = rb_intern("ordinal");
|
|
5157
|
+
id_polygon = rb_intern("polygon");
|
|
5158
|
+
id_multipolygon = rb_intern("multipolygon");
|
|
5159
|
+
id_point = rb_intern("point");
|
|
5160
|
+
id_linestring = rb_intern("linestring");
|
|
5161
|
+
id_multipoint = rb_intern("multipoint");
|
|
5162
|
+
id_multilinestring = rb_intern("multilinestring");
|
|
5163
|
+
id_geometrycollection = rb_intern("geometrycollection");
|
|
5164
|
+
|
|
5165
|
+
mTG = rb_define_module("TG");
|
|
5166
|
+
mTGGeometry = rb_define_module_under(mTG, "Geometry");
|
|
5167
|
+
|
|
5168
|
+
eTGGeometryError = rb_define_class_under(mTGGeometry, "Error", rb_eStandardError);
|
|
5169
|
+
eTGGeometryParseError = rb_define_class_under(mTGGeometry, "ParseError", eTGGeometryError);
|
|
5170
|
+
eTGGeometryArgumentError = rb_define_class_under(mTGGeometry, "ArgumentError", rb_eArgError);
|
|
5171
|
+
eTGGeometryFrozenIndexError =
|
|
5172
|
+
rb_define_class_under(mTGGeometry, "FrozenIndexError", eTGGeometryError);
|
|
5173
|
+
|
|
5174
|
+
rb_define_singleton_method(mTGGeometry, "parse", rb_tg_geometry_parse, -1);
|
|
5175
|
+
rb_define_singleton_method(mTGGeometry, "parse_geojson", rb_tg_geometry_parse_geojson, -1);
|
|
5176
|
+
rb_define_singleton_method(mTGGeometry, "parse_wkt", rb_tg_geometry_parse_wkt, -1);
|
|
5177
|
+
rb_define_singleton_method(mTGGeometry, "parse_wkb", rb_tg_geometry_parse_wkb, -1);
|
|
5178
|
+
rb_define_singleton_method(mTGGeometry, "parse_hex", rb_tg_geometry_parse_hex, -1);
|
|
5179
|
+
rb_define_singleton_method(mTGGeometry, "parse_geobin", rb_tg_geometry_parse_geobin, -1);
|
|
5180
|
+
rb_define_singleton_method(mTGGeometry, "point", rb_tg_geometry_point, 2);
|
|
5181
|
+
rb_define_singleton_method(mTGGeometry, "point_z", rb_tg_geometry_point_z, 3);
|
|
5182
|
+
rb_define_singleton_method(mTGGeometry, "point_m", rb_tg_geometry_point_m, 3);
|
|
5183
|
+
rb_define_singleton_method(mTGGeometry, "point_zm", rb_tg_geometry_point_zm, 4);
|
|
5184
|
+
rb_define_singleton_method(mTGGeometry, "empty_point", rb_tg_geometry_empty_point, 0);
|
|
5185
|
+
rb_define_singleton_method(mTGGeometry, "empty_linestring", rb_tg_geometry_empty_linestring, 0);
|
|
5186
|
+
rb_define_singleton_method(mTGGeometry, "empty_polygon", rb_tg_geometry_empty_polygon, 0);
|
|
5187
|
+
rb_define_singleton_method(mTGGeometry, "empty_multipoint", rb_tg_geometry_empty_multipoint, 0);
|
|
5188
|
+
rb_define_singleton_method(mTGGeometry, "empty_multilinestring",
|
|
5189
|
+
rb_tg_geometry_empty_multilinestring, 0);
|
|
5190
|
+
rb_define_singleton_method(mTGGeometry, "empty_multipolygon", rb_tg_geometry_empty_multipolygon,
|
|
5191
|
+
0);
|
|
5192
|
+
rb_define_singleton_method(mTGGeometry, "empty_geometrycollection",
|
|
5193
|
+
rb_tg_geometry_empty_geometrycollection, 0);
|
|
5194
|
+
|
|
5195
|
+
cTGGeometryGeom = rb_define_class_under(mTGGeometry, "Geom", rb_cObject);
|
|
5196
|
+
rb_undef_alloc_func(cTGGeometryGeom);
|
|
5197
|
+
rb_define_method(cTGGeometryGeom, "type", rb_tg_geometry_geom_type, 0);
|
|
5198
|
+
rb_define_method(cTGGeometryGeom, "bbox", rb_tg_geometry_geom_bbox, 0);
|
|
5199
|
+
rb_define_method(cTGGeometryGeom, "covers_xy?", rb_tg_geometry_geom_covers_xy_p, 2);
|
|
5200
|
+
rb_define_method(cTGGeometryGeom, "intersects_xy?", rb_tg_geometry_geom_intersects_xy_p, 2);
|
|
5201
|
+
rb_define_method(cTGGeometryGeom, "equals?", rb_tg_geometry_geom_equals_p, 1);
|
|
5202
|
+
rb_define_method(cTGGeometryGeom, "contains?", rb_tg_geometry_geom_contains_p, 1);
|
|
5203
|
+
rb_define_method(cTGGeometryGeom, "intersects?", rb_tg_geometry_geom_intersects_p, 1);
|
|
5204
|
+
rb_define_method(cTGGeometryGeom, "disjoint?", rb_tg_geometry_geom_disjoint_p, 1);
|
|
5205
|
+
rb_define_method(cTGGeometryGeom, "within?", rb_tg_geometry_geom_within_p, 1);
|
|
5206
|
+
rb_define_method(cTGGeometryGeom, "covers?", rb_tg_geometry_geom_covers_p, 1);
|
|
5207
|
+
rb_define_method(cTGGeometryGeom, "covered_by?", rb_tg_geometry_geom_covered_by_p, 1);
|
|
5208
|
+
rb_define_method(cTGGeometryGeom, "touches?", rb_tg_geometry_geom_touches_p, 1);
|
|
5209
|
+
rb_define_method(cTGGeometryGeom, "intersects_rect?", rb_tg_geometry_geom_intersects_rect_p,
|
|
5210
|
+
-1);
|
|
5211
|
+
rb_define_method(cTGGeometryGeom, "to_geojson", rb_tg_geometry_geom_to_geojson, 0);
|
|
5212
|
+
rb_define_method(cTGGeometryGeom, "to_wkt", rb_tg_geometry_geom_to_wkt, 0);
|
|
5213
|
+
rb_define_method(cTGGeometryGeom, "to_wkb", rb_tg_geometry_geom_to_wkb, 0);
|
|
5214
|
+
rb_define_method(cTGGeometryGeom, "to_hex", rb_tg_geometry_geom_to_hex, 0);
|
|
5215
|
+
rb_define_method(cTGGeometryGeom, "to_geobin", rb_tg_geometry_geom_to_geobin, 0);
|
|
5216
|
+
rb_define_method(cTGGeometryGeom, "extra_json", rb_tg_geometry_geom_extra_json, 0);
|
|
5217
|
+
rb_define_method(cTGGeometryGeom, "feature?", rb_tg_geometry_geom_feature_p, 0);
|
|
5218
|
+
rb_define_method(cTGGeometryGeom, "feature_collection?",
|
|
5219
|
+
rb_tg_geometry_geom_feature_collection_p, 0);
|
|
5220
|
+
rb_define_method(cTGGeometryGeom, "empty?", rb_tg_geometry_geom_empty_p, 0);
|
|
5221
|
+
rb_define_method(cTGGeometryGeom, "dims", rb_tg_geometry_geom_dims, 0);
|
|
5222
|
+
rb_define_method(cTGGeometryGeom, "has_z?", rb_tg_geometry_geom_has_z_p, 0);
|
|
5223
|
+
rb_define_method(cTGGeometryGeom, "has_m?", rb_tg_geometry_geom_has_m_p, 0);
|
|
5224
|
+
rb_define_method(cTGGeometryGeom, "z", rb_tg_geometry_geom_z, 0);
|
|
5225
|
+
rb_define_method(cTGGeometryGeom, "m", rb_tg_geometry_geom_m, 0);
|
|
5226
|
+
rb_define_method(cTGGeometryGeom, "extra_coords", rb_tg_geometry_geom_extra_coords, 0);
|
|
5227
|
+
rb_define_method(cTGGeometryGeom, "point", rb_tg_geometry_geom_point, 0);
|
|
5228
|
+
rb_define_method(cTGGeometryGeom, "num_points", rb_tg_geometry_geom_num_points, 0);
|
|
5229
|
+
rb_define_method(cTGGeometryGeom, "point_at", rb_tg_geometry_geom_point_at, 1);
|
|
5230
|
+
rb_define_method(cTGGeometryGeom, "points", rb_tg_geometry_geom_points, 0);
|
|
5231
|
+
rb_define_method(cTGGeometryGeom, "line", rb_tg_geometry_geom_line, 0);
|
|
5232
|
+
rb_define_method(cTGGeometryGeom, "num_lines", rb_tg_geometry_geom_num_lines, 0);
|
|
5233
|
+
rb_define_method(cTGGeometryGeom, "line_at", rb_tg_geometry_geom_line_at, 1);
|
|
5234
|
+
rb_define_method(cTGGeometryGeom, "lines", rb_tg_geometry_geom_lines, 0);
|
|
5235
|
+
rb_define_method(cTGGeometryGeom, "polygon", rb_tg_geometry_geom_polygon, 0);
|
|
5236
|
+
rb_define_method(cTGGeometryGeom, "num_polygons", rb_tg_geometry_geom_num_polygons, 0);
|
|
5237
|
+
rb_define_method(cTGGeometryGeom, "polygon_at", rb_tg_geometry_geom_polygon_at, 1);
|
|
5238
|
+
rb_define_method(cTGGeometryGeom, "polygons", rb_tg_geometry_geom_polygons, 0);
|
|
5239
|
+
rb_define_method(cTGGeometryGeom, "num_geometries", rb_tg_geometry_geom_num_geometries, 0);
|
|
5240
|
+
rb_define_method(cTGGeometryGeom, "geometry_at", rb_tg_geometry_geom_geometry_at, 1);
|
|
5241
|
+
rb_define_method(cTGGeometryGeom, "geometries", rb_tg_geometry_geom_geometries, 0);
|
|
5242
|
+
|
|
5243
|
+
cTGGeometryLine = rb_define_class_under(mTGGeometry, "Line", rb_cObject);
|
|
5244
|
+
rb_undef_alloc_func(cTGGeometryLine);
|
|
5245
|
+
rb_define_method(cTGGeometryLine, "bbox", rb_tg_geometry_line_bbox, 0);
|
|
5246
|
+
rb_define_method(cTGGeometryLine, "num_points", rb_tg_geometry_line_num_points, 0);
|
|
5247
|
+
rb_define_method(cTGGeometryLine, "point_at", rb_tg_geometry_line_point_at, 1);
|
|
5248
|
+
rb_define_method(cTGGeometryLine, "points", rb_tg_geometry_line_points, 0);
|
|
5249
|
+
rb_define_method(cTGGeometryLine, "num_segments", rb_tg_geometry_line_num_segments, 0);
|
|
5250
|
+
rb_define_method(cTGGeometryLine, "segment_at", rb_tg_geometry_line_segment_at, 1);
|
|
5251
|
+
rb_define_method(cTGGeometryLine, "segments", rb_tg_geometry_line_segments, 0);
|
|
5252
|
+
rb_define_method(cTGGeometryLine, "length", rb_tg_geometry_line_length, 0);
|
|
5253
|
+
rb_define_method(cTGGeometryLine, "clockwise?", rb_tg_geometry_line_clockwise_p, 0);
|
|
5254
|
+
|
|
5255
|
+
cTGGeometryRing = rb_define_class_under(mTGGeometry, "Ring", rb_cObject);
|
|
5256
|
+
rb_undef_alloc_func(cTGGeometryRing);
|
|
5257
|
+
rb_define_method(cTGGeometryRing, "bbox", rb_tg_geometry_ring_bbox, 0);
|
|
5258
|
+
rb_define_method(cTGGeometryRing, "num_points", rb_tg_geometry_ring_num_points, 0);
|
|
5259
|
+
rb_define_method(cTGGeometryRing, "point_at", rb_tg_geometry_ring_point_at, 1);
|
|
5260
|
+
rb_define_method(cTGGeometryRing, "points", rb_tg_geometry_ring_points, 0);
|
|
5261
|
+
rb_define_method(cTGGeometryRing, "num_segments", rb_tg_geometry_ring_num_segments, 0);
|
|
5262
|
+
rb_define_method(cTGGeometryRing, "segment_at", rb_tg_geometry_ring_segment_at, 1);
|
|
5263
|
+
rb_define_method(cTGGeometryRing, "segments", rb_tg_geometry_ring_segments, 0);
|
|
5264
|
+
rb_define_method(cTGGeometryRing, "area", rb_tg_geometry_ring_area, 0);
|
|
5265
|
+
rb_define_method(cTGGeometryRing, "perimeter", rb_tg_geometry_ring_perimeter, 0);
|
|
5266
|
+
rb_define_method(cTGGeometryRing, "clockwise?", rb_tg_geometry_ring_clockwise_p, 0);
|
|
5267
|
+
rb_define_method(cTGGeometryRing, "convex?", rb_tg_geometry_ring_convex_p, 0);
|
|
5268
|
+
|
|
5269
|
+
cTGGeometryPolygon = rb_define_class_under(mTGGeometry, "Polygon", rb_cObject);
|
|
5270
|
+
rb_undef_alloc_func(cTGGeometryPolygon);
|
|
5271
|
+
rb_define_method(cTGGeometryPolygon, "bbox", rb_tg_geometry_polygon_bbox, 0);
|
|
5272
|
+
rb_define_method(cTGGeometryPolygon, "exterior_ring", rb_tg_geometry_polygon_exterior_ring, 0);
|
|
5273
|
+
rb_define_method(cTGGeometryPolygon, "num_holes", rb_tg_geometry_polygon_num_holes, 0);
|
|
5274
|
+
rb_define_method(cTGGeometryPolygon, "hole_at", rb_tg_geometry_polygon_hole_at, 1);
|
|
5275
|
+
rb_define_method(cTGGeometryPolygon, "holes", rb_tg_geometry_polygon_holes, 0);
|
|
5276
|
+
rb_define_method(cTGGeometryPolygon, "clockwise?", rb_tg_geometry_polygon_clockwise_p, 0);
|
|
5277
|
+
|
|
5278
|
+
cTGGeometrySegment = rb_define_class_under(mTGGeometry, "Segment", rb_cObject);
|
|
5279
|
+
rb_undef_alloc_func(cTGGeometrySegment);
|
|
5280
|
+
rb_define_method(cTGGeometrySegment, "a", rb_tg_geometry_segment_a, 0);
|
|
5281
|
+
rb_define_method(cTGGeometrySegment, "b", rb_tg_geometry_segment_b, 0);
|
|
5282
|
+
rb_define_method(cTGGeometrySegment, "points", rb_tg_geometry_segment_points, 0);
|
|
5283
|
+
rb_define_method(cTGGeometrySegment, "bbox", rb_tg_geometry_segment_bbox, 0);
|
|
5284
|
+
rb_define_method(cTGGeometrySegment, "intersects?", rb_tg_geometry_segment_intersects_p, 1);
|
|
5285
|
+
|
|
5286
|
+
cTGGeometryRect = rb_define_class_under(mTGGeometry, "Rect", rb_cObject);
|
|
5287
|
+
rb_define_alloc_func(cTGGeometryRect, rb_tg_geometry_rect_alloc);
|
|
5288
|
+
rb_define_method(cTGGeometryRect, "initialize", rb_tg_geometry_rect_initialize, -1);
|
|
5289
|
+
rb_define_method(cTGGeometryRect, "min_x", rb_tg_geometry_rect_min_x, 0);
|
|
5290
|
+
rb_define_method(cTGGeometryRect, "min_y", rb_tg_geometry_rect_min_y, 0);
|
|
5291
|
+
rb_define_method(cTGGeometryRect, "max_x", rb_tg_geometry_rect_max_x, 0);
|
|
5292
|
+
rb_define_method(cTGGeometryRect, "max_y", rb_tg_geometry_rect_max_y, 0);
|
|
5293
|
+
rb_define_method(cTGGeometryRect, "center", rb_tg_geometry_rect_center, 0);
|
|
5294
|
+
rb_define_method(cTGGeometryRect, "intersects?", rb_tg_geometry_rect_intersects_p, 1);
|
|
5295
|
+
rb_define_method(cTGGeometryRect, "contains_point?", rb_tg_geometry_rect_contains_point_p, 2);
|
|
5296
|
+
rb_define_method(cTGGeometryRect, "expand_to_include", rb_tg_geometry_rect_expand_to_include,
|
|
5297
|
+
1);
|
|
5298
|
+
rb_define_method(cTGGeometryRect, "expand_to_include_point",
|
|
5299
|
+
rb_tg_geometry_rect_expand_to_include_point, 2);
|
|
5300
|
+
|
|
5301
|
+
mTGGeometryFeatureSource = rb_define_module_under(mTGGeometry, "FeatureSource");
|
|
5302
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_entries_file",
|
|
5303
|
+
rb_tg_feature_source_read_entries_file, -1);
|
|
5304
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_entries_json",
|
|
5305
|
+
rb_tg_feature_source_read_entries_json, -1);
|
|
5306
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_entries_io",
|
|
5307
|
+
rb_tg_feature_source_read_entries_io, -1);
|
|
5308
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_features_file",
|
|
5309
|
+
rb_tg_feature_source_read_features_file, -1);
|
|
5310
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_features_json",
|
|
5311
|
+
rb_tg_feature_source_read_features_json, -1);
|
|
5312
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "read_features_io",
|
|
5313
|
+
rb_tg_feature_source_read_features_io, -1);
|
|
5314
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "build_index_file",
|
|
5315
|
+
rb_tg_feature_source_build_index_file, -1);
|
|
5316
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "build_index_json",
|
|
5317
|
+
rb_tg_feature_source_build_index_json, -1);
|
|
5318
|
+
rb_define_singleton_method(mTGGeometryFeatureSource, "build_index_io",
|
|
5319
|
+
rb_tg_feature_source_build_index_io, -1);
|
|
3017
5320
|
|
|
3018
5321
|
cTGGeometryIndex = rb_define_class_under(mTGGeometry, "Index", rb_cObject);
|
|
3019
5322
|
rb_undef_alloc_func(cTGGeometryIndex);
|