1 : /******************************************************************************
2 : *
3 : * Project: WMS 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 Pleasea at jpl.nasa.gov)
7 : * Adam Nowacki
8 : *
9 : ******************************************************************************
10 : * Copyright (c) 2007, Adam Nowacki
11 : *
12 : * Permission is hereby granted, free of charge, to any person obtaining a
13 : * copy of this software and associated documentation files (the "Software"),
14 : * to deal in the Software without restriction, including without limitation
15 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 : * and/or sell copies of the Software, and to permit persons to whom the
17 : * Software is furnished to do so, subject to the following conditions:
18 : *
19 : * The above copyright notice and this permission notice shall be included
20 : * in all copies or substantial portions of the Software.
21 : *
22 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 : * DEALINGS IN THE SOFTWARE.
29 : ****************************************************************************/
30 :
31 : #include "stdinc.h"
32 :
33 1061 : CPP_GDALWMSMiniDriverFactory(TiledWMS)
34 :
35 : /************************************************************************/
36 : /* SearchXMLSiblings() */
37 : /************************************************************************/
38 :
39 : /*
40 : * \brief Search for a sibling of the root node with a given name.
41 : *
42 : * Searches only the next siblings of the node passed in for the named element or attribute.
43 : * If the first character of the pszElement is '=', the search includes the psRoot node
44 : *
45 : * @param psRoot the root node to search. This should be a node of type
46 : * CXT_Element. NULL is safe.
47 : *
48 : * @param pszElement the name of the element or attribute to search for.
49 : *
50 : *
51 : * @return The first matching node or NULL on failure.
52 : */
53 :
54 26 : static CPLXMLNode *SearchXMLSiblings( CPLXMLNode *psRoot, const char *pszElement )
55 :
56 : {
57 26 : if( psRoot == NULL || pszElement == NULL )
58 0 : return NULL;
59 :
60 : // If the strings starts with '=', include the current node
61 26 : if (pszElement[0]=='=') {
62 24 : if (EQUAL(psRoot->pszValue,pszElement+1))
63 22 : return psRoot;
64 2 : else return SearchXMLSiblings(psRoot,pszElement+1);
65 : }
66 :
67 : // Only search the siblings, starting with psRoot->psNext
68 13 : for (psRoot=psRoot->psNext;psRoot!=NULL;psRoot=psRoot->psNext) {
69 13 : if ( (psRoot->eType == CXT_Element ||
70 : psRoot->eType == CXT_Attribute)
71 : && EQUAL(pszElement,psRoot->pszValue) )
72 2 : return psRoot;
73 : }
74 :
75 0 : return NULL;
76 : }
77 :
78 : /************************************************************************/
79 : /* SearchLeafGroupName() */
80 : /************************************************************************/
81 :
82 : /*
83 : * \brief Search for a leaf TileGroup node by name.
84 : *
85 : * @param psRoot the root node to search. This should be a node of type
86 : * CXT_Element. NULL is safe.
87 : *
88 : * @param pszElement the name of the TileGroup to search for.
89 : *
90 : * @return The XML node of the matching TileGroup or NULL on failure.
91 : */
92 :
93 75 : static CPLXMLNode *SearchLeafGroupName( CPLXMLNode *psRoot, const char *name )
94 :
95 : {
96 75 : CPLXMLNode *ret=NULL;
97 :
98 75 : if( psRoot == NULL || name == NULL ) return NULL;
99 :
100 : // Has to be a leaf TileGroup with the right name
101 71 : if (NULL==CPLSearchXMLNode(psRoot->psChild,"=TiledGroup"))
102 : {
103 67 : if (EQUAL(name,CPLGetXMLValue(psRoot,"Name","")))
104 : {
105 2 : return psRoot;
106 : }
107 : else
108 : { // Try a sibling
109 65 : return SearchLeafGroupName(psRoot->psNext,name);
110 : }
111 : }
112 : else
113 : { // Is metagroup, try children then siblings
114 4 : ret=SearchLeafGroupName(psRoot->psChild,name);
115 4 : if (NULL!=ret) return ret;
116 4 : return SearchLeafGroupName(psRoot->psNext,name);
117 : }
118 : }
119 :
120 : /************************************************************************/
121 : /* BandInterp() */
122 : /************************************************************************/
123 :
124 : /*
125 : * \brief Utility function to calculate color band interpretation.
126 : * Only handles Gray, GrayAlpha, RGB and RGBA, based on total band count
127 : *
128 : * @param nbands is the total number of bands in the image
129 : *
130 : * @param band is the band number, starting with 1
131 : *
132 : * @return GDALColorInterp of the band
133 : */
134 :
135 4 : static GDALColorInterp BandInterp(int nbands, int band) {
136 4 : switch (nbands) {
137 1 : case 1: return GCI_GrayIndex;
138 0 : case 2: return ((band==1)?GCI_GrayIndex:GCI_AlphaBand);
139 : case 3: // RGB
140 : case 4: // RBGA
141 3 : if (band<3)
142 2 : return ((band==1)?GCI_RedBand:GCI_GreenBand);
143 1 : return ((band==3)?GCI_BlueBand:GCI_AlphaBand);
144 : default:
145 0 : return GCI_Undefined;
146 : }
147 : }
148 :
149 : /************************************************************************/
150 : /* FindBbox() */
151 : /************************************************************************/
152 :
153 : /*
154 : * \brief Utility function to find the position of the bbox parameter value
155 : * within a request string. The search for the bbox is case insensitive
156 : *
157 : * @param in, the string to search into
158 : *
159 : * @return The position from the begining of the string or -1 if not found
160 : */
161 :
162 205 : static int FindBbox(CPLString in) {
163 :
164 205 : size_t pos = in.ifind("&bbox=");
165 205 : if (pos == std::string::npos)
166 0 : return -1;
167 : else
168 205 : return (int)pos + 6;
169 : }
170 :
171 : /************************************************************************/
172 : /* FindChangePattern() */
173 : /************************************************************************/
174 :
175 : /*
176 : * \brief Utility function to pick the right request pattern based on
177 : * the change request list
178 : *
179 : * @param cdata, the list of possible requests, white space separated
180 : * @param substs, the list of substitutions
181 : * @param ret The best match request
182 : */
183 :
184 24 : void FindChangePattern( char *cdata,char **substs, CPLString &ret) {
185 : char **papszTokens=CSLTokenizeString2(cdata," \t\n\r",
186 24 : CSLT_STRIPLEADSPACES|CSLT_STRIPENDSPACES);
187 :
188 24 : int matchcount=CSLCount(substs);
189 24 : for (int j=0;j<CSLCount(papszTokens);j++)
190 : {
191 24 : int thiscount=0;
192 24 : CPLString this_string=papszTokens[j];
193 24 : for (int i=0;i<matchcount;i++) {
194 0 : char *key = NULL;
195 0 : CPLParseNameValue(substs[i],&key);
196 0 : if (key)
197 : {
198 0 : if (std::string::npos!=this_string.find(key,0))
199 0 : thiscount++;
200 0 : CPLFree(key);
201 : }
202 : }
203 24 : if (thiscount==matchcount) {
204 24 : ret=papszTokens[j];
205 : break;
206 : }
207 : }
208 :
209 : // if no match is found, return first string
210 24 : if (ret.empty()) ret=papszTokens[0];
211 24 : CSLDestroy(papszTokens);
212 24 : }
213 :
214 2 : GDALWMSMiniDriver_TiledWMS::GDALWMSMiniDriver_TiledWMS() {
215 2 : m_requests = NULL;
216 2 : m_substs = NULL;
217 2 : }
218 :
219 2 : GDALWMSMiniDriver_TiledWMS::~GDALWMSMiniDriver_TiledWMS() {
220 2 : CSLDestroy(m_requests);
221 2 : CSLDestroy(m_substs);
222 2 : }
223 :
224 :
225 : // Returns the scale of a WMS request as compared to the base resolution
226 181 : double GDALWMSMiniDriver_TiledWMS::Scale(const char *request) {
227 181 : int bbox=FindBbox(request);
228 181 : if (bbox<0) return 0;
229 : double x,y,X,Y;
230 181 : sscanf(request+bbox,"%lf,%lf,%lf,%lf",&x,&y,&X,&Y);
231 181 : return (m_data_window.m_x1-m_data_window.m_x0)/(X-x)*m_bsx/m_data_window.m_sx;
232 : }
233 :
234 :
235 : // Finds, extracts, and returns the highest resolution request string from a list, starting at item i
236 24 : void GDALWMSMiniDriver_TiledWMS::GetLowestScale(char **& list,int i, CPLString &req) {
237 24 : req="";
238 24 : double scale=-1;
239 24 : int position=-1;
240 205 : while (NULL!=list[i]) {
241 157 : double tscale=Scale(list[i]);
242 157 : if (tscale>=scale) {
243 102 : scale=tscale;
244 102 : position=i;
245 : }
246 157 : i++;
247 : }
248 24 : if (position>-1) {
249 24 : req=list[position];
250 24 : list = CSLRemoveStrings(list,position,1,NULL);
251 : }
252 24 : }
253 :
254 :
255 2 : CPLErr GDALWMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config) {
256 2 : CPLErr ret = CE_None;
257 2 : CPLXMLNode *tileServiceConfig=NULL;
258 2 : CPLHTTPResult *psResult=NULL;
259 2 : CPLXMLNode *TG=NULL;
260 :
261 2 : char **requests=NULL;
262 2 : char **substs=NULL;
263 :
264 2 : for (int once=1;once;once--) { // Something to break out of
265 : // Parse info from the service
266 :
267 2 : m_end_url = CPLGetXMLValue(config,"AdditionalArgs","");
268 4 : m_base_url = CPLGetXMLValue(config, "ServerURL", "");
269 2 : if (m_base_url.empty()) {
270 0 : CPLError(ret=CE_Failure, CPLE_AppDefined, "GDALWMS, WMS mini-driver: ServerURL missing.");
271 0 : break;
272 : }
273 :
274 2 : m_tiledGroupName = CPLGetXMLValue(config, "TiledGroupName", "");
275 2 : if (m_tiledGroupName.empty()) {
276 0 : CPLError(ret=CE_Failure, CPLE_AppDefined, "GDALWMS, Tiled WMS: TiledGroupName missing.");
277 0 : break;
278 : }
279 :
280 : // Change strings, key is an attribute, value is the value of the Change node
281 : // Multiple substitutions are possible
282 2 : TG=CPLSearchXMLNode(config, "Change");
283 2 : while(TG!=NULL) {
284 0 : CPLString name=CPLGetXMLValue(TG,"key","");
285 0 : if (!name.empty()) {
286 0 : CPLString value=CPLGetXMLValue(TG,"","");
287 0 : substs=CSLSetNameValue(substs,name,value);
288 : } else {
289 : CPLError(ret=CE_Failure, CPLE_AppDefined, "GDALWMS, Tiled WMS: Syntax error in configuration file.\n"
290 0 : "Change element needs a non-empty \"key\" attribute");
291 : break;
292 : }
293 0 : TG=SearchXMLSiblings(TG,"Change");
294 : }
295 2 : if (ret!=CE_None) break;
296 :
297 2 : CPLString getTileServiceUrl = m_base_url + "request=GetTileService";
298 2 : psResult = CPLHTTPFetch(getTileServiceUrl, NULL);
299 :
300 2 : if (NULL==psResult) {
301 : CPLError(ret=CE_Failure, CPLE_AppDefined,
302 0 : "GDALWMS, Tiled WMS: Can't use GDAL HTTP, no curl support.");
303 : break;
304 : }
305 :
306 2 : if ((psResult->nStatus!=0)||(NULL==psResult->pabyData)||('\0'==psResult->pabyData[0])) {
307 : CPLError(ret=CE_Failure, CPLE_AppDefined,
308 0 : "GDALWMS, Tiled WMS: Can't get server response to GetTileService.");
309 : break;
310 : }
311 :
312 2 : if (NULL==(tileServiceConfig=CPLParseXMLString((const char*)psResult->pabyData))) {
313 0 : CPLError(ret=CE_Failure,CPLE_AppDefined, "GDALWMS, Tiled WMS: Error parsing the GetTileService response.");
314 : break;
315 : }
316 :
317 2 : m_base_url=CPLGetXMLValue(tileServiceConfig,"TiledPatterns.OnlineResource.xlink:href","");
318 2 : if (m_base_url.empty()) {
319 0 : CPLError(ret=CE_Failure,CPLE_AppDefined, "GDALWMS, Tiled WMS: Can't locate OnlineResource in the server response.");
320 : break;
321 : }
322 :
323 2 : if (NULL==(TG=CPLSearchXMLNode(tileServiceConfig, "TiledPatterns"))) {
324 : CPLError(ret=CE_Failure,CPLE_AppDefined,
325 0 : "GDALWMS, Tiled WMS: Can't locate TiledPatterns in server response.");
326 : break;
327 : }
328 :
329 2 : if (NULL==(TG=SearchLeafGroupName(TG->psChild,m_tiledGroupName))) {
330 : CPLError(ret=CE_Failure,CPLE_AppDefined,
331 0 : "GDALWMS, Tiled WMS: Can't locate TiledGroup in server response.");
332 : break;
333 : }
334 :
335 2 : if (0>(m_bands_count=atoi(CPLGetXMLValue(TG, "Bands", "3")))) {
336 : CPLError(ret=CE_Failure,CPLE_AppDefined,
337 0 : "GDALWMS, Tiled WMS: Invalid number of bands in server response");
338 : break;
339 : }
340 2 : if (!GDALCheckBandCount(m_bands_count, FALSE))
341 : {
342 0 : ret = CE_Failure;
343 : break;
344 : }
345 :
346 2 : m_parent_dataset->WMSSetBandsCount(m_bands_count);
347 2 : m_parent_dataset->WMSSetDataType(GDALGetDataTypeByName(CPLGetXMLValue(TG, "DataType", "Byte")));
348 2 : m_projection_wkt=CPLGetXMLValue(TG, "Projection","");
349 :
350 : // Bounding box for the group itself
351 2 : CPLXMLNode *latlonbbox = CPLSearchXMLNode(TG, "LatLonBoundingBox");
352 2 : if (NULL==latlonbbox) {
353 : CPLError(ret=CE_Failure,CPLE_AppDefined,
354 0 : "GDALWMS, Tiled WMS: Can't locate the LatLonBoundingBox in server response.");
355 : break;
356 : }
357 :
358 2 : m_data_window.m_x0=atof(CPLGetXMLValue(latlonbbox,"minx","0"));
359 2 : m_data_window.m_x1=atof(CPLGetXMLValue(latlonbbox,"maxx","-1"));
360 2 : m_data_window.m_y0=atof(CPLGetXMLValue(latlonbbox,"maxy","0"));
361 2 : m_data_window.m_y1=atof(CPLGetXMLValue(latlonbbox,"miny","-1"));
362 :
363 2 : if ((m_data_window.m_x1-m_data_window.m_x0)<0) {
364 : CPLError(ret=CE_Failure,CPLE_AppDefined,
365 0 : "GDALWMS, Tiled WMS: Coordinate order in boundingbox problem in server response.");
366 : break;
367 : }
368 :
369 2 : m_overview_count=0;
370 2 : CPLXMLNode *Pattern=TG->psChild;
371 :
372 2 : m_bsx=m_bsy=-1;
373 2 : m_data_window.m_sx=m_data_window.m_sy=0;
374 :
375 4 : for (int once=1;once;once--) { // Something to break out of
376 2 : while ((NULL!=Pattern)&&(NULL!=(Pattern=SearchXMLSiblings(Pattern,"=TilePattern")))) {
377 : int mbsx,mbsy;
378 :
379 24 : CPLString request;
380 24 : FindChangePattern(Pattern->psChild->pszValue,substs,request);
381 :
382 24 : char **papszTokens=CSLTokenizeString2(request,"&",0);
383 :
384 24 : mbsx=atoi(CSLFetchNameValue(papszTokens,"WIDTH"));
385 24 : mbsy=atoi(CSLFetchNameValue(papszTokens,"HEIGHT"));
386 24 : if (m_projection_wkt.empty()) {
387 2 : const char* pszSRS = CSLFetchNameValue(papszTokens,"SRS");
388 2 : m_projection_wkt = (pszSRS) ? pszSRS : "";
389 2 : if (!m_projection_wkt.empty())
390 2 : m_projection_wkt=ProjToWKT(m_projection_wkt);
391 : }
392 :
393 24 : if (-1==m_bsx) m_bsx=mbsx;
394 24 : if (-1==m_bsy) m_bsy=mbsy;
395 24 : if ((m_bsy!=mbsy)||(m_bsy!=mbsy)) {
396 : CPLError(ret=CE_Failure,CPLE_AppDefined,
397 0 : "GDALWMS, Tiled WMS: Tileset uses different block sizes.");
398 0 : m_overview_count=0;
399 0 : CSLDestroy(papszTokens);
400 : break;
401 : }
402 :
403 24 : const char* pszBBOX = CSLFetchNameValue(papszTokens,"BBOX");
404 24 : if (pszBBOX == NULL)
405 : {
406 : CPLError(ret=CE_Failure,CPLE_AppDefined,
407 0 : "GDALWMS, Tiled WMS: BBOX parameter not found in server response.");
408 0 : CSLDestroy(papszTokens);
409 : break;
410 : }
411 :
412 : double x,y,X,Y;
413 24 : if (sscanf(pszBBOX,"%lf,%lf,%lf,%lf",&x,&y,&X,&Y) != 4)
414 : {
415 : CPLError(ret=CE_Failure,CPLE_AppDefined,
416 0 : "GDALWMS, Tiled WMS: Invalid value for BBOX parameter in server response.");
417 0 : CSLDestroy(papszTokens);
418 : break;
419 : }
420 24 : int sx=static_cast<int>((m_data_window.m_x1-m_data_window.m_x0)/(X-x)*m_bsx);
421 24 : int sy=static_cast<int>(fabs((m_data_window.m_y1-m_data_window.m_y0)/(Y-y)*m_bsy));
422 24 : if (sx>m_data_window.m_sx) m_data_window.m_sx=sx;
423 24 : if (sy>m_data_window.m_sy) m_data_window.m_sy=sy;
424 24 : CSLDestroy(papszTokens);
425 :
426 : // Only use overlays where the top coordinate is within a pixel from the top of coverage
427 : double pix_off,temp;
428 24 : pix_off=m_bsy*modf(fabs((Y-m_data_window.m_y0)/(Y-y)),&temp);
429 48 : if ((pix_off<1)||((m_bsy-pix_off)<1)) {
430 24 : requests=CSLAddString(requests,request);
431 24 : m_overview_count++;
432 : } else
433 : CPLError(CE_Warning,CPLE_AppDefined,
434 0 : "GDALWMS, Tiled WMS: Overlay size %dX%d can't be used due to alignment",sx,sy);
435 :
436 24 : Pattern=Pattern->psNext;
437 :
438 : }
439 :
440 : // The tlevel is needed, the tx and ty are not used by this minidriver
441 2 : m_data_window.m_tlevel = 0;
442 2 : m_data_window.m_tx = 0;
443 2 : m_data_window.m_ty = 0;
444 :
445 : // Make sure the parent_dataset values are set before creating the bands
446 2 : m_parent_dataset->WMSSetBlockSize(m_bsx,m_bsy);
447 2 : m_parent_dataset->WMSSetRasterSize(m_data_window.m_sx,m_data_window.m_sy);
448 :
449 2 : m_parent_dataset->WMSSetDataWindow(m_data_window);
450 2 : m_parent_dataset->WMSSetOverviewCount(m_overview_count);
451 2 : m_parent_dataset->WMSSetClamp(false);
452 :
453 : // Ready for the Rasterband creation
454 : int i;
455 2 : for (i=0;i<m_overview_count;i++) {
456 24 : CPLString request="";
457 24 : GetLowestScale(requests,i,request);
458 24 : double scale=Scale(request);
459 :
460 24 : if (i == 0)
461 : {
462 2 : if (fabs(scale-1.0) >1e-6)
463 : {
464 : CPLError(ret=CE_Failure,CPLE_AppDefined,
465 0 : "GDALWMS, Tiled WMS: Did not get expected scale : %.15f", scale);
466 : break;
467 : }
468 : }
469 :
470 : // Prepare the request and insert it back into the list
471 24 : int startBbox=FindBbox(request);
472 24 : int BboxSize=request.find_first_of("&",startBbox);
473 24 : request.replace(startBbox,BboxSize,"${GDAL_BBOX}");
474 24 : requests = CSLInsertString(requests,i,request);
475 :
476 : // Create the Rasterband or overview
477 74 : for (int j = 1; j <= m_bands_count; j++) {
478 50 : if (i == 0) {
479 4 : GDALWMSRasterBand *band=new GDALWMSRasterBand(m_parent_dataset, j, scale);
480 4 : band->SetColorInterpretation(BandInterp(m_bands_count,j));
481 4 : m_parent_dataset->mSetBand(j, band);
482 : } else
483 46 : m_parent_dataset->mGetBand(j)->AddOverview(scale);
484 : }
485 : }
486 :
487 2 : if (i != m_overview_count)
488 0 : break;
489 :
490 2 : if ((m_overview_count==0)||(m_bsx<1)||(m_bsy<1)) {
491 : CPLError(ret=CE_Failure,CPLE_AppDefined,
492 0 : "GDALWMS, Tiled WMS: No usable TilePattern elements found");
493 0 : break;
494 : }
495 : }
496 : }
497 :
498 2 : m_requests=requests;
499 2 : m_substs=substs;
500 :
501 2 : if (tileServiceConfig) CPLDestroyXMLNode(tileServiceConfig);
502 2 : if (psResult) CPLHTTPDestroyResult(psResult);
503 :
504 2 : return ret;
505 : }
506 :
507 2 : void GDALWMSMiniDriver_TiledWMS::GetCapabilities(GDALWMSMiniDriverCapabilities *caps) {
508 2 : caps->m_capabilities_version = 1;
509 2 : caps->m_has_arb_overviews = 0;
510 2 : caps->m_has_image_request = 1;
511 2 : caps->m_has_tiled_image_requeset = 1;
512 2 : caps->m_max_overview_count = 32;
513 2 : }
514 :
515 :
516 : // not called
517 0 : void GDALWMSMiniDriver_TiledWMS::ImageRequest(CPLString *url, const GDALWMSImageRequestInfo &iri) {
518 0 : }
519 :
520 2 : void GDALWMSMiniDriver_TiledWMS::TiledImageRequest(CPLString *url, const GDALWMSImageRequestInfo &iri, const GDALWMSTiledImageRequestInfo &tiri) {
521 2 : *url = m_base_url;
522 2 : URLAppend(url,CSLGetField(m_requests,-tiri.m_level));
523 : URLSearchAndReplace(url,"${GDAL_BBOX}","%013.8f,%013.8f,%013.8f,%013.8f",
524 2 : iri.m_x0,iri.m_y1,iri.m_x1,iri.m_y0);
525 2 : if (m_substs!=NULL) {
526 0 : for (int i=0;i<CSLCount(m_substs);i++) {
527 : char *k;
528 0 : const char *v=CPLParseNameValue(m_substs[i],&k);
529 0 : URLSearchAndReplace(url,k,"%s",v);
530 0 : VSIFree(k);
531 : }
532 : }
533 2 : URLAppend(url,m_end_url);
534 2 : }
535 :
536 2 : const char *GDALWMSMiniDriver_TiledWMS::GetProjectionInWKT() {
537 2 : return m_projection_wkt.c_str();
538 : }
539 :
|