geodetic 0.5.2 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44b5221a45f63382e712aa88d033d1cb0b8165f8804833a1fd8523a9d7d8ce32
4
- data.tar.gz: 787dc88782b277262e15dde4fa1f61801894d1d34eff304dc63965147a3b10e8
3
+ metadata.gz: 7e34b06e1bf493a580a70871173cab9e79b442c1667a1d68e9b9b2cd6181b660
4
+ data.tar.gz: 3d7cdabbff9c142ae61812dfec12d92cf764cc85cf31458c41226d4f00cc19a2
5
5
  SHA512:
6
- metadata.gz: ffb91f043c3c0c9a2bb6b44fad7952936121a09b18879350398cf260fd2be1f594f33c405df7cea0027f59c0e06679f1d4f718192d7626032a4b5fab195626f9
7
- data.tar.gz: dd922521aa23db80a5d9fdf56dbb98dcdfab811f72a126978d9b8dffe248f72f3dd83c3cae1e41d3ee2a1cf11455d70120d6c81fff7523f3aac798397250fbca
6
+ metadata.gz: a8c06b5625fcbb2df864e913ec687f0075c971ff91910a295bfe112ab645bca2174b7d01e32cb9ef03aa9cad05311cfdee7c00572f629d9c4a5b26af70604d3e
7
+ data.tar.gz: 3a9e274d50a491b2b1b1791402e4e5ff5c52f2be2cf172162fadc8d246ef64cf8bc410bb7eb869b1c3d1748cbaa33eb4d7e7cddaf503f6a191fcac09b9c93024
data/CHANGELOG.md CHANGED
@@ -8,6 +8,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  ## [Unreleased]
9
9
 
10
10
 
11
+ ## [0.6.0] - 2026-03-10
12
+
13
+ ### Added
14
+
15
+ - **`Geodetic::WKT` module** — Well-Known Text serialization for all geometry types
16
+ - **`to_wkt(precision: 6, srid: nil)`** instance method on all 18 coordinate classes, Segment, Path, Areas::Polygon (and subclasses), Areas::Circle, Areas::BoundingBox, and Feature
17
+ - **Coordinate order**: longitude latitude (OGC Simple Features standard)
18
+ - **Z suffix**: automatically added when any point in the geometry has non-zero altitude; Z-dimensionality is uniform within each geometry
19
+ - **EWKT**: `srid:` option prepends `SRID=N;` for PostGIS compatibility
20
+ - **`WKT.parse(string)`** — parse WKT/EWKT into Geodetic objects (Point → LLA, LineString → Segment/Path, Polygon → Areas::Polygon, Multi*/GeometryCollection → Array)
21
+ - **`WKT.parse_with_srid(string)`** — returns `[object, srid]` tuple
22
+ - **`WKT.save!(path, *objects, srid:, precision:)`** — write one WKT per line
23
+ - **`WKT.load(path)`** — read WKT file into Array of Geodetic objects
24
+ - ENU/NED raise `ArgumentError` (relative systems cannot be exported)
25
+ - **`Geodetic::WKB` module** — Well-Known Binary serialization for all geometry types
26
+ - **`to_wkb(srid: nil)`** and **`to_wkb_hex(srid: nil)`** instance methods on all 18 coordinate classes, Segment, Path, Areas::Polygon (and subclasses), Areas::Circle, Areas::BoundingBox, and Feature
27
+ - **Byte order**: output is always little-endian (NDR), matching PostGIS, GEOS, RGeo, and Shapely; parser supports both LE and BE input
28
+ - **ISO WKB Z**: type code + 1000 (Point Z = 1001, LineString Z = 1002, Polygon Z = 1003) when any point has non-zero altitude
29
+ - **EWKB**: `srid:` option embeds SRID via `0x20000000` flag for PostGIS compatibility
30
+ - **`WKB.parse(input)`** — parse WKB from binary or hex string (auto-detects encoding)
31
+ - **`WKB.parse_with_srid(input)`** — returns `[object, srid]` tuple
32
+ - **Binary file I/O**: `WKB.save!(path, *objects, srid:)` / `WKB.load(path)` — framed format (4-byte LE count + size-prefixed WKB)
33
+ - **Hex file I/O**: `WKB.save_hex!(path, *objects, srid:)` / `WKB.load_hex(path)` — one hex string per line, supports `#` comments
34
+ - Supports all WKB types: Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection
35
+ - ENU/NED raise `ArgumentError` (relative systems cannot be exported)
36
+ - WKT example (`examples/10_wkt_serialization.rb`) — 10-section demo covering export, SRID/EWKT, Z-dimension, parsing, roundtrip, and file I/O
37
+ - WKB example (`examples/11_wkb_serialization.rb`) — 10-section demo covering export, EWKB/SRID, Z-dimension, parsing, roundtrip, and binary/hex file I/O
38
+ - WKB fixture files: `examples/sample_geometries.wkb` (9 geometries) and `examples/sample_geometries.wkb.hex` (15 geometries with comments)
39
+ - 53 WKT tests (134 assertions) and 54 WKB tests (144 assertions)
40
+ - Documentation: `docs/reference/wkt.md` and `docs/reference/wkb.md`
41
+
42
+ ### Changed
43
+
44
+ - Updated README with WKT and WKB sections, key features, and examples 10-11 in the examples table
45
+ - Updated `docs/index.md` with WKT and WKB in key features and reference links
46
+ - Updated `examples/README.md` with example 10 and 11 descriptions
47
+ - Updated `mkdocs.yml` nav with WKT and WKB reference pages
48
+ - Added `require_relative "geodetic/wkt"` and `require_relative "geodetic/wkb"` to `lib/geodetic.rb`
49
+
11
50
  ## [0.5.2] - 2026-03-10
