tg_geometry 0.3.1 → 0.3.2

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;
@@ -6346,6 +7167,7 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
6346
7167
  id_geometrycollection = rb_intern("geometrycollection");
6347
7168
  id_exterior = rb_intern("exterior");
6348
7169
  id_holes = rb_intern("holes");
7170
+ id_sort = rb_intern("sort");
6349
7171
 
6350
7172
  mTG = rb_define_module("TG");
6351
7173
  mTGGeometry = rb_define_module_under(mTG, "Geometry");
@@ -6397,6 +7219,16 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
6397
7219
  rb_define_method(cTGGeometryGeom, "touches?", rb_tg_geometry_geom_touches_p, 1);
6398
7220
  rb_define_method(cTGGeometryGeom, "intersects_rect?", rb_tg_geometry_geom_intersects_rect_p,
6399
7221
  -1);
7222
+ rb_define_method(cTGGeometryGeom, "distance_to_lnglat_meters",
7223
+ rb_tg_geometry_geom_distance_to_lnglat_meters, -1);
7224
+ rb_define_method(cTGGeometryGeom, "boundary_distance_to_lnglat_meters",
7225
+ rb_tg_geometry_geom_boundary_distance_to_lnglat_meters, -1);
7226
+ rb_define_method(cTGGeometryGeom, "nearest_point_lnglat",
7227
+ rb_tg_geometry_geom_nearest_point_lnglat, -1);
7228
+ rb_define_method(cTGGeometryGeom, "distance_to_xy", rb_tg_geometry_geom_distance_to_xy, -1);
7229
+ rb_define_method(cTGGeometryGeom, "boundary_distance_to_xy",
7230
+ rb_tg_geometry_geom_boundary_distance_to_xy, -1);
7231
+ rb_define_method(cTGGeometryGeom, "nearest_point_xy", rb_tg_geometry_geom_nearest_point_xy, -1);
6400
7232
  rb_define_method(cTGGeometryGeom, "to_geojson", rb_tg_geometry_geom_to_geojson, 0);
6401
7233
  rb_define_method(cTGGeometryGeom, "to_wkt", rb_tg_geometry_geom_to_wkt, 0);
6402
7234
  rb_define_method(cTGGeometryGeom, "to_wkb", rb_tg_geometry_geom_to_wkb, 0);
@@ -6532,6 +7364,14 @@ RUBY_FUNC_EXPORTED void Init_tg_geometry_ext_geometry_ext(void) {
6532
7364
  rb_tg_geometry_index_containing_geom_ids, 1);
6533
7365
  rb_define_method(cTGGeometryIndex, "intersecting_rect", rb_tg_geometry_index_intersecting_rect,
6534
7366
  4);
7367
+ rb_define_method(cTGGeometryIndex, "within_distance_lnglat_meters",
7368
+ rb_tg_geometry_index_within_distance_lnglat_meters, -1);
7369
+ rb_define_method(cTGGeometryIndex, "within_distance_ids_lnglat_meters",
7370
+ rb_tg_geometry_index_within_distance_ids_lnglat_meters, -1);
7371
+ rb_define_method(cTGGeometryIndex, "within_distance_xy",
7372
+ rb_tg_geometry_index_within_distance_xy, -1);
7373
+ rb_define_method(cTGGeometryIndex, "within_distance_ids_xy",
7374
+ rb_tg_geometry_index_within_distance_ids_xy, -1);
6535
7375
  rb_define_method(cTGGeometryIndex, "covering_ids_batch_packed",
6536
7376
  rb_tg_geometry_index_covering_ids_batch_packed, 1);
6537
7377
  rb_define_method(cTGGeometryIndex, "size", rb_tg_geometry_index_size, 0);