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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -79
  3. data/README.md +82 -191
  4. data/Rakefile +3 -3
  5. data/benchmark/falcon_concurrency.rb +1 -1
  6. data/benchmark/feature_source.rb +92 -0
  7. data/docs/ARCHITECTURE.md +29 -107
  8. data/docs/BENCHMARKING.md +20 -1
  9. data/docs/CASUAL_EXAMPLE.md +71 -458
  10. data/docs/CONCURRENCY.md +13 -7
  11. data/docs/ERROR_HANDLING.md +30 -0
  12. data/docs/FEATURE_SOURCE.md +166 -0
  13. data/docs/LIMITATIONS.md +11 -50
  14. data/docs/MEMORY_OWNERSHIP.md +20 -2
  15. data/ext/tg_geometry/extconf.rb +46 -4
  16. data/ext/tg_geometry/tg_geometry_ext.c +2453 -150
  17. data/ext/tg_geometry/tg_geometry_vendor_json.c +17 -0
  18. data/ext/tg_geometry/tg_geometry_vendor_tg.c +3 -0
  19. data/ext/tg_geometry/vendor/.vendored +8 -2
  20. data/ext/tg_geometry/vendor/json/LICENSE +20 -0
  21. data/ext/tg_geometry/vendor/json/VERSION +3 -0
  22. data/ext/tg_geometry/vendor/json/json.c +1024 -0
  23. data/ext/tg_geometry/vendor/json/json.h +207 -0
  24. data/lib/tg/geometry/registry.rb +3 -3
  25. data/lib/tg/geometry/version.rb +1 -1
  26. data/script/vendor_libs.rb +22 -6
  27. data/spec/{expansion_a_auto_strategy_spec.rb → auto_strategy_spec.rb} +1 -1
  28. data/spec/{block_12_batch_packed_spec.rb → batch_packed_spec.rb} +1 -1
  29. data/spec/{block_20_concurrency_spec.rb → concurrency_spec.rb} +1 -1
  30. data/spec/{block_13_error_hardening_spec.rb → error_hardening_spec.rb} +1 -1
  31. data/spec/feature_source_nogvl_spec.rb +51 -0
  32. data/spec/feature_source_spec.rb +268 -0
  33. data/spec/{expansion_d_format_coverage_spec.rb → format_coverage_spec.rb} +1 -1
  34. data/spec/{block_20_fuzz_spec.rb → fuzz_spec.rb} +1 -1
  35. data/spec/{block_4_geom_api_spec.rb → geom_api_spec.rb} +1 -1
  36. data/spec/{block_3_geom_parse_spec.rb → geom_parse_spec.rb} +1 -1
  37. data/spec/{block_8_index_borrowed_geometry_spec.rb → index_borrowed_geometry_spec.rb} +1 -1
  38. data/spec/{block_6_index_build_spec.rb → index_build_spec.rb} +2 -2
  39. data/spec/{block_9_flat_query_spec.rb → index_flat_query_spec.rb} +1 -1
  40. data/spec/{block_7_index_owned_geometry_spec.rb → index_owned_geometry_spec.rb} +1 -1
  41. data/spec/{block_10_rtree_strategy_spec.rb → index_rtree_accounting_spec.rb} +1 -1
  42. data/spec/{block_11_rtree_order_spec.rb → index_rtree_order_spec.rb} +1 -1
  43. data/spec/{block_1_skeleton_spec.rb → load_and_errors_spec.rb} +1 -1
  44. data/spec/{expansion_e_low_level_geometry_spec.rb → low_level_geometry_spec.rb} +1 -1
  45. data/spec/{block_14_memory_gc_hardening_spec.rb → memory_gc_spec.rb} +1 -1
  46. data/spec/{expansion_i_ractor_spec.rb → ractor_spec.rb} +1 -1
  47. data/spec/{block_5_rect_api_spec.rb → rect_api_spec.rb} +1 -1
  48. data/spec/{expansion_b_registry_spec.rb → registry_spec.rb} +1 -1
  49. data/spec/{expansion_j_full_tg_api_coverage_spec.rb → tg_api_coverage_spec.rb} +1 -1
  50. data/spec/{block_2_vendor_spec.rb → vendor_sources_spec.rb} +4 -4
  51. metadata +39 -38
  52. data/docs/ACTIVE_RECORD.md +0 -26
  53. data/docs/AUTO_STRATEGY.md +0 -15
  54. data/docs/EXPANSION_E_TO_H_STATUS.md +0 -51
  55. data/docs/FORMAT_COVERAGE.md +0 -23
  56. data/docs/FULL_TG_API_COVERAGE.md +0 -109
  57. data/docs/LOW_LEVEL_GEOMETRY.md +0 -121
  58. data/docs/RACTOR.md +0 -40
  59. data/docs/REGISTRY.md +0 -37
  60. data/docs/RELEASE_CHECKLIST.md +0 -39
  61. /data/spec/{expansion_c_active_record_source_spec.rb → active_record_source_spec.rb} +0 -0
