/******************************************************************************
 * $Id$
 *
 * Project:  MapServer
 * Purpose:  Implementation of dynamic charting (MS-RFC-29)
 * Author:   Thomas Bonfort ( thomas.bonfort[at]gmail.com )
 *
 ******************************************************************************
 * Copyright (c) 1996-2007 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.h"



#define MS_CHART_TYPE_PIE 1
#define MS_CHART_TYPE_BAR 2
#define MS_CHART_TYPE_VBAR 3

/*
** check if an object of width w and height h placed at point x,y can fit in an image of width mw and height mh
*/
#define MS_CHART_FITS(x,y,w,h,mw,mh) (((x)-(w)/2.>0.)&&((x)+(w)/2.<(mw))&&((y)-(h)/2.>0.)&&((y)+(h)/2.)<(mh))

/*
** find a point on a shape. check if it fits in image
** returns
**  MS_SUCCESS and point coordinates in 'p' if chart fits in image
**  MS_FAILURE if no point could be found
*/
int findChartPoint(mapObj *map, shapeObj *shape, int width, int height, pointObj *center)
{
  int middle,numpoints;
  double invcellsize = 1.0/map->cellsize; /*speed up MAP2IMAGE_X/Y_IC_DBL*/
  switch(shape->type) {
    case MS_SHAPE_POINT:
      center->x=MS_MAP2IMAGE_X_IC_DBL(shape->line[0].point[0].x, map->extent.minx, invcellsize);
      center->y=MS_MAP2IMAGE_Y_IC_DBL(shape->line[0].point[0].y, map->extent.maxy, invcellsize);

      if(MS_CHART_FITS(center->x,center->y,width,height,map->width,map->height))
        return MS_SUCCESS;
      else
        return MS_FAILURE;
      break;
    case MS_SHAPE_LINE:
      /*loop through line segments starting from middle (alternate between before and after middle point)
      **first segment that fits is chosen
      */
      middle=shape->line[0].numpoints/2; /*start with middle segment of line*/
      numpoints=shape->line[0].numpoints;
      if( 1 <= middle ) {
        int idx=middle+1;
        if(idx<numpoints) {
          center->x=(shape->line[0].point[idx-1].x+shape->line[0].point[idx].x)/2.;
          center->y=(shape->line[0].point[idx-1].y+shape->line[0].point[idx].y)/2.;
          center->x=MS_MAP2IMAGE_X_IC_DBL(center->x, map->extent.minx, invcellsize);
          center->y=MS_MAP2IMAGE_Y_IC_DBL(center->y, map->extent.maxy, invcellsize);

          if(MS_CHART_FITS(center->x,center->y,width,height,map->width,map->height))
            return MS_SUCCESS;

          return MS_FAILURE;
        }
        idx=middle-1;
        center->x=(shape->line[0].point[idx].x+shape->line[0].point[idx+1].x)/2;
        center->y=(shape->line[0].point[idx].y+shape->line[0].point[idx+1].y)/2;
        center->x=MS_MAP2IMAGE_X_IC_DBL(center->x, map->extent.minx, invcellsize);
        center->y=MS_MAP2IMAGE_Y_IC_DBL(center->y, map->extent.maxy, invcellsize);

        if(MS_CHART_FITS(center->x,center->y,width,height,map->width,map->height))
          return MS_SUCCESS;
        return MS_FAILURE;
      }
      return MS_FAILURE;
      break;
    case MS_SHAPE_POLYGON:
      msPolygonLabelPoint(shape, center, -1);
      center->x=MS_MAP2IMAGE_X_IC_DBL(center->x, map->extent.minx, invcellsize);
      center->y=MS_MAP2IMAGE_Y_IC_DBL(center->y, map->extent.maxy, invcellsize);

      if(MS_CHART_FITS(center->x,center->y,width,height,map->width,map->height))
        return MS_SUCCESS;
      else
        return MS_FAILURE;
      break;
    default:
      return MS_FAILURE;
  }
}

int WARN_UNUSED drawRectangle(mapObj *map, imageObj *image, double mx, double my, double Mx, double My,
                   styleObj *style)
{
  shapeObj shape;
  lineObj line;
  pointObj point[5];
  line.point = point;
  line.numpoints = 5;
  shape.line = &line;
  shape.numlines = 1;

  point[0].x = point[4].x = point[3].x = mx;
  point[0].y = point[4].y = point[1].y = my;
  /* cppcheck-suppress unreadVariable */
  point[1].x = point[2].x = Mx;
  /* cppcheck-suppress unreadVariable */
  point[2].y = point[3].y = My;

  return msDrawShadeSymbol(map,image,&shape,style,1.0);
}

