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