PostGIS 3.7.0dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
flatgeobuf.c
Go to the documentation of this file.
1/**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * PostGIS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * PostGIS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **********************************************************************
20 *
21 * Copyright (C) 2021 Björn Harrtell <bjorn@wololo.org>
22 *
23 **********************************************************************/
24
25#include <math.h>
26#include "flatgeobuf.h"
27#include "funcapi.h"
28#include "parser/parse_type.h"
29#include "pgtime.h"
30#include "utils/timestamp.h"
31#include "miscadmin.h"
32#include "utils/date.h"
33#include "utils/datetime.h"
34#include "utils/jsonb.h"
35
36static uint8_t get_column_type(Oid typoid) {
37 switch (typoid)
38 {
39 case BOOLOID:
40 return flatgeobuf_column_type_bool;
41 case INT2OID:
42 return flatgeobuf_column_type_short;
43 case INT4OID:
44 return flatgeobuf_column_type_int;
45 case INT8OID:
46 return flatgeobuf_column_type_long;
47 case FLOAT4OID:
48 return flatgeobuf_column_type_float;
49 case FLOAT8OID:
50 return flatgeobuf_column_type_double;
51 case TEXTOID:
52 case VARCHAROID:
53 return flatgeobuf_column_type_string;
54 case JSONBOID:
55 return flatgeobuf_column_type_json;
56 case BYTEAOID:
57 return flatgeobuf_column_type_binary;
58 case DATEOID:
59 case TIMEOID:
60 case TIMESTAMPOID:
61 case TIMESTAMPTZOID:
62 return flatgeobuf_column_type_datetime;
63 }
64 elog(ERROR, "flatgeobuf: get_column_type: '%d' column type not supported",
65 typoid);
66}
67
68static void inspect_table(struct flatgeobuf_agg_ctx *ctx)
69{
70 flatgeobuf_column *c;
71 flatgeobuf_column **columns;
72 uint32_t columns_size = 0;
73 Oid tupType = HeapTupleHeaderGetTypeId(ctx->row);
74 int32 tupTypmod = HeapTupleHeaderGetTypMod(ctx->row);
75 TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
76 int natts = tupdesc->natts;
77 bool geom_found = false;
78
79 POSTGIS_DEBUG(2, "calling inspect_table");
80
81 columns = palloc(sizeof(flatgeobuf_column *) * natts);
82 ctx->tupdesc = tupdesc;
83
84 // inspect columns
85 // NOTE: last element will be unused if geom attr is found
86 for (int i = 0; i < natts; i++) {
87 Oid typoid = getBaseType(TupleDescAttr(tupdesc, i)->atttypid);
88 const char *key = TupleDescAttr(tupdesc, i)->attname.data;
89 POSTGIS_DEBUGF(2, "inspecting column definition for %s with oid %d", key, typoid);
90 if (ctx->geom_name == NULL) {
91 if (!geom_found && typoid == postgis_oid(GEOMETRYOID)) {
92 ctx->geom_index = i;
93 geom_found = true;
94 continue;
95 }
96 } else {
97 if (!geom_found && strcmp(key, ctx->geom_name) == 0) {
98 ctx->geom_index = i;
99 geom_found = true;
100 continue;
101 }
102 }
103 POSTGIS_DEBUGF(2, "creating column definition for %s with oid %d", key, typoid);
104
105 c = (flatgeobuf_column *) palloc0(sizeof(flatgeobuf_column));
106 c->name = pstrdup(key);
107 c->type = get_column_type(typoid);
108 columns[columns_size] = c;
109 columns_size++;
110 }
111
112 if (!geom_found)
113 elog(ERROR, "no geom column found");
114
115 if (columns_size > 0) {
116 ctx->ctx->columns = columns;
117 ctx->ctx->columns_size = columns_size;
118 }
119}
120
121// ensure properties has room for at least size
122static void ensure_properties_size(struct flatgeobuf_agg_ctx *ctx, size_t size)
123{
124 if (ctx->ctx->properties_size == 0) {
125 ctx->ctx->properties_size = 1024 * 4;
126 POSTGIS_DEBUGF(2, "flatgeobuf: properties buffer to size %d", ctx->ctx->properties_size);
127 ctx->ctx->properties = palloc(ctx->ctx->properties_size);
128 }
129 if (ctx->ctx->properties_size < size) {
130 ctx->ctx->properties_size = ctx->ctx->properties_size * 2;
131 POSTGIS_DEBUGF(2, "flatgeobuf: reallocating properties buffer to size %d", ctx->ctx->properties_size);
132 ctx->ctx->properties = repalloc(ctx->ctx->properties, ctx->ctx->properties_size);
133 ensure_properties_size(ctx, size);
134 }
135}
136
137// ensure items have room for at least ctx->ctx->features_count + 1
138static void ensure_items_len(struct flatgeobuf_agg_ctx *ctx)
139{
140 if (ctx->ctx->features_count == 0) {
141 ctx->ctx->items_len = 32;
142 ctx->ctx->items = palloc(sizeof(flatgeobuf_item *) * ctx->ctx->items_len);
143 }
144 if (ctx->ctx->items_len < (ctx->ctx->features_count + 1)) {
145 ctx->ctx->items_len = ctx->ctx->items_len * 2;
146 POSTGIS_DEBUGF(2, "flatgeobuf: reallocating items to len %lld", ctx->ctx->items_len);
147 ctx->ctx->items = repalloc(ctx->ctx->items, sizeof(flatgeobuf_item *) * ctx->ctx->items_len);
148 ensure_items_len(ctx);
149 }
150}
151
153{
154 uint16_t ci = 0;
155 size_t offset = 0;
156 Datum datum;
157 bool isnull;
158 Oid typoid;
159 uint32_t len;
160 uint32_t i;
161
162 uint8_t byte_value;
163 int16_t short_value;
164 int32_t int_value;
165 int64_t long_value;
166 float float_value;
167 double double_value;
168 char *string_value;
169
170 //Jsonb *jb;
171
172 for (i = 0; i < (uint32_t) ctx->tupdesc->natts; i++) {
173 if (ctx->geom_index == i)
174 continue;
175 datum = GetAttributeByNum(ctx->row, i + 1, &isnull);
176 if (isnull)
177 continue;
178 ensure_properties_size(ctx, offset + sizeof(ci));
179 memcpy(ctx->ctx->properties + offset, &ci, sizeof(ci));
180 offset += sizeof(ci);
181 typoid = getBaseType(TupleDescAttr(ctx->tupdesc, i)->atttypid);
182 switch (typoid) {
183 case BOOLOID:
184 byte_value = DatumGetBool(datum) ? 1 : 0;
185 ensure_properties_size(ctx, offset + sizeof(byte_value));
186 memcpy(ctx->ctx->properties + offset, &byte_value, sizeof(byte_value));
187 offset += sizeof(byte_value);
188 break;
189 case INT2OID:
190 short_value = DatumGetInt16(datum);
191 ensure_properties_size(ctx, offset + sizeof(short_value));
192 memcpy(ctx->ctx->properties + offset, &short_value, sizeof(short_value));
193 offset += sizeof(short_value);
194 break;
195 case INT4OID:
196 int_value = DatumGetInt32(datum);
197 ensure_properties_size(ctx, offset + sizeof(int_value));
198 memcpy(ctx->ctx->properties + offset, &int_value, sizeof(int_value));
199 offset += sizeof(int_value);
200 break;
201 case INT8OID:
202 long_value = DatumGetInt64(datum);
203 ensure_properties_size(ctx, offset + sizeof(long_value));
204 memcpy(ctx->ctx->properties + offset, &long_value, sizeof(long_value));
205 offset += sizeof(long_value);
206 break;
207 case FLOAT4OID:
208 float_value = DatumGetFloat4(datum);
209 ensure_properties_size(ctx, offset + sizeof(float_value));
210 memcpy(ctx->ctx->properties + offset, &float_value, sizeof(float_value));
211 offset += sizeof(float_value);
212 break;
213 case FLOAT8OID:
214 double_value = DatumGetFloat8(datum);
215 ensure_properties_size(ctx, offset + sizeof(double_value));
216 memcpy(ctx->ctx->properties + offset, &double_value, sizeof(double_value));
217 offset += sizeof(double_value);
218 break;
219 case TEXTOID:
220 string_value = text_to_cstring(DatumGetTextP(datum));
221 len = strlen(string_value);
222 ensure_properties_size(ctx, offset + sizeof(len));
223 memcpy(ctx->ctx->properties + offset, &len, sizeof(len));
224 offset += sizeof(len);
225 ensure_properties_size(ctx, offset + len);
226 memcpy(ctx->ctx->properties + offset, string_value, len);
227 offset += len;
228 break;
229 case TIMESTAMPTZOID: {
230 struct pg_tm tm;
231 fsec_t fsec;
232 int tz;
233 const char *tzn = NULL;
234 TimestampTz timestamp;
235 timestamp = DatumGetTimestampTz(datum);
236 timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL);
237 string_value = palloc(MAXDATELEN + 1);
238 EncodeDateTime(&tm, fsec, true, tz, tzn, USE_ISO_DATES, string_value);
239 len = strlen(string_value);
240 ensure_properties_size(ctx, offset + sizeof(len));
241 memcpy(ctx->ctx->properties + offset, &len, sizeof(len));
242 offset += sizeof(len);
243 ensure_properties_size(ctx, offset + len);
244 memcpy(ctx->ctx->properties + offset, string_value, len);
245 offset += len;
246 break;
247 }
248 // TODO: handle date/time types
249 // case JSONBOID:
250 // jb = DatumGetJsonbP(datum);
251 // string_value = JsonbToCString(NULL, &jb->root, VARSIZE(jb));
252 // len = strlen(string_value);
253 // memcpy(data + offset, &len, sizeof(len));
254 // offset += sizeof(len);
255 // memcpy(data + offset, string_value, len);
256 // offset += len;
257 // break;
258 }
259 ci++;
260 }
261
262 if (offset > 0) {
263 POSTGIS_DEBUGF(3, "offset %ld", offset);
264 ctx->ctx->properties_len = offset;
265 }
266}
267
269{
270 uint8_t *buf = ctx->ctx->buf + ctx->ctx->offset;
271 uint32_t i;
272
273 for (i = 0; i < FLATGEOBUF_MAGICBYTES_SIZE / 2; i++)
274 if (buf[i] != flatgeobuf_magicbytes[i])
275 elog(ERROR, "Data is not FlatGeobuf");
276 ctx->ctx->offset += FLATGEOBUF_MAGICBYTES_SIZE;
277}
278
279static void decode_properties(struct flatgeobuf_decode_ctx *ctx, Datum *values, bool *isnull)
280{
281 uint16_t i, ci;
282 flatgeobuf_column *column;
283 uint8_t type;
284 uint32_t offset = 0;
285 uint8_t *data = ctx->ctx->properties;
286 uint32_t size = ctx->ctx->properties_len;
287
288 POSTGIS_DEBUGF(3, "flatgeobuf: decode_properties from byte array with length %d at offset %d", size, offset);
289
290 // TODO: init isnull
291
292 if (size > 0 && size < (sizeof(uint16_t) + sizeof(uint8_t)))
293 elog(ERROR, "flatgeobuf: decode_properties: Unexpected properties data size %d", size);
294 while (offset + 1 < size) {
295 if (offset + sizeof(uint16_t) > size)
296 elog(ERROR, "flatgeobuf: decode_properties: Unexpected offset %d", offset);
297 memcpy(&i, data + offset, sizeof(uint16_t));
298 ci = i + 2;
299 offset += sizeof(uint16_t);
300 if (i >= ctx->ctx->columns_size)
301 elog(ERROR, "flatgeobuf: decode_properties: Column index %hu out of range", i);
302 column = ctx->ctx->columns[i];
303 type = column->type;
304 isnull[ci] = false;
305 switch (type) {
306 case flatgeobuf_column_type_bool: {
307 uint8_t value;
308 if (offset + sizeof(uint8_t) > size)
309 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for bool value");
310 memcpy(&value, data + offset, sizeof(uint8_t));
311 values[ci] = BoolGetDatum(value);
312 offset += sizeof(uint8_t);
313 break;
314 }
315 case flatgeobuf_column_type_byte: {
316 int8_t value;
317 if (offset + sizeof(int8_t) > size)
318 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for byte value");
319 memcpy(&value, data + offset, sizeof(int8_t));
320 values[ci] = Int8GetDatum(value);
321 offset += sizeof(int8_t);
322 break;
323 }
324 case flatgeobuf_column_type_ubyte: {
325 uint8_t value;
326 if (offset + sizeof(uint8_t) > size)
327 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for ubyte value");
328 memcpy(&value, data + offset, sizeof(uint8_t));
329 values[ci] = UInt8GetDatum(value);
330 offset += sizeof(uint8_t);
331 break;
332 }
333 case flatgeobuf_column_type_short: {
334 int16_t value;
335 if (offset + sizeof(int16_t) > size)
336 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for short value");
337 memcpy(&value, data + offset, sizeof(int16_t));
338 values[ci] = Int16GetDatum(value);
339 offset += sizeof(int16_t);
340 break;
341 }
342 case flatgeobuf_column_type_ushort: {
343 uint16_t value;
344 if (offset + sizeof(uint16_t) > size)
345 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for ushort value");
346 memcpy(&value, data + offset, sizeof(uint16_t));
347 values[ci] = UInt16GetDatum(value);
348 offset += sizeof(uint16_t);
349 break;
350 }
351 case flatgeobuf_column_type_int: {
352 int32_t value;
353 if (offset + sizeof(int32_t) > size)
354 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for int value");
355 memcpy(&value, data + offset, sizeof(int32_t));
356 values[ci] = Int32GetDatum(value);
357 offset += sizeof(int32_t);
358 break;
359 }
360 case flatgeobuf_column_type_uint: {
361 uint32_t value;
362 if (offset + sizeof(uint32_t) > size)
363 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for uint value");
364 memcpy(&value, data + offset, sizeof(uint32_t));
365 values[ci] = UInt32GetDatum(value);
366 offset += sizeof(uint32_t);
367 break;
368 }
369 case flatgeobuf_column_type_long: {
370 int64_t value;
371 if (offset + sizeof(int64_t) > size)
372 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for long value");
373 memcpy(&value, data + offset, sizeof(int64_t));
374 values[ci] = Int64GetDatum(value);
375 offset += sizeof(int64_t);
376 break;
377 }
378 case flatgeobuf_column_type_ulong: {
379 uint64_t value;
380 if (offset + sizeof(uint64_t) > size)
381 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for ulong value");
382 memcpy(&value, data + offset, sizeof(uint64_t));
383 values[ci] = UInt64GetDatum(value);
384 offset += sizeof(uint64_t);
385 break;
386 }
387 case flatgeobuf_column_type_float: {
388 float value;
389 if (offset + sizeof(float) > size)
390 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for float value");
391 memcpy(&value, data + offset, sizeof(float));
392 values[ci] = Float4GetDatum(value);
393 offset += sizeof(float);
394 break;
395 }
396 case flatgeobuf_column_type_double: {
397 double value;
398 if (offset + sizeof(double) > size)
399 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for double value");
400 memcpy(&value, data + offset, sizeof(double));
401 values[ci] = Float8GetDatum(value);
402 offset += sizeof(double);
403 break;
404 }
405 case flatgeobuf_column_type_string: {
406 uint32_t len;
407 if (offset + sizeof(len) > size)
408 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for string value");
409 memcpy(&len, data + offset, sizeof(uint32_t));
410 offset += sizeof(len);
411 values[ci] = PointerGetDatum(cstring_to_text_with_len((const char *) data + offset, len));
412 offset += len;
413 break;
414 }
415 case flatgeobuf_column_type_datetime: {
416 uint32_t len;
417 char *buf;
418 char workbuf[MAXDATELEN + MAXDATEFIELDS];
419 char *field[MAXDATEFIELDS];
420 int ftype[MAXDATEFIELDS];
421 int dtype;
422 int nf;
423 struct pg_tm tt, *tm = &tt;
424 fsec_t fsec;
425 int tzp;
426 TimestampTz dttz;
427#if POSTGIS_PGSQL_VERSION >= 160
428 DateTimeErrorExtra extra;
429#endif
430 if (offset + sizeof(len) > size)
431 elog(ERROR, "flatgeobuf: decode_properties: Invalid size for string value");
432 memcpy(&len, data + offset, sizeof(uint32_t));
433 offset += sizeof(len);
434 buf = palloc0(len + 1);
435 memcpy(buf, (const char *) data + offset, len);
436 ParseDateTime((const char *) buf, workbuf, sizeof(workbuf), field, ftype, MAXDATEFIELDS, &nf);
437
438#if POSTGIS_PGSQL_VERSION >= 160
439 DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp, &extra);
440#else
441 DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
442#endif
443 tm2timestamp(tm, fsec, &tzp, &dttz);
444 values[ci] = TimestampTzGetDatum(dttz);
445 offset += len;
446 break;
447 }
448 // TODO: find out how to handle string to jsonb Datum
449 // case FlatGeobuf_ColumnType_Json:
450 // if (offset + sizeof(len) > size)
451 // elog(ERROR, "flatgeobuf: decode_properties: Invalid size for json value");
452 // len = *((uint32_t *)(data + offset));
453 // offset += sizeof(len);
454 // values[ci] = jsonb_from_cstring((const char *) data + offset, len);
455 // offset += len;
456 // break;
457 default:
458 elog(ERROR, "flatgeobuf: decode_properties: Unknown type %d", type);
459 }
460 }
461
462}
463
465{
466 HeapTuple heapTuple;
467 uint32_t natts = ctx->tupdesc->natts;
468
469 Datum *values = palloc0(natts * sizeof(Datum *));
470 bool *isnull = palloc0(natts * sizeof(bool *));
471
472 values[0] = Int32GetDatum(ctx->fid);
473
474 if (flatgeobuf_decode_feature(ctx->ctx))
475 elog(ERROR, "flatgeobuf_decode_feature: unsuccessful");
476
477 if (ctx->ctx->lwgeom != NULL) {
478 values[1] = PointerGetDatum(geometry_serialize(ctx->ctx->lwgeom));
479 } else {
480 POSTGIS_DEBUG(3, "geometry is null");
481 isnull[1] = true;
482 }
483
484 if (natts > 2 && ctx->ctx->properties_len > 0)
485 decode_properties(ctx, values, isnull);
486
487 heapTuple = heap_form_tuple(ctx->tupdesc, values, isnull);
488 ctx->result = HeapTupleGetDatum(heapTuple);
489 ctx->fid++;
490
491 POSTGIS_DEBUGF(3, "fid now %d", ctx->fid);
492
493 if (ctx->ctx->offset == ctx->ctx->size) {
494 POSTGIS_DEBUGF(3, "reached end at %lld", ctx->ctx->offset);
495 ctx->done = true;
496 }
497}
498
503{
504 struct flatgeobuf_agg_ctx *ctx;
505 size_t size = VARHDRSZ + FLATGEOBUF_MAGICBYTES_SIZE;
506 ctx = palloc0(sizeof(*ctx));
507 ctx->ctx = palloc0(sizeof(flatgeobuf_ctx));
508 ctx->ctx->buf = lwalloc(size);
509 memcpy(ctx->ctx->buf + VARHDRSZ, flatgeobuf_magicbytes, FLATGEOBUF_MAGICBYTES_SIZE);
510 ctx->geom_name = geom_name;
511 ctx->geom_index = 0;
512 ctx->ctx->features_count = 0;
513 ctx->ctx->offset = size;
514 ctx->tupdesc = NULL;
515 ctx->ctx->create_index = create_index;
516 return ctx;
517}
518
527{
528 LWGEOM *lwgeom = NULL;
529 bool isnull = false;
530 Datum datum;
531 GSERIALIZED *gs;
532
533 if (ctx->ctx->features_count == 0)
535
536 datum = GetAttributeByNum(ctx->row, ctx->geom_index + 1, &isnull);
537 if (!isnull) {
538 gs = (GSERIALIZED *) PG_DETOAST_DATUM_COPY(datum);
539 lwgeom = lwgeom_from_gserialized(gs);
540 }
541 ctx->ctx->lwgeom = lwgeom;
542
543 if (ctx->ctx->features_count == 0)
544 flatgeobuf_encode_header(ctx->ctx);
545
547 if (ctx->ctx->create_index)
549 flatgeobuf_encode_feature(ctx->ctx);
550}
551
558{
559 POSTGIS_DEBUGF(3, "called at offset %lld", ctx->ctx->offset);
560 if (ctx == NULL)
561 flatgeobuf_agg_ctx_init(NULL, false);
562 // header only result
563 if (ctx->ctx->features_count == 0) {
564 flatgeobuf_encode_header(ctx->ctx);
565 } else if (ctx->ctx->create_index) {
566 ctx->ctx->index_node_size = 16;
567 flatgeobuf_create_index(ctx->ctx);
568 }
569 if (ctx->tupdesc != NULL)
570 ReleaseTupleDesc(ctx->tupdesc);
571 SET_VARSIZE(ctx->ctx->buf, ctx->ctx->offset);
572 return ctx->ctx->buf;
573}
void flatgeobuf_check_magicbytes(struct flatgeobuf_decode_ctx *ctx)
Definition flatgeobuf.c:268
static void inspect_table(struct flatgeobuf_agg_ctx *ctx)
Definition flatgeobuf.c:68
static void encode_properties(flatgeobuf_agg_ctx *ctx)
Definition flatgeobuf.c:152
uint8_t * flatgeobuf_agg_finalfn(struct flatgeobuf_agg_ctx *ctx)
Finalize aggregation.
Definition flatgeobuf.c:557
static uint8_t get_column_type(Oid typoid)
Definition flatgeobuf.c:36
struct flatgeobuf_agg_ctx * flatgeobuf_agg_ctx_init(const char *geom_name, const bool create_index)
Initialize aggregation context.
Definition flatgeobuf.c:502
static void ensure_items_len(struct flatgeobuf_agg_ctx *ctx)
Definition flatgeobuf.c:138
static void ensure_properties_size(struct flatgeobuf_agg_ctx *ctx, size_t size)
Definition flatgeobuf.c:122
void flatgeobuf_agg_transfn(struct flatgeobuf_agg_ctx *ctx)
Aggregation step.
Definition flatgeobuf.c:526
static void decode_properties(struct flatgeobuf_decode_ctx *ctx, Datum *values, bool *isnull)
Definition flatgeobuf.c:279
void flatgeobuf_decode_row(struct flatgeobuf_decode_ctx *ctx)
Definition flatgeobuf.c:464
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
void * lwalloc(size_t size)
Definition lwutil.c:227
static int create_index(const char *schema, const char *table, const char *column, const char *tablespace, STRINGBUFFER *buffer)
unsigned int int32
Definition shpopen.c:54
HeapTupleHeader row
Definition flatgeobuf.h:52
const char * geom_name
Definition flatgeobuf.h:49
flatgeobuf_ctx * ctx
Definition flatgeobuf.h:48
uint32_t geom_index
Definition flatgeobuf.h:50
TupleDesc tupdesc
Definition flatgeobuf.h:51
flatgeobuf_ctx * ctx
Definition flatgeobuf.h:62