• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MapServer / MapServer / 21904832566

11 Feb 2026 12:21PM UTC coverage: 41.789% (+0.02%) from 41.77%
21904832566

push

github

web-flow
[Backport branch-8-6] CI: upgrade PHPUnit version to 13 (#7428)

* CI: upgrade PHPUnit version to 13

* CI: upgrade to PHPUnit 13

---------

Co-authored-by: Jeff McKenna <jmckenna@gatewaygeomatics.com>

62955 of 150648 relevant lines covered (41.79%)

25319.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.45
/src/mapwcs20.cpp
1
/******************************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  OpenGIS Web Coverage Server (WCS) 2.0 implementation.
6
 * Author:   Stephan Meissl <stephan.meissl@eox.at>
7
 *           Fabian Schindler <fabian.schindler@eox.at>
8
 *           and the MapServer team.
9
 *
10
 ******************************************************************************
11
 * Copyright (c) 2010, 2011 EOX IT Services GmbH, Austria
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a
14
 * copy of this software and associated documentation files (the "Software"),
15
 * to deal in the Software without restriction, including without limitation
16
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17
 * and/or sell copies of the Software, and to permit persons to whom the
18
 * Software is furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in
21
 * all copies of this Software or works derived from this Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29
 * DEALINGS IN THE SOFTWARE.
30
 ****************************************************************************/
31

32
#include "mapserver-config.h"
33
#if defined(USE_WCS_SVR)
34

35
#include <assert.h>
36
#include "mapserver.h"
37
#include "maperror.h"
38
#include "mapthread.h"
39
#include "mapows.h"
40
#include "mapwcs.h"
41
#include "mapgdal.h"
42
#include <float.h>
43
#include "gdal.h"
44
#include "cpl_port.h"
45
#include "maptime.h"
46
#include "mapprimitive.h"
47
#include "cpl_string.h"
48
#include <string.h>
49

50
#if defined(USE_LIBXML2)
51

52
#include <libxml/tree.h>
53
#include "maplibxml2.h"
54
#include <libxml/parser.h>
55

56
#endif /* defined(USE_LIBXML2) */
57

58
#include <string>
59

60
/************************************************************************/
61
/*                          msXMLStripIndentation                       */
62
/************************************************************************/
63

64
static void msXMLStripIndentation(char *ptr) {
20✔
65
  /* Remove spaces between > and < to get properly indented result */
66
  char *afterLastClosingBracket = NULL;
67
  if (*ptr == ' ')
20✔
68
    afterLastClosingBracket = ptr;
69
  while (*ptr != '\0') {
31,930✔
70
    if (*ptr == '<' && afterLastClosingBracket != NULL) {
31,910✔
71
      memmove(afterLastClosingBracket, ptr, strlen(ptr) + 1);
590✔
72
      ptr = afterLastClosingBracket;
590✔
73
    } else if (*ptr == '>') {
31,320✔
74
      afterLastClosingBracket = ptr + 1;
800✔
75
    } else if (*ptr != ' ' && *ptr != '\n')
30,520✔
76
      afterLastClosingBracket = NULL;
77
    ptr++;
31,910✔
78
  }
79
}
20✔
80

81
/************************************************************************/
82
/*                   msStringParseInteger()                             */
83
/*                                                                      */
84
/*      Tries to parse a string as a integer value. If not possible     */
85
/*      the value MS_WCS20_UNBOUNDED is stored as value.                */
86
/*      If no characters could be parsed, MS_FAILURE is returned. If at */
87
/*      least some characters could be parsed, MS_DONE is returned and  */
88
/*      only if all characters could be parsed, MS_SUCCESS is returned. */
89
/************************************************************************/
90

91
static int msStringParseInteger(const char *string, int *dest) {
497✔
92
  char *parse_check;
93
  *dest = (int)strtol(string, &parse_check, 0);
497✔
94
  if (parse_check == string) {
497✔
95
    return MS_FAILURE;
96
  } else if (parse_check - strlen(string) != string) {
450✔
97
    return MS_DONE;
1✔
98
  }
99
  return MS_SUCCESS;
100
}
101

102
/************************************************************************/
103
/*                   msStringParseDouble()                              */
104
/*                                                                      */
105
/*      Tries to parse a string as a double value. If not possible      */
106
/*      the value 0 is stored as value.                                 */
107
/*      If no characters could be parsed, MS_FAILURE is returned. If at */
108
/*      least some characters could be parsed, MS_DONE is returned and  */
109
/*      only if all characters could be parsed, MS_SUCCESS is returned. */
110
/************************************************************************/
111

112
static int msStringParseDouble(const char *string, double *dest) {
345✔
113
  char *parse_check = NULL;
345✔
114
  *dest = strtod(string, &parse_check);
345✔
115
  if (parse_check == string) {
345✔
116
    return MS_FAILURE;
117
  } else if (parse_check - strlen(string) != string) {
345✔
118
    return MS_DONE;
×
119
  }
120
  return MS_SUCCESS;
121
}
122

123
/************************************************************************/
124
/*                   msWCSParseTimeOrScalar20()                         */
125
/*                                                                      */
126
/*      Parses a string, either as a time or a scalar value and         */
127
/*      writes the output into the timeScalarUnion.                     */
128
/************************************************************************/
129

130
static int msWCSParseTimeOrScalar20(timeScalarUnion *u, const char *string) {
235✔
131
  struct tm time;
132
  if (string) {
235✔
133
    while (*string == ' ')
239✔
134
      string++;
4✔
135
  }
136

137
  if (!string || strlen(string) == 0 || !u) {
235✔
138
    msSetError(MS_WCSERR, "Invalid string", "msWCSParseTimeOrScalar20()");
×
139
    return MS_WCS20_ERROR_VALUE;
×
140
  }
141
  /* if the string is equal to "*" it means the value
142
   *  of the interval is unbounded                    */
143
  if (EQUAL(string, "*")) {
235✔
144
    u->scalar = MS_WCS20_UNBOUNDED;
12✔
145
    u->unbounded = 1;
12✔
146
    return MS_WCS20_UNDEFINED_VALUE;
12✔
147
  }
148

149
  /* if returned a valid value, use it */
150
  if (msStringParseDouble(string, &(u->scalar)) == MS_SUCCESS) {
223✔
151
    return MS_WCS20_SCALAR_VALUE;
152
  }
153
  /* otherwise it might be a time value */
154
  msTimeInit(&time);
×
155
  if (msParseTime(string, &time) == MS_TRUE) {
×
156
    u->time = mktime(&time);
×
157
    return MS_WCS20_TIME_VALUE;
×
158
  }
159
  /* the value could neither be parsed as a double nor as a time value */
160
  else {
161
    msSetError(MS_WCSERR,
×
162
               "String %s could not be parsed to a time or scalar value",
163
               "msWCSParseTimeOrScalar20()", string);
164
    return MS_WCS20_ERROR_VALUE;
×
165
  }
166
}
167

168
/************************************************************************/
169
/*                   msWCSCreateSubsetObj20()                           */
170
/*                                                                      */
171
/*      Creates a new wcs20SubsetObj and initializes it to standard     */
172
/*      values.                                                         */
173
/************************************************************************/
174

175
static wcs20SubsetObjPtr msWCSCreateSubsetObj20() {
118✔
176
  wcs20SubsetObjPtr subset = (wcs20SubsetObjPtr)malloc(sizeof(wcs20SubsetObj));
118✔
177
  MS_CHECK_ALLOC(subset, sizeof(wcs20SubsetObj), NULL);
118✔
178

179
  subset->axis = NULL;
118✔
180
  subset->crs = NULL;
118✔
181
  subset->min.scalar = subset->max.scalar = MS_WCS20_UNBOUNDED;
118✔
182
  subset->min.unbounded = subset->max.unbounded = 0;
118✔
183
  subset->operation = MS_WCS20_SLICE;
118✔
184

185
  return subset;
118✔
186
}
187

188
/************************************************************************/
189
/*                   msWCSFreeSubsetObj20()                             */
190
/*                                                                      */
191
/*      Frees a wcs20SubsetObj and releases all linked resources.       */
192
/************************************************************************/
193

194
static void msWCSFreeSubsetObj20(wcs20SubsetObjPtr subset) {
182✔
195
  if (NULL == subset) {
182✔
196
    return;
197
  }
198
  msFree(subset->axis);
118✔
199
  msFree(subset->crs);
118✔
200
  msFree(subset);
118✔
201
}
202

203
/************************************************************************/
204
/*                   msWCSCreateAxisObj20()                             */
205
/*                                                                      */
206
/*      Creates a new wcs20AxisObj and initializes it to standard       */
207
/*      values.                                                         */
208
/************************************************************************/
209

210
static wcs20AxisObjPtr msWCSCreateAxisObj20() {
180✔
211
  wcs20AxisObj *axis = (wcs20AxisObjPtr)malloc(sizeof(wcs20AxisObj));
180✔
212
  MS_CHECK_ALLOC(axis, sizeof(wcs20AxisObj), NULL);
180✔
213

214
  axis->name = NULL;
180✔
215
  axis->size = 0;
180✔
216
  axis->resolution = MS_WCS20_UNBOUNDED;
180✔
217
  axis->scale = MS_WCS20_UNBOUNDED;
180✔
218
  axis->resolutionUOM = NULL;
180✔
219
  axis->subset = NULL;
180✔
220

221
  return axis;
180✔
222
}
223

224
/************************************************************************/
225
/*                   msWCSFreeAxisObj20()                               */
226
/*                                                                      */
227
/*      Frees a wcs20AxisObj and releases all linked resources.         */
228
/************************************************************************/
229

230
static void msWCSFreeAxisObj20(wcs20AxisObjPtr axis) {
180✔
231
  if (NULL == axis) {
180✔
232
    return;
233
  }
234

235
  msFree(axis->name);
180✔
236
  msFree(axis->resolutionUOM);
180✔
237
  msWCSFreeSubsetObj20(axis->subset);
180✔
238
  msFree(axis);
180✔
239
}
240

241
/************************************************************************/
242
/*                   msWCSFindAxis20()                                  */
243
/*                                                                      */
244
/*      Helper function to retrieve an axis by the name from a params   */
245
/*      object.                                                         */
246
/************************************************************************/
247

248
static wcs20AxisObjPtr msWCSFindAxis20(wcs20ParamsObjPtr params,
231✔
249
                                       const char *name) {
250
  int i = 0;
251
  for (i = 0; i < params->numaxes; ++i) {
331✔
252
    if (EQUAL(params->axes[i]->name, name)) {
151✔
253
      return params->axes[i];
51✔
254
    }
255
  }
256
  return NULL;
257
}
258

259
/************************************************************************/
260
/*                   msWCSInsertAxisObj20()                             */
261
/*                                                                      */
262
/*      Helper function to insert an axis object into the axes list of  */
263
/*      a params object.                                                */
264
/************************************************************************/
265

266
static void msWCSInsertAxisObj20(wcs20ParamsObjPtr params,
180✔
267
                                 wcs20AxisObjPtr axis) {
268
  params->numaxes++;
180✔
269
  params->axes = (wcs20AxisObjPtr *)msSmallRealloc(
360✔
270
      params->axes, sizeof(wcs20AxisObjPtr) * (params->numaxes));
180✔
271
  params->axes[params->numaxes - 1] = axis;
180✔
272
}
180✔
273

274
/************************************************************************/
275
/*                   msWCSCreateParamsObj20()                           */
276
/*                                                                      */
277
/*      Creates a new wcs20ParamsObj and initializes it to standard     */
278
/*      values.                                                         */
279
/************************************************************************/
280

281
wcs20ParamsObjPtr msWCSCreateParamsObj20() {
213✔
282
  wcs20ParamsObjPtr params = (wcs20ParamsObjPtr)malloc(sizeof(wcs20ParamsObj));
213✔
283
  MS_CHECK_ALLOC(params, sizeof(wcs20ParamsObj), NULL);
213✔
284

285
  params->version = NULL;
213✔
286
  params->request = NULL;
213✔
287
  params->service = NULL;
213✔
288
  params->accept_versions = NULL;
213✔
289
  params->accept_languages = NULL;
213✔
290
  params->sections = NULL;
213✔
291
  params->updatesequence = NULL;
213✔
292
  params->ids = NULL;
213✔
293
  params->width = 0;
213✔
294
  params->height = 0;
213✔
295
  params->resolutionX = MS_WCS20_UNBOUNDED;
213✔
296
  params->resolutionY = MS_WCS20_UNBOUNDED;
213✔
297
  params->scale = MS_WCS20_UNBOUNDED;
213✔
298
  params->scaleX = MS_WCS20_UNBOUNDED;
213✔
299
  params->scaleY = MS_WCS20_UNBOUNDED;
213✔
300
  params->resolutionUnits = NULL;
213✔
301
  params->numaxes = 0;
213✔
302
  params->axes = NULL;
213✔
303
  params->format = NULL;
213✔
304
  params->multipart = 0;
213✔
305
  params->interpolation = NULL;
213✔
306
  params->outputcrs = NULL;
213✔
307
  params->subsetcrs = NULL;
213✔
308
  params->bbox.minx = params->bbox.miny = -DBL_MAX;
213✔
309
  params->bbox.maxx = params->bbox.maxy = DBL_MAX;
213✔
310
  params->range_subset = NULL;
213✔
311
  params->format_options = NULL;
213✔
312

313
  return params;
213✔
314
}
315

316
/************************************************************************/
317
/*                   msWCSFreeParamsObj20()                             */
318
/*                                                                      */
319
/*      Frees a wcs20ParamsObj and releases all linked resources.       */
320
/************************************************************************/
321

322
void msWCSFreeParamsObj20(wcs20ParamsObjPtr params) {
213✔
323
  if (NULL == params) {
213✔
324
    return;
325
  }
326

327
  msFree(params->version);
213✔
328
  msFree(params->request);
213✔
329
  msFree(params->service);
213✔
330
  CSLDestroy(params->accept_versions);
213✔
331
  CSLDestroy(params->accept_languages);
213✔
332
  CSLDestroy(params->sections);
213✔
333
  msFree(params->updatesequence);
213✔
334
  CSLDestroy(params->ids);
213✔
335
  msFree(params->resolutionUnits);
213✔
336
  msFree(params->format);
213✔
337
  msFree(params->interpolation);
213✔
338
  msFree(params->outputcrs);
213✔
339
  msFree(params->subsetcrs);
213✔
340
  while (params->numaxes > 0) {
393✔
341
    params->numaxes -= 1;
180✔
342
    msWCSFreeAxisObj20(params->axes[params->numaxes]);
180✔
343
  }
344
  msFree(params->axes);
213✔
345
  CSLDestroy(params->range_subset);
213✔
346
  CSLDestroy(params->format_options);
213✔
347
  msFree(params);
213✔
348
}
349

350
/************************************************************************/
351
/*                   msWCSParseSubset20()                               */
352
/*                                                                      */
353
/*      Parses several string parameters and fills them into the        */
354
/*      subset object.                                                  */
355
/************************************************************************/
356

357
static int msWCSParseSubset20(wcs20SubsetObjPtr subset, const char *axis,
118✔
358
                              const char *crs, const char *min,
359
                              const char *max) {
360
  int ts1, ts2;
361
  ts1 = ts2 = MS_WCS20_UNDEFINED_VALUE;
362

363
  if (subset == NULL) {
118✔
364
    return MS_FAILURE;
365
  }
366

367
  if (axis == NULL || strlen(axis) == 0) {
118✔
368
    msSetError(MS_WCSERR, "Subset axis is not given.", "msWCSParseSubset20()");
×
369
    return MS_FAILURE;
×
370
  }
371

372
  subset->axis = msStrdup(axis);
118✔
373
  if (crs != NULL) {
118✔
374
    subset->crs = msStrdup(crs);
39✔
375
  }
376

377
  /* Parse first (probably only) part of interval/point;
378
   * check whether its a time value or a scalar value     */
379
  ts1 = msWCSParseTimeOrScalar20(&(subset->min), min);
118✔
380
  if (ts1 == MS_WCS20_ERROR_VALUE) {
118✔
381
    return MS_FAILURE;
382
  }
383

384
  /* check if its an interval */
385
  /* if there is a comma, then it is */
386
  if (max != NULL && strlen(max) > 0) {
118✔
387
    subset->operation = MS_WCS20_TRIM;
117✔
388

389
    /* Parse the second value of the interval */
390
    ts2 = msWCSParseTimeOrScalar20(&(subset->max), max);
117✔
391
    if (ts2 == MS_WCS20_ERROR_VALUE) {
117✔
392
      return MS_FAILURE;
393
    }
394

395
    /* if at least one boundary is defined, use that value */
396
    if ((ts1 == MS_WCS20_UNDEFINED_VALUE) ^ (ts2 == MS_WCS20_UNDEFINED_VALUE)) {
117✔
397
      if (ts1 == MS_WCS20_UNDEFINED_VALUE) {
12✔
398
        ts1 = ts2;
399
      }
400
    }
401
    /* if time and scalar values do not fit, throw an error */
402
    else if (ts1 != MS_WCS20_UNDEFINED_VALUE &&
105✔
403
             ts2 != MS_WCS20_UNDEFINED_VALUE && ts1 != ts2) {
105✔
404
      msSetError(MS_WCSERR,
×
405
                 "Interval error: minimum is a %s value, maximum is a %s value",
406
                 "msWCSParseSubset20()", ts1 ? "time" : "scalar",
407
                 ts2 ? "time" : "scalar");
408
      return MS_FAILURE;
×
409
    }
410
    /* if both min and max are unbounded -> throw an error */
411
    if (subset->min.unbounded && subset->max.unbounded) {
117✔
412
      msSetError(MS_WCSERR, "Invalid values: no bounds could be parsed",
×
413
                 "msWCSParseSubset20()");
414
      return MS_FAILURE;
×
415
    }
416
  }
417
  /* there is no second value, therefore it is a point.
418
   * consequently set the operation to slice */
419
  else {
420
    subset->operation = MS_WCS20_SLICE;
1✔
421
    if (ts1 == MS_WCS20_UNDEFINED_VALUE) {
1✔
422
      msSetError(MS_WCSERR, "Invalid point value given",
×
423
                 "msWCSParseSubset20()");
424
      return MS_FAILURE;
×
425
    }
426
  }
427

428
  subset->timeOrScalar = ts1;
118✔
429

430
  /* check whether the min is smaller than the max */
431
  if (subset->operation == MS_WCS20_TRIM) {
118✔
432
    if (subset->timeOrScalar == MS_WCS20_SCALAR_VALUE &&
117✔
433
        subset->min.scalar == MS_WCS20_UNBOUNDED) {
117✔
434
      subset->min.scalar = -MS_WCS20_UNBOUNDED;
6✔
435
    }
436

437
    if (subset->timeOrScalar == MS_WCS20_TIME_VALUE &&
117✔
438
        subset->min.time > subset->max.time) {
×
439
      msSetError(MS_WCSERR,
×
440
                 "Minimum value of subset axis %s is larger than maximum value",
441
                 "msWCSParseSubset20()", subset->axis);
442
      return MS_FAILURE;
×
443
    }
444
    if (subset->timeOrScalar == MS_WCS20_SCALAR_VALUE &&
117✔
445
        subset->min.scalar > subset->max.scalar) {
117✔
446
      msSetError(MS_WCSERR,
1✔
447
                 "Minimum value (%f) of subset axis '%s' is larger than "
448
                 "maximum value (%f).",
449
                 "msWCSParseSubset20()", subset->min.scalar, subset->axis,
450
                 subset->max.scalar);
451
      return MS_FAILURE;
1✔
452
    }
453
  }
454
  return MS_SUCCESS;
455
}
456

457
/************************************************************************/
458
/*                   msWCSParseSubsetKVPString20()                      */
459
/*                                                                      */
460
/*      Creates a new wcs20SubsetObj, parses a string and fills         */
461
/*      the parsed values into the object. Returns NULL on failure.     */
462
/*      Subset string: axis [ , crs ] ( intervalOrPoint )               */
463
/************************************************************************/
464

465
static int msWCSParseSubsetKVPString20(wcs20SubsetObjPtr subset, char *string) {
78✔
466
  char *axis, *crs, *min, *max;
467

468
  axis = string;
469
  crs = NULL;
470
  min = NULL;
471
  max = NULL;
472

473
  /* find first '(' */
474
  min = strchr(string, '(');
475

476
  /* if min could not be found, the string is invalid */
477
  if (min == NULL) {
78✔
478
    msSetError(MS_WCSERR, "Invalid axis subset string: '%s'",
×
479
               "msWCSParseSubsetKVPString20()", string);
480
    return MS_FAILURE;
×
481
  }
482
  /* set min to first letter */
483
  *min = '\0';
78✔
484
  ++min;
78✔
485

486
  /* cut the trailing ')' */
487
  if (min[strlen(min) - 1] == ')') {
78✔
488
    min[strlen(min) - 1] = '\0';
78✔
489
  }
490
  /* look if also a max is defined */
491
  max = strchr(min, ',');
492
  if (max != NULL) {
78✔
493
    *max = '\0';
77✔
494
    ++max;
77✔
495
  }
496

497
  /* look if also a crs is defined */
498
  crs = strchr(axis, ',');
499
  if (crs != NULL) {
78✔
500
    *crs = '\0';
19✔
501
    ++crs;
19✔
502
  }
503

504
  return msWCSParseSubset20(subset, axis, crs, min, max);
78✔
505
}
506

507
/************************************************************************/
508
/*                   msWCSParseSizeString20()                           */
509
/*                                                                      */
510
/*      Parses a string containing the axis and the size as an integer. */
511
/*      Size string: axis ( size )                                      */
512
/************************************************************************/
513

514
static int msWCSParseSizeString20(char *string, char *outAxis,
42✔
515
                                  size_t axisStringLen, int *outSize) {
516
  char *number = NULL;
517
  char *check = NULL;
518

519
  /* find first '(', the character before the number */
520
  number = strchr(string, '(');
521

522
  if (NULL == number) {
42✔
523
    msSetError(MS_WCSERR, "Invalid size parameter value.",
×
524
               "msWCSParseSize20()");
525
    return MS_FAILURE;
×
526
  }
527

528
  /* cut trailing ')' */
529
  check = strchr(string, ')');
530
  if (NULL == check) {
42✔
531
    msSetError(MS_WCSERR, "Invalid size parameter value.",
×
532
               "msWCSParseSize20()");
533
    return MS_FAILURE;
×
534
  }
535
  *number = '\0';
42✔
536
  ++number;
42✔
537
  *check = '\0';
42✔
538

539
  strlcpy(outAxis, string, axisStringLen);
540

541
  /* parse size value */
542
  if (msStringParseInteger(number, outSize) != MS_SUCCESS) {
42✔
543
    msSetError(MS_WCSERR, "Parameter value '%s' is not a valid integer.",
1✔
544
               "msWCSParseSize20()", number);
545
    return MS_FAILURE;
1✔
546
  }
547

548
  return MS_SUCCESS;
549
}
550

551
/************************************************************************/
552
/*                   msWCSParseResolutionString20()                     */
553
/*                                                                      */
554
/*      Parses a resolution string and returns the axis, the units of   */
555
/*      measure and the resolution value.                               */
556
/*      Subset string: axis ( value )                                   */
557
/************************************************************************/
558

559
static int msWCSParseResolutionString20(char *string, char *outAxis,
25✔
560
                                        size_t axisStringLen,
561
                                        double *outResolution) {
562
  char *number = NULL;
563
  char *check = NULL;
564

565
  /* find brackets */
566
  number = strchr(string, '(');
567

568
  if (NULL == number) {
25✔
569
    msSetError(MS_WCSERR, "Invalid resolution parameter value : %s.",
×
570
               "msWCSParseSize20()", string);
571
    return MS_FAILURE;
×
572
  }
573

574
  /* cut trailing ')' */
575
  check = strchr(string, ')');
576
  if (NULL == check) {
25✔
577
    msSetError(MS_WCSERR, "Invalid size parameter value.",
×
578
               "msWCSParseSize20()");
579
    return MS_FAILURE;
×
580
  }
581

582
  *number = '\0';
25✔
583
  ++number;
25✔
584
  *check = '\0';
25✔
585

586
  strlcpy(outAxis, string, axisStringLen);
587

588
  if (msStringParseDouble(number, outResolution) != MS_SUCCESS) {
25✔
589
    *outResolution = MS_WCS20_UNBOUNDED;
×
590
    msSetError(MS_WCSERR, "Invalid resolution parameter value : %s.",
×
591
               "msWCSParseSize20()", number);
592
    return MS_FAILURE;
×
593
  }
594

595
  return MS_SUCCESS;
596
}
597

598
/************************************************************************/
599
/*                   msWCSParseScaleString20()                          */
600
/*                                                                      */
601
/*      Parses a scale string and returns the axis, the and the value.  */
602
/*      Subset string: axis ( value )                                   */
603
/************************************************************************/
604

605
static int msWCSParseScaleString20(char *string, char *outAxis,
2✔
606
                                   size_t axisStringLen, double *outScale) {
607
  char *number = NULL;
608
  char *check = NULL;
609

610
  /* find brackets */
611
  number = strchr(string, '(');
612

613
  if (NULL == number) {
2✔
614
    msSetError(MS_WCSERR, "Invalid resolution parameter value : %s.",
×
615
               "msWCSParseScaleString20()", string);
616
    return MS_FAILURE;
×
617
  }
618

619
  /* cut trailing ')' */
620
  check = strchr(string, ')');
621
  if (NULL == check || check < number) {
2✔
622
    msSetError(MS_WCSERR, "Invalid scale parameter value.",
×
623
               "msWCSParseScaleString20()");
624
    return MS_FAILURE;
×
625
  }
626

627
  *number = '\0';
2✔
628
  ++number;
2✔
629
  *check = '\0';
2✔
630

631
  strlcpy(outAxis, string, axisStringLen);
632

633
  if (msStringParseDouble(number, outScale) != MS_SUCCESS || *outScale <= 0.0) {
2✔
634
    *outScale = MS_WCS20_UNBOUNDED;
×
635
    msSetError(MS_WCSERR, "Invalid scale parameter value : %s.",
×
636
               "msWCSParseScaleString20()", number);
637
    return MS_FAILURE;
×
638
  }
639

640
  return MS_SUCCESS;
641
}
642

643
/************************************************************************/
644
/*                   msWCSParseResolutionString20()                     */
645
/*                                                                      */
646
/*      Parses a resolution string and returns the axis, the units of   */
647
/*      measure and the resolution value.                               */
648
/*      Subset string: axis ( value )                                   */
649
/************************************************************************/
650

651
static int msWCSParseScaleExtentString20(char *string, char *outAxis,
2✔
652
                                         size_t axisStringLen, int *outMin,
653
                                         int *outMax) {
654
  char *number = NULL;
655
  char *check = NULL;
656
  char *colon = NULL;
657

658
  /* find brackets */
659
  number = strchr(string, '(');
660

661
  if (NULL == number) {
2✔
662
    msSetError(MS_WCSERR, "Invalid extent parameter value : %s.",
×
663
               "msWCSParseScaleExtentString20()", string);
664
    return MS_FAILURE;
×
665
  }
666

667
  /* find colon */
668
  colon = strchr(string, ':');
669

670
  if (NULL == colon || colon < number) {
2✔
671
    msSetError(MS_WCSERR, "Invalid extent parameter value : %s.",
×
672
               "msWCSParseScaleExtentString20()", string);
673
    return MS_FAILURE;
×
674
  }
675

676
  /* cut trailing ')' */
677
  check = strchr(string, ')');
678

679
  if (NULL == check || check < colon) {
2✔
680
    msSetError(MS_WCSERR, "Invalid extent parameter value.",
×
681
               "msWCSParseScaleExtentString20()");
682
    return MS_FAILURE;
×
683
  }
684

685
  *number = '\0';
2✔
686
  ++number;
2✔
687
  *colon = '\0';
2✔
688
  ++colon;
2✔
689
  *check = '\0';
2✔
690

691
  strlcpy(outAxis, string, axisStringLen);
692

693
  if (msStringParseInteger(number, outMin) != MS_SUCCESS) {
2✔
694
    *outMin = 0;
×
695
    msSetError(MS_WCSERR, "Invalid min parameter value : %s.",
×
696
               "msWCSParseScaleExtentString20()", number);
697
    return MS_FAILURE;
×
698
  } else if (msStringParseInteger(colon, outMax) != MS_SUCCESS) {
2✔
699
    *outMax = 0;
×
700
    msSetError(MS_WCSERR, "Invalid resolution parameter value : %s.",
×
701
               "msWCSParseScaleExtentString20()", colon);
702
    return MS_FAILURE;
×
703
  }
704

705
  if (*outMin > *outMax) {
2✔
706
    msSetError(MS_WCSERR,
×
707
               "Invalid extent: lower part is higher than upper part.",
708
               "msWCSParseScaleExtentString20()");
709
    return MS_FAILURE;
×
710
  }
711

712
  return MS_SUCCESS;
713
}
714

715
#if defined(USE_LIBXML2)
716
/*
717
  Utility function to get the first child of a node with a given node name
718
  */
719

720
xmlNodePtr msLibXml2GetFirstChild(xmlNodePtr parent, const char *name) {
35✔
721
  xmlNodePtr node;
722
  if (!parent || !name) {
35✔
723
    return NULL;
724
  }
725

726
  XML_FOREACH_CHILD(parent, node) {
54✔
727
    XML_LOOP_IGNORE_COMMENT_OR_TEXT(node);
54✔
728
    if (EQUAL((char *)node->name, name)) {
54✔
729
      return node;
35✔
730
    }
731
  }
732
  return NULL;
733
}
734

735
/*
736
  Utility function to get the first child of a node with a given node name and
737
  namespace.
738
  */
739

740
xmlNodePtr msLibXml2GetFirstChildNs(xmlNodePtr parent, const char *name,
×
741
                                    xmlNsPtr ns) {
742
  xmlNodePtr node;
743
  if (!parent || !name || !ns) {
×
744
    return NULL;
745
  }
746

747
  XML_FOREACH_CHILD(parent, node) {
×
748
    XML_LOOP_IGNORE_COMMENT_OR_TEXT(node);
×
749
    if (EQUAL((char *)node->name, name) && ns == node->ns) {
×
750
      return node;
×
751
    }
752
  }
753
  return NULL;
754
}
755

756
/*
757
  Utility function to get the first child of a node with a given node name
758
  */
759

760
xmlNodePtr msLibXml2GetFirstChildElement(xmlNodePtr parent) {
14✔
761
  xmlNodePtr node;
762
  if (!parent) {
14✔
763
    return NULL;
764
  }
765

766
  XML_FOREACH_CHILD(parent, node) {
14✔
767
    if (node->type == XML_ELEMENT_NODE) {
14✔
768
      return node;
14✔
769
    }
770
  }
771
  return NULL;
772
}
773
#endif /* defined(USE_LIBXML2) */
774

775
/************************************************************************/
776
/*                   msWCSParseRequest20_XMLGetCapabilities()           */
777
/*                                                                      */
778
/*      Parses a DOM element, representing a GetCapabilities-request    */
779
/*      to a params object.                                             */
780
/************************************************************************/
781
#if defined(USE_LIBXML2)
782
static int msWCSParseRequest20_XMLGetCapabilities(xmlNodePtr root,
11✔
783
                                                  wcs20ParamsObjPtr params) {
784
  xmlNodePtr child;
785
  char *content = NULL;
786
  XML_FOREACH_CHILD(root, child) {
19✔
787
    XML_LOOP_IGNORE_COMMENT_OR_TEXT(child)
8✔
788
    else if (EQUAL((char *)child->name, "AcceptVersions")) {
8✔
789
      xmlNodePtr versionNode = NULL;
790
      XML_FOREACH_CHILD(child, versionNode) {
×
791
        XML_LOOP_IGNORE_COMMENT_OR_TEXT(versionNode);
×
792
        XML_ASSERT_NODE_NAME(versionNode, "Version");
×
793

794
        content = (char *)xmlNodeGetContent(versionNode);
×
795
        params->accept_versions =
×
796
            CSLAddString(params->accept_versions, content);
×
797
        xmlFree(content);
×
798
      }
799
    }
800
    else if (EQUAL((char *)child->name, "Sections")) {
8✔
801
      xmlNodePtr sectionNode = NULL;
802
      XML_FOREACH_CHILD(child, sectionNode) {
×
803
        XML_LOOP_IGNORE_COMMENT_OR_TEXT(sectionNode)
×
804
        XML_ASSERT_NODE_NAME(sectionNode, "Section");
×
805

806
        content = (char *)xmlNodeGetContent(sectionNode);
×
807
        params->sections = CSLAddString(params->sections, content);
×
808
        xmlFree(content);
×
809
      }
810
    }
811
    else if (EQUAL((char *)child->name, "UpdateSequence")) {
8✔
812
      params->updatesequence = (char *)xmlNodeGetContent(child);
×
813
    }
814
    else if (EQUAL((char *)child->name, "AcceptFormats")) {
8✔
815
      /* Maybe not necessary, since only format is xml.   */
816
      /* At least ignore it, to not generate an error.    */
817
    }
818
    else if (EQUAL((char *)child->name, "AcceptLanguages")) {
8✔
819
      xmlNodePtr languageNode;
820
      XML_FOREACH_CHILD(child, languageNode) {
18✔
821
        XML_LOOP_IGNORE_COMMENT_OR_TEXT(languageNode)
10✔
822
        XML_ASSERT_NODE_NAME(languageNode, "Language");
10✔
823

824
        content = (char *)xmlNodeGetContent(languageNode);
10✔
825
        params->accept_languages =
10✔
826
            CSLAddString(params->accept_languages, content);
10✔
827
        xmlFree(content);
10✔
828
      }
829
    }
830
    else {
831
      XML_UNKNOWN_NODE_ERROR(child);
×
832
    }
833
  }
834
  return MS_SUCCESS;
835
}
836
#endif
837

838
/************************************************************************/
839
/*                   msWCSParseRequest20_XMLDescribeCoverage()          */
840
/*                                                                      */
841
/*      Parses a DOM element, representing a DescribeCoverage-request   */
842
/*      to a params object.                                             */
843
/************************************************************************/
844
#if defined(USE_LIBXML2)
845
static int msWCSParseRequest20_XMLDescribeCoverage(xmlNodePtr root,
1✔
846
                                                   wcs20ParamsObjPtr params) {
847
  xmlNodePtr child;
848
  char *id;
849

850
  XML_FOREACH_CHILD(root, child) {
2✔
851
    XML_LOOP_IGNORE_COMMENT_OR_TEXT(child)
1✔
852
    XML_ASSERT_NODE_NAME(child, "CoverageID");
1✔
853

854
    /* Node content is the coverage ID */
855
    id = (char *)xmlNodeGetContent(child);
1✔
856
    if (id == NULL || strlen(id) == 0) {
1✔
857
      msSetError(MS_WCSERR, "CoverageID could not be parsed.",
×
858
                 "msWCSParseRequest20_XMLDescribeCoverage()");
859
      return MS_FAILURE;
×
860
    }
861
    /* insert coverage ID into the list */
862
    params->ids = CSLAddString(params->ids, (char *)id);
1✔
863
    xmlFree(id);
1✔
864
  }
865
  return MS_SUCCESS;
866
}
867
#endif
868

869
/************************************************************************/
870
/*                   msWCSParseRequest20_XMLGetCoverage()               */
871
/*                                                                      */
872
/*      Parses a DOM element, representing a GetCoverage-request to a   */
873
/*      params object.                                                  */
874
/************************************************************************/
875
#if defined(USE_LIBXML2)
876
static int msWCSParseRequest20_XMLGetCoverage(mapObj *map, xmlNodePtr root,
56✔
877
                                              wcs20ParamsObjPtr params) {
878
  xmlNodePtr child;
879
  char *id;
880

881
  XML_FOREACH_CHILD(root, child) {
277✔
882
    XML_LOOP_IGNORE_COMMENT_OR_TEXT(child)
221✔
883
    else if (EQUAL((char *)child->name, "CoverageID")) {
221✔
884
      /* Node content is the coverage ID */
885
      id = (char *)xmlNodeGetContent(child);
56✔
886
      if (id == NULL || strlen(id) == 0) {
56✔
887
        msSetError(MS_WCSERR, "CoverageID could not be parsed.",
×
888
                   "msWCSParseRequest20_XMLGetCoverage()");
889
        return MS_FAILURE;
×
890
      }
891

892
      /* insert coverage ID into the list */
893
      params->ids = CSLAddString(params->ids, (char *)id);
56✔
894
      xmlFree(id);
56✔
895
    }
896
    else if (EQUAL((char *)child->name, "Format")) {
165✔
897
      msFree(params->format);
55✔
898
      params->format = (char *)xmlNodeGetContent(child);
55✔
899
    }
900
    else if (EQUAL((char *)child->name, "Mediatype")) {
110✔
901
      char *content = (char *)xmlNodeGetContent(child);
3✔
902
      if (content != NULL && (EQUAL(content, "multipart/mixed") ||
3✔
903
                              EQUAL(content, "multipart/related"))) {
3✔
904
        params->multipart = MS_TRUE;
3✔
905
      } else {
906
        msSetError(MS_WCSERR, "Invalid value '%s' for parameter 'Mediatype'.",
×
907
                   "msWCSParseRequest20()", content);
908
        xmlFree(content);
×
909
        return MS_FAILURE;
×
910
      }
911
      xmlFree(content);
3✔
912
    }
913
    else if (EQUAL((char *)child->name, "DimensionTrim")) {
107✔
914
      wcs20AxisObjPtr axis = NULL;
915
      wcs20SubsetObjPtr subset = NULL;
916
      xmlNodePtr node = NULL;
917
      char *axisName = NULL, *min = NULL, *max = NULL, *crs = NULL;
918

919
      /* get strings for axis, min and max */
920
      XML_FOREACH_CHILD(child, node) {
156✔
921
        XML_LOOP_IGNORE_COMMENT_OR_TEXT(node)
116✔
922
        else if (EQUAL((char *)node->name, "Dimension")) {
116✔
923
          if (axisName != NULL) {
40✔
924
            msSetError(MS_WCSERR, "Parameter 'Dimension' is already set.",
×
925
                       "msWCSParseRequest20_XMLGetCoverage()");
926
            return MS_FAILURE;
×
927
          }
928
          axisName = (char *)xmlNodeGetContent(node);
40✔
929
          crs = (char *)xmlGetProp(node, BAD_CAST "crs");
40✔
930
        }
931
        else if (EQUAL((char *)node->name, "trimLow")) {
76✔
932
          min = (char *)xmlNodeGetContent(node);
38✔
933
        }
934
        else if (EQUAL((char *)node->name, "trimHigh")) {
38✔
935
          max = (char *)xmlNodeGetContent(node);
38✔
936
        }
937
        else {
938
          msFree(axisName);
×
939
          msFree(min);
×
940
          msFree(max);
×
941
          msFree(crs);
×
942
          XML_UNKNOWN_NODE_ERROR(node);
×
943
        }
944
      }
945
      if (NULL == (subset = msWCSCreateSubsetObj20())) {
40✔
946
        msFree(axisName);
×
947
        msFree(min);
×
948
        msFree(max);
×
949
        msFree(crs);
×
950
        return MS_FAILURE;
×
951
      }
952

953
      /* min and max have to have a value */
954
      if (min == NULL) {
40✔
955
        min = msStrdup("*");
2✔
956
      }
957
      if (max == NULL) {
40✔
958
        max = msStrdup("*");
2✔
959
      }
960
      if (msWCSParseSubset20(subset, axisName, crs, min, max) == MS_FAILURE) {
40✔
961
        msWCSFreeSubsetObj20(subset);
×
962
        msWCSException(map, "InvalidSubsetting", "subset", "2.0.1");
×
963
        return MS_DONE;
×
964
      }
965

966
      if (NULL == (axis = msWCSFindAxis20(params, subset->axis))) {
40✔
967
        if (NULL == (axis = msWCSCreateAxisObj20())) {
24✔
968
          msFree(axisName);
×
969
          msFree(min);
×
970
          msFree(max);
×
971
          msFree(crs);
×
972
          return MS_FAILURE;
×
973
        }
974
        axis->name = msStrdup(subset->axis);
24✔
975
        msWCSInsertAxisObj20(params, axis);
24✔
976
      }
977

978
      axis->subset = subset;
40✔
979

980
      /* cleanup */
981
      msFree(axisName);
40✔
982
      msFree(min);
40✔
983
      msFree(max);
40✔
984
      msFree(crs);
40✔
985
    }
986
    else if (EQUAL((char *)child->name, "DimensionSlice")) {
67✔
987
      msSetError(MS_WCSERR, "Operation '%s' is not supported by MapServer.",
×
988
                 "msWCSParseRequest20_XMLGetCoverage()", (char *)child->name);
989
      return MS_FAILURE;
×
990
    }
991
    else if (EQUAL((char *)child->name, "Size")) {
67✔
992
      wcs20AxisObjPtr axis;
993
      char *axisName;
994
      char *content;
995

996
      if (NULL ==
19✔
997
          (axisName = (char *)xmlGetProp(child, BAD_CAST "dimension"))) {
19✔
998
        msSetError(MS_WCSERR,
×
999
                   "Attribute 'dimension' is missing in element 'Size'.",
1000
                   "msWCSParseRequest20_XMLGetCoverage()");
1001
        return MS_FAILURE;
×
1002
      }
1003

1004
      if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
19✔
1005
        if (NULL == (axis = msWCSCreateAxisObj20())) {
19✔
1006
          xmlFree(axisName);
×
1007
          return MS_FAILURE;
×
1008
        }
1009
        axis->name = msStrdup(axisName);
19✔
1010
        msWCSInsertAxisObj20(params, axis);
19✔
1011
      }
1012
      xmlFree(axisName);
19✔
1013

1014
      content = (char *)xmlNodeGetContent(child);
19✔
1015
      if (msStringParseInteger(content, &(axis->size)) != MS_SUCCESS) {
19✔
1016
        xmlFree(content);
×
1017
        msSetError(MS_WCSERR,
×
1018
                   "Value of element 'Size' could not "
1019
                   "be parsed to a valid integer.",
1020
                   "msWCSParseRequest20_XMLGetCoverage()");
1021
        return MS_FAILURE;
×
1022
      }
1023
      xmlFree(content);
19✔
1024
    }
1025
    else if (EQUAL((char *)child->name, "Resolution")) {
48✔
1026
      wcs20AxisObjPtr axis;
1027
      char *axisName;
1028
      char *content;
1029

1030
      if (NULL ==
13✔
1031
          (axisName = (char *)xmlGetProp(child, BAD_CAST "dimension"))) {
13✔
1032
        msSetError(MS_WCSERR,
×
1033
                   "Attribute 'dimension' is missing "
1034
                   "in element 'Resolution'.",
1035
                   "msWCSParseRequest20_XMLGetCoverage()");
1036
        return MS_FAILURE;
×
1037
      }
1038

1039
      if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
13✔
1040
        if (NULL == (axis = msWCSCreateAxisObj20())) {
12✔
1041
          xmlFree(axisName);
×
1042
          return MS_FAILURE;
×
1043
        }
1044
        axis->name = msStrdup(axisName);
12✔
1045
        msWCSInsertAxisObj20(params, axis);
12✔
1046
      }
1047
      xmlFree(axisName);
13✔
1048

1049
      axis->resolutionUOM = (char *)xmlGetProp(child, BAD_CAST "uom");
13✔
1050

1051
      content = (char *)xmlNodeGetContent(child);
13✔
1052
      if (msStringParseDouble(content, &(axis->resolution)) != MS_SUCCESS) {
13✔
1053
        msSetError(MS_WCSERR,
×
1054
                   "Value of element 'Resolution' could not "
1055
                   "be parsed to a valid value.",
1056
                   "msWCSParseRequest20_XMLGetCoverage()");
1057
        xmlFree(content);
×
1058
        return MS_FAILURE;
×
1059
      }
1060
      xmlFree(content);
13✔
1061
    }
1062
    else if (EQUAL((char *)child->name, "Interpolation")) {
35✔
1063
      /* Deprecated, use wcs:Extension/int:Interpolation/int:globalInterpolation
1064
       */
1065
      msFree(params->interpolation);
4✔
1066
      params->interpolation = (char *)xmlNodeGetContent(child);
4✔
1067
    }
1068
    else if (EQUAL((char *)child->name, "OutputCRS")) {
31✔
1069
      params->outputcrs = (char *)xmlNodeGetContent(child);
×
1070
    }
1071
    else if (EQUAL((char *)child->name, "rangeSubset")) {
31✔
1072
      /* Deprecated, use wcs:Extension/rsub:RangeSubset */
1073
      xmlNodePtr bandNode = NULL;
1074
      XML_FOREACH_CHILD(child, bandNode) {
24✔
1075
        char *content = NULL;
1076
        XML_LOOP_IGNORE_COMMENT_OR_TEXT(bandNode);
18✔
1077
        XML_ASSERT_NODE_NAME(bandNode, "band");
18✔
1078

1079
        content = (char *)xmlNodeGetContent(bandNode);
18✔
1080
        params->range_subset = CSLAddString(params->range_subset, content);
18✔
1081
        xmlFree(content);
18✔
1082
      }
1083
    }
1084
    else if (EQUAL((char *)child->name, "Extension")) {
25✔
1085
      xmlNodePtr extensionNode = NULL;
1086
      XML_FOREACH_CHILD(child, extensionNode) {
54✔
1087
        XML_LOOP_IGNORE_COMMENT_OR_TEXT(extensionNode);
29✔
1088

1089
        if (EQUAL((char *)extensionNode->name, "Scaling")) {
29✔
1090
          xmlNodePtr scaleMethodNode =
1091
              msLibXml2GetFirstChildElement(extensionNode);
7✔
1092

1093
          if (EQUAL((char *)scaleMethodNode->name, "ScaleByFactor")) {
7✔
1094
            xmlNodePtr scaleFactorNode =
1095
                msLibXml2GetFirstChildElement(scaleMethodNode);
1✔
1096
            char *content;
1097
            if (!scaleFactorNode ||
1✔
1098
                !EQUAL((char *)scaleFactorNode->name, "scaleFactor")) {
1✔
1099
              msSetError(MS_WCSERR, "Missing 'scaleFactor' node.",
×
1100
                         "msWCSParseRequest20_XMLGetCoverage()");
1101
              return MS_FAILURE;
×
1102
            }
1103
            content = (char *)xmlNodeGetContent(scaleFactorNode);
1✔
1104
            if (msStringParseDouble(content, &(params->scale)) != MS_SUCCESS ||
1✔
1105
                params->scale < 0.0) {
1✔
1106
              msSetError(MS_WCSERR, "Invalid scaleFactor '%s'.",
×
1107
                         "msWCSParseRequest20_XMLGetCoverage()", content);
1108
              xmlFree(content);
×
1109
              return MS_FAILURE;
×
1110
            }
1111
            xmlFree(content);
1✔
1112
          }
1113

1114
          else if (EQUAL((char *)scaleMethodNode->name, "ScaleAxesByFactor")) {
6✔
1115
            xmlNodePtr scaleAxisNode, axisNode, scaleFactorNode;
1116
            char *axisName, *content;
1117
            wcs20AxisObjPtr axis;
1118

1119
            XML_FOREACH_CHILD(scaleMethodNode, scaleAxisNode) {
3✔
1120
              XML_LOOP_IGNORE_COMMENT_OR_TEXT(scaleAxisNode);
2✔
1121

1122
              if (!EQUAL((char *)scaleAxisNode->name, "ScaleAxis")) {
2✔
1123
                msSetError(MS_WCSERR, "Invalid ScaleAxesByFactor.",
×
1124
                           "msWCSParseRequest20_XMLGetCoverage()");
1125
                return MS_FAILURE;
×
1126
              }
1127

1128
              /* axis */
1129
              if (NULL ==
2✔
1130
                  (axisNode = msLibXml2GetFirstChild(scaleAxisNode, "axis"))) {
2✔
1131
                msSetError(MS_WCSERR, "Missing axis node",
×
1132
                           "msWCSParseRequest20_XMLGetCoverage()");
1133
                return MS_FAILURE;
×
1134
              }
1135
              axisName = (char *)xmlNodeGetContent(axisNode);
2✔
1136
              if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
2✔
1137
                if (NULL == (axis = msWCSCreateAxisObj20())) {
2✔
1138
                  xmlFree(axisName);
×
1139
                  return MS_FAILURE;
×
1140
                }
1141
                axis->name = msStrdup(axisName);
2✔
1142
                msWCSInsertAxisObj20(params, axis);
2✔
1143
              }
1144
              xmlFree(axisName);
2✔
1145

1146
              if (axis->scale != MS_WCS20_UNBOUNDED) {
2✔
1147
                msSetError(MS_WCSERR,
×
1148
                           "scaleFactor was already set for axis '%s'.",
1149
                           "msWCSParseRequest20_XMLGetCoverage()", axis->name);
1150
                return MS_FAILURE;
×
1151
              }
1152

1153
              /* scaleFactor */
1154
              if (NULL == (scaleFactorNode = msLibXml2GetFirstChild(
2✔
1155
                               scaleAxisNode, "scaleFactor"))) {
1156
                msSetError(MS_WCSERR, "Missing scaleFactor node",
×
1157
                           "msWCSParseRequest20_XMLGetCoverage()");
1158
                return MS_FAILURE;
×
1159
              }
1160

1161
              content = (char *)xmlNodeGetContent(scaleFactorNode);
2✔
1162
              if (msStringParseDouble(content, &(axis->scale)) != MS_SUCCESS ||
2✔
1163
                  axis->scale < 0.0) {
2✔
1164
                msSetError(MS_WCSERR, "Invalid scaleFactor '%s'.",
×
1165
                           "msWCSParseRequest20_XMLGetCoverage()", content);
1166
                xmlFree(content);
×
1167
                return MS_FAILURE;
×
1168
              }
1169
              xmlFree(content);
2✔
1170
            }
1171
          }
1172

1173
          else if (EQUAL((char *)scaleMethodNode->name, "ScaleToSize")) {
5✔
1174
            xmlNodePtr scaleAxisNode, axisNode, targetSizeNode;
1175
            char *axisName, *content;
1176
            wcs20AxisObjPtr axis;
1177

1178
            XML_FOREACH_CHILD(scaleMethodNode, scaleAxisNode) {
12✔
1179
              XML_LOOP_IGNORE_COMMENT_OR_TEXT(scaleAxisNode);
8✔
1180

1181
              if (!EQUAL((char *)scaleAxisNode->name, "TargetAxisSize")) {
8✔
1182
                msSetError(MS_WCSERR, "Invalid ScaleToSize.",
×
1183
                           "msWCSParseRequest20_XMLGetCoverage()");
1184
                return MS_FAILURE;
×
1185
              }
1186

1187
              /* axis */
1188
              if (NULL ==
8✔
1189
                  (axisNode = msLibXml2GetFirstChild(scaleAxisNode, "axis"))) {
8✔
1190
                msSetError(MS_WCSERR, "Missing axis node",
×
1191
                           "msWCSParseRequest20_XMLGetCoverage()");
1192
                return MS_FAILURE;
×
1193
              }
1194
              axisName = (char *)xmlNodeGetContent(axisNode);
8✔
1195
              if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
8✔
1196
                if (NULL == (axis = msWCSCreateAxisObj20())) {
8✔
1197
                  xmlFree(axisName);
×
1198
                  return MS_FAILURE;
×
1199
                }
1200
                axis->name = msStrdup(axisName);
8✔
1201
                msWCSInsertAxisObj20(params, axis);
8✔
1202
              }
1203
              xmlFree(axisName);
8✔
1204

1205
              if (axis->size != 0) {
8✔
1206
                msSetError(MS_WCSERR,
×
1207
                           "targetSize was already set for axis '%s'.",
1208
                           "msWCSParseRequest20_XMLGetCoverage()", axis->name);
1209
                return MS_FAILURE;
×
1210
              }
1211

1212
              /* targetSize */
1213
              if (NULL == (targetSizeNode = msLibXml2GetFirstChild(
8✔
1214
                               scaleAxisNode, "targetSize"))) {
1215
                msSetError(MS_WCSERR, "Missing targetSize node",
×
1216
                           "msWCSParseRequest20_XMLGetCoverage()");
1217
                return MS_FAILURE;
×
1218
              }
1219

1220
              content = (char *)xmlNodeGetContent(targetSizeNode);
8✔
1221
              if (msStringParseInteger(content, &(axis->size)) != MS_SUCCESS ||
8✔
1222
                  axis->size <= 0) {
8✔
1223
                msSetError(MS_WCSERR, "Invalid targetSize '%s'.",
×
1224
                           "msWCSParseRequest20_XMLGetCoverage()", content);
1225
                xmlFree(content);
×
1226
                return MS_FAILURE;
×
1227
              }
1228
              xmlFree(content);
8✔
1229
            }
1230
          }
1231

1232
          else if (EQUAL((char *)scaleMethodNode->name, "ScaleToExtent")) {
1✔
1233
            xmlNodePtr scaleAxisNode, axisNode, lowNode, highNode;
1234
            char *axisName, *content;
1235
            wcs20AxisObjPtr axis;
1236
            int low, high;
1237

1238
            XML_FOREACH_CHILD(scaleMethodNode, scaleAxisNode) {
3✔
1239
              XML_LOOP_IGNORE_COMMENT_OR_TEXT(scaleAxisNode);
2✔
1240

1241
              if (!EQUAL((char *)scaleAxisNode->name, "TargetAxisExtent")) {
2✔
1242
                msSetError(MS_WCSERR, "Invalid ScaleToExtent.",
×
1243
                           "msWCSParseRequest20_XMLGetCoverage()");
1244
                return MS_FAILURE;
×
1245
              }
1246

1247
              /* axis */
1248
              if (NULL ==
2✔
1249
                  (axisNode = msLibXml2GetFirstChild(scaleAxisNode, "axis"))) {
2✔
1250
                msSetError(MS_WCSERR, "Missing axis node",
×
1251
                           "msWCSParseRequest20_XMLGetCoverage()");
1252
                return MS_FAILURE;
×
1253
              }
1254
              axisName = (char *)xmlNodeGetContent(axisNode);
2✔
1255
              if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
2✔
1256
                if (NULL == (axis = msWCSCreateAxisObj20())) {
2✔
1257
                  xmlFree(axisName);
×
1258
                  return MS_FAILURE;
×
1259
                }
1260
                axis->name = msStrdup(axisName);
2✔
1261
                msWCSInsertAxisObj20(params, axis);
2✔
1262
              }
1263
              xmlFree(axisName);
2✔
1264

1265
              if (axis->size != 0) {
2✔
1266
                msSetError(MS_WCSERR,
×
1267
                           "targetSize was already set for axis '%s'.",
1268
                           "msWCSParseRequest20_XMLGetCoverage()", axis->name);
1269
                return MS_FAILURE;
×
1270
              }
1271

1272
              /* targetSize */
1273
              if (NULL ==
2✔
1274
                  (lowNode = msLibXml2GetFirstChild(scaleAxisNode, "low"))) {
2✔
1275
                msSetError(MS_WCSERR, "Missing low node",
×
1276
                           "msWCSParseRequest20_XMLGetCoverage()");
1277
                return MS_FAILURE;
×
1278
              }
1279

1280
              if (NULL ==
2✔
1281
                  (highNode = msLibXml2GetFirstChild(scaleAxisNode, "high"))) {
2✔
1282
                msSetError(MS_WCSERR, "Missing high node",
×
1283
                           "msWCSParseRequest20_XMLGetCoverage()");
1284
                return MS_FAILURE;
×
1285
              }
1286

1287
              content = (char *)xmlNodeGetContent(lowNode);
2✔
1288
              if (msStringParseInteger(content, &low) != MS_SUCCESS) {
2✔
1289
                msSetError(MS_WCSERR, "Invalid low value '%s'.",
×
1290
                           "msWCSParseRequest20_XMLGetCoverage()", content);
1291
                xmlFree(content);
×
1292
                return MS_FAILURE;
×
1293
              }
1294
              xmlFree(content);
2✔
1295

1296
              content = (char *)xmlNodeGetContent(highNode);
2✔
1297
              if (msStringParseInteger(content, &high) != MS_SUCCESS) {
2✔
1298
                msSetError(MS_WCSERR, "Invalid high value '%s'.",
×
1299
                           "msWCSParseRequest20_XMLGetCoverage()", content);
1300
                xmlFree(content);
×
1301
                return MS_FAILURE;
×
1302
              }
1303
              xmlFree(content);
2✔
1304

1305
              if (high <= low) {
2✔
1306
                msSetError(MS_WCSERR, "Invalid extent, high is lower than low.",
×
1307
                           "msWCSParseRequest20_XMLGetCoverage()");
1308
                return MS_FAILURE;
×
1309
              }
1310

1311
              axis->size = high - low;
2✔
1312
            }
1313
          }
1314
        }
