Coverage Report

Created: 2026-08-11 08:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/wms/minidriver_tiled_wms.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  tiledWMS Client Driver
4
 * Purpose:  Implementation of the OnEarth Tiled WMS minidriver.
5
 *           http://onearth.jpl.nasa.gov/tiled.html
6
 * Author:   Lucian Plesea (Lucian dot Plesea at jpl.nasa.gov)
7
 *           Adam Nowacki
8
 *
9
 ******************************************************************************
10
 * Copyright (c) 2007, Adam Nowacki
11
 * Copyright (c) 2011-2012, Even Rouault <even dot rouault at spatialys.com>
12
 *
13
 * SPDX-License-Identifier: MIT
14
 ****************************************************************************/
15
16
//
17
// Also known as the OnEarth tile protocol
18
//
19
// A few open options are supported by tiled WMS
20
//
21
// TiledGroupName=<Name>
22
//
23
//  This option is only valid when the WMS file does not contain a
24
//  TiledGroupName. The name value should match exactly the name declared by the
25
//  server, including possible white spaces, otherwise the open will fail.
26
//
27
// Change=<key>:<value>
28
//
29
//  If the tiled group selected supports the change key, this option will set
30
//  the value The <key> here does not include the brackets present in the
31
//  GetTileService For example, if a TiledPattern include a key of ${time}, the
32
//  matching open option will be Change=time:<YYYY-MM-DD> The Change open option
33
//  may be present multiple times, with different keys If a key is not supported
34
//  by the selected TilePattern, the open will fail Alternate syntax is:
35
//  Change=<key>=<value>
36
//
37
// StoreConfiguration=Yes
38
//
39
//  This boolean option is only useful when doing a createcopy of a tiledWMS
40
//  dataset into another tiledWMS dataset. When set, the source tiledWMS will
41
//  store the server configuration into the XML metadata representation, which
42
//  then gets copied to the XML output. This will eliminate the need to fetch
43
//  the server configuration when opening the output datafile
44
//
45
46
#include "wmsdriver.h"
47
#include "minidriver_tiled_wms.h"
48
49
#include "gdal_colortable.h"
50
#include "gdal_cpp_functions.h"
51
52
#include <set>
53
54
static const char SIG[] = "GDAL_WMS TiledWMS: ";
55
56
/*
57
 *\brief Read a number from an xml element
58
 */
59
60
static double getXMLNum(const CPLXMLNode *poRoot, const char *pszPath,
61
                        const char *pszDefault)
62
0
{  // Sets errno
63
0
    return CPLAtof(CPLGetXMLValue(poRoot, pszPath, pszDefault));
64
0
}
65
66
/*
67
 *\brief Read a ColorEntry XML node, return a GDALColorEntry structure
68
 *
69
 */
70
71
static GDALColorEntry GetXMLColorEntry(const CPLXMLNode *p)
72
0
{
73
0
    GDALColorEntry ce;
74
0
    ce.c1 = static_cast<short>(getXMLNum(p, "c1", "0"));
75
0
    ce.c2 = static_cast<short>(getXMLNum(p, "c2", "0"));
76
0
    ce.c3 = static_cast<short>(getXMLNum(p, "c3", "0"));
77
0
    ce.c4 = static_cast<short>(getXMLNum(p, "c4", "255"));
78
0
    return ce;
79
0
}
80
81
/************************************************************************/
82
/*                         SearchXMLSiblings()                          */
83
/************************************************************************/
84
85
/*
86
 * \brief Search for a sibling of the root node with a given name.
87
 *
88
 * Searches only the next siblings of the node passed in for the named element
89
 * or attribute. If the first character of the pszElement is '=', the search
90
 * includes the psRoot node
91
 *
92
 * @param psRoot the root node to search.  This should be a node of type
93
 * CXT_Element.  NULL is safe.
94
 *
95
 * @param pszElement the name of the element or attribute to search for.
96
 *
97
 *
98
 * @return The first matching node or NULL on failure.
99
 */
100
101
static const CPLXMLNode *SearchXMLSiblings(const CPLXMLNode *psRoot,
102
                                           const char *pszElement)
103
104
0
{
105
0
    if (psRoot == nullptr || pszElement == nullptr)
106
0
        return nullptr;
107
108
    // If the strings starts with '=', skip it and test the root
109
    // If not, start testing with the next sibling
110
0
    if (pszElement[0] == '=')
111
0
        pszElement++;
112
0
    else
113
0
        psRoot = psRoot->psNext;
114
115
0
    for (; psRoot != nullptr; psRoot = psRoot->psNext)
116
0
    {
117
0
        if ((psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute) &&
118
0
            EQUAL(pszElement, psRoot->pszValue))
119
0
            return psRoot;
120
0
    }
121
0
    return nullptr;
122
0
}
123
124
/************************************************************************/
125
/*                        SearchLeafGroupName()                         */
126
/************************************************************************/
127
128
/*
129
 * \brief Search for a leaf TileGroup node by name.
130
 *
131
 * @param psRoot the root node to search.  This should be a node of type
132
 * CXT_Element.  NULL is safe.
133
 *
134
 * @param pszElement the name of the TileGroup to search for.
135
 *
136
 * @return The XML node of the matching TileGroup or NULL on failure.
137
 */