data/docs/ARCHITECTURE.md CHANGED
@@ -1,130 +1,52 @@
1
- # tg_geometry architecture
1
+ # Architecture
2
2
 
3
- `tg_geometry` is a Ruby C extension for the public namespace `TG::Geometry`. It vendors the upstream `tidwall/tg` geometry engine and `tidwall/rtree.c`; it does not depend on GEOS, PostGIS, PROJ, GDAL, system TG, or system rtree libraries.
3
+ `tg_geometry` is a Ruby C extension around vendored `tidwall/tg`, `tidwall/rtree.c`, and `tidwall/json.c`.
4
4
 
5
- The gem targets a small production-grade core:
5
+ The public Ruby API is under `TG::Geometry`. The native extension owns the hot paths for parsing, predicates, indexing, batch point lookup, and GeoJSON FeatureCollection extraction.
6
6
 
7
- - parsing and writing TG geometries from Ruby;
8
- - exact planar geometry predicates;
9
- - immutable rectangles for bounding boxes and query windows;
10
- - immutable geofencing-oriented indexes that return user ids;
11
- - flat and rtree collection-level search strategies;
12
- - native-endian packed point batches for high-throughput same-process calls;
13
- - read-only borrowed low-level Line/Ring/Polygon wrappers;
14
- - grouped TG API coverage for predicates, accessors, point/empty constructors, Segment values, and GeometryCollection children.
7
+ ## Immutable geometry wrappers
15
8
 
16
- The gem is not a full GIS system. See `docs/LIMITATIONS.md`.
9
+ `TG::Geometry::Geom` wraps one TG geometry pointer. It is frozen before being returned to Ruby and does not expose manual free, close, detach, or replacement APIs.
17
10
 
18
- ## Public namespace
11
+ Owned wrappers call `tg_geom_free` from their typed-data free function and adjust Ruby GC memory pressure using the cached TG memory size.
19
12
 
20
- The canonical require path is:
13
+ Borrowed child wrappers, where exposed, keep the parent `Geom` alive through a Ruby owner reference and do not free the borrowed TG pointer directly.
21
14
 
22
- ```ruby
23
- require "tg/geometry"
24
- ```
15
+ ## Rect
25
16
 
26
- The public API lives under `TG::Geometry`. The top-level `TG` module is only a namespace container, not the public gem API.
17
+ `TG::Geometry::Rect` is a small immutable Ruby object for bounding boxes and rectangle queries. It rejects non-finite coordinates and invalid coordinate order.
27
18
 
28
- ## Native extension shape
19
+ ## Immutable Index
29
20
 
30
- The current extension is built by `ext/tg_geometry/extconf.rb` and loaded as `tg_geometry_ext_geometry_ext`. The build requires Ruby >= 3.0 and a C11 compiler. Vendor sources are compiled into the extension through small wrapper files:
21
+ `TG::Geometry::Index` is built once and then read-only. Reloading is done by building a new index and swapping the Ruby reference.
31
22
 
32
- - `ext/tg_geometry/tg_geometry_vendor_tg.c` includes `vendor/tg/tg.c`;
33
- - `ext/tg_geometry/tg_geometry_vendor_rtree.c` includes `vendor/rtree/rtree.c`.
23
+ Index entries store:
34
24
 
35
- No visibility-hiding flag is enabled unless the Init symbol is explicitly exported. The Init function is exported with `RUBY_FUNC_EXPORTED`.
25
+ - Ruby id;
26
+ - optional Ruby geometry owner for borrowed `via: :geom` entries;
27
+ - TG geometry pointer;
28
+ - cached bbox;
29
+ - insertion ordinal;
30
+ - ownership flag and native byte count.
36
31
 
