OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
GpkgGeometry.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2//
3// Copyright 2026 Caleb Buahin
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
34
35#ifndef OPENSWMM_GPKG_GEOMETRY_HPP
36#define OPENSWMM_GPKG_GEOMETRY_HPP
37
38#include "core/PolygonRings.hpp"
39
40#include <algorithm>
41#include <vector>
42#include <cstdint>
43#include <cstring>
44#include <stdexcept>
45
46namespace openswmm::gpkg {
47
48// WKB geometry type codes (ISO 13249)
49enum WkbType : uint32_t {
54};
55
56// Byte order
57constexpr uint8_t WKB_LITTLE_ENDIAN = 1;
58
59// GeoPackage header magic
60constexpr uint8_t GP_MAGIC_1 = 0x47; // 'G'
61constexpr uint8_t GP_MAGIC_2 = 0x50; // 'P'
62
63// ============================================================================
64// Encoding helpers
65// ============================================================================
66
67namespace detail {
68
78inline void append_u8(std::vector<uint8_t>& buf, uint8_t v) {
79 buf.push_back(v);
80}
81
92inline void append_u32(std::vector<uint8_t>& buf, uint32_t v) {
93 buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&v),
94 reinterpret_cast<const uint8_t*>(&v) + 4);
95}
96
107inline void append_f64(std::vector<uint8_t>& buf, double v) {
108 buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&v),
109 reinterpret_cast<const uint8_t*>(&v) + 8);
110}
111
112// GP header with envelope type 1 (min/max XY = 32 bytes envelope)
127inline void write_gp_header(std::vector<uint8_t>& buf, int32_t srs_id, double min_x, double min_y,
128 double max_x, double max_y) {
129 append_u8(buf, GP_MAGIC_1);
130 append_u8(buf, GP_MAGIC_2);
131 append_u8(buf, 0x00); // version 0
132 // flags: byte_order=1 (LE), envelope_type=1 (XY), empty=0
133 // bits: [empty:1][envelope:3][byte_order:1] -> 0b0_001_1 = 0x03 (LE) with envelope type 1
134 uint8_t flags = 0x00;
135 flags |= WKB_LITTLE_ENDIAN; // bit 0: byte order (1=LE)
136 flags |= (1 << 1); // bits 1-3: envelope type 1 (XY only)
137 append_u8(buf, flags);
138 // SRS ID
139 buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&srs_id),
140 reinterpret_cast<const uint8_t*>(&srs_id) + 4);
141 // Envelope (32 bytes for type 1: min_x, max_x, min_y, max_y)
142 append_f64(buf, min_x);
143 append_f64(buf, max_x);
144 append_f64(buf, min_y);
145 append_f64(buf, max_y);
146}
147
148// GP header with no envelope (empty geometry)
159inline void write_gp_header_empty(std::vector<uint8_t>& buf, int32_t srs_id) {
160 append_u8(buf, GP_MAGIC_1);
161 append_u8(buf, GP_MAGIC_2);
162 append_u8(buf, 0x00);
163 uint8_t flags = WKB_LITTLE_ENDIAN | (1 << 4); // empty flag set
164 append_u8(buf, flags);
165 buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&srs_id),
166 reinterpret_cast<const uint8_t*>(&srs_id) + 4);
167}
168
169} // namespace detail
170
171// ============================================================================
172// Encode functions
173// ============================================================================
174
185inline std::vector<uint8_t> encode_point(double x, double y, int32_t srs_id) {
186 std::vector<uint8_t> buf;
187 buf.reserve(8 + 32 + 21); // header + envelope + WKB point
188 detail::write_gp_header(buf, srs_id, x, y, x, y);
189 // WKB POINT
192 detail::append_f64(buf, x);
193 detail::append_f64(buf, y);
194 return buf;
195}
196
209inline std::vector<uint8_t> encode_linestring(const std::vector<double>& xs,
210 const std::vector<double>& ys, int32_t srs_id) {
211 if (xs.empty()) {
212 std::vector<uint8_t> buf;
216 detail::append_u32(buf, 0);
217 return buf;
218 }
219 double min_x = xs[0], max_x = xs[0], min_y = ys[0], max_y = ys[0];
220 for (size_t i = 1; i < xs.size(); ++i) {
221 min_x = std::min(min_x, xs[i]);
222 max_x = std::max(max_x, xs[i]);
223 min_y = std::min(min_y, ys[i]);
224 max_y = std::max(max_y, ys[i]);
225 }
226 std::vector<uint8_t> buf;
227 buf.reserve(8 + 32 + 9 + xs.size() * 16);
228 detail::write_gp_header(buf, srs_id, min_x, min_y, max_x, max_y);
231 detail::append_u32(buf, static_cast<uint32_t>(xs.size()));
232 for (size_t i = 0; i < xs.size(); ++i) {
233 detail::append_f64(buf, xs[i]);
234 detail::append_f64(buf, ys[i]);
235 }
236 return buf;
237}
238
254inline std::vector<uint8_t> encode_multipolygon(const std::vector<double>& xs,
255 const std::vector<double>& ys, int32_t srs_id) {
256 if (xs.empty()) {
257 std::vector<uint8_t> buf;
261 detail::append_u32(buf, 0);
262 return buf;
263 }
264 const auto rings = polygon::rings(xs, ys);
265 // Containment depth identifies exteriors (even) and holes (odd), without
266 // relying on ring winding. Valid polygon rings do not cross each other.
267 std::vector<int> depth(rings.size(), 0), parent(rings.size(), -1);
268 for (size_t i = 0; i < rings.size(); ++i)
269 for (size_t j = 0; j < rings.size(); ++j)
270 if (i != j && polygon::contains(xs, ys, rings[j], xs[rings[i].first],
271 ys[rings[i].first]))
272 ++depth[i];
273 for (size_t i = 0; i < rings.size(); ++i)
274 if (depth[i] % 2)
275 for (size_t j = 0; j < rings.size(); ++j)
276 if (depth[j] == depth[i] - 1 &&
277 polygon::contains(xs, ys, rings[j], xs[rings[i].first], ys[rings[i].first])) {
278 parent[i] = static_cast<int>(j);
279 break;
280 }
281
282 const auto minmax_x = std::minmax_element(xs.begin(), xs.end());
283 const auto minmax_y = std::minmax_element(ys.begin(), ys.end());
284 std::vector<uint8_t> buf;
285 detail::write_gp_header(buf, srs_id, *minmax_x.first, *minmax_y.first,
286 *minmax_x.second, *minmax_y.second);
289 const auto polygon_count = std::count_if(depth.begin(), depth.end(),
290 [](int d) { return d % 2 == 0; });
291 detail::append_u32(buf, static_cast<uint32_t>(polygon_count));
292 const auto write_ring = [&](polygon::Ring ring) {
293 detail::append_u32(buf, static_cast<uint32_t>(ring.count + 1));
294 for (size_t k = 0; k <= ring.count; ++k) {
295 const auto v = ring.first + k % ring.count;
296 detail::append_f64(buf, xs[v]);
297 detail::append_f64(buf, ys[v]);
298 }
299 };
300 for (size_t i = 0; i < rings.size(); ++i) {
301 if (depth[i] % 2)
302 continue;
305 detail::append_u32(buf, 1 + static_cast<uint32_t>(
306 std::count(parent.begin(), parent.end(), static_cast<int>(i))));
307 write_ring(rings[i]);
308 for (size_t j = 0; j < rings.size(); ++j)
309 if (parent[j] == static_cast<int>(i))
310 write_ring(rings[j]);
311 }
312 return buf;
313}
314
329inline std::vector<uint8_t> encode_polygon(const std::vector<double>& xs,
330 const std::vector<double>& ys, int32_t srs_id) {
331 if (xs.empty()) {
332 std::vector<uint8_t> buf;
336 detail::append_u32(buf, 0);
337 return buf;
338 }
339 // Ensure ring closure
340 bool closed = (xs.front() == xs.back() && ys.front() == ys.back());
341 uint32_t n_pts = static_cast<uint32_t>(xs.size()) + (closed ? 0 : 1);
342
343 double min_x = xs[0], max_x = xs[0], min_y = ys[0], max_y = ys[0];
344 for (size_t i = 1; i < xs.size(); ++i) {
345 min_x = std::min(min_x, xs[i]);
346 max_x = std::max(max_x, xs[i]);
347 min_y = std::min(min_y, ys[i]);
348 max_y = std::max(max_y, ys[i]);
349 }
350
351 std::vector<uint8_t> buf;
352 buf.reserve(8 + 32 + 30 + n_pts * 16);
353 detail::write_gp_header(buf, srs_id, min_x, min_y, max_x, max_y);
354
355 // WKB POLYGON
358 detail::append_u32(buf, 1); // 1 ring
359 detail::append_u32(buf, n_pts);
360 for (size_t i = 0; i < xs.size(); ++i) {
361 detail::append_f64(buf, xs[i]);
362 detail::append_f64(buf, ys[i]);
363 }
364 if (!closed) {
365 detail::append_f64(buf, xs.front());
366 detail::append_f64(buf, ys.front());
367 }
368 return buf;
369}
370
371// ============================================================================
372// Decode structures
373// ============================================================================
374
376 double x = 0.0, y = 0.0;
377 int32_t srs_id = 0;
378};
379
381 std::vector<double> xs, ys;
382 int32_t srs_id = 0;
383};
384
386 // All rings, explicitly closed and concatenated (exterior, holes, next polygon).
387 std::vector<double> xs, ys;
388 int32_t srs_id = 0;
389};
390
391// ============================================================================
392// Decode functions
393// ============================================================================
394
395namespace detail {
396
411inline size_t parse_gp_header(const uint8_t* data, size_t size, int32_t& srs_id) {
412 if (size < 8)
413 throw std::runtime_error("GeoPackage binary too short");
414 if (data[0] != GP_MAGIC_1 || data[1] != GP_MAGIC_2)
415 throw std::runtime_error("Invalid GeoPackage binary magic");
416 uint8_t flags = data[3];
417 std::memcpy(&srs_id, data + 4, 4);
418 // Determine envelope size from flags bits 1-3
419 int envelope_type = (flags >> 1) & 0x07;
420 size_t envelope_size = 0;
421 switch (envelope_type) {
422 case 0:
423 envelope_size = 0;
424 break;
425 case 1:
426 envelope_size = 32;
427 break; // XY
428 case 2:
429 envelope_size = 48;
430 break; // XYZ
431 case 3:
432 envelope_size = 48;
433 break; // XYM
434 case 4:
435 envelope_size = 64;
436 break; // XYZM
437 default:
438 throw std::runtime_error("Unknown envelope type");
439 }
440 return 8 + envelope_size; // offset to WKB payload
441}
442
454template <typename T> inline T read_val(const uint8_t* data, size_t& offset) {
455 T val;
456 std::memcpy(&val, data + offset, sizeof(T));
457 offset += sizeof(T);
458 return val;
459}
460
461} // namespace detail
462
476inline DecodedPoint decode_point(const std::vector<uint8_t>& blob) {
477 DecodedPoint pt;
478 size_t offset = detail::parse_gp_header(blob.data(), blob.size(), pt.srs_id);
479 offset += 1; // byte order
480 uint32_t type = detail::read_val<uint32_t>(blob.data(), offset);
481 if (type != WKB_POINT)
482 throw std::runtime_error("Expected WKB POINT");
483 pt.x = detail::read_val<double>(blob.data(), offset);
484 pt.y = detail::read_val<double>(blob.data(), offset);
485 return pt;
486}
487
501inline DecodedLinestring decode_linestring(const std::vector<uint8_t>& blob) {
503 size_t offset = detail::parse_gp_header(blob.data(), blob.size(), ls.srs_id);
504 offset += 1; // byte order
505 uint32_t type = detail::read_val<uint32_t>(blob.data(), offset);
506 if (type != WKB_LINESTRING)
507 throw std::runtime_error("Expected WKB LINESTRING");
508 uint32_t n = detail::read_val<uint32_t>(blob.data(), offset);
509 ls.xs.resize(n);
510 ls.ys.resize(n);
511 for (uint32_t i = 0; i < n; ++i) {
512 ls.xs[i] = detail::read_val<double>(blob.data(), offset);
513 ls.ys[i] = detail::read_val<double>(blob.data(), offset);
514 }
515 return ls;
516}
517
533inline DecodedMultipolygon decode_multipolygon(const std::vector<uint8_t>& blob) {
535 size_t offset = detail::parse_gp_header(blob.data(), blob.size(), mp.srs_id);
536 offset += 1; // byte order
537 uint32_t type = detail::read_val<uint32_t>(blob.data(), offset);
538 if (type != WKB_MULTIPOLYGON)
539 throw std::runtime_error("Expected WKB MULTIPOLYGON");
540 uint32_t n_polys = detail::read_val<uint32_t>(blob.data(), offset);
541 if (n_polys == 0)
542 return mp;
543 const auto require = [&](size_t bytes) {
544 if (offset > blob.size() || bytes > blob.size() - offset)
545 throw std::runtime_error("Truncated WKB MULTIPOLYGON");
546 };
547 for (uint32_t p = 0; p < n_polys; ++p) {
548 require(9);
549 offset += 1; // native little-endian polygon byte order
550 const auto poly_type = detail::read_val<uint32_t>(blob.data(), offset);
551 if (poly_type != WKB_POLYGON)
552 throw std::runtime_error("Expected WKB POLYGON member");
553 const auto n_rings = detail::read_val<uint32_t>(blob.data(), offset);
554 for (uint32_t r = 0; r < n_rings; ++r) {
555 require(4);
556 const auto n_pts = detail::read_val<uint32_t>(blob.data(), offset);
557 if (n_pts > (blob.size() - offset) / (2 * sizeof(double)))
558 throw std::runtime_error("Truncated WKB polygon ring");
559 const auto first = mp.xs.size();
560 for (uint32_t i = 0; i < n_pts; ++i) {
561 mp.xs.push_back(detail::read_val<double>(blob.data(), offset));
562 mp.ys.push_back(detail::read_val<double>(blob.data(), offset));
563 }
564 if (n_pts && (mp.xs.back() != mp.xs[first] || mp.ys.back() != mp.ys[first])) {
565 mp.xs.push_back(mp.xs[first]);
566 mp.ys.push_back(mp.ys[first]);
567 }
568 }
569 }
570 return mp;
571}
572
573} // namespace openswmm::gpkg
574
575#endif // OPENSWMM_GPKG_GEOMETRY_HPP
Definition GpkgGeometry.hpp:67
void append_u32(std::vector< uint8_t > &buf, uint32_t v)
Append 4 native representation bytes.
Definition GpkgGeometry.hpp:92
void write_gp_header_empty(std::vector< uint8_t > &buf, int32_t srs_id)
Append an empty GP header without an envelope.
Definition GpkgGeometry.hpp:159
size_t parse_gp_header(const uint8_t *data, size_t size, int32_t &srs_id)
Read GP magic and envelope kind to locate the WKB payload.
Definition GpkgGeometry.hpp:411
T read_val(const uint8_t *data, size_t &offset)
Read an unchecked native scalar and advance a byte offset.
Definition GpkgGeometry.hpp:454
void append_u8(std::vector< uint8_t > &buf, uint8_t v)
Append 1 native representation byte.
Definition GpkgGeometry.hpp:78
void append_f64(std::vector< uint8_t > &buf, double v)
Append 8 native representation bytes.
Definition GpkgGeometry.hpp:107
void write_gp_header(std::vector< uint8_t > &buf, int32_t srs_id, double min_x, double min_y, double max_x, double max_y)
Append a version-zero nonempty GP header with XY envelope.
Definition GpkgGeometry.hpp:127
Definition ExternalContentReader.cpp:50
std::vector< uint8_t > encode_point(double x, double y, int32_t srs_id)
Encode an XY point with a matching XY envelope.
Definition GpkgGeometry.hpp:185
DecodedMultipolygon decode_multipolygon(const std::vector< uint8_t > &blob)
Decode MULTIPOLYGON coordinates from a prevalidated native-endian GP blob.
Definition GpkgGeometry.hpp:533
DecodedPoint decode_point(const std::vector< uint8_t > &blob)
Decode POINT coordinates from a prevalidated native-endian GP blob.
Definition GpkgGeometry.hpp:476
std::vector< uint8_t > encode_polygon(const std::vector< double > &xs, const std::vector< double > &ys, int32_t srs_id)
Encode a single exterior ring in POLYGON form.
Definition GpkgGeometry.hpp:329
constexpr uint8_t WKB_LITTLE_ENDIAN
Definition GpkgGeometry.hpp:57
DecodedLinestring decode_linestring(const std::vector< uint8_t > &blob)
Decode LINESTRING coordinates from a prevalidated native-endian GP blob.
Definition GpkgGeometry.hpp:501
std::vector< uint8_t > encode_linestring(const std::vector< double > &xs, const std::vector< double > &ys, int32_t srs_id)
Encode an XY LINESTRING.
Definition GpkgGeometry.hpp:209
constexpr uint8_t GP_MAGIC_2
Definition GpkgGeometry.hpp:61
WkbType
Definition GpkgGeometry.hpp:49
@ WKB_POINT
Definition GpkgGeometry.hpp:50
@ WKB_POLYGON
Definition GpkgGeometry.hpp:52
@ WKB_MULTIPOLYGON
Definition GpkgGeometry.hpp:53
@ WKB_LINESTRING
Definition GpkgGeometry.hpp:51
constexpr uint8_t GP_MAGIC_1
Definition GpkgGeometry.hpp:60
std::vector< uint8_t > encode_multipolygon(const std::vector< double > &xs, const std::vector< double > &ys, int32_t srs_id)
Encode closed-ring spatial coordinates in MULTIPOLYGON form.
Definition GpkgGeometry.hpp:254
bool contains(const std::vector< double > &x, const std::vector< double > &y, Ring ring, double px, double py)
Definition PolygonRings.hpp:31
std::vector< Ring > rings(const std::vector< double > &x, const std::vector< double > &y)
Definition PolygonRings.hpp:15
double * y
Definition odesolve.c:37
Definition GpkgGeometry.hpp:380
std::vector< double > xs
Definition GpkgGeometry.hpp:381
std::vector< double > ys
Definition GpkgGeometry.hpp:381
int32_t srs_id
Definition GpkgGeometry.hpp:382
Definition GpkgGeometry.hpp:385
int32_t srs_id
Definition GpkgGeometry.hpp:388
std::vector< double > ys
Definition GpkgGeometry.hpp:387
std::vector< double > xs
Definition GpkgGeometry.hpp:387
Definition GpkgGeometry.hpp:375
double y
Definition GpkgGeometry.hpp:376
int32_t srs_id
Definition GpkgGeometry.hpp:377
double x
Definition GpkgGeometry.hpp:376
Definition PolygonRings.hpp:9