1315

1316
        /* Range Subset */
1317
        else if (EQUAL((char *)extensionNode->name, "RangeSubset")) {
22✔
1318
          xmlNodePtr rangeItemNode = NULL;
1319

1320
          XML_FOREACH_CHILD(extensionNode, rangeItemNode) {
9✔
1321
            xmlNodePtr rangeItemNodeChild =
1322
                msLibXml2GetFirstChildElement(rangeItemNode);
6✔
1323
            XML_LOOP_IGNORE_COMMENT_OR_TEXT(rangeItemNode);
6✔
1324

1325
            XML_ASSERT_NODE_NAME(rangeItemNode, "RangeItem");
6✔
1326

1327
            if (!rangeItemNodeChild) {
6✔
1328
              msSetError(MS_WCSERR, "Missing RangeComponent or RangeInterval.",
×
1329
                         "msWCSParseRequest20_XMLGetCoverage()");
1330
              return MS_FAILURE;
×
1331
            } else if (EQUAL((char *)rangeItemNodeChild->name,
6✔
1332
                             "RangeComponent")) {
1333
              char *content = (char *)xmlNodeGetContent(rangeItemNodeChild);
3✔
1334
              params->range_subset =
3✔
1335
                  CSLAddString(params->range_subset, content);
3✔
1336
              xmlFree(content);
3✔
1337
            } else if (EQUAL((char *)rangeItemNodeChild->name,
3✔
1338
                             "RangeInterval")) {
1339
              xmlNodePtr intervalNode = rangeItemNodeChild;
1340
              xmlNodePtr startComponentNode =
1341
                  msLibXml2GetFirstChild(intervalNode, "startComponent");
3✔
1342
              xmlNodePtr endComponentNode =
1343
                  msLibXml2GetFirstChild(intervalNode, "endComponent");
3✔
1344
              char *start;
1345
              char *stop;
1346

1347
              if (!startComponentNode || !endComponentNode) {
3✔
1348
                msSetError(MS_WCSERR, "Wrong RangeInterval.",
×
1349
                           "msWCSParseRequest20_XMLGetCoverage()");
1350
                return MS_FAILURE;
×
1351
              }
1352

1353
              start = (char *)xmlNodeGetContent(startComponentNode);
3✔
1354
              stop = (char *)xmlNodeGetContent(endComponentNode);
3✔
1355

1356
              std::string value(start);
3✔
1357
              value += ':';
1358
              value += stop;
1359

1360
              xmlFree(start);
3✔
1361
              xmlFree(stop);
3✔
1362

1363
              params->range_subset =
3✔
1364
                  CSLAddString(params->range_subset, value.c_str());
3✔
1365
            }
1366
          }
1367
        }
1368

1369
        else if (EQUAL((char *)extensionNode->name, "subsettingCrs")) {
19✔
1370
          msFree(params->subsetcrs);
14✔
1371
          params->subsetcrs = (char *)xmlNodeGetContent(extensionNode);
14✔
1372
        }
1373

1374
        else if (EQUAL((char *)extensionNode->name, "outputCrs")) {
5✔
1375
          msFree(params->outputcrs);
1✔
1376
          params->outputcrs = (char *)xmlNodeGetContent(extensionNode);
1✔
1377
        }
1378

1379
        else if (EQUAL((char *)extensionNode->name, "Interpolation")) {
4✔
1380
          xmlNodePtr globalInterpolation =
1381
              msLibXml2GetFirstChild(extensionNode, "globalInterpolation");
3✔
1382
          char *content;
1383
          if (globalInterpolation == NULL) {
3✔
1384
            msSetError(MS_WCSERR, "Missing 'globalInterpolation' node.",
×
1385
                       "msWCSParseRequest20_XMLGetCoverage()");
1386
            return MS_FAILURE;
×
1387
          }
1388
          content = (char *)xmlNodeGetContent(globalInterpolation);
3✔
1389
          msFree(params->interpolation);
3✔
1390
          /* TODO: use URIs/URLs once they are specified */
1391
          params->interpolation = msStrdup(content);
3✔
1392
          xmlFree(content);
3✔
1393
        }
1394

1395
        /* GeoTIFF parameters */
1396
        else if (EQUAL((char *)extensionNode->name, "parameters") &&
1✔
1397
                 extensionNode->ns &&
1✔
1398
                 EQUAL((char *)extensionNode->ns->href,
1✔
1399
                       "http://www.opengis.net/gmlcov/geotiff/1.0")) {
1400

1401
          xmlNodePtr parameter;
1402

1403
          XML_FOREACH_CHILD(extensionNode, parameter) {
3✔
1404
            char *content;
1405
            XML_LOOP_IGNORE_COMMENT_OR_TEXT(parameter);
2✔
1406

1407
            content = (char *)xmlNodeGetContent(parameter);
2✔
1408

1409
            params->format_options = CSLAddNameValue(
4✔
1410
                params->format_options, (char *)parameter->name, content);
2✔
1411
            xmlFree(content);
2✔
1412
          }
1413
        }
1414
      }
1415
    }
1416
    else {
1417
      XML_UNKNOWN_NODE_ERROR(child);
×
1418
    }
1419
  }
1420
  return MS_SUCCESS;
1421
}
1422
#endif
1423

1424
/************************************************************************/
1425
/*                   msWCSParseRequest20()                              */
1426
/*                                                                      */
1427
/*      Parses a CGI-request to a WCS 20 params object. It is           */
1428
/*      either a POST or a GET request. In case of a POST request       */
1429
/*      the xml content has to be parsed to a DOM structure             */
1430
/*      before the parameters can be extracted.                         */
1431
/************************************************************************/
1432