138
139
static CPLXMLNode *SearchLeafGroupName(CPLXMLNode *psRoot, const char *name)
140
141
0
{
142
0
    if (psRoot == nullptr || name == nullptr)
143
0
        return nullptr;
144
145
    // Has to be a leaf TileGroup with the right name
146
0
    if (nullptr == SearchXMLSiblings(psRoot->psChild, "TiledGroup"))
147
0
    {
148
0
        if (EQUAL(name, CPLGetXMLValue(psRoot, "Name", "")))
149
0
            return psRoot;
150
0
    }
151
0
    else
152
0
    {  // Is metagroup, try children then siblings
153
0
        CPLXMLNode *ret = SearchLeafGroupName(psRoot->psChild, name);
154
0
        if (nullptr != ret)
155
0
            return ret;
156
0
    }
157
0
    return SearchLeafGroupName(psRoot->psNext, name);
158
0
}
159
160
/************************************************************************/
161
/*                             BandInterp()                             */
162
/************************************************************************/
163
164
/*
165
 * \brief Utility function to calculate color band interpretation.
166
 * Only handles Gray, GrayAlpha, RGB and RGBA, based on total band count
167
 *
168
 * @param nbands is the total number of bands in the image
169
 *
170
 * @param band is the band number, starting with 1
171
 *
172
 * @return GDALColorInterp of the band
173
 */
174
175
static GDALColorInterp BandInterp(int nbands, int band)
176
0
{
177
0
    switch (nbands)
178
0
    {
179
0
        case 1:
180
0
            return GCI_GrayIndex;
181
0
        case 2:
182
0
            return band == 1 ? GCI_GrayIndex : GCI_AlphaBand;
183
0
        case 3:  // RGB
184
0
        case 4:  // RBGA
185
0
            if (band < 3)
186
0
                return band == 1 ? GCI_RedBand : GCI_GreenBand;
187
0
            return band == 3 ? GCI_BlueBand : GCI_AlphaBand;
188
0
        default:
189
0
            return GCI_Undefined;
190
0
    }
191
0
}
192
193
/************************************************************************/
194
/*                              FindBbox()                              */
195
/************************************************************************/
196
197
/*
198
 * \brief Utility function to find the position of the bbox parameter value
199
 * within a request string.  The search for the bbox is case insensitive
200
 *
201
 * @param in, the string to search into
202
 *
203
 * @return The position from the beginning of the string or -1 if not found
204
 */
205
206
static int FindBbox(CPLString in)
207
0
{
208
209
0
    size_t pos = in.ifind("&bbox=");
210
0
    if (pos == std::string::npos)
211
0
        return -1;
212
0
    return static_cast<int>(pos) + 6;
213
0
}
214
215
/************************************************************************/
216
/*                         FindChangePattern()                          */
217
/************************************************************************/
218
219
/*
220
 * \brief Build the right request pattern based on the change request list
221
 * It only gets called on initialization
222
 * @param cdata, possible request strings, white space separated
223
 * @param substs, the list of substitutions to be applied
224
 * @param keys, the list of available substitution keys
225
 * @param ret The return value, a matching request or an empty string
226
 */
227
228
static void FindChangePattern(const char *cdata, const char *const *substs,
229
                              const char *const *keys, CPLString &ret)