12
51
 
13
52
  ### Added
data/README.md CHANGED
@@ -25,6 +25,8 @@
25
25
  - <strong>Vectors</strong> - Geodetic displacement (distance + bearing) with full arithmetic and Vincenty direct<br>
26
26
  - <strong>Geodetic Arithmetic</strong> - Compose geometry with operators: P1 + P2 → Segment, + P3 → Path, + Distance → Circle, * Vector → translate<br>
27
27
  - <strong>GeoJSON Export</strong> - Build FeatureCollections from any mix of objects and save to file<br>
28
+ - <strong>WKT Serialization</strong> - Well-Known Text export/import with SRID/EWKT and Z-dimension support<br>
29
+ - <strong>WKB Serialization</strong> - Well-Known Binary export/import with EWKB, SRID, hex encoding, and file I/O<br>
28
30
  - <strong>Validated Setters</strong> - Type coercion and range validation on all coordinate attributes<br>
29
31
  - <strong>Serialization</strong> - to_s(precision), to_a, from_string, from_array, DMS format<br>
30
32
  - <strong>Multiple Datums</strong> - WGS84, Clarke 1866, GRS 1980, Airy 1830, and more<br>
@@ -769,6 +771,73 @@ objects = Geodetic::GeoJSON.load("map.geojson")
769
771
 
770
772
  `load` returns an Array of Geodetic objects. Features with a `"name"` or non-empty properties round-trip as `Feature` objects; bare geometries with empty properties return as raw coordinates, segments, paths, or polygons. `GeoJSON.parse(hash)` does the same from an already-parsed Hash.
771
773
 
774
+ ### WKT Serialization
775
+
776
+ `WKT` provides Well-Known Text export and import for all geometry types. WKT is the standard format used by PostGIS, RGeo, Shapely, JTS, and most GIS tools.
777
+
778
+ ```ruby
779
+ seattle.to_wkt # => "POINT(-122.3493 47.6205)"
780
+ seattle.to_wkt(srid: 4326) # => "SRID=4326;POINT(-122.3493 47.6205)"
781
+ seattle.to_wkt(precision: 2) # => "POINT(-122.35 47.62)"
782
+
783
+ Segment.new(seattle, portland).to_wkt # => "LINESTRING(-122.3493 47.6205, -122.6784 45.5152)"
784
+ route.to_wkt # => "LINESTRING(...)"
785
+ polygon.to_wkt # => "POLYGON((...))
786
+ circle.to_wkt(segments: 64) # => "POLYGON((...)) (64-gon)"
787
+ feature.to_wkt # => delegates to geometry (WKT has no properties)
788
+ ```
789
+
790
+ Altitude triggers the Z suffix. When any point has altitude, all points in that geometry get Z:
791
+
792
+ ```ruby
793
+ Geodetic::Coordinate::LLA.new(lat: 47.62, lng: -122.35, alt: 184.0).to_wkt
794
+ # => "POINT Z(-122.35 47.62 184.0)"
795
+ ```
796
+
797
+ **File I/O and parsing:**
798
+
799
+ ```ruby
800
+ # Save to file (one WKT per line)
801
+ Geodetic::WKT.save!("shapes.wkt", seattle, segment, polygon, srid: 4326)
802
+
803
+ # Load from file
804
+ objects = Geodetic::WKT.load("shapes.wkt")
805
+ # => [Coordinate::LLA, Segment, Areas::Polygon]
806
+
807
+ # Parse a single WKT string
808
+ obj = Geodetic::WKT.parse("POINT(-122.3493 47.6205)")
809
+ obj, srid = Geodetic::WKT.parse_with_srid("SRID=4326;POLYGON((-122 47, -121 46, -123 46, -122 47))")
810
+ ```
811
+
812
+ ### WKB Serialization
813
+
814
+ `WKB` provides Well-Known Binary export and import — the binary counterpart to WKT used by PostGIS, GEOS, RGeo, and Shapely for efficient geometry storage. Output is always little-endian (NDR).
815
+
816
+ ```ruby
817
+ seattle.to_wkb # => 21-byte binary string
818
+ seattle.to_wkb_hex # => "01010000008a1f63ee5a965ec0..."
819
+ seattle.to_wkb_hex(srid: 4326) # => EWKB with embedded SRID
820
+
821
+ Segment.new(seattle, portland).to_wkb_hex # => LINESTRING hex
822
+ polygon.to_wkb_hex # => POLYGON hex
823
+ ```
824
+
825
+ **File I/O (binary and hex):**
826
+
827
+ ```ruby
828
+ # Binary format (framed: count + size-prefixed WKB)
829
+ Geodetic::WKB.save!("shapes.wkb", seattle, segment, polygon)
830
+ objects = Geodetic::WKB.load("shapes.wkb")
831
+
832
+ # Hex format (one hex string per line, supports comments)
833
+ Geodetic::WKB.save_hex!("shapes.wkb.hex", seattle, segment, polygon)
834
+ objects = Geodetic::WKB.load_hex("shapes.wkb.hex")
835
+
836
+ # Parse a single hex or binary string
837
+ obj = Geodetic::WKB.parse("01010000008a1f63ee5a965ec08195438b6ccf4740")
838
+ obj, srid = Geodetic::WKB.parse_with_srid(ewkb_hex)
839
+ ```
840
+
772
841
  ### Web Mercator Tile Coordinates