37
- ## Immutable `TG::Geometry::Geom`
32
+ For owned inputs (`via: :geojson` / `via: :wkb`), the index owns the parsed TG geometry. For borrowed inputs (`via: :geom`), the index marks the source `Geom` wrapper so the native pointer remains valid.
38
33
 
39
- `TG::Geometry::Geom` usually wraps one owned `struct tg_geom *`. Expansion Block J also allows internal borrowed `TG::Geometry::Geom` wrappers for GeometryCollection children. Borrowed wrappers keep a parent `geom_owner`, report no owned native bytes, and do not call `tg_geom_free` on the borrowed child pointer.
34
+ ## Flat and rtree strategies
40
35
 
41
- Rules:
36
+ `:flat` scans entries in insertion order.
42
37
 
43
- - public `.allocate` is disabled;
44
- - objects are created only by parse APIs, safe constructors, or internal borrowed child wrappers;
45
- - the native pointer is never replaced;
46
- - there is no `close!`, `free!`, `detach!`, or mutation API;
47
- - parsed objects are frozen before being returned.
38
+ `:rtree` uses `tidwall/rtree.c` as a bbox prefilter. Query results are still returned in insertion order, not rtree traversal order. Every bbox/rtree candidate is filtered with exact TG geometry predicates before returning ids.
48
39
 
49
- This immutability is required because `TG::Geometry::Index` can borrow a native geometry pointer from a `TG::Geometry::Geom` and keep the Ruby owner alive.
40
+ Rtree memory is accounted exactly through a custom malloc/free allocator with per-allocation headers.
50
41
 
51
- ## Immutable `TG::Geometry::Rect`
42
+ ## FeatureSource
52
43
 
53
- `TG::Geometry::Rect` is a small Ruby object around four finite coordinates. It is constructible from Ruby and frozen after initialization. The first release exposes only unambiguous rectangle APIs:
44
+ `TG::Geometry::FeatureSource` reads a full GeoJSON FeatureCollection into an owned C buffer, validates it with `json_validn`, traverses it with `json.c`, and extracts raw geometry/properties ranges while the buffer is alive.
54
45
 
55
- - coordinate readers;
56
- - `center`;
57
- - `intersects?`;
58
- - `contains_point?`;
59
- - expansion methods returning new Rect objects.
46
+ `read_entries_*` and `read_features_*` copy returned geometry/properties strings into Ruby strings before the source buffer is freed.
60
47
 
61
- `Rect#contains?` is intentionally not exposed because the name is ambiguous.
48
+ `build_index_*` parses geometry ranges directly into native index entries and does not allocate Ruby geometry strings.
62
49
 
63
- ## Immutable `TG::Geometry::Index`
50
+ ## Concurrency
64
51
 
