geodetic 0.5.2 → 0.7.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 +75 -0
- data/README.md +94 -0
- data/docs/index.md +4 -0
- data/docs/reference/geos-acceleration.md +170 -0
- data/docs/reference/wkb.md +385 -0
- data/docs/reference/wkt.md +354 -0
- data/examples/10_wkt_serialization.rb +248 -0
- data/examples/11_wkb_serialization.rb +261 -0
- data/examples/12_geos_benchmark.rb +258 -0
- data/examples/13_geos_operations.rb +538 -0
- data/examples/README.md +82 -0
- data/examples/geodetic_demo.wkb +0 -0
- data/examples/geodetic_demo.wkt +5 -0
- data/examples/geodetic_demo_output.wkb.hex +5 -0
- data/examples/sample_geometries.wkb +0 -0
- data/examples/sample_geometries.wkb.hex +60 -0
- data/lib/geodetic/areas/polygon.rb +66 -10
- data/lib/geodetic/geos.rb +547 -0
- data/lib/geodetic/path.rb +20 -8
- data/lib/geodetic/version.rb +1 -1
- data/lib/geodetic/wkb.rb +360 -0
- data/lib/geodetic/wkt.rb +313 -0
- data/lib/geodetic.rb +3 -0
- data/mkdocs.yml +2 -0
- metadata +16 -1
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# WKB Serialization Reference
|
|
2
|
+
|
|
3
|
+
`Geodetic::WKB` provides [Well-Known Binary](https://en.wikipedia.org/wiki/Well-known_binary_representation_of_geometry) (WKB) serialization for all Geodetic geometry types. WKB is the binary counterpart to WKT, used by PostGIS, GEOS, RGeo, Shapely, and most GIS tools for efficient geometry storage and transfer.
|
|
4
|
+
|
|
5
|
+
Every geometry type gains `to_wkb` and `to_wkb_hex` instance methods. The module also provides `WKB.parse` and `WKB.parse_with_srid` for importing WKB data back into Geodetic objects.
|
|
6
|
+
|
|
7
|
+
**Byte order:** Output is always little-endian (NDR), matching PostGIS, GEOS, RGeo, Shapely, and virtually all modern GIS tools. Big-endian (XDR) input is fully supported by the parser.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Export
|
|
12
|
+
|
|
13
|
+
### Coordinates → POINT
|
|
14
|
+
|
|
15
|
+
All 18 coordinate classes gain `to_wkb` and `to_wkb_hex` methods. They convert the coordinate to LLA and return WKB binary or hex-encoded output.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
seattle = Geodetic::Coordinate::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
19
|
+
|
|
20
|
+
seattle.to_wkb # => 21-byte binary string
|
|
21
|
+
seattle.to_wkb_hex # => "01010000008a1f63ee5a965ec08195438b6ccf4740"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Altitude handling:** When altitude is non-zero, the Z type code is used (Point Z = 1001):
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
Geodetic::Coordinate::LLA.new(lat: 47.62, lng: -122.35, alt: 184.0).to_wkb_hex
|
|
28
|
+
# => "01e90300008a1f63ee5a965ec08195438b6ccf47400000000000006740"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Cross-system:** Any coordinate type works — UTM, ECEF, MGRS, etc. are converted to LLA internally.
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
seattle.to_utm.to_wkb_hex # => hex string
|
|
35
|
+
seattle.to_ecef.to_wkb_hex # => hex string
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**ENU and NED:** These are relative coordinate systems. Calling `to_wkb` raises `ArgumentError`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### Segment → LINESTRING
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
seg = Geodetic::Segment.new(seattle, portland)
|
|
46
|
+
seg.to_wkb # => 41-byte binary string
|
|
47
|
+
seg.to_wkb_hex # => hex string
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### Path → LINESTRING / POLYGON
|
|
53
|
+
|
|
54
|
+
Returns a LINESTRING by default. Pass `as: :polygon` for a closed POLYGON.
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
route = Geodetic::Path.new(coordinates: [seattle, portland, sf])
|
|
58
|
+
route.to_wkb # => LINESTRING binary
|
|
59
|
+
route.to_wkb(as: :polygon) # => POLYGON binary
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### Areas::Polygon → POLYGON
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
poly = Geodetic::Areas::Polygon.new(boundary: [a, b, c])
|
|
68
|
+
poly.to_wkb # => binary
|
|
69
|
+
poly.to_wkb_hex # => hex string
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
All Polygon subclasses (`Triangle`, `Rectangle`, `Pentagon`, `Hexagon`, `Octagon`) inherit this method.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### Areas::Circle → POLYGON
|
|
77
|
+
|
|
78
|
+
Approximated as a regular N-gon. Default is 32 segments.
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
circle = Geodetic::Areas::Circle.new(centroid: seattle, radius: 10_000)
|
|
82
|
+
|
|
83
|
+
circle.to_wkb # => 32-gon binary (541 bytes)
|
|
84
|
+
circle.to_wkb(segments: 64) # => 64-gon
|
|
85
|
+
circle.to_wkb(segments: 8) # => 8-gon (157 bytes)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### Areas::BoundingBox → POLYGON
|
|
91
|
+
|
|
92
|
+
5 positions (4 corners + closing point).
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
bbox = Geodetic::Areas::BoundingBox.new(
|
|
96
|
+
nw: Geodetic::Coordinate::LLA.new(lat: 48.0, lng: -123.0, alt: 0),
|
|
97
|
+
se: Geodetic::Coordinate::LLA.new(lat: 46.0, lng: -121.0, alt: 0)
|
|
98
|
+
)
|
|
99
|
+
bbox.to_wkb # => 93-byte binary
|
|
100
|
+
bbox.to_wkb_hex # => hex string
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### Feature → delegates to geometry
|
|
106
|
+
|
|
107
|
+
WKB has no concept of properties or labels. `Feature#to_wkb` delegates directly to the underlying geometry.
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
f = Geodetic::Feature.new(label: "Seattle", geometry: seattle, metadata: { pop: 750_000 })
|
|
111
|
+
f.to_wkb_hex # => same as seattle.to_wkb_hex
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Options
|
|
117
|
+
|
|
118
|
+
All `to_wkb` and `to_wkb_hex` methods accept:
|
|
119
|
+
|
|
120
|
+
| Parameter | Default | Description |
|
|
121
|
+
|-----------|---------|-------------|
|
|
122
|
+
| `srid:` | `nil` | When set, produces EWKB with embedded SRID |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Z-Dimension Consistency
|
|
127
|
+
|
|
128
|
+
When any point in a geometry has non-zero altitude, **all** points in that geometry use the Z type code. This follows the OGC rule that Z-dimensionality is uniform within a geometry.
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# seattle (alt=0) + sf (alt=100) → LineString Z (type code 1002)
|
|
132
|
+
mixed = Geodetic::Segment.new(seattle, sf_with_altitude)
|
|
133
|
+
mixed.to_wkb # type code = 1002 (LineString Z)
|
|
134
|
+
|
|
135
|
+
# Both at alt=0 → LineString (type code 2)
|
|
136
|
+
flat = Geodetic::Segment.new(seattle, portland)
|
|
137
|
+
flat.to_wkb # type code = 2 (LineString)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## SRID / EWKB
|
|
143
|
+
|
|
144
|
+
Pass `srid:` to any `to_wkb` or `to_wkb_hex` call to produce Extended WKB (EWKB), the format used by PostGIS:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
seattle.to_wkb_hex(srid: 4326)
|
|
148
|
+
# => "0101000020e61000008a1f63ee5a965ec08195438b6ccf4740"
|
|
149
|
+
|
|
150
|
+
seg.to_wkb_hex(srid: 4326)
|
|
151
|
+
# => "0102000020e6100000020000008a1f63ee..."
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
EWKB embeds the SRID in the type code using the `0x20000000` flag, followed by a 4-byte SRID value.
|
|
155
|
+
|
|
156
|
+
Common SRIDs:
|
|
157
|
+
|
|
158
|
+
| SRID | Description |
|
|
159
|
+
|------|-------------|
|
|
160
|
+
| 4326 | WGS84 geographic (lat/lng) |
|
|
161
|
+
| 3857 | Web Mercator |
|
|
162
|
+
| 32610 | UTM Zone 10N |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## File I/O
|
|
167
|
+
|
|
168
|
+
### Binary Format
|
|
169
|
+
|
|
170
|
+
#### `WKB.save!(path, *objects, srid: nil)`
|
|
171
|
+
|
|
172
|
+
Write geometries to a binary file using a framed format: 4-byte LE count, then for each geometry a 4-byte LE size followed by raw WKB bytes. Accepts individual objects or an array.
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
Geodetic::WKB.save!("shapes.wkb", seattle, segment, polygon)
|
|
176
|
+
Geodetic::WKB.save!("shapes.wkb", [seattle, segment, polygon])
|
|
177
|
+
Geodetic::WKB.save!("shapes.wkb", seattle, polygon, srid: 4326)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### `WKB.load(path)`
|
|
181
|
+
|
|
182
|
+
Read a binary WKB file and return an Array of Geodetic objects.
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
objects = Geodetic::WKB.load("shapes.wkb")
|
|
186
|
+
# => [Coordinate::LLA, Segment, Areas::Polygon]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Hex Format
|
|
190
|
+
|
|
191
|
+
#### `WKB.save_hex!(path, *objects, srid: nil)`
|
|
192
|
+
|
|
193
|
+
Write one hex-encoded WKB string per line. Human-readable and diff-friendly.
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
Geodetic::WKB.save_hex!("shapes.wkb.hex", seattle, segment, polygon)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Output file:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
01010000008a1f63ee5a965ec08195438b6ccf4740
|
|
203
|
+
0102000000020000008a1f63ee5a965ec0...
|
|
204
|
+
01030000000100000004000000...
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### `WKB.load_hex(path)`
|
|
208
|
+
|
|
209
|
+
Read a hex WKB file and return an Array of Geodetic objects. Blank lines and lines starting with `#` are skipped.
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
objects = Geodetic::WKB.load_hex("shapes.wkb.hex")
|
|
213
|
+
# => [Coordinate::LLA, Segment, Areas::Polygon]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Roundtrip
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
objects = [seattle, segment, polygon]
|
|
220
|
+
|
|
221
|
+
# Binary roundtrip
|
|
222
|
+
Geodetic::WKB.save!("data.wkb", objects)
|
|
223
|
+
loaded = Geodetic::WKB.load("data.wkb")
|
|
224
|
+
|
|
225
|
+
# Hex roundtrip
|
|
226
|
+
Geodetic::WKB.save_hex!("data.wkb.hex", objects, srid: 4326)
|
|
227
|
+
loaded = Geodetic::WKB.load_hex("data.wkb.hex")
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Import
|
|
233
|
+
|
|
234
|
+
### `WKB.parse(input)`
|
|
235
|
+
|
|
236
|
+
Parse a WKB binary string or hex string and return a Geodetic object (or Array for Multi*/GeometryCollection types). Auto-detects binary vs hex encoding.
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
Geodetic::WKB.parse("01010000008a1f63ee5a965ec08195438b6ccf4740")
|
|
240
|
+
# => Coordinate::LLA
|
|
241
|
+
|
|
242
|
+
Geodetic::WKB.parse(binary_string)
|
|
243
|
+
# => Coordinate::LLA
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### `WKB.parse_with_srid(input)`
|
|
247
|
+
|
|
248
|
+
Parse WKB/EWKB and return both the object and the SRID:
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
obj, srid = Geodetic::WKB.parse_with_srid("0101000020e61000008a1f63ee5a965ec08195438b6ccf4740")
|
|
252
|
+
obj # => Coordinate::LLA
|
|
253
|
+
srid # => 4326
|
|
254
|
+
|
|
255
|
+
obj, srid = Geodetic::WKB.parse_with_srid(plain_wkb_hex)
|
|
256
|
+
srid # => nil
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## WKB → Geodetic Type Mapping
|
|
262
|
+
|
|
263
|
+
| WKB Type Code | WKB Type | Geodetic Type |
|
|
264
|
+
|---------------|----------|---------------|
|
|
265
|
+
| 1 | POINT | `Coordinate::LLA` |
|
|
266
|
+
| 2 (2 points) | LINESTRING | `Segment` |
|
|
267
|
+
| 2 (3+ points) | LINESTRING | `Path` |
|
|
268
|
+
| 3 | POLYGON | `Areas::Polygon` (outer ring only; holes are dropped) |
|
|
269
|
+
| 4 | MULTIPOINT | Array of `Coordinate::LLA` |
|
|
270
|
+
| 5 | MULTILINESTRING | Array of `Segment` or `Path` |
|
|
271
|
+
| 6 | MULTIPOLYGON | Array of `Areas::Polygon` |
|
|
272
|
+
| 7 | GEOMETRYCOLLECTION | Array of mixed types |
|
|
273
|
+
|
|
274
|
+
Z variants (type + 1000) are supported: Point Z = 1001, LineString Z = 1002, Polygon Z = 1003.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Geometry Mapping (Export)
|
|
279
|
+
|
|
280
|
+
| Geodetic Type | WKB Type | Notes |
|
|
281
|
+
|---------------|----------|-------|
|
|
282
|
+
| Any coordinate (LLA, UTM, ECEF, ...) | POINT | Converts through LLA |
|
|
283
|
+
| `Segment` | LINESTRING | 2 positions |
|
|
284
|
+
| `Path` | LINESTRING | N positions (default) |
|
|
285
|
+
| `Path` (with `as: :polygon`) | POLYGON | Auto-closes the ring |
|
|
286
|
+
| `Areas::Polygon` (and subclasses) | POLYGON | Boundary ring already closed |
|
|
287
|
+
| `Areas::Circle` | POLYGON | Approximated as N-gon (default 32) |
|
|
288
|
+
| `Areas::BoundingBox` | POLYGON | 4 corners, closed |
|
|
289
|
+
| `Feature` | Delegates to geometry | Properties are lost |
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## WKB Binary Structure
|
|
294
|
+
|
|
295
|
+
Each WKB geometry starts with:
|
|
296
|
+
|
|
297
|
+
| Offset | Size | Description |
|
|
298
|
+
|--------|------|-------------|
|
|
299
|
+
| 0 | 1 byte | Byte order: `0x01` = little-endian (NDR), `0x00` = big-endian (XDR) |
|
|
300
|
+
| 1 | 4 bytes | Type code (uint32) |
|
|
301
|
+
| 5 | 4 bytes | SRID (uint32, only in EWKB when SRID flag is set) |
|
|
302
|
+
| 5 or 9 | varies | Geometry data (IEEE 754 doubles) |
|
|
303
|
+
|
|
304
|
+
**Type codes:**
|
|
305
|
+
|
|
306
|
+
| Code | Type | Z Code |
|
|
307
|
+
|------|------|--------|
|
|
308
|
+
| 1 | Point | 1001 |
|
|
309
|
+
| 2 | LineString | 1002 |
|
|
310
|
+
| 3 | Polygon | 1003 |
|
|
311
|
+
| 4 | MultiPoint | 1004 |
|
|
312
|
+
| 5 | MultiLineString | 1005 |
|
|
313
|
+
| 6 | MultiPolygon | 1006 |
|
|
314
|
+
| 7 | GeometryCollection | 1007 |
|
|
315
|
+
|
|
316
|
+
**EWKB flags** (OR'd into type code):
|
|
317
|
+
|
|
318
|
+
| Flag | Value | Description |
|
|
319
|
+
|------|-------|-------------|
|
|
320
|
+
| SRID | `0x20000000` | SRID follows the type code |
|
|
321
|
+
| Z | `0x80000000` | Coordinates include Z values |
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Roundtrip Example
|
|
326
|
+
|
|
327
|
+
```ruby
|
|
328
|
+
require "geodetic"
|
|
329
|
+
|
|
330
|
+
seattle = Geodetic::Coordinate::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0)
|
|
331
|
+
|
|
332
|
+
# Export
|
|
333
|
+
hex = seattle.to_wkb_hex(srid: 4326)
|
|
334
|
+
# => "0101000020e61000008a1f63ee5a965ec08195438b6ccf4740"
|
|
335
|
+
|
|
336
|
+
# Import
|
|
337
|
+
obj, srid = Geodetic::WKB.parse_with_srid(hex)
|
|
338
|
+
obj.lat # => 47.6205
|
|
339
|
+
obj.lng # => -122.3493
|
|
340
|
+
srid # => 4326
|
|
341
|
+
|
|
342
|
+
# Re-export
|
|
343
|
+
obj.to_wkb_hex(srid: srid) == hex # => true
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Integration with PostGIS and RGeo
|
|
349
|
+
|
|
350
|
+
WKB is the preferred format for exchanging geometry with spatial databases:
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
# Writing to PostGIS (hex format)
|
|
354
|
+
hex = polygon.to_wkb_hex(srid: 4326)
|
|
355
|
+
ActiveRecord::Base.connection.execute(
|
|
356
|
+
"INSERT INTO regions (geom) VALUES (ST_GeomFromWKB(decode('#{hex}', 'hex'), 4326))"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Or using EWKB directly
|
|
360
|
+
ewkb_hex = polygon.to_wkb_hex(srid: 4326)
|
|
361
|
+
ActiveRecord::Base.connection.execute(
|
|
362
|
+
"INSERT INTO regions (geom) VALUES ('#{ewkb_hex}')"
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Reading from PostGIS
|
|
366
|
+
row = ActiveRecord::Base.connection.select_one("SELECT ST_AsEWKB(geom)::text FROM regions LIMIT 1")
|
|
367
|
+
obj, srid = Geodetic::WKB.parse_with_srid(row["st_asewkb"])
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
WKB binary is also accepted by RGeo's `WKRep::WKBParser`, Shapely's `wkb.loads()`, JTS, GEOS, and virtually every GIS library.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## WKB vs WKT
|
|
375
|
+
|
|
376
|
+
| | WKB | WKT |
|
|
377
|
+
|---|-----|-----|
|
|
378
|
+
| **Format** | Binary | Text |
|
|
379
|
+
| **Size** | Compact | Larger |
|
|
380
|
+
| **Human-readable** | No (use hex) | Yes |
|
|
381
|
+
| **Speed** | Faster to parse | Slower |
|
|
382
|
+
| **Use case** | Database storage, wire transfer | Debugging, config files, SQL |
|
|
383
|
+
| **SRID support** | EWKB flag | EWKT prefix |
|
|
384
|
+
|
|
385
|
+
Both formats support the same geometry types and Z-dimension handling.
|