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 <vector>
39#include <cstdint>
40#include <cstring>
41#include <stdexcept>
42
43namespace openswmm::gpkg {
44
45// WKB geometry type codes (ISO 13249)
46enum WkbType : uint32_t {
51};
52
53// Byte order
54constexpr uint8_t WKB_LITTLE_ENDIAN = 1;
55
56// GeoPackage header magic
57constexpr uint8_t GP_MAGIC_1 = 0x47; // 'G'
58constexpr uint8_t GP_MAGIC_2 = 0x50; // 'P'
59
60// ============================================================================
61// Encoding helpers
62// ============================================================================
63
64namespace detail {
65
66inline void append_u8(std::vector<uint8_t>& buf, uint8_t v) {
67 buf.push_back(v);
68}
69
70inline void append_u32(std::vector<uint8_t>& buf, uint32_t v) {
71 buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&v),
72 reinterpret_cast<const uint8_t*>(&v) + 4);
73}
74
75inline void append_f64(std::vector<uint8_t>& buf, double v) {
76 buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&v),
77 reinterpret_cast<const uint8_t*>(&v) + 8);
78}
79
80// GP header with envelope type 1 (min/max XY = 32 bytes envelope)
81inline void write_gp_header(std::vector<uint8_t>& buf, int32_t srs_id,
82 double min_x, double min_y, double max_x, double max_y) {
85 append_u8(buf, 0x00); // version 0
86 // flags: byte_order=1 (LE), envelope_type=1 (XY), empty=0
87 // bits: [empty:1][envelope:3][byte_order:1] -> 0b0_001_1 = 0x03 (LE) with envelope type 1
88 uint8_t flags = 0x00;
89 flags |= WKB_LITTLE_ENDIAN; // bit 0: byte order (1=LE)
90 flags |= (1 << 1); // bits 1-3: envelope type 1 (XY only)
91 append_u8(buf, flags);
92 // SRS ID
93 buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&srs_id),
94 reinterpret_cast<const uint8_t*>(&srs_id) + 4);
95 // Envelope (32 bytes for type 1: min_x, max_x, min_y, max_y)
96 append_f64(buf, min_x);
97 append_f64(buf, max_x);
98 append_f64(buf, min_y);
99 append_f64(buf, max_y);
100}
101
102// GP header with no envelope (empty geometry)
103inline void write_gp_header_empty(std::vector<uint8_t>& buf, int32_t srs_id) {
104 append_u8(buf, GP_MAGIC_1);
105 append_u8(buf, GP_MAGIC_2);
106 append_u8(buf, 0x00);
107 uint8_t flags = WKB_LITTLE_ENDIAN | (1 << 4); // empty flag set
108 append_u8(buf, flags);
109 buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&srs_id),
110 reinterpret_cast<const uint8_t*>(&srs_id) + 4);
111}
112
113} // namespace detail
114
115// ============================================================================
116// Encode functions
117// ============================================================================
118
122inline std::vector<uint8_t> encode_point(double x, double y, int32_t srs_id) {
123 std::vector<uint8_t> buf;
124 buf.reserve(8 + 32 + 21); // header + envelope + WKB point
125 detail::write_gp_header(buf, srs_id, x, y, x, y);
126 // WKB POINT
129 detail::append_f64(buf, x);
130 detail::append_f64(buf, y);
131 return buf;
132}
133
139inline std::vector<uint8_t> encode_linestring(const std::vector<double>& xs,
140 const std::vector<double>& ys,
141 int32_t srs_id) {
142 if (xs.empty()) {
143 std::vector<uint8_t> buf;
147 detail::append_u32(buf, 0);
148 return buf;
149 }
150 double min_x = xs[0], max_x = xs[0], min_y = ys[0], max_y = ys[0];
151 for (size_t i = 1; i < xs.size(); ++i) {
152 min_x = std::min(min_x, xs[i]);
153 max_x = std::max(max_x, xs[i]);
154 min_y = std::min(min_y, ys[i]);
155 max_y = std::max(max_y, ys[i]);
156 }
157 std::vector<uint8_t> buf;
158 buf.reserve(8 + 32 + 9 + xs.size() * 16);
159 detail::write_gp_header(buf, srs_id, min_x, min_y, max_x, max_y);
162 detail::append_u32(buf, static_cast<uint32_t>(xs.size()));
163 for (size_t i = 0; i < xs.size(); ++i) {
164 detail::append_f64(buf, xs[i]);
165 detail::append_f64(buf, ys[i]);
166 }
167 return buf;
168}
169
175inline std::vector<uint8_t> encode_multipolygon(const std::vector<double>& xs,
176 const std::vector<double>& ys,
177 int32_t srs_id) {
178 if (xs.empty()) {
179 std::vector<uint8_t> buf;
183 detail::append_u32(buf, 0);
184 return buf;
185 }
186 // Ensure ring closure
187 bool closed = (xs.front() == xs.back() && ys.front() == ys.back());
188 uint32_t n_pts = static_cast<uint32_t>(xs.size()) + (closed ? 0 : 1);
189
190 double min_x = xs[0], max_x = xs[0], min_y = ys[0], max_y = ys[0];
191 for (size_t i = 1; i < xs.size(); ++i) {
192 min_x = std::min(min_x, xs[i]);
193 max_x = std::max(max_x, xs[i]);
194 min_y = std::min(min_y, ys[i]);
195 max_y = std::max(max_y, ys[i]);
196 }
197
198 std::vector<uint8_t> buf;
199 buf.reserve(8 + 32 + 50 + n_pts * 16);
200 detail::write_gp_header(buf, srs_id, min_x, min_y, max_x, max_y);
201
202 // WKB MULTIPOLYGON
205 detail::append_u32(buf, 1); // 1 polygon
206
207 // WKB POLYGON
210 detail::append_u32(buf, 1); // 1 ring
211 detail::append_u32(buf, n_pts);
212 for (size_t i = 0; i < xs.size(); ++i) {
213 detail::append_f64(buf, xs[i]);
214 detail::append_f64(buf, ys[i]);
215 }
216 if (!closed) {
217 detail::append_f64(buf, xs.front());
218 detail::append_f64(buf, ys.front());
219 }
220 return buf;
221}
222
232inline std::vector<uint8_t> encode_polygon(const std::vector<double>& xs,
233 const std::vector<double>& ys,
234 int32_t srs_id) {
235 if (xs.empty()) {
236 std::vector<uint8_t> buf;
240 detail::append_u32(buf, 0);
241 return buf;
242 }
243 // Ensure ring closure
244 bool closed = (xs.front() == xs.back() && ys.front() == ys.back());
245 uint32_t n_pts = static_cast<uint32_t>(xs.size()) + (closed ? 0 : 1);
246
247 double min_x = xs[0], max_x = xs[0], min_y = ys[0], max_y = ys[0];
248 for (size_t i = 1; i < xs.size(); ++i) {
249 min_x = std::min(min_x, xs[i]);
250 max_x = std::max(max_x, xs[i]);
251 min_y = std::min(min_y, ys[i]);
252 max_y = std::max(max_y, ys[i]);
253 }
254
255 std::vector<uint8_t> buf;
256 buf.reserve(8 + 32 + 30 + n_pts * 16);
257 detail::write_gp_header(buf, srs_id, min_x, min_y, max_x, max_y);
258
259 // WKB POLYGON
262 detail::append_u32(buf, 1); // 1 ring
263 detail::append_u32(buf, n_pts);
264 for (size_t i = 0; i < xs.size(); ++i) {
265 detail::append_f64(buf, xs[i]);
266 detail::append_f64(buf, ys[i]);
267 }
268 if (!closed) {
269 detail::append_f64(buf, xs.front());
270 detail::append_f64(buf, ys.front());
271 }
272 return buf;
273}
274
275// ============================================================================
276// Decode structures
277// ============================================================================
278
280 double x = 0.0, y = 0.0;
281 int32_t srs_id = 0;
282};
283
285 std::vector<double> xs, ys;
286 int32_t srs_id = 0;
287};
288
290 // Outer ring of the first polygon only (sufficient for SWMM subcatchments)
291 std::vector<double> xs, ys;
292 int32_t srs_id = 0;
293};
294
295// ============================================================================
296// Decode functions
297// ============================================================================
298
299namespace detail {
300
301inline size_t parse_gp_header(const uint8_t* data, size_t size, int32_t& srs_id) {
302 if (size < 8) throw std::runtime_error("GeoPackage binary too short");
303 if (data[0] != GP_MAGIC_1 || data[1] != GP_MAGIC_2)
304 throw std::runtime_error("Invalid GeoPackage binary magic");
305 uint8_t flags = data[3];
306 std::memcpy(&srs_id, data + 4, 4);
307 // Determine envelope size from flags bits 1-3
308 int envelope_type = (flags >> 1) & 0x07;
309 size_t envelope_size = 0;
310 switch (envelope_type) {
311 case 0: envelope_size = 0; break;
312 case 1: envelope_size = 32; break; // XY
313 case 2: envelope_size = 48; break; // XYZ
314 case 3: envelope_size = 48; break; // XYM
315 case 4: envelope_size = 64; break; // XYZM
316 default: throw std::runtime_error("Unknown envelope type");
317 }
318 return 8 + envelope_size; // offset to WKB payload
319}
320
321template<typename T>
322inline T read_val(const uint8_t* data, size_t& offset) {
323 T val;
324 std::memcpy(&val, data + offset, sizeof(T));
325 offset += sizeof(T);
326 return val;
327}
328
329} // namespace detail
330
331inline DecodedPoint decode_point(const std::vector<uint8_t>& blob) {
332 DecodedPoint pt;
333 size_t offset = detail::parse_gp_header(blob.data(), blob.size(), pt.srs_id);
334 offset += 1; // byte order
335 uint32_t type = detail::read_val<uint32_t>(blob.data(), offset);
336 if (type != WKB_POINT) throw std::runtime_error("Expected WKB POINT");
337 pt.x = detail::read_val<double>(blob.data(), offset);
338 pt.y = detail::read_val<double>(blob.data(), offset);
339 return pt;
340}
341
342inline DecodedLinestring decode_linestring(const std::vector<uint8_t>& blob) {
344 size_t offset = detail::parse_gp_header(blob.data(), blob.size(), ls.srs_id);
345 offset += 1; // byte order
346 uint32_t type = detail::read_val<uint32_t>(blob.data(), offset);
347 if (type != WKB_LINESTRING) throw std::runtime_error("Expected WKB LINESTRING");
348 uint32_t n = detail::read_val<uint32_t>(blob.data(), offset);
349 ls.xs.resize(n);
350 ls.ys.resize(n);
351 for (uint32_t i = 0; i < n; ++i) {
352 ls.xs[i] = detail::read_val<double>(blob.data(), offset);
353 ls.ys[i] = detail::read_val<double>(blob.data(), offset);
354 }
355 return ls;
356}
357
358inline DecodedMultipolygon decode_multipolygon(const std::vector<uint8_t>& blob) {
360 size_t offset = detail::parse_gp_header(blob.data(), blob.size(), mp.srs_id);
361 offset += 1; // byte order
362 uint32_t type = detail::read_val<uint32_t>(blob.data(), offset);
363 if (type != WKB_MULTIPOLYGON) throw std::runtime_error("Expected WKB MULTIPOLYGON");
364 uint32_t n_polys = detail::read_val<uint32_t>(blob.data(), offset);
365 if (n_polys == 0) return mp;
366 // Read first polygon
367 offset += 1; // polygon byte order
368 uint32_t poly_type = detail::read_val<uint32_t>(blob.data(), offset);
369 (void)poly_type;
370 uint32_t n_rings = detail::read_val<uint32_t>(blob.data(), offset);
371 if (n_rings == 0) return mp;
372 // Read first ring (outer ring)
373 uint32_t n_pts = detail::read_val<uint32_t>(blob.data(), offset);
374 mp.xs.resize(n_pts);
375 mp.ys.resize(n_pts);
376 for (uint32_t i = 0; i < n_pts; ++i) {
377 mp.xs[i] = detail::read_val<double>(blob.data(), offset);
378 mp.ys[i] = detail::read_val<double>(blob.data(), offset);
379 }
380 return mp;
381}
382
383} // namespace openswmm::gpkg
384
385#endif // OPENSWMM_GPKG_GEOMETRY_HPP
Definition GpkgGeometry.hpp:64
void append_u32(std::vector< uint8_t > &buf, uint32_t v)
Definition GpkgGeometry.hpp:70
void write_gp_header_empty(std::vector< uint8_t > &buf, int32_t srs_id)
Definition GpkgGeometry.hpp:103
size_t parse_gp_header(const uint8_t *data, size_t size, int32_t &srs_id)
Definition GpkgGeometry.hpp:301
T read_val(const uint8_t *data, size_t &offset)
Definition GpkgGeometry.hpp:322
void append_u8(std::vector< uint8_t > &buf, uint8_t v)
Definition GpkgGeometry.hpp:66
void append_f64(std::vector< uint8_t > &buf, double v)
Definition GpkgGeometry.hpp:75
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)
Definition GpkgGeometry.hpp:81
Definition ExternalContentReader.cpp:49
std::vector< uint8_t > encode_point(double x, double y, int32_t srs_id)
Encode a POINT geometry in GeoPackage Binary format.
Definition GpkgGeometry.hpp:122
DecodedMultipolygon decode_multipolygon(const std::vector< uint8_t > &blob)
Definition GpkgGeometry.hpp:358
DecodedPoint decode_point(const std::vector< uint8_t > &blob)
Definition GpkgGeometry.hpp:331
std::vector< uint8_t > encode_polygon(const std::vector< double > &xs, const std::vector< double > &ys, int32_t srs_id)
Encode a POLYGON geometry (single ring) in GeoPackage Binary format.
Definition GpkgGeometry.hpp:232
constexpr uint8_t WKB_LITTLE_ENDIAN
Definition GpkgGeometry.hpp:54
DecodedLinestring decode_linestring(const std::vector< uint8_t > &blob)
Definition GpkgGeometry.hpp:342
std::vector< uint8_t > encode_linestring(const std::vector< double > &xs, const std::vector< double > &ys, int32_t srs_id)
Encode a LINESTRING geometry in GeoPackage Binary format.
Definition GpkgGeometry.hpp:139
constexpr uint8_t GP_MAGIC_2
Definition GpkgGeometry.hpp:58
WkbType
Definition GpkgGeometry.hpp:46
@ WKB_POINT
Definition GpkgGeometry.hpp:47
@ WKB_POLYGON
Definition GpkgGeometry.hpp:49
@ WKB_MULTIPOLYGON
Definition GpkgGeometry.hpp:50
@ WKB_LINESTRING
Definition GpkgGeometry.hpp:48
constexpr uint8_t GP_MAGIC_1
Definition GpkgGeometry.hpp:57
std::vector< uint8_t > encode_multipolygon(const std::vector< double > &xs, const std::vector< double > &ys, int32_t srs_id)
Encode a MULTIPOLYGON geometry (single polygon, single ring) in GeoPackage Binary format.
Definition GpkgGeometry.hpp:175
double * y
Definition odesolve.c:28
Definition GpkgGeometry.hpp:284
std::vector< double > xs
Definition GpkgGeometry.hpp:285
std::vector< double > ys
Definition GpkgGeometry.hpp:285
int32_t srs_id
Definition GpkgGeometry.hpp:286
Definition GpkgGeometry.hpp:289
int32_t srs_id
Definition GpkgGeometry.hpp:292
std::vector< double > ys
Definition GpkgGeometry.hpp:291
std::vector< double > xs
Definition GpkgGeometry.hpp:291
Definition GpkgGeometry.hpp:279
double y
Definition GpkgGeometry.hpp:280
int32_t srs_id
Definition GpkgGeometry.hpp:281
double x
Definition GpkgGeometry.hpp:280