Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Virtual GDAL Datasets
4 : * Purpose: Implementation of a sourced raster band that derives its raster
5 : * by applying an algorithm (GDALDerivedPixelFunc) to the sources.
6 : * Author: Pete Nagy
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2005 Vexcel Corp.
10 : * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com>
11 : *
12 : * Permission is hereby granted, free of charge, to any person obtaining a
13 : * copy of this software and associated documentation files (the "Software"),
14 : * to deal in the Software without restriction, including without limitation
15 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 : * and/or sell copies of the Software, and to permit persons to whom the
17 : * Software is furnished to do so, subject to the following conditions:
18 : *
19 : * The above copyright notice and this permission notice shall be included
20 : * in all copies or substantial portions of the Software.
21 : *
22 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 : * DEALINGS IN THE SOFTWARE.
29 : *****************************************************************************/
30 :
31 : #include "cpl_minixml.h"
32 : #include "cpl_string.h"
33 : #include "vrtdataset.h"
34 : #include "cpl_multiproc.h"
35 : #include "gdalpython.h"
36 :
37 : #include <algorithm>
38 : #include <map>
39 : #include <vector>
40 : #include <utility>
41 :
42 : /*! @cond Doxygen_Suppress */
43 :
44 : using namespace GDALPy;
45 :
46 : // #define GDAL_VRT_DISABLE_PYTHON
47 :
48 : #ifndef GDAL_VRT_ENABLE_PYTHON_DEFAULT
49 : // Can be YES, NO or TRUSTED_MODULES
50 : #define GDAL_VRT_ENABLE_PYTHON_DEFAULT "TRUSTED_MODULES"
51 : #endif
52 :
53 : /* Flags for getting buffers */
54 : #define PyBUF_WRITABLE 0x0001
55 : #define PyBUF_FORMAT 0x0004
56 : #define PyBUF_ND 0x0008
57 : #define PyBUF_STRIDES (0x0010 | PyBUF_ND)
58 : #define PyBUF_INDIRECT (0x0100 | PyBUF_STRIDES)
59 : #define PyBUF_FULL (PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT)
60 :
61 : /************************************************************************/
62 : /* GDALCreateNumpyArray() */
63 : /************************************************************************/
64 :
65 393 : static PyObject *GDALCreateNumpyArray(PyObject *pCreateArray, void *pBuffer,
66 : GDALDataType eType, int nHeight,
67 : int nWidth)
68 : {
69 : PyObject *poPyBuffer;
70 : const size_t nSize =
71 393 : static_cast<size_t>(nHeight) * nWidth * GDALGetDataTypeSizeBytes(eType);
72 : Py_buffer pybuffer;
73 393 : if (PyBuffer_FillInfo(&pybuffer, nullptr, static_cast<char *>(pBuffer),
74 393 : nSize, 0, PyBUF_FULL) != 0)
75 : {
76 0 : return nullptr;
77 : }
78 393 : poPyBuffer = PyMemoryView_FromBuffer(&pybuffer);
79 393 : PyObject *pArgsCreateArray = PyTuple_New(4);
80 393 : PyTuple_SetItem(pArgsCreateArray, 0, poPyBuffer);
81 393 : const char *pszDataType = nullptr;
82 393 : switch (eType)
83 : {
84 358 : case GDT_Byte:
85 358 : pszDataType = "uint8";
86 358 : break;
87 2 : case GDT_Int8:
88 2 : pszDataType = "int8";
89 2 : break;
90 3 : case GDT_UInt16:
91 3 : pszDataType = "uint16";
92 3 : break;
93 8 : case GDT_Int16:
94 8 : pszDataType = "int16";
95 8 : break;
96 3 : case GDT_UInt32:
97 3 : pszDataType = "uint32";
98 3 : break;
99 3 : case GDT_Int32:
100 3 : pszDataType = "int32";
101 3 : break;
102 2 : case GDT_Int64:
103 2 : pszDataType = "int64";
104 2 : break;
105 2 : case GDT_UInt64:
106 2 : pszDataType = "uint64";
107 2 : break;
108 3 : case GDT_Float32:
109 3 : pszDataType = "float32";
110 3 : break;
111 3 : case GDT_Float64:
112 3 : pszDataType = "float64";
113 3 : break;
114 0 : case GDT_CInt16:
115 : case GDT_CInt32:
116 0 : CPLAssert(FALSE);
117 : break;
118 3 : case GDT_CFloat32:
119 3 : pszDataType = "complex64";
120 3 : break;
121 3 : case GDT_CFloat64:
122 3 : pszDataType = "complex128";
123 3 : break;
124 0 : case GDT_Unknown:
125 : case GDT_TypeCount:
126 0 : CPLAssert(FALSE);
127 : break;
128 : }
129 393 : PyTuple_SetItem(
130 : pArgsCreateArray, 1,
131 : PyBytes_FromStringAndSize(pszDataType, strlen(pszDataType)));
132 393 : PyTuple_SetItem(pArgsCreateArray, 2, PyLong_FromLong(nHeight));
133 393 : PyTuple_SetItem(pArgsCreateArray, 3, PyLong_FromLong(nWidth));
134 : PyObject *poNumpyArray =
135 393 : PyObject_Call(pCreateArray, pArgsCreateArray, nullptr);
136 393 : Py_DecRef(pArgsCreateArray);
137 393 : if (PyErr_Occurred())
138 0 : PyErr_Print();
139 393 : return poNumpyArray;
140 : }
141 :
142 : /************************************************************************/
143 : /* ==================================================================== */
144 : /* VRTDerivedRasterBandPrivateData */
145 : /* ==================================================================== */
146 : /************************************************************************/
147 :
148 : class VRTDerivedRasterBandPrivateData
149 : {
150 : VRTDerivedRasterBandPrivateData(const VRTDerivedRasterBandPrivateData &) =
151 : delete;
152 : VRTDerivedRasterBandPrivateData &
153 : operator=(const VRTDerivedRasterBandPrivateData &) = delete;
154 :
155 : public:
156 : CPLString m_osCode{};
157 : CPLString m_osLanguage = "C";
158 : int m_nBufferRadius = 0;
159 : PyObject *m_poGDALCreateNumpyArray = nullptr;
160 : PyObject *m_poUserFunction = nullptr;
161 : bool m_bPythonInitializationDone = false;
162 : bool m_bPythonInitializationSuccess = false;
163 : bool m_bExclusiveLock = false;
164 : bool m_bFirstTime = true;
165 : std::vector<std::pair<CPLString, CPLString>> m_oFunctionArgs{};
166 : bool m_bSkipNonContributingSourcesSpecified = false;
167 : bool m_bSkipNonContributingSources = false;
168 :
169 181 : VRTDerivedRasterBandPrivateData() = default;
170 :
171 362 : virtual ~VRTDerivedRasterBandPrivateData()
172 181 : {
173 181 : if (m_poGDALCreateNumpyArray)
174 43 : Py_DecRef(m_poGDALCreateNumpyArray);
175 181 : if (m_poUserFunction)
176 44 : Py_DecRef(m_poUserFunction);
177 362 : }
178 : };
179 :
180 : /************************************************************************/
181 : /* ==================================================================== */
182 : /* VRTDerivedRasterBand */
183 : /* ==================================================================== */
184 : /************************************************************************/
185 :
186 : /************************************************************************/
187 : /* VRTDerivedRasterBand() */
188 : /************************************************************************/
189 :
190 162 : VRTDerivedRasterBand::VRTDerivedRasterBand(GDALDataset *poDSIn, int nBandIn)
191 : : VRTSourcedRasterBand(poDSIn, nBandIn), m_poPrivate(nullptr),
192 162 : pszFuncName(nullptr), eSourceTransferType(GDT_Unknown)
193 : {
194 162 : m_poPrivate = new VRTDerivedRasterBandPrivateData;
195 162 : }
196 :
197 : /************************************************************************/
198 : /* VRTDerivedRasterBand() */
199 : /************************************************************************/
200 :
201 19 : VRTDerivedRasterBand::VRTDerivedRasterBand(GDALDataset *poDSIn, int nBandIn,
202 : GDALDataType eType, int nXSize,
203 19 : int nYSize)
204 : : VRTSourcedRasterBand(poDSIn, nBandIn, eType, nXSize, nYSize),
205 : m_poPrivate(nullptr), pszFuncName(nullptr),
206 19 : eSourceTransferType(GDT_Unknown)
207 : {
208 19 : m_poPrivate = new VRTDerivedRasterBandPrivateData;
209 19 : }
210 :
211 : /************************************************************************/
212 : /* ~VRTDerivedRasterBand() */
213 : /************************************************************************/
214 :
215 362 : VRTDerivedRasterBand::~VRTDerivedRasterBand()
216 :
217 : {
218 181 : CPLFree(pszFuncName);
219 181 : delete m_poPrivate;
220 362 : }
221 :
222 : /************************************************************************/
223 : /* Cleanup() */
224 : /************************************************************************/
225 :
226 1706 : void VRTDerivedRasterBand::Cleanup()
227 : {
228 1706 : }
229 :
230 : /************************************************************************/
231 : /* GetGlobalMapPixelFunction() */
232 : /************************************************************************/
233 :
234 : static std::map<std::string,
235 : std::pair<VRTDerivedRasterBand::PixelFunc, std::string>> &
236 34208 : GetGlobalMapPixelFunction()
237 : {
238 : static std::map<std::string,
239 : std::pair<VRTDerivedRasterBand::PixelFunc, std::string>>
240 34208 : gosMapPixelFunction;
241 34208 : return gosMapPixelFunction;
242 : }
243 :
244 : /************************************************************************/
245 : /* AddPixelFunction() */
246 : /************************************************************************/
247 :
248 : /*! @endcond */
249 :
250 : /**
251 : * This adds a pixel function to the global list of available pixel
252 : * functions for derived bands. Pixel functions must be registered
253 : * in this way before a derived band tries to access data.
254 : *
255 : * Derived bands are stored with only the name of the pixel function
256 : * that it will apply, and if a pixel function matching the name is not
257 : * found the IRasterIO() call will do nothing.
258 : *
259 : * @param pszName Name used to access pixel function
260 : * @param pfnNewFunction Pixel function associated with name. An
261 : * existing pixel function registered with the same name will be
262 : * replaced with the new one.
263 : *
264 : * @return CE_None, invalid (NULL) parameters are currently ignored.
265 : */
266 18272 : CPLErr CPL_STDCALL GDALAddDerivedBandPixelFunc(
267 : const char *pszName, GDALDerivedPixelFunc pfnNewFunction)
268 : {
269 18272 : if (pszName == nullptr || pszName[0] == '\0' || pfnNewFunction == nullptr)
270 : {
271 0 : return CE_None;
272 : }
273 :
274 36544 : GetGlobalMapPixelFunction()[pszName] = {
275 52 : [pfnNewFunction](void **papoSources, int nSources, void *pData,
276 : int nBufXSize, int nBufYSize, GDALDataType eSrcType,
277 : GDALDataType eBufType, int nPixelSpace, int nLineSpace,
278 52 : CSLConstList papszFunctionArgs)
279 : {
280 : (void)papszFunctionArgs;
281 52 : return pfnNewFunction(papoSources, nSources, pData, nBufXSize,
282 : nBufYSize, eSrcType, eBufType, nPixelSpace,
283 52 : nLineSpace);
284 : },
285 54816 : ""};
286 :
287 18272 : return CE_None;
288 : }
289 :
290 : /**
291 : * This adds a pixel function to the global list of available pixel
292 : * functions for derived bands. Pixel functions must be registered
293 : * in this way before a derived band tries to access data.
294 : *
295 : * Derived bands are stored with only the name of the pixel function
296 : * that it will apply, and if a pixel function matching the name is not
297 : * found the IRasterIO() call will do nothing.
298 : *
299 : * @param pszName Name used to access pixel function
300 : * @param pfnNewFunction Pixel function associated with name. An
301 : * existing pixel function registered with the same name will be
302 : * replaced with the new one.
303 : * @param pszMetadata Pixel function metadata (not currently implemented)
304 : *
305 : * @return CE_None, invalid (NULL) parameters are currently ignored.
306 : * @since GDAL 3.4
307 : */
308 15836 : CPLErr CPL_STDCALL GDALAddDerivedBandPixelFuncWithArgs(
309 : const char *pszName, GDALDerivedPixelFuncWithArgs pfnNewFunction,
310 : const char *pszMetadata)
311 : {
312 15836 : if (!pszName || pszName[0] == '\0' || !pfnNewFunction)
313 : {
314 0 : return CE_None;
315 : }
316 :
317 31672 : GetGlobalMapPixelFunction()[pszName] = {pfnNewFunction,
318 47508 : pszMetadata ? pszMetadata : ""};
319 :
320 15836 : return CE_None;
321 : }
322 :
323 : /*! @cond Doxygen_Suppress */
324 :
325 : /**
326 : * This adds a pixel function to the global list of available pixel
327 : * functions for derived bands.
328 : *
329 : * This is the same as the C function GDALAddDerivedBandPixelFunc()
330 : *
331 : * @param pszFuncNameIn Name used to access pixel function
332 : * @param pfnNewFunction Pixel function associated with name. An
333 : * existing pixel function registered with the same name will be
334 : * replaced with the new one.
335 : *
336 : * @return CE_None, invalid (NULL) parameters are currently ignored.
337 : */
338 : CPLErr
339 0 : VRTDerivedRasterBand::AddPixelFunction(const char *pszFuncNameIn,
340 : GDALDerivedPixelFunc pfnNewFunction)
341 : {
342 0 : return GDALAddDerivedBandPixelFunc(pszFuncNameIn, pfnNewFunction);
343 : }
344 :
345 0 : CPLErr VRTDerivedRasterBand::AddPixelFunction(
346 : const char *pszFuncNameIn, GDALDerivedPixelFuncWithArgs pfnNewFunction,
347 : const char *pszMetadata)
348 : {
349 0 : return GDALAddDerivedBandPixelFuncWithArgs(pszFuncNameIn, pfnNewFunction,
350 0 : pszMetadata);
351 : }
352 :
353 : /************************************************************************/
354 : /* GetPixelFunction() */
355 : /************************************************************************/
356 :
357 : /**
358 : * Get a pixel function previously registered using the global
359 : * AddPixelFunction.
360 : *
361 : * @param pszFuncNameIn The name associated with the pixel function.
362 : *
363 : * @return A derived band pixel function, or NULL if none have been
364 : * registered for pszFuncName.
365 : */
366 : const std::pair<VRTDerivedRasterBand::PixelFunc, std::string> *
367 100 : VRTDerivedRasterBand::GetPixelFunction(const char *pszFuncNameIn)
368 : {
369 100 : if (pszFuncNameIn == nullptr || pszFuncNameIn[0] == '\0')
370 : {
371 0 : return nullptr;
372 : }
373 :
374 100 : const auto &oMapPixelFunction = GetGlobalMapPixelFunction();
375 100 : const auto oIter = oMapPixelFunction.find(pszFuncNameIn);
376 :
377 100 : if (oIter == oMapPixelFunction.end())
378 1 : return nullptr;
379 :
380 99 : return &(oIter->second);
381 : }
382 :
383 : /************************************************************************/
384 : /* SetPixelFunctionName() */
385 : /************************************************************************/
386 :
387 : /**
388 : * Set the pixel function name to be applied to this derived band. The
389 : * name should match a pixel function registered using AddPixelFunction.
390 : *
391 : * @param pszFuncNameIn Name of pixel function to be applied to this derived
392 : * band.
393 : */
394 182 : void VRTDerivedRasterBand::SetPixelFunctionName(const char *pszFuncNameIn)
395 : {
396 182 : CPLFree(pszFuncName);
397 182 : pszFuncName = CPLStrdup(pszFuncNameIn);
398 182 : }
399 :
400 : /************************************************************************/
401 : /* SetPixelFunctionLanguage() */
402 : /************************************************************************/
403 :
404 : /**
405 : * Set the language of the pixel function.
406 : *
407 : * @param pszLanguage Language of the pixel function (only "C" and "Python"
408 : * are supported currently)
409 : * @since GDAL 2.3
410 : */
411 1 : void VRTDerivedRasterBand::SetPixelFunctionLanguage(const char *pszLanguage)
412 : {
413 1 : m_poPrivate->m_osLanguage = pszLanguage;
414 1 : }
415 :
416 : /************************************************************************/
417 : /* SetSourceTransferType() */
418 : /************************************************************************/
419 :
420 : /**
421 : * Set the transfer type to be used to obtain pixel information from
422 : * all of the sources. If unset, the transfer type used will be the
423 : * same as the derived band data type. This makes it possible, for
424 : * example, to pass CFloat32 source pixels to the pixel function, even
425 : * if the pixel function generates a raster for a derived band that
426 : * is of type Byte.
427 : *
428 : * @param eDataTypeIn Data type to use to obtain pixel information from
429 : * the sources to be passed to the derived band pixel function.
430 : */
431 16 : void VRTDerivedRasterBand::SetSourceTransferType(GDALDataType eDataTypeIn)
432 : {
433 16 : eSourceTransferType = eDataTypeIn;
434 16 : }
435 :
436 : /************************************************************************/
437 : /* InitializePython() */
438 : /************************************************************************/
439 :
440 379 : bool VRTDerivedRasterBand::InitializePython()
441 : {
442 379 : if (m_poPrivate->m_bPythonInitializationDone)
443 321 : return m_poPrivate->m_bPythonInitializationSuccess;
444 :
445 58 : m_poPrivate->m_bPythonInitializationDone = true;
446 58 : m_poPrivate->m_bPythonInitializationSuccess = false;
447 :
448 116 : const CPLString osPythonFullname(pszFuncName ? pszFuncName : "");
449 58 : const size_t nIdxDot = osPythonFullname.rfind(".");
450 116 : CPLString osPythonModule;
451 116 : CPLString osPythonFunction;
452 58 : if (nIdxDot != std::string::npos)
453 : {
454 25 : osPythonModule = osPythonFullname.substr(0, nIdxDot);
455 25 : osPythonFunction = osPythonFullname.substr(nIdxDot + 1);
456 : }
457 : else
458 : {
459 33 : osPythonFunction = osPythonFullname;
460 : }
461 :
462 : #ifndef GDAL_VRT_DISABLE_PYTHON
463 : const char *pszPythonEnabled =
464 58 : CPLGetConfigOption("GDAL_VRT_ENABLE_PYTHON", nullptr);
465 : #else
466 : const char *pszPythonEnabled = "NO";
467 : #endif
468 : const CPLString osPythonEnabled(
469 116 : pszPythonEnabled ? pszPythonEnabled : GDAL_VRT_ENABLE_PYTHON_DEFAULT);
470 :
471 58 : if (EQUAL(osPythonEnabled, "TRUSTED_MODULES"))
472 : {
473 12 : bool bIsTrustedModule = false;
474 : const CPLString osVRTTrustedModules(
475 12 : CPLGetConfigOption("GDAL_VRT_PYTHON_TRUSTED_MODULES", ""));
476 12 : if (!osPythonModule.empty())
477 : {
478 : char **papszTrustedModules =
479 10 : CSLTokenizeString2(osVRTTrustedModules, ",", 0);
480 23 : for (char **papszIter = papszTrustedModules;
481 23 : !bIsTrustedModule && papszIter && *papszIter; ++papszIter)
482 : {
483 13 : const char *pszIterModule = *papszIter;
484 13 : size_t nIterModuleLen = strlen(pszIterModule);
485 13 : if (nIterModuleLen > 2 &&
486 12 : strncmp(pszIterModule + nIterModuleLen - 2, ".*", 2) == 0)
487 : {
488 2 : bIsTrustedModule =
489 2 : (strncmp(osPythonModule, pszIterModule,
490 3 : nIterModuleLen - 2) == 0) &&
491 1 : (osPythonModule.size() == nIterModuleLen - 2 ||
492 0 : (osPythonModule.size() >= nIterModuleLen &&
493 0 : osPythonModule[nIterModuleLen - 1] == '.'));
494 : }
495 11 : else if (nIterModuleLen >= 1 &&
496 11 : pszIterModule[nIterModuleLen - 1] == '*')
497 : {
498 4 : bIsTrustedModule = (strncmp(osPythonModule, pszIterModule,
499 : nIterModuleLen - 1) == 0);
500 : }
501 : else
502 : {
503 7 : bIsTrustedModule =
504 7 : (strcmp(osPythonModule, pszIterModule) == 0);
505 : }
506 : }
507 10 : CSLDestroy(papszTrustedModules);
508 : }
509 :
510 12 : if (!bIsTrustedModule)
511 : {
512 7 : if (osPythonModule.empty())
513 : {
514 2 : CPLError(
515 : CE_Failure, CPLE_AppDefined,
516 : "Python code needs to be executed, but it uses inline code "
517 : "in the VRT whereas the current policy is to trust only "
518 : "code from external trusted modules (defined in the "
519 : "GDAL_VRT_PYTHON_TRUSTED_MODULES configuration option). "
520 : "If you trust the code in %s, you can set the "
521 : "GDAL_VRT_ENABLE_PYTHON configuration option to YES.",
522 2 : GetDataset() ? GetDataset()->GetDescription()
523 : : "(unknown VRT)");
524 : }
525 5 : else if (osVRTTrustedModules.empty())
526 : {
527 2 : CPLError(
528 : CE_Failure, CPLE_AppDefined,
529 : "Python code needs to be executed, but it uses code "
530 : "from module '%s', whereas the current policy is to "
531 : "trust only code from modules defined in the "
532 : "GDAL_VRT_PYTHON_TRUSTED_MODULES configuration option, "
533 : "which is currently unset. "
534 : "If you trust the code in '%s', you can add module '%s' "
535 : "to GDAL_VRT_PYTHON_TRUSTED_MODULES (or set the "
536 : "GDAL_VRT_ENABLE_PYTHON configuration option to YES).",
537 : osPythonModule.c_str(),
538 1 : GetDataset() ? GetDataset()->GetDescription()
539 : : "(unknown VRT)",
540 : osPythonModule.c_str());
541 : }
542 : else
543 : {
544 8 : CPLError(
545 : CE_Failure, CPLE_AppDefined,
546 : "Python code needs to be executed, but it uses code "
547 : "from module '%s', whereas the current policy is to "
548 : "trust only code from modules '%s' (defined in the "
549 : "GDAL_VRT_PYTHON_TRUSTED_MODULES configuration option). "
550 : "If you trust the code in '%s', you can add module '%s' "
551 : "to GDAL_VRT_PYTHON_TRUSTED_MODULES (or set the "
552 : "GDAL_VRT_ENABLE_PYTHON configuration option to YES).",
553 : osPythonModule.c_str(), osVRTTrustedModules.c_str(),
554 4 : GetDataset() ? GetDataset()->GetDescription()
555 : : "(unknown VRT)",
556 : osPythonModule.c_str());
557 : }
558 7 : return false;
559 : }
560 : }
561 :
562 : #ifdef disabled_because_this_is_probably_broken_by_design
563 : // See https://lwn.net/Articles/574215/
564 : // and http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
565 : else if (EQUAL(osPythonEnabled, "IF_SAFE"))
566 : {
567 : bool bSafe = true;
568 : // If the function comes from another module, then we don't know
569 : if (!osPythonModule.empty())
570 : {
571 : CPLDebug("VRT", "Python function is from another module");
572 : bSafe = false;
573 : }
574 :
575 : CPLString osCode(m_poPrivate->m_osCode);
576 :
577 : // Reject all imports except a few trusted modules
578 : const char *const apszTrustedImports[] = {
579 : "import math",
580 : "from math import",
581 : "import numpy", // caution: numpy has lots of I/O functions !
582 : "from numpy import",
583 : // TODO: not sure if importing arbitrary stuff from numba is OK
584 : // so let's just restrict to jit.
585 : "from numba import jit",
586 :
587 : // Not imports but still whitelisted, whereas other __ is banned
588 : "__init__",
589 : "__call__",
590 : };
591 : for (size_t i = 0; i < CPL_ARRAYSIZE(apszTrustedImports); ++i)
592 : {
593 : osCode.replaceAll(CPLString(apszTrustedImports[i]), "");
594 : }
595 :
596 : // Some dangerous built-in functions or numpy functions
597 : const char *const apszUntrusted[] = {
598 : "import", // and __import__
599 : "eval", "compile", "open",
600 : "load", // reload, numpy.load
601 : "file", // and exec_file, numpy.fromfile, numpy.tofile
602 : "input", // and raw_input
603 : "save", // numpy.save
604 : "memmap", // numpy.memmap
605 : "DataSource", // numpy.DataSource
606 : "genfromtxt", // numpy.genfromtxt
607 : "getattr",
608 : "ctypeslib", // numpy.ctypeslib
609 : "testing", // numpy.testing
610 : "dump", // numpy.ndarray.dump
611 : "fromregex", // numpy.fromregex
612 : "__"};
613 : for (size_t i = 0; i < CPL_ARRAYSIZE(apszUntrusted); ++i)
614 : {
615 : if (osCode.find(apszUntrusted[i]) != std::string::npos)
616 : {
617 : CPLDebug("VRT", "Found '%s' word in Python code",
618 : apszUntrusted[i]);
619 : bSafe = false;
620 : }
621 : }
622 :
623 : if (!bSafe)
624 : {
625 : CPLError(CE_Failure, CPLE_AppDefined,
626 : "Python code needs to be executed, but we cannot verify "
627 : "if it is safe, so this is disabled by default. "
628 : "If you trust the code in %s, you can set the "
629 : "GDAL_VRT_ENABLE_PYTHON configuration option to YES.",
630 : GetDataset() ? GetDataset()->GetDescription()
631 : : "(unknown VRT)");
632 : return false;
633 : }
634 : }
635 : #endif // disabled_because_this_is_probably_broken_by_design
636 :
637 47 : else if (!EQUAL(osPythonEnabled, "YES") && !EQUAL(osPythonEnabled, "ON") &&
638 1 : !EQUAL(osPythonEnabled, "TRUE"))
639 : {
640 1 : if (pszPythonEnabled == nullptr)
641 : {
642 : // Note: this is dead code with our current default policy
643 : // GDAL_VRT_ENABLE_PYTHON == "TRUSTED_MODULES"
644 0 : CPLError(CE_Failure, CPLE_AppDefined,
645 : "Python code needs to be executed, but this is "
646 : "disabled by default. If you trust the code in %s, "
647 : "you can set the GDAL_VRT_ENABLE_PYTHON configuration "
648 : "option to YES.",
649 0 : GetDataset() ? GetDataset()->GetDescription()
650 : : "(unknown VRT)");
651 : }
652 : else
653 : {
654 1 : CPLError(
655 : CE_Failure, CPLE_AppDefined,
656 : "Python code in %s needs to be executed, but this has been "
657 : "explicitly disabled.",
658 1 : GetDataset() ? GetDataset()->GetDescription()
659 : : "(unknown VRT)");
660 : }
661 1 : return false;
662 : }
663 :
664 50 : if (!GDALPythonInitialize())
665 2 : return false;
666 :
667 : // Whether we should just use our own global mutex, in addition to Python
668 : // GIL locking.
669 96 : m_poPrivate->m_bExclusiveLock =
670 48 : CPLTestBool(CPLGetConfigOption("GDAL_VRT_PYTHON_EXCLUSIVE_LOCK", "NO"));
671 :
672 : // numba jit'ification doesn't seem to be thread-safe, so force use of
673 : // lock now and at first execution of function. Later executions seem to
674 : // be thread-safe. This problem doesn't seem to appear for code in
675 : // regular files
676 : const bool bUseExclusiveLock =
677 96 : m_poPrivate->m_bExclusiveLock ||
678 48 : m_poPrivate->m_osCode.find("@jit") != std::string::npos;
679 96 : GIL_Holder oHolder(bUseExclusiveLock);
680 :
681 : // As we don't want to depend on numpy C API/ABI, we use a trick to build
682 : // a numpy array object. We define a Python function to which we pass a
683 : // Python buffer object.
684 :
685 : // We need to build a unique module name, otherwise this will crash in
686 : // multithreaded use cases.
687 96 : CPLString osModuleName(CPLSPrintf("gdal_vrt_module_%p", this));
688 96 : PyObject *poCompiledString = Py_CompileString(
689 : ("import numpy\n"
690 : "def GDALCreateNumpyArray(buffer, dtype, height, width):\n"
691 : " return numpy.frombuffer(buffer, str(dtype.decode('ascii')))."
692 : "reshape([height, width])\n"
693 48 : "\n" +
694 48 : m_poPrivate->m_osCode)
695 : .c_str(),
696 : osModuleName, Py_file_input);
697 48 : if (poCompiledString == nullptr || PyErr_Occurred())
698 : {
699 1 : CPLError(CE_Failure, CPLE_AppDefined, "Couldn't compile code:\n%s",
700 2 : GetPyExceptionString().c_str());
701 1 : return false;
702 : }
703 : PyObject *poModule =
704 47 : PyImport_ExecCodeModule(osModuleName, poCompiledString);
705 47 : Py_DecRef(poCompiledString);
706 :
707 47 : if (poModule == nullptr || PyErr_Occurred())
708 : {
709 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
710 2 : GetPyExceptionString().c_str());
711 1 : return false;
712 : }
713 :
714 : // Fetch user computation function
715 46 : if (!osPythonModule.empty())
716 : {
717 20 : PyObject *poUserModule = PyImport_ImportModule(osPythonModule);
718 20 : if (poUserModule == nullptr || PyErr_Occurred())
719 : {
720 1 : CPLString osException = GetPyExceptionString();
721 1 : if (!osException.empty() && osException.back() == '\n')
722 : {
723 1 : osException.resize(osException.size() - 1);
724 : }
725 1 : if (osException.find("ModuleNotFoundError") == 0)
726 : {
727 1 : osException += ". You may need to define PYTHONPATH";
728 : }
729 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s", osException.c_str());
730 1 : Py_DecRef(poModule);
731 1 : return false;
732 : }
733 38 : m_poPrivate->m_poUserFunction =
734 19 : PyObject_GetAttrString(poUserModule, osPythonFunction);
735 19 : Py_DecRef(poUserModule);
736 : }
737 : else
738 : {
739 52 : m_poPrivate->m_poUserFunction =
740 26 : PyObject_GetAttrString(poModule, osPythonFunction);
741 : }
742 45 : if (m_poPrivate->m_poUserFunction == nullptr || PyErr_Occurred())
743 : {
744 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
745 2 : GetPyExceptionString().c_str());
746 1 : Py_DecRef(poModule);
747 1 : return false;
748 : }
749 44 : if (!PyCallable_Check(m_poPrivate->m_poUserFunction))
750 : {
751 1 : CPLError(CE_Failure, CPLE_AppDefined, "Object '%s' is not callable",
752 : osPythonFunction.c_str());
753 1 : Py_DecRef(poModule);
754 1 : return false;
755 : }
756 :
757 : // Fetch our GDALCreateNumpyArray python function
758 86 : m_poPrivate->m_poGDALCreateNumpyArray =
759 43 : PyObject_GetAttrString(poModule, "GDALCreateNumpyArray");
760 43 : if (m_poPrivate->m_poGDALCreateNumpyArray == nullptr || PyErr_Occurred())
761 : {
762 : // Shouldn't happen normally...
763 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
764 0 : GetPyExceptionString().c_str());
765 0 : Py_DecRef(poModule);
766 0 : return false;
767 : }
768 43 : Py_DecRef(poModule);
769 :
770 43 : m_poPrivate->m_bPythonInitializationSuccess = true;
771 43 : return true;
772 : }
773 :
774 46 : CPLErr VRTDerivedRasterBand::GetPixelFunctionArguments(
775 : const CPLString &osMetadata,
776 : std::vector<std::pair<CPLString, CPLString>> &oAdditionalArgs)
777 : {
778 :
779 92 : auto poArgs = CPLXMLTreeCloser(CPLParseXMLString(osMetadata));
780 92 : if (poArgs != nullptr && poArgs->eType == CXT_Element &&
781 46 : !strcmp(poArgs->pszValue, "PixelFunctionArgumentsList"))
782 : {
783 117 : for (CPLXMLNode *psIter = poArgs->psChild; psIter != nullptr;
784 71 : psIter = psIter->psNext)
785 : {
786 72 : if (psIter->eType == CXT_Element &&
787 72 : !strcmp(psIter->pszValue, "Argument"))
788 : {
789 72 : CPLString osName, osType, osValue;
790 72 : auto pszName = CPLGetXMLValue(psIter, "name", nullptr);
791 72 : if (pszName != nullptr)
792 61 : osName = pszName;
793 72 : auto pszType = CPLGetXMLValue(psIter, "type", nullptr);
794 72 : if (pszType != nullptr)
795 72 : osType = pszType;
796 72 : auto pszValue = CPLGetXMLValue(psIter, "value", nullptr);
797 72 : if (pszValue != nullptr)
798 16 : osValue = pszValue;
799 72 : if (osType == "constant" && osValue != "" && osName != "")
800 1 : oAdditionalArgs.push_back(
801 2 : std::pair<CPLString, CPLString>(osName, osValue));
802 72 : if (osType == "builtin")
803 : {
804 : double dfVal;
805 : int success;
806 11 : if (osValue == "NoData")
807 8 : dfVal = this->GetNoDataValue(&success);
808 3 : else if (osValue == "scale")
809 2 : dfVal = this->GetScale(&success);
810 1 : else if (osValue == "offset")
811 1 : dfVal = this->GetOffset(&success);
812 : else
813 : {
814 0 : CPLError(CE_Failure, CPLE_NotSupported,
815 : "PixelFunction builtin %s not supported",
816 : osValue.c_str());
817 1 : return CE_Failure;
818 : }
819 11 : if (!success)
820 : {
821 2 : if (CPLTestBool(
822 : CPLGetXMLValue(psIter, "optional", "false")))
823 1 : continue;
824 :
825 1 : CPLError(CE_Failure, CPLE_AppDefined,
826 : "Raster has no %s", osValue.c_str());
827 1 : return CE_Failure;
828 : }
829 :
830 9 : oAdditionalArgs.push_back(std::pair<CPLString, CPLString>(
831 9 : osValue, CPLSPrintf("%.18g", dfVal)));
832 9 : CPLDebug("VRT",
833 : "Added builtin pixel function argument %s = %s",
834 : osValue.c_str(), CPLSPrintf("%.18g", dfVal));
835 : }
836 : }
837 : }
838 : }
839 :
840 45 : return CE_None;
841 : }
842 :
843 : /************************************************************************/
844 : /* IRasterIO() */
845 : /************************************************************************/
846 :
847 : /**
848 : * Read/write a region of image data for this band.
849 : *
850 : * Each of the sources for this derived band will be read and passed to
851 : * the derived band pixel function. The pixel function is responsible
852 : * for applying whatever algorithm is necessary to generate this band's
853 : * pixels from the sources.
854 : *
855 : * The sources will be read using the transfer type specified for sources
856 : * using SetSourceTransferType(). If no transfer type has been set for
857 : * this derived band, the band's data type will be used as the transfer type.
858 : *
859 : * @see gdalrasterband
860 : *
861 : * @param eRWFlag Either GF_Read to read a region of data, or GT_Write to
862 : * write a region of data.
863 : *
864 : * @param nXOff The pixel offset to the top left corner of the region
865 : * of the band to be accessed. This would be zero to start from the left side.
866 : *
867 : * @param nYOff The line offset to the top left corner of the region
868 : * of the band to be accessed. This would be zero to start from the top.
869 : *
870 : * @param nXSize The width of the region of the band to be accessed in pixels.
871 : *
872 : * @param nYSize The height of the region of the band to be accessed in lines.
873 : *
874 : * @param pData The buffer into which the data should be read, or from which
875 : * it should be written. This buffer must contain at least nBufXSize *
876 : * nBufYSize words of type eBufType. It is organized in left to right,
877 : * top to bottom pixel order. Spacing is controlled by the nPixelSpace,
878 : * and nLineSpace parameters.
879 : *
880 : * @param nBufXSize The width of the buffer image into which the desired
881 : * region is to be read, or from which it is to be written.
882 : *
883 : * @param nBufYSize The height of the buffer image into which the desired
884 : * region is to be read, or from which it is to be written.
885 : *
886 : * @param eBufType The type of the pixel values in the pData data buffer. The
887 : * pixel values will automatically be translated to/from the GDALRasterBand
888 : * data type as needed.
889 : *
890 : * @param nPixelSpace The byte offset from the start of one pixel value in
891 : * pData to the start of the next pixel value within a scanline. If defaulted
892 : * (0) the size of the datatype eBufType is used.
893 : *
894 : * @param nLineSpace The byte offset from the start of one scanline in
895 : * pData to the start of the next. If defaulted the size of the datatype
896 : * eBufType * nBufXSize is used.
897 : *
898 : * @return CE_Failure if the access fails, otherwise CE_None.
899 : */
900 487 : CPLErr VRTDerivedRasterBand::IRasterIO(
901 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
902 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
903 : GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
904 : {
905 487 : if (eRWFlag == GF_Write)
906 : {
907 1 : CPLError(CE_Failure, CPLE_AppDefined,
908 : "Writing through VRTSourcedRasterBand is not supported.");
909 1 : return CE_Failure;
910 : }
911 :
912 486 : const int nBufTypeSize = GDALGetDataTypeSizeBytes(eBufType);
913 486 : GDALDataType eSrcType = eSourceTransferType;
914 486 : if (eSrcType == GDT_Unknown || eSrcType >= GDT_TypeCount)
915 : {
916 405 : eSrcType = eBufType;
917 : }
918 486 : const int nSrcTypeSize = GDALGetDataTypeSizeBytes(eSrcType);
919 :
920 : /* -------------------------------------------------------------------- */
921 : /* Initialize the buffer to some background value. Use the */
922 : /* nodata value if available. */
923 : /* -------------------------------------------------------------------- */
924 486 : if (SkipBufferInitialization())
925 : {
926 : // Do nothing
927 : }
928 431 : else if (nPixelSpace == nBufTypeSize &&
929 431 : (!m_bNoDataValueSet || m_dfNoDataValue == 0))
930 : {
931 430 : memset(pData, 0,
932 430 : static_cast<size_t>(nBufXSize) * nBufYSize * nBufTypeSize);
933 : }
934 1 : else if (m_bNoDataValueSet)
935 : {
936 1 : double dfWriteValue = m_dfNoDataValue;
937 :
938 51 : for (int iLine = 0; iLine < nBufYSize; iLine++)
939 : {
940 50 : GDALCopyWords(&dfWriteValue, GDT_Float64, 0,
941 50 : static_cast<GByte *>(pData) + nLineSpace * iLine,
942 : eBufType, static_cast<int>(nPixelSpace), nBufXSize);
943 : }
944 : }
945 :
946 : /* -------------------------------------------------------------------- */
947 : /* Do we have overviews that would be appropriate to satisfy */
948 : /* this request? */
949 : /* -------------------------------------------------------------------- */
950 486 : if ((nBufXSize < nXSize || nBufYSize < nYSize) && GetOverviewCount() > 0)
951 : {
952 0 : if (OverviewRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
953 : nBufXSize, nBufYSize, eBufType, nPixelSpace,
954 0 : nLineSpace, psExtraArg) == CE_None)
955 0 : return CE_None;
956 : }
957 :
958 : /* ---- Get pixel function for band ---- */
959 486 : const std::pair<PixelFunc, std::string> *poPixelFunc = nullptr;
960 972 : std::vector<std::pair<CPLString, CPLString>> oAdditionalArgs;
961 :
962 486 : if (EQUAL(m_poPrivate->m_osLanguage, "C"))
963 : {
964 100 : poPixelFunc = VRTDerivedRasterBand::GetPixelFunction(pszFuncName);
965 100 : if (poPixelFunc == nullptr)
966 : {
967 1 : CPLError(CE_Failure, CPLE_IllegalArg,
968 : "VRTDerivedRasterBand::IRasterIO:"
969 : "Derived band pixel function '%s' not registered.",
970 : this->pszFuncName);
971 1 : return CE_Failure;
972 : }
973 :
974 99 : if (poPixelFunc->second != "")
975 : {
976 92 : if (GetPixelFunctionArguments(poPixelFunc->second,
977 46 : oAdditionalArgs) != CE_None)
978 : {
979 1 : return CE_Failure;
980 : }
981 : }
982 : }
983 :
984 : /* TODO: It would be nice to use a MallocBlock function for each
985 : individual buffer that would recycle blocks of memory from a
986 : cache by reassigning blocks that are nearly the same size.
987 : A corresponding FreeBlock might only truly free if the total size
988 : of freed blocks gets to be too great of a percentage of the size
989 : of the allocated blocks. */
990 :
991 : // Get buffers for each source.
992 484 : const int nBufferRadius = m_poPrivate->m_nBufferRadius;
993 484 : if (nBufferRadius > (INT_MAX - nBufXSize) / 2 ||
994 484 : nBufferRadius > (INT_MAX - nBufYSize) / 2)
995 : {
996 0 : return CE_Failure;
997 : }
998 484 : const int nExtBufXSize = nBufXSize + 2 * nBufferRadius;
999 484 : const int nExtBufYSize = nBufYSize + 2 * nBufferRadius;
1000 484 : int nBufferCount = 0;
1001 : void **pBuffers =
1002 484 : static_cast<void **>(CPLMalloc(sizeof(void *) * nSources));
1003 968 : std::vector<int> anMapBufferIdxToSourceIdx(nSources);
1004 651 : for (int iSource = 0; iSource < nSources; iSource++)
1005 : {
1006 175 : if (m_poPrivate->m_bSkipNonContributingSources &&
1007 8 : papoSources[iSource]->IsSimpleSource())
1008 : {
1009 8 : bool bError = false;
1010 : double dfReqXOff, dfReqYOff, dfReqXSize, dfReqYSize;
1011 : int nReqXOff, nReqYOff, nReqXSize, nReqYSize;
1012 : int nOutXOff, nOutYOff, nOutXSize, nOutYSize;
1013 8 : auto poSource =
1014 8 : static_cast<VRTSimpleSource *>(papoSources[iSource]);
1015 8 : if (!poSource->GetSrcDstWindow(
1016 : nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
1017 : &dfReqXOff, &dfReqYOff, &dfReqXSize, &dfReqYSize, &nReqXOff,
1018 : &nReqYOff, &nReqXSize, &nReqYSize, &nOutXOff, &nOutYOff,
1019 : &nOutXSize, &nOutYSize, bError))
1020 : {
1021 4 : if (bError)
1022 : {
1023 0 : for (int i = 0; i < nBufferCount; i++)
1024 : {
1025 0 : VSIFree(pBuffers[i]);
1026 : }
1027 0 : CPLFree(pBuffers);
1028 0 : return CE_Failure;
1029 : }
1030 :
1031 : // Skip non contributing source
1032 4 : continue;
1033 : }
1034 : }
1035 :
1036 163 : anMapBufferIdxToSourceIdx[nBufferCount] = iSource;
1037 326 : pBuffers[nBufferCount] =
1038 163 : VSI_MALLOC3_VERBOSE(nSrcTypeSize, nExtBufXSize, nExtBufYSize);
1039 163 : if (pBuffers[nBufferCount] == nullptr)
1040 : {
1041 0 : for (int i = 0; i < nBufferCount; i++)
1042 : {
1043 0 : VSIFree(pBuffers[i]);
1044 : }
1045 0 : CPLFree(pBuffers);
1046 0 : return CE_Failure;
1047 : }
1048 :
1049 : /* ------------------------------------------------------------ */
1050 : /* #4045: Initialize the newly allocated buffers before handing */
1051 : /* them off to the sources. These buffers are packed, so we */
1052 : /* don't need any special line-by-line handling when a nonzero */
1053 : /* nodata value is set. */
1054 : /* ------------------------------------------------------------ */
1055 163 : if (!m_bNoDataValueSet || m_dfNoDataValue == 0)
1056 : {
1057 160 : memset(pBuffers[nBufferCount], 0,
1058 160 : static_cast<size_t>(nSrcTypeSize) * nExtBufXSize *
1059 160 : nExtBufYSize);
1060 : }
1061 : else
1062 : {
1063 3 : GDALCopyWords(&m_dfNoDataValue, GDT_Float64, 0,
1064 3 : static_cast<GByte *>(pBuffers[nBufferCount]),
1065 : eSrcType, nSrcTypeSize, nExtBufXSize * nExtBufYSize);
1066 : }
1067 :
1068 163 : ++nBufferCount;
1069 : }
1070 :
1071 : // No contributing sources and SkipNonContributingSources mode ?
1072 : // Do not call the pixel function and just return the 0/nodata initialized
1073 : // output buffer.
1074 484 : if (nBufferCount == 0 && m_poPrivate->m_bSkipNonContributingSources)
1075 : {
1076 1 : CPLFree(pBuffers);
1077 1 : return CE_None;
1078 : }
1079 :
1080 : GDALRasterIOExtraArg sExtraArg;
1081 483 : GDALCopyRasterIOExtraArg(&sExtraArg, psExtraArg);
1082 :
1083 483 : int nXShiftInBuffer = 0;
1084 483 : int nYShiftInBuffer = 0;
1085 483 : int nExtBufXSizeReq = nExtBufXSize;
1086 483 : int nExtBufYSizeReq = nExtBufYSize;
1087 :
1088 483 : int nXOffExt = nXOff;
1089 483 : int nYOffExt = nYOff;
1090 483 : int nXSizeExt = nXSize;
1091 483 : int nYSizeExt = nYSize;
1092 :
1093 483 : if (nBufferRadius)
1094 : {
1095 9 : double dfXRatio = static_cast<double>(nXSize) / nBufXSize;
1096 9 : double dfYRatio = static_cast<double>(nYSize) / nBufYSize;
1097 :
1098 9 : if (!sExtraArg.bFloatingPointWindowValidity)
1099 : {
1100 9 : sExtraArg.dfXOff = nXOff;
1101 9 : sExtraArg.dfYOff = nYOff;
1102 9 : sExtraArg.dfXSize = nXSize;
1103 9 : sExtraArg.dfYSize = nYSize;
1104 : }
1105 :
1106 9 : sExtraArg.dfXOff -= dfXRatio * nBufferRadius;
1107 9 : sExtraArg.dfYOff -= dfYRatio * nBufferRadius;
1108 9 : sExtraArg.dfXSize += 2 * dfXRatio * nBufferRadius;
1109 9 : sExtraArg.dfYSize += 2 * dfYRatio * nBufferRadius;
1110 9 : if (sExtraArg.dfXOff < 0)
1111 : {
1112 9 : nXShiftInBuffer = -static_cast<int>(sExtraArg.dfXOff / dfXRatio);
1113 9 : nExtBufXSizeReq -= nXShiftInBuffer;
1114 9 : sExtraArg.dfXSize += sExtraArg.dfXOff;
1115 9 : sExtraArg.dfXOff = 0;
1116 : }
1117 9 : if (sExtraArg.dfYOff < 0)
1118 : {
1119 9 : nYShiftInBuffer = -static_cast<int>(sExtraArg.dfYOff / dfYRatio);
1120 9 : nExtBufYSizeReq -= nYShiftInBuffer;
1121 9 : sExtraArg.dfYSize += sExtraArg.dfYOff;
1122 9 : sExtraArg.dfYOff = 0;
1123 : }
1124 9 : if (sExtraArg.dfXOff + sExtraArg.dfXSize > nRasterXSize)
1125 : {
1126 9 : nExtBufXSizeReq -= static_cast<int>(
1127 9 : (sExtraArg.dfXOff + sExtraArg.dfXSize - nRasterXSize) /
1128 : dfXRatio);
1129 9 : sExtraArg.dfXSize = nRasterXSize - sExtraArg.dfXOff;
1130 : }
1131 9 : if (sExtraArg.dfYOff + sExtraArg.dfYSize > nRasterYSize)
1132 : {
1133 9 : nExtBufYSizeReq -= static_cast<int>(
1134 9 : (sExtraArg.dfYOff + sExtraArg.dfYSize - nRasterYSize) /
1135 : dfYRatio);
1136 9 : sExtraArg.dfYSize = nRasterYSize - sExtraArg.dfYOff;
1137 : }
1138 :
1139 9 : nXOffExt = static_cast<int>(sExtraArg.dfXOff);
1140 9 : nYOffExt = static_cast<int>(sExtraArg.dfYOff);
1141 18 : nXSizeExt = std::min(static_cast<int>(sExtraArg.dfXSize + 0.5),
1142 9 : nRasterXSize - nXOffExt);
1143 18 : nYSizeExt = std::min(static_cast<int>(sExtraArg.dfYSize + 0.5),
1144 9 : nRasterYSize - nYOffExt);
1145 : }
1146 :
1147 : // Load values for sources into packed buffers.
1148 483 : CPLErr eErr = CE_None;
1149 483 : VRTSource::WorkingState oWorkingState;
1150 646 : for (int iBuffer = 0; iBuffer < nBufferCount && eErr == CE_None; iBuffer++)
1151 : {
1152 163 : const int iSource = anMapBufferIdxToSourceIdx[iBuffer];
1153 163 : GByte *pabyBuffer = static_cast<GByte *>(pBuffers[iBuffer]);
1154 163 : eErr = static_cast<VRTSource *>(papoSources[iSource])
1155 326 : ->RasterIO(
1156 : eSrcType, nXOffExt, nYOffExt, nXSizeExt, nYSizeExt,
1157 163 : pabyBuffer +
1158 163 : (nYShiftInBuffer * nExtBufXSize + nXShiftInBuffer) *
1159 : nSrcTypeSize,
1160 : nExtBufXSizeReq, nExtBufYSizeReq, eSrcType, nSrcTypeSize,
1161 163 : static_cast<GSpacing>(nSrcTypeSize) * nExtBufXSize,
1162 163 : &sExtraArg, oWorkingState);
1163 :
1164 : // Extend first lines
1165 172 : for (int iY = 0; iY < nYShiftInBuffer; iY++)
1166 : {
1167 9 : memcpy(pabyBuffer +
1168 9 : static_cast<size_t>(iY) * nExtBufXSize * nSrcTypeSize,
1169 9 : pabyBuffer + static_cast<size_t>(nYShiftInBuffer) *
1170 9 : nExtBufXSize * nSrcTypeSize,
1171 9 : static_cast<size_t>(nExtBufXSize) * nSrcTypeSize);
1172 : }
1173 : // Extend last lines
1174 172 : for (int iY = nYShiftInBuffer + nExtBufYSizeReq; iY < nExtBufYSize;
1175 : iY++)
1176 : {
1177 9 : memcpy(pabyBuffer +
1178 9 : static_cast<size_t>(iY) * nExtBufXSize * nSrcTypeSize,
1179 9 : pabyBuffer + static_cast<size_t>(nYShiftInBuffer +
1180 9 : nExtBufYSizeReq - 1) *
1181 9 : nExtBufXSize * nSrcTypeSize,
1182 9 : static_cast<size_t>(nExtBufXSize) * nSrcTypeSize);
1183 : }
1184 : // Extend first cols
1185 163 : if (nXShiftInBuffer)
1186 : {
1187 1116 : for (int iY = 0; iY < nExtBufYSize; iY++)
1188 : {
1189 2214 : for (int iX = 0; iX < nXShiftInBuffer; iX++)
1190 : {
1191 1107 : memcpy(pabyBuffer +
1192 1107 : static_cast<size_t>(iY * nExtBufXSize + iX) *
1193 1107 : nSrcTypeSize,
1194 1107 : pabyBuffer +
1195 1107 : (static_cast<size_t>(iY) * nExtBufXSize +
1196 1107 : nXShiftInBuffer) *
1197 1107 : nSrcTypeSize,
1198 : nSrcTypeSize);
1199 : }
1200 : }
1201 : }
1202 : // Extent last cols
1203 163 : if (nXShiftInBuffer + nExtBufXSizeReq < nExtBufXSize)
1204 : {
1205 1116 : for (int iY = 0; iY < nExtBufYSize; iY++)
1206 : {
1207 1107 : for (int iX = nXShiftInBuffer + nExtBufXSizeReq;
1208 2214 : iX < nExtBufXSize; iX++)
1209 : {
1210 1107 : memcpy(pabyBuffer +
1211 1107 : (static_cast<size_t>(iY) * nExtBufXSize + iX) *
1212 1107 : nSrcTypeSize,
1213 1107 : pabyBuffer +
1214 1107 : (static_cast<size_t>(iY) * nExtBufXSize +
1215 1107 : nXShiftInBuffer + nExtBufXSizeReq - 1) *
1216 1107 : nSrcTypeSize,
1217 : nSrcTypeSize);
1218 : }
1219 : }
1220 : }
1221 : }
1222 :
1223 : // Apply pixel function.
1224 483 : if (eErr == CE_None && EQUAL(m_poPrivate->m_osLanguage, "Python"))
1225 : {
1226 385 : eErr = CE_Failure;
1227 :
1228 : // numpy doesn't have native cint16/cint32
1229 385 : if (eSrcType == GDT_CInt16 || eSrcType == GDT_CInt32)
1230 : {
1231 2 : CPLError(
1232 : CE_Failure, CPLE_AppDefined,
1233 : "CInt16/CInt32 data type not supported for SourceTransferType");
1234 2 : goto end;
1235 : }
1236 383 : if (eDataType == GDT_CInt16 || eDataType == GDT_CInt32)
1237 : {
1238 4 : CPLError(CE_Failure, CPLE_AppDefined,
1239 : "CInt16/CInt32 data type not supported for data type");
1240 4 : goto end;
1241 : }
1242 :
1243 379 : if (!InitializePython())
1244 15 : goto end;
1245 :
1246 364 : GByte *pabyTmpBuffer = nullptr;
1247 : // Do we need a temporary buffer or can we use directly the output
1248 : // buffer ?
1249 364 : if (nBufferRadius != 0 || eDataType != eBufType ||
1250 24 : nPixelSpace != nBufTypeSize ||
1251 24 : nLineSpace != static_cast<GSpacing>(nBufTypeSize) * nBufXSize)
1252 : {
1253 340 : pabyTmpBuffer = static_cast<GByte *>(VSI_CALLOC_VERBOSE(
1254 : static_cast<size_t>(nExtBufXSize) * nExtBufYSize,
1255 : GDALGetDataTypeSizeBytes(eDataType)));
1256 340 : if (!pabyTmpBuffer)
1257 0 : goto end;
1258 : }
1259 :
1260 : {
1261 : const bool bUseExclusiveLock =
1262 728 : m_poPrivate->m_bExclusiveLock ||
1263 407 : (m_poPrivate->m_bFirstTime &&
1264 43 : m_poPrivate->m_osCode.find("@jit") != std::string::npos);
1265 364 : m_poPrivate->m_bFirstTime = false;
1266 364 : GIL_Holder oHolder(bUseExclusiveLock);
1267 :
1268 : // Prepare target numpy array
1269 : PyObject *poPyDstArray =
1270 364 : GDALCreateNumpyArray(m_poPrivate->m_poGDALCreateNumpyArray,
1271 : pabyTmpBuffer ? pabyTmpBuffer : pData,
1272 : eDataType, nExtBufYSize, nExtBufXSize);
1273 364 : if (!poPyDstArray)
1274 : {
1275 0 : VSIFree(pabyTmpBuffer);
1276 0 : goto end;
1277 : }
1278 :
1279 : // Wrap source buffers as input numpy arrays
1280 364 : PyObject *pyArgInputArray = PyTuple_New(nBufferCount);
1281 393 : for (int i = 0; i < nBufferCount; i++)
1282 : {
1283 29 : GByte *pabyBuffer = static_cast<GByte *>(pBuffers[i]);
1284 58 : PyObject *poPySrcArray = GDALCreateNumpyArray(
1285 29 : m_poPrivate->m_poGDALCreateNumpyArray, pabyBuffer, eSrcType,
1286 : nExtBufYSize, nExtBufXSize);
1287 29 : CPLAssert(poPySrcArray);
1288 29 : PyTuple_SetItem(pyArgInputArray, i, poPySrcArray);
1289 : }
1290 :
1291 : // Create arguments
1292 364 : PyObject *pyArgs = PyTuple_New(10);
1293 364 : PyTuple_SetItem(pyArgs, 0, pyArgInputArray);
1294 364 : PyTuple_SetItem(pyArgs, 1, poPyDstArray);
1295 364 : PyTuple_SetItem(pyArgs, 2, PyLong_FromLong(nXOff));
1296 364 : PyTuple_SetItem(pyArgs, 3, PyLong_FromLong(nYOff));
1297 364 : PyTuple_SetItem(pyArgs, 4, PyLong_FromLong(nXSize));
1298 364 : PyTuple_SetItem(pyArgs, 5, PyLong_FromLong(nYSize));
1299 364 : PyTuple_SetItem(pyArgs, 6, PyLong_FromLong(nRasterXSize));
1300 364 : PyTuple_SetItem(pyArgs, 7, PyLong_FromLong(nRasterYSize));
1301 364 : PyTuple_SetItem(pyArgs, 8, PyLong_FromLong(nBufferRadius));
1302 :
1303 : double adfGeoTransform[6];
1304 364 : adfGeoTransform[0] = 0;
1305 364 : adfGeoTransform[1] = 1;
1306 364 : adfGeoTransform[2] = 0;
1307 364 : adfGeoTransform[3] = 0;
1308 364 : adfGeoTransform[4] = 0;
1309 364 : adfGeoTransform[5] = 1;
1310 364 : if (GetDataset())
1311 364 : GetDataset()->GetGeoTransform(adfGeoTransform);
1312 364 : PyObject *pyGT = PyTuple_New(6);
1313 2548 : for (int i = 0; i < 6; i++)
1314 2184 : PyTuple_SetItem(pyGT, i,
1315 : PyFloat_FromDouble(adfGeoTransform[i]));
1316 364 : PyTuple_SetItem(pyArgs, 9, pyGT);
1317 :
1318 : // Prepare kwargs
1319 364 : PyObject *pyKwargs = PyDict_New();
1320 374 : for (size_t i = 0; i < m_poPrivate->m_oFunctionArgs.size(); ++i)
1321 : {
1322 : const char *pszKey =
1323 10 : m_poPrivate->m_oFunctionArgs[i].first.c_str();
1324 : const char *pszValue =
1325 10 : m_poPrivate->m_oFunctionArgs[i].second.c_str();
1326 10 : PyDict_SetItemString(
1327 : pyKwargs, pszKey,
1328 : PyBytes_FromStringAndSize(pszValue, strlen(pszValue)));
1329 : }
1330 :
1331 : // Call user function
1332 : PyObject *pRetValue =
1333 364 : PyObject_Call(m_poPrivate->m_poUserFunction, pyArgs, pyKwargs);
1334 :
1335 364 : Py_DecRef(pyArgs);
1336 364 : Py_DecRef(pyKwargs);
1337 :
1338 364 : if (ErrOccurredEmitCPLError())
1339 : {
1340 : // do nothing
1341 : }
1342 : else
1343 : {
1344 362 : eErr = CE_None;
1345 : }
1346 364 : if (pRetValue)
1347 362 : Py_DecRef(pRetValue);
1348 : } // End of GIL section
1349 :
1350 364 : if (pabyTmpBuffer)
1351 : {
1352 : // Copy numpy destination array to user buffer
1353 41181 : for (int iY = 0; iY < nBufYSize; iY++)
1354 : {
1355 : size_t nSrcOffset =
1356 40841 : (static_cast<size_t>(iY + nBufferRadius) * nExtBufXSize +
1357 40841 : nBufferRadius) *
1358 40841 : GDALGetDataTypeSizeBytes(eDataType);
1359 40828 : GDALCopyWords(pabyTmpBuffer + nSrcOffset, eDataType,
1360 : GDALGetDataTypeSizeBytes(eDataType),
1361 40832 : static_cast<GByte *>(pData) + iY * nLineSpace,
1362 : eBufType, static_cast<int>(nPixelSpace),
1363 : nBufXSize);
1364 : }
1365 :
1366 340 : VSIFree(pabyTmpBuffer);
1367 : }
1368 : }
1369 98 : else if (eErr == CE_None && poPixelFunc != nullptr)
1370 : {
1371 98 : char **papszArgs = nullptr;
1372 :
1373 98 : oAdditionalArgs.insert(oAdditionalArgs.end(),
1374 98 : m_poPrivate->m_oFunctionArgs.begin(),
1375 196 : m_poPrivate->m_oFunctionArgs.end());
1376 146 : for (const auto &oArg : oAdditionalArgs)
1377 : {
1378 48 : const char *pszKey = oArg.first.c_str();
1379 48 : const char *pszValue = oArg.second.c_str();
1380 48 : papszArgs = CSLSetNameValue(papszArgs, pszKey, pszValue);
1381 : }
1382 :
1383 98 : eErr = (poPixelFunc->first)(
1384 : static_cast<void **>(pBuffers), nBufferCount, pData, nBufXSize,
1385 : nBufYSize, eSrcType, eBufType, static_cast<int>(nPixelSpace),
1386 : static_cast<int>(nLineSpace), papszArgs);
1387 :
1388 98 : CSLDestroy(papszArgs);
1389 : }
1390 0 : end:
1391 : // Release buffers.
1392 646 : for (int iBuffer = 0; iBuffer < nBufferCount; iBuffer++)
1393 : {
1394 163 : VSIFree(pBuffers[iBuffer]);
1395 : }
1396 483 : CPLFree(pBuffers);
1397 :
1398 483 : return eErr;
1399 : }
1400 :
1401 : /************************************************************************/
1402 : /* IGetDataCoverageStatus() */
1403 : /************************************************************************/
1404 :
1405 1 : int VRTDerivedRasterBand::IGetDataCoverageStatus(
1406 : int /* nXOff */, int /* nYOff */, int /* nXSize */, int /* nYSize */,
1407 : int /* nMaskFlagStop */, double *pdfDataPct)
1408 : {
1409 1 : if (pdfDataPct != nullptr)
1410 0 : *pdfDataPct = -1.0;
1411 : return GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED |
1412 1 : GDAL_DATA_COVERAGE_STATUS_DATA;
1413 : }
1414 :
1415 : /************************************************************************/
1416 : /* XMLInit() */
1417 : /************************************************************************/
1418 :
1419 162 : CPLErr VRTDerivedRasterBand::XMLInit(
1420 : const CPLXMLNode *psTree, const char *pszVRTPath,
1421 : std::map<CPLString, GDALDataset *> &oMapSharedSources)
1422 :
1423 : {
1424 : const CPLErr eErr =
1425 162 : VRTSourcedRasterBand::XMLInit(psTree, pszVRTPath, oMapSharedSources);
1426 162 : if (eErr != CE_None)
1427 0 : return eErr;
1428 :
1429 : // Read derived pixel function type.
1430 162 : SetPixelFunctionName(CPLGetXMLValue(psTree, "PixelFunctionType", nullptr));
1431 162 : if (pszFuncName == nullptr || EQUAL(pszFuncName, ""))
1432 : {
1433 1 : CPLError(CE_Failure, CPLE_AppDefined, "PixelFunctionType missing");
1434 1 : return CE_Failure;
1435 : }
1436 :
1437 161 : m_poPrivate->m_osLanguage =
1438 161 : CPLGetXMLValue(psTree, "PixelFunctionLanguage", "C");
1439 228 : if (!EQUAL(m_poPrivate->m_osLanguage, "C") &&
1440 67 : !EQUAL(m_poPrivate->m_osLanguage, "Python"))
1441 : {
1442 1 : CPLError(CE_Failure, CPLE_NotSupported,
1443 : "Unsupported PixelFunctionLanguage");
1444 1 : return CE_Failure;
1445 : }
1446 :
1447 160 : m_poPrivate->m_osCode = CPLGetXMLValue(psTree, "PixelFunctionCode", "");
1448 197 : if (!m_poPrivate->m_osCode.empty() &&
1449 37 : !EQUAL(m_poPrivate->m_osLanguage, "Python"))
1450 : {
1451 1 : CPLError(CE_Failure, CPLE_NotSupported,
1452 : "PixelFunctionCode can only be used with Python");
1453 1 : return CE_Failure;
1454 : }
1455 :
1456 159 : m_poPrivate->m_nBufferRadius =
1457 159 : atoi(CPLGetXMLValue(psTree, "BufferRadius", "0"));
1458 159 : if (m_poPrivate->m_nBufferRadius < 0 || m_poPrivate->m_nBufferRadius > 1024)
1459 : {
1460 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for BufferRadius");
1461 1 : return CE_Failure;
1462 : }
1463 169 : if (m_poPrivate->m_nBufferRadius != 0 &&
1464 11 : !EQUAL(m_poPrivate->m_osLanguage, "Python"))
1465 : {
1466 1 : CPLError(CE_Failure, CPLE_NotSupported,
1467 : "BufferRadius can only be used with Python");
1468 1 : return CE_Failure;
1469 : }
1470 :
1471 : const CPLXMLNode *const psArgs =
1472 157 : CPLGetXMLNode(psTree, "PixelFunctionArguments");
1473 157 : if (psArgs != nullptr)
1474 : {
1475 90 : for (const CPLXMLNode *psIter = psArgs->psChild; psIter;
1476 58 : psIter = psIter->psNext)
1477 : {
1478 58 : if (psIter->eType == CXT_Attribute)
1479 : {
1480 58 : m_poPrivate->m_oFunctionArgs.push_back(
1481 58 : std::pair<CPLString, CPLString>(psIter->pszValue,
1482 58 : psIter->psChild->pszValue));
1483 : }
1484 : }
1485 : }
1486 :
1487 : // Read optional source transfer data type.
1488 : const char *pszTypeName =
1489 157 : CPLGetXMLValue(psTree, "SourceTransferType", nullptr);
1490 157 : if (pszTypeName != nullptr)
1491 : {
1492 74 : eSourceTransferType = GDALGetDataTypeByName(pszTypeName);
1493 : }
1494 :
1495 : // Whether to skip non contributing sources
1496 : const char *pszSkipNonContributiongSources =
1497 157 : CPLGetXMLValue(psTree, "SkipNonContributingSources", nullptr);
1498 157 : if (pszSkipNonContributiongSources)
1499 : {
1500 2 : m_poPrivate->m_bSkipNonContributingSourcesSpecified = true;
1501 4 : m_poPrivate->m_bSkipNonContributingSources =
1502 2 : CPLTestBool(pszSkipNonContributiongSources);
1503 : }
1504 :
1505 157 : return CE_None;
1506 : }
1507 :
1508 : /************************************************************************/
1509 : /* SerializeToXML() */
1510 : /************************************************************************/
1511 :
1512 7 : CPLXMLNode *VRTDerivedRasterBand::SerializeToXML(const char *pszVRTPath,
1513 : bool &bHasWarnedAboutRAMUsage,
1514 : size_t &nAccRAMUsage)
1515 : {
1516 7 : CPLXMLNode *psTree = VRTSourcedRasterBand::SerializeToXML(
1517 : pszVRTPath, bHasWarnedAboutRAMUsage, nAccRAMUsage);
1518 :
1519 : /* -------------------------------------------------------------------- */
1520 : /* Set subclass. */
1521 : /* -------------------------------------------------------------------- */
1522 7 : CPLCreateXMLNode(CPLCreateXMLNode(psTree, CXT_Attribute, "subClass"),
1523 : CXT_Text, "VRTDerivedRasterBand");
1524 :
1525 : /* ---- Encode DerivedBand-specific fields ---- */
1526 7 : if (!EQUAL(m_poPrivate->m_osLanguage, "C"))
1527 : {
1528 5 : CPLSetXMLValue(psTree, "PixelFunctionLanguage",
1529 5 : m_poPrivate->m_osLanguage);
1530 : }
1531 7 : if (pszFuncName != nullptr && strlen(pszFuncName) > 0)
1532 6 : CPLSetXMLValue(psTree, "PixelFunctionType", pszFuncName);
1533 7 : if (!m_poPrivate->m_oFunctionArgs.empty())
1534 : {
1535 : CPLXMLNode *psArgs =
1536 1 : CPLCreateXMLNode(psTree, CXT_Element, "PixelFunctionArguments");
1537 3 : for (size_t i = 0; i < m_poPrivate->m_oFunctionArgs.size(); ++i)
1538 : {
1539 2 : const char *pszKey = m_poPrivate->m_oFunctionArgs[i].first.c_str();
1540 : const char *pszValue =
1541 2 : m_poPrivate->m_oFunctionArgs[i].second.c_str();
1542 2 : CPLCreateXMLNode(CPLCreateXMLNode(psArgs, CXT_Attribute, pszKey),
1543 : CXT_Text, pszValue);
1544 : }
1545 : }
1546 7 : if (!m_poPrivate->m_osCode.empty())
1547 : {
1548 4 : if (m_poPrivate->m_osCode.find("<![CDATA[") == std::string::npos)
1549 : {
1550 4 : CPLCreateXMLNode(
1551 : CPLCreateXMLNode(psTree, CXT_Element, "PixelFunctionCode"),
1552 : CXT_Literal,
1553 8 : ("<![CDATA[" + m_poPrivate->m_osCode + "]]>").c_str());
1554 : }
1555 : else
1556 : {
1557 0 : CPLSetXMLValue(psTree, "PixelFunctionCode", m_poPrivate->m_osCode);
1558 : }
1559 : }
1560 7 : if (m_poPrivate->m_nBufferRadius != 0)
1561 1 : CPLSetXMLValue(psTree, "BufferRadius",
1562 1 : CPLSPrintf("%d", m_poPrivate->m_nBufferRadius));
1563 7 : if (this->eSourceTransferType != GDT_Unknown)
1564 2 : CPLSetXMLValue(psTree, "SourceTransferType",
1565 : GDALGetDataTypeName(eSourceTransferType));
1566 :
1567 7 : if (m_poPrivate->m_bSkipNonContributingSourcesSpecified)
1568 : {
1569 1 : CPLSetXMLValue(psTree, "SkipNonContributingSources",
1570 1 : m_poPrivate->m_bSkipNonContributingSources ? "true"
1571 : : "false");
1572 : }
1573 :
1574 7 : return psTree;
1575 : }
1576 :
1577 : /************************************************************************/
1578 : /* GetMinimum() */
1579 : /************************************************************************/
1580 :
1581 5 : double VRTDerivedRasterBand::GetMinimum(int *pbSuccess)
1582 : {
1583 5 : return GDALRasterBand::GetMinimum(pbSuccess);
1584 : }
1585 :
1586 : /************************************************************************/
1587 : /* GetMaximum() */
1588 : /************************************************************************/
1589 :
1590 5 : double VRTDerivedRasterBand::GetMaximum(int *pbSuccess)
1591 : {
1592 5 : return GDALRasterBand::GetMaximum(pbSuccess);
1593 : }
1594 :
1595 : /************************************************************************/
1596 : /* ComputeRasterMinMax() */
1597 : /************************************************************************/
1598 :
1599 1 : CPLErr VRTDerivedRasterBand::ComputeRasterMinMax(int bApproxOK,
1600 : double *adfMinMax)
1601 : {
1602 1 : return GDALRasterBand::ComputeRasterMinMax(bApproxOK, adfMinMax);
1603 : }
1604 :
1605 : /************************************************************************/
1606 : /* ComputeStatistics() */
1607 : /************************************************************************/
1608 :
1609 1 : CPLErr VRTDerivedRasterBand::ComputeStatistics(int bApproxOK, double *pdfMin,
1610 : double *pdfMax, double *pdfMean,
1611 : double *pdfStdDev,
1612 : GDALProgressFunc pfnProgress,
1613 : void *pProgressData)
1614 :
1615 : {
1616 1 : return GDALRasterBand::ComputeStatistics(bApproxOK, pdfMin, pdfMax, pdfMean,
1617 : pdfStdDev, pfnProgress,
1618 1 : pProgressData);
1619 : }
1620 :
1621 : /************************************************************************/
1622 : /* GetHistogram() */
1623 : /************************************************************************/
1624 :
1625 1 : CPLErr VRTDerivedRasterBand::GetHistogram(double dfMin, double dfMax,
1626 : int nBuckets, GUIntBig *panHistogram,
1627 : int bIncludeOutOfRange, int bApproxOK,
1628 : GDALProgressFunc pfnProgress,
1629 : void *pProgressData)
1630 :
1631 : {
1632 1 : return VRTRasterBand::GetHistogram(dfMin, dfMax, nBuckets, panHistogram,
1633 : bIncludeOutOfRange, bApproxOK,
1634 1 : pfnProgress, pProgressData);
1635 : }
1636 :
1637 : /*! @endcond */
|