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