int WARN_UNUSED msDrawVBarChart(mapObj *map, imageObj *image, pointObj *center,
                    double *values, styleObj **styles, int numvalues,
                    double barWidth)
{

  int c;
  double left,bottom,cur; /*shortcut to pixel boundaries of the chart*/
  double height = 0;

  for(c=0; c<numvalues; c++) {
    height += values[c];
  }

  cur = bottom = center->y+height/2.;
  left = center->x-barWidth/2.;

  for(c=0; c<numvalues; c++) {
    if(MS_UNLIKELY(MS_FAILURE == drawRectangle(map, image, left, cur, left+barWidth, cur-values[c], styles[c])))
      return MS_FAILURE;
    cur -= values[c];
  }
  return MS_SUCCESS;
}


int msDrawBarChart(mapObj *map, imageObj *image, pointObj *center,
                   double *values, styleObj **styles, int numvalues,
                   double width, double height, double *maxVal, double *minVal, double barWidth)
{

  double upperLimit,lowerLimit;
  double shapeMaxVal,shapeMinVal,pixperval;
  int c;
  double vertOrigin,vertOriginClipped,horizStart,y;
  double left,top,bottom; /*shortcut to pixel boundaries of the chart*/

  top=center->y-height/2.;
  bottom=center->y+height/2.;
  left=center->x-width/2.;

  shapeMaxVal=shapeMinVal=values[0];
  for(c=1; c<numvalues; c++) {
    if(maxVal==NULL || minVal==NULL) { /*compute bounds if not specified*/
      if(values[c]>shapeMaxVal)
        shapeMaxVal=values[c];
      if(values[c]<shapeMinVal)
        shapeMinVal=values[c];
    }
  }

  /*
   * use specified bounds if wanted
   * if not, always show the origin
   */
  upperLimit = (maxVal!=NULL)? *maxVal : MS_MAX(shapeMaxVal,0);
  lowerLimit = (minVal!=NULL)? *minVal : MS_MIN(shapeMinVal,0);
  if(upperLimit==lowerLimit) {
    /* treat the case where we would have an unspecified behavior */
    upperLimit+=0.5;
    lowerLimit-=0.5;
  }

  pixperval=height/(upperLimit-lowerLimit);
  vertOrigin=bottom+lowerLimit*pixperval;
  vertOriginClipped=(vertOrigin<top) ? top :
                    (vertOrigin>bottom) ? bottom : vertOrigin;
  horizStart=left;
  
  for(c=0; c<numvalues; c++) {
    double barHeight=values[c]*pixperval;
    /*clip bars*/
    y=((vertOrigin-barHeight)<top) ? top :
      (vertOrigin-barHeight>bottom) ? bottom : vertOrigin-barHeight;
    if(y!=vertOriginClipped) { /*don't draw bars of height == 0 (i.e. either values==0, or clipped)*/
      if(values[c]>0) {
        if(MS_UNLIKELY(MS_FAILURE == drawRectangle(map, image, horizStart, y, horizStart+barWidth-1, vertOriginClipped, styles[c])))
          return MS_FAILURE;
      }
      else {
        if(MS_UNLIKELY(MS_FAILURE == drawRectangle(map,image, horizStart, vertOriginClipped, horizStart+barWidth-1 , y, styles[c])))
          return MS_FAILURE;
      }
    }
    horizStart+=barWidth;
  }
  return MS_SUCCESS;
}

int WARN_UNUSED msDrawPieChart(mapObj *map, imageObj *image,
                   pointObj *center, double diameter,
                   double *values, styleObj **styles, int numvalues)
{
  int i;
  double dTotal=0.,start=0;

  for(i=0; i<numvalues; i++) {
    if(values[i]<0.) {
      msSetError(MS_MISCERR, "cannot draw pie charts for negative values", "msDrawPieChart()");
      return MS_FAILURE;
    }
    dTotal+=values[i];
  }

  for(i=0; i < numvalues; i++) {
    double angle = values[i];
    if(angle==0) continue; /*no need to draw. causes artifacts with outlines*/
    angle*=360.0/dTotal;
    if(MS_UNLIKELY(MS_FAILURE == msDrawPieSlice(map ,image, center, styles[i], diameter/2., start, start+angle)))
      return MS_FAILURE;

    start+=angle;
  }
  return MS_SUCCESS;
}

