root/tags/rel-5-0-2/mapserver/mapchart.c

Revision 6428, 21.4 kB (checked in by dmorissette, 1 year ago)

Added missing line at end of license text

  • Property svn:keywords set to Id
Line 
1 /******************************************************************************
2  *
3  * Project:  MapServer
4  * Purpose:  Implementation of dynamic charting (MS-RFC-29)
5  * Author:   Thomas Bonfort ( thomas.bonfort[at]gmail.com )
6  *
7  ******************************************************************************
8  * Copyright (c) 1996-2007 Regents of the University of Minnesota.
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included in
18  * all copies of this Software or works derived from this Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28
29
30 #include "mapserver.h"
31
32 MS_CVSID("$Id$")
33
34 #define MS_CHART_TYPE_PIE 1
35 #define MS_CHART_TYPE_BAR 2
36
37 /*
38 ** 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
39 */
40 #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))
41
42 /*
43 ** find a point on a shape. check if it fits in image
44 ** returns
45 **  MS_SUCCESS and point coordinates in 'p' if chart fits in image
46 **  MS_FAILURE if no point could be found
47 */
48 int findChartPoint(mapObj *map, shapeObj *shape, int width, int height, pointObj *center) {
49     int middle,numpoints,idx,offset;
50     double invcellsize = 1.0/map->cellsize; /*speed up MAP2IMAGE_X/Y_IC_DBL*/
51     switch(shape->type) {
52         case MS_SHAPE_POINT:
53             if( MS_RENDERER_GD(map->outputformat) ) {
54                 center->x=MS_MAP2IMAGE_X(shape->line[0].point[0].x, map->extent.minx, map->cellsize);
55                 center->y=MS_MAP2IMAGE_Y(shape->line[0].point[0].y, map->extent.maxy, map->cellsize);
56             }
57             #ifdef USE_AGG
58             else if( MS_RENDERER_AGG(map->outputformat) ) {
59                 center->x=MS_MAP2IMAGE_X_IC_DBL(shape->line[0].point[0].x, map->extent.minx, invcellsize);
60                 center->y=MS_MAP2IMAGE_Y_IC_DBL(shape->line[0].point[0].y, map->extent.maxy, invcellsize);
61             }
62             #endif
63            
64             if(MS_CHART_FITS(center->x,center->y,width,height,map->width,map->height))
65                 return MS_SUCCESS;
66             else
67                 return MS_FAILURE;
68             break;
69         case MS_SHAPE_LINE:
70             /*loop through line segments starting from middle (alternate between before and after middle point)
71             **first segment that fits is chosen
72             */
73             middle=shape->line[0].numpoints/2; /*start with middle segment of line*/
74             numpoints=shape->line[0].numpoints;
75             for(offset=1;offset<=middle;offset++) {
76                 idx=middle+offset;
77                 if(idx<numpoints) {
78                     center->x=(shape->line[0].point[idx-1].x+shape->line[0].point[idx].x)/2.;
79                     center->y=(shape->line[0].point[idx-1].y+shape->line[0].point[idx].y)/2.;
80                     if( MS_RENDERER_GD(map->outputformat) ) {
81                         center->x=MS_MAP2IMAGE_X(center->x, map->extent.minx, map->cellsize);
82                         center->y=MS_MAP2IMAGE_Y(center->y, map->extent.maxy, map->cellsize);
83                     }
84                     #ifdef USE_AGG
85                     else if( MS_RENDERER_AGG(map->outputformat) ) {
86                         center->x=MS_MAP2IMAGE_X_IC_DBL(center->x, map->extent.minx, invcellsize);
87                         center->y=MS_MAP2IMAGE_Y_IC_DBL(center->y, map->extent.maxy, invcellsize);
88                     }
89                     #endif
90                     if(MS_CHART_FITS(center->x,center->y,width,height,map->width,map->height))
91                         return MS_SUCCESS;
92                    
93                     break;
94                 }
95                 idx=middle-offset;
96                 if(idx>=0) {
97                     center->x=(shape->line[0].point[idx].x+shape->line[0].point[idx+1].x)/2;
98                     center->y=(shape->line[0].point[idx].y+shape->line[0].point[idx+1].y)/2;
99                     if( MS_RENDERER_GD(map->outputformat) ) {
100                         center->x=MS_MAP2IMAGE_X(center->x, map->extent.minx, map->cellsize);
101                         center->y=MS_MAP2IMAGE_Y(center->y, map->extent.maxy, map->cellsize);
102                     }
103                     #ifdef USE_AGG
104                     else if( MS_RENDERER_AGG(map->outputformat) ) {
105                         center->x=MS_MAP2IMAGE_X_IC_DBL(center->x, map->extent.minx, invcellsize);
106                         center->y=MS_MAP2IMAGE_Y_IC_DBL(center->y, map->extent.maxy, invcellsize);
107                     }
108                     #endif
109                     if(MS_CHART_FITS(center->x,center->y,width,height,map->width,map->height))
110                         return MS_SUCCESS;
111                     break;
112                 }
113             }
114             return MS_FAILURE;
115         break;
116         case MS_SHAPE_POLYGON:
117             msPolygonLabelPoint(shape, center, -1);
118             if( MS_RENDERER_GD(map->outputformat) ) {
119                 center->x=MS_MAP2IMAGE_X(center->x, map->extent.minx, map->cellsize);
120                 center->y=MS_MAP2IMAGE_Y(center->y, map->extent.maxy, map->cellsize);
121             }
122             #ifdef USE_AGG
123             else if( MS_RENDERER_AGG(map->outputformat) ) {
124                 center->x=MS_MAP2IMAGE_X_IC_DBL(center->x, map->extent.minx, invcellsize);
125                 center->y=MS_MAP2IMAGE_Y_IC_DBL(center->y, map->extent.maxy, invcellsize);
126             }
127             #endif
128             if(MS_CHART_FITS(center->x,center->y,width,height,map->width,map->height))
129                 return MS_SUCCESS;
130             else
131                 return MS_FAILURE;
132             break;
133         default:
134             return MS_FAILURE;
135     }
136 }
137
138 int msDrawBarChart(mapObj *map, layerObj *layer, shapeObj *shape, imageObj *image,
139                     int width, int height, float *maxVal, float *minVal, float barWidth)
140 {
141
142     pointObj center;
143     float upperLimit,lowerLimit;
144     float *values,shapeMaxVal,shapeMinVal,pixperval;
145     int c,color,outlinecolor,outlinewidth;
146     float vertOrigin,vertOriginClipped,horizStart,y;
147     float left,top,bottom; /*shortcut to pixel boundaries of the chart*/
148     msDrawStartShape(map, layer, image, shape);
149 #ifdef USE_PROJ
150     if(layer->project && msProjectionsDiffer(&(layer->projection), &(map->projection)))
151         msProjectShape(&layer->projection, &map->projection, shape);
152     else
153         layer->project = MS_FALSE;
154 #endif
155     if(layer->transform == MS_TRUE) {
156       if(findChartPoint(map, shape, width, height, &center)==MS_FAILURE)
157         return MS_SUCCESS; /*next shape*/
158     } else {
159         /* why would this ever be used? */
160         msOffsetPointRelativeTo(&center, layer);
161     }
162    
163     top=center.y-height/2.;
164     bottom=center.y+height/2.;
165     left=center.x-width/2.;
166
167     if(msBindLayerToShape(layer, shape) != MS_SUCCESS)
168         return MS_FAILURE; /* error message is set in msBindLayerToShape() */
169
170     values=(float*)calloc(layer->numclasses,sizeof(float));
171
172     shapeMaxVal=shapeMinVal=0;
173     for(c=0;c<layer->numclasses;c++)
174     {
175         values[c]=(layer->class[c]->styles[0]->size);
176         if(maxVal==NULL || minVal==NULL) { /*compute bounds if not specified*/
177             if(c==0)
178                 shapeMaxVal=shapeMinVal=values[0];
179             if(values[c]>shapeMaxVal)
180                 shapeMaxVal=values[c];
181             if(values[c]<shapeMinVal)
182                 shapeMinVal=values[c];
183         }
184     }
185    
186     /*
187      * use specified bounds if wanted
188      * if not, always show the origin
189      */
190     upperLimit = (maxVal!=NULL)? *maxVal : MS_MAX(shapeMaxVal,0);
191     lowerLimit = (minVal!=NULL)? *minVal : MS_MIN(shapeMinVal,0);
192    
193     pixperval=(float)height/(upperLimit-lowerLimit);
194     vertOrigin=bottom+lowerLimit*pixperval;
195     vertOriginClipped=(vertOrigin<top) ? top :
196                         (vertOrigin>bottom) ? bottom : vertOrigin;
197     horizStart=left;
198     /*
199     color = gdImageColorAllocate(image->img.gd, 0,0,0);
200     gdImageRectangle(image->img.gd, left-1,top-1, center.x+width/2.+1,bottom+1,color);
201     */
202     for(c=0;c<layer->numclasses;c++)
203     {
204         int barHeight=values[c]*pixperval;
205         /*clip bars*/
206         y=((vertOrigin-barHeight)<top) ? top :
207                     (vertOrigin-barHeight>bottom) ? bottom : vertOrigin-barHeight;
208         if(y!=vertOriginClipped) { /*don't draw bars of height == 0 (i.e. either values==0, or clipped)*/
209             if( MS_RENDERER_GD(map->outputformat) ) {
210                 color = gdImageColorAllocate(image->img.gd, layer->class[c]->styles[0]->color.red,
211                         layer->class[c]->styles[0]->color.green,
212                         layer->class[c]->styles[0]->color.blue);
213                 outlinecolor=-1;outlinewidth=1;
214                 if(MS_VALID_COLOR(layer->class[c]->styles[0]->outlinecolor)) {
215                     outlinecolor = gdImageColorAllocate(image->img.gd, layer->class[c]->styles[0]->outlinecolor.red,
216                             layer->class[c]->styles[0]->outlinecolor.green,
217                             layer->class[c]->styles[0]->outlinecolor.blue);
218                 }
219                 if(layer->class[c]->styles[0]->width!=-1)
220                     outlinewidth=layer->class[c]->styles[0]->width;
221                 if(values[c]>0) {
222                     if(outlinecolor==-1) {
223                         gdImageFilledRectangle(image->img.gd, horizStart,y, horizStart+barWidth-1 , vertOriginClipped,color);
224                     } else {
225                         gdImageFilledRectangle(image->img.gd, horizStart,y, horizStart+barWidth-1 , vertOriginClipped,outlinecolor);
226                         gdImageFilledRectangle(image->img.gd, horizStart+outlinewidth,y+outlinewidth, horizStart+barWidth-1-outlinewidth , vertOriginClipped-outlinewidth,color);
227                     }
228                 }
229                 else {
230                     if(outlinecolor==-1) {
231                         gdImageFilledRectangle(image->img.gd, horizStart, vertOriginClipped, horizStart+barWidth-1 , y,color);
232                     } else {
233                         gdImageFilledRectangle(image->img.gd, horizStart, vertOriginClipped, horizStart+barWidth-1 , y,outlinecolor);
234                         gdImageFilledRectangle(image->img.gd, horizStart+outlinewidth, vertOriginClipped+outlinewidth, horizStart+barWidth-1-outlinewidth , y-outlinewidth,color); 
235                     }
236                 }
237                    
238             }
239             #ifdef USE_AGG
240             else if( MS_RENDERER_AGG(map->outputformat) ) {
241                 int outline=0;
242                 if(MS_VALID_COLOR(layer->class[c]->styles[0]->outlinecolor))
243                     outline=1; /*outlining is wierd if this isn't done*/
244                 msFilledRectangleAGG(image, layer->class[c]->styles[0], horizStart, y, horizStart+barWidth-outline , vertOriginClipped);
245             }
246             #endif       
247         }
248         horizStart+=barWidth;
249     }
250     free(values);
251
252     return MS_SUCCESS;
253 }
254
255 int msDrawPieChart(mapObj *map, layerObj *layer, shapeObj *shape, imageObj *image, int diameter)
256 {
257     int i,c,color,outlinecolor,outlinewidth;
258     pointObj center;
259     float *values;
260     float dTotal=0.,start=0,center_x,center_y;
261
262     msDrawStartShape(map, layer, image, shape);
263 #ifdef USE_PROJ
264     if(layer->project && msProjectionsDiffer(&(layer->projection), &(map->projection)))
265         msProjectShape(&layer->projection, &map->projection, shape);
266     else
267         layer->project = MS_FALSE;
268 #endif
269
270     if(layer->transform == MS_TRUE) {
271       if(findChartPoint(map, shape, diameter, diameter, &center)==MS_FAILURE)
272         return MS_SUCCESS; /*next shape*/
273     } else {
274         /* why would this ever be used? */
275         msOffsetPointRelativeTo(&center, layer);
276     }
277
278     if(msBindLayerToShape(layer, shape) != MS_SUCCESS)
279         return MS_FAILURE; /* error message is set in msBindLayerToShape() */
280
281     values=(float*)calloc(layer->numclasses,sizeof(float));
282     for(c=0;c<layer->numclasses;c++)
283     {
284         values[c]=(layer->class[c]->styles[0]->size);
285         if(values[c]<0.) {
286             msSetError(MS_MISCERR, "cannot draw pie charts for negative values", "msDrawPieChart()");
287             return MS_FAILURE;
288         }
289         dTotal+=values[c];
290     }
291
292     for(i=0; i < layer->numclasses; i++)
293     {
294         if(values[i]==0) continue; /*no need to draw. causes artifacts with outlines*/
295         values[i]*=360.0/dTotal;
296         if( MS_RENDERER_GD(map->outputformat) )
297         {
298             color = gdImageColorAllocate(image->img.gd, layer->class[i]->styles[0]->color.red,
299                                                 layer->class[i]->styles[0]->color.green,
300                                                 layer->class[i]->styles[0]->color.blue);
301             outlinecolor=-1;outlinewidth=1;
302             if(MS_VALID_COLOR(layer->class[i]->styles[0]->outlinecolor)) {
303                 outlinecolor = gdImageColorAllocate(image->img.gd, layer->class[i]->styles[0]->outlinecolor.red,
304                         layer->class[i]->styles[0]->outlinecolor.green,
305                         layer->class[i]->styles[0]->outlinecolor.blue);
306             }
307             if(layer->class[i]->styles[0]->width!=-1)
308                 outlinewidth=layer->class[i]->styles[0]->width;
309             /*
310              * offset the center of the slice
311              * NOTE: angles are anti-trigonometric
312              *
313              */
314             if(layer->class[i]->styles[0]->offsetx>0) {
315                 center_x=center.x+layer->class[i]->styles[0]->offsetx*cos(((-start-values[i]/2)*MS_PI/180.));
316                 center_y=center.y-layer->class[i]->styles[0]->offsetx*sin(((-start-values[i]/2)*MS_PI/180.));
317             } else {
318                 center_x=center.x;
319                 center_y=center.y;
320             }
321            
322             if(outlinecolor==-1) {
323                 gdImageFilledArc(image->img.gd, center_x, center_y, diameter, diameter, (int)start, (int)(start+values[i]), color, gdPie);               
324             }
325             else {
326                 gdImageFilledArc(image->img.gd, center_x, center_y, diameter, diameter, (int)start, (int)(start+values[i]), color, gdPie);
327                 gdImageSetThickness(image->img.gd, outlinewidth);
328                 gdImageFilledArc(image->img.gd, center_x, center_y, diameter, diameter, (int)start, (int)(start+values[i]), outlinecolor,gdNoFill|gdEdged);
329                 gdImageSetThickness(image->img.gd, 1);                             
330             }
331         }
332         #ifdef USE_AGG
333         else if( MS_RENDERER_AGG(map->outputformat) )
334         {
335             msPieSliceAGG(image, layer->class[i]->styles[0], center.x, center.y, diameter/2., start, start+values[i]);
336         }
337         #endif
338
339         start+=values[i];
340     }
341     free(values);
342     return MS_SUCCESS;
343 }
344
345 int msDrawPieChartLayer(mapObj *map, layerObj *layer, imageObj *image,
346                         int radius)
347 {
348     shapeObj    shape;
349     int         status=MS_SUCCESS;
350     /* step through the target shapes */
351     msInitShape(&shape);
352     while((status==MS_SUCCESS)&&(msLayerNextShape(layer, &shape)) == MS_SUCCESS) {
353         status = msDrawPieChart(map, layer, &shape, image,radius);
354         msFreeShape(&shape);
355     }
356     return status;
357 }
358
359 int msDrawBarChartLayer(mapObj *map, layerObj *layer, imageObj *image,
360                         int width, int height)
361 {
362     shapeObj    shape;
363     int         status=MS_SUCCESS;
364     const char *barMax=msLayerGetProcessingKey( layer,"CHART_BAR_MAXVAL" );
365     const char *barMin=msLayerGetProcessingKey( layer,"CHART_BAR_MINVAL" );
366     float barWidth;
367     float barMaxVal,barMinVal;
368
369     if(barMax){
370         if(sscanf(barMax,"%f",&barMaxVal)!=1) {
371             msSetError(MS_MISCERR, "Error reading value for processing key \"CHART_BAR_MAXVAL\"", "msDrawBarChartLayerGD()");
372             return MS_FAILURE;
373         }
374     }
375     if(barMin) {
376         if(sscanf(barMin,"%f",&barMinVal)!=1) {
377             msSetError(MS_MISCERR, "Error reading value for processing key \"CHART_BAR_MINVAL\"", "msDrawBarChartLayerGD()");
378             return MS_FAILURE;
379         }
380     }
381     if(barMin && barMax && barMinVal>=barMaxVal) {
382         msSetError(MS_MISCERR, "\"CHART_BAR_MINVAL\" must be less than \"CHART_BAR_MAXVAL\"", "msDrawBarChartLayerGD()");
383         return MS_FAILURE;
384     }
385     barWidth=(float)width/(float)layer->numclasses;
386     if(!barWidth)
387     {
388         msSetError(MS_MISCERR, "Specified width of chart too small to fit given number of classes", "msDrawBarChartLayerGD()");
389         return MS_FAILURE;
390     }
391     /* step through the target shapes */
392     msInitShape(&shape);
393     while((status==MS_SUCCESS)&&(msLayerNextShape(layer, &shape)) == MS_SUCCESS) {
394         status = msDrawBarChart(map, layer, &shape, image,width,height,(barMax!=NULL)?&barMaxVal:NULL,(barMin!=NULL)?&barMinVal:NULL,barWidth);
395         msFreeShape(&shape);
396     }
397     return status;
398 }
399
400 /**
401  * Generic function to render chart layers.
402  */
403 int msDrawChartLayer(mapObj *map, layerObj *layer, imageObj *image)
404 {
405
406     char        annotate=MS_TRUE;
407     rectObj     searchrect;
408     const char *chartTypeProcessingKey=msLayerGetProcessingKey( layer,"CHART_TYPE" );
409     const char *chartSizeProcessingKey=msLayerGetProcessingKey( layer,"CHART_SIZE" );
410     int chartType=MS_CHART_TYPE_PIE;
411     int width,height;
412     int status = MS_FAILURE;
413    
414     if (image && map && layer)
415     {
416         if( !(MS_RENDERER_GD(image->format) || MS_RENDERER_AGG(image->format) )) {
417             msSetError(MS_MISCERR, "chart drawing currently only supports GD and AGG renderers", "msDrawChartLayer()");
418             return MS_FAILURE;
419         }
420        
421         if( layer->numclasses < 2 ) {
422             msSetError(MS_MISCERR,"chart drawing requires at least 2 classes in layer", "msDrawChartLayer()");
423             return MS_FAILURE;
424         }
425    
426         if(chartTypeProcessingKey!=NULL) {
427             if( strcasecmp(chartTypeProcessingKey,"PIE") == 0 ) {
428                 chartType=MS_CHART_TYPE_PIE;
429             }
430             else if( strcasecmp(chartTypeProcessingKey,"BAR") == 0 ) {
431                 chartType=MS_CHART_TYPE_BAR;
432             }
433             else {
434                 msSetError(MS_MISCERR,"unknown chart type for processing key \"CHART_TYPE\", must be one of \"PIE\" or \"BAR\"", "msDrawChartLayer()");
435                 return MS_FAILURE;
436             }
437         }
438            
439         if(chartSizeProcessingKey==NULL)
440         {
441             width=height=20;
442         }
443         else
444         {
445             switch(sscanf(chartSizeProcessingKey ,"%d %d",&width,&height))
446             {
447             case 2:
448                 if(chartType==MS_CHART_TYPE_PIE) {
449                     msSetError(MS_MISCERR,"only one size (radius) supported for processing key \"CHART_SIZE\" for pie chart layers", "msDrawChartLayer()");
450                     return MS_FAILURE;
451                 }
452                 break;
453             case 1: height=width;break;
454             default:
455                 msSetError(MS_MISCERR, "msDrawChartGD format error for processing arg \"CHART_SIZE\"", "msDrawChartGD()");
456                 return MS_FAILURE;
457             }
458         }
459    
460         annotate = msEvalContext(map, layer, layer->labelrequires);
461         if(map->scaledenom > 0) {
462             if((layer->labelmaxscaledenom != -1) && (map->scaledenom >= layer->labelmaxscaledenom)) annotate = MS_FALSE;
463             if((layer->labelminscaledenom != -1) && (map->scaledenom < layer->labelminscaledenom)) annotate = MS_FALSE;
464         }
465    
466         /* open this layer */
467         status = msLayerOpen(layer);
468         if(status != MS_SUCCESS) return MS_FAILURE;
469    
470         status = msLayerWhichItems(layer, MS_TRUE, annotate, NULL);
471         if(status != MS_SUCCESS) {
472             msLayerClose(layer);
473             return MS_FAILURE;
474         }
475         /* identify target shapes */
476         if(layer->transform == MS_TRUE)
477             searchrect = map->extent;
478         else {
479             searchrect.minx = searchrect.miny = 0;
480             searchrect.maxx = map->width-1;
481             searchrect.maxy = map->height-1;
482         }
483    
484     #ifdef USE_PROJ
485         if((map->projection.numargs > 0) && (layer->projection.numargs > 0))
486             msProjectRect(&map->projection, &layer->projection, &searchrect); /* project the searchrect to source coords */
487     #endif
488        
489         status = msLayerWhichShapes(layer, searchrect);
490         if(status == MS_DONE) { /* no overlap */
491             msLayerClose(layer);
492             return MS_SUCCESS;
493         } else if(status != MS_SUCCESS) {
494             msLayerClose(layer);
495             return MS_FAILURE;
496         }
497         switch(chartType) {
498             case MS_CHART_TYPE_PIE:
499                 status = msDrawPieChartLayer(map, layer, image,width);
500                 break;
501             case MS_CHART_TYPE_BAR:
502                 status = msDrawBarChartLayer(map, layer, image,width,height);
503                 break;
504             default:
505                 return MS_FAILURE;/*shouldn't be here anyways*/
506         }
507    
508         msLayerClose(layer); 
509     }
510     return status;
511 }
Note: See TracBrowser for help on using the browser.