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

geographika / mapserver / 22953070987

11 Mar 2026 12:41PM UTC coverage: 42.422% (+0.4%) from 42.009%
22953070987

push

github

geographika
Switch result to ASCII

64590 of 152256 relevant lines covered (42.42%)

27315.35 hits per line

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

76.99
/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 <cmath>
43
#include <float.h>
44
#include "gdal.h"
45
#include "cpl_port.h"
46
#include "maptime.h"
47
#include "mapprimitive.h"
48
#include "cpl_string.h"
49
#include <string.h>
50

51
#if defined(USE_LIBXML2)
52

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

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

59
#include <string>
60

61
/************************************************************************/
62
/*                          msXMLStripIndentation                       */
63
/************************************************************************/
64

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

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

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

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

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

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

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

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

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

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

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

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

186
  return subset;
122✔
187
}
188

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

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

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

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

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

222
  return axis;
184✔
223
}
224

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

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

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

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

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

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

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

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

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

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

314
  return params;
217✔
315
}
316

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

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

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

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

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

364
  if (subset == NULL) {
122✔
365
    return MS_FAILURE;
366
  }
367

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

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

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

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

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

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

429
  subset->timeOrScalar = ts1;
122✔
430

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

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

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

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

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

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

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

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

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

505
  return msWCSParseSubset20(subset, axis, crs, min, max);
82✔
506
}
507

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

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

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

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

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

540
  strlcpy(outAxis, string, axisStringLen);
541

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

549
  return MS_SUCCESS;
550
}
551

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

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

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

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

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

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

587
  strlcpy(outAxis, string, axisStringLen);
588

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

596
  return MS_SUCCESS;
597
}
598

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

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

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

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

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

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

632
  strlcpy(outAxis, string, axisStringLen);
633

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

641
  return MS_SUCCESS;
642
}
643

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

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

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

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

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

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

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

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

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

692
  strlcpy(outAxis, string, axisStringLen);
693

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

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

713
  return MS_SUCCESS;
714
}
715

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

979
      axis->subset = subset;
40✔
980

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1402
          xmlNodePtr parameter;
1403

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

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

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

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

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

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

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

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

1465
    root = xmlDocGetRootElement(doc);
68✔
1466

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1782
  return MS_SUCCESS;
1783
}
1784

1785
#if defined(USE_LIBXML2)
1786

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

1807
  for (i = 0; i < numAxis; ++i) {
477✔
1808
    outAxes[i] = NULL;
318✔
1809
  }
1810

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

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

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

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

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

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

1869
  xmlSetNs(psRootNode,
59✔
1870
           xmlNewNs(psRootNode, BAD_CAST MS_OWSCOMMON_WCS_20_NAMESPACE_URI,
1871
                    BAD_CAST MS_OWSCOMMON_WCS_NAMESPACE_PREFIX));
1872

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

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

1897
  psXsiNs = xmlSearchNs(pDoc, psRootNode,
59✔
1898
                        BAD_CAST MS_OWSCOMMON_W3C_XSI_NAMESPACE_PREFIX);
1899

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

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

1917
  xmlNewNsProp(psRootNode, psXsiNs, BAD_CAST "schemaLocation",
59✔
1918
               BAD_CAST xsi_schemaLocation);
1919

1920
  msFree(schemaLocation);
59✔
1921
  msFree(xsi_schemaLocation);
59✔
1922
}
59✔
1923

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

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

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

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

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

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

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

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

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

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

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

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

2015
    formats[numformats++] = msStrdup(mimetype);
245✔
2016
  }
2017

2018
  msFreeCharArray(tokens, numtokens);
36✔
2019

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

2032
  return format_list;
36✔
2033
}
2034

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2168
      xmlNewChild(psGrid, psGmlNs, BAD_CAST "axisLabels", BAD_CAST axisLabels);
18✔
2169

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