1433
int msWCSParseRequest20(mapObj *map, cgiRequestObj *request,
213✔
1434
                        owsRequestObj *ows_request, wcs20ParamsObjPtr params) {
1435
  int i;
1436
  if (params == NULL || request == NULL || ows_request == NULL) {
213✔
1437
    msSetError(MS_WCSERR, "Internal error.", "msWCSParseRequest20()");
×
1438
    return MS_FAILURE;
×
1439
  }
1440

1441
  /* Copy arbitrary service, version and request. */
1442
  params->service = msStrdup(ows_request->service);
213✔
1443
  if (ows_request->version != NULL) {
213✔
1444
    params->version = msStrdup(ows_request->version);
211✔
1445
  }
1446
  params->request = msStrdup(ows_request->request);
213✔
1447

1448
  /* Parse the POST request */
1449
  if (request->type == MS_POST_REQUEST) {
213✔
1450
#if defined(USE_LIBXML2)
1451
    xmlDocPtr doc = static_cast<xmlDocPtr>(ows_request->document);
68✔
1452
    xmlNodePtr root = NULL;
1453
    const char *validate;
1454
    int ret = MS_SUCCESS;
1455

1456
    /* parse to DOM-Structure and get root element */
1457
    if (doc == NULL) {
68✔
1458
      const xmlError *error = xmlGetLastError();
×
1459
      msSetError(MS_WCSERR, "XML parsing error: %s", "msWCSParseRequest20()",
×
1460
                 error->message);
×
1461
      return MS_FAILURE;
×
1462
    }
1463

1464
    root = xmlDocGetRootElement(doc);
68✔
1465

1466
    validate = msOWSLookupMetadata(&(map->web.metadata), "CO", "validate_xml");
68✔
1467
    if (validate != NULL && EQUAL(validate, "TRUE")) {
68✔
1468
      char *schema_dir = msStrdup(
×
1469
          msOWSLookupMetadata(&(map->web.metadata), "CO", "schemas_dir"));
1470
      if (schema_dir != NULL &&
×
1471
          (params->version == NULL || EQUALN(params->version, "2.0", 3))) {
×
1472
        schema_dir = msStringConcatenate(schema_dir, "wcs/2.0.0/wcsAll.xsd");
×
1473
        if (msOWSSchemaValidation(schema_dir, request->postrequest) != 0) {
×
1474
          msSetError(MS_WCSERR,
×
1475
                     "Invalid POST request. "
1476
                     "XML is not valid",
1477
                     "msWCSParseRequest20()");
1478
          return MS_FAILURE;
×
1479
        }
1480
      }
1481
      msFree(schema_dir);
×
1482
    }
1483

1484
    if (EQUAL(params->request, "GetCapabilities")) {
68✔
1485
      ret = msWCSParseRequest20_XMLGetCapabilities(root, params);
11✔
1486
    } else if (params->version != NULL && EQUALN(params->version, "2.0", 3)) {
57✔
1487
      if (EQUAL(params->request, "DescribeCoverage")) {
57✔
1488
        ret = msWCSParseRequest20_XMLDescribeCoverage(root, params);
1✔
1489
      } else if (EQUAL(params->request, "GetCoverage")) {
56✔
1490
        ret = msWCSParseRequest20_XMLGetCoverage(map, root, params);
56✔
1491
      }
1492
    }
1493
    return ret;
68✔
1494

1495
#else  /* defined(USE_LIBXML2) */
1496
    /* TODO: maybe with CPLXML? */
1497
    return MS_FAILURE;
1498
#endif /* defined(USE_LIBXML2) */
1499
  }
1500

1501
  /* Parse the KVP GET request */
1502
  for (i = 0; i < request->NumParams; ++i) {
1,186✔
1503
    char *key = NULL, *value = NULL;
1504
    char **tokens;
1505
    int num, j;
1506
    key = request->ParamNames[i];
1,045✔
1507
    value = request->ParamValues[i];
1,045✔
1508

1509
    if (EQUAL(key, "VERSION")) {
1,045✔
1510
      continue;
433✔
1511
    } else if (EQUAL(key, "REQUEST")) {
902✔
1512
      continue;
145✔
1513
    } else if (EQUAL(key, "SERVICE")) {
757✔
1514
      continue;
145✔
1515
    } else if (EQUAL(key, "ACCEPTVERSIONS")) {
612✔
1516
      tokens = msStringSplit(value, ',', &num);
1✔
1517
      for (j = 0; j < num; ++j) {
6✔
1518
        params->accept_versions =
5✔
1519
            CSLAddString(params->accept_versions, tokens[j]);
5✔
1520
      }
1521
      msFreeCharArray(tokens, num);
1✔
1522
    } else if (EQUAL(key, "SECTIONS")) {
611✔
1523
      tokens = msStringSplit(value, ',', &num);
7✔
1524
      for (j = 0; j < num; ++j) {
18✔
1525
        params->sections = CSLAddString(params->sections, tokens[j]);
11✔
1526
      }
1527
      msFreeCharArray(tokens, num);
7✔
1528
    } else if (EQUAL(key, "UPDATESEQUENCE")) {
604✔
1529
      msFree(params->updatesequence);
3✔
1530
      params->updatesequence = msStrdup(value);
3✔
1531
    } else if (EQUAL(key, "ACCEPTFORMATS")) {
601✔
1532
      /* ignore */
1533
    } else if (EQUAL(key, "ACCEPTLANGUAGES")) {
601✔
1534
      if (params->accept_languages != NULL) {
8✔
1535
        CSLDestroy(params->accept_languages);
×
1536
      }
1537
      params->accept_languages = CSLTokenizeString2(value, ",", 0);
8✔
1538
    } else if (EQUAL(key, "COVERAGEID")) {
593✔
1539
      if (params->ids != NULL) {
114✔
1540
        msSetError(MS_WCSERR,
×
1541
                   "Parameter 'CoverageID' is already set. "
1542
                   "For multiple IDs use a comma separated list.",
1543
                   "msWCSParseRequest20()");
1544
        return MS_FAILURE;
4✔
1545
      }
1546
      params->ids = CSLTokenizeString2(value, ",", 0);
114✔
1547
    } else if (EQUAL(key, "FORMAT")) {
479✔
1548
      msFree(params->format);
105✔
1549
      params->format = msStrdup(value);
105✔
1550
    } else if (EQUAL(key, "MEDIATYPE")) {
374✔
1551
      if (EQUAL(value, "multipart/mixed") ||
10✔
1552
          EQUAL(value, "multipart/related")) {
7✔
1553
        params->multipart = MS_TRUE;
9✔
1554
      } else {
1555
        msSetError(MS_WCSERR, "Invalid value '%s' for parameter 'Mediatype'.",
1✔
1556
                   "msWCSParseRequest20()", value);
1557
        return MS_FAILURE;
1✔
1558
      }
1559
    } else if (EQUAL(key, "INTERPOLATION")) {
364✔
1560
      msFree(params->interpolation);
7✔
1561
      params->interpolation = msStrdup(value);
7✔
1562
    } else if (EQUAL(key, "OUTPUTCRS")) {
357✔
1563
      msFree(params->outputcrs);
4✔
1564
      params->outputcrs = msStrdup(value);
4✔
1565
    } else if (EQUAL(key, "SUBSETTINGCRS")) {
353✔
1566
      msFree(params->subsetcrs);
34✔
1567
      params->subsetcrs = msStrdup(value);
34✔
1568
    } else if (EQUAL(key, "SCALEFACTOR")) {
319✔
1569
      double scale = MS_WCS20_UNBOUNDED;
1✔
1570
      if (params->scale != MS_WCS20_UNBOUNDED) {
1✔
1571
        msSetError(MS_WCSERR, "Parameter 'SCALEFACTOR' already set.",
×
1572
                   "msWCSParseRequest20()");
1573
        return MS_FAILURE;
×
1574
      } else if (msStringParseDouble(value, &scale) != MS_SUCCESS) {
1✔
1575
        msSetError(MS_WCSERR, "Could not parse parameter 'SCALEFACTOR'.",
×
1576
                   "msWCSParseRequest20()");
1577
        return MS_FAILURE;
×
1578
      } else if (scale <= 0.0) {
1✔
1579
        msSetError(MS_WCSERR, "Invalid value for 'SCALEFACTOR'.",
×
1580
                   "msWCSParseRequest20()");
1581
        return MS_FAILURE;
×
1582
      }
1583
      params->scale = scale;
1✔
1584
    } else if (EQUAL(key, "SCALEAXES")) {
318✔
1585
      wcs20AxisObjPtr axis = NULL;
1586
      tokens = msStringSplit(value, ',', &num);
1✔
1587
      for (j = 0; j < num; ++j) {
3✔
1588
        char axisName[500];
1589
        double scale;
1590

1591
        if (msWCSParseScaleString20(tokens[j], axisName, sizeof(axisName),
2✔
1592
                                    &scale) != MS_SUCCESS) {
1593
          msFreeCharArray(tokens, num);
×
1594
          return MS_FAILURE;
×
1595
        }
1596

1597
        if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
2✔
1598
          if (NULL == (axis = msWCSCreateAxisObj20())) {
2✔
1599
            msFreeCharArray(tokens, num);
×
1600
            return MS_FAILURE;
×
1601
          }
1602
          axis->name = msStrdup(axisName);
2✔
1603
          msWCSInsertAxisObj20(params, axis);
2✔
1604
        }
1605

1606
        /* check if the size of the axis is already set */
1607
        if (axis->scale != MS_WCS20_UNBOUNDED) {
2✔
1608
          msFreeCharArray(tokens, num);
×
1609
          msSetError(MS_WCSERR, "The scale of the axis is already set.",
×
1610
                     "msWCSParseRequest20()");
1611
          return MS_FAILURE;
×
1612
        }
1613
        axis->scale = scale;
2✔
1614
      }
1615
      msFreeCharArray(tokens, num);
1✔
1616
    } else if (EQUAL(key, "SCALESIZE")) {
317✔
1617
      wcs20AxisObjPtr axis = NULL;
1618
      tokens = msStringSplit(value, ',', &num);
1✔
1619
      for (j = 0; j < num; ++j) {
3✔
1620
        char axisName[500];
1621
        int size;
1622

1623
        if (msWCSParseSizeString20(tokens[j], axisName, sizeof(axisName),
2✔
1624
                                   &size) != MS_SUCCESS) {
1625
          msFreeCharArray(tokens, num);
×
1626
          return MS_FAILURE;
×
1627
        }
1628

1629
        if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
2✔
1630
          if (NULL == (axis = msWCSCreateAxisObj20())) {
2✔
1631
            msFreeCharArray(tokens, num);
×
1632
            return MS_FAILURE;
×
1633
          }
1634
          axis->name = msStrdup(axisName);
2✔
1635
          msWCSInsertAxisObj20(params, axis);
2✔
1636
        }
1637

1638
        /* check if the size of the axis is already set */
1639
        if (axis->size != 0) {
2✔
1640
          msFreeCharArray(tokens, num);
×
1641
          msSetError(MS_WCSERR, "The size of the axis is already set.",
×
1642
                     "msWCSParseRequest20()");
1643
          return MS_FAILURE;
×
1644
        }
1645
        axis->size = size;
2✔
1646
      }
1647
      msFreeCharArray(tokens, num);
1✔
1648
    } else if (EQUAL(key, "SCALEEXTENT")) {
316✔
1649
      wcs20AxisObjPtr axis = NULL;
1650
      /* No real support for scaleextent, we just interpret it as SCALESIZE */
1651
      tokens = msStringSplit(value, ',', &num);
1✔
1652
      for (j = 0; j < num; ++j) {
3✔
1653
        char axisName[500];
1654
        int min, max;
1655

1656
        if (msWCSParseScaleExtentString20(tokens[j], axisName, sizeof(axisName),
2✔
1657
                                          &min, &max) != MS_SUCCESS) {
1658
          msFreeCharArray(tokens, num);
×
1659
          return MS_FAILURE;
×
1660
        }
1661

1662
        if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
2✔
1663
          if (NULL == (axis = msWCSCreateAxisObj20())) {
2✔
1664
            msFreeCharArray(tokens, num);
×
1665
            return MS_FAILURE;
×
1666
          }
1667
          axis->name = msStrdup(axisName);
2✔
1668
          msWCSInsertAxisObj20(params, axis);
2✔
1669
        }
1670

1671
        /* check if the size of the axis is already set */
1672
        if (axis->size != 0) {
2✔
1673
          msFreeCharArray(tokens, num);
×
1674
          msSetError(MS_WCSERR, "The size of the axis is already set.",
×
1675
                     "msWCSParseRequest20()");
1676
          return MS_FAILURE;
×
1677
        }
1678
        axis->size = max - min;
2✔
1679
      }
1680
      msFreeCharArray(tokens, num);
1✔
1681
      /* We explicitly don't test for strict equality as the parameter name is
1682
       * supposed to be unique */
1683
    } else if (EQUALN(key, "SIZE", 4)) {
315✔
1684
      /* Deprecated scaling */
1685
      wcs20AxisObjPtr axis = NULL;
1686
      char axisName[500];
1687
      int size = 0;
40✔
1688

1689
      if (msWCSParseSizeString20(value, axisName, sizeof(axisName), &size) ==
40✔
1690
          MS_FAILURE) {
1691
        return MS_FAILURE;
1✔
1692
      }
1693

1694
      if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
39✔
1695
        if (NULL == (axis = msWCSCreateAxisObj20())) {
22✔
1696
          return MS_FAILURE;
1697
        }
1698
        axis->name = msStrdup(axisName);
22✔
1699
        msWCSInsertAxisObj20(params, axis);
22✔
1700
      }
1701

1702
      /* check if the size of the axis is already set */
1703
      if (axis->size != 0) {
39✔
1704
        msSetError(MS_WCSERR, "The size of the axis is already set.",
×
1705
                   "msWCSParseRequest20()");
1706
        return MS_FAILURE;
×
1707
      }
1708
      axis->size = size;
39✔
1709
      /* We explicitly don't test for strict equality as the parameter name is
1710
       * supposed to be unique */
1711
    } else if (EQUALN(key, "RESOLUTION", 10)) {
275✔
1712
      wcs20AxisObjPtr axis = NULL;
1713
      char axisName[500];
1714
      double resolution = 0;
25✔
1715

1716
      if (msWCSParseResolutionString20(value, axisName, sizeof(axisName),
25✔
1717
                                       &resolution) == MS_FAILURE) {
1718
        return MS_FAILURE;
×
1719
      }
1720

1721
      /* check if axis object already exists, otherwise create a new one */
1722
      if (NULL == (axis = msWCSFindAxis20(params, axisName))) {
25✔
1723
        if (NULL == (axis = msWCSCreateAxisObj20())) {
9✔
1724
          return MS_FAILURE;
1725
        }
1726
        axis->name = msStrdup(axisName);
9✔
1727
        msWCSInsertAxisObj20(params, axis);
9✔
1728
      }
1729

1730
      /* check if the resolution of the axis is already set */
1731
      if (axis->resolution != MS_WCS20_UNBOUNDED) {
25✔
1732
        msSetError(MS_WCSERR, "The resolution of the axis is already set.",
×
1733
                   "msWCSParseRequest20()");
1734
        return MS_FAILURE;
×
1735
      }
1736
      axis->resolution = resolution;
25✔
1737
      /* We explicitly don't test for strict equality as the parameter name is
1738
       * supposed to be unique */
1739
    } else if (EQUALN(key, "SUBSET", 6)) {
250✔
1740
      wcs20AxisObjPtr axis = NULL;
1741
      wcs20SubsetObjPtr subset = msWCSCreateSubsetObj20();
78✔
1742
      if (NULL == subset) {
78✔
1743
        return MS_FAILURE;
1744
      }
1745
      if (msWCSParseSubsetKVPString20(subset, value) == MS_FAILURE) {
78✔
1746
        msWCSFreeSubsetObj20(subset);
1✔
1747
        msWCSException(map, "InvalidSubsetting", "subset",
1✔
1748
                       ows_request->version);
1✔
1749
        return MS_DONE;
1✔
1750
      }
1751

1752
      if (NULL == (axis = msWCSFindAxis20(params, subset->axis))) {
77✔
1753
        if (NULL == (axis = msWCSCreateAxisObj20())) {
76✔
1754
          return MS_FAILURE;
1755
        }
1756
        axis->name = msStrdup(subset->axis);
76✔
1757
        msWCSInsertAxisObj20(params, axis);
76✔
1758
      }
1759

1760
      if (NULL != axis->subset) {
77✔
1761
        msSetError(MS_WCSERR, "The axis '%s' is already subsetted.",
1✔
1762
                   "msWCSParseRequest20()", axis->name);
1763
        msWCSFreeSubsetObj20(subset);
1✔
1764
        msWCSException(map, "InvalidAxisLabel", "subset", ows_request->version);
1✔
1765
        return MS_DONE;
1✔
1766
      }
1767
      axis->subset = subset;
76✔
1768
    } else if (EQUAL(key, "RANGESUBSET")) {
172✔
1769
      tokens = msStringSplit(value, ',', &num);
18✔
1770
      for (j = 0; j < num; ++j) {
56✔
1771
        params->range_subset = CSLAddString(params->range_subset, tokens[j]);
38✔
1772
      }
1773
      msFreeCharArray(tokens, num);
18✔
1774
    } else if (EQUALN(key, "GEOTIFF:", 8)) {
154✔
1775
      params->format_options =
8✔
1776
          CSLAddNameValue(params->format_options, key, value);
8✔
1777
    }
1778
    /* Ignore all other parameters here */
1779
  }
1780

1781
  return MS_SUCCESS;
1782
}
1783

1784
#if defined(USE_LIBXML2)
1785

1786
/************************************************************************/
1787
/*                   msWCSValidateAndFindSubsets20()                    */
1788
/*                                                                      */
1789
/*      Iterates over every axis in the parameters and checks if the    */
1790
/*      axis name is in any string list. If found, but there already is */
1791
/*      a sample found for this axis, an Error is returned. Also if no  */
1792
/*      axis is found for a given axis, an error is returned.           */
1793
/************************************************************************/
1794
static int msWCSValidateAndFindAxes20(wcs20ParamsObjPtr params,
157✔
1795
                                      wcs20AxisObjPtr outAxes[]) {
1796
  static const int numAxis = 2;
1797
  const char *const validXAxisNames[] = {
157✔
1798
      "x",         "xaxis", "x-axis",   "x_axis",   "long", "long_axis",
1799
      "long-axis", "lon",   "lon_axis", "lon-axis", NULL};
1800
  const char *const validYAxisNames[] = {
157✔
1801
      "y", "yaxis", "y-axis", "y_axis", "lat", "lat_axis", "lat-axis", NULL};
1802
  const char *const *const validAxisNames[2] = {validXAxisNames,
1803
                                                validYAxisNames};
157✔
1804
  int iParamAxis, iAcceptedAxis, iName, i;
1805

1806
  for (i = 0; i < numAxis; ++i) {
471✔
1807
    outAxes[i] = NULL;
314✔
1808
  }
1809

1810
  /* iterate over all subsets */
1811
  for (iParamAxis = 0; iParamAxis < params->numaxes; ++iParamAxis) {
334✔
1812
    int found = 0;
1813

1814
    /* iterate over all given axes */
1815
    for (iAcceptedAxis = 0; iAcceptedAxis < numAxis; ++iAcceptedAxis) {
266✔
1816
      /* iterate over all possible names for the current axis */
1817
      for (iName = 0; validAxisNames[iAcceptedAxis][iName] != NULL; ++iName) {
1,243✔
1818
        /* compare axis name with current possible name */
1819
        if (EQUAL(params->axes[iParamAxis]->name,
1,155✔
1820
                  validAxisNames[iAcceptedAxis][iName])) {
1821
          /* if there is already a sample for the axis, throw error */
1822
          if (outAxes[iAcceptedAxis] != NULL) {
177✔
1823
            msSetError(MS_WCSERR,
×
1824
                       "The axis with the name '%s' corresponds "
1825
                       "to the same axis as the subset with the name '%s'.",
1826
                       "msWCSValidateAndFindAxes20()",
1827
                       outAxes[iAcceptedAxis]->name,
1828
                       params->axes[iParamAxis]->name);
1829
            return MS_FAILURE;
×
1830
          }
1831

1832
          /* if match is found, save it */
1833
          outAxes[iAcceptedAxis] = params->axes[iParamAxis];
177✔
1834
          found = 1;
1835
          break;
1836
        }
1837
      }
1838
      if (found) {
1839
        break;
1840
      }
1841
    }
1842

1843
    /* no valid representation for current subset found */
1844
    /* exit and throw error                             */
1845
    if (found == 0) {
178✔
1846
      msSetError(MS_WCSERR, "Invalid subset axis '%s'.",
1✔
1847
                 "msWCSValidateAndFindAxes20()",
1848
                 params->axes[iParamAxis]->name);
1✔
1849
      return MS_FAILURE;
1✔
1850
    }
1851
  }
1852
  return MS_SUCCESS;
1853
}
1854

1855
/************************************************************************/
1856
/*                   msWCSPrepareNamespaces20()                         */
1857
/*                                                                      */
1858
/*      Inserts namespace definitions into the root node of a DOM       */
1859
/*      structure.                                                      */
1860
/************************************************************************/
1861

1862
static void msWCSPrepareNamespaces20(xmlDocPtr pDoc, xmlNodePtr psRootNode,
57✔
1863
                                     mapObj *map, int addInspire) {
1864
  xmlNsPtr psXsiNs;
1865
  char *schemaLocation = NULL;
1866
  char *xsi_schemaLocation = NULL;
1867

1868
  xmlSetNs(psRootNode,
57✔
1869
           xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_WCS_20_NAMESPACE_URI,
1870
                    BAD_CAST MS_OWSCOMMON_WCS_NAMESPACE_PREFIX));
1871

1872
  xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_OWS_20_NAMESPACE_URI,
57✔
1873
           BAD_CAST MS_OWSCOMMON_OWS_NAMESPACE_PREFIX);
1874
  xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_OGC_NAMESPACE_URI,
57✔
1875
           BAD_CAST MS_OWSCOMMON_OGC_NAMESPACE_PREFIX);
1876
  xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_W3C_XSI_NAMESPACE_URI,
57✔
1877
           BAD_CAST MS_OWSCOMMON_W3C_XSI_NAMESPACE_PREFIX);
1878
  xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_W3C_XLINK_NAMESPACE_URI,
57✔
1879
           BAD_CAST MS_OWSCOMMON_W3C_XLINK_NAMESPACE_PREFIX);
1880
  xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_WCS_20_NAMESPACE_URI,
57✔
1881
           BAD_CAST MS_OWSCOMMON_WCS_NAMESPACE_PREFIX);
1882
  xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_GML_32_NAMESPACE_URI,
57✔
1883
           BAD_CAST MS_OWSCOMMON_GML_NAMESPACE_PREFIX);
1884
  xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_GMLCOV_10_NAMESPACE_URI,
57✔
1885
           BAD_CAST MS_OWSCOMMON_GMLCOV_NAMESPACE_PREFIX);
1886
  xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_SWE_20_NAMESPACE_URI,
57✔
1887
           BAD_CAST MS_OWSCOMMON_SWE_NAMESPACE_PREFIX);
1888

1889
  if (addInspire) {
57✔
1890
    xmlNewNs(psRootNode, BAD_CAST MS_INSPIRE_COMMON_NAMESPACE_URI,
20✔
1891
             BAD_CAST MS_INSPIRE_COMMON_NAMESPACE_PREFIX);
1892
    xmlNewNs(psRootNode, BAD_CAST MS_INSPIRE_DLS_NAMESPACE_URI,
20✔
1893
             BAD_CAST MS_INSPIRE_DLS_NAMESPACE_PREFIX);
1894
  }
1895

1896
  psXsiNs = xmlSearchNs(pDoc, psRootNode,
57✔
1897
                        BAD_CAST MS_OWSCOMMON_W3C_XSI_NAMESPACE_PREFIX);
1898

1899
  schemaLocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
57✔
1900
  xsi_schemaLocation = msStrdup(MS_OWSCOMMON_WCS_20_NAMESPACE_URI);
57✔
1901
  xsi_schemaLocation = msStringConcatenate(xsi_schemaLocation, " ");
57✔
1902
  xsi_schemaLocation = msStringConcatenate(xsi_schemaLocation, schemaLocation);
57✔
1903
  xsi_schemaLocation = msStringConcatenate(
57✔
1904
      xsi_schemaLocation, MS_OWSCOMMON_WCS_20_SCHEMAS_LOCATION);
1905
  xsi_schemaLocation = msStringConcatenate(xsi_schemaLocation, " ");
57✔
1906

1907
  if (addInspire) {
57✔
1908
    xsi_schemaLocation = msStringConcatenate(xsi_schemaLocation,
20✔
1909
                                             MS_INSPIRE_DLS_NAMESPACE_URI " ");
1910
    xsi_schemaLocation = msStringConcatenate(
20✔
1911
        xsi_schemaLocation, msOWSGetInspireSchemasLocation(map));
1912
    xsi_schemaLocation =
1913
        msStringConcatenate(xsi_schemaLocation, MS_INSPIRE_DLS_SCHEMA_LOCATION);
20✔
1914
  }
1915

1916
  xmlNewNsProp(psRootNode, psXsiNs, BAD_CAST "schemaLocation",
57✔
1917
               BAD_CAST xsi_schemaLocation);
1918

1919
  msFree(schemaLocation);
57✔
1920
  msFree(xsi_schemaLocation);
57✔
1921
}
57✔
1922

1923
/************************************************************************/
1924
/*                   msWCSGetFormatList20()                             */
1925
/*                                                                      */
1926
/*      Copied from mapwcs.c.                                           */
1927
/************************************************************************/
1928

1929
static char *msWCSGetFormatsList20(mapObj *map, layerObj *layer) {
35✔
1930
  char *format_list = msStrdup("");
35✔
1931
  char **tokens = NULL, **formats = NULL;
1932
  int i, numtokens = 0, numformats;
35✔
1933
  char *value;
1934

1935
  /* -------------------------------------------------------------------- */
1936
  /*      Parse from layer metadata.                                      */
1937
  /* -------------------------------------------------------------------- */
1938
  if (layer != NULL &&
35✔
1939
      (value = msOWSGetEncodeMetadata(&(layer->metadata), "CO", "formats",
×
1940
                                      NULL)) != NULL) {
1941
    tokens = msStringSplit(value, ' ', &numtokens);
×
1942
    msFree(value);
×
1943
  }
1944

1945
  /* -------------------------------------------------------------------- */
1946
  /*      Parse from map.web metadata.                                    */
1947
  /* -------------------------------------------------------------------- */
1948
  else if ((value = msOWSGetEncodeMetadata(&(map->web.metadata), "CO",
35✔
1949
                                           "formats", NULL)) != NULL) {
1950
    tokens = msStringSplit(value, ' ', &numtokens);
×
1951
    msFree(value);
×
1952
  }
1953

1954
  /* -------------------------------------------------------------------- */
1955
  /*      Or generate from all configured raster output formats that      */
1956
  /*      look plausible.                                                 */
1957
  /* -------------------------------------------------------------------- */
1958
  else {
1959
    tokens = (char **)msSmallCalloc(map->numoutputformats, sizeof(char *));
35✔
1960
    for (i = 0; i < map->numoutputformats; i++) {
626✔
1961
      switch (map->outputformatlist[i]->renderer) {
591✔
1962
        /* seemingly normal raster format */
1963
      case MS_RENDER_WITH_AGG:
311✔
1964
      case MS_RENDER_WITH_RAWDATA:
1965
        tokens[numtokens++] = msStrdup(map->outputformatlist[i]->name);
311✔
1966
        break;
311✔
1967
        /* rest of formats aren't really WCS compatible */
1968
      default:
1969
        break;
1970
      }
1971
    }
1972
  }
1973

1974
  /* -------------------------------------------------------------------- */
1975
  /*      Convert outputFormatObj names into mime types and remove        */
1976
  /*      duplicates.                                                     */
1977
  /* -------------------------------------------------------------------- */
1978
  numformats = 0;
1979
  formats = (char **)msSmallCalloc(sizeof(char *), numtokens);
35✔
1980

1981
  for (i = 0; i < numtokens; i++) {
346✔
1982
    int format_i, j;
1983
    const char *mimetype;
1984

1985
    for (format_i = 0; format_i < map->numoutputformats; format_i++) {
1,646✔
1986
      if (EQUAL(map->outputformatlist[format_i]->name, tokens[i]))
1,646✔
1987
        break;
1988
    }
1989

1990
    if (format_i == map->numoutputformats) {
311✔
1991
      msDebug("Failed to find outputformat info on format '%s', ignore.\n",
×
1992
              tokens[i]);
×
1993
      continue;
×
1994
    }
1995

1996
    mimetype = map->outputformatlist[format_i]->mimetype;
311✔
1997
    if (mimetype == NULL || strlen(mimetype) == 0) {
311✔
1998
      msDebug("No mimetime for format '%s', ignoring.\n", tokens[i]);
×
1999
      continue;
×
2000
    }
2001

2002
    for (j = 0; j < numformats; j++) {
1,100✔
2003
      if (EQUAL(mimetype, formats[j]))
857✔
2004
        break;
2005
    }
2006

2007
    if (j < numformats) {
311✔
2008
      msDebug("Format '%s' ignored since mimetype '%s' duplicates another "
68✔
2009
              "outputFormatObj.\n",
2010
              tokens[i], mimetype);
68✔
2011
      continue;
68✔
2012
    }
2013

2014
    formats[numformats++] = msStrdup(mimetype);
243✔
2015
  }
2016

2017
  msFreeCharArray(tokens, numtokens);
35✔
2018

2019
  /* -------------------------------------------------------------------- */
2020
  /*      Turn mimetype list into comma delimited form for easy use       */
2021
  /*      with xml functions.                                             */
2022
  /* -------------------------------------------------------------------- */
2023
  for (i = 0; i < numformats; i++) {
278✔
2024
    if (i > 0) {
243✔
2025
      format_list = msStringConcatenate(format_list, (char *)",");
208✔
2026
    }
2027
    format_list = msStringConcatenate(format_list, formats[i]);
243✔
2028
  }
2029
  msFreeCharArray(formats, numformats);
35✔
2030

2031
  return format_list;
35✔
2032
}
2033

2034
/************************************************************************/
2035
/*                   msWCSSwapAxes20                                    */
2036
/*                                                                      */
2037
/*      Helper function to determine if a SRS mandates swapped axes.    */
2038
/************************************************************************/
2039

2040
static int msWCSSwapAxes20(char *srs_uri) {
17✔
2041
  int srid = 0;
17✔
2042

2043
  /* get SRID from the srs uri */
2044
  if (srs_uri != NULL && strlen(srs_uri) > 0) {
17✔
2045
    if (sscanf(srs_uri, "http://www.opengis.net/def/crs/EPSG/0/%d", &srid) !=
17✔
2046
        EOF)
2047
      ;
2048
    else if (sscanf(srs_uri, "http://www.opengis.net/def/crs/%d", &srid) != EOF)
×
2049
      ;
2050
    else
2051
      srid = 0;
×
2052
  }
2053
  if (srid == 0)
17✔
2054
    return MS_FALSE;
2055

2056
  return msIsAxisInverted(srid);
17✔
2057
}
2058

2059
/************************************************************************/
2060
/*                   msWCSCommon20_CreateBoundedBy()                    */
2061
/*                                                                      */
2062
/*      Inserts the BoundedBy section into an existing DOM structure.   */
2063
/************************************************************************/
2064

2065
static void msWCSCommon20_CreateBoundedBy(wcs20coverageMetadataObjPtr cm,
17✔
2066
                                          xmlNsPtr psGmlNs, xmlNodePtr psRoot,
2067
                                          projectionObj *projection,
2068
                                          int swapAxes) {
2069
  xmlNodePtr psBoundedBy, psEnvelope;
2070
  char lowerCorner[100], upperCorner[100], axisLabels[100], uomLabels[100];
2071

2072
  psBoundedBy = xmlNewChild(psRoot, psGmlNs, BAD_CAST "boundedBy", NULL);
17✔
2073
  {
2074
    psEnvelope = xmlNewChild(psBoundedBy, psGmlNs, BAD_CAST "Envelope", NULL);
17✔
2075
    {
2076
      xmlNewProp(psEnvelope, BAD_CAST "srsName", BAD_CAST cm->srs_uri);
17✔
2077

2078
      if (projection->proj != NULL && msProjIsGeographicCRS(projection)) {
17✔
2079
        if (swapAxes == MS_FALSE) {
8✔
2080
          strlcpy(axisLabels, "long lat", sizeof(axisLabels));
2081
        } else {
2082
          strlcpy(axisLabels, "lat long", sizeof(axisLabels));
2083
        }
2084
        strlcpy(uomLabels, "deg deg", sizeof(uomLabels));
2085
      } else {
2086
        if (swapAxes == MS_FALSE) {
9✔
2087
          strlcpy(axisLabels, "x y", sizeof(axisLabels));
2088
        } else {
2089
          strlcpy(axisLabels, "y x", sizeof(axisLabels));
2090
        }
2091
        strlcpy(uomLabels, "m m", sizeof(uomLabels));
2092
      }
2093
      xmlNewProp(psEnvelope, BAD_CAST "axisLabels", BAD_CAST axisLabels);
17✔
2094
      xmlNewProp(psEnvelope, BAD_CAST "uomLabels", BAD_CAST uomLabels);
17✔
2095
      xmlNewProp(psEnvelope, BAD_CAST "srsDimension", BAD_CAST "2");
17✔
2096

2097
      if (swapAxes == MS_FALSE) {
17✔
2098
        snprintf(lowerCorner, sizeof(lowerCorner), "%.15g %.15g",
9✔
2099
                 cm->extent.minx, cm->extent.miny);
2100
        snprintf(upperCorner, sizeof(upperCorner), "%.15g %.15g",
9✔
2101
                 cm->extent.maxx, cm->extent.maxy);
2102
      } else {
2103
        snprintf(lowerCorner, sizeof(lowerCorner), "%.15g %.15g",
8✔
2104
                 cm->extent.miny, cm->extent.minx);
2105
        snprintf(upperCorner, sizeof(upperCorner), "%.15g %.15g",
8✔
2106
                 cm->extent.maxy, cm->extent.maxx);
2107
      }
2108

2109
      xmlNewChild(psEnvelope, psGmlNs, BAD_CAST "lowerCorner",
17✔
2110
                  BAD_CAST lowerCorner);
2111
      xmlNewChild(psEnvelope, psGmlNs, BAD_CAST "upperCorner",
17✔
2112
                  BAD_CAST upperCorner);
2113
    }
2114
  }
2115
}
17✔
2116

2117
/************************************************************************/
2118
/*                   msWCSCommon20_CreateDomainSet()                    */
2119
/*                                                                      */
2120
/*      Inserts the DomainSet section into an existing DOM structure.   */
2121
/************************************************************************/
2122

