Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: WMS Client Driver
4 : * Purpose: GDALWMSRasterBand implementation.
5 : * Author: Adam Nowacki, nowak@xpam.de
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2007, Adam Nowacki
9 : * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10 : * Copyright (c) 2017, Dmitry Baryshnikov, <polimax@mail.ru>
11 : * Copyright (c) 2017, NextGIS, <info@nextgis.com>
12 : *
13 : * SPDX-License-Identifier: MIT
14 : ****************************************************************************/
15 :
16 : #include "wmsdriver.h"
17 :
18 : #include <algorithm>
19 :
20 6959 : GDALWMSRasterBand::GDALWMSRasterBand(GDALWMSDataset *parent_dataset, int band,
21 6959 : double scale)
22 : : m_parent_dataset(parent_dataset), m_scale(scale), m_overview(-1),
23 : m_color_interp(GCI_Undefined), m_nAdviseReadBX0(-1), m_nAdviseReadBY0(-1),
24 6959 : m_nAdviseReadBX1(-1), m_nAdviseReadBY1(-1)
25 : {
26 : #ifdef DEBUG_VERBOSE
27 : printf("[%p] GDALWMSRasterBand::GDALWMSRasterBand(%p, %d, %f)\n", /*ok*/
28 : this, parent_dataset, band, scale);
29 : #endif
30 :
31 6959 : if (scale == 1.0)
32 1336 : poDS = parent_dataset;
33 : else
34 5623 : poDS = nullptr;
35 6959 : if (parent_dataset->m_mini_driver_caps.m_overview_dim_computation_method ==
36 : OVERVIEW_ROUNDED)
37 : {
38 6887 : nRasterXSize = static_cast<int>(
39 6887 : m_parent_dataset->m_data_window.m_sx * scale + 0.5);
40 6887 : nRasterYSize = static_cast<int>(
41 6887 : m_parent_dataset->m_data_window.m_sy * scale + 0.5);
42 : }
43 : else
44 : {
45 72 : nRasterXSize =
46 72 : static_cast<int>(m_parent_dataset->m_data_window.m_sx * scale);
47 72 : nRasterYSize =
48 72 : static_cast<int>(m_parent_dataset->m_data_window.m_sy * scale);
49 : }
50 6959 : nBand = band;
51 6959 : eDataType = m_parent_dataset->m_data_type;
52 6959 : nBlockXSize = m_parent_dataset->m_block_size_x;
53 6959 : nBlockYSize = m_parent_dataset->m_block_size_y;
54 6959 : }
55 :
56 20877 : GDALWMSRasterBand::~GDALWMSRasterBand()
57 : {
58 12582 : while (!m_overviews.empty())
59 : {
60 5623 : delete m_overviews.back();
61 5623 : m_overviews.pop_back();
62 : }
63 13918 : }
64 :
65 : // Request for x, y but all blocks between bx0-bx1 and by0-by1 should be read
66 39 : CPLErr GDALWMSRasterBand::ReadBlocks(int x, int y, void *buffer, int bx0,
67 : int by0, int bx1, int by1, int advise_read)
68 : {
69 39 : CPLErr ret = CE_None;
70 :
71 : // Get a vector of requests large enough for this call
72 39 : std::vector<WMSHTTPRequest> requests(static_cast<size_t>(bx1 - bx0 + 1) *
73 39 : (by1 - by0 + 1));
74 :
75 39 : size_t count = 0; // How many requests are valid
76 39 : GDALWMSCache *cache = m_parent_dataset->m_cache;
77 39 : int offline = m_parent_dataset->m_offline_mode;
78 39 : const char *const *options = m_parent_dataset->GetHTTPRequestOpts();
79 :
80 83 : for (int iy = by0; iy <= by1; ++iy)
81 : {
82 103 : for (int ix = bx0; ix <= bx1; ++ix)
83 : {
84 59 : WMSHTTPRequest &request = requests[count];
85 59 : request.x = ix;
86 59 : request.y = iy;
87 59 : bool need_this_block = false;
88 59 : if (!advise_read)
89 : {
90 248 : for (int ib = 1; ib <= m_parent_dataset->nBands; ++ib)
91 : {
92 189 : if ((ix == x) && (iy == y) && (ib == nBand))
93 : {
94 39 : need_this_block = true;
95 : }
96 : else
97 : {
98 : GDALWMSRasterBand *band =
99 : static_cast<GDALWMSRasterBand *>(
100 150 : m_parent_dataset->GetRasterBand(ib));
101 150 : if (m_overview >= 0)
102 : band = static_cast<GDALWMSRasterBand *>(
103 75 : band->GetOverview(m_overview));
104 150 : if (!band->IsBlockInCache(ix, iy))
105 150 : need_this_block = true;
106 : }
107 : }
108 : }
109 : else
110 : {
111 0 : need_this_block = true;
112 : }
113 :
114 59 : void *p = ((ix == x) && (iy == y)) ? buffer : nullptr;
115 59 : if (need_this_block)
116 : {
117 59 : ret = AskMiniDriverForBlock(request, ix, iy);
118 59 : if (ret != CE_None)
119 : {
120 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
121 : request.Error.c_str());
122 0 : ret = CE_Failure;
123 : }
124 : // A missing tile is signaled by setting a range of "none"
125 59 : if (EQUAL(request.Range, "none"))
126 : {
127 0 : if (!advise_read)
128 : {
129 0 : if (EmptyBlock(ix, iy, nBand, p) != CE_None)
130 : {
131 0 : CPLError(CE_Failure, CPLE_AppDefined,
132 : "GDALWMS: EmptyBlock failed.");
133 0 : ret = CE_Failure;
134 : }
135 : }
136 0 : need_this_block = false;
137 : }
138 59 : if (ret == CE_None && cache != nullptr)
139 : {
140 36 : if (cache->GetItemStatus(request.URL) == CACHE_ITEM_OK)
141 : {
142 12 : if (advise_read)
143 : {
144 0 : need_this_block = false;
145 : }
146 : else
147 : {
148 12 : if (ReadBlockFromCache(request.URL, ix, iy, nBand,
149 12 : p, 0) == CE_None)
150 : {
151 12 : need_this_block = false;
152 : }
153 : }
154 : }
155 : }
156 : }
157 :
158 59 : if (need_this_block)
159 : {
160 47 : if (offline)
161 : {
162 0 : if (!advise_read)
163 : {
164 0 : if (EmptyBlock(ix, iy, nBand, p) != CE_None)
165 : {
166 0 : CPLError(CE_Failure, CPLE_AppDefined,
167 : "GDALWMS: EmptyBlock failed.");
168 0 : ret = CE_Failure;
169 : }
170 : }
171 : }
172 : else
173 : {
174 47 : request.options = options;
175 47 : WMSHTTPInitializeRequest(&request);
176 47 : count++;
177 : }
178 : }
179 : }
180 : }
181 :
182 : // Fetch all the requests, OK to call with count of 0
183 39 : if (WMSHTTPFetchMulti(count ? &requests[0] : nullptr,
184 39 : static_cast<int>(count)) != CE_None)
185 : {
186 0 : CPLError(CE_Failure, CPLE_AppDefined,
187 : "GDALWMS: CPLHTTPFetchMulti failed.");
188 0 : ret = CE_Failure;
189 : }
190 :
191 86 : for (size_t i = 0; i < count; ++i)
192 : {
193 47 : WMSHTTPRequest &request = requests[i];
194 47 : void *p = ((request.x == x) && (request.y == y)) ? buffer : nullptr;
195 47 : if (ret == CE_None)
196 : {
197 53 : int success = (request.nStatus == 200) ||
198 6 : (!request.Range.empty() && request.nStatus == 206);
199 47 : if (success && (request.pabyData != nullptr) &&
200 41 : (request.nDataLen > 0))
201 : {
202 : CPLString file_name(
203 82 : BufferToVSIFile(request.pabyData, request.nDataLen));
204 41 : if (!file_name.empty())
205 : {
206 : /* check for error xml */
207 41 : if (request.nDataLen >= 20)
208 : {
209 41 : const char *download_data =
210 : reinterpret_cast<char *>(request.pabyData);
211 41 : if (STARTS_WITH_CI(download_data, "<?xml ") ||
212 41 : STARTS_WITH_CI(download_data, "<!DOCTYPE ") ||
213 41 : STARTS_WITH_CI(download_data, "<ServiceException"))
214 : {
215 0 : if (ReportWMSException(file_name) != CE_None)
216 : {
217 0 : CPLError(CE_Failure, CPLE_AppDefined,
218 : "GDALWMS: The server returned unknown "
219 : "exception.");
220 : }
221 0 : ret = CE_Failure;
222 : }
223 : }
224 41 : if (ret == CE_None)
225 : {
226 41 : if (advise_read &&
227 0 : !m_parent_dataset->m_verify_advise_read)
228 : {
229 0 : if (cache != nullptr)
230 0 : cache->Insert(request.URL, file_name);
231 : }
232 : else
233 : {
234 41 : ret = ReadBlockFromFile(file_name, request.x,
235 : request.y, nBand, p,
236 : advise_read);
237 41 : if (ret == CE_None)
238 : {
239 41 : if (cache != nullptr)
240 22 : cache->Insert(request.URL, file_name);
241 : }
242 : else
243 : {
244 0 : CPLError(
245 : ret, CPLE_AppDefined,
246 : "GDALWMS: ReadBlockFromFile (%s) failed.",
247 : request.URL.c_str());
248 : }
249 : }
250 : }
251 0 : else if (m_parent_dataset->m_zeroblock_on_serverexceptions)
252 : {
253 0 : ret = EmptyBlock(request.x, request.y, nBand, p);
254 0 : if (ret != CE_None)
255 0 : CPLError(ret, CPLE_AppDefined,
256 : "GDALWMS: EmptyBlock failed.");
257 : }
258 41 : VSIUnlink(file_name);
259 41 : }
260 : }
261 : else
262 : { // HTTP error
263 : // One more try to get cached block. For example if no web
264 : // access available
265 6 : CPLDebug("WMS", "ReadBlockFromCache");
266 :
267 6 : if (m_parent_dataset->m_cache != nullptr)
268 2 : ret = ReadBlockFromCache(request.URL, request.x, request.y,
269 : nBand, p, advise_read);
270 : else
271 4 : ret = CE_Failure;
272 :
273 6 : if (ret != CE_None)
274 : {
275 6 : CPLDebug("WMS", "After ReadBlockFromCache");
276 12 : if (m_parent_dataset->m_http_zeroblock_codes.find(
277 6 : request.nStatus) !=
278 12 : m_parent_dataset->m_http_zeroblock_codes.end())
279 : {
280 2 : if (!advise_read)
281 : {
282 2 : ret = EmptyBlock(request.x, request.y, nBand, p);
283 2 : if (ret != CE_None)
284 0 : CPLError(ret, CPLE_AppDefined,
285 : "GDALWMS: EmptyBlock failed.");
286 : }
287 : }
288 : else
289 : {
290 4 : ret = CE_Failure;
291 7 : CPLError(ret, CPLE_AppDefined,
292 : "GDALWMS: Unable to download block %d, %d.\n"
293 : "URL: %s\n HTTP status code: %d, error: %s.\n"
294 : "Add the HTTP status code to "
295 : "<ZeroBlockHttpCodes> to ignore this error "
296 : "(see https://gdal.org/frmt_wms.html).",
297 : request.x, request.y,
298 4 : !request.URL.empty() ? request.Error.c_str()
299 : : "(null)",
300 : request.nStatus,
301 4 : !request.Error.empty() ? request.Error.c_str()
302 : : "(null)");
303 : }
304 : }
305 : }
306 : }
307 : }
308 :
309 78 : return ret;
310 : }
311 :
312 39 : CPLErr GDALWMSRasterBand::IReadBlock(int x, int y, void *buffer)
313 : {
314 39 : int bx0 = x;
315 39 : int by0 = y;
316 39 : int bx1 = x;
317 39 : int by1 = y;
318 :
319 39 : bool bCancelHint = false;
320 39 : if ((m_parent_dataset->m_hint.m_valid) &&
321 39 : (m_parent_dataset->m_hint.m_overview == m_overview))
322 : {
323 39 : int tbx0 = m_parent_dataset->m_hint.m_x0 / nBlockXSize;
324 39 : int tby0 = m_parent_dataset->m_hint.m_y0 / nBlockYSize;
325 39 : int tbx1 = (m_parent_dataset->m_hint.m_x0 +
326 39 : m_parent_dataset->m_hint.m_sx - 1) /
327 39 : nBlockXSize;
328 39 : int tby1 = (m_parent_dataset->m_hint.m_y0 +
329 39 : m_parent_dataset->m_hint.m_sy - 1) /
330 39 : nBlockYSize;
331 39 : if ((tbx0 <= x) && (tby0 <= y) && (tbx1 >= x) && (tby1 >= y))
332 : {
333 : // Avoid downloading a insane number of tiles at once.
334 : // Limit to 30x30 tiles centered around block of interest.
335 39 : bx0 = std::max(x - 15, tbx0);
336 39 : by0 = std::max(y - 15, tby0);
337 39 : bx1 = std::min(x + 15, tbx1);
338 39 : by1 = std::min(y + 15, tby1);
339 39 : bCancelHint =
340 39 : (bx0 == tbx0 && by0 == tby0 && bx1 == tbx1 && by1 == tby1);
341 : }
342 : }
343 :
344 39 : CPLErr eErr = ReadBlocks(x, y, buffer, bx0, by0, bx1, by1, 0);
345 :
346 39 : if (bCancelHint)
347 : {
348 39 : m_parent_dataset->m_hint.m_valid = false;
349 : }
350 :
351 39 : return eErr;
352 : }
353 :
354 100 : CPLErr GDALWMSRasterBand::IRasterIO(GDALRWFlag rw, int x0, int y0, int sx,
355 : int sy, void *buffer, int bsx, int bsy,
356 : GDALDataType bdt, GSpacing nPixelSpace,
357 : GSpacing nLineSpace,
358 : GDALRasterIOExtraArg *psExtraArg)
359 : {
360 : CPLErr ret;
361 :
362 100 : if (rw != GF_Read)
363 0 : return CE_Failure;
364 100 : if (buffer == nullptr)
365 0 : return CE_Failure;
366 100 : if ((sx == 0) || (sy == 0) || (bsx == 0) || (bsy == 0))
367 0 : return CE_None;
368 :
369 100 : m_parent_dataset->m_hint.m_x0 = x0;
370 100 : m_parent_dataset->m_hint.m_y0 = y0;
371 100 : m_parent_dataset->m_hint.m_sx = sx;
372 100 : m_parent_dataset->m_hint.m_sy = sy;
373 100 : m_parent_dataset->m_hint.m_overview = m_overview;
374 100 : m_parent_dataset->m_hint.m_valid = true;
375 100 : ret = GDALRasterBand::IRasterIO(rw, x0, y0, sx, sy, buffer, bsx, bsy, bdt,
376 : nPixelSpace, nLineSpace, psExtraArg);
377 100 : m_parent_dataset->m_hint.m_valid = false;
378 :
379 100 : return ret;
380 : }
381 :
382 0 : int GDALWMSRasterBand::HasArbitraryOverviews()
383 : {
384 : // return m_parent_dataset->m_mini_driver_caps.m_has_arb_overviews;
385 0 : return 0; // not implemented yet
386 : }
387 :
388 188 : int GDALWMSRasterBand::GetOverviewCount()
389 : {
390 188 : return static_cast<int>(m_overviews.size());
391 : }
392 :
393 293 : GDALRasterBand *GDALWMSRasterBand::GetOverview(int n)
394 : {
395 293 : if ((!m_overviews.empty()) && (static_cast<size_t>(n) < m_overviews.size()))
396 293 : return m_overviews[n];
397 : else
398 0 : return nullptr;
399 : }
400 :
401 5623 : bool GDALWMSRasterBand::AddOverview(double scale)
402 : {
403 : GDALWMSRasterBand *overview =
404 5623 : new GDALWMSRasterBand(m_parent_dataset, nBand, scale);
405 5623 : if (overview->GetXSize() == 0 || overview->GetYSize() == 0)
406 : {
407 0 : delete overview;
408 0 : return false;
409 : }
410 5623 : std::vector<GDALWMSRasterBand *>::iterator it = m_overviews.begin();
411 36761 : for (; it != m_overviews.end(); ++it)
412 : {
413 31138 : GDALWMSRasterBand *p = *it;
414 31138 : if (p->m_scale < scale)
415 0 : break;
416 : }
417 5623 : m_overviews.insert(it, overview);
418 5623 : it = m_overviews.begin();
419 42384 : for (int i = 0; it != m_overviews.end(); ++it, ++i)
420 : {
421 36761 : GDALWMSRasterBand *p = *it;
422 36761 : p->m_overview = i;
423 : }
424 5623 : return true;
425 : }
426 :
427 289 : bool GDALWMSRasterBand::IsBlockInCache(int x, int y)
428 : {
429 289 : bool ret = false;
430 289 : GDALRasterBlock *b = TryGetLockedBlockRef(x, y);
431 289 : if (b != nullptr)
432 : {
433 0 : ret = true;
434 0 : b->DropLock();
435 : }
436 289 : return ret;
437 : }
438 :
439 : // This is the function that calculates the block coordinates for the fetch
440 59 : CPLErr GDALWMSRasterBand::AskMiniDriverForBlock(WMSHTTPRequest &r, int x, int y)
441 : {
442 59 : GDALWMSImageRequestInfo iri;
443 59 : GDALWMSTiledImageRequestInfo tiri;
444 :
445 59 : ComputeRequestInfo(iri, tiri, x, y);
446 118 : return m_parent_dataset->m_mini_driver->TiledImageRequest(r, iri, tiri);
447 : }
448 :
449 59 : void GDALWMSRasterBand::ComputeRequestInfo(GDALWMSImageRequestInfo &iri,
450 : GDALWMSTiledImageRequestInfo &tiri,
451 : int x, int y)
452 : {
453 59 : int x0 = std::max(0, x * nBlockXSize);
454 59 : int y0 = std::max(0, y * nBlockYSize);
455 59 : int x1 = std::max(0, (x + 1) * nBlockXSize);
456 59 : int y1 = std::max(0, (y + 1) * nBlockYSize);
457 59 : if (m_parent_dataset->m_clamp_requests)
458 : {
459 59 : x0 = std::min(x0, nRasterXSize);
460 59 : y0 = std::min(y0, nRasterYSize);
461 59 : x1 = std::min(x1, nRasterXSize);
462 59 : y1 = std::min(y1, nRasterYSize);
463 : }
464 :
465 59 : const double rx = (m_parent_dataset->m_data_window.m_x1 -
466 59 : m_parent_dataset->m_data_window.m_x0) /
467 59 : static_cast<double>(nRasterXSize);
468 59 : const double ry = (m_parent_dataset->m_data_window.m_y1 -
469 59 : m_parent_dataset->m_data_window.m_y0) /
470 59 : static_cast<double>(nRasterYSize);
471 : /* Use different method for x0,y0 and x1,y1 to make sure calculated values
472 : * are exact for corner requests */
473 59 : iri.m_x0 = x0 * rx + m_parent_dataset->m_data_window.m_x0;
474 59 : iri.m_y0 = y0 * ry + m_parent_dataset->m_data_window.m_y0;
475 59 : iri.m_x1 = m_parent_dataset->m_data_window.m_x1 - (nRasterXSize - x1) * rx;
476 59 : iri.m_y1 = m_parent_dataset->m_data_window.m_y1 - (nRasterYSize - y1) * ry;
477 59 : iri.m_sx = x1 - x0;
478 59 : iri.m_sy = y1 - y0;
479 :
480 59 : int level = m_overview + 1;
481 59 : tiri.m_x = (m_parent_dataset->m_data_window.m_tx >> level) + x;
482 59 : tiri.m_y = (m_parent_dataset->m_data_window.m_ty >> level) + y;
483 59 : tiri.m_level = m_parent_dataset->m_data_window.m_tlevel - level;
484 59 : }
485 :
486 : /************************************************************************/
487 : /* GetMetadataDomainList() */
488 : /************************************************************************/
489 :
490 0 : char **GDALWMSRasterBand::GetMetadataDomainList()
491 : {
492 0 : char **m_list = GDALPamRasterBand::GetMetadataDomainList();
493 0 : char **mini_list = m_parent_dataset->m_mini_driver->GetMetadataDomainList();
494 0 : if (mini_list != nullptr)
495 : {
496 0 : m_list = CSLMerge(m_list, mini_list);
497 0 : CSLDestroy(mini_list);
498 : }
499 0 : return m_list;
500 : }
501 :
502 1025 : const char *GDALWMSRasterBand::GetMetadataItem(const char *pszName,
503 : const char *pszDomain)
504 : {
505 1025 : if (!m_parent_dataset->m_mini_driver_caps.m_has_getinfo ||
506 20 : !(pszDomain != nullptr && EQUAL(pszDomain, "LocationInfo") &&
507 0 : (STARTS_WITH_CI(pszName, "Pixel_") ||
508 0 : STARTS_WITH_CI(pszName, "GeoPixel_"))))
509 1025 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
510 :
511 : /* ==================================================================== */
512 : /* LocationInfo handling. */
513 : /* ==================================================================== */
514 :
515 : /* -------------------------------------------------------------------- */
516 : /* What pixel are we aiming at? */
517 : /* -------------------------------------------------------------------- */
518 : int iPixel, iLine;
519 0 : if (STARTS_WITH_CI(pszName, "Pixel_"))
520 : {
521 0 : if (sscanf(pszName + 6, "%d_%d", &iPixel, &iLine) != 2)
522 0 : return nullptr;
523 : }
524 0 : else if (STARTS_WITH_CI(pszName, "GeoPixel_"))
525 : {
526 0 : GDALGeoTransform gt, invGT;
527 : double dfGeoX, dfGeoY;
528 :
529 : {
530 0 : dfGeoX = CPLAtof(pszName + 9);
531 0 : const char *pszUnderscore = strchr(pszName + 9, '_');
532 0 : if (!pszUnderscore)
533 0 : return nullptr;
534 0 : dfGeoY = CPLAtof(pszUnderscore + 1);
535 : }
536 :
537 0 : if (m_parent_dataset->GetGeoTransform(gt) != CE_None)
538 0 : return nullptr;
539 :
540 0 : if (!GDALInvGeoTransform(gt.data(), invGT.data()))
541 0 : return nullptr;
542 :
543 0 : iPixel = static_cast<int>(
544 0 : floor(invGT[0] + invGT[1] * dfGeoX + invGT[2] * dfGeoY));
545 0 : iLine = static_cast<int>(
546 0 : floor(invGT[3] + invGT[4] * dfGeoX + invGT[5] * dfGeoY));
547 :
548 : /* The GetDataset() for the WMS driver is always the main overview
549 : * level, so rescale */
550 : /* the values if we are an overview */
551 0 : if (m_overview >= 0)
552 : {
553 0 : iPixel = static_cast<int>(
554 0 : 1.0 * iPixel * GetXSize() /
555 0 : m_parent_dataset->GetRasterBand(1)->GetXSize());
556 0 : iLine = static_cast<int>(
557 0 : 1.0 * iLine * GetYSize() /
558 0 : m_parent_dataset->GetRasterBand(1)->GetYSize());
559 : }
560 : }
561 : else
562 0 : return nullptr;
563 :
564 0 : if (iPixel < 0 || iLine < 0 || iPixel >= GetXSize() || iLine >= GetYSize())
565 0 : return nullptr;
566 :
567 0 : if (nBand != 1)
568 : {
569 0 : GDALRasterBand *poFirstBand = m_parent_dataset->GetRasterBand(1);
570 0 : if (m_overview >= 0)
571 0 : poFirstBand = poFirstBand->GetOverview(m_overview);
572 0 : if (poFirstBand)
573 0 : return poFirstBand->GetMetadataItem(pszName, pszDomain);
574 : }
575 :
576 0 : GDALWMSImageRequestInfo iri;
577 0 : GDALWMSTiledImageRequestInfo tiri;
578 0 : int nBlockXOff = iPixel / nBlockXSize;
579 0 : int nBlockYOff = iLine / nBlockYSize;
580 :
581 0 : ComputeRequestInfo(iri, tiri, nBlockXOff, nBlockYOff);
582 :
583 0 : CPLString url;
584 0 : m_parent_dataset->m_mini_driver->GetTiledImageInfo(
585 0 : url, iri, tiri, iPixel % nBlockXSize, iLine % nBlockXSize);
586 :
587 0 : if (url.empty())
588 0 : return nullptr;
589 :
590 0 : CPLDebug("WMS", "URL = %s", url.c_str());
591 :
592 0 : if (url == osMetadataItemURL)
593 : {
594 : // osMetadataItem.c_str() MUST be used, and not osMetadataItem,
595 : // otherwise a temporary copy is returned
596 0 : return !osMetadataItem.empty() ? osMetadataItem.c_str() : nullptr;
597 : }
598 :
599 0 : osMetadataItemURL = url;
600 :
601 : // This is OK, CPLHTTPFetch does not touch the options
602 : char **papszOptions =
603 0 : const_cast<char **>(m_parent_dataset->GetHTTPRequestOpts());
604 0 : CPLHTTPResult *psResult = CPLHTTPFetch(url, papszOptions);
605 :
606 0 : CPLString pszRes;
607 :
608 0 : if (psResult && psResult->pabyData)
609 0 : pszRes = reinterpret_cast<const char *>(psResult->pabyData);
610 0 : CPLHTTPDestroyResult(psResult);
611 :
612 0 : if (pszRes.empty())
613 : {
614 0 : osMetadataItem = "";
615 0 : return nullptr;
616 : }
617 :
618 0 : osMetadataItem = "<LocationInfo>";
619 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
620 0 : CPLXMLNode *psXML = CPLParseXMLString(pszRes);
621 0 : CPLPopErrorHandler();
622 0 : if (psXML != nullptr && psXML->eType == CXT_Element)
623 : {
624 0 : if (strcmp(psXML->pszValue, "?xml") == 0)
625 : {
626 0 : if (psXML->psNext)
627 : {
628 0 : char *pszXML = CPLSerializeXMLTree(psXML->psNext);
629 0 : osMetadataItem += pszXML;
630 0 : CPLFree(pszXML);
631 : }
632 : }
633 : else
634 : {
635 0 : osMetadataItem += pszRes;
636 0 : }
637 : }
638 : else
639 : {
640 0 : char *pszEscapedXML = CPLEscapeString(pszRes, -1, CPLES_XML_BUT_QUOTES);
641 0 : osMetadataItem += pszEscapedXML;
642 0 : CPLFree(pszEscapedXML);
643 : }
644 0 : if (psXML != nullptr)
645 0 : CPLDestroyXMLNode(psXML);
646 :
647 0 : osMetadataItem += "</LocationInfo>";
648 :
649 : // osMetadataItem.c_str() MUST be used, and not osMetadataItem,
650 : // otherwise a temporary copy is returned
651 0 : return osMetadataItem.c_str();
652 : }
653 :
654 53 : static const int *GetBandMapForExpand(int nSourceBands, int nWmsBands)
655 : {
656 : static const int bandmap1to1[] = {1};
657 : static const int bandmap2to1[] = {1};
658 : static const int bandmap3to1[] = {1};
659 : static const int bandmap4to1[] = {1};
660 :
661 : static const int bandmap1to2[] = {1, 0}; // 0 == full opaque alpha band
662 : static const int bandmap2to2[] = {1, 2};
663 : static const int bandmap3to2[] = {1, 0};
664 : static const int bandmap4to2[] = {1, 4};
665 :
666 : static const int bandmap1to3[] = {1, 1, 1};
667 : static const int bandmap2to3[] = {1, 1, 1};
668 : static const int bandmap3to3[] = {1, 2, 3};
669 : static const int bandmap4to3[] = {1, 2, 3};
670 :
671 : static const int bandmap1to4[] = {1, 1, 1, 0};
672 : static const int bandmap2to4[] = {1, 1, 1, 2};
673 : static const int bandmap3to4[] = {1, 2, 3, 0};
674 : static const int bandmap4to4[] = {1, 2, 3, 4};
675 :
676 : static const int *const bandmap_selector[4][4] = {
677 : {bandmap1to1, bandmap2to1, bandmap3to1, bandmap4to1},
678 : {bandmap1to2, bandmap2to2, bandmap3to2, bandmap4to2},
679 : {bandmap1to3, bandmap2to3, bandmap3to3, bandmap4to3},
680 : {bandmap1to4, bandmap2to4, bandmap3to4, bandmap4to4},
681 : };
682 :
683 53 : if (nSourceBands > 4 || nSourceBands < 1)
684 : {
685 0 : return nullptr;
686 : }
687 53 : if (nWmsBands > 4 || nWmsBands < 1)
688 : {
689 0 : return nullptr;
690 : }
691 53 : return bandmap_selector[nWmsBands - 1][nSourceBands - 1];
692 : }
693 :
694 53 : CPLErr GDALWMSRasterBand::ReadBlockFromDataset(GDALDataset *ds, int x, int y,
695 : int to_buffer_band, void *buffer,
696 : int advise_read)
697 : {
698 53 : CPLErr ret = CE_None;
699 53 : GByte *color_table = nullptr;
700 : int i;
701 :
702 : // CPLDebug("WMS", "ReadBlockFromDataset: to_buffer_band=%d, (x,y)=(%d,
703 : // %d)", to_buffer_band, x, y);
704 :
705 : /* expected size */
706 53 : const int esx = MIN(MAX(0, (x + 1) * nBlockXSize), nRasterXSize) -
707 53 : MIN(MAX(0, x * nBlockXSize), nRasterXSize);
708 53 : const int esy = MIN(MAX(0, (y + 1) * nBlockYSize), nRasterYSize) -
709 53 : MIN(MAX(0, y * nBlockYSize), nRasterYSize);
710 :
711 53 : int sx = ds->GetRasterXSize();
712 53 : int sy = ds->GetRasterYSize();
713 : /* Allow bigger than expected so pre-tiled constant size images work on
714 : * corners */
715 53 : if ((sx > nBlockXSize) || (sy > nBlockYSize) || (sx < esx) || (sy < esy))
716 : {
717 0 : CPLError(CE_Failure, CPLE_AppDefined,
718 : "GDALWMS: Incorrect size %d x %d of downloaded block, "
719 : "expected %d x %d, max %d x %d.",
720 : sx, sy, esx, esy, nBlockXSize, nBlockYSize);
721 0 : ret = CE_Failure;
722 : }
723 :
724 53 : int nDSRasterCount = ds->GetRasterCount();
725 53 : if (ret == CE_None)
726 : {
727 53 : if (nDSRasterCount != m_parent_dataset->nBands)
728 : {
729 : /* Maybe its an image with color table */
730 34 : if ((eDataType == GDT_Byte) && (ds->GetRasterCount() == 1))
731 : {
732 24 : GDALRasterBand *rb = ds->GetRasterBand(1);
733 24 : if (rb->GetRasterDataType() == GDT_Byte)
734 : {
735 24 : GDALColorTable *ct = rb->GetColorTable();
736 24 : if (ct != nullptr)
737 : {
738 19 : if (!advise_read)
739 : {
740 19 : color_table = new GByte[256 * 4];
741 : const int count =
742 19 : MIN(256, ct->GetColorEntryCount());
743 796 : for (i = 0; i < count; ++i)
744 : {
745 : GDALColorEntry ce;
746 777 : ct->GetColorEntryAsRGB(i, &ce);
747 777 : color_table[i] = static_cast<GByte>(ce.c1);
748 777 : color_table[i + 256] =
749 777 : static_cast<GByte>(ce.c2);
750 777 : color_table[i + 512] =
751 777 : static_cast<GByte>(ce.c3);
752 777 : color_table[i + 768] =
753 777 : static_cast<GByte>(ce.c4);
754 : }
755 :
756 4106 : for (i = count; i < 256; ++i)
757 : {
758 4087 : color_table[i] = 0;
759 4087 : color_table[i + 256] = 0;
760 4087 : color_table[i + 512] = 0;
761 4087 : color_table[i + 768] = 0;
762 : }
763 : }
764 : }
765 5 : else if (m_parent_dataset->nBands <= 4)
766 : { // Promote single band to fake color table
767 5 : color_table = new GByte[256 * 4];
768 1285 : for (i = 0; i < 256; i++)
769 : {
770 1280 : color_table[i] = static_cast<GByte>(i);
771 1280 : color_table[i + 256] = static_cast<GByte>(i);
772 1280 : color_table[i + 512] = static_cast<GByte>(i);
773 1280 : color_table[i + 768] = 255; // Transparency
774 : }
775 5 : if (m_parent_dataset->nBands == 2)
776 : { // Luma-Alpha fixup
777 0 : for (i = 0; i < 256; i++)
778 : {
779 0 : color_table[i + 256] = 255;
780 : }
781 : }
782 : }
783 : }
784 : }
785 : }
786 : }
787 :
788 53 : if (!advise_read)
789 : {
790 : const int *const bandmap =
791 53 : GetBandMapForExpand(nDSRasterCount, m_parent_dataset->nBands);
792 219 : for (int ib = 1; ib <= m_parent_dataset->nBands; ++ib)
793 : {
794 166 : if (ret == CE_None)
795 : {
796 166 : void *p = nullptr;
797 166 : GDALRasterBlock *b = nullptr;
798 166 : if ((buffer != nullptr) && (ib == to_buffer_band))
799 : {
800 33 : p = buffer;
801 : }
802 : else
803 : {
804 : GDALWMSRasterBand *band = static_cast<GDALWMSRasterBand *>(
805 133 : m_parent_dataset->GetRasterBand(ib));
806 133 : if (m_overview >= 0)
807 : {
808 : band = static_cast<GDALWMSRasterBand *>(
809 73 : band->GetOverview(m_overview));
810 : }
811 133 : if (!band->IsBlockInCache(x, y))
812 : {
813 133 : b = band->GetLockedBlockRef(x, y, true);
814 133 : if (b != nullptr)
815 : {
816 133 : p = b->GetDataRef();
817 133 : if (p == nullptr)
818 : {
819 0 : CPLError(CE_Failure, CPLE_AppDefined,
820 : "GDALWMS: GetDataRef returned NULL.");
821 0 : ret = CE_Failure;
822 : }
823 : }
824 : }
825 : else
826 : {
827 : // CPLDebug("WMS", "Band %d, block (x,y)=(%d, %d)
828 : // already in cache", band->GetBand(), x, y);
829 : }
830 : }
831 :
832 166 : if (p != nullptr)
833 : {
834 166 : int pixel_space = GDALGetDataTypeSizeBytes(eDataType);
835 166 : int line_space = pixel_space * nBlockXSize;
836 166 : if (color_table == nullptr)
837 : {
838 88 : if (bandmap == nullptr || bandmap[ib - 1] != 0)
839 : {
840 87 : GDALDataType dt = eDataType;
841 87 : int nSourceBand = ib;
842 87 : if (bandmap != nullptr)
843 : {
844 87 : nSourceBand = bandmap[ib - 1];
845 : }
846 : // Get the data from the PNG as stored instead of
847 : // converting, if the server asks for that
848 : // TODO: This hack is from #3493 - not sure it
849 : // really belongs here.
850 87 : if ((GDT_Int16 == dt) &&
851 : (GDT_UInt16 ==
852 0 : ds->GetRasterBand(ib)->GetRasterDataType()))
853 : {
854 0 : dt = GDT_UInt16;
855 : }
856 :
857 87 : if (ds->RasterIO(GF_Read, 0, 0, sx, sy, p, sx, sy,
858 : dt, 1, &nSourceBand, pixel_space,
859 87 : line_space, 0, nullptr) != CE_None)
860 : {
861 0 : CPLError(CE_Failure, CPLE_AppDefined,
862 : "GDALWMS: RasterIO failed on "
863 : "downloaded block.");
864 0 : ret = CE_Failure;
865 87 : }
866 : }
867 : else // if( bandmap != nullptr && bandmap[ib - 1] == 0
868 : // )
869 : { // parent expects 4 bands but file has fewer count so
870 : // generate a all "opaque" 4th band
871 1 : GByte *byte_buffer = reinterpret_cast<GByte *>(p);
872 129 : for (int l_y = 0; l_y < sy; ++l_y)
873 : {
874 16512 : for (int l_x = 0; l_x < sx; ++l_x)
875 : {
876 16384 : const int offset = l_x + l_y * line_space;
877 16384 : byte_buffer[offset] =
878 : 255; // fill with opaque
879 : }
880 : }
881 : }
882 : }
883 78 : else if (ib <= 4)
884 : {
885 78 : if (ds->RasterIO(GF_Read, 0, 0, sx, sy, p, sx, sy,
886 : eDataType, 1, nullptr, pixel_space,
887 78 : line_space, 0, nullptr) != CE_None)
888 : {
889 0 : CPLError(CE_Failure, CPLE_AppDefined,
890 : "GDALWMS: RasterIO failed on downloaded "
891 : "block.");
892 0 : ret = CE_Failure;
893 : }
894 :
895 78 : if (ret == CE_None)
896 : {
897 78 : GByte *band_color_table =
898 78 : color_table + 256 * (ib - 1);
899 78 : GByte *byte_buffer = reinterpret_cast<GByte *>(p);
900 20558 : for (int l_y = 0; l_y < sy; ++l_y)
901 : {
902 5918720 : for (int l_x = 0; l_x < sx; ++l_x)
903 : {
904 5898240 : const int offset = l_x + l_y * line_space;
905 5898240 : byte_buffer[offset] =
906 5898240 : band_color_table[byte_buffer[offset]];
907 : }
908 : }
909 : }
910 : }
911 : else
912 : {
913 0 : CPLError(CE_Failure, CPLE_AppDefined,
914 : "GDALWMS: Color table supports at most 4 "
915 : "components.");
916 0 : ret = CE_Failure;
917 : }
918 : }
919 166 : if (b != nullptr)
920 : {
921 133 : b->DropLock();
922 : }
923 : }
924 : }
925 : }
926 53 : GDALClose(ds);
927 :
928 53 : if (color_table != nullptr)
929 : {
930 24 : delete[] color_table;
931 : }
932 :
933 53 : return ret;
934 : }
935 :
936 41 : CPLErr GDALWMSRasterBand::ReadBlockFromFile(const CPLString &soFileName, int x,
937 : int y, int to_buffer_band,
938 : void *buffer, int advise_read)
939 : {
940 41 : GDALDataset *ds = GDALDataset::FromHandle(GDALOpenEx(
941 : soFileName, GDAL_OF_RASTER | GDAL_OF_READONLY | GDAL_OF_VERBOSE_ERROR,
942 41 : nullptr, m_parent_dataset->m_tileOO, nullptr));
943 41 : if (ds == nullptr)
944 : {
945 0 : CPLError(CE_Failure, CPLE_AppDefined,
946 : "GDALWMS: Unable to open downloaded block.");
947 0 : return CE_Failure;
948 : }
949 :
950 41 : return ReadBlockFromDataset(ds, x, y, to_buffer_band, buffer, advise_read);
951 : }
952 :
953 14 : CPLErr GDALWMSRasterBand::ReadBlockFromCache(const char *pszKey, int x, int y,
954 : int to_buffer_band, void *buffer,
955 : int advise_read)
956 : {
957 14 : GDALWMSCache *cache = m_parent_dataset->m_cache;
958 14 : if (nullptr == cache)
959 : {
960 0 : CPLError(CE_Failure, CPLE_AppDefined,
961 : "GDALWMS: Unable to open downloaded block.");
962 0 : return CE_Failure;
963 : }
964 14 : GDALDataset *ds = cache->GetDataset(pszKey, m_parent_dataset->m_tileOO);
965 14 : if (ds == nullptr)
966 : {
967 2 : CPLError(CE_Failure, CPLE_AppDefined,
968 : "GDALWMS: Unable to open downloaded block.");
969 2 : return CE_Failure;
970 : }
971 :
972 12 : return ReadBlockFromDataset(ds, x, y, to_buffer_band, buffer, advise_read);
973 : }
974 :
975 2 : CPLErr GDALWMSRasterBand::EmptyBlock(int x, int y, int to_buffer_band,
976 : void *buffer)
977 : {
978 2 : CPLErr ret = CE_None;
979 :
980 10 : for (int ib = 1; ib <= m_parent_dataset->nBands; ++ib)
981 : {
982 8 : if (ret == CE_None)
983 : {
984 8 : void *p = nullptr;
985 8 : GDALRasterBlock *b = nullptr;
986 : GDALWMSRasterBand *band = static_cast<GDALWMSRasterBand *>(
987 8 : m_parent_dataset->GetRasterBand(ib));
988 8 : if (m_overview >= 0)
989 : band = static_cast<GDALWMSRasterBand *>(
990 0 : band->GetOverview(m_overview));
991 8 : if ((buffer != nullptr) && (ib == to_buffer_band))
992 : {
993 2 : p = buffer;
994 : }
995 : else
996 : {
997 6 : if (!band->IsBlockInCache(x, y))
998 : {
999 6 : b = band->GetLockedBlockRef(x, y, true);
1000 6 : if (b != nullptr)
1001 : {
1002 6 : p = b->GetDataRef();
1003 6 : if (p == nullptr)
1004 : {
1005 0 : CPLError(CE_Failure, CPLE_AppDefined,
1006 : "GDALWMS: GetDataRef returned NULL.");
1007 0 : ret = CE_Failure;
1008 : }
1009 : }
1010 : }
1011 : }
1012 8 : if (p != nullptr)
1013 : {
1014 : int hasNDV;
1015 8 : double valNDV = band->GetNoDataValue(&hasNDV);
1016 8 : if (!hasNDV)
1017 8 : valNDV = 0;
1018 8 : GDALCopyWords(&valNDV, GDT_Float64, 0, p, eDataType,
1019 : GDALGetDataTypeSizeBytes(eDataType),
1020 8 : nBlockXSize * nBlockYSize);
1021 : }
1022 8 : if (b != nullptr)
1023 : {
1024 6 : b->DropLock();
1025 : }
1026 : }
1027 : }
1028 :
1029 2 : return ret;
1030 : }
1031 :
1032 0 : CPLErr GDALWMSRasterBand::ReportWMSException(const char *file_name)
1033 : {
1034 0 : CPLErr ret = CE_None;
1035 0 : int reported_errors_count = 0;
1036 :
1037 0 : CPLXMLNode *orig_root = CPLParseXMLFile(file_name);
1038 0 : CPLXMLNode *root = orig_root;
1039 0 : if (root != nullptr)
1040 : {
1041 0 : root = CPLGetXMLNode(root, "=ServiceExceptionReport");
1042 : }
1043 0 : if (root != nullptr)
1044 : {
1045 0 : CPLXMLNode *n = CPLGetXMLNode(root, "ServiceException");
1046 0 : while (n != nullptr)
1047 : {
1048 0 : const char *exception = CPLGetXMLValue(n, "=ServiceException", "");
1049 : const char *exception_code =
1050 0 : CPLGetXMLValue(n, "=ServiceException.code", "");
1051 0 : if (exception[0] != '\0')
1052 : {
1053 0 : if (exception_code[0] != '\0')
1054 : {
1055 0 : CPLError(
1056 : CE_Failure, CPLE_AppDefined,
1057 : "GDALWMS: The server returned exception code '%s': %s",
1058 : exception_code, exception);
1059 0 : ++reported_errors_count;
1060 : }
1061 : else
1062 : {
1063 0 : CPLError(CE_Failure, CPLE_AppDefined,
1064 : "GDALWMS: The server returned exception: %s",
1065 : exception);
1066 0 : ++reported_errors_count;
1067 : }
1068 : }
1069 0 : else if (exception_code[0] != '\0')
1070 : {
1071 0 : CPLError(CE_Failure, CPLE_AppDefined,
1072 : "GDALWMS: The server returned exception code '%s'.",
1073 : exception_code);
1074 0 : ++reported_errors_count;
1075 : }
1076 :
1077 0 : n = n->psNext;
1078 0 : if (n != nullptr)
1079 : {
1080 0 : n = CPLGetXMLNode(n, "=ServiceException");
1081 : }
1082 : }
1083 : }
1084 : else
1085 : {
1086 0 : ret = CE_Failure;
1087 : }
1088 0 : if (orig_root != nullptr)
1089 : {
1090 0 : CPLDestroyXMLNode(orig_root);
1091 : }
1092 :
1093 0 : if (reported_errors_count == 0)
1094 : {
1095 0 : ret = CE_Failure;
1096 : }
1097 :
1098 0 : return ret;
1099 : }
1100 :
1101 0 : CPLErr GDALWMSRasterBand::AdviseRead(int nXOff, int nYOff, int nXSize,
1102 : int nYSize, int nBufXSize, int nBufYSize,
1103 : GDALDataType eDT, char **papszOptions)
1104 : {
1105 : // printf("AdviseRead(%d, %d, %d, %d)\n", nXOff, nYOff, nXSize, nYSize);
1106 0 : if (m_parent_dataset->m_offline_mode ||
1107 0 : !m_parent_dataset->m_use_advise_read)
1108 0 : return CE_None;
1109 0 : if (m_parent_dataset->m_cache == nullptr)
1110 0 : return CE_Failure;
1111 :
1112 : /* ==================================================================== */
1113 : /* Do we have overviews that would be appropriate to satisfy */
1114 : /* this request? */
1115 : /* ==================================================================== */
1116 0 : if ((nBufXSize < nXSize || nBufYSize < nYSize) && GetOverviewCount() > 0)
1117 : {
1118 0 : const int nOverview = GDALBandGetBestOverviewLevel2(
1119 : this, nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize, nullptr);
1120 0 : if (nOverview >= 0)
1121 : {
1122 0 : GDALRasterBand *poOverviewBand = GetOverview(nOverview);
1123 0 : if (poOverviewBand == nullptr)
1124 0 : return CE_Failure;
1125 :
1126 0 : return poOverviewBand->AdviseRead(nXOff, nYOff, nXSize, nYSize,
1127 : nBufXSize, nBufYSize, eDT,
1128 0 : papszOptions);
1129 : }
1130 : }
1131 :
1132 0 : int bx0 = nXOff / nBlockXSize;
1133 0 : int by0 = nYOff / nBlockYSize;
1134 0 : int bx1 = (nXOff + nXSize - 1) / nBlockXSize;
1135 0 : int by1 = (nYOff + nYSize - 1) / nBlockYSize;
1136 :
1137 : // Avoid downloading a insane number of tiles
1138 0 : const int MAX_TILES = 1000; // arbitrary number
1139 0 : if ((bx1 - bx0 + 1) > MAX_TILES / (by1 - by0 + 1))
1140 : {
1141 0 : CPLDebug("WMS", "Too many tiles for AdviseRead()");
1142 0 : return CE_Failure;
1143 : }
1144 :
1145 0 : if (m_nAdviseReadBX0 == bx0 && m_nAdviseReadBY0 == by0 &&
1146 0 : m_nAdviseReadBX1 == bx1 && m_nAdviseReadBY1 == by1)
1147 : {
1148 0 : return CE_None;
1149 : }
1150 0 : m_nAdviseReadBX0 = bx0;
1151 0 : m_nAdviseReadBY0 = by0;
1152 0 : m_nAdviseReadBX1 = bx1;
1153 0 : m_nAdviseReadBY1 = by1;
1154 :
1155 0 : return ReadBlocks(0, 0, nullptr, bx0, by0, bx1, by1, 1);
1156 : }
1157 :
1158 415 : GDALColorInterp GDALWMSRasterBand::GetColorInterpretation()
1159 : {
1160 415 : return m_color_interp;
1161 : }
1162 :
1163 9 : CPLErr GDALWMSRasterBand::SetColorInterpretation(GDALColorInterp eNewInterp)
1164 : {
1165 9 : m_color_interp = eNewInterp;
1166 9 : return CE_None;
1167 : }
1168 :
1169 : // Utility function, returns a value from a vector corresponding to the band
1170 : // index or the first entry
1171 0 : static double getBandValue(const std::vector<double> &v, size_t idx)
1172 : {
1173 0 : idx--;
1174 0 : if (v.size() > idx)
1175 0 : return v[idx];
1176 0 : return v[0];
1177 : }
1178 :
1179 493 : double GDALWMSRasterBand::GetNoDataValue(int *pbSuccess)
1180 : {
1181 493 : std::vector<double> &v = m_parent_dataset->vNoData;
1182 493 : if (v.empty())
1183 493 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1184 0 : if (pbSuccess)
1185 0 : *pbSuccess = TRUE;
1186 0 : return getBandValue(v, nBand);
1187 : }
1188 :
1189 0 : double GDALWMSRasterBand::GetMinimum(int *pbSuccess)
1190 : {
1191 0 : std::vector<double> &v = m_parent_dataset->vMin;
1192 0 : if (v.empty())
1193 0 : return GDALPamRasterBand::GetMinimum(pbSuccess);
1194 0 : if (pbSuccess)
1195 0 : *pbSuccess = TRUE;
1196 0 : return getBandValue(v, nBand);
1197 : }
1198 :
1199 0 : double GDALWMSRasterBand::GetMaximum(int *pbSuccess)
1200 : {
1201 0 : std::vector<double> &v = m_parent_dataset->vMax;
1202 0 : if (v.empty())
1203 0 : return GDALPamRasterBand::GetMaximum(pbSuccess);
1204 0 : if (pbSuccess)
1205 0 : *pbSuccess = TRUE;
1206 0 : return getBandValue(v, nBand);
1207 : }
1208 :
1209 316 : GDALColorTable *GDALWMSRasterBand::GetColorTable()
1210 : {
1211 316 : return m_parent_dataset->m_poColorTable;
1212 : }
|