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.
data/docs/BENCHMARKING.md CHANGED
@@ -15,6 +15,9 @@ The repository includes these benchmark entry points:
15
15
  - `benchmark/falcon_concurrency.rb`
16
16
  - `benchmark/objectspace_memsize.rb`
17
17
  - `benchmark/rss_stability.rb`
18
+ - `benchmark/distance_point_geom.rb`
19
+ - `benchmark/distance_within_radius.rb`
20
+ - `benchmark/distance_memory_accounting.rb`
18
21
 
19
22
  Run after compiling the extension:
20
23
 
@@ -42,7 +45,11 @@ Benchmark generators cover:
42
45
  - flat vs rtree;
43
46
  - scalar vs packed batch;
44
47
  - parse small/medium/large geometry strings;
45
- - RSS stability over repeated build/query/free.
48
+ - RSS stability over repeated build/query/free;
49
+ - point-to-geometry distance over different vertex counts;
50
+ - radius search as `rtree prefilter + exact filter` versus brute-force full index scan;
51
+ - tiny-index/full-extent radius cases where the rtree prefilter may not help;
52
+ - distance receiver memory accounting before and after repeated calls.
46
53
 
47
54
  ## Output format
48
55
 
@@ -54,6 +61,26 @@ kind=compact n=1000 query=point lon=0.4 lat=0.4 flat_sec=... rtree_sec=... flat_
54
61
 
55
62
  These records are intentionally plain text so they can be redirected to files and compared across machines.
56
63
 
64
+
65
+ ## Distance benchmarks
66
+
67
+ `benchmark/distance_point_geom.rb` measures point-to-geometry distance method cost over geometries with different vertex counts. The `*_lnglat_meters` rows measure the local equirectangular frame overhead; they are not geodesic benchmarks.
68
+
69
+ `benchmark/distance_within_radius.rb` compares two query strategies:
70
+
71
+ - `rtree_prefilter_exact_filter`: existing rtree bbox prefilter followed by exact distance filtering;
72
+ - `brute_force_full_scan`: direct scan over every geometry with the same exact distance method.
73
+
74
+ Any speedup ratio from this script is a prefilter-vs-full-scan result. It must not be described as “distance is N times faster”. The benchmark intentionally includes selective-radius cases where the index should help and tiny-index/full-extent-radius cases where the prefilter may be neutral or slower. If rtree still wins on a machine, document that measured result instead of inventing a crossover.
75
+
76
+ `benchmark/distance_memory_accounting.rb` checks `ObjectSpace.memsize_of` before and after repeated distance calls. The expected receiver `delta B` is `0`; Ruby result allocations are still expected for returned arrays and `[id, distance]` pairs.
77
+
78
+ For noisy rows, especially short selective radius runs, increase timing before publishing numbers:
79
+
80
+ ```bash
81
+ TGEOMETRY_BENCH_MIN_SECONDS=1.0 bundle exec ruby benchmark/distance_within_radius.rb
82
+ ```
83
+
57
84
  ## No `:auto` strategy yet
58
85
 
59
86
  The first release does not expose `strategy: :auto`. Choosing a threshold requires project-owned benchmark output across the required scenario matrix. Internal rtree constants such as leaf capacity are not a flat-vs-rtree crossover threshold.
data/docs/CONCURRENCY.md CHANGED
@@ -69,3 +69,11 @@ No Falcon or Async performance claim is made. `benchmark/falcon_concurrency.rb`
69
69
  ## Low-level borrowed wrappers
70
70
 
71
71
  `TG::Geometry::Line`, `TG::Geometry::Ring`, `TG::Geometry::Polygon`, and borrowed GeometryCollection child `TG::Geometry::Geom` wrappers are immutable borrowed wrappers. They do not mutate or free child TG pointers. Each wrapper marks and compacts the parent `TG::Geometry::Geom` through `geom_owner`, so the parent native geometry remains alive while a child wrapper is in use.