2123
static void msWCSCommon20_CreateDomainSet(layerObj *layer,
17✔
2124
                                          wcs20coverageMetadataObjPtr cm,
2125
                                          xmlNsPtr psGmlNs, xmlNodePtr psRoot,
2126
                                          projectionObj *projection,
2127
                                          int swapAxes) {
2128
  xmlNodePtr psDomainSet, psGrid, psLimits, psGridEnvelope, psOrigin, psOffsetX,
2129
      psOffsetY;
2130
  char low[100], high[100], id[100], point[100];
2131
  char offsetVector1[100], offsetVector2[100], axisLabels[100];
2132

2133
  psDomainSet = xmlNewChild(psRoot, psGmlNs, BAD_CAST "domainSet", NULL);
17✔
2134
  {
2135
    psGrid = xmlNewChild(psDomainSet, psGmlNs, BAD_CAST "RectifiedGrid", NULL);
17✔
2136
    {
2137
      double x0 = cm->geotransform[0] + cm->geotransform[1] / 2 +
17✔
2138
                  cm->geotransform[2] / 2;
17✔
2139
      double y0 = cm->geotransform[3] + cm->geotransform[4] / 2 +
17✔
2140
                  cm->geotransform[5] / 2;
17✔
2141
      double resx = cm->geotransform[1];
2142
      double resy = cm->geotransform[5];
2143

2144
      xmlNewProp(psGrid, BAD_CAST "dimension", BAD_CAST "2");
17✔
2145
      snprintf(id, sizeof(id), "grid_%s", layer->name);
17✔
2146
      xmlNewNsProp(psGrid, psGmlNs, BAD_CAST "id", BAD_CAST id);
17✔
2147

2148
      psLimits = xmlNewChild(psGrid, psGmlNs, BAD_CAST "limits", NULL);
17✔
2149
      {
2150
        psGridEnvelope =
2151
            xmlNewChild(psLimits, psGmlNs, BAD_CAST "GridEnvelope", NULL);
17✔
2152
        {
2153
          strlcpy(low, "0 0", sizeof(low));
2154
          snprintf(high, sizeof(high), "%d %d", cm->xsize - 1, cm->ysize - 1);
17✔
2155

2156
          xmlNewChild(psGridEnvelope, psGmlNs, BAD_CAST "low", BAD_CAST low);
17✔
2157
          xmlNewChild(psGridEnvelope, psGmlNs, BAD_CAST "high", BAD_CAST high);
17✔
2158
        }
2159
      }
2160

2161
      if (projection->proj != NULL && msProjIsGeographicCRS(projection)) {
17✔
2162
        strlcpy(axisLabels, "long lat", sizeof(axisLabels));
2163
      } else {
2164
        strlcpy(axisLabels, "x y", sizeof(axisLabels));
2165
      }
2166

2167
      xmlNewChild(psGrid, psGmlNs, BAD_CAST "axisLabels", BAD_CAST axisLabels);
17✔
2168

2169
      psOrigin = xmlNewChild(psGrid, psGmlNs, BAD_CAST "origin", NULL);
17✔
2170
      {
2171
        if (swapAxes == MS_FALSE) {
17✔
2172
          snprintf(point, sizeof(point), "%f %f", x0, y0);
2173
        } else {
2174
          snprintf(point, sizeof(point), "%f %f", y0, x0);
2175
        }
2176
        psOrigin = xmlNewChild(psOrigin, psGmlNs, BAD_CAST "Point", NULL);
17✔
2177
        snprintf(id, sizeof(id), "grid_origin_%s", layer->name);
17✔
2178
        xmlNewNsProp(psOrigin, psGmlNs, BAD_CAST "id", BAD_CAST id);
17✔
2179
        xmlNewProp(psOrigin, BAD_CAST "srsName", BAD_CAST cm->srs_uri);
17✔
2180

2181
        xmlNewChild(psOrigin, psGmlNs, BAD_CAST "pos", BAD_CAST point);
17✔
2182
      }
2183

2184
      if (swapAxes == MS_FALSE) {
17✔
2185
        snprintf(offsetVector1, sizeof(offsetVector1), "%f 0", resx);
2186
        snprintf(offsetVector2, sizeof(offsetVector2), "0 %f", resy);
2187
      } else {
2188
        snprintf(offsetVector1, sizeof(offsetVector1), "0 %f", resx);
2189
        snprintf(offsetVector2, sizeof(offsetVector2), "%f 0", resy);
2190
      }
2191
      psOffsetX = xmlNewChild(psGrid, psGmlNs, BAD_CAST "offsetVector",
17✔
2192
                              BAD_CAST offsetVector1);
2193
      psOffsetY = xmlNewChild(psGrid, psGmlNs, BAD_CAST "offsetVector",
17✔
2194
                              BAD_CAST offsetVector2);
2195

2196
      xmlNewProp(psOffsetX, BAD_CAST "srsName", BAD_CAST cm->srs_uri);
17✔
2197
      xmlNewProp(psOffsetY, BAD_CAST "srsName", BAD_CAST cm->srs_uri);
17✔
2198
    }
2199
  }
2200
}
17✔
2201

2202
/************************************************************************/
2203
/*                   msWCSCommon20_CreateRangeType()                    */
2204
/*                                                                      */
2205
/*      Inserts the RangeType section into an existing DOM structure.   */
2206
/************************************************************************/
2207

2208
static void msWCSCommon20_CreateRangeType(wcs20coverageMetadataObjPtr cm,
17✔
2209
                                          char *bands, xmlNsPtr psGmlcovNs,
2210
                                          xmlNsPtr psSweNs, xmlNodePtr psRoot) {
2211
  xmlNodePtr psRangeType, psDataRecord, psField, psQuantity, psUom,
2212
      psConstraint, psAllowedValues = NULL, psNilValues = NULL;
2213
  char **arr = NULL;
2214
  int num = 0;
17✔
2215

2216
  if (NULL != bands) {
17✔
2217
    arr = msStringSplit(bands, ',', &num);
12✔
2218
  }
2219

2220
  psRangeType = xmlNewChild(psRoot, psGmlcovNs, BAD_CAST "rangeType", NULL);
17✔
2221
  psDataRecord = xmlNewChild(psRangeType, psSweNs, BAD_CAST "DataRecord", NULL);
17✔
2222

2223
  /* iterate over every band */
2224
  for (unsigned i = 0; i < cm->numbands; ++i) {
90✔
2225
    /* only add bands that are in the range subset */
2226
    if (NULL != arr && num > 0) {
73✔
2227
      int found = MS_FALSE, j;
2228
      for (j = 0; j < num; ++j) {
157✔
2229
        int repr = 0;
2230
        if (msStringParseInteger(arr[j], &repr) == MS_SUCCESS &&
127✔
2231
            static_cast<unsigned>(repr) == i + 1) {
127✔
2232
          found = MS_TRUE;
2233
          break;
22✔
2234
        }
2235
      }
2236
      if (found == MS_FALSE) {
30✔
2237
        /* ignore this band since it is not in the range subset */
2238
        continue;
30✔
2239
      }
2240
    }
2241

2242
    /* add field tag */
2243
    psField = xmlNewChild(psDataRecord, psSweNs, BAD_CAST "field", NULL);
43✔
2244

2245
    if (cm->bands[i].name != NULL) {
43✔
2246
      xmlNewProp(psField, BAD_CAST "name", BAD_CAST cm->bands[i].name);
43✔
2247
    } else {
2248
      xmlNewProp(psField, BAD_CAST "name", BAD_CAST "band");
×
2249
    }
2250
    /* add Quantity tag */
2251
    psQuantity = xmlNewChild(psField, psSweNs, BAD_CAST "Quantity", NULL);
43✔
2252
    if (cm->bands[i].definition != NULL) {
43✔
2253
      xmlNewProp(psQuantity, BAD_CAST "definition",
18✔
2254
                 BAD_CAST cm->bands[i].definition);
2255
    }
2256
    if (cm->bands[i].description != NULL) {
43✔
2257
      xmlNewChild(psQuantity, psSweNs, BAD_CAST "description",
43✔
2258
                  BAD_CAST cm->bands[i].description);
2259
    }
2260

2261
    /* if there are given nilvalues -> add them to the first field */
2262
    /* all other fields get a reference to these */
2263
    if (cm->numnilvalues > 0) {
43✔
2264
      psNilValues = xmlNewChild(
43✔
2265
          xmlNewChild(psQuantity, psSweNs, BAD_CAST "nilValues", NULL), psSweNs,
2266
          BAD_CAST "NilValues", NULL);
2267
      for (unsigned j = 0; j < cm->numnilvalues; ++j) {
86✔
2268
        xmlNodePtr psTemp =
2269
            xmlNewChild(psNilValues, psSweNs, BAD_CAST "nilValue",
43✔
2270
                        BAD_CAST cm->nilvalues[j]);
43✔
2271
        if (j < cm->numnilvalues)
43✔
2272
          xmlNewProp(psTemp, BAD_CAST "reason",
43✔
2273
                     BAD_CAST cm->nilvalues_reasons[j]);
43✔
2274
      }
2275
    } else { /* create an empty nilValues tag */
2276
      xmlNewChild(psQuantity, psSweNs, BAD_CAST "nilValues", NULL);
×
2277
    }
2278

2279
    psUom = xmlNewChild(psQuantity, psSweNs, BAD_CAST "uom", NULL);
43✔
2280
    if (cm->bands[i].uom != NULL) {
43✔
2281
      xmlNewProp(psUom, BAD_CAST "code", BAD_CAST cm->bands[i].uom);
18✔
2282
    } else {
2283
      xmlNewProp(psUom, BAD_CAST "code", BAD_CAST "W.m-2.Sr-1");
25✔
2284
    }
2285

2286
    /* add constraint */
2287
    psConstraint =
2288
        xmlNewChild(psQuantity, psSweNs, BAD_CAST "constraint", NULL);
43✔
2289

2290
    {
2291
      char interval[100], significant_figures[100];
2292
      psAllowedValues =
2293
          xmlNewChild(psConstraint, psSweNs, BAD_CAST "AllowedValues", NULL);
43✔
2294

2295
      /* Interval */
2296
      snprintf(interval, sizeof(interval), "%.5g %.5g",
43✔
2297
               cm->bands[i].interval_min, cm->bands[i].interval_max);
43✔
2298
      xmlNewChild(psAllowedValues, psSweNs, BAD_CAST "interval",
43✔
2299
                  BAD_CAST interval);
2300

2301
      /* Significant figures */
2302
      snprintf(significant_figures, sizeof(significant_figures), "%d",
43✔
2303
               cm->bands[i].significant_figures);
43✔
2304
      xmlNewChild(psAllowedValues, psSweNs, BAD_CAST "significantFigures",
43✔
2305
                  BAD_CAST significant_figures);
2306
    }
2307
  }
2308
  msFreeCharArray(arr, num);
17✔
2309
}
17✔
2310

2311
/************************************************************************/
2312
/*                   msWCSWriteDocument20()                             */
2313
/*                                                                      */
2314
/*      Writes out an xml structure to the stream.                      */
2315
/************************************************************************/
2316

2317
static int msWCSWriteDocument20(xmlDocPtr psDoc) {
55✔
2318
  xmlChar *buffer = NULL;
55✔
2319
  int size = 0;
55✔
2320
  msIOContext *context = NULL;
2321
  char *contenttype = NULL;
2322

2323
  if (msIO_needBinaryStdout() == MS_FAILURE) {
55✔
2324
    return MS_FAILURE;
2325
  }
2326

2327
  if (EQUAL((char *)xmlDocGetRootElement(psDoc)->name, "RectifiedGridCoverage"))
55✔
2328
    contenttype = msStrdup("application/gml+xml");
12✔
2329
  else
2330
    contenttype = msStrdup("text/xml");
43✔
2331

2332
  msIO_setHeader("Content-Type", "%s; charset=UTF-8", contenttype);
55✔
2333
  msIO_sendHeaders();
55✔
2334
  msFree(contenttype);
55✔
2335

2336
  context = msIO_getHandler(stdout);
55✔
2337

2338
  xmlDocDumpFormatMemoryEnc(psDoc, &buffer, &size, "UTF-8", 1);
55✔
2339
  msIO_contextWrite(context, buffer, size);
55✔
2340
  xmlFree(buffer);
55✔
2341

2342
  return MS_SUCCESS;
2343
}
2344

2345
/************************************************************************/
2346
/*                   msWCSWriteFile20()                                 */
2347
/*                                                                      */
2348
/*      Writes an image object to the stream. If multipart is set,      */
2349
/*      then content sections are inserted.                             */
2350
/************************************************************************/
2351

2352
static int msWCSWriteFile20(mapObj *map, imageObj *image,
146✔
2353
                            wcs20ParamsObjPtr params, int multipart) {
2354
  int status;
2355
  char *filename = NULL;
2356
  char *base_dir = NULL;
2357
  const char *fo_filename;
2358
  int i;
2359

2360
  fo_filename = msGetOutputFormatOption(image->format, "FILENAME", NULL);
146✔
2361

2362
  /* -------------------------------------------------------------------- */
2363
  /*      Fetch the driver we will be using and check if it supports      */
2364
  /*      VSIL IO.                                                        */
2365
  /* -------------------------------------------------------------------- */
2366
  if (EQUALN(image->format->driver, "GDAL/", 5)) {
146✔
2367
    GDALDriverH hDriver;
2368
    const char *pszExtension = image->format->extension;
144✔
2369

2370
    msAcquireLock(TLOCK_GDAL);
144✔
2371
    hDriver = GDALGetDriverByName(image->format->driver + 5);
144✔
2372
    if (hDriver == NULL) {
144✔
2373
      msReleaseLock(TLOCK_GDAL);
×
2374
      msSetError(MS_MISCERR, "Failed to find %s driver.", "msWCSWriteFile20()",
×
2375
                 image->format->driver + 5);
×
2376
      return msWCSException(map, "NoApplicableCode", "mapserv",
×
2377
                            params->version);
×
2378
    }
2379

2380
    if (pszExtension == NULL)
144✔
2381
      pszExtension = "img.tmp";
2382

2383
    if (msGDALDriverSupportsVirtualIOOutput(hDriver)) {
144✔
2384
      base_dir = msTmpFile(map, map->mappath, "/vsimem/wcsout", NULL);
141✔
2385
      if (fo_filename)
141✔
2386
        filename = msStrdup(CPLFormFilename(base_dir, fo_filename, NULL));
4✔
2387
      else
2388
        filename = msStrdup(CPLFormFilename(base_dir, "out", pszExtension));
137✔
2389

2390
      /*            CleanVSIDir( "/vsimem/wcsout" ); */
2391

2392
      msReleaseLock(TLOCK_GDAL);
141✔
2393
      status = msSaveImage(map, image, filename);
141✔
2394
      if (status != MS_SUCCESS) {
141✔
2395
        msSetError(MS_MISCERR, "msSaveImage() failed", "msWCSWriteFile20()");
×
2396
        return msWCSException20(map, "NoApplicableCode", "mapserv",
×
2397
                                params->version);
×
2398
      }
2399
    }
2400
    msReleaseLock(TLOCK_GDAL);
144✔
2401
  }
2402

2403
  /* -------------------------------------------------------------------- */
2404
  /*      If we weren't able to write data under /vsimem, then we just    */
2405
  /*      output a single "stock" filename.                               */
2406
  /* -------------------------------------------------------------------- */
2407
  if (filename == NULL) {
144✔
2408
    msOutputFormatResolveFromImage(map, image);
5✔
2409
    if (multipart) {
5✔
2410
      msIO_fprintf(stdout, "\r\n--wcs\r\n");
×
2411
      msIO_fprintf(stdout,
×
2412
                   "Content-Type: %s\r\n"
2413
                   "Content-Description: coverage data\r\n"
2414
                   "Content-Transfer-Encoding: binary\r\n",
2415
                   MS_IMAGE_MIME_TYPE(map->outputformat));
×
2416

2417
      if (fo_filename != NULL)
×
2418
        msIO_fprintf(stdout,
×
2419
                     "Content-ID: coverage/%s\r\n"
2420
                     "Content-Disposition: INLINE; filename=%s\r\n\r\n",
2421
                     fo_filename, fo_filename);
2422
      else
2423
        msIO_fprintf(stdout,
×
2424
                     "Content-ID: coverage/wcs.%s\r\n"
2425
                     "Content-Disposition: INLINE\r\n\r\n",
2426
                     MS_IMAGE_EXTENSION(map->outputformat));
×
2427
    } else {
2428
      msIO_setHeader("Content-Type", "%s",
5✔
2429
                     MS_IMAGE_MIME_TYPE(map->outputformat));
5✔
2430
      msIO_setHeader("Content-Description", "coverage data");
5✔
2431
      msIO_setHeader("Content-Transfer-Encoding", "binary");
5✔
2432

2433
      if (fo_filename != NULL) {
5✔
2434
        msIO_setHeader("Content-ID", "coverage/%s", fo_filename);
×
2435
        msIO_setHeader("Content-Disposition", "INLINE; filename=%s",
×
2436
                       fo_filename);
2437
      } else {
2438
        msIO_setHeader("Content-ID", "coverage/wcs.%s",
5✔
2439
                       MS_IMAGE_EXTENSION(map->outputformat));
5✔
2440
        msIO_setHeader("Content-Disposition", "INLINE");
5✔
2441
      }
2442
      msIO_sendHeaders();
5✔
2443
    }
2444

2445
    status = msSaveImage(map, image, NULL);
5✔
2446
    if (status != MS_SUCCESS) {
5✔
2447
      msSetError(MS_MISCERR, "msSaveImage() failed", "msWCSWriteFile20()");
×
2448
      return msWCSException(map, "NoApplicableCode", "mapserv",
×
2449
                            params->version);
×
2450
    }
2451
    if (multipart)
5✔
2452
      msIO_fprintf(stdout, "\r\n--wcs--\r\n");
×
2453
    return MS_SUCCESS;
5✔
2454
  }
2455

2456
  /* -------------------------------------------------------------------- */
2457
  /*      When potentially listing multiple files, we take great care     */
2458
  /*      to identify the "primary" file and list it first.  In fact      */
2459
  /*      it is the only file listed in the coverages document.           */
2460
  /* -------------------------------------------------------------------- */
2461
  {
2462
    char **all_files = CPLReadDir(base_dir);
141✔
2463
    int count = CSLCount(all_files);
141✔
2464

2465
    if (msIO_needBinaryStdout() == MS_FAILURE)
141✔
2466
      return MS_FAILURE;
2467

2468
    msAcquireLock(TLOCK_GDAL);
141✔
2469
    for (i = count - 1; i >= 0; i--) {
290✔
2470
      const char *this_file = all_files[i];
149✔
2471

2472
      if (EQUAL(this_file, ".") || EQUAL(this_file, "..")) {
149✔
2473
        all_files = CSLRemoveStrings(all_files, i, 1, NULL);
×
2474
        continue;
×
2475
      }
2476

2477
      if (i > 0 && EQUAL(this_file, CPLGetFilename(filename))) {
149✔
2478
        all_files = CSLRemoveStrings(all_files, i, 1, NULL);
×
2479
        all_files = CSLInsertString(all_files, 0, CPLGetFilename(filename));
×
2480
        i++;
×
2481
      }
2482
    }
2483

2484
    /* -------------------------------------------------------------------- */
2485
    /*      Dump all the files in the memory directory as mime sections.    */
2486
    /* -------------------------------------------------------------------- */
2487
    count = CSLCount(all_files);
141✔
2488

2489
    if (count > 1 && multipart == MS_FALSE) {
141✔
2490
      msDebug("msWCSWriteFile20(): force multipart output without gml summary "
2✔
2491
              "because we have multiple files in the result.\n");
2492

2493
      multipart = MS_TRUE;
2494
      msIO_setHeader("Content-Type", "multipart/related; boundary=wcs");
2✔
2495
      msIO_sendHeaders();
2✔
2496
    }
2497

2498
    for (i = 0; i < count; i++) {
290✔
2499
      const char *mimetype = NULL;
2500
      VSILFILE *fp;
2501
      unsigned char block[4000];
2502
      int bytes_read;
2503

2504
      if (i == 0 && !EQUAL(MS_IMAGE_MIME_TYPE(map->outputformat), "unknown"))
290✔
2505
        mimetype = MS_IMAGE_MIME_TYPE(map->outputformat);
141✔
2506

2507
      if (mimetype == NULL)
2508
        mimetype = "application/octet-stream";
2509
      if (multipart) {
149✔
2510
        msIO_fprintf(stdout, "\r\n--wcs\r\n");
22✔
2511
        msIO_fprintf(stdout,
22✔
2512
                     "Content-Type: %s\r\n"
2513
                     "Content-Description: coverage data\r\n"
2514
                     "Content-Transfer-Encoding: binary\r\n"
2515
                     "Content-ID: coverage/%s\r\n"
2516
                     "Content-Disposition: INLINE; filename=%s\r\n\r\n",
2517
                     mimetype, all_files[i], all_files[i]);
22✔
2518
      } else {
2519
        msIO_setHeader("Content-Type", "%s", mimetype);
127✔
2520
        msIO_setHeader("Content-Description", "coverage data");
127✔
2521
        msIO_setHeader("Content-Transfer-Encoding", "binary");
127✔
2522
        msIO_setHeader("Content-ID", "coverage/%s", all_files[i]);
127✔
2523
        msIO_setHeader("Content-Disposition", "INLINE; filename=%s",
127✔
2524
                       all_files[i]);
2525
        msIO_sendHeaders();
127✔
2526
      }
2527

2528
      fp = VSIFOpenL(CPLFormFilename(base_dir, all_files[i], NULL), "rb");
149✔
2529
      if (fp == NULL) {
149✔
2530
        msReleaseLock(TLOCK_GDAL);
×
2531
        msSetError(MS_MISCERR, "Failed to open %s for streaming to stdout.",
×
2532
                   "msWCSWriteFile20()", all_files[i]);
2533
        return MS_FAILURE;
×
2534
      }
2535

2536
      while ((bytes_read = VSIFReadL(block, 1, sizeof(block), fp)) > 0)
459✔
2537
        msIO_fwrite(block, 1, bytes_read, stdout);
310✔
2538

2539
      VSIFCloseL(fp);
149✔
2540

2541
      VSIUnlink(CPLFormFilename(base_dir, all_files[i], NULL));
149✔
2542
    }
2543

2544
    msFree(base_dir);
141✔
2545
    msFree(filename);
141✔
2546
    CSLDestroy(all_files);
141✔
2547
    msReleaseLock(TLOCK_GDAL);
141✔
2548
    if (multipart)
141✔
2549
      msIO_fprintf(stdout, "\r\n--wcs--\r\n");
14✔
2550
    return MS_SUCCESS;
2551
  }
2552

2553
  return MS_SUCCESS;
2554
}
2555

2556
/************************************************************************/
2557
/*                   msWCSGetRangesetAxisMetadata20()                   */
2558
/*                                                                      */
2559
/*      Looks up a layers metadata for a specific axis information.     */
2560
/************************************************************************/
2561

2562
static const char *msWCSLookupRangesetAxisMetadata20(hashTableObj *table,
2,672✔
2563
                                                     const char *axis,
2564
                                                     const char *item) {
2565
  char buf[500];
2566
  const char *value;
2567

2568
  if (table == NULL || axis == NULL || item == NULL) {
2,672✔
2569
    return NULL;
2570
  }
2571

2572
  strlcpy(buf, axis, sizeof(buf));
2573
  strlcat(buf, "_", sizeof(buf));
2574
  strlcat(buf, item, sizeof(buf));
2575
  if ((value = msLookupHashTable(table, buf)) != NULL) {
2,670✔
2576
    return value;
2577
  }
2578
  return msOWSLookupMetadata(table, "CO", buf);
2,562✔
2579
}
2580

2581
/************************************************************************/
2582
/*                   msWCSGetCoverageMetadata20()                       */
2583
/*                                                                      */
2584
/*      Inits a coverageMetadataObj. Uses msWCSGetCoverageMetadata()    */
2585
/*      but exchanges the SRS URN by an URI for compliance with 2.0.    */
2586
/************************************************************************/
2587

