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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -79
- data/README.md +82 -191
- data/Rakefile +3 -3
- data/benchmark/falcon_concurrency.rb +1 -1
- data/benchmark/feature_source.rb +92 -0
- data/docs/ARCHITECTURE.md +29 -107
- data/docs/BENCHMARKING.md +20 -1
- data/docs/CASUAL_EXAMPLE.md +71 -458
- data/docs/CONCURRENCY.md +13 -7
- data/docs/ERROR_HANDLING.md +30 -0
- data/docs/FEATURE_SOURCE.md +166 -0
- data/docs/LIMITATIONS.md +11 -50
- data/docs/MEMORY_OWNERSHIP.md +20 -2
- data/ext/tg_geometry/extconf.rb +46 -4
- data/ext/tg_geometry/tg_geometry_ext.c +2453 -150
- data/ext/tg_geometry/tg_geometry_vendor_json.c +17 -0
- data/ext/tg_geometry/tg_geometry_vendor_tg.c +3 -0
- data/ext/tg_geometry/vendor/.vendored +8 -2
- data/ext/tg_geometry/vendor/json/LICENSE +20 -0
- data/ext/tg_geometry/vendor/json/VERSION +3 -0
- data/ext/tg_geometry/vendor/json/json.c +1024 -0
- data/ext/tg_geometry/vendor/json/json.h +207 -0
- data/lib/tg/geometry/registry.rb +3 -3
- data/lib/tg/geometry/version.rb +1 -1
- data/script/vendor_libs.rb +22 -6
- data/spec/{expansion_a_auto_strategy_spec.rb → auto_strategy_spec.rb} +1 -1
- data/spec/{block_12_batch_packed_spec.rb → batch_packed_spec.rb} +1 -1
- data/spec/{block_20_concurrency_spec.rb → concurrency_spec.rb} +1 -1
- data/spec/{block_13_error_hardening_spec.rb → error_hardening_spec.rb} +1 -1
- data/spec/feature_source_nogvl_spec.rb +51 -0
- data/spec/feature_source_spec.rb +268 -0
- data/spec/{expansion_d_format_coverage_spec.rb → format_coverage_spec.rb} +1 -1
- data/spec/{block_20_fuzz_spec.rb → fuzz_spec.rb} +1 -1
- data/spec/{block_4_geom_api_spec.rb → geom_api_spec.rb} +1 -1
- data/spec/{block_3_geom_parse_spec.rb → geom_parse_spec.rb} +1 -1
- data/spec/{block_8_index_borrowed_geometry_spec.rb → index_borrowed_geometry_spec.rb} +1 -1
- data/spec/{block_6_index_build_spec.rb → index_build_spec.rb} +2 -2
- data/spec/{block_9_flat_query_spec.rb → index_flat_query_spec.rb} +1 -1
- data/spec/{block_7_index_owned_geometry_spec.rb → index_owned_geometry_spec.rb} +1 -1
- data/spec/{block_10_rtree_strategy_spec.rb → index_rtree_accounting_spec.rb} +1 -1
- data/spec/{block_11_rtree_order_spec.rb → index_rtree_order_spec.rb} +1 -1
- data/spec/{block_1_skeleton_spec.rb → load_and_errors_spec.rb} +1 -1
- data/spec/{expansion_e_low_level_geometry_spec.rb → low_level_geometry_spec.rb} +1 -1
- data/spec/{block_14_memory_gc_hardening_spec.rb → memory_gc_spec.rb} +1 -1
- data/spec/{expansion_i_ractor_spec.rb → ractor_spec.rb} +1 -1
- data/spec/{block_5_rect_api_spec.rb → rect_api_spec.rb} +1 -1
- data/spec/{expansion_b_registry_spec.rb → registry_spec.rb} +1 -1
- data/spec/{expansion_j_full_tg_api_coverage_spec.rb → tg_api_coverage_spec.rb} +1 -1
- data/spec/{block_2_vendor_spec.rb → vendor_sources_spec.rb} +4 -4
- metadata +39 -38
- data/docs/ACTIVE_RECORD.md +0 -26
- data/docs/AUTO_STRATEGY.md +0 -15
- data/docs/EXPANSION_E_TO_H_STATUS.md +0 -51
- data/docs/FORMAT_COVERAGE.md +0 -23
- data/docs/FULL_TG_API_COVERAGE.md +0 -109
- data/docs/LOW_LEVEL_GEOMETRY.md +0 -121
- data/docs/RACTOR.md +0 -40
- data/docs/REGISTRY.md +0 -37
- data/docs/RELEASE_CHECKLIST.md +0 -39
- /data/spec/{expansion_c_active_record_source_spec.rb → active_record_source_spec.rb} +0 -0
data/docs/ERROR_HANDLING.md
CHANGED
|
@@ -53,3 +53,33 @@ Rtree allocator callbacks return `NULL` on OOM. Rtree search callbacks record fa
|
|
|
53
53
|
## Writer safety
|
|
54
54
|
|
|
55
55
|
Text writers allocate Ruby strings with one extra byte for the null terminator, call the TG writer with that capacity, then set the Ruby string length back to the required content length. WKB allocates exactly the required binary length and associates `ASCII-8BIT` encoding.
|
|
56
|
+
|
|
57
|
+
## FeatureSource errors and reports
|
|
58
|
+
|
|
59
|
+
FeatureSource validates the whole JSON source before traversal. Malformed JSON raises `TG::Geometry::ParseError` with the byte offset when available. A root that is not a GeoJSON `FeatureCollection`, or a `features` member that is missing/not an array, also raises `TG::Geometry::ParseError`.
|
|
60
|
+
|
|
61
|
+
Invalid feature data is fail-fast by default:
|
|
62
|
+
|
|
63
|
+
- missing/non-object geometry;
|
|
64
|
+
- missing/non-string `geometry.type`;
|
|
65
|
+
- unsupported geometry type;
|
|
66
|
+
- invalid properties type for `read_features_*`;
|
|
67
|
+
- TG parse error for a geometry range.
|
|
68
|
+
|
|
69
|
+
`on_invalid: :skip` is accepted only with `report: true`. In report mode invalid features increment `:skipped`; filtered geometry types increment `:filtered`.
|
|
70
|
+
|
|
71
|
+
Missing or `null` ids follow `on_missing_id:`:
|
|
72
|
+
|
|
73
|
+
- `:raise` raises `TG::Geometry::ArgumentError`;
|
|
74
|
+
- `:skip` requires `report: true` and records a skipped feature;
|
|
75
|
+
- `:ordinal` creates an id like `"feature/12"`.
|
|
76
|
+
|
|
77
|
+
Invalid id types raise `TG::Geometry::ArgumentError`: boolean, object, array, fractional number, and non-integer exponent number are not accepted FeatureSource ids.
|
|
78
|
+
|
|
79
|
+
Stored report errors have this shape:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
{ feature_index: Integer, byte_offset: Integer, reason: String }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`max_errors:` caps the number of stored error Hashes, not the exact skipped count. TG geometry parse errors copy `tg_geom_error` before freeing the temporary TG error geometry and include feature index / byte offset in the raised message.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# FeatureSource
|
|
2
|
+
|
|
3
|
+
`TG::Geometry::FeatureSource` reads GeoJSON `FeatureCollection` sources at C level and extracts geometry ranges without building a Ruby `Hash` / `Array` tree for the whole document.
|
|
4
|
+
|
|
5
|
+
The feature exists for Rails-style import and geofencing workflows where the source data is a real GeoJSON file, not a hand-written polygon string.
|
|
6
|
+
|
|
7
|
+
## Public API
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
TG::Geometry::FeatureSource.read_entries_file(path, **opts)
|
|
11
|
+
TG::Geometry::FeatureSource.read_entries_json(json_string, **opts)
|
|
12
|
+
TG::Geometry::FeatureSource.read_entries_io(io, **opts)
|
|
13
|
+
|
|
14
|
+
TG::Geometry::FeatureSource.read_features_file(path, **opts)
|
|
15
|
+
TG::Geometry::FeatureSource.read_features_json(json_string, **opts)
|
|
16
|
+
TG::Geometry::FeatureSource.read_features_io(io, **opts)
|
|
17
|
+
|
|
18
|
+
TG::Geometry::FeatureSource.build_index_file(path, **opts)
|
|
19
|
+
TG::Geometry::FeatureSource.build_index_json(json_string, **opts)
|
|
20
|
+
TG::Geometry::FeatureSource.build_index_io(io, **opts)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Source methods are explicit:
|
|
24
|
+
|
|
25
|
+
- `*_file(path)` treats the argument as a file path only.
|
|
26
|
+
- `*_json(json_string)` treats the argument as raw GeoJSON content only.
|
|
27
|
+
- `*_io(io)` calls `read` and treats the returned content as raw GeoJSON.
|
|
28
|
+
|
|
29
|
+
There is no path/content auto-detection.
|
|
30
|
+
|
|
31
|
+
## Return shapes
|
|
32
|
+
|
|
33
|
+
`read_entries_*` returns pairs suitable for `TG::Geometry::Index.build(..., via: :geojson)`:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
[[id, geometry_json_string], ...]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`read_features_*` also returns raw `properties` JSON for database imports:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
[[id, geometry_json_string, properties_json_string], ...]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`properties_json_string` is a JSON string, not a Ruby `Hash`.
|
|
46
|
+
|
|
47
|
+
## Report mode
|
|
48
|
+
|
|
49
|
+
With `report: true`, read methods return a Hash:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
{
|
|
53
|
+
entries: [[id, geometry_json_string], ...],
|
|
54
|
+
skipped: 0,
|
|
55
|
+
filtered: 0,
|
|
56
|
+
errors: [
|
|
57
|
+
{ feature_index: 1, byte_offset: 1234, reason: "missing geometry" }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For `read_features_*`, the collection key is `:features` instead of `:entries`.
|
|
63
|
+
|
|
64
|
+
`max_errors:` caps stored error Hashes only. `skipped` remains exact.
|
|
65
|
+
|
|
66
|
+
## Options
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
id: ["properties", "@id"]
|
|
70
|
+
only: [:polygon, :multipolygon]
|
|
71
|
+
on_invalid: :raise
|
|
72
|
+
on_missing_id: :raise
|
|
73
|
+
report: false
|
|
74
|
+
max_errors: 100
|
|
75
|
+
geometry_index: :ystripes
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`id:` may be an `Array<String>` or a simple dot-separated `String`. Array form is the primary API and is required for keys that cannot be represented safely with dot syntax.
|
|
79
|
+
|
|
80
|
+
Accepted ids:
|
|
81
|
+
|
|
82
|
+
- JSON string -> Ruby `String`
|
|
83
|
+
- JSON integer number -> Ruby `Integer`
|
|
84
|
+
|
|
85
|
+
Rejected ids:
|
|
86
|
+
|
|
87
|
+
- fractional/exponent non-integer number
|
|
88
|
+
- boolean
|
|
89
|
+
- object
|
|
90
|
+
- array
|
|
91
|
+
|
|
92
|
+
Missing or `null` ids follow `on_missing_id:`:
|
|
93
|
+
|
|
94
|
+
- `:raise` raises `TG::Geometry::ArgumentError`
|
|
95
|
+
- `:skip` skips the feature and requires `report: true`
|
|
96
|
+
- `:ordinal` creates a string id such as `"feature/12"`
|
|
97
|
+
|
|
98
|
+
`only:` defaults to polygons and multipolygons. `only: nil` disables geometry type filtering. A mismatch with `only:` is counted as `filtered`, not invalid.
|
|
99
|
+
|
|
100
|
+
`on_invalid: :skip` requires `report: true`. Silent loss of invalid features is not allowed.
|
|
101
|
+
|
|
102
|
+
## Direct index build
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
index = TG::Geometry::FeatureSource.build_index_file(
|
|
106
|
+
"zones.geojson",
|
|
107
|
+
id: ["properties", "@id"],
|
|
108
|
+
strategy: :rtree,
|
|
109
|
+
predicate: :covers,
|
|
110
|
+
geometry_index: :ystripes
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`build_index_*` returns a frozen `TG::Geometry::Index`.
|
|
115
|
+
|
|
116
|
+
Unlike `read_entries_* + Index.build`, direct build does not allocate Ruby geometry JSON strings. It parses accepted geometry JSON ranges directly into owned native TG geometries and then builds the immutable index.
|
|
117
|
+
|
|
118
|
+
`build_index_*` does not accept `report: true` in this release.
|
|
119
|
+
|
|
120
|
+
## Validation
|
|
121
|
+
|
|
122
|
+
FeatureSource validates in two layers:
|
|
123
|
+
|
|
124
|
+
1. JSON syntax and FeatureCollection shape.
|
|
125
|
+
2. Each returned or indexed geometry is parsed with TG via `tg_parse_geojsonn_ix`.
|
|
126
|
+
|
|
127
|
+
A JSON object being syntactically valid is not enough for index correctness.
|
|
128
|
+
|
|
129
|
+
## Execution and GVL model
|
|
130
|
+
|
|
131
|
+
FeatureSource bulk work is split into two phases. The heavy phase runs without the Ruby GVL and uses only C-owned memory:
|
|
132
|
+
|
|
133
|
+
- `_file` methods copy the Ruby path, then open/read the file in C outside the GVL;
|
|
134
|
+
- JSON validation and `tidwall/json.c` traversal run outside the GVL;
|
|
135
|
+
- geometry validation/parsing with `tg_parse_geojsonn_ix` runs outside the GVL;
|
|
136
|
+
- no Ruby objects are created, no Ruby exceptions are raised, and no `rb_gc_adjust_memory_usage` calls happen in that phase.
|
|
137
|
+
|
|
138
|
+
After the heavy phase finishes, the GVL is reacquired to materialize Ruby ids/strings/reports or to transfer owned native geometries into a frozen `TG::Geometry::Index`.
|
|
139
|
+
|
|
140
|
+
FeatureSource deliberately does not implement a manual Fiber scheduler worker in C. The heavy C-only phase is executed with Ruby VM no-GVL APIs: `RB_NOGVL_OFFLOAD_SAFE` when available, otherwise `rb_thread_call_without_gvl`. This means FeatureSource releases the GVL for other Ruby threads. On Rubies without the offload-safe VM API, no explicit Fiber scheduler friendliness is claimed.
|
|
141
|
+
|
|
142
|
+
## Memory model
|
|
143
|
+
|
|
144
|
+
FeatureSource reads the whole source into one owned C buffer. `tidwall/json.c` traversal is backed by that buffer.
|
|
145
|
+
|
|
146
|
+
For `read_entries_*` / `read_features_*`:
|
|
147
|
+
|
|
148
|
+
- returned geometry strings are copied into Ruby strings;
|
|
149
|
+
- returned properties strings are copied into Ruby strings or literal `"null"`;
|
|
150
|
+
- no returned value points into the freed C buffer.
|
|
151
|
+
|
|
152
|
+
For `build_index_*`:
|
|
153
|
+
|
|
154
|
+
- the source buffer remains alive until all geometry ranges are parsed;
|
|
155
|
+
- the final index owns native TG geometries and does not depend on the source buffer;
|
|
156
|
+
- Ruby ids are stored in native entries and marked/compacted by the index wrapper.
|
|
157
|
+
|
|
158
|
+
## Non-goals in this release
|
|
159
|
+
|
|
160
|
+
- no streaming parser;
|
|
161
|
+
- no NDGeoJSON / GeoJSONSeq;
|
|
162
|
+
- no gzip decompression;
|
|
163
|
+
- no FlatGeobuf;
|
|
164
|
+
- no Ruby callback/yield API;
|
|
165
|
+
- no property parsing into Ruby Hashes;
|
|
166
|
+
- no general JSON query API.
|
data/docs/LIMITATIONS.md
CHANGED
|
@@ -1,61 +1,22 @@
|
|
|
1
1
|
# Limitations
|
|
2
2
|
|
|
3
|
-
`tg_geometry` is
|
|
3
|
+
`tg_geometry` is not a full GIS system.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
The first release does not provide:
|
|
5
|
+
Not included:
|
|
8
6
|
|
|
9
7
|
- geocoding;
|
|
10
8
|
- routing;
|
|
11
9
|
- projections;
|
|
12
|
-
- geodesic distance
|
|
13
|
-
- buffer / union / difference /
|
|
14
|
-
- nearest POI
|
|
15
|
-
-
|
|
16
|
-
- parsed GeoJSON Feature properties as Ruby Hashes;
|
|
17
|
-
- Line/Ring/Polygon public constructors from Ruby coordinate arrays;
|
|
18
|
-
- user callback APIs;
|
|
10
|
+
- geodesic distance/area;
|
|
11
|
+
- buffer / union / difference / overlay result geometry operations;
|
|
12
|
+
- nearest POI indexing;
|
|
13
|
+
- public callback/search APIs;
|
|
19
14
|
- Ractor support claim;
|
|
20
|
-
- no-
|
|
21
|
-
-
|
|
22
|
-
- mutable coordinate APIs;
|
|
23
|
-
- global TG environment configuration or allocator override APIs.
|
|
24
|
-
|
|
25
|
-
## Planar XY only
|
|
26
|
-
|
|
27
|
-
TG works in planar XY coordinates. If users pass lon/lat, area, length, and perimeter concepts are in input coordinate units, not meters. Real-world distance and area need explicit projection/geodesic tooling outside this first-release core.
|
|
28
|
-
|
|
29
|
-
## Boundary semantics
|
|
30
|
-
|
|
31
|
-
Geofencing defaults to `predicate: :covers` because boundary points should count as inside. `predicate: :contains` is stricter and may exclude boundary points.
|
|
32
|
-
|
|
33
|
-
## Point query performance
|
|
34
|
-
|
|
35
|
-
The current implementation allocates a temporary TG point geometry per point query. This is intentional for exact `covers` and `contains` semantics. A no-allocation point path can be added later only after tests prove equivalent boundary behavior and benchmarks prove value.
|
|
36
|
-
|
|
37
|
-
## Build peak memory
|
|
38
|
-
|
|
39
|
-
`TG::Geometry::Index.build` is atomic and immutable. During reload, memory peak can include both the old Index and the new Index, plus original Ruby entry arrays and temporary build state. This is deliberate: exception safety and read-only concurrency are more important than streaming mutation in the first release.
|
|
40
|
-
|
|
41
|
-
## IDs are returned by reference
|
|
42
|
-
|
|
43
|
-
Index query methods return the same Ruby id objects stored in entries. They are not duplicated, frozen, stringified, or copied. If an id object is mutable, user code owns that mutability risk.
|
|
44
|
-
|
|
45
|
-
## Windows
|
|
46
|
-
|
|
47
|
-
Windows is not supported in the first release. The intended first-release platforms are Linux and macOS on x86_64 and arm64.
|
|
48
|
-
|
|
49
|
-
## Expansion limitations
|
|
50
|
-
|
|
51
|
-
No automatic strategy resolver is enabled in the first public release. For unusual datasets, especially heavily overlapping zones or workloads dominated by first-entry hits, choose `:flat` or `:rtree` explicitly after benchmarking.
|
|
52
|
-
|
|
53
|
-
`TG::Geometry::Registry` is application sugar, not a distributed registry. It has no Redis dependency, no background reload thread, and no hidden global singleton.
|
|
54
|
-
|
|
55
|
-
`TG::Geometry::ActiveRecordSource` is optional Ruby helper code. It does not install Rails reload hooks, generators, or background jobs.
|
|
15
|
+
- no-GVL execution claim;
|
|
16
|
+
- automatic strategy selection.
|
|
56
17
|
|
|
57
|
-
|
|
18
|
+
TG works in planar XY coordinates. If lon/lat coordinates are passed in, length, area, and perimeter-style values are in input coordinate units, not meters.
|
|
58
19
|
|
|
59
|
-
|
|
20
|
+
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.
|
|
60
21
|
|
|
61
|
-
|
|
22
|
+
FeatureSource returns raw properties JSON strings. It does not parse properties into Ruby Hash objects.
|
data/docs/MEMORY_OWNERSHIP.md
CHANGED
|
@@ -85,10 +85,28 @@ For `via: :geojson` and `via: :wkb`, the Index owns each parsed TG geometry. The
|
|
|
85
85
|
|
|
86
86
|
## Low-level child wrappers
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
Planned API areas exposes read-only borrowed wrappers for selected TG child types. `TG::Geometry::Line`, `TG::Geometry::Ring`, and `TG::Geometry::Polygon` keep the original parent `TG::Geometry::Geom` Ruby object in `geom_owner`. Their GC callbacks use `rb_gc_mark_movable` and `rb_gc_location`, matching the Index borrowed-geometry model.
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Planned API areas extends this model to borrowed `TG::Geometry::Geom` wrappers returned from GeometryCollection accessors. A borrowed `Geom` stores `owned = false`, `geom_bytes = 0`, and a `geom_owner` reference to the parent wrapper. Its free path only releases the Ruby wrapper struct; it never calls `tg_geom_free` on the borrowed child pointer. This keeps `ObjectSpace.memsize_of` from double-counting parent-owned native geometry.
|
|
91
91
|
|
|
92
92
|
`TG::Geometry::Segment` is different: it stores a `struct tg_segment` by value, not a borrowed pointer. It has no parent owner to mark and no TG deallocator to call.
|
|
93
93
|
|
|
94
94
|
Line/Ring/Polygon wrappers own only their small Ruby-allocated wrapper structs. They do not own the underlying `const struct tg_line *`, `const struct tg_ring *`, or `const struct tg_poly *`. Cleanup therefore only calls `ruby_xfree` for the wrapper; the parent `TG::Geometry::Geom` remains responsible for the single `tg_geom_free`.
|
|
95
|
+
|
|
96
|
+
## FeatureSource ownership
|
|
97
|
+
|
|
98
|
+
FeatureSource adds only method-scope JSON resources. It does not introduce a persistent FeatureSource object type.
|
|
99
|
+
|
|
100
|
+
Resource | Allocator | Deallocator | Owner | Notes
|
|
101
|
+
--- | --- | --- | --- | ---
|
|
102
|
+
FeatureSource source buffer | C heap (`malloc`/`realloc`) | `free` | FeatureSource method scope | full file/json/io content copy; freed through ensure-style cleanup
|
|
103
|
+
FeatureSource geometry JSON output | Ruby `String` | Ruby GC | returned Array | copied from source buffer before buffer free; UTF-8 encoded
|
|
104
|
+
FeatureSource properties JSON output | Ruby `String` | Ruby GC | returned Array | copied from source buffer or literal `"null"`; UTF-8 encoded
|
|
105
|
+
`tidwall/json.c` `struct json` / raw range | backed by source buffer | none | local traversal scope | invalid after source buffer free; never stored in Ruby object/native index
|
|
106
|
+
FeatureSource validation geometry | `tg_parse_geojsonn_ix` | `tg_geom_free` | local validation scope | used by `read_entries_*` / `read_features_*`; freed immediately
|
|
107
|
+
FeatureSource build index geometry | `tg_parse_geojsonn_ix` | `tg_geom_free` via `index_dispose` | `TG::Geometry::Index` | direct parse from JSON raw range; no Ruby geometry string allocation
|
|
108
|
+
FeatureSource build id | Ruby VM | Ruby GC | `TG::Geometry::Index` entry | wrapper is created before entries are filled; `initialized` controls mark/compact boundary
|
|
109
|
+
|
|
110
|
+
The source buffer must outlive every JSON range and every `tg_parse_geojsonn_ix` call that receives a pointer into it. After `build_index_*` succeeds, the returned Index owns native TG geometries and no longer depends on the source buffer.
|
|
111
|
+
|
|
112
|
+
`read_entries_*` and `read_features_*` copy output strings before freeing the buffer. Returned arrays never contain pointers into the native source buffer.
|
data/ext/tg_geometry/extconf.rb
CHANGED
|
@@ -12,6 +12,7 @@ end
|
|
|
12
12
|
VENDOR_DIR = File.expand_path("vendor", __dir__)
|
|
13
13
|
VENDORED_TG_DIR = File.join(VENDOR_DIR, "tg")
|
|
14
14
|
VENDORED_RTREE_DIR = File.join(VENDOR_DIR, "rtree")
|
|
15
|
+
VENDORED_JSON_DIR = File.join(VENDOR_DIR, "json")
|
|
15
16
|
|
|
16
17
|
required_vendor_files = [
|
|
17
18
|
File.join(VENDORED_TG_DIR, "tg.c"),
|
|
@@ -19,7 +20,11 @@ required_vendor_files = [
|
|
|
19
20
|
File.join(VENDORED_TG_DIR, "VERSION"),
|
|
20
21
|
File.join(VENDORED_RTREE_DIR, "rtree.c"),
|
|
21
22
|
File.join(VENDORED_RTREE_DIR, "rtree.h"),
|
|
22
|
-
File.join(VENDORED_RTREE_DIR, "VERSION")
|
|
23
|
+
File.join(VENDORED_RTREE_DIR, "VERSION"),
|
|
24
|
+
File.join(VENDORED_JSON_DIR, "json.c"),
|
|
25
|
+
File.join(VENDORED_JSON_DIR, "json.h"),
|
|
26
|
+
File.join(VENDORED_JSON_DIR, "LICENSE"),
|
|
27
|
+
File.join(VENDORED_JSON_DIR, "VERSION")
|
|
23
28
|
]
|
|
24
29
|
|
|
25
30
|
missing_vendor_files = required_vendor_files.reject { |path| File.file?(path) }
|
|
@@ -27,10 +32,44 @@ unless missing_vendor_files.empty?
|
|
|
27
32
|
abort "tg_geometry requires vendored tidwall/tg and rtree.c sources. Missing: #{missing_vendor_files.join(", ")}. Run `ruby script/vendor_libs.rb --sync`."
|
|
28
33
|
end
|
|
29
34
|
|
|
35
|
+
def tg_geometry_clang_compiler?
|
|
36
|
+
cc = RbConfig::CONFIG["CC"].to_s
|
|
37
|
+
return true if cc.include?("clang")
|
|
38
|
+
|
|
39
|
+
# Some build environments use cc as an alias. Detect __clang__ by compiling
|
|
40
|
+
# a tiny program rather than trusting the command name.
|
|
41
|
+
try_compile(<<~C)
|
|
42
|
+
#ifndef __clang__
|
|
43
|
+
#error not clang
|
|
44
|
+
#endif
|
|
45
|
+
int main(void) { return 0; }
|
|
46
|
+
C
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def tg_geometry_sanitize_warnflags!
|
|
50
|
+
return if tg_geometry_clang_compiler?
|
|
51
|
+
|
|
52
|
+
# Ruby builds produced with clang may leak clang-only warning flags into
|
|
53
|
+
# RbConfig::CONFIG["warnflags"]. GCC prints noisy "unrecognized command-line
|
|
54
|
+
# option" notes once any real diagnostic is emitted. Keep our Linux CI logs
|
|
55
|
+
# focused on tg_geometry diagnostics.
|
|
56
|
+
clang_only_warning_flags = %w[
|
|
57
|
+
-Wno-constant-logical-operand
|
|
58
|
+
-Wno-parentheses-equality
|
|
59
|
+
-Wno-self-assign
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
$warnflags = $warnflags.to_s.split.reject do |flag|
|
|
63
|
+
clang_only_warning_flags.include?(flag)
|
|
64
|
+
end.join(" ")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
tg_geometry_sanitize_warnflags!
|
|
68
|
+
|
|
30
69
|
forbidden_defines = %w[TG_NOATOMICS RTREE_NOATOMICS]
|
|
31
70
|
forbidden_defines.each do |macro|
|
|
32
71
|
if ($CFLAGS.to_s.split + $CPPFLAGS.to_s.split + $defs).any? { |flag| flag.include?(macro) }
|
|
33
|
-
abort "tg_geometry must not define #{macro}; atomics are required by the
|
|
72
|
+
abort "tg_geometry must not define #{macro}; atomics are required by the build requirements"
|
|
34
73
|
end
|
|
35
74
|
end
|
|
36
75
|
|
|
@@ -52,7 +91,8 @@ $INCFLAGS = [
|
|
|
52
91
|
"-isystem $(hdrdir)/ruby/backward",
|
|
53
92
|
"-isystem $(hdrdir)",
|
|
54
93
|
"-I$(srcdir)/vendor/tg",
|
|
55
|
-
"-I$(srcdir)/vendor/rtree"
|
|
94
|
+
"-I$(srcdir)/vendor/rtree",
|
|
95
|
+
"-I$(srcdir)/vendor/json"
|
|
56
96
|
].join(" ")
|
|
57
97
|
|
|
58
98
|
unless try_compile(<<~C)
|
|
@@ -78,6 +118,7 @@ if have_macro("RB_NOGVL_OFFLOAD_SAFE", "ruby/thread.h")
|
|
|
78
118
|
$defs << "-DHAVE_RB_NOGVL_OFFLOAD_SAFE"
|
|
79
119
|
end
|
|
80
120
|
|
|
121
|
+
|
|
81
122
|
if ENV["TG_DEBUG_TEST"] == "1"
|
|
82
123
|
$defs << "-DTG_DEBUG_TEST"
|
|
83
124
|
end
|
|
@@ -85,7 +126,8 @@ end
|
|
|
85
126
|
$srcs = [
|
|
86
127
|
"tg_geometry_ext.c",
|
|
87
128
|
"tg_geometry_vendor_tg.c",
|
|
88
|
-
"tg_geometry_vendor_rtree.c"
|
|
129
|
+
"tg_geometry_vendor_rtree.c",
|
|
130
|
+
"tg_geometry_vendor_json.c"
|
|
89
131
|
]
|
|
90
132
|
|
|
91
133
|
create_makefile("tg_geometry_ext_geometry_ext")
|