2182
        xmlNewChild(psOrigin, psGmlNs, BAD_CAST "pos", BAD_CAST point);
18✔
2183
      }
2184

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2324
  if (msIO_needBinaryStdout() == MS_FAILURE) {
57✔
2325
    return MS_FAILURE;
2326
  }
2327

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

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

2337
  context = msIO_getHandler(stdout);
57✔
2338

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

2343
  return MS_SUCCESS;
2344
}
2345

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2540
      VSIFCloseL(fp);
149✔
2541

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

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

2554
  return MS_SUCCESS;
2555
}
2556

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2819
      const char *significant_figures_key = "significant_figures";
2820

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

2828
      char **interval_array;
2829
      int num_interval;
2830

2831
      wcs20rasterbandMetadataObj default_values;
2832

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3022
    msGDALInitialize();
4✔
3023

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

3041
    msGetGDALGeoTransform(hDS, layer->map, layer, cm->geotransform);
4✔
3042

3043
    cm->xsize = GDALGetRasterXSize(hDS);
4✔
3044
    cm->ysize = GDALGetRasterYSize(hDS);
4✔
3045

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

3053
    cm->xresolution = cm->geotransform[1];
4✔
3054
    cm->yresolution = cm->geotransform[5];
4✔
3055

3056
    cm->numbands = GDALGetRasterCount(hDS);
4✔
3057

3058
    /* TODO nilvalues? */
3059

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

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

3082
    cm->bands = static_cast<wcs20rasterbandMetadataObj *>(
4✔
3083
        msSmallCalloc(sizeof(wcs20rasterbandMetadataObj), cm->numbands));
4✔
3084

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

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

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

3136
    GDALClose(hDS);
4✔
3137
    msReleaseLock(TLOCK_GDAL);
4✔
3138
  }
3139

3140
  return MS_SUCCESS;
3141
}
3142

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3247
  xmlDocSetRootElement(psDoc, psRootNode);
33✔
3248

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

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

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

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

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

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

3281
#define MAX_MIMES 20
3282

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

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

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

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

3328
  while (urls_and_mime_types[i] != NULL) {
432✔
3329
    const char *mime_type = urls_and_mime_types[i + 1];
396✔
3330

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

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

3343
    psTmpNode = psProfile;
3344
    i += 2;
396✔
3345
  }
3346
  return MS_SUCCESS;
36✔
3347
}
3348

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

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

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

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

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

3376
  msWCS_11_20_PrintMetadataLinks(layer, doc, psCSummary);
34✔
3377

3378
  msWCSClearCoverageMetadata20(&cm);
34✔
3379

3380
  return MS_SUCCESS;
34✔
3381
}
3382

3383
/************************************************************************/
3384
/*                          msWCSAddInspireDSID20                       */
3385
/************************************************************************/
3386

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

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

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

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

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

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

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

3467
  psRootNode = xmlNewNode(NULL, BAD_CAST "Capabilities");
42✔
3468

3469
  xmlDocSetRootElement(psDoc, psRootNode);
42✔
3470

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

3475
  msWCSPrepareNamespaces20(psDoc, psRootNode, map,
42✔
3476
                           inspire_capabilities != NULL);
3477

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

3491
  xmlSetNs(psRootNode, psWcsNs);
42✔
3492

3493
  xmlNewProp(psRootNode, BAD_CAST "version", BAD_CAST params->version);
42✔
3494

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

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

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

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

3552
    psOperationsNode =
3553
        xmlAddChild(psRootNode, msOWSCommonOperationsMetadata(psOwsNs));