2588
static int msWCSGetCoverageMetadata20(layerObj *layer,
196✔
2589
                                      wcs20coverageMetadataObj *cm) {
2590
  char *srs_uri = NULL;
2591
  memset(cm, 0, sizeof(wcs20coverageMetadataObj));
2592
  if (msCheckParentPointer(layer->map, "map") == MS_FAILURE)
196✔
2593
    return MS_FAILURE;
2594

2595
  msOWSGetEPSGProj(&(layer->projection), &(layer->metadata), "CO", MS_TRUE,
196✔
2596
                   &(cm->srs_epsg));
2597
  if (!cm->srs_epsg) {
196✔
2598
    msOWSGetEPSGProj(&(layer->map->projection), &(layer->map->web.metadata),
×
2599
                     "CO", MS_TRUE, &cm->srs_epsg);
2600
    if (!cm->srs_epsg) {
×
2601
      msSetError(MS_WCSERR,
×
2602
                 "Unable to determine the SRS for this layer, "
2603
                 "no projection defined and no metadata available.",
2604
                 "msWCSGetCoverageMetadata20()");
2605
      return MS_FAILURE;
×
2606
    }
2607
  }
2608

2609
  /* -------------------------------------------------------------------- */
2610
  /*      Get the SRS in uri format.                                      */
2611
  /* -------------------------------------------------------------------- */
2612
  if ((srs_uri = msOWSGetProjURI(&(layer->projection), &(layer->metadata), "CO",
196✔
2613
                                 MS_TRUE)) == NULL) {
2614
    srs_uri = msOWSGetProjURI(&(layer->map->projection),
×
2615
                              &(layer->map->web.metadata), "CO", MS_TRUE);
×
2616
  }
2617

2618
  if (srs_uri != NULL) {
×
2619
    if (strlen(srs_uri) > sizeof(cm->srs_uri) - 1) {
196✔
2620
      msSetError(MS_WCSERR, "SRS URI too long!", "msWCSGetCoverageMetadata()");
×
2621
      return MS_FAILURE;
×
2622
    }
2623

2624
    strlcpy(cm->srs_uri, srs_uri, sizeof(cm->srs_uri));
196✔
2625
    msFree(srs_uri);
196✔
2626
  } else {
2627
    cm->srs_uri[0] = '\0';
×
2628
  }
2629

2630
  /* setup nilvalues */
2631
  cm->numnilvalues = 0;
196✔
2632
  cm->nilvalues = NULL;
196✔
2633
  cm->nilvalues_reasons = NULL;
196✔
2634
  cm->native_format = NULL;
196✔
2635

2636
  /* -------------------------------------------------------------------- */
2637
  /*      If we have "virtual dataset" metadata on the layer, then use    */
2638
  /*      that in preference to inspecting the file(s).                   */
2639
  /*      We require extent and either size or resolution.                */
2640
  /* -------------------------------------------------------------------- */
2641
  if (msOWSLookupMetadata(&(layer->metadata), "CO", "extent") != NULL &&
392✔
2642
      (msOWSLookupMetadata(&(layer->metadata), "CO", "resolution") != NULL ||
228✔
2643
       msOWSLookupMetadata(&(layer->metadata), "CO", "size") != NULL)) {
32✔
2644
    const char *value;
2645

2646
    /* get extent */
2647
    cm->extent.minx = 0.0;
196✔
2648
    cm->extent.maxx = 0.0;
196✔
2649
    cm->extent.miny = 0.0;
196✔
2650
    cm->extent.maxy = 0.0;
196✔
2651
    if (msOWSGetLayerExtent(layer->map, layer, "CO", &cm->extent) == MS_FAILURE)
196✔
2652
      return MS_FAILURE;
2653

2654
    /* get resolution */
2655
    cm->xresolution = 0.0;
196✔
2656
    cm->yresolution = 0.0;
196✔
2657
    if ((value = msOWSLookupMetadata(&(layer->metadata), "CO", "resolution")) !=
196✔
2658
        NULL) {
2659
      char **tokens;
2660
      int n;
2661

2662
      tokens = msStringSplit(value, ' ', &n);
164✔
2663
      if (tokens == NULL || n != 2) {
164✔
2664
        msSetError(MS_WCSERR,
×
2665
                   "Wrong number of arguments for wcs|ows_resolution metadata.",
2666
                   "msWCSGetCoverageMetadata20()");
2667
        msFreeCharArray(tokens, n);
×
2668
        return MS_FAILURE;
×
2669
      }
2670
      cm->xresolution = atof(tokens[0]);
164✔
2671
      cm->yresolution = atof(tokens[1]);
164✔
2672
      msFreeCharArray(tokens, n);
164✔
2673
    }
2674

2675
    /* get Size (in pixels and lines) */
2676
    cm->xsize = 0;
196✔
2677
    cm->ysize = 0;
196✔
2678
    if ((value = msOWSLookupMetadata(&(layer->metadata), "CO", "size")) !=
196✔
2679
        NULL) {
2680
      char **tokens;
2681
      int n;
2682

2683
      tokens = msStringSplit(value, ' ', &n);
33✔
2684
      if (tokens == NULL || n != 2) {
33✔
2685
        msSetError(MS_WCSERR,
×
2686
                   "Wrong number of arguments for wcs|ows_size metadata.",
2687
                   "msWCSGetCoverageMetadata20()");
2688
        msFreeCharArray(tokens, n);
×
2689
        return MS_FAILURE;
×
2690
      }
2691
      cm->xsize = atoi(tokens[0]);
33✔
2692
      cm->ysize = atoi(tokens[1]);
33✔
2693
      msFreeCharArray(tokens, n);
33✔
2694
    }
2695

2696
    /* try to compute raster size */
2697
    if (cm->xsize == 0 && cm->ysize == 0 && cm->xresolution != 0.0 &&
196✔
2698
        cm->yresolution != 0.0 && cm->extent.minx != cm->extent.maxx &&
163✔
2699
        cm->extent.miny != cm->extent.maxy) {
163✔
2700
      cm->xsize =
163✔
2701
          (int)((cm->extent.maxx - cm->extent.minx) / cm->xresolution + 0.5);
163✔
2702
      cm->ysize = (int)fabs(
163✔
2703
          (cm->extent.maxy - cm->extent.miny) / cm->yresolution + 0.5);
163✔
2704
    }
2705

2706
    /* try to compute raster resolution */
2707
    if ((cm->xresolution == 0.0 || cm->yresolution == 0.0) && cm->xsize != 0 &&
196✔
2708
        cm->ysize != 0) {
32✔
2709
      cm->xresolution = (cm->extent.maxx - cm->extent.minx) / cm->xsize;
32✔
2710
      cm->yresolution = (cm->extent.maxy - cm->extent.miny) / cm->ysize;
32✔
2711
    }
2712

2713
    /* do we have information to do anything */
2714
    if (cm->xresolution == 0.0 || cm->yresolution == 0.0 || cm->xsize == 0 ||
196✔
2715
        cm->ysize == 0) {
196✔
2716
      msSetError(MS_WCSERR,
×
2717
                 "Failed to collect extent and resolution for WCS coverage "
2718
                 "from metadata for layer '%s'.  Need value wcs|ows_resolution "
2719
                 "or wcs|ows_size values.",
2720
                 "msWCSGetCoverageMetadata20()", layer->name);
2721
      return MS_FAILURE;
×
2722
    }
2723

2724
    /* compute geotransform */
2725
    cm->geotransform[0] = cm->extent.minx;
196✔
2726
    cm->geotransform[1] = cm->xresolution;
196✔
2727
    cm->geotransform[2] = 0.0;
196✔
2728
    cm->geotransform[3] = cm->extent.maxy;
196✔
2729
    cm->geotransform[4] = 0.0;
196✔
2730
    cm->geotransform[5] = -fabs(cm->yresolution);
196✔
2731

2732
    /* get bands count, or assume 1 if not found */
2733
    cm->numbands = 1;
196✔
2734
    if ((value = msOWSLookupMetadata(&(layer->metadata), "CO", "bandcount")) !=
196✔
2735
        NULL) {
2736
      int numbands = 0;
2737
      if (msStringParseInteger(value, &numbands) != MS_SUCCESS) {
195✔
2738
        return MS_FAILURE;
×
2739
      }
2740
      cm->numbands = (size_t)numbands;
195✔
2741
    }
2742

2743
    cm->bands = static_cast<wcs20rasterbandMetadataObj *>(
196✔
2744
        msSmallCalloc(sizeof(wcs20rasterbandMetadataObj), cm->numbands));
196✔
2745

2746
    /* get bands type, or assume float if not found */
2747
    cm->imagemode = MS_IMAGEMODE_FLOAT32;
196✔
2748
    if ((value = msOWSLookupMetadata(&(layer->metadata), "CO", "imagemode")) !=
196✔
2749
        NULL) {
2750
      if (EQUAL(value, "INT16"))
195✔
2751
        cm->imagemode = MS_IMAGEMODE_INT16;
×
2752
      else if (EQUAL(value, "FLOAT32"))
195✔
2753
        cm->imagemode = MS_IMAGEMODE_FLOAT32;
3✔
2754
      else if (EQUAL(value, "BYTE"))
192✔
2755
        cm->imagemode = MS_IMAGEMODE_BYTE;
192✔
2756
      else {
2757
        msSetError(MS_WCSERR,
×
2758
                   "Content of wcs|ows_imagemode (%s) not recognised.  Should "
2759
                   "be one of BYTE, INT16 or FLOAT32.",
2760
                   "msWCSGetCoverageMetadata20()", value);
2761
        return MS_FAILURE;
×
2762
      }
2763
    }
2764

2765
    if ((value = msOWSLookupMetadata(&(layer->metadata), "CO",
196✔
2766
                                     "native_format")) != NULL) {
2767
      cm->native_format = msStrdup(value);
151✔
2768
    }
2769

2770
    /* determine nilvalues and reasons */
2771
    {
2772
      int n_nilvalues = 0, n_nilvalues_reasons = 0;
196✔
2773
      char **t_nilvalues = NULL, **t_nilvalues_reasons = NULL;
2774
      if ((value = msOWSLookupMetadata(&(layer->metadata), "CO",
196✔
2775
                                       "nilvalues")) != NULL) {
2776
        t_nilvalues = msStringSplit(value, ' ', &n_nilvalues);
18✔
2777
      } else if ((value = msOWSLookupMetadata(&(layer->metadata), "CO",
178✔
2778
                                              "rangeset_nullvalue")) != NULL) {
2779
        t_nilvalues = msStringSplit(value, ' ', &n_nilvalues);
175✔
2780
      }
2781

2782
      if ((value = msOWSLookupMetadata(&(layer->metadata), "CO",
196✔
2783
                                       "nilvalues_reasons")) != NULL) {
2784
        t_nilvalues_reasons = msStringSplit(value, ' ', &n_nilvalues_reasons);
18✔
2785
      }
2786

2787
      if (n_nilvalues > 0) {
196✔
2788
        int i;
2789
        cm->numnilvalues = n_nilvalues;
193✔
2790
        cm->nilvalues =
193✔
2791
            static_cast<char **>(msSmallCalloc(sizeof(char *), n_nilvalues));
193✔
2792
        cm->nilvalues_reasons =
193✔
2793
            static_cast<char **>(msSmallCalloc(sizeof(char *), n_nilvalues));
193✔
2794
        for (i = 0; i < n_nilvalues; ++i) {
386✔
2795
          cm->nilvalues[i] = msStrdup(t_nilvalues[i]);
193✔
2796
          if (i < n_nilvalues_reasons) {
193✔
2797
            cm->nilvalues_reasons[i] = msStrdup(t_nilvalues_reasons[i]);
18✔
2798
          } else {
2799
            cm->nilvalues_reasons[i] = msStrdup("");
175✔
2800
          }
2801
        }
2802
      }
2803
      msFreeCharArray(t_nilvalues, n_nilvalues);
196✔
2804
      msFreeCharArray(t_nilvalues_reasons, n_nilvalues_reasons);
196✔
2805
    }
2806

2807
    {
2808
      int num_band_names = 0, j;
196✔
2809
      char **band_names = NULL;
2810

2811
      const char *wcs11_band_names_key = "rangeset_axes";
2812
      const char *wcs20_band_names_key = "band_names";
2813

2814
      const char *wcs11_interval_key = "interval";
2815
      const char *wcs20_interval_key = "interval";
2816
      const char *interval_key = NULL;
2817

2818
      const char *significant_figures_key = "significant_figures";
2819

2820
      const char *wcs11_keys[] = {"label", "semantic", "values_type",
196✔
2821
                                  "values_semantic", "description"};
2822
      const char *wcs20_keys[] = {"band_name", "band_interpretation",
196✔
2823
                                  "band_uom", "band_definition",
2824
                                  "band_description"};
2825
      const char **keys = NULL;
2826

2827
      char **interval_array;
2828
      int num_interval;
2829

2830
      wcs20rasterbandMetadataObj default_values;
2831

2832
      /* Decide whether WCS1.1 or WCS2.0 keys should be used */
2833
      if ((value = msOWSLookupMetadata(&(layer->metadata), "CO",
196✔
2834
                                       wcs20_band_names_key)) != NULL) {
2835
        keys = wcs20_keys;
2836
        interval_key = wcs20_interval_key;
2837
        band_names = msStringSplit(value, ' ', &num_band_names);
21✔
2838
      } else if ((value = msOWSLookupMetadata(&(layer->metadata), "CO",
175✔
2839
                                              wcs11_band_names_key)) != NULL) {
2840
        keys = wcs11_keys;
2841
        interval_key = wcs11_interval_key;
2842
        /* "bands" has a special processing in WCS 1.0. See */
2843
        /* msWCSSetDefaultBandsRangeSetInfo */
2844
        if (EQUAL(value, "bands")) {
174✔
2845
          num_band_names = cm->numbands;
174✔
2846
          band_names = (char **)msSmallMalloc(sizeof(char *) * num_band_names);
174✔
2847
          for (int i = 0; i < num_band_names; i++) {
454✔
2848
            char szName[30];
2849
            snprintf(szName, sizeof(szName), "Band%d", i + 1);
280✔
2850
            band_names[i] = msStrdup(szName);
280✔
2851
          }
2852
        } else {
2853
          /* WARNING: in WCS 1.x,, "rangeset_axes" has never been intended */
2854
          /* to contain the list of band names... This code should probably */
2855
          /* be removed */
2856
          band_names = msStringSplit(value, ' ', &num_band_names);
×
2857
        }
2858
      }
2859

2860
      /* return with error when number of bands does not match    */
2861
      /* the number of names                                      */
2862
      if (static_cast<size_t>(num_band_names) != cm->numbands &&
196✔
2863
          num_band_names != 0) {
2864
        msFreeCharArray(band_names, num_band_names);
×
2865
        msSetError(MS_WCSERR,
×
2866
                   "Wrong number of band names given in layer '%s'. "
2867
                   "Expected %d, got %d.",
2868
                   "msWCSGetCoverageMetadata20()", layer->name,
2869
                   (int)cm->numbands, num_band_names);
×
2870
        return MS_FAILURE;
×
2871
      }
2872

2873
      /* look up default metadata values */
2874
      for (j = 1; j < 5; ++j) {
980✔
2875
        if (keys != NULL) {
784✔
2876
          default_values.values[j] =
780✔
2877
              (char *)msOWSLookupMetadata(&(layer->metadata), "CO", keys[j]);
780✔
2878
        }
2879
      }
2880

2881
      /* set default values for interval and significant figures */
2882
      switch (cm->imagemode) {
196✔
2883
      case MS_IMAGEMODE_BYTE:
192✔
2884
        default_values.interval_min = 0.;
192✔
2885
        default_values.interval_max = 255.;
192✔
2886
        default_values.significant_figures = 3;
192✔
2887
        break;
192✔
2888
      case MS_IMAGEMODE_INT16:
×
2889
        default_values.interval_min = 0.;
×
2890
        default_values.interval_max = (double)USHRT_MAX;
×
2891
        default_values.significant_figures = 5;
×
2892
        break;
×
2893
      case MS_IMAGEMODE_FLOAT32:
4✔
2894
        default_values.interval_min = -FLT_MAX;
4✔
2895
        default_values.interval_max = FLT_MAX;
4✔
2896
        default_values.significant_figures = 12;
4✔
2897
        break;
4✔
2898
      default:
×
2899
        // other imagemodes are invalid (see above), just keep the compiler
2900
        // happy
2901
        msFreeCharArray(band_names, num_band_names);
×
2902
        return MS_FAILURE;
×
2903
        break;
2904
      }
2905

2906
      /* lookup default interval values */
2907
      if (interval_key != NULL &&
391✔
2908
          (value = msOWSLookupMetadata(&(layer->metadata), "CO",
195✔
2909
                                       interval_key)) != NULL) {
2910
        interval_array = msStringSplit(value, ' ', &num_interval);
18✔
2911

2912
        if (num_interval != 2 ||
18✔
2913
            msStringParseDouble(interval_array[0],
18✔
2914
                                &(default_values.interval_min)) != MS_SUCCESS ||
36✔
2915
            msStringParseDouble(interval_array[1],
18✔
2916
                                &(default_values.interval_max)) != MS_SUCCESS) {
2917
          msFreeCharArray(band_names, num_band_names);
×
2918
          msFreeCharArray(interval_array, num_interval);
×
2919
          msSetError(MS_WCSERR, "Wrong interval format for default axis.",
×
2920
                     "msWCSGetCoverageMetadata20()");
2921
          return MS_FAILURE;
×
2922
        }
2923
        msFreeCharArray(interval_array, num_interval);
18✔
2924
      }
2925

2926
      /* lookup default value for significant figures */
2927
      if ((value = msOWSLookupMetadata(&(layer->metadata), "CO",
196✔
2928
                                       significant_figures_key)) != NULL) {
2929
        if (msStringParseInteger(
18✔
2930
                value, &(default_values.significant_figures)) != MS_SUCCESS) {
2931
          msFreeCharArray(band_names, num_band_names);
×
2932
          msSetError(MS_WCSERR,
×
2933
                     "Wrong significant figures format "
2934
                     "for default axis.",
2935
                     "msWCSGetCoverageMetadata20()");
2936
          return MS_FAILURE;
×
2937
        }
2938
      }
2939

2940
      /* iterate over every band */
2941
      for (unsigned i = 0; i < cm->numbands; ++i) {
642✔
2942
        cm->bands[i].name = NULL;
446✔
2943

2944
        /* look up band metadata or copy defaults */
2945
        if (num_band_names != 0) {
446✔
2946
          cm->bands[i].name = msStrdup(band_names[i]);
445✔
2947
          for (j = 1; j < 5; ++j) {
2,225✔
2948
            value = (char *)msWCSLookupRangesetAxisMetadata20(
1,780✔
2949
                &(layer->metadata), cm->bands[i].name, keys[j]);
1,780✔
2950
            if (value != NULL) {
1,780✔
2951
              cm->bands[i].values[j] = msStrdup(value);
84✔
2952
            } else if (default_values.values[j] != NULL) {
1,696✔
2953
              cm->bands[i].values[j] = msStrdup(default_values.values[j]);
856✔
2954
            }
2955
          }
2956
        }
2957

2958
        /* set up interval */
2959
        value = (char *)msWCSLookupRangesetAxisMetadata20(
446✔
2960
            &(layer->metadata), cm->bands[i].name, interval_key);
446✔
2961
        if (value != NULL) {
446✔
2962
          num_interval = 0;
21✔
2963
          interval_array = msStringSplit(value, ' ', &num_interval);
21✔
2964

2965
          if (num_interval != 2 ||
21✔
2966
              msStringParseDouble(interval_array[0],
21✔
2967
                                  &(cm->bands[i].interval_min)) != MS_SUCCESS ||
42✔
2968
              msStringParseDouble(interval_array[1],
21✔
2969
                                  &(cm->bands[i].interval_max)) != MS_SUCCESS) {
21✔
2970
            msFreeCharArray(band_names, num_band_names);
×
2971
            msFreeCharArray(interval_array, num_interval);
×
2972
            msSetError(MS_WCSERR, "Wrong interval format for axis %s.",
×
2973
                       "msWCSGetCoverageMetadata20()", cm->bands[i].name);
×
2974
            return MS_FAILURE;
×
2975
          }
2976

2977
          msFreeCharArray(interval_array, num_interval);
21✔
2978
        } else {
2979
          cm->bands[i].interval_min = default_values.interval_min;
425✔
2980
          cm->bands[i].interval_max = default_values.interval_max;
425✔
2981
        }
2982

2983
        /* set up significant figures */
2984
        value = (char *)msWCSLookupRangesetAxisMetadata20(
446✔
2985
            &(layer->metadata), cm->bands[i].name, significant_figures_key);
446✔
2986
        if (value != NULL) {
446✔
2987
          if (msStringParseInteger(
3✔
2988
                  value, &(cm->bands[i].significant_figures)) != MS_SUCCESS) {
3✔
2989
            msFreeCharArray(band_names, num_band_names);
×
2990
            msSetError(MS_WCSERR,
×
2991
                       "Wrong significant figures format "
2992
                       "for axis %s.",
2993
                       "msWCSGetCoverageMetadata20()", cm->bands[i].name);
×
2994
            return MS_FAILURE;
×
2995
          }
2996
        } else {
2997
          cm->bands[i].significant_figures = default_values.significant_figures;
443✔
2998
        }
2999
      }
3000

3001
      msFreeCharArray(band_names, num_band_names);
196✔
3002
    }
3003
  } else if (layer->data == NULL) {
×
3004
    /* no virtual metadata, not ok unless we're talking 1 image, hopefully we
3005
     * can fix that */
3006
    msSetError(
×
3007
        MS_WCSERR,
3008
        "RASTER Layer with no DATA statement and no WCS virtual dataset "
3009
        "metadata.  Tileindexed raster layers not supported for WCS without "
3010
        "virtual dataset metadata (cm->extent, wcs_res, wcs_size).",
3011
        "msWCSGetCoverageMetadata20()");
3012
    return MS_FAILURE;
×
3013
  } else {
3014
    /* work from the file (e.g. DATA) */
3015
    GDALDatasetH hDS;
3016
    GDALDriverH hDriver;
3017
    GDALRasterBandH hBand;
3018
    char szPath[MS_MAXPATHLEN];
3019
    const char *driver_short_name, *driver_long_name;
3020

3021
    msGDALInitialize();
×
3022

3023
    msTryBuildPath3(szPath, layer->map->mappath, layer->map->shapepath,
×
3024
                    layer->data);
×
3025
    msAcquireLock(TLOCK_GDAL);
×
3026
    {
3027
      char **connectionoptions =
3028
          msGetStringListFromHashTable(&(layer->connectionoptions));
×
3029
      hDS = GDALOpenEx(szPath, GDAL_OF_RASTER, NULL,
×
3030
                       (const char *const *)connectionoptions, NULL);
3031
      CSLDestroy(connectionoptions);
×
3032
    }
3033
    if (hDS == NULL) {
×
3034
      msReleaseLock(TLOCK_GDAL);
×
3035
      msSetError(MS_IOERR, "%s", "msWCSGetCoverageMetadata20()",
×
3036
                 CPLGetLastErrorMsg());
3037
      return MS_FAILURE;
×
3038
    }
3039

3040
    msGetGDALGeoTransform(hDS, layer->map, layer, cm->geotransform);
×
3041

3042
    cm->xsize = GDALGetRasterXSize(hDS);
×
3043
    cm->ysize = GDALGetRasterYSize(hDS);
×
3044

3045
    cm->extent.minx = cm->geotransform[0];
×
3046
    cm->extent.maxx = cm->geotransform[0] + cm->geotransform[1] * cm->xsize +
×
3047
                      cm->geotransform[2] * cm->ysize;
×
3048
    cm->extent.miny = cm->geotransform[3] + cm->geotransform[4] * cm->xsize +
×
3049
                      cm->geotransform[5] * cm->ysize;
×
3050
    cm->extent.maxy = cm->geotransform[3];
×
3051

3052
    cm->xresolution = cm->geotransform[1];
×
3053
    cm->yresolution = cm->geotransform[5];
×
3054

3055
    cm->numbands = GDALGetRasterCount(hDS);
×
3056

3057
    /* TODO nilvalues? */
3058

3059
    if (cm->numbands == 0) {
×
3060
      msReleaseLock(TLOCK_GDAL);
×
3061
      msSetError(MS_WCSERR,
×
3062
                 "Raster file %s has no raster bands.  This cannot be used in "
3063
                 "a layer.",
3064
                 "msWCSGetCoverageMetadata20()", layer->data);
3065
      return MS_FAILURE;
×
3066
    }
3067

3068
    hBand = GDALGetRasterBand(hDS, 1);
×
3069
    switch (GDALGetRasterDataType(hBand)) {
×
3070
    case GDT_Byte:
×
3071
      cm->imagemode = MS_IMAGEMODE_BYTE;
×
3072
      break;
×
3073
    case GDT_Int16:
×
3074
      cm->imagemode = MS_IMAGEMODE_INT16;
×
3075
      break;
×
3076
    default:
×
3077
      cm->imagemode = MS_IMAGEMODE_FLOAT32;
×
3078
      break;
×
3079
    }
3080

3081
    cm->bands = static_cast<wcs20rasterbandMetadataObj *>(
×
3082
        msSmallCalloc(sizeof(wcs20rasterbandMetadataObj), cm->numbands));
×
3083

3084
    /* get as much band metadata as possible */
3085
    for (unsigned i = 0; i < cm->numbands; ++i) {
×
3086
      char bandname[32];
3087
      GDALColorInterp colorInterp;
3088
      hBand = GDALGetRasterBand(hDS, i + 1);
×
3089
      snprintf(bandname, sizeof(bandname), "band%d", i + 1);
3090
      cm->bands[i].name = msStrdup(bandname);
×
3091
      colorInterp = GDALGetRasterColorInterpretation(hBand);
×
3092
      cm->bands[i].interpretation =
×
3093
          msStrdup(GDALGetColorInterpretationName(colorInterp));
×
3094
      cm->bands[i].uom = msStrdup(GDALGetRasterUnitType(hBand));
×
3095
      if (strlen(cm->bands[i].uom) == 0) {
×
3096
        msFree(cm->bands[i].uom);
×
3097
        cm->bands[i].uom = NULL;
×
3098
      }
3099

3100
      /* set up interval and significant figures */
3101
      switch (cm->imagemode) {
×
3102
      case MS_IMAGEMODE_BYTE:
×
3103
      case MS_IMAGEMODE_PC256:
3104
        cm->bands[i].interval_min = 0.;
×
3105
        cm->bands[i].interval_max = 255.;
×
3106
        cm->bands[i].significant_figures = 3;
×
3107
        break;
×
3108
      case MS_IMAGEMODE_INT16:
×
3109
        cm->bands[i].interval_min = 0.;
×
3110
        cm->bands[i].interval_max = (double)USHRT_MAX;
×
3111
        cm->bands[i].significant_figures = 5;
×
3112
        break;
×
3113
      case MS_IMAGEMODE_FLOAT32:
×
3114
        cm->bands[i].interval_min = -FLT_MAX;
×
3115
        cm->bands[i].interval_max = FLT_MAX;
×
3116
        cm->bands[i].significant_figures = 12;
×
3117
        break;
×
3118
      }
3119
    }
3120

3121
    hDriver = GDALGetDatasetDriver(hDS);
×
3122
    driver_short_name = GDALGetDriverShortName(hDriver);
×
3123
    driver_long_name = GDALGetDriverLongName(hDriver);
×
3124
    /* TODO: improve this, exchange strstr() */
3125
    for (int i = 0; i < layer->map->numoutputformats; ++i) {
×
3126
      if (strstr(layer->map->outputformatlist[i]->driver, driver_short_name) !=
×
3127
              NULL ||
×
3128
          strstr(layer->map->outputformatlist[i]->driver, driver_long_name) !=
3129
              NULL) {
3130
        cm->native_format = msStrdup(layer->map->outputformatlist[i]->mimetype);
×
3131
        break;
×
3132
      }
3133
    }
3134

3135
    GDALClose(hDS);
×
3136
    msReleaseLock(TLOCK_GDAL);
×
3137
  }
3138

3139
  return MS_SUCCESS;
3140
}
3141

3142
/************************************************************************/
3143
/*                   msWCSClearCoverageMetadata20()                     */
3144
/*                                                                      */
3145
/*      Returns all dynamically allocated memory from a coverage meta-  */
3146
/*      data object.                                                    */
3147
/************************************************************************/
3148

3149
static int msWCSClearCoverageMetadata20(wcs20coverageMetadataObj *cm) {
196✔
3150
  msFree(cm->native_format);
196✔
3151
  for (unsigned i = 0; i < cm->numnilvalues; ++i) {
389✔
3152
    msFree(cm->nilvalues[i]);
193✔
3153
    msFree(cm->nilvalues_reasons[i]);
193✔
3154
  }
3155
  msFree(cm->nilvalues);
196✔
3156
  msFree(cm->nilvalues_reasons);
196✔
3157

3158
  for (unsigned i = 0; i < cm->numbands; ++i) {
642✔
3159
    for (int j = 0; j < 5; ++j) {
2,676✔
3160
      msFree(cm->bands[i].values[j]);
2,230✔
3161
    }
3162
  }
3163
  msFree(cm->bands);
196✔
3164
  msFree(cm->srs_epsg);
196✔
3165
  return MS_SUCCESS;
196✔
3166
}
3167

3168
/************************************************************************/
3169
/*                   msWCSException20()                                 */
3170
/*                                                                      */
3171
/*      Writes out an OWS compliant exception.                          */
3172
/************************************************************************/
3173

3174
int msWCSException20(mapObj *map, const char *exceptionCode,
33✔
3175
                     const char *locator, const char *version) {
3176
  int size = 0;
33✔
3177
  const char *status = "400 Bad Request";
3178
  char *errorString = NULL;
3179
  char *schemasLocation = NULL;
3180
  char *xsi_schemaLocation = NULL;
3181
  char version_string[OWS_VERSION_MAXLEN];
3182

3183
  xmlDocPtr psDoc = NULL;
3184
  xmlNodePtr psRootNode = NULL;
3185
  xmlNodePtr psMainNode = NULL;
3186
  xmlNsPtr psNsOws = NULL;
3187
  xmlNsPtr psNsXsi = NULL;
3188
  xmlChar *buffer = NULL;
33✔
3189

3190
  errorString = msGetErrorString("\n");
33✔
3191
  schemasLocation = msEncodeHTMLEntities(msOWSGetSchemasLocation(map));
33✔
3192

3193
  psDoc = xmlNewDoc(BAD_CAST "1.0");
33✔
3194

3195
  psRootNode = xmlNewNode(NULL, BAD_CAST "ExceptionReport");
33✔
3196
  psNsOws = xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_OWS_20_NAMESPACE_URI,
33✔
3197
                     BAD_CAST MS_OWSCOMMON_OWS_NAMESPACE_PREFIX);
3198
  xmlSetNs(psRootNode, psNsOws);
33✔
3199

3200
  psNsXsi = xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_W3C_XSI_NAMESPACE_URI,
33✔
3201
                     BAD_CAST MS_OWSCOMMON_W3C_XSI_NAMESPACE_PREFIX);
3202

3203
  /* add attributes to root element */
3204
  xmlNewProp(psRootNode, BAD_CAST "version", BAD_CAST version);
33✔
3205
  xmlNewProp(psRootNode, BAD_CAST "xml:lang",
33✔
3206
             BAD_CAST msOWSGetLanguage(map, "exception"));
33✔
3207

3208
  /* get 2 digits version string: '2.0' */
3209
  msOWSGetVersionString(OWS_2_0_0, version_string);
33✔
3210
  version_string[3] = '\0';
33✔
3211

3212
  xsi_schemaLocation = msStrdup((char *)psNsOws->href);
33✔
3213
  xsi_schemaLocation = msStringConcatenate(xsi_schemaLocation, " ");
33✔
3214
  xsi_schemaLocation =
3215
      msStringConcatenate(xsi_schemaLocation, (char *)schemasLocation);
33✔
3216
  xsi_schemaLocation = msStringConcatenate(xsi_schemaLocation, "/ows/");
33✔
3217
  xsi_schemaLocation = msStringConcatenate(xsi_schemaLocation, version_string);
33✔
3218
  xsi_schemaLocation =
3219
      msStringConcatenate(xsi_schemaLocation, "/owsExceptionReport.xsd");
33✔
3220

3221
  /* add namespace'd attributes to root element */
3222
  xmlNewNsProp(psRootNode, psNsXsi, BAD_CAST "schemaLocation",
33✔
3223
               BAD_CAST xsi_schemaLocation);
3224

3225
  /* add child element */
3226
  psMainNode = xmlNewChild(psRootNode, NULL, BAD_CAST "Exception", NULL);
33✔
3227

3228
  /* add attributes to child */
3229
  xmlNewProp(psMainNode, BAD_CAST "exceptionCode", BAD_CAST exceptionCode);
33✔
3230

3231
  if (locator != NULL) {
33✔
3232
    xmlNewProp(psMainNode, BAD_CAST "locator", BAD_CAST locator);
33✔
3233
  }
3234

3235
  if (errorString != NULL) {
33✔
3236
    char *errorMessage = msEncodeHTMLEntities(errorString);
33✔
3237
    xmlNewChild(psMainNode, NULL, BAD_CAST "ExceptionText",
33✔
3238
                BAD_CAST errorMessage);
3239
    msFree(errorMessage);
33✔
3240
  }
3241

3242
  /*psRootNode = msOWSCommonExceptionReport(psNsOws, OWS_2_0_0,
3243
          schemasLocation, version, msOWSGetLanguage(map, "exception"),
3244
          exceptionCode, locator, errorMessage);*/
3245

3246
  xmlDocSetRootElement(psDoc, psRootNode);
33✔
3247

3248
  if (exceptionCode == NULL) {
33✔
3249
    /* Do nothing */
3250
  } else if (EQUAL(exceptionCode, "OperationNotSupported") ||
33✔
3251
             EQUAL(exceptionCode, "OptionNotSupported")) {
33✔
3252
    status = "501 Not Implemented";
3253
  } else if (EQUAL(exceptionCode, "NoSuchCoverage") ||
33✔
3254
             EQUAL(exceptionCode, "emptyCoverageIdList") ||
31✔
3255
             EQUAL(exceptionCode, "InvalidAxisLabel") ||
31✔
3256
             EQUAL(exceptionCode, "InvalidSubsetting")) {
29✔
3257
    status = "404 Not Found";
3258
  }
3259

3260
  msIO_setHeader("Status", "%s", status);
33✔
3261
  msIO_setHeader("Content-Type", "text/xml; charset=UTF-8");
33✔
3262
  msIO_sendHeaders();
33✔
3263

3264
  xmlDocDumpFormatMemoryEnc(psDoc, &buffer, &size, "UTF-8", 1);
33✔
3265

3266
  msIO_printf("%s", buffer);
33✔
3267

3268
  /* free buffer and the document */
3269
  free(errorString);
33✔
3270
  free(schemasLocation);
33✔
3271
  free(xsi_schemaLocation);
33✔
3272
  xmlFree(buffer);
33✔
3273
  xmlFreeDoc(psDoc);
33✔
3274

3275
  /* clear error since we have already reported it */
3276
  msResetErrorList();
33✔
3277
  return MS_FAILURE;
33✔
3278
}
3279

3280
#define MAX_MIMES 20
3281

3282
static int msWCSGetCapabilities20_CreateProfiles(
35✔
3283
    mapObj *map, xmlNodePtr psServiceIdentification, xmlNsPtr psOwsNs) {
3284
  xmlNodePtr psProfile, psTmpNode;
3285
  char *available_mime_types[MAX_MIMES];
3286
  int i = 0;
3287

3288
  /* even indices are urls, uneven are mime-types, or null*/
3289
  const char *const urls_and_mime_types[] = {
35✔
3290
      MS_WCS_20_PROFILE_CORE,
3291
      NULL,
3292
      MS_WCS_20_PROFILE_KVP,
3293
      NULL,
3294
      MS_WCS_20_PROFILE_POST,
3295
      NULL,
3296
      MS_WCS_20_PROFILE_GML,
3297
      NULL,
3298
      MS_WCS_20_PROFILE_GML_MULTIPART,
3299
      NULL,
3300
      MS_WCS_20_PROFILE_GML_SPECIAL,
3301
      NULL,
3302
      MS_WCS_20_PROFILE_GML_GEOTIFF,
3303
      "image/tiff",
3304
      MS_WCS_20_PROFILE_CRS,
3305
      NULL,
3306
      MS_WCS_20_PROFILE_SCALING,
3307
      NULL,
3308
      MS_WCS_20_PROFILE_RANGESUBSET,
3309
      NULL,
3310
      MS_WCS_20_PROFILE_INTERPOLATION,
3311
      NULL,
3312
      NULL,
3313
      NULL /* guardian */
3314
  };
3315

3316
  /* navigate to node where profiles shall be inserted */
3317
  for (psTmpNode = psServiceIdentification->children; psTmpNode->next != NULL;
247✔
3318
       psTmpNode = psTmpNode->next) {
3319
    if (EQUAL((char *)psTmpNode->name, "ServiceTypeVersion") &&
247✔
3320
        !EQUAL((char *)psTmpNode->next->name, "ServiceTypeVersion"))
105✔
3321
      break;
3322
  }
3323

3324
  /* get list of all available mime types */
3325
  msGetOutputFormatMimeList(map, available_mime_types, MAX_MIMES);
35✔
3326

3327
  while (urls_and_mime_types[i] != NULL) {
420✔
3328
    const char *mime_type = urls_and_mime_types[i + 1];
385✔
3329

3330
    /* check if there is a mime type */
3331
    if (mime_type != NULL) {
385✔
3332
      /* check if the mime_type is in the list of outputformats */
3333
      if (CSLPartialFindString(available_mime_types, mime_type) == -1)
35✔
3334
        continue;
×
3335
    }
3336

3337
    /* create a new node and attach it in the tree */
3338
    psProfile = xmlNewNode(psOwsNs, BAD_CAST "Profile");
385✔
3339
    xmlNodeSetContent(psProfile, BAD_CAST urls_and_mime_types[i]);
385✔
3340
    xmlAddNextSibling(psTmpNode, psProfile);
385✔
3341

3342
    psTmpNode = psProfile;
3343
    i += 2;
385✔
3344
  }
3345
  return MS_SUCCESS;
35✔
3346
}
3347

3348
/************************************************************************/
3349
/*                   msWCSGetCapabilities20_CoverageSummary()           */
3350
/*                                                                      */
3351
/*      Helper function to create a short summary for a specific        */
3352
/*      coverage.                                                       */
3353
/************************************************************************/
3354

3355
static int msWCSGetCapabilities20_CoverageSummary(xmlDocPtr doc,
33✔
3356
                                                  xmlNodePtr psContents,
3357
                                                  layerObj *layer) {
3358
  wcs20coverageMetadataObj cm;
3359
  int status;
3360
  xmlNodePtr psCSummary;
3361

3362
  xmlNsPtr psWcsNs =
3363
      xmlSearchNs(doc, xmlDocGetRootElement(doc), BAD_CAST "wcs");
33✔
3364

3365
  status = msWCSGetCoverageMetadata20(layer, &cm);
33✔
3366
  if (status != MS_SUCCESS)
33✔
3367
    return MS_FAILURE;
3368

3369
  psCSummary =
3370
      xmlNewChild(psContents, psWcsNs, BAD_CAST "CoverageSummary", NULL);
33✔
3371
  xmlNewChild(psCSummary, psWcsNs, BAD_CAST "CoverageId", BAD_CAST layer->name);
33✔
3372
  xmlNewChild(psCSummary, psWcsNs, BAD_CAST "CoverageSubtype",
33✔
3373
              BAD_CAST "RectifiedGridCoverage");
3374

3375
  msWCS_11_20_PrintMetadataLinks(layer, doc, psCSummary);
33✔
3376

3377
  msWCSClearCoverageMetadata20(&cm);
33✔
3378

3379
  return MS_SUCCESS;
33✔
3380
}
3381

3382
/************************************************************************/
3383
/*                          msWCSAddInspireDSID20                       */
3384
/************************************************************************/
3385

