tg_geometry 0.2.0 → 0.3.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 +37 -0
- data/README.md +107 -14
- data/benchmark/ewkb_roundtrip.rb +29 -0
- data/benchmark/geom_query.rb +33 -0
- data/benchmark/nearest_segment.rb +20 -0
- data/docs/GEOMETRY_QUERIES.md +23 -0
- data/docs/LIMITATIONS.md +12 -10
- data/docs/NEAREST_SEGMENT.md +17 -0
- data/docs/SRID_AND_EWKB.md +23 -0
- data/ext/tg_geometry/tg_geometry_ext.c +1176 -4
- data/lib/tg/geometry/active_record_source.rb +16 -34
- data/lib/tg/geometry/active_record_type.rb +61 -0
- data/lib/tg/geometry/registry.rb +17 -68
- data/lib/tg/geometry/version.rb +1 -1
- data/lib/tg/geometry.rb +85 -0
- data/spec/active_record_type_spec.rb +45 -0
- data/spec/constructors_spec.rb +104 -0
- data/spec/fixtures/feature_source/invalid_geometry_middle.geojson +8 -0
- data/spec/fixtures/feature_source/malformed_json.geojson +1 -0
- data/spec/fixtures/feature_source/mixed_geometry_types.geojson +8 -0
- data/spec/fixtures/feature_source/osm_like_feature_collection.geojson +10 -0
- data/spec/fixtures/feature_source/properties_null_missing.geojson +7 -0
- data/spec/fixtures/feature_source/simple_feature_collection.geojson +15 -0
- data/spec/fixtures/postgis/README.md +16 -0
- data/spec/fixtures/postgis/boundary_point_cases.geojson +83 -0
- data/spec/fixtures/postgis/multipolygon_large.ewkb +0 -0
- data/spec/fixtures/postgis/point_4326.ewkb +0 -0
- data/spec/fixtures/postgis/polygon_3857.ewkb +0 -0
- data/spec/fixtures/postgis/polygon_4326_simple.ewkb +0 -0
- data/spec/fixtures/postgis/polygon_4326_with_hole.ewkb +0 -0
- data/spec/index_geom_query_spec.rb +68 -0
- data/spec/keyword_validation_spec.rb +31 -0
- data/spec/nearest_segment_spec.rb +62 -0
- data/spec/postgis_fixtures_spec.rb +68 -0
- data/spec/srid_spec.rb +43 -0
- data/spec/to_ewkb_spec.rb +37 -0
- metadata +50 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fdc6ab78992bee6ba1708b13adfdb0e4eec26fef04c4fc8eae87df21fbfed5fa
|
|
4
|
+
data.tar.gz: ada3eff77de10cb101cbedbd35e2d40e119fe37c5a09003afe9df1ab266827fe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87200faec4b6d0442eab7df12f768807193aed725bb75b25502a64cc5b2e24e97f2f9d98cf9254c54e3f62d9b0481f39dc2287818f69a740e9728a33e97d9f0b
|
|
7
|
+
data.tar.gz: 3e14f4563facef36fc109e1c8006b707502bb384f67c1c27da05faa7dcec7b7a7970d82a22d39ac99c00595c74944c7a6467fa81a5cdd1d14129df70f4d3675a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 27.05.2026
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `TG::Geometry.line_string`, `TG::Geometry.polygon`, and `TG::Geometry.multi_polygon` constructors from Ruby arrays.
|
|
8
|
+
- `TG::Geometry::Geom#srid` with automatic EWKB SRID extraction in `parse_wkb` and `parse_hex`.
|
|
9
|
+
- `TG::Geometry::Geom#to_ewkb` writer with explicit `srid:` override.
|
|
10
|
+
- `TG::Geometry::Index#intersecting_geom_ids`, `#covering_geom_ids`, and `#containing_geom_ids`.
|
|
11
|
+
- `TG::Geometry::Line#nearest_segment` and `TG::Geometry::Ring#nearest_segment` with `TG::Geometry::NearestSegment` result objects.
|
|
12
|
+
- `TG::Geometry::ActiveRecordType` read-only optional require.
|
|
13
|
+
- PostGIS/EWKB fixture suite in `spec/fixtures/postgis/`.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Reject unsupported/unknown keywords on v0.3.0 APIs instead of silently ignoring them, including non-goals such as `release_gvl:`, `parse_wkb(srid:)`, and `parse_wkb(ewkb:)`.
|
|
18
|
+
- Harden constructor cleanup paths so intermediate C `tg_ring` / `tg_poly` objects are released when Ruby exceptions occur during nested polygon construction.
|
|
19
|
+
- Keep geometry-index query collection in a C-only status-returning phase before Ruby result materialization, preserving the intended no-GVL-safe internal shape.
|
|
20
|
+
|
|
21
|
+
### Clarified
|
|
22
|
+
|
|
23
|
+
- `Index.build(predicate:)` affects only legacy point query methods: `find_covering`, `covering_ids(x, y)`, and `covering_ids_batch_packed`.
|
|
24
|
+
|
|
25
|
+
### Not included
|
|
26
|
+
|
|
27
|
+
- Geodesic / Haversine distance.
|
|
28
|
+
- Projection / reprojection.
|
|
29
|
+
- Buffer / union / difference / convex hull.
|
|
30
|
+
- Index nearest_ids (KNN).
|
|
31
|
+
- GeoBIN bbox helpers.
|
|
32
|
+
- Streaming FeatureSource.
|
|
33
|
+
- Index serialization / mmap.
|
|
34
|
+
- Ractor support.
|
|
35
|
+
- Windows / JRuby.
|
|
36
|
+
- Write-side ActiveRecordType / AR scopes / migrations.
|
|
37
|
+
- Z/M variants of constructors.
|
|
38
|
+
- Public `release_gvl:` option.
|
|
39
|
+
|
|
3
40
|
## 0.1.0 - unreleased
|
|
4
41
|
|
|
5
42
|
Initial public release candidate for `tg_geometry`.
|
data/README.md
CHANGED
|
@@ -58,6 +58,44 @@ TG::Geometry.parse_geobin(bytes, index: :ystripes)
|
|
|
58
58
|
|
|
59
59
|
`TG::Geometry::Geom` objects are immutable and cannot be manually allocated or manually freed from Ruby.
|
|
60
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
|
+
```
|
|
89
|
+
|
|
90
|
+
`to_wkb` always writes plain WKB. Use `to_ewkb` when a PostGIS-compatible SRID-bearing payload is required:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
ewkb = geom.to_ewkb # uses geom.srid
|
|
94
|
+
ewkb = geom.to_ewkb(srid: 4326) # explicit override
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
SRID is metadata only. `tg_geometry` does not check SRID compatibility, transform coordinates, or calculate geodesic distances.
|
|
98
|
+
|
|
61
99
|
## Rect
|
|
62
100
|
|
|
63
101
|
```ruby
|
|
@@ -127,8 +165,29 @@ Accepted predicates:
|
|
|
127
165
|
- `:covers` — default for geofencing; boundary points are included.
|
|
128
166
|
- `:contains` — stricter containment semantics.
|
|
129
167
|
|
|
168
|
+
The `predicate:` option affects only the legacy point-based query methods (`find_covering`, `covering_ids(x, y)`, `covering_ids_batch_packed`). The geometry-based query methods (`intersecting_geom_ids`, `covering_geom_ids`, `containing_geom_ids`) use their own predicates based on the method name.
|
|
169
|
+
|
|
130
170
|
`strategy: :auto` is intentionally not exposed. Choose the strategy explicitly and benchmark on your own data.
|
|
131
171
|
|
|
172
|
+
## Geometry-based index queries
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
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 |
|
|
188
|
+
|
|
189
|
+
Results are ids only and preserve insertion order. Duplicate ids remain possible if duplicate ids were inserted.
|
|
190
|
+
|
|
132
191
|
## GeoJSON FeatureSource
|
|
133
192
|
|
|
134
193
|
`TG::Geometry::FeatureSource` reads GeoJSON `FeatureCollection` sources without `JSON.parse` of the whole document into Ruby Hash/Array objects.
|
|
@@ -186,6 +245,20 @@ index.covering_ids_batch_packed(points)
|
|
|
186
245
|
|
|
187
246
|
Input is a Ruby String containing native-endian doubles in `lon, lat` pairs. Length must be a multiple of 16 bytes. Empty string returns `[]`.
|
|
188
247
|
|
|
248
|
+
## Nearest segment
|
|
249
|
+
|
|
250
|
+
`Line#nearest_segment(x, y)` and `Ring#nearest_segment(x, y)` return a frozen `TG::Geometry::NearestSegment`:
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
nearest = polygon.polygon.exterior_ring.nearest_segment(5, 5)
|
|
254
|
+
nearest.segment # => TG::Geometry::Segment
|
|
255
|
+
nearest.index # => segment index
|
|
256
|
+
nearest.distance # => Float
|
|
257
|
+
nearest.point # => [x, y] projection on the segment
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
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.
|
|
261
|
+
|
|
189
262
|
## Registry helper
|
|
190
263
|
|
|
191
264
|
`Registry` is Ruby-level sugar over immutable indexes:
|
|
@@ -208,6 +281,23 @@ registry.find_covering(5, 5)
|
|
|
208
281
|
|
|
209
282
|
Reload builds a new immutable index first and swaps the reference only after a successful build. Existing readers keep using the previous index safely.
|
|
210
283
|
|
|
284
|
+
## ActiveRecord integration — read-only
|
|
285
|
+
|
|
286
|
+
`TG::Geometry::ActiveRecordType` is an optional read-only convenience type for PostGIS columns. It is not required by `tg/geometry`; load it explicitly:
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
require "tg/geometry/active_record_type"
|
|
290
|
+
|
|
291
|
+
class Zone < ApplicationRecord
|
|
292
|
+
attribute :geom, TG::Geometry::ActiveRecordType.new
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
zone.geom.srid
|
|
296
|
+
zone.geom.covers_xy?(lon, lat)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
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.
|
|
300
|
+
|
|
211
301
|
## Memory and concurrency
|
|
212
302
|
|
|
213
303
|
The implementation uses explicit allocator pairs and Ruby GC accounting for native memory. `ObjectSpace.memsize_of(index)` includes entries, owned TG geometries, and exact rtree allocation bytes. Borrowed geometries are not double-counted by the index.
|
|
@@ -229,6 +319,9 @@ bundle exec ruby benchmark/rss_stability.rb
|
|
|
229
319
|
bundle exec ruby benchmark/gvl_threshold.rb
|
|
230
320
|
bundle exec ruby benchmark/falcon_concurrency.rb
|
|
231
321
|
bundle exec ruby benchmark/feature_source.rb
|
|
322
|
+
bundle exec ruby benchmark/geom_query.rb
|
|
323
|
+
bundle exec ruby benchmark/nearest_segment.rb
|
|
324
|
+
bundle exec ruby benchmark/ewkb_roundtrip.rb
|
|
232
325
|
```
|
|
233
326
|
|
|
234
327
|
The benchmarks are engineering tools, not marketing claims.
|
|
@@ -239,20 +332,20 @@ The benchmarks are engineering tools, not marketing claims.
|
|
|
239
332
|
|
|
240
333
|
Not included:
|
|
241
334
|
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
245
|
-
-
|
|
246
|
-
-
|
|
247
|
-
-
|
|
248
|
-
-
|
|
249
|
-
-
|
|
250
|
-
-
|
|
251
|
-
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
|
|
255
|
-
TG works in planar XY coordinates. If lon/lat coordinates are passed in, length, area, and
|
|
335
|
+
- Geodesic / Haversine distance;
|
|
336
|
+
- Projection / reprojection;
|
|
337
|
+
- Buffer / union / difference / convex hull;
|
|
338
|
+
- Index nearest_ids (KNN);
|
|
339
|
+
- GeoBIN bbox helpers;
|
|
340
|
+
- Streaming FeatureSource;
|
|
341
|
+
- Index serialization / mmap;
|
|
342
|
+
- Ractor support;
|
|
343
|
+
- Windows / JRuby;
|
|
344
|
+
- Write-side ActiveRecordType / AR scopes / migrations;
|
|
345
|
+
- Z/M variants of array constructors;
|
|
346
|
+
- Public `release_gvl:` option.
|
|
347
|
+
|
|
348
|
+
TG works in planar XY coordinates. If lon/lat coordinates are passed in, length, area, perimeter, and nearest-segment distances are in input coordinate units, not meters.
|
|
256
349
|
|
|
257
350
|
## Development
|
|
258
351
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require_relative "_support"
|
|
5
|
+
|
|
6
|
+
geom = TG::Geometry.polygon([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], srid: 4326)
|
|
7
|
+
ewkb = geom.to_ewkb
|
|
8
|
+
|
|
9
|
+
Benchmark.bm(32) do |x|
|
|
10
|
+
x.report("tg parse_wkb -> to_ewkb 100k") do
|
|
11
|
+
100_000.times { TG::Geometry.parse_wkb(ewkb).to_ewkb }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if ENV["WITH_RGEO"]
|
|
16
|
+
begin
|
|
17
|
+
require "rgeo"
|
|
18
|
+
factory = RGeo::Cartesian.factory(srid: 4326)
|
|
19
|
+
rgeo_geom = factory.parse_wkt("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))")
|
|
20
|
+
|
|
21
|
+
Benchmark.bm(32) do |x|
|
|
22
|
+
x.report("rgeo WKT parse 100k") do
|
|
23
|
+
100_000.times { factory.parse_wkt(rgeo_geom.as_text) }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
rescue LoadError
|
|
27
|
+
warn "rgeo is not installed"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require_relative "_support"
|
|
5
|
+
|
|
6
|
+
COUNTS = [1_000, 10_000].freeze
|
|
7
|
+
STRATEGIES = %i[flat rtree].freeze
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def box(x, y, size = 1.0)
|
|
11
|
+
TG::Geometry.polygon([[x, y], [x + size, y], [x + size, y + size], [x, y + size], [x, y]])
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
COUNTS.each do |count|
|
|
15
|
+
entries = count.times.map do |i|
|
|
16
|
+
x = (i % 100).to_f * 2.0
|
|
17
|
+
y = (i / 100).to_f * 2.0
|
|
18
|
+
[i, box(x, y)]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
small_query = box(10.5, 10.5, 2.0)
|
|
22
|
+
large_query = box(0.5, 0.5, 120.0)
|
|
23
|
+
|
|
24
|
+
puts "\n#{count} polygons"
|
|
25
|
+
STRATEGIES.each do |strategy|
|
|
26
|
+
index = TG::Geometry::Index.build(entries, via: :geom, strategy: strategy)
|
|
27
|
+
|
|
28
|
+
Benchmark.bm(32) do |x|
|
|
29
|
+
x.report("#{strategy} small intersecting_geom_ids") { 10_000.times { index.intersecting_geom_ids(small_query) } }
|
|
30
|
+
x.report("#{strategy} large intersecting_geom_ids") { 1_000.times { index.intersecting_geom_ids(large_query) } }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require_relative "_support"
|
|
5
|
+
|
|
6
|
+
[100, 1_000, 10_000].each do |count|
|
|
7
|
+
points = count.times.map do |i|
|
|
8
|
+
angle = 2.0 * Math::PI * i / count
|
|
9
|
+
[Math.cos(angle), Math.sin(angle)]
|
|
10
|
+
end
|
|
11
|
+
points << points.first
|
|
12
|
+
ring = TG::Geometry.polygon(points).polygon.exterior_ring
|
|
13
|
+
|
|
14
|
+
puts "\nring segments: #{ring.num_segments}"
|
|
15
|
+
Benchmark.bm(28) do |x|
|
|
16
|
+
x.report("1M nearest_segment calls") do
|
|
17
|
+
1_000_000.times { ring.nearest_segment(0.25, 0.33) }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Geometry-based index queries
|
|
2
|
+
|
|
3
|
+
v0.3.0 adds geometry query methods to `TG::Geometry::Index`:
|
|
4
|
+
|
|
5
|
+
| Method | Predicate direction | Boundary semantics |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `intersecting_geom_ids(query)` | `stored_geom` intersects `query` | any intersection |
|
|
8
|
+
| `covering_geom_ids(query)` | `stored_geom` covers `query` | boundary included |
|
|
9
|
+
| `containing_geom_ids(query)` | `stored_geom` contains `query` | strict interior; boundary excluded |
|
|
10
|
+
|
|
11
|
+
The direction is always from the stored geometry to the query geometry. For example, `covering_geom_ids(query)` asks which indexed geometries cover the query.
|
|
12
|
+
|
|
13
|
+
The `predicate:` option on `Index.build` affects only the legacy point-based methods:
|
|
14
|
+
|
|
15
|
+
- `find_covering(x, y)`
|
|
16
|
+
- `covering_ids(x, y)`
|
|
17
|
+
- `covering_ids_batch_packed(packed_doubles)`
|
|
18
|
+
|
|
19
|
+
It does not affect `intersecting_geom_ids`, `covering_geom_ids`, or `containing_geom_ids`.
|
|
20
|
+
|
|
21
|
+
Results are arrays of ids in insertion order. Duplicate ids are preserved if duplicate ids were inserted.
|
|
22
|
+
|
|
23
|
+
In v0.3.0 these operations run under the GVL. The heavy C phase is structured without Ruby API calls so it can be made no-GVL-safe later after benchmarking, but there is no public `release_gvl:` knob.
|
data/docs/LIMITATIONS.md
CHANGED
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
Not included:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- Ractor support
|
|
15
|
-
-
|
|
16
|
-
-
|
|
7
|
+
- Geodesic / Haversine distance;
|
|
8
|
+
- Projection / reprojection;
|
|
9
|
+
- Buffer / union / difference / convex hull;
|
|
10
|
+
- Index nearest_ids (KNN);
|
|
11
|
+
- GeoBIN bbox helpers;
|
|
12
|
+
- Streaming FeatureSource;
|
|
13
|
+
- Index serialization / mmap;
|
|
14
|
+
- Ractor support;
|
|
15
|
+
- Windows / JRuby;
|
|
16
|
+
- Write-side ActiveRecordType / AR scopes / migrations;
|
|
17
|
+
- Z/M variants of array constructors;
|
|
18
|
+
- Public `release_gvl:` option.
|
|
17
19
|
|
|
18
20
|
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.
|
|
19
21
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Nearest segment
|
|
2
|
+
|
|
3
|
+
`Line#nearest_segment(x, y)` and `Ring#nearest_segment(x, y)` compute the nearest segment to a point in planar XY space.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
nearest = ring.nearest_segment(x, y)
|
|
7
|
+
nearest.segment # TG::Geometry::Segment
|
|
8
|
+
nearest.index # Integer segment index in the parent line/ring
|
|
9
|
+
nearest.distance # Float
|
|
10
|
+
nearest.point # [x, y] projection onto the segment
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Distance is Euclidean distance in input coordinate units. It is not meters unless the input coordinate system is already meters.
|
|
14
|
+
|
|
15
|
+
Degenerate segments (`a == b`) are handled as point distance. The projection point for a degenerate segment is the segment endpoint.
|
|
16
|
+
|
|
17
|
+
When several segments have the same distance, tie-break behaviour follows tg iteration order. Code should not rely on a specific equal-distance segment.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# SRID and EWKB
|
|
2
|
+
|
|
3
|
+
EWKB extends WKB with extra metadata bits. PostGIS sets the SRID flag (`0x20000000`) in the geometry type word and inserts a 32-bit SRID after the type header.
|
|
4
|
+
|
|
5
|
+
`tg` already understands EWKB enough to parse the geometry payload correctly, but it does not expose SRID metadata. `tg_geometry` therefore reads the EWKB header at wrapper level before passing the original bytes to `tg_parse_wkb_ix` / `tg_parse_hexn_ix`.
|
|
6
|
+
|
|
7
|
+
The original bytes are not modified. The native `tg_geom` remains a plain planar geometry. The Ruby `TG::Geometry::Geom` wrapper stores SRID metadata in parallel:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
geom = TG::Geometry.parse_wkb(ewkb)
|
|
11
|
+
geom.srid # => Integer or nil
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`parse_wkb` and `parse_hex` preserve SRID metadata. GeoJSON, WKT, GeoBIN, and `parse(format: :auto)` do not guarantee SRID preservation in v0.3.0.
|
|
15
|
+
|
|
16
|
+
`to_wkb` intentionally stays plain WKB. Use `to_ewkb` for PostGIS-compatible SRID-bearing output:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
geom.to_ewkb
|
|
20
|
+
geom.to_ewkb(srid: 4326)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
SRID is metadata only. No reprojection, SRID compatibility check, meter conversion, or geodesic calculation is performed.
|