PostGIS  2.4.9dev-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 "mvt.h"
26 
27 #ifdef HAVE_LIBPROTOBUF
28 
29 #if POSTGIS_PGSQL_VERSION >= 94
30 #include "utils/jsonb.h"
31 #endif
32 
33 #if POSTGIS_PGSQL_VERSION < 110
34 /* See trac ticket #3867 */
35 # define DatumGetJsonbP DatumGetJsonb
36 #endif
37 
38 #include "uthash.h"
39 
40 #define FEATURES_CAPACITY_INITIAL 50
41 
42 enum mvt_cmd_id {
46 };
47 
48 enum mvt_type {
49  MVT_POINT = 1,
50  MVT_LINE = 2,
52 };
53 
54 struct mvt_kv_key {
55  char *name;
58 };
59 
61  char *string_value;
64 };
65 
67  float float_value;
70 };
71 
73  double double_value;
76 };
77 
79  uint64_t uint_value;
82 };
83 
85  int64_t sint_value;
88 };
89 
91  protobuf_c_boolean bool_value;
94 };
95 
96 static inline uint32_t c_int(enum mvt_cmd_id id, uint32_t count)
97 {
98  return (id & 0x7) | (count << 3);
99 }
100 
101 static inline uint32_t p_int(int32_t value)
102 {
103  return (value << 1) ^ (value >> 31);
104 }
105 
107  POINTARRAY *pa, uint32_t *buffer,
108  int32_t *px, int32_t *py)
109 {
110  uint32_t offset = 0;
111  uint32_t i, c = 0;
112  int32_t dx, dy, x, y;
113 
114  /* loop points and add to buffer */
115  for (i = 0; i < pa->npoints; i++) {
116  /* move offset for command */
117  if (i == 0 || (i == 1 && type > MVT_POINT))
118  offset++;
119  /* skip closing point for rings */
120  if (type == MVT_RING && i == pa->npoints - 1)
121  break;
122  const POINT2D *p = getPoint2d_cp(pa, i);
123  x = p->x;
124  y = p->y;
125  dx = x - *px;
126  dy = y - *py;
127  buffer[offset++] = p_int(dx);
128  buffer[offset++] = p_int(dy);
129  *px = x;
130  *py = y;
131  c++;
132  }
133 
134  /* determine initial move and eventual line command */
135  if (type == MVT_POINT) {
136  /* point or multipoint, use actual number of point count */
137  buffer[0] = c_int(CMD_MOVE_TO, c);
138  } else {
139  /* line or polygon, assume count 1 */
140  buffer[0] = c_int(CMD_MOVE_TO, 1);
141  /* line command with move point subtracted from count */
142  buffer[3] = c_int(CMD_LINE_TO, c - 1);
143  }
144 
145  /* add close command if ring */
146  if (type == MVT_RING)
147  buffer[offset++] = c_int(CMD_CLOSE_PATH, 1);
148 
149  return offset;
150 }
151 
153  enum mvt_type type,
154  POINTARRAY *pa, uint32_t *buffer)
155 {
156  int32_t px = 0, py = 0;
157  return encode_ptarray(ctx, type, pa, buffer, &px, &py);
158 }
159 
160 static void encode_point(struct mvt_agg_context *ctx, LWPOINT *point)
161 {
162  VectorTile__Tile__Feature *feature = ctx->feature;
163  feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POINT;
164  feature->has_type = 1;
165  feature->n_geometry = 3;
166  feature->geometry = palloc(sizeof(*feature->geometry) * 3);
167  encode_ptarray_initial(ctx, MVT_POINT, point->point, feature->geometry);
168 }
169 
170 static void encode_mpoint(struct mvt_agg_context *ctx, LWMPOINT *mpoint)
171 {
172  size_t c;
173  VectorTile__Tile__Feature *feature = ctx->feature;
174  // NOTE: inefficient shortcut LWMPOINT->LWLINE
175  LWLINE *lwline = lwline_from_lwmpoint(mpoint->srid, mpoint);
176  feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POINT;
177  feature->has_type = 1;
178  c = 1 + lwline->points->npoints * 2;
179  feature->geometry = palloc(sizeof(*feature->geometry) * c);
180  feature->n_geometry = encode_ptarray_initial(ctx, MVT_POINT,
181  lwline->points, feature->geometry);
182 }
183 
184 static void encode_line(struct mvt_agg_context *ctx, LWLINE *lwline)
185 {
186  size_t c;
187  VectorTile__Tile__Feature *feature = ctx->feature;
188  feature->type = VECTOR_TILE__TILE__GEOM_TYPE__LINESTRING;
189  feature->has_type = 1;
190  c = 2 + lwline->points->npoints * 2;
191  feature->geometry = palloc(sizeof(*feature->geometry) * c);
192  feature->n_geometry = encode_ptarray_initial(ctx, MVT_LINE,
193  lwline->points, feature->geometry);
194 }
195 
196 static void encode_mline(struct mvt_agg_context *ctx, LWMLINE *lwmline)
197 {
198  uint32_t i;
199  int32_t px = 0, py = 0;
200  size_t c = 0, offset = 0;
201  VectorTile__Tile__Feature *feature = ctx->feature;
202  feature->type = VECTOR_TILE__TILE__GEOM_TYPE__LINESTRING;
203  feature->has_type = 1;
204  for (i = 0; i < lwmline->ngeoms; i++)
205  c += 2 + lwmline->geoms[i]->points->npoints * 2;
206  feature->geometry = palloc(sizeof(*feature->geometry) * c);
207  for (i = 0; i < lwmline->ngeoms; i++)
208  offset += encode_ptarray(ctx, MVT_LINE,
209  lwmline->geoms[i]->points,
210  feature->geometry + offset, &px, &py);
211  feature->n_geometry = offset;
212 }
213 
214 static void encode_poly(struct mvt_agg_context *ctx, LWPOLY *lwpoly)
215 {
216  uint32_t i;
217  int32_t px = 0, py = 0;
218  size_t c = 0, offset = 0;
219  VectorTile__Tile__Feature *feature = ctx->feature;
220  feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POLYGON;
221  feature->has_type = 1;
222  for (i = 0; i < lwpoly->nrings; i++)
223  c += 3 + ((lwpoly->rings[i]->npoints - 1) * 2);
224  feature->geometry = palloc(sizeof(*feature->geometry) * c);
225  for (i = 0; i < lwpoly->nrings; i++)
226  offset += encode_ptarray(ctx, MVT_RING,
227  lwpoly->rings[i],
228  feature->geometry + offset, &px, &py);
229  feature->n_geometry = offset;
230 }
231 
232 static void encode_mpoly(struct mvt_agg_context *ctx, LWMPOLY *lwmpoly)
233 {
234  uint32_t i, j;
235  int32_t px = 0, py = 0;
236  size_t c = 0, offset = 0;
237  LWPOLY *poly;
238  VectorTile__Tile__Feature *feature = ctx->feature;
239  feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POLYGON;
240  feature->has_type = 1;
241  for (i = 0; i < lwmpoly->ngeoms; i++)
242  for (j = 0; poly = lwmpoly->geoms[i], j < poly->nrings; j++)
243  c += 3 + ((poly->rings[j]->npoints - 1) * 2);
244  feature->geometry = palloc(sizeof(*feature->geometry) * c);
245  for (i = 0; i < lwmpoly->ngeoms; i++)
246  for (j = 0; poly = lwmpoly->geoms[i], j < poly->nrings; j++)
247  offset += encode_ptarray(ctx, MVT_RING,
248  poly->rings[j], feature->geometry + offset,
249  &px, &py);
250  feature->n_geometry = offset;
251 }
252 
253 static void encode_geometry(struct mvt_agg_context *ctx, LWGEOM *lwgeom)
254 {
255  int type = lwgeom->type;
256 
257  switch (type) {
258  case POINTTYPE:
259  return encode_point(ctx, (LWPOINT*)lwgeom);
260  case LINETYPE:
261  return encode_line(ctx, (LWLINE*)lwgeom);
262  case POLYGONTYPE:
263  return encode_poly(ctx, (LWPOLY*)lwgeom);
264  case MULTIPOINTTYPE:
265  return encode_mpoint(ctx, (LWMPOINT*)lwgeom);
266  case MULTILINETYPE:
267  return encode_mline(ctx, (LWMLINE*)lwgeom);
268  case MULTIPOLYGONTYPE:
269  return encode_mpoly(ctx, (LWMPOLY*)lwgeom);
270  default: elog(ERROR, "encode_geometry: '%s' geometry type not supported",
271  lwtype_name(type));
272  }
273 }
274 
275 static TupleDesc get_tuple_desc(struct mvt_agg_context *ctx)
276 {
277  Oid tupType = HeapTupleHeaderGetTypeId(ctx->row);
278  int32 tupTypmod = HeapTupleHeaderGetTypMod(ctx->row);
279  TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
280  return tupdesc;
281 }
282 
283 static uint32_t get_key_index(struct mvt_agg_context *ctx, char *name)
284 {
285  struct mvt_kv_key *kv;
286  size_t size = strlen(name);
287  HASH_FIND(hh, ctx->keys_hash, name, size, kv);
288  if (!kv)
289  return -1;
290  return kv->id;
291 }
292 
293 static uint32_t add_key(struct mvt_agg_context *ctx, char *name)
294 {
295  struct mvt_kv_key *kv;
296  size_t size = strlen(name);
297  kv = palloc(sizeof(*kv));
298  kv->id = ctx->keys_hash_i++;
299  kv->name = name;
300  HASH_ADD_KEYPTR(hh, ctx->keys_hash, name, size, kv);
301  return kv->id;
302 }
303 
304 static void parse_column_keys(struct mvt_agg_context *ctx)
305 {
306  TupleDesc tupdesc = get_tuple_desc(ctx);
307  int natts = tupdesc->natts;
308  uint32_t i;
309  bool geom_found = false;
310  char *key;
311  POSTGIS_DEBUG(2, "parse_column_keys called");
312 
313  for (i = 0; i < natts; i++) {
314 #if POSTGIS_PGSQL_VERSION < 110
315  Oid typoid = getBaseType(tupdesc->attrs[i]->atttypid);
316  char *tkey = tupdesc->attrs[i]->attname.data;
317 #else
318  Oid typoid = getBaseType(tupdesc->attrs[i].atttypid);
319  char *tkey = tupdesc->attrs[i].attname.data;
320 #endif
321 #if POSTGIS_PGSQL_VERSION >= 94
322  if (typoid == JSONBOID)
323  continue;
324 #endif
325  key = pstrdup(tkey);
326  if (ctx->geom_name == NULL) {
327  if (!geom_found && typoid == postgis_oid(GEOMETRYOID)) {
328  ctx->geom_index = i;
329  geom_found = true;
330  continue;
331  }
332  } else {
333  if (!geom_found && strcmp(key, ctx->geom_name) == 0) {
334  ctx->geom_index = i;
335  geom_found = true;
336  continue;
337  }
338  }
339  add_key(ctx, key);
340  }
341  if (!geom_found)
342  elog(ERROR, "parse_column_keys: no geometry column found");
343  ReleaseTupleDesc(tupdesc);
344 }
345 
346 static void encode_keys(struct mvt_agg_context *ctx)
347 {
348  struct mvt_kv_key *kv;
349  size_t n_keys = ctx->keys_hash_i;
350  char **keys = palloc(n_keys * sizeof(*keys));
351  for (kv = ctx->keys_hash; kv != NULL; kv=kv->hh.next)
352  keys[kv->id] = kv->name;
353  ctx->layer->n_keys = n_keys;
354  ctx->layer->keys = keys;
355 
356  HASH_CLEAR(hh, ctx->keys_hash);
357 }
358 
359 static VectorTile__Tile__Value *create_value()
360 {
361  VectorTile__Tile__Value *value = palloc(sizeof(*value));
362  vector_tile__tile__value__init(value);
363  return value;
364 }
365 
366 #define MVT_CREATE_VALUES(kvtype, hash, hasfield, valuefield) \
367 { \
368  POSTGIS_DEBUG(2, "MVT_CREATE_VALUES called"); \
369  struct kvtype *kv; \
370  for (kv = ctx->hash; kv != NULL; kv=kv->hh.next) { \
371  VectorTile__Tile__Value *value = create_value(); \
372  value->hasfield = 1; \
373  value->valuefield = kv->valuefield; \
374  values[kv->id] = value; \
375  } \
376 }
377 
378 static void encode_values(struct mvt_agg_context *ctx)
379 {
380  POSTGIS_DEBUG(2, "encode_values called");
381  VectorTile__Tile__Value **values;
382  values = palloc(ctx->values_hash_i * sizeof(*values));
383  struct mvt_kv_string_value *kv;
384  for (kv = ctx->string_values_hash; kv != NULL; kv=kv->hh.next) {
385  VectorTile__Tile__Value *value = create_value();
386  value->string_value = kv->string_value;
387  values[kv->id] = value;
388  }
390  float_values_hash, has_float_value, float_value);
392  double_values_hash, has_double_value, double_value);
394  uint_values_hash, has_uint_value, uint_value);
396  sint_values_hash, has_sint_value, sint_value);
398  bool_values_hash, has_bool_value, bool_value);
399 
400  POSTGIS_DEBUGF(3, "encode_values n_values: %d", ctx->values_hash_i);
401  ctx->layer->n_values = ctx->values_hash_i;
402  ctx->layer->values = values;
403 
410 }
411 
412 #define MVT_PARSE_VALUE(value, kvtype, hash, valuefield, size) \
413 { \
414  POSTGIS_DEBUG(2, "MVT_PARSE_VALUE called"); \
415  struct kvtype *kv; \
416  HASH_FIND(hh, ctx->hash, &value, size, kv); \
417  if (!kv) { \
418  POSTGIS_DEBUG(4, "MVT_PARSE_VALUE value not found"); \
419  kv = palloc(sizeof(*kv)); \
420  POSTGIS_DEBUGF(4, "MVT_PARSE_VALUE new hash key: %d", \
421  ctx->values_hash_i); \
422  kv->id = ctx->values_hash_i++; \
423  kv->valuefield = value; \
424  HASH_ADD(hh, ctx->hash, valuefield, size, kv); \
425  } \
426  tags[ctx->c*2] = k; \
427  tags[ctx->c*2+1] = kv->id; \
428 }
429 
430 #define MVT_PARSE_INT_VALUE(value) \
431 { \
432  if (value >= 0) { \
433  uint64_t cvalue = value; \
434  MVT_PARSE_VALUE(cvalue, mvt_kv_uint_value, \
435  uint_values_hash, uint_value, \
436  sizeof(uint64_t)) \
437  } else { \
438  int64_t cvalue = value; \
439  MVT_PARSE_VALUE(cvalue, mvt_kv_sint_value, \
440  sint_values_hash, sint_value, \
441  sizeof(int64_t)) \
442  } \
443 }
444 
445 #define MVT_PARSE_DATUM(type, kvtype, hash, valuefield, datumfunc, size) \
446 { \
447  type value = datumfunc(datum); \
448  MVT_PARSE_VALUE(value, kvtype, hash, valuefield, size); \
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 void add_value_as_string(struct mvt_agg_context *ctx,
458  char *value, uint32_t *tags, uint32_t k)
459 {
460  struct mvt_kv_string_value *kv;
461  size_t size = strlen(value);
462  POSTGIS_DEBUG(2, "add_value_as_string called");
463  HASH_FIND(hh, ctx->string_values_hash, value, size, kv);
464  if (!kv) {
465  POSTGIS_DEBUG(4, "add_value_as_string value not found");
466  kv = palloc(sizeof(*kv));
467  POSTGIS_DEBUGF(4, "add_value_as_string new hash key: %d",
468  ctx->values_hash_i);
469  kv->id = ctx->values_hash_i++;
470  kv->string_value = value;
472  size, kv);
473  }
474  tags[ctx->c*2] = k;
475  tags[ctx->c*2+1] = kv->id;
476 }
477 
478 static void parse_datum_as_string(struct mvt_agg_context *ctx, Oid typoid,
479  Datum datum, uint32_t *tags, uint32_t k)
480 {
481  Oid foutoid;
482  bool typisvarlena;
483  char *value;
484  POSTGIS_DEBUG(2, "parse_value_as_string called");
485  getTypeOutputInfo(typoid, &foutoid, &typisvarlena);
486  value = OidOutputFunctionCall(foutoid, datum);
487  POSTGIS_DEBUGF(4, "parse_value_as_string value: %s", value);
488  add_value_as_string(ctx, value, tags, k);
489 }
490 
491 #if POSTGIS_PGSQL_VERSION >= 94
492 static uint32_t *parse_jsonb(struct mvt_agg_context *ctx, Jsonb *jb,
493  uint32_t *tags)
494 {
495  JsonbIterator *it;
496  JsonbValue v;
497  bool skipNested = false;
498  JsonbIteratorToken r;
499  uint32_t k;
500 
501  if (!JB_ROOT_IS_OBJECT(jb))
502  return tags;
503 
504  it = JsonbIteratorInit(&jb->root);
505 
506  while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE) {
507  skipNested = true;
508 
509  if (r == WJB_KEY && v.type != jbvNull) {
510  char *key;
511  key = palloc(v.val.string.len + 1 * sizeof(char));
512  memcpy(key, v.val.string.val, v.val.string.len);
513  key[v.val.string.len] = '\0';
514 
515  k = get_key_index(ctx, key);
516  if (k == -1) {
517  uint32_t newSize = ctx->keys_hash_i + 1;
518  tags = repalloc(tags, newSize * 2 * sizeof(*tags));
519  k = add_key(ctx, key);
520  }
521 
522  r = JsonbIteratorNext(&it, &v, skipNested);
523 
524  if (v.type == jbvString) {
525  char *value;
526  value = palloc(v.val.string.len + 1 * sizeof(char));
527  memcpy(value, v.val.string.val, v.val.string.len);
528  value[v.val.string.len] = '\0';
529  add_value_as_string(ctx, value, tags, k);
530  ctx->c++;
531  } else if (v.type == jbvBool) {
532  MVT_PARSE_VALUE(v.val.boolean, mvt_kv_bool_value,
533  bool_values_hash, bool_value, sizeof(protobuf_c_boolean));
534  ctx->c++;
535  } else if (v.type == jbvNumeric) {
536  char *str;
537  str = DatumGetCString(DirectFunctionCall1(numeric_out,
538  PointerGetDatum(v.val.numeric)));
539  double d = strtod(str, NULL);
540  long l = strtol(str, NULL, 10);
541  if (FP_NEQUALS(d, (double)l)) {
542  MVT_PARSE_VALUE(d, mvt_kv_double_value, double_values_hash,
543  double_value, sizeof(double));
544  } else {
546  }
547  ctx->c++;
548  }
549  }
550  }
551 
552  return tags;
553 }
554 #endif
555 
556 static void parse_values(struct mvt_agg_context *ctx)
557 {
558  POSTGIS_DEBUG(2, "parse_values called");
559  uint32_t n_keys = ctx->keys_hash_i;
560  uint32_t *tags = palloc(n_keys * 2 * sizeof(*tags));
561  bool isnull;
562  uint32_t i, k;
563  TupleDesc tupdesc = get_tuple_desc(ctx);
564  int natts = tupdesc->natts;
565  ctx->c = 0;
566 
567  POSTGIS_DEBUGF(3, "parse_values natts: %d", natts);
568 
569  for (i = 0; i < natts; i++) {
570  char *key;
571  Oid typoid;
572  Datum datum;
573 
574  if (i == ctx->geom_index)
575  continue;
576 
577 #if POSTGIS_PGSQL_VERSION < 110
578  key = tupdesc->attrs[i]->attname.data;
579  typoid = getBaseType(tupdesc->attrs[i]->atttypid);
580 #else
581  key = tupdesc->attrs[i].attname.data;
582  typoid = getBaseType(tupdesc->attrs[i].atttypid);
583 #endif
584  datum = GetAttributeByNum(ctx->row, i+1, &isnull);
585  k = get_key_index(ctx, key);
586  if (isnull) {
587  POSTGIS_DEBUG(3, "parse_values isnull detected");
588  continue;
589  }
590 #if POSTGIS_PGSQL_VERSION >= 94
591  if (k == -1 && typoid != JSONBOID)
592  elog(ERROR, "parse_values: unexpectedly could not find parsed key name '%s'", key);
593  if (typoid == JSONBOID) {
594  tags = parse_jsonb(ctx, DatumGetJsonbP(datum), tags);
595  continue;
596  }
597 #else
598  if (k == -1)
599  elog(ERROR, "parse_values: unexpectedly could not find parsed key name '%s'", key);
600 #endif
601 
602  switch (typoid) {
603  case BOOLOID:
604  MVT_PARSE_DATUM(protobuf_c_boolean, mvt_kv_bool_value,
605  bool_values_hash, bool_value,
606  DatumGetBool, sizeof(protobuf_c_boolean));
607  break;
608  case INT2OID:
609  MVT_PARSE_INT_DATUM(int16_t, DatumGetInt16);
610  break;
611  case INT4OID:
612  MVT_PARSE_INT_DATUM(int32_t, DatumGetInt32);
613  break;
614  case INT8OID:
615  MVT_PARSE_INT_DATUM(int64_t, DatumGetInt64);
616  break;
617  case FLOAT4OID:
619  float_values_hash, float_value,
620  DatumGetFloat4, sizeof(float));
621  break;
622  case FLOAT8OID:
624  double_values_hash, double_value,
625  DatumGetFloat8, sizeof(double));
626  break;
627  default:
628  parse_datum_as_string(ctx, typoid, datum, tags, k);
629  break;
630  }
631  ctx->c++;
632  }
633 
634  ReleaseTupleDesc(tupdesc);
635 
636  ctx->feature->n_tags = ctx->c * 2;
637  ctx->feature->tags = tags;
638 
639  POSTGIS_DEBUGF(3, "parse_values n_tags %zd", ctx->feature->n_tags);
640 }
641 
642 /* For a given geometry, look for the highest dimensional basic type, that is,
643  * point, line or polygon */
644 static uint8
646 {
647  switch(geom->type)
648  {
649  case POINTTYPE:
650  case LINETYPE:
651  case POLYGONTYPE:
652  return geom->type;
653  case MULTIPOINTTYPE:
654  case MULTILINETYPE:
655  case MULTIPOLYGONTYPE:
656  return geom->type - 3; /* Based on LWTYPE positions */
657  case COLLECTIONTYPE:
658  {
659  uint32_t i;
660  uint8 type = 0;
661  LWCOLLECTION *g = (LWCOLLECTION*)geom;
662  for (i = 0; i < g->ngeoms; i++)
663  {
664  LWGEOM *sg = g->geoms[i];
665  type = Max(type, lwgeom_get_basic_type(sg));
666  }
667  return type;
668  }
669  default:
670  elog(ERROR, "%s: Invalid type (%d)", __func__, geom->type);
671  }
672 }
673 
680 static inline LWGEOM *
681 lwgeom_to_basic_type(LWGEOM *geom, uint8 original_type)
682 {
683  LWGEOM *geom_out = geom;
684  if (lwgeom_get_type(geom) == COLLECTIONTYPE)
685  {
686  LWCOLLECTION *g = (LWCOLLECTION*)geom;
687  geom_out = (LWGEOM *)lwcollection_extract(g, original_type);
688  }
689 
690  /* If a collection only contains 1 geometry return than instead */
691  if (lwgeom_is_collection(geom_out))
692  {
693  LWCOLLECTION *g = (LWCOLLECTION *)geom_out;
694  if (g->ngeoms == 1)
695  {
696  geom_out = g->geoms[0];
697  }
698  }
699 
700  geom_out->srid = geom->srid;
701  return geom_out;
702 }
703 
710 LWGEOM *mvt_geom(const LWGEOM *lwgeom, const GBOX *gbox, uint32_t extent, uint32_t buffer,
711  bool clip_geom)
712 {
713  AFFINE affine;
714  gridspec grid;
715  LWGEOM *lwgeom_out = NULL;
716  double width = gbox->xmax - gbox->xmin;
717  double height = gbox->ymax - gbox->ymin;
718  double fx = extent / width;
719  double fy = -(extent / height);
720  uint8_t basic_type;
721  POSTGIS_DEBUG(2, "mvt_geom called");
722 
723  /* Short circuit out on EMPTY */
724  if (lwgeom_is_empty(lwgeom))
725  return NULL;
726 
727  if (width == 0 || height == 0)
728  elog(ERROR, "mvt_geom: bounds width or height cannot be 0");
729 
730  if (extent == 0)
731  elog(ERROR, "mvt_geom: extent cannot be 0");
732 
733  lwgeom_out = lwgeom_clone_deep(lwgeom);
734  basic_type = lwgeom_get_basic_type(lwgeom_out);
735 
736  /* transform to tile coordinate space */
737  memset(&affine, 0, sizeof(affine));
738  affine.afac = fx;
739  affine.efac = fy;
740  affine.ifac = 1;
741  affine.xoff = -gbox->xmin * fx;
742  affine.yoff = -gbox->ymax * fy;
743  lwgeom_affine(lwgeom_out, &affine);
744 
745  /* snap to integer precision, removing duplicate points */
746  memset(&grid, 0, sizeof(gridspec));
747  grid.ipx = 0;
748  grid.ipy = 0;
749  grid.xsize = 1;
750  grid.ysize = 1;
751  lwgeom_out = lwgeom_grid(lwgeom_out, &grid);
752 
753  if (lwgeom_out == NULL || lwgeom_is_empty(lwgeom_out))
754  return NULL;
755 
756  if (clip_geom)
757  {
758  GBOX bgbox, lwgeom_gbox;
759  gbox_init(&bgbox);
760  gbox_init(&lwgeom_gbox);
761  bgbox.xmax = bgbox.ymax = (double)extent + (double)buffer;
762  bgbox.xmin = bgbox.ymin = -(double)buffer;
763  FLAGS_SET_GEODETIC(lwgeom_gbox.flags, 0);
764  FLAGS_SET_GEODETIC(bgbox.flags, 0);
765  lwgeom_calculate_gbox(lwgeom_out, &lwgeom_gbox);
766 
767  if (!gbox_overlaps_2d(&lwgeom_gbox, &bgbox))
768  {
769  POSTGIS_DEBUG(3, "mvt_geom: geometry outside clip box");
770  return NULL;
771  }
772 
773  if (!gbox_contains_2d(&bgbox, &lwgeom_gbox))
774  {
775  LWGEOM *clipped_geom;
776 #if POSTGIS_GEOS_VERSION < 35
777  LWPOLY *lwenv = lwpoly_construct_envelope(0, bgbox.xmin, bgbox.ymin, bgbox.xmax, bgbox.ymax);
778  clipped_geom = lwgeom_intersection(lwgeom_out, lwpoly_as_lwgeom(lwenv));
779  lwpoly_free(lwenv);
780 #else
781  clipped_geom = lwgeom_clip_by_rect(lwgeom_out, bgbox.xmin, bgbox.ymin, bgbox.xmax, bgbox.ymax);
782 #endif
783  if (clipped_geom == NULL || lwgeom_is_empty(clipped_geom))
784  {
785  POSTGIS_DEBUG(3, "mvt_geom: no geometry after clip");
786  return NULL;
787  }
788 
789  /* For some polygons, the simplify step might have left them
790  * as invalid, which can cause clipping to return the complementary
791  * geometry of what it should */
792  if ((basic_type == POLYGONTYPE) &&
793  !gbox_contains_2d(&lwgeom_gbox, lwgeom_get_bbox(clipped_geom)))
794  {
795  /* TODO: Adapt this when and if Exception Policies are introduced.
796  * Other options would be to fix the geometry and retry
797  * or to calculate the difference between the 2 boxes.
798  */
799  POSTGIS_DEBUG(3, "mvt_geom: Invalid geometry after clipping");
800  lwgeom_free(clipped_geom);
801  return NULL;
802  }
803 
804  lwgeom_out = clipped_geom;
805  }
806  }
807 
808  if (lwgeom_out == NULL || lwgeom_is_empty(lwgeom_out))
809  return NULL;
810 
811  /* if polygon(s) make valid and force clockwise as per MVT spec */
812  if (basic_type == POLYGONTYPE)
813  {
814  lwgeom_out = lwgeom_make_valid(lwgeom_out);
815 
816  /* Because we are in image coordinates, we need to go to CCW in */
817  /* order to get a CW output in image space */
818  lwgeom_force_clockwise(lwgeom_out);
819  lwgeom_reverse(lwgeom_out);
820  }
821 
822  /* Make sure we return the most basic type after simplification and validation */
823  lwgeom_out = lwgeom_to_basic_type(lwgeom_out, basic_type);
824  if (basic_type != lwgeom_get_basic_type(lwgeom_out))
825  {
826  /* Drop type changes to play nice with MVT renderers */
827  POSTGIS_DEBUG(3, "mvt_geom: Dropping geometry after type change");
828  return NULL;
829  }
830 
831  if (lwgeom_out == NULL || lwgeom_is_empty(lwgeom_out))
832  return NULL;
833 
834 
835  /* Clipping and validation might produce float values. Grid again into int
836  * and pray that the output is still valid */
837  lwgeom_out = lwgeom_grid(lwgeom_out, &grid);
838 
839  if (lwgeom_out == NULL || lwgeom_is_empty(lwgeom_out))
840  return NULL;
841 
842  return lwgeom_out;
843 }
844 
849 {
850  VectorTile__Tile__Layer *layer;
851 
852  POSTGIS_DEBUG(2, "mvt_agg_init_context called");
853 
854  if (ctx->extent == 0)
855  elog(ERROR, "mvt_agg_init_context: extent cannot be 0");
856 
858  ctx->keys_hash = NULL;
859  ctx->string_values_hash = NULL;
860  ctx->float_values_hash = NULL;
861  ctx->double_values_hash = NULL;
862  ctx->uint_values_hash = NULL;
863  ctx->sint_values_hash = NULL;
864  ctx->bool_values_hash = NULL;
865  ctx->values_hash_i = 0;
866  ctx->keys_hash_i = 0;
867  ctx->geom_index = UINT32_MAX;
868 
869  layer = palloc(sizeof(*layer));
870  vector_tile__tile__layer__init(layer);
871  layer->version = 2;
872  layer->name = ctx->name;
873  layer->has_extent = 1;
874  layer->extent = ctx->extent;
875  layer->features = palloc (ctx->features_capacity *
876  sizeof(*layer->features));
877 
878  ctx->layer = layer;
879 }
880 
889 {
890  bool isnull = false;
891  Datum datum;
892  GSERIALIZED *gs;
893  LWGEOM *lwgeom;
894  VectorTile__Tile__Feature *feature;
895  VectorTile__Tile__Layer *layer = ctx->layer;
896  POSTGIS_DEBUG(2, "mvt_agg_transfn called");
897 
898  if (layer->n_features >= ctx->features_capacity) {
899  size_t new_capacity = ctx->features_capacity * 2;
900  layer->features = repalloc(layer->features, new_capacity *
901  sizeof(*layer->features));
902  ctx->features_capacity = new_capacity;
903  POSTGIS_DEBUGF(3, "mvt_agg_transfn new_capacity: %zd", new_capacity);
904  }
905 
906  if (ctx->geom_index == UINT32_MAX)
907  parse_column_keys(ctx);
908 
909  datum = GetAttributeByNum(ctx->row, ctx->geom_index + 1, &isnull);
910  POSTGIS_DEBUGF(3, "mvt_agg_transfn ctx->geom_index: %d", ctx->geom_index);
911  POSTGIS_DEBUGF(3, "mvt_agg_transfn isnull: %u", isnull);
912  POSTGIS_DEBUGF(3, "mvt_agg_transfn datum: %lu", datum);
913  if (isnull) /* Skip rows that have null geometry */
914  {
915  POSTGIS_DEBUG(3, "mvt_agg_transfn got null geom");
916  return;
917  }
918 
919  feature = palloc(sizeof(*feature));
920  vector_tile__tile__feature__init(feature);
921 
922  ctx->feature = feature;
923 
924  gs = (GSERIALIZED *) PG_DETOAST_DATUM(datum);
925  lwgeom = lwgeom_from_gserialized(gs);
926 
927  POSTGIS_DEBUGF(3, "mvt_agg_transfn encoded feature count: %zd", layer->n_features);
928  layer->features[layer->n_features++] = feature;
929 
930  encode_geometry(ctx, lwgeom);
931  lwgeom_free(lwgeom);
932  // TODO: free detoasted datum?
933  parse_values(ctx);
934 }
935 
943 {
944  VectorTile__Tile__Layer *layers[1];
945  VectorTile__Tile tile = VECTOR_TILE__TILE__INIT;
946  size_t len;
947  uint8_t *buf;
948 
949  POSTGIS_DEBUG(2, "mvt_agg_finalfn called");
950  POSTGIS_DEBUGF(2, "mvt_agg_finalfn n_features == %zd", ctx->layer->n_features);
951 
952  /* Zero features => empty bytea output */
953  if (ctx->layer->n_features == 0)
954  {
955  buf = palloc(VARHDRSZ);
956  SET_VARSIZE(buf, VARHDRSZ);
957  return buf;
958  }
959 
960  encode_keys(ctx);
961  encode_values(ctx);
962 
963  layers[0] = ctx->layer;
964 
965  tile.n_layers = 1;
966  tile.layers = layers;
967 
968  len = vector_tile__tile__get_packed_size(&tile);
969  buf = palloc(sizeof(*buf) * (len + VARHDRSZ));
970  vector_tile__tile__pack(&tile, buf + VARHDRSZ);
971 
972  SET_VARSIZE(buf, VARHDRSZ + len);
973 
974  return buf;
975 }
976 
977 #endif
static void parse_values(struct mvt_agg_context *ctx)
Definition: mvt.c:556
#define LINETYPE
Definition: liblwgeom.h:86
HeapTupleHeader row
Definition: mvt.h:54
Definition: mvt.c:49
#define HASH_ADD_KEYPTR(hh, head, keyptr, keylen_in, add)
Definition: uthash.h:329
uint32_t c
Definition: mvt.h:67
static uint32_t add_key(struct mvt_agg_context *ctx, char *name)
Definition: mvt.c:293
uint32_t id
Definition: mvt.c:92
unsigned int int32
Definition: shpopen.c:273
struct mvt_kv_double_value * double_values_hash
Definition: mvt.h:61
struct mvt_kv_string_value * string_values_hash
Definition: mvt.h:59
char * name
Definition: mvt.h:50
LWGEOM * lwgeom_grid(const LWGEOM *lwgeom, const gridspec *grid)
Definition: lwgeom.c:1912
uint64_t uint_value
Definition: mvt.c:79
uint8_t * mvt_agg_finalfn(struct mvt_agg_context *ctx)
Finalize aggregation.
Definition: mvt.c:942
int lwgeom_is_collection(const LWGEOM *lwgeom)
Determine whether a LWGEOM can contain sub-geometries or not.
Definition: lwgeom.c:1040
LWCOLLECTION * lwcollection_extract(LWCOLLECTION *col, int type)
Takes a potentially heterogeneous collection and returns a homogeneous collection consisting only of ...
Definition: lwcollection.c:369
static void encode_keys(struct mvt_agg_context *ctx)
Definition: mvt.c:346
char * r
Definition: cu_in_wkt.c:24
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
int npoints
Definition: liblwgeom.h:371
uint32_t lwgeom_get_type(const LWGEOM *geom)
Return LWTYPE number.
Definition: lwgeom.c:878
static TupleDesc get_tuple_desc(struct mvt_agg_context *ctx)
Definition: mvt.c:275
#define POLYGONTYPE
Definition: liblwgeom.h:87
static uint32_t c_int(enum mvt_cmd_id id, uint32_t count)
Definition: mvt.c:96
char * string_value
Definition: mvt.c:61
double xmax
Definition: liblwgeom.h:293
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1099
LWGEOM * lwgeom_clip_by_rect(const LWGEOM *geom1, double x0, double y0, double x1, double y1)
#define MULTIPOINTTYPE
Definition: liblwgeom.h:88
uint32_t id
Definition: mvt.c:56
static void encode_point(struct mvt_agg_context *ctx, LWPOINT *point)
Definition: mvt.c:160
uint32_t id
Definition: mvt.c:74
uint32_t id
Definition: mvt.c:62
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: g_box.c:351
UT_hash_handle hh
Definition: mvt.c:87
static void parse_column_keys(struct mvt_agg_context *ctx)
Definition: mvt.c:304
char * geom_name
Definition: mvt.h:52
LWGEOM * lwgeom_clone_deep(const LWGEOM *lwgeom)
Deep clone an LWGEOM, everything is copied.
Definition: lwgeom.c:482
#define FLAGS_SET_GEODETIC(flags, value)
Definition: liblwgeom.h:149
struct mvt_kv_float_value * float_values_hash
Definition: mvt.h:60
static LWGEOM * lwgeom_to_basic_type(LWGEOM *geom, uint8 original_type)
In place process a collection to find a concrete geometry object and expose that as the actual object...
Definition: mvt.c:681
POINTARRAY * point
Definition: liblwgeom.h:411
static void encode_values(struct mvt_agg_context *ctx)
Definition: mvt.c:378
int32_t srid
Definition: liblwgeom.h:399
LWGEOM * lwpoly_as_lwgeom(const LWPOLY *obj)
Definition: lwgeom.c:288
double ifac
Definition: liblwgeom.h:270
void * next
Definition: uthash.h:1085
int ngeoms
Definition: liblwgeom.h:481
static uint32_t * parse_jsonb(struct mvt_agg_context *ctx, Jsonb *jb, uint32_t *tags)
Definition: mvt.c:492
double xoff
Definition: liblwgeom.h:270
static void add_value_as_string(struct mvt_agg_context *ctx, char *value, uint32_t *tags, uint32_t k)
Definition: mvt.c:457
double afac
Definition: liblwgeom.h:270
static uint32_t p_int(int32_t value)
Definition: mvt.c:101
uint32_t id
Definition: mvt.c:68
static void parse_datum_as_string(struct mvt_agg_context *ctx, Oid typoid, Datum datum, uint32_t *tags, uint32_t k)
Definition: mvt.c:478
unsigned int uint32_t
Definition: uthash.h:78
VectorTile__Tile__Layer * layer
Definition: mvt.h:56
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:701
double x
Definition: liblwgeom.h:328
LWPOLY * lwpoly_construct_envelope(int srid, double x1, double y1, double x2, double y2)
Definition: lwpoly.c:98
void lwgeom_force_clockwise(LWGEOM *lwgeom)
Ensure the outer ring is clockwise oriented and all inner rings are counter-clockwise.
Definition: lwgeom.c:36
LWGEOM * mvt_geom(const 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:710
Datum buffer(PG_FUNCTION_ARGS)
double ymin
Definition: liblwgeom.h:294
const char * lwtype_name(uint8_t type)
Return the type name string associated with a type number (e.g.
Definition: lwutil.c:218
protobuf_c_boolean bool_value
Definition: mvt.c:91
uint32_t keys_hash_i
Definition: mvt.h:66
Definition: mvt.c:51
void gbox_init(GBOX *gbox)
Zero out all the entries in the GBOX.
Definition: g_box.c:51
double xmin
Definition: liblwgeom.h:292
LWGEOM * lwgeom_make_valid(LWGEOM *geom)
Attempts to make an invalid geometries valid w/out losing points.
size_t features_capacity
Definition: mvt.h:57
const POINT2D * getPoint2d_cp(const POINTARRAY *pa, int n)
Returns a POINT2D pointer into the POINTARRAY serialized_ptlist, suitable for reading from...
Definition: lwgeom_api.c:373
static void encode_mpoint(struct mvt_agg_context *ctx, LWMPOINT *mpoint)
Definition: mvt.c:170
void lwpoly_free(LWPOLY *poly)
Definition: lwpoly.c:174
#define HASH_FIND(hh, head, keyptr, keylen, out)
Definition: uthash.h:132
UT_hash_handle hh
Definition: mvt.c:93
float float_value
Definition: mvt.c:67
LWPOLY ** geoms
Definition: liblwgeom.h:496
LWLINE * lwline_from_lwmpoint(int srid, const LWMPOINT *mpoint)
Definition: lwline.c:290
LWGEOM ** geoms
Definition: liblwgeom.h:509
static void encode_mline(struct mvt_agg_context *ctx, LWMLINE *lwmline)
Definition: mvt.c:196
LWGEOM * lwgeom_intersection(const LWGEOM *geom1, const LWGEOM *geom2)
#define FP_NEQUALS(A, B)
static void encode_geometry(struct mvt_agg_context *ctx, LWGEOM *lwgeom)
Definition: mvt.c:253
static uint32_t get_key_index(struct mvt_agg_context *ctx, char *name)
Definition: mvt.c:283
const GBOX * lwgeom_get_bbox(const LWGEOM *lwgeom)
Get a non-empty geometry bounding box, computing and caching it if not already there.
Definition: lwgeom.c:689
POINTARRAY ** rings
Definition: liblwgeom.h:457
int count
Definition: genraster.py:56
#define UINT32_MAX
Definition: lwin_wkt_lex.c:343
Definition: mvt.c:54
struct mvt_kv_uint_value * uint_values_hash
Definition: mvt.h:62
int nrings
Definition: liblwgeom.h:455
uint32_t geom_index
Definition: mvt.h:53
UT_hash_handle hh
Definition: mvt.c:75
double ymax
Definition: liblwgeom.h:295
void mvt_agg_init_context(struct mvt_agg_context *ctx)
Initialize aggregation context.
Definition: mvt.c:848
#define MVT_PARSE_VALUE(value, kvtype, hash, valuefield, size)
Definition: mvt.c:412
double y
Definition: liblwgeom.h:328
uint32_t id
Definition: mvt.c:80
static void encode_poly(struct mvt_agg_context *ctx, LWPOLY *lwpoly)
Definition: mvt.c:214
uint32_t id
Definition: mvt.c:86
int ngeoms
Definition: liblwgeom.h:494
uint8_t flags
Definition: liblwgeom.h:291
static void encode_mpoly(struct mvt_agg_context *ctx, LWMPOLY *lwmpoly)
Definition: mvt.c:232
#define MULTIPOLYGONTYPE
Definition: liblwgeom.h:90
Definition: mvt.c:50
struct mvt_kv_bool_value * bool_values_hash
Definition: mvt.h:64
LWLINE ** geoms
Definition: liblwgeom.h:483
int64_t sint_value
Definition: mvt.c:85
static uint32_t encode_ptarray(struct mvt_agg_context *ctx, enum mvt_type type, POINTARRAY *pa, uint32_t *buffer, int32_t *px, int32_t *py)
Definition: mvt.c:106
void lwgeom_reverse(LWGEOM *lwgeom)
Reverse vertex order of LWGEOM.
Definition: lwgeom.c:93
uint32_t values_hash_i
Definition: mvt.h:65
mvt_cmd_id
Definition: mvt.c:42
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: g_box.c:335
#define FEATURES_CAPACITY_INITIAL
Definition: mvt.c:40
#define HASH_CLEAR(hh, head)
Definition: uthash.h:993
uint32_t extent
Definition: mvt.h:51
static void encode_line(struct mvt_agg_context *ctx, LWLINE *lwline)
Definition: mvt.c:184
static uint32_t encode_ptarray_initial(struct mvt_agg_context *ctx, enum mvt_type type, POINTARRAY *pa, uint32_t *buffer)
Definition: mvt.c:152
UT_hash_handle hh
Definition: mvt.c:81
void mvt_agg_transfn(struct mvt_agg_context *ctx)
Aggregation step.
Definition: mvt.c:888
VectorTile__Tile__Feature * feature
Definition: mvt.h:55
struct mvt_kv_key * keys_hash
Definition: mvt.h:58
static uint8 lwgeom_get_basic_type(LWGEOM *geom)
Definition: mvt.c:645
struct mvt_kv_sint_value * sint_values_hash
Definition: mvt.h:63
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:85
double efac
Definition: liblwgeom.h:270
static VectorTile__Tile__Value * create_value()
Definition: mvt.c:359
#define MVT_PARSE_INT_VALUE(value)
Definition: mvt.c:430
UT_hash_handle hh
Definition: mvt.c:57
uint8_t type
Definition: liblwgeom.h:396
type
Definition: ovdump.py:41
UT_hash_handle hh
Definition: mvt.c:63
double yoff
Definition: liblwgeom.h:270
#define MVT_PARSE_DATUM(type, kvtype, hash, valuefield, datumfunc, size)
Definition: mvt.c:445
int value
Definition: genraster.py:61
#define DatumGetJsonbP
Definition: mvt.c:35
int lwgeom_is_empty(const LWGEOM *geom)
Return true or false depending on whether a geometry is an "empty" geometry (no vertices members) ...
Definition: lwgeom.c:1346
mvt_type
Definition: mvt.c:48
char * name
Definition: mvt.c:55
#define MULTILINETYPE
Definition: liblwgeom.h:89
double double_value
Definition: mvt.c:73
unsigned char uint8_t
Definition: uthash.h:79
int32_t srid
Definition: liblwgeom.h:467
UT_hash_handle hh
Definition: mvt.c:69
#define MVT_CREATE_VALUES(kvtype, hash, hasfield, valuefield)
Definition: mvt.c:366
#define MVT_PARSE_INT_DATUM(type, datumfunc)
Definition: mvt.c:451
void lwgeom_affine(LWGEOM *geom, const AFFINE *affine)
Definition: lwgeom.c:1735
#define COLLECTIONTYPE
Definition: liblwgeom.h:91
POINTARRAY * points
Definition: liblwgeom.h:422
Snap to grid.