3386
static void msWCSAddInspireDSID20(mapObj *map, xmlNsPtr psNsInspireDls,
20✔
3387
                                  xmlNsPtr psNsInspireCommon,
3388
                                  xmlNodePtr pDlsExtendedCapabilities) {
3389
  const char *dsid_code =
3390
      msOWSLookupMetadata(&(map->web.metadata), "CO", "inspire_dsid_code");
20✔
3391
  const char *dsid_ns =
3392
      msOWSLookupMetadata(&(map->web.metadata), "CO", "inspire_dsid_ns");
20✔
3393
  if (dsid_code == NULL) {
20✔
3394
    xmlAddChild(
×
3395
        pDlsExtendedCapabilities,
3396
        xmlNewComment(
3397
            BAD_CAST
3398
            "WARNING: Required metadata \"inspire_dsid_code\" missing"));
3399
  } else {
3400
    int ntokensCode = 0, ntokensNS = 0;
20✔
3401
    char **tokensCode;
3402
    char **tokensNS = NULL;
3403
    int i;
3404

3405
    tokensCode = msStringSplit(dsid_code, ',', &ntokensCode);
20✔
3406
    if (dsid_ns != NULL)
20✔
3407
      tokensNS =
3408
          msStringSplitComplex(dsid_ns, ",", &ntokensNS, MS_ALLOWEMPTYTOKENS);
20✔
3409
    if (ntokensNS > 0 && ntokensNS != ntokensCode) {
20✔
3410
      xmlAddChild(
×
3411
          pDlsExtendedCapabilities,
3412
          xmlNewComment(
3413
              BAD_CAST
3414
              "WARNING: \"inspire_dsid_code\" and \"inspire_dsid_ns\" have not "
3415
              "the same number of elements. Ignoring inspire_dsid_ns"));
3416
      msFreeCharArray(tokensNS, ntokensNS);
×
3417
      tokensNS = NULL;
3418
      ntokensNS = 0;
×
3419
    }
3420
    for (i = 0; i < ntokensCode; i++) {
80✔
3421
      xmlNodePtr pSDSI =
3422
          xmlNewNode(psNsInspireDls, BAD_CAST "SpatialDataSetIdentifier");
60✔
3423
      xmlAddChild(pDlsExtendedCapabilities, pSDSI);
60✔
3424
      xmlNewTextChild(pSDSI, psNsInspireCommon, BAD_CAST "Code",
60✔
3425
                      BAD_CAST tokensCode[i]);
60✔
3426
      if (ntokensNS > 0 && tokensNS[i][0] != '\0')
60✔
3427
        xmlNewTextChild(pSDSI, psNsInspireCommon, BAD_CAST "Namespace",
40✔
3428
                        BAD_CAST tokensNS[i]);
3429
    }
3430
    msFreeCharArray(tokensCode, ntokensCode);
20✔
3431
    msFreeCharArray(tokensNS, ntokensNS);
20✔
3432
  }
3433
}
20✔
3434

3435
/************************************************************************/
3436
/*                   msWCSGetCapabilities20()                           */
3437
/*                                                                      */
3438
/*      Performs the GetCapabilities operation. Writes out a xml        */
3439
/*      structure with server specific information and a summary        */
3440
/*      of all available coverages.                                     */
3441
/************************************************************************/
3442

3443
int msWCSGetCapabilities20(mapObj *map, cgiRequestObj *req,
41✔
3444
                           wcs20ParamsObjPtr params,
3445
                           owsRequestObj *ows_request) {
3446
  xmlDocPtr psDoc = NULL; /* document pointer */
3447
  xmlNodePtr psRootNode, psOperationsNode, psNode;
3448
  const char *updatesequence = NULL;
3449
  xmlNsPtr psOwsNs = NULL, psXLinkNs = NULL, psWcsNs = NULL, psCrsNs = NULL,
3450
           psIntNs = NULL;
3451
  char *script_url = NULL, *script_url_encoded = NULL, *format_list = NULL;
3452
  int i;
3453
  xmlDocPtr pInspireTmpDoc = NULL;
3454

3455
  const char *inspire_capabilities =
3456
      msOWSLookupMetadata(&(map->web.metadata), "CO", "inspire_capabilities");
41✔
3457

3458
  char *validated_language = msOWSLanguageNegotiation(
41✔
3459
      map, "CO", params->accept_languages, CSLCount(params->accept_languages));
41✔
3460

3461
  /* -------------------------------------------------------------------- */
3462
  /*      Create document.                                                */
3463
  /* -------------------------------------------------------------------- */
3464
  psDoc = xmlNewDoc(BAD_CAST "1.0");
41✔
3465

3466
  psRootNode = xmlNewNode(NULL, BAD_CAST "Capabilities");
41✔
3467

3468
  xmlDocSetRootElement(psDoc, psRootNode);
41✔
3469

3470
  /* -------------------------------------------------------------------- */
3471
  /*      Name spaces                                                     */
3472
  /* -------------------------------------------------------------------- */
3473

3474
  msWCSPrepareNamespaces20(psDoc, psRootNode, map,
41✔
3475
                           inspire_capabilities != NULL);
3476

3477
  /* lookup namespaces */
3478
  psOwsNs = xmlSearchNs(psDoc, psRootNode,
41✔
3479
                        BAD_CAST MS_OWSCOMMON_OWS_NAMESPACE_PREFIX);
3480
  psWcsNs = xmlSearchNs(psDoc, psRootNode,
41✔
3481
                        BAD_CAST MS_OWSCOMMON_WCS_NAMESPACE_PREFIX);
3482
  xmlSearchNs(psDoc, psRootNode, BAD_CAST MS_OWSCOMMON_GML_NAMESPACE_PREFIX);
41✔
3483
  psXLinkNs = xmlSearchNs(psDoc, psRootNode, BAD_CAST "xlink");
41✔
3484
  psCrsNs = xmlNewNs(psRootNode, BAD_CAST "http://www.opengis.net/wcs/crs/1.0",
41✔
3485
                     BAD_CAST "crs");
3486
  psIntNs = xmlNewNs(psRootNode,
41✔
3487
                     BAD_CAST "http://www.opengis.net/wcs/interpolation/1.0",
3488
                     BAD_CAST "int");
3489

3490
  xmlSetNs(psRootNode, psWcsNs);
41✔
3491

3492
  xmlNewProp(psRootNode, BAD_CAST "version", BAD_CAST params->version);
41✔
3493

3494
  updatesequence =
3495
      msOWSLookupMetadata(&(map->web.metadata), "CO", "updatesequence");
41✔
3496
  if (params->updatesequence != NULL) {
41✔
3497
    i = msOWSNegotiateUpdateSequence(params->updatesequence, updatesequence);
3✔
3498
    if (i == 0) { /* current */
3✔
3499
      msSetError(
1✔
3500
          MS_WCSERR, "UPDATESEQUENCE parameter (%s) is equal to server (%s)",
3501
          "msWCSGetCapabilities20()", params->updatesequence, updatesequence);
3502
      xmlFreeDoc(psDoc);
1✔
3503
      return msWCSException(map, "CurrentUpdateSequence", "updatesequence",
1✔
3504
                            params->version);
1✔
3505
    }
3506
    if (i > 0) { /* invalid */
2✔
3507
      msSetError(
1✔
3508
          MS_WCSERR, "UPDATESEQUENCE parameter (%s) is higher than server (%s)",
3509
          "msWCSGetCapabilities20()", params->updatesequence, updatesequence);
3510
      xmlFreeDoc(psDoc);
1✔
3511
      return msWCSException(map, "InvalidUpdateSequence", "updatesequence",
1✔
3512
                            params->version);
1✔
3513
    }
3514
  }
3515
  if (updatesequence != NULL) {
39✔
3516
    xmlNewProp(psRootNode, BAD_CAST "updateSequence", BAD_CAST updatesequence);
37✔
3517
  }
3518

3519
  /* -------------------------------------------------------------------- */
3520
  /*      Service identification.                                         */
3521
  /* -------------------------------------------------------------------- */
3522
  if (MS_WCS_20_CAPABILITIES_INCLUDE_SECTION(params, "ServiceIdentification")) {
39✔
3523
    psNode = xmlAddChild(
35✔
3524
        psRootNode, msOWSCommonServiceIdentification(psOwsNs, map, "OGC WCS",
3525
                                                     "2.0.1,1.1.1,1.0.0", "CO",
3526
                                                     validated_language));
3527
    msWCSGetCapabilities20_CreateProfiles(map, psNode, psOwsNs);
35✔
3528
  }
3529

3530
  /* Service Provider */
3531
  if (MS_WCS_20_CAPABILITIES_INCLUDE_SECTION(params, "ServiceProvider")) {
39✔
3532
    xmlAddChild(psRootNode,
35✔
3533
                msOWSCommonServiceProvider(psOwsNs, psXLinkNs, map, "CO",
3534
                                           validated_language));
3535
  }
3536

3537
  /* -------------------------------------------------------------------- */
3538
  /*      Operations metadata.                                            */
3539
  /* -------------------------------------------------------------------- */
3540
  if (MS_WCS_20_CAPABILITIES_INCLUDE_SECTION(params, "OperationsMetadata")) {
39✔
3541
    if ((script_url = msOWSGetOnlineResource(map, "CO", "onlineresource",
35✔
3542
                                             req)) == NULL ||
70✔
3543
        (script_url_encoded = msEncodeHTMLEntities(script_url)) == NULL) {
35✔
3544
      xmlFreeDoc(psDoc);
×
3545
      msSetError(MS_WCSERR, "Server URL not found", "msWCSGetCapabilities20()");
×
3546
      return msWCSException(map, "NoApplicableCode", "mapserv",
×
3547
                            params->version);
×
3548
    }
3549
    free(script_url);
35✔
3550

3551
    psOperationsNode =
3552
        xmlAddChild(psRootNode, msOWSCommonOperationsMetadata(psOwsNs));
35✔
3553

3554
    /* -------------------------------------------------------------------- */
3555
    /*      GetCapabilities - add Sections and AcceptVersions?              */
3556
    /* -------------------------------------------------------------------- */
3557
    psNode = msOWSCommonOperationsMetadataOperation(
35✔
3558
        psOwsNs, psXLinkNs, "GetCapabilities", OWS_METHOD_GETPOST,
3559
        script_url_encoded);
3560

3561
    xmlAddChild(psNode->last->last->last,
35✔
3562
                msOWSCommonOperationsMetadataDomainType(
3563
                    OWS_2_0_0, psOwsNs, "Constraint", "PostEncoding", "XML"));
3564
    xmlAddChild(psOperationsNode, psNode);
35✔
3565

3566
    /* -------------------------------------------------------------------- */
3567
    /*      DescribeCoverage                                                */
3568
    /* -------------------------------------------------------------------- */
3569
    if (msOWSRequestIsEnabled(map, NULL, "C", "DescribeCoverage", MS_FALSE)) {
35✔
3570
      psNode = msOWSCommonOperationsMetadataOperation(
35✔
3571
          psOwsNs, psXLinkNs, "DescribeCoverage", OWS_METHOD_GETPOST,
3572
          script_url_encoded);
3573
      xmlAddChild(psNode->last->last->last,
35✔
3574
                  msOWSCommonOperationsMetadataDomainType(
3575
                      OWS_2_0_0, psOwsNs, "Constraint", "PostEncoding", "XML"));
3576
      xmlAddChild(psOperationsNode, psNode);
35✔
3577
    }
3578

3579
    /* -------------------------------------------------------------------- */
3580
    /*      GetCoverage                                                     */
3581
    /* -------------------------------------------------------------------- */
3582
    if (msOWSRequestIsEnabled(map, NULL, "C", "GetCoverage", MS_FALSE)) {
35✔
3583
      psNode = msOWSCommonOperationsMetadataOperation(
35✔
3584
          psOwsNs, psXLinkNs, "GetCoverage", OWS_METHOD_GETPOST,
3585
          script_url_encoded);
3586

3587
      xmlAddChild(psNode->last->last->last,
35✔
3588
                  msOWSCommonOperationsMetadataDomainType(
3589
                      OWS_2_0_0, psOwsNs, "Constraint", "PostEncoding", "XML"));
3590
      xmlAddChild(psOperationsNode, psNode);
35✔
3591
    }
3592
    msFree(script_url_encoded);
35✔
3593

3594
    /* -------------------------------------------------------------------- */
3595
    /*      Extended Capabilities for inspire                               */
3596
    /* -------------------------------------------------------------------- */
3597

3598
    if (inspire_capabilities) {
35✔
3599
      msIOContext *old_context;
3600
      msIOContext *new_context;
3601
      msIOBuffer *buffer;
3602

3603
      xmlNodePtr pRoot;
3604
      xmlNodePtr pOWSExtendedCapabilities;
3605
      xmlNodePtr pDlsExtendedCapabilities;
3606
      xmlNodePtr pChild;
3607

3608
      xmlNsPtr psInspireCommonNs = xmlSearchNs(
20✔
3609
          psDoc, psRootNode, BAD_CAST MS_INSPIRE_COMMON_NAMESPACE_PREFIX);
3610
      xmlNsPtr psInspireDlsNs = xmlSearchNs(
20✔
3611
          psDoc, psRootNode, BAD_CAST MS_INSPIRE_DLS_NAMESPACE_PREFIX);
3612

3613
      old_context = msIO_pushStdoutToBufferAndGetOldContext();
20✔
3614
      msOWSPrintInspireCommonExtendedCapabilities(
20✔
3615
          stdout, map, "CO", OWS_WARN, "foo",
3616
          "xmlns:" MS_INSPIRE_COMMON_NAMESPACE_PREFIX
3617
          "=\"" MS_INSPIRE_COMMON_NAMESPACE_URI "\" "
3618
          "xmlns:" MS_INSPIRE_DLS_NAMESPACE_PREFIX
3619
          "=\"" MS_INSPIRE_DLS_NAMESPACE_URI "\" "
3620
          "xmlns:xsi=\"" MS_OWSCOMMON_W3C_XSI_NAMESPACE_URI "\"",
3621
          validated_language, OWS_WCS);
3622

3623
      new_context = msIO_getHandler(stdout);
20✔
3624
      buffer = (msIOBuffer *)new_context->cbData;
20✔
3625

3626
      /* Remove spaces between > and < to get properly indented result */
3627
      msXMLStripIndentation((char *)buffer->data);
20✔
3628

3629
      pInspireTmpDoc = xmlParseDoc((const xmlChar *)buffer->data);
20✔
3630
      pRoot = xmlDocGetRootElement(pInspireTmpDoc);
20✔
3631
      xmlReconciliateNs(psDoc, pRoot);
20✔
3632

3633
      pOWSExtendedCapabilities =
3634
          xmlNewNode(psOwsNs, BAD_CAST "ExtendedCapabilities");
20✔
3635
      xmlAddChild(psOperationsNode, pOWSExtendedCapabilities);
20✔
3636

3637
      pDlsExtendedCapabilities =
3638
          xmlNewNode(psInspireDlsNs, BAD_CAST "ExtendedCapabilities");
20✔
3639
      xmlAddChild(pOWSExtendedCapabilities, pDlsExtendedCapabilities);
20✔
3640

3641
      pChild = pRoot->children;
20✔
3642
      while (pChild != NULL) {
150✔
3643
        xmlNodePtr pNext = pChild->next;
130✔
3644
        xmlUnlinkNode(pChild);
130✔
3645
        xmlAddChild(pDlsExtendedCapabilities, pChild);
130✔
3646
        pChild = pNext;
3647
      }
3648

3649
      msWCSAddInspireDSID20(map, psInspireDlsNs, psInspireCommonNs,
20✔
3650
                            pDlsExtendedCapabilities);
3651

3652
      msIO_restoreOldStdoutContext(old_context);
20✔
3653
    }
3654
  }
3655

3656
  /* -------------------------------------------------------------------- */
3657
  /*      Service metadata.                                               */
3658
  /* -------------------------------------------------------------------- */
3659

3660
  if (MS_WCS_20_CAPABILITIES_INCLUDE_SECTION(params, "ServiceMetadata")) {
39✔
3661
    xmlNodePtr psExtensionNode = NULL;
3662
    psNode = xmlNewChild(psRootNode, psWcsNs, BAD_CAST "ServiceMetadata", NULL);
35✔
3663

3664
    /* Apply default formats */
3665
    msApplyDefaultOutputFormats(map);
35✔
3666

3667
    /* Add formats list */
3668
    format_list = msWCSGetFormatsList20(map, NULL);
35✔
3669
    msLibXml2GenerateList(psNode, psWcsNs, "formatSupported", format_list, ',');
35✔
3670
    msFree(format_list);
35✔
3671

3672
    psExtensionNode = xmlNewChild(psNode, psWcsNs, BAD_CAST "Extension", NULL);
35✔
3673
    /* interpolations supported */
3674
    {
3675
      /* TODO: use URIs/URLs once they are specified */
3676
      const char *const interpolation_methods[] = {"NEAREST", "AVERAGE",
35✔
3677
                                                   "BILINEAR"};
3678
      int i;
3679
      xmlNodePtr imNode = xmlNewChild(psExtensionNode, psIntNs,
35✔
3680
                                      BAD_CAST "InterpolationMetadata", NULL);
3681

3682
      for (i = 0; i < 3; ++i) {
140✔
3683
        xmlNewChild(imNode, psIntNs, BAD_CAST "InterpolationSupported",
105✔
3684
                    BAD_CAST interpolation_methods[i]);
105✔
3685
      }
3686
    }
3687

3688
    /* Report the supported CRSs */
3689
    {
3690
      char *crs_list = NULL;
3691
      xmlNodePtr crsMetadataNode =
3692
          xmlNewChild(psExtensionNode, psCrsNs, BAD_CAST "CrsMetadata", NULL);
35✔
3693

3694
      if ((crs_list = msOWSGetProjURI(&(map->projection), &(map->web.metadata),
35✔
3695
                                      "CO", MS_FALSE)) != NULL) {
3696
        msLibXml2GenerateList(crsMetadataNode, psCrsNs, "crsSupported",
33✔
3697
                              crs_list, ' ');
3698
        msFree(crs_list);
33✔
3699
      } else {
3700
        /* could not determine list of CRSs */
3701
      }
3702
    }
3703
  }
3704

3705
  /* -------------------------------------------------------------------- */
3706
  /*      Contents section.                                               */
3707
  /* -------------------------------------------------------------------- */
3708
  if (MS_WCS_20_CAPABILITIES_INCLUDE_SECTION(params, "Contents")) {
39✔
3709
    psNode = xmlNewChild(psRootNode, psWcsNs, BAD_CAST "Contents", NULL);
35✔
3710

3711
    if (ows_request->numlayers == 0) {
35✔
3712
      xmlAddChild(psNode, xmlNewComment(BAD_CAST(
2✔
3713
                              "WARNING: No WCS layers are enabled. "
3714
                              "Check wcs/ows_enable_request settings.")));
3715
    } else {
3716
      for (i = 0; i < map->numlayers; ++i) {
66✔
3717
        layerObj *layer = map->layers[i];
33✔
3718
        int status;
3719

3720
        if (!msWCSIsLayerSupported(layer))
33✔
3721
          continue;
×
3722

3723
        if (!msIntegerInArray(layer->index, ows_request->enabled_layers,
33✔
3724
                              ows_request->numlayers))
3725
          continue;
×
3726

3727
        status = msWCSGetCapabilities20_CoverageSummary(psDoc, psNode, layer);
33✔
3728
        if (status != MS_SUCCESS) {
33✔
3729
          msFree(validated_language);
×
3730
          xmlFreeDoc(psDoc);
×
3731
          xmlCleanupParser();
×
3732
          return msWCSException(map, "Internal", "mapserv", params->version);
×
3733
        }
3734
      }
3735
    }
3736
  }
3737
  /* -------------------------------------------------------------------- */
3738
  /*      Write out the document and clean up.                            */
3739
  /* -------------------------------------------------------------------- */
3740
  msWCSWriteDocument20(psDoc);
39✔
3741
  msFree(validated_language);
39✔
3742
  xmlFreeDoc(psDoc);
39✔
3743
  if (pInspireTmpDoc)
39✔
3744
    xmlFreeDoc(pInspireTmpDoc);
20✔
3745
  xmlCleanupParser();
39✔
3746
  return MS_SUCCESS;
39✔
3747
}
3748

3749
/************************************************************************/
3750
/*                   msWCSDescribeCoverage20_CoverageDescription()      */
3751
/*                                                                      */
3752
/*      Creates a description of a specific coverage with the three     */
3753
/*      major sections: BoundedBy, DomainSet and RangeType.             */
3754
/************************************************************************/
3755

3756
static int msWCSDescribeCoverage20_CoverageDescription(layerObj *layer,
5✔
3757
                                                       xmlDocPtr psDoc,
3758
                                                       xmlNodePtr psRootNode) {
3759
  int status, swapAxes;
3760
  wcs20coverageMetadataObj cm;
3761
  xmlNodePtr psCD;
3762
  xmlNsPtr psWcsNs, psGmlNs, psGmlcovNs, psSweNs, psXLinkNs;
3763
  psWcsNs = psGmlNs = psGmlcovNs = psSweNs = psXLinkNs = NULL;
3764

3765
  psWcsNs = xmlSearchNs(psDoc, xmlDocGetRootElement(psDoc),
5✔
3766
                        BAD_CAST MS_OWSCOMMON_WCS_NAMESPACE_PREFIX);
3767
  psGmlNs = xmlSearchNs(psDoc, xmlDocGetRootElement(psDoc),
5✔
3768
                        BAD_CAST MS_OWSCOMMON_GML_NAMESPACE_PREFIX);
3769
  psGmlcovNs = xmlSearchNs(psDoc, xmlDocGetRootElement(psDoc),
5✔
3770
                           BAD_CAST MS_OWSCOMMON_GMLCOV_NAMESPACE_PREFIX);
3771
  psSweNs = xmlSearchNs(psDoc, xmlDocGetRootElement(psDoc),
5✔
3772
                        BAD_CAST MS_OWSCOMMON_SWE_NAMESPACE_PREFIX);
3773
  psXLinkNs = xmlSearchNs(psDoc, xmlDocGetRootElement(psDoc),
5✔
3774
                          BAD_CAST MS_OWSCOMMON_W3C_XLINK_NAMESPACE_PREFIX);
3775

3776
  /* -------------------------------------------------------------------- */
3777
  /*      Verify layer is processable.                                    */
3778
  /* -------------------------------------------------------------------- */
3779
  if (msCheckParentPointer(layer->map, "map") == MS_FAILURE)
5✔
3780
    return MS_FAILURE;
3781

3782
  if (!msWCSIsLayerSupported(layer))
5✔
3783
    return MS_SUCCESS;
3784

3785
  /* -------------------------------------------------------------------- */
3786
  /*      Setup coverage metadata.                                        */
3787
  /* -------------------------------------------------------------------- */
3788
  status = msWCSGetCoverageMetadata20(layer, &cm);
5✔
3789
  if (status != MS_SUCCESS)
5✔
3790
    return status;
3791

3792
  swapAxes = msWCSSwapAxes20(cm.srs_uri);
5✔
3793

3794
  /* fill in bands rangeset info, if required. */
3795
  /* msWCSSetDefaultBandsRangeSetInfo( NULL, &cm, layer ); */
3796

3797
  /* -------------------------------------------------------------------- */
3798
  /*      Create CoverageDescription node.                                */
3799
  /* -------------------------------------------------------------------- */
3800
  psCD = xmlNewChild(psRootNode, psWcsNs, BAD_CAST "CoverageDescription", NULL);
5✔
3801
  xmlNewNsProp(psCD, psGmlNs, BAD_CAST "id", BAD_CAST layer->name);
5✔
3802

3803
  /* -------------------------------------------------------------------- */
3804
  /*      gml:boundedBy                                                   */
3805
  /* -------------------------------------------------------------------- */
3806
  msWCSCommon20_CreateBoundedBy(&cm, psGmlNs, psCD, &(layer->projection),
5✔
3807
                                swapAxes);
3808

3809
  xmlNewChild(psCD, psWcsNs, BAD_CAST "CoverageId", BAD_CAST layer->name);
5✔
3810

3811
  /* -------------------------------------------------------------------- */
3812
  /*      gml:domainSet                                                   */
3813
  /* -------------------------------------------------------------------- */
3814
  msWCSCommon20_CreateDomainSet(layer, &cm, psGmlNs, psCD, &(layer->projection),
5✔
3815
                                swapAxes);
3816

3817
  /* -------------------------------------------------------------------- */
3818
  /*      gmlcov:rangeType                                                */
3819
  /* -------------------------------------------------------------------- */
3820
  msWCSCommon20_CreateRangeType(&cm, NULL, psGmlcovNs, psSweNs, psCD);
5✔
3821

3822
  /* -------------------------------------------------------------------- */
3823
  /*      wcs:ServiceParameters                                           */
3824
  /* -------------------------------------------------------------------- */
3825
  {
3826
    xmlNodePtr psSP;
3827

3828
    psSP = xmlNewChild(psCD, psWcsNs, BAD_CAST "ServiceParameters", NULL);
5✔
3829
    xmlNewChild(psSP, psWcsNs, BAD_CAST "CoverageSubtype",
5✔
3830
                BAD_CAST "RectifiedGridCoverage");
3831

3832
    /* -------------------------------------------------------------------- */
3833
    /*      SupportedCRS                                                    */
3834
    /* -------------------------------------------------------------------- */
3835
    /* for now, WCS 2.0 does not allow per coverage CRS definitions */
3836
    /*{
3837
      xmlNodePtr psSupportedCrss;
3838
      char *owned_value;
3839

3840
      psSupportedCrss = xmlNewChild(psSP, psWcsNs,
3841
                                    BAD_CAST "SupportedCRSs", NULL);
3842

3843
      if ((owned_value = msOWSGetProjURI(&(layer->projection),
3844
                                         &(layer->metadata), "CO", MS_FALSE)) !=
3845
    NULL) { } else if ((owned_value = msOWSGetProjURI(&(layer->map->projection),
3846
                                              &(layer->map->web.metadata), "CO",
3847
    MS_FALSE)) != NULL) { } else { msDebug("missing required information, no
3848
    SRSs defined.\n");
3849
      }
3850

3851
      if (owned_value != NULL && strlen(owned_value) > 0) {
3852
        msLibXml2GenerateList(psSupportedCrss, psWcsNs,
3853
                              "SupportedCRS", owned_value, ' ');
3854
      }
3855

3856
      xmlNewChild(psSupportedCrss, psWcsNs,
3857
                  BAD_CAST "NativeCRS", BAD_CAST cm.srs_uri);
3858

3859
      msFree(owned_value);
3860
    }*/
3861

3862
    /* -------------------------------------------------------------------- */
3863
    /*      SupportedFormats                                                */
3864
    /* -------------------------------------------------------------------- */
3865
    /* for now, WCS 2.0 does not allow per coverage format definitions */
3866
    /*{
3867
      xmlNodePtr psSupportedFormats;
3868
      char *format_list;
3869

3870
      psSupportedFormats =
3871
      xmlNewChild(psSP, psWcsNs, BAD_CAST "SupportedFormats", NULL);
3872

3873
      format_list = msWCSGetFormatsList20(layer->map, layer);
3874

3875
      if (strlen(format_list) > 0) {
3876
        msLibXml2GenerateList(psSupportedFormats, psWcsNs,
3877
                              "SupportedFormat", format_list, ',');
3878

3879
      msFree(format_list);
3880
    }*/
3881

3882
    /* -------------------------------------------------------------------- */
3883
    /*      nativeFormat                                                    */
3884
    /* -------------------------------------------------------------------- */
3885
    xmlNewChild(psSP, psWcsNs, BAD_CAST "nativeFormat",
5✔
3886
                BAD_CAST(cm.native_format ? cm.native_format : ""));
5✔
3887

3888
    if (!cm.native_format) {
5✔
3889
      msDebug("msWCSDescribeCoverage20_CoverageDescription(): "
1✔
3890
              "No native format specified.\n");
3891
    }
3892
  }
3893

3894
  msWCSClearCoverageMetadata20(&cm);
5✔
3895

3896
  return MS_SUCCESS;
5✔
3897
}
3898

3899
/************************************************************************/
3900
/*                   msWCSDescribeCoverage20()                          */
3901
/*                                                                      */
3902
/*      Implementation of the DescribeCoverage Operation. The result     */
3903
/*      of this operation is a xml document, containing specific        */
3904
/*      information about a coverage identified by an ID. The result    */
3905
/*      is written on the stream.                                       */
3906
/************************************************************************/
3907

3908
int msWCSDescribeCoverage20(mapObj *map, wcs20ParamsObjPtr params,
4✔
3909
                            owsRequestObj *ows_request) {
3910
  xmlDocPtr psDoc = NULL; /* document pointer */
3911
  xmlNodePtr psRootNode;
3912
  xmlNsPtr psWcsNs = NULL;
3913
  int i, j;
3914

3915
  /* create DOM document and root node */
3916
  psDoc = xmlNewDoc(BAD_CAST "1.0");
4✔
3917
  psRootNode = xmlNewNode(NULL, BAD_CAST "CoverageDescriptions");
4✔
3918
  xmlDocSetRootElement(psDoc, psRootNode);
4✔
3919

3920
  /* prepare initial namespace definitions */
3921
  msWCSPrepareNamespaces20(psDoc, psRootNode, map, MS_FALSE);
4✔
3922

3923
  psWcsNs = xmlSearchNs(psDoc, psRootNode,
4✔
3924
                        BAD_CAST MS_OWSCOMMON_WCS_NAMESPACE_PREFIX);
3925
  xmlSetNs(psRootNode, psWcsNs);
4✔
3926

3927
  /* check if IDs are given */
3928
  if (params->ids) {
4✔
3929
    /* for each given ID in the ID-list */
3930
    for (j = 0; params->ids[j]; j++) {
9✔
3931
      i = msGetLayerIndex(map, params->ids[j]);
5✔
3932
      if (i == -1 || (!msIntegerInArray(GET_LAYER(map, i)->index,
5✔
3933
                                        ows_request->enabled_layers,
3934
                                        ows_request->numlayers))) {
3935
        msSetError(MS_WCSERR, "Unknown coverage: (%s)",
×
3936
                   "msWCSDescribeCoverage20()", params->ids[j]);
×
3937
        return msWCSException(map, "NoSuchCoverage", "coverage",
×
3938
                              params->version);
×
3939
      }
3940
      /* create coverage description for the specified layer */
3941
      if (msWCSDescribeCoverage20_CoverageDescription(
5✔
3942
              (GET_LAYER(map, i)), psDoc, psRootNode) == MS_FAILURE) {
5✔
3943
        msSetError(MS_WCSERR, "Error retrieving coverage description.",
×
3944
                   "msWCSDescribeCoverage20()");
3945
        return msWCSException(map, "MissingParameterValue", "coverage",
×
3946
                              params->version);
×
3947
      }
3948
    }
3949
  } else {
3950
    /* Throw error, since IDs are mandatory */
3951
    msSetError(MS_WCSERR, "Missing COVERAGEID parameter.",
×
3952
               "msWCSDescribeCoverage20()");
3953
    return msWCSException(map, "MissingParameterValue", "coverage",
×
3954
                          params->version);
×
3955
  }
3956

3957
  /* write out the DOM document to the stream */
3958
  msWCSWriteDocument20(psDoc);
4✔
3959

3960
  /* cleanup */
3961
  xmlFreeDoc(psDoc);
4✔
3962
  xmlCleanupParser();
4✔
3963

3964
  return MS_SUCCESS;
4✔
3965
}
3966

3967
/************************************************************************/
3968
/*                   msWCSGetCoverage_FinalizeParamsObj20()             */
3969
/*                                                                      */
3970
/*      Finalizes a wcs20ParamsObj for a GetCoverage operation. In the  */
3971
/*      process, the params bounding box is adjusted to the subsets,     */
3972
/*      width, height and resolution are determined and the subset crs  */
3973
/*      is found out.                                                   */
3974
/************************************************************************/
3975

