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