/******************************************************************************
* $Id$
*
* Project: MapServer
* Purpose: Google Earth KML output
* Author: David Kana and the MapServer team
*
******************************************************************************
* Copyright (c) 1996-2009 Regents of the University of Minnesota.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies of this Software or works derived from this Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include "mapserver-config.h"
#ifdef USE_KML
#include "mapserver.h"
#include "maperror.h"
#include "mapkmlrenderer.h"
#include "mapio.h"
#include "mapows.h"
#include "cpl_conv.h"
#include "cpl_vsi.h"
#include "cpl_string.h"
#define KML_MAXFEATURES_TODRAW 1000
KmlRenderer::KmlRenderer(int width, int height, outputFormatObj * /*format*/,
colorObj * /*color*/)
: Width(width), Height(height), MapCellsize(1.0), XmlDoc(NULL),
LayerNode(NULL), GroundOverlayNode(NULL), PlacemarkNode(NULL),
GeomNode(NULL), Items(NULL), NumItems(0), FirstLayer(MS_TRUE), map(NULL),
currentLayer(NULL), mElevationFromAttribute(false),
mElevationAttributeIndex(-1), mCurrentElevationValue(0.0)
{
/*private variables*/
pszLayerDescMetadata = NULL;
papszLayerIncludeItems = NULL;
nIncludeItems = 0;
papszLayerExcludeItems = NULL;
nExcludeItems = 0;
pszLayerNameAttributeMetadata =
NULL; /*metadata to use for a name for each feature*/
LineStyle = NULL;
numLineStyle = 0;
xmlNodePtr styleNode;
xmlNodePtr listStyleNode;
/* Create document.*/
XmlDoc = xmlNewDoc(BAD_CAST "1.0");
xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "kml");
/* Name spaces*/
xmlSetNs(rootNode,
xmlNewNs(rootNode, BAD_CAST "http://www.opengis.net/kml/2.2", NULL));
xmlDocSetRootElement(XmlDoc, rootNode);
DocNode = xmlNewChild(rootNode, NULL, BAD_CAST "Document", NULL);
styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
xmlNewProp(styleNode, BAD_CAST "id", BAD_CAST "LayerFolder_check");
listStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "ListStyle", NULL);
xmlNewChild(listStyleNode, NULL, BAD_CAST "listItemType", BAD_CAST "check");
styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
xmlNewProp(styleNode, BAD_CAST "id",
BAD_CAST "LayerFolder_checkHideChildren");
listStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "ListStyle", NULL);
xmlNewChild(listStyleNode, NULL, BAD_CAST "listItemType",
BAD_CAST "checkHideChildren");
styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
xmlNewProp(styleNode, BAD_CAST "id", BAD_CAST "LayerFolder_checkOffOnly");
listStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "ListStyle", NULL);
xmlNewChild(listStyleNode, NULL, BAD_CAST "listItemType",
BAD_CAST "checkOffOnly");
styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
xmlNewProp(styleNode, BAD_CAST "id", BAD_CAST "LayerFolder_radioFolder");
listStyleNode = xmlNewChild(styleNode, NULL, BAD_CAST "ListStyle", NULL);
xmlNewChild(listStyleNode, NULL, BAD_CAST "listItemType",
BAD_CAST "radioFolder");
StyleHashTable = msCreateHashTable();
}
KmlRenderer::~KmlRenderer() {
if (XmlDoc)
xmlFreeDoc(XmlDoc);
if (StyleHashTable)
msFreeHashTable(StyleHashTable);
if (LineStyle)
msFree(LineStyle);
xmlCleanupParser();
}
imageObj *KmlRenderer::createImage(int, int, outputFormatObj *, colorObj *) {
return NULL;
}
int KmlRenderer::saveImage(imageObj *, FILE *fp, outputFormatObj *format) {
/* -------------------------------------------------------------------- */
/* Write out the document. */
/* -------------------------------------------------------------------- */
int bufSize = 0;
xmlChar *buf = NULL;
msIOContext *context = NULL;
int chunkSize = 4096;
int bZip = MS_FALSE;
if (msIO_needBinaryStdout() == MS_FAILURE)
return MS_FAILURE;
xmlDocDumpFormatMemoryEnc(XmlDoc, &buf, &bufSize, "UTF-8", 1);
if (format && format->driver && strcasecmp(format->driver, "kmz") == 0) {
bZip = MS_TRUE;
}
if (bZip) {
VSILFILE *fpZip;
int bytes_read;
char buffer[1024];
char *zip_filename = NULL;
void *hZip = NULL;
const char *timestamp;
timestamp = msGetOutputFormatOption(format, "TIMESTAMP", "NOW");
char **papszOptions = nullptr;
papszOptions = CSLAddNameValue(papszOptions, "TIMESTAMP", timestamp);
zip_filename = msTmpFile(NULL, NULL, "/vsimem/kmlzip/", "kmz");
hZip = CPLCreateZip(zip_filename, NULL);
CPLCreateFileInZip(hZip, "mapserver.kml", papszOptions);
for (int i = 0; i < bufSize; i += chunkSize) {
int size = chunkSize;
if (i + size > bufSize)
size = bufSize - i;
CPLWriteFileInZip(hZip, buf + i, size);
}
CPLCloseFileInZip(hZip);
CPLCloseZip(hZip);
CSLDestroy(papszOptions);
context = msIO_getHandler(fp);
fpZip = VSIFOpenL(zip_filename, "r");
while ((bytes_read = VSIFReadL(buffer, 1, sizeof(buffer), fpZip)) > 0) {
if (context)
msIO_contextWrite(context, buffer, bytes_read);
else
msIO_fwrite(buffer, 1, bytes_read, fp);
}
VSIFCloseL(fpZip);
msFree(zip_filename);
xmlFree(buf);
return (MS_SUCCESS);
}
context = msIO_getHandler(fp);
for (int i = 0; i < bufSize; i += chunkSize) {
int size = chunkSize;
if (i + size > bufSize)
size = bufSize - i;
if (context)
msIO_contextWrite(context, buf + i, size);
else
msIO_fwrite(buf + i, 1, size, fp);
}
xmlFree(buf);
return (MS_SUCCESS);
}
/************************************************************************/
/* processLayer */
/* */
/* Set parameters that make sense to a kml output. */
/************************************************************************/
void KmlRenderer::processLayer(layerObj *layer, outputFormatObj *format) {
int i;
const char *asRaster = NULL;
int nMaxFeatures = -1;
const char *pszTmp;
char szTmp[10];
if (!layer)
return;
/*turn of labelcache*/
layer->labelcache = MS_OFF;
/*if there are labels we want the coordinates to
be the center of the element.*/
for (i = 0; i < layer->numclasses; i++)
if (layer->_class[i]->numlabels > 0)
layer->_class[i]->labels[0]->position = MS_XY;
/*we do not want to draw multiple styles.
the new rendering architecture does not allow
to know if we are dealing with a multi-style.
So here we remove all styles beside the first one*/
for (i = 0; i < layer->numclasses; i++) {
while (layer->_class[i]->numstyles > 1)
msDeleteStyle(layer->_class[i], layer->_class[i]->numstyles - 1);
}
/*if layer has a metadata KML_OUTPUTASRASTER set to true, add a processing
directive to use an agg driver*/
asRaster = msLookupHashTable(&layer->metadata, "kml_outputasraster");
if (!asRaster)
asRaster =
msLookupHashTable(&(layer->map->web.metadata), "kml_outputasraster");
if (asRaster &&
(strcasecmp(asRaster, "true") == 0 || strcasecmp(asRaster, "yes") == 0))
msLayerAddProcessing(layer, "RENDERER=png24");
/*set a maxfeaturestodraw, if not already set*/
pszTmp = msLookupHashTable(&layer->metadata, "maxfeaturestodraw");
if (pszTmp)
nMaxFeatures = atoi(pszTmp);
else {
pszTmp = msLookupHashTable(&layer->map->web.metadata, "maxfeaturestodraw");
if (pszTmp)
nMaxFeatures = atoi(pszTmp);
}
if (nMaxFeatures < 0 && format)
nMaxFeatures =
atoi(msGetOutputFormatOption(format, "maxfeaturestodraw", "-1"));
if (nMaxFeatures < 0 && format) {
snprintf(szTmp, sizeof(szTmp), "%d", KML_MAXFEATURES_TODRAW);
msSetOutputFormatOption(format, "maxfeaturestodraw", szTmp);
}
}
/************************************************************************/
/* getLayerName */
/* */
/* Internal utility function to build name used for the layer. */
/************************************************************************/
char *KmlRenderer::getLayerName(layerObj *layer) {
char stmp[20];
const char *name = NULL;
;
if (!layer)
return NULL;
name = msLookupHashTable(&layer->metadata, "ows_name");
if (name && strlen(name) > 0)
return msStrdup(name);
if (layer->name && strlen(layer->name) > 0)
return msStrdup(layer->name);
sprintf(stmp, "Layer%d", layer->index);
return msStrdup(stmp);
}
const char *KmlRenderer::getAliasName(layerObj *lp, char *pszItemName,
const char *namespaces) {
const char *pszAlias = NULL;
if (lp && pszItemName && strlen(pszItemName) > 0) {
char szTmp[256];
snprintf(szTmp, sizeof(szTmp), "%s_alias", pszItemName);
pszAlias = msOWSLookupMetadata(&(lp->metadata), namespaces, szTmp);
}
return pszAlias;
}
int KmlRenderer::startNewLayer(imageObj *img, layerObj *layer) {
char *layerName = NULL;
const char *value = NULL;
LayerNode = xmlNewNode(NULL, BAD_CAST "Folder");
layerName = getLayerName(layer);
xmlNewChild(LayerNode, NULL, BAD_CAST "name", BAD_CAST layerName);
msFree(layerName);
const char *layerVisibility = layer->status != MS_OFF ? "1" : "0";
xmlNewChild(LayerNode, NULL, BAD_CAST "visibility", BAD_CAST layerVisibility);
const char *layerDsiplayFolder =
msLookupHashTable(&(layer->metadata), "kml_folder_display");
if (layerDsiplayFolder == NULL)
layerDsiplayFolder =
msLookupHashTable(&(layer->map->web.metadata), "kml_folder_display");
if (!layerDsiplayFolder || strlen(layerDsiplayFolder) <= 0) {
xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl",
BAD_CAST "#LayerFolder_check");
}
else {
if (strcasecmp(layerDsiplayFolder, "checkHideChildren") == 0)
xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl",
BAD_CAST "#LayerFolder_checkHideChildren");
else if (strcasecmp(layerDsiplayFolder, "checkOffOnly") == 0)
xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl",
BAD_CAST "#LayerFolder_checkOffOnly");
else if (strcasecmp(layerDsiplayFolder, "radioFolder") == 0)
xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl",
BAD_CAST "#LayerFolder_radioFolder");
else
xmlNewChild(LayerNode, NULL, BAD_CAST "styleUrl",
BAD_CAST "#LayerFolder_check");
}
/*Init few things on the first layer*/
if (FirstLayer) {
FirstLayer = MS_FALSE;
map = layer->map;
if (layer->map->mappath)
snprintf(MapPath, sizeof(MapPath), "%s", layer->map->mappath);
/*First rendered layer - check mapfile projection*/
checkProjection(layer->map);
/*check for image path and image url*/
if (layer->map->debug &&
(layer->map->web.imageurl == NULL || layer->map->web.imagepath == NULL))
msDebug("KmlRenderer::startNewLayer: imagepath and imageurl should be "
"set in the web object\n");
/*map rect for ground overlay*/
MapExtent = layer->map->extent;
MapCellsize = layer->map->cellsize;
BgColor = layer->map->imagecolor;
xmlNewChild(DocNode, NULL, BAD_CAST "name", BAD_CAST layer->map->name);
aggFormat = msSelectOutputFormat(layer->map, "png24");
aggFormat->transparent = MS_TRUE;
}
currentLayer = layer;
if (!msLayerIsOpen(layer)) {
if (msLayerOpen(layer) != MS_SUCCESS) {
msSetError(MS_MISCERR, "msLayerOpen failed",
"KmlRenderer::startNewLayer");
return MS_FAILURE;
}
}
/*pre process the layer to set things that make sense for kml output*/
if (img)
processLayer(layer, img->format);
else
processLayer(layer, NULL);
if (msLookupHashTable(&layer->metadata, "kml_description"))
pszLayerDescMetadata =
msLookupHashTable(&layer->metadata, "kml_description");
else if (msLookupHashTable(&layer->metadata, "ows_description"))
pszLayerDescMetadata =
msLookupHashTable(&layer->metadata, "ows_description");
value = msLookupHashTable(&layer->metadata, "kml_include_items");
if (!value)
value = msLookupHashTable(&layer->metadata, "ows_include_items");
if (value)
papszLayerIncludeItems = msStringSplit(value, ',', &nIncludeItems);
value = msLookupHashTable(&layer->metadata, "kml_exclude_items");
if (!value)
value = msLookupHashTable(&layer->metadata, "ows_exclude_items");
if (value)
papszLayerExcludeItems = msStringSplit(value, ',', &nExcludeItems);
if (msLookupHashTable(&layer->metadata, "kml_name_item"))
pszLayerNameAttributeMetadata =
msLookupHashTable(&layer->metadata, "kml_name_item");
/*get all attributes*/
if (msLayerWhichItems(layer, MS_TRUE, NULL) != MS_SUCCESS) {
return MS_FAILURE;
}
NumItems = layer->numitems;
if (NumItems) {
Items = (char **)msSmallCalloc(NumItems, sizeof(char *));
for (int i = 0; i < NumItems; i++)
Items[i] = msStrdup(layer->items[i]);
}
const char *elevationAttribute =
msLookupHashTable(&layer->metadata, "kml_elevation_attribute");
if (elevationAttribute) {
mElevationFromAttribute = true;
for (int i = 0; i < layer->numitems; ++i) {
if (strcasecmp(layer->items[i], elevationAttribute) == 0) {
mElevationAttributeIndex = i;
}
}
}
setupRenderingParams(&layer->metadata);
return MS_SUCCESS;
}
int KmlRenderer::closeNewLayer(imageObj *, layerObj *) {
flushPlacemark();
xmlAddChild(DocNode, LayerNode);
if (Items) {
msFreeCharArray(Items, NumItems);
Items = NULL;
NumItems = 0;
}
if (pszLayerDescMetadata)
pszLayerDescMetadata = NULL;
if (pszLayerNameAttributeMetadata)
pszLayerNameAttributeMetadata = NULL;
if (papszLayerIncludeItems && nIncludeItems > 0)
msFreeCharArray(papszLayerIncludeItems, nIncludeItems);
papszLayerIncludeItems = NULL;
if (papszLayerExcludeItems && nExcludeItems > 0)
msFreeCharArray(papszLayerExcludeItems, nExcludeItems);
papszLayerExcludeItems = NULL;
return MS_SUCCESS;
}
int KmlRenderer::mergeRasterBuffer(imageObj *image, rasterBufferObj *rb) {
assert(rb && rb->type == MS_BUFFER_BYTE_RGBA);
char *tmpFileName = NULL;
char *tmpUrl = NULL;
FILE *tmpFile = NULL;
tmpFileName = msTmpFile(NULL, MapPath, image->imagepath, "png");
tmpFile = fopen(tmpFileName, "wb");
if (tmpFile) {
if (!aggFormat->vtable)
msInitializeRendererVTable(aggFormat);
msSaveRasterBuffer(map, rb, tmpFile, aggFormat);
tmpUrl = msStrdup(image->imageurl);
tmpUrl = msStringConcatenate(tmpUrl, (char *)(msGetBasename(tmpFileName)));
tmpUrl = msStringConcatenate(tmpUrl, ".png");
createGroundOverlayNode(LayerNode, tmpUrl, currentLayer);
msFree(tmpFileName);
msFree(tmpUrl);
fclose(tmpFile);
return MS_SUCCESS;
} else {
msSetError(MS_IOERR, "Failed to create file for kml overlay",
"KmlRenderer::mergeRasterBuffer()");
return MS_FAILURE;
}
}
void KmlRenderer::setupRenderingParams(hashTableObj *layerMetadata) {
AltitudeMode = 0;
Extrude = 0;
Tessellate = 0;
const char *altitudeModeVal =
msLookupHashTable(layerMetadata, "kml_altitudeMode");
if (altitudeModeVal) {
if (strcasecmp(altitudeModeVal, "absolute") == 0)
AltitudeMode = absolute;
else if (strcasecmp(altitudeModeVal, "relativeToGround") == 0)
AltitudeMode = relativeToGround;
else if (strcasecmp(altitudeModeVal, "clampToGround") == 0)
AltitudeMode = clampToGround;
}
const char *extrudeVal = msLookupHashTable(layerMetadata, "kml_extrude");
if (altitudeModeVal) {
Extrude = atoi(extrudeVal);
}
const char *tessellateVal =
msLookupHashTable(layerMetadata, "kml_tessellate");
if (tessellateVal) {
Tessellate = atoi(tessellateVal);
}
}
int KmlRenderer::checkProjection(mapObj *map) {
projectionObj *projection = &map->projection;
if (projection && projection->numargs > 0 &&
msProjIsGeographicCRS(projection)) {
return MS_SUCCESS;
} else {
char epsg_string[100];
rectObj sRect;
projectionObj out;
/* for layer the do not have any projection set, set them with the current
map projection*/
if (projection && projection->numargs > 0) {
layerObj *lp = NULL;
int i = 0;
char *pszMapProjectString = msGetProjectionString(projection);
if (pszMapProjectString) {
for (i = 0; i < map->numlayers; i++) {
lp = GET_LAYER(map, i);
if (lp->projection.numargs == 0 && lp->transform == MS_TRUE) {
msFreeProjection(&lp->projection);
msLoadProjectionString(&lp->projection, pszMapProjectString);
}
}
msFree(pszMapProjectString);
}
}
strcpy(epsg_string, "epsg:4326");
msInitProjection(&out);
msProjectionInheritContextFrom(&out, projection);
msLoadProjectionString(&out, epsg_string);
sRect = map->extent;
msProjectRect(projection, &out, &sRect);
msFreeProjectionExceptContext(projection);
msLoadProjectionString(projection, epsg_string);
/*change also units and extents*/
map->extent = sRect;
map->units = MS_DD;
if (map->debug)
msDebug("KmlRenderer::checkProjection: Mapfile projection set to "
"epsg:4326\n");
return MS_SUCCESS;
}
}
xmlNodePtr KmlRenderer::createPlacemarkNode(xmlNodePtr parentNode,
char *styleUrl) {
xmlNodePtr placemarkNode =
xmlNewChild(parentNode, NULL, BAD_CAST "Placemark", NULL);
/*always add a name. It will be replaced by a text value if available*/
char tmpid[100];
char *stmp = NULL, *layerName = NULL;
if (CurrentShapeName && strlen(CurrentShapeName) > 0) {
xmlNewChild(placemarkNode, NULL, BAD_CAST "name",
BAD_CAST CurrentShapeName);
} else {
sprintf(tmpid, ".%d", CurrentShapeIndex);
layerName = getLayerName(currentLayer);
stmp = msStringConcatenate(stmp, layerName);
stmp = msStringConcatenate(stmp, tmpid);
xmlNewChild(placemarkNode, NULL, BAD_CAST "name", BAD_CAST stmp);
}
msFree(layerName);
msFree(stmp);
if (styleUrl)
xmlNewChild(placemarkNode, NULL, BAD_CAST "styleUrl", BAD_CAST styleUrl);
return placemarkNode;
}
void KmlRenderer::renderLine(imageObj *, shapeObj *p, strokeStyleObj *style) {
if (p->numlines == 0)
return;
if (PlacemarkNode == NULL)
PlacemarkNode = createPlacemarkNode(LayerNode, NULL);
if (!PlacemarkNode)
return;
addLineStyleToList(style);
SymbologyFlag[Line] = 1;
/*p->index > CurrentDrawnShapeIndexneed to be reviewd. Added since the height
level code caches shapes when rendering lines*/
if (CurrentDrawnShapeIndex == -1 || p->index > CurrentDrawnShapeIndex) {
xmlNodePtr geomNode = getGeomParentNode("LineString");
addAddRenderingSpecifications(geomNode);
addCoordsNode(geomNode, p->line[0].point, p->line[0].numpoints);
/* more than one line => MultiGeometry*/
if (p->numlines > 1) {
geomNode = getGeomParentNode("LineString"); // returns MultiGeom Node
for (int i = 1; i < p->numlines; i++) {
xmlNodePtr lineStringNode =
xmlNewChild(geomNode, NULL, BAD_CAST "LineString", NULL);
addAddRenderingSpecifications(lineStringNode);
addCoordsNode(lineStringNode, p->line[i].point, p->line[i].numpoints);
}
}
CurrentDrawnShapeIndex = p->index;
}
}
void KmlRenderer::renderPolygon(imageObj *, shapeObj *p, colorObj *color) {
if (PlacemarkNode == NULL)
PlacemarkNode = createPlacemarkNode(LayerNode, NULL);
if (!PlacemarkNode)
return;
memcpy(&PolygonColor, color, sizeof(colorObj));
SymbologyFlag[Polygon] = 1;
if (p->index != CurrentDrawnShapeIndex) {
xmlNodePtr geomParentNode = getGeomParentNode("Polygon");
for (int i = 0; i < p->numlines; i++) {
xmlNodePtr bdryNode = NULL;
if (i == 0) /* __TODO__ check ring order*/
bdryNode =
xmlNewChild(geomParentNode, NULL, BAD_CAST "outerBoundaryIs", NULL);
else
bdryNode =
xmlNewChild(geomParentNode, NULL, BAD_CAST "innerBoundaryIs", NULL);
xmlNodePtr ringNode =
xmlNewChild(bdryNode, NULL, BAD_CAST "LinearRing", NULL);
addAddRenderingSpecifications(ringNode);
addCoordsNode(ringNode, p->line[i].point, p->line[i].numpoints);
}
CurrentDrawnShapeIndex = p->index;
}
}
void KmlRenderer::addCoordsNode(xmlNodePtr parentNode, pointObj *pts,
int numPts) {
char lineBuf[128];
xmlNodePtr coordsNode =
xmlNewChild(parentNode, NULL, BAD_CAST "coordinates", NULL);
xmlNodeAddContent(coordsNode, BAD_CAST "\n");
for (int i = 0; i < numPts; i++) {
if (mElevationFromAttribute) {
sprintf(lineBuf, "\t%.8f,%.8f,%.8f\n", pts[i].x, pts[i].y,
mCurrentElevationValue);
} else if (AltitudeMode == relativeToGround || AltitudeMode == absolute) {
sprintf(lineBuf, "\t%.8f,%.8f,%.8f\n", pts[i].x, pts[i].y, pts[i].z);
} else
sprintf(lineBuf, "\t%.8f,%.8f\n", pts[i].x, pts[i].y);
xmlNodeAddContent(coordsNode, BAD_CAST lineBuf);
}
xmlNodeAddContent(coordsNode, BAD_CAST "\t");
}
void KmlRenderer::renderGlyphs(imageObj *, const textSymbolObj *ts,
colorObj *clr, colorObj * /*oc*/, int /*ow*/) {
if (ts->annotext == NULL || ts->textpath->numglyphs == 0)
return;
if (PlacemarkNode == NULL)
PlacemarkNode = createPlacemarkNode(LayerNode, NULL);
if (!PlacemarkNode)
return;
memcpy(&LabelColor, clr, sizeof(colorObj));
SymbologyFlag[Label] = 1;
/*there is alaws a default name (layer.shapeid). Replace it*/
for (xmlNodePtr node = PlacemarkNode->children; node; node = node->next) {
if (node->type != XML_ELEMENT_NODE)
continue;
if (strcmp((char *)node->name, "name") == 0) {
xmlNodeSetContent(node, BAD_CAST ts->annotext);
break;
}
}
/*xmlNewChild(PlacemarkNode, NULL, BAD_CAST "name", BAD_CAST text);*/
xmlNodePtr geomNode = getGeomParentNode("Point");
addAddRenderingSpecifications(geomNode);
pointObj pt;
pt.x = ts->textpath->glyphs[0].pnt.x;
pt.y = ts->textpath->glyphs[0].pnt.y;
pt.z = 0.0;
addCoordsNode(geomNode, &pt, 1);
}
void KmlRenderer::addAddRenderingSpecifications(xmlNodePtr node) {
/*
0
0
clampToGround
*/
if (Extrude)
xmlNewChild(node, NULL, BAD_CAST "extrude", BAD_CAST "1");
if (Tessellate)
xmlNewChild(node, NULL, BAD_CAST "tessellate", BAD_CAST "1");
if (AltitudeMode == absolute)
xmlNewChild(node, NULL, BAD_CAST "altitudeMode", BAD_CAST "absolute");
else if (AltitudeMode == relativeToGround)
xmlNewChild(node, NULL, BAD_CAST "altitudeMode",
BAD_CAST "relativeToGround");
else if (AltitudeMode == clampToGround)
xmlNewChild(node, NULL, BAD_CAST "altitudeMode", BAD_CAST "clampToGround");
}
imageObj *agg2CreateImage(int width, int height, outputFormatObj *format,
colorObj *bg);
int KmlRenderer::createIconImage(char *fileName, symbolObj *symbol,
symbolStyleObj *symstyle) {
pointObj p;
int status;
imageObj *tmpImg = NULL;
tmpImg =
agg2CreateImage((int)(symbol->sizex * symstyle->scale),
(int)(symbol->sizey * symstyle->scale), aggFormat, NULL);
tmpImg->format = aggFormat;
if (!aggFormat->vtable)
msInitializeRendererVTable(aggFormat);
p.x = symbol->sizex * symstyle->scale / 2;
p.y = symbol->sizey * symstyle->scale / 2;
p.z = 0.0;
status = msDrawMarkerSymbol(map, tmpImg, &p, symstyle->style, 1);
if (status != MS_SUCCESS)
return status;
return msSaveImage(map, tmpImg, fileName);
}
void KmlRenderer::renderSymbol(imageObj *img, double x, double y,
symbolObj *symbol, symbolStyleObj *style) {
if (PlacemarkNode == NULL)
PlacemarkNode = createPlacemarkNode(LayerNode, NULL);
if (!PlacemarkNode)
return;
snprintf(SymbolUrl, sizeof(SymbolUrl), "%s",
lookupSymbolUrl(img, symbol, style));
SymbologyFlag[Symbol] = 1;
xmlNodePtr geomNode = getGeomParentNode("Point");
addAddRenderingSpecifications(geomNode);
pointObj pt;
pt.x = x;
pt.y = y;
pt.z = 0.0;
addCoordsNode(geomNode, &pt, 1);
}
void KmlRenderer::renderPixmapSymbol(imageObj *img, double x, double y,
symbolObj *symbol, symbolStyleObj *style) {
renderSymbol(img, x, y, symbol, style);
}
void KmlRenderer::renderVectorSymbol(imageObj *img, double x, double y,
symbolObj *symbol, symbolStyleObj *style) {
renderSymbol(img, x, y, symbol, style);
}
void KmlRenderer::renderEllipseSymbol(imageObj *img, double x, double y,
symbolObj *symbol,
symbolStyleObj *style) {
renderSymbol(img, x, y, symbol, style);
}
void KmlRenderer::renderTruetypeSymbol(imageObj *img, double x, double y,
symbolObj *symbol,
symbolStyleObj *style) {
renderSymbol(img, x, y, symbol, style);
}
xmlNodePtr KmlRenderer::createGroundOverlayNode(xmlNodePtr parentNode,
char *imageHref,
layerObj *layer) {
/*
GroundOverlay.kml
7fffffff
1
http://www.google.com/intl/en/images/logo.gif
onInterval
86400
0.75
37.83234
37.832122
-122.373033
-122.373724
45
*/
char layerHexColor[32];
xmlNodePtr groundOverlayNode =
xmlNewChild(parentNode, NULL, BAD_CAST "GroundOverlay", NULL);
char *layerName = getLayerName(layer);
xmlNewChild(groundOverlayNode, NULL, BAD_CAST "name", BAD_CAST layerName);
if (layer->compositer && layer->compositer->opacity > 0 &&
layer->compositer->opacity < 100) {
sprintf(layerHexColor, "%02xffffff",
(unsigned int)MS_NINT(layer->compositer->opacity * 2.55));
xmlNewChild(groundOverlayNode, NULL, BAD_CAST "color",
BAD_CAST layerHexColor);
} else
xmlNewChild(groundOverlayNode, NULL, BAD_CAST "color", BAD_CAST "ffffffff");
char stmp[20];
sprintf(stmp, "%d", layer->index);
xmlNewChild(groundOverlayNode, NULL, BAD_CAST "drawOrder", BAD_CAST stmp);
if (imageHref) {
xmlNodePtr iconNode =
xmlNewChild(groundOverlayNode, NULL, BAD_CAST "Icon", NULL);
xmlNewChild(iconNode, NULL, BAD_CAST "href", BAD_CAST imageHref);
}
char crdStr[64];
rectObj mapextent;
if (map->gt.need_geotransform == MS_TRUE)
mapextent = currentLayer->map->saved_extent;
else
mapextent = currentLayer->map->extent;
xmlNodePtr latLonBoxNode =
xmlNewChild(groundOverlayNode, NULL, BAD_CAST "LatLonBox", NULL);
sprintf(crdStr, "%.8f", mapextent.maxy);
xmlNewChild(latLonBoxNode, NULL, BAD_CAST "north", BAD_CAST crdStr);
sprintf(crdStr, "%.8f", mapextent.miny);
xmlNewChild(latLonBoxNode, NULL, BAD_CAST "south", BAD_CAST crdStr);
sprintf(crdStr, "%.8f", mapextent.minx);
xmlNewChild(latLonBoxNode, NULL, BAD_CAST "west", BAD_CAST crdStr);
sprintf(crdStr, "%.8f", mapextent.maxx);
xmlNewChild(latLonBoxNode, NULL, BAD_CAST "east", BAD_CAST crdStr);
xmlNewChild(latLonBoxNode, NULL, BAD_CAST "rotation", BAD_CAST "0.0");
return groundOverlayNode;
}
void KmlRenderer::startShape(imageObj *, shapeObj *shape) {
if (PlacemarkNode)
flushPlacemark();
CurrentShapeIndex = -1;
CurrentDrawnShapeIndex = -1;
CurrentShapeName = NULL;
/*should be done at endshape but the plugin architecture does not call
* endshape yet*/
if (LineStyle) {
msFree(LineStyle);
LineStyle = NULL;
numLineStyle = 0;
}
CurrentShapeIndex = shape->index;
if (pszLayerNameAttributeMetadata) {
for (int i = 0; i < currentLayer->numitems; i++) {
if (strcasecmp(currentLayer->items[i], pszLayerNameAttributeMetadata) ==
0 &&
shape->values[i]) {
CurrentShapeName = msStrdup(shape->values[i]);
break;
}
}
}
PlacemarkNode = NULL;
GeomNode = NULL;
DescriptionNode = createDescriptionNode(shape);
if (mElevationFromAttribute && shape->numvalues > mElevationAttributeIndex &&
mElevationAttributeIndex >= 0 &&
shape->values[mElevationAttributeIndex]) {
mCurrentElevationValue = atof(shape->values[mElevationAttributeIndex]);
}
memset(SymbologyFlag, 0, NumSymbologyFlag);
}
void KmlRenderer::endShape(imageObj *, shapeObj *) {
CurrentShapeIndex = -1;
if (CurrentShapeName)
msFree(CurrentShapeName);
CurrentShapeName = NULL;
}
xmlNodePtr KmlRenderer::getGeomParentNode(const char *geomName) {
/*we do not need a multi-geometry for point layers*/
if (currentLayer->type != MS_LAYER_POINT &&
currentLayer->type != MS_LAYER_ANNOTATION && GeomNode) {
/*placemark geometry already defined, we need multigeometry node*/
xmlNodePtr multiGeomNode = xmlNewNode(NULL, BAD_CAST "MultiGeometry");
xmlAddChild(multiGeomNode, GeomNode);
GeomNode = multiGeomNode;
xmlNodePtr geomNode =
xmlNewChild(multiGeomNode, NULL, BAD_CAST geomName, NULL);
return geomNode;
} else {
GeomNode = xmlNewNode(NULL, BAD_CAST geomName);
return GeomNode;
}
}
const char *KmlRenderer::lookupSymbolUrl(imageObj *img, symbolObj *symbol,
symbolStyleObj *symstyle) {
char symbolHexColor[32];
/*
*/
sprintf(symbolHexColor, "%02x%02x%02x%02x", symstyle->style->color.alpha,
symstyle->style->color.blue, symstyle->style->color.green,
symstyle->style->color.red);
snprintf(SymbolName, sizeof(SymbolName), "symbol_%s_%.1f_%s", symbol->name,
symstyle->scale, symbolHexColor);
const char *symbolUrl = msLookupHashTable(StyleHashTable, SymbolName);
if (!symbolUrl) {
char iconFileName[MS_MAXPATHLEN];
char iconUrl[MS_MAXPATHLEN];
if (img->imagepath) {
char *tmpFileName = msTmpFile(NULL, MapPath, img->imagepath, "png");
snprintf(iconFileName, sizeof(iconFileName), "%s", tmpFileName);
msFree(tmpFileName);
} else {
sprintf(iconFileName, "symbol_%s_%.1f.%s", symbol->name, symstyle->scale,
"png");
}
if (createIconImage(iconFileName, symbol, symstyle) != MS_SUCCESS) {
msSetError(MS_IOERR, "Error creating icon file '%s'",
"KmlRenderer::lookupSymbolStyle()", iconFileName);
return NULL;
}
if (img->imageurl)
sprintf(iconUrl, "%s%s.%s", img->imageurl, msGetBasename(iconFileName),
"png");
else
snprintf(iconUrl, sizeof(iconUrl), "%s", iconFileName);
hashObj *hash = msInsertHashTable(StyleHashTable, SymbolName, iconUrl);
symbolUrl = hash->data;
}
return symbolUrl;
}
const char *KmlRenderer::lookupPlacemarkStyle() {
char lineHexColor[32];
char polygonHexColor[32];
char labelHexColor[32];
char *styleName = NULL;
styleName = msStringConcatenate(styleName, "style");
if (SymbologyFlag[Line]) {
/*
ffffffff
normal
1
*/
for (int i = 0; i < numLineStyle; i++) {
if (currentLayer && currentLayer->compositer &&
currentLayer->compositer->opacity > 0 &&
currentLayer->compositer->opacity < 100 &&
LineStyle[i].color->alpha == 255)
LineStyle[i].color->alpha =
MS_NINT(currentLayer->compositer->opacity * 2.55);
sprintf(lineHexColor, "%02x%02x%02x%02x", LineStyle[i].color->alpha,
LineStyle[0].color->blue, LineStyle[i].color->green,
LineStyle[i].color->red);
char lineStyleName[64];
snprintf(lineStyleName, sizeof(lineStyleName), "_line_%s_w%.1f",
lineHexColor, LineStyle[i].width);
styleName = msStringConcatenate(styleName, lineStyleName);
}
}
if (SymbologyFlag[Polygon]) {
/*
ffffffff
normal
1
1
*/
if (currentLayer && currentLayer->compositer &&
currentLayer->compositer->opacity > 0 &&
currentLayer->compositer->opacity < 100 && PolygonColor.alpha == 255)
PolygonColor.alpha = MS_NINT(currentLayer->compositer->opacity * 2.55);
sprintf(polygonHexColor, "%02x%02x%02x%02x", PolygonColor.alpha,
PolygonColor.blue, PolygonColor.green, PolygonColor.red);
char polygonStyleName[64];
sprintf(polygonStyleName, "_polygon_%s", polygonHexColor);
styleName = msStringConcatenate(styleName, polygonStyleName);
}
if (SymbologyFlag[Label]) {
/*
ffffffff
normal
1
*/
if (currentLayer && currentLayer->compositer &&
currentLayer->compositer->opacity > 0 &&
currentLayer->compositer->opacity < 100 && LabelColor.alpha == 255)
LabelColor.alpha = MS_NINT(currentLayer->compositer->opacity * 2.55);
sprintf(labelHexColor, "%02x%02x%02x%02x", LabelColor.alpha,
LabelColor.blue, LabelColor.green, LabelColor.red);
// __TODO__ add label scale
char labelStyleName[64];
sprintf(labelStyleName, "_label_%s", labelHexColor);
styleName = msStringConcatenate(styleName, labelStyleName);
}
if (SymbologyFlag[Symbol]) {
/*
*/
/* __TODO__ add label scale */
styleName = msStringConcatenate(styleName, "_");
styleName = msStringConcatenate(styleName, SymbolName);
}
const char *styleUrl = msLookupHashTable(StyleHashTable, styleName);
if (!styleUrl) {
char *styleValue = NULL;
styleValue = msStringConcatenate(styleValue, "#");
styleValue = msStringConcatenate(styleValue, styleName);
hashObj *hash = msInsertHashTable(StyleHashTable, styleName, styleValue);
styleUrl = hash->data;
msFree(styleValue);
/* Insert new Style node into Document node*/
xmlNodePtr styleNode = xmlNewChild(DocNode, NULL, BAD_CAST "Style", NULL);
xmlNewProp(styleNode, BAD_CAST "id", BAD_CAST styleName);
if (SymbologyFlag[Polygon]) {
xmlNodePtr polyStyleNode =
xmlNewChild(styleNode, NULL, BAD_CAST "PolyStyle", NULL);
xmlNewChild(polyStyleNode, NULL, BAD_CAST "color",
BAD_CAST polygonHexColor);
}
if (SymbologyFlag[Line]) {
for (int i = 0; i < numLineStyle; i++) {
xmlNodePtr lineStyleNode =
xmlNewChild(styleNode, NULL, BAD_CAST "LineStyle", NULL);
sprintf(lineHexColor, "%02x%02x%02x%02x", LineStyle[i].color->alpha,
LineStyle[i].color->blue, LineStyle[i].color->green,
LineStyle[i].color->red);
xmlNewChild(lineStyleNode, NULL, BAD_CAST "color",
BAD_CAST lineHexColor);
char width[16];
sprintf(width, "%.1f", LineStyle[i].width);
xmlNewChild(lineStyleNode, NULL, BAD_CAST "width", BAD_CAST width);
}
}
if (SymbologyFlag[Symbol]) {
xmlNodePtr iconStyleNode =
xmlNewChild(styleNode, NULL, BAD_CAST "IconStyle", NULL);
xmlNodePtr iconNode =
xmlNewChild(iconStyleNode, NULL, BAD_CAST "Icon", NULL);
xmlNewChild(iconNode, NULL, BAD_CAST "href", BAD_CAST SymbolUrl);
/*char scale[16];
sprintf(scale, "%.1f", style->scale);
xmlNewChild(iconStyleNode, NULL, BAD_CAST "scale", BAD_CAST scale);*/
} else {
const char *value =
msLookupHashTable(¤tLayer->metadata, "kml_default_symbol_href");
if (value && strlen(value) > 0) {
xmlNodePtr iconStyleNode =
xmlNewChild(styleNode, NULL, BAD_CAST "IconStyle", NULL);
xmlNodePtr iconNode =
xmlNewChild(iconStyleNode, NULL, BAD_CAST "Icon", NULL);
xmlNewChild(iconNode, NULL, BAD_CAST "href", BAD_CAST value);
}
}
if (SymbologyFlag[Label]) {
xmlNodePtr labelStyleNode =
xmlNewChild(styleNode, NULL, BAD_CAST "LabelStyle", NULL);
xmlNewChild(labelStyleNode, NULL, BAD_CAST "color",
BAD_CAST labelHexColor);
/*char scale[16];
sprintf(scale, "%.1f", style->scale);
xmlNewChild(iconStyleNode, NULL, BAD_CAST "scale", BAD_CAST scale);*/
}
}
if (styleName)
msFree(styleName);
return styleUrl;
}
void KmlRenderer::flushPlacemark() {
if (PlacemarkNode) {
const char *styleUrl = lookupPlacemarkStyle();
xmlNewChild(PlacemarkNode, NULL, BAD_CAST "styleUrl", BAD_CAST styleUrl);
if (DescriptionNode)
xmlAddChild(PlacemarkNode, DescriptionNode);
if (GeomNode)
xmlAddChild(PlacemarkNode, GeomNode);
}
}
xmlNodePtr KmlRenderer::createDescriptionNode(shapeObj *shape) {
/*
*/
/*description nodes for vector layers:
- if kml_description is set, use it
- if not, dump the attributes */
if (pszLayerDescMetadata) {
char *pszTmp = NULL;
char *pszTmpDesc = NULL;
size_t bufferSize = 0;
pszTmpDesc = msStrdup(pszLayerDescMetadata);
for (int i = 0; i < currentLayer->numitems; i++) {
bufferSize = strlen(currentLayer->items[i]) + 3;
pszTmp = (char *)msSmallMalloc(bufferSize);
snprintf(pszTmp, bufferSize, "%%%s%%", currentLayer->items[i]);
if (strcasestr(pszTmpDesc, pszTmp))
pszTmpDesc =
msCaseReplaceSubstring(pszTmpDesc, pszTmp, shape->values[i]);
msFree(pszTmp);
}
xmlNodePtr descriptionNode = xmlNewNode(NULL, BAD_CAST "description");
xmlNodeAddContent(descriptionNode, BAD_CAST pszTmpDesc);
msFree(pszTmpDesc);
return descriptionNode;
} else if ((papszLayerIncludeItems && nIncludeItems > 0) ||
(papszLayerExcludeItems && nExcludeItems > 0)) {
/* -------------------------------------------------------------------- */
/* preferred way is to use the ExtendedData tag (#3728) */
/* http://code.google.com/apis/kml/documentation/extendeddata.html */
/* -------------------------------------------------------------------- */
xmlNodePtr extendedDataNode = xmlNewNode(NULL, BAD_CAST "ExtendedData");
xmlNodePtr dataNode = NULL;
const char *pszAlias = NULL;
int bIncludeAll = MS_FALSE;
if (papszLayerIncludeItems && nIncludeItems == 1 &&
strcasecmp(papszLayerIncludeItems[0], "all") == 0)
bIncludeAll = MS_TRUE;
for (int i = 0; i < currentLayer->numitems; i++) {
int j = 0, k = 0;
/*TODO optimize to calculate this only once per layer*/
for (j = 0; j < nIncludeItems; j++) {
if (strcasecmp(currentLayer->items[i], papszLayerIncludeItems[j]) == 0)
break;
}
if (j < nIncludeItems || bIncludeAll) {
if (papszLayerExcludeItems && nExcludeItems > 0) {
for (k = 0; k < nExcludeItems; k++) {
if (strcasecmp(currentLayer->items[i], papszLayerExcludeItems[k]) ==
0)
break;
}
}
if (nExcludeItems == 0 || k == nExcludeItems) {
dataNode = xmlNewNode(NULL, BAD_CAST "Data");
xmlNewProp(dataNode, BAD_CAST "name",
BAD_CAST currentLayer->items[i]);
pszAlias = getAliasName(currentLayer, currentLayer->items[i], "GO");
if (pszAlias)
xmlNewChild(dataNode, NULL, BAD_CAST "displayName",
BAD_CAST pszAlias);
else
xmlNewChild(dataNode, NULL, BAD_CAST "displayName",
BAD_CAST currentLayer->items[i]);
if (shape->values[i] && strlen(shape->values[i]))
xmlNewChild(dataNode, NULL, BAD_CAST "value",
BAD_CAST shape->values[i]);
else
xmlNewChild(dataNode, NULL, BAD_CAST "value", NULL);
xmlAddChild(extendedDataNode, dataNode);
}
}
}
return extendedDataNode;
}
return NULL;
}
void KmlRenderer::addLineStyleToList(strokeStyleObj *style) {
/*actually this is not necessary. kml only uses the last LineStyle
so we should not bother keeping them all*/
int i = 0;
for (i = 0; i < numLineStyle; i++) {
if (style->width == LineStyle[i].width &&
LineStyle[i].color->alpha == style->color->alpha &&
LineStyle[i].color->red == style->color->red &&
LineStyle[i].color->green == style->color->green &&
LineStyle[i].color->blue == style->color->blue)
break;
}
if (i == numLineStyle) {
numLineStyle++;
if (LineStyle == NULL)
LineStyle = (strokeStyleObj *)msSmallMalloc(sizeof(strokeStyleObj));
else
LineStyle = (strokeStyleObj *)msSmallRealloc(
LineStyle, sizeof(strokeStyleObj) * numLineStyle);
memcpy(&LineStyle[numLineStyle - 1], style, sizeof(strokeStyleObj));
}
}
#endif