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 : char **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 esx = MIN(MAX(0, (x + 1) * nBlockXSize), nRasterXSize) -
711 53 : MIN(MAX(0, x * nBlockXSize), nRasterXSize);
712 53 : const int esy = MIN(MAX(0, (y + 1) * nBlockYSize), nRasterYSize) -
713 53 : MIN(MAX(0, y * nBlockYSize), 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 < esx) || (sy < esy))
720 : {
721 0 : CPLError(CE_Failure, CPLE_AppDefined,
722 : "GDALWMS: Incorrect size %d x %d of downloaded block, "
723 : "expected %d x %d, max %d x %d.",
724 : sx, sy, esx, esy, nBlockXSize, nBlockYSize);
725 0 : ret = CE_Failure;
726 : }
727 :
728 53 : int nDSRasterCount = ds->GetRasterCount();
729 53 : if (ret == CE_None)
730 : {
731 53 : if (nDSRasterCount != m_parent_dataset->nBands)
732 : {
733 : /* Maybe its an image with color table */
734 34 : if ((eDataType == GDT_Byte) && (ds->GetRasterCount() == 1))
735 : {
736 24 : GDALRasterBand *rb = ds->GetRasterBand(1);
737 24 : if (rb->GetRasterDataType() == GDT_Byte)
738 : {
739 24 : GDALColorTable *ct = rb->GetColorTable();
740 24 : if (ct != nullptr)
741 : {
742 19 : if (!advise_read)
743 : {
744 19 : color_table = new GByte[256 * 4];
745 : const int count =
746 19 : MIN(256, ct->GetColorEntryCount());
747 796 : for (i = 0; i < count; ++i)
748 : {
749 : GDALColorEntry ce;
750 777 : ct->GetColorEntryAsRGB(i, &ce);
751 777 : color_table[i] = static_cast<GByte>(ce.c1);
752 777 : color_table[i + 256] =
753 777 : static_cast<GByte>(ce.c2);
754 777 : color_table[i + 512] =
755 777 : static_cast<GByte>(ce.c3);
756 777 : color_table[i + 768] =
757 777 : static_cast<GByte>(ce.c4);
758 : }
759 :
760 4106 : for (i = count; i < 256; ++i)
761 : {
762 4087 : color_table[i] = 0;
763 4087 : color_table[i + 256] = 0;
764 4087 : color_table[i + 512] = 0;
765 4087 : color_table[i + 768] = 0;
766 : }
767 : }
768 : }
769 5 : else if (m_parent_dataset->nBands <= 4)
770 : { // Promote single band to fake color table
771 5 : color_table = new GByte[256 * 4];
772 1285 : for (i = 0; i < 256; i++)
773 : {
774 1280 : color_table[i] = static_cast<GByte>(i);
775 1280 : color_table[i + 256] = static_cast<GByte>(i);
776 1280 : color_table[i + 512] = static_cast<GByte>(i);
777 1280 : color_table[i + 768] = 255; // Transparency
778 : }
779 5 : if (m_parent_dataset->nBands == 2)
780 : { // Luma-Alpha fixup
781 0 : for (i = 0; i < 256; i++)
782 : {
783 0 : color_table[i + 256] = 255;
784 : }
785 : }
786 : }
787 : }
788 : }
789 : }
790 : }
791 :
792 53 : if (!advise_read)
793 : {
794 : const int *const bandmap =
795 53 : GetBandMapForExpand(nDSRasterCount, m_parent_dataset->nBands);
796 219 : for (int ib = 1; ib <= m_parent_dataset->nBands; ++ib)
797 : {
798 166 : if (ret == CE_None)
799 : {
800 166 : void *p = nullptr;
801 166 : GDALRasterBlock *b = nullptr;
802 166 : if ((buffer != nullptr) && (ib == to_buffer_band))
803 : {
804 33 : p = buffer;
805 : }
806 : else
807 : {
808 : GDALWMSRasterBand *band = static_cast<GDALWMSRasterBand *>(
809 133 : m_parent_dataset->GetRasterBand(ib));
810 133 : if (m_overview >= 0)
811 : {
812 : band = static_cast<GDALWMSRasterBand *>(
813 73 : band->GetOverview(m_overview));
814 : }
815 133 : if (!band->IsBlockInCache(x, y))
816 : {
817 133 : b = band->GetLockedBlockRef(x, y, true);
818 133 : if (b != nullptr)
819 : {
820 133 : p = b->GetDataRef();
821 133 : if (p == nullptr)
822 : {
823 0 : CPLError(CE_Failure, CPLE_AppDefined,
824 : "GDALWMS: GetDataRef returned NULL.");
825 0 : ret = CE_Failure;
826 : }
827 : }
828 : }
829 : else
830 : {
831 : // CPLDebug("WMS", "Band %d, block (x,y)=(%d, %d)
832 : // already in cache", band->GetBand(), x, y);
833 : }
834 : }
835 :
836 166 : if (p != nullptr)
837 : {
838 166 : int pixel_space = GDALGetDataTypeSizeBytes(eDataType);
839 166 : int line_space = pixel_space * nBlockXSize;
840 166 : if (color_table == nullptr)
841 : {
842 88 : if (bandmap == nullptr || bandmap[ib - 1] != 0)
843 : {
844 87 : GDALDataType dt = eDataType;
845 87 : int nSourceBand = ib;
846 87 : if (bandmap != nullptr)
847 : {
848 87 : nSourceBand = bandmap[ib - 1];
849 : }
850 : // Get the data from the PNG as stored instead of
851 : // converting, if the server asks for that
852 : // TODO: This hack is from #3493 - not sure it
853 : // really belongs here.
854 87 : if ((GDT_Int16 == dt) &&
855 : (GDT_UInt16 ==
856 0 : ds->GetRasterBand(ib)->GetRasterDataType()))
857 : {
858 0 : dt = GDT_UInt16;
859 : }
860 :
861 87 : if (ds->RasterIO(GF_Read, 0, 0, sx, sy, p, sx, sy,
862 : dt, 1, &nSourceBand, pixel_space,
863 87 : line_space, 0, nullptr) != CE_None)
864 : {
865 0 : CPLError(CE_Failure, CPLE_AppDefined,
866 : "GDALWMS: RasterIO failed on "
867 : "downloaded block.");
868 0 : ret = CE_Failure;
869 87 : }
870 : }
871 : else // if( bandmap != nullptr && bandmap[ib - 1] == 0
872 : // )
873 : { // parent expects 4 bands but file has fewer count so
874 : // generate a all "opaque" 4th band
875 1 : GByte *byte_buffer = reinterpret_cast<GByte *>(p);
876 129 : for (int l_y = 0; l_y < sy; ++l_y)
877 : {
878 16512 : for (int l_x = 0; l_x < sx; ++l_x)
879 : {
880 16384 : const int offset = l_x + l_y * line_space;
881 16384 : byte_buffer[offset] =
882 : 255; // fill with opaque
883 : }
884 : }
885 : }
886 : }
887 78 : else if (ib <= 4)
888 : {
889 78 : if (ds->RasterIO(GF_Read, 0, 0, sx, sy, p, sx, sy,
890 : eDataType, 1, nullptr, pixel_space,
891 78 : line_space, 0, nullptr) != CE_None)
892 : {
893 0 : CPLError(CE_Failure, CPLE_AppDefined,
894 : "GDALWMS: RasterIO failed on downloaded "
895 : "block.");
896 0 : ret = CE_Failure;
897 : }
898 :
899 78 : if (ret == CE_None)
900 : {
901 78 : GByte *band_color_table =
902 78 : color_table + 256 * (ib - 1);
903 78 : GByte *byte_buffer = reinterpret_cast<GByte *>(p);
904 20558 : for (int l_y = 0; l_y < sy; ++l_y)
905 : {
906 5918720 : for (int l_x = 0; l_x < sx; ++l_x)
907 : {
908 5898240 : const int offset = l_x + l_y * line_space;
909 5898240 : byte_buffer[offset] =
910 5898240 : band_color_table[byte_buffer[offset]];
911 : }
912 : }
913 : }
914 : }
915 : else
916 : {
917 0 : CPLError(CE_Failure, CPLE_AppDefined,
918 : "GDALWMS: Color table supports at most 4 "
919 : "components.");
920 0 : ret = CE_Failure;
921 : }
922 : }
923 166 : if (b != nullptr)
924 : {
925 133 : b->DropLock();
926 : }
927 : }
928 : }
929 : }
930 53 : GDALClose(ds);
931 :
932 53 : if (color_table != nullptr)
933 : {
934 24 : delete[] color_table;
935 : }
936 :
937 53 : return ret;
938 : }
939 :
940 41 : CPLErr GDALWMSRasterBand::ReadBlockFromFile(const CPLString &soFileName, int x,
941 : int y, int to_buffer_band,
942 : void *buffer, int advise_read)
943 : {
944 41 : GDALDataset *ds = GDALDataset::FromHandle(GDALOpenEx(
945 : soFileName, GDAL_OF_RASTER | GDAL_OF_READONLY | GDAL_OF_VERBOSE_ERROR,
946 41 : nullptr, m_parent_dataset->m_tileOO, nullptr));
947 41 : if (ds == nullptr)
948 : {
949 0 : CPLError(CE_Failure, CPLE_AppDefined,
950 : "GDALWMS: Unable to open downloaded block.");
951 0 : return CE_Failure;
952 : }
953 :
954 41 : return ReadBlockFromDataset(ds, x, y, to_buffer_band, buffer, advise_read);
955 : }
956 :
957 14 : CPLErr GDALWMSRasterBand::ReadBlockFromCache(const char *pszKey, int x, int y,
958 : int to_buffer_band, void *buffer,
959 : int advise_read)
960 : {
961 14 : GDALWMSCache *cache = m_parent_dataset->m_cache;
962 14 : if (nullptr == cache)
963 : {
964 0 : CPLError(CE_Failure, CPLE_AppDefined,
965 : "GDALWMS: Unable to open downloaded block.");
966 0 : return CE_Failure;
967 : }
968 14 : GDALDataset *ds = cache->GetDataset(pszKey, m_parent_dataset->m_tileOO);
969 14 : if (ds == nullptr)
970 : {
971 2 : CPLError(CE_Failure, CPLE_AppDefined,
972 : "GDALWMS: Unable to open downloaded block.");
973 2 : return CE_Failure;
974 : }
975 :
976 12 : return ReadBlockFromDataset(ds, x, y, to_buffer_band, buffer, advise_read);
977 : }
978 :
979 2 : CPLErr GDALWMSRasterBand::EmptyBlock(int x, int y, int to_buffer_band,
980 : void *buffer)
981 : {
982 2 : CPLErr ret = CE_None;
983 :
984 10 : for (int ib = 1; ib <= m_parent_dataset->nBands; ++ib)
985 : {
986 8 : if (ret == CE_None)
987 : {
988 8 : void *p = nullptr;
989 8 : GDALRasterBlock *b = nullptr;
990 : GDALWMSRasterBand *band = static_cast<GDALWMSRasterBand *>(
991 8 : m_parent_dataset->GetRasterBand(ib));
992 8 : if (m_overview >= 0)
993 : band = static_cast<GDALWMSRasterBand *>(
994 0 : band->GetOverview(m_overview));
995 8 : if ((buffer != nullptr) && (ib == to_buffer_band))
996 : {
997 2 : p = buffer;
998 : }
999 : else
1000 : {
1001 6 : if (!band->IsBlockInCache(x, y))
1002 : {
1003 6 : b = band->GetLockedBlockRef(x, y, true);
1004 6 : if (b != nullptr)
1005 : {
1006 6 : p = b->GetDataRef();
1007 6 : if (p == nullptr)
1008 : {
1009 0 : CPLError(CE_Failure, CPLE_AppDefined,
1010 : "GDALWMS: GetDataRef returned NULL.");
1011 0 : ret = CE_Failure;
1012 : }
1013 : }
1014 : }
1015 : }
1016 8 : if (p != nullptr)
1017 : {
1018 : int hasNDV;
1019 8 : double valNDV = band->GetNoDataValue(&hasNDV);
1020 8 : if (!hasNDV)
1021 8 : valNDV = 0;
1022 8 : GDALCopyWords(&valNDV, GDT_Float64, 0, p, eDataType,
1023 : GDALGetDataTypeSizeBytes(eDataType),
1024 8 : nBlockXSize * nBlockYSize);
1025 : }
1026 8 : if (b != nullptr)
1027 : {
1028 6 : b->DropLock();
1029 : }
1030 : }
1031 : }
1032 :
1033 2 : return ret;
1034 : }
1035 :
1036 0 : CPLErr GDALWMSRasterBand::ReportWMSException(const char *file_name)
1037 : {
1038 0 : CPLErr ret = CE_None;
1039 0 : int reported_errors_count = 0;
1040 :
1041 0 : CPLXMLNode *orig_root = CPLParseXMLFile(file_name);
1042 0 : CPLXMLNode *root = orig_root;
1043 0 : if (root != nullptr)
1044 : {
1045 0 : root = CPLGetXMLNode(root, "=ServiceExceptionReport");
1046 : }
1047 0 : if (root != nullptr)
1048 : {
1049 0 : CPLXMLNode *n = CPLGetXMLNode(root, "ServiceException");
1050 0 : while (n != nullptr)
1051 : {
1052 0 : const char *exception = CPLGetXMLValue(n, "=ServiceException", "");
1053 : const char *exception_code =
1054 0 : CPLGetXMLValue(n, "=ServiceException.code", "");
1055 0 : if (exception[0] != '\0')
1056 : {
1057 0 : if (exception_code[0] != '\0')
1058 : {
1059 0 : CPLError(
1060 : CE_Failure, CPLE_AppDefined,
1061 : "GDALWMS: The server returned exception code '%s': %s",
1062 : exception_code, exception);
1063 0 : ++reported_errors_count;
1064 : }
1065 : else
1066 : {
1067 0 : CPLError(CE_Failure, CPLE_AppDefined,
1068 : "GDALWMS: The server returned exception: %s",
1069 : exception);
1070 0 : ++reported_errors_count;
1071 : }
1072 : }
1073 0 : else if (exception_code[0] != '\0')
1074 : {
1075 0 : CPLError(CE_Failure, CPLE_AppDefined,
1076 : "GDALWMS: The server returned exception code '%s'.",
1077 : exception_code);
1078 0 : ++reported_errors_count;
1079 : }
1080 :
1081 0 : n = n->psNext;
1082 0 : if (n != nullptr)
1083 : {
1084 0 : n = CPLGetXMLNode(n, "=ServiceException");
1085 : }
1086 : }
1087 : }
1088 : else
1089 : {
1090 0 : ret = CE_Failure;
1091 : }
1092 0 : if (orig_root != nullptr)
1093 : {
1094 0 : CPLDestroyXMLNode(orig_root);
1095 : }
1096 :
1097 0 : if (reported_errors_count == 0)
1098 : {
1099 0 : ret = CE_Failure;
1100 : }
1101 :
1102 0 : return ret;
1103 : }
1104 :
1105 0 : CPLErr GDALWMSRasterBand::AdviseRead(int nXOff, int nYOff, int nXSize,
1106 : int nYSize, int nBufXSize, int nBufYSize,
1107 : GDALDataType eDT, char **papszOptions)
1108 : {
1109 : // printf("AdviseRead(%d, %d, %d, %d)\n", nXOff, nYOff, nXSize, nYSize);
1110 0 : if (m_parent_dataset->m_offline_mode ||
1111 0 : !m_parent_dataset->m_use_advise_read)
1112 0 : return CE_None;
1113 0 : if (m_parent_dataset->m_cache == nullptr)
1114 0 : return CE_Failure;
1115 :
1116 : /* ==================================================================== */
1117 : /* Do we have overviews that would be appropriate to satisfy */
1118 : /* this request? */
1119 : /* ==================================================================== */
1120 0 : if ((nBufXSize < nXSize || nBufYSize < nYSize) && GetOverviewCount() > 0)
1121 : {
1122 0 : const int nOverview = GDALBandGetBestOverviewLevel2(
1123 : this, nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize, nullptr);
1124 0 : if (nOverview >= 0)
1125 : {
1126 0 : GDALRasterBand *poOverviewBand = GetOverview(nOverview);
1127 0 : if (poOverviewBand == nullptr)
1128 0 : return CE_Failure;
1129 :
1130 0 : return poOverviewBand->AdviseRead(nXOff, nYOff, nXSize, nYSize,
1131 : nBufXSize, nBufYSize, eDT,
1132 0 : papszOptions);
1133 : }
1134 : }
1135 :
1136 0 : int bx0 = nXOff / nBlockXSize;
1137 0 : int by0 = nYOff / nBlockYSize;
1138 0 : int bx1 = (nXOff + nXSize - 1) / nBlockXSize;
1139 0 : int by1 = (nYOff + nYSize - 1) / nBlockYSize;
1140 :
1141 : // Avoid downloading a insane number of tiles
1142 0 : const int MAX_TILES = 1000; // arbitrary number
1143 0 : if ((bx1 - bx0 + 1) > MAX_TILES / (by1 - by0 + 1))
1144 : {
1145 0 : CPLDebug("WMS", "Too many tiles for AdviseRead()");
1146 0 : return CE_Failure;
1147 : }
1148 :
1149 0 : if (m_nAdviseReadBX0 == bx0 && m_nAdviseReadBY0 == by0 &&
1150 0 : m_nAdviseReadBX1 == bx1 && m_nAdviseReadBY1 == by1)
1151 : {
1152 0 : return CE_None;
1153 : }
1154 0 : m_nAdviseReadBX0 = bx0;
1155 0 : m_nAdviseReadBY0 = by0;
1156 0 : m_nAdviseReadBX1 = bx1;
1157 0 : m_nAdviseReadBY1 = by1;
1158 :
1159 0 : return ReadBlocks(0, 0, nullptr, bx0, by0, bx1, by1, 1);
1160 : }
1161 :
1162 415 : GDALColorInterp GDALWMSRasterBand::GetColorInterpretation()
1163 : {
1164 415 : return m_color_interp;
1165 : }
1166 :
1167 9 : CPLErr GDALWMSRasterBand::SetColorInterpretation(GDALColorInterp eNewInterp)
1168 : {
1169 9 : m_color_interp = eNewInterp;
1170 9 : return CE_None;
1171 : }
1172 :
1173 : // Utility function, returns a value from a vector corresponding to the band
1174 : // index or the first entry
1175 0 : static double getBandValue(const std::vector<double> &v, size_t idx)
1176 : {
1177 0 : idx--;
1178 0 : if (v.size() > idx)
1179 0 : return v[idx];
1180 0 : return v[0];
1181 : }
1182 :
1183 493 : double GDALWMSRasterBand::GetNoDataValue(int *pbSuccess)
1184 : {
1185 493 : std::vector<double> &v = m_parent_dataset->vNoData;
1186 493 : if (v.empty())
1187 493 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1188 0 : if (pbSuccess)
1189 0 : *pbSuccess = TRUE;
1190 0 : return getBandValue(v, nBand);
1191 : }
1192 :
1193 0 : double GDALWMSRasterBand::GetMinimum(int *pbSuccess)
1194 : {
1195 0 : std::vector<double> &v = m_parent_dataset->vMin;
1196 0 : if (v.empty())
1197 0 : return GDALPamRasterBand::GetMinimum(pbSuccess);
1198 0 : if (pbSuccess)
1199 0 : *pbSuccess = TRUE;
1200 0 : return getBandValue(v, nBand);
1201 : }
1202 :
1203 0 : double GDALWMSRasterBand::GetMaximum(int *pbSuccess)
1204 : {
1205 0 : std::vector<double> &v = m_parent_dataset->vMax;
1206 0 : if (v.empty())
1207 0 : return GDALPamRasterBand::GetMaximum(pbSuccess);
1208 0 : if (pbSuccess)
1209 0 : *pbSuccess = TRUE;
1210 0 : return getBandValue(v, nBand);
1211 : }
1212 :
1213 316 : GDALColorTable *GDALWMSRasterBand::GetColorTable()
1214 : {
1215 316 : return m_parent_dataset->m_poColorTable;
1216 : }
|