int getNextShape(mapObj *map, layerObj *layer, double *values, int *nvalues, styleObj **styles, shapeObj *shape)
{
  int status;
  int c;
  status = msLayerNextShape(layer, shape);
  if(status == MS_SUCCESS) {

    if(layer->project)
    {
      if( layer->reprojectorLayerToMap == NULL )
      {
        layer->reprojectorLayerToMap = msProjectCreateReprojector(
            &layer->projection, &map->projection);
        if( layer->reprojectorLayerToMap == NULL )
        {
            return MS_FAILURE;
        }
      }
      msProjectShapeEx(layer->reprojectorLayerToMap, shape);
    }

    if(msBindLayerToShape(layer, shape, MS_DRAWMODE_FEATURES|MS_DRAWMODE_LABELS) != MS_SUCCESS)
      return MS_FAILURE; /* error message is set in msBindLayerToShape() */

    *nvalues = 0;
    for(c=0; c<layer->numclasses; c++) {
      if(msEvalExpression(layer, shape, &(layer->class[c]->expression), layer->classitemindex) == MS_TRUE) {
        values[*nvalues]=(layer->class[c]->styles[0]->size);
        styles[*nvalues]=layer->class[c]->styles[0];
        (*nvalues)++;
      }
    }
  }
  return status;
}

/* eventually add a class to the layer to get the diameter from an attribute */
int pieLayerProcessDynamicDiameter(layerObj *layer)
{
  const char *chartRangeProcessingKey=NULL;
  char *attrib;
  double mindiameter=-1, maxdiameter, minvalue, maxvalue;
  classObj *newclass;
  styleObj *newstyle;
  const char *chartSizeProcessingKey=msLayerGetProcessingKey( layer,"CHART_SIZE" );
  if(chartSizeProcessingKey != NULL)
    return MS_FALSE;
  chartRangeProcessingKey=msLayerGetProcessingKey( layer,"CHART_SIZE_RANGE" );
  if(chartRangeProcessingKey==NULL)
    return MS_FALSE;
  attrib = msStrdup(chartRangeProcessingKey);
  char* space = strchr(attrib, ' ');
  if( space ) {
      *space = '\0';
      switch(sscanf(space+1,"%lf %lf %lf %lf",
                    &mindiameter,&maxdiameter,&minvalue,&maxvalue)) {
        case 4: /*we have the attribute and the four range values*/
          break;
        default:
          free(attrib);
          msSetError(MS_MISCERR, "Chart Layer format error for processing key \"CHART_RANGE\"", "msDrawChartLayer()");
          return MS_FAILURE;
      }
  }

  /*create a new class in the layer containing the wanted attribute
   * as the SIZE of its first STYLE*/
  newclass=msGrowLayerClasses(layer);
  if(newclass==NULL) {
    free(attrib);
    return MS_FAILURE;
  }
  initClass(newclass);
  layer->numclasses++;

  /*create and attach a new styleObj to our temp class
   * and bind the wanted attribute to its SIZE
   */
  newstyle=msGrowClassStyles(newclass);
  if(newstyle==NULL) {
    free(attrib);
    return MS_FAILURE;
  }
  initStyle(newstyle);
  newclass->numstyles++;
  newclass->name=(char*)msStrdup("__MS_SIZE_ATTRIBUTE_");
  newstyle->bindings[MS_STYLE_BINDING_SIZE].item=msStrdup(attrib);
  newstyle->numbindings++;
  free(attrib);

  return MS_TRUE;

}

/* clean up the class added temporarily */
static void pieLayerCleanupDynamicDiameter(layerObj *layer)
{
  if( layer->numclasses > 0 && EQUALN(layer->class[layer->numclasses - 1]->name, "__MS_SIZE_ATTRIBUTE_", 20) ) {
    classObj *c=msRemoveClass(layer, layer->numclasses - 1);
    freeClass(c);
    msFree(c);
  }
}


