1 
2 ///////////////////////////////////////////////////////////
3 //                                                       //
4 //                         SAGA                          //
5 //                                                       //
6 //      System for Automated Geoscientific Analyses      //
7 //                                                       //
8 //                     Tool Library                      //
9 //                                                       //
10 //                       io_gdal                         //
11 //                                                       //
12 //-------------------------------------------------------//
13 //                                                       //
14 //                  gdal_import_wms.cpp                  //
15 //                                                       //
16 //            Copyright (C) 2016 O. Conrad               //
17 //                                                       //
18 //-------------------------------------------------------//
19 //                                                       //
20 // This file is part of 'SAGA - System for Automated     //
21 // Geoscientific Analyses'. SAGA is free software; you   //
22 // can redistribute it and/or modify it under the terms  //
23 // of the GNU General Public License as published by the //
24 // Free Software Foundation, either version 2 of the     //
25 // License, or (at your option) any later version.       //
26 //                                                       //
27 // SAGA is distributed in the hope that it will be       //
28 // useful, but WITHOUT ANY WARRANTY; without even the    //
29 // implied warranty of MERCHANTABILITY or FITNESS FOR A  //
30 // PARTICULAR PURPOSE. See the GNU General Public        //
31 // License for more details.                             //
32 //                                                       //
33 // You should have received a copy of the GNU General    //
34 // Public License along with this program; if not, see   //
35 // <http://www.gnu.org/licenses/>.                       //
36 //                                                       //
37 //-------------------------------------------------------//
38 //                                                       //
39 //    e-mail:     oconrad@saga-gis.de                    //
40 //                                                       //
41 //    contact:    Olaf Conrad                            //
42 //                Institute of Geography                 //
43 //                University of Hamburg                  //
44 //                Germany                                //
45 //                                                       //
46 ///////////////////////////////////////////////////////////
47 
48 //---------------------------------------------------------
49 #include "gdal_import_wms.h"
50 
51 
52 ///////////////////////////////////////////////////////////
53 //														 //
54 //														 //
55 //														 //
56 ///////////////////////////////////////////////////////////
57 
58 //---------------------------------------------------------
CGDAL_Import_WMS(void)59 CGDAL_Import_WMS::CGDAL_Import_WMS(void)
60 {
61 	//-----------------------------------------------------
62 	Set_Name	(_TL("Import TMS Image"));
63 
64 	Set_Author	("O.Conrad (c) 2016");
65 
66 	CSG_String	Description;
67 
68 	Description	= _TW(
69 		"The \"Import TMS Image\" tool imports a map image from a Tile Mapping Service (TMS) using the "
70 		"\"Geospatial Data Abstraction Library\" (GDAL) by Frank Warmerdam. "
71 	);
72 
73 	Description	+= CSG_String::Format("\nGDAL %s:%s\n\n", _TL("Version"), SG_Get_GDAL_Drivers().Get_Version().c_str());
74 
75 	Set_Description(Description);
76 
77 	Add_Reference("GDAL/OGR contributors", "2019",
78 		"GDAL/OGR Geospatial Data Abstraction software Library",
79 		"A translator library for raster and vector geospatial data formats. Open Source Geospatial Foundation.",
80 		SG_T("https://gdal.org"), SG_T("Link")
81 	);
82 
83 	//-----------------------------------------------------
84 	Parameters.Add_Grid("",
85 		"TARGET"	, _TL("Target System"),
86 		_TL(""),
87 		PARAMETER_INPUT_OPTIONAL
88 	)->Get_Parent();
89 
90 	Parameters.Add_Grid("TARGET",
91 		"TARGET_MAP"	, _TL("Target Map"),
92 		_TL(""),
93 		PARAMETER_OUTPUT
94 	);
95 
96 	Parameters.Add_Grid_Output("",
97 		"MAP"		, _TL("Map"),
98 		_TL("")
99 	);
100 
101 	//-----------------------------------------------------
102 	Parameters.Add_Choice("",
103 		"SERVER"	, _TL("Server"),
104 		_TL(""),
105 		CSG_String::Format("%s|%s|%s|%s|%s|%s|%s|%s|%s",
106 			_TL("Open Street Map"),
107 			_TL("Google Map"),
108 			_TL("Google Satellite"),
109 			_TL("Google Hybrid"),
110 			_TL("Google Terrain"),
111 			_TL("Google Terrain, Streets and Water"),
112 			_TL("ArcGIS MapServer Tiles"),
113 			_TL("TopPlusOpen"),
114 			_TL("user defined")
115 		), 0
116 	);
117 
118 	Parameters.Add_Int("SERVER",
119 		"BLOCKSIZE"	, _TL("Block Size"),
120 		_TL(""),
121 		256, 32, true
122 	);
123 
124 	Parameters.Add_String("SERVER",
125 		"SERVER_USER", _TL("Server"),
126 		_TL(""),
127 		"tile.openstreetmap.org/${z}/${x}/${y}.png"
128 	);
129 
130 	Parameters.Add_Bool("",
131 		"CACHE"		, _TL("Cache"),
132 		_TL("Enable local disk cache. Allows for offline operation."),
133 		false
134 	);
135 
136 	Parameters.Add_FilePath("CACHE",
137 		"CACHE_DIR"	, _TL("Cache Directory"),
138 		_TL("If not specified the cache will be created in the current user's temporary directory."),
139 		NULL, NULL, false, true
140 	);
141 
142 	Parameters.Add_Bool("",
143 		"GRAYSCALE"	, _TL("Gray Scale Image"),
144 		_TL(""),
145 		false
146 	);
147 
148 	//-----------------------------------------------------
149 	Parameters.Add_Node("", "TARGET_NODE", _TL("Target Grid"), _TL(""));
150 
151 	Parameters.Add_Double("TARGET_NODE", "XMIN", _TL("West"   ), _TL(""), -20037508.34, -20037508.34, true, 20037508.34, true);
152 	Parameters.Add_Double("TARGET_NODE", "YMIN", _TL("South"  ), _TL(""), -20037508.34, -20037508.34, true, 20037508.34, true);
153 	Parameters.Add_Double("TARGET_NODE", "XMAX", _TL("East"   ), _TL(""),  20037508.34, -20037508.34, true, 20037508.34, true);
154 	Parameters.Add_Double("TARGET_NODE", "YMAX", _TL("North"  ), _TL(""),  20037508.34, -20037508.34, true, 20037508.34, true);
155 	Parameters.Add_Int   ("TARGET_NODE", "NX"  , _TL("Columns"), _TL(""),  600, 1, true);
156 	Parameters.Add_Int   ("TARGET_NODE", "NY"  , _TL("Rows"   ), _TL(""),  600, 1, true);
157 }
158 
159 
160 ///////////////////////////////////////////////////////////
161 //														 //
162 ///////////////////////////////////////////////////////////
163 
164 //---------------------------------------------------------
On_Parameter_Changed(CSG_Parameters * pParameters,CSG_Parameter * pParameter)165 int CGDAL_Import_WMS::On_Parameter_Changed(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
166 {
167 	CSG_Parameter	*pXMin	= pParameters->Get_Parameter("XMIN");
168 	CSG_Parameter	*pYMin	= pParameters->Get_Parameter("YMIN");
169 	CSG_Parameter	*pXMax	= pParameters->Get_Parameter("XMAX");
170 	CSG_Parameter	*pYMax	= pParameters->Get_Parameter("YMAX");
171 	CSG_Parameter	*pNX	= pParameters->Get_Parameter("NX"  );
172 	CSG_Parameter	*pNY	= pParameters->Get_Parameter("NY"  );
173 
174 	if( pParameter->Cmp_Identifier("NX") )
175 	{
176 		double	d	= fabs(pXMax->asDouble() - pXMin->asDouble()) / pNX->asDouble();
177 		pNY  ->Set_Value(fabs(pYMax->asDouble() - pYMin->asDouble()) / d);
178 		pYMax->Set_Value(pYMin->asDouble() + d * pNY->asDouble());
179 	}
180 
181 	if( pParameter->Cmp_Identifier("NY") )
182 	{
183 		double	d	= fabs(pYMax->asDouble() - pYMin->asDouble()) / pNY->asDouble();
184 		pNX  ->Set_Value(fabs(pXMax->asDouble() - pXMin->asDouble()) / d);
185 		pXMax->Set_Value(pXMin->asDouble() + d * pNX->asDouble());
186 	}
187 
188 	if( pParameter->Cmp_Identifier("XMIN") )
189 	{
190 		double	d	= fabs(pYMax->asDouble() - pYMin->asDouble()) / pNY->asDouble();
191 		pNX  ->Set_Value(fabs(pXMax->asDouble() - pXMin->asDouble()) / d);
192 		pXMax->Set_Value(pXMin->asDouble() + d * pNX->asDouble());
193 	}
194 
195 	if( pParameter->Cmp_Identifier("YMIN") )
196 	{
197 		double	d	= fabs(pXMax->asDouble() - pXMin->asDouble()) / pNX->asDouble();
198 		pNY  ->Set_Value(fabs(pYMax->asDouble() - pYMin->asDouble()) / d);
199 		pYMax->Set_Value(pYMin->asDouble() + d * pNY->asDouble());
200 	}
201 
202 	if( pParameter->Cmp_Identifier("XMAX") )
203 	{
204 		double	d	= fabs(pYMax->asDouble() - pYMin->asDouble()) / pNY->asDouble();
205 		pNX  ->Set_Value(fabs(pXMax->asDouble() - pXMin->asDouble()) / d);
206 		pYMax->Set_Value(pYMax->asDouble() - d * pNY->asDouble());
207 	}
208 
209 	if( pParameter->Cmp_Identifier("YMAX") )
210 	{
211 		double	d	= fabs(pXMax->asDouble() - pXMin->asDouble()) / pNX->asDouble();
212 		pNY  ->Set_Value(fabs(pYMax->asDouble() - pYMin->asDouble()) / d);
213 		pXMax->Set_Value(pXMax->asDouble() - d * pNX->asDouble());
214 	}
215 
216 	return( CSG_Tool::On_Parameter_Changed(pParameters, pParameter) );
217 }
218 
219 //---------------------------------------------------------
On_Parameters_Enable(CSG_Parameters * pParameters,CSG_Parameter * pParameter)220 int CGDAL_Import_WMS::On_Parameters_Enable(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
221 {
222 	if( pParameter->Cmp_Identifier("TARGET") )
223 	{
224 		pParameters->Set_Enabled("TARGET_MAP" , pParameter->asPointer() != NULL);
225 		pParameters->Set_Enabled("TARGET_NODE", pParameter->asPointer() == NULL);
226 	}
227 
228 	if( pParameter->Cmp_Identifier("SERVER") )
229 	{
230 		pParameters->Set_Enabled("SERVER_USER", pParameter->asInt() >= pParameter->asChoice()->Get_Count() - 1);
231 	}
232 
233 	if( pParameter->Cmp_Identifier("CACHE") )
234 	{
235 		pParameters->Set_Enabled("CACHE_DIR", pParameter->asBool());
236 	}
237 
238 	return( CSG_Tool::On_Parameters_Enable(pParameters, pParameter) );
239 }
240 
241 
242 ///////////////////////////////////////////////////////////
243 //														 //
244 ///////////////////////////////////////////////////////////
245 
246 //---------------------------------------------------------
On_Execute(void)247 bool CGDAL_Import_WMS::On_Execute(void)
248 {
249 	//-----------------------------------------------------
250 	CSG_Grid_System	System;
251 
252 	if( !Get_System(System, Parameters("TARGET")->asGrid()) )
253 	{
254 		return( false );
255 	}
256 
257 	//-----------------------------------------------------
258 	CSG_Grid	*pBands[3];
259 
260 	if( !Get_Bands(pBands, System) )
261 	{
262 		Error_Set(_TL("failed to retrieve map image data"));
263 
264 		return( false );
265 	}
266 
267 	//-----------------------------------------------------
268 	if( Parameters("TARGET")->asGrid() )
269 	{
270 		Get_Projected(pBands, Parameters("TARGET")->asGrid());
271 	}
272 
273 	//-----------------------------------------------------
274 	return( Set_Image(pBands) );
275 }
276 
277 
278 ///////////////////////////////////////////////////////////
279 //														 //
280 ///////////////////////////////////////////////////////////
281 
282 //---------------------------------------------------------
Get_System(CSG_Grid_System & System,CSG_Grid * pTarget)283 bool CGDAL_Import_WMS::Get_System(CSG_Grid_System &System, CSG_Grid *pTarget)
284 {
285 	//-----------------------------------------------------
286 	if( !pTarget )
287 	{
288 		CSG_Rect	Extent(
289 			Parameters("XMIN")->asDouble(), Parameters("YMIN")->asDouble(),
290 			Parameters("XMAX")->asDouble(), Parameters("YMAX")->asDouble()
291 		);
292 
293 		double	Cellsize	= Extent.Get_XRange() / Parameters("NX")->asDouble();
294 
295 		return( System.Assign(Cellsize, Extent) );
296 	}
297 
298 	//-----------------------------------------------------
299 	if( !pTarget->Get_Projection().is_Okay() )
300 	{
301 		return( false );
302 	}
303 
304 	CSG_Shapes	rTarget(SHAPE_TYPE_Point), rSource;
305 
306 	rTarget.Get_Projection()	= pTarget->Get_Projection();
307 
308 	CSG_Rect	Extent	= pTarget->Get_Extent(true);
309 
310 	rTarget.Add_Shape()->Add_Point(Extent.Get_XMin   (), Extent.Get_YMin   ());
311 	rTarget.Add_Shape()->Add_Point(Extent.Get_XMin   (), Extent.Get_YCenter());
312 	rTarget.Add_Shape()->Add_Point(Extent.Get_XMin   (), Extent.Get_YMax   ());
313 	rTarget.Add_Shape()->Add_Point(Extent.Get_XCenter(), Extent.Get_YMax   ());
314 	rTarget.Add_Shape()->Add_Point(Extent.Get_XMax   (), Extent.Get_YMax   ());
315 	rTarget.Add_Shape()->Add_Point(Extent.Get_XMax   (), Extent.Get_YCenter());
316 	rTarget.Add_Shape()->Add_Point(Extent.Get_XMax   (), Extent.Get_YMin   ());
317 	rTarget.Add_Shape()->Add_Point(Extent.Get_XCenter(), Extent.Get_YMin   ());
318 
319 	//-----------------------------------------------------
320 	CSG_Tool	*pTool	= SG_Get_Tool_Library_Manager().Create_Tool("pj_proj4", 2);	// Coordinate Transformation (Shapes);
321 
322 	if(	!pTool )
323 	{
324 		return( false );
325 	}
326 
327 	pTool->Set_Manager(NULL);
328 
329 	if( SG_TOOL_PARAMETER_SET("CRS_PROJ4", SG_T("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +k=1.0"))
330 	&&  SG_TOOL_PARAMETER_SET("SOURCE"   , &rTarget)
331 	&&  SG_TOOL_PARAMETER_SET("TARGET"   , &rSource)
332 	&&  pTool->Execute() )
333 	{
334 		Extent	= rSource.Get_Extent();
335 
336 		double	Cellsize	= Extent.Get_XRange() / pTarget->Get_NX() < Extent.Get_YRange() / pTarget->Get_NY()
337 							? Extent.Get_XRange() / pTarget->Get_NX() : Extent.Get_YRange() / pTarget->Get_NY();
338 
339 		System.Assign(Cellsize, Extent);
340 
341 		SG_Get_Tool_Library_Manager().Delete_Tool(pTool);
342 
343 		return( true );
344 	}
345 
346 	SG_Get_Tool_Library_Manager().Delete_Tool(pTool);
347 
348 	return( false );
349 }
350 
351 
352 ///////////////////////////////////////////////////////////
353 //														 //
354 ///////////////////////////////////////////////////////////
355 
356 //---------------------------------------------------------
Get_Projected(CSG_Grid * pBands[3],CSG_Grid * pTarget)357 bool CGDAL_Import_WMS::Get_Projected(CSG_Grid *pBands[3], CSG_Grid *pTarget)
358 {
359 	CSG_Tool	*pTool	= SG_Get_Tool_Library_Manager().Create_Tool("pj_proj4", 3);	// Coordinate Transformation (Grid List);
360 
361 	if(	!pTool )
362 	{
363 		return( false );
364 	}
365 
366 	//-----------------------------------------------------
367 	pTool->Set_Manager(NULL);
368 
369 	if( SG_TOOL_PARAMETER_SET("CRS_PROJ4"        , pTarget->Get_Projection().Get_Proj4())
370 	&&  SG_TOOL_PARAMETER_SET("RESAMPLING"       , 3)
371 	&&  SG_TOOL_PARAMETER_SET("KEEP_TYPE"        , true)
372 	&&  SG_TOOL_PARAMLIST_ADD("SOURCE"           , pBands[0])
373 	&&  SG_TOOL_PARAMLIST_ADD("SOURCE"           , pBands[1])
374 	&&  SG_TOOL_PARAMLIST_ADD("SOURCE"           , pBands[2])
375 	&&  SG_TOOL_PARAMETER_SET("TARGET_DEFINITION", 1)
376 	&&  SG_TOOL_PARAMETER_SET("TARGET_SYSTEM"    , (void *)&pTarget->Get_System())
377 	&&  pTool->Execute() )
378 	{
379 		CSG_Parameter_Grid_List	*pGrids	= pTool->Get_Parameters()->Get_Parameter("GRIDS")->asGridList();
380 
381 		delete(pBands[0]);	pBands[0]	= pGrids->Get_Grid(0);
382 		delete(pBands[1]);	pBands[1]	= pGrids->Get_Grid(1);
383 		delete(pBands[2]);	pBands[2]	= pGrids->Get_Grid(2);
384 
385 		SG_Get_Tool_Library_Manager().Delete_Tool(pTool);
386 
387 		return( true );
388 	}
389 
390 	SG_Get_Tool_Library_Manager().Delete_Tool(pTool);
391 
392 	return( false );
393 }
394 
395 
396 ///////////////////////////////////////////////////////////
397 //														 //
398 ///////////////////////////////////////////////////////////
399 
400 //---------------------------------------------------------
Set_Image(CSG_Grid * pBands[3])401 bool CGDAL_Import_WMS::Set_Image(CSG_Grid *pBands[3])
402 {
403 	//-----------------------------------------------------
404 	CSG_Grid	*pMap	= Parameters("TARGET_MAP")->asGrid();
405 
406 	if( !pMap )
407 	{
408 		pMap	= SG_Create_Grid();
409 	}
410 
411 	if( !pMap->Get_System().is_Equal(pBands[0]->Get_System()) )
412 	{
413 		pMap->Create(pBands[0]->Get_System(), SG_DATATYPE_Int);
414 	}
415 
416 	pMap->Set_Name(_TL("Open Street Map"));
417 
418 	pMap->Get_Projection()	= pBands[0]->Get_Projection();
419 
420 	//-----------------------------------------------------
421 	bool	bGrayscale	= Parameters("GRAYSCALE")->asBool();
422 
423 	#pragma omp parallel for
424 	for(int y=0; y<pMap->Get_NY(); y++)	for(int x=0; x<pMap->Get_NX(); x++)
425 	{
426 		if( bGrayscale )
427 		{
428 			double	z	= (pBands[0]->asInt(x, y) + pBands[1]->asInt(x, y) + pBands[2]->asInt(x, y)) / 3.0;
429 
430 			pMap->Set_Value(x, y, SG_GET_RGB(z, z, z));
431 		}
432 		else
433 		{
434 			pMap->Set_Value(x, y, SG_GET_RGB(pBands[0]->asInt(x, y), pBands[1]->asInt(x, y), pBands[2]->asInt(x, y)));
435 		}
436 	}
437 
438 	delete(pBands[0]);
439 	delete(pBands[1]);
440 	delete(pBands[2]);
441 
442 	Parameters("MAP")->Set_Value(pMap);
443 
444 	DataObject_Add(pMap);
445 	DataObject_Set_Parameter(pMap, "COLORS_TYPE", 5);	// Color Classification Type: RGB Coded Values
446 
447 	return( true );
448 }
449 
450 
451 ///////////////////////////////////////////////////////////
452 //														 //
453 ///////////////////////////////////////////////////////////
454 
455 //---------------------------------------------------------
Get_Bands(CSG_Grid * pBands[3],const CSG_Grid_System & System)456 bool CGDAL_Import_WMS::Get_Bands(CSG_Grid *pBands[3], const CSG_Grid_System &System)
457 {
458 	//-----------------------------------------------------
459 	CSG_GDAL_DataSet	DataSet;
460 
461 	if( DataSet.Open_Read(Get_Request(), System) == false || DataSet.Get_Count() != 3 )
462 	{
463 		return( false );
464 	}
465 
466 	Message_Add("\n", false);
467 	Message_Fmt("\n%s: %s", _TL("Driver" ), DataSet.Get_DriverID().c_str());
468 	Message_Fmt("\n%s: %d", _TL("Bands"  ), DataSet.Get_Count()           );
469 	Message_Fmt("\n%s: %d", _TL("Rows"   ), DataSet.Get_NX()              );
470 	Message_Fmt("\n%s: %d", _TL("Columns"), DataSet.Get_NY()              );
471 	Message_Add("\n", false);
472 
473 	//-----------------------------------------------------
474 	SG_UI_Progress_Lock(true);
475 
476 	pBands[0]	= DataSet.Read(0);
477 	pBands[1]	= DataSet.Read(1);
478 	pBands[2]	= DataSet.Read(2);
479 
480 	SG_UI_Progress_Lock(false);
481 
482 	//-----------------------------------------------------
483 	if( !pBands[0] || !pBands[1] || !pBands[2] )
484 	{
485 		if( pBands[0] )	delete(pBands[0]);
486 		if( pBands[1] )	delete(pBands[1]);
487 		if( pBands[2] )	delete(pBands[2]);
488 
489 		return( false );
490 	}
491 
492 	return( true );
493 }
494 
495 
496 ///////////////////////////////////////////////////////////
497 //														 //
498 ///////////////////////////////////////////////////////////
499 
500 //---------------------------------------------------------
Get_Request(void)501 CSG_String CGDAL_Import_WMS::Get_Request(void)
502 {
503 	CSG_String	Server, Projection	= "EPSG:3857";
504 
505 	switch( Parameters("SERVER")->asInt() )
506 	{
507 	default:	Server	= "tile.openstreetmap.org/${z}/${x}/${y}.png"                                                     ;	break;	// Open Street Map
508 	case  1:	Server	= "mt.google.com/vt/lyrs=m&x=${x}&y=${y}&z=${z}"                                                  ;	break;	// Google Map
509 	case  2:	Server	= "mt.google.com/vt/lyrs=s&x=${x}&y=${y}&z=${z}"                                                  ;	break;	// Google Satellite
510 	case  3:	Server	= "mt.google.com/vt/lyrs=y&x=${x}&y=${y}&z=${z}"                                                  ;	break;	// Google Hybrid
511 	case  4:	Server	= "mt.google.com/vt/lyrs=t&x=${x}&y=${y}&z=${z}"                                                  ;	break;	// Google Terrain
512 	case  5:	Server	= "mt.google.com/vt/lyrs=p&x=${x}&y=${y}&z=${z}"                                                  ;	break;	// Google Terrain, Streets and Water
513 	case  6:	Server	= "services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/${z}/${y}/${x}" ;	break;	// ArcGIS MapServer Tiles
514 	case  7:	Server	= "sgx.geodatenzentrum.de/wmts_topplus_open/tile/1.0.0/web/default/WEBMERCATOR/${z}/${y}/${x}.png";	break;	// TopPlusOpen
515 	case  8:	Server	= Parameters("SERVER_USER")->asString()                                                           ;	break;	// user defined
516 //	case  x:	Server	= "s3.amazonaws.com/com.modestmaps.bluemarble/${z}-r${y}-c${x}.jpg"                               ;	break;	// Blue Marble
517 	}
518 
519 	//-----------------------------------------------------
520 	CSG_MetaData	XML, *pEntry;
521 
522 	XML.Set_Name("GDAL_WMS");
523 
524 	//-----------------------------------------------------
525 	pEntry	= XML.Add_Child("Service");	pEntry->Add_Property("name", "TMS");
526 
527 	pEntry->Add_Child("ServerUrl"  , "http://" + Server);
528 
529 	//-----------------------------------------------------
530 	pEntry	= XML.Add_Child("DataWindow");		// Define size and extents of the data. (required, except for TiledWMS and VirtualEarth)
531 
532 	pEntry->Add_Child("UpperLeftX" , -20037508.34);		// X (longitude) coordinate of upper-left corner. (optional, defaults to -180.0, except for VirtualEarth)
533 	pEntry->Add_Child("UpperLeftY" ,  20037508.34);		// Y (latitude) coordinate of upper-left corner. (optional, defaults to 90.0, except for VirtualEarth)
534 	pEntry->Add_Child("LowerRightX",  20037508.34);		// X (longitude) coordinate of lower-right corner. (optional, defaults to 180.0, except for VirtualEarth)
535 	pEntry->Add_Child("LowerRightY", -20037508.34);		// Y (latitude) coordinate of lower-right corner. (optional, defaults to -90.0, except for VirtualEarth)
536 	pEntry->Add_Child("TileLevel"  ,           18);		// Tile level at highest resolution. (tiled image sources only, optional, defaults to 0)
537 	pEntry->Add_Child("TileCountX" ,            1);		// Can be used to define image size, SizeX = TileCountX * BlockSizeX * 2TileLevel. (tiled image sources only, optional, defaults to 0)
538 	pEntry->Add_Child("TileCountY" ,            1);		// Can be used to define image size, SizeY = TileCountY * BlockSizeY * 2TileLevel. (tiled image sources only, optional, defaults to 0)
539 	pEntry->Add_Child("YOrigin"    ,        "top");		// Can be used to define the position of the Y origin with respect to the tile grid. Possible values are 'top', 'bottom', and 'default', where the default behavior is mini-driver-specific. (TMS mini-driver only, optional, defaults to 'bottom' for TMS)
540 
541 	//-----------------------------------------------------
542 	if( !Projection.is_Empty() )
543 	{
544 		pEntry	= XML.Add_Child("Projection", Projection);	// Image projection (optional, defaults to value reported by mini-driver or EPSG:4326)
545 	}
546 
547 	pEntry	= XML.Add_Child("BandsCount",         3);	// Number of bands/channels, 1 for grayscale data, 3 for RGB, 4 for RGBA. (optional, defaults to 3)
548 
549 	int	Blocksize	= Parameters("BLOCKSIZE")->asInt();
550 	pEntry	= XML.Add_Child("BlockSizeX", Blocksize);	// Block size in pixels. (optional, defaults to 1024, except for VirtualEarth)
551 	pEntry	= XML.Add_Child("BlockSizeY", Blocksize);
552 
553 	//-----------------------------------------------------
554 	if( Parameters("CACHE")->asBool() )
555 	{
556 		pEntry	= XML.Add_Child("Cache");
557 
558 		CSG_String	Path	= Parameters("CACHE_DIR")->asString();
559 
560 		if( !SG_Dir_Exists(Path) )
561 		{
562 			Path	= SG_Dir_Get_Temp();
563 		}
564 
565 		pEntry->Add_Child("Path", SG_File_Make_Path(Path, SG_T("gdalwmscache")));
566 	}
567 
568 	//-----------------------------------------------------
569 	return( XML.asText(2) );
570 }
571 
572 
573 ///////////////////////////////////////////////////////////
574 //														 //
575 //														 //
576 //														 //
577 ///////////////////////////////////////////////////////////
578 
579 //---------------------------------------------------------
580