tg_geometry 0.3.1 → 0.3.3

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.
@@ -199,6 +199,7 @@ static ID id_multilinestring;
199
199
  static ID id_geometrycollection;
200
200
  static ID id_exterior;
201
201
  static ID id_holes;
202
+ static ID id_sort;
202
203
 
203
204
  #ifdef TG_DEBUG_TEST
204
205
  static bool tg_debug_fail_next_entries_alloc = false;
@@ -2904,6 +2905,540 @@ static VALUE rb_tg_geometry_nearest_segment_point(VALUE self) {
2904
2905
  return point_array_from_tg_point(w->point);
2905
2906
  }
2906
2907
 
2908
+ static struct tg_geom *tg_query_point_new(double x, double y);
2909
+ static void tg_query_point_raise_if_invalid(struct tg_geom *point);
2910
+
2911
+ static const double TG_DISTANCE_EARTH_RADIUS_M = 6371008.8;
2912
+ static const double TG_DISTANCE_DEG_TO_RAD = 0.017453292519943295769236907684886;
2913
+ static const double TG_DISTANCE_RAD_TO_DEG = 57.295779513082320876798154814105;
2914
+ static const double TG_DISTANCE_POLE_EPS = 1e-12;
2915
+
2916
+ typedef struct {
2917
+ double ref_x;
2918
+ double ref_y;
2919
+ double a;
2920
+ double b;
2921
+ } tg_distance_metric_t;
2922
+
2923
+ typedef struct {
2924
+ const tg_distance_metric_t *metric;
2925
+ double best_distance;
2926
+ struct tg_point best_point;
2927
+ bool found;
2928
+ } tg_distance_accum_t;
2929
+
2930
+ typedef struct {
2931
+ const tg_distance_metric_t *metric;
2932
+ struct tg_segment best_segment;
2933
+ long best_index;
2934
+ double best_distance;
2935
+ struct tg_point best_point;
2936
+ bool found;
2937
+ } tg_distance_nearest_ctx_t;
2938
+
2939
+ typedef struct {
2940
+ VALUE value;
2941
+ } tg_coerce_float_args_t;
2942
+
2943
+ static VALUE tg_coerce_float_body(VALUE arg) {
2944
+ tg_coerce_float_args_t *args = (tg_coerce_float_args_t *)arg;
2945
+ return rb_Float(args->value);
2946
+ }
2947
+
2948
+ static double tg_coerce_finite_double(VALUE value, const char *name) {
2949
+ tg_coerce_float_args_t args;
2950
+ VALUE coerced;
2951
+ double result;
2952
+ int state = 0;
2953
+
2954
+ args.value = value;
2955
+ coerced = rb_protect(tg_coerce_float_body, (VALUE)&args, &state);
2956
+ if (state) {
2957
+ rb_set_errinfo(Qnil);
2958
+ rb_raise(eTGGeometryArgumentError, "%s must be Float-coercible and finite", name);
2959
+ }
2960
+
2961
+ result = NUM2DBL(coerced);
2962
+ if (!isfinite(result)) {
2963
+ rb_raise(eTGGeometryArgumentError, "%s must be Float-coercible and finite", name);
2964
+ }
2965
+
2966
+ return result;
2967
+ }
2968
+
2969
+ static double tg_distance_lng_value(VALUE value) {
2970
+ double lng = tg_coerce_finite_double(value, "lng");
2971
+ if (lng < -180.0 || lng > 180.0) {
2972
+ rb_raise(eTGGeometryArgumentError, "lng must be in [-180.0, 180.0]");
2973
+ }
2974
+ return lng;
2975
+ }
2976
+
2977
+ static double tg_distance_lat_value(VALUE value) {
2978
+ double lat = tg_coerce_finite_double(value, "lat");
2979
+ if (lat < -90.0 || lat > 90.0) {
2980
+ rb_raise(eTGGeometryArgumentError, "lat must be in [-90.0, 90.0]");
2981
+ }
2982
+ return lat;
2983
+ }
2984
+
2985
+ static double tg_distance_radius_value(VALUE value, const char *name) {
2986
+ double radius = tg_coerce_finite_double(value, name);
2987
+ if (radius < 0.0) {
2988
+ rb_raise(eTGGeometryArgumentError, "%s must be >= 0.0", name);
2989
+ }
2990
+ return radius;
2991
+ }
2992
+
2993
+ static void tg_distance_parse_two_no_keywords(int argc, VALUE *argv, VALUE *a, VALUE *b) {
2994
+ VALUE kwargs = Qnil;
2995
+
2996
+ rb_scan_args(argc, argv, "20:", a, b, &kwargs);
2997
+ validate_keywords(kwargs, NULL, 0);
2998
+ }
2999
+
3000
+ static void tg_distance_metric_xy(tg_distance_metric_t *metric, double x, double y) {
3001
+ metric->ref_x = x;
3002
+ metric->ref_y = y;
3003
+ metric->a = 1.0;
3004
+ metric->b = 1.0;
3005
+ }
3006
+
3007
+ static void tg_distance_metric_lnglat(tg_distance_metric_t *metric, double lng, double lat) {
3008
+ double lat_ref_rad = lat * TG_DISTANCE_DEG_TO_RAD;
3009
+ double cos_lat = cos(lat_ref_rad);
3010
+
3011
+ if (fabs(cos_lat) < TG_DISTANCE_POLE_EPS) {
3012
+ cos_lat = 0.0;
3013
+ }
3014
+
3015
+ metric->ref_x = lng;
3016
+ metric->ref_y = lat;
3017
+ metric->a = TG_DISTANCE_EARTH_RADIUS_M * TG_DISTANCE_DEG_TO_RAD * cos_lat;
3018
+ metric->b = TG_DISTANCE_EARTH_RADIUS_M * TG_DISTANCE_DEG_TO_RAD;
3019
+ }
3020
+
3021
+ static struct tg_point tg_distance_map_point(const tg_distance_metric_t *metric,
3022
+ struct tg_point point) {
3023
+ struct tg_point mapped;
3024
+ mapped.x = metric->a * (point.x - metric->ref_x);
3025
+ mapped.y = metric->b * (point.y - metric->ref_y);
3026
+ return mapped;
3027
+ }
3028
+
3029
+ static struct tg_segment tg_distance_map_segment(const tg_distance_metric_t *metric,
3030
+ struct tg_segment seg) {
3031
+ struct tg_segment mapped;
3032
+ mapped.a = tg_distance_map_point(metric, seg.a);
3033
+ mapped.b = tg_distance_map_point(metric, seg.b);
3034
+ return mapped;
3035
+ }
3036
+
3037
+ static double tg_distance_point_distance(const tg_distance_metric_t *metric,
3038
+ struct tg_point point) {
3039
+ struct tg_point mapped = tg_distance_map_point(metric, point);
3040
+ return hypot(mapped.x, mapped.y);
3041
+ }
3042
+
3043
+ static double tg_distance_origin_to_segment(const tg_distance_metric_t *metric,
3044
+ struct tg_segment seg) {
3045
+ struct tg_point origin = {0.0, 0.0};
3046
+ struct tg_segment mapped = tg_distance_map_segment(metric, seg);
3047
+ return point_to_segment_distance(origin, mapped);
3048
+ }
3049
+
3050
+ static struct tg_point tg_distance_project_raw_point(const tg_distance_metric_t *metric,
3051
+ struct tg_segment raw_seg) {
3052
+ struct tg_segment mapped = tg_distance_map_segment(metric, raw_seg);
3053
+ double dx = mapped.b.x - mapped.a.x;
3054
+ double dy = mapped.b.y - mapped.a.y;
3055
+ double len_sq = dx * dx + dy * dy;
3056
+ double t;
3057
+ struct tg_point result;
3058
+
3059
+ if (len_sq == 0.0) {
3060
+ return raw_seg.a;
3061
+ }
3062
+
3063
+ t = (-(mapped.a.x * dx + mapped.a.y * dy)) / len_sq;
3064
+ if (t < 0.0)
3065
+ t = 0.0;
3066
+ if (t > 1.0)
3067
+ t = 1.0;
3068
+
3069
+ result.x = raw_seg.a.x + t * (raw_seg.b.x - raw_seg.a.x);
3070
+ result.y = raw_seg.a.y + t * (raw_seg.b.y - raw_seg.a.y);
3071
+ return result;
3072
+ }
3073
+
3074
+ static double tg_distance_metric_rect_distance(struct tg_rect rect,
3075
+ const tg_distance_metric_t *metric) {
3076
+ double min_x = metric->a * (rect.min.x - metric->ref_x);
3077
+ double max_x = metric->a * (rect.max.x - metric->ref_x);
3078
+ double min_y = metric->b * (rect.min.y - metric->ref_y);
3079
+ double max_y = metric->b * (rect.max.y - metric->ref_y);
3080
+ double dx = 0.0;
3081
+ double dy = 0.0;
3082
+
3083
+ if (min_x > max_x) {
3084
+ double tmp = min_x;
3085
+ min_x = max_x;
3086
+ max_x = tmp;
3087
+ }
3088
+ if (min_y > max_y) {
3089
+ double tmp = min_y;
3090
+ min_y = max_y;
3091
+ max_y = tmp;
3092
+ }
3093
+
3094
+ if (0.0 < min_x) {
3095
+ dx = min_x;
3096
+ } else if (0.0 > max_x) {
3097
+ dx = -max_x;
3098
+ }
3099
+
3100
+ if (0.0 < min_y) {
3101
+ dy = min_y;
3102
+ } else if (0.0 > max_y) {
3103
+ dy = -max_y;
3104
+ }
3105
+
3106
+ return hypot(dx, dy);
3107
+ }
3108
+
3109
+ static double tg_distance_nearest_rect_distance(struct tg_rect rect, int *more, void *udata) {
3110
+ tg_distance_nearest_ctx_t *ctx = (tg_distance_nearest_ctx_t *)udata;
3111
+ *more = 0;
3112
+ return tg_distance_metric_rect_distance(rect, ctx->metric);
3113
+ }
3114
+
3115
+ static double tg_distance_nearest_segment_distance(struct tg_segment seg, int *more, void *udata) {
3116
+ tg_distance_nearest_ctx_t *ctx = (tg_distance_nearest_ctx_t *)udata;
3117
+ *more = 0;
3118
+ return tg_distance_origin_to_segment(ctx->metric, seg);
3119
+ }
3120
+
3121
+ static bool tg_distance_nearest_segment_iter(struct tg_segment seg, double dist, int index,
3122
+ void *udata) {
3123
+ tg_distance_nearest_ctx_t *ctx = (tg_distance_nearest_ctx_t *)udata;
3124
+
3125
+ if (!ctx->found) {
3126
+ ctx->best_segment = seg;
3127
+ ctx->best_index = (long)index;
3128
+ ctx->best_distance = dist;
3129
+ ctx->best_point = tg_distance_project_raw_point(ctx->metric, seg);
3130
+ ctx->found = true;
3131
+ return false;
3132
+ }
3133
+
3134
+ return true;
3135
+ }
3136
+
3137
+ static void tg_distance_accum_consider(tg_distance_accum_t *accum, double distance,
3138
+ struct tg_point point) {
3139
+ if (!isfinite(distance)) {
3140
+ return;
3141
+ }
3142
+
3143
+ if (!accum->found || distance < accum->best_distance) {
3144
+ accum->best_distance = distance;
3145
+ accum->best_point = point;
3146
+ accum->found = true;
3147
+ }
3148
+ }
3149
+
3150
+ static void tg_distance_accum_point(tg_distance_accum_t *accum, struct tg_point point) {
3151
+ tg_distance_accum_consider(accum, tg_distance_point_distance(accum->metric, point), point);
3152
+ }
3153
+
3154
+ static bool tg_distance_accum_line(tg_distance_accum_t *accum, const struct tg_line *line) {
3155
+ tg_distance_nearest_ctx_t ctx;
3156
+ bool ok;
3157
+
3158
+ if (!line || tg_line_num_segments(line) <= 0) {
3159
+ return true;
3160
+ }
3161
+
3162
+ memset(&ctx, 0, sizeof(ctx));
3163
+ ctx.metric = accum->metric;
3164
+ ctx.best_index = -1;
3165
+ ctx.best_distance = INFINITY;
3166
+
3167
+ ok = tg_line_nearest_segment(line, tg_distance_nearest_rect_distance,
3168
+ tg_distance_nearest_segment_distance,
3169
+ tg_distance_nearest_segment_iter, &ctx);
3170
+ if (!ok) {
3171
+ return false;
3172
+ }
3173
+ if (ctx.found) {
3174
+ tg_distance_accum_consider(accum, ctx.best_distance, ctx.best_point);
3175
+ }
3176
+ return true;
3177
+ }
3178
+
3179
+ static bool tg_distance_accum_ring(tg_distance_accum_t *accum, const struct tg_ring *ring) {
3180
+ tg_distance_nearest_ctx_t ctx;
3181
+ bool ok;
3182
+
3183
+ if (!ring || tg_ring_num_segments(ring) <= 0) {
3184
+ return true;
3185
+ }
3186
+
3187
+ memset(&ctx, 0, sizeof(ctx));
3188
+ ctx.metric = accum->metric;
3189
+ ctx.best_index = -1;
3190
+ ctx.best_distance = INFINITY;
3191
+
3192
+ ok = tg_ring_nearest_segment(ring, tg_distance_nearest_rect_distance,
3193
+ tg_distance_nearest_segment_distance,
3194
+ tg_distance_nearest_segment_iter, &ctx);
3195
+ if (!ok) {
3196
+ return false;
3197
+ }
3198
+ if (ctx.found) {
3199
+ tg_distance_accum_consider(accum, ctx.best_distance, ctx.best_point);
3200
+ }
3201
+ return true;
3202
+ }
3203
+
3204
+ static bool tg_distance_accum_poly(tg_distance_accum_t *accum, const struct tg_poly *poly) {
3205
+ int nholes;
3206
+
3207
+ if (!poly) {
3208
+ return true;
3209
+ }
3210
+
3211
+ if (!tg_distance_accum_ring(accum, tg_poly_exterior(poly))) {
3212
+ return false;
3213
+ }
3214
+
3215
+ nholes = tg_poly_num_holes(poly);
3216
+ for (int i = 0; i < nholes; i++) {
3217
+ if (!tg_distance_accum_ring(accum, tg_poly_hole_at(poly, i))) {
3218
+ return false;
3219
+ }
3220
+ }
3221
+
3222
+ return true;
3223
+ }
3224
+
3225
+ static bool tg_distance_accum_geom(tg_distance_accum_t *accum, const struct tg_geom *geom) {
3226
+ int count;
3227
+
3228
+ if (!geom || tg_geom_is_empty(geom)) {
3229
+ return true;
3230
+ }
3231
+
3232
+ switch (tg_geom_typeof(geom)) {
3233
+ case TG_POINT:
3234
+ tg_distance_accum_point(accum, tg_geom_point(geom));
3235
+ return true;
3236
+ case TG_MULTIPOINT:
3237
+ count = tg_geom_num_points(geom);
3238
+ for (int i = 0; i < count; i++) {
3239
+ tg_distance_accum_point(accum, tg_geom_point_at(geom, i));
3240
+ }
3241
+ return true;
3242
+ case TG_LINESTRING:
3243
+ return tg_distance_accum_line(accum, tg_geom_line(geom));
3244
+ case TG_MULTILINESTRING:
3245
+ count = tg_geom_num_lines(geom);
3246
+ for (int i = 0; i < count; i++) {
3247
+ if (!tg_distance_accum_line(accum, tg_geom_line_at(geom, i))) {
3248
+ return false;
3249
+ }
3250
+ }
3251
+ return true;
3252
+ case TG_POLYGON:
3253
+ return tg_distance_accum_poly(accum, tg_geom_poly(geom));
3254
+ case TG_MULTIPOLYGON:
3255
+ count = tg_geom_num_polys(geom);
3256
+ for (int i = 0; i < count; i++) {
3257
+ if (!tg_distance_accum_poly(accum, tg_geom_poly_at(geom, i))) {
3258
+ return false;
3259
+ }
3260
+ }
3261
+ return true;
3262
+ case TG_GEOMETRYCOLLECTION:
3263
+ count = tg_geom_num_geometries(geom);
3264
+ for (int i = 0; i < count; i++) {
3265
+ if (!tg_distance_accum_geom(accum, tg_geom_geometry_at(geom, i))) {
3266
+ return false;
3267
+ }
3268
+ }
3269
+ return true;
3270
+ }
3271
+
3272
+ return true;
3273
+ }
3274
+
3275
+ static bool tg_distance_boundary_result(const struct tg_geom *geom,
3276
+ const tg_distance_metric_t *metric, double *distance_out,
3277
+ struct tg_point *point_out) {
3278
+ tg_distance_accum_t accum;
3279
+
3280
+ memset(&accum, 0, sizeof(accum));
3281
+ accum.metric = metric;
3282
+ accum.best_distance = INFINITY;
3283
+
3284
+ if (!tg_distance_accum_geom(&accum, geom)) {
3285
+ rb_raise(rb_eNoMemError, "nearest segment search failed");
3286
+ }
3287
+
3288
+ if (!accum.found) {
3289
+ return false;
3290
+ }
3291
+
3292
+ *distance_out = accum.best_distance;
3293
+ *point_out = accum.best_point;
3294
+ return true;
3295
+ }
3296
+
3297
+ static bool tg_distance_geom_covers_point(const struct tg_geom *geom, double x, double y) {
3298
+ struct tg_geom *point = tg_query_point_new(x, y);
3299
+ bool covered;
3300
+
3301
+ tg_query_point_raise_if_invalid(point);
3302
+ covered = tg_geom_covers(geom, point);
3303
+ tg_geom_free(point);
3304
+ return covered;
3305
+ }
3306
+
3307
+ static double tg_distance_to_geom(const struct tg_geom *geom, const tg_distance_metric_t *metric,
3308
+ double query_x, double query_y) {
3309
+ double distance = 0.0;
3310
+ struct tg_point nearest = {0.0, 0.0};
3311
+
3312
+ if (tg_distance_geom_covers_point(geom, query_x, query_y)) {
3313
+ return 0.0;
3314
+ }
3315
+
3316
+ if (!tg_distance_boundary_result(geom, metric, &distance, &nearest)) {
3317
+ rb_raise(eTGGeometryArgumentError, "geometry has no measurable component");
3318
+ }
3319
+
3320
+ return distance;
3321
+ }
3322
+
3323
+ static double tg_boundary_distance_to_geom(const struct tg_geom *geom,
3324
+ const tg_distance_metric_t *metric) {
3325
+ double distance = 0.0;
3326
+ struct tg_point nearest = {0.0, 0.0};
3327
+
3328
+ if (!tg_distance_boundary_result(geom, metric, &distance, &nearest)) {
3329
+ rb_raise(eTGGeometryArgumentError, "geometry has no measurable component");
3330
+ }
3331
+
3332
+ return distance;
3333
+ }
3334
+
3335
+ static struct tg_point tg_nearest_point_on_geom(const struct tg_geom *geom,
3336
+ const tg_distance_metric_t *metric) {
3337
+ double distance = 0.0;
3338
+ struct tg_point nearest = {0.0, 0.0};
3339
+
3340
+ if (!tg_distance_boundary_result(geom, metric, &distance, &nearest)) {
3341
+ rb_raise(eTGGeometryArgumentError, "geometry has no measurable component");
3342
+ }
3343
+
3344
+ return nearest;
3345
+ }
3346
+
3347
+ static VALUE rb_tg_geometry_geom_distance_to_xy(int argc, VALUE *argv, VALUE self) {
3348
+ tg_geom_wrapper_t *w = get_geom_wrapper(self);
3349
+ VALUE x_value, y_value;
3350
+ tg_distance_metric_t metric;
3351
+ double x;
3352
+ double y;
3353
+
3354
+ tg_distance_parse_two_no_keywords(argc, argv, &x_value, &y_value);
3355
+ x = tg_coerce_finite_double(x_value, "x");
3356
+ y = tg_coerce_finite_double(y_value, "y");
3357
+
3358
+ tg_distance_metric_xy(&metric, x, y);
3359
+ return rb_float_new(tg_distance_to_geom(w->geom, &metric, x, y));
3360
+ }
3361
+
3362
+ static VALUE rb_tg_geometry_geom_boundary_distance_to_xy(int argc, VALUE *argv, VALUE self) {
3363
+ tg_geom_wrapper_t *w = get_geom_wrapper(self);
3364
+ VALUE x_value, y_value;
3365
+ tg_distance_metric_t metric;
3366
+ double x;
3367
+ double y;
3368
+
3369
+ tg_distance_parse_two_no_keywords(argc, argv, &x_value, &y_value);
3370
+ x = tg_coerce_finite_double(x_value, "x");
3371
+ y = tg_coerce_finite_double(y_value, "y");
3372
+
3373
+ tg_distance_metric_xy(&metric, x, y);
3374
+ return rb_float_new(tg_boundary_distance_to_geom(w->geom, &metric));
3375
+ }
3376
+
3377
+ static VALUE rb_tg_geometry_geom_nearest_point_xy(int argc, VALUE *argv, VALUE self) {
3378
+ tg_geom_wrapper_t *w = get_geom_wrapper(self);
3379
+ VALUE x_value, y_value;
3380
+ tg_distance_metric_t metric;
3381
+ struct tg_point nearest;
3382
+ double x;
3383
+ double y;
3384
+
3385
+ tg_distance_parse_two_no_keywords(argc, argv, &x_value, &y_value);
3386
+ x = tg_coerce_finite_double(x_value, "x");
3387
+ y = tg_coerce_finite_double(y_value, "y");
3388
+
3389
+ tg_distance_metric_xy(&metric, x, y);
3390
+ nearest = tg_nearest_point_on_geom(w->geom, &metric);
3391
+ return point_array_from_tg_point(nearest);
3392
+ }
3393
+
3394
+ static VALUE rb_tg_geometry_geom_distance_to_lnglat_meters(int argc, VALUE *argv, VALUE self) {
3395
+ tg_geom_wrapper_t *w = get_geom_wrapper(self);
3396
+ VALUE lng_value, lat_value;
3397
+ tg_distance_metric_t metric;
3398
+ double lng;
3399
+ double lat;
3400
+
3401
+ tg_distance_parse_two_no_keywords(argc, argv, &lng_value, &lat_value);
3402
+ lng = tg_distance_lng_value(lng_value);
3403
+ lat = tg_distance_lat_value(lat_value);
3404
+
3405
+ tg_distance_metric_lnglat(&metric, lng, lat);
3406
+ return rb_float_new(tg_distance_to_geom(w->geom, &metric, lng, lat));
3407
+ }
3408
+
3409
+ static VALUE rb_tg_geometry_geom_boundary_distance_to_lnglat_meters(int argc, VALUE *argv,
3410
+ VALUE self) {
3411
+ tg_geom_wrapper_t *w = get_geom_wrapper(self);
3412
+ VALUE lng_value, lat_value;
3413
+ tg_distance_metric_t metric;
3414
+ double lng;
3415
+ double lat;
3416
+
3417
+ tg_distance_parse_two_no_keywords(argc, argv, &lng_value, &lat_value);
3418
+ lng = tg_distance_lng_value(lng_value);
3419
+ lat = tg_distance_lat_value(lat_value);
3420
+
3421
+ tg_distance_metric_lnglat(&metric, lng, lat);
3422
+ return rb_float_new(tg_boundary_distance_to_geom(w->geom, &metric));
3423
+ }
3424
+
3425
+ static VALUE rb_tg_geometry_geom_nearest_point_lnglat(int argc, VALUE *argv, VALUE self) {
3426
+ tg_geom_wrapper_t *w = get_geom_wrapper(self);
3427
+ VALUE lng_value, lat_value;
3428
+ tg_distance_metric_t metric;
3429
+ struct tg_point nearest;
3430
+ double lng;
3431
+ double lat;
3432
+
3433
+ tg_distance_parse_two_no_keywords(argc, argv, &lng_value, &lat_value);
3434
+ lng = tg_distance_lng_value(lng_value);
3435
+ lat = tg_distance_lat_value(lat_value);
3436
+
3437
+ tg_distance_metric_lnglat(&metric, lng, lat);
3438
+ nearest = tg_nearest_point_on_geom(w->geom, &metric);
3439
+ return point_array_from_tg_point(nearest);
3440
+ }
3441
+
2907
3442
  static tg_index_t *get_index_wrapper(VALUE value) {
2908
3443
  tg_index_t *idx;
2909
3444
 
@@ -3584,6 +4119,292 @@ static VALUE rb_tg_geometry_index_containing_geom_ids(VALUE self, VALUE geom) {
3584
4119
  return index_geom_query_ids(self, geom, TG_GEOMETRY_GEOM_QUERY_CONTAINS);
3585
4120
  }
3586
4121
 
4122
+ typedef struct {
4123
+ VALUE id;
4124
+ double distance;
4125
+ long ordinal;
4126
+ } tg_distance_match_t;
4127
+
4128
+ static int tg_distance_match_compare(const void *a, const void *b) {
4129
+ const tg_distance_match_t *ma = (const tg_distance_match_t *)a;
4130
+ const tg_distance_match_t *mb = (const tg_distance_match_t *)b;
4131
+
4132
+ if (ma->distance < mb->distance)
4133
+ return -1;
4134
+ if (ma->distance > mb->distance)
4135
+ return 1;
4136
+ if (ma->ordinal < mb->ordinal)
4137
+ return -1;
4138
+ if (ma->ordinal > mb->ordinal)
4139
+ return 1;
4140
+ return 0;
4141
+ }
4142
+
4143
+ static bool tg_distance_bbox_intersects(const tg_index_entry_t *entry, struct tg_rect rect) {
4144
+ return tg_rect_intersects_rect(entry->bbox, rect);
4145
+ }
4146
+
4147
+ static struct tg_rect tg_distance_lnglat_prefilter_rect(double lng, double lat, double radius_m) {
4148
+ double lat_ref_rad = lat * TG_DISTANCE_DEG_TO_RAD;
4149
+ double cos_lat = cos(lat_ref_rad);
4150
+ double delta_lat = (radius_m / TG_DISTANCE_EARTH_RADIUS_M) * TG_DISTANCE_RAD_TO_DEG;
4151
+ double min_lat = lat - delta_lat;
4152
+ double max_lat = lat + delta_lat;
4153
+ double min_lng;
4154
+ double max_lng;
4155
+
4156
+ if (min_lat < -90.0)
4157
+ min_lat = -90.0;
4158
+ if (max_lat > 90.0)
4159
+ max_lat = 90.0;
4160
+
4161
+ if (fabs(cos_lat) < TG_DISTANCE_POLE_EPS) {
4162
+ min_lng = -180.0;
4163
+ max_lng = 180.0;
4164
+ } else {
4165
+ double delta_lng = delta_lat / cos_lat;
4166
+ if (delta_lng >= 180.0) {
4167
+ min_lng = -180.0;
4168
+ max_lng = 180.0;
4169
+ } else {
4170
+ min_lng = lng - delta_lng;
4171
+ max_lng = lng + delta_lng;
4172
+ }
4173
+ }
4174
+
4175
+ return tg_rect_from_xyxy(min_lng, min_lat, max_lng, max_lat);
4176
+ }
4177
+
4178
+ static unsigned char *tg_distance_candidate_marks(tg_index_t *idx, struct tg_rect query_rect) {
4179
+ unsigned char *marks;
4180
+
4181
+ if (idx->len == 0) {
4182
+ return NULL;
4183
+ }
4184
+
4185
+ if (idx->strategy == TG_GEOMETRY_INDEX_STRATEGY_RTREE) {
4186
+ return rtree_candidate_marks(idx, query_rect);
4187
+ }
4188
+
4189
+ marks = alloc_match_marks(idx->len);
4190
+ for (long i = 0; i < idx->len; i++) {
4191
+ if (tg_distance_bbox_intersects(&idx->entries[i], query_rect)) {
4192
+ marks[i] = 1;
4193
+ }
4194
+ }
4195
+ return marks;
4196
+ }
4197
+
4198
+ static VALUE tg_distance_build_pairs(tg_distance_match_t *matches, long count) {
4199
+ VALUE result = rb_ary_new_capa(count);
4200
+
4201
+ for (long i = 0; i < count; i++) {
4202
+ VALUE pair = rb_ary_new_capa(2);
4203
+ rb_ary_push(pair, matches[i].id);
4204
+ rb_ary_push(pair, rb_float_new(matches[i].distance));
4205
+ rb_ary_push(result, pair);
4206
+ }
4207
+
4208
+ return result;
4209
+ }
4210
+
4211
+ static VALUE tg_distance_build_ids(tg_distance_match_t *matches, long count) {
4212
+ VALUE result = rb_ary_new_capa(count);
4213
+
4214
+ for (long i = 0; i < count; i++) {
4215
+ rb_ary_push(result, matches[i].id);
4216
+ }
4217
+
4218
+ return result;
4219
+ }
4220
+
4221
+ typedef struct {
4222
+ tg_index_t *idx;
4223
+ struct tg_rect query_rect;
4224
+ const tg_distance_metric_t *metric;
4225
+ double query_x;
4226
+ double query_y;
4227
+ double radius;
4228
+ bool sort;
4229
+ bool ids_only;
4230
+ unsigned char *marks;
4231
+ tg_distance_match_t *matches;
4232
+ long count;
4233
+ } tg_index_within_distance_args_t;
4234
+
4235
+ static VALUE tg_index_within_distance_body(VALUE arg) {
4236
+ tg_index_within_distance_args_t *args = (tg_index_within_distance_args_t *)arg;
4237
+
4238
+ if (args->idx->len == 0) {
4239
+ return rb_ary_new();
4240
+ }
4241
+
4242
+ if ((size_t)args->idx->len > SIZE_MAX / sizeof(*args->matches)) {
4243
+ rb_raise(rb_eNoMemError, "distance result allocation overflow");
4244
+ }
4245
+
4246
+ args->marks = tg_distance_candidate_marks(args->idx, args->query_rect);
4247
+ args->matches = (tg_distance_match_t *)calloc((size_t)args->idx->len, sizeof(*args->matches));
4248
+ if (!args->matches) {
4249
+ rb_raise(rb_eNoMemError, "distance result allocation failed");
4250
+ }
4251
+
4252
+ for (long i = 0; i < args->idx->len; i++) {
4253
+ double distance;
4254
+
4255
+ if (!args->marks[i]) {
4256
+ continue;
4257
+ }
4258
+
4259
+ distance = tg_distance_to_geom(args->idx->entries[i].geom, args->metric, args->query_x,
4260
+ args->query_y);
4261
+ if (distance <= args->radius) {
4262
+ args->matches[args->count].id = args->idx->entries[i].id;
4263
+ args->matches[args->count].distance = distance;
4264
+ args->matches[args->count].ordinal = i;
4265
+ args->count++;
4266
+ }
4267
+ }
4268
+
4269
+ if (args->sort && args->count > 1) {
4270
+ qsort(args->matches, (size_t)args->count, sizeof(*args->matches),
4271
+ tg_distance_match_compare);
4272
+ }
4273
+
4274
+ return args->ids_only ? tg_distance_build_ids(args->matches, args->count)
4275
+ : tg_distance_build_pairs(args->matches, args->count);
4276
+ }
4277
+
4278
+ static VALUE tg_index_within_distance_ensure(VALUE arg) {
4279
+ tg_index_within_distance_args_t *args = (tg_index_within_distance_args_t *)arg;
4280
+
4281
+ free(args->marks);
4282
+ args->marks = NULL;
4283
+ free(args->matches);
4284
+ args->matches = NULL;
4285
+ return Qnil;
4286
+ }
4287
+
4288
+ static VALUE tg_index_within_distance_common(tg_index_t *idx, struct tg_rect query_rect,
4289
+ const tg_distance_metric_t *metric, double query_x,
4290
+ double query_y, double radius, bool sort,
4291
+ bool ids_only) {
4292
+ tg_index_within_distance_args_t args;
4293
+
4294
+ memset(&args, 0, sizeof(args));
4295
+ args.idx = idx;
4296
+ args.query_rect = query_rect;
4297
+ args.metric = metric;
4298
+ args.query_x = query_x;
4299
+ args.query_y = query_y;
4300
+ args.radius = radius;
4301
+ args.sort = sort;
4302
+ args.ids_only = ids_only;
4303
+
4304
+ return rb_ensure(tg_index_within_distance_body, (VALUE)&args, tg_index_within_distance_ensure,
4305
+ (VALUE)&args);
4306
+ }
4307
+
4308
+ static bool tg_distance_parse_sort_kw(int argc, VALUE *argv, VALUE *a, VALUE *b, VALUE *radius) {
4309
+ VALUE kwargs = Qnil;
4310
+ ID local_allowed[1];
4311
+
4312
+ rb_scan_args(argc, argv, "30:", a, b, radius, &kwargs);
4313
+
4314
+ local_allowed[0] = id_sort;
4315
+ validate_keywords(kwargs, local_allowed, 1);
4316
+ return RTEST(kwargs_value(kwargs, id_sort, Qfalse));
4317
+ }
4318
+
4319
+ static void tg_distance_parse_no_keywords(int argc, VALUE *argv, VALUE *a, VALUE *b,
4320
+ VALUE *radius) {
4321
+ VALUE kwargs = Qnil;
4322
+
4323
+ rb_scan_args(argc, argv, "30:", a, b, radius, &kwargs);
4324
+ validate_keywords(kwargs, NULL, 0);
4325
+ }
4326
+
4327
+ static VALUE rb_tg_geometry_index_within_distance_xy(int argc, VALUE *argv, VALUE self) {
4328
+ tg_index_t *idx = get_index_wrapper(self);
4329
+ VALUE x_value, y_value, radius_value;
4330
+ tg_distance_metric_t metric;
4331
+ struct tg_rect query_rect;
4332
+ double x;
4333
+ double y;
4334
+ double radius;
4335
+ bool sort = tg_distance_parse_sort_kw(argc, argv, &x_value, &y_value, &radius_value);
4336
+
4337
+ x = tg_coerce_finite_double(x_value, "x");
4338
+ y = tg_coerce_finite_double(y_value, "y");
4339
+ radius = tg_distance_radius_value(radius_value, "radius");
4340
+ tg_distance_metric_xy(&metric, x, y);
4341
+ query_rect = tg_rect_from_xyxy(x - radius, y - radius, x + radius, y + radius);
4342
+
4343
+ RB_GC_GUARD(self);
4344
+ return tg_index_within_distance_common(idx, query_rect, &metric, x, y, radius, sort, false);
4345
+ }
4346
+
4347
+ static VALUE rb_tg_geometry_index_within_distance_ids_xy(int argc, VALUE *argv, VALUE self) {
4348
+ tg_index_t *idx = get_index_wrapper(self);
4349
+ VALUE x_value, y_value, radius_value;
4350
+ tg_distance_metric_t metric;
4351
+ struct tg_rect query_rect;
4352
+ double x;
4353
+ double y;
4354
+ double radius;
4355
+
4356
+ tg_distance_parse_no_keywords(argc, argv, &x_value, &y_value, &radius_value);
4357
+ x = tg_coerce_finite_double(x_value, "x");
4358
+ y = tg_coerce_finite_double(y_value, "y");
4359
+ radius = tg_distance_radius_value(radius_value, "radius");
4360
+ tg_distance_metric_xy(&metric, x, y);
4361
+ query_rect = tg_rect_from_xyxy(x - radius, y - radius, x + radius, y + radius);
4362
+
4363
+ RB_GC_GUARD(self);
4364
+ return tg_index_within_distance_common(idx, query_rect, &metric, x, y, radius, false, true);
4365
+ }
4366
+
4367
+ static VALUE rb_tg_geometry_index_within_distance_lnglat_meters(int argc, VALUE *argv, VALUE self) {
4368
+ tg_index_t *idx = get_index_wrapper(self);
4369
+ VALUE lng_value, lat_value, radius_value;
4370
+ tg_distance_metric_t metric;
4371
+ struct tg_rect query_rect;
4372
+ double lng;
4373
+ double lat;
4374
+ double radius;
4375
+ bool sort = tg_distance_parse_sort_kw(argc, argv, &lng_value, &lat_value, &radius_value);
4376
+
4377
+ lng = tg_distance_lng_value(lng_value);
4378
+ lat = tg_distance_lat_value(lat_value);
4379
+ radius = tg_distance_radius_value(radius_value, "radius_m");
4380
+ tg_distance_metric_lnglat(&metric, lng, lat);
4381
+ query_rect = tg_distance_lnglat_prefilter_rect(lng, lat, radius);
4382
+
4383
+ RB_GC_GUARD(self);
4384
+ return tg_index_within_distance_common(idx, query_rect, &metric, lng, lat, radius, sort, false);
4385
+ }
4386
+
4387
+ static VALUE rb_tg_geometry_index_within_distance_ids_lnglat_meters(int argc, VALUE *argv,
4388
+ VALUE self) {
4389
+ tg_index_t *idx = get_index_wrapper(self);
4390
+ VALUE lng_value, lat_value, radius_value;
4391
+ tg_distance_metric_t metric;
4392
+ struct tg_rect query_rect;
4393
+ double lng;
4394
+ double lat;
4395
+ double radius;
4396
+
4397
+ tg_distance_parse_no_keywords(argc, argv, &lng_value, &lat_value, &radius_value);
4398
+ lng = tg_distance_lng_value(lng_value);
4399
+ lat = tg_distance_lat_value(lat_value);
4400
+ radius = tg_distance_radius_value(radius_value, "radius_m");
4401
+ tg_distance_metric_lnglat(&metric, lng, lat);
4402
+ query_rect = tg_distance_lnglat_prefilter_rect(lng, lat, radius);
4403
+
4404
+ RB_GC_GUARD(self);
4405
+ return tg_index_within_distance_common(idx, query_rect, &metric, lng, lat, radius, false, true);
4406
+ }
4407
+
3587
4408
  typedef struct {
3588
4409
  tg_index_t *idx;
3589
4410
  VALUE entries;
@@ -5271,8 +6092,6 @@ static char *fs_safe_tg_error_message(struct tg_geom *geom) {
5271
6092
  char *msg;
5272
6093
  int n;
5273
6094
 
5274
- if (!geom)
5275
- return fs_safe_strdup_cstr("TG geometry allocation failed");
5276
6095
  err = tg_geom_error(geom);
5277
6096
  if (!err)
5278
6097
  return NULL;
@@ -5291,12 +6110,13 @@ static bool fs_safe_validate_geometry(fs_safe_job_t *job, struct json geometry,
5291
6110
  *skipped_out = false;
5292
6111
  struct tg_geom *geom = tg_parse_geojsonn_ix(json_raw(geometry), json_raw_length(geometry),
5293
6112
  job->opts.geometry_index);
5294
- char *message = fs_safe_tg_error_message(geom);
6113
+ char *message;
5295
6114
 
5296
6115
  if (!geom) {
5297
6116
  fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "TG geometry allocation failed");
5298
6117
  return false;
5299
6118
  }
6119
+ message = fs_safe_tg_error_message(geom);
5300
6120
 
5301
6121
  if (message) {
5302
6122
  if (job->opts.on_invalid == FS_ON_INVALID_SKIP) {
@@ -5430,11 +6250,11 @@ static bool fs_safe_fill_build(fs_safe_job_t *job, struct json features) {
5430
6250
 
5431
6251
  geom = tg_parse_geojsonn_ix(json_raw(prepared.geometry), json_raw_length(prepared.geometry),
5432
6252
  job->opts.geometry_index);
5433
- message = fs_safe_tg_error_message(geom);
5434
6253
  if (!geom) {
5435
6254
  fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "TG geometry allocation failed");
5436
6255
  return false;
5437
6256
  }
6257
+ message = fs_safe_tg_error_message(geom);
5438
6258
  if (message) {
5439
6259
  fs_safe_set_feature_error(job, FS_SAFE_ERROR_PARSE, feature_index,
5440
6260
  fs_safe_json_offset(job, prepared.geometry), message);
@@ -6346,6 +7166,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
6346
7166
  id_geometrycollection = rb_intern("geometrycollection");
6347
7167
  id_exterior = rb_intern("exterior");
6348
7168
  id_holes = rb_intern("holes");
7169
+ id_sort = rb_intern("sort");
6349
7170
 
6350
7171
  mTG = rb_define_module("TG");
6351
7172
  mTGGeometry = rb_define_module_under(mTG, "Geometry");
@@ -6397,6 +7218,16 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
6397
7218
  rb_define_method(cTGGeometryGeom, "touches?", rb_tg_geometry_geom_touches_p, 1);
6398
7219
  rb_define_method(cTGGeometryGeom, "intersects_rect?", rb_tg_geometry_geom_intersects_rect_p,
6399
7220
  -1);
7221
+ rb_define_method(cTGGeometryGeom, "distance_to_lnglat_meters",
7222
+ rb_tg_geometry_geom_distance_to_lnglat_meters, -1);
7223
+ rb_define_method(cTGGeometryGeom, "boundary_distance_to_lnglat_meters",
7224
+ rb_tg_geometry_geom_boundary_distance_to_lnglat_meters, -1);
7225
+ rb_define_method(cTGGeometryGeom, "nearest_point_lnglat",
7226
+ rb_tg_geometry_geom_nearest_point_lnglat, -1);
7227
+ rb_define_method(cTGGeometryGeom, "distance_to_xy", rb_tg_geometry_geom_distance_to_xy, -1);
7228
+ rb_define_method(cTGGeometryGeom, "boundary_distance_to_xy",
7229
+ rb_tg_geometry_geom_boundary_distance_to_xy, -1);
7230
+ rb_define_method(cTGGeometryGeom, "nearest_point_xy", rb_tg_geometry_geom_nearest_point_xy, -1);
6400
7231
  rb_define_method(cTGGeometryGeom, "to_geojson", rb_tg_geometry_geom_to_geojson, 0);
6401
7232
  rb_define_method(cTGGeometryGeom, "to_wkt", rb_tg_geometry_geom_to_wkt, 0);
6402
7233
  rb_define_method(cTGGeometryGeom, "to_wkb", rb_tg_geometry_geom_to_wkb, 0);
@@ -6532,6 +7363,14 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
6532
7363
  rb_tg_geometry_index_containing_geom_ids, 1);
6533
7364
  rb_define_method(cTGGeometryIndex, "intersecting_rect", rb_tg_geometry_index_intersecting_rect,
6534
7365
  4);
7366
+ rb_define_method(cTGGeometryIndex, "within_distance_lnglat_meters",
7367
+ rb_tg_geometry_index_within_distance_lnglat_meters, -1);
7368
+ rb_define_method(cTGGeometryIndex, "within_distance_ids_lnglat_meters",
7369
+ rb_tg_geometry_index_within_distance_ids_lnglat_meters, -1);
7370
+ rb_define_method(cTGGeometryIndex, "within_distance_xy",
7371
+ rb_tg_geometry_index_within_distance_xy, -1);
7372
+ rb_define_method(cTGGeometryIndex, "within_distance_ids_xy",
7373
+ rb_tg_geometry_index_within_distance_ids_xy, -1);
6535
7374
  rb_define_method(cTGGeometryIndex, "covering_ids_batch_packed",
6536
7375
  rb_tg_geometry_index_covering_ids_batch_packed, 1);
6537
7376
  rb_define_method(cTGGeometryIndex, "size", rb_tg_geometry_index_size, 0);