int msDrawPieChartLayer(mapObj *map, layerObj *layer, imageObj *image)
{
  shapeObj    shape;
  int         status=MS_SUCCESS;
  const char *chartRangeProcessingKey=NULL;
  const char *chartSizeProcessingKey=msLayerGetProcessingKey( layer,"CHART_SIZE" );
  double diameter=0, mindiameter=-1, maxdiameter=0, minvalue=0, maxvalue=0, exponent=0;
  double *values;
  styleObj **styles;
  pointObj center;
  int numvalues = layer->numclasses; /* the number of classes to represent in the graph */
  int numvalues_for_shape = 0;

  if(chartSizeProcessingKey==NULL) {
    chartRangeProcessingKey=msLayerGetProcessingKey( layer,"CHART_SIZE_RANGE" );
    if(chartRangeProcessingKey==NULL)
      diameter=20;
    else {
      sscanf(chartRangeProcessingKey,"%*s %lf %lf %lf %lf %lf",
             &mindiameter,&maxdiameter,&minvalue,&maxvalue,&exponent);
    }
  } else {
    if(sscanf(chartSizeProcessingKey ,"%lf",&diameter)!=1) {
      msSetError(MS_MISCERR, "msDrawChart format error for processing key \"CHART_SIZE\"", "msDrawPieChartLayer()");
      return MS_FAILURE;
    }
  }

  layer->project = msProjectionsDiffer(&(layer->projection), &(map->projection));

  /* step through the target shapes */
  msInitShape(&shape);

  values=(double*)calloc(numvalues,sizeof(double));
  MS_CHECK_ALLOC(values, numvalues*sizeof(double), MS_FAILURE);
  styles = (styleObj**)malloc((numvalues)*sizeof(styleObj*));
  if (styles == NULL) {
    msSetError(MS_MEMERR, "%s: %d: Out of memory allocating %u bytes.\n", "msDrawPieChartLayer()",
               __FILE__, __LINE__, (unsigned int)(numvalues*sizeof(styleObj*)));
    free(values);
    return MS_FAILURE;
  }

  while(MS_SUCCESS == getNextShape(map,layer,values,&numvalues_for_shape,styles,&shape)) {
    if(chartRangeProcessingKey!=NULL)
      numvalues_for_shape--;
    if(numvalues_for_shape == 0) {
      msFreeShape(&shape);
      continue;
    }
    msDrawStartShape(map, layer, image, &shape);
    if(chartRangeProcessingKey!=NULL) {
      diameter = values[numvalues_for_shape];
      if(mindiameter>=0) {
        if(diameter<=minvalue)
          diameter=mindiameter;
        else if(diameter>=maxvalue)
          diameter=maxdiameter;
        else {
          if (exponent <= 0)
            diameter=MS_NINT(
                       mindiameter+
                       ((diameter-minvalue)/(maxvalue-minvalue))*
                       (maxdiameter-mindiameter)
                     );
          else
            diameter=MS_NINT(
                       mindiameter+
                       pow((diameter-minvalue)/(maxvalue-minvalue),1.0/exponent)*
                       (maxdiameter-mindiameter)
                     );
        }
      }
    }
    if(findChartPoint(map, &shape, diameter, diameter, &center) == MS_SUCCESS) {
      status = msDrawPieChart(map,image, &center, diameter,
                              values,styles,numvalues_for_shape);
    }
    msDrawEndShape(map,layer,image,&shape);
    msFreeShape(&shape);
  }
  free(values);
  free(styles);
  return status;
}

