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) 2016-2017 Björn Harrtell <bjorn@wololo.org>
22 *
23 **********************************************************************/
24
25 #include "mvt.h"
26 #include "lwgeom_geos.h"
27
28 #ifdef HAVE_LIBPROTOBUF
29
30 #if POSTGIS_PGSQL_VERSION >= 94
31 #include "utils/jsonb.h"
32 #endif
33
34 #if POSTGIS_PGSQL_VERSION < 110
35 /* See trac ticket #3867 */
36 # define DatumGetJsonbP DatumGetJsonb
37 #endif
38
39 #include "uthash.h"
40
41 #define FEATURES_CAPACITY_INITIAL 50
42
43 enum mvt_cmd_id
44 {
45 CMD_MOVE_TO = 1,
46 CMD_LINE_TO = 2,
47 CMD_CLOSE_PATH = 7
48 };
49
50 enum mvt_type
51 {
52 MVT_POINT = 1,
53 MVT_LINE = 2,
54 MVT_RING = 3
55 };
56
57 struct mvt_kv_key
58 {
59 char *name;
60 uint32_t id;
61 UT_hash_handle hh;
62 };
63
64 struct mvt_kv_string_value
65 {
66 char *string_value;
67 uint32_t id;
68 UT_hash_handle hh;
69 };
70
71 struct mvt_kv_float_value
72 {
73 float float_value;
74 uint32_t id;
75 UT_hash_handle hh;
76 };
77
78 struct mvt_kv_double_value
79 {
80 double double_value;
81 uint32_t id;
82 UT_hash_handle hh;
83 };
84
85 struct mvt_kv_uint_value
86 {
87 uint64_t uint_value;
88 uint32_t id;
89 UT_hash_handle hh;
90 };
91
92 struct mvt_kv_sint_value
93 {
94 int64_t sint_value;
95 uint32_t id;
96 UT_hash_handle hh;
97 };
98
99 struct mvt_kv_bool_value
100 {
101 protobuf_c_boolean bool_value;
102 uint32_t id;
103 UT_hash_handle hh;
104 };
105
c_int(enum mvt_cmd_id id,uint32_t count)106 static inline uint32_t c_int(enum mvt_cmd_id id, uint32_t count)
107 {
108 return (id & 0x7) | (count << 3);
109 }
110
p_int(int32_t value)111 static inline uint32_t p_int(int32_t value)
112 {
113 return (value << 1) ^ (value >> 31);
114 }
115
encode_ptarray(mvt_agg_context * ctx,enum mvt_type type,POINTARRAY * pa,uint32_t * buffer,int32_t * px,int32_t * py)116 static uint32_t encode_ptarray(__attribute__((__unused__)) mvt_agg_context *ctx,
117 enum mvt_type type, POINTARRAY *pa, uint32_t *buffer,
118 int32_t *px, int32_t *py)
119 {
120 uint32_t offset = 0;
121 uint32_t i, c = 0;
122 int32_t dx, dy, x, y;
123 const POINT2D *p;
124
125 /* loop points and add to buffer */
126 for (i = 0; i < pa->npoints; i++)
127 {
128 /* move offset for command */
129 if (i == 0 || (i == 1 && type > MVT_POINT))
130 offset++;
131 /* skip closing point for rings */
132 if (type == MVT_RING && i == pa->npoints - 1)
133 break;
134 p = getPoint2d_cp(pa, i);
135 x = p->x;
136 y = p->y;
137 dx = x - *px;
138 dy = y - *py;
139 buffer[offset++] = p_int(dx);
140 buffer[offset++] = p_int(dy);
141 *px = x;
142 *py = y;
143 c++;
144 }
145
146 /* determine initial move and eventual line command */
147 if (type == MVT_POINT)
148 {
149 /* point or multipoint, use actual number of point count */
150 buffer[0] = c_int(CMD_MOVE_TO, c);
151 }
152 else
153 {
154 /* line or polygon, assume count 1 */
155 buffer[0] = c_int(CMD_MOVE_TO, 1);
156 /* line command with move point subtracted from count */
157 buffer[3] = c_int(CMD_LINE_TO, c - 1);
158 }
159
160 /* add close command if ring */
161 if (type == MVT_RING)
162 buffer[offset++] = c_int(CMD_CLOSE_PATH, 1);
163
164 return offset;
165 }
166
encode_ptarray_initial(mvt_agg_context * ctx,enum mvt_type type,POINTARRAY * pa,uint32_t * buffer)167 static uint32_t encode_ptarray_initial(mvt_agg_context *ctx,
168 enum mvt_type type,
169 POINTARRAY *pa, uint32_t *buffer)
170 {
171 int32_t px = 0, py = 0;
172 return encode_ptarray(ctx, type, pa, buffer, &px, &py);
173 }
174
encode_point(mvt_agg_context * ctx,LWPOINT * point)175 static void encode_point(mvt_agg_context *ctx, LWPOINT *point)
176 {
177 VectorTile__Tile__Feature *feature = ctx->feature;
178 feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POINT;
179 feature->has_type = 1;
180 feature->n_geometry = 3;
181 feature->geometry = palloc(sizeof(*feature->geometry) * 3);
182 encode_ptarray_initial(ctx, MVT_POINT, point->point, feature->geometry);
183 }
184
encode_mpoint(mvt_agg_context * ctx,LWMPOINT * mpoint)185 static void encode_mpoint(mvt_agg_context *ctx, LWMPOINT *mpoint)
186 {
187 size_t c;
188 VectorTile__Tile__Feature *feature = ctx->feature;
189 // NOTE: inefficient shortcut LWMPOINT->LWLINE
190 LWLINE *lwline = lwline_from_lwmpoint(mpoint->srid, mpoint);
191 feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POINT;
192 feature->has_type = 1;
193 c = 1 + lwline->points->npoints * 2;
194 feature->geometry = palloc(sizeof(*feature->geometry) * c);
195 feature->n_geometry = encode_ptarray_initial(ctx, MVT_POINT,
196 lwline->points, feature->geometry);
197 }
198
encode_line(mvt_agg_context * ctx,LWLINE * lwline)199 static void encode_line(mvt_agg_context *ctx, LWLINE *lwline)
200 {
201 size_t c;
202 VectorTile__Tile__Feature *feature = ctx->feature;
203 feature->type = VECTOR_TILE__TILE__GEOM_TYPE__LINESTRING;
204 feature->has_type = 1;
205 c = 2 + lwline->points->npoints * 2;
206 feature->geometry = palloc(sizeof(*feature->geometry) * c);
207 feature->n_geometry = encode_ptarray_initial(ctx, MVT_LINE,
208 lwline->points, feature->geometry);
209 }
210
encode_mline(mvt_agg_context * ctx,LWMLINE * lwmline)211 static void encode_mline(mvt_agg_context *ctx, LWMLINE *lwmline)
212 {
213 uint32_t i;
214 int32_t px = 0, py = 0;
215 size_t c = 0, offset = 0;
216 VectorTile__Tile__Feature *feature = ctx->feature;
217 feature->type = VECTOR_TILE__TILE__GEOM_TYPE__LINESTRING;
218 feature->has_type = 1;
219 for (i = 0; i < lwmline->ngeoms; i++)
220 c += 2 + lwmline->geoms[i]->points->npoints * 2;
221 feature->geometry = palloc(sizeof(*feature->geometry) * c);
222 for (i = 0; i < lwmline->ngeoms; i++)
223 offset += encode_ptarray(ctx, MVT_LINE,
224 lwmline->geoms[i]->points,
225 feature->geometry + offset, &px, &py);
226 feature->n_geometry = offset;
227 }
228
encode_poly(mvt_agg_context * ctx,LWPOLY * lwpoly)229 static void encode_poly(mvt_agg_context *ctx, LWPOLY *lwpoly)
230 {
231 uint32_t i;
232 int32_t px = 0, py = 0;
233 size_t c = 0, offset = 0;
234 VectorTile__Tile__Feature *feature = ctx->feature;
235 feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POLYGON;
236 feature->has_type = 1;
237 for (i = 0; i < lwpoly->nrings; i++)
238 c += 3 + ((lwpoly->rings[i]->npoints - 1) * 2);
239 feature->geometry = palloc(sizeof(*feature->geometry) * c);
240 for (i = 0; i < lwpoly->nrings; i++)
241 offset += encode_ptarray(ctx, MVT_RING,
242 lwpoly->rings[i],
243 feature->geometry + offset, &px, &py);
244 feature->n_geometry = offset;
245 }
246
encode_mpoly(mvt_agg_context * ctx,LWMPOLY * lwmpoly)247 static void encode_mpoly(mvt_agg_context *ctx, LWMPOLY *lwmpoly)
248 {
249 uint32_t i, j;
250 int32_t px = 0, py = 0;
251 size_t c = 0, offset = 0;
252 LWPOLY *poly;
253 VectorTile__Tile__Feature *feature = ctx->feature;
254 feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POLYGON;
255 feature->has_type = 1;
256 for (i = 0; i < lwmpoly->ngeoms; i++)
257 for (j = 0; poly = lwmpoly->geoms[i], j < poly->nrings; j++)
258 c += 3 + ((poly->rings[j]->npoints - 1) * 2);
259 feature->geometry = palloc(sizeof(*feature->geometry) * c);
260 for (i = 0; i < lwmpoly->ngeoms; i++)
261 for (j = 0; poly = lwmpoly->geoms[i], j < poly->nrings; j++)
262 offset += encode_ptarray(ctx, MVT_RING,
263 poly->rings[j], feature->geometry + offset,
264 &px, &py);
265 feature->n_geometry = offset;
266 }
267
encode_geometry(mvt_agg_context * ctx,LWGEOM * lwgeom)268 static void encode_geometry(mvt_agg_context *ctx, LWGEOM *lwgeom)
269 {
270 int type = lwgeom->type;
271
272 switch (type)
273 {
274 case POINTTYPE:
275 return encode_point(ctx, (LWPOINT*)lwgeom);
276 case LINETYPE:
277 return encode_line(ctx, (LWLINE*)lwgeom);
278 case POLYGONTYPE:
279 return encode_poly(ctx, (LWPOLY*)lwgeom);
280 case MULTIPOINTTYPE:
281 return encode_mpoint(ctx, (LWMPOINT*)lwgeom);
282 case MULTILINETYPE:
283 return encode_mline(ctx, (LWMLINE*)lwgeom);
284 case MULTIPOLYGONTYPE:
285 return encode_mpoly(ctx, (LWMPOLY*)lwgeom);
286 default: elog(ERROR, "encode_geometry: '%s' geometry type not supported",
287 lwtype_name(type));
288 }
289 }
290
get_tuple_desc(mvt_agg_context * ctx)291 static TupleDesc get_tuple_desc(mvt_agg_context *ctx)
292 {
293 Oid tupType = HeapTupleHeaderGetTypeId(ctx->row);
294 int32 tupTypmod = HeapTupleHeaderGetTypMod(ctx->row);
295 TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
296 return tupdesc;
297 }
298
get_key_index_with_size(mvt_agg_context * ctx,const char * name,size_t size)299 static uint32_t get_key_index_with_size(mvt_agg_context *ctx, const char *name, size_t size)
300 {
301 struct mvt_kv_key *kv;
302 HASH_FIND(hh, ctx->keys_hash, name, size, kv);
303 if (!kv)
304 return UINT32_MAX;
305 return kv->id;
306 }
307
add_key(mvt_agg_context * ctx,char * name)308 static uint32_t add_key(mvt_agg_context *ctx, char *name)
309 {
310 struct mvt_kv_key *kv;
311 size_t size = strlen(name);
312 kv = palloc(sizeof(*kv));
313 kv->id = ctx->keys_hash_i++;
314 kv->name = name;
315 HASH_ADD_KEYPTR(hh, ctx->keys_hash, name, size, kv);
316 return kv->id;
317 }
318
parse_column_keys(mvt_agg_context * ctx)319 static void parse_column_keys(mvt_agg_context *ctx)
320 {
321 uint32_t i, natts;
322 bool geom_found = false;
323
324 POSTGIS_DEBUG(2, "parse_column_keys called");
325
326 ctx->column_cache.tupdesc = get_tuple_desc(ctx);
327 natts = ctx->column_cache.tupdesc->natts;
328
329 ctx->column_cache.column_keys_index = palloc(sizeof(uint32_t) * natts);
330 ctx->column_cache.column_oid = palloc(sizeof(uint32_t) * natts);
331 ctx->column_cache.values = palloc(sizeof(Datum) * natts);
332 ctx->column_cache.nulls = palloc(sizeof(bool) * natts);
333
334 for (i = 0; i < natts; i++)
335 {
336 #if POSTGIS_PGSQL_VERSION < 110
337 Oid typoid = getBaseType(ctx->column_cache.tupdesc->attrs[i]->atttypid);
338 char *tkey = ctx->column_cache.tupdesc->attrs[i]->attname.data;
339 #else
340 Oid typoid = getBaseType(ctx->column_cache.tupdesc->attrs[i].atttypid);
341 char *tkey = ctx->column_cache.tupdesc->attrs[i].attname.data;
342 #endif
343
344 ctx->column_cache.column_oid[i] = typoid;
345 #if POSTGIS_PGSQL_VERSION >= 94
346 if (typoid == JSONBOID)
347 {
348 ctx->column_cache.column_keys_index[i] = UINT32_MAX;
349 continue;
350 }
351 #endif
352
353 if (ctx->geom_name == NULL)
354 {
355 if (!geom_found && typoid == postgis_oid(GEOMETRYOID))
356 {
357 ctx->geom_index = i;
358 geom_found = true;
359 continue;
360 }
361 }
362 else
363 {
364 if (!geom_found && strcmp(tkey, ctx->geom_name) == 0)
365 {
366 ctx->geom_index = i;
367 geom_found = true;
368 continue;
369 }
370 }
371
372 ctx->column_cache.column_keys_index[i] = add_key(ctx, pstrdup(tkey));
373 }
374
375 if (!geom_found)
376 elog(ERROR, "parse_column_keys: no geometry column found");
377 }
378
encode_keys(mvt_agg_context * ctx)379 static void encode_keys(mvt_agg_context *ctx)
380 {
381 struct mvt_kv_key *kv;
382 size_t n_keys = ctx->keys_hash_i;
383 char **keys = palloc(n_keys * sizeof(*keys));
384 for (kv = ctx->keys_hash; kv != NULL; kv=kv->hh.next)
385 keys[kv->id] = kv->name;
386 ctx->layer->n_keys = n_keys;
387 ctx->layer->keys = keys;
388
389 HASH_CLEAR(hh, ctx->keys_hash);
390 }
391
create_value()392 static VectorTile__Tile__Value *create_value()
393 {
394 VectorTile__Tile__Value *value = palloc(sizeof(*value));
395 vector_tile__tile__value__init(value);
396 return value;
397 }
398
399 #define MVT_CREATE_VALUES(kvtype, hash, hasfield, valuefield) \
400 { \
401 POSTGIS_DEBUG(2, "MVT_CREATE_VALUES called"); \
402 { \
403 struct kvtype *kv; \
404 for (kv = ctx->hash; kv != NULL; kv=kv->hh.next) \
405 { \
406 VectorTile__Tile__Value *value = create_value(); \
407 value->hasfield = 1; \
408 value->valuefield = kv->valuefield; \
409 values[kv->id] = value; \
410 } \
411 } \
412 }
413
encode_values(mvt_agg_context * ctx)414 static void encode_values(mvt_agg_context *ctx)
415 {
416 VectorTile__Tile__Value **values;
417 struct mvt_kv_string_value *kv;
418
419 POSTGIS_DEBUG(2, "encode_values called");
420
421 values = palloc(ctx->values_hash_i * sizeof(*values));
422 for (kv = ctx->string_values_hash; kv != NULL; kv=kv->hh.next)
423 {
424 VectorTile__Tile__Value *value = create_value();
425 value->string_value = kv->string_value;
426 values[kv->id] = value;
427 }
428 MVT_CREATE_VALUES(mvt_kv_float_value,
429 float_values_hash, has_float_value, float_value);
430 MVT_CREATE_VALUES(mvt_kv_double_value,
431 double_values_hash, has_double_value, double_value);
432 MVT_CREATE_VALUES(mvt_kv_uint_value,
433 uint_values_hash, has_uint_value, uint_value);
434 MVT_CREATE_VALUES(mvt_kv_sint_value,
435 sint_values_hash, has_sint_value, sint_value);
436 MVT_CREATE_VALUES(mvt_kv_bool_value,
437 bool_values_hash, has_bool_value, bool_value);
438
439 POSTGIS_DEBUGF(3, "encode_values n_values: %d", ctx->values_hash_i);
440 ctx->layer->n_values = ctx->values_hash_i;
441 ctx->layer->values = values;
442
443 HASH_CLEAR(hh, ctx->string_values_hash);
444 HASH_CLEAR(hh, ctx->float_values_hash);
445 HASH_CLEAR(hh, ctx->double_values_hash);
446 HASH_CLEAR(hh, ctx->uint_values_hash);
447 HASH_CLEAR(hh, ctx->sint_values_hash);
448 HASH_CLEAR(hh, ctx->bool_values_hash);
449
450 pfree(ctx->column_cache.column_keys_index);
451 pfree(ctx->column_cache.column_oid);
452 pfree(ctx->column_cache.values);
453 pfree(ctx->column_cache.nulls);
454 ReleaseTupleDesc(ctx->column_cache.tupdesc);
455 memset(&ctx->column_cache, 0, sizeof(ctx->column_cache));
456
457 }
458
459 #define MVT_PARSE_VALUE(value, kvtype, hash, valuefield, size) \
460 { \
461 POSTGIS_DEBUG(2, "MVT_PARSE_VALUE called"); \
462 { \
463 struct kvtype *kv; \
464 HASH_FIND(hh, ctx->hash, &value, size, kv); \
465 if (!kv) \
466 { \
467 POSTGIS_DEBUG(4, "MVT_PARSE_VALUE value not found"); \
468 kv = palloc(sizeof(*kv)); \
469 POSTGIS_DEBUGF(4, "MVT_PARSE_VALUE new hash key: %d", \
470 ctx->values_hash_i); \
471 kv->id = ctx->values_hash_i++; \
472 kv->valuefield = value; \
473 HASH_ADD(hh, ctx->hash, valuefield, size, kv); \
474 } \
475 tags[ctx->row_columns*2] = k; \
476 tags[ctx->row_columns*2+1] = kv->id; \
477 } \
478 }
479
480 #define MVT_PARSE_INT_VALUE(value) \
481 { \
482 if (value >= 0) \
483 { \
484 uint64_t cvalue = value; \
485 MVT_PARSE_VALUE(cvalue, mvt_kv_uint_value, \
486 uint_values_hash, uint_value, \
487 sizeof(uint64_t)) \
488 } \
489 else \
490 { \
491 int64_t cvalue = value; \
492 MVT_PARSE_VALUE(cvalue, mvt_kv_sint_value, \
493 sint_values_hash, sint_value, \
494 sizeof(int64_t)) \
495 } \
496 }
497
498 #define MVT_PARSE_DATUM(type, kvtype, hash, valuefield, datumfunc, size) \
499 { \
500 type value = datumfunc(datum); \
501 MVT_PARSE_VALUE(value, kvtype, hash, valuefield, size); \
502 }
503
504 #define MVT_PARSE_INT_DATUM(type, datumfunc) \
505 { \
506 type value = datumfunc(datum); \
507 MVT_PARSE_INT_VALUE(value); \
508 }
509
add_value_as_string_with_size(mvt_agg_context * ctx,char * value,size_t size,uint32_t * tags,uint32_t k)510 static void add_value_as_string_with_size(mvt_agg_context *ctx,
511 char *value, size_t size, uint32_t *tags, uint32_t k)
512 {
513 struct mvt_kv_string_value *kv;
514 POSTGIS_DEBUG(2, "add_value_as_string called");
515 HASH_FIND(hh, ctx->string_values_hash, value, size, kv);
516 if (!kv)
517 {
518 POSTGIS_DEBUG(4, "add_value_as_string value not found");
519 kv = palloc(sizeof(*kv));
520 POSTGIS_DEBUGF(4, "add_value_as_string new hash key: %d",
521 ctx->values_hash_i);
522 kv->id = ctx->values_hash_i++;
523 kv->string_value = value;
524 HASH_ADD_KEYPTR(hh, ctx->string_values_hash, kv->string_value,
525 size, kv);
526 }
527 tags[ctx->row_columns*2] = k;
528 tags[ctx->row_columns*2+1] = kv->id;
529 }
530
add_value_as_string(mvt_agg_context * ctx,char * value,uint32_t * tags,uint32_t k)531 static void add_value_as_string(mvt_agg_context *ctx,
532 char *value, uint32_t *tags, uint32_t k)
533 {
534 return add_value_as_string_with_size(ctx, value, strlen(value), tags, k);
535 }
536
parse_datum_as_string(mvt_agg_context * ctx,Oid typoid,Datum datum,uint32_t * tags,uint32_t k)537 static void parse_datum_as_string(mvt_agg_context *ctx, Oid typoid,
538 Datum datum, uint32_t *tags, uint32_t k)
539 {
540 Oid foutoid;
541 bool typisvarlena;
542 char *value;
543 POSTGIS_DEBUG(2, "parse_value_as_string called");
544 getTypeOutputInfo(typoid, &foutoid, &typisvarlena);
545 value = OidOutputFunctionCall(foutoid, datum);
546 POSTGIS_DEBUGF(4, "parse_value_as_string value: %s", value);
547 add_value_as_string(ctx, value, tags, k);
548 }
549
550 #if POSTGIS_PGSQL_VERSION >= 94
parse_jsonb(mvt_agg_context * ctx,Jsonb * jb,uint32_t * tags)551 static uint32_t *parse_jsonb(mvt_agg_context *ctx, Jsonb *jb,
552 uint32_t *tags)
553 {
554 JsonbIterator *it;
555 JsonbValue v;
556 bool skipNested = false;
557 JsonbIteratorToken r;
558 uint32_t k;
559
560 if (!JB_ROOT_IS_OBJECT(jb))
561 return tags;
562
563 it = JsonbIteratorInit(&jb->root);
564
565 while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
566 {
567 skipNested = true;
568
569 if (r == WJB_KEY && v.type != jbvNull)
570 {
571
572 k = get_key_index_with_size(ctx, v.val.string.val, v.val.string.len);
573 if (k == UINT32_MAX)
574 {
575 char *key;
576 uint32_t newSize = ctx->keys_hash_i + 1;
577
578 key = palloc(v.val.string.len + 1);
579 memcpy(key, v.val.string.val, v.val.string.len);
580 key[v.val.string.len] = '\0';
581
582 tags = repalloc(tags, newSize * 2 * sizeof(*tags));
583 k = add_key(ctx, key);
584 }
585
586 r = JsonbIteratorNext(&it, &v, skipNested);
587
588 if (v.type == jbvString)
589 {
590 char *value;
591 value = palloc(v.val.string.len + 1);
592 memcpy(value, v.val.string.val, v.val.string.len);
593 value[v.val.string.len] = '\0';
594 add_value_as_string(ctx, value, tags, k);
595 ctx->row_columns++;
596 }
597 else if (v.type == jbvBool)
598 {
599 MVT_PARSE_VALUE(v.val.boolean, mvt_kv_bool_value,
600 bool_values_hash, bool_value, sizeof(protobuf_c_boolean));
601 ctx->row_columns++;
602 }
603 else if (v.type == jbvNumeric)
604 {
605 char *str;
606 double d;
607 long l;
608 str = DatumGetCString(DirectFunctionCall1(numeric_out,
609 PointerGetDatum(v.val.numeric)));
610 d = strtod(str, NULL);
611 l = strtol(str, NULL, 10);
612 if (FP_NEQUALS(d, (double)l))
613 {
614 MVT_PARSE_VALUE(d, mvt_kv_double_value, double_values_hash,
615 double_value, sizeof(double));
616 }
617 else
618 {
619 MVT_PARSE_INT_VALUE(l);
620 }
621 ctx->row_columns++;
622 }
623 }
624 }
625
626 return tags;
627 }
628 #endif
629
parse_values(mvt_agg_context * ctx)630 static void parse_values(mvt_agg_context *ctx)
631 {
632 uint32_t n_keys = ctx->keys_hash_i;
633 uint32_t *tags = palloc(n_keys * 2 * sizeof(*tags));
634 uint32_t i;
635 mvt_column_cache cc = ctx->column_cache;
636 uint32_t natts = (uint32_t) cc.tupdesc->natts;
637
638 HeapTupleData tuple;
639
640 POSTGIS_DEBUG(2, "parse_values called");
641 ctx->row_columns = 0;
642
643 /* Build a temporary HeapTuple control structure */
644 tuple.t_len = HeapTupleHeaderGetDatumLength(ctx->row);
645 ItemPointerSetInvalid(&(tuple.t_self));
646 tuple.t_tableOid = InvalidOid;
647 tuple.t_data = ctx->row;
648
649 /* We use heap_deform_tuple as it costs only O(N) vs O(N^2) of GetAttributeByNum */
650 heap_deform_tuple(&tuple, cc.tupdesc, cc.values, cc.nulls);
651
652 POSTGIS_DEBUGF(3, "parse_values natts: %d", natts);
653
654 for (i = 0; i < natts; i++)
655 {
656 char *key;
657 Oid typoid;
658 uint32_t k;
659 Datum datum = cc.values[i];
660
661 if (i == ctx->geom_index)
662 continue;
663
664 if (cc.nulls[i])
665 {
666 POSTGIS_DEBUG(3, "parse_values isnull detected");
667 continue;
668 }
669
670 #if POSTGIS_PGSQL_VERSION < 110
671 key = cc.tupdesc->attrs[i]->attname.data;
672 #else
673 key = cc.tupdesc->attrs[i].attname.data;
674 #endif
675 k = cc.column_keys_index[i];
676 typoid = cc.column_oid[i];
677
678 #if POSTGIS_PGSQL_VERSION >= 94
679 if (k == UINT32_MAX && typoid != JSONBOID)
680 elog(ERROR, "parse_values: unexpectedly could not find parsed key name '%s'", key);
681 if (typoid == JSONBOID)
682 {
683 tags = parse_jsonb(ctx, DatumGetJsonbP(datum), tags);
684 continue;
685 }
686 #else
687 if (k == UINT32_MAX)
688 elog(ERROR, "parse_values: unexpectedly could not find parsed key name '%s'", key);
689 #endif
690
691 switch (typoid)
692 {
693 case BOOLOID:
694 MVT_PARSE_DATUM(protobuf_c_boolean, mvt_kv_bool_value,
695 bool_values_hash, bool_value,
696 DatumGetBool, sizeof(protobuf_c_boolean));
697 break;
698 case INT2OID:
699 MVT_PARSE_INT_DATUM(int16_t, DatumGetInt16);
700 break;
701 case INT4OID:
702 MVT_PARSE_INT_DATUM(int32_t, DatumGetInt32);
703 break;
704 case INT8OID:
705 MVT_PARSE_INT_DATUM(int64_t, DatumGetInt64);
706 break;
707 case FLOAT4OID:
708 MVT_PARSE_DATUM(float, mvt_kv_float_value,
709 float_values_hash, float_value,
710 DatumGetFloat4, sizeof(float));
711 break;
712 case FLOAT8OID:
713 MVT_PARSE_DATUM(double, mvt_kv_double_value,
714 double_values_hash, double_value,
715 DatumGetFloat8, sizeof(double));
716 break;
717 default:
718 parse_datum_as_string(ctx, typoid, datum, tags, k);
719 break;
720 }
721 ctx->row_columns++;
722 }
723
724
725 ctx->feature->n_tags = ctx->row_columns * 2;
726 ctx->feature->tags = tags;
727
728 POSTGIS_DEBUGF(3, "parse_values n_tags %zd", ctx->feature->n_tags);
729 }
730
731 /* For a given geometry, look for the highest dimensional basic type, that is,
732 * point, line or polygon */
733 static uint8
lwgeom_get_basic_type(LWGEOM * geom)734 lwgeom_get_basic_type(LWGEOM *geom)
735 {
736 switch(geom->type)
737 {
738 case POINTTYPE:
739 case LINETYPE:
740 case POLYGONTYPE:
741 return geom->type;
742 case MULTIPOINTTYPE:
743 case MULTILINETYPE:
744 case MULTIPOLYGONTYPE:
745 return geom->type - 3; /* Based on LWTYPE positions */
746 case COLLECTIONTYPE:
747 {
748 uint32_t i;
749 uint8 type = 0;
750 LWCOLLECTION *g = (LWCOLLECTION*)geom;
751 for (i = 0; i < g->ngeoms; i++)
752 {
753 LWGEOM *sg = g->geoms[i];
754 type = Max(type, lwgeom_get_basic_type(sg));
755 }
756 return type;
757 }
758 default:
759 elog(ERROR, "%s: Invalid type (%d)", __func__, geom->type);
760 }
761 }
762
763
764 /**
765 * In place process a collection to find a concrete geometry
766 * object and expose that as the actual object. Will some
767 * geom be lost? Sure, but your MVT renderer couldn't
768 * draw it anyways.
769 */
770 static inline LWGEOM *
lwgeom_to_basic_type(LWGEOM * geom,uint8 original_type)771 lwgeom_to_basic_type(LWGEOM *geom, uint8 original_type)
772 {
773 LWGEOM *geom_out = geom;
774 if (lwgeom_get_type(geom) == COLLECTIONTYPE)
775 {
776 LWCOLLECTION *g = (LWCOLLECTION*)geom;
777 geom_out = (LWGEOM *)lwcollection_extract(g, original_type);
778 }
779
780 /* If a collection only contains 1 geometry return than instead */
781 if (lwgeom_is_collection(geom_out))
782 {
783 LWCOLLECTION *g = (LWCOLLECTION *)geom_out;
784 if (g->ngeoms == 1)
785 {
786 geom_out = g->geoms[0];
787 }
788 }
789
790 geom_out->srid = geom->srid;
791 return geom_out;
792 }
793
794 /* Clips a geometry using lwgeom_clip_by_rect. Might return NULL */
795 static LWGEOM *
mvt_unsafe_clip_by_box(LWGEOM * lwg_in,GBOX * clip_box)796 mvt_unsafe_clip_by_box(LWGEOM *lwg_in, GBOX *clip_box)
797 {
798 LWGEOM *geom_clipped;
799 GBOX geom_box;
800
801 gbox_init(&geom_box);
802 FLAGS_SET_GEODETIC(geom_box.flags, 0);
803 lwgeom_calculate_gbox(lwg_in, &geom_box);
804
805 if (!gbox_overlaps_2d(&geom_box, clip_box))
806 {
807 POSTGIS_DEBUG(3, "mvt_geom: geometry outside clip box");
808 return NULL;
809 }
810
811 if (gbox_contains_2d(clip_box, &geom_box))
812 {
813 POSTGIS_DEBUG(3, "mvt_geom: geometry contained fully inside the box");
814 return lwg_in;
815 }
816
817 geom_clipped = lwgeom_clip_by_rect(lwg_in, clip_box->xmin, clip_box->ymin, clip_box->xmax, clip_box->ymax);
818 if (!geom_clipped || lwgeom_is_empty(geom_clipped))
819 return NULL;
820 return geom_clipped;
821 }
822
823 /**
824 * Clips an input geometry using GEOSIntersection
825 * It used to try to use GEOSClipByRect (as mvt_unsafe_clip_by_box) but since that produces
826 * invalid output when an invalid geometry is given and detecting it resulted to be impossible,
827 * we use intersection instead and, upon error, force validation of the input and retry.
828 * Might return NULL
829 */
830 static LWGEOM *
mvt_safe_clip_polygon_by_box(LWGEOM * lwg_in,GBOX * clip_box)831 mvt_safe_clip_polygon_by_box(LWGEOM *lwg_in, GBOX *clip_box)
832 {
833 LWGEOM *geom_clipped, *envelope;
834 GBOX geom_box;
835 GEOSGeometry *geos_input, *geos_box, *geos_result;
836
837 gbox_init(&geom_box);
838 FLAGS_SET_GEODETIC(geom_box.flags, 0);
839 lwgeom_calculate_gbox(lwg_in, &geom_box);
840
841 if (!gbox_overlaps_2d(&geom_box, clip_box))
842 {
843 POSTGIS_DEBUG(3, "mvt_geom: geometry outside clip box");
844 return NULL;
845 }
846
847 if (gbox_contains_2d(clip_box, &geom_box))
848 {
849 POSTGIS_DEBUG(3, "mvt_geom: geometry contained fully inside the box");
850 return lwg_in;
851 }
852
853 initGEOS(lwnotice, lwgeom_geos_error);
854 if (!(geos_input = LWGEOM2GEOS(lwg_in, 1)))
855 return NULL;
856
857 envelope = (LWGEOM *)lwpoly_construct_envelope(
858 lwg_in->srid, clip_box->xmin, clip_box->ymin, clip_box->xmax, clip_box->ymax);
859 geos_box = LWGEOM2GEOS(envelope, 1);
860 lwgeom_free(envelope);
861 if (!geos_box)
862 {
863 GEOSGeom_destroy(geos_input);
864 return NULL;
865 }
866
867 geos_result = GEOSIntersection(geos_input, geos_box);
868 if (!geos_result)
869 {
870 POSTGIS_DEBUG(3, "mvt_geom: no geometry after intersection. Retrying after validation");
871 GEOSGeom_destroy(geos_input);
872 lwg_in = lwgeom_make_valid(lwg_in);
873 if (!(geos_input = LWGEOM2GEOS(lwg_in, 1)))
874 {
875 GEOSGeom_destroy(geos_box);
876 return NULL;
877 }
878 geos_result = GEOSIntersection(geos_input, geos_box);
879 if (!geos_result)
880 {
881 GEOSGeom_destroy(geos_box);
882 GEOSGeom_destroy(geos_input);
883 return NULL;
884 }
885 }
886
887 GEOSSetSRID(geos_result, lwg_in->srid);
888 geom_clipped = GEOS2LWGEOM(geos_result, 0);
889
890 GEOSGeom_destroy(geos_box);
891 GEOSGeom_destroy(geos_input);
892 GEOSGeom_destroy(geos_result);
893
894 if (!geom_clipped || lwgeom_is_empty(geom_clipped))
895 {
896 POSTGIS_DEBUG(3, "mvt_geom: no geometry after clipping");
897 return NULL;
898 }
899
900 return geom_clipped;
901 }
902
903 /**
904 * Clips the geometry using GEOSIntersection in a "safe way", cleaning the input
905 * if necessary and clipping MULTIPOLYGONs separately to reduce the impact
906 * of using invalid input in GEOS
907 * Might return NULL
908 */
909 static LWGEOM *
mvt_iterate_clip_by_box_geos(LWGEOM * lwgeom,GBOX * clip_gbox,uint8_t basic_type)910 mvt_iterate_clip_by_box_geos(LWGEOM *lwgeom, GBOX *clip_gbox, uint8_t basic_type)
911 {
912 if (basic_type != POLYGONTYPE)
913 {
914 return mvt_unsafe_clip_by_box(lwgeom, clip_gbox);
915 }
916
917 if (lwgeom->type != MULTIPOLYGONTYPE || ((LWMPOLY *)lwgeom)->ngeoms == 1)
918 {
919 return mvt_safe_clip_polygon_by_box(lwgeom, clip_gbox);
920 }
921 else
922 {
923 GBOX geom_box;
924 uint32_t i;
925 LWCOLLECTION *lwmg;
926 LWCOLLECTION *res;
927
928 gbox_init(&geom_box);
929 FLAGS_SET_GEODETIC(geom_box.flags, 0);
930 lwgeom_calculate_gbox(lwgeom, &geom_box);
931
932 lwmg = ((LWCOLLECTION *)lwgeom);
933 res = lwcollection_construct_empty(
934 MULTIPOLYGONTYPE, lwgeom->srid, FLAGS_GET_Z(lwgeom->flags), FLAGS_GET_M(lwgeom->flags));
935 for (i = 0; i < lwmg->ngeoms; i++)
936 {
937 LWGEOM *clipped = mvt_safe_clip_polygon_by_box(lwcollection_getsubgeom(lwmg, i), clip_gbox);
938 if (clipped)
939 {
940 clipped = lwgeom_to_basic_type(clipped, POLYGONTYPE);
941 if (!lwgeom_is_empty(clipped) &&
942 (clipped->type == POLYGONTYPE || clipped->type == MULTIPOLYGONTYPE))
943 {
944 if (!lwgeom_is_collection(clipped))
945 {
946 lwcollection_add_lwgeom(res, clipped);
947 }
948 else
949 {
950 uint32_t j;
951 for (j = 0; j < ((LWCOLLECTION *)clipped)->ngeoms; j++)
952 lwcollection_add_lwgeom(
953 res, lwcollection_getsubgeom((LWCOLLECTION *)clipped, j));
954 }
955 }
956 }
957 }
958 return lwcollection_as_lwgeom(res);
959 }
960 }
961
962 /**
963 * Given a geometry, it uses GEOS operations to make sure that it's valid according
964 * to the MVT spec and that all points are snapped into int coordinates
965 * It iterates several times if needed, if it fails, returns NULL
966 */
967 static LWGEOM *
mvt_grid_and_validate_geos(LWGEOM * ng,uint8_t basic_type)968 mvt_grid_and_validate_geos(LWGEOM *ng, uint8_t basic_type)
969 {
970 gridspec grid = {0, 0, 0, 0, 1, 1, 0, 0};
971 ng = lwgeom_to_basic_type(ng, basic_type);
972
973 if (basic_type != POLYGONTYPE)
974 {
975 /* Make sure there is no pending float values (clipping can do that) */
976 lwgeom_grid_in_place(ng, &grid);
977 }
978 else
979 {
980 /* For polygons we have to both snap to the integer grid and force validation.
981 * The problem with this procedure is that snapping to the grid can create
982 * an invalid geometry and making it valid can create float values; so
983 * we iterate several times (up to 3) to generate a valid geom with int coordinates
984 */
985 GEOSGeometry *geo;
986 uint32_t iterations = 0;
987 static const uint32_t max_iterations = 3;
988 bool valid = false;
989
990 /* Grid to int */
991 lwgeom_grid_in_place(ng, &grid);
992
993 initGEOS(lwgeom_geos_error, lwgeom_geos_error);
994 geo = LWGEOM2GEOS(ng, 0);
995 if (!geo)
996 return NULL;
997 valid = GEOSisValid(geo) == 1;
998
999 while (!valid && iterations < max_iterations)
1000 {
1001 GEOSGeometry *geo_valid = LWGEOM_GEOS_makeValid(geo);
1002 GEOSGeom_destroy(geo);
1003 if (!geo_valid)
1004 return NULL;
1005
1006 ng = GEOS2LWGEOM(geo_valid, 0);
1007 GEOSGeom_destroy(geo_valid);
1008 if (!ng)
1009 return NULL;
1010
1011 lwgeom_grid_in_place(ng, &grid);
1012 ng = lwgeom_to_basic_type(ng, basic_type);
1013 geo = LWGEOM2GEOS(ng, 0);
1014 valid = GEOSisValid(geo) == 1;
1015 iterations++;
1016 }
1017 GEOSGeom_destroy(geo);
1018
1019 if (!valid)
1020 {
1021 POSTGIS_DEBUG(1, "mvt_geom: Could not transform into a valid MVT geometry");
1022 return NULL;
1023 }
1024
1025 /* In image coordinates CW actually comes out a CCW, so we reverse */
1026 lwgeom_force_clockwise(ng);
1027 lwgeom_reverse_in_place(ng);
1028 }
1029 return ng;
1030 }
1031
1032 /* Clips and validates a geometry for MVT using GEOS
1033 * Might return NULL
1034 */
1035 static LWGEOM *
mvt_clip_and_validate_geos(LWGEOM * lwgeom,uint8_t basic_type,uint32_t extent,uint32_t buffer,bool clip_geom)1036 mvt_clip_and_validate_geos(LWGEOM *lwgeom, uint8_t basic_type, uint32_t extent, uint32_t buffer, bool clip_geom)
1037 {
1038 LWGEOM *ng = lwgeom;
1039
1040 if (clip_geom)
1041 {
1042 GBOX bgbox;
1043 gbox_init(&bgbox);
1044 bgbox.xmax = bgbox.ymax = (double)extent + (double)buffer;
1045 bgbox.xmin = bgbox.ymin = -(double)buffer;
1046 FLAGS_SET_GEODETIC(bgbox.flags, 0);
1047
1048 ng = mvt_iterate_clip_by_box_geos(lwgeom, &bgbox, basic_type);
1049 if (!ng || lwgeom_is_empty(ng))
1050 {
1051 POSTGIS_DEBUG(3, "mvt_geom: no geometry after clip");
1052 return NULL;
1053 }
1054 }
1055
1056 ng = mvt_grid_and_validate_geos(ng, basic_type);
1057
1058 /* Make sure we return the expected type */
1059 if (!ng || basic_type != lwgeom_get_basic_type(ng))
1060 {
1061 /* Drop type changes to play nice with MVT renderers */
1062 POSTGIS_DEBUG(1, "mvt_geom: Dropping geometry after type change");
1063 return NULL;
1064 }
1065
1066 return ng;
1067 }
1068
1069 /**
1070 * Transform a geometry into vector tile coordinate space.
1071 *
1072 * Makes best effort to keep validity. Might collapse geometry into lower
1073 * dimension.
1074 *
1075 * NOTE: modifies in place if possible (not currently possible for polygons)
1076 */
mvt_geom(LWGEOM * lwgeom,const GBOX * gbox,uint32_t extent,uint32_t buffer,bool clip_geom)1077 LWGEOM *mvt_geom(LWGEOM *lwgeom, const GBOX *gbox, uint32_t extent, uint32_t buffer,
1078 bool clip_geom)
1079 {
1080 AFFINE affine = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
1081 gridspec grid = {0, 0, 0, 0, 1, 1, 0, 0};
1082 double width = gbox->xmax - gbox->xmin;
1083 double height = gbox->ymax - gbox->ymin;
1084 double resx, resy, res, fx, fy;
1085 const uint8_t basic_type = lwgeom_get_basic_type(lwgeom);
1086 int preserve_collapsed = LW_FALSE;
1087 POSTGIS_DEBUG(2, "mvt_geom called");
1088
1089 /* Simplify it as soon as possible */
1090 lwgeom = lwgeom_to_basic_type(lwgeom, basic_type);
1091
1092 /* Short circuit out on EMPTY */
1093 if (lwgeom_is_empty(lwgeom))
1094 return NULL;
1095
1096 if (width == 0 || height == 0)
1097 elog(ERROR, "mvt_geom: bounds width or height cannot be 0");
1098
1099 if (extent == 0)
1100 elog(ERROR, "mvt_geom: extent cannot be 0");
1101
1102 resx = width / extent;
1103 resy = height / extent;
1104 res = (resx < resy ? resx : resy)/2;
1105 fx = extent / width;
1106 fy = -(extent / height);
1107
1108 /* Remove all non-essential points (under the output resolution) */
1109 lwgeom_remove_repeated_points_in_place(lwgeom, res);
1110 lwgeom_simplify_in_place(lwgeom, res, preserve_collapsed);
1111
1112 /* If geometry has disappeared, you're done */
1113 if (lwgeom_is_empty(lwgeom))
1114 return NULL;
1115
1116 /* transform to tile coordinate space */
1117 affine.afac = fx;
1118 affine.efac = fy;
1119 affine.ifac = 1;
1120 affine.xoff = -gbox->xmin * fx;
1121 affine.yoff = -gbox->ymax * fy;
1122 lwgeom_affine(lwgeom, &affine);
1123
1124 /* snap to integer precision, removing duplicate points */
1125 lwgeom_grid_in_place(lwgeom, &grid);
1126
1127 if (lwgeom == NULL || lwgeom_is_empty(lwgeom))
1128 return NULL;
1129
1130 lwgeom = mvt_clip_and_validate_geos(lwgeom, basic_type, extent, buffer, clip_geom);
1131 if (lwgeom == NULL || lwgeom_is_empty(lwgeom))
1132 return NULL;
1133
1134 return lwgeom;
1135 }
1136
1137 /**
1138 * Initialize aggregation context.
1139 */
mvt_agg_init_context(mvt_agg_context * ctx)1140 void mvt_agg_init_context(mvt_agg_context *ctx)
1141 {
1142 VectorTile__Tile__Layer *layer;
1143
1144 POSTGIS_DEBUG(2, "mvt_agg_init_context called");
1145
1146 if (ctx->extent == 0)
1147 elog(ERROR, "mvt_agg_init_context: extent cannot be 0");
1148
1149 ctx->tile = NULL;
1150 ctx->features_capacity = FEATURES_CAPACITY_INITIAL;
1151 ctx->keys_hash = NULL;
1152 ctx->string_values_hash = NULL;
1153 ctx->float_values_hash = NULL;
1154 ctx->double_values_hash = NULL;
1155 ctx->uint_values_hash = NULL;
1156 ctx->sint_values_hash = NULL;
1157 ctx->bool_values_hash = NULL;
1158 ctx->values_hash_i = 0;
1159 ctx->keys_hash_i = 0;
1160 ctx->geom_index = UINT32_MAX;
1161
1162 memset(&ctx->column_cache, 0, sizeof(ctx->column_cache));
1163
1164 layer = palloc(sizeof(*layer));
1165 vector_tile__tile__layer__init(layer);
1166 layer->version = 2;
1167 layer->name = ctx->name;
1168 layer->has_extent = 1;
1169 layer->extent = ctx->extent;
1170 layer->features = palloc (ctx->features_capacity *
1171 sizeof(*layer->features));
1172
1173 ctx->layer = layer;
1174 }
1175
1176 /**
1177 * Aggregation step.
1178 *
1179 * Expands features array if needed by a factor of 2.
1180 * Allocates a new feature, increment feature counter and
1181 * encode geometry and properties into it.
1182 */
mvt_agg_transfn(mvt_agg_context * ctx)1183 void mvt_agg_transfn(mvt_agg_context *ctx)
1184 {
1185 bool isnull = false;
1186 Datum datum;
1187 GSERIALIZED *gs;
1188 LWGEOM *lwgeom;
1189 VectorTile__Tile__Feature *feature;
1190 VectorTile__Tile__Layer *layer = ctx->layer;
1191 /* VectorTile__Tile__Feature **features = layer->features; */
1192 POSTGIS_DEBUG(2, "mvt_agg_transfn called");
1193
1194 if (layer->n_features >= ctx->features_capacity)
1195 {
1196 size_t new_capacity = ctx->features_capacity * 2;
1197 layer->features = repalloc(layer->features, new_capacity *
1198 sizeof(*layer->features));
1199 ctx->features_capacity = new_capacity;
1200 POSTGIS_DEBUGF(3, "mvt_agg_transfn new_capacity: %zd", new_capacity);
1201 }
1202
1203 if (ctx->geom_index == UINT32_MAX)
1204 parse_column_keys(ctx);
1205
1206 datum = GetAttributeByNum(ctx->row, ctx->geom_index + 1, &isnull);
1207 POSTGIS_DEBUGF(3, "mvt_agg_transfn ctx->geom_index: %d", ctx->geom_index);
1208 POSTGIS_DEBUGF(3, "mvt_agg_transfn isnull: %u", isnull);
1209 POSTGIS_DEBUGF(3, "mvt_agg_transfn datum: %lu", datum);
1210 if (isnull) /* Skip rows that have null geometry */
1211 {
1212 POSTGIS_DEBUG(3, "mvt_agg_transfn got null geom");
1213 return;
1214 }
1215
1216 feature = palloc(sizeof(*feature));
1217 vector_tile__tile__feature__init(feature);
1218
1219 ctx->feature = feature;
1220
1221 gs = (GSERIALIZED *) PG_DETOAST_DATUM(datum);
1222 lwgeom = lwgeom_from_gserialized(gs);
1223
1224 POSTGIS_DEBUGF(3, "mvt_agg_transfn encoded feature count: %zd", layer->n_features);
1225 layer->features[layer->n_features++] = feature;
1226
1227 encode_geometry(ctx, lwgeom);
1228 lwgeom_free(lwgeom);
1229 // TODO: free detoasted datum?
1230 parse_values(ctx);
1231 }
1232
mvt_ctx_to_tile(mvt_agg_context * ctx)1233 static VectorTile__Tile * mvt_ctx_to_tile(mvt_agg_context *ctx)
1234 {
1235 int n_layers = 1;
1236 VectorTile__Tile *tile;
1237 encode_keys(ctx);
1238 encode_values(ctx);
1239
1240 tile = palloc(sizeof(VectorTile__Tile));
1241 vector_tile__tile__init(tile);
1242 tile->layers = palloc(sizeof(VectorTile__Tile__Layer*) * n_layers);
1243 tile->layers[0] = ctx->layer;
1244 tile->n_layers = n_layers;
1245 return tile;
1246 }
1247
mvt_ctx_to_bytea(mvt_agg_context * ctx)1248 static bytea *mvt_ctx_to_bytea(mvt_agg_context *ctx)
1249 {
1250 /* Fill out the file slot, if it's not already filled. */
1251 /* We should only have a filled slow when all the work of building */
1252 /* out the data is complete, so after a serialize/deserialize cycle */
1253 /* or after a context combine */
1254 size_t len;
1255 bytea *ba;
1256
1257 if (!ctx->tile)
1258 {
1259 ctx->tile = mvt_ctx_to_tile(ctx);
1260 }
1261
1262 /* Zero features => empty bytea output */
1263 if (ctx && ctx->layer && ctx->layer->n_features == 0)
1264 {
1265 bytea *ba = palloc(VARHDRSZ);
1266 SET_VARSIZE(ba, VARHDRSZ);
1267 return ba;
1268 }
1269
1270 /* Serialize the Tile */
1271 len = VARHDRSZ + vector_tile__tile__get_packed_size(ctx->tile);
1272 ba = palloc(len);
1273 vector_tile__tile__pack(ctx->tile, (uint8_t*)VARDATA(ba));
1274 SET_VARSIZE(ba, len);
1275 return ba;
1276 }
1277
1278
mvt_ctx_serialize(mvt_agg_context * ctx)1279 bytea * mvt_ctx_serialize(mvt_agg_context *ctx)
1280 {
1281 return mvt_ctx_to_bytea(ctx);
1282 }
1283
mvt_allocator(void * data,size_t size)1284 static void * mvt_allocator(__attribute__((__unused__)) void *data, size_t size)
1285 {
1286 return palloc(size);
1287 }
1288
mvt_deallocator(void * data,void * ptr)1289 static void mvt_deallocator(__attribute__((__unused__)) void *data, void *ptr)
1290 {
1291 return pfree(ptr);
1292 }
1293
mvt_ctx_deserialize(const bytea * ba)1294 mvt_agg_context * mvt_ctx_deserialize(const bytea *ba)
1295 {
1296 ProtobufCAllocator allocator =
1297 {
1298 mvt_allocator,
1299 mvt_deallocator,
1300 NULL
1301 };
1302
1303 size_t len = VARSIZE(ba) - VARHDRSZ;
1304 VectorTile__Tile *tile = vector_tile__tile__unpack(&allocator, len, (uint8_t*)VARDATA(ba));
1305 mvt_agg_context *ctx = palloc(sizeof(mvt_agg_context));
1306 memset(ctx, 0, sizeof(mvt_agg_context));
1307 ctx->tile = tile;
1308 return ctx;
1309 }
1310
1311 static VectorTile__Tile__Value *
tile_value_copy(const VectorTile__Tile__Value * value)1312 tile_value_copy(const VectorTile__Tile__Value *value)
1313 {
1314 VectorTile__Tile__Value *nvalue = palloc(sizeof(VectorTile__Tile__Value));
1315 memcpy(nvalue, value, sizeof(VectorTile__Tile__Value));
1316 if (value->string_value)
1317 nvalue->string_value = pstrdup(value->string_value);
1318 return nvalue;
1319 }
1320
1321 static VectorTile__Tile__Feature *
tile_feature_copy(const VectorTile__Tile__Feature * feature,int key_offset,int value_offset)1322 tile_feature_copy(const VectorTile__Tile__Feature *feature, int key_offset, int value_offset)
1323 {
1324 uint32_t i;
1325 VectorTile__Tile__Feature *nfeature;
1326
1327 /* Null in => Null out */
1328 if (!feature) return NULL;
1329
1330 /* Init object */
1331 nfeature = palloc(sizeof(VectorTile__Tile__Feature));
1332 vector_tile__tile__feature__init(nfeature);
1333
1334 /* Copy settings straight over */
1335 nfeature->has_id = feature->has_id;
1336 nfeature->id = feature->id;
1337 nfeature->has_type = feature->has_type;
1338 nfeature->type = feature->type;
1339
1340 /* Copy tags over, offsetting indexes so they match the dictionaries */
1341 /* at the Tile_Layer level */
1342 if (feature->n_tags > 0)
1343 {
1344 nfeature->n_tags = feature->n_tags;
1345 nfeature->tags = palloc(sizeof(uint32_t)*feature->n_tags);
1346 for (i = 0; i < feature->n_tags/2; i++)
1347 {
1348 nfeature->tags[2*i] = feature->tags[2*i] + key_offset;
1349 nfeature->tags[2*i+1] = feature->tags[2*i+1] + value_offset;
1350 }
1351 }
1352
1353 /* Copy the raw geometry data over literally */
1354 if (feature->n_geometry > 0)
1355 {
1356 nfeature->n_geometry = feature->n_geometry;
1357 nfeature->geometry = palloc(sizeof(uint32_t)*feature->n_geometry);
1358 memcpy(nfeature->geometry, feature->geometry, sizeof(uint32_t)*feature->n_geometry);
1359 }
1360
1361 /* Done */
1362 return nfeature;
1363 }
1364
1365 static VectorTile__Tile__Layer *
vectortile_layer_combine(const VectorTile__Tile__Layer * layer1,const VectorTile__Tile__Layer * layer2)1366 vectortile_layer_combine(const VectorTile__Tile__Layer *layer1, const VectorTile__Tile__Layer *layer2)
1367 {
1368 uint32_t i, j;
1369 int key2_offset, value2_offset;
1370 VectorTile__Tile__Layer *layer = palloc(sizeof(VectorTile__Tile__Layer));
1371 vector_tile__tile__layer__init(layer);
1372
1373 /* Take globals from layer1 */
1374 layer->version = layer1->version;
1375 layer->name = pstrdup(layer1->name);
1376 layer->has_extent = layer1->has_extent;
1377 layer->extent = layer1->extent;
1378
1379 /* Copy keys into new layer */
1380 j = 0;
1381 layer->n_keys = layer1->n_keys + layer2->n_keys;
1382 layer->keys = layer->n_keys ? palloc(layer->n_keys * sizeof(void*)) : NULL;
1383 for (i = 0; i < layer1->n_keys; i++)
1384 layer->keys[j++] = pstrdup(layer1->keys[i]);
1385 key2_offset = j;
1386 for (i = 0; i < layer2->n_keys; i++)
1387 layer->keys[j++] = pstrdup(layer2->keys[i]);
1388
1389 /* Copy values into new layer */
1390 /* TODO, apply hash logic here too, so that merged tiles */
1391 /* retain unique value maps */
1392 layer->n_values = layer1->n_values + layer2->n_values;
1393 layer->values = layer->n_values ? palloc(layer->n_values * sizeof(void*)) : NULL;
1394 j = 0;
1395 for (i = 0; i < layer1->n_values; i++)
1396 layer->values[j++] = tile_value_copy(layer1->values[i]);
1397 value2_offset = j;
1398 for (i = 0; i < layer2->n_values; i++)
1399 layer->values[j++] = tile_value_copy(layer2->values[i]);
1400
1401
1402 layer->n_features = layer1->n_features + layer2->n_features;
1403 layer->features = layer->n_features ? palloc(layer->n_features * sizeof(void*)) : NULL;
1404 j = 0;
1405 for (i = 0; i < layer1->n_features; i++)
1406 layer->features[j++] = tile_feature_copy(layer1->features[i], 0, 0);
1407 for (i = 0; i < layer2->n_features; i++)
1408 layer->features[j++] = tile_feature_copy(layer2->features[i], key2_offset, value2_offset);
1409
1410 return layer;
1411 }
1412
1413
1414 static VectorTile__Tile *
vectortile_tile_combine(VectorTile__Tile * tile1,VectorTile__Tile * tile2)1415 vectortile_tile_combine(VectorTile__Tile *tile1, VectorTile__Tile *tile2)
1416 {
1417 uint32_t i, j;
1418 VectorTile__Tile *tile;
1419
1420 /* Hopelessly messing up memory ownership here */
1421 if (tile1->n_layers == 0 && tile2->n_layers == 0)
1422 return tile1;
1423 else if (tile1->n_layers == 0)
1424 return tile2;
1425 else if (tile2->n_layers == 0)
1426 return tile1;
1427
1428 tile = palloc(sizeof(VectorTile__Tile));
1429 vector_tile__tile__init(tile);
1430 tile->layers = palloc(sizeof(void*));
1431 tile->n_layers = 0;
1432
1433 /* Merge all matching layers in the files (basically always only one) */
1434 for (i = 0; i < tile1->n_layers; i++)
1435 {
1436 for (j = 0; j < tile2->n_layers; j++)
1437 {
1438 VectorTile__Tile__Layer *l1 = tile1->layers[i];
1439 VectorTile__Tile__Layer *l2 = tile2->layers[j];
1440 if (strcmp(l1->name, l2->name)==0)
1441 {
1442 VectorTile__Tile__Layer *layer = vectortile_layer_combine(l1, l2);
1443 if (!layer)
1444 continue;
1445 tile->layers[tile->n_layers++] = layer;
1446 /* Add a spare slot at the end of the array */
1447 tile->layers = repalloc(tile->layers, (tile->n_layers+1) * sizeof(void*));
1448 }
1449 }
1450 }
1451 return tile;
1452 }
1453
mvt_ctx_combine(mvt_agg_context * ctx1,mvt_agg_context * ctx2)1454 mvt_agg_context * mvt_ctx_combine(mvt_agg_context *ctx1, mvt_agg_context *ctx2)
1455 {
1456 if (ctx1 || ctx2)
1457 {
1458 if (ctx1 && ! ctx2) return ctx1;
1459 if (ctx2 && ! ctx1) return ctx2;
1460 if (ctx1 && ctx2 && ctx1->tile && ctx2->tile)
1461 {
1462 mvt_agg_context *ctxnew = palloc(sizeof(mvt_agg_context));
1463 memset(ctxnew, 0, sizeof(mvt_agg_context));
1464 ctxnew->tile = vectortile_tile_combine(ctx1->tile, ctx2->tile);
1465 return ctxnew;
1466 }
1467 else
1468 {
1469 elog(DEBUG2, "ctx1->tile = %p", ctx1->tile);
1470 elog(DEBUG2, "ctx2->tile = %p", ctx2->tile);
1471 elog(ERROR, "%s: unable to combine contexts where tile attribute is null", __func__);
1472 return NULL;
1473 }
1474 }
1475 else
1476 {
1477 return NULL;
1478 }
1479 }
1480
1481 /**
1482 * Finalize aggregation.
1483 *
1484 * Encode keys and values and put the aggregated Layer message into
1485 * a Tile message and returns it packed as a bytea.
1486 */
mvt_agg_finalfn(mvt_agg_context * ctx)1487 bytea *mvt_agg_finalfn(mvt_agg_context *ctx)
1488 {
1489 return mvt_ctx_to_bytea(ctx);
1490 }
1491
1492
1493 #endif
1494