65
- `TG::Geometry::Index` is built once and read-only afterwards.
66
-
67
- ```ruby
68
- index = TG::Geometry::Index.build(
69
- [[id1, object1], [id2, object2]],
70
- via: :geojson,
71
- strategy: :rtree,
72
- predicate: :covers,
73
- geometry_index: :ystripes
74
- )
75
- ```
76
-
77
- Accepted `via:` modes:
78
-
79
- - `:geom` borrows native geometry from existing `TG::Geometry::Geom` wrappers and marks the owner Ruby objects;
80
- - `:geojson` parses entry strings into Index-owned TG geometries;
81
- - `:wkb` parses entry strings as raw WKB bytes into Index-owned TG geometries.
82
-
83
- Accepted strategies:
84
-
85
- - `:flat` scans entries in insertion order with bbox prefiltering and exact TG geometry filtering;
86
- - `:rtree` uses vendored `rtree.c` over entry bboxes, then applies exact TG geometry filtering.
87
-
88
- `strategy: :auto` is not implemented in the first public release. Use explicit `:flat` or `:rtree` and validate with repository benchmarks.
89
-
90
- ## Result order
91
-
92
- Insertion order is public behavior.
93
-
94
- Each entry stores a unique `ordinal`. Flat strategy naturally scans entries in ordinal order. Rtree strategy uses rtree only as a candidate prefilter; candidate marks are local to the query and results are emitted by scanning entries in ordinal order. Rtree traversal order never leaks into Ruby results.
95
-
96
- ## Point predicate implementation
97
-
98
- Point queries allocate a temporary TG point geometry and use exact TG predicates:
99
-
100
- - `:covers` calls `tg_geom_covers(entry_geom, point_geom)`;
101
- - `:contains` calls `tg_geom_contains(entry_geom, point_geom)`.
102
-
103
- This is intentionally not the fastest possible point path. The first release chooses exact `covers` / `contains` semantics over a no-allocation shortcut. A faster path such as `tg_geom_intersects_xy` can only replace it after boundary and hole-boundary equivalence tests plus benchmarks are added.
104
-
105
- ## Reload pattern
106
-
107
- The intended application reload pattern is atomic reference replacement:
108
-
109
- ```ruby
110
- new_index = TG::Geometry::Index.build(entries, via: :geojson, strategy: :rtree)
111
- @index = new_index
112
- ```
113
-
114
- Old readers keep using the old immutable object until they release it. New readers see the new object after the Ruby reference swap. There is no in-place reload, mutation, add, delete, or builder API in the first release.
115
-
116
- ## Expansion Blocks A-E and I-J
117
-
118
- Expansion Block A (`strategy: :auto`) is not enabled in the first public release. The native Index stores only explicit concrete strategies (`:flat` or `:rtree`).
119
-
120
- Expansion Block B adds `TG::Geometry::Registry` in Ruby. Registry wraps an immutable Index reference and reloads by building a new Index before swapping the reference.
121
-
122
- Expansion Block C adds `TG::Geometry::ActiveRecordSource` as optional Ruby-only source sugar. The native extension does not depend on Rails or ActiveRecord.
123
-
124
- Expansion Block D adds Hex/GeoBIN parse/write helpers and raw `extra_json` copying. It does not parse properties into Ruby Hashes.
125
-
126
- Expansion Block E adds read-only borrowed wrappers for `TG::Geometry::Line`, `TG::Geometry::Ring`, and `TG::Geometry::Polygon`. These wrappers keep the parent `TG::Geometry::Geom` alive through `geom_owner`, mark it for GC, update it during compaction, and never free borrowed TG child pointers directly.
127
-
128
- Expansion Block I documents and tests the current Ractor boundary: native wrappers are not advertised as Ractor-shareable objects. Normal thread read-only access remains the supported concurrency model.
129
-
130
- Expansion Block J adds grouped TG API coverage without exposing global mutable environment settings or callback-based APIs. Implemented groups are additional predicates, geometry metadata/collection accessors, point and empty geometry constructors, value `TG::Geometry::Segment`, and borrowed GeometryCollection child `Geom` wrappers. See `docs/FULL_TG_API_COVERAGE.md`.
52
+ `Geom` and `Index` are immutable after construction. Concurrent read-only use from normal Ruby threads is supported. The implementation keeps the GVL for parse, write, query, batch, FeatureSource, and rtree build/free paths.
data/docs/BENCHMARKING.md CHANGED
@@ -70,6 +70,25 @@ The first release does not expose `strategy: :auto`. Choosing a threshold requir
70
70
 
71
71
  The first release does not claim Falcon or Async behavior. A dedicated Falcon/Async benchmark remains an open setup item until the dependency and scenario are approved.
72
72
 
73
- ## Expansion Block A: auto strategy threshold
73
+ ## Planned API areas: auto strategy threshold
74
74
 
75
75
  `strategy: :auto` remains postponed for the first public release. A future implementation must use a complete project-owned benchmark matrix and document the selected threshold before exposing the public option.
76
+
77
+ ## FeatureSource benchmark
78
+
79
+ `benchmark/feature_source.rb` compares:
80
+
81
+ - `JSON.parse + Ruby extraction` baseline;
82
+ - `FeatureSource.read_entries_file`;
83
+ - `FeatureSource.read_features_file`;
84
+ - direct `FeatureSource.build_index_file`;
85
+ - `Index.build(read_entries, via: :geojson)`.
86
+
87
+ Run:
88
+
89
+ ```bash
90
+ bundle exec ruby benchmark/feature_source.rb
91
+ TGEOMETRY_BENCH_FULL=1 bundle exec ruby benchmark/feature_source.rb
92
+ ```
93
+
94
+ Do not publish FeatureSource performance claims unless they come from this benchmark on the target dataset and environment.