Coverage Report

Created: 2025-08-28 06:57

/src/MapServer/src/maptile.c
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  MapServer Tile Access API
6
 * Author:   Paul Ramsey <pramsey@cleverelephant.ca>
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2008, Paul Ramsey
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 "maptile.h"
31
#include "mapproject.h"
32
33
#ifdef USE_TILE_API
34
0
static void msTileResetMetatileLevel(mapObj *map) {
35
0
  hashTableObj *meta = &(map->web.metadata);
36
0
  const char *zero = "0";
37
38
  /*  Is the tile_metatile_levetl set... */
39
0
  if (msLookupHashTable(meta, "tile_metatile_level") != NULL) {
40
0
    msRemoveHashTable(meta, "tile_metatile_level");
41
0
  }
42
0
  msInsertHashTable(meta, "tile_metatile_level", zero);
43
0
}
44
#endif
45
46
/************************************************************************
47
 *                            msTileGetGMapCoords                       *
48
 ************************************************************************/
49
static int msTileGetGMapCoords(const char *coordstring, int *x, int *y,
50
0
                               int *zoom) {
51
52
0
  int num_coords = 0;
53
0
  char **coords = NULL;
54
55
0
  if (coordstring) {
56
0
    coords = msStringSplit(coordstring, ' ', &(num_coords));
57
0
    if (num_coords != 3) {
58
0
      msFreeCharArray(coords, num_coords);
59
0
      msSetError(MS_WEBERR,
60
0
                 "Invalid number of tile coordinates (should be three).",
61
0
                 "msTileSetup()");
62
0
      return MS_FAILURE;
63
0
    }
64
0
  } else {
65
0
    msSetError(MS_WEBERR, "Tile parameter not set.", "msTileSetup()");
66
0
    return MS_FAILURE;
67
0
  }
68
69
0
  if (x)
70
0
    *x = strtol(coords[0], NULL, 10);
71
0
  if (y)
72
0
    *y = strtol(coords[1], NULL, 10);
73
0
  if (zoom)
74
0
    *zoom = strtol(coords[2], NULL, 10);
75
76
0
  msFreeCharArray(coords, 3);
77
0
  return MS_SUCCESS;
78
0
}
79
80
/************************************************************************
81
 *                            msTileSetParams                           *
82
 ************************************************************************/
