1 /******************************************************************************
2  * $Id$
3  *
4  * Project:  MapServer
5  * Purpose:  Google Earth KML output
6  * Author:   David Kana and the MapServer team
7  *
8  ******************************************************************************
9  * Copyright (c) 1996-2009 Regents of the University of Minnesota.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies of this Software or works derived from this Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  *****************************************************************************/
29 
30 #include "mapserver-config.h"
31 #ifdef USE_KML
32 
33 #include "mapserver.h"
34 #include "maperror.h"
35 #include "mapkmlrenderer.h"
36 #include "mapio.h"
37 #include "mapows.h"
38 
39 #include "cpl_conv.h"
40 #include "cpl_vsi.h"
41 
42 #define  KML_MAXFEATURES_TODRAW 1000
43 
KmlRenderer(int width,int height,outputFormatObj * format,colorObj * color)44 KmlRenderer::KmlRenderer(int width, int height, outputFormatObj *format, colorObj* color/*=NULL*/)
45   : Width(width), Height(height), MapCellsize(1.0), XmlDoc(NULL), LayerNode(NULL), GroundOverlayNode(NULL),
46     PlacemarkNode(NULL), GeomNode(NULL),
47     Items(NULL), NumItems(0), FirstLayer(MS_TRUE), map(NULL), currentLayer(NULL),
48     mElevationFromAttribute( false ), mElevationAttributeIndex( -1 ), mCurrentElevationValue(0.0)
49 
50 {
51   /*private variables*/
52   pszLayerDescMetadata = NULL;
53   papszLayerIncludeItems = NULL;
54   nIncludeItems=0;
55   papszLayerExcludeItems = NULL;
56   nExcludeItems=0;
57   pszLayerNameAttributeMetadata = NULL;  /*metadata to use for a name for each feature*/
58 
59   LineStyle = NULL;
60   numLineStyle = 0;
61 
62   xmlNodePtr styleNode;
63   xmlNodePtr listStyleNode;
64   /*  Create document.*/
65   XmlDoc = xmlNewDoc(BAD_CAST "1.0");
66 
67   xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "kml");
68 
69   /*  Name spaces*/
70   xmlSetNs(rootNode, xmlNewNs(rootNode, BAD_CAST "http://www.opengis.net/kml/2.2", NULL));
71 
72   xmlDocSetRootElement(XmlDoc, rootNode);
73 
74   DocNode = xmlNewChild(rootNode, NULL, BAD_CAST "Document", NULL);
75 
76   styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
77   xmlNewProp(styleNode, BAD_CAST "id", BAD_CAST "LayerFolder_check");
78   listStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "ListStyle", NULL);
79   xmlNewChild(listStyleNode, NULL, BAD_CAST "listItemType", BAD_CAST "check");
80 
81   styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
82   xmlNewProp(styleNode, BAD_CAST "id", BAD_CAST "LayerFolder_checkHideChildren");
83   listStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "ListStyle", NULL);
84   xmlNewChild(listStyleNode, NULL, BAD_CAST "listItemType", BAD_CAST "checkHideChildren");
85 
86   styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
87   xmlNewProp(styleNode, BAD_CAST "id", BAD_CAST "LayerFolder_checkOffOnly");
88   listStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "ListStyle", NULL);
89   xmlNewChild(listStyleNode, NULL, BAD_CAST "listItemType", BAD_CAST "checkOffOnly");
90 
91   styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
92   xmlNewProp(styleNode, BAD_CAST "id", BAD_CAST "LayerFolder_radioFolder");
93   listStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "ListStyle", NULL);
94   xmlNewChild(listStyleNode, NULL, BAD_CAST "listItemType", BAD_CAST "radioFolder");
95 
96 
97   StyleHashTable = msCreateHashTable();
98 
99 }
100 
~KmlRenderer()101 KmlRenderer::~KmlRenderer()
102 {
103   if (XmlDoc)
104     xmlFreeDoc(XmlDoc);
105 
106   if (StyleHashTable)
107     msFreeHashTable(StyleHashTable);
108 
109   if(LineStyle)
110     msFree(LineStyle);
111 
112   xmlCleanupParser();
113 }
114 
createImage(int,int,outputFormatObj *,colorObj *)115 imageObj* KmlRenderer::createImage(int, int, outputFormatObj*, colorObj*)
116 {
117   return NULL;
118 }
119 
saveImage(imageObj *,FILE * fp,outputFormatObj * format)120 int KmlRenderer::saveImage(imageObj *, FILE *fp, outputFormatObj *format)
121 {
122   /* -------------------------------------------------------------------- */
123   /*      Write out the document.                                         */
124   /* -------------------------------------------------------------------- */
125 
126   int bufSize = 0;
127   xmlChar *buf = NULL;
128   msIOContext *context = NULL;
129   int chunkSize = 4096;
130   int bZip = MS_FALSE;
131 
132   if( msIO_needBinaryStdout() == MS_FAILURE )
133     return MS_FAILURE;
134 
135   xmlDocDumpFormatMemoryEnc(XmlDoc, &buf, &bufSize, "UTF-8", 1);
136 
137   if (format && format->driver && strcasecmp(format->driver, "kmz") == 0) {
138     bZip = MS_TRUE;
139   }
140 
141   if (bZip) {
142     VSILFILE *fpZip;
143     int bytes_read;
144     char buffer[1024];
145     char *zip_filename =NULL;
146     void *hZip=NULL;
147 
148     zip_filename = msTmpFile(NULL, NULL, "/vsimem/kmlzip/", "kmz" );
149     hZip = CPLCreateZip( zip_filename, NULL );
150     CPLCreateFileInZip( hZip, "mapserver.kml", NULL );
151     for (int i=0; i<bufSize; i+=chunkSize) {
152       int size = chunkSize;
153       if (i + size > bufSize)
154         size = bufSize - i;
155       CPLWriteFileInZip( hZip,  buf+i, size);
156     }
157     CPLCloseFileInZip( hZip );
158     CPLCloseZip( hZip );
159 
160     context = msIO_getHandler(fp);
161     fpZip = VSIFOpenL( zip_filename, "r" );
162 
163     while( (bytes_read = VSIFReadL( buffer, 1, sizeof(buffer), fpZip )) > 0 ) {
164       if (context)
165         msIO_contextWrite(context, buffer, bytes_read);
166       else
167         msIO_fwrite( buffer, 1, bytes_read, fp );
168     }
169     VSIFCloseL( fpZip );
170     msFree( zip_filename);
171     xmlFree(buf);
172     return(MS_SUCCESS);
173   }
174 
175   context = msIO_getHandler(fp);
176 
177 
178   for (int i=0; i<bufSize; i+=chunkSize) {
179     int size = chunkSize;
180     if (i + size > bufSize)
181       size = bufSize - i;
182 
183     if (context)
184       msIO_contextWrite(context, buf+i, size);
185     else
186       msIO_fwrite(buf+i, 1, size, fp);
187   }
188 
189   xmlFree(buf);
190 
191   return(MS_SUCCESS);
192 }
193 
194 
195 /************************************************************************/
196 /*                               processLayer                           */
197 /*                                                                      */
198 /*      Set parameters that make sense to a kml output.                 */
199 /************************************************************************/
processLayer(layerObj * layer,outputFormatObj * format)200 void KmlRenderer::processLayer(layerObj *layer, outputFormatObj *format)
201 {
202   int i;
203   const char *asRaster = NULL;
204   int nMaxFeatures = -1;
205   const char *pszTmp;
206   char szTmp[10];
207 
208   if (!layer)
209     return;
210 
211   /*turn of labelcache*/
212   layer->labelcache = MS_OFF;
213 
214   /*if there are labels we want the coordinates to
215     be the center of the element.*/
216   for(i=0; i<layer->numclasses; i++)
217     if(layer->_class[i]->numlabels > 0) layer->_class[i]->labels[0]->position = MS_XY;
218 
219   /*we do not want to draw multiple styles.
220     the new rendering architecture does not allow
221     to know if we are dealing with a multi-style.
222     So here we remove all styles beside the first one*/
223 
224   for(i=0; i<layer->numclasses; i++) {
225     while (layer->_class[i]->numstyles > 1)
226       msDeleteStyle(layer->_class[i], layer->_class[i]->numstyles-1);
227   }
228 
229   /*if layer has a metadata KML_OUTPUTASRASTER set to true, add a processing directive
230     to use an agg driver*/
231   asRaster = msLookupHashTable(&layer->metadata, "kml_outputasraster");
232   if (!asRaster)
233     asRaster = msLookupHashTable(&(layer->map->web.metadata), "kml_outputasraster");
234   if (asRaster && (strcasecmp(asRaster, "true") == 0 ||
235                    strcasecmp(asRaster, "yes") == 0))
236     msLayerAddProcessing(layer, "RENDERER=png24");
237 
238 
239   /*set a maxfeaturestodraw, if not already set*/
240 
241   pszTmp = msLookupHashTable(&layer->metadata, "maxfeaturestodraw");
242   if (pszTmp)
243     nMaxFeatures = atoi(pszTmp);
244   else {
245     pszTmp = msLookupHashTable(&layer->map->web.metadata, "maxfeaturestodraw");
246     if (pszTmp)
247       nMaxFeatures = atoi(pszTmp);
248   }
249   if (nMaxFeatures < 0 && format)
250     nMaxFeatures = atoi(msGetOutputFormatOption( format, "maxfeaturestodraw", "-1"));
251 
252   if (nMaxFeatures < 0 && format) {
253     snprintf(szTmp, sizeof(szTmp), "%d", KML_MAXFEATURES_TODRAW);
254     msSetOutputFormatOption( format, "maxfeaturestodraw", szTmp);
255   }
256 
257 }
258 
259 /************************************************************************/
260 /*                               getLayerName                           */
261 /*                                                                      */
262 /*      Internal utility function to build name used fo rthe layer.     */
263 /************************************************************************/
getLayerName(layerObj * layer)264 char* KmlRenderer::getLayerName(layerObj *layer)
265 {
266   char stmp[20];
267   const char *name=NULL;;
268 
269   if (!layer)
270     return NULL;
271 
272 
273   name = msLookupHashTable(&layer->metadata, "ows_name");
274   if (name && strlen(name) > 0)
275     return msStrdup(name);
276 
277   if (layer->name && strlen(layer->name) > 0)
278     return msStrdup(layer->name);
279 
280   sprintf(stmp, "Layer%d",layer->index);
281   return msStrdup(stmp);
282 
283 }
284 
getAliasName(layerObj * lp,char * pszItemName,const char * namespaces)285 const char* KmlRenderer::getAliasName(layerObj *lp, char *pszItemName, const char *namespaces)
286 {
287   const char *pszAlias = NULL;
288   if (lp && pszItemName && strlen(pszItemName) > 0) {
289     char szTmp[256];
290     snprintf(szTmp, sizeof(szTmp), "%s_alias", pszItemName);
291     pszAlias = msOWSLookupMetadata(&(lp->metadata), namespaces, szTmp);
292   }
293   return pszAlias;
294 }
295 
startNewLayer(imageObj * img,layerObj * layer)296 int KmlRenderer::startNewLayer(imageObj *img, layerObj *layer)
297 {
298   char *layerName=NULL;
299   const char *value=NULL;
300 
301   LayerNode = xmlNewNode(NULL, BAD_CAST "Folder");
302 
303   layerName = getLayerName(layer);
304   xmlNewChild(LayerNode, NULL, BAD_CAST "name", BAD_CAST layerName);
305   msFree(layerName);
306 
307   const char *layerVisibility = layer->status != MS_OFF ? "1" : "0";
308   xmlNewChild(LayerNode, NULL, BAD_CAST "visibility", BAD_CAST layerVisibility);
309 
310   const char *layerDsiplayFolder = msLookupHashTable(&(layer->metadata), "kml_folder_display");
311   if (layerDsiplayFolder == NULL)
312     layerDsiplayFolder = msLookupHashTable(&(layer->map->web.metadata), "kml_folder_display");
313   if (!layerDsiplayFolder || strlen(layerDsiplayFolder)<=0) {
314     xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl", BAD_CAST "#LayerFolder_check");
315   }
316 
317   else {
318     if (strcasecmp(layerDsiplayFolder, "checkHideChildren") == 0)
319       xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl", BAD_CAST "#LayerFolder_checkHideChildren");
320     else if (strcasecmp(layerDsiplayFolder, "checkOffOnly") == 0)
321       xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl", BAD_CAST "#LayerFolder_checkOffOnly");
322     else if (strcasecmp(layerDsiplayFolder, "radioFolder") == 0)
323       xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl", BAD_CAST "#LayerFolder_radioFolder");
324     else
325       xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl", BAD_CAST "#LayerFolder_check");
326   }
327 
328 
329   /*Init few things on the first layer*/
330   if (FirstLayer) {
331     FirstLayer = MS_FALSE;
332     map = layer->map;
333 
334     if (layer->map->mappath)
335       snprintf(MapPath, sizeof(MapPath), "%s", layer->map->mappath);
336 
337     /*First rendered layer - check mapfile projection*/
338     checkProjection(layer->map);
339 
340     /*check for image path and image url*/
341     if (layer->map->debug && (layer->map->web.imageurl == NULL ||   layer->map->web.imagepath == NULL))
342       msDebug("KmlRenderer::startNewLayer: imagepath and imageurl should be set in the web object\n");
343 
344 
345     /*map rect for ground overlay*/
346     MapExtent = layer->map->extent;
347     MapCellsize = layer->map->cellsize;
348     BgColor = layer->map->imagecolor;
349 
350     xmlNewChild(DocNode, NULL, BAD_CAST "name", BAD_CAST layer->map->name);
351     aggFormat = msSelectOutputFormat( layer->map, "png24");
352     aggFormat->transparent = MS_TRUE;
353 
354   }
355 
356   currentLayer = layer;
357 
358   if (!msLayerIsOpen(layer)) {
359     if (msLayerOpen(layer) != MS_SUCCESS) {
360       msSetError(MS_MISCERR, "msLayerOpen failed", "KmlRenderer::startNewLayer" );
361       return MS_FAILURE;
362     }
363   }
364 
365   /*pre process the layer to set things that make sense for kml output*/
366   if (img)
367     processLayer(layer, img->format);
368   else
369     processLayer(layer, NULL);
370 
371   if (msLookupHashTable(&layer->metadata, "kml_description"))
372     pszLayerDescMetadata = msLookupHashTable(&layer->metadata, "kml_description");
373   else if (msLookupHashTable(&layer->metadata, "ows_description"))
374     pszLayerDescMetadata = msLookupHashTable(&layer->metadata, "ows_description");
375 
376   value=msLookupHashTable(&layer->metadata, "kml_include_items");
377   if (!value)
378     value=msLookupHashTable(&layer->metadata, "ows_include_items");
379   if (value)
380     papszLayerIncludeItems = msStringSplit(value, ',', &nIncludeItems);
381 
382   value=msLookupHashTable(&layer->metadata, "kml_exclude_items");
383   if (!value)
384     value=msLookupHashTable(&layer->metadata, "ows_exclude_items");
385   if (value)
386     papszLayerExcludeItems = msStringSplit(value, ',', &nExcludeItems);
387 
388 
389   if (msLookupHashTable(&layer->metadata, "kml_name_item"))
390     pszLayerNameAttributeMetadata = msLookupHashTable(&layer->metadata, "kml_name_item");
391 
392   /*get all attributes*/
393   if(msLayerWhichItems(layer, MS_TRUE, NULL) != MS_SUCCESS) {
394     return MS_FAILURE;
395   }
396 
397 
398   NumItems = layer->numitems;
399   if (NumItems) {
400     Items = (char **)msSmallCalloc(NumItems, sizeof(char *));
401     for (int i=0; i<NumItems; i++)
402       Items[i] = msStrdup(layer->items[i]);
403   }
404 
405 
406   const char* elevationAttribute = msLookupHashTable(&layer->metadata, "kml_elevation_attribute");
407   if( elevationAttribute ) {
408     mElevationFromAttribute = true;
409     for( int i = 0; i < layer->numitems; ++i ) {
410       if( strcasecmp( layer->items[i], elevationAttribute ) == 0 ) {
411         mElevationAttributeIndex = i;
412       }
413     }
414   }
415 
416   setupRenderingParams(&layer->metadata);
417   return MS_SUCCESS;
418 }
419 
closeNewLayer(imageObj * img,layerObj * layer)420 int KmlRenderer::closeNewLayer(imageObj *img, layerObj *layer)
421 {
422   flushPlacemark();
423 
424   xmlAddChild(DocNode, LayerNode);
425 
426   if(Items) {
427     msFreeCharArray(Items, NumItems);
428     Items = NULL;
429     NumItems = 0;
430   }
431 
432   if (pszLayerDescMetadata)
433     pszLayerDescMetadata = NULL;
434   if (pszLayerNameAttributeMetadata)
435     pszLayerNameAttributeMetadata = NULL;
436 
437   if (papszLayerIncludeItems && nIncludeItems>0)
438     msFreeCharArray(papszLayerIncludeItems, nIncludeItems);
439   papszLayerIncludeItems=NULL;
440 
441   if (papszLayerExcludeItems && nExcludeItems>0)
442     msFreeCharArray(papszLayerExcludeItems, nExcludeItems);
443   papszLayerExcludeItems=NULL;
444 
445   return MS_SUCCESS;
446 }
447 
mergeRasterBuffer(imageObj * image,rasterBufferObj * rb)448 int KmlRenderer::mergeRasterBuffer(imageObj *image, rasterBufferObj *rb)
449 {
450   assert(rb && rb->type == MS_BUFFER_BYTE_RGBA);
451   char *tmpFileName = NULL;
452   char *tmpUrl = NULL;
453   FILE *tmpFile = NULL;
454 
455   tmpFileName = msTmpFile(NULL, MapPath, image->imagepath, "png");
456   tmpFile = fopen(tmpFileName,"wb");
457   if (tmpFile) {
458 
459     if (!aggFormat->vtable)
460       msInitializeRendererVTable(aggFormat);
461 
462     msSaveRasterBuffer(map,rb,tmpFile,aggFormat);
463     tmpUrl = msStrdup( image->imageurl);
464     tmpUrl = msStringConcatenate(tmpUrl, (char *)(msGetBasename(tmpFileName)));
465     tmpUrl = msStringConcatenate(tmpUrl, ".png");
466 
467     createGroundOverlayNode(LayerNode, tmpUrl, currentLayer);
468     msFree(tmpFileName);
469     msFree(tmpUrl);
470     fclose(tmpFile);
471     return MS_SUCCESS;
472   } else {
473     msSetError(MS_IOERR,"Failed to create file for kml overlay","KmlRenderer::mergeRasterBuffer()");
474     return MS_FAILURE;
475   }
476 }
477 
setupRenderingParams(hashTableObj * layerMetadata)478 void KmlRenderer::setupRenderingParams(hashTableObj *layerMetadata)
479 {
480   AltitudeMode = 0;
481   Extrude = 0;
482   Tessellate = 0;
483 
484   const char *altitudeModeVal = msLookupHashTable(layerMetadata, "kml_altitudeMode");
485   if (altitudeModeVal) {
486     if(strcasecmp(altitudeModeVal, "absolute") == 0)
487       AltitudeMode = absolute;
488     else if(strcasecmp(altitudeModeVal, "relativeToGround") == 0)
489       AltitudeMode = relativeToGround;
490     else if(strcasecmp(altitudeModeVal, "clampToGround") == 0)
491       AltitudeMode = clampToGround;
492   }
493 
494   const char *extrudeVal = msLookupHashTable(layerMetadata, "kml_extrude");
495   if (altitudeModeVal) {
496     Extrude = atoi(extrudeVal);
497   }
498 
499   const char *tessellateVal = msLookupHashTable(layerMetadata, "kml_tessellate");
500   if (tessellateVal) {
501     Tessellate = atoi(tessellateVal);
502   }
503 
504 }
505 
checkProjection(mapObj * map)506 int KmlRenderer::checkProjection(mapObj *map)
507 {
508   projectionObj *projection= &map->projection;
509   if (projection && projection->numargs > 0 && msProjIsGeographicCRS(projection)) {
510     return MS_SUCCESS;
511   } else {
512     char epsg_string[100];
513     rectObj sRect;
514     projectionObj out;
515 
516     /* for layer the do not have any projection set, set them with the current map
517        projection*/
518     if (projection && projection->numargs > 0) {
519       layerObj *lp = NULL;
520       int i =0;
521       char *pszMapProjectString = msGetProjectionString(projection);
522       if (pszMapProjectString) {
523         for(i=0; i<map->numlayers; i++) {
524           lp = GET_LAYER(map, i);
525           if (lp->projection.numargs == 0 && lp->transform == MS_TRUE) {
526             msFreeProjection(&lp->projection);
527             msLoadProjectionString(&lp->projection, pszMapProjectString);
528           }
529         }
530         msFree(pszMapProjectString);
531       }
532     }
533     strcpy(epsg_string, "epsg:4326" );
534     msInitProjection(&out);
535     msProjectionInheritContextFrom(&out, projection);
536     msLoadProjectionString(&out, epsg_string);
537 
538     sRect = map->extent;
539     msProjectRect(projection, &out, &sRect);
540     msFreeProjectionExceptContext(projection);
541     msLoadProjectionString(projection, epsg_string);
542 
543     /*change also units and extents*/
544     map->extent = sRect;
545     map->units = MS_DD;
546 
547 
548     if (map->debug)
549       msDebug("KmlRenderer::checkProjection: Mapfile projection set to epsg:4326\n");
550 
551     return MS_SUCCESS;
552   }
553 }
554 
createPlacemarkNode(xmlNodePtr parentNode,char * styleUrl)555 xmlNodePtr KmlRenderer::createPlacemarkNode(xmlNodePtr parentNode, char *styleUrl)
556 {
557   xmlNodePtr placemarkNode = xmlNewChild(parentNode, NULL, BAD_CAST "Placemark", NULL);
558   /*always add a name. It will be replaced by a text value if available*/
559   char tmpid[100];
560   char *stmp=NULL, *layerName=NULL;
561   if (CurrentShapeName && strlen(CurrentShapeName)>0) {
562     xmlNewChild(placemarkNode, NULL, BAD_CAST "name", BAD_CAST CurrentShapeName);
563   } else {
564     sprintf(tmpid, ".%d", CurrentShapeIndex);
565     layerName = getLayerName(currentLayer);
566     stmp = msStringConcatenate(stmp, layerName);
567     stmp = msStringConcatenate(stmp, tmpid);
568     xmlNewChild(placemarkNode, NULL, BAD_CAST "name", BAD_CAST stmp);
569   }
570   msFree(layerName);
571   msFree(stmp);
572   if (styleUrl)
573     xmlNewChild(placemarkNode, NULL, BAD_CAST "styleUrl", BAD_CAST styleUrl);
574 
575   return placemarkNode;
576 }
577 
renderLine(imageObj *,shapeObj * p,strokeStyleObj * style)578 void KmlRenderer::renderLine(imageObj*, shapeObj *p, strokeStyleObj *style)
579 {
580   if (p->numlines == 0)
581     return;
582 
583   if (PlacemarkNode == NULL)
584     PlacemarkNode = createPlacemarkNode(LayerNode, NULL);
585 
586   if (!PlacemarkNode)
587     return;
588 
589   addLineStyleToList(style);
590   SymbologyFlag[Line] = 1;
591 
592   /*p->index > CurrentDrawnShapeIndexneed to be reviewd. Added since the hight
593     level code caches shapes when rendering lines*/
594   if (CurrentDrawnShapeIndex == -1 || p->index > CurrentDrawnShapeIndex) {
595     xmlNodePtr geomNode = getGeomParentNode("LineString");
596     addAddRenderingSpecifications(geomNode);
597     addCoordsNode(geomNode, p->line[0].point, p->line[0].numpoints);
598 
599     /* more than one line => MultiGeometry*/
600     if (p->numlines > 1) {
601       geomNode = getGeomParentNode("LineString"); // returns MultiGeom Node
602       for (int i=1; i<p->numlines; i++) {
603         xmlNodePtr lineStringNode = xmlNewChild(geomNode, NULL, BAD_CAST "LineString", NULL);
604         addAddRenderingSpecifications(lineStringNode);
605         addCoordsNode(lineStringNode, p->line[i].point, p->line[i].numpoints);
606       }
607     }
608 
609     CurrentDrawnShapeIndex = p->index;
610   }
611 
612 }
613 
renderPolygon(imageObj *,shapeObj * p,colorObj * color)614 void KmlRenderer::renderPolygon(imageObj*, shapeObj *p, colorObj *color)
615 {
616   if (PlacemarkNode == NULL)
617     PlacemarkNode = createPlacemarkNode(LayerNode, NULL);
618 
619   if (!PlacemarkNode)
620     return;
621 
622   memcpy(&PolygonColor, color, sizeof(colorObj));
623   SymbologyFlag[Polygon] = 1;
624 
625 
626   if (p->index != CurrentDrawnShapeIndex) {
627 
628     xmlNodePtr geomParentNode = getGeomParentNode("Polygon");
629 
630     for (int i=0; i<p->numlines; i++) {
631       xmlNodePtr bdryNode = NULL;
632 
633       if (i==0) /* __TODO__ check ring order*/
634         bdryNode = xmlNewChild(geomParentNode, NULL, BAD_CAST "outerBoundaryIs", NULL);
635       else
636         bdryNode = xmlNewChild(geomParentNode, NULL, BAD_CAST "innerBoundaryIs", NULL);
637 
638       xmlNodePtr ringNode = xmlNewChild(bdryNode, NULL, BAD_CAST "LinearRing", NULL);
639       addAddRenderingSpecifications(ringNode);
640       addCoordsNode(ringNode, p->line[i].point, p->line[i].numpoints);
641     }
642 
643     CurrentDrawnShapeIndex = p->index;
644 
645   }
646 
647 }
648 
addCoordsNode(xmlNodePtr parentNode,pointObj * pts,int numPts)649 void KmlRenderer::addCoordsNode(xmlNodePtr parentNode, pointObj *pts, int numPts)
650 {
651   char lineBuf[128];
652 
653   xmlNodePtr coordsNode = xmlNewChild(parentNode, NULL, BAD_CAST "coordinates", NULL);
654   xmlNodeAddContent(coordsNode, BAD_CAST "\n");
655 
656   for (int i=0; i<numPts; i++) {
657     if( mElevationFromAttribute ) {
658       sprintf(lineBuf, "\t%.8f,%.8f,%.8f\n", pts[i].x, pts[i].y, mCurrentElevationValue);
659     } else if (AltitudeMode == relativeToGround || AltitudeMode == absolute) {
660 #ifdef USE_POINT_Z_M
661       sprintf(lineBuf, "\t%.8f,%.8f,%.8f\n", pts[i].x, pts[i].y, pts[i].z);
662 #else
663       msSetError(MS_MISCERR, "Z coordinates support not available  (mapserver not compiled with USE_POINT_Z_M option)", "KmlRenderer::addCoordsNode()");
664 #endif
665     } else
666       sprintf(lineBuf, "\t%.8f,%.8f\n", pts[i].x, pts[i].y);
667 
668     xmlNodeAddContent(coordsNode, BAD_CAST lineBuf);
669   }
670   xmlNodeAddContent(coordsNode, BAD_CAST "\t");
671 }
672 
renderGlyphs(imageObj * img,pointObj * labelpnt,char * text,double angle,colorObj * clr,colorObj * olcolor,int olwidth)673 void KmlRenderer::renderGlyphs(imageObj *img, pointObj *labelpnt, char *text, double angle, colorObj *clr, colorObj *olcolor, int olwidth)
674 {
675   xmlNodePtr node;
676 
677   if (PlacemarkNode == NULL)
678     PlacemarkNode = createPlacemarkNode(LayerNode, NULL);
679 
680   if (!PlacemarkNode)
681     return;
682 
683   memcpy(&LabelColor, clr, sizeof(colorObj));
684   SymbologyFlag[Label] = 1;
685 
686   /*there is alaws a default name (layer.shapeid). Replace it*/
687   for (node = PlacemarkNode->children; node; node = node->next) {
688     if (node->type != XML_ELEMENT_NODE)
689       continue;
690 
691     if (strcmp((char *)node->name, "name") == 0) {
692       xmlNodeSetContent(node,  BAD_CAST text);
693       break;
694     }
695   }
696 
697   /*xmlNewChild(PlacemarkNode, NULL, BAD_CAST "name", BAD_CAST text);*/
698 
699   xmlNodePtr geomNode = getGeomParentNode("Point");
700   addAddRenderingSpecifications(geomNode);
701 
702   pointObj pt;
703   pt.x = labelpnt->x;
704   pt.y = labelpnt->y;
705   addCoordsNode(geomNode, &pt, 1);
706 }
707 
addAddRenderingSpecifications(xmlNodePtr node)708 void KmlRenderer::addAddRenderingSpecifications(xmlNodePtr node)
709 {
710   /*
711     <extrude>0</extrude>                   <!-- boolean -->
712     <tessellate>0</tessellate>             <!-- boolean -->
713     <altitudeMode>clampToGround</altitudeMode>
714   */
715 
716   if (Extrude)
717     xmlNewChild(node, NULL, BAD_CAST "extrude", BAD_CAST "1");
718 
719   if (Tessellate)
720     xmlNewChild(node, NULL, BAD_CAST "tessellate", BAD_CAST "1");
721 
722   if (AltitudeMode == absolute)
723     xmlNewChild(node, NULL, BAD_CAST "altitudeMode", BAD_CAST "absolute");
724   else if (AltitudeMode == relativeToGround)
725     xmlNewChild(node, NULL, BAD_CAST "altitudeMode", BAD_CAST "relativeToGround");
726   else if (AltitudeMode == clampToGround)
727     xmlNewChild(node, NULL, BAD_CAST "altitudeMode", BAD_CAST "clampToGround");
728 }
729 
730 
731 imageObj *agg2CreateImage(int width, int height, outputFormatObj *format, colorObj * bg);
732 
createIconImage(char * fileName,symbolObj * symbol,symbolStyleObj * symstyle)733 int KmlRenderer::createIconImage(char *fileName, symbolObj *symbol, symbolStyleObj *symstyle)
734 {
735   pointObj p;
736   int status;
737 
738   imageObj *tmpImg = NULL;
739 
740   tmpImg = agg2CreateImage((int)(symbol->sizex*symstyle->scale),
741                            (int)(symbol->sizey*symstyle->scale),
742                            aggFormat, NULL);
743   tmpImg->format = aggFormat;
744   if (!aggFormat->vtable)
745     msInitializeRendererVTable(aggFormat);
746 
747   p.x = symbol->sizex * symstyle->scale / 2;
748   p.y = symbol->sizey *symstyle->scale / 2;
749 #ifdef USE_POINT_Z_M
750   p.z = 0.0;
751 #endif
752 
753   status = msDrawMarkerSymbol(map,tmpImg, &p, symstyle->style, 1);
754   if( status != MS_SUCCESS )
755     return status;
756 
757   return msSaveImage(map, tmpImg, fileName);
758 }
759 
renderSymbol(imageObj * img,double x,double y,symbolObj * symbol,symbolStyleObj * style)760 void KmlRenderer::renderSymbol(imageObj *img, double x, double y, symbolObj *symbol, symbolStyleObj *style)
761 {
762   if (PlacemarkNode == NULL)
763     PlacemarkNode = createPlacemarkNode(LayerNode, NULL);
764 
765   if (!PlacemarkNode)
766     return;
767 
768   snprintf(SymbolUrl, sizeof(SymbolUrl), "%s", lookupSymbolUrl(img, symbol, style));
769   SymbologyFlag[Symbol] = 1;
770 
771   xmlNodePtr geomNode = getGeomParentNode("Point");
772   addAddRenderingSpecifications(geomNode);
773 
774   pointObj pt;
775   pt.x = x;
776   pt.y = y;
777   addCoordsNode(geomNode, &pt, 1);
778 }
779 
renderPixmapSymbol(imageObj * img,double x,double y,symbolObj * symbol,symbolStyleObj * style)780 void KmlRenderer::renderPixmapSymbol(imageObj *img, double x, double y, symbolObj *symbol,
781                                      symbolStyleObj *style)
782 {
783   renderSymbol(img, x, y, symbol, style);
784 }
785 
renderVectorSymbol(imageObj * img,double x,double y,symbolObj * symbol,symbolStyleObj * style)786 void KmlRenderer::renderVectorSymbol(imageObj *img, double x, double y, symbolObj *symbol,
787                                      symbolStyleObj *style)
788 {
789   renderSymbol(img, x, y, symbol, style);
790 }
791 
renderEllipseSymbol(imageObj * img,double x,double y,symbolObj * symbol,symbolStyleObj * style)792 void KmlRenderer::renderEllipseSymbol(imageObj *img, double x, double y, symbolObj *symbol,
793                                       symbolStyleObj *style)
794 {
795   renderSymbol(img, x, y, symbol, style);
796 }
797 
renderTruetypeSymbol(imageObj * img,double x,double y,symbolObj * symbol,symbolStyleObj * style)798 void KmlRenderer::renderTruetypeSymbol(imageObj *img, double x, double y, symbolObj *symbol,
799                                        symbolStyleObj *style)
800 {
801   renderSymbol(img, x, y, symbol, style);
802 }
803 
createGroundOverlayNode(xmlNodePtr parentNode,char * imageHref,layerObj * layer)804 xmlNodePtr KmlRenderer::createGroundOverlayNode(xmlNodePtr parentNode, char *imageHref, layerObj *layer)
805 {
806   /*
807     <?xml version="1.0" encoding="UTF-8"?>
808     <kml xmlns="http://www.opengis.net/kml/2.2">
809     <GroundOverlay>
810     <name>GroundOverlay.kml</name>
811     <color>7fffffff</color>
812     <drawOrder>1</drawOrder>
813     <Icon>
814     <href>http://www.google.com/intl/en/images/logo.gif</href>
815     <refreshMode>onInterval</refreshMode>
816     <refreshInterval>86400</refreshInterval>
817     <viewBoundScale>0.75</viewBoundScale>
818     </Icon>
819     <LatLonBox>
820     <north>37.83234</north>
821     <south>37.832122</south>
822     <east>-122.373033</east>
823     <west>-122.373724</west>
824     <rotation>45</rotation>
825     </LatLonBox>
826     </GroundOverlay>
827     </kml>
828   */
829   char  layerHexColor[32];
830   xmlNodePtr groundOverlayNode = xmlNewChild(parentNode, NULL, BAD_CAST "GroundOverlay", NULL);
831   char *layerName = getLayerName(layer);
832   xmlNewChild(groundOverlayNode, NULL, BAD_CAST "name", BAD_CAST layerName);
833   if (layer->compositer && layer->compositer->opacity > 0 && layer->compositer->opacity < 100) {
834     sprintf(layerHexColor, "%02xffffff", (unsigned int)MS_NINT(layer->compositer->opacity*2.55));
835     xmlNewChild(groundOverlayNode, NULL, BAD_CAST "color", BAD_CAST layerHexColor);
836   } else
837     xmlNewChild(groundOverlayNode, NULL, BAD_CAST "color", BAD_CAST "ffffffff");
838   char stmp[20];
839   sprintf(stmp, "%d",layer->index);
840   xmlNewChild(groundOverlayNode, NULL, BAD_CAST "drawOrder", BAD_CAST stmp);
841 
842   if (imageHref) {
843     xmlNodePtr iconNode = xmlNewChild(groundOverlayNode, NULL, BAD_CAST "Icon", NULL);
844     xmlNewChild(iconNode, NULL, BAD_CAST "href", BAD_CAST imageHref);
845   }
846 
847   char crdStr[64];
848   rectObj mapextent;
849   if (map->gt.need_geotransform == MS_TRUE)
850     mapextent = currentLayer->map->saved_extent;
851   else
852     mapextent = currentLayer->map->extent;
853 
854   xmlNodePtr latLonBoxNode = xmlNewChild(groundOverlayNode, NULL, BAD_CAST "LatLonBox", NULL);
855   sprintf(crdStr, "%.8f", mapextent.maxy);
856   xmlNewChild(latLonBoxNode, NULL, BAD_CAST "north", BAD_CAST crdStr);
857 
858   sprintf(crdStr, "%.8f", mapextent.miny);
859   xmlNewChild(latLonBoxNode, NULL, BAD_CAST "south", BAD_CAST crdStr);
860 
861   sprintf(crdStr, "%.8f", mapextent.minx);
862   xmlNewChild(latLonBoxNode, NULL, BAD_CAST "west", BAD_CAST crdStr);
863 
864   sprintf(crdStr, "%.8f", mapextent.maxx);
865   xmlNewChild(latLonBoxNode, NULL, BAD_CAST "east", BAD_CAST crdStr);
866 
867   xmlNewChild(latLonBoxNode, NULL, BAD_CAST "rotation", BAD_CAST "0.0");
868 
869   return groundOverlayNode;
870 }
871 
startShape(imageObj *,shapeObj * shape)872 void KmlRenderer::startShape(imageObj *, shapeObj *shape)
873 {
874   if (PlacemarkNode)
875     flushPlacemark();
876 
877   CurrentShapeIndex=-1;
878   CurrentDrawnShapeIndex = -1;
879   CurrentShapeName=NULL;
880 
881   /*should be done at endshape but the plugin architecture does not call endshape yet*/
882   if(LineStyle) {
883     msFree(LineStyle);
884 
885     LineStyle = NULL;
886     numLineStyle = 0;
887   }
888 
889   CurrentShapeIndex = shape->index;
890   if (pszLayerNameAttributeMetadata) {
891     for (int i=0; i<currentLayer->numitems; i++) {
892       if (strcasecmp(currentLayer->items[i], pszLayerNameAttributeMetadata) == 0 && shape->values[i]) {
893         CurrentShapeName = msStrdup(shape->values[i]);
894         break;
895       }
896     }
897   }
898   PlacemarkNode = NULL;
899   GeomNode = NULL;
900 
901   DescriptionNode = createDescriptionNode(shape);
902 
903   if( mElevationFromAttribute && shape->numvalues > mElevationAttributeIndex &&
904       mElevationAttributeIndex >= 0 && shape->values[mElevationAttributeIndex]) {
905     mCurrentElevationValue = atof( shape->values[mElevationAttributeIndex] );
906   }
907 
908 
909   memset(SymbologyFlag, 0, NumSymbologyFlag);
910 }
911 
endShape(imageObj *,shapeObj *)912 void KmlRenderer::endShape(imageObj*, shapeObj*)
913 {
914   CurrentShapeIndex = -1;
915   if (CurrentShapeName)
916     msFree(CurrentShapeName);
917   CurrentShapeName = NULL;
918 }
919 
getGeomParentNode(const char * geomName)920 xmlNodePtr KmlRenderer::getGeomParentNode(const char *geomName)
921 {
922   /*we do not need a multi-geometry for point layers*/
923   if (currentLayer->type != MS_LAYER_POINT && currentLayer->type != MS_LAYER_ANNOTATION && GeomNode) {
924     /*placemark geometry already defined, we need multigeometry node*/
925     xmlNodePtr multiGeomNode = xmlNewNode(NULL, BAD_CAST "MultiGeometry");
926     xmlAddChild(multiGeomNode, GeomNode);
927     GeomNode = multiGeomNode;
928 
929     xmlNodePtr geomNode = xmlNewChild(multiGeomNode, NULL, BAD_CAST geomName, NULL);
930     return geomNode;
931   } else {
932     GeomNode = xmlNewNode(NULL, BAD_CAST geomName);
933     return GeomNode;
934   }
935 }
936 
lookupSymbolUrl(imageObj * img,symbolObj * symbol,symbolStyleObj * symstyle)937 const char* KmlRenderer::lookupSymbolUrl(imageObj *img, symbolObj *symbol, symbolStyleObj *symstyle)
938 {
939   char  symbolHexColor[32];
940   /*
941       <Style id="randomColorIcon">
942       <IconStyle>
943       <color>ff00ff00</color>
944       <colorMode>random</colorMode>
945       <scale>1.1</scale>
946       <Icon>
947       <href>http://maps.google.com/mapfiles/kml/pal3/icon21.png</href>
948       </Icon>
949       </IconStyle>
950       </Style>
951   */
952 
953   sprintf(symbolHexColor,"%02x%02x%02x%02x", symstyle->style->color.alpha, symstyle->style->color.blue,
954           symstyle->style->color.green, symstyle->style->color.red);
955   snprintf(SymbolName, sizeof(SymbolName), "symbol_%s_%.1f_%s", symbol->name, symstyle->scale, symbolHexColor);
956 
957   const char *symbolUrl = msLookupHashTable(StyleHashTable, SymbolName);
958   if (!symbolUrl) {
959     char iconFileName[MS_MAXPATHLEN];
960     char iconUrl[MS_MAXPATHLEN];
961 
962     if (img->imagepath) {
963       char *tmpFileName = msTmpFile(NULL, MapPath, img->imagepath, "png");
964       snprintf(iconFileName, sizeof(iconFileName), "%s", tmpFileName);
965       msFree(tmpFileName);
966     } else {
967       sprintf(iconFileName, "symbol_%s_%.1f.%s", symbol->name, symstyle->scale, "png");
968     }
969 
970     if (createIconImage(iconFileName, symbol, symstyle) != MS_SUCCESS) {
971       msSetError(MS_IOERR, "Error creating icon file '%s'", "KmlRenderer::lookupSymbolStyle()", iconFileName);
972       return NULL;
973     }
974 
975     if (img->imageurl)
976       sprintf(iconUrl, "%s%s.%s", img->imageurl, msGetBasename(iconFileName), "png");
977     else
978       snprintf(iconUrl, sizeof(iconUrl), "%s", iconFileName);
979 
980     hashObj *hash = msInsertHashTable(StyleHashTable, SymbolName, iconUrl);
981     symbolUrl = hash->data;
982   }
983 
984   return symbolUrl;
985 }
986 
lookupPlacemarkStyle()987 const char* KmlRenderer::lookupPlacemarkStyle()
988 {
989   char  lineHexColor[32];
990   char  polygonHexColor[32];
991   char  labelHexColor[32];
992   char        *styleName=NULL;
993 
994   styleName = msStringConcatenate(styleName, "style");
995 
996   if (SymbologyFlag[Line]) {
997     /*
998       <LineStyle id="ID">
999       <!-- inherited from ColorStyle -->
1000       <color>ffffffff</color>            <!-- kml:color -->
1001       <colorMode>normal</colorMode>      <!-- colorModeEnum: normal or random -->
1002 
1003       <!-- specific to LineStyle -->
1004       <width>1</width>                   <!-- float -->
1005       </LineStyle>
1006     */
1007 
1008     for (int i=0; i<numLineStyle; i++) {
1009       if (currentLayer && currentLayer->compositer && currentLayer->compositer->opacity > 0 && currentLayer->compositer->opacity < 100 &&
1010           LineStyle[i].color->alpha == 255)
1011         LineStyle[i].color->alpha = MS_NINT(currentLayer->compositer->opacity*2.55);
1012 
1013       sprintf(lineHexColor,"%02x%02x%02x%02x", LineStyle[i].color->alpha, LineStyle[0].color->blue,
1014               LineStyle[i].color->green, LineStyle[i].color->red);
1015 
1016       char lineStyleName[32];
1017       sprintf(lineStyleName, "_line_%s_w%.1f", lineHexColor, LineStyle[i].width);
1018       styleName = msStringConcatenate(styleName, lineStyleName);
1019     }
1020   }
1021 
1022   if (SymbologyFlag[Polygon]) {
1023     /*
1024       <PolyStyle id="ID">
1025       <!-- inherited from ColorStyle -->
1026       <color>ffffffff</color>            <!-- kml:color -->
1027       <colorMode>normal</colorMode>      <!-- kml:colorModeEnum: normal or random -->
1028 
1029       <!-- specific to PolyStyle -->
1030       <fill>1</fill>                     <!-- boolean -->
1031       <outline>1</outline>               <!-- boolean -->
1032       </PolyStyle>
1033     */
1034 
1035     if (currentLayer && currentLayer->compositer && currentLayer->compositer->opacity > 0 && currentLayer->compositer->opacity < 100 &&
1036         PolygonColor.alpha == 255)
1037       PolygonColor.alpha = MS_NINT(currentLayer->compositer->opacity*2.55);
1038     sprintf(polygonHexColor,"%02x%02x%02x%02x", PolygonColor.alpha, PolygonColor.blue, PolygonColor.green, PolygonColor.red);
1039 
1040     char polygonStyleName[64];
1041     sprintf(polygonStyleName, "_polygon_%s", polygonHexColor);
1042     styleName = msStringConcatenate(styleName, polygonStyleName);
1043   }
1044 
1045   if (SymbologyFlag[Label]) {
1046     /*
1047       <LabelStyle id="ID">
1048       <!-- inherited from ColorStyle -->
1049       <color>ffffffff</color>            <!-- kml:color -->
1050       <colorMode>normal</colorMode>      <!-- kml:colorModeEnum: normal or random -->
1051 
1052       <!-- specific to LabelStyle -->
1053       <scale>1</scale>                   <!-- float -->
1054       </LabelStyle>
1055     */
1056 
1057     if (currentLayer && currentLayer->compositer && currentLayer->compositer->opacity > 0 && currentLayer->compositer->opacity < 100 &&
1058         LabelColor.alpha == 255)
1059       LabelColor.alpha = MS_NINT(currentLayer->compositer->opacity*2.55);
1060     sprintf(labelHexColor,"%02x%02x%02x%02x", LabelColor.alpha, LabelColor.blue, LabelColor.green, LabelColor.red);
1061 
1062     // __TODO__ add label scale
1063 
1064     char labelStyleName[64];
1065     sprintf(labelStyleName, "_label_%s", labelHexColor);
1066     styleName = msStringConcatenate(styleName, labelStyleName);
1067   }
1068 
1069   if (SymbologyFlag[Symbol]) {
1070     /*
1071     <Style id="randomColorIcon">
1072             <IconStyle>
1073             <color>ff00ff00</color>
1074             <colorMode>random</colorMode>
1075             <scale>1.1</scale>
1076             <Icon>
1077             <href>http://maps.google.com/mapfiles/kml/pal3/icon21.png</href>
1078             </Icon>
1079             </IconStyle>
1080     </Style>
1081     */
1082 
1083     /* __TODO__ add label scale */
1084 
1085     styleName = msStringConcatenate(styleName, "_");
1086     styleName = msStringConcatenate(styleName, SymbolName);
1087   }
1088 
1089   const char *styleUrl = msLookupHashTable(StyleHashTable, styleName);
1090   if (!styleUrl) {
1091     char *styleValue=NULL;
1092     styleValue = msStringConcatenate(styleValue, "#");
1093     styleValue = msStringConcatenate(styleValue, styleName);
1094     hashObj *hash = msInsertHashTable(StyleHashTable, styleName, styleValue);
1095     styleUrl = hash->data;
1096     msFree(styleValue);
1097 
1098     /* Insert new Style node into Document node*/
1099     xmlNodePtr styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
1100     xmlNewProp(styleNode, BAD_CAST "id", BAD_CAST styleName);
1101 
1102     if (SymbologyFlag[Polygon]) {
1103       xmlNodePtr polyStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "PolyStyle", NULL);
1104       xmlNewChild(polyStyleNode, NULL, BAD_CAST "color", BAD_CAST polygonHexColor);
1105     }
1106 
1107     if (SymbologyFlag[Line]) {
1108       for (int i=0; i<numLineStyle; i++) {
1109         xmlNodePtr lineStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "LineStyle", NULL);
1110         sprintf(lineHexColor,"%02x%02x%02x%02x", LineStyle[i].color->alpha, LineStyle[i].color->blue,
1111                 LineStyle[i].color->green, LineStyle[i].color->red);
1112         xmlNewChild(lineStyleNode, NULL, BAD_CAST "color", BAD_CAST lineHexColor);
1113 
1114         char width[16];
1115         sprintf(width, "%.1f", LineStyle[i].width);
1116         xmlNewChild(lineStyleNode, NULL, BAD_CAST "width", BAD_CAST width);
1117       }
1118     }
1119 
1120     if (SymbologyFlag[Symbol]) {
1121       xmlNodePtr iconStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "IconStyle", NULL);
1122 
1123       xmlNodePtr iconNode = xmlNewChild(iconStyleNode, NULL, BAD_CAST "Icon", NULL);
1124       xmlNewChild(iconNode, NULL, BAD_CAST "href", BAD_CAST SymbolUrl);
1125 
1126       /*char scale[16];
1127         sprintf(scale, "%.1f", style->scale);
1128         xmlNewChild(iconStyleNode, NULL, BAD_CAST "scale", BAD_CAST scale);*/
1129     } else {
1130       const char *value=msLookupHashTable(&currentLayer->metadata, "kml_default_symbol_href");
1131       if (value && strlen(value) > 0) {
1132         xmlNodePtr iconStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "IconStyle", NULL);
1133 
1134         xmlNodePtr iconNode = xmlNewChild(iconStyleNode, NULL, BAD_CAST "Icon", NULL);
1135         xmlNewChild(iconNode, NULL, BAD_CAST "href", BAD_CAST value);
1136       }
1137     }
1138 
1139     if (SymbologyFlag[Label]) {
1140       xmlNodePtr labelStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "LabelStyle", NULL);
1141       xmlNewChild(labelStyleNode, NULL, BAD_CAST "color", BAD_CAST labelHexColor);
1142 
1143       /*char scale[16];
1144         sprintf(scale, "%.1f", style->scale);
1145         xmlNewChild(iconStyleNode, NULL, BAD_CAST "scale", BAD_CAST scale);*/
1146     }
1147   }
1148 
1149   if (styleName)
1150     msFree(styleName);
1151 
1152   return styleUrl;
1153 }
1154 
flushPlacemark()1155 void KmlRenderer::flushPlacemark()
1156 {
1157   if (PlacemarkNode) {
1158     const char *styleUrl = lookupPlacemarkStyle();
1159     xmlNewChild(PlacemarkNode, NULL, BAD_CAST "styleUrl", BAD_CAST styleUrl);
1160 
1161     if (DescriptionNode)
1162       xmlAddChild(PlacemarkNode, DescriptionNode);
1163 
1164     if (GeomNode)
1165       xmlAddChild(PlacemarkNode, GeomNode);
1166   }
1167 }
1168 
1169 
createDescriptionNode(shapeObj * shape)1170 xmlNodePtr KmlRenderer::createDescriptionNode(shapeObj *shape)
1171 {
1172   /*
1173     <description>
1174     <![CDATA[
1175     special characters here
1176     ]]>
1177     <description>
1178   */
1179 
1180 
1181   /*description nodes for vector layers:
1182     - if kml_description is set, use it
1183     - if not, dump the attributes */
1184 
1185   if (pszLayerDescMetadata) {
1186     char *pszTmp=NULL;
1187     char *pszTmpDesc = NULL;
1188     size_t bufferSize = 0;
1189     pszTmpDesc = msStrdup(pszLayerDescMetadata);
1190 
1191     for (int i=0; i<currentLayer->numitems; i++) {
1192       bufferSize = strlen(currentLayer->items[i]) + 3;
1193       pszTmp = (char *)msSmallMalloc(bufferSize);
1194       snprintf(pszTmp, bufferSize, "%%%s%%",currentLayer->items[i]);
1195       if (strcasestr(pszTmpDesc, pszTmp))
1196         pszTmpDesc = msCaseReplaceSubstring(pszTmpDesc,  pszTmp, shape->values[i]);
1197       msFree(pszTmp);
1198     }
1199     xmlNodePtr descriptionNode = xmlNewNode(NULL, BAD_CAST "description");
1200     xmlNodeAddContent(descriptionNode, BAD_CAST pszTmpDesc);
1201     msFree(pszTmpDesc);
1202     return descriptionNode;
1203   } else if ((papszLayerIncludeItems && nIncludeItems > 0) ||
1204              (papszLayerExcludeItems && nExcludeItems > 0)) {
1205     /* -------------------------------------------------------------------- */
1206     /*      preffered way is to use the ExtendedData tag (#3728)            */
1207     /*      http://code.google.com/apis/kml/documentation/extendeddata.html */
1208     /* -------------------------------------------------------------------- */
1209 
1210     xmlNodePtr extendedDataNode = xmlNewNode(NULL, BAD_CAST "ExtendedData");
1211     xmlNodePtr dataNode = NULL;
1212     const char*pszAlias=NULL;
1213     int bIncludeAll = MS_FALSE;
1214 
1215     if(papszLayerIncludeItems && nIncludeItems == 1 &&
1216         strcasecmp(papszLayerIncludeItems[0], "all") == 0)
1217       bIncludeAll = MS_TRUE;
1218 
1219     for (int i=0; i<currentLayer->numitems; i++) {
1220       int j=0,k=0;
1221 
1222       /*TODO optimize to calculate this only once per layer*/
1223       for (j=0; j<nIncludeItems; j++) {
1224         if (strcasecmp(currentLayer->items[i], papszLayerIncludeItems[j]) == 0)
1225           break;
1226       }
1227       if (j<nIncludeItems || bIncludeAll) {
1228         if (papszLayerExcludeItems && nExcludeItems > 0) {
1229           for (k=0; k<nExcludeItems; k++) {
1230             if (strcasecmp(currentLayer->items[i], papszLayerExcludeItems[k]) == 0)
1231               break;
1232           }
1233         }
1234         if (nExcludeItems == 0 || k == nExcludeItems) {
1235           dataNode = xmlNewNode(NULL, BAD_CAST "Data");
1236           xmlNewProp(dataNode, BAD_CAST "name", BAD_CAST  currentLayer->items[i]);
1237           pszAlias = getAliasName(currentLayer, currentLayer->items[i], "GO");
1238           if (pszAlias)
1239             xmlNewChild(dataNode, NULL, BAD_CAST "displayName", BAD_CAST  pszAlias);
1240           else
1241             xmlNewChild(dataNode, NULL, BAD_CAST "displayName", BAD_CAST  currentLayer->items[i]);
1242           if (shape->values[i] && strlen(shape->values[i]))
1243             xmlNewChild(dataNode, NULL, BAD_CAST "value", BAD_CAST  shape->values[i]);
1244           else
1245             xmlNewChild(dataNode, NULL, BAD_CAST "value", NULL);
1246           xmlAddChild(extendedDataNode, dataNode);
1247         }
1248       }
1249     }
1250 
1251     return extendedDataNode;
1252 
1253 
1254   }
1255 
1256   return NULL;
1257 }
1258 
addLineStyleToList(strokeStyleObj * style)1259 void KmlRenderer::addLineStyleToList(strokeStyleObj *style)
1260 {
1261   /*actually this is not necessary. kml only uses the last LineStyle
1262     so we should not bother keeping them all*/
1263   int i =0;
1264   for (i=0; i<numLineStyle; i++) {
1265     if (style->width == LineStyle[i].width &&
1266         LineStyle[i].color->alpha == style->color->alpha &&
1267         LineStyle[i].color->red == style->color->red &&
1268         LineStyle[i].color->green == style->color->green &&
1269         LineStyle[i].color->blue == style->color->blue)
1270       break;
1271   }
1272   if (i == numLineStyle) {
1273     numLineStyle++;
1274     if (LineStyle == NULL)
1275       LineStyle = (strokeStyleObj *)msSmallMalloc(sizeof(strokeStyleObj));
1276     else
1277       LineStyle = (strokeStyleObj *)msSmallRealloc(LineStyle, sizeof(strokeStyleObj)*numLineStyle);
1278 
1279     memcpy(&LineStyle[numLineStyle-1], style, sizeof(strokeStyleObj));
1280   }
1281 
1282 }
1283 
1284 #endif
1285