773
842
 
774
843
  ```ruby
@@ -802,6 +871,8 @@ The [`examples/`](examples/) directory contains runnable demo scripts showing pr
802
871
  | [`07_segments_and_shapes.rb`](examples/07_segments_and_shapes.rb) | Segment and polygon subclasses: Triangle, Rectangle, Pentagon, Hexagon, Octagon with containment, edges, and bounding boxes |
803
872
  | [`08_geodetic_arithmetic.rb`](examples/08_geodetic_arithmetic.rb) | Geodetic arithmetic: building geometry with + (Segments, Paths, Circles), Vector class (Vincenty direct, components, arithmetic, dot/cross products), translation with * (Coordinates, Segments, Paths, Circles, Polygons), and corridors |
804
873
  | [`09_geojson_export.rb`](examples/09_geojson_export.rb) | GeoJSON export: `to_geojson` on all geometry types, `GeoJSON` class for building FeatureCollections with `<<`, delete/clear, Enumerable, and `save` to file |
874
+ | [`10_wkt_serialization.rb`](examples/10_wkt_serialization.rb) | WKT serialization: `to_wkt` on all geometry types, SRID/EWKT, Z-dimension handling, parsing, and roundtrip verification |
875
+ | [`11_wkb_serialization.rb`](examples/11_wkb_serialization.rb) | WKB serialization: `to_wkb`/`to_wkb_hex` on all geometry types, EWKB/SRID, Z-dimension, parsing, roundtrip, and binary/hex file I/O |
805
876
 
806
877
  Run any example with:
807
878
 
data/docs/index.md CHANGED
@@ -19,6 +19,8 @@
19
19
  <li><strong>Paths</strong> - Directed coordinate sequences with navigation, interpolation, closest approach, intersection, and area conversion<br>
20
20
  <li><strong>Features</strong> - Named geometry wrapper with metadata and delegated distance/bearing<br>
21
21
  <li><strong>GeoJSON Export</strong> - Build FeatureCollections from any mix of objects and save to file<br>
22
+ <li><strong>WKT Serialization</strong> - Well-Known Text export/import with SRID/EWKT and Z-dimension support<br>
23
+ <li><strong>WKB Serialization</strong> - Well-Known Binary export/import with EWKB, SRID, hex encoding, and file I/O<br>
22
24
  <li><strong>Validated Setters</strong> - Type coercion and range validation on all coordinate attributes<br>
23
25
  <li><strong>Serialization</strong> - to_s(precision), to_a, from_string, from_array, DMS format<br>
24
26
  <li><strong>Multiple Datums</strong> - WGS84, Clarke 1866, GRS 1980, Airy 1830, and more<br>
@@ -114,4 +116,6 @@ puts lla_again.to_s
114
116
  - [Vector](reference/vector.md)
115
117
  - [Arithmetic](reference/arithmetic.md)
116
118
  - [GeoJSON Export](reference/geojson.md)
119
+ - [WKT Serialization](reference/wkt.md)
120
+ - [WKB Serialization](reference/wkb.md)
117
121
  - [Map Rendering](reference/map-rendering.md)
@@ -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.