83
0
static void msTileGetParams(const mapservObj *msObj, tileParams *params) {
84
85
0
  const char *value = NULL;
86
0
  const mapObj *map = msObj->map;
87
0
  const hashTableObj *meta = &(map->web.metadata);
88
89
0
  if (msObj->TileWidth < 0)
90
0
    params->tile_width = SPHEREMERC_IMAGE_SIZE;
91
0
  else
92
0
    params->tile_width = msObj->TileWidth;
93
0
  if (msObj->TileHeight < 0)
94
0
    params->tile_height = SPHEREMERC_IMAGE_SIZE;
95
0
  else
96
0
    params->tile_height = msObj->TileHeight;
97
98
  /* Check for tile buffer, set to buffer==0 as default */
99
0
  if ((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
100
0
    params->map_edge_buffer = atoi(value);
101
0
    if (map->debug)
102
0
      msDebug("msTileSetParams(): tile_map_edge_buffer = %d\n",
103
0
              params->map_edge_buffer);
104
0
  } else
105
0
    params->map_edge_buffer = 0;
106
107
  /* Check for metatile size, set to tile==metatile as default */
108
0
  if ((value = msLookupHashTable(meta, "tile_metatile_level")) != NULL) {
109
0
    params->metatile_level = atoi(value);
110
    /* Quietly force metatile_level to be sane */
111
0
    if (params->metatile_level < 0)
112
0
      params->metatile_level = 0;
113
0
    if (params->metatile_level > 2)
114
0
      params->metatile_level = 2;
115
0
    if (map->debug)
116
0
      msDebug("msTileSetParams(): tile_metatile_level = %d\n",
117
0
              params->metatile_level);
118
0
  } else
119
0
    params->metatile_level = 0;
120
0
}
121
122
/************************************************************************
123
 *                            msTileExtractSubTile                      *
124
 *                                                                      *
125
 ************************************************************************/
126
static imageObj *msTileExtractSubTile(const mapservObj *msObj,
127
0
                                      const imageObj *img) {
128
129
0
  int width, mini, minj;
130
0
  int zoom = 2;
131
0
  imageObj *imgOut = NULL;
132
0
  tileParams params;
133
0
  rendererVTableObj *renderer;
134
0
  rasterBufferObj imgBuffer;
135
136
0
  if (!MS_RENDERER_PLUGIN(msObj->map->outputformat) ||
137
0
      msObj->map->outputformat->renderer != img->format->renderer ||
138
0
      !MS_MAP_RENDERER(msObj->map)->supports_pixel_buffer) {
139
0
    msSetError(MS_MISCERR, "unsupported or mixed renderers",
140
0
               "msTileExtractSubTile()");
141
0
    return NULL;
142
0
  }
143
0
  renderer = MS_MAP_RENDERER(msObj->map);
144
145
0
  if (renderer->getRasterBufferHandle((imageObj *)img, &imgBuffer) !=
146
0
      MS_SUCCESS) {
147
0
    return NULL;
148
0
  }
149
150
  /*
151
  ** Load the metatiling information from the map file.
152
  */
153
0
  msTileGetParams(msObj, &params);
154
155
  /*
156
  ** Initialize values for the metatile clip area.
157
  */
158
0
  width = img->width - 2 * params.map_edge_buffer;
159
0
  mini = params.map_edge_buffer;
160
0
  minj = params.map_edge_buffer;
161
162
0
  if (msObj->TileMode == TILE_GMAP) {
163
0
    int x, y, zoom;
164
165
0
    if (msObj->TileCoords) {
166
0
      if (msTileGetGMapCoords(msObj->TileCoords, &x, &y, &zoom) == MS_FAILURE)
167
0
        return NULL;
168
0
    } else {
169
0
      msSetError(MS_WEBERR, "Tile parameter not set.", "msTileSetup()");
170
0
      return NULL;
171
0
    }
172
173
0
    if (msObj->map->debug)
174
0
      msDebug("msTileExtractSubTile(): gmaps coords (x: %d, y: %d)\n", x, y);
175
176
    /*
177
    ** The bottom N bits of the coordinates give us the subtile
178
    ** location relative to the metatile.
179
    */
180
0
    x = (0xffff ^ (0xffff << params.metatile_level)) & x;
181
0
    y = (0xffff ^ (0xffff << params.metatile_level)) & y;
182
183
0
    if (msObj->map->debug)
184
0
      msDebug("msTileExtractSubTile(): gmaps image coords (x: %d, y: %d)\n", x,
185
0
              y);
186
187
0
    mini = mini + x * params.tile_width;
188
0
    minj = minj + y * params.tile_height;
189
190
0
  } else if (msObj->TileMode == TILE_VE) {
191
0
    int tsize;
192
193
0
    const int lenTileCoords = (int)strlen(msObj->TileCoords);
194
0
    if (lenTileCoords - params.metatile_level < 0) {
195
0
      return (NULL);
196
0
    }
197
198
    /*
199
    ** Process the last elements of the VE coordinate string to place the
200
    ** requested tile in the context of the metatile
201
    */
202
0
    for (int i = lenTileCoords - params.metatile_level; i < lenTileCoords;
203
0
         i++) {
204
0
      char j = msObj->TileCoords[i];
205
0
      tsize = width / zoom;
206
0
      if (j == '1' || j == '3')
207
0
        mini += tsize;
208
0
      if (j == '2' || j == '3')
209
0
        minj += tsize;
210
0
      zoom *= 2;
211
0
    }
212
0
  } else {
213
0
    return (NULL); /* Huh? Should have a mode. */
214
0
  }
215
216
0
  imgOut = msImageCreate(
217
0
      params.tile_width, params.tile_height, msObj->map->outputformat, NULL,
218
0
      NULL, msObj->map->resolution, msObj->map->defresolution, NULL);
219
220
0
  if (imgOut == NULL) {
221
0
    return NULL;
222
0
  }
223
224
0
  if (msObj->map->debug)
225
0
    msDebug("msTileExtractSubTile(): extracting (%d x %d) tile, top corner "
226
0
            "(%d, %d)\n",
227
0
            params.tile_width, params.tile_height, mini, minj);
228
229
0
  if (MS_UNLIKELY(MS_FAILURE == renderer->mergeRasterBuffer(
230
0
                                    imgOut, &imgBuffer, 1.0, mini, minj, 0, 0,
231
0
                                    params.tile_width, params.tile_height))) {
232
0
    msFreeImage(imgOut);
233
0
    return NULL;
234
0
  }
235
236
0
  return imgOut;
237
0
}
238
239
/************************************************************************
240
 *                            msTileSetup                               *
241
 *                                                                      *
242
 *  Called from mapserv.c, this is where the fun begins                 *
243
 *  Set up projections and test the parameters for legality.            *
244
 ************************************************************************/
245
0
int msTileSetup(mapservObj *msObj) {
246
0
#ifdef USE_TILE_API
247
248
0
  char *outProjStr = NULL;
249
0
  tileParams params;
250
251
  /*
252
  ** Load the metatiling information from the map file.
253
  */
254
0
  msTileGetParams(msObj, &params);
255
256
  /*
257
  ** Ensure all the LAYERs have a projection.
258
  */
259
0
  if (msMapSetLayerProjections(msObj->map) != 0) {
260
0
    return (MS_FAILURE);
261
0
  }
262
263
  /*
264
  ** Set the projection string for this mode.
265
  */
266
0
  if (msObj->TileMode == TILE_GMAP || msObj->TileMode == TILE_VE) {
267
0
    outProjStr = SPHEREMERC_PROJ4;
268
0
  } else {
269
0
    return MS_FAILURE; /* Huh? No mode? */
270
0
  }
271
0
  if (msLoadProjectionString(&(msObj->map->projection), outProjStr) != 0) {
272
0
    msSetError(MS_CGIERR, "Unable to load projection string.", "msTileSetup()");
273
0
    return MS_FAILURE;
274
0
  }
275
276
  /*
277
  ** Set up the output extents for this tilemode and tile coordinates
278
  */
279
0
  if (msObj->TileMode == TILE_GMAP) {
280
281
0
    int x, y, zoom;
282
0
    double zoomfactor;
283
284
0
    if (msObj->TileCoords) {
285
0
      if (msTileGetGMapCoords(msObj->TileCoords, &x, &y, &zoom) == MS_FAILURE)
286
0
        return MS_FAILURE;
287
0
    } else {
288
0
      msSetError(MS_WEBERR, "Tile parameter not set.", "msTileSetup()");
289
0
      return MS_FAILURE;
290
0
    }
291
292
0
    if (params.metatile_level >= zoom) {
293
0
      msTileResetMetatileLevel(msObj->map);
294
0
    }
295
296
0
    zoomfactor = pow(2.0, (double)zoom);
297
298
    /*
299
    ** Check the input request for sanity.
300
    */
301
0
    if (x >= zoomfactor || y >= zoomfactor) {
302
0
      msSetError(MS_CGIERR,
303
0
                 "GMap tile coordinates are too large for supplied zoom.",
304
0
                 "msTileSetup()");
305
0
      return (MS_FAILURE);
306
0
    }
307
0
    if (x < 0 || y < 0) {
308
0
      msSetError(MS_CGIERR,
309
0
                 "GMap tile coordinates should not be less than zero.",
310
0
                 "msTileSetup()");
311
0
      return (MS_FAILURE);
312
0
    }
313
314
0
  } else if (msObj->TileMode == TILE_VE) {
315
316
0
    if (strspn(msObj->TileCoords, "0123") < strlen(msObj->TileCoords)) {
317
0
      msSetError(MS_CGIERR,
318
0
                 "VE tile name should only include characters 0, 1, 2 and 3.",
319
0
                 "msTileSetup()");
320
0
      return (MS_FAILURE);
321
0
    }
322
323
0
    if (params.metatile_level >= (int)strlen(msObj->TileCoords)) {
324
0
      msTileResetMetatileLevel(msObj->map);
325
0
    }
326
327
0
  } else {
328
0
    return (MS_FAILURE); /* Huh? Should have a mode. */
329
0
  }
330
331
0
  return MS_SUCCESS;
332
#else
333
  msSetError(MS_CGIERR, "Tile API is not available.", "msTileSetup()");
334
  return (MS_FAILURE);
335
#endif
336
0
}
337
338
/************************************************************************
339
 *                            msTileSetExtent                           *
340
 *                                                                      *
341
 *  Based on the input parameters, set the output extent for this       *
342
 *  tile.                                                               *
343
 ************************************************************************/
344
0
int msTileSetExtent(mapservObj *msObj) {
345
0
#ifdef USE_TILE_API
346
347
0
  mapObj *map = msObj->map;
348
0
  double dx, dy, buffer;
349
0
  tileParams params;
350
351
  /* Read the tile-mode map file parameters */
352
0
  msTileGetParams(msObj, &params);
353
354
0
  if (msObj->TileMode == TILE_GMAP) {
355
0
    int x, y, zoom;
356
0
    double zoomfactor, tilesize, xmin, xmax, ymin, ymax;
357
358
0
    if (msObj->TileCoords) {
359
0
      if (msTileGetGMapCoords(msObj->TileCoords, &x, &y, &zoom) == MS_FAILURE)
360
0
        return MS_FAILURE;
361
0
    } else {
362
0
      msSetError(MS_WEBERR, "Tile parameter not set.", "msTileSetup()");
363
0
      return MS_FAILURE;
364
0
    }
365
366
0
    if (map->debug)
367
0
      msDebug("msTileSetExtent(): gmaps coords (x: %d, y: %d, z: %d)\n", x, y,
368
0
              zoom);
369
370
    /*
371
    ** If we are metatiling, adjust the zoom level appropriately,
372
    ** then scale back the x/y coordinates to match the new level.
373
    */
374
0
    if (params.metatile_level > 0) {
375
0
      zoom = zoom - params.metatile_level;
376
0
      x = x >> params.metatile_level;
377
0
      y = y >> params.metatile_level;
378
0
    }
379
380
0
    if (map->debug)
381
0
      msDebug("msTileSetExtent(): gmaps metacoords (x: %d, y: %d, z: %d)\n", x,
382
0
              y, zoom);
383
384
0
    zoomfactor = pow(2.0, (double)zoom);
385
386
    /*
387
    ** Calculate the ground extents of the tile request.
388
    */
389
    /* printf("X: %i  Y: %i  Z: %i\n",x,y,zoom); */
390
0
    tilesize = SPHEREMERC_GROUND_SIZE / zoomfactor;
391
0
    xmin = (x * tilesize) - (SPHEREMERC_GROUND_SIZE / 2.0);
392
0
    xmax = ((x + 1) * tilesize) - (SPHEREMERC_GROUND_SIZE / 2.0);
393
0
    ymin = (SPHEREMERC_GROUND_SIZE / 2.0) - ((y + 1) * tilesize);
394
0
    ymax = (SPHEREMERC_GROUND_SIZE / 2.0) - (y * tilesize);
395
396
0
    map->extent.minx = xmin;
397
0
    map->extent.maxx = xmax;
398
0
    map->extent.miny = ymin;
399
0
    map->extent.maxy = ymax;
400
401
0
  } else if (msObj->TileMode == TILE_VE) {
402
403
0
    double minx = SPHEREMERC_GROUND_SIZE / -2.0;
404
0
    double miny = SPHEREMERC_GROUND_SIZE / -2.0;
405
0
    double maxx = SPHEREMERC_GROUND_SIZE / 2.0;
406
0
    double maxy = SPHEREMERC_GROUND_SIZE / 2.0;
407
0
    double zoom = 2.0;
408
0
    double tsize;
409
410
    /*
411
    ** Walk down the VE URL string, adjusting the extent each time.
412
    ** For meta-tiling cases, we stop early, to draw a larger image.
413
    */
414
0
    for (int i = 0; i < (int)strlen(msObj->TileCoords) - params.metatile_level;
415
0
         i++) {
416
0
      char j = msObj->TileCoords[i];
417
0
      tsize = SPHEREMERC_GROUND_SIZE / zoom;
418
0
      if (j == '1' || j == '3')
419
0
        minx += tsize;
420
0
      if (j == '0' || j == '2')
421
0
        maxx -= tsize;
422
0
      if (j == '2' || j == '3')
423
0
        maxy -= tsize;
424
0
      if (j == '0' || j == '1')
425
0
        miny += tsize;
426
0
      zoom *= 2.0;
427
0
    }
428
429
0
    map->extent.minx = minx;
430
0
    map->extent.maxx = maxx;
431
0
    map->extent.miny = miny;
432
0
    map->extent.maxy = maxy;
433
434
0
  } else {
435
0
    return (MS_FAILURE); /* Huh? Should have a mode. */
436
0
  }
437
438
  /*
439
  ** Set the output tile size.
440
  */
441
0
  map->width = params.tile_width << params.metatile_level;
442
0
  map->height = params.tile_height << params.metatile_level;
443
444
0
  if (map->debug)
445
0
    msDebug("msTileSetExtent(): base image size (%d x %d)\n", map->width,
446
0
            map->height);
447
448
  /*
449
  ** Add the gutters
450
  ** First calculate ground units in the buffer at current extent
451
  */
452
0
  buffer = params.map_edge_buffer * (map->extent.maxx - map->extent.minx) /
453
0
           (double)map->width;
454
  /*
455
  ** Then adjust the map extents out by that amount
456
  */
457
0
  map->extent.minx -= buffer;
458
0
  map->extent.maxx += buffer;
459
0
  map->extent.miny -= buffer;
460
0
  map->extent.maxy += buffer;
461
  /*
462
  ** Finally adjust the map image size by the pixel buffer
463
  */
464
0
  map->width += 2 * params.map_edge_buffer;
465
0
  map->height += 2 * params.map_edge_buffer;
466
467
0
  if (map->debug)
468
0
    msDebug("msTileSetExtent(): buffered image size (%d x %d)\n", map->width,
469
0
            map->height);
470
471
  /*
472
  ** Adjust the extents inwards by 1/2 pixel so they are from
473
  ** center-of-pixel to center-of-pixel, instead of edge-to-edge.
474
  ** This is the way mapserver does it.
475
  */
476
0
  dx = (map->extent.maxx - map->extent.minx) / map->width;
477
0
  map->extent.minx += dx * 0.5;
478
0
  map->extent.maxx -= dx * 0.5;
479
0
  dy = (map->extent.maxy - map->extent.miny) / map->height;
480
0
  map->extent.miny += dy * 0.5;
481
0
  map->extent.maxy -= dy * 0.5;
482
483
  /*
484
  ** Ensure the labelcache buffer is greater than the tile buffer.
485
  */
486
0
  if (params.map_edge_buffer > 0) {
487
0
    const char *value;
488
0
    hashTableObj *meta = &(map->web.metadata);
489
0
    char tilebufferstr[64];
490
491
    /* Write the tile buffer to a string */
492
0
    snprintf(tilebufferstr, sizeof(tilebufferstr), "-%d",
493
0
             params.map_edge_buffer);
494
495
    /* Hm, the labelcache buffer is set... */
496
0
    if ((value = msLookupHashTable(meta, "labelcache_map_edge_buffer")) !=
497
0
        NULL) {
498
      /* If it's too small, replace with a bigger one */
499
0
      if (params.map_edge_buffer > abs(atoi(value))) {
500
0
        msRemoveHashTable(meta, "labelcache_map_edge_buffer");
501
0
        msInsertHashTable(meta, "labelcache_map_edge_buffer", tilebufferstr);
502
0
      }
503
0
    }
504
    /* No labelcache buffer value? Then we use the tile buffer. */
505
0
    else {
506
0
      msInsertHashTable(meta, "labelcache_map_edge_buffer", tilebufferstr);
507
0
    }
508
0
  }
509
510
0
  if (map->debug) {
511
0
    msDebug("msTileSetExtent (%f, %f) (%f, %f)\n", map->extent.minx,
512
0
            map->extent.miny, map->extent.maxx, map->extent.maxy);
513
0
  }
514
515
0
  map->units = MS_METERS; // now the units are meters
516
517
0
  return MS_SUCCESS;
518
#else
519
  msSetError(MS_CGIERR, "Tile API is not available.", "msTileSetExtent()");
520
  return (MS_FAILURE);
521
#endif
522
0
}
523
524
/************************************************************************
525
 *                            msDrawTile                                *
526
 *                                                                      *
527
 *   Draw the tile once with gutters, metatiling and buffers, then      *
528
 *   clip out the final tile.                                           *
529
 *   WARNING: Call msTileSetExtent() first or this will be a pointless  *
530
 *   fucnction call.                                                    *
531
 ************************************************************************/
532
533
0
imageObj *msTileDraw(mapservObj *msObj) {
534
0
  imageObj *img;
535
0
  tileParams params;
536
0
  msTileGetParams(msObj, &params);
537
0
  img = msDrawMap(msObj->map, MS_FALSE);
538
0
  if (img == NULL)
539
0
    return NULL;
540
0
  if (params.metatile_level > 0 || params.map_edge_buffer > 0) {
541
0
    imageObj *tmp = msTileExtractSubTile(msObj, img);
542
0
    msFreeImage(img);
543
0
    if (tmp == NULL)
544
0
      return NULL;
545
0
    img = tmp;
546
0
  }
547
0
  return img;
548
0
}