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