Coverage Report

Created: 2025-11-16 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/MapServer/src/mapcontour.c
Line
Count
Source
1
/**********************************************************************
2
 * $Id: mapcontour.c 12629 2011-10-06 18:06:34Z aboudreault $
3
 *
4
 * Project:  MapServer
5
 * Purpose:  Contour Layer
6
 * Author:   Alan Boudreault (aboudreault@mapgears.com)
7
 * Author:   Daniel Morissette (dmorissette@mapgears.com)
8
 *
9
 **********************************************************************
10
 * Copyright (c) 2011, Alan Boudreault, MapGears
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a
13
 * copy of this software and associated documentation files (the "Software"),
14
 * to deal in the Software without restriction, including without limitation
15
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16
 * and/or sell copies of the Software, and to permit persons to whom the
17
 * Software is furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in
20
 * all copies of this Software or works derived from this Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
25
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28
 * DEALINGS IN THE SOFTWARE.
29
 **********************************************************************/
30
31
#include "mapserver.h"
32
#include "mapcopy.h"
33
#include "mapresample.h"
34
35
#include "ogr_api.h"
36
#include "ogr_srs_api.h"
37
#include "gdal.h"
38
#include "gdal_alg.h"
39
40
#include "mapows.h"
41
#include "mapthread.h"
42
#include "mapraster.h"
43
#include "cpl_string.h"
44
45
0
#define GEO_TRANS(tr, x, y) ((tr)[0] + (tr)[1] * (x) + (tr)[2] * (y))
46
47
extern int InvGeoTransform(double *gt_in, double *gt_out);
48
49
typedef struct {
50
51
  /* OGR DataSource */
52
  layerObj ogrLayer;
53
54
  /* internal use */
55
  GDALDatasetH hOrigDS;
56
  GDALDatasetH hDS;
57
  double *buffer; /* memory dataset buffer */
58
  rectObj extent; /* original dataset extent */
59
  OGRDataSourceH hOGRDS;
60
  double cellsize;
61
62
} contourLayerInfo;
63
64
0
static int msContourLayerInitItemInfo(layerObj *layer) {
65
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
66
67
0
  if (clinfo == NULL) {
68
0
    msSetError(MS_MISCERR, "Assertion failed: Contour layer not opened!!!",
69
0
               "msContourLayerInitItemInfo()");
70
0
    return MS_FAILURE;
71
0
  }
72
73
0
  return msLayerInitItemInfo(&clinfo->ogrLayer);
74
0
}
75
76
0
static void msContourLayerFreeItemInfo(layerObj *layer) {
77
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
78
79
0
  if (clinfo == NULL) {
80
0
    msSetError(MS_MISCERR, "Assertion failed: Contour layer not opened!!!",
81
0
               "msContourLayerFreeItemInfo()");
82
0
    return;
83
0
  }
84
85
0
  msLayerFreeItemInfo(&clinfo->ogrLayer);
86
0
}
87
88
0
static void msContourLayerInfoInitialize(layerObj *layer) {
89
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
90
91
0
  if (clinfo != NULL)
92
0
    return;
93
94
0
  clinfo = (contourLayerInfo *)msSmallCalloc(1, sizeof(contourLayerInfo));
95
0
  layer->layerinfo = clinfo;
96
0
  clinfo->hOrigDS = NULL;
97
0
  clinfo->hDS = NULL;
98
0
  clinfo->extent.minx = -1.0;
99
0
  clinfo->extent.miny = -1.0;
100
0
  clinfo->extent.maxx = -1.0;
101
0
  clinfo->extent.maxy = -1.0;
102
103
0
  initLayer(&clinfo->ogrLayer, layer->map);
104
0
  clinfo->ogrLayer.type = layer->type;
105
0
  clinfo->ogrLayer.debug = layer->debug;
106
0
  clinfo->ogrLayer.connectiontype = MS_OGR;
107
0
  clinfo->ogrLayer.name = msStrdup(layer->name);
108
0
  clinfo->ogrLayer.connection =
109
0
      (char *)msSmallMalloc(strlen(clinfo->ogrLayer.name) + 13);
110
0
  sprintf(clinfo->ogrLayer.connection, "__%s_CONTOUR__", clinfo->ogrLayer.name);
111
0
  clinfo->ogrLayer.units = layer->units;
112
113
0
  if (msOWSLookupMetadata(&(layer->metadata), "OFG", "ID_type") == NULL) {
114
0
    msInsertHashTable(&(layer->metadata), "gml_ID_type", "Integer");
115
0
  }
116
0
  {
117
0
    const char *elevItem = CSLFetchNameValue(layer->processing, "CONTOUR_ITEM");
118
0
    if (elevItem && strlen(elevItem) > 0) {
119
0
      char szTmp[100];
120
0
      snprintf(szTmp, sizeof(szTmp), "%s_type", elevItem);
121
0
      if (msOWSLookupMetadata(&(layer->metadata), "OFG", szTmp) == NULL) {
122
0
        snprintf(szTmp, sizeof(szTmp), "gml_%s_type", elevItem);
123
0
        msInsertHashTable(&(layer->metadata), szTmp, "Real");
124
0
      }
125
0
    }
126
0
  }
127
0
}
128
129
0
static void msContourLayerInfoFree(layerObj *layer) {
130
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
131
132
0
  if (clinfo == NULL)
133
0
    return;
134
135
0
  freeLayer(&clinfo->ogrLayer);
136
0
  free(clinfo);
137
138
0
  layer->layerinfo = NULL;
139
0
}
140
141
0
static int msContourLayerReadRaster(layerObj *layer, rectObj rect) {
142
0
  mapObj *map = layer->map;
143
0
  char **bands;
144
0
  int band = 1;
145
0
  double adfGeoTransform[6], adfInvGeoTransform[6];
146
0
  double llx, lly, urx, ury;
147
0
  rectObj copyRect, mapRect;
148
0
  int dst_xsize, dst_ysize;
149
0
  int virtual_grid_step_x, virtual_grid_step_y;
150
0
  int src_xoff, src_yoff, src_xsize, src_ysize;
151
0
  double map_cellsize_x, map_cellsize_y, dst_cellsize_x, dst_cellsize_y;
152
0
  GDALRasterBandH hBand = NULL;
153
0
  CPLErr eErr;
154
155
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
156
157
0
  if (layer->debug)
158
0
    msDebug("Entering msContourLayerReadRaster().\n");
159
160
0
  if (clinfo == NULL || clinfo->hOrigDS == NULL) {
161
0
    msSetError(MS_MISCERR, "Assertion failed: Contour layer not opened!!!",
162
0
               "msContourLayerReadRaster()");
163
0
    return MS_FAILURE;
164
0
  }
165
166
0
  bands = CSLTokenizeStringComplex(
167
0
      CSLFetchNameValue(layer->processing, "BANDS"), " ,", FALSE, FALSE);
168
0
  if (CSLCount(bands) > 0) {
169
0
    band = atoi(bands[0]);
170
0
    if (band < 1 || band > GDALGetRasterCount(clinfo->hOrigDS)) {
171
0
      msSetError(MS_IMGERR,
172
0
                 "BANDS PROCESSING directive includes illegal band '%d', "
173
0
                 "should be from 1 to %d.",
174
0
                 "msContourLayerReadRaster()", band,
175
0
                 GDALGetRasterCount(clinfo->hOrigDS));
176
0
      CSLDestroy(bands);
177
0
      return MS_FAILURE;
178
0
    }
179
0
  }
180
0
  CSLDestroy(bands);
181
182
0
  hBand = GDALGetRasterBand(clinfo->hOrigDS, band);
183
0
  if (hBand == NULL) {
184
0
    msSetError(MS_IMGERR, "Band %d does not exist on dataset.",
185
0
               "msContourLayerReadRaster()", band);
186
0
    return MS_FAILURE;
187
0
  }
188
189
0
  if (layer->projection.numargs > 0 &&
190
0
      EQUAL(layer->projection.args[0], "auto")) {
191
0
    const char *wkt;
192
0
    wkt = GDALGetProjectionRef(clinfo->hOrigDS);
193
0
    if (wkt != NULL && strlen(wkt) > 0) {
194
0
      if (msOGCWKT2ProjectionObj(wkt, &(layer->projection), layer->debug) !=
195
0
          MS_SUCCESS) {
196
0
        char msg[MESSAGELENGTH * 2];
197
0
        errorObj *ms_error = msGetErrorObj();
198
199
0
        snprintf(msg, sizeof(msg),
200
0
                 "%s\n"
201
0
                 "PROJECTION AUTO cannot be used for this "
202
0
                 "GDAL raster (`%s').",
203
0
                 ms_error->message, layer->data);
204
0
        msg[MESSAGELENGTH - 1] = '\0';
205
206
0
        msSetError(MS_OGRERR, "%s", "msDrawRasterLayer()", msg);
207
0
        return MS_FAILURE;
208
0
      }
209
0
    }
210
0
  }
211
212
  /*
213
   * Compute the georeferenced window of overlap, and read the source data
214
   * downsampled to match output resolution, or at full resolution if
215
   * output resolution is lower than the source resolution.
216
   *
217
   * A large portion of this overlap calculation code was borrowed from
218
   * msDrawRasterLayerGDAL().
219
   * Would be possible to move some of this to a reusable function?
220
   *
221
   * Note: This code works only if no reprojection is involved. It would
222
   * need rework to support cases where output projection differs from source
223
   * data file projection.
224
   */
225
226
0
  src_xsize = GDALGetRasterXSize(clinfo->hOrigDS);
227
0
  src_ysize = GDALGetRasterYSize(clinfo->hOrigDS);
228
229
  /* set the Dataset extent */
230
0
  msGetGDALGeoTransform(clinfo->hOrigDS, map, layer, adfGeoTransform);
231
0
  clinfo->extent.minx = adfGeoTransform[0];
232
0
  clinfo->extent.maxy = adfGeoTransform[3];
233
0
  clinfo->extent.maxx = adfGeoTransform[0] + src_xsize * adfGeoTransform[1];
234
0
  clinfo->extent.miny = adfGeoTransform[3] + src_ysize * adfGeoTransform[5];
235
236
0
  if (layer->transform) {
237
0
    if (layer->debug)
238
0
      msDebug("msContourLayerReadRaster(): Entering transform.\n");
239
240
0
    InvGeoTransform(adfGeoTransform, adfInvGeoTransform);
241
242
0
    mapRect = rect;
243
0
    if (map->cellsize == 0) {
244
0
      map->cellsize = msAdjustExtent(&mapRect, map->width, map->height);
245
0
    }
246
0
    map_cellsize_x = map_cellsize_y = map->cellsize;
247
    /* if necessary, project the searchrect to source coords */
248
0
    if (msProjectionsDiffer(&(map->projection), &(layer->projection))) {
249
0
      if (msProjectRect(&map->projection, &layer->projection, &mapRect) !=
250
0
          MS_SUCCESS) {
251
0
        msDebug("msContourLayerReadRaster(%s): unable to reproject map request "
252
0
                "rectangle into layer projection, canceling.\n",
253
0
                layer->name);
254
0
        return MS_FAILURE;
255
0
      }
256
257
0
      map_cellsize_x = MS_CELLSIZE(mapRect.minx, mapRect.maxx, map->width);
258
0
      map_cellsize_y = MS_CELLSIZE(mapRect.miny, mapRect.maxy, map->height);
259
260
      /* if the projection failed to project the extent requested, we need to
261
         calculate the cellsize to preserve the initial map cellsize ratio */
262
0
      if ((mapRect.minx < GEO_TRANS(adfGeoTransform, 0, src_ysize)) ||
263
0
          (mapRect.maxx > GEO_TRANS(adfGeoTransform, src_xsize, 0)) ||
264
0
          (mapRect.miny < GEO_TRANS(adfGeoTransform + 3, 0, src_ysize)) ||
265
0
          (mapRect.maxy > GEO_TRANS(adfGeoTransform + 3, src_xsize, 0))) {
266
267
0
        int src_unit, dst_unit;
268
0
        src_unit = GetMapserverUnitUsingProj(&map->projection);
269
0
        dst_unit = GetMapserverUnitUsingProj(&layer->projection);
270
0
        if (src_unit == -1 || dst_unit == -1) {
271
0
          msDebug("msContourLayerReadRaster(%s): unable to reproject map "
272
0
                  "request rectangle into layer projection, canceling.\n",
273
0
                  layer->name);
274
0
          return MS_FAILURE;
275
0
        }
276
277
0
        map_cellsize_x = MS_CONVERT_UNIT(
278
0
            src_unit, dst_unit, MS_CELLSIZE(rect.minx, rect.maxx, map->width));
279
0
        map_cellsize_y = MS_CONVERT_UNIT(
280
0
            src_unit, dst_unit, MS_CELLSIZE(rect.miny, rect.maxy, map->height));
281
0
      }
282
0
    }
283
284
0
    if (map_cellsize_x == 0 || map_cellsize_y == 0) {
285
0
      if (layer->debug)
286
0
        msDebug("msContourLayerReadRaster(): Cellsize can't be 0.\n");
287
0
      return MS_FAILURE;
288
0
    }
289
290
    /* Adjust MapServer pixel model to GDAL pixel model */
291
0
    mapRect.minx -= map_cellsize_x * 0.5;
292
0
    mapRect.maxx += map_cellsize_x * 0.5;
293
0
    mapRect.miny -= map_cellsize_y * 0.5;
294
0
    mapRect.maxy += map_cellsize_y * 0.5;
295
296
    /*
297
     * If raw data cellsize (from geotransform) is larger than output
298
     * map_cellsize then we want to extract only enough data to match the output
299
     * map resolution which means that GDAL will automatically sample the data
300
     * on read.
301
     *
302
     * To prevent bad contour effects on tile edges, we adjust the target
303
     * cellsize to align the extracted window with a virtual grid based on the
304
     * origin of the raw data and a virtual grid step size corresponding to an
305
     * integer sampling step.
306
     *
307
     * If source data has a greater cellsize (i.e. lower res) that requested
308
     * output map then we use the raw data cellsize as target cellsize since
309
     * there is no point in interpolating the data for contours in this case.
310
     */
311
312
0
    virtual_grid_step_x = (int)floor(map_cellsize_x / ABS(adfGeoTransform[1]));
313
0
    if (virtual_grid_step_x < 1)
314
0
      virtual_grid_step_x =
315
0
          1; /* Do not interpolate data if grid sampling step < 1 */
316
317
0
    virtual_grid_step_y = (int)floor(map_cellsize_y / ABS(adfGeoTransform[5]));
318
0
    if (virtual_grid_step_y < 1)
319
0
      virtual_grid_step_y =
320
0
          1; /* Do not interpolate data if grid sampling step < 1 */
321
322
    /* target cellsize is a multiple of raw data cellsize based on grid step*/
323
0
    dst_cellsize_x = ABS(adfGeoTransform[1]) * virtual_grid_step_x;
324
0
    dst_cellsize_y = ABS(adfGeoTransform[5]) * virtual_grid_step_y;
325
326
    /* Compute overlap between source and target views */
327
328
0
    copyRect = mapRect;
329
330
0
    if (copyRect.minx < GEO_TRANS(adfGeoTransform, 0, src_ysize))
331
0
      copyRect.minx = GEO_TRANS(adfGeoTransform, 0, src_ysize);
332
0
    if (copyRect.maxx > GEO_TRANS(adfGeoTransform, src_xsize, 0))
333
0
      copyRect.maxx = GEO_TRANS(adfGeoTransform, src_xsize, 0);
334
0
    if (copyRect.miny < GEO_TRANS(adfGeoTransform + 3, 0, src_ysize))
335
0
      copyRect.miny = GEO_TRANS(adfGeoTransform + 3, 0, src_ysize);
336
0
    if (copyRect.maxy > GEO_TRANS(adfGeoTransform + 3, src_xsize, 0))
337
0
      copyRect.maxy = GEO_TRANS(adfGeoTransform + 3, src_xsize, 0);
338
339
0
    if (copyRect.minx >= copyRect.maxx || copyRect.miny >= copyRect.maxy) {
340
0
      if (layer->debug)
341
0
        msDebug("msContourLayerReadRaster(): No overlap.\n");
342
0
      return MS_SUCCESS;
343
0
    }
344
345
    /*
346
     * Convert extraction window to raster coordinates
347
     */
348
0
    llx = GEO_TRANS(adfInvGeoTransform + 0, copyRect.minx, copyRect.miny);
349
0
    lly = GEO_TRANS(adfInvGeoTransform + 3, copyRect.minx, copyRect.miny);
350
0
    urx = GEO_TRANS(adfInvGeoTransform + 0, copyRect.maxx, copyRect.maxy);
351
0
    ury = GEO_TRANS(adfInvGeoTransform + 3, copyRect.maxx, copyRect.maxy);
352
353
    /*
354
     * Align extraction window with virtual grid
355
     * (keep in mind raster coordinates origin is at upper-left)
356
     * We also add an extra buffer to fix tile boundarie issues when zoomed
357
     */
358
0
    llx = floor(llx / virtual_grid_step_x) * virtual_grid_step_x -
359
0
          (virtual_grid_step_x * 5);
360
0
    urx = ceil(urx / virtual_grid_step_x) * virtual_grid_step_x +
361
0
          (virtual_grid_step_x * 5);
362
0
    ury = floor(ury / virtual_grid_step_y) * virtual_grid_step_y -
363
0
          (virtual_grid_step_x * 5);
364
0
    lly = ceil(lly / virtual_grid_step_y) * virtual_grid_step_y +
365
0
          (virtual_grid_step_x * 5);
366
367
0
    src_xoff = MS_MAX(0, (int)floor(llx + 0.5));
368
0
    src_yoff = MS_MAX(0, (int)floor(ury + 0.5));
369
0
    src_xsize = MS_MIN(MS_MAX(0, (int)(urx - llx + 0.5)),
370
0
                       GDALGetRasterXSize(clinfo->hOrigDS) - src_xoff);
371
0
    src_ysize = MS_MIN(MS_MAX(0, (int)(lly - ury + 0.5)),
372
0
                       GDALGetRasterYSize(clinfo->hOrigDS) - src_yoff);
373
374
    /* Update the geographic extent (buffer added) */
375
    /* TODO: a better way to go the geo_trans */
376
0
    copyRect.minx = GEO_TRANS(adfGeoTransform + 0, src_xoff, 0);
377
0
    copyRect.maxx = GEO_TRANS(adfGeoTransform + 0, src_xoff + src_xsize, 0);
378
0
    copyRect.miny = GEO_TRANS(adfGeoTransform + 3, 0, src_yoff + src_ysize);
379
0
    copyRect.maxy = GEO_TRANS(adfGeoTransform + 3, 0, src_yoff);
380
381
    /*
382
     * If input window is to small then stop here
383
     */
384
0
    if (src_xsize < 2 || src_ysize < 2) {
385
0
      if (layer->debug)
386
0
        msDebug("msContourLayerReadRaster(): input window too small, or no "
387
0
                "apparent overlap between map view and this window(1).\n");
388
0
      return MS_SUCCESS;
389
0
    }
390
391
    /* Target buffer size */
392
0
    dst_xsize = (int)ceil((copyRect.maxx - copyRect.minx) / dst_cellsize_x);
393
0
    dst_ysize = (int)ceil((copyRect.maxy - copyRect.miny) / dst_cellsize_y);
394
395
0
    if (dst_xsize == 0 || dst_ysize == 0) {
396
0
      if (layer->debug)
397
0
        msDebug("msContourLayerReadRaster(): no apparent overlap between map "
398
0
                "view and this window(2).\n");
399
0
      return MS_SUCCESS;
400
0
    }
401
402
0
    if (layer->debug)
403
0
      msDebug("msContourLayerReadRaster(): src=%d,%d,%d,%d, dst=%d,%d,%d,%d\n",
404
0
              src_xoff, src_yoff, src_xsize, src_ysize, 0, 0, dst_xsize,
405
0
              dst_ysize);
406
0
  } else {
407
0
    src_xoff = 0;
408
0
    src_yoff = 0;
409
0
    dst_xsize = src_xsize = MS_MIN(map->width, src_xsize);
410
0
    dst_ysize = src_ysize = MS_MIN(map->height, src_ysize);
411
0
    copyRect.minx = 0;
412
0
    copyRect.miny = 0;
413
0
    (void)copyRect.miny;
414
0
    copyRect.maxx = map->width;
415
0
    copyRect.maxy = map->height;
416
0
    (void)copyRect.maxx;
417
0
    dst_cellsize_y = dst_cellsize_x = 1;
418
0
  }
419
420
  /* -------------------------------------------------------------------- */
421
  /*      Allocate buffer, and read data into it.                         */
422
  /* -------------------------------------------------------------------- */
423
424
0
  clinfo->buffer = (double *)malloc(sizeof(double) * dst_xsize * dst_ysize);
425
0
  if (clinfo->buffer == NULL) {
426
0
    msSetError(MS_MEMERR, "Malloc(): Out of memory.",
427
0
               "msContourLayerReadRaster()");
428
0
    return MS_FAILURE;
429
0
  }
430
431
0
  eErr = GDALRasterIO(hBand, GF_Read, src_xoff, src_yoff, src_xsize, src_ysize,
432
0
                      clinfo->buffer, dst_xsize, dst_ysize, GDT_Float64, 0, 0);
433
434
0
  if (eErr != CE_None) {
435
0
    msSetError(MS_IOERR, "GDALRasterIO() failed: %s",
436
0
               "msContourLayerReadRaster()", CPLGetLastErrorMsg());
437
0
    free(clinfo->buffer);
438
0
    clinfo->buffer = NULL;
439
0
    return MS_FAILURE;
440
0
  }
441
442
0
  GDALDriverH hMemDRV = GDALGetDriverByName("MEM");
443
0
  if (!hMemDRV) {
444
0
    msSetError(MS_IOERR, "GDAL MEM driver not available",
445
0
               "msContourLayerReadRaster()");
446
0
    free(clinfo->buffer);
447
0
    clinfo->buffer = NULL;
448
0
    return MS_FAILURE;
449
0
  }
450
451
0
  char pointer[64];
452
0
  memset(pointer, 0, sizeof(pointer));
453
0
  CPLPrintPointer(pointer, clinfo->buffer, sizeof(pointer));
454
455
0
  clinfo->hDS = GDALCreate(hMemDRV, "", dst_xsize, dst_ysize, 0, 0, NULL);
456
0
  if (clinfo->hDS == NULL) {
457
0
    msSetError(MS_IMGERR, "Unable to create GDAL Memory dataset.",
458
0
               "msContourLayerReadRaster()");
459
0
    free(clinfo->buffer);
460
0
    clinfo->buffer = NULL;
461
0
    return MS_FAILURE;
462
0
  }
463
464
0
  char **papszOptions = CSLSetNameValue(NULL, "DATAPOINTER", pointer);
465
0
  eErr = GDALAddBand(clinfo->hDS, GDT_Float64, papszOptions);
466
0
  CSLDestroy(papszOptions);
467
0
  if (eErr != CE_None) {
468
0
    msSetError(MS_IMGERR, "Unable to add band to GDAL Memory dataset.",
469
0
               "msContourLayerReadRaster()");
470
0
    free(clinfo->buffer);
471
0
    clinfo->buffer = NULL;
472
0
    return MS_FAILURE;
473
0
  }
474
475
0
  {
476
    // Copy nodata value from source dataset to memory dataset
477
0
    int bHasNoData = FALSE;
478
0
    double dfNoDataValue = GDALGetRasterNoDataValue(hBand, &bHasNoData);
479
0
    if (bHasNoData) {
480
0
      GDALSetRasterNoDataValue(GDALGetRasterBand(clinfo->hDS, 1),
481
0
                               dfNoDataValue);
482
0
    }
483
0
  }
484
485
0
  adfGeoTransform[0] = copyRect.minx;
486
0
  adfGeoTransform[1] = dst_cellsize_x;
487
0
  adfGeoTransform[2] = 0;
488
0
  adfGeoTransform[3] = copyRect.maxy;
489
0
  adfGeoTransform[4] = 0;
490
0
  adfGeoTransform[5] = -dst_cellsize_y;
491
492
0
  clinfo->cellsize = MS_MAX(dst_cellsize_x, dst_cellsize_y);
493
0
  {
494
0
    char buf[64];
495
0
    sprintf(buf, "%lf", clinfo->cellsize);
496
0
    msInsertHashTable(&layer->metadata, "__data_cellsize__", buf);
497
0
  }
498
499
0
  GDALSetGeoTransform(clinfo->hDS, adfGeoTransform);
500
0
  return MS_SUCCESS;
501
0
}
502
503
0
static void msContourOGRCloseConnection(void *conn_handle) {
504
0
  OGRDataSourceH hDS = (OGRDataSourceH)conn_handle;
505
506
0
  msAcquireLock(TLOCK_OGR);
507
0
  OGR_DS_Destroy(hDS);
508
0
  msReleaseLock(TLOCK_OGR);
509
0
}
510
511
/* Function that parses multiple options in the a list. It also supports
512
   min/maxscaledenom checks. ie. "CONTOUR_INTERVAL=0,3842942:10" */