3976
static int msWCSGetCoverage20_FinalizeParamsObj(wcs20ParamsObjPtr params,
156✔
3977
                                                wcs20AxisObjPtr *axes) {
3978
  char *crs = NULL;
3979
  int have_scale, have_size, have_resolution;
3980
  int have_global_scale = (params->scale != MS_WCS20_UNBOUNDED) ? 1 : 0;
156✔
3981

3982
  if (axes[0] != NULL) {
156✔
3983
    if (axes[0]->subset != NULL) {
91✔
3984
      msDebug("Subset for X-axis found: %s\n", axes[0]->subset->axis);
58✔
3985
      if (!axes[0]->subset->min.unbounded)
58✔
3986
        params->bbox.minx = axes[0]->subset->min.scalar;
55✔
3987
      if (!axes[0]->subset->max.unbounded)
58✔
3988
        params->bbox.maxx = axes[0]->subset->max.scalar;
55✔
3989
      crs = axes[0]->subset->crs;
58✔
3990
    }
3991
    params->width = axes[0]->size;
91✔
3992
    params->resolutionX = axes[0]->resolution;
91✔
3993
    params->scaleX = axes[0]->scale;
91✔
3994

3995
    if (axes[0]->resolutionUOM != NULL) {
91✔
3996
      params->resolutionUnits = msStrdup(axes[0]->resolutionUOM);
×
3997
    }
3998

3999
    have_scale = (params->scaleX != MS_WCS20_UNBOUNDED) ? 1 : 0;
91✔
4000
    have_size = (params->width != 0) ? 1 : 0;
91✔
4001
    have_resolution = (params->resolutionX != MS_WCS20_UNBOUNDED) ? 1 : 0;
91✔
4002

4003
    if ((have_global_scale + have_scale + have_size + have_resolution) > 1) {
91✔
4004
      msSetError(
2✔
4005
          MS_WCSERR,
4006
          "Axis '%s' defines scale, size and/or resolution multiple times.",
4007
          "msWCSGetCoverage20_FinalizeParamsObj()", axes[0]->name);
2✔
4008
      return MS_FAILURE;
2✔
4009
    }
4010
  }
4011

4012
  if (axes[1] != NULL) {
154✔
4013
    if (axes[1]->subset != NULL) {
86✔
4014
      msDebug("Subset for Y-axis found: %s\n", axes[1]->subset->axis);
55✔
4015
      if (!axes[1]->subset->min.unbounded)
55✔
4016
        params->bbox.miny = axes[1]->subset->min.scalar;
52✔
4017
      if (!axes[1]->subset->max.unbounded)
55✔
4018
        params->bbox.maxy = axes[1]->subset->max.scalar;
52✔
4019
      if (crs != NULL && axes[0] != NULL && axes[0]->subset != NULL) {
55✔
4020
        if (!EQUAL(crs, axes[1]->subset->crs)) {
18✔
4021
          msSetError(MS_WCSERR, "CRS for axis %s and axis %s are not the same.",
×
4022
                     "msWCSGetCoverage20_FinalizeParamsObj()", axes[0]->name,
4023
                     axes[1]->name);
4024
          return MS_FAILURE;
×
4025
        }
4026
      } else {
4027
        crs = axes[1]->subset->crs;
37✔
4028
      }
4029
    }
4030
    params->height = axes[1]->size;
86✔
4031
    params->resolutionY = axes[1]->resolution;
86✔
4032
    params->scaleY = axes[1]->scale;
86✔
4033

4034
    if (params->resolutionUnits == NULL && axes[1]->resolutionUOM != NULL) {
86✔
4035
      params->resolutionUnits = msStrdup(axes[1]->resolutionUOM);
×
4036
    } else if (params->resolutionUnits != NULL &&
86✔
4037
               axes[1]->resolutionUOM != NULL &&
×
4038
               !EQUAL(params->resolutionUnits, axes[1]->resolutionUOM)) {
×
4039
      msSetError(MS_WCSERR,
×
4040
                 "The units of measure of the resolution for"
4041
                 "axis %s and axis %s are not the same.",
4042
                 "msWCSGetCoverage20_FinalizeParamsObj()", axes[0]->name,
×
4043
                 axes[1]->name);
4044
      return MS_FAILURE;
×
4045
    }
4046

4047
    have_scale = (params->scaleY != MS_WCS20_UNBOUNDED) ? 1 : 0;
86✔
4048
    have_size = (params->height != 0) ? 1 : 0;
86✔
4049
    have_resolution = (params->resolutionY != MS_WCS20_UNBOUNDED) ? 1 : 0;
86✔
4050

4051
    if ((have_global_scale + have_scale + have_size + have_resolution) > 1) {
86✔
4052
      msSetError(
×
4053
          MS_WCSERR,
4054
          "Axis '%s' defines scale, size and/or resolution multiple times.",
4055
          "msWCSGetCoverage20_FinalizeParamsObj()", axes[1]->name);
×
4056
      return MS_FAILURE;
×
4057
    }
4058
  }
4059

4060
  /* if scale is globally set, use this value instead */
4061
  if (params->scale != MS_WCS20_UNBOUNDED) {
154✔
4062
    params->scaleX = params->scaleY = params->scale;
2✔
4063
  }
4064

4065
  /* check if projections are equal */
4066
  if (crs != NULL) {
154✔
4067
    if (params->subsetcrs && !EQUAL(crs, params->subsetcrs)) {
21✔
4068
      /* already set and not equal -> raise exception */
4069
      msSetError(MS_WCSERR,
×
4070
                 "SubsetCRS does not match the CRS of the axes: "
4071
                 "'%s' != '%s'",
4072
                 "msWCSCreateBoundingBox20()", params->subsetcrs, crs);
4073
      return MS_FAILURE;
×
4074
    } else if (params->subsetcrs) {
21✔
4075
      /* equal, and already set -> do nothing */
4076
    } else {
4077
      /* not yet globally set -> set it */
4078
      params->subsetcrs = msStrdup(crs);
18✔
4079
    }
4080
  } else if (!params->subsetcrs) {
4081
    /* default to CRS of image */
4082
    /* leave params->subsetcrs to null */
4083
  }
4084

4085
  return MS_SUCCESS;
4086
}
4087

4088
/************************************************************************/
4089
/*                   msWCSGetCoverage20_GetBands()                      */
4090
/*                                                                      */
4091
/*      Returns a string, containing a comma-separated list of band     */
4092
/*      indices.                                                        */
4093
/************************************************************************/
4094

4095
static int msWCSGetCoverage20_GetBands(layerObj *layer,
148✔
4096
                                       wcs20ParamsObjPtr params,
4097
                                       wcs20coverageMetadataObjPtr cm,
4098
                                       char **bandlist) {
4099
  int maxlen, index;
4100
  char *interval_stop;
4101
  char **band_ids = NULL;
4102

4103
  /* if rangesubset parameter is not given, default to all bands */
4104
  if (NULL == params->range_subset) {
148✔
4105
    *bandlist = msStrdup("1");
121✔
4106
    for (unsigned i = 1; i < cm->numbands; ++i) {
145✔
4107
      char strnumber[12];
4108
      snprintf(strnumber, sizeof(strnumber), ",%d", i + 1);
24✔
4109
      *bandlist = msStringConcatenate(*bandlist, strnumber);
24✔
4110
    }
4111
    return MS_SUCCESS;
4112
  }
4113

4114
  maxlen = cm->numbands * 4 * sizeof(char);
27✔
4115
  *bandlist = static_cast<char *>(msSmallCalloc(sizeof(char), maxlen));
27✔
4116

4117
  /* Use WCS 2.0 metadata items in priority */
4118
  {
4119
    char *tmp =
4120
        msOWSGetEncodeMetadata(&layer->metadata, "CO", "band_names", NULL);
27✔
4121

4122
    if (NULL == tmp) {
27✔
4123
      /* Otherwise default to WCS 1.x*/
4124
      tmp =
4125
          msOWSGetEncodeMetadata(&layer->metadata, "CO", "rangeset_axes", NULL);
11✔
4126
      /* "bands" has a special processing in WCS 1.0. See */
4127
      /* msWCSSetDefaultBandsRangeSetInfo */
4128
      if (tmp != NULL && EQUAL(tmp, "bands")) {
11✔
4129
        int num_band_names = cm->numbands;
11✔
4130
        band_ids = (char **)msSmallCalloc(sizeof(char *), (num_band_names + 1));
11✔
4131
        for (int i = 0; i < num_band_names; i++) {
104✔
4132
          char szName[30];
4133
          snprintf(szName, sizeof(szName), "Band%d", i + 1);
93✔
4134
          band_ids[i] = msStrdup(szName);
93✔
4135
        }
4136
      }
4137
    }
4138

4139
    if (NULL != tmp && band_ids == NULL) {
27✔
4140
      band_ids = CSLTokenizeString2(tmp, " ", 0);
16✔
4141
    }
4142
    msFree(tmp);
27✔
4143
  }
4144

4145
  /* If we still don't have band names, use the band names from the coverage
4146
   * metadata */
4147
  if (band_ids == NULL) {
27✔
4148
    band_ids = (char **)CPLCalloc(sizeof(char *), (cm->numbands + 1));
×
4149
    for (unsigned i = 0; i < cm->numbands; ++i) {
×
4150
      band_ids[i] = CPLStrdup(cm->bands[i].name);
×
4151
    }
4152
  }
4153

4154
  /* Iterate over all supplied range */
4155
  const int count = CSLCount(params->range_subset);
27✔
4156
  for (int i = 0; i < count; ++i) {
89✔
4157
    /* RangeInterval case: defined as "<start>:<stop>" */
4158
    if ((interval_stop = strchr(params->range_subset[i], ':')) != NULL) {
62✔
4159
      int start, stop;
4160
      *interval_stop = '\0';
12✔
4161
      ++interval_stop;
12✔
4162

4163
      /* check if the string represents an integer */
4164
      if (msStringParseInteger(params->range_subset[i], &start) == MS_SUCCESS) {
12✔
4165
      }
4166
      /* get index of start clause, or raise an error if none was found */
4167
      else if ((start = CSLFindString(band_ids, params->range_subset[i])) !=
8✔
4168
               -1) {
4169
        start += 1; /* adjust index, since GDAL bands start with index 1 */
8✔
4170
      } else {
4171
        msSetError(MS_WCSERR, "'%s' is not a valid band identifier.",
×
4172
                   "msWCSGetCoverage20_GetBands()", params->range_subset[i]);
×
4173
        return MS_FAILURE;
×
4174
      }
4175

4176
      /* check if the string represents an integer */
4177
      if (msStringParseInteger(interval_stop, &stop) == MS_SUCCESS) {
12✔
4178
      }
4179
      /* get index of stop clause, or raise an error if none was found */
4180
      else if ((stop = CSLFindString(band_ids, interval_stop)) != -1) {
8✔
4181
        stop += 1; /* adjust index, since GDAL bands start with index 1 */
8✔
4182
      } else {
4183
        msSetError(MS_WCSERR, "'%s' is not a valid band identifier.",
×
4184
                   "msWCSGetCoverage20_GetBands()", interval_stop);
4185
        return MS_FAILURE;
×
4186
      }
4187

4188
      /* Check whether or not start and stop are different and stop is higher
4189
       * than start */
4190
      if (stop <= start) {
12✔
4191
        msSetError(MS_WCSERR, "Invalid range interval given.",
×
4192
                   "msWCSGetCoverage20_GetBands()");
4193
        return MS_FAILURE;
×
4194
      } else if (start < 1 || static_cast<unsigned>(stop) > cm->numbands) {
12✔
4195
        msSetError(MS_WCSERR, "Band interval is out of the valid range: 1-%d",
×
4196
                   "msWCSGetCoverage20_GetBands()", (int)cm->numbands);
×
4197
        return MS_FAILURE;
×
4198
      }
4199

4200
      /* expand the interval to a list of indices and push them on the list */
4201
      for (int j = start; j <= stop; ++j) {
63✔
4202
        char *tmp;
4203
        if (i != 0 || j != start) {
51✔
4204
          strlcat(*bandlist, ",", maxlen);
45✔
4205
        }
4206
        tmp = msIntToString(j);
51✔
4207
        strlcat(*bandlist, tmp, maxlen);
51✔
4208
        msFree(tmp);
51✔
4209
      }
4210
    }
4211
    /* RangeComponent case */
4212
    else {
4213
      if (i != 0) {
50✔
4214
        strlcat(*bandlist, ",", maxlen);
29✔
4215
      }
4216

4217
      /* check if the string represents an integer */
4218
      if (msStringParseInteger(params->range_subset[i], &index) == MS_SUCCESS) {
50✔
4219
        char *tmp;
4220
        if (index < 1 || static_cast<unsigned>(index) > cm->numbands) {
19✔
4221
          msSetError(MS_WCSERR, "Band index is out of the valid range: 1-%d",
×
4222
                     "msWCSGetCoverage20_GetBands()", (int)cm->numbands);
×
4223
          return MS_FAILURE;
×
4224
        }
4225

4226
        tmp = msIntToString((int)index);
19✔
4227
        strlcat(*bandlist, tmp, maxlen);
19✔
4228
        msFree(tmp);
19✔
4229
        continue;
19✔
4230
      }
19✔
4231

4232
      /* check if the string is equal to a band identifier    */
4233
      /* if so, what is the index of the band                 */
4234
      if ((index = CSLFindString(band_ids, params->range_subset[i])) != -1) {
31✔
4235
        char *tmp = msIntToString((int)index + 1);
31✔
4236
        strlcat(*bandlist, tmp, maxlen);
31✔
4237
        msFree(tmp);
31✔
4238
      } else {
4239
        msSetError(MS_WCSERR, "'%s' is not a valid band identifier.",
×
4240
                   "msWCSGetCoverage20_GetBands()", params->range_subset[i]);
×
4241
        return MS_FAILURE;
×
4242
      }
4243
    }
4244
  }
4245

4246
  CSLDestroy(band_ids);
27✔
4247
  return MS_SUCCESS;
4248
}
4249

4250
/* Fixes CPLParseNameValue to allow ':' characters for namespaced keys */
4251

4252
static const char *fixedCPLParseNameValue(const char *string, char **key) {
10✔
4253
  const char *value;
4254
  size_t size;
4255
  value = strchr(string, '=');
4256

4257
  if (value == NULL) {
10✔
4258
    *key = NULL;
×
4259
    return NULL;
×
4260
  }
4261

4262
  size = value - string;
10✔
4263
  *key = static_cast<char *>(msSmallMalloc(size + 1));
10✔
4264
  strncpy(*key, string, size + 1);
4265
  (*key)[size] = '\0';
10✔
4266
  return value + 1;
10✔
4267
}
4268

4269
/************************************************************************/
4270
/*                   msWCSSetFormatParams20()                           */
4271
/*                                                                      */
4272
/*      Parses the given format options and sets the appropriate        */
4273
/*      output format values.                                           */
4274
/************************************************************************/
4275

4276
static int msWCSSetFormatParams20(outputFormatObj *format,
148✔
4277
                                  char **format_options) {
4278
  /* currently geotiff only */
4279
  char *format_option;
4280
  int i = 0;
4281
  int is_geotiff = (format->mimetype && EQUAL(format->mimetype, "image/tiff"));
148✔
4282

4283
  if (!is_geotiff || !format_options) {
148✔
4284
    /* Currently only geotiff available */
4285
    return MS_SUCCESS;
4286
  }
4287

4288
  format_option = format_options[0];
5✔
4289
  while (format_option) {
15✔
4290
    /* key, value */
4291
    char *key;
4292
    const char *value = fixedCPLParseNameValue(format_option, &key);
10✔
4293

4294
    if (!key) {
10✔
4295
      msSetError(MS_WCSERR, "Could not deduct the option key.",
×
4296
                 "msWCSSetFormatParams20()");
4297
      return MS_FAILURE;
×
4298
    } else if (!value) {
10✔
4299
      msSetError(MS_WCSERR, "Missing value for parameter '%s'.",
×
4300
                 "msWCSSetFormatParams20()", key);
4301
      return MS_FAILURE;
×
4302
    }
4303

4304
    if (EQUAL(key, "geotiff:compression") && is_geotiff) {
10✔
4305
      /*COMPRESS=[JPEG/LZW/PACKBITS/DEFLATE/CCITTRLE/CCITTFAX3/CCITTFAX4/NONE]*/
4306
      if (EQUAL(value, "None")) {
2✔
4307
        msSetOutputFormatOption(format, "COMPRESS", "NONE");
×
4308
      } else if (EQUAL(value, "PackBits")) {
2✔
4309
        msSetOutputFormatOption(format, "COMPRESS", "PACKBITS");
×
4310
      } else if (EQUAL(value, "Deflate")) {
2✔
4311
        msSetOutputFormatOption(format, "COMPRESS", "DEFLATE");
1✔
4312
      } else if (EQUAL(value, "Huffman")) {
1✔
4313
        msSetOutputFormatOption(format, "COMPRESS", "CCITTRLE");
×
4314
      } else if (EQUAL(value, "LZW")) {
1✔
4315
        msSetOutputFormatOption(format, "COMPRESS", "LZW");
×
4316
      } else if (EQUAL(value, "JPEG")) {
1✔
4317
        msSetOutputFormatOption(format, "COMPRESS", "JPEG");
1✔
4318
      }
4319
      /* unsupported compression methods: CCITTFAX3/CCITTFAX4 */
4320
      else {
4321
        msSetError(MS_WCSERR, "Compression method '%s' not supported.",
×
4322
                   "msWCSSetFormatParams20()", value);
4323
        return MS_FAILURE;
×
4324
      }
4325
    }
4326

4327
    else if (EQUAL(key, "geotiff:jpeg_quality") && is_geotiff) {
8✔
4328
      int quality;
4329
      /* JPEG_QUALITY=[1-100] */
4330
      if (msStringParseInteger(value, &quality) != MS_SUCCESS) {
1✔
4331
        msSetError(MS_WCSERR, "Could not parse jpeg_quality value.",
×
4332
                   "msWCSSetFormatParams20()");
4333
        return MS_FAILURE;
×
4334
      } else if (quality < 1 || quality > 100) {
1✔
4335
        msSetError(MS_WCSERR, "Invalid jpeg_quality value '%d'.",
×
4336
                   "msWCSSetFormatParams20()", quality);
4337
        return MS_FAILURE;
×
4338
      }
4339
      msSetOutputFormatOption(format, "JPEG_QUALITY", value);
1✔
4340
    } else if (EQUAL(key, "geotiff:predictor") && is_geotiff) {
8✔
4341
      /* PREDICTOR=[None/Horizontal/FloatingPoint] */
4342
      const char *predictor;
4343
      if (EQUAL(value, "None") || EQUAL(value, "1")) {
1✔
4344
        predictor = "1";
4345
      } else if (EQUAL(value, "Horizontal") || EQUAL(value, "2")) {
1✔
4346
        predictor = "2";
4347
      } else if (EQUAL(value, "FloatingPoint") || EQUAL(value, "3")) {
×
4348
        predictor = "3";
4349
      } else {
4350
        msSetError(MS_WCSERR, "Invalid predictor value '%s'.",
×
4351
                   "msWCSSetFormatParams20()", value);
4352
        return MS_FAILURE;
×
4353
      }
4354
      msSetOutputFormatOption(format, "PREDICTOR", predictor);
1✔
4355
    } else if (EQUAL(key, "geotiff:interleave") && is_geotiff) {
6✔
4356
      /* INTERLEAVE=[BAND,PIXEL] */
4357
      if (EQUAL(value, "Band")) {
1✔
4358
        msSetOutputFormatOption(format, "INTERLEAVE", "BAND");
1✔
4359
      } else if (EQUAL(value, "Pixel")) {
×
4360
        msSetOutputFormatOption(format, "INTERLEAVE", "PIXEL");
×
4361
      } else {
4362
        msSetError(MS_WCSERR, "Interleave method '%s' not supported.",
×
4363
                   "msWCSSetFormatParams20()", value);
4364
        return MS_FAILURE;
×
4365
      }
4366
    } else if (EQUAL(key, "geotiff:tiling") && is_geotiff) {
5✔
4367
      /* TILED=YES */
4368
      if (EQUAL(value, "true")) {
1✔
4369
        msSetOutputFormatOption(format, "TILED", "YES");
1✔
4370
      } else if (EQUAL(value, "false")) {
×
4371
        msSetOutputFormatOption(format, "TILED", "NO");
×
4372
      } else {
4373
        msSetError(MS_WCSERR, "Invalid boolean value '%s'.",
×
4374
                   "msWCSSetFormatParams20()", value);
4375
        return MS_FAILURE;
×
4376
      }
4377
    } else if (EQUAL(key, "geotiff:tileheight") && is_geotiff) {
4✔
4378
      /* BLOCKXSIZE=n */
4379
      int tileheight;
4380

4381
      if (msStringParseInteger(value, &tileheight) != MS_SUCCESS) {
1✔
4382
        msSetError(MS_WCSERR, "Could not parse tileheight value.",
×
4383
                   "msWCSSetFormatParams20()");
4384
        return MS_FAILURE;
×
4385
      } else if (tileheight < 1 || tileheight % 16) {
1✔
4386
        msSetError(MS_WCSERR,
×
4387
                   "Invalid tileheight value '%d'. "
4388
                   "Must be greater than 0 and dividable by 16.",
4389
                   "msWCSSetFormatParams20()", tileheight);
4390
        return MS_FAILURE;
×
4391
      }
4392
      msSetOutputFormatOption(format, "BLOCKXSIZE", value);
1✔
4393
    } else if (EQUAL(key, "geotiff:tilewidth") && is_geotiff) {
4✔
4394
      /* BLOCKYSIZE=n */
4395
      int tilewidth;
4396

4397
      if (msStringParseInteger(value, &tilewidth) != MS_SUCCESS) {
1✔
4398
        msSetError(MS_WCSERR, "Could not parse tilewidth value.",
×
4399
                   "msWCSSetFormatParams20()");
4400
        return MS_FAILURE;
×
4401
      } else if (tilewidth < 1 || tilewidth % 16) {
1✔
4402
        msSetError(MS_WCSERR,
×
4403
                   "Invalid tilewidth value '%d'. "
4404
                   "Must be greater than 0 and dividable by 16.",
4405
                   "msWCSSetFormatParams20()", tilewidth);
4406
        return MS_FAILURE;
×
4407
      }
4408
      msSetOutputFormatOption(format, "BLOCKYSIZE", value);
1✔
4409
    } else if (EQUALN(key, "geotiff:", 8)) {
3✔
4410
      msSetError(MS_WCSERR, "Unrecognized GeoTIFF parameter '%s'.",
×
4411
                 "msWCSSetFormatParams20()", key);
4412
      return MS_FAILURE;
×
4413
    }
4414

4415
    msFree(key);
10✔
4416

4417
    /* fetch next option */
4418
    format_option = format_options[++i];
10✔
4419
  }
4420

4421
  return MS_SUCCESS;
4422
}
4423

4424
/************************************************************************/
4425
/*                   msWCSGetCoverage20()                               */
4426
/*                                                                      */
4427
/*      Implementation of the GetCoverage Operation. The coverage       */
4428
/*      is either returned as an image or as a multipart xml/image.     */
4429
/*      The result is written on the stream.                            */
4430
/************************************************************************/
4431

