tg_geometry 0.3.2 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +81 -291
- data/ext/tg_geometry/tg_geometry_ext.c +3 -4
- data/ext/tg_geometry/vendor/.vendored +8 -8
- data/ext/tg_geometry/vendor/json/VERSION +1 -1
- data/ext/tg_geometry/vendor/json/json.c +20 -20
- data/ext/tg_geometry/vendor/json/json.h +20 -20
- data/ext/tg_geometry/vendor/rtree/VERSION +2 -2
- data/ext/tg_geometry/vendor/rtree/rtree.c +121 -148
- data/ext/tg_geometry/vendor/rtree/rtree.h +14 -11
- data/ext/tg_geometry/vendor/tg/VERSION +1 -1
- data/ext/tg_geometry/vendor/tg/tg.c +1854 -1339
- data/ext/tg_geometry/vendor/tg/tg.h +18 -17
- data/lib/tg/geometry/version.rb +1 -1
- data/script/vendor_libs.rb +11 -7
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27136bafb4a0114a2921af0413c047391bfffb38a6ff41f6c4f011f7cf4ab2ad
|
|
4
|
+
data.tar.gz: 7b2148e42f78fa74b3d5ac41e492e78673dd2617aa887c0023affcfa8fe555bf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e91a5dd12b3240540e9f6171c9c8a3c0f959d9e24779e392ae6c02c2e478712d2585bff5e37b3524bdf7d3b8581ebb30e14029b4aa038eb549229f92f0eee388
|
|
7
|
+
data.tar.gz: 806918f8eaaa7025b0693ef07e6d244e37cdb4998ff4cc5845a752c4b200f2cb341391a43f37afbdb696525e18d3415f55833c8d09712c31161eb2e879e179f5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.3 - 28.10.2026
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Updated vendored `tidwall/tg`, `rtree.c` to v0.6.1, and `json.c` to pinned upstream revisions.
|
|
8
|
+
- Vendor sync now removes trailing whitespace from C sources and headers.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Avoided querying TG parse errors when geometry allocation fails in FeatureSource.
|
|
13
|
+
- Corrected JSON UTF-16 surrogate boundary handling through the updated `json.c` vendor.
|
|
14
|
+
|
|
3
15
|
## 0.3.2 - unreleased
|
|
4
16
|
|
|
5
17
|
### Added
|
data/README.md
CHANGED
|
@@ -8,7 +8,25 @@ It exposes the public Ruby namespace `TG::Geometry`:
|
|
|
8
8
|
require "tg/geometry"
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
The gem is focused on fast in-process planar geometry parsing, predicates, format conversion, GeoJSON FeatureCollection imports, and immutable geofencing indexes. It is not a full GIS system.
|
|
11
|
+
The gem is focused on fast in-process planar geometry parsing, predicates, format conversion, GeoJSON FeatureCollection imports, point-to-geometry distance queries, and immutable geofencing indexes. It is not a full GIS system.
|
|
12
|
+
|
|
13
|
+
## Start here
|
|
14
|
+
|
|
15
|
+
For runnable examples, use **[GET_STARTED.md](GET_STARTED.md)**.
|
|
16
|
+
|
|
17
|
+
That guide shows the normal path from install to real queries:
|
|
18
|
+
|
|
19
|
+
- parsing GeoJSON/WKT/WKB/Hex/GeoBIN;
|
|
20
|
+
- direct Ruby constructors;
|
|
21
|
+
- immutable `Index` builds;
|
|
22
|
+
- point geofencing queries;
|
|
23
|
+
- packed batch point queries;
|
|
24
|
+
- point-to-geometry distance in planar XY and approximate lon/lat meters;
|
|
25
|
+
- GeoJSON FeatureCollection imports;
|
|
26
|
+
- SRID/EWKB conversion;
|
|
27
|
+
- nearest segment helpers;
|
|
28
|
+
- Registry and optional ActiveRecord integration;
|
|
29
|
+
- safe immutable-index reload pattern.
|
|
12
30
|
|
|
13
31
|
## Installation
|
|
14
32
|
|
|
@@ -26,317 +44,86 @@ The extension builds from vendored C sources. It does not require GEOS, PostGIS,
|
|
|
26
44
|
|
|
27
45
|
Supported platforms: Linux and macOS on x86_64/aarch64. Windows is not supported for the first release.
|
|
28
46
|
|
|
29
|
-
##
|
|
30
|
-
|
|
31
|
-
```ruby
|
|
32
|
-
zone = TG::Geometry.parse_geojson(<<~JSON)
|
|
33
|
-
{
|
|
34
|
-
"type": "Polygon",
|
|
35
|
-
"coordinates": [[[0,0], [10,0], [10,10], [0,10], [0,0]]]
|
|
36
|
-
}
|
|
37
|
-
JSON
|
|
38
|
-
|
|
39
|
-
zone.frozen? # => true
|
|
40
|
-
zone.type # => :polygon
|
|
41
|
-
zone.covers_xy?(5, 5) # => true
|
|
42
|
-
zone.covers_xy?(0, 0) # => true, boundary is covered
|
|
43
|
-
zone.bbox # => #<TG::Geometry::Rect ...>
|
|
44
|
-
zone.to_wkt
|
|
45
|
-
zone.to_wkb
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Parse shortcuts:
|
|
49
|
-
|
|
50
|
-
```ruby
|
|
51
|
-
TG::Geometry.parse(str, format: :auto, index: :ystripes)
|
|
52
|
-
TG::Geometry.parse_geojson(str, index: :ystripes)
|
|
53
|
-
TG::Geometry.parse_wkt(str, index: :ystripes)
|
|
54
|
-
TG::Geometry.parse_wkb(bytes, index: :ystripes)
|
|
55
|
-
TG::Geometry.parse_hex(str, index: :ystripes)
|
|
56
|
-
TG::Geometry.parse_geobin(bytes, index: :ystripes)
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
`TG::Geometry::Geom` objects are immutable and cannot be manually allocated or manually freed from Ruby.
|
|
60
|
-
|
|
61
|
-
## Constructors
|
|
62
|
-
|
|
63
|
-
Construct simple planar geometries directly from Ruby arrays without parsing strings or bytes:
|
|
64
|
-
|
|
65
|
-
```ruby
|
|
66
|
-
line = TG::Geometry.line_string([[0.0, 0.0], [10.0, 0.0]], index: :natural, srid: 4326)
|
|
67
|
-
poly = TG::Geometry.polygon(
|
|
68
|
-
[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]],
|
|
69
|
-
holes: [[[2, 2], [4, 2], [4, 4], [2, 4], [2, 2]]],
|
|
70
|
-
index: :ystripes,
|
|
71
|
-
srid: 4326
|
|
72
|
-
)
|
|
73
|
-
mp = TG::Geometry.multi_polygon([
|
|
74
|
-
{ exterior: [[0, 0], [1, 0], [1, 1], [0, 0]], holes: [] },
|
|
75
|
-
[[10, 10], [11, 10], [11, 11], [10, 10]]
|
|
76
|
-
])
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Constructors are strict: rings must already be closed, invalid coordinates raise `TG::Geometry::ArgumentError`, and no winding/self-intersection fixes are performed. `srid:` is metadata on the wrapper; it does not alter coordinates or perform reprojection.
|
|
80
|
-
|
|
81
|
-
## SRID and EWKB
|
|
82
|
-
|
|
83
|
-
`parse_wkb` and `parse_hex` preserve EWKB SRID metadata when the EWKB SRID flag is present:
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
geom = TG::Geometry.parse_wkb(postgis_bytea)
|
|
87
|
-
geom.srid # => 4326, 3857, 0, or nil for plain WKB
|
|
88
|
-
```
|
|
47
|
+
## API map
|
|
89
48
|
|
|
90
|
-
|
|
49
|
+
This README intentionally stays compact. The runnable examples live in [GET_STARTED.md](GET_STARTED.md); deeper design notes live in `docs/`.
|
|
91
50
|
|
|
92
|
-
|
|
93
|
-
ewkb = geom.to_ewkb # uses geom.srid
|
|
94
|
-
ewkb = geom.to_ewkb(srid: 4326) # explicit override
|
|
95
|
-
```
|
|
51
|
+
### Parsing and format conversion
|
|
96
52
|
|
|
97
|
-
|
|
53
|
+
`TG::Geometry.parse` auto-detects GeoJSON, WKT, WKB, Hex, and GeoBIN. Explicit shortcuts are also available: `parse_geojson`, `parse_wkt`, `parse_wkb`, `parse_hex`, and `parse_geobin`.
|
|
98
54
|
|
|
99
|
-
|
|
55
|
+
`TG::Geometry::Geom` objects are immutable and can be converted back to WKT/WKB/GeoJSON where supported.
|
|
100
56
|
|
|
101
|
-
|
|
102
|
-
rect = TG::Geometry::Rect.new(0, 0, 10, 10)
|
|
103
|
-
rect.center # => [5.0, 5.0]
|
|
104
|
-
rect.contains_point?(5, 5) # => true
|
|
105
|
-
rect.intersects?(other_rect)
|
|
106
|
-
rect.expand_to_include(other_rect)
|
|
107
|
-
rect.expand_to_include_point(x, y)
|
|
108
|
-
```
|
|
57
|
+
See [GET_STARTED.md#2-parse-a-geometry](GET_STARTED.md#2-parse-a-geometry).
|
|
109
58
|
|
|
110
|
-
|
|
59
|
+
### Constructors
|
|
111
60
|
|
|
112
|
-
|
|
61
|
+
Planar geometries can be constructed directly from Ruby arrays without parsing strings or bytes: point, multipoint, line string, polygon, multiline, multipolygon, and geometry collection helpers are available.
|
|
113
62
|
|
|
114
|
-
|
|
115
|
-
entries = [
|
|
116
|
-
[:zone_a, '{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}'],
|
|
117
|
-
[:zone_b, '{"type":"Polygon","coordinates":[[[20,20],[30,20],[30,30],[20,30],[20,20]]]}']
|
|
118
|
-
]
|
|
119
|
-
|
|
120
|
-
index = TG::Geometry::Index.build(
|
|
121
|
-
entries,
|
|
122
|
-
via: :geojson,
|
|
123
|
-
strategy: :rtree,
|
|
124
|
-
predicate: :covers,
|
|
125
|
-
geometry_index: :ystripes
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
index.frozen? # => true
|
|
129
|
-
index.size # => 2
|
|
130
|
-
index.strategy # => :rtree
|
|
131
|
-
index.predicate # => :covers
|
|
132
|
-
index.find_covering(5, 5) # => :zone_a
|
|
133
|
-
index.covering_ids(5, 5) # => [:zone_a]
|
|
134
|
-
index.intersecting_rect(0, 0, 25, 25)
|
|
135
|
-
```
|
|
63
|
+
See [GET_STARTED.md#3-construct-geometries-directly](GET_STARTED.md#3-construct-geometries-directly).
|
|
136
64
|
|
|
137
|
-
|
|
65
|
+
### SRID and EWKB
|
|
138
66
|
|
|
139
|
-
|
|
140
|
-
[[id1, object1], [id2, object2], ...]
|
|
141
|
-
```
|
|
67
|
+
EWKB SRID metadata is preserved on parse when present. `to_wkb` writes plain WKB; `to_ewkb` writes a PostGIS-compatible SRID-bearing payload. SRID is metadata only: no coordinate transformation is performed.
|
|
142
68
|
|
|
143
|
-
|
|
69
|
+
See [GET_STARTED.md#8-preserve-srid-and-write-ewkb](GET_STARTED.md#8-preserve-srid-and-write-ewkb) and [docs/SRID_AND_EWKB.md](docs/SRID_AND_EWKB.md).
|
|
144
70
|
|
|
145
|
-
|
|
146
|
-
- Every entry must be a two-element Array.
|
|
147
|
-
- `id` may be any Ruby object except `nil`.
|
|
148
|
-
- Duplicate ids are allowed.
|
|
149
|
-
- Returned ids are the same Ruby objects stored in the index.
|
|
150
|
-
- Result order is insertion order for both `:flat` and `:rtree`.
|
|
71
|
+
### Rect
|
|
151
72
|
|
|
152
|
-
|
|
73
|
+
`TG::Geometry::Rect` is an immutable helper for bounding boxes, containment checks, intersections, and expansion.
|
|
153
74
|
|
|
154
|
-
|
|
155
|
-
- `:geojson` — parse and own native TG geometries inside the index.
|
|
156
|
-
- `:wkb` — parse and own native TG geometries inside the index.
|
|
75
|
+
See [GET_STARTED.md#4-use-rect-helpers](GET_STARTED.md#4-use-rect-helpers).
|
|
157
76
|
|
|
158
|
-
|
|
77
|
+
### Immutable Index
|
|
159
78
|
|
|
160
|
-
|
|
161
|
-
- `:rtree`
|
|
79
|
+
`TG::Geometry::Index` stores immutable entries with explicit `via:`, `strategy:`, and `predicate:` options. `:rtree` builds a spatial index; `:flat` does a linear scan with bbox prefiltering. There is no `:auto` strategy by design.
|
|
162
80
|
|
|
163
|
-
|
|
81
|
+
See [GET_STARTED.md#5-build-an-immutable-index](GET_STARTED.md#5-build-an-immutable-index).
|
|
164
82
|
|
|
165
|
-
|
|
166
|
-
- `:contains` — stricter containment semantics.
|
|
83
|
+
### Point and geometry queries
|
|
167
84
|
|
|
168
|
-
|
|
85
|
+
Index methods cover point geofencing, packed batch point queries, rectangle intersection, and geometry-vs-geometry predicates.
|
|
169
86
|
|
|
170
|
-
|
|
87
|
+
See:
|
|
171
88
|
|
|
172
|
-
|
|
89
|
+
- [GET_STARTED.md#5-build-an-immutable-index](GET_STARTED.md#5-build-an-immutable-index)
|
|
90
|
+
- [GET_STARTED.md#6-batch-point-queries](GET_STARTED.md#6-batch-point-queries)
|
|
91
|
+
- [docs/GEOMETRY_QUERIES.md](docs/GEOMETRY_QUERIES.md)
|
|
173
92
|
|
|
174
|
-
|
|
175
|
-
query = TG::Geometry.polygon([[1, 1], [2, 1], [2, 2], [1, 1]])
|
|
176
|
-
index.intersecting_geom_ids(query)
|
|
177
|
-
index.covering_geom_ids(query)
|
|
178
|
-
index.containing_geom_ids(query)
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Predicate direction is explicit:
|
|
182
|
-
|
|
183
|
-
| Method | Predicate direction | Boundary semantics |
|
|
184
|
-
| --- | --- | --- |
|
|
185
|
-
| `intersecting_geom_ids(query)` | stored geom intersects query | any intersection |
|
|
186
|
-
| `covering_geom_ids(query)` | stored geom covers query | boundary included |
|
|
187
|
-
| `containing_geom_ids(query)` | stored geom contains query | strict interior; boundary excluded |
|
|
93
|
+
### Point-to-geometry distance
|
|
188
94
|
|
|
189
|
-
|
|
95
|
+
Distance APIs are explicit about units:
|
|
190
96
|
|
|
97
|
+
- `*_xy` returns input coordinate units;
|
|
98
|
+
- `*_lnglat_meters` returns approximate local meters for lon/lat geofencing.
|
|
191
99
|
|
|
100
|
+
There is no `metric:` option, no automatic lon/lat-vs-XY detection, no kNN index, and no geodesic/great-circle segment distance.
|
|
192
101
|
|
|
193
|
-
|
|
102
|
+
See [GET_STARTED.md#7-measure-distance-to-a-zone](GET_STARTED.md#7-measure-distance-to-a-zone) and [docs/GEOMETRY_QUERIES.md](docs/GEOMETRY_QUERIES.md).
|
|
194
103
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```ruby
|
|
198
|
-
zone.distance_to_lnglat_meters(lng, lat) # => Float, approximate meters
|
|
199
|
-
zone.boundary_distance_to_lnglat_meters(lng, lat) # => Float, approximate meters
|
|
200
|
-
zone.nearest_point_lnglat(lng, lat) # => [lng, lat]
|
|
104
|
+
### GeoJSON FeatureSource
|
|
201
105
|
|
|
202
|
-
|
|
203
|
-
zone.boundary_distance_to_xy(x, y) # => Float in input coordinate units
|
|
204
|
-
zone.nearest_point_xy(x, y) # => [x, y]
|
|
205
|
-
```
|
|
106
|
+
`TG::Geometry::FeatureSource` reads GeoJSON `FeatureCollection` sources without `JSON.parse` of the whole document into Ruby Hash/Array objects. It can return index entries, feature triples with raw properties JSON, reports, or build an index directly.
|
|
206
107
|
|
|
207
|
-
|
|
108
|
+
See [GET_STARTED.md#9-import-a-geojson-featurecollection](GET_STARTED.md#9-import-a-geojson-featurecollection) and [docs/FEATURE_SOURCE.md](docs/FEATURE_SOURCE.md).
|
|
208
109
|
|
|
209
|
-
|
|
110
|
+
### Nearest segment
|
|
210
111
|
|
|
211
|
-
|
|
112
|
+
`Line#nearest_segment(x, y)` and `Ring#nearest_segment(x, y)` expose low-level planar nearest-segment helpers.
|
|
212
113
|
|
|
213
|
-
|
|
114
|
+
See [GET_STARTED.md#10-use-nearest-segment-helpers](GET_STARTED.md#10-use-nearest-segment-helpers) and [docs/NEAREST_SEGMENT.md](docs/NEAREST_SEGMENT.md).
|
|
214
115
|
|
|
215
|
-
|
|
216
|
-
index.within_distance_lnglat_meters(lng, lat, radius_m, sort: false)
|
|
217
|
-
# => [[id, distance_m], ...]
|
|
218
|
-
index.within_distance_ids_lnglat_meters(lng, lat, radius_m)
|
|
219
|
-
# => [id, ...]
|
|
220
|
-
|
|
221
|
-
index.within_distance_xy(x, y, radius, sort: false)
|
|
222
|
-
# => [[id, distance], ...]
|
|
223
|
-
index.within_distance_ids_xy(x, y, radius)
|
|
224
|
-
# => [id, ...]
|
|
225
|
-
```
|
|
116
|
+
### Registry helper
|
|
226
117
|
|
|
227
|
-
`
|
|
118
|
+
`TG::Geometry::Registry` is Ruby-level sugar over immutable indexes with safe reload semantics.
|
|
228
119
|
|
|
229
|
-
|
|
120
|
+
See [GET_STARTED.md#11-wrap-an-index-in-a-registry](GET_STARTED.md#11-wrap-an-index-in-a-registry).
|
|
230
121
|
|
|
231
|
-
|
|
122
|
+
### Optional ActiveRecord integration
|
|
232
123
|
|
|
233
|
-
`TG::Geometry::
|
|
124
|
+
`TG::Geometry::ActiveRecordType` is an optional read-only convenience type for PostGIS-like geometry columns. It is not required by `tg/geometry`; load it explicitly.
|
|
234
125
|
|
|
235
|
-
|
|
236
|
-
entries = TG::Geometry::FeatureSource.read_entries_file(
|
|
237
|
-
"zones.geojson",
|
|
238
|
-
id: ["properties", "@id"],
|
|
239
|
-
only: [:polygon, :multipolygon]
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
index = TG::Geometry::Index.build(
|
|
243
|
-
entries,
|
|
244
|
-
via: :geojson,
|
|
245
|
-
strategy: :rtree,
|
|
246
|
-
predicate: :covers
|
|
247
|
-
)
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
For imports that also need raw properties JSON:
|
|
251
|
-
|
|
252
|
-
```ruby
|
|
253
|
-
features = TG::Geometry::FeatureSource.read_features_file(
|
|
254
|
-
"zones.geojson",
|
|
255
|
-
id: ["properties", "@id"],
|
|
256
|
-
report: true,
|
|
257
|
-
on_invalid: :skip
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
features[:features].each do |id, geometry_json, properties_json|
|
|
261
|
-
# Store geometry_json and parse properties_json in application code if needed.
|
|
262
|
-
end
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
For direct file-to-index loading:
|
|
266
|
-
|
|
267
|
-
```ruby
|
|
268
|
-
index = TG::Geometry::FeatureSource.build_index_file(
|
|
269
|
-
"zones.geojson",
|
|
270
|
-
id: ["properties", "@id"],
|
|
271
|
-
strategy: :rtree,
|
|
272
|
-
predicate: :covers
|
|
273
|
-
)
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
FeatureSource methods are explicit: use `_file` for paths, `_json` for raw content strings, and `_io` for IO objects. There is no path/content auto-detection.
|
|
277
|
-
|
|
278
|
-
## Packed batch point queries
|
|
279
|
-
|
|
280
|
-
```ruby
|
|
281
|
-
points = [5.0, 5.0, 25.0, 25.0].pack("d*")
|
|
282
|
-
index.covering_ids_batch_packed(points)
|
|
283
|
-
# => [:zone_a, :zone_b]
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
Input is a Ruby String containing native-endian doubles in `lon, lat` pairs. Length must be a multiple of 16 bytes. Empty string returns `[]`.
|
|
287
|
-
|
|
288
|
-
## Nearest segment
|
|
289
|
-
|
|
290
|
-
`Line#nearest_segment(x, y)` and `Ring#nearest_segment(x, y)` return a frozen `TG::Geometry::NearestSegment`:
|
|
291
|
-
|
|
292
|
-
```ruby
|
|
293
|
-
nearest = polygon.polygon.exterior_ring.nearest_segment(5, 5)
|
|
294
|
-
nearest.segment # => TG::Geometry::Segment
|
|
295
|
-
nearest.index # => segment index
|
|
296
|
-
nearest.distance # => Float
|
|
297
|
-
nearest.point # => [x, y] projection on the segment
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
Distance is planar Euclidean distance in input coordinate units. It is not meters unless your input coordinates are already meters. Equal-distance tie-breaks follow tg iteration order and are not API-stable.
|
|
301
|
-
|
|
302
|
-
## Registry helper
|
|
303
|
-
|
|
304
|
-
`Registry` is Ruby-level sugar over immutable indexes:
|
|
305
|
-
|
|
306
|
-
```ruby
|
|
307
|
-
class DeliveryZones < TG::Geometry::Registry
|
|
308
|
-
source do
|
|
309
|
-
[
|
|
310
|
-
[:zone_a, '{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}']
|
|
311
|
-
]
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
index_options via: :geojson, strategy: :rtree, predicate: :covers
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
registry = DeliveryZones.new
|
|
318
|
-
registry.reload!
|
|
319
|
-
registry.find_covering(5, 5)
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
Reload builds a new immutable index first and swaps the reference only after a successful build. Existing readers keep using the previous index safely.
|
|
323
|
-
|
|
324
|
-
## ActiveRecord integration — read-only
|
|
325
|
-
|
|
326
|
-
`TG::Geometry::ActiveRecordType` is an optional read-only convenience type for PostGIS columns. It is not required by `tg/geometry`; load it explicitly:
|
|
327
|
-
|
|
328
|
-
```ruby
|
|
329
|
-
require "tg/geometry/active_record_type"
|
|
330
|
-
|
|
331
|
-
class Zone < ApplicationRecord
|
|
332
|
-
attribute :geom, TG::Geometry::ActiveRecordType.new
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
zone.geom.srid
|
|
336
|
-
zone.geom.covers_xy?(lon, lat)
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
It can deserialize EWKB bytes, hex EWKB, `\x`-prefixed hex EWKB, GeoJSON, and WKT. Writing `Geom` values is intentionally unsupported in v0.3.0. User applications need `activemodel >= 6.0`; `activerecord` is not a gem dependency.
|
|
126
|
+
See [GET_STARTED.md#12-optional-activerecord-read-only-type](GET_STARTED.md#12-optional-activerecord-read-only-type).
|
|
340
127
|
|
|
341
128
|
## Memory and concurrency
|
|
342
129
|
|
|
@@ -346,25 +133,13 @@ The implementation uses explicit allocator pairs and Ruby GC accounting for nati
|
|
|
346
133
|
|
|
347
134
|
No Ractor support and no universal performance claim are advertised.
|
|
348
135
|
|
|
349
|
-
|
|
136
|
+
See [docs/MEMORY_OWNERSHIP.md](docs/MEMORY_OWNERSHIP.md) and [docs/CONCURRENCY.md](docs/CONCURRENCY.md).
|
|
350
137
|
|
|
351
|
-
|
|
138
|
+
## Benchmarks
|
|
352
139
|
|
|
353
|
-
|
|
354
|
-
bundle exec ruby benchmark/parse_throughput.rb
|
|
355
|
-
bundle exec ruby benchmark/flat_vs_rtree.rb
|
|
356
|
-
bundle exec ruby benchmark/batch_packed_vs_loop.rb
|
|
357
|
-
bundle exec ruby benchmark/objectspace_memsize.rb
|
|
358
|
-
bundle exec ruby benchmark/rss_stability.rb
|
|
359
|
-
bundle exec ruby benchmark/gvl_threshold.rb
|
|
360
|
-
bundle exec ruby benchmark/falcon_concurrency.rb
|
|
361
|
-
bundle exec ruby benchmark/feature_source.rb
|
|
362
|
-
bundle exec ruby benchmark/geom_query.rb
|
|
363
|
-
bundle exec ruby benchmark/nearest_segment.rb
|
|
364
|
-
bundle exec ruby benchmark/ewkb_roundtrip.rb
|
|
365
|
-
```
|
|
140
|
+
Benchmark scripts live in `benchmark/`. They are engineering tools, not marketing claims.
|
|
366
141
|
|
|
367
|
-
|
|
142
|
+
Start with [docs/BENCHMARKING.md](docs/BENCHMARKING.md).
|
|
368
143
|
|
|
369
144
|
## Limitations
|
|
370
145
|
|
|
@@ -385,7 +160,21 @@ Not included:
|
|
|
385
160
|
- Z/M variants of array constructors;
|
|
386
161
|
- Public `release_gvl:` option.
|
|
387
162
|
|
|
388
|
-
TG works in planar XY coordinates. If lon/lat coordinates are passed in, length, area, perimeter, and low-level nearest-segment distances are in input coordinate units, not meters. The explicit `*_lnglat_meters` point-to-geometry APIs are the exception: they return approximate local meters using
|
|
163
|
+
TG works in planar XY coordinates. If lon/lat coordinates are passed in, length, area, perimeter, and low-level nearest-segment distances are in input coordinate units, not meters. The explicit `*_lnglat_meters` point-to-geometry APIs are the exception: they return approximate local meters using a query-local equirectangular frame, not geodesic meters.
|
|
164
|
+
|
|
165
|
+
See [docs/LIMITATIONS.md](docs/LIMITATIONS.md) for the full list.
|
|
166
|
+
|
|
167
|
+
## Documentation
|
|
168
|
+
|
|
169
|
+
- [GET_STARTED.md](GET_STARTED.md) — runnable examples
|
|
170
|
+
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — how the C extension is wired
|
|
171
|
+
- [docs/MEMORY_OWNERSHIP.md](docs/MEMORY_OWNERSHIP.md) — allocator pair table
|
|
172
|
+
- [docs/CONCURRENCY.md](docs/CONCURRENCY.md) — threading rules
|
|
173
|
+
- [docs/ERROR_HANDLING.md](docs/ERROR_HANDLING.md) — exception hierarchy
|
|
174
|
+
- [docs/BENCHMARKING.md](docs/BENCHMARKING.md) — running the benchmark scripts
|
|
175
|
+
- [docs/FEATURE_SOURCE.md](docs/FEATURE_SOURCE.md) — GeoJSON FeatureCollection imports
|
|
176
|
+
- [docs/GEOMETRY_QUERIES.md](docs/GEOMETRY_QUERIES.md) — geometry query details
|
|
177
|
+
- [docs/LIMITATIONS.md](docs/LIMITATIONS.md) — non-goals and accuracy boundaries
|
|
389
178
|
|
|
390
179
|
## Development
|
|
391
180
|
|
|
@@ -402,6 +191,7 @@ bundle exec rspec spec/batch_packed_spec.rb
|
|
|
402
191
|
bundle exec rspec spec/memory_gc_spec.rb
|
|
403
192
|
bundle exec rspec spec/concurrency_spec.rb
|
|
404
193
|
bundle exec rspec spec/fuzz_spec.rb
|
|
194
|
+
bundle exec rspec spec/distance_spec.rb
|
|
405
195
|
```
|
|
406
196
|
|
|
407
197
|
## License
|
|
@@ -6092,8 +6092,6 @@ static char *fs_safe_tg_error_message(struct tg_geom *geom) {
|
|
|
6092
6092
|
char *msg;
|
|
6093
6093
|
int n;
|
|
6094
6094
|
|
|
6095
|
-
if (!geom)
|
|
6096
|
-
return fs_safe_strdup_cstr("TG geometry allocation failed");
|
|
6097
6095
|
err = tg_geom_error(geom);
|
|
6098
6096
|
if (!err)
|
|
6099
6097
|
return NULL;
|
|
@@ -6112,12 +6110,13 @@ static bool fs_safe_validate_geometry(fs_safe_job_t *job, struct json geometry,
|
|
|
6112
6110
|
*skipped_out = false;
|
|
6113
6111
|
struct tg_geom *geom = tg_parse_geojsonn_ix(json_raw(geometry), json_raw_length(geometry),
|
|
6114
6112
|
job->opts.geometry_index);
|
|
6115
|
-
char *message
|
|
6113
|
+
char *message;
|
|
6116
6114
|
|
|
6117
6115
|
if (!geom) {
|
|
6118
6116
|
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "TG geometry allocation failed");
|
|
6119
6117
|
return false;
|
|
6120
6118
|
}
|
|
6119
|
+
message = fs_safe_tg_error_message(geom);
|
|
6121
6120
|
|
|
6122
6121
|
if (message) {
|
|
6123
6122
|
if (job->opts.on_invalid == FS_ON_INVALID_SKIP) {
|
|
@@ -6251,11 +6250,11 @@ static bool fs_safe_fill_build(fs_safe_job_t *job, struct json features) {
|
|
|
6251
6250
|
|
|
6252
6251
|
geom = tg_parse_geojsonn_ix(json_raw(prepared.geometry), json_raw_length(prepared.geometry),
|
|
6253
6252
|
job->opts.geometry_index);
|
|
6254
|
-
message = fs_safe_tg_error_message(geom);
|
|
6255
6253
|
if (!geom) {
|
|
6256
6254
|
fs_safe_set_error(job, FS_SAFE_ERROR_NOMEM, "TG geometry allocation failed");
|
|
6257
6255
|
return false;
|
|
6258
6256
|
}
|
|
6257
|
+
message = fs_safe_tg_error_message(geom);
|
|
6259
6258
|
if (message) {
|
|
6260
6259
|
fs_safe_set_feature_error(job, FS_SAFE_ERROR_PARSE, feature_index,
|
|
6261
6260
|
fs_safe_json_offset(job, prepared.geometry), message);
|
|
@@ -3,20 +3,20 @@ gem=tg_geometry
|
|
|
3
3
|
libraries=tg,rtree,json
|
|
4
4
|
tg_repo=https://github.com/tidwall/tg.git
|
|
5
5
|
tg_ref=main
|
|
6
|
-
tg_commit=
|
|
6
|
+
tg_commit=532fc38e1a7bd440339cf83259a9d7983f04ad69
|
|
7
7
|
tg_target=tg
|
|
8
8
|
tg_files=tg.c,tg.h,LICENSE,README.md,VERSION
|
|
9
|
-
tg_tree_sha256=
|
|
9
|
+
tg_tree_sha256=dbf9a49d5ff326974312e94fb39c5687260a2c2b27b10971b24635ed6f79184c
|
|
10
10
|
rtree_repo=https://github.com/tidwall/rtree.c.git
|
|
11
|
-
rtree_ref=v0.
|
|
12
|
-
rtree_commit=
|
|
11
|
+
rtree_ref=v0.6.1
|
|
12
|
+
rtree_commit=eebc774929f89fd5f4095f7c8835a87f2d2fb73f
|
|
13
13
|
rtree_target=rtree
|
|
14
14
|
rtree_files=rtree.c,rtree.h,LICENSE,README.md,VERSION
|
|
15
|
-
rtree_tree_sha256=
|
|
15
|
+
rtree_tree_sha256=6779f4b2c9df8f199907d26c3fba11bc07c6f923d80881577c19f50119e15177
|
|
16
16
|
json_repo=https://github.com/tidwall/json.c.git
|
|
17
17
|
json_ref=main
|
|
18
|
-
json_commit=
|
|
18
|
+
json_commit=104d8cbf5d09ceba37d469574cbf5696349bcaf1
|
|
19
19
|
json_target=json
|
|
20
20
|
json_files=json.c,json.h,LICENSE,VERSION
|
|
21
|
-
json_tree_sha256=
|
|
22
|
-
manifest_sha256=
|
|
21
|
+
json_tree_sha256=764155b839b910f45aaaf0d0881a47570d4ef0919465c5117c7658ab3dac0594
|
|
22
|
+
manifest_sha256=fc8693eafbd0310bfae0d4f8e9d1d68344da7e5169acfb65cdcfc8be33f1b692
|