Line data Source code
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 12 : static double getXMLNum(const CPLXMLNode *poRoot, const char *pszPath,
61 : const char *pszDefault)
62 : { // Sets errno
63 12 : return CPLAtof(CPLGetXMLValue(poRoot, pszPath, pszDefault));
64 : }
65 :
66 : /*
67 : *\brief Read a ColorEntry XML node, return a GDALColorEntry structure
68 : *
69 : */
70 :
71 0 : static GDALColorEntry GetXMLColorEntry(const CPLXMLNode *p)
72 : {
73 : 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 : }
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 2076 : static const CPLXMLNode *SearchXMLSiblings(const CPLXMLNode *psRoot,
102 : const char *pszElement)
103 :
104 : {
105 2076 : 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 2076 : if (pszElement[0] == '=')
111 27 : pszElement++;
112 : else
113 2049 : psRoot = psRoot->psNext;
114 :
115 29974 : for (; psRoot != nullptr; psRoot = psRoot->psNext)
116 : {
117 27925 : if ((psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute) &&
118 27925 : EQUAL(pszElement, psRoot->pszValue))
119 27 : return psRoot;
120 : }
121 2049 : return nullptr;
122 : }
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 2046 : static CPLXMLNode *SearchLeafGroupName(CPLXMLNode *psRoot, const char *name)
140 :
141 : {
142 2046 : if (psRoot == nullptr || name == nullptr)
143 0 : return nullptr;
144 :
145 : // Has to be a leaf TileGroup with the right name
146 2046 : if (nullptr == SearchXMLSiblings(psRoot->psChild, "TiledGroup"))
147 : {
148 2046 : if (EQUAL(name, CPLGetXMLValue(psRoot, "Name", "")))
149 3 : return psRoot;
150 : }
151 : else
152 : { // Is metagroup, try children then siblings
153 0 : CPLXMLNode *ret = SearchLeafGroupName(psRoot->psChild, name);
154 0 : if (nullptr != ret)
155 0 : return ret;
156 : }
157 2043 : return SearchLeafGroupName(psRoot->psNext, name);
158 : }
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 9 : static GDALColorInterp BandInterp(int nbands, int band)
176 : {
177 9 : switch (nbands)
178 : {
179 0 : case 1:
180 0 : return GCI_GrayIndex;
181 0 : case 2:
182 0 : return band == 1 ? GCI_GrayIndex : GCI_AlphaBand;
183 9 : case 3: // RGB
184 : case 4: // RBGA
185 9 : if (band < 3)
186 6 : return band == 1 ? GCI_RedBand : GCI_GreenBand;
187 3 : return band == 3 ? GCI_BlueBand : GCI_AlphaBand;
188 0 : default:
189 0 : return GCI_Undefined;
190 : }
191 : }
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 189 : static int FindBbox(CPLString in)
207 : {
208 :
209 189 : size_t pos = in.ifind("&bbox=");
210 189 : if (pos == std::string::npos)
211 0 : return -1;
212 189 : return static_cast<int>(pos) + 6;
213 : }
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 27 : static void FindChangePattern(const char *cdata, const char *const *substs,
229 : const char *const *keys, CPLString &ret)
230 : {
231 : const CPLStringList aosTokens(CSLTokenizeString2(
232 27 : cdata, " \t\n\r", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
233 27 : ret.clear();
234 :
235 27 : int matchcount = CSLCount(substs);
236 27 : int keycount = CSLCount(keys);
237 27 : if (keycount < matchcount)
238 : {
239 0 : return;
240 : }
241 :
242 : // A valid string has only the keys in the substs list and none other
243 36 : for (int j = 0; j < aosTokens.size(); j++)
244 : {
245 36 : ret = aosTokens[j]; // The target string
246 36 : bool matches = true;
247 :
248 72 : for (int k = 0; k < keycount && keys != nullptr; k++)
249 : {
250 36 : const char *key = keys[k];
251 36 : int sub_number = CSLPartialFindString(substs, key);
252 36 : if (sub_number != -1)
253 : { // It is a listed match
254 : // But is the match for the key position?
255 18 : char *found_key = nullptr;
256 : const char *found_value =
257 18 : CPLParseNameValue(substs[sub_number], &found_key);
258 18 : if (found_key != nullptr && EQUAL(found_key, key))
259 : { // Should exist in the request
260 18 : if (std::string::npos == ret.find(key))
261 0 : matches = false;
262 18 : if (matches)
263 : // Execute the substitution on the "ret" string
264 18 : URLSearchAndReplace(&ret, key, "%s", found_value);
265 : }
266 : else
267 : {
268 0 : matches = false;
269 : }
270 18 : CPLFree(found_key);
271 : }
272 : else
273 : { // Key not in the subst list, should not match
274 18 : if (std::string::npos != ret.find(key))
275 9 : matches = false;
276 : }
277 : } // Key loop
278 36 : if (matches)
279 : {
280 27 : return; // We got the string ready, all keys accounted for and
281 : // substs applied
282 : }
283 : }
284 0 : ret.clear();
285 : }
286 :
287 : WMSMiniDriver_TiledWMS::WMSMiniDriver_TiledWMS() = default;
288 :
289 : WMSMiniDriver_TiledWMS::~WMSMiniDriver_TiledWMS() = default;
290 :
291 : // Returns the scale of a WMS request as compared to the base resolution
292 162 : double WMSMiniDriver_TiledWMS::Scale(const char *request) const
293 : {
294 162 : int bbox = FindBbox(request);
295 162 : if (bbox < 0)
296 0 : return 0;
297 : double x, y, X, Y;
298 162 : CPLsscanf(request + bbox, "%lf,%lf,%lf,%lf", &x, &y, &X, &Y);
299 162 : return (m_data_window.m_x1 - m_data_window.m_x0) / (X - x) * m_bsx /
300 162 : m_data_window.m_sx;
301 : }
302 :
303 : // Finds, extracts, and returns the highest resolution request string from a
304 : // list, starting at item i
305 27 : CPLString WMSMiniDriver_TiledWMS::GetLowestScale(CPLStringList &list,
306 : int i) const
307 : {
308 27 : CPLString req;
309 27 : double scale = -1;
310 27 : int position = -1;
311 162 : while (nullptr != list[i])
312 : {
313 135 : double tscale = Scale(list[i]);
314 135 : if (tscale >= scale)
315 : {
316 27 : scale = tscale;
317 27 : position = i;
318 : }
319 135 : i++;
320 : }
321 27 : if (position > -1)
322 : {
323 27 : req = list[position];
324 : list.Assign(CSLRemoveStrings(list.StealList(), position, 1, nullptr),
325 27 : true);
326 : }
327 27 : return req;
328 : }
329 :
330 : /*
331 : *\Brief Initialize minidriver with info from the server
332 : */
333 :
334 4 : CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config,
335 : CPL_UNUSED char **OpenOptions)
336 : {
337 4 : CPLErr ret = CE_None;
338 8 : CPLXMLTreeCloser tileServiceConfig(nullptr);
339 4 : const CPLXMLNode *TG = nullptr;
340 :
341 8 : CPLStringList requests;
342 8 : CPLStringList substs;
343 8 : CPLStringList keys;
344 4 : CPLStringList changes;
345 :
346 : try
347 : { // Parse info from the WMS Service node
348 : // m_end_url = CPLGetXMLValue(config, "AdditionalArgs", "");
349 4 : m_base_url = CPLGetXMLValue(config, "ServerURL", "");
350 :
351 4 : if (m_base_url.empty())
352 0 : throw CPLOPrintf("%s ServerURL missing.", SIG);
353 :
354 : CPLString tiledGroupName(
355 8 : CSLFetchNameValueDef(OpenOptions, "TiledGroupName", ""));
356 : tiledGroupName =
357 4 : CPLGetXMLValue(config, "TiledGroupName", tiledGroupName);
358 4 : if (tiledGroupName.empty())
359 1 : 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 3 : changes = CSLFetchNameValueMultiple(OpenOptions, "Change");
366 : // Transfer them to subst list
367 5 : for (int i = 0; i < changes.size(); i++)
368 : {
369 2 : char *key = nullptr;
370 2 : const char *value = CPLParseNameValue(changes[i], &key);
371 : // Add the ${} around the key
372 2 : if (value != nullptr && key != nullptr)
373 2 : substs.SetNameValue(CPLOPrintf("${%s}", key), value);
374 2 : CPLFree(key);
375 : }
376 :
377 : // Then process the configuration file itself
378 3 : const CPLXMLNode *nodeChange = CPLSearchXMLNode(config, "Change");
379 3 : while (nodeChange != nullptr)
380 : {
381 0 : CPLString key = CPLGetXMLValue(nodeChange, "key", "");
382 0 : if (key.empty())
383 : throw CPLOPrintf(
384 : "%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 : }
389 :
390 3 : m_parent_dataset->SetMetadataItem("ServerURL", m_base_url, nullptr);
391 3 : m_parent_dataset->SetMetadataItem("TiledGroupName", tiledGroupName,
392 3 : nullptr);
393 5 : for (const char *subst : substs)
394 2 : m_parent_dataset->SetMetadataItem("Change", subst, nullptr);
395 :
396 : const char *pszConfiguration =
397 3 : CPLGetXMLValue(config, "Configuration", nullptr);
398 6 : CPLString decodedGTS;
399 :
400 3 : if (pszConfiguration)
401 : { // Probably XML encoded because it is XML itself
402 : // The copy will be replaced by the decoded result
403 1 : decodedGTS = pszConfiguration;
404 1 : WMSUtilDecode(decodedGTS,
405 : CPLGetXMLValue(config, "Configuration.encoding", ""));
406 : }
407 : else
408 : { // Not local, use the WMSdriver to fetch the server config
409 4 : CPLString getTileServiceUrl = m_base_url + "request=GetTileService";
410 :
411 : // This returns a string managed by the cfg cache, do not free
412 2 : const char *pszTmp = GDALWMSDataset::GetServerConfig(
413 : getTileServiceUrl,
414 2 : const_cast<char **>(m_parent_dataset->GetHTTPRequestOpts()));
415 2 : decodedGTS = pszTmp ? pszTmp : "";
416 :
417 2 : if (decodedGTS.empty())
418 0 : throw CPLOPrintf("%s Can't fetch server GetTileService", SIG);
419 : }
420 :
421 : // decodedGTS contains the GetTileService return now
422 3 : tileServiceConfig.reset(CPLParseXMLString(decodedGTS));
423 3 : if (!tileServiceConfig)
424 : throw CPLOPrintf("%s Error parsing the GetTileService response",
425 0 : SIG);
426 :
427 3 : if (nullptr ==
428 3 : (TG = CPLSearchXMLNode(tileServiceConfig.get(), "TiledPatterns")))
429 : 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 : const char *global_base_url =
436 3 : CPLGetXMLValue(tileServiceConfig.get(),
437 : "TiledPatterns.OnlineResource.xlink:href", "");
438 3 : const CPLXMLNode *global_latlonbbox = CPLGetXMLNode(
439 : tileServiceConfig.get(), "TiledPatterns.LatLonBoundingBox");
440 : const CPLXMLNode *global_bbox =
441 3 : CPLGetXMLNode(tileServiceConfig.get(), "TiledPatterns.BoundingBox");
442 3 : const char *pszProjection = CPLGetXMLValue(
443 3 : tileServiceConfig.get(), "TiledPatterns.Projection", "");
444 3 : if (pszProjection[0] != 0)
445 0 : m_oSRS.SetFromUserInput(
446 : pszProjection,
447 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
448 :
449 3 : if (nullptr == (TG = SearchLeafGroupName(TG->psChild, tiledGroupName)))
450 : throw CPLOPrintf("%s No TiledGroup "
451 : "%s"
452 : " in server response.",
453 0 : SIG, tiledGroupName.c_str());
454 :
455 3 : int band_count = atoi(CPLGetXMLValue(TG, "Bands", "3"));
456 :
457 3 : if (!GDALCheckBandCount(band_count, FALSE))
458 : throw CPLOPrintf("%s Invalid number of bands in server response",
459 0 : SIG);
460 :
461 3 : if (nullptr != CPLGetXMLNode(TG, "Key"))
462 : { // Collect all keys defined by this tileset
463 3 : const CPLXMLNode *node = CPLGetXMLNode(TG, "Key");
464 6 : while (nullptr != node)
465 : { // the TEXT of the Key node
466 3 : const char *val = CPLGetXMLValue(node, nullptr, nullptr);
467 3 : if (nullptr != val)
468 3 : keys.AddString(val);
469 3 : node = SearchXMLSiblings(node, "Key");
470 : }
471 : }
472 :
473 : // Data values are attributes, they include NoData Min and Max
474 3 : if (nullptr != CPLGetXMLNode(TG, "DataValues"))
475 : {
476 : const char *nodata =
477 0 : CPLGetXMLValue(TG, "DataValues.NoData", nullptr);
478 0 : if (nodata != nullptr)
479 : {
480 0 : m_parent_dataset->WMSSetNoDataValue(nodata);
481 0 : m_parent_dataset->SetTileOO("@NDV", nodata);
482 : }
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 : }
490 :
491 3 : m_parent_dataset->WMSSetBandsCount(band_count);
492 : GDALDataType dt =
493 3 : GDALGetDataTypeByName(CPLGetXMLValue(TG, "DataType", "Byte"));
494 3 : m_parent_dataset->WMSSetDataType(dt);
495 3 : if (dt != GDT_Byte)
496 0 : m_parent_dataset->SetTileOO("@DATATYPE", GDALGetDataTypeName(dt));
497 : // Let the TiledGroup override the projection
498 3 : pszProjection = CPLGetXMLValue(TG, "Projection", "");
499 3 : if (pszProjection[0] != 0)
500 3 : m_oSRS = ProjToSRS(pszProjection);
501 :
502 : m_base_url =
503 3 : CPLGetXMLValue(TG, "OnlineResource.xlink:href", global_base_url);
504 3 : if (m_base_url[0] == '\0')
505 : 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 3 : const CPLXMLNode *bbox = CPLGetXMLNode(TG, "BoundingBox");
511 3 : if (nullptr == bbox)
512 3 : bbox = global_bbox;
513 3 : if (nullptr == bbox)
514 3 : bbox = CPLGetXMLNode(TG, "LatLonBoundingBox");
515 3 : if (nullptr == bbox)
516 0 : bbox = global_latlonbbox;
517 3 : if (nullptr == bbox)
518 : throw CPLOPrintf(
519 : "%s Can't locate the LatLonBoundingBox in server response",
520 0 : SIG);
521 :
522 : // Check for errors during conversion
523 3 : errno = 0;
524 3 : int err = 0;
525 3 : m_data_window.m_x0 = getXMLNum(bbox, "minx", "0");
526 3 : err |= errno;
527 3 : m_data_window.m_x1 = getXMLNum(bbox, "maxx", "-1");
528 3 : err |= errno;
529 3 : m_data_window.m_y0 = getXMLNum(bbox, "maxy", "0");
530 3 : err |= errno;
531 3 : m_data_window.m_y1 = getXMLNum(bbox, "miny", "-1");
532 3 : err |= errno;
533 3 : if (err)
534 0 : throw CPLOPrintf("%s Can't parse LatLonBoundingBox", SIG);
535 :
536 3 : if ((m_data_window.m_x1 - m_data_window.m_x0) <= 0 ||
537 3 : (m_data_window.m_y0 - m_data_window.m_y1) <= 0)
538 : 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 3 : bool bHasColorTable = false;
557 :
558 3 : if ((band_count == 1) && CPLGetXMLNode(TG, "Palette"))
559 : {
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 : throw CPLOPrintf(
568 : "%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 : int start_idx;
576 : 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 : {
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 : 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 : {
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 : 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 : &ce_end);
607 0 : ce_start = ce_end;
608 0 : start_idx = end_idx;
609 : }
610 : }
611 :
612 : // Dataset has ownership
613 0 : m_parent_dataset->SetColorTable(poColorTable.release());
614 0 : bHasColorTable = true;
615 : } // If palette
616 :
617 3 : int overview_count = 0;
618 3 : const CPLXMLNode *Pattern = TG->psChild;
619 :
620 3 : m_bsx = -1;
621 3 : m_bsy = -1;
622 3 : m_data_window.m_sx = 0;
623 3 : m_data_window.m_sy = 0;
624 :
625 27 : while (
626 57 : (nullptr != Pattern) &&
627 27 : (nullptr != (Pattern = SearchXMLSiblings(Pattern, "=TilePattern"))))
628 : {
629 : int mbsx, mbsy, sx, sy;
630 : double x, y, X, Y;
631 :
632 27 : CPLString request;
633 27 : FindChangePattern(Pattern->psChild->pszValue, substs, keys,
634 27 : request);
635 27 : if (request.empty())
636 0 : break; // No point to drag, this level doesn't match the keys
637 :
638 27 : const CPLStringList aosTokens(CSLTokenizeString2(request, "&", 0));
639 :
640 27 : const char *pszWIDTH = aosTokens.FetchNameValue("WIDTH");
641 27 : const char *pszHEIGHT = aosTokens.FetchNameValue("HEIGHT");
642 27 : if (pszWIDTH == nullptr || pszHEIGHT == nullptr)
643 : throw CPLOPrintf(
644 : "%s Cannot find width or height parameters in %s", SIG,
645 0 : request.c_str());
646 :
647 27 : mbsx = atoi(pszWIDTH);
648 27 : mbsy = atoi(pszHEIGHT);
649 : // If unset until now, try to get the projection from the
650 : // pattern
651 27 : if (m_oSRS.IsEmpty())
652 : {
653 0 : const char *pszSRS = aosTokens.FetchNameValueDef("SRS", "");
654 0 : if (pszSRS[0] != 0)
655 0 : m_oSRS = ProjToSRS(pszSRS);
656 : }
657 :
658 27 : if (-1 == m_bsx)
659 3 : m_bsx = mbsx;
660 27 : if (-1 == m_bsy)
661 3 : m_bsy = mbsy;
662 27 : if ((m_bsx != mbsx) || (m_bsy != mbsy))
663 0 : throw CPLOPrintf("%s Tileset uses different block sizes", SIG);
664 :
665 27 : if (CPLsscanf(aosTokens.FetchNameValueDef("BBOX", ""),
666 27 : "%lf,%lf,%lf,%lf", &x, &y, &X, &Y) != 4)
667 : throw CPLOPrintf("%s Error parsing BBOX, pattern %d\n", SIG,
668 0 : overview_count + 1);
669 :
670 : // Pick the largest size
671 27 : sx = static_cast<int>((m_data_window.m_x1 - m_data_window.m_x0) /
672 27 : (X - x) * m_bsx);
673 27 : sy = static_cast<int>(fabs(
674 27 : (m_data_window.m_y1 - m_data_window.m_y0) / (Y - y) * m_bsy));
675 27 : if (sx > m_data_window.m_sx)
676 3 : m_data_window.m_sx = sx;
677 27 : if (sy > m_data_window.m_sy)
678 3 : 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 : double pix_off, temp;
683 27 : pix_off =
684 27 : m_bsy * modf(fabs((Y - m_data_window.m_y0) / (Y - y)), &temp);
685 27 : if ((pix_off < 1) || ((m_bsy - pix_off) < 1))
686 : {
687 27 : requests.AddString(request);
688 27 : overview_count++;
689 : }
690 : else
691 : { // Just a warning
692 0 : CPLError(CE_Warning, CPLE_AppDefined,
693 : "%s Overlay size %dX%d can't be used due to alignment",
694 : SIG, sx, sy);
695 : }
696 :
697 27 : Pattern = Pattern->psNext;
698 : } // Search for matching TilePattern
699 :
700 : // Did we find anything
701 3 : if (requests.empty())
702 : 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 3 : m_data_window.m_tlevel = 0;
707 3 : m_data_window.m_tx = 0;
708 3 : m_data_window.m_ty = 0;
709 :
710 : // Make sure the parent_dataset values are set before creating the bands
711 3 : m_parent_dataset->WMSSetBlockSize(m_bsx, m_bsy);
712 3 : m_parent_dataset->WMSSetRasterSize(m_data_window.m_sx,
713 : m_data_window.m_sy);
714 :
715 3 : m_parent_dataset->WMSSetDataWindow(m_data_window);
716 : // m_parent_dataset->WMSSetOverviewCount(overview_count);
717 3 : m_parent_dataset->WMSSetClamp(false);
718 :
719 : // Ready for the Rasterband creation
720 30 : for (int i = 0; i < overview_count; i++)
721 : {
722 54 : CPLString request = GetLowestScale(requests, i);
723 27 : double scale = Scale(request);
724 :
725 : // Base scale should be very close to 1
726 27 : 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 27 : size_t startBbox = FindBbox(request);
732 27 : size_t endBbox = request.find('&', startBbox);
733 27 : if (endBbox == std::string::npos)
734 27 : endBbox = request.size();
735 27 : request.replace(startBbox, endBbox - startBbox, "${GDAL_BBOX}");
736 27 : requests.InsertString(i, request);
737 :
738 : // Create the Rasterband or overview
739 108 : for (int j = 1; j <= band_count; j++)
740 : {
741 81 : if (i != 0)
742 : {
743 72 : m_parent_dataset->mGetBand(j)->AddOverview(scale);
744 : }
745 : else
746 : { // Base resolution
747 : GDALWMSRasterBand *band =
748 9 : new GDALWMSRasterBand(m_parent_dataset, j, 1);
749 9 : if (bHasColorTable)
750 0 : band->SetColorInterpretation(GCI_PaletteIndex);
751 : else
752 9 : band->SetColorInterpretation(BandInterp(band_count, j));
753 9 : m_parent_dataset->mSetBand(j, band);
754 : }
755 : }
756 : }
757 :
758 3 : 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 3 : if (0 != CSLCount(OpenOptions))
763 : {
764 : // Get the proposed XML, it will exist at this point
765 : CPLXMLTreeCloser cfg_root(CPLParseXMLString(
766 4 : m_parent_dataset->GetMetadataItem("XML", "WMS")));
767 2 : char *pszXML = nullptr;
768 :
769 2 : if (cfg_root)
770 : {
771 2 : bool modified = false;
772 :
773 : // Set openoption StoreConfiguration to Yes to save the server
774 : // GTS in the output XML
775 2 : if (CSLFetchBoolean(OpenOptions, "StoreConfiguration", 0) &&
776 : nullptr ==
777 0 : CPLGetXMLNode(cfg_root.get(), "Service.Configuration"))
778 : {
779 0 : char *xmlencodedGTS = CPLEscapeString(
780 0 : decodedGTS, static_cast<int>(decodedGTS.size()),
781 : CPLES_XML);
782 :
783 : // It doesn't have a Service.Configuration element, safe to
784 : // add one
785 0 : CPLXMLNode *scfg = CPLCreateXMLElementAndValue(
786 : CPLGetXMLNode(cfg_root.get(), "Service"),
787 : "Configuration", xmlencodedGTS);
788 0 : CPLAddXMLAttributeAndValue(scfg, "encoding", "XMLencoded");
789 0 : modified = true;
790 0 : CPLFree(xmlencodedGTS);
791 : }
792 :
793 : // Set the TiledGroupName if it's not already there and we have
794 : // it as an open option
795 3 : if (!CPLGetXMLNode(cfg_root.get(), "Service.TiledGroupName") &&
796 1 : nullptr != CSLFetchNameValue(OpenOptions, "TiledGroupName"))
797 : {
798 1 : CPLCreateXMLElementAndValue(
799 : CPLGetXMLNode(cfg_root.get(), "Service"),
800 : "TiledGroupName",
801 : CSLFetchNameValue(OpenOptions, "TiledGroupName"));
802 1 : modified = true;
803 : }
804 :
805 2 : if (!substs.empty())
806 : {
807 : // Get all the existing Change elements
808 4 : std::set<std::string> oExistingKeys;
809 : auto nodechange =
810 2 : CPLGetXMLNode(cfg_root.get(), "Service.Change");
811 2 : while (nodechange)
812 : {
813 : const char *key =
814 0 : CPLGetXMLValue(nodechange, "Key", nullptr);
815 0 : if (key)
816 0 : oExistingKeys.insert(key);
817 0 : nodechange = nodechange->psNext;
818 : }
819 :
820 4 : for (const char *subst : substs)
821 : {
822 2 : CPLString kv(subst);
823 : auto sep_pos =
824 2 : kv.find_first_of("=:"); // It should find it
825 2 : if (sep_pos == CPLString::npos)
826 0 : continue;
827 4 : CPLString key(kv.substr(0, sep_pos));
828 4 : CPLString val(kv.substr(sep_pos + 1));
829 : // Add to the cfg_root if this change is not already
830 : // there
831 2 : if (oExistingKeys.find(key) == oExistingKeys.end())
832 : {
833 2 : auto cnode = CPLCreateXMLElementAndValue(
834 : CPLGetXMLNode(cfg_root.get(), "Service"),
835 : "Change", val);
836 2 : CPLAddXMLAttributeAndValue(cnode, "Key", key);
837 2 : modified = true;
838 : }
839 : }
840 : }
841 :
842 2 : if (modified)
843 : {
844 2 : pszXML = CPLSerializeXMLTree(cfg_root.get());
845 2 : m_parent_dataset->SetXML(pszXML);
846 : }
847 : }
848 :
849 2 : CPLFree(pszXML);
850 : }
851 : }
852 2 : catch (const CPLString &msg)
853 : {
854 1 : ret = CE_Failure;
855 1 : CPLError(ret, CPLE_AppDefined, "%s", msg.c_str());
856 : }
857 :
858 4 : m_requests = std::move(requests);
859 8 : return ret;
860 : }
861 :
862 0 : CPLErr WMSMiniDriver_TiledWMS::TiledImageRequest(
863 : WMSHTTPRequest &request, const GDALWMSImageRequestInfo &iri,
864 : const GDALWMSTiledImageRequestInfo &tiri)
865 : {
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 : }
|