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
@@ -3,7 +3,7 @@
3
3
  require "spec_helper"
4
4
  require "objspace"
5
5
 
6
- RSpec.describe "Release Core Block 3 Geom parsing" do
6
+ RSpec.describe "Geom parsing" do
7
7
  let(:geojson_point) { %({"type":"Point","coordinates":[1.0,2.0]}) }
8
8
  let(:wkt_point) { "POINT (1 2)" }
9
9
  let(:wkb_point) { [1, 1].pack("CL<") + [1.0, 2.0].pack("E2") }
@@ -5,7 +5,7 @@ require "json"
5
5
  require "objspace"
6
6
  require "weakref"
7
7
 
8
- RSpec.describe "Release Core Block 8 Index borrowed geometry ingestion" do
8
+ RSpec.describe "Index borrowed geometry ingestion" do
9
9
  let(:polygon_a_wkt) { "POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))" }
10
10
  let(:polygon_b_wkt) { "POLYGON ((10 10, 12 10, 12 12, 10 12, 10 10))" }
11
11
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe "Release Core Block 6 Index build skeleton" do
5
+ RSpec.describe "Index build" do
6
6
  let(:geom) { TG::Geometry.parse_wkt("POINT (1 2)") }
7
7
 
8
8
  it "builds an empty immutable index" do
@@ -54,7 +54,7 @@ RSpec.describe "Release Core Block 6 Index build skeleton" do
54
54
  .to raise_error(TG::Geometry::ArgumentError)
55
55
  end
56
56
 
57
- it "requires String values for owned geometry modes in the block 6 skeleton" do
57
+ it "requires String values for owned geometry modes in the owned geometry build path" do
58
58
  expect { TG::Geometry::Index.build([[1, Object.new]], via: :geojson, strategy: :flat) }
59
59
  .to raise_error(TypeError)
60
60
  expect { TG::Geometry::Index.build([[1, Object.new]], via: :wkb, strategy: :flat) }
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe "Release Core Block 9 flat query engine" do
5
+ RSpec.describe "Flat index query engine" do
6
6
  let(:zone_a) { TG::Geometry.parse_wkt("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))") }
7
7
  let(:zone_b) { TG::Geometry.parse_wkt("POLYGON ((5 5, 12 5, 12 12, 5 12, 5 5))") }
8
8
  let(:zone_c) { TG::Geometry.parse_wkt("POLYGON ((20 20, 22 20, 22 22, 20 22, 20 20))") }
@@ -3,7 +3,7 @@
3
3
  require "spec_helper"
4
4
  require "objspace"
5
5
 
6
- RSpec.describe "Release Core Block 7 Index owned geometry ingestion" do
6
+ RSpec.describe "Index owned geometry ingestion" do
7
7
  let(:geojson_a) do
8
8
  '{"type":"Polygon","coordinates":[[[0,0],[2,0],[2,2],[0,2],[0,0]]]}'
9
9
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe "Release Core Block 10 exact rtree accounting" do
5
+ RSpec.describe "Exact rtree accounting" do
6
6
  let(:zone_a) { TG::Geometry.parse_wkt("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))") }
7
7
  let(:zone_b) { TG::Geometry.parse_wkt("POLYGON ((20 20, 30 20, 30 30, 20 30, 20 20))") }
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe "Release Core Block 11 rtree ordered results" do
5
+ RSpec.describe "Rtree ordered results" do
6
6
  let(:entries) do