int msDrawVBarChartLayer(mapObj *map, layerObj *layer, imageObj *image)
{
  shapeObj    shape;
  int         status=MS_SUCCESS;
  const char *chartSizeProcessingKey=msLayerGetProcessingKey( layer,"CHART_SIZE" );
  const char *chartScaleProcessingKey=msLayerGetProcessingKey( layer,"CHART_SCALE" );
  double barWidth,scale=1.0;
  double *values;
  styleObj **styles;
  pointObj center;
  int numvalues = layer->numclasses;
  int numvalues_for_shape;
  if(chartSizeProcessingKey==NULL) {
    barWidth=20;
  } else {
    if(sscanf(chartSizeProcessingKey ,"%lf",&barWidth) != 1) {
      msSetError(MS_MISCERR, "msDrawChart format error for processing key \"CHART_SIZE\"", "msDrawVBarChartLayer()");
      return MS_FAILURE;
    }
  }

  if(chartScaleProcessingKey) {
    if(sscanf(chartScaleProcessingKey,"%lf",&scale)!=1) {
      msSetError(MS_MISCERR, "Error reading value for processing key \"CHART_SCALE\"", "msDrawVBarChartLayer()");
      return MS_FAILURE;
    }
  }
  msInitShape(&shape);

  values=(double*)calloc(numvalues,sizeof(double));
  MS_CHECK_ALLOC(values, numvalues*sizeof(double), MS_FAILURE);
  styles = (styleObj**)malloc(numvalues*sizeof(styleObj*));
  if (styles == NULL) {
    msSetError(MS_MEMERR, "%s: %d: Out of memory allocating %u bytes.\n", "msDrawVBarChartLayer()",
               __FILE__, __LINE__, (unsigned int)(numvalues*sizeof(styleObj*)));
    free(values);
    return MS_FAILURE;
  }

  while(MS_SUCCESS == getNextShape(map,layer,values,&numvalues_for_shape,styles,&shape)) {
    int i;
    double h=0;
    if(numvalues_for_shape == 0) {
      continue;
    }
    for(i=0; i<numvalues_for_shape; i++) {
      values[i]*=scale;
      h += values[i];
    }
    msDrawStartShape(map, layer, image, &shape);
    if(findChartPoint(map, &shape, barWidth,h, &center)==MS_SUCCESS) {
      status = msDrawVBarChart(map,image,
                               &center,
                               values, styles, numvalues_for_shape,
                               barWidth);
    }
    msDrawEndShape(map,layer,image,&shape);
    msFreeShape(&shape);
  }
  free(values);
  free(styles);
  return status;
}


int msDrawBarChartLayer(mapObj *map, layerObj *layer, imageObj *image)
{
  shapeObj    shape;
  int         status=MS_SUCCESS;
  const char *chartSizeProcessingKey=msLayerGetProcessingKey( layer,"CHART_SIZE" );
  const char *barMax=msLayerGetProcessingKey( layer,"CHART_BAR_MAXVAL" );
  const char *barMin=msLayerGetProcessingKey( layer,"CHART_BAR_MINVAL" );
  double width,height;
  double barWidth;
  double *values;
  styleObj **styles;
  pointObj center;
  double barMaxVal = 0.0,barMinVal = 0.0;
  int numvalues = layer->numclasses;
  int numvalues_for_shape;
  if(chartSizeProcessingKey==NULL) {
    width=height=20;
  } else {
    switch(sscanf(chartSizeProcessingKey ,"%lf %lf",&width,&height)) {
      case 2:
        break;
      case 1:
        height = width;
        break;
      default:
        msSetError(MS_MISCERR, "msDrawChart format error for processing key \"CHART_SIZE\"", "msDrawBarChartLayer()");
        return MS_FAILURE;
    }
  }

  if(barMax) {
    if(sscanf(barMax,"%lf",&barMaxVal)!=1) {
      msSetError(MS_MISCERR, "Error reading value for processing key \"CHART_BAR_MAXVAL\"", "msDrawBarChartLayer()");
      return MS_FAILURE;
    }
  }
  if(barMin) {
    if(sscanf(barMin,"%lf",&barMinVal)!=1) {
      msSetError(MS_MISCERR, "Error reading value for processing key \"CHART_BAR_MINVAL\"", "msDrawBarChartLayer()");
      return MS_FAILURE;
    }
  }
  if(barMin && barMax && barMinVal>=barMaxVal) {
    msSetError(MS_MISCERR, "\"CHART_BAR_MINVAL\" must be less than \"CHART_BAR_MAXVAL\"", "msDrawBarChartLayer()");
    return MS_FAILURE;
  }
  barWidth=(double)width/(double)layer->numclasses;
  if(!barWidth) {
    msSetError(MS_MISCERR, "Specified width of chart too small to fit given number of classes", "msDrawBarChartLayer()");
    return MS_FAILURE;
  }

  msInitShape(&shape);

  values=(double*)calloc(numvalues,sizeof(double));
  MS_CHECK_ALLOC(values, numvalues*sizeof(double), MS_FAILURE);
  styles = (styleObj**)malloc(numvalues*sizeof(styleObj*));
  if (styles == NULL) {
    msSetError(MS_MEMERR, "%s: %d: Out of memory allocating %u bytes.\n", "msDrawBarChartLayer()",
               __FILE__, __LINE__, (unsigned int)(numvalues*sizeof(styleObj*)));
    free(values);
    return MS_FAILURE;
  }

  while(MS_SUCCESS == getNextShape(map,layer,values,&numvalues_for_shape,styles,&shape)) {
    if(numvalues_for_shape == 0 ) continue;
    msDrawStartShape(map, layer, image, &shape);
    if(findChartPoint(map, &shape, width,height, &center)==MS_SUCCESS) {
      status = msDrawBarChart(map,image,
                              &center,
                              values, styles, numvalues_for_shape,
                              width,height,
                              (barMax!=NULL)?&barMaxVal:NULL,
                              (barMin!=NULL)?&barMinVal:NULL,
                              barWidth);
    }
    msDrawEndShape(map,layer,image,&shape);
    msFreeShape(&shape);
  }
  free(values);
  free(styles);
  return status;
}