4432
int msWCSGetCoverage20(mapObj *map, cgiRequestObj *request,
164✔
4433
                       wcs20ParamsObjPtr params, owsRequestObj *ows_request) {
4434
  (void)request;
4435
  layerObj *layer = NULL;
4436
  wcs20coverageMetadataObj cm;
4437
  imageObj *image = NULL;
4438
  outputFormatObj *format = NULL;
4439

4440
  rectObj subsets, bbox;
4441
  projectionObj imageProj;
4442

4443
  int status, i;
4444
  double x_1, x_2, y_1, y_2;
4445
  char *coverageName, *bandlist = NULL, numbands[12];
164✔
4446

4447
  int doDrawRasterLayerDraw = MS_TRUE;
4448
  GDALDatasetH hDS = NULL;
4449

4450
  int widthFromComputationInImageCRS = 0;
4451
  int heightFromComputationInImageCRS = 0;
4452

4453
  /* number of coverage ids should be 1 */
4454
  if (params->ids == NULL || params->ids[0] == NULL) {
164✔
4455
    msSetError(MS_WCSERR, "Required parameter CoverageID was not supplied.",
2✔
4456
               "msWCSGetCoverage20()");
4457
    return msWCSException(map, "MissingParameterValue", "coverage",
2✔
4458
                          params->version);
2✔
4459
  }
4460
  if (params->ids[1] != NULL) {
162✔
4461
    msSetError(MS_WCSERR, "GetCoverage operation supports only one coverage.",
2✔
4462
               "msWCSGetCoverage20()");
4463
    return msWCSException(map, "TooManyParameterValues", "coverage",
2✔
4464
                          params->version);
2✔
4465
  }
4466

4467
  /* find the right layer */
4468
  layer = NULL;
4469
  for (i = 0; i < map->numlayers; i++) {
369✔
4470
    coverageName = msOWSGetEncodeMetadata(&(GET_LAYER(map, i)->metadata), "CO",
209✔
4471
                                          "name", GET_LAYER(map, i)->name);
209✔
4472
    if (EQUAL(coverageName, params->ids[0]) &&
367✔
4473
        (msIntegerInArray(GET_LAYER(map, i)->index, ows_request->enabled_layers,
158✔
4474
                          ows_request->numlayers))) {
4475
      layer = GET_LAYER(map, i);
158✔
4476
      i = map->numlayers; /* to exit loop don't use break, we want to free
158✔
4477
                             resources first */
4478
    }
4479
    msFree(coverageName);
209✔
4480
  }
4481

4482
  /* throw exception if no Layer was found */
4483
  if (layer == NULL) {
160✔
4484
    msSetError(
2✔
4485
        MS_WCSERR,
4486
        "COVERAGEID=%s not found, not in supported layer list. A layer might be disabled for \
4487
this request. Check wcs/ows_enable_request settings.",
4488
        "msWCSGetCoverage20()", params->ids[0]);
2✔
4489
    return msWCSException(map, "NoSuchCoverage", "coverageid", params->version);
2✔
4490
  }
4491
  /* retrieve coverage metadata  */
4492
  status = msWCSGetCoverageMetadata20(layer, &cm);
158✔
4493
  if (status != MS_SUCCESS) {
158✔
4494
    msWCSClearCoverageMetadata20(&cm);
×
4495
    return MS_FAILURE;
×
4496
  }
4497

4498
  /* fill in bands rangeset info, if required.  */
4499
  /* msWCSSetDefaultBandsRangeSetInfo(NULL, &cm, layer ); */
4500

4501
  /* set  resolution, size and maximum extent */
4502
  layer->extent = map->extent = cm.extent;
158✔
4503
  map->cellsize = cm.xresolution;
158✔
4504
  map->width = cm.xsize;
158✔
4505
  map->height = cm.ysize;
158✔
4506

4507
  /************************************************************************/
4508
  /*      finalize the params object. determine subset crs and subset     */
4509
  /*      bbox. Also project the image to the subset crs.                 */
4510
  /************************************************************************/
4511

4512
  msInitProjection(&imageProj);
158✔
4513
  msProjectionInheritContextFrom(&imageProj, &(layer->projection));
158✔
4514
  if (msLoadProjectionString(&imageProj, cm.srs_epsg) == -1) {
158✔
4515
    msFreeProjection(&imageProj);
×
4516
    msWCSClearCoverageMetadata20(&cm);
×
4517
    msSetError(MS_WCSERR, "Error loading CRS %s.", "msWCSGetCoverage20()",
×
4518
               cm.srs_epsg);
4519
    return msWCSException(map, "InvalidParameterValue", "projection",
×
4520
                          params->version);
×
4521
  }
4522

4523
  /* iterate over all subsets and check if they are valid*/
4524
  for (i = 0; i < params->numaxes; ++i) {
336✔
4525
    if (params->axes[i]->subset != NULL) {
179✔
4526
      if (params->axes[i]->subset->timeOrScalar == MS_WCS20_TIME_VALUE) {
115✔
4527
        msFreeProjection(&imageProj);
×
4528
        msWCSClearCoverageMetadata20(&cm);
×
4529
        msSetError(MS_WCSERR, "Time values for subsets are not supported. ",
×
4530
                   "msWCSGetCoverage20()");
4531
        return msWCSException(map, "InvalidSubsetting", "subset",
×
4532
                              params->version);
×
4533
      }
4534
      if (params->axes[i]->subset->operation == MS_WCS20_SLICE) {
115✔
4535
        msFreeProjection(&imageProj);
1✔
4536
        msWCSClearCoverageMetadata20(&cm);
1✔
4537
        msSetError(MS_WCSERR, "Subset operation 'slice' is not supported.",
1✔
4538
                   "msWCSGetCoverage20()");
4539
        return msWCSException(map, "InvalidSubsetting", "subset",
1✔
4540
                              params->version);
1✔
4541
      }
4542
    }
4543
  }
4544

4545
  {
4546
    wcs20AxisObjPtr axes[2];
4547
    if (msWCSValidateAndFindAxes20(params, axes) == MS_FAILURE) {
157✔
4548
      msFreeProjection(&imageProj);
1✔
4549
      msWCSClearCoverageMetadata20(&cm);
1✔
4550
      return msWCSException(map, "InvalidAxisLabel", "subset", params->version);
3✔
4551
    }
4552
    if (msWCSGetCoverage20_FinalizeParamsObj(params, axes) == MS_FAILURE) {
156✔
4553
      msFreeProjection(&imageProj);
2✔
4554
      msWCSClearCoverageMetadata20(&cm);
2✔
4555
      return msWCSException(map, "InvalidParameterValue", "extent",
2✔
4556
                            params->version);
2✔
4557
    }
4558
  }
4559

4560
  subsets = params->bbox;
154✔
4561

4562
  /* if no subsetCRS was specified use the coverages CRS
4563
     (Requirement 27 of the WCS 2.0 specification) */
4564
  if (!params->subsetcrs) {
154✔
4565
    params->subsetcrs = msStrdup(cm.srs_epsg);
88✔
4566
  }
4567

4568
  if (EQUAL(params->subsetcrs, "imageCRS")) {
154✔
4569
    /* subsets are in imageCRS; reproject them to real coordinates */
4570
    rectObj orig_bbox = subsets;
41✔
4571

4572
    msFreeProjection(&(map->projection));
41✔
4573
    map->projection = imageProj;
41✔
4574

4575
    if (subsets.minx != -DBL_MAX || subsets.maxx != DBL_MAX) {
41✔
4576
      x_1 = cm.geotransform[0] + orig_bbox.minx * cm.geotransform[1] +
31✔
4577
            orig_bbox.miny * cm.geotransform[2];
31✔
4578
      // below +1 look suspicious to me (E. Rouault) and probably mean that
4579
      // there are still issues with pixel-center vs pixel-corner convention
4580
      // The resolution computed on wcs_20_post_getcov_trim_x_y_both_1px
4581
      // with that is 11, whereas it should be 10
4582
      x_2 = cm.geotransform[0] + (orig_bbox.maxx + 1) * cm.geotransform[1] +
31✔
4583
            (orig_bbox.maxy + 1) * cm.geotransform[2];
31✔
4584

4585
      subsets.minx = MS_MIN(x_1, x_2);
31✔
4586
      subsets.maxx = MS_MAX(x_1, x_2);
62✔
4587
    }
4588
    if (subsets.miny != -DBL_MAX || subsets.maxy != DBL_MAX) {
41✔
4589
      // below +1 are suspicious. See bove comment in the minx/maxx cases
4590
      y_1 = cm.geotransform[3] + (orig_bbox.maxx + 1) * cm.geotransform[4] +
31✔
4591
            (orig_bbox.maxy + 1) * cm.geotransform[5];
31✔
4592
      /*subsets.miny -= cm.geotransform[4]/2 + cm.geotransform[5]/2;*/
4593
      y_2 = cm.geotransform[3] + orig_bbox.minx * cm.geotransform[4] +
31✔
4594
            orig_bbox.miny * cm.geotransform[5];
31✔
4595

4596
      subsets.miny = MS_MIN(y_1, y_2);
31✔
4597
      subsets.maxy = MS_MAX(y_1, y_2);
62✔
4598
    }
4599
  } else { /* if crs is not the 'imageCRS' */
4600
    projectionObj subsetProj;
4601

4602
    /* if the subsets have a crs given, project the image extent to it */
4603
    msInitProjection(&subsetProj);
113✔
4604
    msProjectionInheritContextFrom(&subsetProj, &(layer->projection));
113✔
4605
    if (msLoadProjectionString(&subsetProj, params->subsetcrs) != MS_SUCCESS) {
113✔
4606
      msFreeProjection(&subsetProj);
×
4607
      msFreeProjection(&imageProj);
×
4608
      msWCSClearCoverageMetadata20(&cm);
×
4609
      msSetError(MS_WCSERR, "Error loading CRS %s.", "msWCSGetCoverage20()",
×
4610
                 params->subsetcrs);
4611
      return msWCSException(map, "InvalidParameterValue", "projection",
×
4612
                            params->version);
×
4613
    }
4614

4615
    if (msProjectionsDiffer(&imageProj, &subsetProj)) {
113✔
4616

4617
      /* Reprojection of source raster extent of (-180,-90,180,90) to any */
4618
      /* projected CRS is going to exhibit strong anomalies. So instead */
4619
      /* do the reverse, project the subset extent to the layer CRS, and */
4620
      /* see how much the subset extent takes with respect to the source */
4621
      /* raster extent. This is only used if output width and resolutionX (or */
4622
      /* (height and resolutionY) are unknown. */
4623
      if (((params->width == 0 && params->resolutionX == MS_WCS20_UNBOUNDED) ||
11✔
4624
           (params->height == 0 &&
6✔
4625
            params->resolutionY == MS_WCS20_UNBOUNDED)) &&
25✔
4626
          (msProjIsGeographicCRS(&imageProj) &&
12✔
4627
           !msProjIsGeographicCRS(&subsetProj) &&
4✔
4628
           fabs(layer->extent.minx - -180.0) < 1e-5 &&
×
4629
           fabs(layer->extent.miny - -90.0) < 1e-5 &&
×
4630
           fabs(layer->extent.maxx - 180.0) < 1e-5 &&
×
4631
           fabs(layer->extent.maxy - 90.0) < 1e-5)) {
×
4632
        rectObj subsetInImageProj = subsets;
×
4633
        if (msProjectRect(&subsetProj, &imageProj, &(subsetInImageProj)) ==
×
4634
            MS_SUCCESS) {
4635
          subsetInImageProj.minx =
4636
              MS_MAX(subsetInImageProj.minx, layer->extent.minx);
×
4637
          subsetInImageProj.miny =
4638
              MS_MAX(subsetInImageProj.miny, layer->extent.miny);
×
4639
          subsetInImageProj.maxx =
4640
              MS_MIN(subsetInImageProj.maxx, layer->extent.maxx);
×
4641
          subsetInImageProj.maxy =
4642
              MS_MIN(subsetInImageProj.maxy, layer->extent.maxy);
×
4643
          {
4644
            double total = ABS(layer->extent.maxx - layer->extent.minx);
×
4645
            double part = ABS(subsetInImageProj.maxx - subsetInImageProj.minx);
×
4646
            widthFromComputationInImageCRS =
×
4647
                MS_NINT((part * map->width) / total);
×
4648
          }
4649
          {
4650
            double total = ABS(layer->extent.maxy - layer->extent.miny);
×
4651
            double part = ABS(subsetInImageProj.maxy - subsetInImageProj.miny);
×
4652
            heightFromComputationInImageCRS =
×
4653
                MS_NINT((part * map->height) / total);
×
4654
          }
4655
        }
4656
      }
4657

4658
      msProjectRect(&imageProj, &subsetProj, &(layer->extent));
14✔
4659
      fprintf(stderr, "%f %f %f %f\n", layer->extent.minx, layer->extent.miny,
14✔
4660
              layer->extent.maxx, layer->extent.maxy);
4661
      if (msProjIsGeographicCRS(&subsetProj)) {
14✔
4662
        if (layer->extent.minx > 180 && subsets.maxx <= 180) {
14✔
4663
          layer->extent.minx -= 360;
1✔
4664
          layer->extent.maxx -= 360;
1✔
4665
        } else if (layer->extent.maxx < -180 && subsets.minx >= -180) {
13✔
4666
          layer->extent.minx += 360;
×
4667
          layer->extent.maxx += 360;
×
4668
        }
4669
      }
4670
      map->extent = layer->extent;
14✔
4671
      msFreeProjection(&(map->projection));
14✔
4672
      map->projection = subsetProj;
14✔
4673
      msFreeProjection(&imageProj);
14✔
4674
    } else {
4675
      msFreeProjection(&(map->projection));
99✔
4676
      map->projection = imageProj;
99✔
4677
      msFreeProjection(&subsetProj);
99✔
4678
    }
4679
  }
4680

4681
  /* create boundings of params subsets and image extent */
4682
  if (msRectOverlap(&subsets, &(layer->extent)) == MS_FALSE) {
154✔
4683
    /* extent and bbox do not overlap -> exit */
4684
    msWCSClearCoverageMetadata20(&cm);
2✔
4685
    msSetError(MS_WCSERR,
2✔
4686
               "Image extent does not intersect with desired region.",
4687
               "msWCSGetCoverage20()");
4688
    return msWCSException(map, "ExtentError", "extent", params->version);
2✔
4689
  }
4690

4691
  /* write combined bounding box */
4692
  bbox.minx = MS_MAX(subsets.minx, map->extent.minx);
152✔
4693
  bbox.miny = MS_MAX(subsets.miny, map->extent.miny);
152✔
4694
  bbox.maxx = MS_MIN(subsets.maxx, map->extent.maxx);
152✔
4695
  bbox.maxy = MS_MIN(subsets.maxy, map->extent.maxy);
152✔
4696

4697
  /* check if we are overspecified  */
4698
  if ((params->width != 0 && params->resolutionX != MS_WCS20_UNBOUNDED) ||
152✔
4699
      (params->height != 0 && params->resolutionY != MS_WCS20_UNBOUNDED)) {
152✔
4700
    msWCSClearCoverageMetadata20(&cm);
×
4701
    msSetError(MS_WCSERR,
×
4702
               "GetCoverage operation supports only one of SIZE or RESOLUTION "
4703
               "per axis.",
4704
               "msWCSGetCoverage20()");
4705
    return msWCSException(map, "TooManyParameterValues", "coverage",
×
4706
                          params->version);
×
4707
  }
4708

4709
  /************************************************************************/
4710
  /* check both axes: see if either size or resolution are given (and     */
4711
  /* calculate the other value). If both are not given, calculate them    */
4712
  /* from the bounding box.                                               */
4713
  /************************************************************************/
4714

4715
  /* check x axis */
4716
  if (params->width != 0) {
152✔
4717
    /* TODO Unit Of Measure? */
4718
    params->resolutionX = (bbox.maxx - bbox.minx) / params->width;
35✔
4719
  } else if (params->resolutionX != MS_WCS20_UNBOUNDED) {
117✔
4720
    params->width = MS_NINT((bbox.maxx - bbox.minx) / params->resolutionX);
18✔
4721
  } else {
4722
    if (widthFromComputationInImageCRS != 0) {
99✔
4723
      params->width = widthFromComputationInImageCRS;
×
4724
    } else if (ABS(bbox.maxx - bbox.minx) !=
198✔
4725
               ABS(map->extent.maxx - map->extent.minx)) {
99✔
4726
      double total = ABS(map->extent.maxx - map->extent.minx),
4727
             part = ABS(bbox.maxx - bbox.minx);
4728
      params->width = MS_NINT((part * map->width) / total);
31✔
4729
    } else {
4730
      params->width = map->width;
68✔
4731
    }
4732

4733
    params->resolutionX = (bbox.maxx - bbox.minx) / params->width;
99✔
4734

4735
    if (params->scaleX != MS_WCS20_UNBOUNDED) {
99✔
4736
      params->resolutionX /= params->scaleX;
4✔
4737
      params->width = (long)(((double)params->width) * params->scaleX);
4✔
4738
    }
4739
  }
4740

4741
  /* check y axis */
4742
  if (params->height != 0) {
152✔
4743
    params->resolutionY = (bbox.maxy - bbox.miny) / params->height;
35✔
4744
  } else if (params->resolutionY != MS_WCS20_UNBOUNDED) {
117✔
4745
    params->height = MS_NINT((bbox.maxy - bbox.miny) / params->resolutionY);
18✔
4746
  } else {
4747
    if (heightFromComputationInImageCRS != 0) {
99✔
4748
      params->height = heightFromComputationInImageCRS;
×
4749
    } else if (ABS(bbox.maxy - bbox.miny) !=
198✔
4750
               ABS(map->extent.maxy - map->extent.miny)) {
99✔
4751
      double total = ABS(map->extent.maxy - map->extent.miny),
4752
             part = ABS(bbox.maxy - bbox.miny);
4753
      params->height = MS_NINT((part * map->height) / total);
29✔
4754
    } else {
4755
      params->height = map->height;
70✔
4756
    }
4757

4758
    params->resolutionY = (bbox.maxy - bbox.miny) / params->height;
99✔
4759

4760
    if (params->scaleY != MS_WCS20_UNBOUNDED) {
99✔
4761
      params->resolutionY /= params->scaleY;
4✔
4762
      params->height = (long)(((double)params->height) * params->scaleY);
4✔
4763
    }
4764
  }
4765

4766
  /* WCS 2.0 is center of pixel oriented */
4767
  rectObj bboxOriginIsCorner = bbox;
152✔
4768
  bbox.minx += params->resolutionX * 0.5;
152✔
4769
  bbox.maxx -= params->resolutionX * 0.5;
152✔
4770
  bbox.miny += params->resolutionY * 0.5;
152✔
4771
  bbox.maxy -= params->resolutionY * 0.5;
152✔
4772

4773
  /* if parameter 'outputcrs' is given, project the image to this crs */
4774
  if (params->outputcrs != NULL) {
152✔
4775
    projectionObj outputProj;
4776

4777
    msInitProjection(&outputProj);
5✔
4778
    msProjectionInheritContextFrom(&outputProj, &(layer->projection));
5✔
4779
    if (msLoadProjectionString(&outputProj, params->outputcrs) == -1) {
5✔
4780
      msFreeProjection(&outputProj);
×
4781
      msWCSClearCoverageMetadata20(&cm);
×
4782
      return msWCSException(map, "InvalidParameterValue", "coverage",
×
4783
                            params->version);
×
4784
    }
4785
    if (msProjectionsDiffer(&(map->projection), &outputProj)) {
5✔
4786

4787
      msDebug("msWCSGetCoverage20(): projecting to outputcrs %s\n",
5✔
4788
              params->outputcrs);
4789

4790
      msProjectRect(&(map->projection), &outputProj, &bbox);
5✔
4791

4792
      /* recalculate resolutions, needed if UOM changes (e.g: deg -> m) */
4793
      if (params->width == 1 || params->height == 1) {
5✔
4794
        // if one of the dimension is 1, we cannot use the center-of-pixel bbox,
4795
        // but must use the origin-is-corner one.
4796
        msProjectRect(&(map->projection), &outputProj, &bboxOriginIsCorner);
×
4797
        params->resolutionX =
×
4798
            (bboxOriginIsCorner.maxx - bboxOriginIsCorner.minx) / params->width;
×
4799
        params->resolutionY =
×
4800
            (bboxOriginIsCorner.maxy - bboxOriginIsCorner.miny) /
×
4801
            params->height;
×
4802
      } else {
4803
        params->resolutionX = (bbox.maxx - bbox.minx) / (params->width - 1);
5✔
4804
        params->resolutionY = (bbox.maxy - bbox.miny) / (params->height - 1);
5✔
4805
      }
4806

4807
      msFreeProjection(&(map->projection));
5✔
4808
      map->projection = outputProj;
5✔
4809
    } else {
4810
      msFreeProjection(&outputProj);
×
4811
    }
4812
  }
4813

4814
  /* set the bounding box as new map extent */
4815
  map->extent = bbox;
152✔
4816
  map->width = params->width;
152✔
4817
  map->height = params->height;
152✔
4818

4819
  /* Are we exceeding the MAXSIZE limit on result size? */
4820
  if (map->width > map->maxsize || map->height > map->maxsize) {
152✔
4821
    msWCSClearCoverageMetadata20(&cm);
1✔
4822
    msSetError(MS_WCSERR,
1✔
4823
               "Raster size out of range, width and height of "
4824
               "resulting coverage must be no more than MAXSIZE=%d.",
4825
               "msWCSGetCoverage20()", map->maxsize);
4826

4827
    return msWCSException(map, "InvalidParameterValue", "size",
1✔
4828
                          params->version);
1✔
4829
  }
4830

4831
  /* Mapserver only supports square cells */
4832
  if (params->resolutionX <= params->resolutionY)
151✔
4833
    map->cellsize = params->resolutionX;
104✔
4834
  else
4835
    map->cellsize = params->resolutionY;
47✔
4836

4837
  msDebug("msWCSGetCoverage20(): Set parameters from original"
151✔
4838
          "data. Width: %d, height: %d, cellsize: %f, extent: %f,%f,%f,%f\n",
4839
          map->width, map->height, map->cellsize, map->extent.minx,
4840
          map->extent.miny, map->extent.maxx, map->extent.maxy);
4841

4842
  /**
4843
   * Which format to use?
4844
   *
4845
   * 1) format parameter
4846
   * 2) native format (from metadata) or GDAL format of the input dataset
4847
   * 3) exception
4848
   **/
4849

4850
  if (!params->format) {
151✔
4851
    if (cm.native_format) {
8✔
4852
      params->format = msStrdup(cm.native_format);
7✔
4853
    }
4854
  }
4855

4856
  if (!params->format) {
151✔
4857
    msSetError(MS_WCSERR,
1✔
4858
               "Output format could not be automatically determined. "
4859
               "Use the FORMAT parameter to specify a format.",
4860
               "msWCSGetCoverage20()");
4861
    msWCSClearCoverageMetadata20(&cm);
1✔
4862
    return msWCSException(map, "MissingParameterValue", "format",
1✔
4863
                          params->version);
1✔
4864
  }
4865

4866
  /*    make sure layer is on   */
4867
  layer->status = MS_ON;
150✔
4868

4869
  if (params->width == 1 || params->height == 1)
150✔
4870
    msMapComputeGeotransformEx(map, params->resolutionX, params->resolutionY);
5✔
4871
  else
4872
    msMapComputeGeotransform(map);
145✔
4873

4874
  /*    fill in bands rangeset info, if required.  */
4875
  /* msWCSSetDefaultBandsRangeSetInfo(params, &cm, layer); */
4876
  /* msDebug("Bandcount: %d\n", cm.bandcount); */
4877

4878
  msApplyDefaultOutputFormats(map);
150✔
4879

4880
  if (msGetOutputFormatIndex(map, params->format) == -1) {
150✔
4881
    msSetError(MS_WCSERR, "Unrecognized value '%s' for the FORMAT parameter.",
2✔
4882
               "msWCSGetCoverage20()", params->format);
4883
    msWCSClearCoverageMetadata20(&cm);
2✔
4884
    return msWCSException(map, "InvalidParameterValue", "format",
2✔
4885
                          params->version);
2✔
4886
  }
4887

4888
  /* create a temporary outputformat (we likely will need to tweak parts) */
4889
  format = msCloneOutputFormat(msSelectOutputFormat(map, params->format));
148✔
4890
  msApplyOutputFormat(&(map->outputformat), format, MS_NOOVERRIDE);
148✔
4891

4892
  /* set format specific parameters */
4893
  if (msWCSSetFormatParams20(format, params->format_options) != MS_SUCCESS) {
148✔
4894
    msFree(bandlist);
4895
    msWCSClearCoverageMetadata20(&cm);
×
4896
    return msWCSException(map, "InvalidParameterValue", "format",
×
4897
                          params->version);
×
4898
  }
4899

4900
  if (msWCSGetCoverage20_GetBands(layer, params, &cm, &bandlist) !=
148✔
4901
      MS_SUCCESS) {
4902
    msFree(bandlist);
×
4903
    msWCSClearCoverageMetadata20(&cm);
×
4904
    return msWCSException(map, "InvalidParameterValue", "rangesubset",
×
4905
                          params->version);
×
4906
  }
4907
  msLayerSetProcessingKey(layer, "BANDS", bandlist);
148✔
4908
  snprintf(numbands, sizeof(numbands), "%d", msCountChars(bandlist, ',') + 1);
148✔
4909
  msSetOutputFormatOption(map->outputformat, "BAND_COUNT", numbands);
148✔
4910

4911
  msWCSApplyLayerCreationOptions(layer, map->outputformat, bandlist);
148✔
4912
  msWCSApplyLayerMetadataItemOptions(layer, map->outputformat, bandlist);
148✔
4913

4914
  /* check for the interpolation */
4915
  /* Defaults to NEAREST */
4916
  if (params->interpolation != NULL) {
148✔
4917
    if (EQUALN(params->interpolation, "NEAREST", 7)) {
14✔
4918
      msLayerSetProcessingKey(layer, "RESAMPLE", "NEAREST");
4✔
4919
    } else if (EQUAL(params->interpolation, "BILINEAR")) {
10✔
4920
      msLayerSetProcessingKey(layer, "RESAMPLE", "BILINEAR");
4✔
4921
    } else if (EQUAL(params->interpolation, "AVERAGE")) {
6✔
4922
      msLayerSetProcessingKey(layer, "RESAMPLE", "AVERAGE");
4✔
4923
    } else {
4924
      msFree(bandlist);
2✔
4925
      msSetError(MS_WCSERR,
2✔
4926
                 "'%s' specifies an unsupported interpolation method.",
4927
                 "msWCSGetCoverage20()", params->interpolation);
4928
      msWCSClearCoverageMetadata20(&cm);
2✔
4929
      return msWCSException(map, "InvalidParameterValue", "interpolation",
2✔
4930
                            params->version);
2✔
4931
    }
4932
  } else {
4933
    msLayerSetProcessingKey(layer, "RESAMPLE", "NEAREST");
134✔
4934
  }
4935

4936
  /* since the dataset is only used in one layer, set it to be    */
4937
  /* closed after drawing the layer. This normally defaults to    */
4938
  /* DEFER and will produce a memory leak, because the dataset    */
4939
  /* will not be closed.                                          */
4940
  if (msLayerGetProcessingKey(layer, "CLOSE_CONNECTION") == NULL) {
146✔
4941
    msLayerSetProcessingKey(layer, "CLOSE_CONNECTION", "NORMAL");
146✔
4942
  }
4943

4944
  if (layer->tileindex == NULL && layer->data != NULL &&
146✔
4945
      strlen(layer->data) > 0 && layer->connectiontype != MS_KERNELDENSITY) {
35✔
4946
    if (msDrawRasterLayerLowCheckIfMustDraw(map, layer)) {
35✔
4947
      char *decrypted_path = NULL;
35✔
4948
      char szPath[MS_MAXPATHLEN];
4949
      hDS = (GDALDatasetH)msDrawRasterLayerLowOpenDataset(
35✔
4950
          map, layer, layer->data, szPath, &decrypted_path);
35✔
4951
      msFree(decrypted_path);
35✔
4952
      if (hDS) {
35✔
4953
        msWCSApplyDatasetMetadataAsCreationOptions(layer, map->outputformat,
35✔
4954
                                                   bandlist, hDS);
4955
        msWCSApplySourceDatasetMetadata(layer, map->outputformat, bandlist,
35✔
4956
                                        hDS);
4957
      }
4958
    } else {
4959
      doDrawRasterLayerDraw = MS_FALSE;
4960
    }
4961
  }
4962

4963
  /* create the image object  */
4964
  if (MS_RENDERER_PLUGIN(map->outputformat)) {
146✔
4965
    image =
4966
        msImageCreate(map->width, map->height, map->outputformat,
2✔
4967
                      map->web.imagepath, map->web.imageurl, map->resolution,
4968
                      map->defresolution, &map->imagecolor);
4969
  } else if (MS_RENDERER_RAWDATA(map->outputformat)) {
144✔
4970
    image =
4971
        msImageCreate(map->width, map->height, map->outputformat,
144✔
4972
                      map->web.imagepath, map->web.imageurl, map->resolution,
4973
                      map->defresolution, &map->imagecolor);
4974
  } else {
4975
    msFree(bandlist);
×
4976
    msWCSClearCoverageMetadata20(&cm);
×
4977
    msSetError(MS_WCSERR, "Map outputformat not supported for WCS!",
×
4978
               "msWCSGetCoverage20()");
4979
    msDrawRasterLayerLowCloseDataset(layer, hDS);
×
4980
    return msWCSException(map, NULL, NULL, params->version);
×
4981
  }
4982

4983
  if (image == NULL) {
146✔
4984
    msFree(bandlist);
×
4985
    msWCSClearCoverageMetadata20(&cm);
×
4986
    msDrawRasterLayerLowCloseDataset(layer, hDS);
×
4987
    return msWCSException(map, NULL, NULL, params->version);
×
4988
  }
4989

4990
  if (layer->mask) {
146✔
4991
    int maskLayerIdx = msGetLayerIndex(map, layer->mask);
32✔
4992
    layerObj *maskLayer;
4993
    outputFormatObj *altFormat;
4994
    if (maskLayerIdx == -1) {
32✔
4995
      msSetError(MS_MISCERR, "Layer (%s) references unknown mask layer (%s)",
×
4996
                 "msDrawLayer()", layer->name, layer->mask);
4997
      msFreeImage(image);
×
4998
      msFree(bandlist);
×
4999
      msWCSClearCoverageMetadata20(&cm);
×
5000
      msDrawRasterLayerLowCloseDataset(layer, hDS);
×
5001
      return msWCSException(map, NULL, NULL, params->version);
×
5002
    }
5003
    maskLayer = GET_LAYER(map, maskLayerIdx);
32✔
5004
    if (!maskLayer->maskimage) {
32✔
5005
      int i, retcode;
5006
      int origstatus, origlabelcache;
5007
      char *origImageType = msStrdup(map->imagetype);
32✔
5008
      altFormat = msSelectOutputFormat(map, "png24");
32✔
5009
      msInitializeRendererVTable(altFormat);
32✔
5010
      /* TODO: check the png24 format hasn't been tampered with, i.e. it's agg
5011
       */
5012
      maskLayer->maskimage = msImageCreate(
32✔
5013
          image->width, image->height, altFormat, image->imagepath,
5014
          image->imageurl, map->resolution, map->defresolution, NULL);
5015
      if (!maskLayer->maskimage) {
32✔
5016
        msSetError(MS_MISCERR, "Unable to initialize mask image.",
×
5017
                   "msDrawLayer()");
5018
        msFreeImage(image);
×
5019
        msFree(bandlist);
×
5020
        msWCSClearCoverageMetadata20(&cm);
×
5021
        msDrawRasterLayerLowCloseDataset(layer, hDS);
×
5022
        return msWCSException(map, NULL, NULL, params->version);
×
5023
      }
5024

5025
      /*
5026
       * force the masked layer to status on, and turn off the labelcache so
5027
       * that eventual labels are added to the temporary image instead of being
5028
       * added to the labelcache
5029
       */
5030
      origstatus = maskLayer->status;
32✔
5031
      origlabelcache = maskLayer->labelcache;
32✔
5032
      maskLayer->status = MS_ON;
32✔
5033
      maskLayer->labelcache = MS_OFF;
32✔
5034

5035
      /* draw the mask layer in the temporary image */
5036
      retcode = msDrawLayer(map, maskLayer, maskLayer->maskimage);
32✔
5037
      maskLayer->status = origstatus;
32✔
5038
      maskLayer->labelcache = origlabelcache;
32✔
5039
      if (retcode != MS_SUCCESS) {
32✔
5040
        free(origImageType);
×
5041
        msFreeImage(image);
×
5042
        msFree(bandlist);
×
5043
        msWCSClearCoverageMetadata20(&cm);
×
5044
        msDrawRasterLayerLowCloseDataset(layer, hDS);
×
5045
        return msWCSException(map, NULL, NULL, params->version);
×
5046
      }
5047
      /*
5048
       * hack to work around bug #3834: if we have use an alternate renderer,
5049
       * the symbolset may contain symbols that reference it. We want to remove
5050
       * those references before the altFormat is destroyed to avoid a segfault
5051
       * and/or a leak, and so the the main renderer doesn't pick the cache up
5052
       * thinking it's for him.
5053
       */
5054
      for (i = 0; i < map->symbolset.numsymbols; i++) {
64✔
5055
        if (map->symbolset.symbol[i] != NULL) {
32✔
5056
          symbolObj *s = map->symbolset.symbol[i];
5057
          if (s->renderer == MS_IMAGE_RENDERER(maskLayer->maskimage)) {
32✔
5058
            MS_IMAGE_RENDERER(maskLayer->maskimage)->freeSymbol(s);
×
5059
            s->renderer = NULL;
×
5060
          }
5061
        }
5062
      }
5063
      /* set the imagetype from the original outputformat back (it was removed
5064
       * by msSelectOutputFormat() */
5065
      msFree(map->imagetype);
32✔
5066
      map->imagetype = origImageType;
32✔
5067
    }
5068
  }
5069

5070
  /* Actually produce the "grid". */
5071
  if (MS_RENDERER_RAWDATA(map->outputformat)) {
146✔
5072
    if (doDrawRasterLayerDraw) {
144✔
5073
      status = msDrawRasterLayerLowWithDataset(map, layer, image, NULL, hDS);
144✔
5074
    } else {
5075
      status = MS_SUCCESS;
5076
    }
5077
  } else {
5078
    rasterBufferObj rb;
5079
    status = MS_IMAGE_RENDERER(image)->getRasterBufferHandle(image, &rb);
2✔
5080
    if (MS_LIKELY(status == MS_SUCCESS)) {
2✔
5081
      if (doDrawRasterLayerDraw) {
2✔
5082
        status = msDrawRasterLayerLowWithDataset(map, layer, image, &rb, hDS);
2✔
5083
      } else {
5084
        status = MS_SUCCESS;
5085
      }
5086
    }
5087
  }
5088

5089
  msDrawRasterLayerLowCloseDataset(layer, hDS);
146✔
5090

5091
  if (status != MS_SUCCESS) {
146✔
5092
    msFree(bandlist);
×
5093
    msFreeImage(image);
×
5094
    msWCSClearCoverageMetadata20(&cm);
×
5095
    return msWCSException(map, NULL, NULL, params->version);
×
5096
  }
5097

5098
  /* GML+Image */
5099
  /* Embed the image into multipart message */
5100
  if (params->multipart == MS_TRUE) {
146✔
5101
    xmlDocPtr psDoc = NULL; /* document pointer */
5102
    xmlNodePtr psRootNode, psRangeSet, psFile, psRangeParameters;
5103
    xmlNsPtr psGmlNs = NULL, psGmlcovNs = NULL, psSweNs = NULL,
5104
             psXLinkNs = NULL;
5105
    wcs20coverageMetadataObj tmpCm;
5106
    char *srs_uri, *default_filename;
5107
    const char *filename;
5108
    int swapAxes;
5109

5110
    /* Create Document  */
5111
    psDoc = xmlNewDoc(BAD_CAST "1.0");
12✔
5112
    psRootNode = xmlNewNode(
12✔
5113
        NULL, BAD_CAST MS_WCS_GML_COVERAGETYPE_RECTIFIED_GRID_COVERAGE);
5114
    xmlDocSetRootElement(psDoc, psRootNode);
12✔
5115

5116
    msWCSPrepareNamespaces20(psDoc, psRootNode, map, MS_FALSE);
12✔
5117

5118
    psGmlNs = xmlSearchNs(psDoc, psRootNode,
12✔
5119
                          BAD_CAST MS_OWSCOMMON_GML_NAMESPACE_PREFIX);
5120
    psGmlcovNs = xmlSearchNs(psDoc, psRootNode,
12✔
5121
                             BAD_CAST MS_OWSCOMMON_GMLCOV_NAMESPACE_PREFIX);
5122
    psSweNs = xmlSearchNs(psDoc, psRootNode,
12✔
5123
                          BAD_CAST MS_OWSCOMMON_SWE_NAMESPACE_PREFIX);
5124
    xmlSearchNs(psDoc, psRootNode, BAD_CAST MS_OWSCOMMON_WCS_NAMESPACE_PREFIX);
12✔
5125
    psXLinkNs = xmlSearchNs(psDoc, psRootNode,
12✔
5126
                            BAD_CAST MS_OWSCOMMON_W3C_XLINK_NAMESPACE_PREFIX);
5127

5128
    xmlNewNsProp(psRootNode, psGmlNs, BAD_CAST "id", BAD_CAST layer->name);
12✔
5129

5130
    xmlSetNs(psRootNode, psGmlcovNs);
12✔
5131

5132
    srs_uri = msOWSGetProjURI(&map->projection, NULL, "CO", 1);
12✔
5133

5134
    tmpCm = cm;
12✔
5135
    tmpCm.extent = map->extent;
12✔
5136
    tmpCm.xsize = map->width;
12✔
5137
    tmpCm.ysize = map->height;
12✔
5138
    strlcpy(tmpCm.srs_uri, srs_uri, sizeof(tmpCm.srs_uri));
5139

5140
    tmpCm.xresolution = map->gt.geotransform[1];
12✔
5141
    tmpCm.yresolution = map->gt.geotransform[5];
12✔
5142

5143
    tmpCm.extent.minx =
12✔
5144
        MS_MIN(map->gt.geotransform[0],
12✔
5145
               map->gt.geotransform[0] + map->width * tmpCm.xresolution);
5146
    tmpCm.extent.miny =
12✔
5147
        MS_MIN(map->gt.geotransform[3],
12✔
5148
               map->gt.geotransform[3] + map->height * tmpCm.yresolution);
5149
    tmpCm.extent.maxx =
12✔
5150
        MS_MAX(map->gt.geotransform[0],
12✔
5151
               map->gt.geotransform[0] + map->width * tmpCm.xresolution);
5152
    tmpCm.extent.maxy =
12✔
5153
        MS_MAX(map->gt.geotransform[3],
12✔
5154
               map->gt.geotransform[3] + map->height * tmpCm.yresolution);
5155

5156
    swapAxes = msWCSSwapAxes20(srs_uri);
12✔
5157
    msFree(srs_uri);
12✔
5158

5159
    /* Setup layer information  */
5160
    msWCSCommon20_CreateBoundedBy(&tmpCm, psGmlNs, psRootNode,
12✔
5161
                                  &(map->projection), swapAxes);
5162
    msWCSCommon20_CreateDomainSet(layer, &tmpCm, psGmlNs, psRootNode,
12✔
5163
                                  &(map->projection), swapAxes);
5164

5165
    psRangeSet = xmlNewChild(psRootNode, psGmlNs, BAD_CAST "rangeSet", NULL);
12✔
5166
    psFile = xmlNewChild(psRangeSet, psGmlNs, BAD_CAST "File", NULL);
12✔
5167

5168
    /* TODO: wait for updated specifications */
5169
    psRangeParameters =
5170
        xmlNewChild(psFile, psGmlNs, BAD_CAST "rangeParameters", NULL);
12✔
5171

5172
    default_filename = msStrdup("out.");
12✔
5173
    default_filename = msStringConcatenate(default_filename,
12✔
5174
                                           MS_IMAGE_EXTENSION(image->format));
12✔
5175

5176
    filename =
5177
        msGetOutputFormatOption(image->format, "FILENAME", default_filename);
12✔
5178
    std::string file_ref("cid:coverage/");
12✔
5179
    file_ref += filename;
5180

5181
    msFree(default_filename);
12✔
5182

5183
    std::string role;
5184
    if (EQUAL(MS_IMAGE_MIME_TYPE(map->outputformat), "image/tiff")) {
24✔
5185
      role = MS_WCS_20_PROFILE_GML_GEOTIFF;
5186
    } else {
5187
      role = MS_IMAGE_MIME_TYPE(map->outputformat);
5188
    }
5189

5190
    xmlNewNsProp(psRangeParameters, psXLinkNs, BAD_CAST "href",
12✔
5191
                 BAD_CAST file_ref.c_str());
5192
    xmlNewNsProp(psRangeParameters, psXLinkNs, BAD_CAST "role",
12✔
5193
                 BAD_CAST role.c_str());
5194
    xmlNewNsProp(psRangeParameters, psXLinkNs, BAD_CAST "arcrole",
12✔
5195
                 BAD_CAST "fileReference");
5196

5197
    xmlNewChild(psFile, psGmlNs, BAD_CAST "fileReference",
12✔
5198
                BAD_CAST file_ref.c_str());
5199
    xmlNewChild(psFile, psGmlNs, BAD_CAST "fileStructure", NULL);
12✔
5200
    xmlNewChild(psFile, psGmlNs, BAD_CAST "mimeType",
12✔
5201
                BAD_CAST MS_IMAGE_MIME_TYPE(map->outputformat));
12✔
5202

5203
    msWCSCommon20_CreateRangeType(&cm, bandlist, psGmlcovNs, psSweNs,
12✔
5204
                                  psRootNode);
5205

5206
    msIO_setHeader("Content-Type", "multipart/related; boundary=wcs");
12✔
5207
    msIO_sendHeaders();
12✔
5208
    msIO_printf("\r\n--wcs\r\n");
12✔
5209

5210
    msWCSWriteDocument20(psDoc);
12✔
5211
    msWCSWriteFile20(map, image, params, 1);
12✔
5212

5213
    xmlFreeDoc(psDoc);
12✔
5214
    xmlCleanupParser();
12✔
5215
    /* just print out the file without gml */
5216
  } else {
5217
    msWCSWriteFile20(map, image, params, 0);
134✔
5218
  }
5219

5220
  msFree(bandlist);
146✔
5221
  msWCSClearCoverageMetadata20(&cm);
146✔
5222
  msFreeImage(image);
146✔
5223
  return MS_SUCCESS;
5224
}
5225

5226
#endif /* defined(USE_LIBXML2) */
5227

5228
#endif /* defined(USE_WCS_SVR) */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc