/src/MapServer/src/mapchart.c
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * $Id$ |
3 | | * |
4 | | * Project: MapServer |
5 | | * Purpose: Implementation of dynamic charting (MS-RFC-29) |
6 | | * Author: Thomas Bonfort ( thomas.bonfort[at]gmail.com ) |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 1996-2007 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.h" |
31 | | |
32 | 0 | #define MS_CHART_TYPE_PIE 1 |
33 | 0 | #define MS_CHART_TYPE_BAR 2 |
34 | 0 | #define MS_CHART_TYPE_VBAR 3 |
35 | | |
36 | | /* |
37 | | ** check if an object of width w and height h placed at point x,y can fit in an |
38 | | *image of width mw and height mh |
39 | | */ |
40 | | #define MS_CHART_FITS(x, y, w, h, mw, mh) \ |
41 | 0 | (((x) - (w) / 2. > 0.) && ((x) + (w) / 2. < (mw)) && \ |
42 | 0 | ((y) - (h) / 2. > 0.) && ((y) + (h) / 2.) < (mh)) |
43 | | |
44 | | /* |
45 | | ** find a point on a shape. check if it fits in image |
46 | | ** returns |
47 | | ** MS_SUCCESS and point coordinates in 'p' if chart fits in image |
48 | | ** MS_FAILURE if no point could be found |
49 | | */ |
50 | | int findChartPoint(mapObj *map, shapeObj *shape, int width, int height, |
51 | 0 | pointObj *center) { |
52 | 0 | int middle, numpoints; |
53 | 0 | double invcellsize = 1.0 / map->cellsize; /*speed up MAP2IMAGE_X/Y_IC_DBL*/ |
54 | 0 | switch (shape->type) { |
55 | 0 | case MS_SHAPE_POINT: |
56 | 0 | center->x = MS_MAP2IMAGE_X_IC_DBL(shape->line[0].point[0].x, |
57 | 0 | map->extent.minx, invcellsize); |
58 | 0 | center->y = MS_MAP2IMAGE_Y_IC_DBL(shape->line[0].point[0].y, |
59 | 0 | map->extent.maxy, invcellsize); |
60 | |
|
61 | 0 | if (MS_CHART_FITS(center->x, center->y, width, height, map->width, |
62 | 0 | map->height)) |
63 | 0 | return MS_SUCCESS; |
64 | 0 | else |
65 | 0 | return MS_FAILURE; |
66 | 0 | break; |
67 | 0 | case MS_SHAPE_LINE: |
68 | | /*loop through line segments starting from middle (alternate between before |
69 | | *and after middle point) *first segment that fits is chosen |
70 | | */ |
71 | 0 | middle = shape->line[0].numpoints / 2; /*start with middle segment of line*/ |
72 | 0 | numpoints = shape->line[0].numpoints; |
73 | 0 | if (1 <= middle) { |
74 | 0 | int idx = middle + 1; |
75 | 0 | if (idx < numpoints) { |
76 | 0 | center->x = |
77 | 0 | (shape->line[0].point[idx - 1].x + shape->line[0].point[idx].x) / |
78 | 0 | 2.; |
79 | 0 | center->y = |
80 | 0 | (shape->line[0].point[idx - 1].y + shape->line[0].point[idx].y) / |
81 | 0 | 2.; |
82 | 0 | center->x = |
83 | 0 | MS_MAP2IMAGE_X_IC_DBL(center->x, map->extent.minx, invcellsize); |
84 | 0 | center->y = |
85 | 0 | MS_MAP2IMAGE_Y_IC_DBL(center->y, map->extent.maxy, invcellsize); |
86 | |
|
87 | 0 | if (MS_CHART_FITS(center->x, center->y, width, height, map->width, |
88 | 0 | map->height)) |
89 | 0 | return MS_SUCCESS; |
90 | | |
91 | 0 | return MS_FAILURE; |
92 | 0 | } |
93 | 0 | idx = middle - 1; |
94 | 0 | center->x = |
95 | 0 | (shape->line[0].point[idx].x + shape->line[0].point[idx + 1].x) / 2; |
96 | 0 | center->y = |
97 | 0 | (shape->line[0].point[idx].y + shape->line[0].point[idx + 1].y) / 2; |
98 | 0 | center->x = |
99 | 0 | MS_MAP2IMAGE_X_IC_DBL(center->x, map->extent.minx, invcellsize); |
100 | 0 | center->y = |
101 | 0 | MS_MAP2IMAGE_Y_IC_DBL(center->y, map->extent.maxy, invcellsize); |
102 | |
|
103 | 0 | if (MS_CHART_FITS(center->x, center->y, width, height, map->width, |
104 | 0 | map->height)) |
105 | 0 | return MS_SUCCESS; |
106 | 0 | return MS_FAILURE; |
107 | 0 | } |
108 | 0 | return MS_FAILURE; |
109 | 0 | break; |
110 | 0 | case MS_SHAPE_POLYGON: |
111 | 0 | msPolygonLabelPoint(shape, center, -1); |
112 | 0 | center->x = MS_MAP2IMAGE_X_IC_DBL(center->x, map->extent.minx, invcellsize); |
113 | 0 | center->y = MS_MAP2IMAGE_Y_IC_DBL(center->y, map->extent.maxy, invcellsize); |
114 | |
|
115 | 0 | if (MS_CHART_FITS(center->x, center->y, width, height, map->width, |
116 | 0 | map->height)) |
117 | 0 | return MS_SUCCESS; |
118 | 0 | else |
119 | 0 | return MS_FAILURE; |
120 | 0 | break; |
121 | 0 | default: |
122 | 0 | return MS_FAILURE; |
123 | 0 | } |
124 | 0 | } |
125 | | |
126 | | int WARN_UNUSED drawRectangle(mapObj *map, imageObj *image, double mx, |
127 | | double my, double Mx, double My, |
128 | 0 | styleObj *style) { |
129 | 0 | shapeObj shape; |
130 | 0 | lineObj line; |
131 | 0 | pointObj point[5]; |
132 | 0 | line.point = point; |
133 | 0 | line.numpoints = 5; |
134 | 0 | shape.line = &line; |
135 | 0 | shape.numlines = 1; |
136 | |
|
137 | 0 | point[0].x = point[4].x = point[3].x = mx; |
138 | 0 | point[0].y = point[4].y = point[1].y = my; |
139 | | /* cppcheck-suppress unreadVariable */ |
140 | 0 | point[1].x = point[2].x = Mx; |
141 | | /* cppcheck-suppress unreadVariable */ |
142 | 0 | point[2].y = point[3].y = My; |
143 | |
|
144 | 0 | return msDrawShadeSymbol(map, image, &shape, style, 1.0); |
145 | 0 | } |
146 | | |
147 | | int WARN_UNUSED msDrawVBarChart(mapObj *map, imageObj *image, pointObj *center, |
148 | | double *values, styleObj **styles, |
149 | 0 | int numvalues, double barWidth) { |
150 | |
|
151 | 0 | int c; |
152 | 0 | double left, cur; /*shortcut to pixel boundaries of the chart*/ |
153 | 0 | double height = 0; |
154 | |
|
155 | 0 | for (c = 0; c < numvalues; c++) { |
156 | 0 | height += values[c]; |
157 | 0 | } |
158 | |
|
159 | 0 | cur = center->y + height / 2.; |
160 | 0 | left = center->x - barWidth / 2.; |
161 | |
|
162 | 0 | for (c = 0; c < numvalues; c++) { |
163 | 0 | if (MS_UNLIKELY(MS_FAILURE == drawRectangle(map, image, left, cur, |
164 | 0 | left + barWidth, |
165 | 0 | cur - values[c], styles[c]))) |
166 | 0 | return MS_FAILURE; |
167 | 0 | cur -= values[c]; |
168 | 0 | } |
169 | 0 | return MS_SUCCESS; |
170 | 0 | } |
171 | | |
172 | | int msDrawBarChart(mapObj *map, imageObj *image, pointObj *center, |
173 | | double *values, styleObj **styles, int numvalues, |
174 | | double width, double height, double *maxVal, double *minVal, |
175 | 0 | double barWidth) { |
176 | |
|
177 | 0 | double upperLimit, lowerLimit; |
178 | 0 | double shapeMaxVal, shapeMinVal, pixperval; |
179 | 0 | int c; |
180 | 0 | double vertOrigin, vertOriginClipped, horizStart, y; |
181 | 0 | double left, top, bottom; /*shortcut to pixel boundaries of the chart*/ |
182 | |
|
183 | 0 | top = center->y - height / 2.; |
184 | 0 | bottom = center->y + height / 2.; |
185 | 0 | left = center->x - width / 2.; |
186 | |
|
187 | 0 | shapeMaxVal = shapeMinVal = values[0]; |
188 | 0 | for (c = 1; c < numvalues; c++) { |
189 | 0 | if (maxVal == NULL || minVal == NULL) { /*compute bounds if not specified*/ |
190 | 0 | if (values[c] > shapeMaxVal) |
191 | 0 | shapeMaxVal = values[c]; |
192 | 0 | if (values[c] < shapeMinVal) |
193 | 0 | shapeMinVal = values[c]; |
194 | 0 | } |
195 | 0 | } |
196 | | |
197 | | /* |
198 | | * use specified bounds if wanted |
199 | | * if not, always show the origin |
200 | | */ |
201 | 0 | upperLimit = (maxVal != NULL) ? *maxVal : MS_MAX(shapeMaxVal, 0); |
202 | 0 | lowerLimit = (minVal != NULL) ? *minVal : MS_MIN(shapeMinVal, 0); |
203 | 0 | if (upperLimit == lowerLimit) { |
204 | | /* treat the case where we would have an unspecified behavior */ |
205 | 0 | upperLimit += 0.5; |
206 | 0 | lowerLimit -= 0.5; |
207 | 0 | } |
208 | |
|
209 | 0 | pixperval = height / (upperLimit - lowerLimit); |
210 | 0 | vertOrigin = bottom + lowerLimit * pixperval; |
211 | 0 | vertOriginClipped = (vertOrigin < top) ? top |
212 | 0 | : (vertOrigin > bottom) ? bottom |
213 | 0 | : vertOrigin; |
214 | 0 | horizStart = left; |
215 | |
|
216 | 0 | for (c = 0; c < numvalues; c++) { |
217 | 0 | double barHeight = values[c] * pixperval; |
218 | | /*clip bars*/ |
219 | 0 | y = ((vertOrigin - barHeight) < top) ? top |
220 | 0 | : (vertOrigin - barHeight > bottom) ? bottom |
221 | 0 | : vertOrigin - barHeight; |
222 | 0 | if (y != vertOriginClipped) { /*don't draw bars of height == 0 (i.e. either |
223 | | values==0, or clipped)*/ |
224 | 0 | if (values[c] > 0) { |
225 | 0 | if (MS_UNLIKELY(MS_FAILURE == drawRectangle(map, image, horizStart, y, |
226 | 0 | horizStart + barWidth - 1, |
227 | 0 | vertOriginClipped, |
228 | 0 | styles[c]))) |
229 | 0 | return MS_FAILURE; |
230 | 0 | } else { |
231 | 0 | if (MS_UNLIKELY(MS_FAILURE == |
232 | 0 | drawRectangle(map, image, horizStart, vertOriginClipped, |
233 | 0 | horizStart + barWidth - 1, y, styles[c]))) |
234 | 0 | return MS_FAILURE; |
235 | 0 | } |
236 | 0 | } |
237 | 0 | horizStart += barWidth; |
238 | 0 | } |
239 | 0 | return MS_SUCCESS; |
240 | 0 | } |
241 | | |
242 | | int WARN_UNUSED msDrawPieChart(mapObj *map, imageObj *image, pointObj *center, |
243 | | double diameter, double *values, |
244 | 0 | styleObj **styles, int numvalues) { |
245 | 0 | int i; |
246 | 0 | double dTotal = 0., start = 0; |
247 | |
|
248 | 0 | for (i = 0; i < numvalues; i++) { |
249 | 0 | if (values[i] < 0.) { |
250 | 0 | msSetError(MS_MISCERR, "cannot draw pie charts for negative values", |
251 | 0 | "msDrawPieChart()"); |
252 | 0 | return MS_FAILURE; |
253 | 0 | } |
254 | 0 | dTotal += values[i]; |
255 | 0 | } |
256 | | |
257 | 0 | for (i = 0; i < numvalues; i++) { |
258 | 0 | double angle = values[i]; |
259 | 0 | if (angle == 0) |
260 | 0 | continue; /*no need to draw. causes artifacts with outlines*/ |
261 | 0 | angle *= 360.0 / dTotal; |
262 | 0 | if (MS_UNLIKELY(MS_FAILURE == msDrawPieSlice(map, image, center, styles[i], |
263 | 0 | diameter / 2., start, |
264 | 0 | start + angle))) |
265 | 0 | return MS_FAILURE; |
266 | | |
267 | 0 | start += angle; |
268 | 0 | } |
269 | 0 | return MS_SUCCESS; |
270 | 0 | } |
271 | | |
272 | | int getNextShape(mapObj *map, layerObj *layer, double *values, int *nvalues, |
273 | 0 | styleObj **styles, shapeObj *shape) { |
274 | 0 | int status; |
275 | 0 | int c; |
276 | 0 | status = msLayerNextShape(layer, shape); |
277 | 0 | if (status == MS_SUCCESS) { |
278 | |
|
279 | 0 | if (layer->project) { |
280 | 0 | if (layer->reprojectorLayerToMap == NULL) { |
281 | 0 | layer->reprojectorLayerToMap = |
282 | 0 | msProjectCreateReprojector(&layer->projection, &map->projection); |
283 | 0 | if (layer->reprojectorLayerToMap == NULL) { |
284 | 0 | return MS_FAILURE; |
285 | 0 | } |
286 | 0 | } |
287 | 0 | msProjectShapeEx(layer->reprojectorLayerToMap, shape); |
288 | 0 | } |
289 | | |
290 | 0 | if (msBindLayerToShape(layer, shape, |
291 | 0 | MS_DRAWMODE_FEATURES | MS_DRAWMODE_LABELS) != |
292 | 0 | MS_SUCCESS) |
293 | 0 | return MS_FAILURE; /* error message is set in msBindLayerToShape() */ |
294 | | |
295 | 0 | *nvalues = 0; |
296 | 0 | for (c = 0; c < layer->numclasses; c++) { |
297 | 0 | if (msEvalExpression(layer, shape, &(layer->class[c] -> expression), |
298 | 0 | layer->classitemindex) == MS_TRUE) { |
299 | 0 | values[*nvalues] = (layer->class[c] -> styles[0] -> size); |
300 | 0 | styles[*nvalues] = layer->class[c]->styles[0]; |
301 | 0 | (*nvalues)++; |
302 | 0 | } |
303 | 0 | } |
304 | 0 | } |
305 | 0 | return status; |
306 | 0 | } |
307 | | |
308 | | /* eventually add a class to the layer to get the diameter from an attribute */ |
309 | 0 | int pieLayerProcessDynamicDiameter(layerObj *layer) { |
310 | 0 | const char *chartRangeProcessingKey = NULL; |
311 | 0 | char *attrib; |
312 | 0 | double mindiameter = -1, maxdiameter, minvalue, maxvalue; |
313 | 0 | classObj *newclass; |
314 | 0 | styleObj *newstyle; |
315 | 0 | const char *chartSizeProcessingKey = |
316 | 0 | msLayerGetProcessingKey(layer, "CHART_SIZE"); |
317 | 0 | if (chartSizeProcessingKey != NULL) |
318 | 0 | return MS_FALSE; |
319 | 0 | chartRangeProcessingKey = msLayerGetProcessingKey(layer, "CHART_SIZE_RANGE"); |
320 | 0 | if (chartRangeProcessingKey == NULL) |
321 | 0 | return MS_FALSE; |
322 | 0 | attrib = msStrdup(chartRangeProcessingKey); |
323 | 0 | char *space = strchr(attrib, ' '); |
324 | 0 | if (space) { |
325 | 0 | *space = '\0'; |
326 | 0 | if (sscanf(space + 1, "%lf %lf %lf %lf", &mindiameter, &maxdiameter, |
327 | 0 | &minvalue, &maxvalue) != 4) { |
328 | | /*we don't have the attribute and the four range values*/ |
329 | 0 | free(attrib); |
330 | 0 | msSetError(MS_MISCERR, |
331 | 0 | "Chart Layer format error for processing key \"CHART_RANGE\"", |
332 | 0 | "msDrawChartLayer()"); |
333 | 0 | return MS_FAILURE; |
334 | 0 | } |
335 | 0 | } |
336 | | |
337 | | /*create a new class in the layer containing the wanted attribute |
338 | | * as the SIZE of its first STYLE*/ |
339 | 0 | newclass = msGrowLayerClasses(layer); |
340 | 0 | if (newclass == NULL) { |
341 | 0 | free(attrib); |
342 | 0 | return MS_FAILURE; |
343 | 0 | } |
344 | 0 | initClass(newclass); |
345 | 0 | layer->numclasses++; |
346 | | |
347 | | /*create and attach a new styleObj to our temp class |
348 | | * and bind the wanted attribute to its SIZE |
349 | | */ |
350 | 0 | newstyle = msGrowClassStyles(newclass); |
351 | 0 | if (newstyle == NULL) { |
352 | 0 | free(attrib); |
353 | 0 | return MS_FAILURE; |
354 | 0 | } |
355 | 0 | initStyle(newstyle); |
356 | 0 | newclass->numstyles++; |
357 | 0 | newclass->name = (char *)msStrdup("__MS_SIZE_ATTRIBUTE_"); |
358 | 0 | newstyle->bindings[MS_STYLE_BINDING_SIZE].item = msStrdup(attrib); |
359 | 0 | newstyle->numbindings++; |
360 | 0 | free(attrib); |
361 | |
|
362 | 0 | return MS_TRUE; |
363 | 0 | } |
364 | | |
365 | | /* clean up the class added temporarily */ |
366 | 0 | static void pieLayerCleanupDynamicDiameter(layerObj *layer) { |
367 | 0 | if (layer->numclasses > 0 && |
368 | 0 | EQUALN(layer->class[layer->numclasses - 1] -> name, |
369 | 0 | "__MS_SIZE_ATTRIBUTE_", 20)) { |
370 | 0 | classObj *c = msRemoveClass(layer, layer->numclasses - 1); |
371 | 0 | freeClass(c); |
372 | 0 | msFree(c); |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | 0 | int msDrawPieChartLayer(mapObj *map, layerObj *layer, imageObj *image) { |
377 | 0 | shapeObj shape; |
378 | 0 | int status = MS_SUCCESS; |
379 | 0 | const char *chartRangeProcessingKey = NULL; |
380 | 0 | const char *chartSizeProcessingKey = |
381 | 0 | msLayerGetProcessingKey(layer, "CHART_SIZE"); |
382 | 0 | double diameter = 0, mindiameter = -1, maxdiameter = 0, minvalue = 0, |
383 | 0 | maxvalue = 0, exponent = 0; |
384 | 0 | double *values; |
385 | 0 | styleObj **styles; |
386 | 0 | pointObj center; |
387 | 0 | int numvalues = |
388 | 0 | layer->numclasses; /* the number of classes to represent in the graph */ |
389 | 0 | int numvalues_for_shape = 0; |
390 | |
|
391 | 0 | if (chartSizeProcessingKey == NULL) { |
392 | 0 | chartRangeProcessingKey = |
393 | 0 | msLayerGetProcessingKey(layer, "CHART_SIZE_RANGE"); |
394 | 0 | if (chartRangeProcessingKey == NULL) |
395 | 0 | diameter = 20; |
396 | 0 | else { |
397 | 0 | const int nvalues = |
398 | 0 | sscanf(chartRangeProcessingKey, "%*s %lf %lf %lf %lf %lf", |
399 | 0 | &mindiameter, &maxdiameter, &minvalue, &maxvalue, &exponent); |
400 | 0 | if (nvalues != 4 && nvalues != 5) { |
401 | 0 | msSetError( |
402 | 0 | MS_MISCERR, |
403 | 0 | "msDrawChart format error for processing key \"CHART_SIZE_RANGE\": " |
404 | 0 | "itemname minsize maxsize minval maxval [exponent] is expected", |
405 | 0 | "msDrawPieChartLayer()"); |
406 | 0 | return MS_FAILURE; |
407 | 0 | } |
408 | 0 | } |
409 | 0 | } else { |
410 | 0 | if (sscanf(chartSizeProcessingKey, "%lf", &diameter) != 1) { |
411 | 0 | msSetError(MS_MISCERR, |
412 | 0 | "msDrawChart format error for processing key \"CHART_SIZE\"", |
413 | 0 | "msDrawPieChartLayer()"); |
414 | 0 | return MS_FAILURE; |
415 | 0 | } |
416 | 0 | } |
417 | | |
418 | 0 | layer->project = |
419 | 0 | msProjectionsDiffer(&(layer->projection), &(map->projection)); |
420 | | |
421 | | /* step through the target shapes */ |
422 | 0 | msInitShape(&shape); |
423 | |
|
424 | 0 | values = (double *)calloc(numvalues, sizeof(double)); |
425 | 0 | MS_CHECK_ALLOC(values, numvalues * sizeof(double), MS_FAILURE); |
426 | 0 | styles = (styleObj **)malloc((numvalues) * sizeof(styleObj *)); |
427 | 0 | if (styles == NULL) { |
428 | 0 | msSetError(MS_MEMERR, "%s: %d: Out of memory allocating %u bytes.\n", |
429 | 0 | "msDrawPieChartLayer()", __FILE__, __LINE__, |
430 | 0 | (unsigned int)(numvalues * sizeof(styleObj *))); |
431 | 0 | free(values); |
432 | 0 | return MS_FAILURE; |
433 | 0 | } |
434 | | |
435 | 0 | while (MS_SUCCESS == getNextShape(map, layer, values, &numvalues_for_shape, |
436 | 0 | styles, &shape)) { |
437 | 0 | if (chartRangeProcessingKey != NULL) |
438 | 0 | numvalues_for_shape--; |
439 | 0 | if (numvalues_for_shape == 0) { |
440 | 0 | msFreeShape(&shape); |
441 | 0 | continue; |
442 | 0 | } |
443 | 0 | msDrawStartShape(map, layer, image, &shape); |
444 | 0 | if (chartRangeProcessingKey != NULL) { |
445 | 0 | diameter = values[numvalues_for_shape]; |
446 | 0 | if (mindiameter >= 0) { |
447 | 0 | if (diameter <= minvalue) |
448 | 0 | diameter = mindiameter; |
449 | 0 | else if (diameter >= maxvalue) |
450 | 0 | diameter = maxdiameter; |
451 | 0 | else { |
452 | 0 | if (exponent <= 0) |
453 | 0 | diameter = MS_NINT(mindiameter + |
454 | 0 | ((diameter - minvalue) / (maxvalue - minvalue)) * |
455 | 0 | (maxdiameter - mindiameter)); |
456 | 0 | else |
457 | 0 | diameter = MS_NINT( |
458 | 0 | mindiameter + pow((diameter - minvalue) / (maxvalue - minvalue), |
459 | 0 | 1.0 / exponent) * |
460 | 0 | (maxdiameter - mindiameter)); |
461 | 0 | } |
462 | 0 | } |
463 | 0 | } |
464 | 0 | if (findChartPoint(map, &shape, diameter, diameter, ¢er) == |
465 | 0 | MS_SUCCESS) { |
466 | 0 | status = msDrawPieChart(map, image, ¢er, diameter, values, styles, |
467 | 0 | numvalues_for_shape); |
468 | 0 | } |
469 | 0 | msDrawEndShape(map, layer, image, &shape); |
470 | 0 | msFreeShape(&shape); |
471 | 0 | } |
472 | 0 | free(values); |
473 | 0 | free(styles); |
474 | 0 | return status; |
475 | 0 | } |
476 | | |
477 | 0 | int msDrawVBarChartLayer(mapObj *map, layerObj *layer, imageObj *image) { |
478 | 0 | shapeObj shape; |
479 | 0 | int status = MS_SUCCESS; |
480 | 0 | const char *chartSizeProcessingKey = |
481 | 0 | msLayerGetProcessingKey(layer, "CHART_SIZE"); |
482 | 0 | const char *chartScaleProcessingKey = |
483 | 0 | msLayerGetProcessingKey(layer, "CHART_SCALE"); |
484 | 0 | double barWidth, scale = 1.0; |
485 | 0 | double *values; |
486 | 0 | styleObj **styles; |
487 | 0 | pointObj center; |
488 | 0 | int numvalues = layer->numclasses; |
489 | 0 | int numvalues_for_shape; |
490 | 0 | if (chartSizeProcessingKey == NULL) { |
491 | 0 | barWidth = 20; |
492 | 0 | } else { |
493 | 0 | if (sscanf(chartSizeProcessingKey, "%lf", &barWidth) != 1) { |
494 | 0 | msSetError(MS_MISCERR, |
495 | 0 | "msDrawChart format error for processing key \"CHART_SIZE\"", |
496 | 0 | "msDrawVBarChartLayer()"); |
497 | 0 | return MS_FAILURE; |
498 | 0 | } |
499 | 0 | } |
500 | | |
501 | 0 | if (chartScaleProcessingKey) { |
502 | 0 | if (sscanf(chartScaleProcessingKey, "%lf", &scale) != 1) { |
503 | 0 | msSetError(MS_MISCERR, |
504 | 0 | "Error reading value for processing key \"CHART_SCALE\"", |
505 | 0 | "msDrawVBarChartLayer()"); |
506 | 0 | return MS_FAILURE; |
507 | 0 | } |
508 | 0 | } |
509 | 0 | msInitShape(&shape); |
510 | |
|
511 | 0 | values = (double *)calloc(numvalues, sizeof(double)); |
512 | 0 | MS_CHECK_ALLOC(values, numvalues * sizeof(double), MS_FAILURE); |
513 | 0 | styles = (styleObj **)malloc(numvalues * sizeof(styleObj *)); |
514 | 0 | if (styles == NULL) { |
515 | 0 | msSetError(MS_MEMERR, "%s: %d: Out of memory allocating %u bytes.\n", |
516 | 0 | "msDrawVBarChartLayer()", __FILE__, __LINE__, |
517 | 0 | (unsigned int)(numvalues * sizeof(styleObj *))); |
518 | 0 | free(values); |
519 | 0 | return MS_FAILURE; |
520 | 0 | } |
521 | | |
522 | 0 | while (MS_SUCCESS == getNextShape(map, layer, values, &numvalues_for_shape, |
523 | 0 | styles, &shape)) { |
524 | 0 | int i; |
525 | 0 | double h = 0; |
526 | 0 | if (numvalues_for_shape == 0) { |
527 | 0 | continue; |
528 | 0 | } |
529 | 0 | for (i = 0; i < numvalues_for_shape; i++) { |
530 | 0 | values[i] *= scale; |
531 | 0 | h += values[i]; |
532 | 0 | } |
533 | 0 | msDrawStartShape(map, layer, image, &shape); |
534 | 0 | if (findChartPoint(map, &shape, barWidth, h, ¢er) == MS_SUCCESS) { |
535 | 0 | status = msDrawVBarChart(map, image, ¢er, values, styles, |
536 | 0 | numvalues_for_shape, barWidth); |
537 | 0 | } |
538 | 0 | msDrawEndShape(map, layer, image, &shape); |
539 | 0 | msFreeShape(&shape); |
540 | 0 | } |
541 | 0 | free(values); |
542 | 0 | free(styles); |
543 | 0 | return status; |
544 | 0 | } |
545 | | |
546 | 0 | int msDrawBarChartLayer(mapObj *map, layerObj *layer, imageObj *image) { |
547 | 0 | shapeObj shape; |
548 | 0 | int status = MS_SUCCESS; |
549 | 0 | const char *chartSizeProcessingKey = |
550 | 0 | msLayerGetProcessingKey(layer, "CHART_SIZE"); |
551 | 0 | const char *barMax = msLayerGetProcessingKey(layer, "CHART_BAR_MAXVAL"); |
552 | 0 | const char *barMin = msLayerGetProcessingKey(layer, "CHART_BAR_MINVAL"); |
553 | 0 | double width, height; |
554 | 0 | double barWidth; |
555 | 0 | double *values; |
556 | 0 | styleObj **styles; |
557 | 0 | pointObj center; |
558 | 0 | double barMaxVal = 0.0, barMinVal = 0.0; |
559 | 0 | int numvalues = layer->numclasses; |
560 | 0 | int numvalues_for_shape; |
561 | 0 | if (chartSizeProcessingKey == NULL) { |
562 | 0 | width = height = 20; |
563 | 0 | } else { |
564 | 0 | const int ret = sscanf(chartSizeProcessingKey, "%lf %lf", &width, &height); |
565 | 0 | if (ret == 1) { |
566 | 0 | height = width; |
567 | 0 | } else if (ret != 2) { |
568 | 0 | msSetError(MS_MISCERR, |
569 | 0 | "msDrawChart format error for processing key \"CHART_SIZE\"", |
570 | 0 | "msDrawBarChartLayer()"); |
571 | 0 | return MS_FAILURE; |
572 | 0 | } |
573 | 0 | } |
574 | | |
575 | 0 | if (barMax) { |
576 | 0 | if (sscanf(barMax, "%lf", &barMaxVal) != 1) { |
577 | 0 | msSetError(MS_MISCERR, |
578 | 0 | "Error reading value for processing key \"CHART_BAR_MAXVAL\"", |
579 | 0 | "msDrawBarChartLayer()"); |
580 | 0 | return MS_FAILURE; |
581 | 0 | } |
582 | 0 | } |
583 | 0 | if (barMin) { |
584 | 0 | if (sscanf(barMin, "%lf", &barMinVal) != 1) { |
585 | 0 | msSetError(MS_MISCERR, |
586 | 0 | "Error reading value for processing key \"CHART_BAR_MINVAL\"", |
587 | 0 | "msDrawBarChartLayer()"); |
588 | 0 | return MS_FAILURE; |
589 | 0 | } |
590 | 0 | } |
591 | 0 | if (barMin && barMax && barMinVal >= barMaxVal) { |
592 | 0 | msSetError(MS_MISCERR, |
593 | 0 | "\"CHART_BAR_MINVAL\" must be less than \"CHART_BAR_MAXVAL\"", |
594 | 0 | "msDrawBarChartLayer()"); |
595 | 0 | return MS_FAILURE; |
596 | 0 | } |
597 | 0 | barWidth = (double)width / (double)layer->numclasses; |
598 | 0 | if (!barWidth) { |
599 | 0 | msSetError( |
600 | 0 | MS_MISCERR, |
601 | 0 | "Specified width of chart too small to fit given number of classes", |
602 | 0 | "msDrawBarChartLayer()"); |
603 | 0 | return MS_FAILURE; |
604 | 0 | } |
605 | | |
606 | 0 | msInitShape(&shape); |
607 | |
|
608 | 0 | values = (double *)calloc(numvalues, sizeof(double)); |
609 | 0 | MS_CHECK_ALLOC(values, numvalues * sizeof(double), MS_FAILURE); |
610 | 0 | styles = (styleObj **)malloc(numvalues * sizeof(styleObj *)); |
611 | 0 | if (styles == NULL) { |
612 | 0 | msSetError(MS_MEMERR, "%s: %d: Out of memory allocating %u bytes.\n", |
613 | 0 | "msDrawBarChartLayer()", __FILE__, __LINE__, |
614 | 0 | (unsigned int)(numvalues * sizeof(styleObj *))); |
615 | 0 | free(values); |
616 | 0 | return MS_FAILURE; |
617 | 0 | } |
618 | | |
619 | 0 | while (MS_SUCCESS == getNextShape(map, layer, values, &numvalues_for_shape, |
620 | 0 | styles, &shape)) { |
621 | 0 | if (numvalues_for_shape == 0) |
622 | 0 | continue; |
623 | 0 | msDrawStartShape(map, layer, image, &shape); |
624 | 0 | if (findChartPoint(map, &shape, width, height, ¢er) == MS_SUCCESS) { |
625 | 0 | status = msDrawBarChart(map, image, ¢er, values, styles, |
626 | 0 | numvalues_for_shape, width, height, |
627 | 0 | (barMax != NULL) ? &barMaxVal : NULL, |
628 | 0 | (barMin != NULL) ? &barMinVal : NULL, barWidth); |
629 | 0 | } |
630 | 0 | msDrawEndShape(map, layer, image, &shape); |
631 | 0 | msFreeShape(&shape); |
632 | 0 | } |
633 | 0 | free(values); |
634 | 0 | free(styles); |
635 | 0 | return status; |
636 | 0 | } |
637 | | |
638 | | /** |
639 | | * Generic function to render chart layers. |
640 | | */ |
641 | 0 | int msDrawChartLayer(mapObj *map, layerObj *layer, imageObj *image) { |
642 | |
|
643 | 0 | rectObj searchrect; |
644 | 0 | const char *chartTypeProcessingKey = |
645 | 0 | msLayerGetProcessingKey(layer, "CHART_TYPE"); |
646 | 0 | int chartType = MS_CHART_TYPE_PIE; |
647 | 0 | int status = MS_FAILURE; |
648 | |
|
649 | 0 | if (image && map) { |
650 | 0 | if (!(MS_RENDERER_PLUGIN(image->format))) { |
651 | 0 | msSetError(MS_MISCERR, |
652 | 0 | "chart drawing currently only supports GD and AGG renderers", |
653 | 0 | "msDrawChartLayer()"); |
654 | 0 | return MS_FAILURE; |
655 | 0 | } |
656 | | |
657 | 0 | if (chartTypeProcessingKey != NULL) { |
658 | 0 | if (strcasecmp(chartTypeProcessingKey, "PIE") == 0) { |
659 | 0 | chartType = MS_CHART_TYPE_PIE; |
660 | 0 | } else if (strcasecmp(chartTypeProcessingKey, "BAR") == 0) { |
661 | 0 | chartType = MS_CHART_TYPE_BAR; |
662 | 0 | } else if (strcasecmp(chartTypeProcessingKey, "VBAR") == 0) { |
663 | 0 | chartType = MS_CHART_TYPE_VBAR; |
664 | 0 | } else { |
665 | 0 | msSetError(MS_MISCERR, |
666 | 0 | "unknown chart type for processing key \"CHART_TYPE\", must " |
667 | 0 | "be one of \"PIE\" or \"BAR\"", |
668 | 0 | "msDrawChartLayer()"); |
669 | 0 | return MS_FAILURE; |
670 | 0 | } |
671 | 0 | } |
672 | 0 | if (chartType == MS_CHART_TYPE_PIE) { |
673 | 0 | pieLayerProcessDynamicDiameter(layer); |
674 | 0 | } |
675 | | |
676 | | /* open this layer */ |
677 | 0 | status = msLayerOpen(layer); |
678 | 0 | if (status != MS_SUCCESS) |
679 | 0 | return MS_FAILURE; |
680 | | |
681 | 0 | status = msLayerWhichItems(layer, MS_FALSE, NULL); |
682 | 0 | if (status != MS_SUCCESS) { |
683 | 0 | msLayerClose(layer); |
684 | 0 | return MS_FAILURE; |
685 | 0 | } |
686 | | /* identify target shapes */ |
687 | 0 | if (layer->transform == MS_TRUE) |
688 | 0 | searchrect = map->extent; |
689 | 0 | else { |
690 | 0 | searchrect.minx = searchrect.miny = 0; |
691 | 0 | searchrect.maxx = map->width - 1; |
692 | 0 | searchrect.maxy = map->height - 1; |
693 | 0 | } |
694 | |
|
695 | 0 | if ((map->projection.numargs > 0) && (layer->projection.numargs > 0)) |
696 | 0 | msProjectRect(&map->projection, &layer->projection, |
697 | 0 | &searchrect); /* project the searchrect to source coords */ |
698 | |
|
699 | 0 | status = msLayerWhichShapes(layer, searchrect, MS_FALSE); |
700 | 0 | if (status == MS_DONE) { /* no overlap */ |
701 | 0 | msLayerClose(layer); |
702 | 0 | if (chartType == MS_CHART_TYPE_PIE) |
703 | 0 | pieLayerCleanupDynamicDiameter(layer); |
704 | 0 | return MS_SUCCESS; |
705 | 0 | } else if (status != MS_SUCCESS) { |
706 | 0 | msLayerClose(layer); |
707 | 0 | if (chartType == MS_CHART_TYPE_PIE) |
708 | 0 | pieLayerCleanupDynamicDiameter(layer); |
709 | 0 | return MS_FAILURE; |
710 | 0 | } |
711 | 0 | switch (chartType) { |
712 | 0 | case MS_CHART_TYPE_PIE: |
713 | 0 | status = msDrawPieChartLayer(map, layer, image); |
714 | 0 | break; |
715 | 0 | case MS_CHART_TYPE_BAR: |
716 | 0 | status = msDrawBarChartLayer(map, layer, image); |
717 | 0 | break; |
718 | 0 | case MS_CHART_TYPE_VBAR: |
719 | 0 | status = msDrawVBarChartLayer(map, layer, image); |
720 | 0 | break; |
721 | 0 | default: |
722 | 0 | return MS_FAILURE; /*shouldn't be here anyways*/ |
723 | 0 | } |
724 | | |
725 | 0 | msLayerClose(layer); |
726 | |
|
727 | 0 | if (chartType == MS_CHART_TYPE_PIE) |
728 | 0 | pieLayerCleanupDynamicDiameter(layer); |
729 | 0 | } |
730 | 0 | return status; |
731 | 0 | } |