7
7
  [
8
8
  [:first, TG::Geometry.parse_wkt("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))")],
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe "Release Core Block 1 skeleton" do
5
+ RSpec.describe "Load and error hierarchy" do
6
6
  it "compiles the native extension" do
7
7
  expect(File.file?(EXT_SO)).to be(true)
8
8
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe "Expansion Block E low-level geometry wrappers" do
5
+ RSpec.describe "Low-level geometry wrappers" do
6
6
  it "exposes Point coordinates without constructing a mutable point wrapper" do
7
7
  geom = TG::Geometry.parse_wkt("POINT (1 2)")
8
8
 
@@ -3,7 +3,7 @@
3
3
  require "spec_helper"
4
4
  require "objspace"
5
5
 
6
- RSpec.describe "Release Core Block 14 memory, GC, and compaction hardening" do
6
+ RSpec.describe "Memory, GC, and compaction hardening" do
7
7
  let(:small_polygon_wkt) { "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))" }
8
8
  let(:small_polygon_geojson) { '{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}' }
9
9
  let(:malformed_geojson) { '{"type":"Polygon","coordinates":[' }
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe "Expansion Block I Ractor investigation" do
5
+ RSpec.describe "Ractor boundary" do
6
6
  it "keeps native wrappers outside Ractor shareability claims" do
7
7
  skip "Ractor is not available on this Ruby" unless defined?(Ractor)
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe "Release Core Block 5 Rect API" do
5
+ RSpec.describe "Rect API" do
6
6
  it "constructs an immutable rect with coordinate accessors" do
7
7
  rect = TG::Geometry::Rect.new(1, 2, 5, 8)
8
8
 
@@ -26,7 +26,7 @@ RSpec.describe TG::Geometry::Registry do
26
26
  expect(registry.find_covering(25, 25)).to eq(:b)
27
27
  end
28
28
 
29
- it "supports subclass source blocks" do
29
+ it "supports subclass source definitions" do
30
30
  entries = [[:a, zone_a]]
31
31
  klass = Class.new(described_class) do
32
32
  source { entries }
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- RSpec.describe "Expansion Block J grouped TG API coverage" do
5
+ RSpec.describe "TG API coverage" do
6
6
  it "adds safe point and empty geometry constructors without public allocation" do
7
7
  point = TG::Geometry.point_zm(1, 2, 3, 4)
8
8
 
@@ -4,16 +4,16 @@ require "spec_helper"
4
4
  require "open3"
5
5
  require "tmpdir"
6
6
 
7
- RSpec.describe "Release Core Block 2 vendored build" do
7
+ RSpec.describe "Vendored source build" do
8
8
  let(:vendor_dir) { File.join(ROOT, "ext", "tg_geometry", "vendor") }
9
9
 
10
- it "keeps tidwall/tg vendored under the contract path" do
10
+ it "keeps tidwall/tg vendored under the expected path" do
11
11
  %w[tg.c tg.h VERSION].each do |name|
12
12
  expect(File.file?(File.join(vendor_dir, "tg", name))).to be(true), "missing vendor/tg/#{name}"
13
13
  end
14
14
  end
15
15
 
16
- it "keeps rtree.c vendored under the contract path" do
16
+ it "keeps rtree.c vendored under the expected path" do
17
17
  %w[rtree.c rtree.h VERSION].each do |name|
18
18
  expect(File.file?(File.join(vendor_dir, "rtree", name))).to be(true), "missing vendor/rtree/#{name}"
19
19
  end
@@ -51,7 +51,7 @@ RSpec.describe "Release Core Block 2 vendored build" do
51
51
  end
52
52
  end
53
53
 
54
- it "uses the release-core compiler warning flags" do
54
+ it "uses the expected compiler warning flags" do
55
55
  extconf = File.read(File.join(ROOT, "ext", "tg_geometry", "extconf.rb"))
56
56
 
57
57
  expect(extconf).to include('"-std=c11"')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tg_geometry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Haydarov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-25 00:00:00.000000000 Z
11
+ date: 2026-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -44,8 +44,9 @@ description: Defines TG::Geometry with immutable Geom parsing and constructor wr
44
44
  wrappers, value Segment wrappers, Registry reload sugar, optional ActiveRecord source
45
45
  helpers, and an immutable geofencing-oriented Index with owned and borrowed geometry
46
46
  ingestion, flat/rtree strategies, deterministic ordered id results, exact rtree
47
- allocation accounting, and native-endian packed point batch queries over vendored
48
- TG sources. Ractor support is not claimed.
47
+ allocation accounting, and native-endian packed point batch queries, and FeatureSource
48
+ GeoJSON FeatureCollection extraction/build paths over vendored C sources. Ractor
49
+ support is not claimed.
49
50
  email:
50
51
  - romnhajdarov@gmail.com
51
52
  executables: []
@@ -61,32 +62,30 @@ files:
61
62
  - benchmark/_support.rb
62
63
  - benchmark/batch_packed_vs_loop.rb
63
64
  - benchmark/falcon_concurrency.rb
65
+ - benchmark/feature_source.rb
64
66
  - benchmark/flat_vs_rtree.rb
65
67
  - benchmark/gvl_threshold.rb
66
68
  - benchmark/objectspace_memsize.rb
67
69
  - benchmark/parse_throughput.rb
68
70
  - benchmark/rss_stability.rb
69
- - docs/ACTIVE_RECORD.md
70
71
  - docs/ARCHITECTURE.md
71
- - docs/AUTO_STRATEGY.md
72
72
  - docs/BENCHMARKING.md
73
73
  - docs/CASUAL_EXAMPLE.md
74
74
  - docs/CONCURRENCY.md
75
75
  - docs/ERROR_HANDLING.md
76
- - docs/EXPANSION_E_TO_H_STATUS.md
77
- - docs/FORMAT_COVERAGE.md
78
- - docs/FULL_TG_API_COVERAGE.md
76
+ - docs/FEATURE_SOURCE.md
79
77
  - docs/LIMITATIONS.md
80
- - docs/LOW_LEVEL_GEOMETRY.md
81
78
  - docs/MEMORY_OWNERSHIP.md
82
- - docs/RACTOR.md
83
- - docs/REGISTRY.md
84
- - docs/RELEASE_CHECKLIST.md
85
79
  - ext/tg_geometry/extconf.rb
86
80
  - ext/tg_geometry/tg_geometry_ext.c
81
+ - ext/tg_geometry/tg_geometry_vendor_json.c
87
82
  - ext/tg_geometry/tg_geometry_vendor_rtree.c
88
83
  - ext/tg_geometry/tg_geometry_vendor_tg.c
89
84
  - ext/tg_geometry/vendor/.vendored
85
+ - ext/tg_geometry/vendor/json/LICENSE
86
+ - ext/tg_geometry/vendor/json/VERSION
87
+ - ext/tg_geometry/vendor/json/json.c
88
+ - ext/tg_geometry/vendor/json/json.h
90
89
  - ext/tg_geometry/vendor/rtree/LICENSE
91
90
  - ext/tg_geometry/vendor/rtree/README.md
92
91
  - ext/tg_geometry/vendor/rtree/VERSION
@@ -103,30 +102,32 @@ files:
103
102
  - lib/tg/geometry/version.rb
104
103
  - lib/tg_geometry.rb
105
104
  - script/vendor_libs.rb
106
- - spec/block_10_rtree_strategy_spec.rb
107
- - spec/block_11_rtree_order_spec.rb
108
- - spec/block_12_batch_packed_spec.rb
109
- - spec/block_13_error_hardening_spec.rb
110
- - spec/block_14_memory_gc_hardening_spec.rb
111
- - spec/block_1_skeleton_spec.rb
112
- - spec/block_20_concurrency_spec.rb
113
- - spec/block_20_fuzz_spec.rb
114
- - spec/block_2_vendor_spec.rb
115
- - spec/block_3_geom_parse_spec.rb
116
- - spec/block_4_geom_api_spec.rb
117
- - spec/block_5_rect_api_spec.rb
118
- - spec/block_6_index_build_spec.rb
119
- - spec/block_7_index_owned_geometry_spec.rb
120
- - spec/block_8_index_borrowed_geometry_spec.rb
121
- - spec/block_9_flat_query_spec.rb
122
- - spec/expansion_a_auto_strategy_spec.rb
123
- - spec/expansion_b_registry_spec.rb
124
- - spec/expansion_c_active_record_source_spec.rb
125
- - spec/expansion_d_format_coverage_spec.rb
126
- - spec/expansion_e_low_level_geometry_spec.rb
127
- - spec/expansion_i_ractor_spec.rb
128
- - spec/expansion_j_full_tg_api_coverage_spec.rb
105
+ - spec/active_record_source_spec.rb
106
+ - spec/auto_strategy_spec.rb
107
+ - spec/batch_packed_spec.rb
108
+ - spec/concurrency_spec.rb
109
+ - spec/error_hardening_spec.rb
110
+ - spec/feature_source_nogvl_spec.rb
111
+ - spec/feature_source_spec.rb
112
+ - spec/format_coverage_spec.rb
113
+ - spec/fuzz_spec.rb
114
+ - spec/geom_api_spec.rb
115
+ - spec/geom_parse_spec.rb
116
+ - spec/index_borrowed_geometry_spec.rb
117
+ - spec/index_build_spec.rb
118
+ - spec/index_flat_query_spec.rb
119
+ - spec/index_owned_geometry_spec.rb
120
+ - spec/index_rtree_accounting_spec.rb
121
+ - spec/index_rtree_order_spec.rb
122
+ - spec/load_and_errors_spec.rb
123
+ - spec/low_level_geometry_spec.rb
124
+ - spec/memory_gc_spec.rb
125
+ - spec/ractor_spec.rb
126
+ - spec/rect_api_spec.rb
127
+ - spec/registry_spec.rb
129
128
  - spec/spec_helper.rb
129
+ - spec/tg_api_coverage_spec.rb
130
+ - spec/vendor_sources_spec.rb
130
131
  homepage: https://github.com/roman-haidarov/tg_geometry
131
132
  licenses:
132
133
  - MIT
@@ -142,7 +143,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
143
  requirements:
143
144
  - - ">="
144
145
  - !ruby/object:Gem::Version
145
- version: '3.0'
146
+ version: '3.1'
146
147
  required_rubygems_version: !ruby/object:Gem::Requirement
147
148
  requirements:
148
149
  - - ">="
@@ -153,5 +154,5 @@ rubygems_version: 3.3.27
153
154
  signing_key:
154
155
  specification_version: 4
155
156
  summary: Native extension for TG::Geometry parsing, predicates, immutable indexes,
156
- registries, low-level wrappers, and packed point batches
157
+ FeatureSource imports, registries, low-level wrappers, and packed point batches
157
158
  test_files: []
@@ -1,26 +0,0 @@
1
- # ActiveRecord source helper
2
-
3
- `TG::Geometry::ActiveRecordSource` is an optional Ruby helper. It does not require Rails or ActiveRecord and is not loaded from a separate integration gem.
4
-
5
- It accepts any object that responds to `find_each`, or any Enumerable:
6
-
7
- ```ruby
8
- entries = TG::Geometry::ActiveRecordSource.call(
9
- Zone.where(active: true),
10
- id: :id,
11
- geometry: :geojson,
12
- batch_size: 1_000
13
- )
14
-
15
- index = TG::Geometry::Index.build(entries, via: :geojson, strategy: :rtree)
16
- ```
17
-
18
- It can also feed a Registry:
19
-
20
- ```ruby
21
- class DeliveryZones < TG::Geometry::Registry
22
- active_record_source Zone.where(active: true), id: :id, geometry: :geojson
23
- end
24
- ```
25
-
26
- Field readers may be Symbols, Strings, or Procs. The helper only converts application records into `[[id, object], ...]`; it does not mutate records, keep database connections, create background jobs, install reload hooks, or add a Rails dependency to the native extension.
@@ -1,15 +0,0 @@
1
- # Auto strategy status
2
-
3
- `strategy: :auto` is not exposed in the first public release.
4
-
5
- The release-core contract only enables explicit `strategy: :flat` and `strategy: :rtree`. Automatic threshold selection requires a complete project-owned benchmark matrix and explicit approval before it can become public API.
6
-
7
- Use `benchmark/flat_vs_rtree.rb` to compare strategies for a workload, then pass the chosen strategy explicitly:
8
-
9
- ```ruby
10
- index = TG::Geometry::Index.build(entries, via: :geojson, strategy: :rtree)
11
- # or
12
- index = TG::Geometry::Index.build(entries, via: :geojson, strategy: :flat)
13
- ```
14
-
15
- Do not infer a universal crossover from rtree internals or from a partial benchmark run.
@@ -1,51 +0,0 @@
1
- # Expansion Blocks E–H status
2
-
3
- ## Expansion Block E — Low-level Ring / Line / Polygon APIs
4
-
5
- Implemented as read-only borrowed wrappers:
6
-
7
- - `TG::Geometry::Geom#point`
8
- - `TG::Geometry::Geom#line`
9
- - `TG::Geometry::Geom#polygon`
10
- - `TG::Geometry::Line`
11
- - `TG::Geometry::Ring`
12
- - `TG::Geometry::Polygon`
13
-
14
- Invariant satisfied: child pointers are borrowed from the parent TG geometry and the wrapper keeps the parent Ruby `TG::Geometry::Geom` alive through `geom_owner` with compaction-aware marking.
15
-
16
- Tests: `spec/expansion_e_low_level_geometry_spec.rb` covers accessors, private allocation, out-of-range errors, and parent survival after `GC.start` / `GC.compact`.
17
-
18
- ## Expansion Block F — Callback/search APIs
19
-
20
- OPEN QUESTION: not implemented.
21
-
22
- Reason: the roadmap allows callback/search APIs only with a new callback safety contract. The current contract still forbids public callback/block APIs in the first release. No `geom.search`, `index.search`, or `each_match` method is exposed.
23
-
24
- Required before implementation:
25
-
26
- - explicit callback exception propagation semantics;
27
- - GVL behavior for Ruby callbacks inside C loops;
28
- - proof that borrowed pointers cannot be invalidated by callback reentrancy;
29
- - benchmark of callback overhead;
30
- - tests for exceptions raised from callbacks.
31
-
32
- ## Expansion Block G — Fast no-allocation point query optimization
33
-
34
- OPEN QUESTION: not implemented.
35
-
36
- Reason: the current implementation intentionally constructs a temporary TG point geometry for `covers` / `contains` correctness. Replacing this with `tg_geom_intersects_xy` or a specialized helper requires proof of exact boundary semantics and a benchmark proving benefit.
37
-
38
- Current invariant: point query semantics remain exact and boundary behavior is covered by existing tests.
39
-
40
- ## Expansion Block H — Geodesic helpers or projection integration
41
-
42
- OPEN QUESTION: not implemented.
43
-
44
- Reason: TG core remains planar XY. No optional geodesic/projection dependency has been approved. The gem continues to document that `length`, `area`, and `perimeter` are in input coordinate units, not meters for lon/lat.
45
-
46
- Required before implementation:
47
-
48
- - explicit optional dependency decision;
49
- - public API shape;
50
- - tests separating planar TG behavior from geodesic/projection helpers;
51
- - documentation that TG itself does not handle geodesics.
@@ -1,23 +0,0 @@
1
- # Format coverage
2
-
3
- Expansion Block D exposes additional TG format helpers without changing ownership rules.
4
-
5
- ## Parse helpers
6
-
7
- - `TG::Geometry.parse_hex(str, index: :ystripes)`
8
- - `TG::Geometry.parse_geobin(bytes, index: :ystripes)`
9
-
10
- These are shortcuts over `TG::Geometry.parse(..., format: :hex)` and `TG::Geometry.parse(..., format: :geobin)`.
11
-
12
- ## Writer helpers
13
-
14
- - `TG::Geometry::Geom#to_hex` -> UTF-8 String
15
- - `TG::Geometry::Geom#to_geobin` -> ASCII-8BIT String
16
-
17
- Writers use the same direct Ruby string buffer pattern as existing writers. Hex is text and GeoBIN is binary.
18
-
19
- ## Raw extra_json
20
-
21
- - `TG::Geometry::Geom#extra_json` -> UTF-8 String or nil
22
-
23
- This returns a copied Ruby String from TG's raw extra JSON pointer. It does not parse JSON into Hashes and does not expose borrowed child wrappers. This follows the constraint that raw `extra_json` is safer than implicit `JSON.parse`.
@@ -1,109 +0,0 @@
1
- # Full TG API coverage status
2
-
3
- This document covers Expansion Block J as implemented in grouped, safe increments.
4
-
5
- Block J does not mean exposing every upstream TG function in one change. The rule is: implement in groups, define ownership for each group, add tests, and avoid environment allocator overrides or global mutable settings.
6
-
7
- ## Implemented group: predicates
8
-
9
- `TG::Geometry::Geom` now exposes additional read-only predicates:
10
-
11
- - `equals?(other)`
12
- - `disjoint?(other)`
13
- - `within?(other)`
14
- - `covers?(other)`
15
- - `covered_by?(other)`
16
- - `touches?(other)`
17
- - `intersects_xy?(x, y)`
18
- - `intersects_rect?(rect)`
19
- - `intersects_rect?(min_x, min_y, max_x, max_y)`
20
-
21
- Existing methods remain:
22
-
23
- - `contains?(other)`
24
- - `intersects?(other)`
25
- - `covers_xy?(x, y)`
26
-
27
- `equals?` intentionally does not change Ruby `==`, `eql?`, or `hash`. Ruby equality remains identity-based until that open API decision is explicitly made.
28
-
29
- ## Implemented group: geometry accessors
30
-
31
- `TG::Geometry::Geom` now exposes safe read-only accessors for upstream geometry collections and coordinate metadata:
32
-
33
- - `feature?`
34
- - `feature_collection?`
35
- - `empty?`
36
- - `dims`
37
- - `has_z?`
38
- - `has_m?`
39
- - `z`
40
- - `m`
41
- - `extra_coords`
42
- - `num_points`
43
- - `point_at(index)`
44
- - `points`
45
- - `num_lines`
46
- - `line_at(index)`
47
- - `lines`
48
- - `num_polygons`
49
- - `polygon_at(index)`
50
- - `polygons`
51
- - `num_geometries`
52
- - `geometry_at(index)`
53
- - `geometries`
54
-
55
- `geometry_at` and `geometries` return borrowed immutable `TG::Geometry::Geom` wrappers. They keep the parent wrapper alive through `geom_owner`, mark it for GC, update it during compaction, and do not call `tg_geom_free` for the borrowed child pointer.
56
-
57
- ## Implemented group: point and empty constructors
58
-
59
- Safe constructors are exposed only where ownership is simple and the returned object owns exactly one `struct tg_geom *`:
60
-
61
- - `TG::Geometry.point(x, y)`
62
- - `TG::Geometry.point_z(x, y, z)`
63
- - `TG::Geometry.point_m(x, y, m)`
64
- - `TG::Geometry.point_zm(x, y, z, m)`
65
- - `TG::Geometry.empty_point`
66
- - `TG::Geometry.empty_linestring`
67
- - `TG::Geometry.empty_polygon`
68
- - `TG::Geometry.empty_multipoint`
69
- - `TG::Geometry.empty_multilinestring`
70
- - `TG::Geometry.empty_multipolygon`
71
- - `TG::Geometry.empty_geometrycollection`
72
-
73
- Numeric coordinates are converted with `NUM2DBL` and rejected if NaN or Infinity.
74
-
75
- ## Implemented group: segments
76
-
77
- `TG::Geometry::Segment` is a frozen value wrapper over one copied `struct tg_segment`.
78
-
79
- Created by:
80
-
81
- - `TG::Geometry::Line#segment_at(index)`
82
- - `TG::Geometry::Line#segments`
83
- - `TG::Geometry::Ring#segment_at(index)`
84
- - `TG::Geometry::Ring#segments`
85
-
86
- Methods:
87
-
88
- - `a -> [Float, Float]`
89
- - `b -> [Float, Float]`
90
- - `points -> [[Float, Float], [Float, Float]]`
91
- - `bbox -> TG::Geometry::Rect`
92
- - `intersects?(other_segment)`
93
-
94
- `TG::Geometry::Segment.allocate` is disabled. Segment wrappers do not borrow parent TG memory and do not call any TG free function.
95
-
96
- ## Still not implemented
97
-
98
- The following remain outside this grouped implementation:
99
-
100
- - Line/Ring/Polygon public constructors from Ruby arrays;
101
- - MultiLineString / MultiPolygon constructors from low-level wrappers;
102
- - callback/search APIs;
103
- - nearest segment APIs;
104
- - environment configuration functions;
105
- - global allocator override;
106
- - mutable settings;
107
- - user callbacks inside C loops.
108
-
109
- Those require separate ownership and callback-safety contracts before implementation.
@@ -1,121 +0,0 @@
1
- # Low-level geometry wrappers
2
-
3
- This document covers Expansion Block E.
4
-
5
- ## Scope
6
-
7
- The first low-level API exposes borrowed read-only views over TG child types:
8
-
9
- - `TG::Geometry::Line`
10
- - `TG::Geometry::Ring`
11
- - `TG::Geometry::Polygon`
12
- - `TG::Geometry::Segment`
13
-
14
- These wrappers are created only from `TG::Geometry::Geom`, `TG::Geometry::Polygon`, `TG::Geometry::Line`, and `TG::Geometry::Ring` methods. Public `.allocate` is disabled for all four classes.
15
-
16
- ## Ownership model
17
-
18
- TG child accessors return borrowed pointers owned by the parent `struct tg_geom`. The Line/Ring/Polygon Ruby wrappers therefore store:
19
-
20
- - `geom_owner`: the original `TG::Geometry::Geom` Ruby object;
21
- - a borrowed `const struct tg_line *`, `const struct tg_ring *`, or `const struct tg_poly *` pointer.
22
-
23
- The wrappers mark `geom_owner` with `rb_gc_mark_movable` and update it in `dcompact` with `rb_gc_location`. They do not call `tg_line_free`, `tg_ring_free`, or `tg_poly_free` for borrowed children.
24
-
25
- `TG::Geometry::Segment` is a value wrapper over a copied `struct tg_segment`, so it does not store `geom_owner` and does not free any TG-owned pointer.
26
-
27
- Allocator pairs:
28
-
29
- Resource | Allocator | Deallocator | Owner | Notes
30
- --- | --- | --- | --- | ---
31
- `tg_line_wrapper_t` | `TypedData_Make_Struct` / Ruby allocator | `ruby_xfree` | Ruby object | borrowed pointer, marks parent `geom_owner`
32
- `tg_ring_wrapper_t` | `TypedData_Make_Struct` / Ruby allocator | `ruby_xfree` | Ruby object | borrowed pointer, marks parent `geom_owner`
33
- `tg_polygon_wrapper_t` | `TypedData_Make_Struct` / Ruby allocator | `ruby_xfree` | Ruby object | borrowed pointer, marks parent `geom_owner`
34
- `tg_segment_wrapper_t` | `TypedData_Make_Struct` / Ruby allocator | `ruby_xfree` | Ruby object | owns copied `struct tg_segment` value
35
- TG child pointers | parent `struct tg_geom` | parent `TG::Geometry::Geom` dfree | parent geometry | never freed by child wrappers
36
-
37
- ## Public API
38
-
39
- ### `TG::Geometry::Geom#point`
40
-
41
- Returns `[x, y]` for point geometries. Returns `nil` for non-point geometries.
42
-
43
- ### `TG::Geometry::Geom#line`
44
-
45
- Returns a frozen `TG::Geometry::Line` for LineString geometries. Returns `nil` otherwise.
46
-
47
- ### `TG::Geometry::Geom#polygon`
48
-
49
- Returns a frozen `TG::Geometry::Polygon` for Polygon geometries. Returns `nil` otherwise.
50
-
51
- ### `TG::Geometry::Line`
52
-
53
- Methods:
54
-
55
- - `bbox -> TG::Geometry::Rect`
56
- - `num_points -> Integer`
57
- - `point_at(index) -> [Float, Float]`
58
- - `points -> Array<[Float, Float]>`
59
- - `num_segments -> Integer`
60
- - `segment_at(index) -> TG::Geometry::Segment`
61
- - `segments -> Array<TG::Geometry::Segment>`
62
- - `length -> Float`
63
- - `clockwise? -> Boolean`
64
-
65
- `length` is measured in input coordinate units. For lon/lat data this is not meters.
66
-
67
- ### `TG::Geometry::Ring`
68
-
69
- Methods:
70
-
71
- - `bbox -> TG::Geometry::Rect`
72
- - `num_points -> Integer`
73
- - `point_at(index) -> [Float, Float]`
74
- - `points -> Array<[Float, Float]>`
75
- - `num_segments -> Integer`
76
- - `segment_at(index) -> TG::Geometry::Segment`
77
- - `segments -> Array<TG::Geometry::Segment>`
78
- - `area -> Float`
79
- - `perimeter -> Float`
80
- - `clockwise? -> Boolean`
81
- - `convex? -> Boolean`
82
-
83
- `area` and `perimeter` are measured in input coordinate units. For lon/lat data these are not square meters or meters.
84
-
85
- ### `TG::Geometry::Polygon`
86
-
87
- Methods:
88
-
89
- - `bbox -> TG::Geometry::Rect`
90
- - `exterior_ring -> TG::Geometry::Ring`
91
- - `num_holes -> Integer`
92
- - `hole_at(index) -> TG::Geometry::Ring`
93
- - `holes -> Array<TG::Geometry::Ring>`
94
- - `clockwise? -> Boolean`
95
-
96
- `hole_at` rejects out-of-range indexes with `TG::Geometry::ArgumentError`.
97
-
98
- ### `TG::Geometry::Segment`
99
-
100
- Methods:
101
-
102
- - `a -> [Float, Float]`
103
- - `b -> [Float, Float]`
104
- - `points -> [[Float, Float], [Float, Float]]`
105
- - `bbox -> TG::Geometry::Rect`
106
- - `intersects?(other_segment) -> Boolean`
107
-
108
- Segments are copied by value from a line or ring. They do not keep or free borrowed TG pointers.
109
-
110
- ## Error paths
111
-
112
- - Wrong wrapper type is handled by `TypedData_Get_Struct` and raises `TypeError`.
113
- - Out-of-range child indexes raise `TG::Geometry::ArgumentError`.
114
- - Child wrappers are immutable and cannot be constructed directly from Ruby.
115
-
116
- ## Not implemented in this block
117
-
118
- - Low-level constructors for Ring, Line, or Polygon.
119
- - Mutable coordinate access.
120
- - Polygon `area` / `perimeter` convenience aggregation. Ring-level values are exposed first to avoid undocumented assumptions about hole orientation and aggregation semantics.
121
- - Geodesic length/area.