/**
 * Generic function to render chart layers.
 */
int msDrawChartLayer(mapObj *map, layerObj *layer, imageObj *image)
{

  rectObj     searchrect;
  const char *chartTypeProcessingKey=msLayerGetProcessingKey( layer,"CHART_TYPE" );
  int chartType=MS_CHART_TYPE_PIE;
  int status = MS_FAILURE;

  if (image && map) {
    if( !(MS_RENDERER_PLUGIN(image->format) )) {
      msSetError(MS_MISCERR, "chart drawing currently only supports GD and AGG renderers", "msDrawChartLayer()");
      return MS_FAILURE;
    }

    if(chartTypeProcessingKey!=NULL) {
      if( strcasecmp(chartTypeProcessingKey,"PIE") == 0 ) {
        chartType=MS_CHART_TYPE_PIE;
      } else if( strcasecmp(chartTypeProcessingKey,"BAR") == 0 ) {
        chartType=MS_CHART_TYPE_BAR;
      } else if( strcasecmp(chartTypeProcessingKey,"VBAR") == 0 ) {
        chartType=MS_CHART_TYPE_VBAR;
      } else {
        msSetError(MS_MISCERR,"unknown chart type for processing key \"CHART_TYPE\", must be one of \"PIE\" or \"BAR\"", "msDrawChartLayer()");
        return MS_FAILURE;
      }
    }
    if(chartType == MS_CHART_TYPE_PIE) {
      pieLayerProcessDynamicDiameter(layer);
    }

    /* open this layer */
    status = msLayerOpen(layer);
    if(status != MS_SUCCESS) return MS_FAILURE;

    status = msLayerWhichItems(layer, MS_FALSE, NULL);
    if(status != MS_SUCCESS) {
      msLayerClose(layer);
      return MS_FAILURE;
    }
    /* identify target shapes */
    if(layer->transform == MS_TRUE)
      searchrect = map->extent;
    else {
      searchrect.minx = searchrect.miny = 0;
      searchrect.maxx = map->width-1;
      searchrect.maxy = map->height-1;
    }

    if((map->projection.numargs > 0) && (layer->projection.numargs > 0))
      msProjectRect(&map->projection, &layer->projection, &searchrect); /* project the searchrect to source coords */

    status = msLayerWhichShapes(layer, searchrect, MS_FALSE);
    if(status == MS_DONE) { /* no overlap */
      msLayerClose(layer);
      if(chartType == MS_CHART_TYPE_PIE)
        pieLayerCleanupDynamicDiameter(layer);
      return MS_SUCCESS;
    } else if(status != MS_SUCCESS) {
      msLayerClose(layer);
      if(chartType == MS_CHART_TYPE_PIE)
        pieLayerCleanupDynamicDiameter(layer);
      return MS_FAILURE;
    }
    switch(chartType) {
      case MS_CHART_TYPE_PIE:
        status = msDrawPieChartLayer(map, layer, image);
        break;
      case MS_CHART_TYPE_BAR:
        status = msDrawBarChartLayer(map, layer, image);
        break;
      case MS_CHART_TYPE_VBAR:
        status = msDrawVBarChartLayer(map, layer, image);
        break;
      default:
        return MS_FAILURE;/*shouldn't be here anyways*/
    }

    msLayerClose(layer);

    if(chartType == MS_CHART_TYPE_PIE)
      pieLayerCleanupDynamicDiameter(layer);
  }
  return status;
}