230
0
{
231
0
    const CPLStringList aosTokens(CSLTokenizeString2(
232
0
        cdata, " \t\n\r", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
233
0
    ret.clear();
234
235
0
    int matchcount = CSLCount(substs);
236
0
    int keycount = CSLCount(keys);
237
0
    if (keycount < matchcount)
238
0
    {
239
0
        return;
240
0
    }
241
242
    // A valid string has only the keys in the substs list and none other
243
0
    for (int j = 0; j < aosTokens.size(); j++)
244
0
    {
245
0
        ret = aosTokens[j];  // The target string
246
0
        bool matches = true;
247
248
0
        for (int k = 0; k < keycount && keys != nullptr; k++)
249
0
        {
250
0
            const char *key = keys[k];
251
0
            int sub_number = CSLPartialFindString(substs, key);
252
0
            if (sub_number != -1)
253
0
            {  // It is a listed match
254
                // But is the match for the key position?
255
0
                char *found_key = nullptr;
256
0
                const char *found_value =
257
0
                    CPLParseNameValue(substs[sub_number], &found_key);
258
0
                if (found_key != nullptr && EQUAL(found_key, key))
259
0
                {  // Should exist in the request
260
0
                    if (std::string::npos == ret.find(key))
261
0
                        matches = false;
262
0
                    if (matches)
263
                        // Execute the substitution on the "ret" string
264
0
                        URLSearchAndReplace(&ret, key, "%s", found_value);
265
0
                }
266
0
                else
267
0
                {
268
0
                    matches = false;
269
0
                }
270
0
                CPLFree(found_key);
271
0
            }
272
0
            else
273
0
            {  // Key not in the subst list, should not match
274
0
                if (std::string::npos != ret.find(key))
275
0
                    matches = false;
276
0
            }
277
0
        }  // Key loop
278
0
        if (matches)
279
0
        {
280
0
            return;  // We got the string ready, all keys accounted for and
281
                     // substs applied
282
0
        }
283
0
    }
284
0
    ret.clear();
285
0
}
286
287
0
WMSMiniDriver_TiledWMS::WMSMiniDriver_TiledWMS() = default;
288
289
0
WMSMiniDriver_TiledWMS::~WMSMiniDriver_TiledWMS() = default;
290
291
// Returns the scale of a WMS request as compared to the base resolution
292
double WMSMiniDriver_TiledWMS::Scale(const char *request) const
293
0
{
294
0
    int bbox = FindBbox(request);
295
0
    if (bbox < 0)
296
0
        return 0;
297
0
    double x, y, X, Y;
298
0
    CPLsscanf(request + bbox, "%lf,%lf,%lf,%lf", &x, &y, &X, &Y);
299
0
    return (m_data_window.m_x1 - m_data_window.m_x0) / (X - x) * m_bsx /
300
0
           m_data_window.m_sx;
301
0
}
302
303
// Finds, extracts, and returns the highest resolution request string from a
304
// list, starting at item i
305
CPLString WMSMiniDriver_TiledWMS::GetLowestScale(CPLStringList &list,
306
                                                 int i) const
307
0
{
308
0
    CPLString req;
309
0
    double scale = -1;
310
0
    int position = -1;
311
0
    while (nullptr != list[i])
312
0
    {
313
0
        double tscale = Scale(list[i]);
314
0
        if (tscale >= scale)
315
0
        {
316
0
            scale = tscale;
317
0
            position = i;
318
0
        }
319
0
        i++;
320
0
    }
321
0
    if (position > -1)
322
0
    {
323
0
        req = list[position];
324
0
        list.Assign(CSLRemoveStrings(list.StealList(), position, 1, nullptr),
325
0
                    true);
326
0
    }
327
0
    return req;
328
0
}
329
330
/*
331
 *\Brief Initialize minidriver with info from the server
332
 */
333
334
CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config,
335
                                          CPL_UNUSED char **OpenOptions)
336
0
{
337
0
    CPLErr ret = CE_None;
338
0
    CPLXMLTreeCloser tileServiceConfig(nullptr);
339
0
    const CPLXMLNode *TG = nullptr;
340
341
0
    CPLStringList requests;
342
0
    CPLStringList substs;
343
0
    CPLStringList keys;
344
0
    CPLStringList changes;
345
346
0
    try
347
0
    {  // Parse info from the WMS Service node
348
        //        m_end_url = CPLGetXMLValue(config, "AdditionalArgs", "");
349
0
        m_base_url = CPLGetXMLValue(config, "ServerURL", "");
350
351
0
        if (m_base_url.empty())
352
0
            throw CPLOPrintf("%s ServerURL missing.", SIG);
353
354
0
        CPLString tiledGroupName(
355
0
            CSLFetchNameValueDef(OpenOptions, "TiledGroupName", ""));
356
0
        tiledGroupName =
357
0
            CPLGetXMLValue(config, "TiledGroupName", tiledGroupName);
358
0
        if (tiledGroupName.empty())
359
0
            throw CPLOPrintf("%s TiledGroupName missing.", SIG);
360
361
        // Change strings, key is an attribute, value is the value of the Change
362
        // node Multiple keys are possible
363
364
        // First process the changes from open options, if present
365
0
        changes = CSLFetchNameValueMultiple(OpenOptions, "Change");
366
        // Transfer them to subst list
367
0
        for (int i = 0; i < changes.size(); i++)
368
0
        {
369
0
            char *key = nullptr;
370
0
            const char *value = CPLParseNameValue(changes[i], &key);
371
            // Add the ${} around the key
372
0
            if (value != nullptr && key != nullptr)
373
0
                substs.SetNameValue(CPLOPrintf("${%s}", key), value);
374
0
            CPLFree(key);
375
0
        }
376
377
        // Then process the configuration file itself
378
0
        const CPLXMLNode *nodeChange = CPLSearchXMLNode(config, "Change");
379
0
        while (nodeChange != nullptr)
380
0
        {
381
0
            CPLString key = CPLGetXMLValue(nodeChange, "key", "");
382
0
            if (key.empty())
383
0
                throw CPLOPrintf(
384
0
                    "%s Change element needs a non-empty \"key\" attribute",
385
0
                    SIG);
386
0
            substs.SetNameValue(key, CPLGetXMLValue(nodeChange, "", ""));
387
0
            nodeChange = SearchXMLSiblings(nodeChange, "Change");
388
0
        }
389
390
0
        m_parent_dataset->SetMetadataItem("ServerURL", m_base_url, nullptr);
391
0
        m_parent_dataset->SetMetadataItem("TiledGroupName", tiledGroupName,
392
0
                                          nullptr);
393
0
        for (const char *subst : substs)
394
0
            m_parent_dataset->SetMetadataItem("Change", subst, nullptr);
395
396
0
        const char *pszConfiguration =
397
0
            CPLGetXMLValue(config, "Configuration", nullptr);
398
0
        CPLString decodedGTS;
399
400
0
        if (pszConfiguration)
401
0
        {  // Probably XML encoded because it is XML itself
402
            // The copy will be replaced by the decoded result
403
0
            decodedGTS = pszConfiguration;
404
0
            WMSUtilDecode(decodedGTS,
405
0
                          CPLGetXMLValue(config, "Configuration.encoding", ""));
406
0
        }
407
0
        else
408
0
        {  // Not local, use the WMSdriver to fetch the server config
409
0
            CPLString getTileServiceUrl = m_base_url + "request=GetTileService";
410
411
            // This returns a string managed by the cfg cache, do not free
412
0
            const char *pszTmp = GDALWMSDataset::GetServerConfig(
413
0
                getTileServiceUrl,
414
0
                const_cast<char **>(m_parent_dataset->GetHTTPRequestOpts()));
415
0
            decodedGTS = pszTmp ? pszTmp : "";
416
417
0
            if (decodedGTS.empty())
418
0
                throw CPLOPrintf("%s Can't fetch server GetTileService", SIG);
419
0
        }
420
421
        // decodedGTS contains the GetTileService return now
422
0
        tileServiceConfig.reset(CPLParseXMLString(decodedGTS));
423
0
        if (!tileServiceConfig)
424
0
            throw CPLOPrintf("%s Error parsing the GetTileService response",
425
0
                             SIG);
426
427
0
        if (nullptr ==
428
0
            (TG = CPLSearchXMLNode(tileServiceConfig.get(), "TiledPatterns")))
429
0
            throw CPLOPrintf(
430
0
                "%s Can't locate TiledPatterns in server response.", SIG);
431
432
        // Get the global base_url and bounding box, these can be overwritten at
433
        // the tileGroup level They are just pointers into existing structures,
434
        // cleanup is not required
435
0
        const char *global_base_url =
436
0
            CPLGetXMLValue(tileServiceConfig.get(),
437
0
                           "TiledPatterns.OnlineResource.xlink:href", "");
438
0
        const CPLXMLNode *global_latlonbbox = CPLGetXMLNode(
439
0
            tileServiceConfig.get(), "TiledPatterns.LatLonBoundingBox");
440
0
        const CPLXMLNode *global_bbox =
441
0
            CPLGetXMLNode(tileServiceConfig.get(), "TiledPatterns.BoundingBox");
442
0
        const char *pszProjection = CPLGetXMLValue(
443
0
            tileServiceConfig.get(), "TiledPatterns.Projection", "");
444
0
        if (pszProjection[0] != 0)
445
0
            m_oSRS.SetFromUserInput(
446
0
                pszProjection,
447
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
448
449
0
        if (nullptr == (TG = SearchLeafGroupName(TG->psChild, tiledGroupName)))
450
0
            throw CPLOPrintf("%s No TiledGroup "
451
0
                             "%s"
452
0
                             " in server response.",
453
0
                             SIG, tiledGroupName.c_str());
454
455
0
        int band_count = atoi(CPLGetXMLValue(TG, "Bands", "3"));
456
457
0
        if (!GDALCheckBandCount(band_count, FALSE))
458
0
            throw CPLOPrintf("%s Invalid number of bands in server response",
459
0
                             SIG);
460
461
0
        if (nullptr != CPLGetXMLNode(TG, "Key"))
462
0
        {  // Collect all keys defined by this tileset
463
0
            const CPLXMLNode *node = CPLGetXMLNode(TG, "Key");
464
0
            while (nullptr != node)
465
0
            {  // the TEXT of the Key node
466
0
                const char *val = CPLGetXMLValue(node, nullptr, nullptr);
467
0
                if (nullptr != val)
468
0
                    keys.AddString(val);
469
0
                node = SearchXMLSiblings(node, "Key");
470
0
            }
471
0
        }
472
473
        // Data values are attributes, they include NoData Min and Max
474
0
        if (nullptr != CPLGetXMLNode(TG, "DataValues"))
475
0
        {
476
0
            const char *nodata =
477
0
                CPLGetXMLValue(TG, "DataValues.NoData", nullptr);
478
0
            if (nodata != nullptr)
479
0
            {
480
0
                m_parent_dataset->WMSSetNoDataValue(nodata);
481
0
                m_parent_dataset->SetTileOO("@NDV", nodata);
482
0
            }
483
0
            const char *min = CPLGetXMLValue(TG, "DataValues.min", nullptr);
484
0
            if (min != nullptr)
485
0
                m_parent_dataset->WMSSetMinValue(min);
486
0
            const char *max = CPLGetXMLValue(TG, "DataValues.max", nullptr);
487
0
            if (max != nullptr)
488
0
                m_parent_dataset->WMSSetMaxValue(max);
489
0
        }
490
491
0
        m_parent_dataset->WMSSetBandsCount(band_count);
492
0
        GDALDataType dt =
493
0
            GDALGetDataTypeByName(CPLGetXMLValue(TG, "DataType", "Byte"));
494
0
        m_parent_dataset->WMSSetDataType(dt);
495
0
        if (dt != GDT_UInt8)
496
0
            m_parent_dataset->SetTileOO("@DATATYPE", GDALGetDataTypeName(dt));
497
        // Let the TiledGroup override the projection
498
0
        pszProjection = CPLGetXMLValue(TG, "Projection", "");
499
0
        if (pszProjection[0] != 0)
500
0
            m_oSRS = ProjToSRS(pszProjection);
501
502
0
        m_base_url =
503
0
            CPLGetXMLValue(TG, "OnlineResource.xlink:href", global_base_url);
504
0
        if (m_base_url[0] == '\0')
505
0
            throw CPLOPrintf(
506
0
                "%s Can't locate OnlineResource in the server response", SIG);
507
508
        // Bounding box, local, global, local lat-lon, global lat-lon, in this
509
        // order
510
0
        const CPLXMLNode *bbox = CPLGetXMLNode(TG, "BoundingBox");
511
0
        if (nullptr == bbox)
512
0
            bbox = global_bbox;
513
0
        if (nullptr == bbox)
514
0
            bbox = CPLGetXMLNode(TG, "LatLonBoundingBox");
515
0
        if (nullptr == bbox)
516
0
            bbox = global_latlonbbox;
517
0
        if (nullptr == bbox)
518
0
            throw CPLOPrintf(
519
0
                "%s Can't locate the LatLonBoundingBox in server response",
520
0
                SIG);
521
522
        // Check for errors during conversion
523
0
        errno = 0;
524
0
        int err = 0;
525
0
        m_data_window.m_x0 = getXMLNum(bbox, "minx", "0");
526
0
        err |= errno;
527
0
        m_data_window.m_x1 = getXMLNum(bbox, "maxx", "-1");
528
0
        err |= errno;
529
0
        m_data_window.m_y0 = getXMLNum(bbox, "maxy", "0");
530
0
        err |= errno;
531
0
        m_data_window.m_y1 = getXMLNum(bbox, "miny", "-1");
532
0
        err |= errno;
533
0
        if (err)
534
0
            throw CPLOPrintf("%s Can't parse LatLonBoundingBox", SIG);
535
536
0
        if ((m_data_window.m_x1 - m_data_window.m_x0) <= 0 ||
537
0
            (m_data_window.m_y0 - m_data_window.m_y1) <= 0)
538
0
            throw CPLOPrintf(
539
0
                "%s Coordinate order in BBox problem in server response", SIG);
540
541
        // Is there a palette?
542
        //
543
        // Format is
544
        // <Palette>
545
        //   <Size>N</Size> : Optional
546
        //   <Model>RGBA|RGB</Model> : Optional, defaults to RGB
547
        //   <Entry idx=i c1=v1 c2=v2 c3=v3 c4=v4/> :Optional
548
        //   <Entry .../>
549
        // </Palette>
550
        // the idx attribute is optional, it autoincrements
551
        // The entries are vertices, interpolation takes place in between if the
552
        // indices are not successive index values have to be in increasing
553
        // order The palette starts initialized with zeros
554
        //
555
556
0
        bool bHasColorTable = false;
557
558
0
        if ((band_count == 1) && CPLGetXMLNode(TG, "Palette"))
559
0
        {
560
0
            const CPLXMLNode *node = CPLGetXMLNode(TG, "Palette");
561
562
0
            int entries = static_cast<int>(getXMLNum(node, "Size", "255"));
563
0
            GDALPaletteInterp eInterp = GPI_RGB;  // RGB and RGBA are the same
564
565
0
            CPLString pModel = CPLGetXMLValue(node, "Model", "RGB");
566
0
            if (!pModel.empty() && pModel.find("RGB") == std::string::npos)
567
0
                throw CPLOPrintf(
568
0
                    "%s Palette Model %s is unknown, use RGB or RGBA", SIG,
569
0
                    pModel.c_str());
570
571
0
            if ((entries < 1) || (entries > 256))
572
0
                throw CPLOPrintf("%s Palette definition error", SIG);
573
574
            // Create it and initialize it to nothing
575
0
            int start_idx;
576
0
            int end_idx;
577
0
            GDALColorEntry ce_start = {0, 0, 0, 255};
578
0
            GDALColorEntry ce_end = {0, 0, 0, 255};
579
580
0
            auto poColorTable = std::make_unique<GDALColorTable>(eInterp);
581
0
            poColorTable->CreateColorRamp(0, &ce_start, entries - 1, &ce_end);
582
            // Read the values
583
0
            const CPLXMLNode *p = CPLGetXMLNode(node, "Entry");
584
0
            if (p)
585
0
            {
586
                // Initialize the first entry
587
0
                start_idx = static_cast<int>(getXMLNum(p, "idx", "0"));
588
0
                ce_start = GetXMLColorEntry(p);
589
590
0
                if (start_idx < 0)
591
0
                    throw CPLOPrintf("%s Palette index %d not allowed", SIG,
592
0
                                     start_idx);
593
594
0
                poColorTable->SetColorEntry(start_idx, &ce_start);
595
0
                while (nullptr != (p = SearchXMLSiblings(p, "Entry")))
596
0
                {
597
                    // For every entry, create a ramp
598
0
                    ce_end = GetXMLColorEntry(p);
599
0
                    end_idx = static_cast<int>(
600
0
                        getXMLNum(p, "idx", CPLOPrintf("%d", start_idx + 1)));
601
0
                    if ((end_idx <= start_idx) || (start_idx >= entries))
602
0
                        throw CPLOPrintf("%s Index Error at index %d", SIG,
603
0
                                         end_idx);
604
605
0
                    poColorTable->CreateColorRamp(start_idx, &ce_start, end_idx,
606
0
                                                  &ce_end);
607
0
                    ce_start = ce_end;
608
0
                    start_idx = end_idx;
609
0
                }
610
0
            }
611
612
            // Dataset has ownership
613
0
            m_parent_dataset->SetColorTable(poColorTable.release());
614
0
            bHasColorTable = true;
615
0
        }  // If palette
616
617
0
        int overview_count = 0;
618
0
        const CPLXMLNode *Pattern = TG->psChild;
619
620
0
        m_bsx = -1;
621
0
        m_bsy = -1;
622
0
        m_data_window.m_sx = 0;
623
0
        m_data_window.m_sy = 0;
624
625
0
        while (
626
0
            (nullptr != Pattern) &&
627
0
            (nullptr != (Pattern = SearchXMLSiblings(Pattern, "=TilePattern"))))
628
0
        {
629
0
            int mbsx, mbsy, sx, sy;
630
0
            double x, y, X, Y;
631
632
0
            CPLString request;
633
0
            FindChangePattern(Pattern->psChild->pszValue, substs, keys,
634
0
                              request);
635
0
            if (request.empty())
636
0
                break;  // No point to drag, this level doesn't match the keys
637
638
0
            const CPLStringList aosTokens(CSLTokenizeString2(request, "&", 0));
639
640
0
            const char *pszWIDTH = aosTokens.FetchNameValue("WIDTH");
641
0
            const char *pszHEIGHT = aosTokens.FetchNameValue("HEIGHT");
642
0
            if (pszWIDTH == nullptr || pszHEIGHT == nullptr)
643
0
                throw CPLOPrintf(
644
0
                    "%s Cannot find width or height parameters in %s", SIG,
645
0
                    request.c_str());
646
647
0
            mbsx = atoi(pszWIDTH);
648
0
            mbsy = atoi(pszHEIGHT);
649
            // If unset until now, try to get the projection from the
650
            // pattern
651
0
            if (m_oSRS.IsEmpty())
652
0
            {
653
0
                const char *pszSRS = aosTokens.FetchNameValueDef("SRS", "");
654
0
                if (pszSRS[0] != 0)
655
0
                    m_oSRS = ProjToSRS(pszSRS);
656
0
            }
657
658
0
            if (-1 == m_bsx)
659
0
                m_bsx = mbsx;
660
0
            if (-1 == m_bsy)
661
0
                m_bsy = mbsy;
662
0
            if ((m_bsx != mbsx) || (m_bsy != mbsy))
663
0
                throw CPLOPrintf("%s Tileset uses different block sizes", SIG);
664
665
0
            if (CPLsscanf(aosTokens.FetchNameValueDef("BBOX", ""),
666
0
                          "%lf,%lf,%lf,%lf", &x, &y, &X, &Y) != 4)
667
0
                throw CPLOPrintf("%s Error parsing BBOX, pattern %d\n", SIG,
668
0
                                 overview_count + 1);
669
670
            // Pick the largest size
671
0
            sx = static_cast<int>((m_data_window.m_x1 - m_data_window.m_x0) /
672
0
                                  (X - x) * m_bsx);
673
0
            sy = static_cast<int>(fabs(
674
0
                (m_data_window.m_y1 - m_data_window.m_y0) / (Y - y) * m_bsy));
675
0
            if (sx > m_data_window.m_sx)
676
0
                m_data_window.m_sx = sx;
677
0
            if (sy > m_data_window.m_sy)
678
0
                m_data_window.m_sy = sy;
679
680
            // Only use overlays where the top coordinate is within a pixel from
681
            // the top of coverage
682
0
            double pix_off, temp;
683
0
            pix_off =
684
0
                m_bsy * modf(fabs((Y - m_data_window.m_y0) / (Y - y)), &temp);
685
0
            if ((pix_off < 1) || ((m_bsy - pix_off) < 1))
686
0
            {
687
0
                requests.AddString(request);
688
0
                overview_count++;
689
0
            }
690
0
            else
691
0
            {  // Just a warning
692
0
                CPLError(CE_Warning, CPLE_AppDefined,
693
0
                         "%s Overlay size %dX%d can't be used due to alignment",
694
0
                         SIG, sx, sy);
695
0
            }
696
697
0
            Pattern = Pattern->psNext;
698
0
        }  // Search for matching TilePattern
699
700
        // Did we find anything
701
0
        if (requests.empty())
702
0
            throw CPLOPrintf("Can't find any usable TilePattern, maybe the "
703
0
                             "Changes are not correct?");
704
705
        // The tlevel is needed, the tx and ty are not used by this minidriver
706
0
        m_data_window.m_tlevel = 0;
707
0
        m_data_window.m_tx = 0;
708
0
        m_data_window.m_ty = 0;
709
710
        // Make sure the parent_dataset values are set before creating the bands
711
0
        m_parent_dataset->WMSSetBlockSize(m_bsx, m_bsy);
712
0
        m_parent_dataset->WMSSetRasterSize(m_data_window.m_sx,
713
0
                                           m_data_window.m_sy);
714
715
0
        m_parent_dataset->WMSSetDataWindow(m_data_window);
716
        // m_parent_dataset->WMSSetOverviewCount(overview_count);
717
0
        m_parent_dataset->WMSSetClamp(false);
718
719
        // Ready for the Rasterband creation
720
0
        for (int i = 0; i < overview_count; i++)
721
0
        {
722
0
            CPLString request = GetLowestScale(requests, i);
723
0
            double scale = Scale(request);
724
725
            // Base scale should be very close to 1
726
0
            if ((0 == i) && (fabs(scale - 1) > 1e-6))
727
0
                throw CPLOPrintf("%s Base resolution pattern missing", SIG);
728
729
            // Prepare the request and insert it back into the list
730
            // Find returns an answer relative to the original string start!
731
0
            size_t startBbox = FindBbox(request);
732
0
            size_t endBbox = request.find('&', startBbox);
733
0
            if (endBbox == std::string::npos)
734
0
                endBbox = request.size();
735
0
            request.replace(startBbox, endBbox - startBbox, "${GDAL_BBOX}");
736
0
            requests.InsertString(i, request);
737
738
            // Create the Rasterband or overview
739
0
            for (int j = 1; j <= band_count; j++)
740
0
            {
741
0
                if (i != 0)
742
0
                {
743
0
                    m_parent_dataset->mGetBand(j)->AddOverview(scale);
744
0
                }
745
0
                else
746
0
                {  // Base resolution
747
0
                    GDALWMSRasterBand *band =
748
0
                        new GDALWMSRasterBand(m_parent_dataset, j, 1);
749
0
                    if (bHasColorTable)
750
0
                        band->SetColorInterpretation(GCI_PaletteIndex);
751
0
                    else
752
0
                        band->SetColorInterpretation(BandInterp(band_count, j));
753
0
                    m_parent_dataset->mSetBand(j, band);
754
0
                }
755
0
            }
756
0
        }
757
758
0
        if ((overview_count == 0) || (m_bsx < 1) || (m_bsy < 1))
759
0
            throw CPLOPrintf("%s No usable TilePattern elements found", SIG);
760
761
        // Do we need to modify the output XML
762
0
        if (0 != CSLCount(OpenOptions))
763
0
        {
764
            // Get the proposed XML, it will exist at this point
765
0
            CPLXMLTreeCloser cfg_root(CPLParseXMLString(
766
0
                m_parent_dataset->GetMetadataItem("XML", "WMS")));
767
0
            char *pszXML = nullptr;
768
769
0
            if (cfg_root)
770
0
            {
771
0
                bool modified = false;
772
773
                // Set openoption StoreConfiguration to Yes to save the server
774
                // GTS in the output XML
775
0
                if (CSLFetchBoolean(OpenOptions, "StoreConfiguration", 0) &&
776
0
                    nullptr ==
777
0
                        CPLGetXMLNode(cfg_root.get(), "Service.Configuration"))
778
0
                {
779
0
                    char *xmlencodedGTS = CPLEscapeString(
780
0
                        decodedGTS, static_cast<int>(decodedGTS.size()),
781
0
                        CPLES_XML);
782
783
                    // It doesn't have a Service.Configuration element, safe to
784
                    // add one
785
0
                    CPLXMLNode *scfg = CPLCreateXMLElementAndValue(
786
0
                        CPLGetXMLNode(cfg_root.get(), "Service"),
787
0
                        "Configuration", xmlencodedGTS);
788
0
                    CPLAddXMLAttributeAndValue(scfg, "encoding", "XMLencoded");
789
0
                    modified = true;
790
0
                    CPLFree(xmlencodedGTS);
791
0
                }
792
793
                // Set the TiledGroupName if it's not already there and we have
794
                // it as an open option
795
0
                if (!CPLGetXMLNode(cfg_root.get(), "Service.TiledGroupName") &&
796
0
                    nullptr != CSLFetchNameValue(OpenOptions, "TiledGroupName"))
797
0
                {
798
0
                    CPLCreateXMLElementAndValue(
799
0
                        CPLGetXMLNode(cfg_root.get(), "Service"),
800
0
                        "TiledGroupName",
801
0
                        CSLFetchNameValue(OpenOptions, "TiledGroupName"));
802
0
                    modified = true;
803
0
                }
804
805
0
                if (!substs.empty())
806
0
                {
807
                    // Get all the existing Change elements
808
0
                    std::set<std::string> oExistingKeys;
809
0
                    auto nodechange =
810
0
                        CPLGetXMLNode(cfg_root.get(), "Service.Change");
811
0
                    while (nodechange)
812
0
                    {
813
0
                        const char *key =
814
0
                            CPLGetXMLValue(nodechange, "Key", nullptr);
815
0
                        if (key)
816
0
                            oExistingKeys.insert(key);
817
0
                        nodechange = nodechange->psNext;
818
0
                    }
819
820
0
                    for (const char *subst : substs)
821
0
                    {
822
0
                        CPLString kv(subst);
823
0
                        auto sep_pos =
824
0
                            kv.find_first_of("=:");  // It should find it
825
0
                        if (sep_pos == CPLString::npos)
826
0
                            continue;
827
0
                        CPLString key(kv.substr(0, sep_pos));
828
0
                        CPLString val(kv.substr(sep_pos + 1));
829
                        // Add to the cfg_root if this change is not already
830
                        // there
831
0
                        if (oExistingKeys.find(key) == oExistingKeys.end())
832
0
                        {
833
0
                            auto cnode = CPLCreateXMLElementAndValue(
834
0
                                CPLGetXMLNode(cfg_root.get(), "Service"),
835
0
                                "Change", val);
836
0
                            CPLAddXMLAttributeAndValue(cnode, "Key", key);
837
0
                            modified = true;
838
0
                        }
839
0
                    }
840
0
                }
841
842
0
                if (modified)
843
0
                {
844
0
                    pszXML = CPLSerializeXMLTree(cfg_root.get());
845
0
                    m_parent_dataset->SetXML(pszXML);
846
0
                }
847
0
            }
848
849
0
            CPLFree(pszXML);
850
0
        }
851
0
    }
852
0
    catch (const CPLString &msg)
853
0
    {
854
0
        ret = CE_Failure;
855
0
        CPLError(ret, CPLE_AppDefined, "%s", msg.c_str());
856
0
    }
857
858
0
    m_requests = std::move(requests);
859
0
    return ret;
860
0
}
861
862
CPLErr WMSMiniDriver_TiledWMS::TiledImageRequest(
863
    WMSHTTPRequest &request, const GDALWMSImageRequestInfo &iri,
864
    const GDALWMSTiledImageRequestInfo &tiri)
865
0
{
866
0
    CPLString &url = request.URL;
867
0
    url = m_base_url;
868
0
    URLPrepare(url);
869
0
    url += CSLGetField(m_requests.List(), -tiri.m_level);
870
0
    URLSearchAndReplace(&url, "${GDAL_BBOX}", "%013.8f,%013.8f,%013.8f,%013.8f",
871
0
                        iri.m_x0, iri.m_y1, iri.m_x1, iri.m_y0);
872
0
    return CE_None;
873
0
}