72
+
73
+ ## Distance query concurrency
74
+
75
+ Point-to-geometry distance methods are read-only over immutable `Geom` and `Index` objects. They do not mutate geometry, mutate index entries, cache query state on receivers, or add persistent native memory. They do not call `rb_gc_adjust_memory_usage`.
76
+
77
+ `Index#within_distance_*` uses rtree callbacks only to mark candidate ordinals in C memory. Ruby arrays and `[id, distance]` pairs are materialized after `rtree_search` returns under the GVL. The callback safety rules above apply unchanged.
78
+
79
+ Distance methods deliberately keep the GVL. Do not treat them as no-GVL, Falcon/Async-aware, or Ractor-shareable APIs.
@@ -21,3 +21,45 @@ It does not affect `intersecting_geom_ids`, `covering_geom_ids`, or `containing_
21
21
  Results are arrays of ids in insertion order. Duplicate ids are preserved if duplicate ids were inserted.
22
22
 
23
23
  In v0.3.0 these operations run under the GVL. The heavy C phase is structured without Ruby API calls so it can be made no-GVL-safe later after benchmarking, but there is no public `release_gvl:` knob.
24
+
25
+ ## Point-to-geometry distance queries
26
+
27
+ Distance APIs are intentionally split by unit/model:
28
+
29
+ | Receiver | Method | Units / result |
30
+ | --- | --- | --- |
31
+ | `Geom` | `distance_to_lnglat_meters(lng, lat)` | `Float`, approximate meters |
32
+ | `Geom` | `boundary_distance_to_lnglat_meters(lng, lat)` | `Float`, approximate meters to nearest boundary/segment/point |
33
+ | `Geom` | `nearest_point_lnglat(lng, lat)` | `[lng, lat]`, raw planar nearest point |
34
+ | `Geom` | `distance_to_xy(x, y)` | `Float`, input coordinate units |
35
+ | `Geom` | `boundary_distance_to_xy(x, y)` | `Float`, input coordinate units |
36
+ | `Geom` | `nearest_point_xy(x, y)` | `[x, y]`, input coordinate units |
37
+ | `Index` | `within_distance_lnglat_meters(lng, lat, radius_m, sort: false)` | `[[id, distance_m], ...]` |
38
+ | `Index` | `within_distance_ids_lnglat_meters(lng, lat, radius_m)` | `[id, ...]` |
39
+ | `Index` | `within_distance_xy(x, y, radius, sort: false)` | `[[id, distance], ...]` |
40
+ | `Index` | `within_distance_ids_xy(x, y, radius)` | `[id, ...]` |
41
+
42
+ Coordinate order is always `(lng, lat)` for `*_lnglat_meters` and `(x, y)` for `*_xy`. There is no `metric:` keyword and no automatic coordinate-system detection.
43
+
44
+ Areal geometry semantics:
45
+
46
+ - `Polygon`, `MultiPolygon`, and areal `GeometryCollection` members return `0.0` from `distance_to_*` when the query point is inside the covered area or on the boundary.
47
+ - Holes are excluded: a point inside a hole measures to the nearest hole-ring segment.
48
+ - `boundary_distance_to_*` always measures to the nearest boundary/ring/segment. For an interior point it does not return `0.0` merely because the point is covered.
49
+ - `nearest_point_*` returns the nearest boundary point for areal types, including interior queries.
50
+
51
+ Non-areal geometry semantics:
52
+
53
+ - `Point` / `MultiPoint` measure to the nearest point.
54
+ - `LineString` / `MultiLineString` measure to the nearest segment.
55
+ - `GeometryCollection` returns the minimum over measurable members; empty members are skipped. A geometry with no measurable component raises `TG::Geometry::ArgumentError`.
56
+
57
+ Distance methods for lng/lat geometries return approximate meters using a per-query local equirectangular frame. Segments are GeoJSON straight coordinate segments, not great-circle arcs. This is geofencing-grade metric distance, not geodesy. Accuracy is intended for local geofencing and degrades with latitude separation.
58
+
59
+ The lng/lat metric is raw planar lng/lat. It does not wrap longitude at `+/-180`, does not split antimeridian boxes, and does not normalize returned `nearest_point_lnglat` longitudes. A geometry near `179.9` and a query near `-179.9` are treated as far apart in raw planar coordinates, consistent with `covers_xy?`. Cut antimeridian-crossing data at `+/-180` before import.
60
+
61
+ `Index#within_distance_*` uses two phases: one bbox prefilter through the existing index path and one exact distance filter over candidates. The returned distance is the exact filter value; it is not discarded and recomputed later. `sort: true` sorts the filtered pair array by ascending distance. The ids-only variants intentionally reject `sort:`.
62
+
63
+ Radius-query benchmarks should be read as `rtree prefilter + exact filter` versus brute-force full index scan. They measure the value of avoiding a full scan for selective radii, not a standalone speedup of the exact distance primitive. Tiny indexes and radii covering the whole data extent are benchmarked separately because the prefilter can become neutral or slower there.
64
+
65
+ No kNN, `nearest_ids`, rtree nearest traversal, projection/reprojection, signed distance, geometry-to-geometry distance, or geodesic distance is implemented by these APIs.
data/docs/LIMITATIONS.md CHANGED
@@ -22,3 +22,15 @@ TG works in planar XY coordinates. If lon/lat coordinates are passed in, length,
22
22
  FeatureSource reads the full source into memory. It avoids a Ruby `JSON.parse` object tree, but it is not a streaming backend. There is no gzip, NDGeoJSON, GeoJSONSeq, or FlatGeobuf support.
23
23
 
24
24
  FeatureSource returns raw properties JSON strings. It does not parse properties into Ruby Hash objects.
25
+
26
+ ## Distance limitations
27
+
28
+ `*_lnglat_meters` distance methods are approximate local-meter helpers for geofencing. They use a per-query local equirectangular frame anchored at the query latitude. Segments are GeoJSON straight coordinate segments, not great-circle arcs. Accuracy is intended for local geofencing and degrades with latitude separation. This is not geodesy.
29
+
30
+ The metric is raw planar lng/lat and does not wrap longitude at `+/-180`. Cross-antimeridian proximity is not detected: `179.9` and `-179.9` are treated as roughly `360` degrees apart. This matches the rest of the gem's planar model and `covers_xy?` behavior. Data that legitimately crosses the antimeridian should be cut at `+/-180` before import.
31
+
32
+ Near the poles, longitude scale approaches zero. Distance methods remain finite and avoid NaN/Inf, but the local frame understates longitude distance. Radius queries near poles may therefore include accepted false positives relative to real geodesy; that is an accuracy limitation of the planar metric, not a geodesic guarantee.
33
+
34
+ `*_xy` distance methods never convert units. They return input coordinate units.
35
+
36
+ Distance queries keep the GVL. There is no no-GVL, Falcon, Async, Ractor, projection/reprojection, kNN, `nearest_ids`, signed distance, polygon-to-polygon distance, or geometry-to-geometry distance support in this feature.