513
0
static char *msContourGetOption(layerObj *layer, const char *name) {
514
0
  int c, i, found = MS_FALSE;
515
0
  char **values, **tmp, **options;
516
0
  double maxscaledenom, minscaledenom;
517
0
  char *value = NULL;
518
519
0
  options = CSLFetchNameValueMultiple(layer->processing, name);
520
0
  c = CSLCount(options);
521
522
  /* First pass to find the value among options that have min/maxscaledenom */
523
  /* specified */
524
0
  for (i = 0; i < c && found == MS_FALSE; ++i) {
525
0
    values = CSLTokenizeStringComplex(options[i], ":", FALSE, FALSE);
526
0
    if (CSLCount(values) == 2) {
527
0
      tmp = CSLTokenizeStringComplex(values[0], ",", FALSE, FALSE);
528
0
      if (CSLCount(tmp) == 2) {
529
0
        minscaledenom = atof(tmp[0]);
530
0
        maxscaledenom = atof(tmp[1]);
531
0
        if (layer->map->scaledenom <= 0 ||
532
0
            (((maxscaledenom <= 0) ||
533
0
              (layer->map->scaledenom <= maxscaledenom)) &&
534
0
             ((minscaledenom <= 0) ||
535
0
              (layer->map->scaledenom > minscaledenom)))) {
536
0
          value = msStrdup(values[1]);
537
0
          found = MS_TRUE;
538
0
        }
539
0
      }
540
0
      CSLDestroy(tmp);
541
0
    }
542
0
    CSLDestroy(values);
543
0
  }
544
545
  /* Second pass to find the value among options that do NOT have */
546
  /* min/maxscaledenom specified */
547
0
  for (i = 0; i < c && found == MS_FALSE; ++i) {
548
0
    values = CSLTokenizeStringComplex(options[i], ":", FALSE, FALSE);
549
0
    if (CSLCount(values) == 1) {
550
0
      value = msStrdup(values[0]);
551
0
      found = MS_TRUE;
552
0
    }
553
0
    CSLDestroy(values);
554
0
  }
555
556
0
  CSLDestroy(options);
557
558
0
  return value;
559
0
}
560
561
0
static int msContourLayerGenerateContour(layerObj *layer) {
562
0
  OGRSFDriverH hDriver;
563
0
  OGRFieldDefnH hFld;
564
0
  OGRLayerH hLayer;
565
0
  const char *elevItem;
566
0
  char *option;
567
0
  double interval = 1.0, levels[1000];
568
0
  int levelCount = 0;
569
0
  GDALRasterBandH hBand = NULL;
570
0
  CPLErr eErr;
571
0
  int bHasNoData = FALSE;
572
0
  double dfNoDataValue;
573
574
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
575
576
0
  OGRRegisterAll();
577
578
0
  if (clinfo == NULL) {
579
0
    msSetError(MS_MISCERR, "Assertion failed: Contour layer not opened!!!",
580
0
               "msContourLayerCreateOGRDataSource()");
581
0
    return MS_FAILURE;
582
0
  }
583
584
0
  if (!clinfo->hDS) { /* no overlap */
585
0
    return MS_SUCCESS;
586
0
  }
587
588
0
  hBand = GDALGetRasterBand(clinfo->hDS, 1);
589
0
  if (hBand == NULL) {
590
0
    msSetError(MS_IMGERR, "Band %d does not exist on dataset.",
591
0
               "msContourLayerGenerateContour()", 1);
592
0
    return MS_FAILURE;
593
0
  }
594
595
  /* Create the OGR DataSource */
596
0
  hDriver = OGRGetDriverByName("Memory");
597
0
  if (hDriver == NULL) {
598
0
    msSetError(MS_OGRERR, "Unable to get OGR driver 'Memory'.",
599
0
               "msContourLayerCreateOGRDataSource()");
600
0
    return MS_FAILURE;
601
0
  }
602
603
0
  clinfo->hOGRDS = OGR_Dr_CreateDataSource(hDriver, "", NULL);
604
0
  if (clinfo->hOGRDS == NULL) {
605
0
    msSetError(MS_OGRERR, "Unable to create OGR DataSource.",
606
0
               "msContourLayerCreateOGRDataSource()");
607
0
    return MS_FAILURE;
608
0
  }
609
610
0
  hLayer = OGR_DS_CreateLayer(clinfo->hOGRDS, clinfo->ogrLayer.name, NULL,
611
0
                              wkbLineString, NULL);
612
613
0
  hFld = OGR_Fld_Create("ID", OFTInteger);
614
0
  OGR_Fld_SetWidth(hFld, 8);
615
0
  OGR_L_CreateField(hLayer, hFld, FALSE);
616
0
  OGR_Fld_Destroy(hFld);
617
618
  /* Check if we have a coutour item specified */
619
0
  elevItem = CSLFetchNameValue(layer->processing, "CONTOUR_ITEM");
620
0
  if (elevItem && strlen(elevItem) > 0) {
621
0
    hFld = OGR_Fld_Create(elevItem, OFTReal);
622
0
    OGR_Fld_SetWidth(hFld, 12);
623
0
    OGR_Fld_SetPrecision(hFld, 3);
624
0
    OGR_L_CreateField(hLayer, hFld, FALSE);
625
0
    OGR_Fld_Destroy(hFld);
626
0
  } else {
627
0
    elevItem = NULL;
628
0
  }
629
630
0
  option = msContourGetOption(layer, "CONTOUR_INTERVAL");
631
0
  if (option) {
632
0
    interval = atof(option);
633
0
    free(option);
634
0
  }
635
636
0
  option = msContourGetOption(layer, "CONTOUR_LEVELS");
637
0
  if (option) {
638
0
    int i, c;
639
0
    char **levelsTmp;
640
0
    levelsTmp = CSLTokenizeStringComplex(option, ",", FALSE, FALSE);
641
0
    c = CSLCount(levelsTmp);
642
0
    for (i = 0; i < c && i < (int)(sizeof(levels) / sizeof(double)); ++i)
643
0
      levels[levelCount++] = atof(levelsTmp[i]);
644
645
0
    CSLDestroy(levelsTmp);
646
0
    free(option);
647
0
  }
648
649
0
  dfNoDataValue = GDALGetRasterNoDataValue(hBand, &bHasNoData);
650
651
0
  eErr = GDALContourGenerate(
652
0
      hBand, interval, 0.0, levelCount, levels, bHasNoData, dfNoDataValue,
653
0
      hLayer, OGR_FD_GetFieldIndex(OGR_L_GetLayerDefn(hLayer), "ID"),
654
0
      (elevItem == NULL)
655
0
          ? -1
656
0
          : OGR_FD_GetFieldIndex(OGR_L_GetLayerDefn(hLayer), elevItem),
657
0
      NULL, NULL);
658
659
0
  if (eErr != CE_None) {
660
0
    msSetError(MS_IOERR, "GDALContourGenerate() failed: %s",
661
0
               "msContourLayerGenerateContour()", CPLGetLastErrorMsg());
662
0
    return MS_FAILURE;
663
0
  }
664
665
0
  msConnPoolRegister(&clinfo->ogrLayer, clinfo->hOGRDS,
666
0
                     msContourOGRCloseConnection);
667
668
0
  return MS_SUCCESS;
669
0
}
670
671
0
int msContourLayerOpen(layerObj *layer) {
672
0
  char *decrypted_path;
673
0
  char szPath[MS_MAXPATHLEN];
674
0
  contourLayerInfo *clinfo;
675
676
0
  if (layer->debug)
677
0
    msDebug("Entering msContourLayerOpen().\n");
678
679
  /* If we don't have info, initialize an empty one now */
680
0
  if (layer->layerinfo == NULL)
681
0
    msContourLayerInfoInitialize(layer);
682
683
0
  clinfo = (contourLayerInfo *)layer->layerinfo;
684
0
  if (layer->data == NULL && layer->tileindex == NULL) {
685
0
    msSetError(MS_MISCERR, "Layer %s has neither DATA nor TILEINDEX defined.",
686
0
               "msContourLayerOpen()", layer->name);
687
0
    return MS_FAILURE;
688
0
  }
689
690
0
  if (layer->tileindex != NULL) {
691
0
    char szTilename[MS_MAXPATHLEN];
692
0
    int status;
693
0
    int tilelayerindex, tileitemindex, tilesrsindex;
694
0
    rectObj searchrect;
695
0
    layerObj *tlp;
696
0
    shapeObj tshp;
697
0
    char tilesrsname[1];
698
699
0
    msInitShape(&tshp);
700
0
    searchrect = layer->map->extent;
701
702
0
    status = msDrawRasterSetupTileLayer(layer->map, layer, &searchrect,
703
0
                                        MS_FALSE, &tilelayerindex,
704
0
                                        &tileitemindex, &tilesrsindex, &tlp);
705
0
    if (status == MS_FAILURE) {
706
0
      return MS_FAILURE;
707
0
    }
708
709
0
    status = msDrawRasterIterateTileIndex(layer, tlp, &tshp, tileitemindex, -1,
710
0
                                          szTilename, sizeof(szTilename),
711
0
                                          tilesrsname, sizeof(tilesrsname));
712
0
    if (status == MS_FAILURE || status == MS_DONE) {
713
0
      if (status == MS_DONE) {
714
0
        if (layer->debug)
715
0
          msDebug("No raster matching filter.\n");
716
0
      }
717
0
      msDrawRasterCleanupTileLayer(tlp, tilelayerindex);
718
0
      return MS_FAILURE;
719
0
    }
720
721
0
    msDrawRasterCleanupTileLayer(tlp, tilelayerindex);
722
723
0
    msDrawRasterBuildRasterPath(layer->map, layer, szTilename, szPath);
724
0
    decrypted_path = msStrdup(szPath);
725
726
    /* Cancel the time filter that might have been set on ours in case of */
727
    /* a inline tileindex */
728
0
    msFreeExpression(&layer->filter);
729
0
    msInitExpression(&layer->filter);
730
0
  } else {
731
0
    msTryBuildPath3(szPath, layer->map->mappath, layer->map->shapepath,
732
0
                    layer->data);
733
0
    decrypted_path = msDecryptStringTokens(layer->map, szPath);
734
0
  }
735
736
0
  GDALAllRegister();
737
738
  /* Open the original Dataset */
739
740
0
  msAcquireLock(TLOCK_GDAL);
741
0
  if (decrypted_path) {
742
0
    clinfo->hOrigDS = GDALOpen(decrypted_path, GA_ReadOnly);
743
0
    msFree(decrypted_path);
744
0
  } else
745
0
    clinfo->hOrigDS = NULL;
746
747
0
  msReleaseLock(TLOCK_GDAL);
748
749
0
  if (clinfo->hOrigDS == NULL) {
750
0
    msSetError(MS_IMGERR, "Unable to open GDAL dataset.",
751
0
               "msContourLayerOpen()");
752
0
    return MS_FAILURE;
753
0
  }
754
755
  /* Open the raster source */
756
0
  if (msContourLayerReadRaster(layer, layer->map->extent) != MS_SUCCESS)
757
0
    return MS_FAILURE;
758
759
  /* Generate Contour Dataset */
760
0
  if (msContourLayerGenerateContour(layer) != MS_SUCCESS)
761
0
    return MS_FAILURE;
762
763
0
  if (clinfo->hDS) {
764
0
    GDALClose(clinfo->hDS);
765
0
    clinfo->hDS = NULL;
766
0
    free(clinfo->buffer);
767
0
  }
768
769
  /* Open our virtual ogr layer */
770
0
  if (clinfo->hOGRDS && (msLayerOpen(&clinfo->ogrLayer) != MS_SUCCESS))
771
0
    return MS_FAILURE;
772
773
0
  return MS_SUCCESS;
774
0
}
775
776
0
int msContourLayerIsOpen(layerObj *layer) {
777
0
  if (layer->layerinfo)
778
0
    return MS_TRUE;
779
0
  return MS_FALSE;
780
0
}
781
782
0
int msContourLayerClose(layerObj *layer) {
783
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
784
785
0
  if (layer->debug)
786
0
    msDebug("Entering msContourLayerClose().\n");
787
788
0
  if (clinfo) {
789
0
    if (clinfo->hOGRDS)
790
0
      msConnPoolRelease(&clinfo->ogrLayer, clinfo->hOGRDS);
791
792
0
    msLayerClose(&clinfo->ogrLayer);
793
794
0
    if (clinfo->hDS) {
795
0
      GDALClose(clinfo->hDS);
796
0
      clinfo->hDS = NULL;
797
0
      free(clinfo->buffer);
798
0
    }
799
800
0
    if (clinfo->hOrigDS) {
801
0
      GDALClose(clinfo->hOrigDS);
802
0
      clinfo->hOrigDS = NULL;
803
0
    }
804
805
0
    msContourLayerInfoFree(layer);
806
0
  }
807
808
0
  return MS_SUCCESS;
809
0
}
810
811
0
int msContourLayerGetItems(layerObj *layer) {
812
0
  const char *elevItem;
813
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
814
815
0
  if (clinfo == NULL) {
816
0
    msSetError(MS_MISCERR, "Assertion failed: Contour layer not opened!!!",
817
0
               "msContourLayerGetItems()");
818
0
    return MS_FAILURE;
819
0
  }
820
821
0
  layer->numitems = 0;
822
0
  layer->items = (char **)msSmallCalloc(sizeof(char *), 2);
823
824
0
  layer->items[layer->numitems++] = msStrdup("ID");
825
0
  elevItem = CSLFetchNameValue(layer->processing, "CONTOUR_ITEM");
826
0
  if (elevItem && strlen(elevItem) > 0) {
827
0
    layer->items[layer->numitems++] = msStrdup(elevItem);
828
0
  }
829
830
0
  return msLayerGetItems(&clinfo->ogrLayer);
831
0
}
832
833
0
int msContourLayerWhichShapes(layerObj *layer, rectObj rect, int isQuery) {
834
0
  int i;
835
0
  rectObj newRect;
836
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
837
838
0
  if (layer->debug)
839
0
    msDebug("Entering msContourLayerWhichShapes().\n");
840
841
0
  if (clinfo == NULL) {
842
0
    msSetError(MS_MISCERR, "Assertion failed: Contour layer not opened!!!",
843
0
               "msContourLayerWhichShapes()");
844
0
    return MS_FAILURE;
845
0
  }
846
847
0
  if (isQuery) {
848
0
    newRect = layer->map->extent;
849
0
  } else {
850
0
    newRect = rect;
851
    /* if necessary, project the searchrect to source coords */
852
0
    if (msProjectionsDiffer(&(layer->map->projection), &(layer->projection))) {
853
0
      if (msProjectRect(&layer->projection, &layer->map->projection,
854
0
                        &newRect) != MS_SUCCESS) {
855
0
        msDebug("msContourLayerWhichShapes(%s): unable to reproject map "
856
0
                "request rectangle into layer projection, canceling.\n",
857
0
                layer->name);
858
0
        return MS_FAILURE;
859
0
      }
860
0
    }
861
0
  }
862
863
  /* regenerate the raster io */
864
0
  if (clinfo->hOGRDS)
865
0
    msConnPoolRelease(&clinfo->ogrLayer, clinfo->hOGRDS);
866
867
0
  msLayerClose(&clinfo->ogrLayer);
868
869
  /* Open the raster source */
870
0
  if (msContourLayerReadRaster(layer, newRect) != MS_SUCCESS)
871
0
    return MS_FAILURE;
872
873
  /* Generate Contour Dataset */
874
0
  if (msContourLayerGenerateContour(layer) != MS_SUCCESS)
875
0
    return MS_FAILURE;
876
877
0
  if (clinfo->hDS) {
878
0
    GDALClose(clinfo->hDS);
879
0
    clinfo->hDS = NULL;
880
0
    free(clinfo->buffer);
881
0
  }
882
883
0
  if (!clinfo->hOGRDS) /* no overlap */
884
0
    return MS_DONE;
885
886
  /* Open our virtual ogr layer */
887
0
  if (msLayerOpen(&clinfo->ogrLayer) != MS_SUCCESS)
888
0
    return MS_FAILURE;
889
890
0
  clinfo->ogrLayer.numitems = layer->numitems;
891
0
  clinfo->ogrLayer.items =
892
0
      (char **)msSmallMalloc(sizeof(char *) * layer->numitems);
893
0
  for (i = 0; i < layer->numitems; ++i) {
894
0
    clinfo->ogrLayer.items[i] = msStrdup(layer->items[i]);
895
0
  }
896
897
0
  return msLayerWhichShapes(&clinfo->ogrLayer, rect, isQuery);
898
0
}
899
900
int msContourLayerGetShape(layerObj *layer, shapeObj *shape,
901
0
                           resultObj *record) {
902
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
903
904
0
  if (layer->debug)
905
0
    msDebug("Entering msContourLayerGetShape().\n");
906
907
0
  if (clinfo == NULL) {
908
0
    msSetError(MS_MISCERR, "Assertion failed: Contour layer not opened!!!",
909
0
               "msContourLayerGetShape()");
910
0
    return MS_FAILURE;
911
0
  }
912
913
0
  return msLayerGetShape(&clinfo->ogrLayer, shape, record);
914
0
}
915
916
0
int msContourLayerNextShape(layerObj *layer, shapeObj *shape) {
917
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
918
919
0
  if (layer->debug)
920
0
    msDebug("Entering msContourLayerNextShape().\n");
921
922
0
  if (clinfo == NULL) {
923
0
    msSetError(MS_MISCERR, "Assertion failed: Contour layer not opened!!!",
924
0
               "msContourLayerNextShape()");
925
0
    return MS_FAILURE;
926
0
  }
927
928
0
  return msLayerNextShape(&clinfo->ogrLayer, shape);
929
0
}
930
931
/************************************************************************/
932
/*                       msContourLayerGetExtent()                     */
933
/* Simple copy of the maprasterquery.c file. might change in the future */
934
/************************************************************************/
935
936
0
int msContourLayerGetExtent(layerObj *layer, rectObj *extent) {
937
0
  contourLayerInfo *clinfo = (contourLayerInfo *)layer->layerinfo;
938
939
0
  if (layer->debug)
940
0
    msDebug("Entering msContourLayerGetExtent().\n");
941
942
0
  if (clinfo == NULL) {
943
0
    msSetError(MS_MISCERR, "Assertion failed: Contour layer not opened!!!",
944
0
               "msContourLayerGetExtent()");
945
0
    return MS_FAILURE;
946
0
  }
947
948
0
  MS_COPYRECT(extent, &clinfo->extent);
949
950
0
  return MS_SUCCESS;
951
0
}
952
953
/************************************************************************/
954
/*                     msContourLayerSetTimeFilter()                    */
955
/*                                                                      */
956
/*      This function is actually just used in the context of           */
957
/*      setting a filter on the tileindex for time based queries.       */
958
/*      For instance via WMS requests.                                  */
959
/*                                                                      */
960
/*      If a local shapefile tileindex is in use, we will set a         */
961
/*      backtics filter (shapefile compatible).  If another layer is    */
962
/*      being used as the tileindex then we will forward the            */
963
/*      SetTimeFilter call to it.  If there is no tileindex in          */
964
/*      place, we do nothing.                                           */
965
/************************************************************************/
966
967
int msContourLayerSetTimeFilter(layerObj *layer, const char *timestring,
968
0
                                const char *timefield) {
969
0
  int tilelayerindex;
970
971
0
  if (layer->debug)
972
0
    msDebug("msContourLayerSetTimeFilter(%s,%s).\n", timestring, timefield);
973
974
  /* -------------------------------------------------------------------- */
975
  /*      If we don't have a tileindex the time filter has no effect.     */
976
  /* -------------------------------------------------------------------- */
977
0
  if (layer->tileindex == NULL) {
978
0
    if (layer->debug)
979
0
      msDebug("msContourLayerSetTimeFilter(): time filter without effect on "
980
0
              "layers without tileindex.\n");
981
0
    return MS_SUCCESS;
982
0
  }
983
984
  /* -------------------------------------------------------------------- */
985
  /*      Find the tileindex layer.                                       */
986
  /* -------------------------------------------------------------------- */
987
0
  tilelayerindex = msGetLayerIndex(layer->map, layer->tileindex);
988
989
  /* -------------------------------------------------------------------- */
990
  /*      If we are using a local shapefile as our tileindex (that is     */
991
  /*      to say, the tileindex name is not of another layer), then we    */
992
  /*      just install a backtics style filter on the current layer.      */
993
  /* -------------------------------------------------------------------- */
994
0
  if (tilelayerindex == -1)
995
0
    return msLayerMakeBackticsTimeFilter(layer, timestring, timefield);
996
997
  /* -------------------------------------------------------------------- */
998
  /*      Otherwise we invoke the tileindex layers SetTimeFilter          */
999
  /*      method.                                                         */
1000
  /* -------------------------------------------------------------------- */
1001
0
  if (msCheckParentPointer(layer->map, "map") == MS_FAILURE)
1002
0
    return MS_FAILURE;
1003
0
  return msLayerSetTimeFilter(layer->GET_LAYER(map, tilelayerindex), timestring,
1004
0
                              timefield);
1005
0
}
1006
1007
/************************************************************************/
1008
/*                msRASTERLayerInitializeVirtualTable()                 */
1009
/************************************************************************/
1010
1011
0
int msContourLayerInitializeVirtualTable(layerObj *layer) {
1012
0
  assert(layer != NULL);
1013
0
  assert(layer->vtable != NULL);
1014
1015
0
  layer->vtable->LayerInitItemInfo = msContourLayerInitItemInfo;
1016
0
  layer->vtable->LayerFreeItemInfo = msContourLayerFreeItemInfo;
1017
0
  layer->vtable->LayerOpen = msContourLayerOpen;
1018
0
  layer->vtable->LayerIsOpen = msContourLayerIsOpen;
1019
0
  layer->vtable->LayerWhichShapes = msContourLayerWhichShapes;
1020
0
  layer->vtable->LayerNextShape = msContourLayerNextShape;
1021
0
  layer->vtable->LayerGetShape = msContourLayerGetShape;
1022
  /* layer->vtable->LayerGetShapeCount, use default */
1023
0
  layer->vtable->LayerClose = msContourLayerClose;
1024
0
  layer->vtable->LayerGetItems = msContourLayerGetItems;
1025
0
  layer->vtable->LayerGetExtent = msContourLayerGetExtent;
1026
  /* layer->vtable->LayerGetAutoStyle, use default */
1027
  /* layer->vtable->LayerApplyFilterToLayer, use default */
1028
  /*layer->vtable->LayerCloseConnection = msContourLayerClose;*/
1029
  /* we use backtics for proper tileindex shapefile functioning */
1030
0
  layer->vtable->LayerSetTimeFilter = msContourLayerSetTimeFilter;
1031
  /* layer->vtable->LayerCreateItems, use default */
1032
  /* layer->vtable->LayerGetNumFeatures, use default */
1033
1034
0
  return MS_SUCCESS;
1035
0
}