36✔
3554

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3661
  if (MS_WCS_20_CAPABILITIES_INCLUDE_SECTION(params, "ServiceMetadata")) {
40✔
3662
    xmlNodePtr psExtensionNode = NULL;
3663
    psNode = xmlNewChild(psRootNode, psWcsNs, BAD_CAST "ServiceMetadata", NULL);
36✔
3664

3665
    /* Apply default formats */
3666
    msApplyDefaultOutputFormats(map);
36✔
3667

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

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

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

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

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

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

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

3721
        if (!msWCSIsLayerSupported(layer))
34✔
3722
          continue;
×
3723

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

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

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

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

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

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

3783
  if (!msWCSIsLayerSupported(layer))
6✔
3784
    return MS_SUCCESS;
3785

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

3793
  swapAxes = msWCSSwapAxes20(cm.srs_uri);
6✔
3794

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

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

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

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

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

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

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

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

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

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

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

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

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

3860
      msFree(owned_value);
3861
    }*/
3862

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

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

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

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

3880
      msFree(format_list);
3881
    }*/
3882

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

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

3895
  msWCSClearCoverageMetadata20(&cm);
6✔
3896

3897
  return MS_SUCCESS;
6✔
3898
}
3899

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

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

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

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

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

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

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

3961
  /* cleanup */
3962
  xmlFreeDoc(psDoc);
5✔
3963
  xmlCleanupParser();
5✔
3964

3965
  return MS_SUCCESS;
5✔
3966
}
3967

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

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

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

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

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

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

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

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

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

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

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

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

4086
  return MS_SUCCESS;
4087
}
4088

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4416
    msFree(key);
10✔
4417

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

4422
  return MS_SUCCESS;
4423
}
4424

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

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

4441
  rectObj subsets, bbox;
4442
  projectionObj imageProj;
4443

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

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

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

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

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

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

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

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

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

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

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

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

4561
  subsets = params->bbox;
156✔
4562

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

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

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

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

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

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

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

4616
    if (msProjectionsDiffer(&imageProj, &subsetProj)) {
115✔
4617

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

4661
      msProjectRect(&imageProj, &subsetProj, &(layer->extent));
16✔
4662
      if (msProjIsGeographicCRS(&subsetProj)) {
16✔
4663
        if (layer->extent.minx > 180 && subsets.maxx <= 180) {
14✔
4664
          layer->extent.minx -= 360;
1✔
4665
          layer->extent.maxx -= 360;
1✔
4666
        } else if (layer->extent.maxx < -180 && subsets.minx >= -180) {
13✔
4667
          layer->extent.minx += 360;
×
4668
          layer->extent.maxx += 360;
×
4669
        }
4670
      }
4671
      map->extent = layer->extent;
16✔
4672
      msFreeProjection(&(map->projection));
16✔
4673
      map->projection = subsetProj;
16✔
4674
      msFreeProjection(&imageProj);
16✔
4675
    } else {
4676
      msFreeProjection(&(map->projection));
99✔
4677
      map->projection = imageProj;
99✔
4678
      msFreeProjection(&subsetProj);
99✔
4679
    }
4680
  }
4681

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4791
      msProjectRect(&(map->projection), &outputProj, &bbox);
6✔
4792

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

4808
      msFreeProjection(&(map->projection));
6✔
4809
      map->projection = outputProj;
6✔
4810
    } else {
4811
      msFreeProjection(&outputProj);
1✔
4812
    }
4813
  }
4814

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

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

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

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

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

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

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

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

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

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

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

4879
  msApplyDefaultOutputFormats(map);
152✔
4880

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

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

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

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

4912
  msWCSApplyLayerCreationOptions(layer, map->outputformat, bandlist);
150✔
4913
  msWCSApplyLayerMetadataItemOptions(layer, map->outputformat, bandlist);
150✔
4914

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

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

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

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

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

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

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

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

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

5090
  msDrawRasterLayerLowCloseDataset(layer, hDS);
148✔
5091

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

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

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

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

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

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

5131
    xmlSetNs(psRootNode, psGmlcovNs);
12✔
5132

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

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

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

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

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

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

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

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

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

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

5182
    msFree(default_filename);
12✔
5183

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

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

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

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

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

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

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

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

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

5229
#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