Coverage Report

Created: 2025-06-13 06:18

/src/MapServer/src/mapmvt.c
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  MapBox Vector Tile rendering.
6
 * Author:   Thomas Bonfort and the MapServer team.
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 1996-2015 Regents of the University of Minnesota.
10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a
12
 * copy of this software and associated documentation files (the "Software"),
13
 * to deal in the Software without restriction, including without limitation
14
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
 * and/or sell copies of the Software, and to permit persons to whom the
16
 * Software is furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies of this Software or works derived from this Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
 * DEALINGS IN THE SOFTWARE.
28
 *****************************************************************************/
29
30
#include "mapserver.h"
31
#include "maptile.h"
32
33
#ifdef USE_PBF
34
#include "vector_tile.pb-c.h"
35
#include "mapows.h"
36
#include "uthash.h"
37
#include <float.h>
38
39
#define MOVETO 1
40
#define LINETO 2
41
#define CLOSEPATH 7
42
43
#define FEATURES_INCREMENT_SIZE 5
44
45
enum MS_RING_DIRECTION {
46
  MS_DIRECTION_INVALID_RING,
47
  MS_DIRECTION_CLOCKWISE,
48
  MS_DIRECTION_COUNTERCLOCKWISE
49
};
50
51
typedef struct {
52
  char *value;
53
  unsigned int index;
54
  UT_hash_handle hh;
55
} value_lookup;
56
57
typedef struct {
58
  value_lookup *cache;
59
} value_lookup_table;
60
61
#define COMMAND(id, count) (((id)&0x7) | ((count) << 3))
62
#define PARAMETER(n) (((n) << 1) ^ ((n) >> 31))
63
64
static double getTriangleHeight(lineObj *ring) {
65
  int i;
66
  double s = 0, b = 0;
67
68
  if (ring->numpoints != 4)
69
    return -1; /* not a triangle */
70
71
  for (i = 0; i < ring->numpoints - 1; i++) {
72
    s += (ring->point[i].x * ring->point[i + 1].y -
73
          ring->point[i + 1].x * ring->point[i].y);
74
    b = MS_MAX(b, msDistancePointToPoint(&ring->point[i], &ring->point[i + 1]));
75
  }
76
77
  return (MS_ABS(s / b));
78
}
79
80
static enum MS_RING_DIRECTION mvtGetRingDirection(lineObj *ring) {
81
  int i, sum = 0;
82
83
  if (ring->numpoints < 4)
84
    return MS_DIRECTION_INVALID_RING;
85
86
  /* step through the edges */
87
  for (i = 0; i < ring->numpoints - 1; i++) {
88
    sum += ring->point[i].x * ring->point[i + 1].y -
89
           ring->point[i + 1].x * ring->point[i].y;
90
  }
91
92
  return sum > 0   ? MS_DIRECTION_CLOCKWISE
93
         : sum < 0 ? MS_DIRECTION_COUNTERCLOCKWISE
94
                   : MS_DIRECTION_INVALID_RING;
95
}
96
97
static void mvtReverseRingDirection(lineObj *ring) {
98
  pointObj temp;
99
  int start = 1, end = ring->numpoints -
100
                       2; /* first and last points are the same so skip 'em */
101
102
  while (start < end) {
103
    temp.x = ring->point[start].x;
104
    temp.y = ring->point[start].y;
105
    ring->point[start].x = ring->point[end].x;
106
    ring->point[start].y = ring->point[end].y;
107
    ring->point[end].x = temp.x;
108
    ring->point[end].y = temp.y;
109
    start++;
110
    end--;
111
  }
112
}
113
114
static void mvtReorderRings(shapeObj *shape, int *outers) {
115
  int i, j;
116
  int t1;
117
  lineObj t2;
118
119
  for (i = 0; i < (shape->numlines - 1); i++) {
120
    for (j = 0; j < (shape->numlines - i - 1); j++) {
121
      if (outers[j] < outers[j + 1]) {
122
        /* swap */
123
        t1 = outers[j];
124
        outers[j] = outers[j + 1];
125
        outers[j + 1] = t1;
126
127
        t2 = shape->line[j];
128
        shape->line[j] = shape->line[j + 1];
129
        shape->line[j + 1] = t2;
130
      }
131
    }
132
  }
133
}
134
135
static int mvtTransformShape(shapeObj *shape, rectObj *extent, int layer_type,
136
                             int mvt_layer_extent) {
137
  double scale_x, scale_y;
138
  int i, j, outj;
139
140
  int *outers = NULL, ring_direction;
141
142
  scale_x = (double)mvt_layer_extent / (extent->maxx - extent->minx);
143
  scale_y = (double)mvt_layer_extent / (extent->maxy - extent->miny);
144
145
  if (layer_type == MS_LAYER_POLYGON) {
146
    outers = msGetOuterList(shape); /* compute before we muck with the shape */
147
    if (outers[0] == 0)             /* first ring must be an outer */
148
      mvtReorderRings(shape, outers);
149
  }
150
151
  for (i = 0; i < shape->numlines; i++) {
152
    for (j = 0, outj = 0; j < shape->line[i].numpoints; j++) {
153
154
      shape->line[i].point[outj].x =
155
          (int)((shape->line[i].point[j].x - extent->minx) * scale_x);
156
      shape->line[i].point[outj].y =
157
          mvt_layer_extent -
158
          (int)((shape->line[i].point[j].y - extent->miny) * scale_y);
159
160
      if (!outj ||
161
          shape->line[i].point[outj].x != shape->line[i].point[outj - 1].x ||
162
          shape->line[i].point[outj].y != shape->line[i].point[outj - 1].y)
163
        outj++; /* add the point to the shape only if it's the first one or if
164
                   it's different than the previous one */
165
    }
166
    shape->line[i].numpoints = outj;
167
168
    if (layer_type == MS_LAYER_POLYGON) {
169
      if (shape->line[i].numpoints == 4 &&
170
          getTriangleHeight(&shape->line[i]) < 1) {
171
        shape->line[i].numpoints = 0; /* so it's not considered anymore */
172
        continue;                     /* next ring */
173
      }
174
175
      ring_direction = mvtGetRingDirection(&shape->line[i]);
176
      if (ring_direction == MS_DIRECTION_INVALID_RING)
177
        shape->line[i].numpoints = 0; /* so it's not considered anymore */
178
      else if ((outers[i] && ring_direction != MS_DIRECTION_CLOCKWISE) ||
179
               (!outers[i] && ring_direction != MS_DIRECTION_COUNTERCLOCKWISE))
180
        mvtReverseRingDirection(&shape->line[i]);
181
    }
182
  }
183
184
  msComputeBounds(
185
      shape); /* TODO: might need to limit this to just valid parts... */
186
  msFree(outers);
187
188
  return (shape->numlines == 0) ? MS_FAILURE
189
                                : MS_SUCCESS; /* success if at least one line */
190
}
191
192
static int mvtClipShape(shapeObj *shape, int layer_type, int buffer,
193
                        int mvt_layer_extent) {
194
  rectObj tile_rect;
195
  tile_rect.minx = tile_rect.miny = -buffer * 16;
196
  tile_rect.maxx = tile_rect.maxy = mvt_layer_extent + buffer * 16;
197
198
  if (layer_type == MS_LAYER_POLYGON) {
199
    msClipPolygonRect(shape, tile_rect);
200
  } else if (layer_type == MS_LAYER_LINE) {
201
    msClipPolylineRect(shape, tile_rect);
202
  }
203
204
  /* success if at least one line and not a degenerate bounding box */
205
  if (shape->numlines > 0 && (layer_type == MS_LAYER_POINT ||
206
                              (shape->bounds.minx != shape->bounds.maxx ||
207
                               shape->bounds.miny != shape->bounds.maxy)))
208
    return MS_SUCCESS;
209
  else
210
    return MS_FAILURE;
211
}
212
213
static void freeMvtFeature(VectorTile__Tile__Feature *mvt_feature) {
214
  if (mvt_feature->tags)
215
    msFree(mvt_feature->tags);
216
  if (mvt_feature->geometry)
217
    msFree(mvt_feature->geometry);
218
}
219
220
static void freeMvtValue(VectorTile__Tile__Value *mvt_value) {
221
  if (mvt_value->string_value)
222
    msFree(mvt_value->string_value);
223
}
224
225
static void freeMvtLayer(VectorTile__Tile__Layer *mvt_layer) {
226
  if (mvt_layer->keys) {
227
    for (unsigned i = 0; i < mvt_layer->n_keys; i++) {
228
      msFree(mvt_layer->keys[i]);
229
    }
230
    msFree(mvt_layer->keys);
231
  }
232
  if (mvt_layer->values) {
233
    for (unsigned i = 0; i < mvt_layer->n_values; i++) {
234
      freeMvtValue(mvt_layer->values[i]);
235
      msFree(mvt_layer->values[i]);
236
    }
237
    msFree(mvt_layer->values);
238
  }
239
  if (mvt_layer->features) {
240
    for (unsigned i = 0; i < mvt_layer->n_features; i++) {
241
      freeMvtFeature(mvt_layer->features[i]);
242
      msFree(mvt_layer->features[i]);
243
    }
244
    msFree(mvt_layer->features);
245
  }
246
}
247
248
int mvtWriteShape(layerObj *layer, shapeObj *shape,
249
                  VectorTile__Tile__Layer *mvt_layer, gmlItemListObj *item_list,
250
                  value_lookup_table *value_lookup_cache,
251
                  rectObj *unbuffered_bbox, int buffer) {
252
  VectorTile__Tile__Feature *mvt_feature;
253
  int i, j, iout;
254
  value_lookup *value;
255
  long int n_geometry;
256
257
  /* could consider an intersection test here */
258
259
  if (mvtTransformShape(shape, unbuffered_bbox, layer->type,
260
                        mvt_layer->extent) != MS_SUCCESS) {
261
    return MS_SUCCESS; /* degenerate shape */
262
  }
263
  if (mvtClipShape(shape, layer->type, buffer, mvt_layer->extent) !=
264
      MS_SUCCESS) {
265
    return MS_SUCCESS; /* no features left after clipping */
266
  }
267
268
  n_geometry = 0;
269
  if (layer->type == MS_LAYER_POINT) {
270
    for (i = 0; i < shape->numlines; i++)
271
      n_geometry += shape->line[i].numpoints * 2;
272
    if (n_geometry)
273
      n_geometry++; /* one MOVETO */
274
  } else if (layer->type == MS_LAYER_LINE) {
275
    for (i = 0; i < shape->numlines; i++)
276
      if (shape->line[i].numpoints >= 2)
277
        n_geometry +=
278
            2 + shape->line[i].numpoints * 2; /* one MOVETO, one LINETO */
279
  } else {                                    /* MS_LAYER_POLYGON */
280
    for (i = 0; i < shape->numlines; i++)
281
      if (shape->line[i].numpoints >= 4)
282
        n_geometry += 3 + (shape->line[i].numpoints - 1) *
283
                              2; /* one MOVETO, one LINETO, one CLOSEPATH (don't
284
                                    consider last duplicate point) */
285
  }
286
287
  if (n_geometry == 0)
288
    return MS_SUCCESS;
289
290
  mvt_layer->features[mvt_layer->n_features++] =
291
      msSmallMalloc(sizeof(VectorTile__Tile__Feature));
292
  mvt_feature = mvt_layer->features[mvt_layer->n_features - 1];
293
  vector_tile__tile__feature__init(mvt_feature);
294
  mvt_feature->n_tags = mvt_layer->n_keys * 2;
295
  mvt_feature->tags = msSmallMalloc(mvt_feature->n_tags * sizeof(uint32_t));
296
  mvt_feature->id = shape->index;
297
  mvt_feature->has_id = 1;
298
299
  if (layer->type == MS_LAYER_POLYGON)
300
    mvt_feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POLYGON;
301
  else if (layer->type == MS_LAYER_LINE)
302
    mvt_feature->type = VECTOR_TILE__TILE__GEOM_TYPE__LINESTRING;
303
  else
304
    mvt_feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POINT;
305
  mvt_feature->has_type = 1;
306
307
  /* output values */
308
  for (i = 0, iout = 0; i < item_list->numitems; i++) {
309
    gmlItemObj *item = item_list->items + i;
310
311
    if (!item->visible)
312
      continue;
313
314
    UT_HASH_FIND_STR(value_lookup_cache->cache, shape->values[i], value);
315
    if (!value) {
316
      VectorTile__Tile__Value *mvt_value;
317
      value = msSmallMalloc(sizeof(value_lookup));
318
      value->value = msStrdup(shape->values[i]);
319
      value->index = mvt_layer->n_values;
320
      mvt_layer->values = msSmallRealloc(mvt_layer->values,
321
                                         (++mvt_layer->n_values) *
322
                                             sizeof(VectorTile__Tile__Value *));
323
      mvt_layer->values[mvt_layer->n_values - 1] =
324
          msSmallMalloc(sizeof(VectorTile__Tile__Value));
325
      mvt_value = mvt_layer->values[mvt_layer->n_values - 1];
326
      vector_tile__tile__value__init(mvt_value);
327
328
      if (item->type && EQUAL(item->type, "Integer")) {
329
        mvt_value->int_value = atoi(value->value);
330
        mvt_value->has_int_value = 1;
331
      } else if (item->type && EQUAL(item->type, "Long")) { /* signed */
332
        mvt_value->sint_value = atol(value->value);
333
        mvt_value->has_sint_value = 1;
334
      } else if (item->type && EQUAL(item->type, "Real")) {
335
        mvt_value->float_value = atof(value->value);
336
        mvt_value->has_float_value = 1;
337
      } else if (item->type && EQUAL(item->type, "Boolean")) {
338
        if (EQUAL(value->value, "0") || EQUAL(value->value, "false"))
339
          mvt_value->bool_value = 0;
340
        else
341
          mvt_value->bool_value = 1;
342
        mvt_value->has_bool_value = 1;
343
      } else {
344
        mvt_value->string_value = msStrdup(value->value);
345
      }
346
      UT_HASH_ADD_KEYPTR(hh, value_lookup_cache->cache, value->value,
347
                         strlen(value->value), value);
348
    }
349
    mvt_feature->tags[iout * 2] = iout;
350
    mvt_feature->tags[iout * 2 + 1] = value->index;
351
352
    iout++;
353
  }
354
355
  /* output geom */
356
  mvt_feature->n_geometry = n_geometry;
357
  mvt_feature->geometry =
358
      msSmallMalloc(mvt_feature->n_geometry * sizeof(uint32_t));
359
360
  if (layer->type == MS_LAYER_POINT) {
361
    int idx = 0, lastx = 0, lasty = 0;
362
    mvt_feature->geometry[idx++] =
363
        COMMAND(MOVETO, (mvt_feature->n_geometry - 1) / 2);
364
    for (i = 0; i < shape->numlines; i++) {
365
      for (j = 0; j < shape->line[i].numpoints; j++) {
366
        mvt_feature->geometry[idx++] =
367
            PARAMETER(MS_NINT(shape->line[i].point[j].x) - lastx);
368
        mvt_feature->geometry[idx++] =
369
            PARAMETER(MS_NINT(shape->line[i].point[j].y) - lasty);
370
        lastx = MS_NINT(shape->line[i].point[j].x);
371
        lasty = MS_NINT(shape->line[i].point[j].y);
372
      }
373
    }
374
  } else { /* MS_LAYER_LINE or MS_LAYER_POLYGON */
375
    int numpoints;
376
    int idx = 0, lastx = 0, lasty = 0;
377
    for (i = 0; i < shape->numlines; i++) {
378
379
      if ((layer->type == MS_LAYER_LINE && !(shape->line[i].numpoints >= 2)) ||
380
          (layer->type == MS_LAYER_POLYGON &&
381
           !(shape->line[i].numpoints >= 4))) {
382
        continue; /* skip malformed parts */
383
      }
384
385
      numpoints = (layer->type == MS_LAYER_LINE)
386
                      ? shape->line[i].numpoints
387
                      : (shape->line[i].numpoints -
388
                         1); /* don't consider last point for polygons */
389
      for (j = 0; j < numpoints; j++) {
390
        if (j == 0) {
391
          mvt_feature->geometry[idx++] = COMMAND(MOVETO, 1);
392
        } else if (j == 1) {
393
          mvt_feature->geometry[idx++] = COMMAND(LINETO, numpoints - 1);
394
        }
395
        mvt_feature->geometry[idx++] =
396
            PARAMETER(MS_NINT(shape->line[i].point[j].x) - lastx);
397
        mvt_feature->geometry[idx++] =
398
            PARAMETER(MS_NINT(shape->line[i].point[j].y) - lasty);
399
        lastx = MS_NINT(shape->line[i].point[j].x);
400
        lasty = MS_NINT(shape->line[i].point[j].y);
401
      }
402
      if (layer->type == MS_LAYER_POLYGON) {
403
        mvt_feature->geometry[idx++] = COMMAND(CLOSEPATH, 1);
404
      }
405
    }
406
  }
407
408
  return MS_SUCCESS;
409
}
410
411
static void freeMvtTile(VectorTile__Tile *mvt_tile) {
412
  for (unsigned iLayer = 0; iLayer < mvt_tile->n_layers; iLayer++) {
413
    freeMvtLayer(mvt_tile->layers[iLayer]);
414
    msFree(mvt_tile->layers[iLayer]);
415
  }
416
  msFree(mvt_tile->layers);
417
}
418
419
int msMVTWriteTile(mapObj *map, int sendheaders) {
420
  int iLayer, retcode = MS_SUCCESS;
421
  unsigned len;
422
  void *buf;
423
  const char *mvt_extent =
424
      msGetOutputFormatOption(map->outputformat, "EXTENT", "4096");
425
  const char *mvt_buffer =
426
      msGetOutputFormatOption(map->outputformat, "EDGE_BUFFER", "10");
427
  int buffer = MS_ABS(atoi(mvt_buffer));
428
  VectorTile__Tile mvt_tile = VECTOR_TILE__TILE__INIT;
429
  mvt_tile.layers =
430
      msSmallCalloc(map->numlayers, sizeof(VectorTile__Tile__Layer *));
431
432
  /* make sure we have a scale and cellsize computed */
433
  map->cellsize = MS_CELLSIZE(map->extent.minx, map->extent.maxx, map->width);
434
  msCalculateScale(map->extent, map->units, map->width, map->height,
435
                   map->resolution, &map->scaledenom);
436
437
  /* expand the map->extent so it goes from pixel center (MapServer) to pixel
438
   * edge (OWS) */
439
  map->extent.minx -= map->cellsize * 0.5;
440
  map->extent.maxx += map->cellsize * 0.5;
441
  map->extent.miny -= map->cellsize * 0.5;
442
  map->extent.maxy += map->cellsize * 0.5;
443
444
  for (iLayer = 0; iLayer < map->numlayers; iLayer++) {
445
    int status = MS_SUCCESS;
446
    layerObj *layer = GET_LAYER(map, iLayer);
447
    int i;
448
    shapeObj shape;
449
    gmlItemListObj *item_list = NULL;
450
    VectorTile__Tile__Layer *mvt_layer;
451
    value_lookup_table value_lookup_cache = {NULL};
452
    value_lookup *cur_value_lookup, *tmp_value_lookup;
453
    rectObj rect;
454
455
    int nclasses = 0;
456
    int *classgroup = NULL;
457
458
    unsigned features_size = 0;
459
460
    if (!msLayerIsVisible(map, layer))
461
      continue;
462
463
    if (layer->type != MS_LAYER_POINT && layer->type != MS_LAYER_POLYGON &&
464
        layer->type != MS_LAYER_LINE)
465
      continue;
466
467
    status = msLayerOpen(layer);
468
    if (status != MS_SUCCESS) {
469
      retcode = status;
470
      goto layer_cleanup;
471
    }
472
473
    status = msLayerWhichItems(
474
        layer, MS_TRUE,
475
        NULL); /* we want all items - behaves like a query in that sense */
476
    if (status != MS_SUCCESS) {
477
      retcode = status;
478
      goto layer_cleanup;
479
    }
480
481
    /* -------------------------------------------------------------------- */
482
    /*      Will we need to reproject?                                      */
483
    /* -------------------------------------------------------------------- */
484
    layer->project =
485
        msProjectionsDiffer(&(layer->projection), &(map->projection));
486
487
    rect = map->extent;
488
    if (layer->project)
489
      msProjectRect(&(map->projection), &(layer->projection), &rect);
490
491
    status = msLayerWhichShapes(layer, rect, MS_TRUE);
492
    if (status == MS_DONE) { /* no overlap - that's ok */
493
      retcode = MS_SUCCESS;
494
      goto layer_cleanup;
495
    } else if (status != MS_SUCCESS) {
496
      retcode = status;
497
      goto layer_cleanup;
498
    }
499
500
    mvt_tile.layers[mvt_tile.n_layers++] =
501
        msSmallMalloc(sizeof(VectorTile__Tile__Layer));
502
    mvt_layer = mvt_tile.layers[mvt_tile.n_layers - 1];
503
    vector_tile__tile__layer__init(mvt_layer);
504
    mvt_layer->version = 2;
505
    mvt_layer->name = layer->name;
506
507
    mvt_layer->extent = MS_ABS(atoi(mvt_extent));
508
    mvt_layer->has_extent = 1;
509
510
    /* -------------------------------------------------------------------- */
511
    /*      Create appropriate attributes on this layer.                    */
512
    /* -------------------------------------------------------------------- */
513
    item_list = msGMLGetItems(layer, "G");
514
    assert(item_list->numitems == layer->numitems);
515
516
    mvt_layer->keys = msSmallMalloc(layer->numitems * sizeof(char *));
517
518
    for (i = 0; i < layer->numitems; i++) {
519
      gmlItemObj *item = item_list->items + i;
520
521
      if (!item->visible)
522
        continue;
523
524
      if (item->alias)
525
        mvt_layer->keys[mvt_layer->n_keys++] = msStrdup(item->alias);
526
      else
527
        mvt_layer->keys[mvt_layer->n_keys++] = msStrdup(item->name);
528
    }
529
530
    /* -------------------------------------------------------------------- */
531
    /*      Setup joins if needed.  This is likely untested.                */
532
    /* -------------------------------------------------------------------- */
533
    if (layer->numjoins > 0) {
534
      int j;
535
      for (j = 0; j < layer->numjoins; j++) {
536
        status = msJoinConnect(layer, &(layer->joins[j]));
537
        if (status != MS_SUCCESS) {
538
          retcode = status;
539
          goto layer_cleanup;
540
        }
541
      }
542
    }
543
544
    /* -------------------------------------------------------------------- */
545
    /*      Setup classgroup if needed.                                     */
546
    /* -------------------------------------------------------------------- */
547
    if (layer->classgroup && layer->numclasses > 0)
548
      classgroup = msAllocateValidClassGroups(layer, &nclasses);
549
550
    mvt_layer->features = msSmallCalloc(FEATURES_INCREMENT_SIZE,
551
                                        sizeof(VectorTile__Tile__Feature *));
552
    features_size = FEATURES_INCREMENT_SIZE;
553
554
    msInitShape(&shape);
555
    i = 0;
556
    for (;;) {
557
      if (layer->resultcache) {
558
        status = (i < layer->resultcache->numresults)
559
                     ? msLayerGetShape(layer, &shape,
560
                                       &(layer->resultcache->results[i]))
561
                     : MS_DONE;
562
        i++;
563
      } else {
564
        status = msLayerNextShape(layer, &shape);
565
      }
566
567
      if (status != MS_SUCCESS)
568
        goto feature_cleanup;
569
570
      if (layer->numclasses > 0) {
571
        /* Should be equivalent to shape.classindex =
572
         * layer->resultcache->results[i].classindex; */
573
        shape.classindex =
574
            msShapeGetClass(layer, map, &shape, classgroup,
575
                            nclasses); /* Perform classification, and some
576
                                          annotation related magic. */
577
578
        if (shape.classindex < 0)
579
          goto feature_cleanup; /* no matching CLASS found, skip this feature */
580
      }
581
582
      /*
583
      ** prepare any necessary JOINs here (one-to-one only)
584
      */
585
      if (layer->numjoins > 0) {
586
        int j;
587
588
        for (j = 0; j < layer->numjoins; j++) {
589
          if (layer->joins[j].type == MS_JOIN_ONE_TO_ONE) {
590
            msJoinPrepare(&(layer->joins[j]), &shape);
591
            msJoinNext(&(layer->joins[j])); /* fetch the first row */
592
          }
593
        }
594
      }
595
596
      if (mvt_layer->n_features ==
597
          features_size) { /* need to allocate more space */
598
        features_size += FEATURES_INCREMENT_SIZE;
599
        mvt_layer->features = msSmallRealloc(
600
            mvt_layer->features,
601
            sizeof(VectorTile__Tile__Feature *) * (features_size));
602
      }
603
604
      if (layer->project) {
605
        if (layer->reprojectorLayerToMap == NULL) {
606
          layer->reprojectorLayerToMap =
607
              msProjectCreateReprojector(&layer->projection, &map->projection);
608
        }
609
        if (layer->reprojectorLayerToMap)
610
          status = msProjectShapeEx(layer->reprojectorLayerToMap, &shape);
611
        else
612
          status = MS_FAILURE;
613
      }
614
      if (status == MS_SUCCESS) {
615
        status = mvtWriteShape(layer, &shape, mvt_layer, item_list,
616
                               &value_lookup_cache, &map->extent, buffer);
617
      }
618
619
    feature_cleanup:
620
      msFreeShape(&shape);
621
      if (status != MS_SUCCESS)
622
        goto layer_cleanup;
623
    } /* next shape */
624
  layer_cleanup:
625
    if (classgroup)
626
      msFree(classgroup);
627
    msLayerClose(layer);
628
    msGMLFreeItems(item_list);
629
    UT_HASH_ITER(hh, value_lookup_cache.cache, cur_value_lookup,
630
                 tmp_value_lookup) {
631
      msFree(cur_value_lookup->value);
632
      UT_HASH_DEL(value_lookup_cache.cache, cur_value_lookup);
633
      msFree(cur_value_lookup);
634
    }
635
    if (retcode != MS_SUCCESS)
636
      goto cleanup;
637
  } /* next layer */
638
639
  len = vector_tile__tile__get_packed_size(
640
      &mvt_tile); // This is the calculated packing length
641
642
  buf = msSmallMalloc(len); // Allocate memory
643
  vector_tile__tile__pack(&mvt_tile, buf);
644
  if (sendheaders) {
645
    msIO_fprintf(stdout,
646
                 "Content-Length: %d\r\n"
647
                 "Content-Type: %s\r\n\r\n",
648
                 len, MS_IMAGE_MIME_TYPE(map->outputformat));
649
  }
650
  msIO_fwrite(buf, len, 1, stdout);
651
  msFree(buf);
652
653
cleanup:
654
  freeMvtTile(&mvt_tile);
655
656
  return retcode;
657
}
658
659
int msPopulateRendererVTableMVT(rendererVTableObj *renderer) {
660
  (void)renderer;
661
  return MS_SUCCESS;
662
}
663
#else
664
0
int msPopulateRendererVTableMVT(rendererVTableObj *renderer) {
665
0
  (void)renderer;
666
0
  msSetError(MS_MISCERR,
667
0
             "Vector Tile Driver requested but support is not compiled in",
668
0
             "msPopulateRendererVTableMVT()");
669
0
  return MS_FAILURE;
670
0
}
671
672
0
int msMVTWriteTile(mapObj *map, int sendheaders) {
673
0
  (void)map;
674
0
  (void)sendheaders;
675
0
  msSetError(MS_MISCERR, "Vector Tile support is not available.",
676
0
             "msMVTWriteTile()");
677
0
  return MS_FAILURE;
678
0
}
679
#endif